tiny-markdown-editor 0.1.8 → 0.1.10
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/README.md +75 -58
- package/dist/tiny-mde.js +7 -1
- package/dist/tiny-mde.min.js +1 -1
- package/dist/tiny-mde.tiny.js +1 -1
- package/globals.d.ts +172 -0
- package/lib/TinyMDE.js +62 -62
- package/lib/TinyMDECommandBar.js +14 -21
- package/lib/grammar.js +9 -16
- package/lib/index.js +3 -20
- package/lib/svg/svg.js +1 -7
- package/lib/tiny.js +2 -13
- package/package.json +6 -3
package/globals.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
declare module "tiny-markdown-editor" {
|
|
2
|
+
type CursorPosition = {
|
|
3
|
+
col: number;
|
|
4
|
+
row: number;
|
|
5
|
+
node?: HTMLElement;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type CommonCommandName =
|
|
9
|
+
| "bold"
|
|
10
|
+
| "italic"
|
|
11
|
+
| "strikethrough"
|
|
12
|
+
| "code"
|
|
13
|
+
| "h1"
|
|
14
|
+
| "h2"
|
|
15
|
+
| "ul"
|
|
16
|
+
| "ol"
|
|
17
|
+
| "blockquote";
|
|
18
|
+
|
|
19
|
+
type CommandBarCommandName =
|
|
20
|
+
| CommonCommandName
|
|
21
|
+
| "hr"
|
|
22
|
+
| "insertLink"
|
|
23
|
+
| "insertImage";
|
|
24
|
+
|
|
25
|
+
type EditorCommandName = CommonCommandName | "h3" | "h4" | "h5" | "h6";
|
|
26
|
+
|
|
27
|
+
type ChangeEvent = {
|
|
28
|
+
content: string;
|
|
29
|
+
linesDirty: boolean[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type SelectionEvent = {
|
|
33
|
+
focus: CursorPosition;
|
|
34
|
+
anchor: CursorPosition;
|
|
35
|
+
commandState: Record<EditorCommandName, boolean>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Listener<T extends "change" | "selection"> = (
|
|
39
|
+
event: T extends "change" ? ChangeEvent : SelectionEvent
|
|
40
|
+
) => void;
|
|
41
|
+
|
|
42
|
+
type EditorParams = {
|
|
43
|
+
element?: string | HTMLElement;
|
|
44
|
+
content?: string;
|
|
45
|
+
textarea?: string | HTMLElement;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class Editor {
|
|
49
|
+
constructor(params: EditorParams?);
|
|
50
|
+
|
|
51
|
+
public getContent(): string;
|
|
52
|
+
|
|
53
|
+
public getSelection(getAnchor: boolean): CursorPosition | null;
|
|
54
|
+
|
|
55
|
+
public setSelection(
|
|
56
|
+
focus: CursorPosition,
|
|
57
|
+
anchor?: CursorPosition | null
|
|
58
|
+
): void;
|
|
59
|
+
|
|
60
|
+
public paste(
|
|
61
|
+
text: string,
|
|
62
|
+
anchor: CursorPosition,
|
|
63
|
+
focus: CursorPosition
|
|
64
|
+
): void;
|
|
65
|
+
|
|
66
|
+
public wrapSelection(
|
|
67
|
+
pre: string,
|
|
68
|
+
post: string,
|
|
69
|
+
anchor?: CursorPosition | null,
|
|
70
|
+
focus?: CursorPosition | null
|
|
71
|
+
): void;
|
|
72
|
+
|
|
73
|
+
public addEventListener<T extends "change" | "selection">(
|
|
74
|
+
type: T,
|
|
75
|
+
listener: Listener<T>
|
|
76
|
+
): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type ModifierKey =
|
|
80
|
+
| "Ctrl"
|
|
81
|
+
| "Cmd"
|
|
82
|
+
| "Alt"
|
|
83
|
+
| "Option"
|
|
84
|
+
| "Win"
|
|
85
|
+
| "Shift"
|
|
86
|
+
| "Mod"
|
|
87
|
+
| "Mod2";
|
|
88
|
+
type AlphanumericKey =
|
|
89
|
+
| "A"
|
|
90
|
+
| "B"
|
|
91
|
+
| "C"
|
|
92
|
+
| "D"
|
|
93
|
+
| "E"
|
|
94
|
+
| "F"
|
|
95
|
+
| "G"
|
|
96
|
+
| "H"
|
|
97
|
+
| "I"
|
|
98
|
+
| "J"
|
|
99
|
+
| "K"
|
|
100
|
+
| "L"
|
|
101
|
+
| "M"
|
|
102
|
+
| "N"
|
|
103
|
+
| "O"
|
|
104
|
+
| "P"
|
|
105
|
+
| "Q"
|
|
106
|
+
| "R"
|
|
107
|
+
| "S"
|
|
108
|
+
| "T"
|
|
109
|
+
| "U"
|
|
110
|
+
| "V"
|
|
111
|
+
| "W"
|
|
112
|
+
| "X"
|
|
113
|
+
| "Y"
|
|
114
|
+
| "Z"
|
|
115
|
+
| "a"
|
|
116
|
+
| "b"
|
|
117
|
+
| "c"
|
|
118
|
+
| "d"
|
|
119
|
+
| "e"
|
|
120
|
+
| "f"
|
|
121
|
+
| "g"
|
|
122
|
+
| "h"
|
|
123
|
+
| "i"
|
|
124
|
+
| "j"
|
|
125
|
+
| "k"
|
|
126
|
+
| "l"
|
|
127
|
+
| "m"
|
|
128
|
+
| "n"
|
|
129
|
+
| "o"
|
|
130
|
+
| "p"
|
|
131
|
+
| "q"
|
|
132
|
+
| "r"
|
|
133
|
+
| "s"
|
|
134
|
+
| "t"
|
|
135
|
+
| "u"
|
|
136
|
+
| "v"
|
|
137
|
+
| "w"
|
|
138
|
+
| "x"
|
|
139
|
+
| "y"
|
|
140
|
+
| "z"
|
|
141
|
+
| "0"
|
|
142
|
+
| "1"
|
|
143
|
+
| "2"
|
|
144
|
+
| "3"
|
|
145
|
+
| "4"
|
|
146
|
+
| "5"
|
|
147
|
+
| "6"
|
|
148
|
+
| "7"
|
|
149
|
+
| "8"
|
|
150
|
+
| "9";
|
|
151
|
+
type HotKey = AlphanumericKey | `${ModifierKey}-${HotKey}`;
|
|
152
|
+
|
|
153
|
+
type CommandBarCommand =
|
|
154
|
+
| CommandBarCommandName
|
|
155
|
+
| "|"
|
|
156
|
+
| {
|
|
157
|
+
name: CommandBarCommandName | string;
|
|
158
|
+
title?: string | undefined;
|
|
159
|
+
innerHTML?: string;
|
|
160
|
+
action?: (editor: Editor) => void;
|
|
161
|
+
hotkey?: HotKey;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
type CommandBarParams = {
|
|
165
|
+
element?: string | HTMLElement;
|
|
166
|
+
editor: Editor;
|
|
167
|
+
commands?: CommandBarCommand[];
|
|
168
|
+
};
|
|
169
|
+
export class CommandBar {
|
|
170
|
+
constructor(params: CommandBarParams);
|
|
171
|
+
}
|
|
172
|
+
}
|
package/lib/TinyMDE.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _grammar = require("./grammar");
|
|
1
|
+
import { inlineGrammar, lineGrammar, punctuationLeading, punctuationTrailing, htmlescape, htmlBlockGrammar, commands } from "./grammar";
|
|
8
2
|
class Editor {
|
|
9
3
|
constructor() {
|
|
10
4
|
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
@@ -121,7 +115,7 @@ class Editor {
|
|
|
121
115
|
this.linkLabels = [];
|
|
122
116
|
for (let l = 0; l < this.lines.length; l++) {
|
|
123
117
|
if (this.lineTypes[l] == 'TMLinkReferenceDefinition') {
|
|
124
|
-
this.linkLabels.push(this.lineCaptures[l][
|
|
118
|
+
this.linkLabels.push(this.lineCaptures[l][lineGrammar.TMLinkReferenceDefinition.labelPlaceholder]);
|
|
125
119
|
}
|
|
126
120
|
}
|
|
127
121
|
}
|
|
@@ -138,7 +132,7 @@ class Editor {
|
|
|
138
132
|
*/
|
|
139
133
|
replace(replacement, capture) {
|
|
140
134
|
return replacement.replace(/(\${1,2})([0-9])/g, (str, p1, p2) => {
|
|
141
|
-
if (p1 == '$') return
|
|
135
|
+
if (p1 == '$') return htmlescape(capture[p2]);else return `<span class="TMInlineFormatted">${this.processInlineStyles(capture[p2])}</span>`;
|
|
142
136
|
});
|
|
143
137
|
}
|
|
144
138
|
|
|
@@ -178,10 +172,10 @@ class Editor {
|
|
|
178
172
|
// if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceBacktickOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeBacktick')) {
|
|
179
173
|
if (codeBlockType == 'TMCodeFenceBacktickOpen') {
|
|
180
174
|
// We're in a backtick-fenced code block, check if the current line closes it
|
|
181
|
-
let capture =
|
|
175
|
+
let capture = lineGrammar.TMCodeFenceBacktickClose.regexp.exec(this.lines[lineNum]);
|
|
182
176
|
if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
|
|
183
177
|
lineType = 'TMCodeFenceBacktickClose';
|
|
184
|
-
lineReplacement =
|
|
178
|
+
lineReplacement = lineGrammar.TMCodeFenceBacktickClose.replacement;
|
|
185
179
|
lineCapture = capture;
|
|
186
180
|
codeBlockType = false;
|
|
187
181
|
} else {
|
|
@@ -193,10 +187,10 @@ class Editor {
|
|
|
193
187
|
// if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceTildeOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeTilde')) {
|
|
194
188
|
else if (codeBlockType == 'TMCodeFenceTildeOpen') {
|
|
195
189
|
// We're in a tilde-fenced code block
|
|
196
|
-
let capture =
|
|
190
|
+
let capture = lineGrammar.TMCodeFenceTildeClose.regexp.exec(this.lines[lineNum]);
|
|
197
191
|
if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
|
|
198
192
|
lineType = 'TMCodeFenceTildeClose';
|
|
199
|
-
lineReplacement =
|
|
193
|
+
lineReplacement = lineGrammar.TMCodeFenceTildeClose.replacement;
|
|
200
194
|
lineCapture = capture;
|
|
201
195
|
codeBlockType = false;
|
|
202
196
|
} else {
|
|
@@ -208,7 +202,7 @@ class Editor {
|
|
|
208
202
|
|
|
209
203
|
// Check HTML block types
|
|
210
204
|
if (lineType == 'TMPara' && htmlBlock === false) {
|
|
211
|
-
for (let htmlBlockType of
|
|
205
|
+
for (let htmlBlockType of htmlBlockGrammar) {
|
|
212
206
|
if (this.lines[lineNum].match(htmlBlockType.start)) {
|
|
213
207
|
// Matching start condition. Check if this tag can start here (not all start conditions allow breaking a paragraph).
|
|
214
208
|
if (htmlBlockType.paraInterrupt || lineNum == 0 || !(this.lineTypes[lineNum - 1] == 'TMPara' || this.lineTypes[lineNum - 1] == 'TMUL' || this.lineTypes[lineNum - 1] == 'TMOL' || this.lineTypes[lineNum - 1] == 'TMBlockquote')) {
|
|
@@ -231,7 +225,7 @@ class Editor {
|
|
|
231
225
|
}
|
|
232
226
|
} else {
|
|
233
227
|
// No specific end condition, ends with blank line
|
|
234
|
-
if (lineNum == this.lines.length - 1 || this.lines[lineNum + 1].match(
|
|
228
|
+
if (lineNum == this.lines.length - 1 || this.lines[lineNum + 1].match(lineGrammar.TMBlankLine.regexp)) {
|
|
235
229
|
htmlBlock = false;
|
|
236
230
|
}
|
|
237
231
|
}
|
|
@@ -239,12 +233,12 @@ class Editor {
|
|
|
239
233
|
|
|
240
234
|
// Check all regexps if we haven't applied one of the code block types
|
|
241
235
|
if (lineType == 'TMPara') {
|
|
242
|
-
for (let type in
|
|
243
|
-
if (
|
|
244
|
-
let capture =
|
|
236
|
+
for (let type in lineGrammar) {
|
|
237
|
+
if (lineGrammar[type].regexp) {
|
|
238
|
+
let capture = lineGrammar[type].regexp.exec(this.lines[lineNum]);
|
|
245
239
|
if (capture) {
|
|
246
240
|
lineType = type;
|
|
247
|
-
lineReplacement =
|
|
241
|
+
lineReplacement = lineGrammar[type].replacement;
|
|
248
242
|
lineCapture = capture;
|
|
249
243
|
break;
|
|
250
244
|
}
|
|
@@ -268,10 +262,10 @@ class Editor {
|
|
|
268
262
|
|
|
269
263
|
// Setext H2 markers that can also be interpreted as an empty list item should be regarded as such (as per CommonMark spec)
|
|
270
264
|
if (lineType == 'TMSetextH2Marker') {
|
|
271
|
-
let capture =
|
|
265
|
+
let capture = lineGrammar.TMUL.regexp.exec(this.lines[lineNum]);
|
|
272
266
|
if (capture) {
|
|
273
267
|
lineType = 'TMUL';
|
|
274
|
-
lineReplacement =
|
|
268
|
+
lineReplacement = lineGrammar.TMUL.replacement;
|
|
275
269
|
lineCapture = capture;
|
|
276
270
|
}
|
|
277
271
|
}
|
|
@@ -280,12 +274,12 @@ class Editor {
|
|
|
280
274
|
if (lineType == 'TMSetextH1Marker' || lineType == 'TMSetextH2Marker') {
|
|
281
275
|
if (lineNum == 0 || this.lineTypes[lineNum - 1] != 'TMPara') {
|
|
282
276
|
// Setext marker is invalid. However, a H2 marker might still be a valid HR, so let's check that
|
|
283
|
-
let capture =
|
|
277
|
+
let capture = lineGrammar.TMHR.regexp.exec(this.lines[lineNum]);
|
|
284
278
|
if (capture) {
|
|
285
279
|
// Valid HR
|
|
286
280
|
lineType = 'TMHR';
|
|
287
281
|
lineCapture = capture;
|
|
288
|
-
lineReplacement =
|
|
282
|
+
lineReplacement = lineGrammar.TMHR.replacement;
|
|
289
283
|
} else {
|
|
290
284
|
// Not valid HR, format as TMPara
|
|
291
285
|
lineType = 'TMPara';
|
|
@@ -353,7 +347,7 @@ class Editor {
|
|
|
353
347
|
// Capture any escapes and code blocks at current position, they bind more strongly than links
|
|
354
348
|
// We don't have to actually process them here, that'll be done later in case the link / image is valid, but we need to skip over them.
|
|
355
349
|
for (let rule of ['escape', 'code', 'autolink', 'html']) {
|
|
356
|
-
let cap =
|
|
350
|
+
let cap = inlineGrammar[rule].regexp.exec(string);
|
|
357
351
|
if (cap) {
|
|
358
352
|
currentOffset += cap[0].length;
|
|
359
353
|
continue textOuter;
|
|
@@ -361,7 +355,7 @@ class Editor {
|
|
|
361
355
|
}
|
|
362
356
|
|
|
363
357
|
// Check for image. It's okay for an image to be included in a link or image
|
|
364
|
-
if (string.match(
|
|
358
|
+
if (string.match(inlineGrammar.imageOpen.regexp)) {
|
|
365
359
|
// Opening image. It's okay if this is a matching pair of brackets
|
|
366
360
|
bracketLevel++;
|
|
367
361
|
currentOffset += 2;
|
|
@@ -369,7 +363,7 @@ class Editor {
|
|
|
369
363
|
}
|
|
370
364
|
|
|
371
365
|
// Check for link (not an image because that would have been captured and skipped over above)
|
|
372
|
-
if (string.match(
|
|
366
|
+
if (string.match(inlineGrammar.linkOpen.regexp)) {
|
|
373
367
|
// Opening bracket. Two things to do:
|
|
374
368
|
// 1) it's okay if this part of a pair of brackets.
|
|
375
369
|
// 2) If we are currently trying to parse a link, this nested bracket musn't start a valid link (no nested links allowed)
|
|
@@ -409,14 +403,14 @@ class Editor {
|
|
|
409
403
|
// REFERENCE LINKS
|
|
410
404
|
if (nextChar == '[') {
|
|
411
405
|
let string = originalString.substr(currentOffset);
|
|
412
|
-
let cap =
|
|
406
|
+
let cap = inlineGrammar.linkLabel.regexp.exec(string);
|
|
413
407
|
if (cap) {
|
|
414
408
|
// Valid link label
|
|
415
409
|
currentOffset += cap[0].length;
|
|
416
410
|
linkLabel.push(cap[1], cap[2], cap[3]);
|
|
417
|
-
if (cap[
|
|
411
|
+
if (cap[inlineGrammar.linkLabel.labelPlaceholder]) {
|
|
418
412
|
// Full reference link
|
|
419
|
-
linkRef = cap[
|
|
413
|
+
linkRef = cap[inlineGrammar.linkLabel.labelPlaceholder];
|
|
420
414
|
} else {
|
|
421
415
|
// Collapsed reference link
|
|
422
416
|
linkRef = linkText.trim();
|
|
@@ -490,7 +484,7 @@ class Editor {
|
|
|
490
484
|
}
|
|
491
485
|
|
|
492
486
|
// Process backslash escapes
|
|
493
|
-
cap =
|
|
487
|
+
cap = inlineGrammar.escape.regexp.exec(string);
|
|
494
488
|
if (cap) {
|
|
495
489
|
switch (linkDetails.length) {
|
|
496
490
|
case 0:
|
|
@@ -719,20 +713,20 @@ class Editor {
|
|
|
719
713
|
outer: while (string) {
|
|
720
714
|
// Process simple rules (non-delimiter)
|
|
721
715
|
for (let rule of ['escape', 'code', 'autolink', 'html']) {
|
|
722
|
-
let cap =
|
|
716
|
+
let cap = inlineGrammar[rule].regexp.exec(string);
|
|
723
717
|
if (cap) {
|
|
724
718
|
string = string.substr(cap[0].length);
|
|
725
719
|
offset += cap[0].length;
|
|
726
|
-
processed +=
|
|
720
|
+
processed += inlineGrammar[rule].replacement
|
|
727
721
|
// .replace(/\$\$([1-9])/g, (str, p1) => processInlineStyles(cap[p1])) // todo recursive calling
|
|
728
|
-
.replace(/\$([1-9])/g, (str, p1) =>
|
|
722
|
+
.replace(/\$([1-9])/g, (str, p1) => htmlescape(cap[p1]));
|
|
729
723
|
continue outer;
|
|
730
724
|
}
|
|
731
725
|
}
|
|
732
726
|
|
|
733
727
|
// Check for links / images
|
|
734
|
-
let potentialLink = string.match(
|
|
735
|
-
let potentialImage = string.match(
|
|
728
|
+
let potentialLink = string.match(inlineGrammar.linkOpen.regexp);
|
|
729
|
+
let potentialImage = string.match(inlineGrammar.imageOpen.regexp);
|
|
736
730
|
if (potentialImage || potentialLink) {
|
|
737
731
|
let result = this.parseLinkOrImage(string, potentialImage);
|
|
738
732
|
if (result) {
|
|
@@ -756,8 +750,8 @@ class Editor {
|
|
|
756
750
|
|
|
757
751
|
const preceding = offset > 0 ? originalString.substr(0, offset) : ' '; // beginning and end of line count as whitespace
|
|
758
752
|
const following = offset + cap[0].length < originalString.length ? string : ' ';
|
|
759
|
-
const punctuationFollows = following.match(
|
|
760
|
-
const punctuationPrecedes = preceding.match(
|
|
753
|
+
const punctuationFollows = following.match(punctuationLeading);
|
|
754
|
+
const punctuationPrecedes = preceding.match(punctuationTrailing);
|
|
761
755
|
const whitespaceFollows = following.match(/^\s/);
|
|
762
756
|
const whitespacePrecedes = preceding.match(/\s$/);
|
|
763
757
|
|
|
@@ -876,11 +870,11 @@ class Editor {
|
|
|
876
870
|
}
|
|
877
871
|
|
|
878
872
|
// Process 'default' rule
|
|
879
|
-
cap =
|
|
873
|
+
cap = inlineGrammar.default.regexp.exec(string);
|
|
880
874
|
if (cap) {
|
|
881
875
|
string = string.substr(cap[0].length);
|
|
882
876
|
offset += cap[0].length;
|
|
883
|
-
processed +=
|
|
877
|
+
processed += inlineGrammar.default.replacement.replace(/\$([1-9])/g, (str, p1) => htmlescape(cap[p1]));
|
|
884
878
|
continue outer;
|
|
885
879
|
}
|
|
886
880
|
throw 'Infinite loop!';
|
|
@@ -984,7 +978,7 @@ class Editor {
|
|
|
984
978
|
sel.col = 0;
|
|
985
979
|
if (continuableType) {
|
|
986
980
|
// Check if the previous line was non-empty
|
|
987
|
-
let capture =
|
|
981
|
+
let capture = lineGrammar[continuableType].regexp.exec(this.lines[sel.row - 1]);
|
|
988
982
|
if (capture) {
|
|
989
983
|
// Convention: capture[1] is the line type marker, capture[2] is the content
|
|
990
984
|
if (capture[2]) {
|
|
@@ -1169,6 +1163,7 @@ class Editor {
|
|
|
1169
1163
|
/**
|
|
1170
1164
|
* Sets the selection based on rows and columns within the editor Markdown content.
|
|
1171
1165
|
* @param {object} focus Object representing the selection, needs to have properties row and col.
|
|
1166
|
+
* @param anchor Anchor of the selection. If not given, assumes the current anchor.
|
|
1172
1167
|
*/
|
|
1173
1168
|
setSelection(focus) {
|
|
1174
1169
|
let anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
@@ -1371,6 +1366,7 @@ class Editor {
|
|
|
1371
1366
|
/**
|
|
1372
1367
|
* Returns the state (true / false) of all commands.
|
|
1373
1368
|
* @param focus Focus of the selection. If not given, assumes the current focus.
|
|
1369
|
+
* @param anchor Anchor of the selection. If not given, assumes the current anchor.
|
|
1374
1370
|
*/
|
|
1375
1371
|
getCommandState() {
|
|
1376
1372
|
let focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
@@ -1379,7 +1375,7 @@ class Editor {
|
|
|
1379
1375
|
if (!focus) focus = this.getSelection(false);
|
|
1380
1376
|
if (!anchor) anchor = this.getSelection(true);
|
|
1381
1377
|
if (!focus) {
|
|
1382
|
-
for (let cmd in
|
|
1378
|
+
for (let cmd in commands) {
|
|
1383
1379
|
commandState[cmd] = null;
|
|
1384
1380
|
}
|
|
1385
1381
|
return commandState;
|
|
@@ -1398,25 +1394,25 @@ class Editor {
|
|
|
1398
1394
|
end.col = this.lines[end.row].length; // Selection to beginning of next line is said to end at the beginning of the last line
|
|
1399
1395
|
}
|
|
1400
1396
|
|
|
1401
|
-
for (let cmd in
|
|
1402
|
-
if (
|
|
1397
|
+
for (let cmd in commands) {
|
|
1398
|
+
if (commands[cmd].type == 'inline') {
|
|
1403
1399
|
if (!focus || focus.row != anchor.row || !this.isInlineFormattingAllowed(focus, anchor)) {
|
|
1404
1400
|
commandState[cmd] = null;
|
|
1405
1401
|
} else {
|
|
1406
1402
|
// The command state is true if there is a respective enclosing markup node (e.g., the selection is enclosed in a <b>..</b>) ...
|
|
1407
|
-
commandState[cmd] = !!this.computeEnclosingMarkupNode(focus, anchor,
|
|
1403
|
+
commandState[cmd] = !!this.computeEnclosingMarkupNode(focus, anchor, commands[cmd].className) ||
|
|
1408
1404
|
// ... or if it's an empty string preceded by and followed by formatting markers, e.g. **|** where | is the cursor
|
|
1409
1405
|
|
|
1410
|
-
focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(
|
|
1406
|
+
focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(commands[cmd].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(commands[cmd].unset.postPattern);
|
|
1411
1407
|
}
|
|
1412
1408
|
}
|
|
1413
|
-
if (
|
|
1409
|
+
if (commands[cmd].type == 'line') {
|
|
1414
1410
|
if (!focus) {
|
|
1415
1411
|
commandState[cmd] = null;
|
|
1416
1412
|
} else {
|
|
1417
|
-
let state = this.lineTypes[start.row] ==
|
|
1413
|
+
let state = this.lineTypes[start.row] == commands[cmd].className;
|
|
1418
1414
|
for (let line = start.row; line <= end.row; line++) {
|
|
1419
|
-
if (this.lineTypes[line] ==
|
|
1415
|
+
if (this.lineTypes[line] == commands[cmd].className != state) {
|
|
1420
1416
|
state = null;
|
|
1421
1417
|
break;
|
|
1422
1418
|
}
|
|
@@ -1434,14 +1430,14 @@ class Editor {
|
|
|
1434
1430
|
* @param {boolean} state
|
|
1435
1431
|
*/
|
|
1436
1432
|
setCommandState(command, state) {
|
|
1437
|
-
if (
|
|
1433
|
+
if (commands[command].type == 'inline') {
|
|
1438
1434
|
let anchor = this.getSelection(true);
|
|
1439
1435
|
let focus = this.getSelection(false);
|
|
1440
1436
|
if (!anchor) anchor = focus;
|
|
1441
1437
|
if (!anchor) return;
|
|
1442
1438
|
if (anchor.row != focus.row) return;
|
|
1443
1439
|
if (!this.isInlineFormattingAllowed(focus, anchor)) return;
|
|
1444
|
-
let markupNode = this.computeEnclosingMarkupNode(focus, anchor,
|
|
1440
|
+
let markupNode = this.computeEnclosingMarkupNode(focus, anchor, commands[command].className);
|
|
1445
1441
|
this.clearDirtyFlag();
|
|
1446
1442
|
|
|
1447
1443
|
// First case: There's an enclosing markup node, remove the markers around that markup node
|
|
@@ -1449,24 +1445,26 @@ class Editor {
|
|
|
1449
1445
|
this.lineDirty[focus.row] = true;
|
|
1450
1446
|
const startCol = this.computeColumn(markupNode, 0);
|
|
1451
1447
|
const len = markupNode.textContent.length;
|
|
1452
|
-
const left = this.lines[focus.row].substr(0, startCol).replace(
|
|
1448
|
+
const left = this.lines[focus.row].substr(0, startCol).replace(commands[command].unset.prePattern, '');
|
|
1453
1449
|
const mid = this.lines[focus.row].substr(startCol, len);
|
|
1454
|
-
const right = this.lines[focus.row].substr(startCol + len).replace(
|
|
1450
|
+
const right = this.lines[focus.row].substr(startCol + len).replace(commands[command].unset.postPattern, '');
|
|
1455
1451
|
this.lines[focus.row] = left.concat(mid, right);
|
|
1456
1452
|
anchor.col = left.length;
|
|
1457
1453
|
focus.col = anchor.col + len;
|
|
1458
1454
|
this.updateFormatting();
|
|
1459
1455
|
this.setSelection(focus, anchor);
|
|
1456
|
+
this.fireChange();
|
|
1460
1457
|
|
|
1461
1458
|
// Second case: Empty selection with surrounding formatting markers, remove those
|
|
1462
|
-
} else if (focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(
|
|
1459
|
+
} else if (focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(commands[command].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(commands[command].unset.postPattern)) {
|
|
1463
1460
|
this.lineDirty[focus.row] = true;
|
|
1464
|
-
const left = this.lines[focus.row].substr(0, focus.col).replace(
|
|
1465
|
-
const right = this.lines[focus.row].substr(focus.col).replace(
|
|
1461
|
+
const left = this.lines[focus.row].substr(0, focus.col).replace(commands[command].unset.prePattern, '');
|
|
1462
|
+
const right = this.lines[focus.row].substr(focus.col).replace(commands[command].unset.postPattern, '');
|
|
1466
1463
|
this.lines[focus.row] = left.concat(right);
|
|
1467
1464
|
focus.col = anchor.col = left.length;
|
|
1468
1465
|
this.updateFormatting();
|
|
1469
1466
|
this.setSelection(focus, anchor);
|
|
1467
|
+
this.fireChange();
|
|
1470
1468
|
|
|
1471
1469
|
// Not currently formatted, insert formatting markers
|
|
1472
1470
|
} else {
|
|
@@ -1490,10 +1488,11 @@ class Editor {
|
|
|
1490
1488
|
anchor.col = endCol;
|
|
1491
1489
|
|
|
1492
1490
|
// Just insert markup before and after and hope for the best.
|
|
1493
|
-
this.wrapSelection(
|
|
1491
|
+
this.wrapSelection(commands[command].set.pre, commands[command].set.post, focus, anchor);
|
|
1492
|
+
this.fireChange();
|
|
1494
1493
|
// TODO clean this up so that markup remains properly nested
|
|
1495
1494
|
}
|
|
1496
|
-
} else if (
|
|
1495
|
+
} else if (commands[command].type == 'line') {
|
|
1497
1496
|
let anchor = this.getSelection(true);
|
|
1498
1497
|
let focus = this.getSelection(false);
|
|
1499
1498
|
if (!anchor) anchor = focus;
|
|
@@ -1505,12 +1504,12 @@ class Editor {
|
|
|
1505
1504
|
end.row--;
|
|
1506
1505
|
}
|
|
1507
1506
|
for (let line = start.row; line <= end.row; line++) {
|
|
1508
|
-
if (state && this.lineTypes[line] !=
|
|
1509
|
-
this.lines[line] = this.lines[line].replace(
|
|
1507
|
+
if (state && this.lineTypes[line] != commands[command].className) {
|
|
1508
|
+
this.lines[line] = this.lines[line].replace(commands[command].set.pattern, commands[command].set.replacement.replace('$#', line - start.row + 1));
|
|
1510
1509
|
this.lineDirty[line] = true;
|
|
1511
1510
|
}
|
|
1512
|
-
if (!state && this.lineTypes[line] ==
|
|
1513
|
-
this.lines[line] = this.lines[line].replace(
|
|
1511
|
+
if (!state && this.lineTypes[line] == commands[command].className) {
|
|
1512
|
+
this.lines[line] = this.lines[line].replace(commands[command].unset.pattern, commands[command].unset.replacement);
|
|
1514
1513
|
this.lineDirty[line] = true;
|
|
1515
1514
|
}
|
|
1516
1515
|
}
|
|
@@ -1522,6 +1521,7 @@ class Editor {
|
|
|
1522
1521
|
row: start.row,
|
|
1523
1522
|
col: 0
|
|
1524
1523
|
});
|
|
1524
|
+
this.fireChange();
|
|
1525
1525
|
}
|
|
1526
1526
|
}
|
|
1527
1527
|
|
|
@@ -1642,4 +1642,4 @@ class Editor {
|
|
|
1642
1642
|
}
|
|
1643
1643
|
}
|
|
1644
1644
|
}
|
|
1645
|
-
|
|
1645
|
+
export default Editor;
|
package/lib/TinyMDECommandBar.js
CHANGED
|
@@ -1,70 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _svg = _interopRequireDefault(require("./svg/svg"));
|
|
8
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
1
|
+
import svg from './svg/svg';
|
|
9
2
|
const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
|
10
3
|
const DefaultCommands = {
|
|
11
4
|
'bold': {
|
|
12
5
|
name: 'bold',
|
|
13
6
|
action: 'bold',
|
|
14
|
-
innerHTML:
|
|
7
|
+
innerHTML: svg.bold,
|
|
15
8
|
title: 'Bold',
|
|
16
9
|
hotkey: 'Mod-B'
|
|
17
10
|
},
|
|
18
11
|
'italic': {
|
|
19
12
|
name: 'italic',
|
|
20
13
|
action: 'italic',
|
|
21
|
-
innerHTML:
|
|
14
|
+
innerHTML: svg.italic,
|
|
22
15
|
title: 'Italic',
|
|
23
16
|
hotkey: 'Mod-I'
|
|
24
17
|
},
|
|
25
18
|
'strikethrough': {
|
|
26
19
|
name: 'strikethrough',
|
|
27
20
|
action: 'strikethrough',
|
|
28
|
-
innerHTML:
|
|
21
|
+
innerHTML: svg.strikethrough,
|
|
29
22
|
title: 'Strikethrough',
|
|
30
23
|
hotkey: 'Mod2-Shift-5'
|
|
31
24
|
},
|
|
32
25
|
'code': {
|
|
33
26
|
name: 'code',
|
|
34
27
|
action: 'code',
|
|
35
|
-
innerHTML:
|
|
28
|
+
innerHTML: svg.code,
|
|
36
29
|
title: 'Format as code'
|
|
37
30
|
},
|
|
38
31
|
'h1': {
|
|
39
32
|
name: 'h1',
|
|
40
33
|
action: 'h1',
|
|
41
|
-
innerHTML:
|
|
34
|
+
innerHTML: svg.h1,
|
|
42
35
|
title: 'Level 1 heading',
|
|
43
36
|
hotkey: 'Mod-Shift-1'
|
|
44
37
|
},
|
|
45
38
|
'h2': {
|
|
46
39
|
name: 'h2',
|
|
47
40
|
action: 'h2',
|
|
48
|
-
innerHTML:
|
|
41
|
+
innerHTML: svg.h2,
|
|
49
42
|
title: 'Level 2 heading',
|
|
50
43
|
hotkey: 'Mod-Shift-2'
|
|
51
44
|
},
|
|
52
45
|
'ul': {
|
|
53
46
|
name: 'ul',
|
|
54
47
|
action: 'ul',
|
|
55
|
-
innerHTML:
|
|
48
|
+
innerHTML: svg.ul,
|
|
56
49
|
title: 'Bulleted list'
|
|
57
50
|
},
|
|
58
51
|
'ol': {
|
|
59
52
|
name: 'ol',
|
|
60
53
|
action: 'ol',
|
|
61
|
-
innerHTML:
|
|
54
|
+
innerHTML: svg.ol,
|
|
62
55
|
title: 'Numbered list'
|
|
63
56
|
},
|
|
64
57
|
'blockquote': {
|
|
65
58
|
name: 'blockquote',
|
|
66
59
|
action: 'blockquote',
|
|
67
|
-
innerHTML:
|
|
60
|
+
innerHTML: svg.blockquote,
|
|
68
61
|
title: 'Quote',
|
|
69
62
|
hotkey: 'Mod2-Shift-Q'
|
|
70
63
|
},
|
|
@@ -74,7 +67,7 @@ const DefaultCommands = {
|
|
|
74
67
|
if (editor.isInlineFormattingAllowed()) editor.wrapSelection('[', ']()');
|
|
75
68
|
},
|
|
76
69
|
enabled: (editor, focus, anchor) => editor.isInlineFormattingAllowed(focus, anchor) ? false : null,
|
|
77
|
-
innerHTML:
|
|
70
|
+
innerHTML: svg.link,
|
|
78
71
|
title: 'Insert link',
|
|
79
72
|
hotkey: 'Mod-K'
|
|
80
73
|
},
|
|
@@ -84,7 +77,7 @@ const DefaultCommands = {
|
|
|
84
77
|
if (editor.isInlineFormattingAllowed()) editor.wrapSelection('![', ']()');
|
|
85
78
|
},
|
|
86
79
|
enabled: (editor, focus, anchor) => editor.isInlineFormattingAllowed(focus, anchor) ? false : null,
|
|
87
|
-
innerHTML:
|
|
80
|
+
innerHTML: svg.image,
|
|
88
81
|
title: 'Insert image',
|
|
89
82
|
hotkey: 'Mod2-Shift-I'
|
|
90
83
|
},
|
|
@@ -92,7 +85,7 @@ const DefaultCommands = {
|
|
|
92
85
|
name: 'hr',
|
|
93
86
|
action: editor => editor.paste('\n***\n'),
|
|
94
87
|
enabled: () => false,
|
|
95
|
-
innerHTML:
|
|
88
|
+
innerHTML: svg.hr,
|
|
96
89
|
title: 'Insert horizontal line',
|
|
97
90
|
hotkey: 'Mod2-Shift-L'
|
|
98
91
|
}
|
|
@@ -262,4 +255,4 @@ class CommandBar {
|
|
|
262
255
|
}
|
|
263
256
|
}
|
|
264
257
|
}
|
|
265
|
-
|
|
258
|
+
export default CommandBar;
|