tiny-markdown-editor 0.2.0 → 0.2.2
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 +88 -0
- package/dist/tiny-mde.js +86 -25
- package/dist/tiny-mde.min.js +1 -1
- package/dist/tiny-mde.tiny.js +1 -1
- package/lib/TinyMDE.d.ts +4 -0
- package/lib/TinyMDE.js +55 -28
- package/lib/grammar.d.ts +7 -0
- package/lib/grammar.js +27 -0
- package/package.json +10 -6
package/lib/TinyMDE.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { GrammarRule } from "./grammar";
|
|
1
2
|
export interface EditorProps {
|
|
2
3
|
element?: string | HTMLElement;
|
|
3
4
|
editor?: string | HTMLElement;
|
|
4
5
|
content?: string;
|
|
5
6
|
textarea?: string | HTMLTextAreaElement;
|
|
7
|
+
customInlineGrammar?: Record<string, GrammarRule>;
|
|
6
8
|
}
|
|
7
9
|
export interface Position {
|
|
8
10
|
row: number;
|
|
@@ -38,6 +40,8 @@ export declare class Editor {
|
|
|
38
40
|
linkLabels: string[];
|
|
39
41
|
lineDirty: boolean[];
|
|
40
42
|
lastCommandState: Record<string, boolean | null> | null;
|
|
43
|
+
private customInlineGrammar;
|
|
44
|
+
private mergedInlineGrammar;
|
|
41
45
|
listeners: {
|
|
42
46
|
change: EventHandler<ChangeEvent>[];
|
|
43
47
|
selection: EventHandler<SelectionEvent>[];
|
package/lib/TinyMDE.js
CHANGED
|
@@ -14,6 +14,8 @@ class Editor {
|
|
|
14
14
|
this.linkLabels = [];
|
|
15
15
|
this.lineDirty = [];
|
|
16
16
|
this.lastCommandState = null;
|
|
17
|
+
this.customInlineGrammar = {};
|
|
18
|
+
this.mergedInlineGrammar = grammar_1.inlineGrammar;
|
|
17
19
|
this.listeners = {
|
|
18
20
|
change: [],
|
|
19
21
|
selection: [],
|
|
@@ -33,6 +35,8 @@ class Editor {
|
|
|
33
35
|
this.linkLabels = [];
|
|
34
36
|
this.lineDirty = [];
|
|
35
37
|
this.lastCommandState = null;
|
|
38
|
+
this.customInlineGrammar = props.customInlineGrammar || {};
|
|
39
|
+
this.mergedInlineGrammar = (0, grammar_1.createMergedInlineGrammar)(this.customInlineGrammar);
|
|
36
40
|
this.listeners = {
|
|
37
41
|
change: [],
|
|
38
42
|
selection: [],
|
|
@@ -374,7 +378,7 @@ class Editor {
|
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
getSelection(getAnchor = false) {
|
|
377
|
-
var _a;
|
|
381
|
+
var _a, _b;
|
|
378
382
|
const selection = window.getSelection();
|
|
379
383
|
let startNode = getAnchor ? selection.anchorNode : selection.focusNode;
|
|
380
384
|
if (!startNode)
|
|
@@ -399,16 +403,25 @@ class Editor {
|
|
|
399
403
|
node = node.parentElement;
|
|
400
404
|
}
|
|
401
405
|
let row = 0;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
// If the node doesn't have a previous sibling, it must be the first line
|
|
407
|
+
if (node.previousSibling) {
|
|
408
|
+
const currentLineNumData = (_a = node.dataset) === null || _a === void 0 ? void 0 : _a.lineNum;
|
|
409
|
+
const previousLineNumData = (_b = node.previousSibling.dataset) === null || _b === void 0 ? void 0 : _b.lineNum;
|
|
410
|
+
if (currentLineNumData && previousLineNumData) {
|
|
411
|
+
const currentLineNum = parseInt(currentLineNumData);
|
|
412
|
+
const previousLineNum = parseInt(previousLineNumData);
|
|
413
|
+
if (currentLineNum === previousLineNum + 1) {
|
|
414
|
+
row = currentLineNum;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// If the current line is NOT the previous line + 1, then either
|
|
418
|
+
// the current line got split in two or merged with the previous line
|
|
419
|
+
// Either way, we need to recalculate the row number
|
|
420
|
+
while (node.previousSibling) {
|
|
421
|
+
row++;
|
|
422
|
+
node = node.previousSibling;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
412
425
|
}
|
|
413
426
|
}
|
|
414
427
|
return { row: row, col: col };
|
|
@@ -555,17 +568,31 @@ class Editor {
|
|
|
555
568
|
outer: while (string) {
|
|
556
569
|
// Process simple rules (non-delimiter)
|
|
557
570
|
for (let rule of ["escape", "code", "autolink", "html"]) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
571
|
+
if (this.mergedInlineGrammar[rule]) {
|
|
572
|
+
let cap = this.mergedInlineGrammar[rule].regexp.exec(string);
|
|
573
|
+
if (cap) {
|
|
574
|
+
string = string.substr(cap[0].length);
|
|
575
|
+
offset += cap[0].length;
|
|
576
|
+
processed += this.mergedInlineGrammar[rule].replacement.replace(/\$([1-9])/g, (str, p1) => (0, grammar_1.htmlescape)(cap[p1]));
|
|
577
|
+
continue outer;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Process custom inline grammar rules
|
|
582
|
+
for (let rule in this.customInlineGrammar) {
|
|
583
|
+
if (rule !== "escape" && rule !== "code" && rule !== "autolink" && rule !== "html" && rule !== "linkOpen" && rule !== "imageOpen" && rule !== "linkLabel" && rule !== "default") {
|
|
584
|
+
let cap = this.mergedInlineGrammar[rule].regexp.exec(string);
|
|
585
|
+
if (cap) {
|
|
586
|
+
string = string.substr(cap[0].length);
|
|
587
|
+
offset += cap[0].length;
|
|
588
|
+
processed += this.mergedInlineGrammar[rule].replacement.replace(/\$([1-9])/g, (str, p1) => (0, grammar_1.htmlescape)(cap[p1]));
|
|
589
|
+
continue outer;
|
|
590
|
+
}
|
|
564
591
|
}
|
|
565
592
|
}
|
|
566
593
|
// Check for links / images
|
|
567
|
-
let potentialLink = string.match(
|
|
568
|
-
let potentialImage = string.match(
|
|
594
|
+
let potentialLink = string.match(this.mergedInlineGrammar.linkOpen.regexp);
|
|
595
|
+
let potentialImage = string.match(this.mergedInlineGrammar.imageOpen.regexp);
|
|
569
596
|
if (potentialImage || potentialLink) {
|
|
570
597
|
let result = this.parseLinkOrImage(string, !!potentialImage);
|
|
571
598
|
if (result) {
|
|
@@ -673,11 +700,11 @@ class Editor {
|
|
|
673
700
|
continue outer;
|
|
674
701
|
}
|
|
675
702
|
// Process 'default' rule
|
|
676
|
-
cap =
|
|
703
|
+
cap = this.mergedInlineGrammar.default.regexp.exec(string);
|
|
677
704
|
if (cap) {
|
|
678
705
|
string = string.substr(cap[0].length);
|
|
679
706
|
offset += cap[0].length;
|
|
680
|
-
processed +=
|
|
707
|
+
processed += this.mergedInlineGrammar.default.replacement.replace(/\$([1-9])/g, (str, p1) => (0, grammar_1.htmlescape)(cap[p1]));
|
|
681
708
|
continue outer;
|
|
682
709
|
}
|
|
683
710
|
throw "Infinite loop!";
|
|
@@ -924,20 +951,20 @@ class Editor {
|
|
|
924
951
|
let string = originalString.substr(currentOffset);
|
|
925
952
|
// Capture any escapes and code blocks at current position
|
|
926
953
|
for (let rule of ["escape", "code", "autolink", "html"]) {
|
|
927
|
-
let cap =
|
|
954
|
+
let cap = this.mergedInlineGrammar[rule].regexp.exec(string);
|
|
928
955
|
if (cap) {
|
|
929
956
|
currentOffset += cap[0].length;
|
|
930
957
|
continue textOuter;
|
|
931
958
|
}
|
|
932
959
|
}
|
|
933
960
|
// Check for image
|
|
934
|
-
if (string.match(
|
|
961
|
+
if (string.match(this.mergedInlineGrammar.imageOpen.regexp)) {
|
|
935
962
|
bracketLevel++;
|
|
936
963
|
currentOffset += 2;
|
|
937
964
|
continue textOuter;
|
|
938
965
|
}
|
|
939
966
|
// Check for link
|
|
940
|
-
if (string.match(
|
|
967
|
+
if (string.match(this.mergedInlineGrammar.linkOpen.regexp)) {
|
|
941
968
|
bracketLevel++;
|
|
942
969
|
if (!isImage) {
|
|
943
970
|
if (this.parseLinkOrImage(string, false)) {
|
|
@@ -967,12 +994,12 @@ class Editor {
|
|
|
967
994
|
// REFERENCE LINKS
|
|
968
995
|
if (nextChar === "[") {
|
|
969
996
|
let string = originalString.substr(currentOffset);
|
|
970
|
-
let cap =
|
|
997
|
+
let cap = this.mergedInlineGrammar.linkLabel.regexp.exec(string);
|
|
971
998
|
if (cap) {
|
|
972
999
|
currentOffset += cap[0].length;
|
|
973
1000
|
linkLabel.push(cap[1], cap[2], cap[3]);
|
|
974
|
-
if (cap[
|
|
975
|
-
linkRef = cap[
|
|
1001
|
+
if (cap[this.mergedInlineGrammar.linkLabel.labelPlaceholder]) {
|
|
1002
|
+
linkRef = cap[this.mergedInlineGrammar.linkLabel.labelPlaceholder];
|
|
976
1003
|
}
|
|
977
1004
|
else {
|
|
978
1005
|
linkRef = linkText.trim();
|
|
@@ -1033,7 +1060,7 @@ class Editor {
|
|
|
1033
1060
|
continue inlineOuter;
|
|
1034
1061
|
}
|
|
1035
1062
|
// Process backslash escapes
|
|
1036
|
-
cap =
|
|
1063
|
+
cap = this.mergedInlineGrammar.escape.regexp.exec(string);
|
|
1037
1064
|
if (cap) {
|
|
1038
1065
|
switch (linkDetails.length) {
|
|
1039
1066
|
case 0:
|
package/lib/grammar.d.ts
CHANGED
|
@@ -49,6 +49,13 @@ export declare const htmlBlockGrammar: HTMLBlockRule[];
|
|
|
49
49
|
* In the regular expressions, replacements from the object 'replacements' will be processed before compiling into the property regexp.
|
|
50
50
|
*/
|
|
51
51
|
export declare const inlineGrammar: Record<string, GrammarRule>;
|
|
52
|
+
/**
|
|
53
|
+
* Creates a merged inline grammar by combining the default inline grammar with custom rules.
|
|
54
|
+
* Custom rules are processed and their regexp patterns are expanded with replacements.
|
|
55
|
+
* @param customRules - Object containing custom inline grammar rules
|
|
56
|
+
* @returns Merged inline grammar object
|
|
57
|
+
*/
|
|
58
|
+
export declare function createMergedInlineGrammar(customRules?: Record<string, GrammarRule>): Record<string, GrammarRule>;
|
|
52
59
|
/**
|
|
53
60
|
* Escapes HTML special characters (<, >, and &) in the string.
|
|
54
61
|
* @param {string} string The raw string to be escaped
|
package/lib/grammar.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.commands = exports.inlineGrammar = exports.htmlBlockGrammar = exports.lineGrammar = exports.punctuationTrailing = exports.punctuationLeading = void 0;
|
|
4
|
+
exports.createMergedInlineGrammar = createMergedInlineGrammar;
|
|
4
5
|
exports.htmlescape = htmlescape;
|
|
5
6
|
const replacements = {
|
|
6
7
|
ASCIIPunctuation: /[!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~\\]/,
|
|
@@ -178,6 +179,32 @@ for (let rule of exports.htmlBlockGrammar) {
|
|
|
178
179
|
}
|
|
179
180
|
rule.start = new RegExp(re, rule.start.flags);
|
|
180
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a merged inline grammar by combining the default inline grammar with custom rules.
|
|
184
|
+
* Custom rules are processed and their regexp patterns are expanded with replacements.
|
|
185
|
+
* @param customRules - Object containing custom inline grammar rules
|
|
186
|
+
* @returns Merged inline grammar object
|
|
187
|
+
*/
|
|
188
|
+
function createMergedInlineGrammar(customRules = {}) {
|
|
189
|
+
const merged = { ...exports.inlineGrammar };
|
|
190
|
+
// Process custom rules
|
|
191
|
+
for (const [ruleName, rule] of Object.entries(customRules)) {
|
|
192
|
+
// Copy the rule to avoid modifying the original
|
|
193
|
+
const processedRule = { ...rule };
|
|
194
|
+
// Process replacements in the regexp
|
|
195
|
+
let regexpSource = rule.regexp.source;
|
|
196
|
+
const replacementRegexp = new RegExp(Object.keys(replacements).join('|'));
|
|
197
|
+
// Replace while there is something to replace
|
|
198
|
+
while (regexpSource.match(replacementRegexp)) {
|
|
199
|
+
regexpSource = regexpSource.replace(replacementRegexp, (match) => {
|
|
200
|
+
return replacements[match].source;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
processedRule.regexp = new RegExp(regexpSource, rule.regexp.flags);
|
|
204
|
+
merged[ruleName] = processedRule;
|
|
205
|
+
}
|
|
206
|
+
return merged;
|
|
207
|
+
}
|
|
181
208
|
/**
|
|
182
209
|
* Escapes HTML special characters (<, >, and &) in the string.
|
|
183
210
|
* @param {string} string The raw string to be escaped
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiny-markdown-editor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "TinyMDE: A tiny, ultra low dependency, embeddable HTML/JavaScript Markdown editor.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
"editor"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "npx gulp
|
|
18
|
+
"test": "npx gulp && npx jest",
|
|
19
|
+
"test:chromium": "npx gulp && npx jest --selectProjects chromium",
|
|
20
|
+
"test:firefox": "npx gulp && npx jest --selectProjects firefox",
|
|
21
|
+
"test:webkit": "npx gulp && npx jest --selectProjects webkit",
|
|
22
|
+
"test:single": "npx gulp test",
|
|
19
23
|
"build": "npx gulp",
|
|
20
24
|
"dev": "npx gulp dev",
|
|
21
25
|
"typecheck": "npx tsc --noEmit",
|
|
@@ -44,19 +48,20 @@
|
|
|
44
48
|
"@babel/preset-env": "^7.27.2",
|
|
45
49
|
"@babel/preset-typescript": "^7.27.1",
|
|
46
50
|
"@octokit/rest": "^22.0.0",
|
|
51
|
+
"@playwright/test": "^1.54.1",
|
|
47
52
|
"@rollup/plugin-babel": "^6.0.4",
|
|
48
53
|
"@rollup/plugin-commonjs": "^28.0.3",
|
|
49
54
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
50
55
|
"@rollup/plugin-typescript": "^12.1.1",
|
|
51
56
|
"@rollup/stream": "^3.0.1",
|
|
52
57
|
"@types/jest": "^29.5.14",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
53
60
|
"autoprefixer": "^10.4.21",
|
|
54
61
|
"cssnano": "^7.0.7",
|
|
55
62
|
"del": "^8.0.0",
|
|
56
63
|
"dotenv": "^16.5.0",
|
|
57
64
|
"eslint": "^9.28.0",
|
|
58
|
-
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
59
|
-
"@typescript-eslint/parser": "^8.20.0",
|
|
60
65
|
"express": "^5.1.0",
|
|
61
66
|
"gulp": "^5.0.1",
|
|
62
67
|
"gulp-babel": "^8.0.0",
|
|
@@ -67,9 +72,8 @@
|
|
|
67
72
|
"gulp-typescript": "^6.0.0-alpha.1",
|
|
68
73
|
"jest": "^29.7.0",
|
|
69
74
|
"jest-cli": "^29.7.0",
|
|
70
|
-
"
|
|
75
|
+
"playwright": "^1.54.1",
|
|
71
76
|
"postcss-import": "^16.1.0",
|
|
72
|
-
"puppeteer": "^24.10.0",
|
|
73
77
|
"rollup": "^4.41.1",
|
|
74
78
|
"rollup-plugin-eslint": "^7.0.0",
|
|
75
79
|
"terser": "^5.40.0",
|