#45,#46: done

This commit is contained in:
Simon Brooke 2020-02-16 13:51:41 +00:00
parent ee46b0d545
commit 151987e598
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
5 changed files with 131 additions and 63 deletions

View file

@ -30,37 +30,35 @@
:content-dir "resources/public/content" :content-dir "resources/public/content"
;; where content is served from. ;; where content is served from.
:default-locale "en-GB" ;; default language used for messages :default-locale "en-GB" ;; default language used for messages
:extensions-from :local ;; where to load JavaScript libraries
;; from: options are :local, :remote.
:formatters ;; formatters for processing markdown :formatters ;; formatters for processing markdown
;; extensions. ;; extensions.
{:backticks {:formatter "smeagol.formatting/process-backticks" {:backticks {:formatter "smeagol.formatting/process-backticks"
:scripts {} :scripts {}
:styles {}} :styles {}}
:mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid" :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
:scripts {:core {:local "vendor/mermaid/dist/mermaid.js"}} :scripts {:core {:local "vendor/mermaid/dist/mermaid.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.4.6/mermaid.min.js"}}
:styles {}} :styles {}}
:pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe" :pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
:scripts {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.min.js" :scripts {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"} :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
:ui {:local "/vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js" :ui {:local "vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}} :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
:styles {:core {:local "/vendor/node_modules/photoswipe/dist/photoswipe.css" :styles {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.css"}
:remote "/vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}} :skin {:local "vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
:test {:formatter "smeagol.extensions.test/process-test" } :test {:formatter "smeagol.extensions.test/process-test" }
:vega {:formatter "smeagol.extensions.vega/process-vega" :vega {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"} :scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"} :lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"} :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}
:styles {}}}
:vis {:formatter "smeagol.extensions.vega/process-vega" :vis {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"} :scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"} :lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"} :embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}}
:styles {}}}
}
: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
;; from: options are :local, :cdnjs
:passwd "resources/passwd" :passwd "resources/passwd"
;; where the password file is stored ;; where the password file is stored
:site-title "Smeagol" ;; overall title of the site, used in :site-title "Smeagol" ;; overall title of the site, used in
@ -71,4 +69,10 @@
;; stored in the /small directory ;; stored in the /small directory
:med 400 ;; maximum dimension of thumbnails :med 400 ;; maximum dimension of thumbnails
;; stored in the /med directory ;; stored in the /med directory
}} ;; you can add as many extra keys and values as
;; you like here for additional sizes of images.
;; Images will only be scaled if their maximum
;; dimension (in pixels) is greater than the value;
;; only JPEG and PNG images will be scaled.
}
}

View file

@ -2,11 +2,9 @@
{% block extra-headers %} {% block extra-headers %}
{% for script in scripts %} {% for script in scripts %}
<script src="{{script}}"></script> <script src="{{script}}"></script>{% endfor %}
{% endfor %}
{% for style in styles %} {% for style in styles %}
<link href="{{style}}" rel="stylesheet" type="text/css" /> <link href="{{style}}" rel="stylesheet" type="text/css" />{% endfor %}
{% endfor %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View file

@ -93,6 +93,7 @@
fragments fragments
(cons fragment processed))) (cons fragment processed)))
(defn deep-merge [v & vs] (defn deep-merge [v & vs]
"Cripped in its entirety from https://clojuredocs.org/clojure.core/merge." "Cripped in its entirety from https://clojuredocs.org/clojure.core/merge."
(letfn [(rec-merge [v1 v2] (letfn [(rec-merge [v1 v2]
@ -124,18 +125,18 @@
fragments fragments
(cons ident processed)))) (cons ident processed))))
(apply-formatter ;; (apply-formatter
3 ;; 3
{:inclusions {}} ;; {:inclusions {}}
'() ;; '()
'() ;; '()
"pswp ;; "pswp
![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)"
"pswp" ;; "pswp"
smeagol.extensions.photoswipe/process-photoswipe) ;; smeagol.extensions.photoswipe/process-photoswipe)
(defn process-text (defn process-text
"Process this `text`, assumed to be markdown potentially containing both local links "Process this `text`, assumed to be markdown potentially containing both local links
@ -145,6 +146,8 @@
The map has two top-level keys: `:inclusions`, a map of constructed keywords to The map has two top-level keys: `:inclusions`, a map of constructed keywords to
inclusion specifications, and `:text`, an HTML text string with the keywords inclusion specifications, and `:text`, an HTML text string with the keywords
present where the corresponding inclusion should be inserted." present where the corresponding inclusion should be inserted."
;; TODO: the inclusion->index bug is in here somewhere.
([^String text] ([^String text]
(process-text 0 {} (cs/split (or text "") #"```") '())) (process-text 0 {} (cs/split (or text "") #"```") '()))
([index result fragments processed] ([index result fragments processed]
@ -185,19 +188,19 @@
(rest fragments) (rest fragments)
(cons ident processed)) (cons ident processed))
{:inclusions {ident (apply formatter (list (subs fragment (count first-token)) index))} {:inclusions {ident (apply formatter (list (subs fragment (count first-token)) index))}
:extensions (cons kw (:extensions result))})) :extensions (assoc (or (:extensions result) {}) kw (-> config :formatters kw))}))
:else :else
;; Otherwise process the current fragment as markdown and recurse on ;; Otherwise process the current fragment as markdown and recurse on
;; down the list ;; down the list
(process-markdown-fragment (process-markdown-fragment
index result remarked (rest fragments) processed))))) index result remarked (rest fragments) processed)))))
(process-text ;; (process-text
"pswp ;; "pswp
![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)" )
(defn reintegrate-inclusions (defn reintegrate-inclusions
"Given a map of the form produced by `process-text`, return a string of HTML text "Given a map of the form produced by `process-text`, return a string of HTML text
@ -236,3 +239,4 @@

View file

@ -28,7 +28,8 @@
[com.stuartsierra.component :as component] [com.stuartsierra.component :as component]
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[smeagol.include.resolve-local-file :as resolve] [smeagol.include.resolve-local-file :as resolve]
[smeagol.include :as include])) [smeagol.include :as include]
[smeagol.util :refer [content-dir local-url]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -118,6 +119,9 @@
(def md-include-system (def md-include-system
"Allowing Markdown includes. Unfortunately the contributor who contributed
this didn't document it, and I haven't yet worked out how it works. TODO:
investigate and document."
(component/start (component/start
(component/system-map (component/system-map
:resolver (resolve/new-resolver util/content-dir) :resolver (resolve/new-resolver util/content-dir)
@ -125,6 +129,7 @@
(include/new-includer) (include/new-includer)
[:resolver])))) [:resolver]))))
(defn preferred-source (defn preferred-source
"Here, `component` is expected to be a map with two keys, `:local` and "Here, `component` is expected to be a map with two keys, `:local` and
`:remote`. If the value of `:extensions-from` in `config.edn` is remote `:remote`. If the value of `:extensions-from` in `config.edn` is remote
@ -132,26 +137,51 @@
be returned. Otherwise, if the value of `:local` is nil and the value of be returned. Otherwise, if the value of `:local` is nil and the value of
`:remote` is non-nil, the value of `:remote` will be returned. By default, `:remote` is non-nil, the value of `:remote` will be returned. By default,
the value of `:local` will be returned." the value of `:local` will be returned."
[component] [component ks]
(let [l (:local component) ;; TODO: look at the trick in Selmer to get relative URL (try
r (:remote component)] (let [l (:local component)
(cond l' (if-not (empty? l) (local-url l) l)
(= (:extensions-from config) :remote) (if (empty? r) l r) r (:remote component)]
(empty? l) r (cond
:else l))) (= (:extensions-from config) :remote)
(if (empty? r) l' r)
(empty? l') r
:else l'))
(catch Exception any
(log/error "Failed to find appropriate source for component" ks "because:" any)
nil)))
;; (preferred-source {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js",
;; :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"} :core)
(defn collect-preferred (defn collect-preferred
"From extensions referenced in this `processed-text`, extract the preferred ([processed-text]
URLs for this keyword `k`, expected to be either `:scripts` or `:styles`." (concat
[processed-text k] (collect-preferred processed-text :scripts)
(set (collect-preferred processed-text :styles)))
(remove ([processed-text resource-type]
nil? (reduce concat
(map (map
preferred-source (fn [extension-key]
(apply (map
concat (fn [requirement]
(map vals (map k (vals (:extensions processed-text))))))))) (let [r (preferred-source
(-> processed-text :extensions extension-key resource-type requirement)
requirement)]
(if (empty? r)
(log/warn "Found no valid URL for requirement"
requirement "of extension" extension-key))
r))
(keys (-> processed-text :extensions extension-key resource-type))))
(keys (:extensions processed-text))))))
(cjio/file content-dir "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
(def processed-text (md->html (slurp "resources/public/content/Simplified example gallery.md" )))
(preferred-source (-> processed-text :extensions :pswp :scripts :core) :pswp)
(collect-preferred processed-text :scripts)
(defn wiki-page (defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page" "Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"

View file

@ -51,6 +51,8 @@
(str (cjio/file content-dir "uploads"))) (str (cjio/file content-dir "uploads")))
(def local-url-base (def local-url-base
"Essentially, the slash-terminated absolute path of the `public` resource
directory."
(let [a (str (fs/absolute content-dir))] (let [a (str (fs/absolute content-dir))]
(subs a 0 (- (count a) (count "content"))))) (subs a 0 (- (count a) (count "content")))))
@ -59,7 +61,11 @@
if it is safe to serve. This reason may be logged, but should *not* be 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." shown to remote users, as it would allow file system probing."
[file-path] [file-path]
(let [path (fs/absolute file-path)] (try
(let [path (if
(cs/starts-with? (str file-path) "/")
file-path
(cjio/file local-url-base file-path))]
(cond (cond
(cs/includes? file-path "..") (cs/includes? file-path "..")
(cs/join " " file-path (cs/join " " file-path
@ -69,31 +75,57 @@
(not (fs/exists? path)) (not (fs/exists? path))
(cs/join " " [path "does not exist"]) (cs/join " " [path "does not exist"])
(not (fs/readable? path)) (not (fs/readable? path))
(cs/join " " [path "is not readable"])))) (cs/join " " [path "is not readable"])))
(catch Exception any (cs/join " " file-path "is not servable because" (.getMessage any)))))
;; (not-servable-reason "/home/simon/workspace/smeagol/resources/public/content/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
;; (not-servable-reason "/root/froboz")
(defn local-url? (defn local-url?
"True if this `file-path` can be served as a local URL, else false." "True if this `file-path` can be served as a local URL, else false."
[file-path] [file-path]
(empty? (not-servable-reason file-path))) (try
(if
(empty? (not-servable-reason file-path))
true
(do
(log/error
"In `smeagol.util/local-url? `" file-path "` is not a servable resource.")
false))
(catch Exception any
(log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
false)))
(defn local-url (defn local-url
"Return a local URL for this `file-path`, or a deliberate 404 if none "Return a local URL for this `file-path`, or a deliberate 404 if none
can be safely served." can be safely served."
;; TODO: this actually returns a relative URL relative to local-url-base.
;; That's not quite what we want because in Tomcat contexts the absolute
;; URL may be different. We *ought* to be able to extract the offset from the
;; servlet context, but it may be simpler to jam it in the config.
[file-path] [file-path]
(try (try
(let [path (fs/absolute file-path) (let [path (if
(cs/starts-with? file-path local-url-base)
(subs file-path (count local-url-base))
file-path)
problem (not-servable-reason path)] problem (not-servable-reason path)]
(if (if
(empty? problem) (empty? problem)
(subs (str path) (count local-url-base)) path
(do (do
(log/error (log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource.") "In `smeagol.util/local-url `" file-path "` is not a servable resource.")
(str "404-not-found?path=" file-path)))) (str "404-not-found?path=" file-path))))
(catch Exception any (catch Exception any
(log/error (log/error
"In `smeagol.util/local-url `" file-path "` is not a servable resource:" any) "In `smeagol.util/local-url `" file-path "` is not a servable resource:" any)
(str "404-not-found?path=" file-path)))) (str "404-not-found?path=" file-path))))
(local-url? "vendor/node_modules/photoswipe/dist/photoswipe.min.js")
(local-url? "/home/simon/workspace/smeagol/resources/public/vendor/node_modules/photoswipe/dist/photoswipe.min.js")
(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."