2
0
mirror of https://github.com/Ionaru/easy-markdown-editor synced 2025-12-13 20:54:54 -07:00
easy-markdown-editor/src/utils/toggle-block.ts
Jeroen akkerman 779331aaa0 Some fixes
2024-06-07 12:37:45 +02:00

162 lines
4.6 KiB
TypeScript

import { EditorSelection, EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import escapeStringRegexp from 'escape-string-regexp';
/**
* Checks whether the selection matches a formatted block of text.
*/
export const checkBlock = (
editor: EditorView,
characters: string,
minimal = false,
): RegExpExecArray | null => {
// Checks whether the selection matches a block of formatted text.
const { state } = editor;
const { from, to } = getExpandedSelection(state, characters, minimal);
const text = state.sliceDoc(from, to);
const escapedCharacters = escapeStringRegexp(characters);
const regularExpression = new RegExp(
`^${escapedCharacters}(.*)${escapedCharacters}$`,
'gs',
);
const checkResult = regularExpression.exec(text);
let doubleCharactersCheckResult = null;
let tripleCharactersCheckResult = null;
if (characters.length === 1) {
doubleCharactersCheckResult = checkBlock(editor, characters.repeat(2), minimal);
tripleCharactersCheckResult = checkBlock(editor, characters.repeat(3), minimal);
}
if (
(checkResult &&
doubleCharactersCheckResult &&
tripleCharactersCheckResult) ||
(checkResult &&
!doubleCharactersCheckResult &&
tripleCharactersCheckResult) ||
(checkResult &&
!doubleCharactersCheckResult &&
!tripleCharactersCheckResult)
) {
return checkResult;
}
return null;
};
/**
* Toggles a block of text to be formatted.
*/
export const toggleBlock = (editor: EditorView, characters: string) => {
const { state } = editor;
const { from, to } = getExpandedSelection(state, characters);
const text = state.sliceDoc(from, to);
const textMatch = checkBlock(editor, characters);
console.log(from, to, text, textMatch);
editor.dispatch(
state.changeByRange(() =>
textMatch
? {
changes: [{ from, insert: textMatch[1], to }],
range: EditorSelection.range(
from,
to - (characters.length + characters.length),
),
}
: {
changes: [
{
from,
insert: `${characters}${text}${characters}`,
to,
},
],
range: EditorSelection.range(
from,
to + (characters.length + characters.length),
),
},
),
);
editor.focus();
};
/**
* Attempts to expand the cursor selection to the nearest logical block of text needs to be formatted.
*/
export const getExpandedSelection = (
state: EditorState,
characters: string,
minimal = false,
): { from: number; to: number } => {
let { from, to } = state.selection.main;
let fromPosition = from;
while (fromPosition >= 0) {
const newText = state.sliceDoc(fromPosition, to);
if (newText.startsWith('\n') || newText.startsWith('\t')) {
fromPosition++;
break;
} else if (minimal && newText.startsWith(' ')) {
fromPosition++;
break;
} else if (newText.startsWith(characters + ' ')) {
fromPosition += characters.length + 1;
break;
} else if (
newText.length > characters.length &&
newText.startsWith(characters)
) {
break;
}
fromPosition--;
}
from = fromPosition;
let toPosition = to;
while (toPosition < state.doc.length) {
const newText = state.sliceDoc(from, toPosition);
if (newText.endsWith('\n') || newText.endsWith('\t')) {
toPosition--;
break;
} else if (minimal && newText.endsWith(' ')) {
toPosition--;
break;
} else if (
newText.length > characters.length &&
newText.endsWith(characters)
) {
break;
}
toPosition++;
}
to = toPosition;
return correctInvalidSelection({ from, to });
};
/**
* Sometimes the selection expands beyond the start of the document, which causes an error.
* This function corrects the selection if it is invalid.
*/
const correctInvalidSelection = ({
from,
to,
}: {
from: number;
to: number;
}): { from: number; to: number } => {
if (from < 0) {
from = 0;
}
return { from, to };
};