mirror of
https://github.com/logseq/logseq.git
synced 2026-04-29 16:36:27 +00:00
Core outliner operations refactoring (#4880)
* Add outliner nested transact! Copied the code mostly from https://github.com/logseq/logseq/pull/4671 by zhiyuan * refactor: insert-blocks * fix: insert-blocks * fix: move cursor to the last block when inserting * fix: replace the current block when inserting and its content is empty * keep only :insert-blocks * expose only :delete-blocks * Use existing implementations for move-nodes-up-down and indent/outdent. * fix editing state not updated immediately * fix editing status * fix: avoid recursive copy * fix: inserting blocks after an empty block * Implement move-blocks with insert-blocks * fix: block left * Implement move-blocks-up-down with move-blocks * fix: paste text * Implement indent-outdent-blocks with move-blocks * fix: indent/outdent * feat: multiple blocks drag && drop * fix: indent/outdent blocks * fix: drag drop * Port unit tests for outliner.core * enhance: open collapsed parent when indenting blocks * refactor: block selection * fix: indent/outdent blocks with different levels * Add instrument on invalid outliner structure * fix: can't write a block if the page has any outdated blocks * fix: editing status for empty page * fix: multiple drag & drop * fix: drag & drop disallows moving from parents to its child * fix: public property * fix: can't delete first empty block * Remove unused code * fix: e2e tests A workaround is to not select/highlight the block when pressing esc if it has fenced code. * remove unused code * Add batch transaction test * fix: update :block/page when dragging targets' children to another page * Add more tests * Simplify extract * Replace db/get-conn with db/get-db * Simplify extracting blocks from ast * Code cleanup * Code cleanup * Add outliner core fuzzy tests * Remove unused code * fix: cursor not jump to the upper block when pressing Enter in the beginning * fix: Enter in the beginning of a non-empty block * Fix lint warnings * Add editor random e2e tests * Fix typo * enhance: move some fns and add some comments * enhance(outliner): add page-block? util * fix: increase td width to prevent content overflow Signed-off-by: Yue Yang <g1enyy0ung@gmail.com> * First pass at file tests for file-sync Each action usually passes by 5th try * Fix two incorrect calls caught by tests * More test improvements - Easier auth setup - subdirectory is configurable - list graphs api also exercised * Address cleanup from #3839 - Remove unused translation key - Delete or TODO commented code - Capitalize notifications to users * fix quick capture template not working * enhance(sync): add logout * enhance: add logout i18n * fix(plugin): sometimes plugin settings of gui not work when entry from app settings * enable show-brackets? toggle for orgmode [[file:./pages/demo.org][demo]] * fix(sync): fix unfinishable sync loop * feature: logseq protocol; refactor persistGraph * fix: deeplink support * fix: broadcast persist graph on opening new graph with logseq protocol * feat: logseq protocol open action for page-name and uuid * fix: logseq protocol graph param validation * ux: copy logseq URL of block * enhance: remove the redundant 'open' from logseq protocol (v0.1) * ux: page dropdown button for copy page URL * chore: logseq protocol comments * don't create new contents file when changing format Logseq now creates a new contents file when users try to toggle the preferred format, which causes file duplications error. * fix pasting in src block not working on iOS close https://github.com/logseq/logseq/issues/4914 * fix playing video goes into editing mode on iOS * fix copy to clipboard failure on iOS * add Podfile item * fix mobile toolbar order not persisting after restart * test(e2e): add test for backspace and cursor pos (#4896) * test(e2e): add test for backspace and cursor pos * fix(test): refine, fix wrong helper * fix(ui): warn about illegal git commit interval * enhance(editor): allow global git cmd shortcut * style(settings): line-space of general/journals * enhance(editor): accept enter in dummy block Fix #4931 * fix editing state not updated immediately * fix: can't write a block if the page has any outdated blocks TODO: clean outdated blocks * fix: editing status for empty page * Random tree for outliner core tests * Add pre assertions and fn docs based on Zhiyuan's suggestions * Made some changes based on Gabriel's suggestions * fix: tests * fix: save current block before moving * Updated the timeout to 100ms based on llcc's suggestion https://github.com/logseq/logseq/pull/4880#discussion_r851966301 * api-insert-new-block! supports replace-empty-target? * fix: replace all :reuse-last-block? usage Co-authored-by: rcmerci <rcmerci@gmail.com> Co-authored-by: Yue Yang <g1enyy0ung@gmail.com> Co-authored-by: Gabriel Horner <gabriel@logseq.com> Co-authored-by: llcc <lzhes43@gmail.com> Co-authored-by: charlie <xyhp915@qq.com> Co-authored-by: Junyi Du <junyidu.cn@gmail.com> Co-authored-by: Andelf <andelf@gmail.com>
This commit is contained in:
@@ -341,7 +341,7 @@
|
||||
refs (distinct (concat (:refs block) ref-blocks))]
|
||||
(assoc block :refs refs))))
|
||||
|
||||
(defn block-keywordize
|
||||
(defn- block-keywordize
|
||||
[block]
|
||||
(medley/map-keys
|
||||
(fn [k]
|
||||
@@ -350,7 +350,7 @@
|
||||
(keyword "block" k)))
|
||||
block))
|
||||
|
||||
(defn safe-blocks
|
||||
(defn- sanity-blocks-data
|
||||
[blocks]
|
||||
(map (fn [block]
|
||||
(if (map? block)
|
||||
@@ -414,15 +414,13 @@
|
||||
block))
|
||||
|
||||
(defn- get-block-content
|
||||
[utf8-content block format block-content]
|
||||
(let [meta (:meta block)
|
||||
content (or block-content
|
||||
(if-let [end-pos (:end-pos meta)]
|
||||
(utf8/substring utf8-content
|
||||
(:start-pos meta)
|
||||
end-pos)
|
||||
(utf8/substring utf8-content
|
||||
(:start-pos meta))))
|
||||
[utf8-content block format meta]
|
||||
(let [content (if-let [end-pos (:end_pos meta)]
|
||||
(utf8/substring utf8-content
|
||||
(:start_pos meta)
|
||||
end-pos)
|
||||
(utf8/substring utf8-content
|
||||
(:start_pos meta)))
|
||||
content (when content
|
||||
(let [content (text/remove-level-spaces content format)]
|
||||
(if (or (:pre-block? block)
|
||||
@@ -465,25 +463,24 @@
|
||||
block-tags->pages
|
||||
(update :refs (fn [col] (remove nil? col)))))
|
||||
|
||||
(defn extract-blocks*
|
||||
[blocks body pre-block-properties encoded-content with-body?]
|
||||
(defn with-pre-block-if-exists
|
||||
[blocks body pre-block-properties encoded-content]
|
||||
(let [first-block (first blocks)
|
||||
first-block-start-pos (get-in first-block [:block/meta :start-pos])
|
||||
first-block-start-pos (get-in first-block [:block/meta :start_pos])
|
||||
|
||||
;; Add pre-block
|
||||
blocks (if (or (> first-block-start-pos 0)
|
||||
(empty? blocks))
|
||||
(cons
|
||||
(merge
|
||||
(let [content (utf8/substring encoded-content 0 first-block-start-pos)
|
||||
{:keys [properties properties-order]} @pre-block-properties
|
||||
{:keys [properties properties-order]} pre-block-properties
|
||||
id (get-custom-id-or-new-id {:properties properties})
|
||||
property-refs (->> (get-page-refs-from-properties properties)
|
||||
(map :block/original-name))
|
||||
block {:uuid id
|
||||
:content content
|
||||
:level 1
|
||||
:meta {:start-pos 0
|
||||
:end-pos (or first-block-start-pos
|
||||
(utf8/length encoded-content))}
|
||||
:properties properties
|
||||
:properties-order properties-order
|
||||
:refs property-refs
|
||||
@@ -494,120 +491,102 @@
|
||||
(block-keywordize block))
|
||||
(select-keys first-block [:block/format :block/page]))
|
||||
blocks)
|
||||
blocks)
|
||||
blocks (map (fn [block]
|
||||
(if with-body?
|
||||
block
|
||||
(dissoc block :block/body))) blocks)]
|
||||
blocks)]
|
||||
(with-path-refs blocks)))
|
||||
|
||||
(defn ^:large-vars/cleanup-todo extract-blocks
|
||||
([blocks content with-id? format]
|
||||
(extract-blocks blocks content with-id? format false))
|
||||
([blocks content with-id? format with-body?]
|
||||
(try
|
||||
(defn- construct-block
|
||||
[block properties timestamps body encoded-content format pos-meta with-id?]
|
||||
(let [id (get-custom-id-or-new-id properties)
|
||||
ref-pages-in-properties (->> (:page-refs properties)
|
||||
(remove string/blank?))
|
||||
block (second block)
|
||||
unordered? (:unordered block)
|
||||
markdown-heading? (and (:size block) (= :markdown format))
|
||||
block (if markdown-heading?
|
||||
(assoc block
|
||||
:type :heading
|
||||
:level (if unordered? (:level block) 1)
|
||||
:heading-level (or (:size block) 6))
|
||||
block)
|
||||
block (cond->
|
||||
(assoc block
|
||||
:uuid id
|
||||
:refs ref-pages-in-properties
|
||||
:format format
|
||||
:meta pos-meta)
|
||||
(seq (:properties properties))
|
||||
(assoc :properties (:properties properties))
|
||||
|
||||
(seq (:properties-order properties))
|
||||
(assoc :properties-order (:properties-order properties)))
|
||||
block (if (get-in block [:properties :collapsed])
|
||||
(assoc block :collapsed? true)
|
||||
block)
|
||||
block (assoc block
|
||||
:content (get-block-content encoded-content block format pos-meta))
|
||||
block (if (seq timestamps)
|
||||
(merge block (timestamps->scheduled-and-deadline timestamps))
|
||||
block)
|
||||
block (assoc block :body body)
|
||||
block (with-page-block-refs block with-id?)
|
||||
{:keys [created-at updated-at]} (:properties properties)
|
||||
block (cond-> block
|
||||
(and created-at (integer? created-at))
|
||||
(assoc :block/created-at created-at)
|
||||
|
||||
(and updated-at (integer? updated-at))
|
||||
(assoc :block/updated-at updated-at))]
|
||||
(dissoc block :title :body :anchor)))
|
||||
|
||||
(defn extract-blocks
|
||||
"Extract headings from mldoc ast.
|
||||
Args:
|
||||
`blocks`: mldoc ast.
|
||||
`content`: markdown or org-mode text.
|
||||
`with-id?`: If `with-id?` equals to true, all the referenced pages will have new db ids.
|
||||
`format`: content's format, it could be either :markdown or :org-mode."
|
||||
[blocks content with-id? format]
|
||||
{:pre [(seq blocks) (string? content) (boolean? with-id?) (contains? #{:markdown :org} format)]}
|
||||
(try
|
||||
(let [encoded-content (utf8/encode content)
|
||||
last-pos (utf8/length encoded-content)
|
||||
pre-block-properties (atom nil)
|
||||
[blocks body]
|
||||
[blocks body pre-block-properties]
|
||||
(loop [headings []
|
||||
blocks (reverse blocks)
|
||||
timestamps {}
|
||||
properties {}
|
||||
last-pos last-pos
|
||||
last-level 1000
|
||||
children []
|
||||
block-all-content []
|
||||
body []]
|
||||
(if (seq blocks)
|
||||
(let [[block {:keys [start_pos _end_pos] :as block-content}] (first blocks)
|
||||
block-content (when (string? block-content) block-content)
|
||||
unordered? (:unordered (second block))
|
||||
markdown-heading? (and (:size (second block)) (= :markdown format))]
|
||||
(let [[block pos-meta] (first blocks)
|
||||
;; fix start_pos
|
||||
pos-meta (assoc pos-meta :end_pos
|
||||
(if (seq headings)
|
||||
(get-in (last headings) [:meta :start_pos])
|
||||
nil))]
|
||||
(cond
|
||||
(paragraph-timestamp-block? block)
|
||||
(let [timestamps (extract-timestamps block)
|
||||
timestamps' (merge timestamps timestamps)]
|
||||
(recur headings (rest blocks) timestamps' properties last-pos last-level children (conj block-all-content block-content) body))
|
||||
(recur headings (rest blocks) timestamps' properties body))
|
||||
|
||||
(property/properties-ast? block)
|
||||
(let [properties (extract-properties format (second block))]
|
||||
(recur headings (rest blocks) timestamps properties last-pos last-level children (conj block-all-content block-content) body))
|
||||
(recur headings (rest blocks) timestamps properties body))
|
||||
|
||||
(heading-block? block)
|
||||
(let [id (get-custom-id-or-new-id properties)
|
||||
ref-pages-in-properties (->> (:page-refs properties)
|
||||
(remove string/blank?))
|
||||
block (second block)
|
||||
block (if markdown-heading?
|
||||
(assoc block
|
||||
:type :heading
|
||||
:level (if unordered? (:level block) 1)
|
||||
:heading-level (or (:size block) 6))
|
||||
block)
|
||||
level (:level block)
|
||||
[children current-block-children]
|
||||
(cond
|
||||
(< level last-level)
|
||||
(let [current-block-children (set (->> (filter #(< level (second %)) children)
|
||||
(map first)
|
||||
(map (fn [id]
|
||||
[:block/uuid id]))))
|
||||
others (vec (remove #(< level (second %)) children))]
|
||||
[(conj others [id level])
|
||||
current-block-children])
|
||||
|
||||
(>= level last-level)
|
||||
[(conj children [id level])
|
||||
#{}])
|
||||
block (cond->
|
||||
(assoc block
|
||||
:uuid id
|
||||
:refs ref-pages-in-properties
|
||||
:children (or current-block-children [])
|
||||
:format format)
|
||||
(seq (:properties properties))
|
||||
(assoc :properties (:properties properties))
|
||||
|
||||
(seq (:properties-order properties))
|
||||
(assoc :properties-order (:properties-order properties)))
|
||||
block (if (get-in block [:properties :collapsed])
|
||||
(assoc block :collapsed? true)
|
||||
block)
|
||||
block (-> block
|
||||
(assoc-in [:meta :start-pos] start_pos)
|
||||
(assoc-in [:meta :end-pos] last-pos)
|
||||
((fn [block]
|
||||
(assoc block
|
||||
:content (get-block-content encoded-content block format (and block-content (string/join "\n" (reverse (conj block-all-content block-content)))))))))
|
||||
block (if (seq timestamps)
|
||||
(merge block (timestamps->scheduled-and-deadline timestamps))
|
||||
block)
|
||||
block (assoc block :body body)
|
||||
block (with-page-block-refs block with-id?)
|
||||
last-pos' (get-in block [:meta :start-pos])
|
||||
{:keys [created-at updated-at]} (:properties properties)
|
||||
block (cond-> block
|
||||
(and created-at (integer? created-at))
|
||||
(assoc :block/created-at created-at)
|
||||
|
||||
(and updated-at (integer? updated-at))
|
||||
(assoc :block/updated-at updated-at))]
|
||||
(recur (conj headings block) (rest blocks) {} {} last-pos' (:level block) children [] []))
|
||||
(let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id?)]
|
||||
(recur (conj headings block) (rest blocks) {} {} []))
|
||||
|
||||
:else
|
||||
(recur headings (rest blocks) timestamps properties last-pos last-level children
|
||||
(conj block-all-content block-content)
|
||||
(conj body block))))
|
||||
(do
|
||||
(when (seq properties)
|
||||
(reset! pre-block-properties properties))
|
||||
[(-> (reverse headings)
|
||||
safe-blocks) body])))]
|
||||
(extract-blocks* blocks body pre-block-properties encoded-content with-body?))
|
||||
(recur headings (rest blocks) timestamps properties (conj body block))))
|
||||
[(-> (reverse headings)
|
||||
sanity-blocks-data)
|
||||
body
|
||||
properties]))
|
||||
result (with-pre-block-if-exists blocks body pre-block-properties encoded-content)]
|
||||
(map #(dissoc % :block/meta) result))
|
||||
(catch js/Error e
|
||||
(js/console.error "extract-blocks-failed")
|
||||
(log/error :exception e)))))
|
||||
(log/error :exception e))))
|
||||
|
||||
(defn with-parent-and-left
|
||||
[page-id blocks]
|
||||
@@ -615,7 +594,6 @@
|
||||
parents [{:page/id page-id ; db id or a map {:block/name "xxx"}
|
||||
:block/level 0
|
||||
:block/level-spaces 0}]
|
||||
_sibling nil
|
||||
result []]
|
||||
(if (empty? blocks)
|
||||
(map #(dissoc % :block/level-spaces) result)
|
||||
@@ -623,7 +601,7 @@
|
||||
level-spaces (:block/level-spaces block)
|
||||
{:block/keys [uuid level parent] :as last-parent} (last parents)
|
||||
parent-spaces (:block/level-spaces last-parent)
|
||||
[blocks parents sibling result]
|
||||
[blocks parents result]
|
||||
(cond
|
||||
(= level-spaces parent-spaces) ; sibling
|
||||
(let [block (assoc block
|
||||
@@ -632,7 +610,7 @@
|
||||
:block/level level)
|
||||
parents' (conj (vec (butlast parents)) block)
|
||||
result' (conj result block)]
|
||||
[others parents' block result'])
|
||||
[others parents' result'])
|
||||
|
||||
(> level-spaces parent-spaces) ; child
|
||||
(let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
|
||||
@@ -649,7 +627,7 @@
|
||||
(assoc :block/level (inc level)))
|
||||
parents' (conj parents block)
|
||||
result' (conj result block)]
|
||||
[others parents' block result'])
|
||||
[others parents' result'])
|
||||
|
||||
(< level-spaces parent-spaces)
|
||||
(cond
|
||||
@@ -660,7 +638,7 @@
|
||||
:block/level (dec level)
|
||||
:block/left [:block/uuid (:block/uuid left)])
|
||||
(rest blocks))]
|
||||
[blocks parents' left result])
|
||||
[blocks parents' result])
|
||||
|
||||
:else
|
||||
(let [[f r] (split-with (fn [p] (<= (:block/level-spaces p) level-spaces)) parents)
|
||||
@@ -677,8 +655,8 @@
|
||||
|
||||
parents' (->> (concat f [block]) vec)
|
||||
result' (conj result block)]
|
||||
[others parents' block result'])))]
|
||||
(recur blocks parents sibling result)))))
|
||||
[others parents' result'])))]
|
||||
(recur blocks parents result)))))
|
||||
|
||||
(defn parse-block
|
||||
([block]
|
||||
|
||||
Reference in New Issue
Block a user