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/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
- if (node.dataset &&
403
- node.dataset.lineNum &&
404
- (!node.previousSibling ||
405
- (((_a = node.previousSibling.dataset) === null || _a === void 0 ? void 0 : _a.lineNum) !== node.dataset.lineNum))) {
406
- row = parseInt(node.dataset.lineNum);
407
- }
408
- else {
409
- while (node.previousSibling) {
410
- row++;
411
- node = node.previousSibling;
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
- let cap = grammar_1.inlineGrammar[rule].regexp.exec(string);
559
- if (cap) {
560
- string = string.substr(cap[0].length);
561
- offset += cap[0].length;
562
- processed += grammar_1.inlineGrammar[rule].replacement.replace(/\$([1-9])/g, (str, p1) => (0, grammar_1.htmlescape)(cap[p1]));
563
- continue outer;
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(grammar_1.inlineGrammar.linkOpen.regexp);
568
- let potentialImage = string.match(grammar_1.inlineGrammar.imageOpen.regexp);
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 = grammar_1.inlineGrammar.default.regexp.exec(string);
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 += grammar_1.inlineGrammar.default.replacement.replace(/\$([1-9])/g, (str, p1) => (0, grammar_1.htmlescape)(cap[p1]));
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 = grammar_1.inlineGrammar[rule].regexp.exec(string);
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(grammar_1.inlineGrammar.imageOpen.regexp)) {
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(grammar_1.inlineGrammar.linkOpen.regexp)) {
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 = grammar_1.inlineGrammar.linkLabel.regexp.exec(string);
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[grammar_1.inlineGrammar.linkLabel.labelPlaceholder]) {
975
- linkRef = cap[grammar_1.inlineGrammar.linkLabel.labelPlaceholder];
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 = grammar_1.inlineGrammar.escape.regexp.exec(string);
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.0",
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 test",
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
- "jest-puppeteer": "^11.0.0",
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",