Merge branch 'feature/grcss' into develop

This commit is contained in:
Simon Brooke 2020-02-14 16:09:12 +00:00
commit 10e5d15e65
7 changed files with 174 additions and 78 deletions

View file

@ -131,6 +131,8 @@
"Smeagol has been unable to find some of the resources on which it depends, "Smeagol has been unable to find some of the resources on which it depends,
possibly because of misconfiguration or missing environment variables." possibly because of misconfiguration or missing environment variables."
;; used in sanity check report ;; used in sanity check report
:sortable "You can sort this table by selecting column headers"
;; used for sortable tables
:user-lacks-field "User record in the passwd file lacks a field" :user-lacks-field "User record in the passwd file lacks a field"
;; used in sanity check report ;; used in sanity check report
:username-prompt "Username" ;; text of the username widget prompt on edit user page :username-prompt "Username" ;; text of the username widget prompt on edit user page

View file

@ -24,6 +24,16 @@
## html elements generally in alphabetic order ## html elements generally in alphabetic order
*/ */
a {
color: darkgray;
font-weight: bold;
}
a:hover {
color: darkgray;
background:rgba(200,200,200,0.8);
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -52,14 +62,15 @@ dd {
/* footer of the page - not-editable, provided by Smeagol */ /* footer of the page - not-editable, provided by Smeagol */
footer { footer {
border-top: thin solid gray; border-top: thin solid silver;
color: gray;
background:rgba(200,200,200,0.8);
clear: both; clear: both;
font-size: smaller; font-size: smaller;
text-align: center; text-align: center;
color: gray;
background: rgba(224,224,224,0.95);
width: 100%; width: 100%;
margin: 0; margin: 0;
min-height: 4px;
padding: 0.25em 0; padding: 0.25em 0;
bottom:0; bottom:0;
position:fixed; position:fixed;
@ -71,9 +82,14 @@ footer {
} }
footer div { footer div {
display: none;
padding: 0.1em; padding: 0.1em;
} }
footer:hover div {
display: block;
}
form { form {
border: thin solid silver; border: thin solid silver;
} }
@ -83,8 +99,6 @@ header {
margin-top: 0; margin-top: 0;
width:100%; width:100%;
max-width: 100%; max-width: 100%;
background-color: gray;
color: white;
} }
header h1 { header h1 {
@ -93,7 +107,6 @@ header h1 {
header a { header a {
font-weight: bold; font-weight: bold;
color: white;
} }
header a:hover { header a:hover {
@ -131,12 +144,12 @@ ins {
label { label {
width: 20%; width: 20%;
min-width: 20em; min-width: 20em;
border-right: thin solid gray; border-right: thin solid silver;
display: inline-block; display: inline-block;
} }
table { table {
border: 2px solid black; border: thin solid silver;
border-collapse: collapse; border-collapse: collapse;
} }
@ -148,7 +161,7 @@ th, td {
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
padding: 0.15em 1.5em; padding: 0.15em 1.5em;
border: 1px solid gray; border: 1px solid silver;
} }
th { th {
@ -166,6 +179,7 @@ th {
/* left bar for all pages in the Wiki - editable, provided by users. Within main-container */ /* left bar for all pages in the Wiki - editable, provided by users. Within main-container */
#side-bar { #side-bar {
display: none;
width: 17%; width: 17%;
height: 100%; height: 100%;
float: left; float: left;
@ -173,10 +187,10 @@ th {
/* cookies information box, fixed, in right margin, just above footer */ /* cookies information box, fixed, in right margin, just above footer */
#cookies { #cookies {
width: 30%; width: 20%;
float: right; float: right;
position: fixed; position: fixed;
bottom: 3.5em; bottom: 8px;
right: 0; right: 0;
z-index: 175; z-index: 175;
background: transparent; background: transparent;
@ -190,8 +204,8 @@ th {
text-align: right; text-align: right;
padding: 0.25em 2em; padding: 0.25em 2em;
border-radius: 0.25em; border-radius: 0.25em;
color: white; color: gray;
background:rgba(40,40,40,0.8); background:rgba(200,200,200,0.8);
} }
/* more-about-cookies box, normally hidden */ /* more-about-cookies box, normally hidden */
@ -199,9 +213,9 @@ th {
display: none; display: none;
padding: 0.5em 2em; padding: 0.5em 2em;
border-radius: 0.5em; border-radius: 0.5em;
color: white; color: gray;
background:rgba(40,40,40,0.8); background:rgba(200,200,200,0.8);
border-bottom: thin solid white; border-bottom: thin solid gray;
} }
/* but magically appears on mouseover */ /* but magically appears on mouseover */
@ -242,8 +256,8 @@ th {
right: 0; right: 0;
padding: 0.25em 2em; padding: 0.25em 2em;
border-radius: 0.25em; border-radius: 0.25em;
color: white; color: gray;
background:rgba(40,40,40,0.8); background:rgba(200,200,200,0.8);
font-size: 66%; font-size: 66%;
} }
@ -254,7 +268,11 @@ th {
.minor-controls a { .minor-controls a {
float: right; float: right;
padding: 0.25em 2em; padding: 0.25em 2em;
color: white; color: gray;
}
.minor-controls a:hover {
color: darkgray;
} }
.pseudo-input { .pseudo-input {
@ -303,8 +321,7 @@ th {
/* content of the current page in the Wiki - editable, provided by users. Within main-container */ /* content of the current page in the Wiki - editable, provided by users. Within main-container */
#content { #content {
border: thin solid silver; border: thin solid silver;
width: 80%; width: 100%;
float: right;
padding-bottom: 5em; padding-bottom: 5em;
} }
@ -312,16 +329,29 @@ th {
display: none; display: none;
} }
#header {
font-size: smaller;
}
/* top-of-page navigation, not editable, provided by Smeagol */ /* top-of-page navigation, not editable, provided by Smeagol */
#nav{ #nav{
margin: 0; margin: 0;
padding: 0; padding: 0;
top: 0; top: 0;
width: 100%; min-height: 4px;
_position: absolute; _position: absolute;
_top: expression(document.documentElement.scrollTop); _top: expression(document.documentElement.scrollTop);
z-index: 149; z-index: 149;
background:rgba(40,40,40,0.8); color: gray;
background:rgba(200,200,200,0.8);
}
#nav #nav-menu {
display: none;
}
#nav:hover #nav-menu {
display: block;
} }
/* only needed for fly-out menu effect on tablet and phone stylesheets */ /* only needed for fly-out menu effect on tablet and phone stylesheets */
@ -341,14 +371,14 @@ th {
} }
#nav menu li a { #nav menu li a {
color: white; color: gray;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
padding: 0.1em 0.75em; padding: 0.1em 0.75em;
margin: 0; margin: 0;
} }
#nav menu li.active a { background: gray;} #nav menu li.active a { background: gray; color: white;}
li.nav-item a:hover { background: rgb( 240, 240, 240) } li.nav-item a:hover { background: rgb( 240, 240, 240) }
li.nav-item a:active { background: gray; color: white; } li.nav-item a:active { background: gray; color: white; }
@ -379,17 +409,15 @@ th {
padding: 0; padding: 0;
position: fixed; position: fixed;
z-index: 149; z-index: 149;
color: silver; color: black;
background:rgba(40,40,40,0.9); background:rgba(200,200,200,0.9);
} }
#nav a { #nav a {
color: white;
text-decoration: none;
font-weight: bold; font-weight: bold;
} }
#nav:hover #nav-menu { #nav:hover #nav-menu, #nav:hover #phone-side-bar {
display: block; display: block;
list-style-type: none; list-style-type: none;
width: 100%; width: 100%;
@ -455,18 +483,21 @@ th {
display: none; display: none;
} }
#header {
display: none;
}
#nav{ #nav{
margin: 0; margin: 0;
padding: 0; padding: 0;
position: fixed; position: fixed;
z-index: 149; z-index: 149;
color: silver; color: black;
background:rgba(40,40,40,0.9); background:rgba(200,200,200,0.9);
} }
#nav a { #nav a {
color: white; color: black;
text-decoration: none;
font-weight: bold; font-weight: bold;
} }
@ -491,6 +522,8 @@ th {
} }
#nav menu li a { #nav menu li a {
color: black;
font-weight: bold;
} }
#nav ul li.active a { background: silver;} #nav ul li.active a { background: silver;}

View file

@ -1,10 +1,18 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% block extra-headers %}
{% script "/vendor/node_modules/tablesort/dist/tablesort.min.js" %}
{% endblock %}
{% block content %} {% block content %}
<div id="content"> <div id="content">
<table> <p>
<tr> {% i18n sortable %}
<th/><th>{% i18n edit-col-hdr %}</th><th>{% i18n del-col-hdr %}</th> </p>
<table id="userstable">
<tr data-sort-method='none'>
<th>{% i18n user-title-prefix %}</th>
<th data-sort-method='none'>{% i18n edit-col-hdr %}</th>
<th data-sort-method='none'>{% i18n del-col-hdr %}</th>
</tr> </tr>
{% for user in users %} {% for user in users %}
<tr> <tr>
@ -13,11 +21,12 @@
<td><a href="delete-user?target={{user}}">{% i18n del-col-hdr %} {{user}}</a></td> <td><a href="delete-user?target={{user}}">{% i18n del-col-hdr %} {{user}}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr> <tr data-sort-method='none'>
<td><a href="edit-user">{% i18n add-user-label %}</a></td> <td colspan="3"><a href="edit-user">{% i18n add-user-label %}</a></td>
<td></td>
<td></td>
</tr> </tr>
</table> </table>
</div> </div>
<script>
new Tablesort(document.getElementById('userstable'));
</script>
{% endblock %} {% endblock %}

View file

@ -1,11 +1,11 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% block extra-headers %} {% block extra-headers %}
{% ifequal js-from ":cloudflare" %} {% ifequal js-from ":cdnjs" %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.css" rel="stylesheet" type="text/css" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.css" rel="stylesheet" type="text/css" />
{% else %} {% else %}
{% style "/vendor/simplemde/dist/simplemde.min.css" %} {% style "vendor/simplemde/dist/simplemde.min.css" %}
{% script "/vendor/simplemde/dist/simplemde.min.js" %} {% script "vendor/simplemde/dist/simplemde.min.js" %}
{% endifequal %} {% endifequal %}
{% endblock %} {% endblock %}

View file

@ -1,4 +1,10 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% block extra-headers %}
{% script "/vendor/node_modules/tablesort/dist/tablesort.min.js" %}
{% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.number.min.js" %}
{% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.date.min.js" %}
{% script "/vendor/node_modules/tablesort/dist/sorts/tablesort.monthname.min.js" %}
{% endblock %}
{% block content %} {% block content %}
<div id="content" class="list-uploads"> <div id="content" class="list-uploads">
@ -9,12 +15,15 @@
<input name="search" id="search" type="text" value="{{search}}" required/> <input name="search" id="search" type="text" value="{{search}}" required/>
</p> </p>
</form> </form>
<table> <p>
<tr> {% i18n sortable %}
</p>
<table id="uploads">
<tr data-sort-method='none'>
<th>Name</th> <th>Name</th>
<th>Uploaded</th> <th>Uploaded</th>
<th>Type this</th> <th>Type this</th>
<th>To get this</th> <th data-sort-method='none'>To get this</th>
</tr> </tr>
{% for entry in files %} {% for entry in files %}
<tr> <tr>
@ -26,9 +35,11 @@
<td> <td>
{% if entry.is-image %} <img src="{{entry.resource}}" alt="{{entry.name|capitalize}}"/> {% else %} <a href="{{entry.resource}}">link</a> {% endif %} {% if entry.is-image %} <img src="{{entry.resource}}" alt="{{entry.name|capitalize}}"/> {% else %} <a href="{{entry.resource}}">link</a> {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
<script>
new Tablesort(document.getElementById('uploads'));
</script>
{% endblock %} {% endblock %}

View file

@ -161,7 +161,8 @@
(log/info (format "Showing history of page '%s'" page)) (log/info (format "Showing history of page '%s'" page))
(layout/render "history.html" (layout/render "history.html"
(merge (util/standard-params request) (merge (util/standard-params request)
{:title (util/get-message :history-title-prefix request) {:title (str (util/get-message :history-title-prefix request)
" " page)
:page page :page page
:history (hist/find-history repo-path file-name)})))) :history (hist/find-history repo-path file-name)}))))
@ -175,7 +176,7 @@
If `template` is supplied, use that as the formatting template as specified for If `template` is supplied, use that as the formatting template as specified for
java.time.Formatter. Assumes system default timezone. Returns a string." java.time.Formatter. Assumes system default timezone. Returns a string."
([^Long unix-time] ([^Long unix-time]
(format-instant unix-time "EEEE, dd MMMM YYYY")) (format-instant unix-time "dd MMMM YYYY"))
([^Long unix-time ^String template] ([^Long unix-time ^String template]
(jt/format (jt/format
(java-time/formatter template) (java-time/formatter template)
@ -188,8 +189,9 @@
[request] [request]
(let (let
[params (keywordize-keys (:params request)) [params (keywordize-keys (:params request))
cl (count (io/resource-path))
files files
(sort-by
(juxt :name (fn [x] (- 0 (count (:resource x)))))
(map (map
#(zipmap #(zipmap
[:base-name :is-image :modified :name :resource] [:base-name :is-image :modified :name :resource]
@ -202,19 +204,11 @@
(fs/mod-time %) (fs/mod-time %)
(format-instant (fs/mod-time %))) (format-instant (fs/mod-time %)))
(fs/name %) (fs/name %)
(try (util/local-url %)])
(subs (str (fs/absolute %)) cl)
(catch StringIndexOutOfBoundsException x
(log/error "Could not resolve relative path for" %
";\n resource-path is:" (io/resource-path)
";\n absolute path is:" (fs/absolute %)
";\n data-path is:" util/upload-dir
";\n content path is:" (:content-dir config))
%))])
(remove (remove
#(or (cs/starts-with? (fs/name %) ".") #(or (cs/starts-with? (fs/name %) ".")
(fs/directory? %)) (fs/directory? %))
(file-seq (clojure.java.io/file util/upload-dir))))] (file-seq (clojure.java.io/file util/upload-dir)))))]
(log/info (with-out-str (pprint files))) (log/info (with-out-str (pprint files)))
(layout/render (layout/render
"list-uploads.html" "list-uploads.html"
@ -236,6 +230,7 @@
files) files)
})))) }))))
;;;; end of list-uploads section ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; end of list-uploads section ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn upload-page (defn upload-page

View file

@ -2,6 +2,7 @@
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.util smeagol.util
(:require [clojure.java.io :as cjio] (:require [clojure.java.io :as cjio]
[clojure.string :as cs]
[environ.core :refer [env]] [environ.core :refer [env]]
[me.raynes.fs :as fs] [me.raynes.fs :as fs]
[noir.io :as io] [noir.io :as io]
@ -10,7 +11,7 @@
[smeagol.authenticate :as auth] [smeagol.authenticate :as auth]
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[smeagol.formatting :refer [md->html]] [smeagol.formatting :refer [md->html]]
[taoensso.timbre :as timbre])) [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -49,6 +50,51 @@
(def upload-dir (def upload-dir
(str (cjio/file content-dir "uploads"))) (str (cjio/file content-dir "uploads")))
(def local-url-base
(let [a (str (fs/absolute content-dir))]
(subs a 0 (- (count a) (count "content")))))
(defn not-servable-reason
"As a string, the reason this `file-path` cannot safely be served, or `nil`
if it is safe to serve. This reason may be logged, but should *not* be
shown to remote users, as it would allow file system probing."
[file-path]
(let [path (fs/absolute file-path)]
(cond
(cs/includes? file-path "..")
(cs/join " " file-path
"Attempts to ascend the file hierarchy are disallowed.")
(not (cs/starts-with? path local-url-base))
(cs/join " " [path "is not servable"])
(not (fs/exists? path))
(cs/join " " [path "does not exist"])
(not (fs/readable? path))
(cs/join " " [path "is not readable"]))))
(defn local-url?
"True if this `file-path` can be served as a local URL, else false."
[file-path]
(empty? (not-servable-reason file-path)))
(defn local-url
"Return a local URL for this `file-path`, or a deliberate 404 if none
can be safely served."
[file-path]
(try
(let [path (fs/absolute file-path)
problem (not-servable-reason path)]
(if
(empty? problem)
(subs (str path) (count local-url-base))
(do
(log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource.")
(str "404-not-found?path=" file-path))))
(catch Exception any
(log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
(str "404-not-found?path=" file-path))))
(defn standard-params (defn standard-params
"Return a map of standard parameters to pass to the template renderer." "Return a map of standard parameters to pass to the template renderer."
[request] [request]
@ -69,7 +115,7 @@
messages (try messages (try
(i18n/get-messages specifier "i18n" "en-GB") (i18n/get-messages specifier "i18n" "en-GB")
(catch Exception any (catch Exception any
(timbre/error (log/error
any any
(str (str
"Failed to parse accept-language header '" "Failed to parse accept-language header '"