From 81bbc2ed47a357ed467be5cd777e28875afd714f Mon Sep 17 00:00:00 2001 From: wolimst <64784258+wolimst@users.noreply.github.com> Date: Tue, 20 Feb 2024 23:25:27 +0900 Subject: [PATCH 1/3] Add feature for moving the current block up and down --- src/editor/block/commands.js | 55 ++++++++++++++++++++++++++++++++++++ src/editor/keymap.js | 3 ++ 2 files changed, 58 insertions(+) diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index 7265b37c..63144f74 100644 --- a/src/editor/block/commands.js +++ b/src/editor/block/commands.js @@ -313,3 +313,58 @@ export function triggerCurrenciesLoaded(state, dispatch) { annotations: [heynoteEvent.of(CURRENCIES_LOADED)], })) } + +export function moveCurrentBlockUp({state, dispatch}) { + return moveCurrentBlock(state, dispatch, true) +} + +export function moveCurrentBlockDown({state, dispatch}) { + return moveCurrentBlock(state, dispatch, false) +} + +function moveCurrentBlock(state, dispatch, up) { + if (state.readOnly) { + return false + } + + const blocks = state.facet(blockState) + const currentBlock = getActiveNoteBlock(state) + const blockIndex = blocks.indexOf(currentBlock) + if ((up && blockIndex === 0) || (!up && blockIndex === blocks.length - 1)) { + return false + } + + const dir = up ? -1 : 1 + const neighborBlock = blocks[blockIndex + dir] + + const currentBlockContent = state.sliceDoc(currentBlock.delimiter.from, currentBlock.content.to) + const neighborBlockContent = state.sliceDoc(neighborBlock.delimiter.from, neighborBlock.content.to) + const newContent = up ? currentBlockContent + neighborBlockContent : neighborBlockContent + currentBlockContent + + const selectionRange = state.selection.asSingle().ranges[0] + let newSelectionRange + if (up) { + newSelectionRange = EditorSelection.range( + selectionRange.anchor - currentBlock.delimiter.from + neighborBlock.delimiter.from, + selectionRange.head - currentBlock.delimiter.from + neighborBlock.delimiter.from, + ) + } else { + newSelectionRange = EditorSelection.range( + selectionRange.anchor + neighborBlock.content.to - neighborBlock.delimiter.from, + selectionRange.head + neighborBlock.content.to - neighborBlock.delimiter.from, + ) + } + + dispatch(state.update({ + changes: { + from: up ? neighborBlock.delimiter.from : currentBlock.delimiter.from, + to: up ? currentBlock.content.to : neighborBlock.content.to, + insert: newContent, + }, + selection: newSelectionRange, + }, { + scrollIntoView: true, + userEvent: "input", + })) + return true +} diff --git a/src/editor/keymap.js b/src/editor/keymap.js index 745aaf31..1037bb99 100644 --- a/src/editor/keymap.js +++ b/src/editor/keymap.js @@ -15,6 +15,7 @@ import { gotoPreviousParagraph, gotoNextParagraph, selectNextParagraph, selectPreviousParagraph, newCursorBelow, newCursorAbove, + moveCurrentBlockUp, moveCurrentBlockDown, } from "./block/commands.js" import { pasteCommand, copyCommand, cutCommand } from "./copy-paste.js" @@ -65,5 +66,7 @@ export function heynoteKeymap(editor) { {key:"Mod-ArrowDown", run:gotoNextBlock, shift:selectNextBlock}, {key:"Ctrl-ArrowUp", run:gotoPreviousParagraph, shift:selectPreviousParagraph}, {key:"Ctrl-ArrowDown", run:gotoNextParagraph, shift:selectNextParagraph}, + ["Mod-Shift-Alt-ArrowUp", moveCurrentBlockUp], + ["Mod-Shift-Alt-ArrowDown", moveCurrentBlockDown], ]) } From 650bb18c0b25928a1415c4410abf4aaa23ad36c9 Mon Sep 17 00:00:00 2001 From: wolimst <64784258+wolimst@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:11:30 +0900 Subject: [PATCH 2/3] Add annotation on state change to ignore first block protection filter --- src/editor/annotation.js | 1 + src/editor/block/commands.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editor/annotation.js b/src/editor/annotation.js index 6b4e83cb..ad0c5ebc 100644 --- a/src/editor/annotation.js +++ b/src/editor/annotation.js @@ -5,3 +5,4 @@ export const LANGUAGE_CHANGE = "heynote-change" export const CURRENCIES_LOADED = "heynote-currencies-loaded" export const SET_CONTENT = "heynote-set-content" export const ADD_NEW_BLOCK = "heynote-add-new-block" +export const MOVE_BLOCK = "heynote-move-block" diff --git a/src/editor/block/commands.js b/src/editor/block/commands.js index 63144f74..08c9ff97 100644 --- a/src/editor/block/commands.js +++ b/src/editor/block/commands.js @@ -1,5 +1,5 @@ import { EditorSelection } from "@codemirror/state" -import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED, ADD_NEW_BLOCK } from "../annotation.js"; +import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED, ADD_NEW_BLOCK, MOVE_BLOCK } from "../annotation.js"; import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./block" import { moveLineDown, moveLineUp } from "./move-lines.js"; import { selectAll } from "./select-all.js"; @@ -362,6 +362,7 @@ function moveCurrentBlock(state, dispatch, up) { insert: newContent, }, selection: newSelectionRange, + annotations: [heynoteEvent.of(MOVE_BLOCK)], }, { scrollIntoView: true, userEvent: "input", From 4e1ddac12ca80dce544e3da25c7bc4d43fdf6c21 Mon Sep 17 00:00:00 2001 From: wolimst <64784258+wolimst@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:31:41 +0900 Subject: [PATCH 3/3] Add tests for block moving feature --- tests/move-block.spec.js | 112 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/move-block.spec.js diff --git a/tests/move-block.spec.js b/tests/move-block.spec.js new file mode 100644 index 00000000..56206aba --- /dev/null +++ b/tests/move-block.spec.js @@ -0,0 +1,112 @@ +import { expect, test } from "@playwright/test" +import { HeynotePage } from "./test-utils.js" + +let heynotePage + +test.beforeEach(async ({ page }) => { + heynotePage = new HeynotePage(page) + await heynotePage.goto() + + expect((await heynotePage.getBlocks()).length).toBe(1) + await heynotePage.setContent(` +∞∞∞text +Block A +∞∞∞text +Block B +∞∞∞text +Block C`) + + // check that blocks are created + expect((await heynotePage.getBlocks()).length).toBe(3) + + // check that visual block layers are created + await expect(page.locator("css=.heynote-blocks-layer > div")).toHaveCount(3) +}) + +test("move the first block up", async ({ page }) => { + // select the first block, cursor position: "Block A|" + await page.locator("body").press("ArrowUp") + await page.locator("body").press("ArrowUp") + + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowUp`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block A") + expect(await heynotePage.getBlockContent(1)).toBe("Block B") + expect(await heynotePage.getBlockContent(2)).toBe("Block C") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("A") +}) + +test("move the middle block up", async ({ page }) => { + // select the second block, cursor position: "Block B|" + await page.locator("body").press("ArrowUp") + + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowUp`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block B") + expect(await heynotePage.getBlockContent(1)).toBe("Block A") + expect(await heynotePage.getBlockContent(2)).toBe("Block C") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("B") +}) + +test("move the last block up", async ({ page }) => { + // cursor position: "Block C|" + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowUp`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block A") + expect(await heynotePage.getBlockContent(1)).toBe("Block C") + expect(await heynotePage.getBlockContent(2)).toBe("Block B") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("C") +}) + +test("move the first block down", async ({ page }) => { + // select the first block, cursor position: "Block A|" + await page.locator("body").press("ArrowUp") + await page.locator("body").press("ArrowUp") + + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowDown`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block B") + expect(await heynotePage.getBlockContent(1)).toBe("Block A") + expect(await heynotePage.getBlockContent(2)).toBe("Block C") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("A") +}) + +test("move the middle block down", async ({ page }) => { + // select the second block, cursor position: "Block B|" + await page.locator("body").press("ArrowUp") + + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowDown`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block A") + expect(await heynotePage.getBlockContent(1)).toBe("Block C") + expect(await heynotePage.getBlockContent(2)).toBe("Block B") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("B") +}) + +test("move the last block down", async ({ page }) => { + // cursor position: "Block C|" + await page.locator("body").press(`${heynotePage.isMac ? "Meta" : "Control"}+Shift+Alt+ArrowDown`) + const cursorPosition = await heynotePage.getCursorPosition() + const content = await heynotePage.getContent() + + expect((await heynotePage.getBlocks()).length).toBe(3) + expect(await heynotePage.getBlockContent(0)).toBe("Block A") + expect(await heynotePage.getBlockContent(1)).toBe("Block B") + expect(await heynotePage.getBlockContent(2)).toBe("Block C") + expect(content.slice(cursorPosition - 1, cursorPosition)).toBe("C") +})