mirror of
https://github.com/logseq/logseq.git
synced 2026-05-02 01:46:35 +00:00
Merge branch 'master' into gesture-support-on-block
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
(ns frontend.handler.editor
|
||||
(:require ["/frontend/utils" :as utils]
|
||||
["path" :as path]
|
||||
[cljs.core.match :refer [match]]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as w]
|
||||
[dommy.core :as dom]
|
||||
[frontend.commands :as commands
|
||||
:refer [*angle-bracket-caret-pos *show-block-commands
|
||||
*show-commands *slash-caret-pos]]
|
||||
:refer [*angle-bracket-caret-pos
|
||||
*show-block-commands *show-commands
|
||||
*slash-caret-pos]]
|
||||
[frontend.config :as config]
|
||||
[frontend.date :as date]
|
||||
[frontend.db :as db]
|
||||
@@ -25,35 +27,35 @@
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.repeated :as repeated]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[frontend.image :as image]
|
||||
[frontend.idb :as idb]
|
||||
[frontend.image :as image]
|
||||
[frontend.mobile.util :as mobile-util]
|
||||
[frontend.modules.outliner.core :as outliner-core]
|
||||
[frontend.modules.outliner.tree :as tree]
|
||||
[frontend.modules.outliner.transaction :as outliner-tx]
|
||||
[frontend.modules.outliner.tree :as tree]
|
||||
[frontend.search :as search]
|
||||
[frontend.state :as state]
|
||||
[frontend.template :as template]
|
||||
[frontend.text :as text]
|
||||
[frontend.utf8 :as utf8]
|
||||
[logseq.graph-parser.text :as text]
|
||||
[logseq.graph-parser.utf8 :as utf8]
|
||||
[frontend.util :as util :refer [profile]]
|
||||
[frontend.util.clock :as clock]
|
||||
[frontend.util.cursor :as cursor]
|
||||
[frontend.util.drawer :as drawer]
|
||||
[frontend.util.marker :as marker]
|
||||
[frontend.util.property :as property]
|
||||
[frontend.util.priority :as priority]
|
||||
[frontend.util.thingatpt :as thingatpt]
|
||||
[frontend.util.keycode :as keycode]
|
||||
[frontend.util.list :as list]
|
||||
[frontend.util.marker :as marker]
|
||||
[frontend.util.priority :as priority]
|
||||
[frontend.util.property :as property]
|
||||
[frontend.util.thingatpt :as thingatpt]
|
||||
[goog.dom :as gdom]
|
||||
[goog.dom.classes :as gdom-classes]
|
||||
[goog.object :as gobj]
|
||||
[lambdaisland.glogi :as log]
|
||||
[medley.core :as medley]
|
||||
[promesa.core :as p]
|
||||
[frontend.util.keycode :as keycode]
|
||||
[logseq.graph-parser.util :as gp-util]
|
||||
["path" :as path]))
|
||||
[logseq.graph-parser.mldoc :as gp-mldoc]
|
||||
[logseq.graph-parser.block :as gp-block]))
|
||||
|
||||
;; FIXME: should support multiple images concurrently uploading
|
||||
|
||||
@@ -254,10 +256,9 @@
|
||||
|
||||
(defn- another-block-with-same-id-exists?
|
||||
[current-id block-id]
|
||||
(and (string? block-id)
|
||||
(gp-util/uuid-string? block-id)
|
||||
(not= current-id (cljs.core/uuid block-id))
|
||||
(db/entity [:block/uuid (cljs.core/uuid block-id)])))
|
||||
(when-let [id (and (string? block-id) (parse-uuid block-id))]
|
||||
(and (not= current-id id)
|
||||
(db/entity [:block/uuid id]))))
|
||||
|
||||
(defn- attach-page-properties-if-exists!
|
||||
[block]
|
||||
@@ -336,7 +337,7 @@
|
||||
(if (and (state/enable-timetracking?)
|
||||
(not= (:block/content block) value))
|
||||
(let [format (:block/format block)
|
||||
new-marker (last (gp-util/safe-re-find (marker/marker-pattern format) (or value "")))
|
||||
new-marker (last (util/safe-re-find (marker/marker-pattern format) (or value "")))
|
||||
new-value (with-marker-time value block format
|
||||
new-marker
|
||||
(:block/marker block))]
|
||||
@@ -356,7 +357,7 @@
|
||||
content (drawer/with-logbook block content)
|
||||
content (with-timetracking block content)
|
||||
first-block? (= left page)
|
||||
ast (mldoc/->edn (string/trim content) (mldoc/default-config format))
|
||||
ast (mldoc/->edn (string/trim content) (gp-mldoc/default-config format))
|
||||
first-elem-type (first (ffirst ast))
|
||||
first-elem-meta (second (ffirst ast))
|
||||
properties? (contains? #{"Property_Drawer" "Properties"} first-elem-type)
|
||||
@@ -471,7 +472,7 @@
|
||||
(not has-children?))]
|
||||
(outliner-tx/transact!
|
||||
{:outliner-op :insert-blocks}
|
||||
(save-current-block!)
|
||||
(save-current-block! {:current-block current-block})
|
||||
(outliner-core/insert-blocks! [new-block] current-block {:sibling? sibling?
|
||||
:keep-uuid? keep-uuid?
|
||||
:replace-empty-target? replace-empty-target?}))))
|
||||
@@ -479,14 +480,9 @@
|
||||
(defn- block-self-alone-when-insert?
|
||||
[config uuid]
|
||||
(let [current-page (state/get-current-page)
|
||||
block-id (or
|
||||
(and (:id config)
|
||||
(gp-util/uuid-string? (:id config))
|
||||
(:id config))
|
||||
(and current-page
|
||||
(gp-util/uuid-string? current-page)
|
||||
current-page))]
|
||||
(= uuid (and block-id (medley/uuid block-id)))))
|
||||
block-id (or (some-> (:id config) parse-uuid)
|
||||
(some-> current-page parse-uuid))]
|
||||
(= uuid block-id)))
|
||||
|
||||
(defn insert-new-block-before-block-aux!
|
||||
[config block _value {:keys [ok-handler]}]
|
||||
@@ -668,7 +664,9 @@
|
||||
(defn properties-block
|
||||
[properties format page]
|
||||
(let [content (property/insert-properties format "" properties)
|
||||
refs (block/get-page-refs-from-properties properties)]
|
||||
refs (gp-block/get-page-refs-from-properties properties
|
||||
(db/get-db (state/get-current-repo))
|
||||
(state/get-date-formatter))]
|
||||
{:block/pre-block? true
|
||||
:block/uuid (db/new-block-id)
|
||||
:block/properties properties
|
||||
@@ -1141,7 +1139,7 @@
|
||||
[]
|
||||
(when-let [page (get-nearest-page)]
|
||||
(let [page-name (string/lower-case page)
|
||||
block? (gp-util/uuid-string? page-name)]
|
||||
block? (util/uuid-string? page-name)]
|
||||
(when-let [page (db/get-page page-name)]
|
||||
(if block?
|
||||
(state/sidebar-add-block!
|
||||
@@ -1169,10 +1167,7 @@
|
||||
[]
|
||||
(if (state/editing?)
|
||||
(let [page (state/get-current-page)
|
||||
block-id (and
|
||||
(string? page)
|
||||
(gp-util/uuid-string? page)
|
||||
(medley/uuid page))]
|
||||
block-id (and (string? page) (parse-uuid page))]
|
||||
(when block-id
|
||||
(let [block-parent (db/get-block-parent block-id)]
|
||||
(if-let [id (and
|
||||
@@ -1272,7 +1267,7 @@
|
||||
"skip-properties? if set true, when editing block is likely be properties, skip saving"
|
||||
([]
|
||||
(save-current-block! {}))
|
||||
([{:keys [force? skip-properties?] :as opts}]
|
||||
([{:keys [force? skip-properties? current-block] :as opts}]
|
||||
;; non English input method
|
||||
(when-not (state/editor-in-composition?)
|
||||
(when (state/get-current-repo)
|
||||
@@ -1293,7 +1288,8 @@
|
||||
db-content (:block/content db-block)
|
||||
db-content-without-heading (and db-content
|
||||
(gp-util/safe-subs db-content (:block/level db-block)))
|
||||
value (and elem (gobj/get elem "value"))]
|
||||
value (or (:block/content current-block)
|
||||
(and elem (gobj/get elem "value")))]
|
||||
(cond
|
||||
force?
|
||||
(save-block-aux! db-block value opts)
|
||||
@@ -1312,7 +1308,7 @@
|
||||
|
||||
(defn- clean-content!
|
||||
[format content]
|
||||
(->> (text/remove-level-spaces content format)
|
||||
(->> (text/remove-level-spaces content format (config/get-block-pattern format))
|
||||
(drawer/remove-logbook)
|
||||
(property/remove-properties format)
|
||||
string/trim))
|
||||
@@ -1348,7 +1344,7 @@
|
||||
|
||||
(defn get-asset-file-link
|
||||
[format url file-name image?]
|
||||
(let [pdf? (and url (string/ends-with? url ".pdf"))]
|
||||
(let [pdf? (and url (string/ends-with? (string/lower-case url) ".pdf"))]
|
||||
(case (keyword format)
|
||||
:markdown (util/format (str (when (or image? pdf?) "!") "[%s](%s)") file-name url)
|
||||
:org (if image?
|
||||
@@ -1414,7 +1410,7 @@
|
||||
(util/electron?)
|
||||
(str "assets://" repo-dir path)
|
||||
|
||||
(mobile-util/is-native-platform?)
|
||||
(mobile-util/native-platform?)
|
||||
(mobile-util/convert-file-src (str repo-dir path))
|
||||
|
||||
:else
|
||||
@@ -1680,7 +1676,8 @@
|
||||
(move-nodes blocks))
|
||||
(when-let [input-id (state/get-edit-input-id)]
|
||||
(when-let [input (gdom/getElement input-id)]
|
||||
(.focus input))))
|
||||
(.focus input)
|
||||
(js/setTimeout #(util/scroll-editor-cursor input) 100))))
|
||||
(let [ids (state/get-selection-block-ids)]
|
||||
(when (seq ids)
|
||||
(let [lookup-refs (map (fn [id] [:block/uuid id]) ids)
|
||||
@@ -1701,7 +1698,8 @@
|
||||
(let [blocks (get-selected-ordered-blocks)]
|
||||
(when (seq blocks)
|
||||
(outliner-tx/transact!
|
||||
{:outliner-op :move-blocks}
|
||||
{:outliner-op :move-blocks
|
||||
:real-outliner-op :indent-outdent}
|
||||
(outliner-core/indent-outdent-blocks! blocks (= direction :right))))))
|
||||
|
||||
(defn- get-link [format link label]
|
||||
@@ -1940,7 +1938,7 @@
|
||||
props (into [] (:properties block))
|
||||
content* (str (if (= :markdown format) "- " "* ")
|
||||
(property/insert-properties format content props))
|
||||
ast (mldoc/->edn content* (mldoc/default-config format))
|
||||
ast (mldoc/->edn content* (gp-mldoc/default-config format))
|
||||
blocks (block/extract-blocks ast content* true format)
|
||||
fst-block (first blocks)]
|
||||
(assert fst-block "fst-block shouldn't be nil")
|
||||
@@ -1953,7 +1951,7 @@
|
||||
(let [blocks (block-tree->blocks tree-vec format)
|
||||
target-block (db/pull target-block-id)
|
||||
page-id (:db/id (:block/page target-block))
|
||||
blocks (block/with-parent-and-left page-id blocks)]
|
||||
blocks (gp-block/with-parent-and-left page-id blocks)]
|
||||
(paste-blocks
|
||||
blocks
|
||||
{:target-block target-block
|
||||
@@ -2026,15 +2024,16 @@
|
||||
(when-not (parent-is-page? node)
|
||||
(let [parent-node (tree/-get-parent node)]
|
||||
(outliner-tx/transact!
|
||||
{:outliner-op :move-blocks}
|
||||
{:outliner-op :move-blocks
|
||||
:real-outliner-op :indent-outdent}
|
||||
(save-current-block!)
|
||||
(outliner-core/move-blocks! [(:data node)] (:data parent-node) true)))))
|
||||
|
||||
(defn- last-top-level-child?
|
||||
[{:keys [id]} current-node]
|
||||
(when id
|
||||
(when-let [entity (if (gp-util/uuid-string? (str id))
|
||||
(db/entity [:block/uuid (uuid id)])
|
||||
(when-let [entity (if-let [id' (parse-uuid (str id))]
|
||||
(db/entity [:block/uuid id'])
|
||||
(db/entity [:block/name (util/page-name-sanity-lc id)]))]
|
||||
(= (:block/uuid entity) (tree/-get-parent-id current-node)))))
|
||||
|
||||
@@ -2583,7 +2582,8 @@
|
||||
(when block
|
||||
(state/set-editor-last-pos! pos)
|
||||
(outliner-tx/transact!
|
||||
{:outliner-op :move-blocks}
|
||||
{:outliner-op :move-blocks
|
||||
:real-outliner-op :indent-outdent}
|
||||
(save-current-block!)
|
||||
(outliner-core/indent-outdent-blocks! [block] indent?)))
|
||||
(state/set-editor-op! :nil)))
|
||||
@@ -2630,7 +2630,7 @@
|
||||
;; FIXME: On mobile, a backspace click to call keydown-backspace-handler
|
||||
;; does not work sometimes in an empty block, hence the empty block
|
||||
;; can't be deleted. Need to figure out why and find a better solution.
|
||||
(and (mobile-util/is-native-platform?)
|
||||
(and (mobile-util/native-platform?)
|
||||
(= key "Backspace")
|
||||
(= value ""))
|
||||
(do
|
||||
@@ -2813,24 +2813,9 @@
|
||||
[id]
|
||||
(fn [_e]
|
||||
(let [input (gdom/getElement id)]
|
||||
(util/scroll-editor-cursor input)
|
||||
(close-autocomplete-if-outside input))))
|
||||
|
||||
(defonce mobile-toolbar-height 40)
|
||||
(defn editor-on-height-change!
|
||||
[id]
|
||||
(fn [box-height ^js row-height]
|
||||
(let [row-height (:rowHeight (js->clj row-height :keywordize-keys true))
|
||||
input (gdom/getElement id)
|
||||
caret (cursor/get-caret-pos input)
|
||||
cursor-bottom (if caret (+ row-height (:top caret)) box-height)
|
||||
box-top (gobj/get (.getBoundingClientRect input) "top")
|
||||
cursor-y (+ cursor-bottom box-top)
|
||||
vw-height (.-height js/window.visualViewport)]
|
||||
(when (< vw-height (+ cursor-y mobile-toolbar-height))
|
||||
(let [main-node (gdom/getElement "main-content-container")
|
||||
scroll-top (.-scrollTop main-node)]
|
||||
(set! (.-scrollTop main-node) (+ scroll-top row-height)))))))
|
||||
|
||||
(defn editor-on-change!
|
||||
[block id search-timeout]
|
||||
(fn [e]
|
||||
@@ -2842,15 +2827,17 @@
|
||||
(js/setTimeout
|
||||
#(edit-box-on-change! e block id)
|
||||
timeout)))
|
||||
(edit-box-on-change! e block id))))
|
||||
(let [input (gdom/getElement id)]
|
||||
(edit-box-on-change! e block id)
|
||||
(util/scroll-editor-cursor input)))))
|
||||
|
||||
(defn- paste-text-parseable
|
||||
[format text]
|
||||
(when-let [editing-block (state/get-edit-block)]
|
||||
(let [page-id (:db/id (:block/page editing-block))
|
||||
blocks (block/extract-blocks
|
||||
(mldoc/->edn text (mldoc/default-config format)) text true format)
|
||||
blocks' (block/with-parent-and-left page-id blocks)]
|
||||
(mldoc/->edn text (gp-mldoc/default-config format)) text true format)
|
||||
blocks' (gp-block/with-parent-and-left page-id blocks)]
|
||||
(paste-blocks blocks' {}))))
|
||||
|
||||
(defn- paste-segmented-text
|
||||
@@ -2860,7 +2847,7 @@
|
||||
(string/join "\n"
|
||||
(mapv (fn [p] (->> (string/trim p)
|
||||
((fn [p]
|
||||
(if (gp-util/safe-re-find (if (= format :org)
|
||||
(if (util/safe-re-find (if (= format :org)
|
||||
#"\s*\*+\s+"
|
||||
#"\s*-\s+") p)
|
||||
p
|
||||
@@ -2878,6 +2865,18 @@
|
||||
(recur (remove (set (map :block/uuid result)) (rest ids)) result))
|
||||
result)))
|
||||
|
||||
(defn wrap-macro-url
|
||||
[url]
|
||||
(cond
|
||||
(boolean (text/get-matched-video url))
|
||||
(util/format "{{video %s}}" url)
|
||||
|
||||
(string/includes? url "twitter.com")
|
||||
(util/format "{{twitter %s}}" url)
|
||||
|
||||
:else
|
||||
(notification/show! (util/format "No macro is available for %s" url) :warning)))
|
||||
|
||||
(defn- paste-copied-blocks-or-text
|
||||
[text e]
|
||||
(let [copied-blocks (state/get-copied-blocks)
|
||||
@@ -2902,21 +2901,10 @@
|
||||
(state/set-copied-full-blocks! blocks)
|
||||
(paste-blocks blocks {})))
|
||||
|
||||
(and (util/url? text)
|
||||
(and (gp-util/url? text)
|
||||
(not (string/blank? (util/get-selected-text))))
|
||||
(html-link-format! text)
|
||||
|
||||
(and (util/url? text)
|
||||
(or (string/includes? text "youtube.com")
|
||||
(string/includes? text "youtu.be"))
|
||||
(mobile-util/is-native-platform?))
|
||||
(commands/simple-insert! (state/get-edit-input-id) (util/format "{{youtube %s}}" text) nil)
|
||||
|
||||
(and (util/url? text)
|
||||
(string/includes? text "twitter.com")
|
||||
(mobile-util/is-native-platform?))
|
||||
(commands/simple-insert! (state/get-edit-input-id) (util/format "{{twitter %s}}" text) nil)
|
||||
|
||||
|
||||
(and (text/block-ref? text)
|
||||
(wrapped-by? input "((" "))"))
|
||||
(commands/simple-insert! (state/get-edit-input-id) (text/get-block-ref text) nil)
|
||||
@@ -2925,9 +2913,9 @@
|
||||
;; from external
|
||||
(let [format (or (db/get-page-format (state/get-current-page)) :markdown)]
|
||||
(match [format
|
||||
(nil? (gp-util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
|
||||
(nil? (gp-util/safe-re-find #"(?m)^\s*\*+\s+" text))
|
||||
(nil? (gp-util/safe-re-find #"(?:\r?\n){2,}" text))]
|
||||
(nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
|
||||
(nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
|
||||
(nil? (util/safe-re-find #"(?:\r?\n){2,}" text))]
|
||||
[:markdown false _ _]
|
||||
(paste-text-parseable format text)
|
||||
|
||||
@@ -2953,7 +2941,10 @@
|
||||
(utils/getClipText
|
||||
(fn [clipboard-data]
|
||||
(when-let [_ (state/get-input)]
|
||||
(state/append-current-edit-content! clipboard-data)))
|
||||
(let [data (if (gp-util/url? clipboard-data)
|
||||
(wrap-macro-url clipboard-data)
|
||||
clipboard-data)]
|
||||
(state/append-current-edit-content! data))))
|
||||
(fn [error]
|
||||
(js/console.error error))))
|
||||
|
||||
@@ -2964,7 +2955,8 @@
|
||||
(let [text (.getData (gobj/get e "clipboardData") "text")
|
||||
input (state/get-input)]
|
||||
(if-not (string/blank? text)
|
||||
(if (thingatpt/org-admonition&src-at-point input)
|
||||
(if (or (thingatpt/markdown-src-at-point input)
|
||||
(thingatpt/org-admonition&src-at-point input))
|
||||
(when-not (mobile-util/native-ios?)
|
||||
(util/stop e)
|
||||
(paste-text-in-one-block-at-point))
|
||||
@@ -3100,7 +3092,7 @@
|
||||
(when-let [block-id (some-> (state/get-selection-blocks)
|
||||
first
|
||||
(dom/attr "blockid")
|
||||
medley/uuid)]
|
||||
uuid)]
|
||||
(util/stop e)
|
||||
(let [block {:block/uuid block-id}
|
||||
block-id (-> (state/get-selection-blocks)
|
||||
@@ -3169,7 +3161,7 @@
|
||||
[format content semantic?]
|
||||
(and (string/includes? content "\n")
|
||||
(if semantic?
|
||||
(let [ast (mldoc/->edn content (mldoc/default-config format))
|
||||
(let [ast (mldoc/->edn content (gp-mldoc/default-config format))
|
||||
first-elem-type (first (ffirst ast))]
|
||||
(mldoc/block-with-title? first-elem-type))
|
||||
true)))
|
||||
@@ -3212,8 +3204,7 @@
|
||||
:or {collapse? false expanded? false incremental? true root-block nil}}]
|
||||
(when-let [page (or (state/get-current-page)
|
||||
(date/today))]
|
||||
(let [block? (gp-util/uuid-string? page)
|
||||
block-id (or root-block (and block? (uuid page)))
|
||||
(let [block-id (or root-block (parse-uuid page))
|
||||
blocks (if block-id
|
||||
(db/get-block-and-children (state/get-current-repo) block-id)
|
||||
(db/get-page-blocks-no-cache page))
|
||||
@@ -3310,7 +3301,7 @@
|
||||
(->> (get-selected-blocks)
|
||||
(map (fn [dom]
|
||||
(-> (dom/attr dom "blockid")
|
||||
medley/uuid
|
||||
uuid
|
||||
expand-block!)))
|
||||
doall)
|
||||
(and clear-selection? (clear-selection!)))
|
||||
@@ -3343,7 +3334,7 @@
|
||||
(->> (get-selected-blocks)
|
||||
(map (fn [dom]
|
||||
(-> (dom/attr dom "blockid")
|
||||
medley/uuid
|
||||
uuid
|
||||
collapse-block!)))
|
||||
doall)
|
||||
(and clear-selection? (clear-selection!)))
|
||||
|
||||
Reference in New Issue
Block a user