@adminforth/markdown 1.9.0 → 1.10.0
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/build.log
CHANGED
|
@@ -9,6 +9,8 @@ custom/MarkdownRenderer.vue
|
|
|
9
9
|
custom/package-lock.json
|
|
10
10
|
custom/package.json
|
|
11
11
|
custom/tsconfig.json
|
|
12
|
+
custom/utils/
|
|
13
|
+
custom/utils/monacoMarkdownToggle.ts
|
|
12
14
|
|
|
13
|
-
sent
|
|
14
|
-
total size is
|
|
15
|
+
sent 38,446 bytes received 146 bytes 77,184.00 bytes/sec
|
|
16
|
+
total size is 37,901 speedup is 0.98
|
|
@@ -18,6 +18,7 @@ import { callAdminForthApi } from '@/utils';
|
|
|
18
18
|
import * as monaco from 'monaco-editor';
|
|
19
19
|
import TurndownService from 'turndown';
|
|
20
20
|
import { gfm, tables } from 'turndown-plugin-gfm';
|
|
21
|
+
import { toggleWrapSmart } from './utils/monacoMarkdownToggle';
|
|
21
22
|
|
|
22
23
|
const props = defineProps<{
|
|
23
24
|
column: any,
|
|
@@ -233,55 +234,6 @@ function getTurndownService(): TurndownService {
|
|
|
233
234
|
return turndownService;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
function toggleWrap(ed: monaco.editor.IStandaloneCodeEditor, left: string, right = left) {
|
|
237
|
-
const m = ed.getModel();
|
|
238
|
-
if (!m) return;
|
|
239
|
-
|
|
240
|
-
const selections = ed.getSelections() || [];
|
|
241
|
-
if (!selections.length) return;
|
|
242
|
-
|
|
243
|
-
const edits: monaco.editor.IIdentifiedSingleEditOperation[] = [];
|
|
244
|
-
const nextSelections: monaco.Selection[] = [];
|
|
245
|
-
|
|
246
|
-
for (const sel of selections) {
|
|
247
|
-
const text = m.getValueInRange(sel);
|
|
248
|
-
|
|
249
|
-
if (sel.isEmpty()) {
|
|
250
|
-
edits.push({ range: sel, text: `${left}${right}` });
|
|
251
|
-
const pos = sel.getStartPosition();
|
|
252
|
-
const col = pos.column + left.length;
|
|
253
|
-
nextSelections.push(new monaco.Selection(pos.lineNumber, col, pos.lineNumber, col));
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const isWrapped = text.startsWith(left) && text.endsWith(right) && text.length >= left.length + right.length;
|
|
258
|
-
|
|
259
|
-
if (isWrapped) {
|
|
260
|
-
const unwrapped = text.slice(left.length, text.length - right.length);
|
|
261
|
-
edits.push({ range: sel, text: unwrapped });
|
|
262
|
-
|
|
263
|
-
const start = sel.getStartPosition();
|
|
264
|
-
nextSelections.push(new monaco.Selection(start.lineNumber, start.column, start.lineNumber, start.column + unwrapped.length));
|
|
265
|
-
} else {
|
|
266
|
-
edits.push({ range: sel, text: `${left}${text}${right}` });
|
|
267
|
-
|
|
268
|
-
const start = sel.getStartPosition();
|
|
269
|
-
nextSelections.push(
|
|
270
|
-
new monaco.Selection(
|
|
271
|
-
start.lineNumber,
|
|
272
|
-
start.column + left.length,
|
|
273
|
-
start.lineNumber,
|
|
274
|
-
start.column + left.length + text.length,
|
|
275
|
-
),
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
ed.pushUndoStop();
|
|
281
|
-
ed.executeEdits('md-format', edits);
|
|
282
|
-
ed.pushUndoStop();
|
|
283
|
-
ed.setSelections(nextSelections);
|
|
284
|
-
}
|
|
285
237
|
|
|
286
238
|
let editor: monaco.editor.IStandaloneCodeEditor | null = null;
|
|
287
239
|
let model: monaco.editor.ITextModel | null = null;
|
|
@@ -495,6 +447,50 @@ async function uploadFileAndGetMarkdownTag(file: File): Promise<string | undefin
|
|
|
495
447
|
return;
|
|
496
448
|
}
|
|
497
449
|
}
|
|
450
|
+
function getSelectedPlainText(): string {
|
|
451
|
+
if (!editor || !model) return '';
|
|
452
|
+
|
|
453
|
+
const sels = editor.getSelections() || [];
|
|
454
|
+
if (!sels.length) return '';
|
|
455
|
+
return sels
|
|
456
|
+
.map((sel) => model!.getValueInRange(sel))
|
|
457
|
+
.join('\n');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const onEditorCopy = (e: ClipboardEvent) => {
|
|
461
|
+
if (!editor || !model) return;
|
|
462
|
+
if (!e.clipboardData) return;
|
|
463
|
+
|
|
464
|
+
if (!(editor.hasTextFocus?.() || isFocused.value)) return;
|
|
465
|
+
|
|
466
|
+
const text = getSelectedPlainText();
|
|
467
|
+
if (!text) return;
|
|
468
|
+
|
|
469
|
+
e.clipboardData.setData('text/plain', text);
|
|
470
|
+
e.clipboardData.setData('text/html', '');
|
|
471
|
+
e.preventDefault();
|
|
472
|
+
e.stopPropagation();
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const onEditorCut = (e: ClipboardEvent) => {
|
|
476
|
+
if (!editor || !model) return;
|
|
477
|
+
if (!e.clipboardData) return;
|
|
478
|
+
if (!(editor.hasTextFocus?.() || isFocused.value)) return;
|
|
479
|
+
|
|
480
|
+
const text = getSelectedPlainText();
|
|
481
|
+
if (!text) return;
|
|
482
|
+
|
|
483
|
+
e.clipboardData.setData('text/plain', text);
|
|
484
|
+
e.clipboardData.setData('text/html', '');
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
e.stopPropagation();
|
|
487
|
+
|
|
488
|
+
const sels = editor.getSelections() || [];
|
|
489
|
+
editor.executeEdits(
|
|
490
|
+
'cut',
|
|
491
|
+
sels.map((range) => ({ range, text: '' })),
|
|
492
|
+
);
|
|
493
|
+
};
|
|
498
494
|
|
|
499
495
|
onMounted(async () => {
|
|
500
496
|
if (!editorContainer.value) return;
|
|
@@ -514,22 +510,26 @@ onMounted(async () => {
|
|
|
514
510
|
},
|
|
515
511
|
scrollBeyondLastColumn: 0,
|
|
516
512
|
});
|
|
517
|
-
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI, () => {
|
|
518
|
-
toggleWrap(editor!, '*');
|
|
519
|
-
});
|
|
520
|
-
|
|
521
513
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB, () => {
|
|
522
|
-
|
|
514
|
+
toggleWrapSmart(editor!, '**');
|
|
523
515
|
});
|
|
524
|
-
|
|
525
|
-
|
|
516
|
+
|
|
517
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI, () => {
|
|
518
|
+
toggleWrapSmart(editor!, '*');
|
|
526
519
|
});
|
|
520
|
+
|
|
527
521
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyE, () => {
|
|
528
|
-
|
|
522
|
+
toggleWrapSmart(editor!, '`');
|
|
529
523
|
});
|
|
530
|
-
|
|
531
|
-
|
|
524
|
+
|
|
525
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyX, () => {
|
|
526
|
+
toggleWrapSmart(editor!, '~~');
|
|
532
527
|
});
|
|
528
|
+
|
|
529
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyU, () => {
|
|
530
|
+
toggleWrapSmart(editor!, '<u>', '</u>');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
533
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, () => {
|
|
534
534
|
const selection = editor!.getSelection();
|
|
535
535
|
if (!selection) return;
|
|
@@ -538,6 +538,7 @@ onMounted(async () => {
|
|
|
538
538
|
const markdownLink = `[${escaped}](url)`;
|
|
539
539
|
editor!.executeEdits('insert-link', [{ range: selection, text: markdownLink, forceMoveMarkers: true }]);
|
|
540
540
|
});
|
|
541
|
+
|
|
541
542
|
debug('Monaco editor created', {
|
|
542
543
|
hasUploadPluginInstanceId: Boolean(props.meta?.uploadPluginInstanceId),
|
|
543
544
|
});
|
|
@@ -620,7 +621,8 @@ onMounted(async () => {
|
|
|
620
621
|
insertAtCursor(`${markdownTags.join('\n\n')}\n`);
|
|
621
622
|
}
|
|
622
623
|
};
|
|
623
|
-
|
|
624
|
+
domNode.addEventListener('copy', onEditorCopy, true);
|
|
625
|
+
domNode.addEventListener('cut', onEditorCut, true);
|
|
624
626
|
domNode.addEventListener('dragover', onDragOver, true);
|
|
625
627
|
domNode.addEventListener('drop', onDrop, true);
|
|
626
628
|
removeDragOverListener = () => domNode.removeEventListener('dragover', onDragOver, true);
|
|
@@ -753,7 +755,7 @@ async function uploadFileToS3(file: File): Promise<string | undefined> {
|
|
|
753
755
|
return;
|
|
754
756
|
}
|
|
755
757
|
|
|
756
|
-
const originalFilename = file.name.split('.').slice(0, -1).join('.')
|
|
758
|
+
const originalFilename = file.name.split('.').slice(0, -1).join('.') + `_${Date.now()}`;
|
|
757
759
|
const originalExtension = file.name.split('.').pop();
|
|
758
760
|
|
|
759
761
|
const { uploadUrl, tagline, previewUrl, error } = await callAdminForthApi({
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
|
|
3
|
+
function trimTrailingEolSelection(
|
|
4
|
+
m: monaco.editor.ITextModel,
|
|
5
|
+
sel: monaco.Selection,
|
|
6
|
+
): monaco.Selection {
|
|
7
|
+
if (!sel.isEmpty() && sel.endColumn === 1 && sel.endLineNumber > sel.startLineNumber) {
|
|
8
|
+
const prevLine = sel.endLineNumber - 1;
|
|
9
|
+
const endCol = m.getLineMaxColumn(prevLine); // after last char
|
|
10
|
+
return new monaco.Selection(sel.startLineNumber, sel.startColumn, prevLine, endCol);
|
|
11
|
+
}
|
|
12
|
+
return sel;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function rangeFromOffsets(m: monaco.editor.ITextModel, startOffset: number, endOffset: number): monaco.Range {
|
|
16
|
+
const s = m.getPositionAt(startOffset);
|
|
17
|
+
const e = m.getPositionAt(endOffset);
|
|
18
|
+
return new monaco.Range(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function toggleWrapSmart(
|
|
22
|
+
ed: monaco.editor.IStandaloneCodeEditor,
|
|
23
|
+
left: string,
|
|
24
|
+
right: string = left,
|
|
25
|
+
) {
|
|
26
|
+
let m = ed.getModel();
|
|
27
|
+
if (!m) return;
|
|
28
|
+
|
|
29
|
+
const selections = ed.getSelections() || [];
|
|
30
|
+
if (!selections.length) return;
|
|
31
|
+
|
|
32
|
+
const leftLen = left.length;
|
|
33
|
+
const rightLen = right.length;
|
|
34
|
+
|
|
35
|
+
const indexed = selections.map((s, idx) => ({ s, idx }));
|
|
36
|
+
|
|
37
|
+
indexed.sort((a, b) => {
|
|
38
|
+
const ma = ed.getModel();
|
|
39
|
+
if (!ma) return 0;
|
|
40
|
+
const ao = ma.getOffsetAt(a.s.getStartPosition());
|
|
41
|
+
const bo = ma.getOffsetAt(b.s.getStartPosition());
|
|
42
|
+
if (ao !== bo) return bo - ao;
|
|
43
|
+
const ae = ma.getOffsetAt(a.s.getEndPosition());
|
|
44
|
+
const be = ma.getOffsetAt(b.s.getEndPosition());
|
|
45
|
+
return be - ae;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const nextSelections: Array<monaco.Selection | null> = new Array(selections.length).fill(null);
|
|
49
|
+
|
|
50
|
+
const getTextByOffsets = (startOffset: number, endOffset: number) => {
|
|
51
|
+
const mm = ed.getModel();
|
|
52
|
+
if (!mm) return '';
|
|
53
|
+
return mm.getValueInRange(rangeFromOffsets(mm, startOffset, endOffset));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
ed.pushUndoStop();
|
|
57
|
+
|
|
58
|
+
for (const item of indexed) {
|
|
59
|
+
m = ed.getModel();
|
|
60
|
+
if (!m) break;
|
|
61
|
+
|
|
62
|
+
let sel = item.s;
|
|
63
|
+
sel = trimTrailingEolSelection(m, sel);
|
|
64
|
+
|
|
65
|
+
const startPos = sel.getStartPosition();
|
|
66
|
+
const endPos = sel.getEndPosition();
|
|
67
|
+
const startOffset = m.getOffsetAt(startPos);
|
|
68
|
+
const endOffset = m.getOffsetAt(endPos);
|
|
69
|
+
|
|
70
|
+
const isEmpty = sel.isEmpty();
|
|
71
|
+
const modelLen = m.getValueLength();
|
|
72
|
+
|
|
73
|
+
const hasAdjacentWrap = () => {
|
|
74
|
+
if (startOffset < leftLen) return false;
|
|
75
|
+
if (endOffset + rightLen > modelLen) return false;
|
|
76
|
+
|
|
77
|
+
const l = getTextByOffsets(startOffset - leftLen, startOffset);
|
|
78
|
+
const r = getTextByOffsets(endOffset, endOffset + rightLen);
|
|
79
|
+
return l === left && r === right;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const removeAdjacentWrap = () => {
|
|
83
|
+
const leftRange = rangeFromOffsets(m!, startOffset - leftLen, startOffset);
|
|
84
|
+
const rightRange = rangeFromOffsets(m!, endOffset, endOffset + rightLen);
|
|
85
|
+
|
|
86
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
87
|
+
{ range: rightRange, text: '', forceMoveMarkers: true },
|
|
88
|
+
{ range: leftRange, text: '', forceMoveMarkers: true },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const mm = ed.getModel()!;
|
|
92
|
+
const ns = startOffset - leftLen;
|
|
93
|
+
const ne = endOffset - leftLen;
|
|
94
|
+
|
|
95
|
+
const s = mm.getPositionAt(ns);
|
|
96
|
+
const e = mm.getPositionAt(ne);
|
|
97
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (isEmpty) {
|
|
101
|
+
if (startOffset >= leftLen && startOffset + rightLen <= modelLen) {
|
|
102
|
+
const l = getTextByOffsets(startOffset - leftLen, startOffset);
|
|
103
|
+
const r = getTextByOffsets(startOffset, startOffset + rightLen);
|
|
104
|
+
if (l === left && r === right) {
|
|
105
|
+
const leftRange = rangeFromOffsets(m, startOffset - leftLen, startOffset);
|
|
106
|
+
const rightRange = rangeFromOffsets(m, startOffset, startOffset + rightLen);
|
|
107
|
+
|
|
108
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
109
|
+
{ range: rightRange, text: '', forceMoveMarkers: true },
|
|
110
|
+
{ range: leftRange, text: '', forceMoveMarkers: true },
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const mm = ed.getModel()!;
|
|
114
|
+
const p = mm.getPositionAt(startOffset - leftLen);
|
|
115
|
+
nextSelections[item.idx] = new monaco.Selection(p.lineNumber, p.column, p.lineNumber, p.column);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const insertRange = rangeFromOffsets(m, startOffset, startOffset);
|
|
121
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
122
|
+
{ range: insertRange, text: `${left}${right}`, forceMoveMarkers: true },
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
const mm = ed.getModel()!;
|
|
126
|
+
const p = mm.getPositionAt(startOffset + leftLen);
|
|
127
|
+
nextSelections[item.idx] = new monaco.Selection(p.lineNumber, p.column, p.lineNumber, p.column);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const selectedText = m.getValueInRange(sel);
|
|
132
|
+
|
|
133
|
+
const isExplicitWrapped =
|
|
134
|
+
selectedText.length >= leftLen + rightLen &&
|
|
135
|
+
selectedText.startsWith(left) &&
|
|
136
|
+
selectedText.endsWith(right);
|
|
137
|
+
|
|
138
|
+
if (isExplicitWrapped) {
|
|
139
|
+
const unwrapped = selectedText.slice(leftLen, selectedText.length - rightLen);
|
|
140
|
+
|
|
141
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
142
|
+
{ range: sel, text: unwrapped, forceMoveMarkers: true },
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const mm = ed.getModel()!;
|
|
146
|
+
const s = mm.getPositionAt(startOffset);
|
|
147
|
+
const e = mm.getPositionAt(startOffset + unwrapped.length);
|
|
148
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hasAdjacentWrap()) {
|
|
153
|
+
removeAdjacentWrap();
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
158
|
+
{ range: sel, text: `${left}${selectedText}${right}`, forceMoveMarkers: true },
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const mm = ed.getModel()!;
|
|
162
|
+
const ns = startOffset + leftLen;
|
|
163
|
+
const ne = endOffset + leftLen;
|
|
164
|
+
const s = mm.getPositionAt(ns);
|
|
165
|
+
const e = mm.getPositionAt(ne);
|
|
166
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ed.pushUndoStop();
|
|
170
|
+
|
|
171
|
+
const finalSelections = nextSelections.map((s, i) => s ?? selections[i]);
|
|
172
|
+
ed.setSelections(finalSelections);
|
|
173
|
+
}
|
|
@@ -18,6 +18,7 @@ import { callAdminForthApi } from '@/utils';
|
|
|
18
18
|
import * as monaco from 'monaco-editor';
|
|
19
19
|
import TurndownService from 'turndown';
|
|
20
20
|
import { gfm, tables } from 'turndown-plugin-gfm';
|
|
21
|
+
import { toggleWrapSmart } from './utils/monacoMarkdownToggle';
|
|
21
22
|
|
|
22
23
|
const props = defineProps<{
|
|
23
24
|
column: any,
|
|
@@ -233,55 +234,6 @@ function getTurndownService(): TurndownService {
|
|
|
233
234
|
return turndownService;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
function toggleWrap(ed: monaco.editor.IStandaloneCodeEditor, left: string, right = left) {
|
|
237
|
-
const m = ed.getModel();
|
|
238
|
-
if (!m) return;
|
|
239
|
-
|
|
240
|
-
const selections = ed.getSelections() || [];
|
|
241
|
-
if (!selections.length) return;
|
|
242
|
-
|
|
243
|
-
const edits: monaco.editor.IIdentifiedSingleEditOperation[] = [];
|
|
244
|
-
const nextSelections: monaco.Selection[] = [];
|
|
245
|
-
|
|
246
|
-
for (const sel of selections) {
|
|
247
|
-
const text = m.getValueInRange(sel);
|
|
248
|
-
|
|
249
|
-
if (sel.isEmpty()) {
|
|
250
|
-
edits.push({ range: sel, text: `${left}${right}` });
|
|
251
|
-
const pos = sel.getStartPosition();
|
|
252
|
-
const col = pos.column + left.length;
|
|
253
|
-
nextSelections.push(new monaco.Selection(pos.lineNumber, col, pos.lineNumber, col));
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const isWrapped = text.startsWith(left) && text.endsWith(right) && text.length >= left.length + right.length;
|
|
258
|
-
|
|
259
|
-
if (isWrapped) {
|
|
260
|
-
const unwrapped = text.slice(left.length, text.length - right.length);
|
|
261
|
-
edits.push({ range: sel, text: unwrapped });
|
|
262
|
-
|
|
263
|
-
const start = sel.getStartPosition();
|
|
264
|
-
nextSelections.push(new monaco.Selection(start.lineNumber, start.column, start.lineNumber, start.column + unwrapped.length));
|
|
265
|
-
} else {
|
|
266
|
-
edits.push({ range: sel, text: `${left}${text}${right}` });
|
|
267
|
-
|
|
268
|
-
const start = sel.getStartPosition();
|
|
269
|
-
nextSelections.push(
|
|
270
|
-
new monaco.Selection(
|
|
271
|
-
start.lineNumber,
|
|
272
|
-
start.column + left.length,
|
|
273
|
-
start.lineNumber,
|
|
274
|
-
start.column + left.length + text.length,
|
|
275
|
-
),
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
ed.pushUndoStop();
|
|
281
|
-
ed.executeEdits('md-format', edits);
|
|
282
|
-
ed.pushUndoStop();
|
|
283
|
-
ed.setSelections(nextSelections);
|
|
284
|
-
}
|
|
285
237
|
|
|
286
238
|
let editor: monaco.editor.IStandaloneCodeEditor | null = null;
|
|
287
239
|
let model: monaco.editor.ITextModel | null = null;
|
|
@@ -495,6 +447,50 @@ async function uploadFileAndGetMarkdownTag(file: File): Promise<string | undefin
|
|
|
495
447
|
return;
|
|
496
448
|
}
|
|
497
449
|
}
|
|
450
|
+
function getSelectedPlainText(): string {
|
|
451
|
+
if (!editor || !model) return '';
|
|
452
|
+
|
|
453
|
+
const sels = editor.getSelections() || [];
|
|
454
|
+
if (!sels.length) return '';
|
|
455
|
+
return sels
|
|
456
|
+
.map((sel) => model!.getValueInRange(sel))
|
|
457
|
+
.join('\n');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const onEditorCopy = (e: ClipboardEvent) => {
|
|
461
|
+
if (!editor || !model) return;
|
|
462
|
+
if (!e.clipboardData) return;
|
|
463
|
+
|
|
464
|
+
if (!(editor.hasTextFocus?.() || isFocused.value)) return;
|
|
465
|
+
|
|
466
|
+
const text = getSelectedPlainText();
|
|
467
|
+
if (!text) return;
|
|
468
|
+
|
|
469
|
+
e.clipboardData.setData('text/plain', text);
|
|
470
|
+
e.clipboardData.setData('text/html', '');
|
|
471
|
+
e.preventDefault();
|
|
472
|
+
e.stopPropagation();
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const onEditorCut = (e: ClipboardEvent) => {
|
|
476
|
+
if (!editor || !model) return;
|
|
477
|
+
if (!e.clipboardData) return;
|
|
478
|
+
if (!(editor.hasTextFocus?.() || isFocused.value)) return;
|
|
479
|
+
|
|
480
|
+
const text = getSelectedPlainText();
|
|
481
|
+
if (!text) return;
|
|
482
|
+
|
|
483
|
+
e.clipboardData.setData('text/plain', text);
|
|
484
|
+
e.clipboardData.setData('text/html', '');
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
e.stopPropagation();
|
|
487
|
+
|
|
488
|
+
const sels = editor.getSelections() || [];
|
|
489
|
+
editor.executeEdits(
|
|
490
|
+
'cut',
|
|
491
|
+
sels.map((range) => ({ range, text: '' })),
|
|
492
|
+
);
|
|
493
|
+
};
|
|
498
494
|
|
|
499
495
|
onMounted(async () => {
|
|
500
496
|
if (!editorContainer.value) return;
|
|
@@ -514,22 +510,26 @@ onMounted(async () => {
|
|
|
514
510
|
},
|
|
515
511
|
scrollBeyondLastColumn: 0,
|
|
516
512
|
});
|
|
517
|
-
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI, () => {
|
|
518
|
-
toggleWrap(editor!, '*');
|
|
519
|
-
});
|
|
520
|
-
|
|
521
513
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB, () => {
|
|
522
|
-
|
|
514
|
+
toggleWrapSmart(editor!, '**');
|
|
523
515
|
});
|
|
524
|
-
|
|
525
|
-
|
|
516
|
+
|
|
517
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI, () => {
|
|
518
|
+
toggleWrapSmart(editor!, '*');
|
|
526
519
|
});
|
|
520
|
+
|
|
527
521
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyE, () => {
|
|
528
|
-
|
|
522
|
+
toggleWrapSmart(editor!, '`');
|
|
529
523
|
});
|
|
530
|
-
|
|
531
|
-
|
|
524
|
+
|
|
525
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyX, () => {
|
|
526
|
+
toggleWrapSmart(editor!, '~~');
|
|
532
527
|
});
|
|
528
|
+
|
|
529
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyU, () => {
|
|
530
|
+
toggleWrapSmart(editor!, '<u>', '</u>');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
533
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, () => {
|
|
534
534
|
const selection = editor!.getSelection();
|
|
535
535
|
if (!selection) return;
|
|
@@ -538,6 +538,7 @@ onMounted(async () => {
|
|
|
538
538
|
const markdownLink = `[${escaped}](url)`;
|
|
539
539
|
editor!.executeEdits('insert-link', [{ range: selection, text: markdownLink, forceMoveMarkers: true }]);
|
|
540
540
|
});
|
|
541
|
+
|
|
541
542
|
debug('Monaco editor created', {
|
|
542
543
|
hasUploadPluginInstanceId: Boolean(props.meta?.uploadPluginInstanceId),
|
|
543
544
|
});
|
|
@@ -620,7 +621,8 @@ onMounted(async () => {
|
|
|
620
621
|
insertAtCursor(`${markdownTags.join('\n\n')}\n`);
|
|
621
622
|
}
|
|
622
623
|
};
|
|
623
|
-
|
|
624
|
+
domNode.addEventListener('copy', onEditorCopy, true);
|
|
625
|
+
domNode.addEventListener('cut', onEditorCut, true);
|
|
624
626
|
domNode.addEventListener('dragover', onDragOver, true);
|
|
625
627
|
domNode.addEventListener('drop', onDrop, true);
|
|
626
628
|
removeDragOverListener = () => domNode.removeEventListener('dragover', onDragOver, true);
|
|
@@ -753,7 +755,7 @@ async function uploadFileToS3(file: File): Promise<string | undefined> {
|
|
|
753
755
|
return;
|
|
754
756
|
}
|
|
755
757
|
|
|
756
|
-
const originalFilename = file.name.split('.').slice(0, -1).join('.')
|
|
758
|
+
const originalFilename = file.name.split('.').slice(0, -1).join('.') + `_${Date.now()}`;
|
|
757
759
|
const originalExtension = file.name.split('.').pop();
|
|
758
760
|
|
|
759
761
|
const { uploadUrl, tagline, previewUrl, error } = await callAdminForthApi({
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
|
|
3
|
+
function trimTrailingEolSelection(
|
|
4
|
+
m: monaco.editor.ITextModel,
|
|
5
|
+
sel: monaco.Selection,
|
|
6
|
+
): monaco.Selection {
|
|
7
|
+
if (!sel.isEmpty() && sel.endColumn === 1 && sel.endLineNumber > sel.startLineNumber) {
|
|
8
|
+
const prevLine = sel.endLineNumber - 1;
|
|
9
|
+
const endCol = m.getLineMaxColumn(prevLine); // after last char
|
|
10
|
+
return new monaco.Selection(sel.startLineNumber, sel.startColumn, prevLine, endCol);
|
|
11
|
+
}
|
|
12
|
+
return sel;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function rangeFromOffsets(m: monaco.editor.ITextModel, startOffset: number, endOffset: number): monaco.Range {
|
|
16
|
+
const s = m.getPositionAt(startOffset);
|
|
17
|
+
const e = m.getPositionAt(endOffset);
|
|
18
|
+
return new monaco.Range(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function toggleWrapSmart(
|
|
22
|
+
ed: monaco.editor.IStandaloneCodeEditor,
|
|
23
|
+
left: string,
|
|
24
|
+
right: string = left,
|
|
25
|
+
) {
|
|
26
|
+
let m = ed.getModel();
|
|
27
|
+
if (!m) return;
|
|
28
|
+
|
|
29
|
+
const selections = ed.getSelections() || [];
|
|
30
|
+
if (!selections.length) return;
|
|
31
|
+
|
|
32
|
+
const leftLen = left.length;
|
|
33
|
+
const rightLen = right.length;
|
|
34
|
+
|
|
35
|
+
const indexed = selections.map((s, idx) => ({ s, idx }));
|
|
36
|
+
|
|
37
|
+
indexed.sort((a, b) => {
|
|
38
|
+
const ma = ed.getModel();
|
|
39
|
+
if (!ma) return 0;
|
|
40
|
+
const ao = ma.getOffsetAt(a.s.getStartPosition());
|
|
41
|
+
const bo = ma.getOffsetAt(b.s.getStartPosition());
|
|
42
|
+
if (ao !== bo) return bo - ao;
|
|
43
|
+
const ae = ma.getOffsetAt(a.s.getEndPosition());
|
|
44
|
+
const be = ma.getOffsetAt(b.s.getEndPosition());
|
|
45
|
+
return be - ae;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const nextSelections: Array<monaco.Selection | null> = new Array(selections.length).fill(null);
|
|
49
|
+
|
|
50
|
+
const getTextByOffsets = (startOffset: number, endOffset: number) => {
|
|
51
|
+
const mm = ed.getModel();
|
|
52
|
+
if (!mm) return '';
|
|
53
|
+
return mm.getValueInRange(rangeFromOffsets(mm, startOffset, endOffset));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
ed.pushUndoStop();
|
|
57
|
+
|
|
58
|
+
for (const item of indexed) {
|
|
59
|
+
m = ed.getModel();
|
|
60
|
+
if (!m) break;
|
|
61
|
+
|
|
62
|
+
let sel = item.s;
|
|
63
|
+
sel = trimTrailingEolSelection(m, sel);
|
|
64
|
+
|
|
65
|
+
const startPos = sel.getStartPosition();
|
|
66
|
+
const endPos = sel.getEndPosition();
|
|
67
|
+
const startOffset = m.getOffsetAt(startPos);
|
|
68
|
+
const endOffset = m.getOffsetAt(endPos);
|
|
69
|
+
|
|
70
|
+
const isEmpty = sel.isEmpty();
|
|
71
|
+
const modelLen = m.getValueLength();
|
|
72
|
+
|
|
73
|
+
const hasAdjacentWrap = () => {
|
|
74
|
+
if (startOffset < leftLen) return false;
|
|
75
|
+
if (endOffset + rightLen > modelLen) return false;
|
|
76
|
+
|
|
77
|
+
const l = getTextByOffsets(startOffset - leftLen, startOffset);
|
|
78
|
+
const r = getTextByOffsets(endOffset, endOffset + rightLen);
|
|
79
|
+
return l === left && r === right;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const removeAdjacentWrap = () => {
|
|
83
|
+
const leftRange = rangeFromOffsets(m!, startOffset - leftLen, startOffset);
|
|
84
|
+
const rightRange = rangeFromOffsets(m!, endOffset, endOffset + rightLen);
|
|
85
|
+
|
|
86
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
87
|
+
{ range: rightRange, text: '', forceMoveMarkers: true },
|
|
88
|
+
{ range: leftRange, text: '', forceMoveMarkers: true },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const mm = ed.getModel()!;
|
|
92
|
+
const ns = startOffset - leftLen;
|
|
93
|
+
const ne = endOffset - leftLen;
|
|
94
|
+
|
|
95
|
+
const s = mm.getPositionAt(ns);
|
|
96
|
+
const e = mm.getPositionAt(ne);
|
|
97
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (isEmpty) {
|
|
101
|
+
if (startOffset >= leftLen && startOffset + rightLen <= modelLen) {
|
|
102
|
+
const l = getTextByOffsets(startOffset - leftLen, startOffset);
|
|
103
|
+
const r = getTextByOffsets(startOffset, startOffset + rightLen);
|
|
104
|
+
if (l === left && r === right) {
|
|
105
|
+
const leftRange = rangeFromOffsets(m, startOffset - leftLen, startOffset);
|
|
106
|
+
const rightRange = rangeFromOffsets(m, startOffset, startOffset + rightLen);
|
|
107
|
+
|
|
108
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
109
|
+
{ range: rightRange, text: '', forceMoveMarkers: true },
|
|
110
|
+
{ range: leftRange, text: '', forceMoveMarkers: true },
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const mm = ed.getModel()!;
|
|
114
|
+
const p = mm.getPositionAt(startOffset - leftLen);
|
|
115
|
+
nextSelections[item.idx] = new monaco.Selection(p.lineNumber, p.column, p.lineNumber, p.column);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const insertRange = rangeFromOffsets(m, startOffset, startOffset);
|
|
121
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
122
|
+
{ range: insertRange, text: `${left}${right}`, forceMoveMarkers: true },
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
const mm = ed.getModel()!;
|
|
126
|
+
const p = mm.getPositionAt(startOffset + leftLen);
|
|
127
|
+
nextSelections[item.idx] = new monaco.Selection(p.lineNumber, p.column, p.lineNumber, p.column);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const selectedText = m.getValueInRange(sel);
|
|
132
|
+
|
|
133
|
+
const isExplicitWrapped =
|
|
134
|
+
selectedText.length >= leftLen + rightLen &&
|
|
135
|
+
selectedText.startsWith(left) &&
|
|
136
|
+
selectedText.endsWith(right);
|
|
137
|
+
|
|
138
|
+
if (isExplicitWrapped) {
|
|
139
|
+
const unwrapped = selectedText.slice(leftLen, selectedText.length - rightLen);
|
|
140
|
+
|
|
141
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
142
|
+
{ range: sel, text: unwrapped, forceMoveMarkers: true },
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const mm = ed.getModel()!;
|
|
146
|
+
const s = mm.getPositionAt(startOffset);
|
|
147
|
+
const e = mm.getPositionAt(startOffset + unwrapped.length);
|
|
148
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hasAdjacentWrap()) {
|
|
153
|
+
removeAdjacentWrap();
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
ed.executeEdits('md-toggle-wrap', [
|
|
158
|
+
{ range: sel, text: `${left}${selectedText}${right}`, forceMoveMarkers: true },
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const mm = ed.getModel()!;
|
|
162
|
+
const ns = startOffset + leftLen;
|
|
163
|
+
const ne = endOffset + leftLen;
|
|
164
|
+
const s = mm.getPositionAt(ns);
|
|
165
|
+
const e = mm.getPositionAt(ne);
|
|
166
|
+
nextSelections[item.idx] = new monaco.Selection(s.lineNumber, s.column, e.lineNumber, e.column);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ed.pushUndoStop();
|
|
170
|
+
|
|
171
|
+
const finalSelections = nextSelections.map((s, i) => s ?? selections[i]);
|
|
172
|
+
ed.setSelections(finalSelections);
|
|
173
|
+
}
|