Browse Source

Wire up frontend to use nbb export and html

- Add tests for export
- Refactor export to be more readable
- Add error notification for export that user can see
- Remove unused :ui/show-recent? state
- Cleanup and move escape fns to proper locations
Gabriel Horner 2 years ago
parent
commit
9ac17d485a

+ 1 - 0
deps.edn

@@ -29,6 +29,7 @@
   org.clojars.mmb90/cljs-cache          {:mvn/version "0.1.4"}
   org.clojars.mmb90/cljs-cache          {:mvn/version "0.1.4"}
   logseq/common                         {:local/root "deps/common"}
   logseq/common                         {:local/root "deps/common"}
   logseq/graph-parser                   {:local/root "deps/graph-parser"}
   logseq/graph-parser                   {:local/root "deps/graph-parser"}
+  logseq/publish-spa                    {:local/root "deps/publish-spa"}
   metosin/malli                         {:mvn/version "0.10.0"}
   metosin/malli                         {:mvn/version "0.10.0"}
   fipp/fipp                             {:mvn/version "0.6.26"}}
   fipp/fipp                             {:mvn/version "0.6.26"}}
 
 

+ 13 - 0
deps/publish-spa/.clj-kondo/config.edn

@@ -0,0 +1,13 @@
+{:linters
+ {:aliased-namespace-symbol {:level :warning}
+  :namespace-name-mismatch {:level :warning}
+  :used-underscored-binding {:level :warning}
+
+  :consistent-alias
+  {:aliases {clojure.string string}}}
+
+ :lint-as
+ {logseq.publish-spa.test.helper/deftest-async clojure.test/deftest
+  promesa.core/let clojure.core/let}
+ :skip-comments true
+ :output {:progress true}}

+ 1 - 0
deps/publish-spa/.gitignore

@@ -1,2 +1,3 @@
 /.clj-kondo/.cache
 /.clj-kondo/.cache
 .nbb
 .nbb
+/tmp

+ 3 - 0
deps/publish-spa/deps.edn

@@ -0,0 +1,3 @@
+{:paths ["src"]
+ :deps
+ {logseq/db {:local/root "../db"}}}

+ 6 - 2
deps/publish-spa/nbb.edn

@@ -1,4 +1,8 @@
-{:deps
+{:paths ["src"]
+ :deps
  {logseq/graph-parser
  {logseq/graph-parser
   ;; Nbb bug. Should just be "../graph-parser"
   ;; Nbb bug. Should just be "../graph-parser"
-  {:local/root "../../../../graph-parser"}}}
+  {:local/root "../../../../graph-parser"}
+  io.github.nextjournal/nbb-test-runner
+  {:git/sha "60ed57aa04bca8d604f5ba6b28848bd887109347"
+   #_#_:local/root "../../../../../../nbb-test-runner"}}}

+ 3 - 2
deps/publish-spa/package.json

@@ -6,9 +6,10 @@
     "@logseq/nbb-logseq": "^1.2.168"
     "@logseq/nbb-logseq": "^1.2.168"
   },
   },
   "dependencies": {
   "dependencies": {
-    "mldoc": "^1.5.1"
+    "mldoc": "^1.5.1",
+    "fs-extra": "9.1.0"
   },
   },
   "scripts": {
   "scripts": {
-    "test": "nbb-logseq -cp src:test -m logseq.publish-spa.nbb-test-runner/run-tests"
+    "test": "nbb-logseq -cp src:test -m nextjournal.test-runner"
   }
   }
 }
 }

+ 7 - 13
deps/publish-spa/src/logseq/publish_spa.cljs

@@ -2,10 +2,9 @@
   (:require [datascript.transit :as dt]
   (:require [datascript.transit :as dt]
             [logseq.publish-spa.html :as html]
             [logseq.publish-spa.html :as html]
             [logseq.publish-spa.export :as export]
             [logseq.publish-spa.export :as export]
-            [logseq.publish-spa.db :as db]
-            ["path" :as path]))
+            [logseq.publish-spa.db :as db]))
 
 
-(defn prep-for-export [db {:keys [app-state repo-config]}]
+(defn prep-for-export [db {:keys [app-state repo-config html-options]}]
   (let [[db asset-filenames']
   (let [[db asset-filenames']
         (if (:publishing/all-pages-public? repo-config)
         (if (:publishing/all-pages-public? repo-config)
           (db/clean-export! db)
           (db/clean-export! db)
@@ -14,17 +13,12 @@
         db-str (dt/write-transit-str db)
         db-str (dt/write-transit-str db)
         state (assoc (select-keys app-state
         state (assoc (select-keys app-state
                             [:ui/theme
                             [:ui/theme
-                             :ui/sidebar-collapsed-blocks
-                             :ui/show-recent?])
+                             :ui/sidebar-collapsed-blocks])
                      :config {"local" repo-config})
                      :config {"local" repo-config})
-        raw-html-str (html/publishing-html db-str (pr-str state))]
+        raw-html-str (html/publishing-html db-str state html-options)]
     {:html raw-html-str
     {:html raw-html-str
      :asset-filenames asset-filenames}))
      :asset-filenames asset-filenames}))
 
 
-(defn publish [db graph-dir output-path options]
-  (let [{:keys [html asset-filenames]}
-        (prep-for-export db options)
-        custom-css-path (path/join graph-dir "logseq" "custom.css")
-        export-css-path (path/join graph-dir "logseq" "export.css")
-        app-path "../../static"]
-    (export/handle-export-publish-assets html app-path custom-css-path export-css-path graph-dir asset-filenames output-path)))
+(defn publish [db static-dir graph-dir output-path options]
+  (let [{:keys [html asset-filenames]} (prep-for-export db options)]
+    (export/export html static-dir graph-dir output-path {:asset-filenames asset-filenames})))

+ 3 - 12
deps/publish-spa/src/logseq/publish_spa/cli.cljs

@@ -5,15 +5,6 @@
             ["path" :as path]
             ["path" :as path]
             [clojure.edn :as edn]))
             [clojure.edn :as edn]))
 
 
-(defn- get-repo-config
-  [dir]
-  (if (fs/existsSync (path/join dir "logseq" "config.edn"))
-    (-> (path/join dir "logseq" "config.edn") fs/readFileSync str edn/read-string)
-    {}))
-
-(defn- get-app-state
-  []
-  {:ui/theme "dark", :ui/sidebar-collapsed-blocks {}, :ui/show-recent? false})
 
 
 (defn- get-db [graph-dir]
 (defn- get-db [graph-dir]
   (let [{:keys [conn]} (gp-cli/parse-graph graph-dir {:verbose false})] @conn))
   (let [{:keys [conn]} (gp-cli/parse-graph graph-dir {:verbose false})] @conn))
@@ -24,9 +15,9 @@
                        (throw (ex-info "GRAPH DIR required" {})))
                        (throw (ex-info "GRAPH DIR required" {})))
         output-path (or (second args)
         output-path (or (second args)
                         (throw (ex-info "OUT DIR required" {})))
                         (throw (ex-info "OUT DIR required" {})))
-        repo-config (get-repo-config graph-dir)]
+        repo-config (-> (path/join graph-dir "logseq" "config.edn") fs/readFileSync str edn/read-string)]
     (publish-spa/publish (get-db graph-dir)
     (publish-spa/publish (get-db graph-dir)
+                         "../../static"
                          graph-dir
                          graph-dir
                          output-path
                          output-path
-                         {:app-state (get-app-state)
-                          :repo-config repo-config})))
+                         {:repo-config repo-config})))

+ 79 - 47
deps/publish-spa/src/logseq/publish_spa/export.cljs

@@ -1,61 +1,93 @@
 (ns logseq.publish-spa.export
 (ns logseq.publish-spa.export
-  (:require ["fs-extra$default" :as fs]
-            ["path" :as path]
+  (:require ["fs-extra" :as fse]
+            ["path" :as node-path]
+            ["fs" :as fs]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
-(defn handle-export-publish-assets
-  [html app-path custom-css-path export-css-path repo-path asset-filenames output-path]
-  (let [root-dir output-path
-        static-dir (path/join root-dir "static")
-        assets-from-dir (path/join repo-path "assets")
-        assets-to-dir (path/join root-dir "assets")
-        index-html-path (path/join root-dir "index.html")]
-    (p/let [_ (. fs ensureDir static-dir)
-            _ (. fs ensureDir assets-to-dir)
-            _ (p/all (concat
-                      [(. fs writeFile index-html-path html)
+(def js-files
+  "js files from publishing release build"
+  ["main.js" "code-editor.js" "excalidraw.js" "tldraw.js"])
 
 
+(def static-dirs
+  "dirs under static dir to copy over"
+  ["css" "fonts" "icons" "img" "js"])
 
 
-                       (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
+(defn- default-notification
+  [msg]
+  (if (= (:type msg) "success")
+    (js/console.log (:payload msg))
+    (js/console.error (:payload msg))))
+
+(defn- cleanup-js-dir
+  "Moves used js files to the correct dir and removes unused js files"
+  [output-static-dir]
+  (let [publishing-dir (node-path/join output-static-dir "js" "publishing")]
+    (p/let [_ (p/all (map (fn [file]
+                            (fs/rmSync (node-path/join output-static-dir "js" file) #js {:force true}))
+                          js-files))
+            _ (p/all (map (fn [file]
+                            (fs/renameSync
+                             (node-path/join publishing-dir file)
+                             (node-path/join output-static-dir "js" file)))
+                          js-files))
+            ;; remove publishing-dir
+            _ (p/all (map (fn [file]
+                            (fs/rmSync (node-path/join publishing-dir file)))
+                          (fs/readdirSync publishing-dir)))
+            _ (fs/rmdirSync publishing-dir)
+            ;; remove source map files
+            _ (p/all (map (fn [file]
+                            (fs/rmSync (node-path/join output-static-dir "js" (str file ".map")) #js {:force true}))
+                          ["main.js" "code-editor.js" "excalidraw.js"]))])))
+
+(defn- copy-static-files-and-assets
+  [static-dir repo-path output-dir {:keys [log-error-fn asset-filenames]
+                                    :or {asset-filenames []
+                                         log-error-fn js/console.error}}]
+  (let [assets-from-dir (node-path/join repo-path "assets")
+        assets-to-dir (node-path/join output-dir "assets")
+        output-static-dir (node-path/join output-dir "static")]
+    (p/let [_ (fs/mkdirSync assets-to-dir #js {:recursive true})
+            _ (p/all (concat
+                      [(fse/copy (node-path/join static-dir "404.html") (node-path/join output-dir "404.html"))]
 
 
                       (map
                       (map
                        (fn [filename]
                        (fn [filename]
-                         (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
+                         (-> (fse/copy (node-path/join assets-from-dir filename) (node-path/join assets-to-dir filename))
                              (p/catch
                              (p/catch
                               (fn [e]
                               (fn [e]
-                                ;; TODO: Make into a callback
-                                (println "Failed to copy"
-                                         (str {:from (path/join assets-from-dir filename)
-                                               :to (path/join assets-to-dir filename)})
-                                         e)))))
+                                (log-error-fn "Failed to copy"
+                                              (str {:from (node-path/join assets-from-dir filename)
+                                                    :to (node-path/join assets-to-dir filename)})
+                                              e)))))
                        asset-filenames)
                        asset-filenames)
 
 
                       (map
                       (map
                        (fn [part]
                        (fn [part]
-                         (. fs copy (path/join app-path part) (path/join static-dir part)))
-                       ["css" "fonts" "icons" "img" "js"])))
-            export-css (if (fs/existsSync export-css-path) (. fs readFile export-css-path) "")
-            _ (. fs writeFile (path/join static-dir "css" "export.css")  export-css)
-            custom-css (if (fs/existsSync custom-css-path) (. fs readFile custom-css-path) "")
-            _ (. fs writeFile (path/join static-dir "css" "custom.css") custom-css)
-            js-files ["main.js" "code-editor.js" "excalidraw.js" "tldraw.js"]
-            _ (p/all (map (fn [file]
-                            (. fs removeSync (path/join static-dir "js" file)))
-                          js-files))
-            _ (p/all (map (fn [file]
-                            (. fs moveSync
-                              (path/join static-dir "js" "publishing" file)
-                              (path/join static-dir "js" file)))
-                          js-files))
-            _ (. fs removeSync (path/join static-dir "js" "publishing"))
-            ;; remove source map files
-            ;; TODO: ugly, replace with ls-files and filter with ".map"
-            _ (p/all (map (fn [file]
-                            (. fs removeSync (path/join static-dir "js" (str file ".map"))))
-                          ["main.js" "code-editor.js" "excalidraw.js"]))]
-
-           ;; TODO: Make into a callback
-           (println
-            :notification
-            {:type "success"
-             :payload (str "Export public pages and publish assets to " root-dir " successfully 🎉")}))))
+                         (fse/copy (node-path/join static-dir part) (node-path/join output-static-dir part)))
+                       static-dirs)))])))
+
+(defn export
+  "Given a graph's directory, the generated html and the directory containing
+  html/static assets, creates an index.html with supporting assets at the
+  specified output directory"
+  [html static-dir repo-path output-dir {:keys [notification-fn]
+                                         :or {notification-fn default-notification}
+                                         :as options}]
+  (let [custom-css-path (node-path/join repo-path "logseq" "custom.css")
+        export-css-path (node-path/join repo-path "logseq" "export.css")
+        output-static-dir (node-path/join output-dir "static")
+        index-html-path (node-path/join output-dir "index.html")]
+    (-> (p/let [_ (fs/mkdirSync output-static-dir #js {:recursive true})
+                _ (fs/writeFileSync index-html-path html)
+                _ (copy-static-files-and-assets static-dir repo-path output-dir options)
+                export-css (if (fs/existsSync export-css-path) (str (fs/readFileSync export-css-path)) "")
+                _ (fs/writeFileSync (node-path/join output-static-dir "css" "export.css")  export-css)
+                custom-css (if (fs/existsSync custom-css-path) (str (fs/readFileSync custom-css-path)) "")
+                _ (fs/writeFileSync (node-path/join output-static-dir "css" "custom.css") custom-css)
+                _ (cleanup-js-dir output-static-dir)]
+               (notification-fn {:type "success"
+                                 :payload (str "Export public pages and publish assets to " output-dir " successfully 🎉")}))
+        (p/catch (fn [error]
+                   (notification-fn {:type "error"
+                                     :payload (str "Export public pages unexpectedly failed with: " error)}))))))

+ 10 - 6
deps/publish-spa/src/logseq/publish_spa/html.cljs

@@ -3,6 +3,8 @@
             [goog.string :as gstring]
             [goog.string :as gstring]
             [goog.string.format]))
             [goog.string.format]))
 
 
+;; Copied from hiccup but tweaked for publish usage
+;; Any changes here should also be made in frontend.publishing/unescape-html
 (defn- escape-html
 (defn- escape-html
   "Change special characters into HTML character entities."
   "Change special characters into HTML character entities."
   [text]
   [text]
@@ -24,16 +26,18 @@
       (gstring/format "<%s%s>%s</%s>\n" tag-name (html attrs) (html elts) tag-name))
       (gstring/format "<%s%s>%s</%s>\n" tag-name (html attrs) (html elts) tag-name))
     (map? v)
     (map? v)
     (string/join ""
     (string/join ""
-              (map (fn [[k v]]
-                     (gstring/format " %s=\"%s\"" (name k) v)) v))
+                 (keep (fn [[k v]]
+                         ;; Skip nil values because some html tags haven't been
+                         ;; given values through html-options
+                         (when (some? v)
+                           (gstring/format " %s=\"%s\"" (name k) v))) v))
     (seq? v)
     (seq? v)
     (string/join " " (map html v))
     (string/join " " (map html v))
     :else (str v)))
     :else (str v)))
 
 
 (defn publishing-html
 (defn publishing-html
-  [transit-db app-state]
-  ;; TODO: Implement get-config
-  (let [{:keys [icon name alias title description url]} (:project {} #_(state/get-config))
+  [transit-db app-state options]
+  (let [{:keys [icon name alias title description url]} options
         icon (or icon "static/img/logo.png")
         icon (or icon "static/img/logo.png")
         project (or alias name)]
         project (or alias name)]
     (str "<!DOCTYPE html>\n"
     (str "<!DOCTYPE html>\n"
@@ -83,7 +87,7 @@
            [:body
            [:body
             [:div {:id "root"}]
             [:div {:id "root"}]
             [:script (gstring/format "window.logseq_db=%s" (js/JSON.stringify (escape-html transit-db)))]
             [:script (gstring/format "window.logseq_db=%s" (js/JSON.stringify (escape-html transit-db)))]
-            [:script (str "window.logseq_state=" (js/JSON.stringify app-state))]
+            [:script (str "window.logseq_state=" (js/JSON.stringify (pr-str app-state)))]
             [:script {:type "text/javascript"}
             [:script {:type "text/javascript"}
              "// Single Page Apps for GitHub Pages
              "// Single Page Apps for GitHub Pages
       // https://github.com/rafgraph/spa-github-pages
       // https://github.com/rafgraph/spa-github-pages

+ 118 - 0
deps/publish-spa/test/logseq/publish_spa/export_test.cljs

@@ -0,0 +1,118 @@
+(ns logseq.publish-spa.export-test
+  (:require [cljs.test :as t :refer [is use-fixtures async]]
+            [logseq.publish-spa.test.helper :as test-helper :include-macros true :refer [deftest-async]]
+            [logseq.publish-spa.export :as export]
+            [promesa.core :as p]
+            [clojure.set :as set]
+            ["fs" :as fs]
+            ["path" :as path]))
+
+(use-fixtures
+ :each
+ ;; Cleaning tmp/ before leaves last tmp/ after a test run for dev and debugging
+ {:before
+  #(async done
+          (if (fs/existsSync "tmp")
+            (fs/rm "tmp" #js {:recursive true} (fn [err]
+                                                 (when err (js/console.log err))
+                                                 (done)))
+            (done)))})
+
+(defn get-dirs [path]
+  (->> (fs/readdirSync path)
+       (map #(path/join path %))
+       (filter #(.isDirectory (fs/statSync %)))))
+
+(defn get-files [path]
+  (->> (fs/readdirSync path)
+       (map #(path/join path %))
+       (filter #(.isFile (fs/statSync %)))))
+
+(defn get-files-recursively [dir]
+  (let [dirs (get-dirs dir)]
+    (->> dirs
+         (map get-files-recursively)
+         (reduce concat)
+         (concat (get-files dir)))))
+
+(defn- export
+  [static-dir graph-dir output-dir {:keys [html assets]
+                                    :or {html "<!DOCTYPE html>"
+                                         assets []}}]
+  (export/export
+   html
+   static-dir
+   graph-dir
+   output-dir
+   {:asset-filenames assets
+    :notification-fn (fn [msg]
+                       (if (= "error" (:type msg))
+                         (throw (ex-info (:payload msg) {}))
+                         (js/console.log (:payload msg))))}))
+
+(defn- create-static-dir [dir]
+  (fs/mkdirSync (path/join dir) #js {:recursive true})
+  (mapv #(fs/mkdirSync (path/join dir %)) export/static-dirs)
+  (fs/mkdirSync (path/join dir "js" "publishing"))
+  (mapv #(fs/writeFileSync (path/join dir "js" "publishing" %) %)
+        (conj export/js-files "manifest.edn"))
+  (fs/writeFileSync (path/join dir "404.html") ""))
+
+(defn- create-logseq-graph
+  "Creates a minimal graph to test publishing"
+  [dir]
+  (fs/mkdirSync (path/join dir "logseq") #js {:recursive true})
+  (fs/writeFileSync (path/join dir "logseq" "config.edn") "{}")
+  (fs/mkdirSync (path/join dir "assets")))
+
+(deftest-async export-with-basic-graph
+  (create-static-dir "tmp/static")
+  (create-logseq-graph "tmp/test-graph")
+
+  (p/let [_ (export "tmp/static" "tmp/test-graph" "tmp/published-graph" {:html "<div>WOOT</div>"})]
+         (let [original-paths (map path/basename (get-files-recursively "tmp/static"))
+               copied-paths (map path/basename (get-files-recursively "tmp/published-graph"))
+               new-files (set/difference (set copied-paths) (set original-paths))]
+           (prn :ORIGINAL original-paths)
+           (prn :COPIED copied-paths)
+           (prn :NEW new-files)
+           (is (= #{"index.html" "custom.css" "export.css"}
+                  new-files)
+               "A published graph has the correct new files")
+           (is (= "<div>WOOT</div>"
+                  (str (fs/readFileSync "tmp/published-graph/index.html")))
+               "index.html is copied correctly")
+           (is (= "main.js"
+                  (str (fs/readFileSync "tmp/published-graph/static/js/main.js")))
+               "cljs frontend compiled as main.js is copied correctly"))))
+
+(deftest-async export-with-css-files
+  (create-static-dir "tmp/static")
+  (create-logseq-graph "tmp/test-graph")
+  (fs/writeFileSync "tmp/test-graph/logseq/custom.css" ".foo {background-color: blue}")
+  (fs/writeFileSync "tmp/test-graph/logseq/export.css" ".foo {background-color: red}")
+
+  (p/let [_ (export "tmp/static" "tmp/test-graph" "tmp/published-graph" {})]
+         (is (= ".foo {background-color: blue}"
+                (str (fs/readFileSync "tmp/published-graph/static/css/custom.css")))
+             "custom.css is copied correctly")
+         (is (= ".foo {background-color: red}"
+                (str (fs/readFileSync "tmp/published-graph/static/css/export.css")))
+             "export.css is copied correctly")))
+
+(deftest-async export-with-assets
+  (create-static-dir "tmp/static")
+  (create-logseq-graph "tmp/test-graph")
+  (fs/writeFileSync "tmp/test-graph/assets/foo.jpg" "foo")
+  (fs/writeFileSync "tmp/test-graph/assets/bar.png" "bar")
+
+  (p/let [_ (export "tmp/static"
+                                          "tmp/test-graph"
+                                          "tmp/published-graph"
+                                          {:assets ["foo.jpg" "bar.png"]})]
+         (is (= "foo"
+                (str (fs/readFileSync "tmp/published-graph/assets/foo.jpg")))
+             "first asset is copied correctly")
+         (is (= "bar"
+                (str (fs/readFileSync "tmp/published-graph/assets/bar.png")))
+             "second asset is copied correctly")))

+ 25 - 0
deps/publish-spa/test/logseq/publish_spa/test/helper.clj

@@ -0,0 +1,25 @@
+(ns logseq.publish-spa.test.helper)
+
+;; Copied from https://github.com/babashka/nbb/blob/e5d84b0fac59774f5d7a4a9e807240cce04bf252/test/nbb/test_macros.clj
+(defmacro deftest-async
+  "A wrapper around deftest that handles async and done in all cases.
+  Importantly, it prevents unexpected failures in an async test from abruptly
+  ending a test suite"
+  [name opts & body]
+  (let [[opts body]
+        (if (map? opts)
+          [opts body]
+          [nil (cons opts body)])]
+    `(cljs.test/deftest ~name
+       ~@(when-let [pre (:before opts)]
+           [pre])
+       (cljs.test/async
+        ~'done
+        (-> (do ~@body)
+            (.catch (fn [err#]
+                      (cljs.test/is (= 1 0) (str err# (.-stack err#)))))
+            (.finally
+             (fn []
+               ~@(when-let [post (:after opts)]
+                   [post])
+               (~'done))))))))

+ 34 - 0
deps/publish-spa/yarn.lock

@@ -19,6 +19,11 @@ ansi-regex@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
   integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
   integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
 
 
+at-least-node@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+  integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
 camelcase@^5.0.0:
 camelcase@^5.0.0:
   version "5.3.1"
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
@@ -81,6 +86,16 @@ find-up@^3.0.0:
   dependencies:
   dependencies:
     locate-path "^3.0.0"
     locate-path "^3.0.0"
 
 
[email protected]:
+  version "9.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+  integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+  dependencies:
+    at-least-node "^1.0.0"
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 get-caller-file@^1.0.1:
 get-caller-file@^1.0.1:
   version "1.0.3"
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@@ -93,6 +108,11 @@ get-stream@^4.0.0:
   dependencies:
   dependencies:
     pump "^3.0.0"
     pump "^3.0.0"
 
 
+graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+  version "4.2.11"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
 import-meta-resolve@^2.1.0:
 import-meta-resolve@^2.1.0:
   version "2.2.2"
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-2.2.2.tgz#75237301e72d1f0fbd74dbc6cca9324b164c2cc9"
   resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-2.2.2.tgz#75237301e72d1f0fbd74dbc6cca9324b164c2cc9"
@@ -125,6 +145,15 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
   integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
 
 
+jsonfile@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+  integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+  dependencies:
+    universalify "^2.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 lcid@^2.0.0:
 lcid@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
@@ -326,6 +355,11 @@ strip-eof@^1.0.0:
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
   integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
   integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
 
 
+universalify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+  integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
 which-module@^2.0.0:
 which-module@^2.0.0:
   version "2.0.0"
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"

+ 12 - 55
src/electron/electron/core.cljs

@@ -12,7 +12,6 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [electron.fs-watcher :as fs-watcher]
             [electron.fs-watcher :as fs-watcher]
-            ["fs-extra" :as fs]
             ["path" :as node-path]
             ["path" :as node-path]
             ["os" :as os]
             ["os" :as os]
             ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
             ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
@@ -22,7 +21,8 @@
             [electron.git :as git]
             [electron.git :as git]
             [electron.window :as win]
             [electron.window :as win]
             [electron.exceptions :as exceptions]
             [electron.exceptions :as exceptions]
-            ["/electron/utils" :as js-utils]))
+            ["/electron/utils" :as js-utils]
+            [logseq.publish-spa.export :as export]))
 
 
 ;; Keep same as main/frontend.util.url
 ;; Keep same as main/frontend.util.url
 (defonce LSP_SCHEME "logseq")
 (defonce LSP_SCHEME "logseq")
@@ -91,62 +91,19 @@
      (.unregisterProtocol protocol FILE_LSP_SCHEME)
      (.unregisterProtocol protocol FILE_LSP_SCHEME)
      (.unregisterProtocol protocol FILE_ASSETS_SCHEME)))
      (.unregisterProtocol protocol FILE_ASSETS_SCHEME)))
 
 
-(defn- handle-export-publish-assets [_event html custom-css-path export-css-path repo-path asset-filenames output-path]
+(defn- handle-export-publish-assets [_event html repo-path asset-filenames output-path]
   (p/let [app-path (. app getAppPath)
   (p/let [app-path (. app getAppPath)
           asset-filenames (->> (js->clj asset-filenames) (remove nil?))
           asset-filenames (->> (js->clj asset-filenames) (remove nil?))
           root-dir (or output-path (handler/open-dir-dialog))]
           root-dir (or output-path (handler/open-dir-dialog))]
-    (when root-dir
-      (let [static-dir (node-path/join root-dir "static")
-            assets-from-dir (node-path/join repo-path "assets")
-            assets-to-dir (node-path/join root-dir "assets")
-            index-html-path (node-path/join root-dir "index.html")]
-        (p/let [_ (. fs ensureDir static-dir)
-                _ (. fs ensureDir assets-to-dir)
-                _ (p/all (concat
-                          [(. fs writeFile index-html-path html)
-
-
-                           (. fs copy (node-path/join app-path "404.html") (node-path/join root-dir "404.html"))]
-
-                          (map
-                           (fn [filename]
-                             (-> (. fs copy (node-path/join assets-from-dir filename) (node-path/join assets-to-dir filename))
-                                 (p/catch
-                                  (fn [e]
-                                    (logger/error "Failed to copy"
-                                            (str {:from (node-path/join assets-from-dir filename)
-                                                  :to (node-path/join assets-to-dir filename)})
-                                            e)))))
-                           asset-filenames)
-
-                          (map
-                           (fn [part]
-                             (. fs copy (node-path/join app-path part) (node-path/join static-dir part)))
-                           ["css" "fonts" "icons" "img" "js"])))
-                export-css (if (fs/existsSync export-css-path) (. fs readFile export-css-path) "")
-                _ (. fs writeFile (node-path/join static-dir "css" "export.css")  export-css)
-                custom-css (if (fs/existsSync custom-css-path) (. fs readFile custom-css-path) "")
-                _ (. fs writeFile (node-path/join static-dir "css" "custom.css") custom-css)
-                js-files ["main.js" "code-editor.js" "excalidraw.js" "tldraw.js"]
-                _ (p/all (map (fn [file]
-                                (. fs removeSync (node-path/join static-dir "js" file)))
-                              js-files))
-                _ (p/all (map (fn [file]
-                                (. fs moveSync
-                                   (node-path/join static-dir "js" "publishing" file)
-                                   (node-path/join static-dir "js" file)))
-                              js-files))
-                _ (. fs removeSync (node-path/join static-dir "js" "publishing"))
-                ;; remove source map files
-                ;; TODO: ugly, replace with ls-files and filter with ".map"
-                _ (p/all (map (fn [file]
-                                (. fs removeSync (node-path/join static-dir "js" (str file ".map"))))
-                              ["main.js" "code-editor.js" "excalidraw.js"]))]
-
-          (send-to-renderer
-           :notification
-           {:type "success"
-            :payload (str "Export public pages and publish assets to " root-dir " successfully 🎉")}))))))
+         (when root-dir
+           (export/export
+            html
+            app-path
+            repo-path
+            root-dir
+            {:asset-filenames asset-filenames
+             :log-error-fn logger/error
+             :notification-fn #(send-to-renderer :notification %)}))))
 
 
 (defn setup-app-manager!
 (defn setup-app-manager!
   [^js win]
   [^js win]

+ 2 - 5
src/main/frontend/handler/export.cljs

@@ -15,7 +15,7 @@
    [frontend.mobile.util :as mobile-util]
    [frontend.mobile.util :as mobile-util]
    [frontend.modules.file.core :as outliner-file]
    [frontend.modules.file.core :as outliner-file]
    [frontend.modules.outliner.tree :as outliner-tree]
    [frontend.modules.outliner.tree :as outliner-tree]
-   [frontend.publishing.html :as html]
+   [logseq.publish-spa.html :as html]
    [frontend.state :as state]
    [frontend.state :as state]
    [frontend.util :as util]
    [frontend.util :as util]
    [frontend.util.property :as property]
    [frontend.util.property :as property]
@@ -89,15 +89,12 @@
                                      :config])
                                      :config])
           state        (update state :config (fn [config]
           state        (update state :config (fn [config]
                                                {"local" (get config repo)}))
                                                {"local" (get config repo)}))
-          _ (prn :STATE state)
-          raw-html-str (html/publishing-html db-str (pr-str state))
+          raw-html-str (html/publishing-html db-str state {})
           html-str     (str "data:text/html;charset=UTF-8,"
           html-str     (str "data:text/html;charset=UTF-8,"
                             (js/encodeURIComponent raw-html-str))]
                             (js/encodeURIComponent raw-html-str))]
       (if (util/electron?)
       (if (util/electron?)
         (js/window.apis.exportPublishAssets
         (js/window.apis.exportPublishAssets
          raw-html-str
          raw-html-str
-         (config/get-custom-css-path)
-         (config/get-export-css-path)
          (config/get-repo-dir repo)
          (config/get-repo-dir repo)
          (clj->js asset-filenames)
          (clj->js asset-filenames)
          (util/mocked-open-dir-path))
          (util/mocked-open-dir-path))

+ 13 - 3
src/main/frontend/publishing.cljs

@@ -1,5 +1,6 @@
 (ns frontend.publishing
 (ns frontend.publishing
-  "Entry ns for publishing build. Handles primary publishing app behaviors"
+  "Entry ns for publishing build. Provides frontend for publishing single page
+  application"
   (:require [frontend.state :as state]
   (:require [frontend.state :as state]
             [datascript.core :as d]
             [datascript.core :as d]
             [frontend.db :as db]
             [frontend.db :as db]
@@ -7,7 +8,7 @@
             [rum.core :as rum]
             [rum.core :as rum]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.page :as page]
             [frontend.page :as page]
-            [frontend.util :as util]
+            [clojure.string :as string]
             [frontend.routes :as routes]
             [frontend.routes :as routes]
             [frontend.context.i18n :as i18n]
             [frontend.context.i18n :as i18n]
             [reitit.frontend :as rf]
             [reitit.frontend :as rf]
@@ -37,11 +38,20 @@
 ;;    data should include all the public pages and blocks.
 ;;    data should include all the public pages and blocks.
 ;; 2. Built-in sync with GitHub Pages, you should specify a GitHub repo for publishing.
 ;; 2. Built-in sync with GitHub Pages, you should specify a GitHub repo for publishing.
 
 
+(defn- unescape-html
+  [text]
+  (-> text
+      (string/replace "logseq____&amp;" "&")
+      (string/replace "logseq____&lt;" "<")
+      (string/replace "logseq____&gt;" ">")
+      (string/replace "logseq____&quot;" "\"")
+      (string/replace "logseq____&apos;" "'")))
+
 (defn restore-from-transit-str!
 (defn restore-from-transit-str!
   []
   []
   (state/set-current-repo! "local")
   (state/set-current-repo! "local")
   (when-let [data js/window.logseq_db]
   (when-let [data js/window.logseq_db]
-    (let [data (util/unescape-html data)
+    (let [data (unescape-html data)
           db-conn (d/create-conn db-schema/schema)
           db-conn (d/create-conn db-schema/schema)
           _ (swap! db/conns assoc "logseq-db/local" db-conn)
           _ (swap! db/conns assoc "logseq-db/local" db-conn)
           db (db/string->db data)]
           db (db/string->db data)]

+ 0 - 93
src/main/frontend/publishing/html.cljs

@@ -1,93 +0,0 @@
-(ns ^:no-doc frontend.publishing.html
-  (:require-macros [hiccups.core])
-  (:require [frontend.state :as state]
-            [frontend.util :as util]
-            [hiccups.runtime]))
-
-(defn publishing-html
-  [transit-db app-state]
-  (prn :PROJECT (:project (state/get-config)))
-  (let [{:keys [icon name alias title description url]} (:project (state/get-config))
-        icon (or icon "static/img/logo.png")
-        project (or alias name)]
-    (str "<!DOCTYPE html>\n"
-         (hiccups.core/html
-          [:head
-           [:meta {:charset "utf-8"}]
-           [:meta
-            {:content
-             "minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no",
-             :name "viewport"}]
-           [:link {:type "text/css", :href "static/css/tabler-icons.min.css", :rel "stylesheet"}]
-           [:link {:type "text/css", :href "static/css/style.css", :rel "stylesheet"}]
-           [:link {:type "text/css", :href "static/css/custom.css", :rel "stylesheet"}]
-           [:link {:type "text/css", :href "static/css/export.css", :rel "stylesheet"}]
-           [:link
-            {:href icon
-             :type "image/png",
-             :rel "shortcut icon"}]
-           [:link
-            {:href icon
-             :sizes "192x192",
-             :rel "shortcut icon"}]
-           [:link
-            {:href icon
-             :rel "apple-touch-icon"}]
-
-           [:meta {:name "apple-mobile-web-app-title" :content project}]
-           [:meta {:name "apple-mobile-web-app-capable" :content "yes"}]
-           [:meta {:name "apple-touch-fullscreen" :content "yes"}]
-           [:meta {:name "apple-mobile-web-app-status-bar-style" :content "black-translucent"}]
-           [:meta {:name "mobile-web-app-capable" :content "yes"}]
-
-           [:meta {:content title, :property "og:title"}]
-           [:meta {:content "site", :property "og:type"}]
-           (when url [:meta {:content url, :property "og:url"}])
-           [:meta
-            {:content icon
-             :property "og:image"}]
-           [:meta
-            {:content description
-             :property "og:description"}]
-           [:title title]
-           [:meta {:content project, :property "og:site_name"}]
-           [:meta
-            {:description description}]]
-          [:body
-           [:div#root]
-           [:script (util/format "window.logseq_db=%s" (js/JSON.stringify (util/escape-html transit-db)))]
-           [:script (str "window.logseq_state=" (js/JSON.stringify app-state))]
-           [:script {:type "text/javascript"}
-            "// Single Page Apps for GitHub Pages
-      // https://github.com/rafgraph/spa-github-pages
-      // Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
-      // ----------------------------------------------------------------------
-      // This script checks to see if a redirect is present in the query string
-      // and converts it back into the correct url and adds it to the
-      // browser's history using window.history.replaceState(...),
-      // which won't cause the browser to attempt to load the new url.
-      // When the single page app is loaded further down in this file,
-      // the correct url will be waiting in the browser's history for
-      // the single page app to route accordingly.
-      (function(l) {
-        if (l.search) {
-          var q = {};
-          l.search.slice(1).split('&').forEach(function(v) {
-            var a = v.split('=');
-            q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
-          });
-          if (q.p !== undefined) {
-            window.history.replaceState(null, null,
-              l.pathname.slice(0, -1) + (q.p || '') +
-              (q.q ? ('?' + q.q) : '') +
-              l.hash
-            );
-          }
-        }
-      }(window.location))"]
-           ;; TODO: should make this configurable
-           [:script {:src "static/js/main.js"}]
-           [:script {:src "static/js/highlight.min.js"}]
-           [:script {:src "static/js/interact.min.js"}]
-           [:script {:src "static/js/katex.min.js"}]
-           [:script {:src "static/js/code-editor.js"}]]))))

+ 0 - 1
src/main/frontend/state.cljs

@@ -92,7 +92,6 @@
      :ui/sidebar-collapsed-blocks           {}
      :ui/sidebar-collapsed-blocks           {}
      :ui/root-component                     nil
      :ui/root-component                     nil
      :ui/file-component                     nil
      :ui/file-component                     nil
-     :ui/show-recent?                       false
      :ui/developer-mode?                    (or (= (storage/get "developer-mode") "true")
      :ui/developer-mode?                    (or (= (storage/get "developer-mode") "true")
                                                 false)
                                                 false)
      ;; remember scroll positions of visited paths
      ;; remember scroll positions of visited paths

+ 0 - 20
src/main/frontend/util.cljc

@@ -1063,26 +1063,6 @@
 
 
 ;; TODO: profile and profileEnd
 ;; TODO: profile and profileEnd
 
 
-;; Copy from hiccup but tweaked for publish usage
-(defn escape-html
-  "Change special characters into HTML character entities."
-  [text]
-  (-> text
-      (string/replace "&"  "logseq____&amp;")
-      (string/replace "<"  "logseq____&lt;")
-      (string/replace ">"  "logseq____&gt;")
-      (string/replace "\"" "logseq____&quot;")
-      (string/replace "'" "logseq____&apos;")))
-
-(defn unescape-html
-  [text]
-  (-> text
-      (string/replace "logseq____&amp;" "&")
-      (string/replace "logseq____&lt;" "<")
-      (string/replace "logseq____&gt;" ">")
-      (string/replace "logseq____&quot;" "\"")
-      (string/replace "logseq____&apos;" "'")))
-
 (comment
 (comment
   (= (get-relative-path "journals/2020_11_18.org" "pages/grant_ideas.org")
   (= (get-relative-path "journals/2020_11_18.org" "pages/grant_ideas.org")
      "../pages/grant_ideas.org")
      "../pages/grant_ideas.org")