diff --git a/.carve/ignore b/.carve/ignore
index 8cd677525c..acaa1900e9 100644
--- a/.carve/ignore
+++ b/.carve/ignore
@@ -48,6 +48,7 @@ frontend.mixins/perf-measure-mixin
frontend.mobile.util/get-idevice-statusbar-height
;; Used in macro
frontend.modules.outliner.datascript/transact!
+frontend.modules.outliner.core/*transaction-opts*
;; Referenced in comment
frontend.page/route-view
;; placeholder fn
diff --git a/deps/common/src/logseq/common/graph.cljs b/deps/common/src/logseq/common/graph.cljs
index d6c012673c..abf64dfdda 100644
--- a/deps/common/src/logseq/common/graph.cljs
+++ b/deps/common/src/logseq/common/graph.cljs
@@ -66,7 +66,8 @@
keyword))
(defn get-files
- "Given a graph's root dir, returns a list of all files that it recognizes"
+ "Given a graph's root dir, returns a list of all files that it recognizes.
+ Graph dir must be an absolute path in order for ignoring to work correctly"
[graph-dir]
(->> (readdir graph-dir)
(remove (partial ignored-path? graph-dir))
diff --git a/deps/graph-parser/src/logseq/graph_parser/cli.cljs b/deps/graph-parser/src/logseq/graph_parser/cli.cljs
index 33b6c2043f..e8c96d9a42 100644
--- a/deps/graph-parser/src/logseq/graph_parser/cli.cljs
+++ b/deps/graph-parser/src/logseq/graph_parser/cli.cljs
@@ -24,14 +24,15 @@
files))
(defn- build-graph-files
- "Given a graph directory, return allowed file paths and their contents in preparation
+ "Given a graph directory, return absolute, allowed file paths and their contents in preparation
for parsing"
- [dir config]
- (->> (common-graph/get-files dir)
- (map #(hash-map :file/path %))
- graph-parser/filter-files
- (remove-hidden-files dir config)
- (mapv #(assoc % :file/content (slurp (:file/path %))))))
+ [dir* config]
+ (let [dir (path/resolve dir*)]
+ (->> (common-graph/get-files dir)
+ (map #(hash-map :file/path %))
+ graph-parser/filter-files
+ (remove-hidden-files dir config)
+ (mapv #(assoc % :file/content (slurp (:file/path %)))))))
(defn- read-config
"Reads repo-specific config from logseq/config.edn"
diff --git a/deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs b/deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
index 0d6b6168a9..c63c2805d9 100644
--- a/deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
+++ b/deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
@@ -69,11 +69,11 @@
ffirst))
(defn- query-assertions
- [db files]
+ [db graph-dir files]
(testing "Query based stats"
(is (= (->> files
;; logseq files aren't saved under :block/file
- (remove #(string/includes? % (str "/" gp-config/app-name "/")))
+ (remove #(string/includes? % (str graph-dir "/" gp-config/app-name "/")))
;; edn files being listed in docs by parse-graph aren't graph files
(remove #(and (not (gp-config/whiteboard? %)) (string/ends-with? % ".edn")))
set)
@@ -148,7 +148,7 @@
logseq app. It is important to run these in both contexts to ensure that the
functionality in frontend.handler.repo and logseq.graph-parser remain the
same"
- [db files]
+ [db graph-dir files]
;; Counts assertions help check for no major regressions. These counts should
;; only increase over time as the docs graph rarely has deletions
(testing "Counts"
@@ -168,4 +168,4 @@
db)))
"Advanced query count"))
- (query-assertions db files))
+ (query-assertions db graph-dir files))
diff --git a/deps/graph-parser/test/logseq/graph_parser/cli_test.cljs b/deps/graph-parser/test/logseq/graph_parser/cli_test.cljs
index 626cc58221..cc3094fb4f 100644
--- a/deps/graph-parser/test/logseq/graph_parser/cli_test.cljs
+++ b/deps/graph-parser/test/logseq/graph_parser/cli_test.cljs
@@ -1,16 +1,30 @@
-(ns logseq.graph-parser.cli-test
- (:require [cljs.test :refer [deftest is testing]]
+(ns ^:node-only logseq.graph-parser.cli-test
+ (:require [cljs.test :refer [deftest is testing async use-fixtures]]
[logseq.graph-parser.cli :as gp-cli]
[logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
- [clojure.string :as string]))
+ [clojure.string :as string]
+ ["fs" :as fs]
+ ["process" :as process]
+ ["path" :as path]))
+
+(use-fixtures
+ :each
+ ;; Cleaning tmp/ before leaves last tmp/ after a test run for dev and debugging
+ {:before
+ #(async done
+ (if (fs/existsSync "tmp")
+ (fs/rm "tmp" #js {:recursive true} (fn [err]
+ (when err (js/console.log err))
+ (done)))
+ (done)))})
;; Integration test that test parsing a large graph like docs
(deftest ^:integration parse-graph
(let [graph-dir "test/docs-0.9.2"
_ (docs-graph-helper/clone-docs-repo-if-not-exists graph-dir "v0.9.2")
- {:keys [conn files asts]} (gp-cli/parse-graph graph-dir {:verbose false})] ;; legacy parsing
+ {:keys [conn files asts]} (gp-cli/parse-graph graph-dir {:verbose false})]
- (docs-graph-helper/docs-graph-assertions @conn files)
+ (docs-graph-helper/docs-graph-assertions @conn graph-dir files)
(testing "Asts"
(is (seq asts) "Asts returned are non-zero")
@@ -25,3 +39,37 @@
(string/includes? (:file %) (str graph-dir "/logseq/")))
asts))
"Parsed files shouldn't have empty asts"))))
+
+(defn- create-logseq-graph
+ "Creates a minimal mock graph"
+ [dir]
+ (fs/mkdirSync (path/join dir "logseq") #js {:recursive true})
+ (fs/mkdirSync (path/join dir "journals"))
+ (fs/mkdirSync (path/join dir "pages")))
+
+(deftest ^:focus build-graph-files
+ (create-logseq-graph "tmp/test-graph")
+ ;; Create files that are recognized
+ (fs/writeFileSync "tmp/test-graph/pages/foo.md" "")
+ (fs/writeFileSync "tmp/test-graph/journals/2023_05_09.md" "")
+ ;; Create file that are ignored and filtered out
+ (fs/writeFileSync "tmp/test-graph/pages/foo.json" "")
+ (fs/mkdirSync (path/join "tmp/test-graph" "logseq" "bak"))
+ (fs/writeFileSync "tmp/test-graph/logseq/bak/baz.md" "")
+
+ (testing "ignored files from common-graph"
+ (is (= (map #(path/join (process/cwd) "tmp/test-graph" %) ["journals/2023_05_09.md" "pages/foo.md"])
+ (map :file/path (#'gp-cli/build-graph-files (path/resolve "tmp/test-graph") {})))
+ "Correct paths returned for absolute dir")
+ (process/chdir "tmp/test-graph")
+ (is (= (map #(path/join (process/cwd) %) ["journals/2023_05_09.md" "pages/foo.md"])
+ (map :file/path (#'gp-cli/build-graph-files "." {})))
+ "Correct paths returned for relative current dir")
+ (process/chdir "../.."))
+
+ (testing ":hidden config"
+ (fs/mkdirSync (path/join "tmp/test-graph" "script"))
+ (fs/writeFileSync "tmp/test-graph/script/README.md" "")
+ (is (= (map #(path/join (process/cwd) "tmp/test-graph" %) ["journals/2023_05_09.md" "pages/foo.md"])
+ (map :file/path (#'gp-cli/build-graph-files "tmp/test-graph" {:hidden ["script"]})))
+ "Correct paths returned")))
\ No newline at end of file
diff --git a/deps/publishing/src/logseq/publishing/export.cljs b/deps/publishing/src/logseq/publishing/export.cljs
index e450a65e4c..2e857e5818 100644
--- a/deps/publishing/src/logseq/publishing/export.cljs
+++ b/deps/publishing/src/logseq/publishing/export.cljs
@@ -78,6 +78,7 @@
:as options}]
(let [custom-css-path (node-path/join repo-path "logseq" "custom.css")
export-css-path (node-path/join repo-path "logseq" "export.css")
+ custom-js-path (node-path/join repo-path "logseq" "custom.js")
output-static-dir (node-path/join output-dir "static")
index-html-path (node-path/join output-dir "index.html")]
(-> (p/let [_ (fs/mkdirSync output-static-dir #js {:recursive true})
@@ -87,6 +88,8 @@
_ (fs/writeFileSync (node-path/join output-static-dir "css" "export.css") export-css)
custom-css (if (fs/existsSync custom-css-path) (str (fs/readFileSync custom-css-path)) "")
_ (fs/writeFileSync (node-path/join output-static-dir "css" "custom.css") custom-css)
+ custom-js (if (fs/existsSync custom-js-path) (str (fs/readFileSync custom-js-path)) "")
+ _ (fs/writeFileSync (node-path/join output-static-dir "js" "custom.js") custom-js)
_ (cleanup-js-dir output-static-dir)]
(notification-fn {:type "success"
:payload (str "Export public pages and publish assets to " output-dir " successfully 🎉")}))
diff --git a/deps/publishing/src/logseq/publishing/html.cljs b/deps/publishing/src/logseq/publishing/html.cljs
index 344f2b0204..16568d9c8d 100644
--- a/deps/publishing/src/logseq/publishing/html.cljs
+++ b/deps/publishing/src/logseq/publishing/html.cljs
@@ -126,7 +126,8 @@ necessary db filtering"
[:script {:src "static/js/highlight.min.js"}]
[:script {:src "static/js/katex.min.js"}]
[:script {:src "static/js/html2canvas.min.js"}]
- [:script {:src "static/js/code-editor.js"}]])))))
+ [:script {:src "static/js/code-editor.js"}]
+ [:script {:src "static/js/custom.js"}]])))))
(defn build-html
"Given the graph's db, filters the db using the given options and returns the
diff --git a/deps/publishing/test/logseq/publishing/export_test.cljs b/deps/publishing/test/logseq/publishing/export_test.cljs
index 4ea36143b2..988c5ac551 100644
--- a/deps/publishing/test/logseq/publishing/export_test.cljs
+++ b/deps/publishing/test/logseq/publishing/export_test.cljs
@@ -73,7 +73,7 @@
(let [original-paths (map path/basename (get-files-recursively "tmp/static"))
copied-paths (map path/basename (get-files-recursively "tmp/published-graph"))
new-files (set/difference (set copied-paths) (set original-paths))]
- (is (= #{"index.html" "custom.css" "export.css"}
+ (is (= #{"index.html" "custom.css" "export.css" "custom.js"}
new-files)
"A published graph has the correct new files")
(is (= "
WOOT
"
@@ -97,6 +97,16 @@
(str (fs/readFileSync "tmp/published-graph/static/css/export.css")))
"export.css is copied correctly")))
+(deftest-async create-export-with-js-files
+ (create-static-dir "tmp/static")
+ (create-logseq-graph "tmp/test-graph")
+ (fs/writeFileSync "tmp/test-graph/logseq/custom.js" "// foo")
+
+ (p/let [_ (create-export "tmp/static" "tmp/test-graph" "tmp/published-graph" {})]
+ (is (= "// foo"
+ (str (fs/readFileSync "tmp/published-graph/static/js/custom.js")))
+ "custom.js is copied correctly")))
+
(deftest-async create-export-with-assets
(create-static-dir "tmp/static")
(create-logseq-graph "tmp/test-graph")
diff --git a/e2e-tests/blockref.spec.ts b/e2e-tests/blockref.spec.ts
new file mode 100644
index 0000000000..b4416ee903
--- /dev/null
+++ b/e2e-tests/blockref.spec.ts
@@ -0,0 +1,74 @@
+import { expect } from '@playwright/test'
+import { test } from './fixtures'
+import { createRandomPage, enterNextBlock, modKey, editNthBlock, moveCursorToBeginning, moveCursorToEnd } from './utils'
+import { dispatch_kb_events } from './util/keyboard-events'
+
+// Create a random page with some pre-defined blocks
+// - a
+// - b
+// id:: UUID
+// - ((id))
+async function setUpBlocks(page, block) {
+ await createRandomPage(page)
+
+ await block.mustFill('a')
+ await block.enterNext()
+ await block.mustFill('b')
+ await page.keyboard.press(modKey + '+c')
+ await page.waitForTimeout(100)
+ await block.enterNext()
+ await page.keyboard.press(modKey + '+v')
+ await page.waitForTimeout(100)
+}
+
+test('backspace at the beginning of a refed block #9406', async ({ page, block }) => {
+ await setUpBlocks(page, block)
+ await editNthBlock(page, 1)
+ await moveCursorToBeginning(page)
+ await page.keyboard.press('Backspace')
+ await expect(page.locator('textarea >> nth=0')).toHaveText("ab")
+ await expect(await block.selectionStart()).toEqual(1)
+ await expect(page.locator('.block-ref >> text="ab"')).toHaveCount(1);
+})
+
+test('delete at the end of a prev block before a refed block #9406', async ({ page, block }) => {
+ await setUpBlocks(page, block)
+ await editNthBlock(page, 0)
+ await moveCursorToEnd(page)
+ await page.keyboard.press('Delete')
+ await expect(page.locator('textarea >> nth=0')).toHaveText("ab")
+ await expect(await block.selectionStart()).toEqual(1)
+ await expect(page.locator('.block-ref >> text="ab"')).toHaveCount(1);
+})
+
+test('delete selected blocks, block ref should be replaced by content #9406', async ({ page, block }) => {
+ await setUpBlocks(page, block)
+ await editNthBlock(page, 0)
+ await page.waitForTimeout(100)
+ await page.keyboard.down('Shift')
+ await page.keyboard.press('ArrowDown')
+ await page.keyboard.press('ArrowDown')
+ await page.keyboard.up('Shift')
+ await block.waitForSelectedBlocks(2)
+ await page.keyboard.press('Backspace')
+ await expect(page.locator('.ls-block')).toHaveCount(1)
+ await editNthBlock(page, 0)
+ await expect(page.locator('textarea >> nth=0')).toHaveText("b")
+})
+
+test('delete and undo #9406', async ({ page, block }) => {
+ await setUpBlocks(page, block)
+ await editNthBlock(page, 0)
+ await page.waitForTimeout(100)
+ await page.keyboard.down('Shift')
+ await page.keyboard.press('ArrowDown')
+ await page.keyboard.press('ArrowDown')
+ await page.keyboard.up('Shift')
+ await block.waitForSelectedBlocks(2)
+ await page.keyboard.press('Backspace')
+ await expect(page.locator('.ls-block')).toHaveCount(1)
+ await page.keyboard.press(modKey + '+z')
+ await page.waitForTimeout(100)
+ await expect(page.locator('.ls-block')).toHaveCount(3)
+ await expect(page.locator('.block-ref >> text="b"')).toHaveCount(1);
+})
diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts
index 503ea7d638..e61023a8aa 100644
--- a/e2e-tests/utils.ts
+++ b/e2e-tests/utils.ts
@@ -31,6 +31,26 @@ export async function lastBlock(page: Page): Promise {
return page.locator('textarea >> nth=0')
}
+/**
+ * Move the cursor to the beginning of the current editor
+ * @param page The Playwright Page object.
+ */
+export async function moveCursorToBeginning(page: Page): Promise {
+ await page.press('textarea >> nth=0', modKey + '+a') // select all
+ await page.press('textarea >> nth=0', 'ArrowLeft')
+ return page.locator('textarea >> nth=0')
+}
+
+/**
+ * Move the cursor to the end of the current editor
+ * @param page The Playwright Page object.
+ */
+export async function moveCursorToEnd(page: Page): Promise {
+ await page.press('textarea >> nth=0', modKey + '+a') // select all
+ await page.press('textarea >> nth=0', 'ArrowRight')
+ return page.locator('textarea >> nth=0')
+}
+
/**
* Press Enter and create the next block.
* @param page The Playwright Page object.
@@ -155,8 +175,12 @@ export async function loadLocalGraph(page: Page, path: string): Promise {
console.log('Graph loaded for ' + path)
}
+export async function editNthBlock(page: Page, n) {
+ await page.click(`.ls-block .block-content >> nth=${n}`)
+}
+
export async function editFirstBlock(page: Page) {
- await page.click('.ls-block .block-content >> nth=0')
+ await editNthBlock(page, 0)
}
/**
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index b777c4ee75..2893ab95c3 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -513,7 +513,7 @@
DEVELOPMENT_TEAM = K378MFWK59;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.6;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
@@ -540,7 +540,7 @@
DEVELOPMENT_TEAM = K378MFWK59;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.6;
PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
diff --git a/src/main/frontend/commands.cljs b/src/main/frontend/commands.cljs
index 5213ef7857..9eeca2676f 100644
--- a/src/main/frontend/commands.cljs
+++ b/src/main/frontend/commands.cljs
@@ -278,7 +278,8 @@
[:editor/exit]] query-doc]
["Zotero" (zotero-steps) "Import Zotero journal article"]
["Query table function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query table function"]
- ["Calculator" [[:editor/input "```calc\n\n```" {:backward-pos 4}]
+ ["Calculator" [[:editor/input "```calc\n\n```" {:type "block"
+ :backward-pos 4}]
[:codemirror/focus]] "Insert a calculator"]
["Draw" (fn []
(let [file (draw/file-name)
@@ -402,17 +403,24 @@
edit-content (gobj/get input "value")
current-pos (cursor/pos input)
prefix (subs edit-content 0 current-pos)
+ surfix (subs edit-content current-pos)
new-value (str prefix
value
- (subs edit-content current-pos))
+ surfix)
new-pos (- (+ (count prefix)
(count value)
(or forward-pos 0))
(or backward-pos 0))]
- (state/set-block-content-and-last-pos! id new-value new-pos)
- (cursor/move-cursor-to input new-pos)
- (when check-fn
- (check-fn new-value (dec (count prefix)) new-pos))))
+ (state/set-edit-content! (state/get-edit-input-id)
+ (str prefix value))
+ ;; HACK: save scroll-pos of current pos, then add trailing content
+ (let [scroll-container (util/nearest-scrollable-container input)
+ scroll-pos (.-scrollTop scroll-container)]
+ (state/set-block-content-and-last-pos! id new-value new-pos)
+ (cursor/move-cursor-to input new-pos)
+ (set! (.-scrollTop scroll-container) scroll-pos)
+ (when check-fn
+ (check-fn new-value (dec (count prefix)) new-pos)))))
(defn simple-replace!
[id value selected
diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs
index c48eb97664..2a01b9b8b9 100644
--- a/src/main/frontend/components/block.cljs
+++ b/src/main/frontend/components/block.cljs
@@ -392,7 +392,9 @@
(util/stop event)
(when (mobile-util/native-platform?)
;; File URL must be legal, so filename muse be URI-encoded
+ ;; incoming href format: "/assets/whatever.ext"
(let [[rel-dir basename] (util/get-dir-and-basename href)
+ rel-dir (string/replace rel-dir #"^/+" "")
asset-url (path/path-join repo-dir rel-dir basename)]
(.share Share (clj->js {:url asset-url
:title "Open file with your favorite app"})))))]
@@ -2309,7 +2311,8 @@
(rum/defc block-content < rum/reactive
[config {:block/keys [uuid content children properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide?]
- (let [{:block/keys [title body] :as block} (if (:block/title block) block
+ (let [content (property/remove-built-in-properties format content)
+ {:block/keys [title body] :as block} (if (:block/title block) block
(merge block (block/parse-title-and-body uuid format pre-block? content)))
collapsed? (util/collapsed? block)
plugin-slotted? (and config/lsp-enabled? (state/slot-hook-exist? uuid))
diff --git a/src/main/frontend/dicts/es.cljc b/src/main/frontend/dicts/es.cljc
index 4ae0d72bf9..25dafebecc 100644
--- a/src/main/frontend/dicts/es.cljc
+++ b/src/main/frontend/dicts/es.cljc
@@ -497,4 +497,32 @@
:command.ui/install-plugins-from-file "Instalar extensiones de plugins.edn"
:command.ui/select-theme-color "Seleccionar temas de colores disponibles"
:command.ui/toggle-cards "Alternar tarjetas"
- :command.ui/toggle-left-sidebar "Alternar barra lateral izquierda"})
\ No newline at end of file
+ :command.ui/toggle-left-sidebar "Alternar barra lateral izquierda"
+ :command.editor/toggle-number-list "Alternar lista de números"
+ :command.whiteboard/bring-forward "Avanzar"
+ :command.whiteboard/bring-to-front "Mover al frente"
+ :command.whiteboard/connector "Herramienta conector"
+ :command.whiteboard/ellipse "Herramienta elipse"
+ :command.whiteboard/eraser "Herramienta borrador"
+ :command.whiteboard/group "Agrupar selección"
+ :command.whiteboard/highlighter "Herramienta resaltar"
+ :command.whiteboard/lock "Bloquear selección"
+ :command.whiteboard/pan "Herramienta mover"
+ :command.whiteboard/pencil "Herramienta lápiz"
+ :command.whiteboard/portal "Herramienta portal"
+ :command.whiteboard/rectangle "Herramienta rectángulo"
+ :command.whiteboard/reset-zoom "Restablecer zoom"
+ :command.whiteboard/select "Seleccionar herramienta"
+ :command.whiteboard/send-backward "Retroceder"
+ :command.whiteboard/send-to-back "Mover hacia atrás"
+ :command.whiteboard/text "Herramienta texto"
+ :command.whiteboard/toggle-grid "Alternar la grilla del lienzo"
+ :command.whiteboard/ungroup "Desagrupar selección"
+ :command.whiteboard/unlock "Desbloquear selección"
+ :command.whiteboard/zoom-in "Acercar"
+ :command.whiteboard/zoom-out "Alejar"
+ :command.whiteboard/zoom-to-fit "Zoom al dibujo"
+ :command.whiteboard/zoom-to-selection "Zoom para ajustar a la selección"
+ :shortcut.category/whiteboard "Pizarra"
+ :context-menu/toggle-number-list "Alternar lista de números"
+ :file-sync/rsapi-cannot-upload-err "Incapaz de comenzar la sincronización, favor de checar si..."})
diff --git a/src/main/frontend/dicts/it.cljc b/src/main/frontend/dicts/it.cljc
index 468e9cf19b..bc6ce06879 100644
--- a/src/main/frontend/dicts/it.cljc
+++ b/src/main/frontend/dicts/it.cljc
@@ -218,7 +218,7 @@
:plugin/load-unpacked "Carica plugin non pacchettizzato"
:plugin/restart "Riavvia app"
:plugin/unpacked-tips "Seleziona la cartella del plugin"
- :plugin/contribute "✨ Svilupppa e sottoponici un nuovo plugin"
+ :plugin/contribute "✨ Sviluppa e sottoponici un nuovo plugin"
:plugin/up-to-date "È aggiornato"
:plugin/custom-js-alert "Trovato il file custom.js, è consentito eseguirlo? (Se non si comprende il contenuto di questo file, si consiglia di non consentire l'esecuzione, che presenta alcuni rischi per la sicurezza.)"
diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs
index 81670020db..c91b398e60 100644
--- a/src/main/frontend/handler/editor.cljs
+++ b/src/main/frontend/handler/editor.cljs
@@ -353,8 +353,16 @@
block (apply dissoc block db-schema/retract-attributes)]
(profile
"Save block: "
- (let [block' (wrap-parse-block block)
- opts' (merge opts {:outliner-op :save-block})]
+ (let [original-uuid (:block/uuid (db/entity (:db/id block)))
+ uuid-changed? (not= (:block/uuid block) original-uuid)
+ block' (-> (wrap-parse-block block)
+ ;; :block/uuid might be changed when backspace/delete
+ ;; a block that has been refed
+ (assoc :block/uuid (:block/uuid block)))
+ opts' (merge opts (cond-> {:outliner-op :save-block}
+ uuid-changed?
+ (assoc :uuid-changed {:from (:block/uuid block)
+ :to original-uuid})))]
(outliner-tx/transact!
opts'
(outliner-core/save-block! block'))
@@ -383,7 +391,8 @@
content (-> (property/remove-built-in-properties format content)
(drawer/remove-logbook))]
(cond
- (another-block-with-same-id-exists? uuid block-id)
+ (and (another-block-with-same-id-exists? uuid block-id)
+ (not (= :delete (:editor/op opts))))
(notification/show!
[:p.content
(util/format "Block with the id %s already exists!" block-id)]
@@ -760,6 +769,9 @@
(let [original-content (util/trim-safe (:block/content block))
value' (-> (property/remove-built-in-properties format original-content)
(drawer/remove-logbook))
+ value (->> value
+ (property/remove-properties format)
+ (drawer/remove-logbook))
new-value (str value' value)
tail-len (count value)
pos (max
@@ -767,10 +779,12 @@
(gobj/get (utf8/encode original-content) "length")
0)
0)
- f (fn [] (edit-block! block pos id
- {:custom-content new-value
- :tail-len tail-len
- :move-cursor? false}))]
+ f (fn []
+ (edit-block! (db/pull (:db/id block))
+ pos
+ id
+ {:custom-content new-value
+ :tail-len tail-len}))]
(when move? (f))
{:prev-block block
:new-content new-value
@@ -788,9 +802,9 @@
(let [page-id (:db/id (:block/page (db/entity [:block/uuid block-id])))
page-blocks-count (and page-id (db/get-page-blocks-count repo page-id))]
(when (> page-blocks-count 1)
- (let [block (db/entity [:block/uuid block-id])
- has-children? (seq (:block/_parent block))
- block (db/pull (:db/id block))
+ (let [block-e (db/entity [:block/uuid block-id])
+ has-children? (seq (:block/_parent block-e))
+ block (db/pull (:db/id block-e))
left (tree/-get-left (outliner-core/block block))
left-has-children? (and left
(when-let [block-id (:block/uuid (:data left))]
@@ -803,14 +817,20 @@
{:keys [prev-block new-content move-fn]} (move-to-prev-block repo sibling-block format id value false)
concat-prev-block? (boolean (and prev-block new-content))
transact-opts (cond->
- {:outliner-op :delete-block}
+ {:outliner-op :delete-blocks}
concat-prev-block?
(assoc :concat-data
{:last-edit-block (:block/uuid block)}))]
(outliner-tx/transact! transact-opts
- (when concat-prev-block?
- (save-block! repo prev-block new-content))
- (delete-block-aux! block delete-children?))
+ (if concat-prev-block?
+ (let [prev-block' (if (seq (:block/_refs block-e))
+ (assoc prev-block
+ :block/uuid (:block/uuid block)
+ :block/additional-properties (:block/properties block))
+ prev-block)]
+ (delete-block-aux! block delete-children?)
+ (save-block! repo prev-block' new-content {:editor/op :delete}))
+ (delete-block-aux! block delete-children?)))
(move-fn)))))))))
(state/set-editor-op! nil)))
@@ -1224,28 +1244,24 @@
(let [value (string/trim value)]
;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
;; maybe we shouldn't save the block/file in "will-unmount" event?
- (save-block-if-changed! block value
- (merge
- {:init-properties (:block/properties block)}
- opts))))
+ (save-block-if-changed! block value opts)))
(defn save-block!
([repo block-or-uuid content]
(save-block! repo block-or-uuid content {}))
- ([repo block-or-uuid content {:keys [properties] :or {}}]
+ ([repo block-or-uuid content {:keys [properties] :as opts}]
(let [block (if (or (uuid? block-or-uuid)
(string? block-or-uuid))
(db-model/query-block-by-uuid block-or-uuid) block-or-uuid)]
(save-block!
- {:block block :repo repo}
- (if (seq properties)
- (property/insert-properties (:block/format block) content properties)
- content)
- )))
- ([{:keys [block repo] :as _state} value]
+ {:block block :repo repo :opts (dissoc opts :properties)}
+ (if (seq properties)
+ (property/insert-properties (:block/format block) content properties)
+ content))))
+ ([{:keys [block repo opts] :as _state} value]
(let [repo (or repo (state/get-current-repo))]
(when (db/entity repo [:block/uuid (:block/uuid block)])
- (save-block-aux! block value {})))))
+ (save-block-aux! block value opts)))))
(defn save-blocks!
[blocks]
@@ -1539,7 +1555,7 @@
"$" "$"
":" ":"))
-(defn autopair
+(defn- autopair
[input-id prefix _format _option]
(let [value (get autopair-map prefix)
selected (util/get-selected-text)
@@ -1576,11 +1592,11 @@
(defn- autopair-left-paren?
[input key]
(and (= key "(")
- (or
- (surround-by? input :start "")
- (surround-by? input " " "")
- (surround-by? input "]" "")
- (surround-by? input "(" ""))))
+ (or (surround-by? input :start "")
+ (surround-by? input "\n" "")
+ (surround-by? input " " "")
+ (surround-by? input "]" "")
+ (surround-by? input "(" ""))))
(defn wrapped-by?
[input before end]
@@ -1967,7 +1983,9 @@
exclude-properties
target-block
sibling?
- keep-uuid?]
+ keep-uuid?
+ cut-paste?
+ revert-cut-txs]
:or {exclude-properties []}}]
(let [editing-block (when-let [editing-block (state/get-edit-block)]
(some-> (db/pull [:block/uuid (:block/uuid editing-block)])
@@ -1982,8 +2000,7 @@
empty-target? (string/blank? (:block/content target-block))
paste-nested-blocks? (nested-blocks blocks)
target-block-has-children? (db/has-children? (:block/uuid target-block))
- replace-empty-target? (if (and paste-nested-blocks? empty-target?
- target-block-has-children?)
+ replace-empty-target? (if (and paste-nested-blocks? empty-target? target-block-has-children?)
false
true)
target-block' (if replace-empty-target? target-block
@@ -2009,13 +2026,15 @@
(outliner-core/save-block! editing-block)))
(outliner-tx/transact!
- {:outliner-op :insert-blocks}
+ {:outliner-op :insert-blocks
+ :additional-tx revert-cut-txs}
(when target-block'
(let [format (or (:block/format target-block') (state/get-preferred-format))
blocks' (map (fn [block]
(paste-block-cleanup block page exclude-properties format content-update-fn keep-uuid?))
blocks)
result (outliner-core/insert-blocks! blocks' target-block' {:sibling? sibling?
+ :cut-paste? cut-paste?
:outliner-op :paste
:replace-empty-target? replace-empty-target?
:keep-uuid? keep-uuid?})]
@@ -2205,10 +2224,18 @@
s1 (subs value 0 selected-start)
s2 (subs value selected-end)]
(state/set-edit-content! (state/get-edit-input-id)
- (str s1 insertion s2))
- (cursor/move-cursor-to input (+ selected-start (count insertion)))))))
+ (str s1 insertion))
+ ;; HACK: save scroll-pos of current pos, then add trailing content
+ ;; This logic is also in commands/simple-insert!
+ (let [scroll-container (util/nearest-scrollable-container input)
+ scroll-pos (.-scrollTop scroll-container)]
+ (state/set-edit-content! (state/get-edit-input-id)
+ (str s1 insertion s2))
+ (cursor/move-cursor-to input (+ selected-start (count insertion)))
+ (set! (.-scrollTop scroll-container) scroll-pos))))))
(defn- keydown-new-line
+ "Insert newline to current cursor position"
[]
(insert "\n"))
@@ -2434,7 +2461,6 @@
(profile
"Insert block"
(outliner-tx/transact! {:outliner-op :insert-blocks}
- (save-current-block!)
(insert-new-block! state)))))))))
(defn- inside-of-single-block
@@ -2585,8 +2611,7 @@
(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)
+ (let [^js input (state/get-input)
current-pos (cursor/pos input)
value (gobj/get input "value")
right (outliner-core/get-right-sibling (:db/id current-block))
@@ -2608,17 +2633,27 @@
:else
(let [edit-block (state/get-edit-block)
- transact-opts {:outliner-op :delete-block
+ transact-opts {:outliner-op :delete-blocks
:concat-data {:last-edit-block (:block/uuid edit-block)
:end? true}}
- new-content (str value "" (:block/content next-block))
- repo (state/get-current-repo)]
+ next-block-has-refs? (some? (:block/_refs (db/entity (:db/id next-block))))
+ new-content (if next-block-has-refs?
+ (str value ""
+ (->> (:block/content next-block)
+ (property/remove-properties (:block/format next-block))
+ (drawer/remove-logbook)))
+ (str value "" (:block/content next-block)))
+ repo (state/get-current-repo)
+ edit-block' (if next-block-has-refs?
+ (assoc edit-block
+ :block/uuid (:block/uuid next-block)
+ :block/additional-properties (dissoc (:block/properties next-block) :block/uuid))
+ edit-block)]
(outliner-tx/transact! transact-opts
- (save-block! repo edit-block new-content)
- (delete-block-aux! next-block false))
-
- (state/set-edit-content! input-id new-content)
- (cursor/move-cursor-to input current-pos)))))
+ (delete-block-aux! next-block false)
+ (save-block! repo edit-block' new-content {:editor/op :delete}))
+ (let [block (if next-block-has-refs? next-block edit-block)]
+ (edit-block! block current-pos (:block/uuid block)))))))
(defn keydown-delete-handler
[_e]
@@ -2832,11 +2867,11 @@
(contains? key)
(or (autopair-left-paren? input key)))
(let [curr (get-current-input-char input)
- prev (util/nth-safe value (dec pos))]
- (util/stop e)
- (if (and (= key "`") (= "`" curr) (not= "`" prev))
- (cursor/move-cursor-forward input)
- (autopair input-id key format nil)))
+ prev (util/nth-safe value (dec pos))]
+ (util/stop e)
+ (if (and (= key "`") (= "`" curr) (not= "`" prev))
+ (cursor/move-cursor-forward input)
+ (autopair input-id key format nil)))
(let [sym "$"]
(and (= key sym)
diff --git a/src/main/frontend/handler/editor/lifecycle.cljs b/src/main/frontend/handler/editor/lifecycle.cljs
index ef57f3fdde..1014d062fa 100644
--- a/src/main/frontend/handler/editor/lifecycle.cljs
+++ b/src/main/frontend/handler/editor/lifecycle.cljs
@@ -36,8 +36,10 @@
[state]
(let [{:keys [value]} (get-state)]
(editor-handler/clear-when-saved!)
- ;; TODO: ugly
- (when-not (contains? #{:insert :indent-outdent :auto-save :undo :redo :delete} (state/get-editor-op))
+ (when (and
+ (not (contains? #{:insert :indent-outdent :auto-save :undo :redo :delete} (state/get-editor-op)))
+ ;; Don't trigger auto-save if the latest op is undo or redo
+ (not (contains? #{:undo :redo} (state/get-editor-latest-op))))
(editor-handler/save-block! (get-state) value)))
state)
diff --git a/src/main/frontend/handler/editor/property.cljs b/src/main/frontend/handler/editor/property.cljs
index dd5532f084..7afd6de4b9 100644
--- a/src/main/frontend/handler/editor/property.cljs
+++ b/src/main/frontend/handler/editor/property.cljs
@@ -38,9 +38,8 @@
(defn edit-block!
([block pos id]
(edit-block! block pos id nil))
- ([block pos id {:keys [custom-content tail-len move-cursor? retry-times]
+ ([block pos id {:keys [custom-content tail-len retry-times]
:or {tail-len 0
- move-cursor? true
retry-times 0}
:as opts}]
(when-not (> retry-times 2)
@@ -70,7 +69,7 @@
(drawer/remove-logbook))]
(clear-selection!)
(if edit-input-id
- (state/set-editing! edit-input-id content block text-range move-cursor?)
+ (state/set-editing! edit-input-id content block text-range)
;; Block may not be rendered yet
(js/setTimeout (fn [] (edit-block! block pos id (update opts :retry-times inc))) 10))))))))
@@ -138,4 +137,4 @@
(defn set-block-property!
[block-id key value]
(let [key (keyword key)]
- (batch-set-block-property! [[block-id key value]])))
\ No newline at end of file
+ (batch-set-block-property! [[block-id key value]])))
diff --git a/src/main/frontend/handler/paste.cljs b/src/main/frontend/handler/paste.cljs
index 90cdef121c..da8e612821 100644
--- a/src/main/frontend/handler/paste.cljs
+++ b/src/main/frontend/handler/paste.cljs
@@ -105,6 +105,15 @@
[text]
(boolean (util/safe-re-find #"(?m)^\s*\*+\s+" text)))
+(defn- get-revert-cut-tx
+ "Get reverted previous cut tx when paste"
+ [blocks]
+ (let [{:keys [retracted-block-ids revert-tx]} (get-in @state/state [:editor/last-replace-ref-content-tx (state/get-current-repo)])
+ recent-cut-block-ids (->> retracted-block-ids (map second) (set))]
+ (state/set-state! [:editor/last-replace-ref-content-tx (state/get-current-repo)] nil)
+ (when (= (set (map :block/uuid blocks)) recent-cut-block-ids)
+ (seq revert-tx))))
+
(defn- paste-copied-blocks-or-text
;; todo: logseq/whiteboard-shapes is now text/html
[text e html]
@@ -120,7 +129,12 @@
(commands/simple-insert! input-id text nil)))
internal-paste? (seq copied-blocks)]
(if internal-paste?
- (editor-handler/paste-blocks copied-blocks {})
+ (let [revert-cut-tx (get-revert-cut-tx copied-blocks)
+ cut-paste? (boolean (seq revert-cut-tx))
+ keep-uuid? cut-paste?]
+ (editor-handler/paste-blocks copied-blocks {:revert-cut-tx revert-cut-tx
+ :cut-paste? cut-paste?
+ :keep-uuid? keep-uuid?}))
(let [shape-refs-text (when (and (not (string/blank? html))
(get-whiteboard-tldr-from-text html))
;; text should always be prepared block-ref generated in tldr
diff --git a/src/main/frontend/modules/editor/undo_redo.cljs b/src/main/frontend/modules/editor/undo_redo.cljs
index d0c4512e24..ab133643f3 100644
--- a/src/main/frontend/modules/editor/undo_redo.cljs
+++ b/src/main/frontend/modules/editor/undo_redo.cljs
@@ -7,7 +7,9 @@
[frontend.util.page :as page-util]
[frontend.state :as state]
[clojure.set :as set]
- [medley.core :as medley]))
+ [medley.core :as medley]
+ [frontend.util.drawer :as drawer]
+ [frontend.util.property :as property]))
;;;; APIs
@@ -157,8 +159,10 @@
"Prevent block auto-save during undo/redo."
[]
(when-let [block (state/get-edit-block)]
- (state/set-edit-content! (state/get-edit-input-id)
- (:block/content (db/entity (:db/id block))))))
+ (when-let [content (:block/content (db/entity (:db/id block)))]
+ (let [content' (-> (property/remove-built-in-properties (:block/format block) content)
+ (drawer/remove-logbook))]
+ (state/set-edit-content! (state/get-edit-input-id) content')))))
(defn- get-next-tx-editor-cursor
[tx-id]
diff --git a/src/main/frontend/modules/file/core.cljs b/src/main/frontend/modules/file/core.cljs
index 4162c5e55c..f5ee06e582 100644
--- a/src/main/frontend/modules/file/core.cljs
+++ b/src/main/frontend/modules/file/core.cljs
@@ -32,8 +32,10 @@
content))
(defn transform-content
- [{:block/keys [collapsed? format pre-block? unordered content left page parent properties]} level {:keys [heading-to-list?]}]
- (let [heading (:heading properties)
+ [{:block/keys [collapsed? format pre-block? unordered content left page parent properties] :as b} level {:keys [heading-to-list?]}]
+ (let [block-ref-not-saved? (and (seq (:block/_refs (db/entity (:db/id b))))
+ (not (string/includes? content (str (:block/uuid b)))))
+ heading (:heading properties)
markdown? (= :markdown format)
content (or content "")
pre-block? (or pre-block?
@@ -80,7 +82,10 @@
(string/blank? new-content))
""
" ")]
- (str prefix sep new-content)))]
+ (str prefix sep new-content)))
+ content (if block-ref-not-saved?
+ (property/insert-property format content :id (str (:block/uuid b)))
+ content)]
content))
diff --git a/src/main/frontend/modules/outliner/core.cljs b/src/main/frontend/modules/outliner/core.cljs
index 2fbb9d5fd9..91d1cd2069 100644
--- a/src/main/frontend/modules/outliner/core.cljs
+++ b/src/main/frontend/modules/outliner/core.cljs
@@ -528,10 +528,11 @@
For example, if `blocks` are from internal copy, the uuids
need to be changed, but there's no need for drag & drop.
`outliner-op`: what's the current outliner operation.
+ `cut-paste?`: whether it's pasted from cut blocks
`replace-empty-target?`: If the `target-block` is an empty block, whether
to replace it, it defaults to be `false`.
``"
- [blocks target-block {:keys [sibling? keep-uuid? outliner-op replace-empty-target?] :as opts}]
+ [blocks target-block {:keys [sibling? keep-uuid? outliner-op replace-empty-target? cut-paste?] :as opts}]
{:pre [(seq blocks)
(s/valid? ::block-map-or-entity target-block)]}
(let [target-block' (get-target-block target-block)
@@ -587,7 +588,10 @@
(when-let [left (last (filter (fn [b] (= 1 (:block/level b))) tx))]
[{:block/uuid (tree/-get-id next)
:block/left (:db/id left)}]))
- full-tx (util/concat-without-nil uuids-tx tx next-tx)]
+ cut-target-tx (when (and cut-paste? replace-empty-target?)
+ [{:db/id (:db/id target-block')
+ :block/uuid (:block/uuid (first blocks'))}])
+ full-tx (util/concat-without-nil uuids-tx tx next-tx cut-target-tx)]
(when (and replace-empty-target? (state/editing?))
(state/set-edit-content! (state/get-edit-input-id) (:block/content (first blocks))))
{:tx-data full-tx
@@ -859,6 +863,11 @@
see also `frontend.modules.outliner.transaction/transact!`"
nil)
+(def ^:private ^:dynamic #_:clj-kondo/ignore *transaction-opts*
+ "Stores transaction opts that are generated by one or more write-operations,
+ see also `frontend.modules.outliner.transaction/transact!`"
+ nil)
+
(defn- op-transact!
[fn-var & args]
{:pre [(var? fn-var)]}
diff --git a/src/main/frontend/modules/outliner/datascript.cljc b/src/main/frontend/modules/outliner/datascript.cljc
index 5a5928775a..204618fefe 100644
--- a/src/main/frontend/modules/outliner/datascript.cljc
+++ b/src/main/frontend/modules/outliner/datascript.cljc
@@ -10,7 +10,11 @@
[frontend.config :as config]
[logseq.graph-parser.util :as gp-util]
[lambdaisland.glogi :as log]
- [frontend.search :as search])))
+ [frontend.search :as search]
+ [clojure.string :as string]
+ [frontend.util :as util]
+ [frontend.util.property :as property]
+ [logseq.graph-parser.util.block-ref :as block-ref])))
#?(:cljs
(defn new-outliner-txs-state [] (atom [])))
@@ -51,6 +55,71 @@
[tx-report]
(get-in tx-report [:tempids :db/current-tx])))
+#?(:cljs
+ (defn update-block-refs
+ [txs opts]
+ (if-let [changed (:uuid-changed opts)]
+ (let [{:keys [from to]} changed
+ from-e (db/entity [:block/uuid from])
+ to-e (db/entity [:block/uuid to])
+ from-id (:db/id from-e)
+ to-id (:db/id to-e)
+ from-refs (:block/_refs from-e)
+ from-path-refs (:block/_path-refs from-e)
+ to-refs (:block/_refs to-e)
+ from-refs-txs (mapcat (fn [ref]
+ (let [id (:db/id ref)]
+ [[:db/retract id :block/refs from-id]
+ [:db/add id :block/refs to-id]])) from-refs)
+ from-path-refs-txs (mapcat (fn [ref]
+ (let [id (:db/id ref)]
+ [[:db/retract id :block/path-refs from-id]
+ [:db/add id :block/path-refs to-id]])) from-path-refs)
+ to-refs-txs (mapcat (fn [ref]
+ (let [id (:db/id ref)
+ new-content (string/replace (:block/content ref)
+ (block-ref/->block-ref to)
+ (block-ref/->block-ref from))]
+ [[:db/add id :block/content new-content]])) to-refs)]
+ (concat txs from-refs-txs from-path-refs-txs to-refs-txs))
+ txs)))
+
+#?(:cljs
+ (defn replace-ref-with-content
+ [txs opts]
+ (if (and (= :delete-blocks (:outliner-op opts))
+ (empty? (:uuid-changed opts)))
+ (let [retracted-block-ids (->> (keep (fn [tx]
+ (when (and (vector? tx)
+ (= :db.fn/retractEntity (first tx)))
+ (second tx))) txs))
+ retracted-blocks (map db/entity retracted-block-ids)
+ retracted-tx (->> (for [block retracted-blocks]
+ (let [refs (:block/_refs block)]
+ (map (fn [ref]
+ (let [id (:db/id ref)
+ block-content (property/remove-properties (:block/format block) (:block/content block))
+ new-content (-> (:block/content ref)
+ (string/replace (re-pattern (util/format "(?i){{embed \\(\\(%s\\)\\)\\s?}}" (str (:block/uuid block))))
+ block-content)
+ (string/replace (block-ref/->block-ref (str (:block/uuid block)))
+ block-content))]
+ {:tx [[:db/retract (:db/id ref) :block/refs (:db/id block)]
+ [:db/retract (:db/id ref) :block/path-refs (:db/id block)]
+ [:db/add id :block/content new-content]]
+ :revert-tx [[:db/add (:db/id ref) :block/refs (:db/id block)]
+ [:db/add (:db/id ref) :block/path-refs (:db/id block)]
+ [:db/add id :block/content (:block/content ref)]]})) refs)))
+ (apply concat))
+ retracted-tx' (mapcat :tx retracted-tx)
+ revert-tx (mapcat :revert-tx retracted-tx)]
+ (when (seq retracted-tx')
+ (state/set-state! [:editor/last-replace-ref-content-tx (state/get-current-repo)]
+ {:retracted-block-ids retracted-block-ids
+ :revert-tx revert-tx}))
+ (concat txs retracted-tx'))
+ txs)))
+
#?(:cljs
(defn transact!
[txs opts before-editor-cursor]
@@ -58,15 +127,26 @@
txs (map (fn [m] (if (map? m)
(dissoc m
:block/children :block/meta :block/top? :block/bottom? :block/anchor
- :block/title :block/body :block/level :block/container :db/other-tx)
- m)) txs)]
+ :block/title :block/body :block/level :block/container :db/other-tx
+ :block/additional-properties)
+ m)) txs)
+ txs (cond-> txs
+ (:uuid-changed opts)
+ (update-block-refs opts)
+
+ (and (= :delete-blocks (:outliner-op opts))
+ (empty? (:uuid-changed opts)))
+ (replace-ref-with-content opts)
+
+ true
+ (distinct))]
(when (and (seq txs)
(not (:skip-transact? opts))
(not (contains? (:file/unlinked-dirs @state/state)
(config/get-repo-dir (state/get-current-repo)))))
;; (prn "[DEBUG] Outliner transact:")
- ;; (frontend.util/pprint txs)
+ ;; (frontend.util/pprint {:txs txs :opts opts})
(try
(let [repo (get opts :repo (state/get-current-repo))
diff --git a/src/main/frontend/modules/outliner/transaction.cljc b/src/main/frontend/modules/outliner/transaction.cljc
index 5f0ad51f7b..7adf7bc5c8 100644
--- a/src/main/frontend/modules/outliner/transaction.cljc
+++ b/src/main/frontend/modules/outliner/transaction.cljc
@@ -23,22 +23,31 @@
(move-blocks! ...)
(delete-blocks! ...))"
[opts & body]
- (assert (or (map? opts) (symbol? opts)) (str "opts is not a map or symbol, type: " (type opts) ))
+ (assert (or (map? opts) (symbol? opts)) (str "opts is not a map or symbol, type: " (type opts)))
`(let [transact-data# frontend.modules.outliner.core/*transaction-data*
+ transaction-opts# frontend.modules.outliner.core/*transaction-opts*
opts# (if transact-data#
(assoc ~opts :nested-transaction? true)
~opts)
before-editor-cursor# (frontend.state/get-current-edit-block-and-position)]
(if transact-data#
- (do ~@body)
- (binding [frontend.modules.outliner.core/*transaction-data* (transient [])]
+ (do
+ (when transaction-opts#
+ (conj! transaction-opts# opts#))
+ ~@body)
+ (binding [frontend.modules.outliner.core/*transaction-data* (transient [])
+ frontend.modules.outliner.core/*transaction-opts* (transient [])]
+ (conj! frontend.modules.outliner.core/*transaction-opts* transaction-opts# opts#)
~@body
(let [r# (persistent! frontend.modules.outliner.core/*transaction-data*)
tx# (mapcat :tx-data r#)
;; FIXME: should we merge all the tx-meta?
tx-meta# (first (map :tx-meta r#))
all-tx# (concat tx# (:additional-tx opts#))
- opts## (merge (dissoc opts# :additional-tx) tx-meta#)]
+ o# (persistent! frontend.modules.outliner.core/*transaction-opts*)
+ full-opts# (apply merge (reverse o#))
+ opts## (merge (dissoc full-opts# :additional-tx :current-block :nested-transaction?) tx-meta#)]
+
(when (seq all-tx#) ;; If it's empty, do nothing
(when-not (:nested-transaction? opts#) ; transact only for the whole transaction
(let [result# (frontend.modules.outliner.datascript/transact! all-tx# opts## before-editor-cursor#)]
diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs
index ef05967334..d57bbcbc3f 100644
--- a/src/main/frontend/state.cljs
+++ b/src/main/frontend/state.cljs
@@ -104,6 +104,8 @@
:config {}
:block/component-editing-mode? false
+ :editor/op nil
+ :editor/latest-op nil
:editor/hidden-editors #{} ;; page names
:editor/draw-mode? false
:editor/action nil
@@ -122,6 +124,9 @@
:editor/on-paste? false
:editor/last-key-code nil
+ ;; Stores deleted refed blocks, indexed by repo
+ :editor/last-replace-ref-content-tx nil
+
;; for audio record
:editor/record-status "NONE"
@@ -261,7 +266,7 @@
;; :file-sync/last-synced-at {}}
:file-sync/graph-state {:current-graph-uuid nil}
;; graph-uuid -> ...
-
+
:user/info {:UserGroups (storage/get :user-groups)}
:encryption/graph-parsing? false
@@ -1691,13 +1696,18 @@ Similar to re-frame subscriptions"
;; TODO: Move those to the uni `state`
-(defonce editor-op (atom nil))
(defn set-editor-op!
[value]
- (reset! editor-op value))
+ (set-state! :editor/op value)
+ (when value (set-state! :editor/latest-op value)))
+
(defn get-editor-op
[]
- @editor-op)
+ (:editor/op @state))
+
+(defn get-editor-latest-op
+ []
+ (:editor/latest-op @state))
(defn get-events-chan
[]
diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc
index 3751c5cf40..9e3e7a043c 100644
--- a/src/main/frontend/util.cljc
+++ b/src/main/frontend/util.cljc
@@ -410,6 +410,13 @@
(defn stop-propagation [e]
(when e (.stopPropagation e))))
+#?(:cljs
+ (defn nearest-scrollable-container [^js/HTMLElement element]
+ (some #(when-let [overflow-y (.-overflowY (js/window.getComputedStyle %))]
+ (when (contains? #{"auto" "scroll" "overlay"} overflow-y)
+ %))
+ (take-while (complement nil?) (iterate #(.-parentElement %) element)))))
+
#?(:cljs
(defn element-top [elem top]
(when elem
diff --git a/src/test/frontend/handler/repo_conversion_test.cljs b/src/test/frontend/handler/repo_conversion_test.cljs
index 20afee5c22..fc383fcce5 100644
--- a/src/test/frontend/handler/repo_conversion_test.cljs
+++ b/src/test/frontend/handler/repo_conversion_test.cljs
@@ -17,11 +17,11 @@
:after test-helper/destroy-test-db!})
(defn- query-assertions-v067
- [db files]
+ [db graph-dir files]
(testing "Query based stats"
(is (= (->> files
;; logseq files aren't saved under :block/file
- (remove #(string/includes? % (str "/" gp-config/app-name "/")))
+ (remove #(string/includes? % (str graph-dir "/" gp-config/app-name "/")))
set)
(->> (d/q '[:find (pull ?b [* {:block/file [:file/path]}])
:where [?b :block/name] [?b :block/file]]
@@ -93,7 +93,7 @@
logseq app. It is important to run these in both contexts to ensure that the
functionality in frontend.handler.repo and logseq.graph-parser remain the
same"
- [db files]
+ [db graph-dir files]
;; Counts assertions help check for no major regressions. These counts should
;; only increase over time as the docs graph rarely has deletions
(testing "Counts"
@@ -113,7 +113,7 @@
db)))
"Advanced query count"))
- (query-assertions-v067 db files))
+ (query-assertions-v067 db graph-dir files))
(defn- convert-to-triple-lowbar
[path]
@@ -144,4 +144,4 @@
db (conn/get-db test-helper/test-db)]
;; Result under new naming rule after conversion should be the same as the old one
- (docs-graph-assertions-v067 db (map :file/path files))))
+ (docs-graph-assertions-v067 db graph-dir (map :file/path files))))
diff --git a/src/test/frontend/handler/repo_test.cljs b/src/test/frontend/handler/repo_test.cljs
index cb3662133a..6738f89160 100644
--- a/src/test/frontend/handler/repo_test.cljs
+++ b/src/test/frontend/handler/repo_test.cljs
@@ -22,7 +22,7 @@
(repo-handler/parse-files-and-load-to-db! test-helper/test-db files {:re-render? false :verbose false}))
db (conn/get-db test-helper/test-db)]
- (docs-graph-helper/docs-graph-assertions db (map :file/path files))))
+ (docs-graph-helper/docs-graph-assertions db graph-dir (map :file/path files))))
(deftest parse-files-and-load-to-db-with-block-refs-on-reload
(testing "Refs to blocks on a page are retained if that page is reloaded"
diff --git a/templates/config.edn b/templates/config.edn
index 9da61e259e..49bb34f951 100644
--- a/templates/config.edn
+++ b/templates/config.edn
@@ -13,7 +13,7 @@
:preferred-workflow :now
;; Exclude directories/files.
- ;; Example usage:
+ ;; Example usage:
;; :hidden ["/archived" "/test.md" "../assets/archived"]
:hidden []
@@ -87,7 +87,7 @@
;; Specify the first day of the week.
;; Available options:
- ;; - integer from 0 to 6 (Monday to Sunday)
+ ;; - integer from 0 to 6 (Monday to Sunday)
;; Default value: 6 (Sunday)
:start-of-week 6
@@ -102,7 +102,7 @@
;; :custom-js-url "https://cdn.logseq.com/custom.js"
;; Set a custom Arweave gateway
- ;; Default value: https://arweave.net
+ ;; Default gateway: https://arweave.net
;; :arweave/gateway "https://arweave.net"
;; Set bullet indentation when exporting
@@ -154,7 +154,7 @@
;; Configure custom shortcuts.
;; Syntax:
;; 1. + indicates simultaneous key presses, e.g., `Ctrl+Shift+a`.
- ;; 2. A space between keys represents key chords, e.g., `t s` means
+ ;; 2. A space between keys represents key chords, e.g., `t s` means
;; pressing `t` followed by `s`.
;; 3. mod refers to `Ctrl` for Windows/Linux and `Command` for Mac.
;; 4. Use false to disable a specific shortcut.
@@ -280,7 +280,7 @@
:ref/default-open-blocks-level 2
;; Configure the threshold for linked references before collapsing.
- ;; Default value: 50
+ ;; Default value: 100
:ref/linked-references-collapsed-threshold 50
;; Graph view configuration.
@@ -325,7 +325,7 @@
;; Properties that are ignored when parsing property values for references
;; Example usage:
;; :ignored-page-references-keywords #{:author :website}
-
+
;; logbook configuration.
;; :logbook/settings
;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated
@@ -341,7 +341,7 @@
;; Mobile features options
;; Gestures
;; Example usage:
- ;; :mobile
+ ;; :mobile
;; {:gestures/disabled-in-block-with-tags ["kanban"]}
;; Extra CodeMirror options
@@ -353,11 +353,11 @@
;; :readOnly false} ; Default value: false
;; Enable logical outdenting
- ;; Default value: false
+ ;; Default value: false
;; :editor/logical-outdenting? false
;; Prefer pasting the file when text and a file are in the clipboard.
- ;; Default value: false
+ ;; Default value: false
;; :editor/preferred-pasting-file? false
;; Quick capture templates for receiving content from other apps.