Browse Source

Merge branch 'master' into feat/outliner-core

Tienson Qin 5 years ago
parent
commit
8f05fb66b7

+ 9 - 0
.clj-kondo/config.edn

@@ -0,0 +1,9 @@
+{:linters {:unresolved-symbol {:exclude [goog.DEBUG]}}
+ :hooks {:analyze-call {rum.core/defc hooks.rum/defc
+                        rum.core/defcs hooks.rum/defcs}}
+ :lint-as {promesa.core/let clojure.core/let
+           garden.def/defstyles clojure.core/def
+           garden.def/defkeyframes clojure.core/def
+           rum.core/defcc rum.core/defc
+           clojure.test.check.clojure-test/defspec clojure.core/def
+           clojure.test.check.properties/for-all clojure.core/for}}

+ 68 - 0
.clj-kondo/hooks/rum.clj

@@ -0,0 +1,68 @@
+(ns hooks.rum
+  (:require [clj-kondo.hooks-api :as api]))
+
+(defn fn-body? [x]
+  (and (seq? x)
+       (vector? (first x))))
+
+(defn rewrite-body [mixins body defcs?]
+  (if defcs?
+    (let [[binding-vec & body] (:children body)
+          [state-arg & rest-args] (:children binding-vec)
+          ;; the original vector without the state argument
+          fn-args (assoc binding-vec :children rest-args)
+          body (api/list-node
+                (list* (api/token-node 'let*)
+                       (api/vector-node [state-arg (api/token-node nil)])
+                       state-arg
+                       (concat mixins body)))
+          body (api/list-node [fn-args body])]
+      body)
+    (let [[binding-vec & body] (:children body)]
+      (api/list-node (cons binding-vec (concat mixins body))))))
+
+(defn rewrite
+  ([node] (rewrite node false))
+  ([node defcs?]
+   (let [args (rest (:children node))
+         component-name (first args)
+         ?docstring (when (string? (api/sexpr (second args)))
+                      (second args))
+         args (if ?docstring
+                (nnext args)
+                (next args))
+         bodies
+         (loop [args* (seq args)
+                mixins []
+                bodies []]
+           (if args*
+             (let [a (first args*)
+                   a-sexpr (api/sexpr a)]
+               (cond (vector? a-sexpr) ;; a-sexpr is a binding vec and the rest is the body of the function
+                     [(rewrite-body mixins (api/list-node args*) defcs?)]
+                     (fn-body? a-sexpr)
+                     (recur (next args*)
+                            mixins
+                            (conj bodies (rewrite-body mixins a defcs?)))
+                     ;; assume mixin
+                     :else (recur (next args*)
+                                  (conj mixins a)
+                                  bodies)))
+             bodies))
+         new-node (with-meta
+                    (api/list-node
+                     (list* (api/token-node 'defn)
+                            component-name
+                            (if ?docstring
+                              (cons ?docstring bodies)
+                              bodies)))
+                    (meta node))]
+     new-node)))
+
+(defn defc [{:keys [:node]}]
+  (let [new-node (rewrite node)]
+    {:node new-node}))
+
+(defn defcs [{:keys [:node]}]
+  (let [new-node (rewrite node true)]
+    {:node new-node}))

+ 1 - 1
.gitignore

@@ -29,5 +29,5 @@ strings.csv
 
 .calva
 resources/electron.js
-.clj-kondo/
+.clj-kondo/.cache
 .lsp/

+ 3 - 3
deps.edn

@@ -7,12 +7,12 @@
   ;; persistent-sorted-set       {:mvn/version "0.1.2"}
   ;; FIXME: doesn't work on my archlinux laptop (tienson)
   ;; The required namespace "datascript.core" is not available, it was required by "frontend/db.cljs".
-  datascript/datascript       {:git/url "https://github.com/tiensonqin/datascript",
-                               :sha "efde8d389e6703b6f60ca3538f484a579b0d6de0"}
+  datascript/datascript       {:git/url "https://github.com/logseq/datascript",
+                               :sha "5c1983cdfbdaa4ba6f8410b54853ea3a78a0cd8c"}
   ;; datascript                  {:mvn/version "1.0.1"}
   datascript-transit/datascript-transit
   {:mvn/version "0.3.0"
-   :exclusions [datascript]}
+   :exclusions [datascript/datascript]}
   borkdude/rewrite-edn        {:git/url "https://github.com/borkdude/rewrite-edn"
                                :sha "edd87dc7f045f28d7afcbfc44bc0f0a2683dde62"}
   funcool/promesa             {:mvn/version "4.0.2"}

+ 4 - 1
gulpfile.js

@@ -14,7 +14,10 @@ const resourceFilePath = path.join(resourcesPath, '**')
 
 const css = {
   watchCSS () {
-    return exec(`yarn css:watch`, {})
+    return cp.spawn(`yarn css:watch`, {
+      shell: true,
+      stdio: 'inherit'
+    })
   },
 
   buildCSS (...params) {

+ 2 - 1
package.json

@@ -71,8 +71,9 @@
         "fuse.js": "^6.4.6",
         "gulp-cached": "^1.1.1",
         "ignore": "^5.1.8",
+        "is-svg": "4.2.2",
         "jszip": "^3.5.0",
-        "mldoc": "0.5.5",
+        "mldoc": "0.5.8",
         "mousetrap": "^1.6.5",
         "path": "^0.12.7",
         "react": "^17.0.1",

+ 2 - 1
resources/package.json

@@ -20,7 +20,8 @@
     "node-fetch": "^2.6.1",
     "open": "^7.3.1",
     "chokidar": "^3.5.1",
-    "fs-extra": "^9.1.0"
+    "fs-extra": "^9.1.0",
+    "electron-window-state": "^5.0.3"
   },
   "devDependencies": {
     "@electron-forge/cli": "^6.0.0-beta.54",

+ 5 - 2
src/electron/electron/core.cljs

@@ -7,6 +7,7 @@
             ["fs-extra" :as fs]
             ["path" :as path]
             ["electron" :refer [BrowserWindow app protocol ipcMain dialog] :as electron]
+            ["electron-window-state" :as windowStateKeeper]
             [clojure.core.async :as async]
             [electron.state :as state]))
 
@@ -22,8 +23,9 @@
 (defn create-main-window
   "Creates main app window"
   []
-  (let [win-opts {:width         980
-                  :height        700
+  (let [win-state (windowStateKeeper (clj->js {:defaultWidth 980 :defaultHeight 700}))
+        win-opts {:width         (.-width win-state)
+                  :height        (.-height win-state)
                   :frame         (not mac?)
                   :autoHideMenuBar (not mac?)
                   :titleBarStyle (if mac? "hidden" nil)
@@ -35,6 +37,7 @@
                    :preload                 (path/join js/__dirname "js/preload.js")}}
         url MAIN_WINDOW_ENTRY
         win (BrowserWindow. (clj->js win-opts))]
+    (.manage win-state win)
     (.loadURL win url)
     (when dev? (.. win -webContents (openDevTools)))
     win))

+ 5 - 0
src/main/frontend/commands.cljs

@@ -147,6 +147,11 @@
      ["Embed Vimeo Video" [[:editor/input "{{vimeo }}" {:last-pattern slash
                                                         :backward-pos 2}]]]
 
+     (when (state/markdown?)
+       ["Underline" [[:editor/input "<ins></ins>"
+                      {:last-pattern slash
+                       :backward-pos 6}]]])
+
      ["Html Inline " (->inline "html")]
 
      ;; TODO:

+ 47 - 31
src/main/frontend/components/block.cljs

@@ -326,7 +326,7 @@
 
 (declare page-reference)
 
-(defn page-cp
+(rum/defc page-cp
   [{:keys [html-export? label children contents-page?] :as config} page]
   (when-let [page-name (:block/name page)]
     (let [source-page (model/get-alias-source-page (state/get-current-repo)
@@ -572,22 +572,22 @@
     ["Subscript" l]
     (->elem :sub (map-inline config l))
     ["Tag" s]
-    (if (and s (util/tag-valid? s))
-      [:a.tag {:data-ref s
-               :href (rfe/href :page {:name s})
-               :on-click (fn [e]
-                           (let [repo (state/get-current-repo)
-                                 page (db/pull repo '[*] [:block/name (string/lower-case (util/url-decode s))])]
-                             (when (gobj/get e "shiftKey")
-                               (state/sidebar-add-block!
-                                repo
-                                (:db/id page)
-                                :page
-                                {:page page})
-                               (.preventDefault e))))}
-       (str "#" s)]
-      [:span.warning.mr-1 {:title "Invalid tag, tags only accept alphanumeric characters, \"-\", \"_\", \"@\" and \"%\"."}
-       (str "#" s)])
+    (when s
+      (let [s (text/page-ref-un-brackets! s)]
+        [:a.tag {:data-ref s
+                 :href (rfe/href :page {:name s})
+                 :on-click (fn [e]
+                             (let [repo (state/get-current-repo)
+                                   page (db/pull repo '[*] [:block/name (string/lower-case (util/url-decode s))])]
+                               (when (gobj/get e "shiftKey")
+                                 (state/sidebar-add-block!
+                                  repo
+                                  (:db/id page)
+                                  :page
+                                  {:page page})
+                                 (.preventDefault e))))}
+         (str "#" s)]))
+
     ["Emphasis" [[kind] data]]
     (let [elem (case kind
                  "Bold" :b
@@ -736,6 +736,12 @@
      (-> (safe-read-string s)
          (security/remove-javascript-links-in-href)))
 
+    ["Inline_Html" s]
+    (when (not html-export?)
+      ;; TODO: how to remove span and only export the content of `s`?
+      [:span {:dangerouslySetInnerHTML
+              {:__html s}}])
+
     ["Break_Line"]
     [:br]
     ["Hard_Break_Line"]
@@ -1257,26 +1263,36 @@
         [:div [:h1 (:page-name config)]]
         block-cp))))
 
+(rum/defc span-comma
+  []
+  [:span ", "])
+
+(rum/defc property-cp
+  [config block k v]
+  [:div.my-1
+   [:b k]
+   [:span.mr-1 ":"]
+   (if (coll? v)
+     (let [v (->> (remove string/blank? v)
+                  (filter string?))
+           vals (for [v-item v]
+                  (page-cp config {:block/name v-item}))
+           elems (interpose (span-comma) vals)]
+       (for [elem elems]
+         (rum/with-key elem (str (random-uuid)))))
+     (let [page-name (string/lower-case (str v))]
+       (if (db/entity [:block/name page-name])
+         (page-cp config {:block/name page-name})
+         (inline-text (:block/format block) (str v)))))])
+
 (rum/defc properties-cp
   [config block]
   (let [properties (apply dissoc (:block/properties block) text/hidden-properties)]
     (when (seq properties)
       [:div.blocks-properties.text-sm.opacity-80.my-1.p-2
        (for [[k v] properties]
-         ^{:key (str (:block/uuid block) "-" k)}
-         [:div.my-1
-          [:b k]
-          [:span.mr-1 ":"]
-          (if (coll? v)
-            (let [v (->> (remove string/blank? v)
-                         (filter string?))
-                  vals (for [v-item v]
-                         (page-cp config {:block/name v-item}))]
-              (interpose [:span ", "] vals))
-            (let [page-name (string/lower-case (str v))]
-              (if (db/entity [:block/name page-name])
-                (page-cp config {:block/name page-name})
-                (inline-text (:block/format block) (str v)))))])])))
+         (rum/with-key (property-cp config block k v)
+           (str (:block/uuid block) "-" k)))])))
 
 (rum/defcs timestamp-cp < rum/reactive
   (rum/local false ::show?)

+ 6 - 6
src/main/frontend/components/content.cljs

@@ -51,7 +51,7 @@
    [:div.py-1.rounded-md.bg-base-3.shadow-xs
     (ui/menu-link
      {:key "cut"
-      :on-click editor-handler/cut-selection-blocks}
+      :on-click #(editor-handler/cut-selection-blocks true)}
      "Cut")
     (ui/menu-link
      {:key "copy"
@@ -254,8 +254,8 @@
 
 
 (defn- cut-blocks-and-clear-selections!
-  [_]
-  (editor-handler/cut-selection-blocks)
+  [copy?]
+  (editor-handler/cut-selection-blocks copy?)
   (editor-handler/clear-selection! nil))
 
 (rum/defc hidden-selection < rum/reactive
@@ -264,11 +264,11 @@
                            (editor-handler/copy-selection-blocks)
                            (editor-handler/clear-selection! nil)))
   (mixins/keyboard-mixin (util/->system-modifier "ctrl+x")
-                         cut-blocks-and-clear-selections!)
+                         (fn [] (cut-blocks-and-clear-selections! true)))
   (mixins/keyboard-mixin "backspace"
-                         cut-blocks-and-clear-selections!)
+                         (fn [] (cut-blocks-and-clear-selections! false)))
   (mixins/keyboard-mixin "delete"
-                         cut-blocks-and-clear-selections!)
+                         (fn [] (cut-blocks-and-clear-selections! false)))
   []
   [:div#selection.hidden])
 

+ 14 - 0
src/main/frontend/components/export.cljs

@@ -28,3 +28,17 @@
        [:a#download-as-html.hidden]
        [:a#download-as-zip.hidden]
        [:a#export-as-markdown.hidden]])))
+
+
+(rum/defc export-page
+  []
+  (when-let [current-repo (state/get-current-repo)]
+    (when-let [page (state/get-current-page)]
+      (rum/with-context [[t] i18n/*tongue-context*]
+        [:div.export.w-96
+         [:h1.title "Export"]
+         [:ul.mr-1
+          [:li.mb-4
+           [:a.font-medium {:on-click #(export/export-page-as-markdown! page)}
+            (t :export-markdown)]]]
+         [:a#export-page-as-markdown.hidden]]))))

+ 5 - 0
src/main/frontend/components/page.cljs

@@ -16,6 +16,7 @@
             [frontend.components.editor :as editor]
             [frontend.components.reference :as reference]
             [frontend.components.svg :as svg]
+            [frontend.components.export :as export]
             [frontend.extensions.graph-2d :as graph-2d]
             [frontend.ui :as ui]
             [frontend.components.content :as content]
@@ -313,6 +314,10 @@
                               {:title (t :page/delete)
                                :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
 
+                            (when (state/get-current-page)
+                              {:title (t :export)
+                               :options {:on-click #(state/set-modal! export/export-page)}})
+
                             (when (util/electron?)
                               {:title  (t (if public? :page/make-private :page/make-public))
                                :options {:on-click

+ 3 - 1
src/main/frontend/db/model.cljs

@@ -271,6 +271,7 @@
       @conn)
      (into {}))))
 
+
 (defn get-files-full
   [repo]
   (when-let [conn (conn/get-files-conn repo)]
@@ -705,7 +706,8 @@
 
 (defn get-page-file
   [page-name]
-  (some-> (db-utils/entity [:block/name page-name])
+  (some-> (or (db-utils/entity [:block/name page-name])
+              (db-utils/entity [:block/original-name page-name]))
           :block/file))
 
 (defn get-block-file

+ 2 - 2
src/main/frontend/handler/editor.cljs

@@ -1201,8 +1201,8 @@
         (common-handler/copy-to-clipboard-without-id-property! content)))))
 
 (defn cut-selection-blocks
-  []
-  (copy-selection-blocks)
+  [copy?]
+  (when copy? (copy-selection-blocks))
   (when-let [blocks (seq (get-selected-blocks-with-children))]
     (let [repo (dom/attr (first blocks) "repo")
           ids (distinct (map #(uuid (dom/attr % "blockid")) blocks))]

+ 39 - 10
src/main/frontend/handler/export.cljs

@@ -178,11 +178,17 @@
         (db/pull-many repo '[*] block-ids)
         pages-name-and-content
         (->> page-ids
-             (d/q '[:find ?n (pull ?p [:file/path])
+             (d/q '[:find ?n ?n2 (pull ?p [:file/path])
                     :in $ [?e ...]
                     :where
                     [?e :block/file ?p]
-                    [?e :block/name ?n]] (db/get-conn repo))
+                    [?e :block/name ?n]
+                    [?e :block/original-name ?n2]] (db/get-conn repo))
+             (mapv (fn [[name origin-name file-path]]
+                    (if (= name origin-name)
+                      [[name file-path]]
+                      [[name file-path] [origin-name file-path]])))
+             (apply concat)
              (mapv (fn [[page-name file-path]] [page-name (:file/path file-path)]))
              (d/q '[:find ?n ?c
                     :in $ [[?n ?p] ...]
@@ -199,23 +205,46 @@
     {:embed_blocks embed-blocks
      :embed_pages pages-name-and-content}))
 
+(defn- export-files-as-markdown
+  [repo files heading-to-list?]
+  (->> files
+       (mapv (fn [{:keys [path content names format]}]
+               (when (first names)
+                 [path (fp/exportMarkdown f/mldoc-record content
+                                          (f/get-default-config format heading-to-list?)
+                                          (js/JSON.stringify
+                                           (clj->js (get-embed-and-refs-blocks-pages repo (first names)))))])))
+       (remove nil?)))
+
 (defn export-repo-as-markdown!
   [repo]
   (when-let [repo (state/get-current-repo)]
     (when-let [files (get-file-contents-with-suffix repo)]
       (let [heading-to-list? (state/export-heading-to-list?)
             files
-            (->> files
-                 (mapv (fn [{:keys [path content names format]}]
-                         (when (first names)
-                           [path (fp/exportMarkdown f/mldoc-record content
-                                                    (f/get-default-config format heading-to-list?)
-                                                    (js/JSON.stringify
-                                                     (clj->js (get-embed-and-refs-blocks-pages repo (first names)))))])))
-                 (remove nil?))
+            (export-files-as-markdown repo files heading-to-list?)
             zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
         (p/let [zipfile (zip/make-zip zip-file-name files)]
           (when-let [anchor (gdom/getElement "export-as-markdown")]
             (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
             (.setAttribute anchor "download" (.-name zipfile))
             (.click anchor)))))))
+
+(defn export-page-as-markdown!
+  [page-name]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [file (db/get-page-file page-name)]
+      (when-let [path (:file/path file)]
+        (when-let [content (db/get-file path)]
+          (let [names [page-name]
+                format (f/get-format path)
+                files [{:path path :content content :names names :format format}]]
+            (let [files
+                  (export-files-as-markdown repo files (state/export-heading-to-list?))]
+              (let [data (js/Blob. [(second (first files))]
+                                   (clj->js {:type "text/plain;charset=utf-8,"}))]
+                (let [anchor (gdom/getElement "export-page-as-markdown")
+                      url (js/window.URL.createObjectURL data)]
+                  (.setAttribute anchor "href" url)
+                  (.setAttribute anchor "download" path)
+                  (.click anchor))))))))))

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

@@ -250,6 +250,11 @@
 
      (get-in @state [:me :preferred_format] "markdown")))))
 
+(defn markdown?
+  []
+  (= (keyword (get-preferred-format))
+     :markdown))
+
 (defn get-pages-directory
   []
   (or

+ 12 - 5
yarn.lock

@@ -2794,7 +2794,7 @@ hsla-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
   integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
 
-html-comment-regex@^1.1.0:
+html-comment-regex@^1.1.0, html-comment-regex@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
   integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
@@ -3221,6 +3221,13 @@ is-stream@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
 
[email protected]:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.2.2.tgz#a4ea0f3f78dada7085db88f1e85b6f845626cfae"
+  integrity sha512-JlA7Mc7mfWjdxxTkJ094oUK9amGD7gQaj5xA/NCY0vlVvZ1stmj4VX+bRuwOMN93IHRZ2ctpPH/0FO6DqvQ5Rw==
+  dependencies:
+    html-comment-regex "^1.1.2"
+
 is-svg@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
@@ -3846,10 +3853,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.5"
 
[email protected].5:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.5.tgz#cb7eea471adc94e1c7858d4ae772ddabe0a75753"
-  integrity sha512-acseZvvwzLNlvp6/fZzqP5rqS80keWOK1XCreem5eXJNxLtJm+xIqzEJVcsmeyFS1Leya1gPT7dMcCUOhEr26g==
[email protected].6:
+  version "0.5.6"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.6.tgz#aa4351791e11b9c8a8df359a9d87619d7ff02e5d"
+  integrity sha512-iRTuTmLUdR8OKiRrrM4dl+51jxAmOJ92+B3rsYuKZERaYcU0B9jiJBT9S2nDZldARNEwjhj8DKITdnJZMxArGQ==
   dependencies:
     yargs "^12.0.2"