mirror of
https://github.com/heyman/heynote.git
synced 2024-11-21 15:33:14 +01:00
Add key bindings for inserting new blocks at the end/top of the buffer, as well as before the current block (#85)
* Add functionality to insert new block after the last block - Update key bindings in `initial-content.ts` to include `Alt + Enter` for adding a new block after the last block. - Implement `getLastNoteBlock` function in `block.js` to retrieve the last block in the note. - Add `addNewBlockAfterLast` command in `commands.js` to handle the insertion of a new block after the last one. - Integrate `addNewBlockAfterLast` command into the keymap in `keymap.js`. * Add block insertion before/after current, before first and after last. Also, tests. - Added `getFirstNoteBlock` in `block.js` for accessing the first text block. - Implemented new functions in `commands.js` like `addNewBlockBeforeCurrent` and `addNewBlockBeforeFirst`. - Updated `keymap.js` with new key bindings to facilitate block creation. - Introduced `block-creation.spec.js` for testing the new block manipulation features. * Fix visual bug when inserting new block at the top * Update help text and Readme * Fix wrong cursor position after inserting new blocks at the top of the buffer, when the previous first block's delimiter is long (e.g. Markdown) * Make RegEx more generic * Fix import * Auto-generate the README.md and initial-content documentation - Add a documentation generator - Add an option to force the initial content to be erased with an env variable * Add more specific tests * Fix Mod key on Mac in test --------- Co-authored-by: Jonatan Heyman <jonatan@heyman.info>
This commit is contained in:
parent
b80230454d
commit
d0d8f872a6
10
README.md
10
README.md
@ -97,8 +97,11 @@ I can totally see the usefulness of such a feature, and it's definitely somethin
|
|||||||
**On Mac**
|
**On Mac**
|
||||||
|
|
||||||
```
|
```
|
||||||
|
⌥ + Shift + Enter Add new block at the start of the buffer
|
||||||
|
⌘ + Shift + Enter Add new block at the end of the buffer
|
||||||
|
⌥ + Enter Add new block before the current block
|
||||||
⌘ + Enter Add new block below the current block
|
⌘ + Enter Add new block below the current block
|
||||||
⌘ + Shift + Enter Split the current block at cursor position
|
⌘ + ⌥ + Enter Split the current block at cursor position
|
||||||
⌘ + L Change block language
|
⌘ + L Change block language
|
||||||
⌘ + Down Goto next block
|
⌘ + Down Goto next block
|
||||||
⌘ + Up Goto previous block
|
⌘ + Up Goto previous block
|
||||||
@ -110,8 +113,11 @@ I can totally see the usefulness of such a feature, and it's definitely somethin
|
|||||||
**On Windows and Linux**
|
**On Windows and Linux**
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Alt + Shift + Enter Add new block at the start of the buffer
|
||||||
|
Ctrl + Shift + Enter Add new block at the end of the buffer
|
||||||
|
Alt + Enter Add new block before the current block
|
||||||
Ctrl + Enter Add new block below the current block
|
Ctrl + Enter Add new block below the current block
|
||||||
Ctrl + Shift + 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 + Down Goto next block
|
Ctrl + Down Goto next block
|
||||||
Ctrl + Up Goto previous block
|
Ctrl + Up Goto previous block
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const os = require('os');
|
import os from 'os';
|
||||||
|
|
||||||
export const isDev = !!process.env.VITE_DEV_SERVER_URL
|
export const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||||
|
|
||||||
|
@ -1,31 +1,13 @@
|
|||||||
import { isLinux, isMac, isWindows } from "./detect-platform.js"
|
import os from "os";
|
||||||
|
import { keyHelpStr } from "../shared-utils/key-helper";
|
||||||
const modChar = isMac ? "⌘" : "Ctrl"
|
|
||||||
const altChar = isMac ? "⌥" : "Alt"
|
|
||||||
|
|
||||||
const keyHelp = [
|
|
||||||
[`${modChar} + Enter`, "Add new block below the current block"],
|
|
||||||
[`${modChar} + Shift + Enter`, "Split the current block at cursor position"],
|
|
||||||
[`${modChar} + L`, "Change block language"],
|
|
||||||
[`${modChar} + Down`, "Goto next block"],
|
|
||||||
[`${modChar} + Up`, "Goto previous block"],
|
|
||||||
[`${modChar} + A`, "Select all text in a note block. Press again to select the whole buffer"],
|
|
||||||
[`${modChar} + ${altChar} + Up/Down`, "Add additional cursor above/below"],
|
|
||||||
[`${altChar} + Shift + F`, "Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)"],
|
|
||||||
]
|
|
||||||
if (isWindows || isLinux) {
|
|
||||||
keyHelp.push([altChar, "Show menu"])
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyMaxLength = keyHelp.map(([key, help]) => key.length).reduce((a, b) => Math.max(a, b))
|
|
||||||
const keyHelpStr = keyHelp.map(([key, help]) => `${key.padEnd(keyMaxLength)} ${help}`).join("\n")
|
|
||||||
|
|
||||||
|
export const eraseInitialContent = !!process.env.ERASE_INITIAL_CONTENT
|
||||||
|
|
||||||
export const initialContent = `
|
export const initialContent = `
|
||||||
∞∞∞text
|
∞∞∞markdown
|
||||||
Welcome to Heynote! 👋
|
Welcome to Heynote! 👋
|
||||||
|
|
||||||
${keyHelpStr}
|
${keyHelpStr(os.platform())}
|
||||||
∞∞∞math
|
∞∞∞math
|
||||||
This is a Math block. Here, rows are evaluated as math expressions.
|
This is a Math block. Here, rows are evaluated as math expressions.
|
||||||
|
|
||||||
@ -54,13 +36,13 @@ export const initialDevContent = initialContent + `
|
|||||||
def my_func():
|
def my_func():
|
||||||
print("hejsan")
|
print("hejsan")
|
||||||
|
|
||||||
|
∞∞∞javascript-a
|
||||||
import {basicSetup} from "codemirror"
|
import {basicSetup} from "codemirror"
|
||||||
import {EditorView, keymap} from "@codemirror/view"
|
import {EditorView, keymap} from "@codemirror/view"
|
||||||
import {javascript} from "@codemirror/lang-javascript"
|
import {javascript} from "@codemirror/lang-javascript"
|
||||||
import {indentWithTab, insertTab, indentLess, indentMore} from "@codemirror/commands"
|
import {indentWithTab, insertTab, indentLess, indentMore} from "@codemirror/commands"
|
||||||
import {nord} from "./nord.mjs"
|
import {nord} from "./nord.mjs"
|
||||||
∞∞∞javascript-a
|
|
||||||
let editor = new EditorView({
|
let editor = new EditorView({
|
||||||
//extensions: [basicSetup, javascript()],
|
//extensions: [basicSetup, javascript()],
|
||||||
extensions: [
|
extensions: [
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { app, BrowserWindow, Tray, shell, ipcMain, Menu, nativeTheme, globalShortcut, nativeImage } from 'electron'
|
import { app, BrowserWindow, Tray, shell, ipcMain, Menu, nativeTheme, globalShortcut, nativeImage } from 'electron'
|
||||||
import { release } from 'node:os'
|
import { release } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import * as jetpack from "fs-jetpack";
|
|
||||||
|
|
||||||
import { menu, getTrayMenu } from './menu'
|
import { menu, getTrayMenu } from './menu'
|
||||||
import { initialContent, initialDevContent } from '../initial-content'
|
import { eraseInitialContent, initialContent, initialDevContent } from '../initial-content'
|
||||||
import { WINDOW_CLOSE_EVENT, SETTINGS_CHANGE_EVENT } from '../constants';
|
import { WINDOW_CLOSE_EVENT, SETTINGS_CHANGE_EVENT } from '../constants';
|
||||||
import CONFIG from "../config"
|
import CONFIG from "../config"
|
||||||
import { onBeforeInputEvent } from "../keymap"
|
import { onBeforeInputEvent } from "../keymap"
|
||||||
@ -127,7 +126,7 @@ async function createWindow() {
|
|||||||
win.loadFile(indexHtml)
|
win.loadFile(indexHtml)
|
||||||
//win.webContents.openDevTools()
|
//win.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom keyboard shortcuts for Emacs keybindings
|
// custom keyboard shortcuts for Emacs keybindings
|
||||||
win.webContents.on("before-input-event", function (event, input) {
|
win.webContents.on("before-input-event", function (event, input) {
|
||||||
onBeforeInputEvent({event, input, win, currentKeymap})
|
onBeforeInputEvent({event, input, win, currentKeymap})
|
||||||
@ -139,11 +138,11 @@ async function createWindow() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Make all links open with the browser, not with the application
|
// Make all links open with the browser, not with the application
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
win.webContents.setWindowOpenHandler(({url}) => {
|
||||||
if (url.startsWith('https:') || url.startsWith('http:')) {
|
if (url.startsWith('https:') || url.startsWith('http:')) {
|
||||||
shell.openExternal(url)
|
shell.openExternal(url)
|
||||||
}
|
}
|
||||||
return { action: 'deny' }
|
return {action: 'deny'}
|
||||||
})
|
})
|
||||||
|
|
||||||
fixElectronCors(win)
|
fixElectronCors(win)
|
||||||
@ -253,17 +252,17 @@ ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
|
|||||||
|
|
||||||
|
|
||||||
const buffer = new Buffer({
|
const buffer = new Buffer({
|
||||||
filePath: getBufferFilePath(),
|
filePath: getBufferFilePath(),
|
||||||
onChange: (eventData) => {
|
onChange: (eventData) => {
|
||||||
win?.webContents.send("buffer-content:change", eventData)
|
win?.webContents.send("buffer-content:change", eventData)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('buffer-content:load', async () => {
|
ipcMain.handle('buffer-content:load', async () => {
|
||||||
if (buffer.exists()) {
|
if (buffer.exists() && !(eraseInitialContent && isDev)) {
|
||||||
return await buffer.load()
|
return await buffer.load()
|
||||||
} else {
|
} else {
|
||||||
return isDev? initialDevContent : initialContent
|
return isDev ? initialDevContent : initialContent
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -271,7 +270,7 @@ async function save(content) {
|
|||||||
return await buffer.save(content)
|
return await buffer.save(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle('buffer-content:save', async (event, content) => {
|
ipcMain.handle('buffer-content:save', async (event, content) => {
|
||||||
return await save(content)
|
return await save(content)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -281,7 +280,7 @@ ipcMain.handle('buffer-content:saveAndQuit', async (event, content) => {
|
|||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('settings:set', (event, settings) => {
|
ipcMain.handle('settings:set', (event, settings) => {
|
||||||
if (settings.keymap !== CONFIG.get("settings.keymap")) {
|
if (settings.keymap !== CONFIG.get("settings.keymap")) {
|
||||||
currentKeymap = settings.keymap
|
currentKeymap = settings.keymap
|
||||||
}
|
}
|
||||||
@ -291,7 +290,7 @@ ipcMain.handle('settings:set', (event, settings) => {
|
|||||||
CONFIG.set("settings", settings)
|
CONFIG.set("settings", settings)
|
||||||
|
|
||||||
win?.webContents.send(SETTINGS_CHANGE_EVENT, settings)
|
win?.webContents.send(SETTINGS_CHANGE_EVENT, settings)
|
||||||
|
|
||||||
if (globalHotkeyChanged) {
|
if (globalHotkeyChanged) {
|
||||||
registerGlobalHotkey()
|
registerGlobalHotkey()
|
||||||
}
|
}
|
||||||
|
25
shared-utils/key-helper.ts
Normal file
25
shared-utils/key-helper.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export const keyHelpStr = (platform: string) => {
|
||||||
|
const modChar = platform === "darwin" ? "⌘" : "Ctrl"
|
||||||
|
const altChar = platform === "darwin" ? "⌥" : "Alt"
|
||||||
|
|
||||||
|
const keyHelp = [
|
||||||
|
[`${altChar} + Shift + Enter`, "Add new block at the start of the buffer"],
|
||||||
|
[`${modChar} + Shift + Enter`, "Add new block at the end of the buffer"],
|
||||||
|
[`${altChar} + Enter`, "Add new block before the current block"],
|
||||||
|
[`${modChar} + Enter`, "Add new block below the current block"],
|
||||||
|
[`${modChar} + ${altChar} + Enter`, "Split the current block at cursor position"],
|
||||||
|
[`${modChar} + L`, "Change block language"],
|
||||||
|
[`${modChar} + Down`, "Goto next block"],
|
||||||
|
[`${modChar} + Up`, "Goto previous block"],
|
||||||
|
[`${modChar} + A`, "Select all text in a note block. Press again to select the whole buffer"],
|
||||||
|
[`${modChar} + ${altChar} + Up/Down`, "Add additional cursor above/below"],
|
||||||
|
[`${altChar} + Shift + F`, "Format block content (works for JSON, JavaScript, HTML, CSS and Markdown)"],
|
||||||
|
]
|
||||||
|
|
||||||
|
if (platform === "win32" || platform === "linux") {
|
||||||
|
keyHelp.push([altChar, "Show menu"])
|
||||||
|
}
|
||||||
|
const keyMaxLength = keyHelp.map(([key]) => key.length).reduce((a, b) => Math.max(a, b))
|
||||||
|
|
||||||
|
return keyHelp.map(([key, help]) => `${key.padEnd(keyMaxLength)} ${help}`).join("\n")
|
||||||
|
}
|
@ -4,4 +4,4 @@ export const heynoteEvent = Annotation.define()
|
|||||||
export const LANGUAGE_CHANGE = "heynote-change"
|
export const LANGUAGE_CHANGE = "heynote-change"
|
||||||
export const CURRENCIES_LOADED = "heynote-currencies-loaded"
|
export const CURRENCIES_LOADED = "heynote-currencies-loaded"
|
||||||
export const SET_CONTENT = "heynote-set-content"
|
export const SET_CONTENT = "heynote-set-content"
|
||||||
|
export const ADD_NEW_BLOCK = "heynote-add-new-block"
|
||||||
|
@ -75,6 +75,14 @@ export function getActiveNoteBlock(state) {
|
|||||||
return state.facet(blockState).find(block => block.range.from <= range.head && block.range.to >= range.head)
|
return state.facet(blockState).find(block => block.range.from <= range.head && block.range.to >= range.head)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFirstNoteBlock(state) {
|
||||||
|
return state.facet(blockState)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLastNoteBlock(state) {
|
||||||
|
return state.facet(blockState)[state.facet(blockState).length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
export function getNoteBlockFromPos(state, pos) {
|
export function getNoteBlockFromPos(state, pos) {
|
||||||
return state.facet(blockState).find(block => block.range.from <= pos && block.range.to >= pos)
|
return state.facet(blockState).find(block => block.range.from <= pos && block.range.to >= pos)
|
||||||
}
|
}
|
||||||
@ -86,8 +94,7 @@ class NoteBlockStart extends WidgetType {
|
|||||||
this.isFirst = isFirst
|
this.isFirst = isFirst
|
||||||
}
|
}
|
||||||
eq(other) {
|
eq(other) {
|
||||||
//return other.checked == this.checked
|
return this.isFirst === other.isFirst
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
toDOM() {
|
toDOM() {
|
||||||
let wrap = document.createElement("div")
|
let wrap = document.createElement("div")
|
||||||
@ -249,7 +256,7 @@ const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr) => {
|
|||||||
* Transaction filter to prevent the selection from being before the first block
|
* Transaction filter to prevent the selection from being before the first block
|
||||||
*/
|
*/
|
||||||
const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) => {
|
const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) => {
|
||||||
if (!firstBlockDelimiterSize) {
|
if (!firstBlockDelimiterSize || tr.annotations.some(a => a.type === heynoteEvent)) {
|
||||||
return tr
|
return tr
|
||||||
}
|
}
|
||||||
tr?.selection?.ranges.forEach(range => {
|
tr?.selection?.ranges.forEach(range => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { EditorSelection } from "@codemirror/state"
|
import { EditorSelection } from "@codemirror/state"
|
||||||
import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED } from "../annotation.js";
|
import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED, ADD_NEW_BLOCK } from "../annotation.js";
|
||||||
import { blockState, getActiveNoteBlock, getNoteBlockFromPos } from "./block"
|
import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./block"
|
||||||
import { moveLineDown, moveLineUp } from "./move-lines.js";
|
import { moveLineDown, moveLineUp } from "./move-lines.js";
|
||||||
import { selectAll } from "./select-all.js";
|
import { selectAll } from "./select-all.js";
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export { moveLineDown, moveLineUp, selectAll }
|
|||||||
export const insertNewBlockAtCursor = ({ state, dispatch }) => {
|
export const insertNewBlockAtCursor = ({ state, dispatch }) => {
|
||||||
if (state.readOnly)
|
if (state.readOnly)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
const currentBlock = getActiveNoteBlock(state)
|
const currentBlock = getActiveNoteBlock(state)
|
||||||
let delimText;
|
let delimText;
|
||||||
if (currentBlock) {
|
if (currentBlock) {
|
||||||
@ -18,9 +18,9 @@ export const insertNewBlockAtCursor = ({ state, dispatch }) => {
|
|||||||
} else {
|
} else {
|
||||||
delimText = "\n∞∞∞text-a\n"
|
delimText = "\n∞∞∞text-a\n"
|
||||||
}
|
}
|
||||||
dispatch(state.replaceSelection(delimText),
|
dispatch(state.replaceSelection(delimText),
|
||||||
{
|
{
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: "input",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -28,9 +28,32 @@ export const insertNewBlockAtCursor = ({ state, dispatch }) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const addNewBlockBeforeCurrent = ({ state, dispatch }) => {
|
||||||
|
console.log("addNewBlockBeforeCurrent")
|
||||||
|
if (state.readOnly)
|
||||||
|
return false
|
||||||
|
|
||||||
|
const block = getActiveNoteBlock(state)
|
||||||
|
const delimText = "\n∞∞∞text-a\n"
|
||||||
|
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: {
|
||||||
|
from: block.delimiter.from,
|
||||||
|
insert: delimText,
|
||||||
|
},
|
||||||
|
selection: EditorSelection.cursor(block.delimiter.from + delimText.length),
|
||||||
|
annotations: [heynoteEvent.of(ADD_NEW_BLOCK)],
|
||||||
|
}, {
|
||||||
|
scrollIntoView: true,
|
||||||
|
userEvent: "input",
|
||||||
|
}))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
|
export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
|
||||||
if (state.readOnly)
|
if (state.readOnly)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
const block = getActiveNoteBlock(state)
|
const block = getActiveNoteBlock(state)
|
||||||
const delimText = "\n∞∞∞text-a\n"
|
const delimText = "\n∞∞∞text-a\n"
|
||||||
|
|
||||||
@ -41,7 +64,47 @@ export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
|
|||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(block.content.to + delimText.length)
|
selection: EditorSelection.cursor(block.content.to + delimText.length)
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
|
userEvent: "input",
|
||||||
|
}))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addNewBlockBeforeFirst = ({ state, dispatch }) => {
|
||||||
|
if (state.readOnly)
|
||||||
|
return false
|
||||||
|
|
||||||
|
const block = getFirstNoteBlock(state)
|
||||||
|
const delimText = "\n∞∞∞text-a\n"
|
||||||
|
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: {
|
||||||
|
from: block.delimiter.from,
|
||||||
|
insert: delimText,
|
||||||
|
},
|
||||||
|
selection: EditorSelection.cursor(delimText.length),
|
||||||
|
annotations: [heynoteEvent.of(ADD_NEW_BLOCK)],
|
||||||
|
}, {
|
||||||
|
scrollIntoView: true,
|
||||||
|
userEvent: "input",
|
||||||
|
}))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addNewBlockAfterLast = ({ state, dispatch }) => {
|
||||||
|
if (state.readOnly)
|
||||||
|
return false
|
||||||
|
const block = getLastNoteBlock(state)
|
||||||
|
const delimText = "\n∞∞∞text-a\n"
|
||||||
|
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: {
|
||||||
|
from: block.content.to,
|
||||||
|
insert: delimText,
|
||||||
|
},
|
||||||
|
selection: EditorSelection.cursor(block.content.to + delimText.length)
|
||||||
|
}, {
|
||||||
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: "input",
|
||||||
}))
|
}))
|
||||||
return true;
|
return true;
|
||||||
@ -50,7 +113,7 @@ export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
|
|||||||
export function changeLanguageTo(state, dispatch, block, language, auto) {
|
export function changeLanguageTo(state, dispatch, block, language, auto) {
|
||||||
if (state.readOnly)
|
if (state.readOnly)
|
||||||
return false
|
return false
|
||||||
const delimRegex = /^\n∞∞∞[a-z]{0,16}(-a)?\n/g
|
const delimRegex = /^\n∞∞∞[a-z]+?(-a)?\n/g
|
||||||
if (state.doc.sliceString(block.delimiter.from, block.delimiter.to).match(delimRegex)) {
|
if (state.doc.sliceString(block.delimiter.from, block.delimiter.to).match(delimRegex)) {
|
||||||
//console.log("changing language to", language)
|
//console.log("changing language to", language)
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
|
@ -139,6 +139,10 @@ export class HeynoteEditor {
|
|||||||
return this.view.state.facet(blockState)
|
return this.view.state.facet(blockState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCursorPosition() {
|
||||||
|
return this.view.state.selection.main.head
|
||||||
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.view.focus()
|
this.view.focus()
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
insertNewBlockAtCursor,
|
insertNewBlockAtCursor,
|
||||||
addNewBlockAfterCurrent,
|
addNewBlockBeforeCurrent, addNewBlockAfterCurrent,
|
||||||
|
addNewBlockBeforeFirst, addNewBlockAfterLast,
|
||||||
moveLineUp, moveLineDown,
|
moveLineUp, moveLineDown,
|
||||||
selectAll,
|
selectAll,
|
||||||
gotoPreviousBlock, gotoNextBlock,
|
gotoPreviousBlock, gotoNextBlock,
|
||||||
@ -38,8 +39,11 @@ export function heynoteKeymap(editor) {
|
|||||||
return keymapFromSpec([
|
return keymapFromSpec([
|
||||||
["Tab", indentMore],
|
["Tab", indentMore],
|
||||||
["Shift-Tab", indentLess],
|
["Shift-Tab", indentLess],
|
||||||
|
["Alt-Shift-Enter", addNewBlockBeforeFirst],
|
||||||
|
["Mod-Shift-Enter", addNewBlockAfterLast],
|
||||||
|
["Alt-Enter", addNewBlockBeforeCurrent],
|
||||||
["Mod-Enter", addNewBlockAfterCurrent],
|
["Mod-Enter", addNewBlockAfterCurrent],
|
||||||
["Mod-Shift-Enter", insertNewBlockAtCursor],
|
["Mod-Alt-Enter", insertNewBlockAtCursor],
|
||||||
["Mod-a", selectAll],
|
["Mod-a", selectAll],
|
||||||
["Alt-ArrowUp", moveLineUp],
|
["Alt-ArrowUp", moveLineUp],
|
||||||
["Alt-ArrowDown", moveLineDown],
|
["Alt-ArrowDown", moveLineDown],
|
||||||
|
117
tests/block-creation.spec.js
Normal file
117
tests/block-creation.spec.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import {expect, test} from "@playwright/test";
|
||||||
|
import {HeynotePage} from "./test-utils.js";
|
||||||
|
|
||||||
|
let heynotePage
|
||||||
|
|
||||||
|
test.beforeEach(async ({page}) => {
|
||||||
|
console.log("beforeEach")
|
||||||
|
heynotePage = new HeynotePage(page)
|
||||||
|
await heynotePage.goto()
|
||||||
|
|
||||||
|
expect((await heynotePage.getBlocks()).length).toBe(1)
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
|
/* from A */
|
||||||
|
test("create block before current (A)", async ({page}) => {
|
||||||
|
// select the first block
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await runTest(page, "Alt+Enter", ['D', 'A', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block after current (A)", async ({page}) => {
|
||||||
|
// select the first block
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await runTest(page, "Mod+Enter", ['A', 'D', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
/* from B */
|
||||||
|
test("create block before current (B)", async ({page}) => {
|
||||||
|
// select the second block
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await runTest(page, "Alt+Enter", ['A', 'D', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block after current (B)", async ({page}) => {
|
||||||
|
// select the second block
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
await runTest(page, "Mod+Enter", ['A', 'B', 'D', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
/* from C */
|
||||||
|
test("create block before current (C)", async ({page}) => {
|
||||||
|
await runTest(page, "Alt+Enter", ['A', 'B', 'D', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block after current (C)", async ({page}) => {
|
||||||
|
await runTest(page, "Mod+Enter", ['A', 'B', 'C', 'D'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block before first", async ({page}) => {
|
||||||
|
await runTest(page, "Alt+Shift+Enter", ['D', 'A', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block after last", async ({page}) => {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
await page.locator("body").press("ArrowUp")
|
||||||
|
}
|
||||||
|
await runTest(page, "Mod+Shift+Enter", ['A', 'B', 'C', 'D'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block before Markdown block", async ({page}) => {
|
||||||
|
await heynotePage.setContent(`
|
||||||
|
∞∞∞markdown
|
||||||
|
# Markdown!
|
||||||
|
`)
|
||||||
|
await page.locator("body").press("Alt+Enter")
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
expect(await heynotePage.getCursorPosition()).toBe(11)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create block before first Markdown block", async ({page}) => {
|
||||||
|
await heynotePage.setContent(`
|
||||||
|
∞∞∞markdown
|
||||||
|
# Markdown!
|
||||||
|
∞∞∞text
|
||||||
|
`)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await page.locator("body").press("ArrowDown")
|
||||||
|
}
|
||||||
|
await page.locator("body").press("Alt+Shift+Enter")
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
expect(await heynotePage.getCursorPosition()).toBe(11)
|
||||||
|
})
|
||||||
|
|
||||||
|
const runTest = async (page, key, expectedBlocks) => {
|
||||||
|
// create a new block
|
||||||
|
await page.locator("body").press(key.replace("Mod", heynotePage.isMac ? "Meta" : "Control"))
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
await page.locator("body").pressSequentially("Block D")
|
||||||
|
|
||||||
|
// check that blocks are created
|
||||||
|
expect((await heynotePage.getBlocks()).length).toBe(4)
|
||||||
|
|
||||||
|
// check that the content of each block is correct
|
||||||
|
for (const expectedBlock of expectedBlocks) {
|
||||||
|
const index = expectedBlocks.indexOf(expectedBlock);
|
||||||
|
expect(await heynotePage.getBlockContent(index)).toBe(`Block ${expectedBlock}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that only one block delimiter widget has the class first
|
||||||
|
await expect(await page.locator("css=.heynote-block-start.first")).toHaveCount(1)
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,10 @@ export class HeynotePage {
|
|||||||
await this.page.evaluate((content) => window._heynote_editor.setContent(content), content)
|
await this.page.evaluate((content) => window._heynote_editor.setContent(content), content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCursorPosition() {
|
||||||
|
return await this.page.evaluate(() => window._heynote_editor.getCursorPosition())
|
||||||
|
}
|
||||||
|
|
||||||
async getBlockContent(blockIndex) {
|
async getBlockContent(blockIndex) {
|
||||||
const blocks = await this.getBlocks()
|
const blocks = await this.getBlocks()
|
||||||
const content = await this.getContent()
|
const content = await this.getContent()
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"allowJs": true
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"," shared-utils"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./tsconfig.node.json" }
|
{ "path": "./tsconfig.node.json" }
|
||||||
]
|
]
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts", "package.json", "electron"]
|
"include": ["vite.config.ts", "package.json", "electron", "shared-utils"]
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,35 @@ import license from 'rollup-plugin-license'
|
|||||||
import pkg from './package.json'
|
import pkg from './package.json'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
import { keyHelpStr } from "./shared-utils/key-helper";
|
||||||
|
|
||||||
rmSync('dist-electron', { recursive: true, force: true })
|
rmSync('dist-electron', { recursive: true, force: true })
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV === "development" || !!process.env.VSCODE_DEBUG
|
const isDevelopment = process.env.NODE_ENV === "development" || !!process.env.VSCODE_DEBUG
|
||||||
const isProduction = process.env.NODE_ENV === "production"
|
const isProduction = process.env.NODE_ENV === "production"
|
||||||
|
|
||||||
|
const updateReadmeKeybinds = async () => {
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const readmePath = path.resolve(__dirname, 'README.md')
|
||||||
|
let readme = fs.readFileSync(readmePath, 'utf-8')
|
||||||
|
const keybindsRegex = /^(### What are the default keyboard shortcuts\?\s*).*?^(```\s+#)/gms
|
||||||
|
const shortcuts = `$1**On Mac**
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
${keyHelpStr('darwin')}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**On Windows and Linux**
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
${keyHelpStr('win32')}
|
||||||
|
$2`
|
||||||
|
|
||||||
|
readme = readme.replace(keybindsRegex, shortcuts)
|
||||||
|
fs.writeFileSync(readmePath, readme)
|
||||||
|
}
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -23,6 +47,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
updateReadmeKeybinds(),
|
||||||
electron([
|
electron([
|
||||||
{
|
{
|
||||||
// Main-Process entry file of the Electron App.
|
// Main-Process entry file of the Electron App.
|
||||||
|
Loading…
Reference in New Issue
Block a user