react-headless-mde 2.0.3 → 3.0.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -18
- package/dist/cjs/index.js +338 -262
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/commands/base-command.d.ts +24 -0
- package/dist/esm/commands/line/heading-level1.d.ts +4 -0
- package/dist/esm/commands/line/heading-level2.d.ts +4 -0
- package/dist/esm/commands/line/heading-level3.d.ts +4 -0
- package/dist/esm/commands/line/heading-level4.d.ts +4 -0
- package/dist/esm/commands/line/heading-level5.d.ts +4 -0
- package/dist/esm/commands/line/heading-level6.d.ts +4 -0
- package/dist/esm/commands/line/line-prefix-command.d.ts +7 -0
- package/dist/esm/commands/line/quote.d.ts +4 -0
- package/dist/esm/commands/list/checked-list.d.ts +4 -0
- package/dist/esm/commands/list/list-command.d.ts +5 -0
- package/dist/esm/commands/list/ordered-list.d.ts +4 -0
- package/dist/esm/commands/list/unordered-list.d.ts +4 -0
- package/dist/esm/commands/word/bold.d.ts +5 -0
- package/dist/esm/commands/word/code-block.d.ts +4 -0
- package/dist/esm/commands/word/code.d.ts +5 -0
- package/dist/esm/commands/word/italic.d.ts +5 -0
- package/dist/esm/commands/word/strikethrough.d.ts +5 -0
- package/dist/esm/commands/word/wrap-command.d.ts +11 -0
- package/dist/esm/commands/word-complex/image.d.ts +4 -0
- package/dist/esm/commands/word-complex/link.d.ts +4 -0
- package/dist/esm/controllers/textarea-controller.d.ts +15 -0
- package/dist/esm/hooks/use-markdown-editor.d.ts +12 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +319 -243
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/command.d.ts +10 -0
- package/dist/esm/types/text-controller.d.ts +18 -0
- package/dist/esm/utils/insert-text-to-selection.d.ts +8 -0
- package/dist/esm/utils/selection-and-text.d.ts +14 -0
- package/dist/index.d.ts +121 -66
- package/package.json +58 -46
- package/.eslintrc.js +0 -24
- package/.github/workflows/npm-publish.yml +0 -27
- package/.husky/pre-commit +0 -4
- package/.husky/pre-push +0 -4
- package/jest.config.js +0 -4
- package/lint-staged.config.js +0 -4
- package/prettier.config.js +0 -13
- package/rollup.config.js +0 -31
- package/vite.config.js +0 -7
package/dist/esm/index.js
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import { useRef, useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
class CommandController {
|
|
4
|
-
constructor(textController, commandMap) {
|
|
5
|
-
this.textController = textController;
|
|
6
|
-
this.commandMap = commandMap;
|
|
7
|
-
}
|
|
8
|
-
executeCommand(commandName) {
|
|
9
|
-
var _a;
|
|
10
|
-
const command = this.commandMap[commandName];
|
|
11
|
-
if (!command) {
|
|
12
|
-
throw new Error(`Cannot execute command. Command not found: ${commandName}`);
|
|
13
|
-
}
|
|
14
|
-
if (command.undo && ((_a = command.shouldUndo) === null || _a === void 0 ? void 0 : _a.call(command, this.textController))) {
|
|
15
|
-
command.undo(this.textController);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
command.do(this.textController);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
import { useRef, useEffect, useMemo } from 'react';
|
|
22
2
|
|
|
23
3
|
/**
|
|
24
4
|
* Inserts the given text at the cursor. If the element contains a selection, the selection
|
|
@@ -118,8 +98,6 @@ function canManipulateViaTextNodes(input) {
|
|
|
118
98
|
return browserSupportsTextareaTextNodes;
|
|
119
99
|
}
|
|
120
100
|
|
|
121
|
-
// A list of helpers for manipulating Markdown text.
|
|
122
|
-
// These helpers do not interface with a textarea.
|
|
123
101
|
// Check if char is "space" or "new line".
|
|
124
102
|
// Char is optional because we pass a char by index,
|
|
125
103
|
// and theoretically index can be outside the string range. See text[i - 1] and text[i].
|
|
@@ -168,6 +146,16 @@ function selectAfterWord({ text, selection }) {
|
|
|
168
146
|
}
|
|
169
147
|
return selection;
|
|
170
148
|
}
|
|
149
|
+
// Expands the selection to cover the whole lines it touches:
|
|
150
|
+
// the start moves to the beginning of the first line, the end — to the end of the last one
|
|
151
|
+
function getLineSelection({ text, selection }) {
|
|
152
|
+
let { start, end } = selection;
|
|
153
|
+
while (start > 0 && text[start - 1] !== '\n')
|
|
154
|
+
start--;
|
|
155
|
+
while (end < text.length && text[end] !== '\n')
|
|
156
|
+
end++;
|
|
157
|
+
return { start, end };
|
|
158
|
+
}
|
|
171
159
|
// Gets the number of line-breaks that would have to be inserted before the given 'startPosition'
|
|
172
160
|
// to make sure there's an empty line between 'startPosition' and the previous text
|
|
173
161
|
function getBreaksNeededForEmptyLineBefore(text = '', startPosition) {
|
|
@@ -191,7 +179,7 @@ function getBreaksNeededForEmptyLineBefore(text = '', startPosition) {
|
|
|
191
179
|
return neededBreaks;
|
|
192
180
|
}
|
|
193
181
|
}
|
|
194
|
-
return isInFirstLine ? 0 : neededBreaks;
|
|
182
|
+
return isInFirstLine ? 0 : Math.max(0, neededBreaks);
|
|
195
183
|
}
|
|
196
184
|
// Gets the number of line-breaks that would have to be inserted after the given 'startPosition'
|
|
197
185
|
// to make sure there's an empty line between 'startPosition' and the next text
|
|
@@ -217,7 +205,7 @@ function getBreaksNeededForEmptyLineAfter(text = '', startPosition) {
|
|
|
217
205
|
return neededBreaks;
|
|
218
206
|
}
|
|
219
207
|
}
|
|
220
|
-
return isInLastLine ? 0 : neededBreaks;
|
|
208
|
+
return isInLastLine ? 0 : Math.max(0, neededBreaks);
|
|
221
209
|
}
|
|
222
210
|
function getSelectedText(textSection) {
|
|
223
211
|
return textSection.text.slice(textSection.selection.start, textSection.selection.end);
|
|
@@ -251,14 +239,15 @@ function insertBeforeEachLine(selectedText, insertBefore) {
|
|
|
251
239
|
|
|
252
240
|
var selectionAndText = /*#__PURE__*/Object.freeze({
|
|
253
241
|
__proto__: null,
|
|
254
|
-
getWordSelection: getWordSelection,
|
|
255
|
-
selectAfterWord: selectAfterWord,
|
|
256
|
-
getBreaksNeededForEmptyLineBefore: getBreaksNeededForEmptyLineBefore,
|
|
257
242
|
getBreaksNeededForEmptyLineAfter: getBreaksNeededForEmptyLineAfter,
|
|
243
|
+
getBreaksNeededForEmptyLineBefore: getBreaksNeededForEmptyLineBefore,
|
|
244
|
+
getLineSelection: getLineSelection,
|
|
258
245
|
getSelectedText: getSelectedText,
|
|
259
|
-
getStringBeforeSelection: getStringBeforeSelection,
|
|
260
246
|
getStringAfterSelection: getStringAfterSelection,
|
|
261
|
-
|
|
247
|
+
getStringBeforeSelection: getStringBeforeSelection,
|
|
248
|
+
getWordSelection: getWordSelection,
|
|
249
|
+
insertBeforeEachLine: insertBeforeEachLine,
|
|
250
|
+
selectAfterWord: selectAfterWord
|
|
262
251
|
});
|
|
263
252
|
|
|
264
253
|
function getStateFromTextArea(textArea) {
|
|
@@ -316,9 +305,9 @@ class TextareaController {
|
|
|
316
305
|
const state1 = this.getState();
|
|
317
306
|
// Replaces the current selection with new string
|
|
318
307
|
const state2 = this.replaceSelection(`${prefix}${getSelectedText(state1)}${suffix}`);
|
|
319
|
-
// Adjust the selection to not contain the
|
|
308
|
+
// Adjust the selection to not contain the prefix and suffix
|
|
320
309
|
return this.setSelection({
|
|
321
|
-
start: state2.selection.end -
|
|
310
|
+
start: state2.selection.end - suffix.length - getSelectedText(state1).length,
|
|
322
311
|
end: state2.selection.end - suffix.length,
|
|
323
312
|
});
|
|
324
313
|
}
|
|
@@ -349,262 +338,349 @@ class TextareaController {
|
|
|
349
338
|
|
|
350
339
|
function useTextAreaMarkdownEditor(options) {
|
|
351
340
|
const textAreaRef = useRef(null);
|
|
341
|
+
// Always execute commands from the most recent render
|
|
342
|
+
const commandMapRef = useRef(options.commandMap);
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
commandMapRef.current = options.commandMap;
|
|
345
|
+
});
|
|
346
|
+
// Command instances persist between executor rebuilds
|
|
347
|
+
const instancesRef = useRef(new Map());
|
|
348
|
+
// Rebuild the executors object only when the set of command names changes,
|
|
349
|
+
// so `commands` (and each executor) stays referentially stable between renders
|
|
350
|
+
const commandNames = Object.keys(options.commandMap).sort().join('\0');
|
|
352
351
|
return useMemo(() => {
|
|
353
352
|
const textController = new TextareaController(textAreaRef);
|
|
354
|
-
const
|
|
353
|
+
const instances = instancesRef.current;
|
|
354
|
+
const executors = {};
|
|
355
|
+
for (const name of commandNames.split('\0')) {
|
|
356
|
+
if (name === '')
|
|
357
|
+
continue;
|
|
358
|
+
executors[name] = (context) => {
|
|
359
|
+
var _a;
|
|
360
|
+
const CommandClass = commandMapRef.current[name];
|
|
361
|
+
if (CommandClass === undefined) {
|
|
362
|
+
throw new Error(`Cannot execute command. Command not found: ${name}`);
|
|
363
|
+
}
|
|
364
|
+
let instance = instances.get(name);
|
|
365
|
+
if (instance === undefined || instance.constructor !== CommandClass) {
|
|
366
|
+
instance = new CommandClass(textController);
|
|
367
|
+
instances.set(name, instance);
|
|
368
|
+
}
|
|
369
|
+
if (instance.undo !== undefined && ((_a = instance.shouldUndo) === null || _a === void 0 ? void 0 : _a.call(instance, context)) === true) {
|
|
370
|
+
instance.undo(context);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
instance.do(context);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
355
377
|
return {
|
|
356
378
|
ref: textAreaRef,
|
|
357
379
|
textController,
|
|
358
|
-
|
|
380
|
+
commands: executors,
|
|
359
381
|
};
|
|
360
|
-
}, [
|
|
382
|
+
}, [commandNames]);
|
|
361
383
|
}
|
|
362
384
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
start: selectionStart,
|
|
383
|
-
end: selectionEnd,
|
|
384
|
-
});
|
|
385
|
+
/**
|
|
386
|
+
* Base class for all commands.
|
|
387
|
+
*
|
|
388
|
+
* `Context` is the type of the value that must be passed when the command is executed.
|
|
389
|
+
* Commands without a context extend `BaseCommand` (Context = void) and are executed
|
|
390
|
+
* without arguments; commands with a context extend `BaseCommand<Context>` and require
|
|
391
|
+
* an argument of that type:
|
|
392
|
+
*
|
|
393
|
+
* class AttachmentCommand extends BaseCommand<Promise<string>> {
|
|
394
|
+
* async do(upload: Promise<string>) {
|
|
395
|
+
* const url = await upload;
|
|
396
|
+
* this.textController.replaceSelection(``);
|
|
397
|
+
* }
|
|
398
|
+
* }
|
|
399
|
+
*/
|
|
400
|
+
class BaseCommand {
|
|
401
|
+
constructor(textController) {
|
|
402
|
+
this.textController = textController;
|
|
403
|
+
}
|
|
385
404
|
}
|
|
386
405
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
// Base class for commands that wrap a word/selection with prefix and suffix: bold, italic etc.
|
|
407
|
+
class WrapCommand extends BaseCommand {
|
|
408
|
+
do() {
|
|
409
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
410
|
+
this.textController.selectWordByCursor();
|
|
411
|
+
this.textController.wrapSelection(this.prefix, this.suffix);
|
|
412
|
+
}
|
|
413
|
+
shouldUndo() {
|
|
414
|
+
const state = this.getWordSelectionState();
|
|
415
|
+
return this.hasMarkersInside(getSelectedText(state)) || this.hasMarkersOutside(state);
|
|
416
|
+
}
|
|
417
|
+
undo() {
|
|
418
|
+
const state = this.getWordSelectionState();
|
|
419
|
+
const selectedText = getSelectedText(state);
|
|
420
|
+
this.textController.setSelection(state.selection);
|
|
421
|
+
if (!this.hasMarkersInside(selectedText)) {
|
|
422
|
+
// The markers surround the selection: `**[word]**`
|
|
423
|
+
this.textController.unwrapSelection(this.prefix.length, this.suffix.length);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// The markers are inside the selection — the caret is within `**wo|rd**`:
|
|
427
|
+
// strip the markers from the edges of the selection
|
|
428
|
+
const innerText = selectedText.slice(this.prefix.length, selectedText.length - this.suffix.length);
|
|
429
|
+
const replacedState = this.textController.replaceSelection(innerText);
|
|
430
|
+
this.textController.setSelection({
|
|
431
|
+
start: replacedState.selection.end - innerText.length,
|
|
432
|
+
end: replacedState.selection.end,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
getWordSelectionState() {
|
|
436
|
+
const state = this.textController.getState();
|
|
437
|
+
return {
|
|
438
|
+
text: state.text,
|
|
439
|
+
selection: getWordSelection(state),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
hasMarkersInside(selectedText) {
|
|
443
|
+
return (selectedText.length >= this.prefix.length + this.suffix.length &&
|
|
444
|
+
selectedText.startsWith(this.prefix) &&
|
|
445
|
+
selectedText.endsWith(this.suffix));
|
|
446
|
+
}
|
|
447
|
+
hasMarkersOutside({ text, selection }) {
|
|
448
|
+
const markersMatch = getStringBeforeSelection({ text, selection }, this.prefix.length) === this.prefix &&
|
|
449
|
+
getStringAfterSelection({ text, selection }, this.suffix.length) === this.suffix;
|
|
450
|
+
if (!markersMatch)
|
|
451
|
+
return false;
|
|
452
|
+
// The characters right next to the markers must not continue the marker sequence:
|
|
453
|
+
// `*` around `[word]` inside `**word**` is bold, not italic
|
|
454
|
+
const charBeforeMarkers = text[selection.start - this.prefix.length - 1];
|
|
455
|
+
const charAfterMarkers = text[selection.end + this.suffix.length];
|
|
456
|
+
return charBeforeMarkers !== this.prefix[0] && charAfterMarkers !== this.suffix[this.suffix.length - 1];
|
|
457
|
+
}
|
|
406
458
|
}
|
|
407
459
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
460
|
+
// Base class for line-level commands that prefix each selected line: quote, headings.
|
|
461
|
+
// The selection is expanded to cover the whole lines it touches.
|
|
462
|
+
// Executing the command on already prefixed lines removes the prefixes (undo).
|
|
463
|
+
class LinePrefixCommand extends BaseCommand {
|
|
464
|
+
do() {
|
|
465
|
+
const state = this.textController.getState();
|
|
466
|
+
const state1 = this.textController.setSelection(getLineSelection(state));
|
|
467
|
+
const selectedText = getSelectedText(state1);
|
|
468
|
+
const { modifiedText } = insertBeforeEachLine(selectedText, this.prefix);
|
|
469
|
+
const state2 = this.textController.replaceSelection(modifiedText);
|
|
470
|
+
// For a single line select the content without the prefix,
|
|
471
|
+
// for multiple lines select the whole modified block
|
|
472
|
+
const prefixOffset = selectedText.includes('\n') ? 0 : this.prefix.length;
|
|
473
|
+
this.textController.setSelection({
|
|
474
|
+
start: state2.selection.end - modifiedText.length + prefixOffset,
|
|
475
|
+
end: state2.selection.end,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
shouldUndo() {
|
|
479
|
+
const state = this.textController.getState();
|
|
480
|
+
const selectedText = getSelectedText({
|
|
481
|
+
text: state.text,
|
|
482
|
+
selection: getLineSelection(state),
|
|
483
|
+
});
|
|
484
|
+
return selectedText.split('\n').every(line => line.startsWith(this.prefix));
|
|
485
|
+
}
|
|
486
|
+
undo() {
|
|
487
|
+
const state = this.textController.getState();
|
|
488
|
+
const state1 = this.textController.setSelection(getLineSelection(state));
|
|
489
|
+
const modifiedText = getSelectedText(state1)
|
|
490
|
+
.split('\n')
|
|
491
|
+
.map(line => (line.startsWith(this.prefix) ? line.slice(this.prefix.length) : line))
|
|
492
|
+
.join('\n');
|
|
493
|
+
this.textController.replaceSelection(modifiedText);
|
|
494
|
+
// Select the content of the un-prefixed line(s)
|
|
495
|
+
this.textController.setSelection({
|
|
496
|
+
start: state1.selection.start,
|
|
497
|
+
end: state1.selection.start + modifiedText.length,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
426
501
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
502
|
+
// Base class for list commands. Each selected line becomes a list item.
|
|
503
|
+
// The selection is expanded to cover the whole lines it touches; when a list is
|
|
504
|
+
// inserted in the middle of a paragraph, it is separated by empty lines.
|
|
505
|
+
class ListCommand extends BaseCommand {
|
|
506
|
+
do() {
|
|
507
|
+
const state0 = this.textController.getState();
|
|
508
|
+
const state1 = this.textController.setSelection(getLineSelection(state0));
|
|
509
|
+
const breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
510
|
+
const breaksBefore = '\n'.repeat(breaksBeforeCount);
|
|
511
|
+
const breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
|
|
512
|
+
const breaksAfter = '\n'.repeat(breaksAfterCount);
|
|
513
|
+
const selectedText = getSelectedText(state1);
|
|
514
|
+
const { modifiedText } = insertBeforeEachLine(selectedText, (line, index) => this.getLinePrefix(line, index));
|
|
515
|
+
this.textController.replaceSelection(`${breaksBefore}${modifiedText}${breaksAfter}`);
|
|
516
|
+
// For a single line select the content without the prefix,
|
|
517
|
+
// for multiple lines select the whole modified block
|
|
518
|
+
const prefixOffset = selectedText.includes('\n') ? 0 : this.getLinePrefix('', 0).length;
|
|
519
|
+
const blockStart = state1.selection.start + breaksBeforeCount;
|
|
520
|
+
this.textController.setSelection({
|
|
521
|
+
start: blockStart + prefixOffset,
|
|
522
|
+
end: blockStart + modifiedText.length,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
439
526
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
textCtrl.wrapSelection('~~', '~~');
|
|
448
|
-
},
|
|
449
|
-
undo(textCtrl) {
|
|
450
|
-
textCtrl.unwrapSelection(2, 2);
|
|
451
|
-
},
|
|
452
|
-
};
|
|
527
|
+
class BoldCommand extends WrapCommand {
|
|
528
|
+
constructor() {
|
|
529
|
+
super(...arguments);
|
|
530
|
+
this.prefix = '**';
|
|
531
|
+
this.suffix = '**';
|
|
532
|
+
}
|
|
533
|
+
}
|
|
453
534
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
textApi.setSelection({
|
|
462
|
-
start: state2.selection.end - 6 - getSelectedText(wordSelectionState).length,
|
|
463
|
-
end: state2.selection.end - 6,
|
|
464
|
-
});
|
|
465
|
-
},
|
|
466
|
-
};
|
|
535
|
+
class ItalicCommand extends WrapCommand {
|
|
536
|
+
constructor() {
|
|
537
|
+
super(...arguments);
|
|
538
|
+
this.prefix = '*';
|
|
539
|
+
this.suffix = '*';
|
|
540
|
+
}
|
|
541
|
+
}
|
|
467
542
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const breaksAfterCount = getBreaksNeededForEmptyLineAfter(state.text, state.selection.end);
|
|
476
|
-
const breaksAfter = Array(breaksAfterCount + 1).join('\n');
|
|
477
|
-
// Replaces the current selection with the quote mark up
|
|
478
|
-
textApi.replaceSelection(`${breaksBefore}> ${getSelectedText(state)}${breaksAfter}`);
|
|
479
|
-
const selectionStart = state.selection.start + breaksBeforeCount + 2;
|
|
480
|
-
const selectionEnd = selectionStart + getSelectedText(state).length;
|
|
481
|
-
textApi.setSelection({
|
|
482
|
-
start: selectionStart,
|
|
483
|
-
end: selectionEnd,
|
|
484
|
-
});
|
|
485
|
-
},
|
|
486
|
-
};
|
|
543
|
+
class StrikethroughCommand extends WrapCommand {
|
|
544
|
+
constructor() {
|
|
545
|
+
super(...arguments);
|
|
546
|
+
this.prefix = '~~';
|
|
547
|
+
this.suffix = '~~';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
487
550
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
},
|
|
496
|
-
undo(textCtrl) {
|
|
497
|
-
textCtrl.unwrapSelection(1, 1);
|
|
498
|
-
},
|
|
499
|
-
};
|
|
551
|
+
class CodeCommand extends WrapCommand {
|
|
552
|
+
constructor() {
|
|
553
|
+
super(...arguments);
|
|
554
|
+
this.prefix = '`';
|
|
555
|
+
this.suffix = '`';
|
|
556
|
+
}
|
|
557
|
+
}
|
|
500
558
|
|
|
501
|
-
|
|
502
|
-
do
|
|
559
|
+
class CodeBlockCommand extends BaseCommand {
|
|
560
|
+
do() {
|
|
561
|
+
const textApi = this.textController;
|
|
503
562
|
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
504
563
|
const state1 = textApi.selectWordByCursor();
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const selectionStart = state1.selection.start + 1;
|
|
510
|
-
const selectionEnd = selectionStart + getSelectedText(state1).length;
|
|
564
|
+
const selectedText = getSelectedText(state1);
|
|
565
|
+
// when there's no breaking line — inline code
|
|
566
|
+
if (!selectedText.includes('\n')) {
|
|
567
|
+
textApi.replaceSelection(`\`${selectedText}\``);
|
|
511
568
|
textApi.setSelection({
|
|
512
|
-
start:
|
|
513
|
-
end:
|
|
569
|
+
start: state1.selection.start + 1,
|
|
570
|
+
end: state1.selection.start + 1 + selectedText.length,
|
|
514
571
|
});
|
|
515
572
|
return;
|
|
516
573
|
}
|
|
517
574
|
const breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
518
|
-
const breaksBefore =
|
|
575
|
+
const breaksBefore = '\n'.repeat(breaksBeforeCount);
|
|
519
576
|
const breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
|
|
520
|
-
const breaksAfter =
|
|
521
|
-
textApi.replaceSelection(`${breaksBefore}\`\`\`\n${
|
|
577
|
+
const breaksAfter = '\n'.repeat(breaksAfterCount);
|
|
578
|
+
textApi.replaceSelection(`${breaksBefore}\`\`\`\n${selectedText}\n\`\`\`${breaksAfter}`);
|
|
522
579
|
const selectionStart = state1.selection.start + breaksBeforeCount + 4;
|
|
523
|
-
const selectionEnd = selectionStart + getSelectedText(state1).length;
|
|
524
580
|
textApi.setSelection({
|
|
525
581
|
start: selectionStart,
|
|
526
|
-
end:
|
|
582
|
+
end: selectionStart + selectedText.length,
|
|
527
583
|
});
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const checkedListCommand = {
|
|
532
|
-
do: textApi => {
|
|
533
|
-
const initialState = textApi.getState();
|
|
534
|
-
makeList(initialState, textApi, () => `- [ ] `);
|
|
535
|
-
},
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
const orderedListCommand = {
|
|
539
|
-
do: textApi => {
|
|
540
|
-
const initialState = textApi.getState();
|
|
541
|
-
makeList(initialState, textApi, (item, index) => `${index + 1}. `);
|
|
542
|
-
},
|
|
543
|
-
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
544
586
|
|
|
545
|
-
|
|
546
|
-
do
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
587
|
+
class LinkCommand extends BaseCommand {
|
|
588
|
+
do() {
|
|
589
|
+
const textApi = this.textController;
|
|
590
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
591
|
+
const wordSelectionState = textApi.selectWordByCursor();
|
|
592
|
+
// Replaces the current selection with the link markup
|
|
593
|
+
const state2 = textApi.replaceSelection(`[${getSelectedText(wordSelectionState)}](url)`);
|
|
594
|
+
// Select the `url` placeholder so it can be replaced right away
|
|
595
|
+
textApi.setSelection({
|
|
596
|
+
start: state2.selection.end - 4,
|
|
597
|
+
end: state2.selection.end - 1,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
551
601
|
|
|
552
|
-
|
|
553
|
-
do
|
|
554
|
-
|
|
602
|
+
class ImageCommand extends BaseCommand {
|
|
603
|
+
do() {
|
|
604
|
+
const textApi = this.textController;
|
|
605
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
555
606
|
const wordSelectionState = textApi.selectWordByCursor();
|
|
556
607
|
// Replaces the current selection with the image
|
|
557
608
|
const imageTemplate = getSelectedText(wordSelectionState) || 'https://example.com/your-image.png';
|
|
558
609
|
textApi.replaceSelection(``);
|
|
559
|
-
//
|
|
610
|
+
// Select the image url so it can be replaced right away
|
|
560
611
|
textApi.setSelection({
|
|
561
612
|
start: wordSelectionState.selection.start + 4,
|
|
562
613
|
end: wordSelectionState.selection.start + 4 + imageTemplate.length,
|
|
563
614
|
});
|
|
564
|
-
}
|
|
565
|
-
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
566
617
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
}
|
|
618
|
+
class QuoteCommand extends LinePrefixCommand {
|
|
619
|
+
constructor() {
|
|
620
|
+
super(...arguments);
|
|
621
|
+
this.prefix = '> ';
|
|
622
|
+
}
|
|
623
|
+
}
|
|
573
624
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
}
|
|
625
|
+
class HeadingLevel1Command extends LinePrefixCommand {
|
|
626
|
+
constructor() {
|
|
627
|
+
super(...arguments);
|
|
628
|
+
this.prefix = '# ';
|
|
629
|
+
}
|
|
630
|
+
}
|
|
580
631
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
}
|
|
632
|
+
class HeadingLevel2Command extends LinePrefixCommand {
|
|
633
|
+
constructor() {
|
|
634
|
+
super(...arguments);
|
|
635
|
+
this.prefix = '## ';
|
|
636
|
+
}
|
|
637
|
+
}
|
|
587
638
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
}
|
|
639
|
+
class HeadingLevel3Command extends LinePrefixCommand {
|
|
640
|
+
constructor() {
|
|
641
|
+
super(...arguments);
|
|
642
|
+
this.prefix = '### ';
|
|
643
|
+
}
|
|
644
|
+
}
|
|
594
645
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
}
|
|
646
|
+
class HeadingLevel4Command extends LinePrefixCommand {
|
|
647
|
+
constructor() {
|
|
648
|
+
super(...arguments);
|
|
649
|
+
this.prefix = '#### ';
|
|
650
|
+
}
|
|
651
|
+
}
|
|
601
652
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
}
|
|
653
|
+
class HeadingLevel5Command extends LinePrefixCommand {
|
|
654
|
+
constructor() {
|
|
655
|
+
super(...arguments);
|
|
656
|
+
this.prefix = '##### ';
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
class HeadingLevel6Command extends LinePrefixCommand {
|
|
661
|
+
constructor() {
|
|
662
|
+
super(...arguments);
|
|
663
|
+
this.prefix = '###### ';
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
class UnorderedListCommand extends ListCommand {
|
|
668
|
+
getLinePrefix() {
|
|
669
|
+
return '- ';
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
class OrderedListCommand extends ListCommand {
|
|
674
|
+
getLinePrefix(_line, index) {
|
|
675
|
+
return `${index + 1}. `;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
class CheckedListCommand extends ListCommand {
|
|
680
|
+
getLinePrefix() {
|
|
681
|
+
return '- [ ] ';
|
|
682
|
+
}
|
|
683
|
+
}
|
|
608
684
|
|
|
609
|
-
export {
|
|
685
|
+
export { BaseCommand, BoldCommand, CheckedListCommand, CodeBlockCommand, CodeCommand, HeadingLevel1Command, HeadingLevel2Command, HeadingLevel3Command, HeadingLevel4Command, HeadingLevel5Command, HeadingLevel6Command, ImageCommand, ItalicCommand, LinePrefixCommand, LinkCommand, ListCommand, OrderedListCommand, QuoteCommand, StrikethroughCommand, TextareaController, UnorderedListCommand, WrapCommand, selectionAndText as textHelpers, useTextAreaMarkdownEditor };
|
|
610
686
|
//# sourceMappingURL=index.js.map
|