mirror of
https://github.com/journeyman-cc/smeagol.git
synced 2026-04-12 18:05:06 +00:00
War on TODOs
Improved mermaid and photoswipe extensions, handled control-s on the edit page.
This commit is contained in:
parent
ac433c3afa
commit
c19580a23e
10 changed files with 142 additions and 61 deletions
|
|
@ -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
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
|
|
||||||
|
|
@ -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 := '' ;
|
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 @@
|
||||||
;; 
|
;; 
|
||||||
;; "))
|
;; "))
|
||||||
|
|
||||||
;; (process-simple-photoswipe
|
(process-simple-photoswipe
|
||||||
;; "
|
"
|
||||||
;; 
|

|
||||||
;; 
|

|
||||||
;; "
|
"
|
||||||
;; 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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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?")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
45
test/smeagol/test/extensions/photoswipe.clj
Normal file
45
test/smeagol/test/extensions/photoswipe.clj
Normal 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
|
||||||
|
""))]
|
||||||
|
(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
|
||||||
|
"
|
||||||
|

|
||||||
|

|
||||||
|
"))]
|
||||||
|
(is (= actual expected) "Valid syntax, should parse"))))
|
||||||
|
|
@ -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]))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue