Add feature for moving the current block to another buffer

- The Ctrl/Cmd-S keyboard shortcut now pops up a buffer search dialog that can be used to select a buffer that the block should be moved to. 
- Update `BufferSelector` UI and logic to support moving blocks
This commit is contained in:
Jonatan Heyman 2025-01-07 23:03:06 +01:00
parent 158ac08418
commit c9c092fc43
17 changed files with 255 additions and 34 deletions

View File

@ -5,6 +5,7 @@ Here are the most notable changes in each release. For a more detailed list of c
## 2.1.0 (not yet released) ## 2.1.0 (not yet released)
- Added support for moving the current block to another (or new) buffer. Pressing `Ctrl/Cmd+S` will now pop up a dialog where you can search for and select another buffer to which the block will be moved. It's also possible to select to create a brand new buffer to which the block will be moved.
- Added support for the following languages: - Added support for the following languages:
* Elixir * Elixir
* Scala * Scala

View File

@ -39,7 +39,7 @@ Available for Mac, Windows, and Linux.
⌘ + ⌥ + Enter Split the current block at cursor position ⌘ + ⌥ + Enter Split the current block at cursor position
⌘ + L Change block language ⌘ + L Change block language
⌘ + N Create a new note buffer ⌘ + N Create a new note buffer
⌘ + S Create a new note buffer from the current block ⌘ + S Move the current block to another (or new) buffer
⌘ + P Open note selector ⌘ + P Open note selector
⌘ + Down Goto next block ⌘ + Down Goto next block
⌘ + Up Goto previous block ⌘ + Up Goto previous block
@ -58,7 +58,7 @@ Alt + Shift + Enter Add new block at the start of the buffer
Ctrl + Alt + Enter Split the current block at cursor position Ctrl + Alt + Enter Split the current block at cursor position
Ctrl + L Change block language Ctrl + L Change block language
Ctrl + N Create a new note buffer Ctrl + N Create a new note buffer
Ctrl + S Create a new note buffer from the current block Ctrl + S Move the current block to another (or new) buffer
Ctrl + P Open note selector Ctrl + P Open note selector
Ctrl + Down Goto next block Ctrl + Down Goto next block
Ctrl + Up Goto previous block Ctrl + Up Goto previous block

View File

@ -10,7 +10,7 @@ export const keyHelpStr = (platform: string) => {
[`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"], [`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"],
[`${modChar} + L`, "Change block language"], [`${modChar} + L`, "Change block language"],
[`${modChar} + N`, "Create a new note buffer"], [`${modChar} + N`, "Create a new note buffer"],
[`${modChar} + S`, "Create a new note buffer from the current block"], [`${modChar} + S`, "Move the current block to another (or new) buffer"],
[`${modChar} + P`, "Open note selector"], [`${modChar} + P`, "Open note selector"],
[`${modChar} + Down`, "Goto next block"], [`${modChar} + Down`, "Goto next block"],
[`${modChar} + Up`, "Goto previous block"], [`${modChar} + Up`, "Goto previous block"],

View File

@ -5,6 +5,7 @@
import { useHeynoteStore } from "../stores/heynote-store" import { useHeynoteStore } from "../stores/heynote-store"
import { useErrorStore } from "../stores/error-store" import { useErrorStore } from "../stores/error-store"
import { useSettingsStore } from "../stores/settings-store" import { useSettingsStore } from "../stores/settings-store"
import { useEditorCacheStore } from '../stores/editor-cache'
import { OPEN_SETTINGS_EVENT, SETTINGS_CHANGE_EVENT } from '@/src/common/constants' import { OPEN_SETTINGS_EVENT, SETTINGS_CHANGE_EVENT } from '@/src/common/constants'
@ -55,6 +56,7 @@
showBufferSelector(value) { this.dialogWatcher(value) }, showBufferSelector(value) { this.dialogWatcher(value) },
showCreateBuffer(value) { this.dialogWatcher(value) }, showCreateBuffer(value) { this.dialogWatcher(value) },
showEditBuffer(value) { this.dialogWatcher(value) }, showEditBuffer(value) { this.dialogWatcher(value) },
showMoveToBufferSelector(value) { this.dialogWatcher(value) },
currentBufferPath() { currentBufferPath() {
this.focusEditor() this.focusEditor()
@ -66,7 +68,7 @@
}, },
computed: { computed: {
...mapStores(useSettingsStore), ...mapStores(useSettingsStore, useEditorCacheStore),
...mapState(useHeynoteStore, [ ...mapState(useHeynoteStore, [
"currentBufferPath", "currentBufferPath",
"currentBufferName", "currentBufferName",
@ -74,6 +76,7 @@
"showBufferSelector", "showBufferSelector",
"showCreateBuffer", "showCreateBuffer",
"showEditBuffer", "showEditBuffer",
"showMoveToBufferSelector",
]), ]),
editorInert() { editorInert() {
@ -89,6 +92,7 @@
"closeDialog", "closeDialog",
"closeBufferSelector", "closeBufferSelector",
"openBuffer", "openBuffer",
"closeMoveToBufferSelector",
]), ]),
// Used as a watcher for the booleans that control the visibility of editor dialogs. // Used as a watcher for the booleans that control the visibility of editor dialogs.
@ -123,6 +127,11 @@
formatCurrentBlock() { formatCurrentBlock() {
this.$refs.editor.formatCurrentBlock() this.$refs.editor.formatCurrentBlock()
}, },
onMoveCurrentBlockToOtherEditor(path) {
this.editorCacheStore.moveCurrentBlockToOtherEditor(path)
this.closeMoveToBufferSelector()
},
}, },
} }
@ -157,8 +166,16 @@
<BufferSelector <BufferSelector
v-if="showBufferSelector" v-if="showBufferSelector"
@openBuffer="openBuffer" @openBuffer="openBuffer"
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('new', nameSuggestion)"
@close="closeBufferSelector" @close="closeBufferSelector"
/> />
<BufferSelector
v-if="showMoveToBufferSelector"
headline="Move block to..."
@openBuffer="onMoveCurrentBlockToOtherEditor"
@openCreateBuffer="(nameSuggestion) => openCreateBuffer('currentBlock', nameSuggestion)"
@close="closeMoveToBufferSelector"
/>
<Settings <Settings
v-if="showSettings" v-if="showSettings"
:initialSettings="settingsStore.settings" :initialSettings="settingsStore.settings"

View File

@ -2,11 +2,14 @@
import fuzzysort from 'fuzzysort' import fuzzysort from 'fuzzysort'
import { mapState, mapActions } from 'pinia' import { mapState, mapActions } from 'pinia'
import { toRaw } from 'vue';
import { SCRATCH_FILE_NAME } from "../common/constants" import { SCRATCH_FILE_NAME } from "../common/constants"
import { useHeynoteStore } from "../stores/heynote-store" import { useHeynoteStore } from "../stores/heynote-store"
export default { export default {
props: {
headline: String,
},
data() { data() {
return { return {
selected: 0, selected: 0,
@ -94,7 +97,6 @@
"updateBuffers", "updateBuffers",
"editBufferMetadata", "editBufferMetadata",
"deleteBuffer", "deleteBuffer",
"openCreateBuffer",
]), ]),
buildItems() { buildItems() {
@ -170,9 +172,9 @@
selectItem(item) { selectItem(item) {
if (item.createNew) { if (item.createNew) {
if (this.filteredItems.length === 1) { if (this.filteredItems.length === 1) {
this.openCreateBuffer("new", this.filter) this.$emit("openCreateBuffer", this.filter)
} else { } else {
this.openCreateBuffer("new", "") this.$emit("openCreateBuffer", "")
} }
} else { } else {
this.$emit("openBuffer", item.path) this.$emit("openBuffer", item.path)
@ -237,6 +239,7 @@
<template> <template>
<form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container"> <form class="note-selector" tabindex="-1" @focusout="onFocusOut" ref="container">
<div class="input-container"> <div class="input-container">
<h1 v-if="headline">{{headline}}</h1>
<input <input
type="text" type="text"
ref="input" ref="input"
@ -310,11 +313,15 @@
+dark-mode +dark-mode
background: #151516 background: #151516
box-shadow: 0 0 10px rgba(0,0,0,0.5) box-shadow: 0 0 10px rgba(0,0,0,0.5)
color: rgba(255,255,255, 0.7)
+webapp-mobile +webapp-mobile
max-width: calc(100% - 80px) max-width: calc(100% - 80px)
.input-container .input-container
padding: 10px padding: 10px
h1
font-weight: bold
margin-bottom: 14px
input input
background: #fff background: #fff
padding: 4px 5px padding: 4px 5px

View File

@ -1,5 +1,4 @@
<script> <script>
import { HeynoteEditor } from '../editor/editor.js'
import { syntaxTree } from "@codemirror/language" import { syntaxTree } from "@codemirror/language"
import { toRaw } from 'vue'; import { toRaw } from 'vue';
import { mapState, mapWritableState, mapActions, mapStores } from 'pinia' import { mapState, mapWritableState, mapActions, mapStores } from 'pinia'
@ -28,6 +27,9 @@
}, },
mounted() { mounted() {
// initialize editorCacheStore (sets up watchers for settings changes, propagating them to all editors)
this.editorCacheStore.setUp(this.$refs.editor);
this.loadBuffer(this.currentBufferPath) this.loadBuffer(this.currentBufferPath)
// set up window close handler that will save the buffer and quit // set up window close handler that will save the buffer and quit
@ -46,9 +48,6 @@
window.heynote.mainProcess.on(WINDOW_CLOSE_EVENT, this.onWindowClose) window.heynote.mainProcess.on(WINDOW_CLOSE_EVENT, this.onWindowClose)
window.heynote.mainProcess.on(REDO_EVENT, this.onRedo) window.heynote.mainProcess.on(REDO_EVENT, this.onRedo)
// initialize editorCacheStore (sets up watchers for settings changes, propagating them to all editors)
this.editorCacheStore.setUp();
// if debugSyntaxTree prop is set, display syntax tree for debugging // if debugSyntaxTree prop is set, display syntax tree for debugging
if (this.debugSyntaxTree) { if (this.debugSyntaxTree) {
setInterval(() => { setInterval(() => {
@ -112,7 +111,7 @@
toRaw(this.editor).show() toRaw(this.editor).show()
} else { } else {
//console.log("create new editor") //console.log("create new editor")
this.editor = this.editorCacheStore.createEditor(path, this.$refs.editor) this.editor = this.editorCacheStore.createEditor(path)
this.editorCacheStore.addEditor(path, toRaw(this.editor)) this.editorCacheStore.addEditor(path, toRaw(this.editor))
} }

View File

@ -91,7 +91,7 @@
}, },
dialogTitle() { dialogTitle() {
return this.createBufferParams.mode === "currentBlock" ? "New Buffer from Block" : "New Buffer" return this.createBufferParams.mode === "currentBlock" ? "Move Block to New Buffer" : "New Buffer"
}, },
}, },

View File

@ -7,3 +7,4 @@ export const SET_CONTENT = "heynote-set-content"
export const ADD_NEW_BLOCK = "heynote-add-new-block" export const ADD_NEW_BLOCK = "heynote-add-new-block"
export const DELETE_BLOCK = "heynote-delete-block" export const DELETE_BLOCK = "heynote-delete-block"
export const CURSOR_CHANGE = "heynote-cursor-change" export const CURSOR_CHANGE = "heynote-cursor-change"
export const APPEND_BLOCK = "heynote-append-block"

View File

@ -169,8 +169,9 @@ const blockLayer = layer({
idx++; idx++;
return return
} }
const fromCoordsTop = view.coordsAtPos(Math.max(block.content.from, view.visibleRanges[0].from)).top // view.coordsAtPos returns null if the editor is not visible
let toCoordsBottom = view.coordsAtPos(Math.min(block.content.to, view.visibleRanges[view.visibleRanges.length - 1].to)).bottom const fromCoordsTop = view.coordsAtPos(Math.max(block.content.from, view.visibleRanges[0].from))?.top
let toCoordsBottom = view.coordsAtPos(Math.min(block.content.to, view.visibleRanges[view.visibleRanges.length - 1].to))?.bottom
if (idx === blocks.length - 1) { if (idx === blocks.length - 1) {
// Calculate how much extra height we need to add to the last block // Calculate how much extra height we need to add to the last block
let extraHeight = view.viewState.editorHeight - ( let extraHeight = view.viewState.editorHeight - (

View File

@ -12,7 +12,7 @@ import { getFontTheme } from "./theme/font-theme.js";
import { customSetup } from "./setup.js" import { customSetup } from "./setup.js"
import { heynoteLang } from "./lang-heynote/heynote.js" import { heynoteLang } from "./lang-heynote/heynote.js"
import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js" import { noteBlockExtension, blockLineNumbers, blockState, getActiveNoteBlock, triggerCursorChange } from "./block/block.js"
import { heynoteEvent, SET_CONTENT, DELETE_BLOCK } from "./annotation.js"; import { heynoteEvent, SET_CONTENT, DELETE_BLOCK, APPEND_BLOCK } from "./annotation.js";
import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock } from "./block/commands.js" import { changeCurrentBlockLanguage, triggerCurrenciesLoaded, getBlockDelimiter, deleteBlock } from "./block/commands.js"
import { formatBlockContent } from "./block/format-code.js" import { formatBlockContent } from "./block/format-code.js"
import { heynoteKeymap } from "./keymap.js" import { heynoteKeymap } from "./keymap.js"
@ -127,7 +127,8 @@ export class HeynoteEditor {
//this.setContent(content) //this.setContent(content)
this.setReadOnly(true) this.setReadOnly(true)
this.loadContent().then(() => { this.contentLoadedPromise = this.loadContent();
this.contentLoadedPromise.then(() => {
this.setReadOnly(false) this.setReadOnly(false)
}) })
@ -166,7 +167,6 @@ export class HeynoteEditor {
const content = await window.heynote.buffer.load(this.path) const content = await window.heynote.buffer.load(this.path)
this.diskContent = content this.diskContent = content
this.contentLoaded = true this.contentLoaded = true
this.setContent(content)
// set up content change listener // set up content change listener
this.onChange = (content) => { this.onChange = (content) => {
@ -174,6 +174,8 @@ export class HeynoteEditor {
this.setContent(content) this.setContent(content)
} }
window.heynote.buffer.addOnChangeCallback(this.path, this.onChange) window.heynote.buffer.addOnChangeCallback(this.path, this.onChange)
await this.setContent(content)
} }
setContent(content) { setContent(content) {
@ -278,6 +280,10 @@ export class HeynoteEditor {
this.notesStore.openCreateBuffer(createMode) this.notesStore.openCreateBuffer(createMode)
} }
openMoveToBufferSelector() {
this.notesStore.openMoveToBufferSelector()
}
async createNewBuffer(path, name) { async createNewBuffer(path, name) {
const data = getBlockDelimiter(this.defaultBlockToken, this.defaultBlockAutoDetect) const data = getBlockDelimiter(this.defaultBlockToken, this.defaultBlockAutoDetect)
await this.notesStore.saveNewBuffer(path, name, data) await this.notesStore.saveNewBuffer(path, name, data)
@ -302,8 +308,35 @@ export class HeynoteEditor {
// by using requestAnimationFrame we avoid a race condition where rendering the block backgrounds // by using requestAnimationFrame we avoid a race condition where rendering the block backgrounds
// would fail if we immediately opened the new note (since the block UI wouldn't have time to update // would fail if we immediately opened the new note (since the block UI wouldn't have time to update
// after the block was deleted) // after the block was deleted)
requestAnimationFrame(() => { //requestAnimationFrame(() => {
this.notesStore.openBuffer(path) // this.notesStore.openBuffer(path)
//})
// add new buffer to recent list so that it shows up at the top of the buffer selector
this.notesStore.addRecentBuffer(path)
this.notesStore.addRecentBuffer(this.notesStore.currentBufferPath)
}
getActiveBlockContent() {
const block = getActiveNoteBlock(this.view.state)
if (!block) {
return
}
return this.view.state.sliceDoc(block.range.from, block.range.to)
}
deleteActiveBlock() {
deleteBlock(this)(this.view)
}
appendBlockContent(content) {
this.view.dispatch({
changes: {
from: this.view.state.doc.length,
to: this.view.state.doc.length,
insert: content,
},
annotations: [heynoteEvent.of(APPEND_BLOCK)],
}) })
} }

View File

@ -59,7 +59,7 @@ export function heynoteKeymap(editor) {
["Alt-ArrowDown", moveLineDown], ["Alt-ArrowDown", moveLineDown],
["Mod-l", () => editor.openLanguageSelector()], ["Mod-l", () => editor.openLanguageSelector()],
["Mod-p", () => editor.openBufferSelector()], ["Mod-p", () => editor.openBufferSelector()],
["Mod-s", () => editor.openCreateBuffer("currentBlock")], ["Mod-s", () => editor.openMoveToBufferSelector()],
["Mod-n", () => editor.openCreateBuffer("new")], ["Mod-n", () => editor.openCreateBuffer("new")],
["Mod-Shift-d", deleteBlock(editor)], ["Mod-Shift-d", deleteBlock(editor)],
["Alt-Shift-f", formatBlockContent], ["Alt-Shift-f", formatBlockContent],

View File

@ -7,7 +7,6 @@ import App from './components/App.vue'
import { loadCurrencies } from './currency' import { loadCurrencies } from './currency'
import { useErrorStore } from './stores/error-store' import { useErrorStore } from './stores/error-store'
import { useHeynoteStore, initHeynoteStore } from './stores/heynote-store' import { useHeynoteStore, initHeynoteStore } from './stores/heynote-store'
import { useEditorCacheStore } from './stores/editor-cache'
const pinia = createPinia() const pinia = createPinia()
@ -20,7 +19,6 @@ app.mount('#app').$nextTick(() => {
}) })
const errorStore = useErrorStore() const errorStore = useErrorStore()
const editorCacheStore = useEditorCacheStore()
//errorStore.addError("test error") //errorStore.addError("test error")
window.heynote.getInitErrors().then((errors) => { window.heynote.getInitErrors().then((errors) => {
errors.forEach((e) => errorStore.addError(e)) errors.forEach((e) => errorStore.addError(e))

View File

@ -4,6 +4,7 @@ import { NoteFormat } from "../common/note-format"
import { useSettingsStore } from './settings-store' import { useSettingsStore } from './settings-store'
import { useErrorStore } from './error-store' import { useErrorStore } from './error-store'
import { useHeynoteStore } from './heynote-store'
import { HeynoteEditor } from '../editor/editor' import { HeynoteEditor } from '../editor/editor'
const NUM_EDITOR_INSTANCES = 5 const NUM_EDITOR_INSTANCES = 5
@ -15,16 +16,17 @@ export const useEditorCacheStore = defineStore("editorCache", {
cache: {}, cache: {},
watchHandler: null, watchHandler: null,
themeWatchHandler: null, themeWatchHandler: null,
containerElement: null,
}, },
}), }),
actions: { actions: {
createEditor(path, element) { createEditor(path) {
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const errorStore = useErrorStore() const errorStore = useErrorStore()
try { try {
return new HeynoteEditor({ return new HeynoteEditor({
element: element, element: this.containerElement,
path: path, path: path,
theme: settingsStore.theme, theme: settingsStore.theme,
keymap: settingsStore.settings.keymap, keymap: settingsStore.settings.keymap,
@ -43,11 +45,29 @@ export const useEditorCacheStore = defineStore("editorCache", {
} }
}, },
getOrCreateEditor(path, updateLru) {
if (updateLru) {
// move to end of LRU
this.editorCache.lru = this.editorCache.lru.filter(p => p !== path)
this.editorCache.lru.push(path)
}
if (this.editorCache.cache[path]) {
return this.editorCache.cache[path]
} else {
const editor = this.createEditor(path)
this.addEditor(path, editor)
if (!updateLru) {
// if need to add the editor to the LRU, but at the top so that it is the first to be removed
this.editorCache.lru.unshift(path)
}
return editor
}
},
getEditor(path) { getEditor(path) {
// move to end of LRU // move to end of LRU
this.editorCache.lru = this.editorCache.lru.filter(p => p !== path) this.editorCache.lru = this.editorCache.lru.filter(p => p !== path)
this.editorCache.lru.push(path) this.editorCache.lru.push(path)
if (this.editorCache.cache[path]) { if (this.editorCache.cache[path]) {
return this.editorCache.cache[path] return this.editorCache.cache[path]
} }
@ -90,7 +110,8 @@ export const useEditorCacheStore = defineStore("editorCache", {
}) })
}, },
setUp() { setUp(containerElement) {
this.containerElement = containerElement
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
this.watchHandler = watch(() => settingsStore.settings, (newSettings, oldSettings) => { this.watchHandler = watch(() => settingsStore.settings, (newSettings, oldSettings) => {
//console.log("Settings changed (watch)", newSettings, oldSettings) //console.log("Settings changed (watch)", newSettings, oldSettings)
@ -144,5 +165,25 @@ export const useEditorCacheStore = defineStore("editorCache", {
window.document.removeEventListener("currenciesLoaded", this.onCurrenciesLoaded) window.document.removeEventListener("currenciesLoaded", this.onCurrenciesLoaded)
}, },
moveCurrentBlockToOtherEditor(targetPath) {
const heynoteStore = useHeynoteStore()
const editor = toRaw(this.getEditor(heynoteStore.currentBufferPath))
let otherEditor = toRaw(this.getOrCreateEditor(targetPath, false))
otherEditor.hide()
const content = editor.getActiveBlockContent()
otherEditor.contentLoadedPromise.then(() => {
otherEditor.appendBlockContent(content)
editor.deleteActiveBlock()
// add the target buffer to recent buffers so that it shows up at the top of the buffer selector
heynoteStore.addRecentBuffer(targetPath)
heynoteStore.addRecentBuffer(heynoteStore.currentBufferPath)
})
//console.log("LRU", this.editorCache.lru)
}
}, },
}) })

View File

@ -27,6 +27,7 @@ export const useHeynoteStore = defineStore("heynote", {
showLanguageSelector: false, showLanguageSelector: false,
showCreateBuffer: false, showCreateBuffer: false,
showEditBuffer: false, showEditBuffer: false,
showMoveToBufferSelector: false,
}), }),
actions: { actions: {
@ -41,7 +42,10 @@ export const useHeynoteStore = defineStore("heynote", {
openBuffer(path) { openBuffer(path) {
this.closeDialog() this.closeDialog()
this.currentBufferPath = path this.currentBufferPath = path
this.addRecentBuffer(path)
},
addRecentBuffer(path) {
const recent = this.recentBufferPaths.filter((p) => p !== path) const recent = this.recentBufferPaths.filter((p) => p !== path)
recent.unshift(path) recent.unshift(path)
this.recentBufferPaths = recent.slice(0, 100) this.recentBufferPaths = recent.slice(0, 100)
@ -55,6 +59,10 @@ export const useHeynoteStore = defineStore("heynote", {
this.closeDialog() this.closeDialog()
this.showBufferSelector = true this.showBufferSelector = true
}, },
openMoveToBufferSelector() {
this.closeDialog()
this.showMoveToBufferSelector = true
},
openCreateBuffer(createMode, nameSuggestion) { openCreateBuffer(createMode, nameSuggestion) {
createMode = createMode || "new" createMode = createMode || "new"
this.closeDialog() this.closeDialog()
@ -69,12 +77,17 @@ export const useHeynoteStore = defineStore("heynote", {
this.showBufferSelector = false this.showBufferSelector = false
this.showLanguageSelector = false this.showLanguageSelector = false
this.showEditBuffer = false this.showEditBuffer = false
this.showMoveToBufferSelector = false
}, },
closeBufferSelector() { closeBufferSelector() {
this.showBufferSelector = false this.showBufferSelector = false
}, },
closeMoveToBufferSelector() {
this.showMoveToBufferSelector = false
},
editBufferMetadata(path) { editBufferMetadata(path) {
if (this.currentBufferPath !== path) { if (this.currentBufferPath !== path) {
this.openBuffer(path) this.openBuffer(path)

View File

@ -3,7 +3,7 @@ import {HeynotePage} from "./test-utils.js";
import { AUTO_SAVE_INTERVAL } from "../src/common/constants.js" import { AUTO_SAVE_INTERVAL } from "../src/common/constants.js"
import { NoteFormat } from "../src/common/note-format.js" import { NoteFormat } from "../src/common/note-format.js"
import exp from "constants";
let heynotePage let heynotePage
@ -40,11 +40,12 @@ test("default buffer saved", async ({page}) => {
test("create new buffer from block", async ({page}) => { test("create new buffer from block", async ({page}) => {
await page.locator("body").press(heynotePage.agnosticKey("Mod+S")) await page.locator("body").press(heynotePage.agnosticKey("Mod+S"))
await page.waitForTimeout(50) await page.waitForTimeout(50)
await page.locator("body").press("ArrowUp")
await page.locator("body").press("Enter")
await page.waitForTimeout(50)
await page.locator("body").pressSequentially("My New Buffer") await page.locator("body").pressSequentially("My New Buffer")
await page.locator("body").press("Enter") await page.locator("body").press("Enter")
await page.waitForTimeout(150) await page.waitForTimeout(150)
await page.locator("body").press("Enter")
await page.locator("body").pressSequentially("New buffer content")
await page.waitForTimeout(AUTO_SAVE_INTERVAL + 50); await page.waitForTimeout(AUTO_SAVE_INTERVAL + 50);
const buffers = Object.keys(await heynotePage.getStoredBufferList()) const buffers = Object.keys(await heynotePage.getStoredBufferList())
@ -62,8 +63,7 @@ Block B`)
expect(newBuffer.content).toBe(` expect(newBuffer.content).toBe(`
text text
Block C Block C`)
New buffer content`)
}) })

101
tests/move-block.spec.js Normal file
View File

@ -0,0 +1,101 @@
import {expect, test} from "@playwright/test";
import {HeynotePage} from "./test-utils.js";
import { AUTO_SAVE_INTERVAL } from "../src/common/constants.js"
import { NoteFormat } from "../src/common/note-format.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`)
await page.waitForTimeout(100);
// 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 block to other buffer", async ({page}) => {
await heynotePage.saveBuffer("other.txt", `
text-a
First block
math
Second block`)
await page.locator("body").press(heynotePage.agnosticKey("Mod+S"))
await page.waitForTimeout(50)
await page.locator("body").press("Enter")
await page.waitForTimeout(AUTO_SAVE_INTERVAL + 50);
const buffers = Object.keys(await heynotePage.getStoredBufferList())
expect(buffers).toContain("other.txt")
const otherBuffer = NoteFormat.load(await heynotePage.getStoredBuffer("other.txt"))
expect(await heynotePage.getContent()).toBe(`
text
Block A
text
Block B`)
expect(otherBuffer.content).toBe(`
text-a
First block
math
Second block
text
Block C`)
})
test("move block to other open/cached buffer", async ({page}) => {
await heynotePage.saveBuffer("other.txt", `
text-a
First block
math
Second block`)
await page.locator("body").press(heynotePage.agnosticKey("Mod+P"))
await page.locator("body").press("Enter")
await page.waitForTimeout(50)
await page.locator("body").press(heynotePage.agnosticKey("Mod+P"))
await page.locator("body").press("Enter")
await page.waitForTimeout(50)
await page.locator("body").press(heynotePage.agnosticKey("Mod+S"))
await page.waitForTimeout(50)
await page.locator("body").press("Enter")
await page.waitForTimeout(AUTO_SAVE_INTERVAL + 50);
const buffers = Object.keys(await heynotePage.getStoredBufferList())
expect(buffers).toContain("other.txt")
const otherBuffer = NoteFormat.load(await heynotePage.getStoredBuffer("other.txt"))
expect(await heynotePage.getContent()).toBe(`
text
Block A
text
Block B`)
expect(otherBuffer.content).toBe(`
text-a
First block
math
Second block
text
Block C`)
})

View File

@ -38,7 +38,10 @@ export class HeynotePage {
async setContent(content) { async setContent(content) {
await expect(this.page.locator("css=.cm-editor")).toBeVisible() await expect(this.page.locator("css=.cm-editor")).toBeVisible()
await this.page.evaluate((content) => window._heynote_editor.setContent(content), content) await this.page.evaluate(async (content) => {
await window._heynote_editor.setContent(content)
await window._heynote_editor.save()
}, content)
} }
async getCursorPosition() { async getCursorPosition() {
@ -65,6 +68,12 @@ export class HeynotePage {
return await this.page.evaluate((path) => window.heynote.buffer.load(path), path) return await this.page.evaluate((path) => window.heynote.buffer.load(path), path)
} }
async saveBuffer(path, content) {
const format = new NoteFormat()
format.content = content
await this.page.evaluate(({path, content}) => window.heynote.buffer.save(path, content), {path, content:format.serialize()})
}
agnosticKey(key) { agnosticKey(key) {
return key.replace("Mod", this.isMac ? "Meta" : "Control") return key.replace("Mod", this.isMac ? "Meta" : "Control")
} }