Browse Source

Merge branch 'feat/outliner-core' into feat/integration-plugins-core

charlie 4 years ago
parent
commit
6f59939484

+ 1 - 1
package.json

@@ -73,7 +73,7 @@
         "ignore": "^5.1.8",
         "is-svg": "4.2.2",
         "jszip": "^3.5.0",
-        "mldoc": "0.6.14",
+        "mldoc": "0.6.16",
         "path": "^0.12.7",
         "react": "^17.0.1",
         "react-dom": "^17.0.1",

+ 0 - 11
resources/forge.config.js

@@ -4,17 +4,6 @@ module.exports = {
   packagerConfig: {
     icon: './icons/canary/logseq_big_sur.icns',
     name: 'Logseq Canary',
-    osxSign: {
-      identity: 'Developer ID Application: Tiansheng Qin',
-      'hardened-runtime': true,
-      entitlements: 'entitlements.plist',
-      'entitlements-inherit': 'entitlements.plist',
-      'signature-flags': 'library'
-    },
-    osxNotarize: {
-      appleId: "my-fake-apple-id",
-      appleIdPassword: "my-fake-apple-id-password",
-    },
   },
   makers: [
     {

+ 9 - 3
src/main/frontend/commands.cljs

@@ -194,9 +194,15 @@
   ([type]
    (->block type nil))
   ([type optional]
-   (let [left (util/format "#+BEGIN_%s"
-                           (string/upper-case type))
-         right (util/format "\n#+END_%s" (string/upper-case type))
+   (let [format (get state/get-edit-block :block/format :markdown)
+         org? (= format :org)
+         left (if org?
+                (util/format "#+BEGIN_%s"
+                             (string/upper-case type))
+                (str "```" (let [t (string/lower-case type)] (if (= t "src") "" t))))
+         right (if org?
+                 (util/format "\n#+END_%s" (string/upper-case type))
+                 (str "\n```"))
          template (str
                    left
                    (if optional (str " " optional) "")

+ 181 - 158
src/main/frontend/components/block.cljs

@@ -1290,7 +1290,8 @@
         pre-block? (:block/pre-block? block)
         properties (if pre-block?
                      (dissoc properties :title :filters)
-                     properties)]
+                     properties)
+        properties (sort properties)]
     (cond
       (seq properties)
       [:div.blocks-properties.text-sm.opacity-80.my-1.p-2
@@ -1994,168 +1995,190 @@
      [:div.ml-4.text-lg
       (markup-elements-cp config result)]]))
 
+;; TODO: move to mldoc
+;; (defn- convert-md-src-to-custom-block
+;;   [item]
+;;   (let [{:keys [language options lines] :as options} (second item)
+;;         lang (string/lower-case (or language ""))]
+;;     (cond
+;;       (= lang "quote")
+;;       (let [content (string/trim (string/join "\n" lines))]
+;;         ["Quote" (first (mldoc/->edn content (mldoc/default-config :markdown)))])
+
+;;       (contains? #{"query" "note" "tip" "important" "caution" "warning" "pinned"} lang)
+;;       (let [content (string/trim (string/join "\n" lines))]
+;;         ["Custom" lang nil (first (mldoc/->edn content (mldoc/default-config :markdown))) content])
+
+;;       :else
+;;       ["Src" options])))
+
 (defn markup-element-cp
   [{:keys [html-export?] :as config} item]
-  (try
-    (match item
-      ["Properties" m]
-      [:div.properties
-       (let [format (:block/format config)]
-         (for [[k v] (dissoc m :roam_alias :roam_tags)]
-           (when (and (not (and (= k :macros) (empty? v))) ; empty macros
-                      (not (= k :title))
-                      (not (= k :filters)))
-             [:div.property
-              [:span.font-medium.mr-1 (str (name k) ": ")]
-              (if (coll? v)
-                (let [vals (for [item v]
-                             (if (coll? v)
-                               (let [config (if (= k :alias)
-                                              (assoc config :block/alias? true))]
-                                 (page-cp config {:block/name item}))
-                               (inline-text format item)))]
-                  (interpose [:span ", "] vals))
-                (inline-text format v))])))]
-
-      ["Paragraph" l]
-      ;; TODO: speedup
-      (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
-        (->elem :div (map-inline config l))
-        (->elem :div.is-paragraph (map-inline config l)))
-
-      ["Horizontal_Rule"]
-      (when-not (:slide? config)
-        [:hr])
-      ["Heading" h]
-      (block-container config h)
-      ["List" l]
-      (let [lists (divide-lists l)]
-        (if (= 1 (count lists))
-          (let [l (first lists)]
-            (->elem
-             (list-element l)
-             (map #(list-item config %) l)))
-          [:div.list-group
-           (for [l lists]
+  (let [format (or (:block/format config)
+                   :markdown)]
+    (try
+     (match item
+       ["Properties" m]
+       [:div.properties
+        (for [[k v] (dissoc m :roam_alias :roam_tags)]
+          (when (and (not (and (= k :macros) (empty? v))) ; empty macros
+                     (not (= k :title))
+                     (not (= k :filters)))
+            [:div.property
+             [:span.font-medium.mr-1 (str (name k) ": ")]
+             (if (coll? v)
+               (let [vals (for [item v]
+                            (if (coll? v)
+                              (let [config (if (= k :alias)
+                                             (assoc config :block/alias? true))]
+                                (page-cp config {:block/name item}))
+                              (inline-text format item)))]
+                 (interpose [:span ", "] vals))
+               (inline-text format v))]))]
+
+       ["Paragraph" l]
+       ;; TODO: speedup
+       (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
+         (->elem :div (map-inline config l))
+         (->elem :div.is-paragraph (map-inline config l)))
+
+       ["Horizontal_Rule"]
+       (when-not (:slide? config)
+         [:hr])
+       ["Heading" h]
+       (block-container config h)
+       ["List" l]
+       (let [lists (divide-lists l)]
+         (if (= 1 (count lists))
+           (let [l (first lists)]
              (->elem
               (list-element l)
-              (map #(list-item config %) l)))]))
-      ["Table" t]
-      (table config t)
-      ["Math" s]
-      (if html-export?
-        (latex/html-export s true true)
-        (latex/latex (str (dc/squuid)) s true true))
-      ["Example" l]
-      [:pre.pre-wrap-white-space
-       (join-lines l)]
-      ["Src" options]
-      (let [{:keys [language options lines pos_meta]} options
-            attr (if language
-                   {:data-lang language})
-            code (join-lines lines)]
-        (cond
-          html-export?
-          (highlight/html-export attr code)
-
-          :else
-          (let [language (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} language) "text/x-clojure" language)]
-            (if (:slide? config)
-              (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
-              [:div
-               (lazy-editor/editor config (str (dc/squuid)) attr code pos_meta)
-               (when (and (= language "text/x-clojure") (contains? (set options) ":results"))
-                 (sci/eval-result code))]))))
-      ["Quote" l]
-      (->elem
-       :blockquote
-       (markup-elements-cp config l))
-      ["Raw_Html" content]
-      (when (not html-export?)
-        [:div.raw_html {:dangerouslySetInnerHTML
-                        {:__html content}}])
-      ["Export" "html" options content]
-      (when (not html-export?)
-        [:div.export_html {:dangerouslySetInnerHTML
-                           {:__html content}}])
-      ["Hiccup" content]
-      (ui/catch-error
-       [:div.warning {:title "Invalid hiccup"}
-        content]
-       (-> (safe-read-string content)
-           (security/remove-javascript-links-in-href)))
-
-      ["Export" "latex" options content]
-      (if html-export?
-        (latex/html-export content true false)
-        (latex/latex (str (dc/squuid)) content true false))
-
-      ["Custom" "query" _options result content]
-      (try
-        (let [query (reader/read-string content)]
-          (custom-query config query))
-        (catch js/Error e
-          (println "read-string error:")
-          (js/console.error e)
-          [:div.warning {:title "Invalid query"}
-           content]))
-
-      ["Custom" "note" options result content]
-      (admonition config "note" options result)
-
-      ["Custom" "tip" options result content]
-      (admonition config "tip" options result)
-
-      ["Custom" "important" options result content]
-      (admonition config "important" options result)
-
-      ["Custom" "caution" options result content]
-      (admonition config "caution" options result)
-
-      ["Custom" "warning" options result content]
-      (admonition config "warning" options result)
-
-      ["Custom" "pinned" options result content]
-      (admonition config "pinned" options result)
-
-      ["Custom" name options l content]
-      (->elem
-       :div
-       {:class name}
-       (markup-elements-cp config l))
-
-      ["Latex_Fragment" l]
-      [:p.latex-fragment
-       (inline config ["Latex_Fragment" l])]
-
-      ["Latex_Environment" name option content]
-      (let [content (latex-environment-content name option content)]
-        (if html-export?
-          (latex/html-export content true true)
-          (latex/latex (str (dc/squuid)) content true true)))
-
-      ["Displayed_Math" content]
-      (if html-export?
-        (latex/html-export content true true)
-        (latex/latex (str (dc/squuid)) content true true))
-
-      ["Footnote_Definition" name definition]
-      (let [id (util/url-encode name)]
-        [:div.footdef
-         [:div.footpara
-          (conj
-           (markup-element-cp config ["Paragraph" definition])
-           [:a.ml-1 {:id (str "fn." id)
-                     :style {:font-size 14}
-                     :class "footnum"
-                     :on-click #(route-handler/jump-to-anchor! (str "fnr." id))}
-            [:sup.fn (str name "↩︎")]])]])
+              (map #(list-item config %) l)))
+           [:div.list-group
+            (for [l lists]
+              (->elem
+               (list-element l)
+               (map #(list-item config %) l)))]))
+       ["Table" t]
+       (table config t)
+       ["Math" s]
+       (if html-export?
+         (latex/html-export s true true)
+         (latex/latex (str (dc/squuid)) s true true))
+       ["Example" l]
+       [:pre.pre-wrap-white-space
+        (join-lines l)]
+       ["Quote" l]
+       (->elem
+        :blockquote
+        (markup-elements-cp config l))
+       ["Raw_Html" content]
+       (when (not html-export?)
+         [:div.raw_html {:dangerouslySetInnerHTML
+                         {:__html content}}])
+       ["Export" "html" options content]
+       (when (not html-export?)
+         [:div.export_html {:dangerouslySetInnerHTML
+                            {:__html content}}])
+       ["Hiccup" content]
+       (ui/catch-error
+        [:div.warning {:title "Invalid hiccup"}
+         content]
+        (-> (safe-read-string content)
+            (security/remove-javascript-links-in-href)))
+
+       ["Export" "latex" options content]
+       (if html-export?
+         (latex/html-export content true false)
+         (latex/latex (str (dc/squuid)) content true false))
+
+       ["Custom" "query" _options result content]
+       (try
+         (let [query (reader/read-string content)]
+           (custom-query config query))
+         (catch js/Error e
+           (println "read-string error:")
+           (js/console.error e)
+           [:div.warning {:title "Invalid query"}
+            content]))
+
+       ["Custom" "note" options result content]
+       (admonition config "note" options result)
+
+       ["Custom" "tip" options result content]
+       (admonition config "tip" options result)
+
+       ["Custom" "important" options result content]
+       (admonition config "important" options result)
+
+       ["Custom" "caution" options result content]
+       (admonition config "caution" options result)
+
+       ["Custom" "warning" options result content]
+       (admonition config "warning" options result)
+
+       ["Custom" "pinned" options result content]
+       (admonition config "pinned" options result)
+
+       ["Custom" name options l content]
+       (->elem
+        :div
+        {:class name}
+        (markup-elements-cp config l))
+
+       ["Latex_Fragment" l]
+       [:p.latex-fragment
+        (inline config ["Latex_Fragment" l])]
+
+       ["Latex_Environment" name option content]
+       (let [content (latex-environment-content name option content)]
+         (if html-export?
+           (latex/html-export content true true)
+           (latex/latex (str (dc/squuid)) content true true)))
+
+       ["Displayed_Math" content]
+       (if html-export?
+         (latex/html-export content true true)
+         (latex/latex (str (dc/squuid)) content true true))
+
+       ["Footnote_Definition" name definition]
+       (let [id (util/url-encode name)]
+         [:div.footdef
+          [:div.footpara
+           (conj
+            (markup-element-cp config ["Paragraph" definition])
+            [:a.ml-1 {:id (str "fn." id)
+                      :style {:font-size 14}
+                      :class "footnum"
+                      :on-click #(route-handler/jump-to-anchor! (str "fnr." id))}
+             [:sup.fn (str name "↩︎")]])]])
+
+       ["Src" options]
+       (when options
+         (let [{:keys [language options lines pos_meta]} options
+               attr (if language
+                      {:data-lang language})
+               code (join-lines lines)]
+           (cond
+             html-export?
+             (highlight/html-export attr code)
 
-      :else
-      "")
-    (catch js/Error e
-      (println "Convert to html failed, error: " e)
-      "")))
+             :else
+             (let [language (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} language) "text/x-clojure" language)]
+               (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
+               ;; (if (:slide? config)
+               ;;   (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
+               ;;   [:div
+               ;;    (lazy-editor/editor config (str (dc/squuid)) attr code pos_meta)
+               ;;    (when (and (= language "text/x-clojure") (contains? (set options) ":results"))
+               ;;      (sci/eval-result code))])
+               ))))
+
+       :else
+       "")
+     (catch js/Error e
+       (println "Convert to html failed, error: " e)
+       ""))))
 
 (defn markup-elements-cp
   [config col]

+ 78 - 71
src/main/frontend/components/diff.cljs

@@ -6,6 +6,7 @@
             [frontend.handler.file :as file]
             [frontend.handler.notification :as notification]
             [frontend.handler.common :as common-handler]
+            [frontend.handler.file :as file-handler]
             [frontend.state :as state]
             [clojure.string :as string]
             [frontend.db :as db]
@@ -49,9 +50,16 @@
                     :style {:background-color bg-color}}
         value]))])
 
-(rum/defc file < rum/reactive
-  [repo type path contents remote-oid]
-  (let [{:keys [collapse? resolved?]} (util/react (rum/cursor diff-state path))
+(rum/defcs file < rum/reactive
+  {:will-mount (fn [state]
+                 (let [*local-content (atom "")
+                       [repo _ path & _others] (:rum/args state)]
+                   (p/let [content (file-handler/load-file repo path )]
+                     (reset! *local-content content))
+                   (assoc state ::local-content *local-content)))}
+  [state repo type path contents remote-oid]
+  (let [local-content (rum/react (get state ::local-content))
+        {:keys [collapse? resolved?]} (util/react (rum/cursor diff-state path))
         edit? (util/react *edit?)
         delete? (= type "remove")]
     [:div.cp__diff-file
@@ -70,74 +78,73 @@
      (let [content (get contents path)]
        (if (or (and delete? (nil? content))
                content)
-         (let [local-content (db/get-file path)]
-           (if (not= content local-content)
-             (let [local-content (or local-content "")
-                   content (or content "")
-                   diff (medley/indexed (diff/diff local-content content))
-                   diff? (some (fn [[_idx {:keys [added removed]}]]
-                                 (or added removed))
-                               diff)]
-               [:div.pre-line-white-space.p-2 {:class (if collapse? "hidden")
-                                               :style {:overflow "auto"}}
-                (if edit?
-                  [:div.grid.grid-cols-2.gap-1
-                   (diff-cp diff)
-                   (ui/textarea
-                    {:default-value local-content
-                     :on-change (fn [e]
-                                  (reset! *edit-content (util/evalue e)))})]
-                  (diff-cp diff))
-
-                (cond
-                  edit?
-                  [:div.mt-2
-                   (ui/button "Save"
-                              :on-click
-                              (fn []
-                                (reset! *edit? false)
-                                (let [new-content @*edit-content]
-                                  (file/alter-file repo path new-content
-                                                   {:commit? false
-                                                    :re-render-root? true})
-                                  (swap! state/state
-                                         assoc-in [:github/contents repo remote-oid path] new-content)
-                                  (mark-as-resolved path))))]
-
-                  diff?
-                  [:div.mt-2
-                   (ui/button "Use remote"
-                              :on-click
-                              (fn []
-                                ;; overwrite the file
-                                (if delete?
-                                  (file/remove-file! repo path)
-                                  (file/alter-file repo path content
-                                                   {:commit? false
-                                                    :re-render-root? true}))
-                                (mark-as-resolved path))
-                              :background "green")
-
-                   [:span.pl-2.pr-2 "or"]
-
-                   (ui/button "Keep local"
-                              :on-click
-                              (fn []
-                                ;; overwrite the file
-                                (swap! state/state
-                                       assoc-in [:github/contents repo remote-oid path] local-content)
-                                (mark-as-resolved path))
-                              :background "pink")
-
-                   [:span.pl-2.pr-2 "or"]
-
-                   (ui/button "Edit"
-                              :on-click
-                              (fn []
-                                (reset! *edit? true)))]
-
-                  :else
-                  nil)])))
+         (if (not= content local-content)
+           (let [local-content (or local-content "")
+                 content (or content "")
+                 diff (medley/indexed (diff/diff local-content content))
+                 diff? (some (fn [[_idx {:keys [added removed]}]]
+                               (or added removed))
+                             diff)]
+             [:div.pre-line-white-space.p-2 {:class (if collapse? "hidden")
+                                             :style {:overflow "auto"}}
+              (if edit?
+                [:div.grid.grid-cols-2.gap-1
+                 (diff-cp diff)
+                 (ui/textarea
+                  {:default-value local-content
+                   :on-change (fn [e]
+                                (reset! *edit-content (util/evalue e)))})]
+                (diff-cp diff))
+
+              (cond
+                edit?
+                [:div.mt-2
+                 (ui/button "Save"
+                   :on-click
+                   (fn []
+                     (reset! *edit? false)
+                     (let [new-content @*edit-content]
+                       (file/alter-file repo path new-content
+                                        {:commit? false
+                                         :re-render-root? true})
+                       (swap! state/state
+                              assoc-in [:github/contents repo remote-oid path] new-content)
+                       (mark-as-resolved path))))]
+
+                diff?
+                [:div.mt-2
+                 (ui/button "Use remote"
+                   :on-click
+                   (fn []
+                     ;; overwrite the file
+                     (if delete?
+                       (file/remove-file! repo path)
+                       (file/alter-file repo path content
+                                        {:commit? false
+                                         :re-render-root? true}))
+                     (mark-as-resolved path))
+                   :background "green")
+
+                 [:span.pl-2.pr-2 "or"]
+
+                 (ui/button "Keep local"
+                   :on-click
+                   (fn []
+                     ;; overwrite the file
+                     (swap! state/state
+                            assoc-in [:github/contents repo remote-oid path] local-content)
+                     (mark-as-resolved path))
+                   :background "pink")
+
+                 [:span.pl-2.pr-2 "or"]
+
+                 (ui/button "Edit"
+                   :on-click
+                   (fn []
+                     (reset! *edit? true)))]
+
+                :else
+                nil)]))
          [:div "loading..."]))]))
 
 ;; TODO: `n` shortcut for next diff, `p` for previous diff

+ 2 - 7
src/main/frontend/components/page.cljs

@@ -89,13 +89,8 @@
         ;; raw-page-blocks (get grouped-blocks-by-file file-path raw-page-blocks)
         page-blocks (block-handler/with-dummy-block raw-page-blocks format
                       (if (empty? raw-page-blocks)
-                        (let [content (db/get-file repo file-path)]
-                          {:block/page {:db/id (:db/id page)}
-                           :block/file {:db/id (:db/id (:block/file page))}
-                           :block/meta
-                           (let [file-id (:db/id (:block/file page))]
-                             {:start-pos (utf8/length (utf8/encode content))
-                              :end-pos nil})}))
+                        {:block/page {:db/id (:db/id page)}
+                         :block/file {:db/id (:db/id (:block/file page))}})
                       {:journal? journal?
                        :page-name page-name})
         hiccup-config {:id (if block? (str block-id) page-name)

+ 6 - 5
src/main/frontend/db.cljs

@@ -68,11 +68,12 @@
 ;; persisting DBs between page reloads
 (defn persist! [repo]
   (let [key (datascript-db repo)
-        conn (get-conn repo false)
-        db (d/db conn)
-        db-str (if db (db->string db) "")]
-    (p/let [_ (idb/set-batch! [{:key key :value db-str}])]
-      (state/set-last-persist-transact-id! repo false (get-max-tx-id db)))))
+        conn (get-conn repo false)]
+    (when conn
+      (let [db (d/db conn)
+            db-str (if db (db->string db) "")]
+        (p/let [_ (idb/set-batch! [{:key key :value db-str}])]
+          (state/set-last-persist-transact-id! repo false (get-max-tx-id db)))))))
 
 (defonce persistent-jobs (atom {}))
 

+ 1 - 2
src/main/frontend/fs/watcher_handler.cljs

@@ -22,7 +22,7 @@
           (= "add" type)
           (when-not (db/file-exists? repo path)
               (let [_ (file-handler/alter-file repo path content {:re-render-root? true
-                                                               :from-disk? true})]
+                                                                  :from-disk? true})]
              (db/set-file-last-modified-at! repo path mtime)))
 
           (and (= "change" type)
@@ -33,7 +33,6 @@
                (not= content (db/get-file path))
                (when-let [last-modified-at (db/get-file-last-modified-at repo path)]
                  (> mtime last-modified-at)))
-
           (let [_ (file-handler/alter-file repo path content {:re-render-root? true
                                                               :from-disk? true})]
             (db/set-file-last-modified-at! repo path mtime))

+ 87 - 31
src/main/frontend/handler/editor.cljs

@@ -337,7 +337,7 @@
   ([block value]
    (save-block-if-changed! block value nil))
   ([block value
-    {:keys []
+    {:keys [force?]
      :as opts}]
    (let [{:block/keys [uuid content file page format repo content properties]} block
          repo (or repo (state/get-current-repo))
@@ -352,6 +352,9 @@
          (util/format "Block with the id % already exists!" block-id)]
         :error)
 
+       force?
+       (save-block-inner! repo block value opts)
+
        :else
        (let [content-changed? (not= (string/trim content) (string/trim value))]
          (when (and content-changed? page)
@@ -565,7 +568,7 @@
       (when content
         (str (string/trimr content)
              "\n"
-             (util/format "- %s -> DONE [%s]"
+             (util/format "* %s -> DONE [%s]"
                           marker
                           (date/get-local-date-time-string)))))
     content))
@@ -1113,7 +1116,7 @@
 (defn save-current-block!
   ([]
    (save-current-block! {}))
-  ([opts]
+  ([{:keys [force?] :as opts}]
    ;; non English input method
    (when-not (state/editor-in-composition?)
      (when-let [repo (state/get-current-repo)]
@@ -1135,11 +1138,16 @@
                  db-content-without-heading (and db-content
                                                  (util/safe-subs db-content (:block/level db-block)))
                  value (and elem (gobj/get elem "value"))]
-             (when (and block value db-content-without-heading
-                        (or
-                         (not= (string/trim db-content-without-heading)
-                               (string/trim value))))
-               (save-block-aux! db-block value (:block/format db-block) opts)))
+             (cond
+               force?
+               (save-block-aux! db-block value (:block/format db-block) opts)
+
+               (and block value db-content-without-heading
+                    (or
+                     (not= (string/trim db-content-without-heading)
+                           (string/trim value))))
+               (save-block-aux! db-block value (:block/format db-block) opts))
+             )
            (catch js/Error error
              (log/error :save-block-failed error))))))))
 
@@ -2048,39 +2056,51 @@
   (.setRangeText input "" start end)
   (state/set-edit-content! (state/get-edit-input-id) (.-value input)))
 
+(defn- delete-concat [current-block]
+  (let [input-id (state/get-edit-input-id)
+        ^js input (state/get-input)
+        current-pos (util/get-input-pos input)
+        value (gobj/get input "value")
+        repo (state/get-current-repo)
+        right (outliner-core/get-right-node (outliner-core/block current-block))
+        current-block-has-children? (db/has-children? repo (:block/uuid current-block))
+        collapsed? (:collapsed (:block/properties current-block))
+        first-child (outliner-db/get-first-child (db/get-conn repo false) (:db/id current-block))
+        next-block (if (or collapsed? (not current-block-has-children?))
+                     (:data right)
+                     first-child)]
+    (cond
+      (and collapsed? right (db/has-children? repo (tree/-get-id right)))
+      nil
+
+      (and (not collapsed?) first-child (db/has-children? repo (:block/uuid first-child)))
+      nil
+
+      :else
+      (do
+        (delete-block-aux! next-block false false)
+        (state/set-edit-content! input-id (str value "" (:block/content next-block)))
+        (util/move-cursor-to input current-pos)))))
+
 (defn keydown-delete-handler
   [e]
   (let [^js input (state/get-input)
-        input-id (state/get-edit-input-id)
         current-pos (util/get-input-pos input)
         value (gobj/get input "value")
         end? (= current-pos (count value))
         current-block (state/get-edit-block)
-        repo (state/get-current-repo)]
+        selected-start (gobj/get input "selectionStart")
+        selected-end (gobj/get input "selectionEnd")]
     (when current-block
-      (if (and end? current-block)
-        (let [right (outliner-core/get-right-node (outliner-core/block current-block))
-              current-block-has-children? (db/has-children? repo (:block/uuid current-block))
-              collapsed? (:collapsed (:block/properties current-block))
-              first-child (outliner-db/get-first-child (db/get-conn repo false) (:db/id current-block))
-              next-block (if (or collapsed? (not current-block-has-children?))
-                           (:data right)
-                           first-child)
-              first-child-has-children? (and first-child
-                                             (db/has-children? repo (:block/uuid first-child)))]
-          (cond
-            (and collapsed? right (db/has-children? repo (tree/-get-id right)))
-            nil
+      (cond
+        (not= selected-start selected-end)
+        (delete-and-update input selected-start selected-end)
 
-            (and (not collapsed?) first-child (db/has-children? repo (:block/uuid first-child)))
-            nil
+        (and end? current-block)
+        (delete-concat current-block)
 
-            :else
-            (do
-              (delete-block-aux! next-block false false)
-              (state/set-edit-content! input-id (str value "" (:block/content next-block)))
-              (util/move-cursor-to input current-pos))))
-       (delete-and-update input current-pos (inc current-pos))))))
+        :else
+        (delete-and-update input current-pos (inc current-pos))))))
 
 (defn keydown-backspace-handler
   [cut? e]
@@ -2533,3 +2553,39 @@
 
         :else
         nil))))
+
+(defn clear-block-content! []
+  (save-current-block! {:force? true})
+  (state/set-edit-content! (state/get-edit-input-id) ""))
+
+(defn kill-line-before! []
+  (save-current-block! {:force? true})
+  (util/kill-line-before! (state/get-input)))
+
+(defn kill-line-after! []
+  (save-current-block! {:force? true})
+  (util/kill-line-after! (state/get-input)))
+
+(defn beginning-of-block []
+  (util/move-cursor-to (state/get-input) 0))
+
+(defn end-of-block []
+  (util/move-cursor-to-end (state/get-input)))
+
+(defn cursor-forward-word []
+  (util/move-cursor-forward-by-word (state/get-input)))
+
+(defn cursor-backward-word []
+  (util/move-cursor-backward-by-word (state/get-input)))
+
+(defn backward-kill-word []
+  (let [input (state/get-input)]
+    (save-current-block! {:force? true})
+    (util/backward-kill-word input)
+    (state/set-edit-content! (state/get-edit-input-id) (.-value input))))
+
+(defn forward-kill-word []
+  (let [input (state/get-input)]
+    (save-current-block! {:force? true})
+    (util/forward-kill-word input)
+    (state/set-edit-content! (state/get-edit-input-id) (.-value input))))

+ 6 - 2
src/main/frontend/modules/file/core.cljs

@@ -25,7 +25,8 @@
   (let [content (or content "")
         heading-with-title? (seq title)
         first-block? (= left page)
-        pre-block? (and first-block? pre-block?)]
+        pre-block? (and first-block? pre-block?)
+        markdown-heading? (and (= format :markdown) (not unordered) (not heading-to-list?))]
     (cond
       (and first-block? pre-block?)
       (let [content (-> (string/trim content)
@@ -41,7 +42,7 @@
                 (repeat level "*")
                 (apply str)) ""]
 
-              (and (= format :markdown) (not unordered) (not heading-to-list?)) ; heading
+              markdown-heading?
               ["" ""]
 
               :else
@@ -60,6 +61,9 @@
                       content)
             new-content (indented-block-content (string/trim content) spaces-tabs)
             sep (cond
+                  markdown-heading?
+                  ""
+
                   heading-with-title?
                   " "
 

+ 17 - 11
src/main/frontend/modules/outliner/core.cljs

@@ -130,7 +130,6 @@
     (let [m (-> (:data this)
                 (dissoc :block/children :block/dummy? :block/meta)
                 (util/remove-nils))
-          m (update m :block/refs db-outliner/remove-non-existed-refs!)
           other-tx (:db/other-tx m)]
       (when (seq other-tx)
         (swap! txs-state (fn [txs]
@@ -492,16 +491,23 @@
   [root target-node sibling?]
   {:pre [(every? tree/satisfied-inode? [root target-node])
          (boolean? sibling?)]}
-  (ds/auto-transact!
-    [txs-state (ds/new-outliner-txs-state)] {:outliner-op :move-subtree}
-    (let [left-node-id (tree/-get-left-id root)
-         right-node (tree/-get-right root)]
-      (when (tree/satisfied-inode? right-node)
-        (let [new-right-node (tree/-set-left-id right-node left-node-id)]
-          (tree/-save new-right-node txs-state)))
-      (if sibling?
-        (insert-node-as-sibling txs-state root target-node)
-        (insert-node-as-first-child txs-state root target-node)))))
+  (let [target-node-id (tree/-get-id target-node)]
+    (when-not (or (and sibling?
+                       (= (tree/-get-left-id root) target-node-id)
+                       (not= (tree/-get-parent-id root) target-node-id))
+                  (and (not sibling?)
+                       (= (tree/-get-left-id root) target-node-id)
+                       (= (tree/-get-parent-id root) target-node-id)))
+      (ds/auto-transact!
+       [txs-state (ds/new-outliner-txs-state)] {:outliner-op :move-subtree}
+       (let [left-node-id (tree/-get-left-id root)
+             right-node (tree/-get-right root)]
+         (when (tree/satisfied-inode? right-node)
+           (let [new-right-node (tree/-set-left-id right-node left-node-id)]
+             (tree/-save new-right-node txs-state)))
+         (if sibling?
+           (insert-node-as-sibling txs-state root target-node)
+           (insert-node-as-first-child txs-state root target-node)))))))
 
 (defn get-right-node
   [node]

+ 20 - 0
src/main/frontend/modules/shortcut/binding.cljc

@@ -58,6 +58,26 @@
    :editor/delete "delete"
    :editor/delete-selection ["backspace" "delete"]
 
+   ;; clear the block content
+   :editor/clear-block (if mac? "ctrl+l" "alt+l")
+   ;; kill the line before the cursor position
+   :editor/kill-line-before (if mac? "ctrl+u" "alt+u")
+   ;; kill the line after the cursor position
+   :editor/kill-line-after (if mac? false "alt+k")
+   ;; go to the beginning of the block
+   :editor/beginning-of-block (if mac? false "alt+a")
+   ;; go to the end of the block
+   :editor/end-of-block (if mac? false "alt+e")
+   ;; forward one word
+   :editor/forward-word (if mac? "ctrl+shift+f" "alt+f")
+   ;; backward one word
+   :editor/backward-word (if mac? "ctrl+shift+b" "alt+b")
+   ;; kill one word backward
+   :editor/backward-kill-word (if mac? "ctrl+w" "alt+w")
+   ;; kill one word forward
+   :editor/forward-kill-word (if mac? false "alt+d")
+
+
    :editor/selection-up "up"
    :editor/selection-down "down"
 

+ 13 - 7
src/main/frontend/modules/shortcut/core.cljs

@@ -21,13 +21,19 @@
   [id]
   (let [shortcut (or (state/get-shortcut id)
                      (get (apply merge @binding-profile) id))]
-    (when-not shortcut
-      (log/error :shortcut/binding-not-found {:id id}))
-    (->>
-     (if (string? shortcut)
-       [shortcut]
-       shortcut)
-     (mapv mod-key))))
+    (cond
+      (nil? shortcut)
+      (log/error :shortcut/binding-not-found {:id id})
+
+      (false? shortcut)
+      (log/debug :shortcut/disabled {:id id})
+
+      :else
+      (->>
+       (if (string? shortcut)
+         [shortcut]
+         shortcut)
+       (mapv mod-key)))))
 
 (def global-keys #js
   [KeyCodes/TAB

+ 17 - 6
src/main/frontend/modules/shortcut/handler.cljs

@@ -34,7 +34,16 @@
     :editor/insert-link editor-handler/html-link-format!
     :editor/select-all-blocks editor-handler/select-all-blocks!
     :editor/move-block-up (editor-handler/move-up-down true)
-    :editor/move-block-down (editor-handler/move-up-down false)}))
+    :editor/move-block-down (editor-handler/move-up-down false)
+    :editor/clear-block editor-handler/clear-block-content!
+    :editor/kill-line-before editor-handler/kill-line-before!
+    :editor/kill-line-after editor-handler/kill-line-after!
+    :editor/beginning-of-block editor-handler/beginning-of-block
+    :editor/end-of-block editor-handler/end-of-block
+    :editor/forward-word editor-handler/cursor-forward-word
+    :editor/backward-word editor-handler/cursor-backward-word
+    :editor/backward-kill-word editor-handler/backward-kill-word
+    :editor/forward-kill-word editor-handler/forward-kill-word}))
 
 (def handler
   [;; global editor shortcut
@@ -53,12 +62,14 @@
     :editor/redo history/redo!}
 
    ;; global
-   {:ui/toggle-brackets config-handler/toggle-ui-show-brackets!
-    :go/search route-handler/go-to-search!
-    :go/journals route-handler/go-to-journals!
+   (before
+    m/prevent-default-behavior
+    {:ui/toggle-brackets config-handler/toggle-ui-show-brackets!
+     :go/search route-handler/go-to-search!
+     :go/journals route-handler/go-to-journals!
 
-    :search/re-index search-handler/rebuild-indices!
-    :graph/re-index #(repo-handler/re-index! nfs-handler/rebuild-index!)}
+     :search/re-index search-handler/rebuild-indices!
+     :graph/re-index #(repo-handler/re-index! nfs-handler/rebuild-index!)})
 
    ;; non-editing only
    (before

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

@@ -70,6 +70,7 @@
 
     :github/contents {}
     :config {}
+    :editor/code-mode? false
     :editor/show-page-search? false
     :editor/show-page-search-hashtag? false
     :editor/show-date-picker? false
@@ -1218,6 +1219,14 @@
   [args]
   (set-state! :editor/args args))
 
+(defn code-mode?
+  []
+  (:editor/code-mode? @state))
+
+(defn go-to-code-mode!
+  []
+  (set-state! :editor/code-mode? true))
+
 (defn get-editor-args
   []
   (:editor/args @state))

+ 1 - 1
src/main/frontend/text.cljs

@@ -141,7 +141,7 @@
 
 (def built-in-properties
   (set/union
-   #{:id :custom-id :background-color :heading :collapsed}
+   #{:id :custom-id :background-color :heading :collapsed :created-at :last-modified-at :created_at :last_modified_at}
    (set (map keyword config/markers))))
 
 (defn properties-built-in?

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

@@ -644,6 +644,24 @@
      (let [pos (count (gobj/get input "value"))]
        (move-cursor-to input pos))))
 
+#?(:cljs
+   (defn kill-line-before!
+     [input]
+     (let [val (.-value input)
+           end (.-selectionStart input)
+           n-pos (string/last-index-of val \newline (dec end))
+           start (if n-pos (inc n-pos) 0)]
+       (.setRangeText input "" start end))))
+
+#?(:cljs
+   (defn kill-line-after!
+     [input]
+     (let [val   (.-value input)
+           start (.-selectionStart input)
+           end   (or (string/index-of val \newline start)
+                     (count val))]
+       (.setRangeText input "" start end))))
+
 #?(:cljs
    (defn move-cursor-up
      "Move cursor up. If EOL, always move cursor to previous EOL."
@@ -1317,3 +1335,77 @@
    coll))
 
 (def pprint clojure.pprint/pprint)
+
+#?(:cljs
+   (defn move-cursor-forward-by-word
+     [input]
+     (let [val   (.-value input)
+           current (.-selectionStart input)
+           current (loop [idx current]
+                     (if (#{\space \newline} (nth-safe val idx))
+                       (recur (inc idx))
+                       idx))
+           idx (or (->> [(string/index-of val \space current)
+                         (string/index-of val \newline current)]
+                        (remove nil?)
+                        (apply min))
+                   (count val))]
+       (move-cursor-to input idx))))
+
+#?(:cljs
+   (defn move-cursor-backward-by-word
+     [input]
+     (let [val     (.-value input)
+           current (.-selectionStart input)
+           prev    (or
+                    (->> [(string/last-index-of val \space (dec current))
+                          (string/last-index-of val \newline (dec current))]
+                         (remove nil?)
+                         (apply max))
+                    0)
+           idx     (if (zero? prev)
+                     0
+                     (->
+                      (loop [idx prev]
+                        (if (#{\space \newline} (nth-safe val idx))
+                          (recur (dec idx))
+                          idx))
+                      inc))]
+       (move-cursor-to input idx))))
+
+#?(:cljs
+   (defn backward-kill-word
+     [input]
+     (let [val     (.-value input)
+           current (.-selectionStart input)
+           prev    (or
+                    (->> [(string/last-index-of val \space (dec current))
+                          (string/last-index-of val \newline (dec current))]
+                         (remove nil?)
+                         (apply max))
+                    0)
+           idx     (if (zero? prev)
+                     0
+                     (->
+                      (loop [idx prev]
+                        (if (#{\space \newline} (nth-safe val idx))
+                          (recur (dec idx))
+                          idx))
+                      inc))]
+       (.setRangeText input "" idx current))))
+
+#?(:cljs
+   (defn forward-kill-word
+     [input]
+     (let [val   (.-value input)
+           current (.-selectionStart input)
+           current (loop [idx current]
+                     (if (#{\space \newline} (nth-safe val idx))
+                       (recur (inc idx))
+                       idx))
+           idx (or (->> [(string/index-of val \space current)
+                         (string/index-of val \newline current)]
+                        (remove nil?)
+                        (apply min))
+                   (count val))]
+       (.setRangeText input "" current (inc idx)))))

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.0.1")
+(defonce version "0.0.2")

+ 4 - 4
yarn.lock

@@ -3853,10 +3853,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.5"
 
[email protected]4:
-  version "0.6.14"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.6.14.tgz#de6728555ef0dd2a48e172e781ccc7c3a50e6954"
-  integrity sha512-w/diYIaI/oMBKmrH6/UkC4MXchQo/G+u02SQ1VvyqcDBtjsBcXi6nY0l0LTFDHn3Yaq3FbiNLgSq37RBD3MY6g==
[email protected]6:
+  version "0.6.16"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.6.16.tgz#6cad0c9ce0015c92a630d7dc4c4fb2835f34eb98"
+  integrity sha512-9f9hCPaxIjd5bFugR1pgcH6m5Fwjnme9eGVty/uUch+k81wdey+fCzbp8szazP2J3Eneu5Z/181Tre501mpwwQ==
   dependencies:
     yargs "^12.0.2"