mirror of
https://github.com/Ionaru/easy-markdown-editor
synced 2025-06-27 13:11:01 -06:00
Some fixes
This commit is contained in:
parent
3aa82e009e
commit
779331aaa0
724
package-lock.json
generated
724
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@
|
||||
"@types/node": "^18.16.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||
"@vitest/coverage-c8": "^0.30.1",
|
||||
"@vitest/coverage-v8": "^0.34.6",
|
||||
"@vitest/ui": "^0.30.1",
|
||||
"cypress": "^12.11.0",
|
||||
"eslint": "^8.39.0",
|
||||
@ -65,6 +66,6 @@
|
||||
"sass": "^1.62.1",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vitest": "^0.30.1"
|
||||
"vitest": "^0.34.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* eslint-disable sort-keys,@typescript-eslint/member-ordering,max-classes-per-file */
|
||||
/* eslint-disable sort-keys,@typescript-eslint/member-ordering */
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
import {
|
||||
HighlightStyle,
|
||||
@ -10,8 +10,8 @@ import { drawSelection, EditorView } from '@codemirror/view';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import { AlreadyConstructedError } from "./errors/already-constructed-error";
|
||||
import { NotConstructedError } from "./errors/not-constructed-error";
|
||||
import { AlreadyConstructedError } from './errors/already-constructed-error';
|
||||
import { NotConstructedError } from './errors/not-constructed-error';
|
||||
import { importDefaultToolbar, importToolbar } from './imports';
|
||||
import { InputOptions, Options } from './options';
|
||||
|
||||
@ -62,10 +62,6 @@ export class EasyMDE {
|
||||
private static verifyAndReturnElement(
|
||||
element?: HTMLElement,
|
||||
): HTMLTextAreaElement {
|
||||
if (!element) {
|
||||
throw new Error('EasyMDE: Parameter "element" is null.');
|
||||
}
|
||||
|
||||
if (!(element instanceof HTMLTextAreaElement)) {
|
||||
throw new TypeError(
|
||||
'EasyMDE: Parameter "element" must be a TextArea.',
|
||||
|
@ -4,7 +4,7 @@ import { EditorView } from '@codemirror/view';
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { checkBlock } from './toggle-block';
|
||||
import { checkBlock, toggleBlock } from './toggle-block';
|
||||
|
||||
const getEditor = (
|
||||
document: string,
|
||||
@ -28,6 +28,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
|
||||
const wordSimple = `${character}foo${character}`;
|
||||
const wordSimpleWithSpace = `${character} foo ${character}`;
|
||||
const wordSquished = `bla${character}foo${character}bla`;
|
||||
const wordMultiWords = `${character}foo boo${character}`;
|
||||
const wordMultiWord = `${character}foo${character} ${character}boo${character}`;
|
||||
const wordMultiLine = `${character}foo${character}\n${character}boo${character}`;
|
||||
const wordMultiTab = `${character}foo${character}\t${character}boo${character}`;
|
||||
@ -234,6 +235,33 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
|
||||
expect(result).toBeTruthy();
|
||||
expect(result[1]).toBe('boo');
|
||||
});
|
||||
|
||||
it('must detect an active block with selection on the first of a multi-word text', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const anchor = Math.floor(wordMultiWords.split(' ')[0].length / 2);
|
||||
const result = checkBlock(
|
||||
getEditor(wordMultiWords, { anchor }),
|
||||
character,
|
||||
);
|
||||
expect(result).toBeTruthy();
|
||||
expect(result[1]).toBe('foo boo');
|
||||
});
|
||||
|
||||
it('must detect an active block with selection on the second of a multi-word text', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const multiWordSplit = wordMultiWords.split(' ');
|
||||
const anchor =
|
||||
Math.floor(multiWordSplit[1].length / 2) +
|
||||
multiWordSplit[0].length;
|
||||
const result = checkBlock(
|
||||
getEditor(wordMultiWords, { anchor }),
|
||||
character,
|
||||
);
|
||||
expect(result).toBeTruthy();
|
||||
expect(result[1]).toBe('foo boo');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@ -290,3 +318,110 @@ describe('checkBlock special cases', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe.skip('toggleBlock', () => {
|
||||
it('must toggle a single word on with the selection at the start', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Word', { anchor: 0 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('*Word*');
|
||||
});
|
||||
|
||||
it('must toggle a single word on with the selection in the middle', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Word', { anchor: 2 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('*Word*');
|
||||
});
|
||||
|
||||
it('must toggle a single word on with the selection at the end', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Word', { anchor: 4 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('*Word*');
|
||||
});
|
||||
|
||||
it('must toggle a single word on in between other words', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Many words are typed here', { anchor: 7 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many *words* are typed here');
|
||||
});
|
||||
|
||||
it('must toggle a single word on in between other words big selection', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Many words are typed here', {
|
||||
anchor: 5,
|
||||
head: 10,
|
||||
});
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many *words* are typed here');
|
||||
});
|
||||
|
||||
it('must toggle a single word off with the selection at the start', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('*Word*', { anchor: 0 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Word');
|
||||
});
|
||||
|
||||
it('must toggle a single word off with the selection in the middle', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('*Word*', { anchor: 3 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Word');
|
||||
});
|
||||
|
||||
it('must toggle a single word off with the selection at the end', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('*Word*', { anchor: 6 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Word');
|
||||
});
|
||||
|
||||
it('must toggle a single word off in between other words', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Many *words* are typed here', { anchor: 8 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many words are typed here');
|
||||
});
|
||||
|
||||
it('must toggle a single word off in between other words big selection', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('Many *words* are typed here', {
|
||||
anchor: 6,
|
||||
head: 11,
|
||||
});
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many words are typed here');
|
||||
});
|
||||
|
||||
it('must toggle a formatted sentence off', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('*Many words are typed here*', { anchor: 8 });
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many words are typed here');
|
||||
});
|
||||
|
||||
it('must toggle a formatted sentence off in a big selection', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const editor = getEditor('*Many words are typed here*', {
|
||||
anchor: 6,
|
||||
head: 11,
|
||||
});
|
||||
toggleBlock(editor, '*');
|
||||
expect(editor.state.doc.toString()).toBe('Many words are typed here');
|
||||
});
|
||||
});
|
||||
|
@ -2,14 +2,18 @@ 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);
|
||||
const { from, to } = getExpandedSelection(state, characters, minimal);
|
||||
const text = state.sliceDoc(from, to);
|
||||
const escapedCharacters = escapeStringRegexp(characters);
|
||||
const regularExpression = new RegExp(
|
||||
@ -22,8 +26,8 @@ export const checkBlock = (
|
||||
let doubleCharactersCheckResult = null;
|
||||
let tripleCharactersCheckResult = null;
|
||||
if (characters.length === 1) {
|
||||
doubleCharactersCheckResult = checkBlock(editor, characters.repeat(2));
|
||||
tripleCharactersCheckResult = checkBlock(editor, characters.repeat(3));
|
||||
doubleCharactersCheckResult = checkBlock(editor, characters.repeat(2), minimal);
|
||||
tripleCharactersCheckResult = checkBlock(editor, characters.repeat(3), minimal);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -43,11 +47,15 @@ export const checkBlock = (
|
||||
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(() =>
|
||||
@ -78,29 +86,36 @@ export const toggleBlock = (editor: EditorView, characters: string) => {
|
||||
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) {
|
||||
while (fromPosition >= 0) {
|
||||
const newText = state.sliceDoc(fromPosition, to);
|
||||
if (
|
||||
newText.startsWith('\n') ||
|
||||
newText.startsWith('\t') ||
|
||||
newText.startsWith(' ')
|
||||
) {
|
||||
|
||||
if (newText.startsWith('\n') || newText.startsWith('\t')) {
|
||||
fromPosition++;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
} 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;
|
||||
@ -108,15 +123,13 @@ export const getExpandedSelection = (
|
||||
let toPosition = to;
|
||||
while (toPosition < state.doc.length) {
|
||||
const newText = state.sliceDoc(from, toPosition);
|
||||
if (
|
||||
newText.endsWith('\n') ||
|
||||
newText.endsWith('\t') ||
|
||||
newText.endsWith(' ')
|
||||
) {
|
||||
if (newText.endsWith('\n') || newText.endsWith('\t')) {
|
||||
toPosition--;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
} else if (minimal && newText.endsWith(' ')) {
|
||||
toPosition--;
|
||||
break;
|
||||
} else if (
|
||||
newText.length > characters.length &&
|
||||
newText.endsWith(characters)
|
||||
) {
|
||||
@ -126,5 +139,23 @@ export const getExpandedSelection = (
|
||||
}
|
||||
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 };
|
||||
};
|
||||
|
@ -47,6 +47,8 @@ last
|
||||
```js
|
||||
const x = "moooo";
|
||||
```
|
||||
|
||||
This is a longer sentence!
|
||||
</textarea
|
||||
>
|
||||
<script type="module">
|
||||
|
@ -7,7 +7,7 @@ export default defineConfig({
|
||||
clean: true,
|
||||
enabled: true,
|
||||
include: ['src/**/*.ts'],
|
||||
provider: 'c8',
|
||||
provider: 'v8',
|
||||
},
|
||||
dir: 'src',
|
||||
environment: 'jsdom',
|
||||
|
Loading…
x
Reference in New Issue
Block a user