Merge branch 'master' into enhance/keymaps-manager-x

This commit is contained in:
Charlie
2023-05-23 11:43:47 +08:00
committed by GitHub
29 changed files with 503 additions and 127 deletions

View File

@@ -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

View File

@@ -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))

View File

@@ -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"

View File

@@ -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))

View File

@@ -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")))

View File

@@ -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 🎉")}))

View File

@@ -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

View File

@@ -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")

View 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);
})

View File

@@ -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)
}
/**

View File

@@ -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;

View File

@@ -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

View File

@@ -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))

View File

@@ -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..."})

View File

@@ -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.)"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]])))

View File

@@ -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

View File

@@ -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]

View File

@@ -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))

View File

@@ -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)]}

View File

@@ -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))

View File

@@ -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#)]

View File

@@ -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
[]

View File

@@ -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

View File

@@ -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))))

View File

@@ -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"

View File

@@ -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.