From 23253388f673d79ca616ff6fa2b55a0a9c73f4f2 Mon Sep 17 00:00:00 2001 From: Brent Hagany Date: Fri, 29 Dec 2017 23:10:09 -0600 Subject: [PATCH 1/3] Implement #181 - web app manifest support This feature will allow sites built with Perun to be installed as an app on systems that support it. When paired with #180, this will be a slick offline experience. --- src/io/perun.clj | 58 +++++++++++++++++++++++++++++++++++++++ src/io/perun/manifest.clj | 15 ++++++++++ test/io/perun_test.clj | 28 +++++++++++++++++-- 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/io/perun/manifest.clj diff --git a/src/io/perun.clj b/src/io/perun.clj index a965482d..996117a7 100644 --- a/src/io/perun.clj +++ b/src/io/perun.clj @@ -1285,3 +1285,61 @@ :passthru-fn content-passthru :task-name "inject-scripts" :tracer :io.perun/inject-scripts})))) + +(def ^:private ^:deps manifest-deps + '[[org.clojure/tools.namespace "0.3.0-alpha3"] + [cheshire "5.7.0"]]) + +(def +manifest-defaults+ + {:out-dir "public" + :icon-path "icon.png" + :resolutions #{192 512} + :theme-color "#ffffff" + :display "standalone" + :scope "/"}) + +(deftask manifest* + [o out-dir OUTDIR str "the output directory" + t site-title TITLE str "name for the installable web application" + c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" + d display DISPLAY str "display mode for browser (default \"standalone\")" + s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] + (let [{:keys [site-title] :as opts} (merge +manifest-defaults+ *opts*) + pod (create-pod manifest-deps)] + (letfn [(manifest-path [fileset] + (let [icon-metas (filter-meta-by-ext fileset {:filterer :manifest-icon}) + path (perun/create-filepath out-dir "manifest.json") + global-meta (pm/get-global-meta fileset) + args (merge opts + {:icons icon-metas + :input-paths (into #{} (map :path icon-metas)) + :site-title (or site-title (:site-title global-meta))})] + {path args}))] + (content-task + {:render-form-fn (fn [data] `(io.perun.manifest/manifest ~data)) + :paths-fn manifest-path + :task-name "manifest" + :tracer :io.perun/manifest + :pod pod})))) + +(deftask manifest + "Creates a manifest.json for Android (currently)" + [o out-dir OUTDIR str "the output directory" + i icon-path PATH str "The input icon to be resized (default \"icon.png\"" + r resolutions RESOLUTIONS #{int} "resolutions to which images should be resized (default #{192 512})" + t site-title TITLE str "name for the installable web application" + c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" + d display DISPLAY str "display mode for browser (default \"standalone\")" + s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] + (let [{:keys [out-dir icon-path resolutions site-title theme-color display scope]} + (merge +manifest-defaults+ *opts*)] + (comp (images-resize :out-dir out-dir + :resolutions resolutions + :filterer #(= (:path %) icon-path) + :meta {:manifest-icon true}) + (mime-type :filterer :manifest-icon) + (manifest* :out-dir out-dir + :site-title site-title + :theme-color theme-color + :display display + :scope scope)))) diff --git a/src/io/perun/manifest.clj b/src/io/perun/manifest.clj new file mode 100644 index 00000000..78082710 --- /dev/null +++ b/src/io/perun/manifest.clj @@ -0,0 +1,15 @@ +(ns io.perun.manifest + (:require [cheshire.core :refer [generate-string]])) + +(defn manifest + [{:keys [icons site-title theme-color display scope input-paths] :as data}] + (let [manifest {:name site-title + :icons (for [{:keys [permalink width height mime-type]} icons] + {:src permalink + :sizes (str width "x" height) + :type mime-type}) + :theme_color theme-color + :display display + :scope scope}] + {:rendered (generate-string manifest) + :input-paths input-paths})) diff --git a/test/io/perun_test.clj b/test/io/perun_test.clj index 8d3a34bc..fa5d30e0 100644 --- a/test/io/perun_test.clj +++ b/test/io/perun_test.clj @@ -444,7 +444,17 @@ This --- be _asciidoc_.") (testing "draft" (file-exists? :path (perun/url-to-path "public/test/index.html") :negate? true - :msg "`draft` should remove files")))) + :msg "`draft` should remove files")) + + (add-image :path "icon.png" :type "PNG" :width 10 :height 10) + (p/manifest) + (testing "manifest" + (file-exists? :path (perun/url-to-path "public/manifest.json") + :msg "`manifest` should write manifest.json") + (file-exists? :path (perun/url-to-path "public/icon_192.png") + :msg "`manifest` should write icon resized to 192px") + (file-exists? :path (perun/url-to-path "public/icon_512.png") + :msg "`manifest` should write icon resized to 512px")))) (deftesttask with-arguments-test [] (comp (boot/with-pre-wrap fileset @@ -757,7 +767,21 @@ This --- be _asciidoc_.") (content-check :path "baz.htm" :content (str "") :negate? true - :msg "`inject-scripts` should not alter the contents of a removed file"))))) + :msg "`inject-scripts` should not alter the contents of a removed file"))) + + (add-image :path "an-icon.png" :type "PNG" :width 10 :height 10) + (p/manifest :out-dir "foop" + :icon-path "an-icon.png" + :resolutions #{20} + :site-title "Blarg" + :theme-color "#f0987d" + :display "fullscreen" + :scope "/blarp") + (testing "manifest" + (file-exists? :path (perun/url-to-path "foop/manifest.json") + :msg "`manifest` should write manifest.json") + (file-exists? :path (perun/url-to-path "foop/an-icon_20.png") + :msg "`manifest` should write icon resized to 20px")))) (deftesttask content-tests [] (comp (testing "Collection works without input files" ;; #77 From 19d11d76e812c72481327e26dd9be97b07de6ed1 Mon Sep 17 00:00:00 2001 From: Brent Hagany Date: Sat, 30 Dec 2017 10:42:43 -0600 Subject: [PATCH 2/3] Update manifest handling The Lighthouse Chrome extension made some recommendations for attributes to include, so I've updated Perun's manifest.json handling --- src/io/perun.clj | 42 ++++++++++++++++++++++++--------------- src/io/perun/manifest.clj | 4 +++- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/io/perun.clj b/src/io/perun.clj index 996117a7..62fc13e7 100644 --- a/src/io/perun.clj +++ b/src/io/perun.clj @@ -1295,25 +1295,30 @@ :icon-path "icon.png" :resolutions #{192 512} :theme-color "#ffffff" + :background-color "#ffffff" :display "standalone" :scope "/"}) (deftask manifest* - [o out-dir OUTDIR str "the output directory" - t site-title TITLE str "name for the installable web application" - c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" - d display DISPLAY str "display mode for browser (default \"standalone\")" - s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] - (let [{:keys [site-title] :as opts} (merge +manifest-defaults+ *opts*) + [o out-dir OUTDIR str "the output directory" + t site-title TITLE str "name for the installable web application" + l short-title SHORTTITLE str "short name for the installable web application (default :site-title)" + c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" + b background-color BGCOLOR str "background color for app loading state (default \"#ffffff\")" + d display DISPLAY str "display mode for browser (default \"standalone\")" + s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] + (let [opts (merge +manifest-defaults+ *opts*) pod (create-pod manifest-deps)] (letfn [(manifest-path [fileset] (let [icon-metas (filter-meta-by-ext fileset {:filterer :manifest-icon}) path (perun/create-filepath out-dir "manifest.json") global-meta (pm/get-global-meta fileset) + site-title (or site-title (:site-title global-meta)) args (merge opts {:icons icon-metas :input-paths (into #{} (map :path icon-metas)) - :site-title (or site-title (:site-title global-meta))})] + :site-title site-title + :short-title (or short-title site-title)})] {path args}))] (content-task {:render-form-fn (fn [data] `(io.perun.manifest/manifest ~data)) @@ -1323,15 +1328,18 @@ :pod pod})))) (deftask manifest - "Creates a manifest.json for Android (currently)" - [o out-dir OUTDIR str "the output directory" - i icon-path PATH str "The input icon to be resized (default \"icon.png\"" - r resolutions RESOLUTIONS #{int} "resolutions to which images should be resized (default #{192 512})" - t site-title TITLE str "name for the installable web application" - c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" - d display DISPLAY str "display mode for browser (default \"standalone\")" - s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] - (let [{:keys [out-dir icon-path resolutions site-title theme-color display scope]} + "Creates a manifest.json for installable web applications" + [o out-dir OUTDIR str "the output directory" + i icon-path PATH str "The input icon to be resized (default \"icon.png\"" + r resolutions RESOLUTIONS #{int} "resolutions to which images should be resized (default #{192 512})" + t site-title TITLE str "name for the installable web application" + l short-title SHORTTITLE str "short name for the installable web application (default :site-title)" + c theme-color COLOR str "background color theme for icon (default \"#ffffff\")" + b background-color BGCOLOR str "background color for app loading state (default \"#ffffff\")" + d display DISPLAY str "display mode for browser (default \"standalone\")" + s scope SCOPE str "the scope to which the manifest applies (default \"/\")"] + (let [{:keys [out-dir icon-path resolutions site-title short-title + theme-color background-color display scope]} (merge +manifest-defaults+ *opts*)] (comp (images-resize :out-dir out-dir :resolutions resolutions @@ -1340,6 +1348,8 @@ (mime-type :filterer :manifest-icon) (manifest* :out-dir out-dir :site-title site-title + :short-title short-title :theme-color theme-color + :background-color background-color :display display :scope scope)))) diff --git a/src/io/perun/manifest.clj b/src/io/perun/manifest.clj index 78082710..27e5f74b 100644 --- a/src/io/perun/manifest.clj +++ b/src/io/perun/manifest.clj @@ -2,13 +2,15 @@ (:require [cheshire.core :refer [generate-string]])) (defn manifest - [{:keys [icons site-title theme-color display scope input-paths] :as data}] + [{:keys [icons site-title short-title theme-color background-color display scope input-paths] :as data}] (let [manifest {:name site-title + :short_name short-title :icons (for [{:keys [permalink width height mime-type]} icons] {:src permalink :sizes (str width "x" height) :type mime-type}) :theme_color theme-color + :background_color background-color :display display :scope scope}] {:rendered (generate-string manifest) From 573357a9a3031b20d343e4f327c8f16fe09edc40 Mon Sep 17 00:00:00 2001 From: Brent Hagany Date: Sat, 30 Dec 2017 11:16:36 -0600 Subject: [PATCH 3/3] typo --- src/io/perun.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/perun.clj b/src/io/perun.clj index 62fc13e7..2d6e994a 100644 --- a/src/io/perun.clj +++ b/src/io/perun.clj @@ -1330,7 +1330,7 @@ (deftask manifest "Creates a manifest.json for installable web applications" [o out-dir OUTDIR str "the output directory" - i icon-path PATH str "The input icon to be resized (default \"icon.png\"" + i icon-path PATH str "The input icon to be resized (default \"icon.png\")" r resolutions RESOLUTIONS #{int} "resolutions to which images should be resized (default #{192 512})" t site-title TITLE str "name for the installable web application" l short-title SHORTTITLE str "short name for the installable web application (default :site-title)"