2
0
mirror of https://github.com/Ionaru/easy-markdown-editor synced 2025-12-14 05:04:54 -07:00
easy-markdown-editor/src/utils/toggle-block.ts

162 lines
4.6 KiB
TypeScript
Raw Normal View History

2023-04-28 02:51:04 +02:00
import { EditorSelection, EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import escapeStringRegexp from 'escape-string-regexp';
2024-06-07 12:37:45 +02:00
/**
* Checks whether the selection matches a formatted block of text.
*/
2023-04-28 02:51:04 +02:00
export const checkBlock = (
editor: EditorView,
characters: string,
2024-06-07 12:37:45 +02:00
minimal = false,
2023-04-28 02:51:04 +02:00
): RegExpExecArray | null => {
// Checks whether the selection matches a block of formatted text.
const { state } = editor;
2024-06-07 12:37:45 +02:00
const { from, to } = getExpandedSelection(state, characters, minimal);
2023-04-28 02:51:04 +02:00
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) {
2024-06-07 12:37:45 +02:00
doubleCharactersCheckResult = checkBlock(editor, characters.repeat(2), minimal);
tripleCharactersCheckResult = checkBlock(editor, characters.repeat(3), minimal);
2023-04-28 02:51:04 +02:00
}
if (
(checkResult &&
doubleCharactersCheckResult &&
tripleCharactersCheckResult) ||
(checkResult &&
!doubleCharactersCheckResult &&
tripleCharactersCheckResult) ||
(checkResult &&
!doubleCharactersCheckResult &&
!tripleCharactersCheckResult)
) {
return checkResult;
}
return null;
};
2024-06-07 12:37:45 +02:00
/**
* Toggles a block of text to be formatted.
*/
2023-04-28 02:51:04 +02:00
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);
2024-06-07 12:37:45 +02:00
console.log(from, to, text, textMatch);
2023-04-28 02:51:04 +02:00
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();
};
2024-06-07 12:37:45 +02:00
/**
* Attempts to expand the cursor selection to the nearest logical block of text needs to be formatted.
*/
2023-04-28 02:51:04 +02:00
export const getExpandedSelection = (
state: EditorState,
characters: string,
2024-06-07 12:37:45 +02:00
minimal = false,
2023-04-28 02:51:04 +02:00
): { from: number; to: number } => {
let { from, to } = state.selection.main;
let fromPosition = from;
2024-06-07 12:37:45 +02:00
while (fromPosition >= 0) {
2023-04-28 02:51:04 +02:00
const newText = state.sliceDoc(fromPosition, to);
2024-06-07 12:37:45 +02:00
if (newText.startsWith('\n') || newText.startsWith('\t')) {
2023-04-28 02:51:04 +02:00
fromPosition++;
break;
2024-06-07 12:37:45 +02:00
} else if (minimal && newText.startsWith(' ')) {
fromPosition++;
break;
} else if (newText.startsWith(characters + ' ')) {
fromPosition += characters.length + 1;
break;
} else if (
2023-04-28 02:51:04 +02:00
newText.length > characters.length &&
newText.startsWith(characters)
) {
break;
}
2024-06-07 12:37:45 +02:00
2023-04-28 02:51:04 +02:00
fromPosition--;
}
from = fromPosition;
let toPosition = to;
while (toPosition < state.doc.length) {
const newText = state.sliceDoc(from, toPosition);
2024-06-07 12:37:45 +02:00
if (newText.endsWith('\n') || newText.endsWith('\t')) {
2023-04-28 02:51:04 +02:00
toPosition--;
break;
2024-06-07 12:37:45 +02:00
} else if (minimal && newText.endsWith(' ')) {
toPosition--;
break;
} else if (
2023-04-28 02:51:04 +02:00
newText.length > characters.length &&
newText.endsWith(characters)
) {
break;
}
toPosition++;
}
to = toPosition;
2024-06-07 12:37:45 +02:00
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;
}
2023-04-28 02:51:04 +02:00
return { from, to };
};