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