Simplified syntax for Photoswipe galleries now works

This commit is contained in:
Simon Brooke 2020-02-11 13:14:36 +00:00
parent fc4dcdb5d3
commit d2e20162ef
10 changed files with 283 additions and 83 deletions

View file

@ -17,6 +17,7 @@
[hiccup "1.0.5"] [hiccup "1.0.5"]
[im.chit/cronj "1.4.4"] [im.chit/cronj "1.4.4"]
[image-resizer "0.1.10"] [image-resizer "0.1.10"]
[instaparse "1.4.10"]
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
[markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]] [markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]]
[me.raynes/fs "1.4.6"] [me.raynes/fs "1.4.6"]

View file

@ -36,7 +36,7 @@
"vis" smeagol.formatting/process-vega "vis" smeagol.formatting/process-vega
"mermaid" smeagol.extensions.mermaid/process-mermaid "mermaid" smeagol.extensions.mermaid/process-mermaid
"backticks" smeagol.formatting/process-backticks "backticks" smeagol.formatting/process-backticks
"pswp" smeagol.formatting/process-photoswipe} "pswp" smeagol.extensions.photoswipe/process-photoswipe}
:log-level :info ;; the minimum logging level; one of :log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal ;; :trace :debug :info :warn :error :fatal
:js-from :cdnjs ;; where to load JavaScript libraries :js-from :cdnjs ;; where to load JavaScript libraries

View file

@ -1,26 +1,3 @@
## The Gallery
This page holds an example Photoswipe gallery.
```pswp
{
slides: [
{ src: 'content/uploads/g1.jpg', w: 2592, h:1944,
title: 'Frost on a gate, Laurieston' },
{ src: 'content/uploads/g2.jpg', w: 2560, h:1920,
title: 'Feathered crystals on snow surface, Taliesin' },
{ src: 'content/uploads/g3.jpg', w: 2560, h:1920,
title: 'Feathered snow on log, Taliesin' },
{ src: 'content/uploads/g4.jpg', w: 2560, h:1920,
title: 'Crystaline growth on seed head, Taliesin' }],
options: {
timeToIdle: 100
},
openImmediately: true
}
```
## How this works ## How this works
The specification for this gallery is as follows: The specification for this gallery is as follows:
@ -57,3 +34,27 @@ Optional. The value of `options` is a JSON object [as documented here](https://p
### openImmediately ### openImmediately
Optional. If the value of `openImmediately` is `true`, the gallery will open immediately, covering the whole page. If false, only a button with the label 'Open the gallery' will be shown. Selecting this button will cause the gallery to open. Optional. If the value of `openImmediately` is `true`, the gallery will open immediately, covering the whole page. If false, only a button with the label 'Open the gallery' will be shown. Selecting this button will cause the gallery to open.
## The Gallery
This page holds an example Photoswipe gallery.
```pswp
{
slides: [
{ src: 'content/uploads/g1.jpg', w: 2592, h:1944,
title: 'Frost on a gate, Laurieston' },
{ src: 'content/uploads/g2.jpg', w: 2560, h:1920,
title: 'Feathered crystals on snow surface, Taliesin' },
{ src: 'content/uploads/g3.jpg', w: 2560, h:1920,
title: 'Feathered snow on log, Taliesin' },
{ src: 'content/uploads/g4.jpg', w: 2560, h:1920,
title: 'Crystaline growth on seed head, Taliesin' }],
options: {
timeToIdle: 100
},
openImmediately: true
}
```

View file

@ -68,9 +68,8 @@ data/classes.mermaid
## Photoswipe galleries ## Photoswipe galleries
Not so much a formatter, this is an extension to allow you to embed image galleries in your markdown. To specify a gallery, use three backticks followed by `pswp`, followed on the following lines by a Photoswipe specification in [JSON](https://www.json.org/json-en.html) Not so much a formatter, this is an extension to allow you to embed image galleries in your markdown. To specify a gallery, use three backticks followed by `pswp`, followed on the following lines by a [Photoswipe](https://photoswipe.com/documentation/getting-started.html) specification in [JSON](https://www.json.org/json-en.html)
followed by three backticks on a line by themselves. There is an [[Example gallery]] so that you can see how this works. followed by three backticks on a line by themselves. There is an [[Example gallery]] with the full PhotoSwipe configuration, and a [[Simplified example gallery]] using a much simpler syntax, so that you can see how this works.
## Writing your own custom formatters ## Writing your own custom formatters

View file

@ -0,0 +1,24 @@
## How this works
The specification for this gallery is as follows:
```
![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)
```
That's all there is to it - a sequence of image links just as you'd write them anywhere else in the wiki.
## 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.
```pswp
![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)
```

View file

@ -1,4 +1,4 @@
(ns ^{:doc "Format Semagol's extended markdown format." (ns ^{:doc "Mermaid formatter for Semagol's extendsible markdown format."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.extensions.mermaid smeagol.extensions.mermaid
(:require [smeagol.extensions.utils :refer :all] (:require [smeagol.extensions.utils :refer :all]

View file

@ -0,0 +1,181 @@
(ns ^{:doc "Photoswipe gallery formatter for Semagol's extendsible markdown
format."
:author "Simon Brooke"}
smeagol.extensions.photoswipe
(:require [clojure.data.json :as json]
[clojure.java.io :as cio]
[clojure.string :as cs]
[image-resizer.util :refer [buffered-image dimensions]]
[instaparse.core :as insta]
[me.raynes.fs :as fs]
[noir.io :as io]
[smeagol.configuration :refer [config]]
[smeagol.extensions.utils :refer :all]
[taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Smeagol: a very simple Wiki engine.
;;;;
;;;; This program is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License
;;;; as published by the Free Software Foundation; either version 2
;;;; of the License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
;;;; USA.
;;;;
;;;; Copyright (C) 2017 Simon Brooke
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn process-full-photoswipe
"Process a specification for a photoswipe gallery, using a JSON
specification based on that documented on the Photoswipe website."
[^String spec ^Integer index]
(str
"<div class=\"pswp\" id=\"pswp-"
index "\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n"
(slurp
(str (io/resource-path) "html-includes/photoswipe-boilerplate.html"))
"</div>
<script>
\n//<![CDATA[\n
var pswpElement = document.getElementById('pswp-" index "');
var spec" index " = "
spec
";
var gallery" index
" = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, spec"
index ".slides, spec" index ".options);
if (spec" index ".openImmediately) { gallery" index ".init(); }
\n//]]\n
</script>
<p><button onclick=\"gallery" index
".init()\">Open the gallery</button></p>
</div>"))
(def simple-grammar
"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
they are very inscrutable."
(insta/parser "SLIDE := START-CAPTION title END-CAPTION src END-SRC;
START-CAPTION := '![' ;
END-CAPTION := '](' ;
END-SRC := ')' ;
title := #'[^]]*' ;
src := #'[^)]*' ;
SPACE := #'[\\r\\n\\W]*'"))
(defn simplify
[tree]
(if
(coll? tree)
(case (first tree)
:SLIDE (remove empty? (map simplify (rest tree)))
:title tree
:src tree
:START-CAPTION nil
:END-CAPTION nil
:END-SRC nil
(remove empty? (map simplify tree)))))
(defn uploaded?
"Does this `url` string appear to be one that has been uploaded to our
`uploads` directory?"
[url]
(and
(cs/starts-with? (str url) "content/uploads")
(fs/exists? (cio/file upload-dir (fs/base-name url)))))
;; (uploaded? "content/uploads/g1.jpg")
(defn slide-merge-dimensions
"If this `slide` appears to be local, return it decorated with the
dimensions of the image it references."
[slide]
(let [url (:src slide)
dimensions (try
(if (uploaded? url)
(dimensions
(buffered-image (cio/file upload-dir (fs/base-name url)))))
(catch Exception x (.getMessage x)))]
(if dimensions
(assoc slide :w (first dimensions) :h (nth dimensions 1))
slide)))
;; (slide-merge-dimensions
;; {:title "Frost on a gate, Laurieston",
;; :src "content/uploads/g1.jpg"})
(defn process-simple-slide
[slide-spec]
(let [s (simplify (simple-grammar slide-spec))
s'(zipmap (map first s) (map #(nth % 1) s))
thumbsizes (:thumbnails config)
thumbsize (first
(sort
#(> (%1 thumbsizes) (%2 thumbsizes))
(keys thumbsizes)))
url (:url 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
(if thumb
(assoc s' :msrc thumb)
s'))))
(def process-simple-photoswipe
"Process a simplified specification for a photoswipe gallery, comprising just
a sequence of MarkDown image links. This is REALLY expensive to do, we don't
want to do it often. Hence memoised."
(memoize
(fn
[^String spec ^Integer index]
(process-full-photoswipe
(json/write-str
{:slides (map
process-simple-slide
(re-seq #"!\[[^(]*\([^)]*\)" spec))
;; TODO: better to split slides in instaparse
:options { :timeToIdle 100 }
:openImmediately true}) index))))
;; (map
;; process-simple-slide
;; (re-seq #"!\[[^(]*\([^)]*\)"
;; "![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)"))
;; (process-simple-photoswipe
;; "![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)"
;; 1)
(defn process-photoswipe
[^String url-or-pswp-spec ^Integer index]
(let [data (resource-url-or-data->data url-or-pswp-spec)
spec (cs/trim (:data data))]
(if
(cs/starts-with? spec "![")
(process-simple-photoswipe spec index)
(process-full-photoswipe spec index))))

View file

@ -2,9 +2,11 @@
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.extensions.utils smeagol.extensions.utils
(:require [cemerick.url :refer (url url-encode url-decode)] (:require [cemerick.url :refer (url url-encode url-decode)]
[clojure.java.io :as cjio]
[clojure.string :as cs] [clojure.string :as cs]
[me.raynes.fs :as fs] [me.raynes.fs :as fs]
[noir.io :as io] [noir.io :as io]
[smeagol.configuration :refer [config]]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -30,7 +32,17 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn resource-url-or-data->data (def content-dir
(str
(fs/absolute
(or
(:content-dir config)
(cjio/file (io/resource-path) "content")))))
(def upload-dir
(str (cjio/file content-dir "uploads")))
(def resource-url-or-data->data
"Interpret this `resource-url-or-data` string as data to be digested by a "Interpret this `resource-url-or-data` string as data to be digested by a
`process-extension` function. It may be a URL or the pathname of a local `process-extension` function. It may be a URL or the pathname of a local
resource, in which case the content should be fetched; or it may just be resource, in which case the content should be fetched; or it may just be
@ -40,7 +52,8 @@
`:text`, and a key `:data` whose value is the data. There will be an `:text`, and a key `:data` whose value is the data. There will be an
additional key being the value of the `:from` key, whose value will be the additional key being the value of the `:from` key, whose value will be the
source of the data." source of the data."
[^String resource-url-or-data] (memoize
(fn [^String resource-url-or-data]
(let [default {:from :text (let [default {:from :text
:text resource-url-or-data :text resource-url-or-data
:data resource-url-or-data}] :data resource-url-or-data}]
@ -69,4 +82,4 @@
"` because " "` because "
(.getName (.getClass x)) (.getName (.getClass x))
(.getMessage x) ) (.getMessage x) )
default)))) default))))))

View file

@ -6,9 +6,9 @@
[cemerick.url :refer (url url-encode url-decode)] [cemerick.url :refer (url url-encode url-decode)]
[clj-yaml.core :as yaml] [clj-yaml.core :as yaml]
[markdown.core :as md] [markdown.core :as md]
[noir.io :as io] ;; used by photoswipe, only
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[smeagol.extensions.mermaid :refer [process-mermaid]])) [smeagol.extensions.mermaid :refer [process-mermaid]]
[smeagol.extensions.photoswipe :refer [process-photoswipe]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -86,28 +86,6 @@
index index
");\n//]]\n</script>")) ");\n//]]\n</script>"))
(defn process-photoswipe
"Process specification for a photoswipe gallery"
[^String spec ^Integer index]
(str
"<div class=\"pswp\" id=\"pswp-"
index "\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n"
(slurp (str (io/resource-path) "html-includes/photoswipe-boilerplate.html"))
"</div>
<script>
\n//<![CDATA[\n
var pswpElement = document.getElementById('pswp-" index "');
var spec" index " = "
spec
";
var gallery" index
" = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, spec" index ".slides, spec" index ".options);
if (spec" index ".openImmediately) { gallery" index ".init(); }
\n//]]\n
</script>
<p><button onclick=\"gallery" index ".init()\">Open the gallery</button></p>
</div>"))
(defn process-backticks (defn process-backticks
"Effectively, escape the backticks surrounding this `text`, by protecting them "Effectively, escape the backticks surrounding this `text`, by protecting them

View file

@ -46,6 +46,9 @@
(:content-dir config) (:content-dir config)
(cjio/file (io/resource-path) "content"))))) (cjio/file (io/resource-path) "content")))))
(def upload-dir
(str (cjio/file content-dir "uploads")))
(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]