planning
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s

This commit is contained in:
2024-10-14 09:15:30 +02:00
parent bcba00a730
commit 6e64e138e2
21059 changed files with 2317811 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
function defaultEmptyBlock(text = '') {
return {
type: 'paragraph',
children: [{ text }],
};
}
export default defaultEmptyBlock;

View File

@@ -0,0 +1,56 @@
import isHotkey from 'is-hotkey';
import { Editor, Transforms } from 'slate';
import keyDownEnter from './keyDownEnter';
import keyDownBackspace from './keyDownBackspace';
import isCursorInNonDefaultBlock from '../locations/isCursorInNonDefaultBlock';
import toggleBlock from './toggleBlock';
import isCursorCollapsedAfterSoftBreak from '../locations/isCursorCollapsedAfterSoftBreak';
const HEADING_HOTKEYS = {
'mod+1': 'heading-one',
'mod+2': 'heading-two',
'mod+3': 'heading-three',
'mod+4': 'heading-four',
'mod+5': 'heading-five',
'mod+6': 'heading-six',
};
function keyDown(event, editor) {
if (!editor.selection) return;
for (const hotkey in HEADING_HOTKEYS) {
if (isHotkey(hotkey, event)) {
toggleBlock(editor, HEADING_HOTKEYS[hotkey]);
event.preventDefault();
return false;
}
}
if (isHotkey('backspace', event) && isCursorCollapsedAfterSoftBreak(editor)) {
const [, path] = Editor.previous(editor);
Transforms.removeNodes(editor, { at: path });
event.preventDefault();
return false;
}
if (!isCursorInNonDefaultBlock(editor)) return;
if (isHotkey('enter', event)) {
const eventIntercepted = keyDownEnter(editor);
if (eventIntercepted) {
event.preventDefault();
return false;
}
}
if (isHotkey('backspace', event)) {
const eventIntercepted = keyDownBackspace(editor);
if (eventIntercepted) {
event.preventDefault();
return false;
}
}
}
export default keyDown;

View File

@@ -0,0 +1,27 @@
import { Transforms } from 'slate';
import unwrapIfCursorAtStart from '../transforms/unwrapIfCursorAtStart';
import isCursorAtStartOfNonEmptyHeading from '../locations/isCursorAtStartOfNonEmptyHeading';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
import areCurrentAndPreviousBlocksOfType from '../locations/areCurrentAndPreviousBlocksOfType';
import isCursorAtStartOfBlockType from '../locations/isCursorAtStartOfBlockType';
function keyDownBackspace(editor) {
if (!editor.selection) return;
if (isCursorAtStartOfNonEmptyHeading(editor)) {
return;
}
if (
isCursorAtStartOfBlockType(editor, 'quote') &&
areCurrentAndPreviousBlocksOfType(editor, 'quote')
) {
Transforms.mergeNodes(editor, lowestMatchedAncestor(editor, 'quote'));
return true;
}
return unwrapIfCursorAtStart(editor, true);
}
export default keyDownBackspace;

View File

@@ -0,0 +1,26 @@
import isCursorInBlockType from '../locations/isCursorInBlockType';
import splitIntoParagraph from '../transforms/splitIntoParagraph';
import unwrapIfCursorAtStart from '../transforms/unwrapIfCursorAtStart';
import isCursorAtEndOfParagraph from '../locations/isCursorAtEndOfParagraph';
function keyDownEnter(editor) {
if (!editor.selection) return;
if (isCursorInBlockType(editor, 'heading', true)) {
return handleHeading(editor);
}
return unwrapIfCursorAtStart(editor);
}
function handleHeading(editor) {
if (isCursorAtEndOfParagraph(editor)) {
// split into paragraph if cursor is at the end of heading
splitIntoParagraph(editor);
return true;
}
return;
}
export default keyDownEnter;

View File

@@ -0,0 +1,39 @@
import { Range, Transforms } from 'slate';
import isCursorInBlockType from '../locations/isCursorInBlockType';
import getListTypeAtCursor from '../locations/getListTypeAtCursor';
import wrapListItemsInBlock from '../transforms/wrapListItemsInBlock';
function toggleBlock(editor, type) {
const { selection } = editor;
if (!selection) return;
const isHeading = type.startsWith('heading-');
const isActive = isCursorInBlockType(editor, type, isHeading, Range.isExpanded(selection));
const listType = getListTypeAtCursor(editor);
// headings do not contain paragraphs so they could be converted, not wrapped/unwrapped
if (isHeading) {
Transforms.setNodes(editor, { type: isActive ? 'paragraph' : type });
return;
}
const { focus, anchor } = selection;
if (
!isActive &&
listType &&
focus.path[focus.path.length - 3] != anchor.path[anchor.path.length - 3]
) {
return wrapListItemsInBlock(editor, type, listType);
}
if (!isActive) {
return Transforms.wrapNodes(editor, { type });
}
Transforms.unwrapNodes(editor, { match: n => n.type === type });
return;
}
export default toggleBlock;

View File

@@ -0,0 +1,15 @@
import { Editor } from 'slate';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
function areCurrentAndPreviousBlocksOfType(editor, type) {
const { selection } = editor;
if (!selection) return false;
const [current] = Editor.nodes(editor, lowestMatchedAncestor(editor, 'block'));
const previous = Editor.previous(editor, lowestMatchedAncestor(editor, type));
return current && previous && current[0].type === previous[0].type;
}
export default areCurrentAndPreviousBlocksOfType;

View File

@@ -0,0 +1,11 @@
import { Editor } from 'slate';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
function getListTypeAtCursor(editor) {
const list = Editor.above(editor, lowestMatchedAncestor(editor, 'list'));
if (!list) return null;
return list[0].type;
}
export default getListTypeAtCursor;

View File

@@ -0,0 +1,14 @@
import { Editor } from 'slate';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
function isCursorAtEndOfParagraph(editor) {
const { selection } = editor;
if (!selection) return false;
const paragraph = Editor.above(editor, lowestMatchedAncestor(editor, 'paragraph'));
return !!paragraph && Editor.isEnd(editor, editor.selection.focus, paragraph[1]);
}
export default isCursorAtEndOfParagraph;

View File

@@ -0,0 +1,14 @@
import { Editor } from 'slate';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
function isCursorAtStartOfBlockType(editor, type) {
const { selection } = editor;
if (!selection) return false;
const block = Editor.above(editor, lowestMatchedAncestor(editor, type));
return !!block && Editor.isStart(editor, editor.selection.focus, block[1]);
}
export default isCursorAtStartOfBlockType;

View File

@@ -0,0 +1,22 @@
import { Editor, Element } from 'slate';
function isCursorAtStartOfNonEmptyHeading(editor) {
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(
Editor.nodes(editor, {
match: n =>
Element.isElement(n) && Editor.isBlock(editor, n) && `${n.type}`.startsWith('heading-'),
mode: 'lowest',
}),
);
return (
!!match &&
Editor.isStart(editor, editor.selection.focus, match[1]) &&
!Editor.isEmpty(editor, match[0])
);
}
export default isCursorAtStartOfNonEmptyHeading;

View File

@@ -0,0 +1,13 @@
import { Editor, Range } from 'slate';
function isCursorCollapsedAfterSoftBreak(editor) {
const { selection } = editor;
if (!selection) return false;
if (Range.isExpanded(selection)) return false;
const previous = Editor.previous(editor);
return previous && previous[0].type == 'break';
}
export default isCursorCollapsedAfterSoftBreak;

View File

@@ -0,0 +1,27 @@
import { Editor, Element } from 'slate';
function isCursorInBlockType(editor, type, ignoreHeadings, ignoreLists) {
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(
Editor.nodes(editor, {
match: n =>
Element.isElement(n) &&
Editor.isBlock(editor, n) &&
n.type !== 'paragraph' &&
n.type !== 'list-item' &&
(ignoreHeadings || !`${n.type}`.startsWith('heading-')) &&
(!ignoreLists || !`${n.type}`.endsWith('-list')),
mode: 'lowest',
}),
);
return (
!!match &&
(match[0].type === type ||
`${match[0].type}`.startsWith(`${type}-` || `${match[0].type}`.endsWith(`-${type}`)))
);
}
export default isCursorInBlockType;

View File

@@ -0,0 +1,17 @@
import { Editor, Element } from 'slate';
function isCursorInNonDefaultBlock(editor) {
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(
Editor.nodes(editor, {
match: n => Element.isElement(n) && Editor.isBlock(editor, n) && n.type !== 'paragraph',
mode: 'lowest',
}),
);
return !!match && !Editor.isEditor(match[0]);
}
export default isCursorInNonDefaultBlock;

View File

@@ -0,0 +1,13 @@
import { Editor, Transforms } from 'slate';
function splitIntoParagraph(editor) {
Editor.withoutNormalizing(editor, () => {
Transforms.splitNodes(editor, { always: true });
Transforms.setNodes(editor, { type: 'paragraph' });
});
Editor.normalize(editor, { force: true });
return true;
}
export default splitIntoParagraph;

View File

@@ -0,0 +1,43 @@
import { Editor, Transforms } from 'slate';
import lowestMatchedAncestor from '../../matchers/lowestMatchedAncestor';
function unwrapIfCursorAtStart(editor, mergeWithPrevious = false) {
if (editor.selection.anchor.offset !== 0) return false;
let [node, path] = Editor.above(editor, lowestMatchedAncestor(editor, 'non-default'));
if (path.length == 0) return false;
const isHeading = `${node.type}`.startsWith('heading-');
if (isHeading) {
Transforms.setNodes(editor, { type: 'paragraph' });
return false;
}
const isBlock = Editor.isBlock(editor, node);
const [parentBlock, parentBlockPath] = Editor.above(
editor,
lowestMatchedAncestor(editor, 'block'),
);
if (!isBlock) {
if (!Editor.isStart(editor, path, parentBlockPath)) {
return false;
}
[node, path] = [parentBlock, parentBlockPath];
}
Editor.withoutNormalizing(editor, () => {
Transforms.unwrapNodes(editor, { match: n => n.type === node.type, split: true });
if (mergeWithPrevious) {
Transforms.mergeNodes(editor);
}
});
Editor.normalize(editor, { force: true });
return true;
}
export default unwrapIfCursorAtStart;

View File

@@ -0,0 +1,11 @@
import { Editor, Transforms } from 'slate';
function wrapListItemsInBlock(editor, blockType, listType) {
Editor.withoutNormalizing(editor, () => {
Transforms.wrapNodes(editor, { type: listType });
Transforms.wrapNodes(editor, { type: blockType }, { match: n => n.type === listType });
Transforms.liftNodes(editor, { match: n => n.type === blockType });
});
Editor.normalize(editor, { force: true });
}
export default wrapListItemsInBlock;

View File

@@ -0,0 +1,15 @@
import keyDown from './events/keyDown';
import toggleBlock from './events/toggleBlock';
function withBlocks(editor) {
if (editor.keyDownHandlers === undefined) {
editor.keyDownHandlers = [];
}
editor.keyDownHandlers.push((event, editor) => keyDown(event, editor));
editor.toggleBlock = type => toggleBlock(editor, type);
return editor;
}
export default withBlocks;