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.