@changerawr/markdown 1.1.5 → 1.1.7

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.
@@ -5,10 +5,12 @@ import React, { useMemo as useMemo2, useEffect as useEffect2 } from "react";
5
5
  import { useState, useCallback, useMemo, useRef, useEffect } from "react";
6
6
 
7
7
  // src/parser.ts
8
- var MarkdownParser = class {
8
+ var MarkdownParser = class _MarkdownParser {
9
+ // Cache compiled regexes
9
10
  constructor(config) {
10
11
  this.rules = [];
11
12
  this.warnings = [];
13
+ this.compiledPatterns = /* @__PURE__ */ new Map();
12
14
  this.config = {
13
15
  debugMode: false,
14
16
  maxIterations: 1e4,
@@ -18,6 +20,10 @@ var MarkdownParser = class {
18
20
  }
19
21
  addRule(rule) {
20
22
  this.rules.push(rule);
23
+ this.compiledPatterns.set(
24
+ rule,
25
+ new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
26
+ );
21
27
  this.rules.sort((a, b) => {
22
28
  const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
23
29
  const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
@@ -63,12 +69,20 @@ var MarkdownParser = class {
63
69
  iterationCount++;
64
70
  let matched = false;
65
71
  let bestMatch = null;
72
+ let nextBestMatchIndex = null;
66
73
  for (const rule of this.rules) {
67
74
  try {
68
- const pattern = new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""));
75
+ const pattern = this.compiledPatterns.get(rule);
69
76
  const match = remaining.match(pattern);
70
77
  if (match && match.index !== void 0) {
71
- const priority = match.index === 0 ? 1e3 : 1e3 - match.index;
78
+ if (match.index === 0) {
79
+ bestMatch = { rule, match, priority: 1e3 };
80
+ break;
81
+ }
82
+ if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
83
+ nextBestMatchIndex = match.index;
84
+ }
85
+ const priority = 1e3 - match.index;
72
86
  if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
73
87
  bestMatch = { rule, match, priority };
74
88
  }
@@ -115,15 +129,14 @@ var MarkdownParser = class {
115
129
  }
116
130
  }
117
131
  if (!matched) {
118
- const char = remaining[0];
119
- if (char) {
120
- tokens.push({
121
- type: "text",
122
- content: char,
123
- raw: char
124
- });
125
- remaining = remaining.slice(1);
126
- }
132
+ const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
133
+ const textChunk = remaining.slice(0, chunkSize);
134
+ tokens.push({
135
+ type: "text",
136
+ content: textChunk,
137
+ raw: textChunk
138
+ });
139
+ remaining = remaining.slice(chunkSize);
127
140
  }
128
141
  }
129
142
  if (iterationCount >= maxIterations) {
@@ -205,7 +218,18 @@ var MarkdownParser = class {
205
218
  recursivelyParseBlockContent(token) {
206
219
  const blockTypes = ["alert", "blockquote", "list-item", "task-item"];
207
220
  if (blockTypes.includes(token.type) && token.content && token.content.trim()) {
208
- const children = this.parse(token.content);
221
+ let children;
222
+ if ((token.type === "list-item" || token.type === "task-item") && this.rules.some((r) => r.name === "list-item")) {
223
+ const parserWithoutListRule = new _MarkdownParser(this.config);
224
+ this.rules.forEach((rule) => {
225
+ if (rule.name !== "list-item" && rule.name !== "task-item") {
226
+ parserWithoutListRule.addRule(rule);
227
+ }
228
+ });
229
+ children = parserWithoutListRule.parse(token.content);
230
+ } else {
231
+ children = this.parse(token.content);
232
+ }
209
233
  return {
210
234
  ...token,
211
235
  children
@@ -541,6 +565,154 @@ var MarkdownRenderer = class {
541
565
  }
542
566
  };
543
567
 
568
+ // src/cache.ts
569
+ var LRUCache = class {
570
+ constructor(capacity = 100) {
571
+ this.cache = /* @__PURE__ */ new Map();
572
+ this.hits = 0;
573
+ this.misses = 0;
574
+ this.evictions = 0;
575
+ if (capacity <= 0) {
576
+ throw new Error("Cache capacity must be greater than 0");
577
+ }
578
+ this.capacity = capacity;
579
+ }
580
+ /**
581
+ * Get a value from the cache
582
+ */
583
+ get(key) {
584
+ const entry = this.cache.get(key);
585
+ if (entry) {
586
+ entry.timestamp = Date.now();
587
+ entry.accessCount++;
588
+ this.hits++;
589
+ this.cache.delete(key);
590
+ this.cache.set(key, entry);
591
+ return entry.value;
592
+ }
593
+ this.misses++;
594
+ return void 0;
595
+ }
596
+ /**
597
+ * Set a value in the cache
598
+ */
599
+ set(key, value) {
600
+ if (this.cache.has(key)) {
601
+ this.cache.delete(key);
602
+ } else if (this.cache.size >= this.capacity) {
603
+ this.evictLRU();
604
+ }
605
+ this.cache.set(key, {
606
+ value,
607
+ timestamp: Date.now(),
608
+ accessCount: 0
609
+ });
610
+ }
611
+ /**
612
+ * Check if a key exists in the cache
613
+ */
614
+ has(key) {
615
+ return this.cache.has(key);
616
+ }
617
+ /**
618
+ * Delete a specific key from the cache
619
+ */
620
+ delete(key) {
621
+ return this.cache.delete(key);
622
+ }
623
+ /**
624
+ * Clear all entries from the cache
625
+ */
626
+ clear() {
627
+ this.cache.clear();
628
+ this.hits = 0;
629
+ this.misses = 0;
630
+ this.evictions = 0;
631
+ }
632
+ /**
633
+ * Get the current size of the cache
634
+ */
635
+ get size() {
636
+ return this.cache.size;
637
+ }
638
+ /**
639
+ * Get cache statistics
640
+ */
641
+ getStats() {
642
+ const totalRequests = this.hits + this.misses;
643
+ return {
644
+ size: this.cache.size,
645
+ capacity: this.capacity,
646
+ hits: this.hits,
647
+ misses: this.misses,
648
+ hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
649
+ evictions: this.evictions
650
+ };
651
+ }
652
+ /**
653
+ * Reset cache statistics
654
+ */
655
+ resetStats() {
656
+ this.hits = 0;
657
+ this.misses = 0;
658
+ this.evictions = 0;
659
+ }
660
+ /**
661
+ * Get all keys in the cache
662
+ */
663
+ keys() {
664
+ return Array.from(this.cache.keys());
665
+ }
666
+ /**
667
+ * Get all values in the cache
668
+ */
669
+ values() {
670
+ return Array.from(this.cache.values()).map((entry) => entry.value);
671
+ }
672
+ /**
673
+ * Update cache capacity and evict if necessary
674
+ */
675
+ setCapacity(newCapacity) {
676
+ if (newCapacity <= 0) {
677
+ throw new Error("Cache capacity must be greater than 0");
678
+ }
679
+ this.capacity = newCapacity;
680
+ while (this.cache.size > this.capacity) {
681
+ this.evictLRU();
682
+ }
683
+ }
684
+ /**
685
+ * Evict the least recently used entry
686
+ */
687
+ evictLRU() {
688
+ const firstKey = this.cache.keys().next().value;
689
+ if (firstKey !== void 0) {
690
+ this.cache.delete(firstKey);
691
+ this.evictions++;
692
+ }
693
+ }
694
+ };
695
+ function hashContent(content) {
696
+ if (content.length > 1e4) {
697
+ const start = content.slice(0, 1e3);
698
+ const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
699
+ const end = content.slice(-1e3);
700
+ const sample = content.length + "|" + start + middle + end;
701
+ let hash2 = 2166136261;
702
+ for (let i = 0; i < sample.length; i++) {
703
+ hash2 ^= sample.charCodeAt(i);
704
+ hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
705
+ }
706
+ return (hash2 >>> 0).toString(36);
707
+ }
708
+ let hash = 2166136261;
709
+ for (let i = 0; i < content.length; i++) {
710
+ hash ^= content.charCodeAt(i);
711
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
712
+ }
713
+ return (hash >>> 0).toString(36);
714
+ }
715
+
544
716
  // src/extensions/core/blockquote.ts
545
717
  var BlockquoteExtension = {
546
718
  name: "blockquote",
@@ -1513,8 +1685,13 @@ function extractVimeoId(url) {
1513
1685
  var ChangerawrMarkdown = class {
1514
1686
  constructor(config) {
1515
1687
  this.extensions = /* @__PURE__ */ new Map();
1688
+ this.parseTime = 0;
1689
+ this.renderTime = 0;
1690
+ this.lastTokenCount = 0;
1516
1691
  this.parser = new MarkdownParser(config?.parser);
1517
1692
  this.renderer = new MarkdownRenderer(config?.renderer);
1693
+ this.parseCache = new LRUCache(100);
1694
+ this.renderCache = new LRUCache(100);
1518
1695
  this.registerCoreExtensions();
1519
1696
  this.registerFeatureExtensions();
1520
1697
  if (config?.extensions) {
@@ -1597,14 +1774,83 @@ var ChangerawrMarkdown = class {
1597
1774
  }
1598
1775
  }
1599
1776
  parse(markdown2) {
1600
- return this.parser.parse(markdown2);
1777
+ const cacheKey = hashContent(markdown2);
1778
+ const cached = this.parseCache.get(cacheKey);
1779
+ if (cached) {
1780
+ this.lastTokenCount = cached.length;
1781
+ return cached;
1782
+ }
1783
+ const startTime = performance.now();
1784
+ const tokens = this.parser.parse(markdown2);
1785
+ this.parseTime = performance.now() - startTime;
1786
+ this.lastTokenCount = tokens.length;
1787
+ this.parseCache.set(cacheKey, tokens);
1788
+ return tokens;
1601
1789
  }
1602
- render(tokens) {
1603
- return this.renderer.render(tokens);
1790
+ render(tokens, cacheKey) {
1791
+ if (cacheKey) {
1792
+ const cached = this.renderCache.get(cacheKey);
1793
+ if (cached) {
1794
+ return cached;
1795
+ }
1796
+ }
1797
+ const startTime = performance.now();
1798
+ const html = this.renderer.render(tokens);
1799
+ this.renderTime = performance.now() - startTime;
1800
+ if (cacheKey) {
1801
+ this.renderCache.set(cacheKey, html);
1802
+ }
1803
+ return html;
1604
1804
  }
1605
1805
  toHtml(markdown2) {
1806
+ const cacheKey = hashContent(markdown2);
1807
+ const cachedHtml = this.renderCache.get(cacheKey);
1808
+ if (cachedHtml) {
1809
+ return cachedHtml;
1810
+ }
1606
1811
  const tokens = this.parse(markdown2);
1607
- return this.render(tokens);
1812
+ return this.render(tokens, cacheKey);
1813
+ }
1814
+ /**
1815
+ * Render markdown with performance metrics
1816
+ */
1817
+ toHtmlWithMetrics(markdown2) {
1818
+ const startTotal = performance.now();
1819
+ const parseCacheKey = hashContent(markdown2);
1820
+ const parseCacheHit = this.parseCache.has(parseCacheKey);
1821
+ const html = this.toHtml(markdown2);
1822
+ const totalTime = performance.now() - startTotal;
1823
+ const metrics = {
1824
+ inputSize: markdown2.length,
1825
+ parseTime: this.parseTime,
1826
+ renderTime: this.renderTime,
1827
+ totalTime,
1828
+ tokenCount: this.lastTokenCount,
1829
+ cacheHit: parseCacheHit
1830
+ };
1831
+ return { html, metrics };
1832
+ }
1833
+ /**
1834
+ * Stream-render large documents in chunks for better performance
1835
+ */
1836
+ async toHtmlStreamed(markdown2, options = {}) {
1837
+ const chunkSize = options.chunkSize || 50;
1838
+ const tokens = this.parse(markdown2);
1839
+ const totalTokens = tokens.length;
1840
+ const chunks = [];
1841
+ for (let i = 0; i < tokens.length; i += chunkSize) {
1842
+ const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
1843
+ const chunkHtml = this.render(chunkTokens);
1844
+ chunks.push(chunkHtml);
1845
+ if (options.onChunk) {
1846
+ options.onChunk({
1847
+ html: chunkHtml,
1848
+ progress: Math.min(i + chunkSize, tokens.length) / totalTokens
1849
+ });
1850
+ }
1851
+ await new Promise((resolve) => setTimeout(resolve, 0));
1852
+ }
1853
+ return chunks.join("");
1608
1854
  }
1609
1855
  getExtensions() {
1610
1856
  return Array.from(this.extensions.keys());
@@ -1618,25 +1864,50 @@ var ChangerawrMarkdown = class {
1618
1864
  getDebugInfo() {
1619
1865
  return {
1620
1866
  warnings: this.getWarnings(),
1621
- parseTime: 0,
1622
- renderTime: 0,
1623
- tokenCount: 0,
1867
+ parseTime: this.parseTime,
1868
+ renderTime: this.renderTime,
1869
+ tokenCount: this.lastTokenCount,
1624
1870
  iterationCount: 0
1625
1871
  };
1626
1872
  }
1627
1873
  getPerformanceMetrics() {
1628
1874
  return {
1629
- parseTime: 0,
1630
- renderTime: 0,
1631
- totalTime: 0,
1632
- tokenCount: 0
1875
+ parseTime: this.parseTime,
1876
+ renderTime: this.renderTime,
1877
+ totalTime: this.parseTime + this.renderTime,
1878
+ tokenCount: this.lastTokenCount
1879
+ };
1880
+ }
1881
+ /**
1882
+ * Get cache statistics
1883
+ */
1884
+ getCacheStats() {
1885
+ return {
1886
+ parse: this.parseCache.getStats(),
1887
+ render: this.renderCache.getStats()
1633
1888
  };
1634
1889
  }
1890
+ /**
1891
+ * Clear all caches
1892
+ */
1893
+ clearCaches() {
1894
+ this.parseCache.clear();
1895
+ this.renderCache.clear();
1896
+ }
1897
+ /**
1898
+ * Update cache capacity
1899
+ */
1900
+ setCacheSize(size) {
1901
+ this.parseCache.setCapacity(size);
1902
+ this.renderCache.setCapacity(size);
1903
+ }
1635
1904
  rebuildParserAndRenderer() {
1636
1905
  const parserConfig = this.parser.getConfig();
1637
1906
  const rendererConfig = this.renderer.getConfig();
1638
1907
  this.parser = new MarkdownParser(parserConfig);
1639
1908
  this.renderer = new MarkdownRenderer(rendererConfig);
1909
+ this.parseCache.clear();
1910
+ this.renderCache.clear();
1640
1911
  const extensionsToRegister = Array.from(this.extensions.values());
1641
1912
  const featureExtensions = extensionsToRegister.filter(
1642
1913
  (ext) => ["alert", "button", "embed"].includes(ext.name)