mirror of
https://github.com/logseq/logseq.git
synced 2026-05-23 12:14:06 +00:00
Merge branch 'master' into enhance/keymaps-manager-x
This commit is contained in:
@@ -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
|
||||
|
||||
3
deps/common/src/logseq/common/graph.cljs
vendored
3
deps/common/src/logseq/common/graph.cljs
vendored
@@ -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))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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")))
|
||||
@@ -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 🎉")}))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (= "<div>WOOT</div>"
|
||||
@@ -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")
|
||||
|
||||
74
e2e-tests/blockref.spec.ts
Normal file
74
e2e-tests/blockref.spec.ts
Normal file
@@ -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);
|
||||
})
|
||||
@@ -31,6 +31,26 @@ export async function lastBlock(page: Page): Promise<Locator> {
|
||||
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<Locator> {
|
||||
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<Locator> {
|
||||
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<void> {
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"})
|
||||
: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..."})
|
||||
|
||||
@@ -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.)"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]])))
|
||||
(batch-set-block-property! [[block-id key value]])))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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)]}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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#)]
|
||||
|
||||
@@ -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
|
||||
[]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user