react-headless-mde 0.0.1
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/.github/workflows/npm-publish.yml +23 -0
- package/.prettierrc +8 -0
- package/LICENSE +21 -0
- package/dist/cjs/index.js +767 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +741 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.ts +192 -0
- package/index.html +12 -0
- package/jest.config.js +4 -0
- package/package.json +68 -0
- package/readme.md +107 -0
- package/rollup.config.js +35 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var react = require('react');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A list of helpers for manipulating markdown text.
|
|
9
|
+
* These helpers do not interface with a textarea. For that, see
|
|
10
|
+
*/
|
|
11
|
+
function getSurroundingWord(text, position) {
|
|
12
|
+
if (!text)
|
|
13
|
+
throw Error("Argument 'text' should be truthy");
|
|
14
|
+
var isWordDelimiter = function (c) { return c === " " || c.charCodeAt(0) === 10; };
|
|
15
|
+
// leftIndex is initialized to 0 because if selection is 0, it won't even enter the iteration
|
|
16
|
+
var start = 0;
|
|
17
|
+
// rightIndex is initialized to text.length because if selection is equal to text.length it won't even enter the interation
|
|
18
|
+
var end = text.length;
|
|
19
|
+
// iterate to the left
|
|
20
|
+
for (var i = position; i - 1 > -1; i--) {
|
|
21
|
+
if (isWordDelimiter(text[i - 1])) {
|
|
22
|
+
start = i;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// iterate to the right
|
|
27
|
+
for (var i = position; i < text.length; i++) {
|
|
28
|
+
if (isWordDelimiter(text[i])) {
|
|
29
|
+
end = i;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { start: start, end: end };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* If the cursor is inside a word and (selection.start === selection.end)
|
|
37
|
+
* returns a new Selection where the whole word is selected
|
|
38
|
+
* @param text
|
|
39
|
+
* @param selection
|
|
40
|
+
*/
|
|
41
|
+
function selectWord(_a) {
|
|
42
|
+
var text = _a.text, selection = _a.selection;
|
|
43
|
+
if (text && text.length && selection.start === selection.end) {
|
|
44
|
+
// the user is pointing to a word
|
|
45
|
+
return getSurroundingWord(text, selection.start);
|
|
46
|
+
}
|
|
47
|
+
return selection;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gets the number of line-breaks that would have to be inserted before the given 'startPosition'
|
|
51
|
+
* to make sure there's an empty line between 'startPosition' and the previous text
|
|
52
|
+
*/
|
|
53
|
+
function getBreaksNeededForEmptyLineBefore(text, startPosition) {
|
|
54
|
+
if (text === void 0) { text = ""; }
|
|
55
|
+
if (startPosition === 0)
|
|
56
|
+
return 0;
|
|
57
|
+
// rules:
|
|
58
|
+
// - If we're in the first line, no breaks are needed
|
|
59
|
+
// - Otherwise there must be 2 breaks before the previous character. Depending on how many breaks exist already, we
|
|
60
|
+
// may need to insert 0, 1 or 2 breaks
|
|
61
|
+
var neededBreaks = 2;
|
|
62
|
+
var isInFirstLine = true;
|
|
63
|
+
for (var i = startPosition - 1; i >= 0 && neededBreaks >= 0; i--) {
|
|
64
|
+
switch (text.charCodeAt(i)) {
|
|
65
|
+
case 32: // blank space
|
|
66
|
+
continue;
|
|
67
|
+
case 10: // line break
|
|
68
|
+
neededBreaks--;
|
|
69
|
+
isInFirstLine = false;
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
return neededBreaks;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return isInFirstLine ? 0 : neededBreaks;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Gets the number of line-breaks that would have to be inserted after the given 'startPosition'
|
|
79
|
+
* to make sure there's an empty line between 'startPosition' and the next text
|
|
80
|
+
*/
|
|
81
|
+
function getBreaksNeededForEmptyLineAfter(text, startPosition) {
|
|
82
|
+
if (text === void 0) { text = ""; }
|
|
83
|
+
if (startPosition === text.length - 1)
|
|
84
|
+
return 0;
|
|
85
|
+
// rules:
|
|
86
|
+
// - If we're in the first line, no breaks are needed
|
|
87
|
+
// - Otherwise there must be 2 breaks before the previous character. Depending on how many breaks exist already, we
|
|
88
|
+
// may need to insert 0, 1 or 2 breaks
|
|
89
|
+
var neededBreaks = 2;
|
|
90
|
+
var isInLastLine = true;
|
|
91
|
+
for (var i = startPosition; i < text.length && neededBreaks >= 0; i++) {
|
|
92
|
+
switch (text.charCodeAt(i)) {
|
|
93
|
+
case 32:
|
|
94
|
+
continue;
|
|
95
|
+
case 10: {
|
|
96
|
+
neededBreaks--;
|
|
97
|
+
isInLastLine = false;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
return neededBreaks;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return isInLastLine ? 0 : neededBreaks;
|
|
105
|
+
}
|
|
106
|
+
function getSelectedText(textSection) {
|
|
107
|
+
return textSection.text.slice(textSection.selection.start, textSection.selection.end);
|
|
108
|
+
}
|
|
109
|
+
function getCharactersBeforeSelection(textState, characters) {
|
|
110
|
+
return textState.text.slice(textState.selection.start - characters, textState.selection.start);
|
|
111
|
+
}
|
|
112
|
+
function getCharactersAfterSelection(textState, characters) {
|
|
113
|
+
return textState.text.slice(textState.selection.end, textState.selection.end + characters);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Inserts insertionString before each line
|
|
117
|
+
*/
|
|
118
|
+
function insertBeforeEachLine(selectedText, insertBefore) {
|
|
119
|
+
var lines = selectedText.split(/\n/);
|
|
120
|
+
var insertionLength = 0;
|
|
121
|
+
var modifiedText = lines
|
|
122
|
+
.map(function (item, index) {
|
|
123
|
+
if (typeof insertBefore === "string") {
|
|
124
|
+
insertionLength += insertBefore.length;
|
|
125
|
+
return insertBefore + item;
|
|
126
|
+
}
|
|
127
|
+
else if (typeof insertBefore === "function") {
|
|
128
|
+
var insertionResult = insertBefore(item, index);
|
|
129
|
+
insertionLength += insertionResult.length;
|
|
130
|
+
return insertBefore(item, index) + item;
|
|
131
|
+
}
|
|
132
|
+
throw Error("insertion is expected to be either a string or a function");
|
|
133
|
+
})
|
|
134
|
+
.join("\n");
|
|
135
|
+
return { modifiedText: modifiedText, insertionLength: insertionLength };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var textHelpers = /*#__PURE__*/Object.freeze({
|
|
139
|
+
__proto__: null,
|
|
140
|
+
getSurroundingWord: getSurroundingWord,
|
|
141
|
+
selectWord: selectWord,
|
|
142
|
+
getBreaksNeededForEmptyLineBefore: getBreaksNeededForEmptyLineBefore,
|
|
143
|
+
getBreaksNeededForEmptyLineAfter: getBreaksNeededForEmptyLineAfter,
|
|
144
|
+
getSelectedText: getSelectedText,
|
|
145
|
+
getCharactersBeforeSelection: getCharactersBeforeSelection,
|
|
146
|
+
getCharactersAfterSelection: getCharactersAfterSelection,
|
|
147
|
+
insertBeforeEachLine: insertBeforeEachLine
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
function setHeader(initialState, api, prefix) {
|
|
151
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
152
|
+
var newSelectionRange = selectWord({
|
|
153
|
+
text: initialState.text,
|
|
154
|
+
selection: initialState.selection
|
|
155
|
+
});
|
|
156
|
+
var state1 = api.setSelectionRange(newSelectionRange);
|
|
157
|
+
// Add the prefix to the selection
|
|
158
|
+
var state2 = api.replaceSelection("" + prefix + getSelectedText(state1));
|
|
159
|
+
// Adjust the selection to not contain the prefix
|
|
160
|
+
api.setSelectionRange({
|
|
161
|
+
start: state2.selection.end - getSelectedText(state1).length,
|
|
162
|
+
end: state2.selection.end
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
var headerHelpers = /*#__PURE__*/Object.freeze({
|
|
167
|
+
__proto__: null,
|
|
168
|
+
setHeader: setHeader
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
var headingLevel1Command = {
|
|
172
|
+
execute: function (_a) {
|
|
173
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
174
|
+
setHeader(initialState, textApi, "# ");
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
var boldCommand = {
|
|
179
|
+
shouldUndo: function (options) {
|
|
180
|
+
return (getCharactersBeforeSelection(options.initialState, 2) === "**" &&
|
|
181
|
+
getCharactersAfterSelection(options.initialState, 2) === "**");
|
|
182
|
+
},
|
|
183
|
+
execute: function (_a) {
|
|
184
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
185
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
186
|
+
var newSelectionRange = selectWord({
|
|
187
|
+
text: initialState.text,
|
|
188
|
+
selection: initialState.selection
|
|
189
|
+
});
|
|
190
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
191
|
+
// Replaces the current selection with the bold mark up
|
|
192
|
+
var state2 = textApi.replaceSelection("**" + getSelectedText(state1) + "**");
|
|
193
|
+
// Adjust the selection to not contain the **
|
|
194
|
+
textApi.setSelectionRange({
|
|
195
|
+
start: state2.selection.end - 2 - getSelectedText(state1).length,
|
|
196
|
+
end: state2.selection.end - 2
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
undo: function (_a) {
|
|
200
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
201
|
+
var text = getSelectedText(initialState);
|
|
202
|
+
textApi.setSelectionRange({
|
|
203
|
+
start: initialState.selection.start - 2,
|
|
204
|
+
end: initialState.selection.end + 2
|
|
205
|
+
});
|
|
206
|
+
textApi.replaceSelection(text);
|
|
207
|
+
textApi.setSelectionRange({
|
|
208
|
+
start: initialState.selection.start - 2,
|
|
209
|
+
end: initialState.selection.end - 2
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
var italicCommand = {
|
|
215
|
+
shouldUndo: function (options) {
|
|
216
|
+
return (getCharactersBeforeSelection(options.initialState, 1) === "*" &&
|
|
217
|
+
getCharactersAfterSelection(options.initialState, 1) === "*");
|
|
218
|
+
},
|
|
219
|
+
execute: function (_a) {
|
|
220
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
221
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
222
|
+
var newSelectionRange = selectWord({
|
|
223
|
+
text: initialState.text,
|
|
224
|
+
selection: initialState.selection
|
|
225
|
+
});
|
|
226
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
227
|
+
// Replaces the current selection with the italic mark up
|
|
228
|
+
var state2 = textApi.replaceSelection("*" + getSelectedText(state1) + "*");
|
|
229
|
+
// Adjust the selection to not contain the *
|
|
230
|
+
textApi.setSelectionRange({
|
|
231
|
+
start: state2.selection.end - 1 - getSelectedText(state1).length,
|
|
232
|
+
end: state2.selection.end - 1
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
undo: function (_a) {
|
|
236
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
237
|
+
var text = getSelectedText(initialState);
|
|
238
|
+
textApi.setSelectionRange({
|
|
239
|
+
start: initialState.selection.start - 1,
|
|
240
|
+
end: initialState.selection.end + 1
|
|
241
|
+
});
|
|
242
|
+
textApi.replaceSelection(text);
|
|
243
|
+
textApi.setSelectionRange({
|
|
244
|
+
start: initialState.selection.start - 1,
|
|
245
|
+
end: initialState.selection.end - 1
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
var strikethroughCommand = {
|
|
251
|
+
execute: function (_a) {
|
|
252
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
253
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
254
|
+
var newSelectionRange = selectWord({
|
|
255
|
+
text: initialState.text,
|
|
256
|
+
selection: initialState.selection
|
|
257
|
+
});
|
|
258
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
259
|
+
// Replaces the current selection with the strikethrough mark up
|
|
260
|
+
var state2 = textApi.replaceSelection("~~" + getSelectedText(state1) + "~~");
|
|
261
|
+
// Adjust the selection to not contain the ~~
|
|
262
|
+
textApi.setSelectionRange({
|
|
263
|
+
start: state2.selection.end - 2 - getSelectedText(state1).length,
|
|
264
|
+
end: state2.selection.end - 2
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
var linkCommand = {
|
|
270
|
+
execute: function (_a) {
|
|
271
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
272
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
273
|
+
var newSelectionRange = selectWord({
|
|
274
|
+
text: initialState.text,
|
|
275
|
+
selection: initialState.selection
|
|
276
|
+
});
|
|
277
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
278
|
+
// Replaces the current selection with the bold mark up
|
|
279
|
+
var state2 = textApi.replaceSelection("[" + getSelectedText(state1) + "](url)");
|
|
280
|
+
// Adjust the selection to not contain the **
|
|
281
|
+
textApi.setSelectionRange({
|
|
282
|
+
start: state2.selection.end - 6 - getSelectedText(state1).length,
|
|
283
|
+
end: state2.selection.end - 6
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
var quoteCommand = {
|
|
289
|
+
execute: function (_a) {
|
|
290
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
291
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
292
|
+
var newSelectionRange = selectWord({
|
|
293
|
+
text: initialState.text,
|
|
294
|
+
selection: initialState.selection
|
|
295
|
+
});
|
|
296
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
297
|
+
var breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
298
|
+
var breaksBefore = Array(breaksBeforeCount + 1).join("\n");
|
|
299
|
+
var breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
|
|
300
|
+
var breaksAfter = Array(breaksAfterCount + 1).join("\n");
|
|
301
|
+
// Replaces the current selection with the quote mark up
|
|
302
|
+
textApi.replaceSelection(breaksBefore + "> " + getSelectedText(state1) + breaksAfter);
|
|
303
|
+
var selectionStart = state1.selection.start + breaksBeforeCount + 2;
|
|
304
|
+
var selectionEnd = selectionStart + getSelectedText(state1).length;
|
|
305
|
+
textApi.setSelectionRange({
|
|
306
|
+
start: selectionStart,
|
|
307
|
+
end: selectionEnd
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
var imageCommand = {
|
|
313
|
+
execute: function (_a) {
|
|
314
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
315
|
+
// Replaces the current selection with the whole word selected
|
|
316
|
+
var state1 = textApi.setSelectionRange(selectWord({
|
|
317
|
+
text: initialState.text,
|
|
318
|
+
selection: initialState.selection
|
|
319
|
+
}));
|
|
320
|
+
// Replaces the current selection with the image
|
|
321
|
+
var imageTemplate = getSelectedText(state1) || "https://example.com/your-image.png";
|
|
322
|
+
textApi.replaceSelection("");
|
|
323
|
+
// Adjust the selection to not contain the **
|
|
324
|
+
textApi.setSelectionRange({
|
|
325
|
+
start: state1.selection.start + 4,
|
|
326
|
+
end: state1.selection.start + 4 + imageTemplate.length
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/******************************************************************************
|
|
332
|
+
Copyright (c) Microsoft Corporation.
|
|
333
|
+
|
|
334
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
335
|
+
purpose with or without fee is hereby granted.
|
|
336
|
+
|
|
337
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
338
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
339
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
340
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
341
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
342
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
343
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
344
|
+
***************************************************************************** */
|
|
345
|
+
|
|
346
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
347
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
348
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
349
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
350
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
351
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
352
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function __generator(thisArg, body) {
|
|
357
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
358
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
359
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
360
|
+
function step(op) {
|
|
361
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
362
|
+
while (_) try {
|
|
363
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
364
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
365
|
+
switch (op[0]) {
|
|
366
|
+
case 0: case 1: t = op; break;
|
|
367
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
368
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
369
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
370
|
+
default:
|
|
371
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
372
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
373
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
374
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
375
|
+
if (t[2]) _.ops.pop();
|
|
376
|
+
_.trys.pop(); continue;
|
|
377
|
+
}
|
|
378
|
+
op = body.call(thisArg, _);
|
|
379
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
380
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
var CommandController = /** @class */ (function () {
|
|
385
|
+
function CommandController(textController, commandMap) {
|
|
386
|
+
/**
|
|
387
|
+
* Indicates whether there is a command currently executing
|
|
388
|
+
*/
|
|
389
|
+
this.isExecuting = false;
|
|
390
|
+
this.textController = textController;
|
|
391
|
+
this.commandMap = commandMap;
|
|
392
|
+
}
|
|
393
|
+
CommandController.prototype.executeCommand = function (commandName, context) {
|
|
394
|
+
var _a;
|
|
395
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
396
|
+
var command, executeOptions;
|
|
397
|
+
return __generator(this, function (_b) {
|
|
398
|
+
switch (_b.label) {
|
|
399
|
+
case 0:
|
|
400
|
+
if (this.isExecuting) {
|
|
401
|
+
// The simplest thing to do is to ignore commands while
|
|
402
|
+
// there is already a command execu
|
|
403
|
+
// ting. The alternative would be to queue commands
|
|
404
|
+
// but there is no guarantee that the state after one command executes will still be compatible
|
|
405
|
+
// with the next one. In fact, it is likely not to be.
|
|
406
|
+
return [2 /*return*/];
|
|
407
|
+
}
|
|
408
|
+
command = this.commandMap[commandName];
|
|
409
|
+
if (!command) {
|
|
410
|
+
throw new Error("Cannot execute command. Command not found: " + commandName);
|
|
411
|
+
}
|
|
412
|
+
executeOptions = {
|
|
413
|
+
initialState: this.textController.getState(),
|
|
414
|
+
textApi: this.textController
|
|
415
|
+
};
|
|
416
|
+
if (!(((_a = command.shouldUndo) === null || _a === void 0 ? void 0 : _a.call(command, executeOptions)) && (command === null || command === void 0 ? void 0 : command.undo))) return [3 /*break*/, 1];
|
|
417
|
+
command.undo(executeOptions);
|
|
418
|
+
return [3 /*break*/, 3];
|
|
419
|
+
case 1: return [4 /*yield*/, command.execute(executeOptions)];
|
|
420
|
+
case 2:
|
|
421
|
+
_b.sent();
|
|
422
|
+
_b.label = 3;
|
|
423
|
+
case 3: return [2 /*return*/];
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
};
|
|
428
|
+
return CommandController;
|
|
429
|
+
}());
|
|
430
|
+
|
|
431
|
+
var TextAreaTextController = /** @class */ (function () {
|
|
432
|
+
function TextAreaTextController(textAreaRef) {
|
|
433
|
+
this.textAreaRef = textAreaRef;
|
|
434
|
+
}
|
|
435
|
+
TextAreaTextController.prototype.replaceSelection = function (text) {
|
|
436
|
+
var textArea = this.textAreaRef.current;
|
|
437
|
+
if (!textArea) {
|
|
438
|
+
throw new Error("TextAreaRef is not set");
|
|
439
|
+
}
|
|
440
|
+
insertText(textArea, text);
|
|
441
|
+
return getStateFromTextArea(textArea);
|
|
442
|
+
};
|
|
443
|
+
TextAreaTextController.prototype.setSelectionRange = function (selection) {
|
|
444
|
+
var textArea = this.textAreaRef.current;
|
|
445
|
+
if (!textArea) {
|
|
446
|
+
throw new Error("TextAreaRef is not set");
|
|
447
|
+
}
|
|
448
|
+
textArea.focus();
|
|
449
|
+
textArea.selectionStart = selection.start;
|
|
450
|
+
textArea.selectionEnd = selection.end;
|
|
451
|
+
return getStateFromTextArea(textArea);
|
|
452
|
+
};
|
|
453
|
+
TextAreaTextController.prototype.getState = function () {
|
|
454
|
+
var textArea = this.textAreaRef.current;
|
|
455
|
+
if (!textArea) {
|
|
456
|
+
throw new Error("TextAreaRef is not set");
|
|
457
|
+
}
|
|
458
|
+
return getStateFromTextArea(textArea);
|
|
459
|
+
};
|
|
460
|
+
return TextAreaTextController;
|
|
461
|
+
}());
|
|
462
|
+
function getStateFromTextArea(textArea) {
|
|
463
|
+
return {
|
|
464
|
+
selection: {
|
|
465
|
+
start: textArea.selectionStart,
|
|
466
|
+
end: textArea.selectionEnd
|
|
467
|
+
},
|
|
468
|
+
text: textArea.value
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Inserts the given text at the cursor. If the element contains a selection, the selection
|
|
473
|
+
* will be replaced by the text.
|
|
474
|
+
* The MIT License
|
|
475
|
+
* Copyright (c) 2018 Dmitriy Kubyshkin
|
|
476
|
+
* Copied from https://github.com/grassator/insert-text-at-cursor
|
|
477
|
+
*/
|
|
478
|
+
function insertText(input, text) {
|
|
479
|
+
var _a;
|
|
480
|
+
// Most of the used APIs only work with the field selected
|
|
481
|
+
input.focus();
|
|
482
|
+
// IE 8-10
|
|
483
|
+
if (document.selection) {
|
|
484
|
+
var ieRange = document.selection.createRange();
|
|
485
|
+
ieRange.text = text;
|
|
486
|
+
// Move cursor after the inserted text
|
|
487
|
+
ieRange.collapse(false /* to the end */);
|
|
488
|
+
ieRange.select();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
// Webkit + Edge
|
|
492
|
+
var isSuccess = document.execCommand("insertText", false, text);
|
|
493
|
+
if (!isSuccess) {
|
|
494
|
+
var start = input.selectionStart || 0;
|
|
495
|
+
var end = input.selectionEnd || 0;
|
|
496
|
+
// Firefox (non-standard method)
|
|
497
|
+
if (typeof input.setRangeText === "function") {
|
|
498
|
+
input.setRangeText(text);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
if (canManipulateViaTextNodes(input)) {
|
|
502
|
+
var textNode = document.createTextNode(text);
|
|
503
|
+
var node = input.firstChild;
|
|
504
|
+
// If textarea is empty, just insert the text
|
|
505
|
+
if (!node) {
|
|
506
|
+
input.appendChild(textNode);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
// Otherwise, we need to find a nodes for start and end
|
|
510
|
+
var offset = 0;
|
|
511
|
+
var startNode = null;
|
|
512
|
+
var endNode = null;
|
|
513
|
+
// To make a change we just need a Range, not a Selection
|
|
514
|
+
var range = document.createRange();
|
|
515
|
+
while (node && (startNode === null || endNode === null)) {
|
|
516
|
+
var nodeLength = ((_a = node.nodeValue) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
517
|
+
// if start of the selection falls into current node
|
|
518
|
+
if (start >= offset && start <= offset + nodeLength) {
|
|
519
|
+
range.setStart((startNode = node), start - offset);
|
|
520
|
+
}
|
|
521
|
+
// if end of the selection falls into current node
|
|
522
|
+
if (end >= offset && end <= offset + nodeLength) {
|
|
523
|
+
range.setEnd((endNode = node), end - offset);
|
|
524
|
+
}
|
|
525
|
+
offset += nodeLength;
|
|
526
|
+
node = node.nextSibling;
|
|
527
|
+
}
|
|
528
|
+
// If there is some text selected, remove it as we should replace it
|
|
529
|
+
if (start !== end) {
|
|
530
|
+
range.deleteContents();
|
|
531
|
+
}
|
|
532
|
+
// Finally insert a new node. The browser will automatically
|
|
533
|
+
// split start and end nodes into two if necessary
|
|
534
|
+
range.insertNode(textNode);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// For the text input the only way is to replace the whole value :(
|
|
539
|
+
var value = input.value;
|
|
540
|
+
input.value = value.slice(0, start) + text + value.slice(end);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Correct the cursor position to be at the end of the insertion
|
|
544
|
+
input.setSelectionRange(start + text.length, start + text.length);
|
|
545
|
+
// Notify any possible listeners of the change
|
|
546
|
+
var e = document.createEvent("UIEvent");
|
|
547
|
+
e.initEvent("input", true, false);
|
|
548
|
+
input.dispatchEvent(e);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The MIT License
|
|
553
|
+
* Copyright (c) 2018 Dmitriy Kubyshkin
|
|
554
|
+
* Copied from https://github.com/grassator/insert-text-at-cursor
|
|
555
|
+
*/
|
|
556
|
+
function canManipulateViaTextNodes(input) {
|
|
557
|
+
if (input.nodeName !== "TEXTAREA") {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
var browserSupportsTextareaTextNodes;
|
|
561
|
+
if (typeof browserSupportsTextareaTextNodes === "undefined") {
|
|
562
|
+
var textarea = document.createElement("textarea");
|
|
563
|
+
textarea.value = "1";
|
|
564
|
+
browserSupportsTextareaTextNodes = !!textarea.firstChild;
|
|
565
|
+
}
|
|
566
|
+
return browserSupportsTextareaTextNodes;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function makeList(state0, textController, insertBefore) {
|
|
570
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
571
|
+
var newSelectionRange = selectWord({
|
|
572
|
+
text: state0.text,
|
|
573
|
+
selection: state0.selection
|
|
574
|
+
});
|
|
575
|
+
var state1 = textController.setSelectionRange(newSelectionRange);
|
|
576
|
+
var breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
577
|
+
var breaksBefore = Array(breaksBeforeCount + 1).join("\n");
|
|
578
|
+
var breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
|
|
579
|
+
var breaksAfter = Array(breaksAfterCount + 1).join("\n");
|
|
580
|
+
var modifiedText = insertBeforeEachLine(getSelectedText(state1), insertBefore);
|
|
581
|
+
textController.replaceSelection("" + breaksBefore + modifiedText.modifiedText + breaksAfter);
|
|
582
|
+
// Specifically when the text has only one line, we can exclude the "- ", for example, from the selection
|
|
583
|
+
var oneLinerOffset = getSelectedText(state1).indexOf("\n") === -1
|
|
584
|
+
? modifiedText.insertionLength
|
|
585
|
+
: 0;
|
|
586
|
+
var selectionStart = state1.selection.start + breaksBeforeCount + oneLinerOffset;
|
|
587
|
+
var selectionEnd = selectionStart + modifiedText.modifiedText.length - oneLinerOffset;
|
|
588
|
+
// Adjust the selection to not contain the **
|
|
589
|
+
textController.setSelectionRange({
|
|
590
|
+
start: selectionStart,
|
|
591
|
+
end: selectionEnd
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
var listHelpers = /*#__PURE__*/Object.freeze({
|
|
596
|
+
__proto__: null,
|
|
597
|
+
makeList: makeList
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
var codeCommand = {
|
|
601
|
+
shouldUndo: function (options) {
|
|
602
|
+
return (getCharactersBeforeSelection(options.initialState, 1) === "`" &&
|
|
603
|
+
getCharactersAfterSelection(options.initialState, 1) === "`");
|
|
604
|
+
},
|
|
605
|
+
execute: function (_a) {
|
|
606
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
607
|
+
// Adjust the selection to encompass the whole word if the caret is inside one
|
|
608
|
+
var newSelectionRange = selectWord({
|
|
609
|
+
text: initialState.text,
|
|
610
|
+
selection: initialState.selection
|
|
611
|
+
});
|
|
612
|
+
var state1 = textApi.setSelectionRange(newSelectionRange);
|
|
613
|
+
// Replaces the current selection with the italic mark up
|
|
614
|
+
var state2 = textApi.replaceSelection("`" + getSelectedText(state1) + "`");
|
|
615
|
+
// Adjust the selection to not contain the *
|
|
616
|
+
textApi.setSelectionRange({
|
|
617
|
+
start: state2.selection.end - 1 - getSelectedText(state1).length,
|
|
618
|
+
end: state2.selection.end - 1
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
undo: function (_a) {
|
|
622
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
623
|
+
var text = getSelectedText(initialState);
|
|
624
|
+
textApi.setSelectionRange({
|
|
625
|
+
start: initialState.selection.start - 1,
|
|
626
|
+
end: initialState.selection.end + 1
|
|
627
|
+
});
|
|
628
|
+
textApi.replaceSelection(text);
|
|
629
|
+
textApi.setSelectionRange({
|
|
630
|
+
start: initialState.selection.start - 1,
|
|
631
|
+
end: initialState.selection.end - 1
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
function useTextAreaMarkdownEditor(options) {
|
|
637
|
+
var textAreaRef = react.useRef(null);
|
|
638
|
+
var textController = react.useMemo(function () {
|
|
639
|
+
return new TextAreaTextController(textAreaRef);
|
|
640
|
+
}, [textAreaRef]);
|
|
641
|
+
var commandController = react.useMemo(function () { return new CommandController(textController, options.commandMap); }, [textAreaRef]);
|
|
642
|
+
return {
|
|
643
|
+
textController: textController,
|
|
644
|
+
commandController: commandController,
|
|
645
|
+
ref: textAreaRef
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
var codeBlockCommand = {
|
|
650
|
+
execute: function (_a) {
|
|
651
|
+
var textApi = _a.textApi, initialState = _a.initialState;
|
|
652
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
653
|
+
var newSelectionRange, state1, selectionStart_1, selectionEnd_1, breaksBeforeCount, breaksBefore, breaksAfterCount, breaksAfter, selectionStart, selectionEnd;
|
|
654
|
+
return __generator(this, function (_b) {
|
|
655
|
+
newSelectionRange = selectWord({
|
|
656
|
+
text: initialState.text,
|
|
657
|
+
selection: initialState.selection
|
|
658
|
+
});
|
|
659
|
+
state1 = textApi.setSelectionRange(newSelectionRange);
|
|
660
|
+
// when there's no breaking line
|
|
661
|
+
if (getSelectedText(state1).indexOf("\n") === -1) {
|
|
662
|
+
textApi.replaceSelection("`" + getSelectedText(state1) + "`");
|
|
663
|
+
selectionStart_1 = state1.selection.start + 1;
|
|
664
|
+
selectionEnd_1 = selectionStart_1 + getSelectedText(state1).length;
|
|
665
|
+
textApi.setSelectionRange({
|
|
666
|
+
start: selectionStart_1,
|
|
667
|
+
end: selectionEnd_1
|
|
668
|
+
});
|
|
669
|
+
return [2 /*return*/];
|
|
670
|
+
}
|
|
671
|
+
breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
672
|
+
breaksBefore = Array(breaksBeforeCount + 1).join("\n");
|
|
673
|
+
breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
|
|
674
|
+
breaksAfter = Array(breaksAfterCount + 1).join("\n");
|
|
675
|
+
textApi.replaceSelection(breaksBefore + "```\n" + getSelectedText(state1) + "\n```" + breaksAfter);
|
|
676
|
+
selectionStart = state1.selection.start + breaksBeforeCount + 4;
|
|
677
|
+
selectionEnd = selectionStart + getSelectedText(state1).length;
|
|
678
|
+
textApi.setSelectionRange({
|
|
679
|
+
start: selectionStart,
|
|
680
|
+
end: selectionEnd
|
|
681
|
+
});
|
|
682
|
+
return [2 /*return*/];
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
var checkedListCommand = {
|
|
689
|
+
execute: function (_a) {
|
|
690
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
691
|
+
makeList(initialState, textApi, function () { return "- [ ] "; });
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
var orderedListCommand = {
|
|
696
|
+
execute: function (_a) {
|
|
697
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
698
|
+
makeList(initialState, textApi, function (item, index) { return index + 1 + ". "; });
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
var unorderedListCommand = {
|
|
703
|
+
execute: function (_a) {
|
|
704
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
705
|
+
makeList(initialState, textApi, "- ");
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
var headingLevel2Command = {
|
|
710
|
+
execute: function (_a) {
|
|
711
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
712
|
+
setHeader(initialState, textApi, "## ");
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
var headingLevel3Command = {
|
|
717
|
+
execute: function (_a) {
|
|
718
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
719
|
+
setHeader(initialState, textApi, "### ");
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
var headingLevel4Command = {
|
|
724
|
+
execute: function (_a) {
|
|
725
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
726
|
+
setHeader(initialState, textApi, "#### ");
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
var headingLevel5Command = {
|
|
731
|
+
execute: function (_a) {
|
|
732
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
733
|
+
setHeader(initialState, textApi, "##### ");
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
var headingLevel6Command = {
|
|
738
|
+
execute: function (_a) {
|
|
739
|
+
var initialState = _a.initialState, textApi = _a.textApi;
|
|
740
|
+
setHeader(initialState, textApi, "###### ");
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
exports.CommandController = CommandController;
|
|
745
|
+
exports.TextAreaTextController = TextAreaTextController;
|
|
746
|
+
exports.boldCommand = boldCommand;
|
|
747
|
+
exports.checkedListCommand = checkedListCommand;
|
|
748
|
+
exports.codeBlockCommand = codeBlockCommand;
|
|
749
|
+
exports.codeCommand = codeCommand;
|
|
750
|
+
exports.headerHelpers = headerHelpers;
|
|
751
|
+
exports.headingLevel1Command = headingLevel1Command;
|
|
752
|
+
exports.headingLevel2Command = headingLevel2Command;
|
|
753
|
+
exports.headingLevel3Command = headingLevel3Command;
|
|
754
|
+
exports.headingLevel4Command = headingLevel4Command;
|
|
755
|
+
exports.headingLevel5Command = headingLevel5Command;
|
|
756
|
+
exports.headingLevel6Command = headingLevel6Command;
|
|
757
|
+
exports.imageCommand = imageCommand;
|
|
758
|
+
exports.italicCommand = italicCommand;
|
|
759
|
+
exports.linkCommand = linkCommand;
|
|
760
|
+
exports.listHelpers = listHelpers;
|
|
761
|
+
exports.orderedListCommand = orderedListCommand;
|
|
762
|
+
exports.quoteCommand = quoteCommand;
|
|
763
|
+
exports.strikethroughCommand = strikethroughCommand;
|
|
764
|
+
exports.textHelpers = textHelpers;
|
|
765
|
+
exports.unorderedListCommand = unorderedListCommand;
|
|
766
|
+
exports.useTextAreaMarkdownEditor = useTextAreaMarkdownEditor;
|
|
767
|
+
//# sourceMappingURL=index.js.map
|