Skip to content

Commit

Permalink
Fix: Delete and backspace deleting block ids and breaking references (#…
Browse files Browse the repository at this point in the history
…8974)

* press delete at the end of a block, if no refs in current block,
delete current block instead of next

* enhance backspace behavior to avoid broke ref

* fix press backspace at the end of the block of the end of the page issue

* add e2e-test for delete and backspace across blocks

---------

Co-authored-by: Junyi Du <junyidu.cn@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Gabriel Horner <97210743+logseq-cldwalker@users.noreply.github.com>
  • Loading branch information
4 people authored and bendyorke committed May 9, 2023
1 parent f6c3512 commit ea87925
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 39 deletions.
90 changes: 88 additions & 2 deletions e2e-tests/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
import fs from 'fs/promises'
import path from 'path'
import { test } from './fixtures'
import { randomString, createRandomPage } from './utils'
import { randomString, createRandomPage, modKey } from './utils'


test('create page and blocks, save to disk', async ({ page, block, graphDir }) => {
Expand Down Expand Up @@ -82,7 +82,93 @@ test('delete and backspace', async ({ page, block }) => {
await page.keyboard.press('Delete', { delay: 50 })
expect(await page.inputValue('textarea >> nth=0')).toBe('te')

// TODO: test delete & backspace across blocks
// delete & backspace across blocks
await block.enterNext()
await block.mustFill('test')
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('Delete', { delay: 50 })
expect(await page.inputValue('textarea >> nth=0')).toBe('tetest')
await block.enterNext()
expect(await page.inputValue('textarea >> nth=0')).toBe('test')
await page.keyboard.press('Backspace', { delay: 50 })
expect(await page.inputValue('textarea >> nth=0')).toBe('tetest')

// delete across blocks, the current block has no refs
await block.clickNext()
await block.mustFill('no ref')
await block.enterNext()
await block.mustType('has a ref')
await page.keyboard.press(modKey + '+c')
await page.waitForTimeout(100)
await block.clickNext()
await page.keyboard.press(modKey + '+v')
await page.waitForTimeout(100)
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('End')
await page.waitForTimeout(100)
await page.keyboard.press('Delete', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).toBe('no refhas a ref')
await expect(page.locator('.warning')).toHaveCount(0)

// delete across blocks, the current block has refs and the next block has no refs
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('End')
await page.waitForTimeout(100)
await page.keyboard.press('Delete', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).toBe('no refhas a ref')
await expect(page.locator('.warning')).toHaveCount(0)

// delete across blocks, the current block and the next block both have refs
await page.keyboard.press('Enter')
await page.keyboard.press(modKey + '+c')
await page.waitForTimeout(100)
await page.keyboard.press('End')
await page.keyboard.press('Enter')
await page.keyboard.press(modKey + '+v')
await page.waitForTimeout(100)
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('ArrowUp', { delay: 50 })
await page.keyboard.press('End')
await page.waitForTimeout(100)
await page.keyboard.press('Delete', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).not.toBe('no refhas a ref')
expect(await page.inputValue('textarea >> nth=0')).toContain('no refhas a ref')
await expect(page.locator('.warning')).toHaveCount(1)

// backspace across blocks, the current block has refs and the prev block has no refs
await page.keyboard.press(modKey + '+z')
await page.waitForTimeout(100)
await page.keyboard.press('Home')
await page.waitForTimeout(100)
await page.keyboard.press('Backspace', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).toBe('tetestno ref')
await expect(page.locator('.warning')).toHaveCount(0)

// backspace across blocks, the current block has no refs
await page.keyboard.press('End')
await page.keyboard.press('Enter')
await block.mustFill('no ref')
await page.keyboard.press('Home')
await page.waitForTimeout(100)
await page.keyboard.press('Backspace', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).toBe('tetestno refno ref')
await expect(page.locator('.warning')).toHaveCount(0)

// backspace across blocks, the current block and the prev block both have refs
await page.keyboard.press('ArrowDown', { delay: 50 })
await page.keyboard.press('Home')
await page.waitForTimeout(100)
await page.keyboard.press('Backspace', { delay: 50 })
await page.waitForTimeout(100)
expect(await page.inputValue('textarea >> nth=0')).toBe('tetestno refno refhas a ref')
await expect(page.locator('.warning')).toHaveCount(1)
})


Expand Down
106 changes: 69 additions & 37 deletions src/main/frontend/handler/editor.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@
block
(dissoc block :block/pre-block?))
block (update block :block/refs remove-non-existed-refs!)
block (if (and left (not= (:block/left block) left)) (assoc block :block/left left) block)
new-properties (merge
(select-keys properties (property/hidden-properties))
(:block/properties block))]
Expand Down Expand Up @@ -778,41 +779,49 @@
(outliner-core/delete-blocks! [block] {:children? children?})))))

(defn- move-to-prev-block
[repo sibling-block format id value]
(when (and repo sibling-block)
(when-let [sibling-block-id (dom/attr sibling-block "blockid")]
(when-let [block (db/pull repo '[*] [:block/uuid (uuid sibling-block-id)])]
(let [original-content (util/trim-safe (:block/content block))
value' (-> (property/remove-built-in-properties format original-content)
(drawer/remove-logbook))
new-value (str value' value)
tail-len (count value)
pos (max
(if original-content
(gobj/get (utf8/encode original-content) "length")
0)
0)]
(edit-block! block pos id
{:custom-content new-value
:tail-len tail-len
:move-cursor? false})
{:prev-block block
:new-content new-value})))))
([repo sibling-block format id value]
(move-to-prev-block repo sibling-block format id value true))
([repo sibling-block format id value edit?]
(when (and repo sibling-block)
(when-let [sibling-block-id (dom/attr sibling-block "blockid")]
(when-let [block (db/pull repo '[*] [:block/uuid (uuid sibling-block-id)])]
(let [original-content (util/trim-safe (:block/content block))
value' (-> (property/remove-built-in-properties format original-content)
(drawer/remove-logbook))
new-value (str value' value)
tail-len (count value)
pos (max
(if original-content
(gobj/get (utf8/encode original-content) "length")
0)
0)]
(when edit?
(edit-block! block pos id
{:custom-content new-value
:tail-len tail-len
:move-cursor? false}))
{:prev-block block
:new-content new-value
:pos pos}))))))

(declare save-block!)

(defn- block-has-no-ref?
[eid]
(empty? (:block/_refs (db/entity eid))))

(defn delete-block!
([repo]
(delete-block! repo true))
([repo delete-children?]
(state/set-editor-op! :delete)
(let [{:keys [id block-id block-parent-id value format]} (get-state)]
(when block-id
(let [page-id (:db/id (:block/page (db/entity [:block/uuid block-id])))
(let [block (db/entity [:block/uuid block-id])
page-id (:db/id (:block/page block))
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))
(let [has-children? (seq (:block/_parent block))
block (db/pull (:db/id block))
left (tree/-get-left (outliner-core/block block))
left-has-children? (and left
Expand All @@ -822,18 +831,31 @@
(when-not (and has-children? left-has-children?)
(when block-parent-id
(let [block-parent (gdom/getElement block-parent-id)
;; it's possible to find a block not belong to the same page of current editing block
sibling-block (util/get-prev-block-non-collapsed-non-embed block-parent)
{:keys [prev-block new-content]} (move-to-prev-block repo sibling-block format id value)
delete_prev? (and (not (block-has-no-ref? (:db/id block)))
(block-has-no-ref? [:block/uuid (uuid (dom/attr sibling-block "blockid"))]))
{:keys [prev-block new-content pos]} (move-to-prev-block repo sibling-block format id value (not delete_prev?))
concat-prev-block? (boolean (and prev-block new-content))
save-page? (= (:db/id (:block/page prev-block)) page-id)
transact-opts (cond->
{:outliner-op :delete-block}
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 (and delete_prev? save-page?)
(let [block (assoc block :block/left (:block/left prev-block))
input (gdom/getElement id)]
(outliner-tx/transact! transact-opts
(delete-block-aux! prev-block false)
(save-block! repo block new-content))
(state/set-edit-content! id new-content)
(cursor/move-cursor-to input pos))
(outliner-tx/transact! transact-opts
(when concat-prev-block?
(save-block! repo prev-block new-content))
(delete-block-aux! block delete-children?)))
))))))))
(state/set-editor-op! nil)))

(defn delete-blocks!
Expand Down Expand Up @@ -2645,11 +2667,9 @@
(util/safe-set-range-text! input "" start end)
(state/set-edit-content! (state/get-edit-input-id) (.-value input)))

(defn- delete-concat [current-block]
(defn- delete-concat
[current-block input current-pos value]
(let [input-id (state/get-edit-input-id)
^js input (state/get-input)
current-pos (cursor/pos input)
value (gobj/get input "value")
right (outliner-core/get-right-node (outliner-core/block current-block))
current-block-has-children? (db/has-children? (:block/uuid current-block))
collapsed? (util/collapsed? current-block)
Expand All @@ -2664,17 +2684,29 @@
(and (not collapsed?) first-child (db/has-children? (:block/uuid first-child)))
nil

:else
(and next-block (block-has-no-ref? (:db/id current-block)))
(let [edit-block (state/get-edit-block)
transact-opts {:outliner-op :delete-block
:concat-data {:last-edit-block (:block/uuid edit-block)
:end? true}}
new-content (str value "" (:block/content next-block))
next-block (assoc next-block :block/left (:block/left current-block))
repo (state/get-current-repo)]
(outliner-tx/transact! transact-opts
(save-block! repo edit-block new-content)
(delete-block-aux! next-block false))
(delete-block-aux! edit-block false)
(save-block! repo next-block new-content))
(edit-block! next-block current-pos (:block/uuid next-block)))

:else
(let [edit-block (state/get-edit-block)
transact-opts {:outliner-op :delete-block
:concat-data {:last-edit-block (:block/uuid edit-block)
:end? true}}
new-content (str value "" (:block/content next-block))
repo (state/get-current-repo)]
(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)))))

Expand All @@ -2696,8 +2728,8 @@
(let [editor-state (get-state)
custom-query? (get-in editor-state [:config :custom-query?])]
(when-not custom-query?
(delete-concat current-block)))

(delete-concat current-block input current-pos value)))
:else
(delete-and-update input current-pos (inc current-pos))))))

Expand Down

0 comments on commit ea87925

Please sign in to comment.