War on TODOs

Improved mermaid and photoswipe extensions, handled control-s on the edit page.
This commit is contained in:
Simon Brooke 2020-02-20 12:35:00 +00:00
parent ac433c3afa
commit c19580a23e
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
10 changed files with 142 additions and 61 deletions

View file

@ -13,7 +13,7 @@ That's all there is to it - a sequence of image links just as you'd write them a
## The Gallery ## The Gallery
This page holds another example Photoswipe gallery, this time using a simpler, Markdown-based specification. Processing this specification takes more work than the full syntax used in the other [[Example gallery]], so the gallery may be slower to load; but it's much easier to configure. This page holds another example Photoswipe gallery, this time using a simpler, Markdown-based specification. Processing this specification takes more work than the full syntax used in the other [[Example gallery]], so the gallery may be slower to load; but it's much easier to configure. To see it, <a href="edit?page=Simplified%20example%20gallery">edit this page.</a>
```pswp ```pswp
![Frost on a gate, Laurieston](content/uploads/g1.jpg) ![Frost on a gate, Laurieston](content/uploads/g1.jpg)

View file

@ -11,7 +11,7 @@
{% block content %} {% block content %}
<div id="content" class="edit"> <div id="content" class="edit">
<form action="{{servlet-context}}/edit" method="POST"> <form id="edit-form" action="{{servlet-context}}/edit" method="POST">
{% csrf-field %} {% csrf-field %}
<input type="hidden" name="page" value="{{page}}"/> <input type="hidden" name="page" value="{{page}}"/>
<textarea name="src" id="src" rows="25" cols="80">{{content}}</textarea> <textarea name="src" id="src" rows="25" cols="80">{{content}}</textarea>
@ -21,12 +21,20 @@
value="{%if exists%}{%else%}New file {{page}}{%endif%}" required/> value="{%if exists%}{%else%}New file {{page}}{%endif%}" required/>
</p> </p>
<p class="widget"> <p class="widget">
<label for="submit">{% i18n save-prompt %}</label> <label for="submit-button">{% i18n save-prompt %}</label>
<input name="submit" id="submit" type="submit" class="action" value="{% i18n save-label %}"/> <input name="submit-button" id="submit-button" type="submit" class="action" value="{% i18n save-label %}"/>
</p> </p>
</form> </form>
</div> </div>
<script> <script>
// intercept control-S (or on Mac meta-S) and submit the form. Saves much annoyance.
document.addEventListener("keydown", function(e) {
if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
document.getElementById('edit-form').submit();
}
}, false);
// initialise the editor
var simplemde = new SimpleMDE({ var simplemde = new SimpleMDE({
autosave: { autosave: {
enabled: true, enabled: true,

View file

@ -17,14 +17,4 @@
{% endif %} {% endif %}
{{content|safe}} {{content|safe}}
</div> </div>
<script>
//<![CDATA[
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function(event) {
mermaid.initialize({startOnLoad:true});
});
}
//]]
</script>
{% endblock %} {% endblock %}

View file

@ -79,7 +79,22 @@
(log/info "Retrieved graph-spec from " (:from data) " `" ((:from data) data) "`") (log/info "Retrieved graph-spec from " (:from data) " `" ((:from data) data) "`")
(str "<div class=\"mermaid data-visualisation\" id=\"mermaid" index "\">\n" (str "<div class=\"mermaid data-visualisation\" id=\"mermaid" index "\">\n"
graph-spec graph-spec
"\n</div>"))) "\n</div>
<script>
//<![CDATA[
/* don't do this twice! */
if ( document.mermaidListenerAdded != true)
{
if (document.addEventListener) {
document.addEventListener(\"DOMContentLoaded\", function(event) {
mermaid.initialize({startOnLoad:true});
});
document.mermaidListenerAdded = true;
}
}
//]]
</script>
")))
;; (fs/file? (str (nio/resource-path) "data/classes.mermaid")) ;; (fs/file? (str (nio/resource-path) "data/classes.mermaid"))
;; (slurp (str (nio/resource-path) "data/classes.mermaid")) ;; (slurp (str (nio/resource-path) "data/classes.mermaid"))

View file

@ -42,11 +42,14 @@
specification based on that documented on the Photoswipe website." specification based on that documented on the Photoswipe website."
[^String spec ^Integer index] [^String spec ^Integer index]
(str (str
"<div class=\"pswp\" id=\"pswp-" "<div class='gallery'>
<div class=\"pswp\" id=\"pswp-"
index "\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n" index "\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n"
(slurp (slurp
(str (io/resource-path) "html-includes/photoswipe-boilerplate.html")) (str (io/resource-path) "html-includes/photoswipe-boilerplate.html"))
"</div> "</div>
<p><button id=\"open-gallery-" index "\" onclick=\"gallery" index
".init(); document.getElementById(`open-gallery-" index "`).style.display = 'none';\">Open the gallery</button></p>
<script> <script>
\n//<![CDATA[\n \n//<![CDATA[\n
var pswpElement = document.getElementById('pswp-" index "'); var pswpElement = document.getElementById('pswp-" index "');
@ -56,11 +59,12 @@
var gallery" index var gallery" index
" = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, spec" " = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, spec"
index ".slides, spec" index ".options); index ".slides, spec" index ".options);
if (spec" index ".openImmediately) { gallery" index ".init(); } if (spec" index ".openImmediately) {
document.getElementById(`open-gallery-" index "`).style.display = 'none';
gallery" index ".init();
}
\n//]]\n \n//]]\n
</script> </script>
<p><button onclick=\"gallery" index
".init()\">Open the gallery</button></p>
</div>")) </div>"))
@ -68,19 +72,25 @@
"Parser to transform a sequence of Markdown image links into something we "Parser to transform a sequence of Markdown image links into something we
can build into JSON. Yes, this could all have been done with regexes, but can build into JSON. Yes, this could all have been done with regexes, but
they are very inscrutable." they are very inscrutable."
(insta/parser "SLIDE := START-CAPTION title END-CAPTION src END-SRC; (insta/parser "SLIDES := SLIDE | SLIDE SPACE SLIDES;
SLIDE := START-CAPTION title END-CAPTION src END-SRC;
START-CAPTION := '![' ; START-CAPTION := '![' ;
END-CAPTION := '](' ; END-CAPTION := '](' ;
END-SRC := ')' ; END-SRC := ')' ;
title := #'[^]]*' ; title := #'[^]]*' ;
src := #'[^)]*' ; src := #'[^)]*' ;
SPACE := #'[\\r\\n\\W]*'")) SPACE := #'[\\s]*'"))
(defn- simplify
(defn simplify
"Simplify a parse-`tree` created by `simple-grammar`, q.v."
[tree] [tree]
(if (if
(coll? tree) (coll? tree)
(case (first tree) (case (first tree)
:SLIDES (cons
(simplify (first (rest tree)))
(first (simplify (rest (rest tree)))))
:SLIDE (remove empty? (map simplify (rest tree))) :SLIDE (remove empty? (map simplify (rest tree)))
:title tree :title tree
:src tree :src tree
@ -89,6 +99,7 @@
:END-SRC nil :END-SRC nil
(remove empty? (map simplify tree))))) (remove empty? (map simplify tree)))))
(defn slide-merge-dimensions (defn slide-merge-dimensions
"If this `slide` appears to be local, return it decorated with the "If this `slide` appears to be local, return it decorated with the
dimensions of the image it references." dimensions of the image it references."
@ -109,29 +120,40 @@
;; {:title "Frost on a gate, Laurieston", ;; {:title "Frost on a gate, Laurieston",
;; :src "content/uploads/g1.jpg"}) ;; :src "content/uploads/g1.jpg"})
(defn- process-simple-slide (defn find-thumb
[slide-spec] [url thumbsize]
(let [s (simplify (simple-grammar slide-spec)) (if
s'(zipmap (map first s) (map #(nth % 1) s)) (and
(uploaded? url)
thumbsize)
(let [p (str (cio/file "uploads" (name thumbsize) (fs/base-name url)))
p' (cio/file content-dir p)
r (str (cio/file "content" p))]
(if
(and (fs/exists? p') (fs/readable? p'))
r))))
(defn process-simple-slide
"Process a single `slide`, as decoded by `simple-grammar`. At this stage a
slide is expected to be represented as a sequence of vectors, one for each
property of the slide (`:title`, `:src`). Each vector contains the name of
the property as a keyword as its first element, and the value of the
property as its second element.
Returns a map of these properties, with, if possible, `:w` (width), `:h`
(height), and `:msrc` (source URL of a low resolution variant) added."
[slide]
(let [s' (zipmap (map first slide) (map #(nth % 1) slide))
thumbsizes (:thumbnails config) thumbsizes (:thumbnails config)
thumbsize (first thumbsize (first
(sort (sort
#(> (%1 thumbsizes) (%2 thumbsizes)) #(> (%1 thumbsizes) (%2 thumbsizes))
(keys thumbsizes))) (keys thumbsizes)))
url (:url s') url (:src s')]
thumb (if
(and
(uploaded? url)
thumbsize)
(let [p (str (cio/file "uploads" (name thumbsize) (fs/base-name url)))
p' (cio/file content-dir p)]
(if
(and (fs/exists? p') (fs/readable? p'))
p)))]
(slide-merge-dimensions (slide-merge-dimensions
(if thumb (assoc s' :msrc (find-thumb url thumbsize)))))
(assoc s' :msrc thumb)
s')))) (process-simple-slide '([:title "Frost on a gate, Laurieston"] [:src "content/uploads/g1.jpg"]))
(def process-simple-photoswipe (def process-simple-photoswipe
"Process a simplified specification for a photoswipe gallery, comprising just "Process a simplified specification for a photoswipe gallery, comprising just
@ -144,8 +166,7 @@
(json/write-str (json/write-str
{:slides (map {:slides (map
process-simple-slide process-simple-slide
(re-seq #"!\[[^(]*\([^)]*\)" spec)) (simplify (simple-grammar spec)))
;; TODO: better to split slides in instaparse
:options { :timeToIdle 100 } :options { :timeToIdle 100 }
:openImmediately true}) index)))) :openImmediately true}) index))))
@ -157,12 +178,12 @@
;; ![Feathered snow on log, Taliesin](content/uploads/g3.jpg) ;; ![Feathered snow on log, Taliesin](content/uploads/g3.jpg)
;; ![Crystaline growth on seed head, Taliesin](content/uploads/g4.jpg)")) ;; ![Crystaline growth on seed head, Taliesin](content/uploads/g4.jpg)"))
;; (process-simple-photoswipe (process-simple-photoswipe
;; "![Frost on a gate, Laurieston](content/uploads/g1.jpg) "![Frost on a gate, Laurieston](content/uploads/g1.jpg)
;; ![Feathered crystals on snow surface, Taliesin](content/uploads/g2.jpg) ![Feathered crystals on snow surface, Taliesin](content/uploads/g2.jpg)
;; ![Feathered snow on log, Taliesin](content/uploads/g3.jpg) ![Feathered snow on log, Taliesin](content/uploads/g3.jpg)
;; ![Crystaline growth on seed head, Taliesin](content/uploads/g4.jpg)" ![Crystaline growth on seed head, Taliesin](content/uploads/g4.jpg)"
;; 1) 1)
(defn process-photoswipe (defn process-photoswipe
"Process a Photoswipe specification which may conform either to the "Process a Photoswipe specification which may conform either to the

View file

@ -165,10 +165,6 @@
([inclusions text] ([inclusions text]
(let [ks (keys inclusions)] (let [ks (keys inclusions)]
(if (empty? (keys inclusions)) (if (empty? (keys inclusions))
;; TODO: this is one opportunity to add scripts at the end of the
;; constructed text. I've a feeling that that would be a mistake and
;; that instead we should hand back a map comprising the text and the
;; keys of the extensions
text text
(let [kw (first ks)] (let [kw (first ks)]
(reintegrate-inclusions (reintegrate-inclusions

View file

@ -29,7 +29,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error to show if text to be rendered is nil. ;; Error to show if text to be rendered is nil.
;; TODO: this should go through i18n ;; TODO: this should go through i18n, but for that to happen we need the
;; request passed through to here.
(def no-text-error "No text: does the file exist?") (def no-text-error "No text: does the file exist?")

View file

@ -320,14 +320,14 @@
uploaded (if upload (ul/store-upload params data-path)) uploaded (if upload (ul/store-upload params data-path))
user (session/get :user) user (session/get :user)
summary (format "%s: %s" user (or (:summary params) "no summary"))] summary (format "%s: %s" user (or (:summary params) "no summary"))]
;; TODO: Get this working! it MUST work! (log/info (session/get :user) "has uploaded" (cs/join "; " (map :resource uploaded)))
;; (if-not (if-not
;; (empty? uploaded) (empty? uploaded)
;; (do (do
;; (map (map
;; #(git/git-add git-repo (str :resource %)) #(git/git-add git-repo (str :resource %))
;; (remove nil? uploaded)) (remove nil? uploaded))
;; (git/git-commit git-repo summary {:name user :email (auth/get-email user)}))) (git/git-commit git-repo summary {:name user :email (auth/get-email user)})))
(layout/render "upload.html" (layout/render "upload.html"
(merge (util/standard-params request) (merge (util/standard-params request)
{:title (util/get-message :file-upload-title request) {:title (util/get-message :file-upload-title request)
@ -408,7 +408,13 @@
(defn wrap-restricted-redirect (defn wrap-restricted-redirect
;; TODO: this is not idiomatic, and it's too late to write something idiomatic just now ;; TODO: this is not idiomatic, and it's too late to write something idiomatic just now
;; TODO TODO: it's also not working. ;; TODO TODO: it's also not working.
;; TODO: probably I need to use either the 'buddy' or 'friend' authentication libraries
;; see https://github.com/cemerick/friend and
;; https://github.com/metosin/compojure-api/wiki/Authentication-and-Authorization
;; but I don't yet see even so how to do redirect to the failed page after successful
;; authorisation.
[f request] [f request]
(log/info "wrap-restricted-redirect: f:" f "; request " (with-out-str (clojure.pprint/pprint request)))
(route/restricted (route/restricted
(apply (apply
f f

View file

@ -0,0 +1,45 @@
(ns smeagol.test.extensions.photoswipe
(:require [clojure.test :refer :all]
[clojure.string :as cs]
[smeagol.extensions.photoswipe :refer :all]))
(deftest simple-syntax-tests
(testing "Process simple slide"
(let [expected {:title "Frost on a gate, Laurieston",
:src "content/uploads/g1.jpg",
:w 2592,
:h 1944
:msrc "content/uploads/med/g1.jpg"}
actual (process-simple-slide
'([:title "Frost on a gate, Laurieston"]
[:src "content/uploads/g1.jpg"]))]
(is (= actual expected))))
(testing "Find thumbnail"
(let [expected "content/uploads/med/g1.jpg"
actual (find-thumb "content/uploads/g1.jpg" :med)]
(is (= actual expected) "`resources/content/uploads/med/g1.jpg` is in
the repository, so should be found"))
(let [expected nil
actual (find-thumb "passwd" :med)]
(is (= actual expected) "`resources/passwd` is in
the repository, but is not uploaded so should NOT be found")))
(testing "Simple slide grammar"
(let [expected '(([:title "Frost on a gate, Laurieston"] [:src "content/uploads/g1.jpg"]))
actual (simplify
(simple-grammar
"![Frost on a gate, Laurieston](content/uploads/g1.jpg)"))]
(is (= actual expected) "Valid syntax, should parse")
(is (empty? (simplify (simple-grammar "[Fred](fred.jpg)")))
"Invalid syntax (no leading `!`), should not parse."))
(let [expected '(([:title "Frost on a gate, Laurieston"] [:src "content/uploads/g1.jpg"])
([:title "Feathered crystals on snow surface, Taliesin"] [:src "content/uploads/g2.jpg"])
([:title "Feathered snow on log, Taliesin"] [:src "content/uploads/g3.jpg"])
([:title "Crystaline growth on seed head, Taliesin"] [:src "content/uploads/g4.jpg"]))
actual (simplify
(simple-grammar
"![Frost on a gate, Laurieston](content/uploads/g1.jpg)
![Feathered crystals on snow surface, Taliesin](content/uploads/g2.jpg)
![Feathered snow on log, Taliesin](content/uploads/g3.jpg)
![Crystaline growth on seed head, Taliesin](content/uploads/g4.jpg)"))]
(is (= actual expected) "Valid syntax, should parse"))))

View file

@ -1,7 +1,6 @@
(ns smeagol.test.local-links (ns smeagol.test.local-links
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[clojure.string :as cs] [clojure.string :as cs]
[smeagol.local-links :refer [local-links no-text-error]]
[smeagol.extensions.test :refer :all] [smeagol.extensions.test :refer :all]
[smeagol.local-links :refer :all])) [smeagol.local-links :refer :all]))