@ember-eui/core 1.2.5 → 1.3.5

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +1 -1
  3. package/addon/components/common.ts +13 -0
  4. package/addon/components/eui-button/index.hbs +2 -0
  5. package/addon/components/eui-button-content/index.hbs +1 -0
  6. package/addon/components/eui-button-empty/index.hbs +2 -0
  7. package/addon/components/eui-button-icon/index.hbs +2 -0
  8. package/addon/components/eui-code/index.hbs +9 -0
  9. package/addon/components/eui-code-block/index.d.ts +2 -0
  10. package/addon/components/eui-code-block/index.hbs +10 -0
  11. package/addon/components/eui-code-block-impl/code-block-controls/index.hbs +33 -0
  12. package/addon/components/eui-code-block-impl/index.hbs +111 -0
  13. package/addon/components/eui-code-block-impl/index.ts +121 -0
  14. package/addon/components/eui-copy/index.hbs +8 -0
  15. package/addon/components/eui-copy/index.ts +37 -0
  16. package/addon/components/eui-icon/index.hbs +37 -32
  17. package/addon/components/eui-icon/index.ts +1 -1
  18. package/addon/components/eui-inner-text/index.hbs +1 -0
  19. package/addon/components/eui-inner-text/index.ts +61 -0
  20. package/addon/components/eui-markdown-editor/index.hbs +63 -0
  21. package/addon/components/eui-markdown-editor/index.ts +221 -0
  22. package/addon/components/eui-markdown-editor-drop-zone/index.hbs +21 -0
  23. package/addon/components/eui-markdown-editor-drop-zone/index.ts +5 -0
  24. package/addon/components/eui-markdown-editor-footer/index.hbs +108 -0
  25. package/addon/components/eui-markdown-editor-footer/index.ts +20 -0
  26. package/addon/components/eui-markdown-editor-text-area/index.hbs +8 -0
  27. package/addon/components/eui-markdown-editor-toolbar/index.hbs +86 -0
  28. package/addon/components/eui-markdown-editor-toolbar/index.ts +97 -0
  29. package/addon/components/eui-markdown-format/index.hbs +13 -0
  30. package/addon/components/eui-markdown-format/index.ts +45 -0
  31. package/addon/components/eui-markdown-format/markdown-checkbox/index.hbs +8 -0
  32. package/addon/components/eui-markdown-format/markdown-checkbox/index.ts +28 -0
  33. package/addon/components/eui-markdown-format/markdown-code/index.hbs +3 -0
  34. package/addon/components/eui-markdown-format/markdown-code-block/index.hbs +7 -0
  35. package/addon/components/eui-markdown-format/markdown-tooltip/index.hbs +8 -0
  36. package/addon/components/markdown-checkmark-icon/index.hbs +0 -0
  37. package/addon/modifiers/get-cursor-node.ts +54 -0
  38. package/addon/modifiers/resize-observer.ts +6 -2
  39. package/addon/utils/copy-to-clipboard.ts +70 -0
  40. package/addon/utils/css-mappings/eui-code-block-impl.ts +24 -0
  41. package/addon/utils/css-mappings/index.ts +2 -0
  42. package/addon/utils/markdown/markdown-actions.ts +616 -0
  43. package/addon/utils/markdown/markdown-modes.ts +23 -0
  44. package/addon/utils/markdown/markdown-types.ts +140 -0
  45. package/addon/utils/markdown/markdown-unified-plugins.d.ts +27 -0
  46. package/addon/utils/markdown/plugins/markdown-add-components.ts +63 -0
  47. package/addon/utils/markdown/plugins/markdown-checkbox.ts +80 -0
  48. package/addon/utils/markdown/plugins/markdown-default-plugins.ts +100 -0
  49. package/addon/utils/markdown/plugins/markdown-tooltip.ts +113 -0
  50. package/addon/utils/markdown/plugins/to-dom.ts +93 -0
  51. package/app/components/eui-code/index.js +1 -0
  52. package/app/components/eui-code-block/index.js +1 -0
  53. package/app/components/eui-code-block-impl/code-block-controls/index.js +1 -0
  54. package/app/components/eui-code-block-impl/index.js +1 -0
  55. package/app/components/eui-copy/index.js +1 -0
  56. package/app/components/eui-inner-text/index.js +1 -0
  57. package/app/components/eui-markdown-editor/index.js +1 -0
  58. package/app/components/eui-markdown-editor-drop-zone/index.js +1 -0
  59. package/app/components/eui-markdown-editor-footer/index.js +1 -0
  60. package/app/components/eui-markdown-editor-text-area/index.js +1 -0
  61. package/app/components/eui-markdown-editor-toolbar/index.js +1 -0
  62. package/app/components/eui-markdown-format/index.js +1 -0
  63. package/app/components/eui-markdown-format/markdown-checkbox/index.js +1 -0
  64. package/app/components/eui-markdown-format/markdown-code/index.js +1 -0
  65. package/app/components/eui-markdown-format/markdown-code-block/index.js +1 -0
  66. package/app/components/eui-markdown-format/markdown-tooltip/index.js +1 -0
  67. package/app/modifiers/get-cursor-node.js +1 -0
  68. package/app/styles/ember-eui.scss +13 -0
  69. package/docs/editors/code/code-block-demo/demo1.md +62 -0
  70. package/docs/editors/code/code-block.md +1 -0
  71. package/docs/editors/code/inline-demo/demo1.md +49 -0
  72. package/docs/editors/code/inline.md +1 -0
  73. package/docs/editors/markdown-editor/base-editor-demo/demo1.md +86 -0
  74. package/docs/editors/markdown-editor/base-editor.md +1 -0
  75. package/package.json +17 -4
  76. package/public/markdown-checkmark.svg +3 -0
  77. package/public/markdown-logo.svg +3 -0
@@ -0,0 +1,616 @@
1
+ /*
2
+ * Licensed to Elasticsearch B.V. under one or more contributor
3
+ * license agreements. See the NOTICE file distributed with
4
+ * this work for additional information regarding copyright
5
+ * ownership. Elasticsearch B.V. licenses this file to you under
6
+ * the Apache License, Version 2.0 (the "License"); you may
7
+ * not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ // The text-formatting logic & code in this file come from
21
+ // https://github.com/github/markdown-toolbar-element/blob/main/src/index.ts
22
+ // under the following MIT license
23
+ /*
24
+ Copyright (c) 2017-2018 GitHub, Inc.
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy
26
+ of this software and associated documentation files (the "Software"), to deal
27
+ in the Software without restriction, including without limitation the rights
28
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29
+ copies of the Software, and to permit persons to whom the Software is
30
+ furnished to do so, subject to the following conditions:
31
+ The above copyright notice and this permission notice shall be included in all
32
+ copies or substantial portions of the Software.
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39
+ SOFTWARE.
40
+ */
41
+
42
+ import {
43
+ EuiMarkdownEditorUiPlugin,
44
+ EuiMarkdownFormatting,
45
+ isPluginWithImmediateFormatting
46
+ } from './markdown-types';
47
+
48
+ /**
49
+ * Class for applying styles to a text editor. Accepts the HTML ID for the textarea
50
+ * desired, and exposes the `.do(ACTION)` method for manipulating the text.
51
+ *
52
+ * @class MarkdownActions
53
+ * @param {string} editorID
54
+ */
55
+ class MarkdownActions {
56
+ styles: Record<string, EuiMarkdownEditorUiPlugin>;
57
+ editorID: string = '';
58
+
59
+ constructor(editorID: string, uiPlugins: EuiMarkdownEditorUiPlugin[]) {
60
+ this.editorID = editorID;
61
+ /**
62
+ * This object is in the format:
63
+ * [nameOfAction]: {[styles to apply]}
64
+ */
65
+ this.styles = {
66
+ ...uiPlugins.reduce<MarkdownActions['styles']>(
67
+ (mappedPlugins, plugin) => {
68
+ mappedPlugins[plugin.name] = plugin;
69
+ return mappedPlugins;
70
+ },
71
+ {}
72
+ ),
73
+ mdBold: {
74
+ name: 'mdBold',
75
+ button: { label: '', iconType: '' },
76
+ formatting: {
77
+ prefix: '**',
78
+ suffix: '**',
79
+ trimFirst: true
80
+ }
81
+ },
82
+ mdItalic: {
83
+ name: 'mdItalic',
84
+ button: { label: '', iconType: '' },
85
+ formatting: {
86
+ prefix: '_',
87
+ suffix: '_',
88
+ trimFirst: true
89
+ }
90
+ },
91
+ mdQuote: {
92
+ name: 'mdQuote',
93
+ button: { label: '', iconType: '' },
94
+ formatting: {
95
+ prefix: '> ',
96
+ multiline: true,
97
+ surroundWithNewlines: true
98
+ }
99
+ },
100
+ mdCode: {
101
+ name: 'mdCode',
102
+ button: { label: '', iconType: '' },
103
+ formatting: {
104
+ prefix: '`',
105
+ suffix: '`',
106
+ blockPrefix: '```',
107
+ blockSuffix: '```'
108
+ }
109
+ },
110
+ mdLink: {
111
+ name: 'mdLink',
112
+ button: { label: '', iconType: '' },
113
+ formatting: {
114
+ prefix: '[',
115
+ suffix: '](url)',
116
+ replaceNext: 'url',
117
+ scanFor: 'https?://'
118
+ }
119
+ },
120
+ mdUl: {
121
+ name: 'mdUl',
122
+ button: { label: '', iconType: '' },
123
+ formatting: {
124
+ prefix: '- ',
125
+ multiline: true,
126
+ surroundWithNewlines: true
127
+ }
128
+ },
129
+ mdOl: {
130
+ name: 'mdOl',
131
+ button: { label: '', iconType: '' },
132
+ formatting: {
133
+ prefix: '1. ',
134
+ multiline: true,
135
+ orderedList: true
136
+ }
137
+ },
138
+ mdTl: {
139
+ name: 'mdTl',
140
+ button: { label: '', iconType: '' },
141
+ formatting: {
142
+ prefix: '- [ ] ',
143
+ multiline: true,
144
+ surroundWithNewlines: true
145
+ }
146
+ }
147
+ };
148
+ }
149
+
150
+ /**
151
+ * .do() accepts a string and retrieves the correlating style object (defined in the
152
+ * constructor). It passes this to applyStyle() that does the text manipulation.
153
+ *
154
+ * @param {string} pluginName
155
+ * @memberof MarkdownActions
156
+ */
157
+ do(pluginName: string) {
158
+ const plugin = this.styles[pluginName];
159
+ if (isPluginWithImmediateFormatting(plugin)) {
160
+ this.applyStyle(plugin.formatting);
161
+ return true;
162
+ } else {
163
+ return plugin;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Sets the default styling object and then superimposes the changes to make on top of
169
+ * it. Calls the `styleSelectedText` helper function that does the heavy lifting.
170
+ * Adapted from https://github.com/github/markdown-toolbar-element/blob/main/src/index.ts
171
+ *
172
+ * @param {object} incomingStyle
173
+ * @memberof MarkdownActions
174
+ */
175
+ applyStyle(incomingStyle: EuiMarkdownFormatting) {
176
+ const defaults = {
177
+ prefix: '',
178
+ suffix: '',
179
+ blockPrefix: '',
180
+ blockSuffix: '',
181
+ multiline: false,
182
+ replaceNext: '',
183
+ prefixSpace: false,
184
+ scanFor: '',
185
+ surroundWithNewlines: false,
186
+ orderedList: false,
187
+ trimFirst: false
188
+ };
189
+
190
+ const outgoingStyle = {
191
+ ...defaults,
192
+ ...incomingStyle
193
+ };
194
+ const editor = document.getElementById(
195
+ this.editorID
196
+ ) as HTMLTextAreaElement;
197
+
198
+ if (editor) {
199
+ editor.focus();
200
+ styleSelectedText(editor, outgoingStyle);
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * The following helper functions and types were copied from the GitHub Markdown Toolbar
207
+ * Element project. The project is MIT-licensed. See it here:
208
+ * https://github.com/github/markdown-toolbar-element
209
+ */
210
+
211
+ interface Newlines {
212
+ newlinesToAppend: string;
213
+ newlinesToPrepend: string;
214
+ }
215
+
216
+ interface SelectionRange {
217
+ text: string;
218
+ selectionStart?: number;
219
+ selectionEnd?: number;
220
+ }
221
+
222
+ interface StyleArgs {
223
+ prefix: string;
224
+ suffix: string;
225
+ blockPrefix: string;
226
+ blockSuffix: string;
227
+ multiline: boolean;
228
+ replaceNext: string;
229
+ prefixSpace: boolean;
230
+ scanFor: string;
231
+ surroundWithNewlines: boolean;
232
+ orderedList: boolean;
233
+ trimFirst: boolean;
234
+ }
235
+
236
+ function isMultipleLines(string: string): boolean {
237
+ return string.trim().split('\n').length > 1;
238
+ }
239
+
240
+ function repeat(string: string, n: number): string {
241
+ return Array(n + 1).join(string);
242
+ }
243
+
244
+ function wordSelectionStart(text: string, i: number): number {
245
+ let index = i;
246
+ while (
247
+ text[index] &&
248
+ text[index - 1] != null &&
249
+ !text[index - 1].match(/\s/)
250
+ ) {
251
+ index--;
252
+ }
253
+ return index;
254
+ }
255
+
256
+ function wordSelectionEnd(text: string, i: number, multiline: boolean): number {
257
+ let index = i;
258
+ const breakpoint = multiline ? /\n/ : /\s/;
259
+ while (text[index] && !text[index].match(breakpoint)) {
260
+ index++;
261
+ }
262
+ return index;
263
+ }
264
+
265
+ const MAX_TRIES = 10;
266
+ const TRY_TIMEOUT = 10; /*ms*/
267
+ // modified from https://github.com/github/markdown-toolbar-element/blob/main/src/index.ts
268
+ export function insertText(
269
+ textarea: HTMLTextAreaElement,
270
+ { text, selectionStart, selectionEnd }: SelectionRange
271
+ ) {
272
+ const originalSelectionStart = textarea.selectionStart;
273
+ const before = textarea.value.slice(0, originalSelectionStart);
274
+ const after = textarea.value.slice(textarea.selectionEnd);
275
+
276
+ // configuration modal/dialog will continue intercepting focus in Safari
277
+ // need to wait until the textarea can receive focus
278
+ let tries = 0;
279
+
280
+ const insertText = () => {
281
+ const insertResult = document.execCommand('insertText', false, text);
282
+
283
+ if (insertResult === false) {
284
+ /**
285
+ * Fallback for Firefox; this kills undo/redo but at least updates the value
286
+ *
287
+ * Note that we're using the native HTMLTextAreaElement.set() method to play nicely with
288
+ * React's synthetic event system.
289
+ * https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04
290
+ */
291
+ const inputEvent = new Event('input', { bubbles: true });
292
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
293
+ window.HTMLTextAreaElement.prototype,
294
+ 'value'
295
+ )!.set;
296
+ nativeInputValueSetter!.call(textarea, before + text + after);
297
+ textarea.dispatchEvent(inputEvent);
298
+ }
299
+
300
+ if (selectionStart != null && selectionEnd != null) {
301
+ textarea.setSelectionRange(selectionStart, selectionEnd);
302
+ } else {
303
+ textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd);
304
+ }
305
+ };
306
+
307
+ const focusTextarea = () => {
308
+ textarea.focus();
309
+ if (document.activeElement === textarea) {
310
+ insertText();
311
+ } else if (++tries === MAX_TRIES) {
312
+ insertText();
313
+ } else {
314
+ setTimeout(focusTextarea, TRY_TIMEOUT);
315
+ }
316
+ };
317
+
318
+ focusTextarea();
319
+ }
320
+
321
+ // from https://github.com/github/markdown-toolbar-element/blob/main/src/index.ts
322
+ function styleSelectedText(
323
+ textarea: HTMLTextAreaElement,
324
+ styleArgs: StyleArgs
325
+ ) {
326
+ const text = textarea.value.slice(
327
+ textarea.selectionStart,
328
+ textarea.selectionEnd
329
+ );
330
+
331
+ let result;
332
+ if (styleArgs.orderedList) {
333
+ result = orderedList(textarea);
334
+ } else if (styleArgs.multiline && isMultipleLines(text)) {
335
+ result = multilineStyle(textarea, styleArgs);
336
+ } else {
337
+ result = blockStyle(textarea, styleArgs);
338
+ }
339
+
340
+ insertText(textarea, result);
341
+ }
342
+
343
+ function expandSelectedText(
344
+ textarea: HTMLTextAreaElement,
345
+ prefixToUse: string,
346
+ suffixToUse: string,
347
+ multiline: boolean = false
348
+ ): string {
349
+ if (textarea.selectionStart === textarea.selectionEnd) {
350
+ textarea.selectionStart = wordSelectionStart(
351
+ textarea.value,
352
+ textarea.selectionStart
353
+ );
354
+ textarea.selectionEnd = wordSelectionEnd(
355
+ textarea.value,
356
+ textarea.selectionEnd,
357
+ multiline
358
+ );
359
+ } else {
360
+ const expandedSelectionStart = textarea.selectionStart - prefixToUse.length;
361
+ const expandedSelectionEnd = textarea.selectionEnd + suffixToUse.length;
362
+ const beginsWithPrefix =
363
+ textarea.value.slice(expandedSelectionStart, textarea.selectionStart) ===
364
+ prefixToUse;
365
+ const endsWithSuffix =
366
+ textarea.value.slice(textarea.selectionEnd, expandedSelectionEnd) ===
367
+ suffixToUse;
368
+ if (beginsWithPrefix && endsWithSuffix) {
369
+ textarea.selectionStart = expandedSelectionStart;
370
+ textarea.selectionEnd = expandedSelectionEnd;
371
+ }
372
+ }
373
+ return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
374
+ }
375
+
376
+ function newlinesToSurroundSelectedText(
377
+ textarea: HTMLTextAreaElement
378
+ ): Newlines {
379
+ const beforeSelection = textarea.value.slice(0, textarea.selectionStart);
380
+ const afterSelection = textarea.value.slice(textarea.selectionEnd);
381
+
382
+ const breaksBefore = beforeSelection.match(/\n*$/);
383
+ const breaksAfter = afterSelection.match(/^\n*/);
384
+ const newlinesBeforeSelection = breaksBefore ? breaksBefore[0].length : 0;
385
+ const newlinesAfterSelection = breaksAfter ? breaksAfter[0].length : 0;
386
+
387
+ let newlinesToAppend;
388
+ let newlinesToPrepend;
389
+
390
+ if (beforeSelection.match(/\S/) && newlinesBeforeSelection < 2) {
391
+ newlinesToAppend = repeat('\n', 2 - newlinesBeforeSelection);
392
+ }
393
+
394
+ if (afterSelection.match(/\S/) && newlinesAfterSelection < 2) {
395
+ newlinesToPrepend = repeat('\n', 2 - newlinesAfterSelection);
396
+ }
397
+
398
+ if (newlinesToAppend == null) {
399
+ newlinesToAppend = '';
400
+ }
401
+
402
+ if (newlinesToPrepend == null) {
403
+ newlinesToPrepend = '';
404
+ }
405
+
406
+ return { newlinesToAppend, newlinesToPrepend };
407
+ }
408
+
409
+ function blockStyle(
410
+ textarea: HTMLTextAreaElement,
411
+ arg: StyleArgs
412
+ ): SelectionRange {
413
+ let newlinesToAppend;
414
+ let newlinesToPrepend;
415
+
416
+ const {
417
+ prefix,
418
+ suffix,
419
+ blockPrefix,
420
+ blockSuffix,
421
+ replaceNext,
422
+ prefixSpace,
423
+ scanFor,
424
+ surroundWithNewlines
425
+ } = arg;
426
+ const originalSelectionStart = textarea.selectionStart;
427
+ const originalSelectionEnd = textarea.selectionEnd;
428
+
429
+ let selectedText = textarea.value.slice(
430
+ textarea.selectionStart,
431
+ textarea.selectionEnd
432
+ );
433
+ let prefixToUse =
434
+ isMultipleLines(selectedText) && blockPrefix.length > 0
435
+ ? `${blockPrefix}\n`
436
+ : prefix;
437
+ let suffixToUse =
438
+ isMultipleLines(selectedText) && blockSuffix.length > 0
439
+ ? `\n${blockSuffix}`
440
+ : suffix;
441
+
442
+ if (prefixSpace) {
443
+ const beforeSelection = textarea.value[textarea.selectionStart - 1];
444
+ if (
445
+ textarea.selectionStart !== 0 &&
446
+ beforeSelection != null &&
447
+ !beforeSelection.match(/\s/)
448
+ ) {
449
+ prefixToUse = ` ${prefixToUse}`;
450
+ }
451
+ }
452
+ selectedText = expandSelectedText(
453
+ textarea,
454
+ prefixToUse,
455
+ suffixToUse,
456
+ arg.multiline
457
+ );
458
+ let selectionStart = textarea.selectionStart;
459
+ let selectionEnd = textarea.selectionEnd;
460
+ const hasReplaceNext =
461
+ replaceNext.length > 0 &&
462
+ suffixToUse.indexOf(replaceNext) > -1 &&
463
+ selectedText.length > 0;
464
+ if (surroundWithNewlines) {
465
+ const ref = newlinesToSurroundSelectedText(textarea);
466
+ newlinesToAppend = ref.newlinesToAppend;
467
+ newlinesToPrepend = ref.newlinesToPrepend;
468
+ prefixToUse = newlinesToAppend + prefix;
469
+ suffixToUse += newlinesToPrepend;
470
+ }
471
+
472
+ if (
473
+ selectedText.startsWith(prefixToUse) &&
474
+ selectedText.endsWith(suffixToUse)
475
+ ) {
476
+ const replacementText = selectedText.slice(
477
+ prefixToUse.length,
478
+ selectedText.length - suffixToUse.length
479
+ );
480
+ if (originalSelectionStart === originalSelectionEnd) {
481
+ let position = originalSelectionStart - prefixToUse.length;
482
+ position = Math.max(position, selectionStart);
483
+ position = Math.min(position, selectionStart + replacementText.length);
484
+ selectionStart = selectionEnd = position;
485
+ } else {
486
+ selectionEnd = selectionStart + replacementText.length;
487
+ }
488
+ return { text: replacementText, selectionStart, selectionEnd };
489
+ } else if (!hasReplaceNext) {
490
+ let replacementText = prefixToUse + selectedText + suffixToUse;
491
+ selectionStart = originalSelectionStart + prefixToUse.length;
492
+ selectionEnd = originalSelectionEnd + prefixToUse.length;
493
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g);
494
+ if (arg.trimFirst && whitespaceEdges) {
495
+ const leadingWhitespace = whitespaceEdges[0] || '';
496
+ const trailingWhitespace = whitespaceEdges[1] || '';
497
+ replacementText =
498
+ leadingWhitespace +
499
+ prefixToUse +
500
+ selectedText.trim() +
501
+ suffixToUse +
502
+ trailingWhitespace;
503
+ selectionStart += leadingWhitespace.length;
504
+ selectionEnd -= trailingWhitespace.length;
505
+ }
506
+ return { text: replacementText, selectionStart, selectionEnd };
507
+ } else if (scanFor.length > 0 && selectedText.match(scanFor)) {
508
+ suffixToUse = suffixToUse.replace(replaceNext, selectedText);
509
+ const replacementText = prefixToUse + suffixToUse;
510
+ selectionStart = selectionEnd = selectionStart + prefixToUse.length;
511
+ return { text: replacementText, selectionStart, selectionEnd };
512
+ } else {
513
+ const replacementText = prefixToUse + selectedText + suffixToUse;
514
+ selectionStart =
515
+ selectionStart +
516
+ prefixToUse.length +
517
+ selectedText.length +
518
+ suffixToUse.indexOf(replaceNext);
519
+ selectionEnd = selectionStart + replaceNext.length;
520
+ return { text: replacementText, selectionStart, selectionEnd };
521
+ }
522
+ }
523
+
524
+ function multilineStyle(textarea: HTMLTextAreaElement, arg: StyleArgs) {
525
+ const { prefix, suffix, surroundWithNewlines } = arg;
526
+ let text = textarea.value.slice(
527
+ textarea.selectionStart,
528
+ textarea.selectionEnd
529
+ );
530
+ let selectionStart = textarea.selectionStart;
531
+ let selectionEnd = textarea.selectionEnd;
532
+ const lines = text.split('\n');
533
+ const undoStyle = lines.every(
534
+ (line) => line.startsWith(prefix) && line.endsWith(suffix)
535
+ );
536
+
537
+ if (undoStyle) {
538
+ text = lines
539
+ .map((line) => line.slice(prefix.length, line.length - suffix.length))
540
+ .join('\n');
541
+ selectionEnd = selectionStart + text.length;
542
+ } else {
543
+ text = lines.map((line) => prefix + line + suffix).join('\n');
544
+ if (surroundWithNewlines) {
545
+ const { newlinesToAppend, newlinesToPrepend } =
546
+ newlinesToSurroundSelectedText(textarea);
547
+ selectionStart += newlinesToAppend.length;
548
+ selectionEnd = selectionStart + text.length;
549
+ text = newlinesToAppend + text + newlinesToPrepend;
550
+ }
551
+ }
552
+
553
+ return { text, selectionStart, selectionEnd };
554
+ }
555
+
556
+ function orderedList(textarea: HTMLTextAreaElement): SelectionRange {
557
+ const orderedListRegex = /^\d+\.\s+/;
558
+ const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
559
+ let selectionEnd;
560
+ let selectionStart;
561
+ let text = textarea.value.slice(
562
+ textarea.selectionStart,
563
+ textarea.selectionEnd
564
+ );
565
+ let textToUnstyle = text;
566
+ let lines = text.split('\n');
567
+ let startOfLine;
568
+ let endOfLine;
569
+ if (noInitialSelection) {
570
+ const linesBefore = textarea.value
571
+ .slice(0, textarea.selectionStart)
572
+ .split(/\n/);
573
+ startOfLine =
574
+ textarea.selectionStart - linesBefore[linesBefore.length - 1].length;
575
+ endOfLine = wordSelectionEnd(textarea.value, textarea.selectionStart, true);
576
+ textToUnstyle = textarea.value.slice(startOfLine, endOfLine);
577
+ }
578
+ const linesToUnstyle = textToUnstyle.split('\n');
579
+ const undoStyling = linesToUnstyle.every((line) =>
580
+ orderedListRegex.test(line)
581
+ );
582
+
583
+ if (undoStyling) {
584
+ lines = linesToUnstyle.map((line) => line.replace(orderedListRegex, ''));
585
+ text = lines.join('\n');
586
+ if (noInitialSelection && startOfLine && endOfLine) {
587
+ const lengthDiff = linesToUnstyle[0].length - lines[0].length;
588
+ selectionStart = selectionEnd = textarea.selectionStart - lengthDiff;
589
+ textarea.selectionStart = startOfLine;
590
+ textarea.selectionEnd = endOfLine;
591
+ }
592
+ } else {
593
+ lines = (function () {
594
+ let i;
595
+ let len;
596
+ let index;
597
+ const results = [];
598
+ for (index = i = 0, len = lines.length; i < len; index = ++i) {
599
+ const line = lines[index];
600
+ results.push(`${index + 1}. ${line}`);
601
+ }
602
+ return results;
603
+ })();
604
+ text = lines.join('\n');
605
+ const { newlinesToAppend, newlinesToPrepend } =
606
+ newlinesToSurroundSelectedText(textarea);
607
+ selectionStart = textarea.selectionStart + newlinesToAppend.length;
608
+ selectionEnd = selectionStart + text.length;
609
+ if (noInitialSelection) selectionStart = selectionEnd;
610
+ text = newlinesToAppend + text + newlinesToPrepend;
611
+ }
612
+
613
+ return { text, selectionStart, selectionEnd };
614
+ }
615
+
616
+ export default MarkdownActions;
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Licensed to Elasticsearch B.V. under one or more contributor
3
+ * license agreements. See the NOTICE file distributed with
4
+ * this work for additional information regarding copyright
5
+ * ownership. Elasticsearch B.V. licenses this file to you under
6
+ * the Apache License, Version 2.0 (the "License"); you may
7
+ * not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ export const MODE_EDITING = 'editing' as const;
21
+ export const MODE_VIEWING = 'viewing' as const;
22
+
23
+ export type MARKDOWN_MODE = typeof MODE_EDITING | typeof MODE_VIEWING;