@changerawr/markdown 1.1.5 → 1.1.6
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 +94 -9
- package/dist/index.d.mts +129 -2
- package/dist/index.d.ts +129 -2
- package/dist/index.js +306 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +303 -23
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +55 -1
- package/dist/react/index.d.ts +55 -1
- package/dist/react/index.js +282 -22
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +282 -22
- package/dist/react/index.mjs.map +1 -1
- package/dist/standalone.browser.js +304 -22
- package/dist/standalone.d.mts +55 -1
- package/dist/standalone.d.ts +55 -1
- package/dist/standalone.js +282 -22
- package/dist/standalone.js.map +1 -1
- package/dist/standalone.mjs +282 -22
- package/dist/standalone.mjs.map +1 -1
- package/package.json +1 -1
package/dist/standalone.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// src/parser.ts
|
|
2
2
|
var MarkdownParser = class {
|
|
3
|
+
// Cache compiled regexes
|
|
3
4
|
constructor(config) {
|
|
4
5
|
this.rules = [];
|
|
5
6
|
this.warnings = [];
|
|
7
|
+
this.compiledPatterns = /* @__PURE__ */ new Map();
|
|
6
8
|
this.config = {
|
|
7
9
|
debugMode: false,
|
|
8
10
|
maxIterations: 1e4,
|
|
@@ -12,6 +14,10 @@ var MarkdownParser = class {
|
|
|
12
14
|
}
|
|
13
15
|
addRule(rule) {
|
|
14
16
|
this.rules.push(rule);
|
|
17
|
+
this.compiledPatterns.set(
|
|
18
|
+
rule,
|
|
19
|
+
new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
|
|
20
|
+
);
|
|
15
21
|
this.rules.sort((a, b) => {
|
|
16
22
|
const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
|
|
17
23
|
const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
|
|
@@ -57,12 +63,20 @@ var MarkdownParser = class {
|
|
|
57
63
|
iterationCount++;
|
|
58
64
|
let matched = false;
|
|
59
65
|
let bestMatch = null;
|
|
66
|
+
let nextBestMatchIndex = null;
|
|
60
67
|
for (const rule of this.rules) {
|
|
61
68
|
try {
|
|
62
|
-
const pattern =
|
|
69
|
+
const pattern = this.compiledPatterns.get(rule);
|
|
63
70
|
const match = remaining.match(pattern);
|
|
64
71
|
if (match && match.index !== void 0) {
|
|
65
|
-
|
|
72
|
+
if (match.index === 0) {
|
|
73
|
+
bestMatch = { rule, match, priority: 1e3 };
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
|
|
77
|
+
nextBestMatchIndex = match.index;
|
|
78
|
+
}
|
|
79
|
+
const priority = 1e3 - match.index;
|
|
66
80
|
if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
|
|
67
81
|
bestMatch = { rule, match, priority };
|
|
68
82
|
}
|
|
@@ -109,15 +123,14 @@ var MarkdownParser = class {
|
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
if (!matched) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
126
|
+
const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
|
|
127
|
+
const textChunk = remaining.slice(0, chunkSize);
|
|
128
|
+
tokens.push({
|
|
129
|
+
type: "text",
|
|
130
|
+
content: textChunk,
|
|
131
|
+
raw: textChunk
|
|
132
|
+
});
|
|
133
|
+
remaining = remaining.slice(chunkSize);
|
|
121
134
|
}
|
|
122
135
|
}
|
|
123
136
|
if (iterationCount >= maxIterations) {
|
|
@@ -535,6 +548,154 @@ var MarkdownRenderer = class {
|
|
|
535
548
|
}
|
|
536
549
|
};
|
|
537
550
|
|
|
551
|
+
// src/cache.ts
|
|
552
|
+
var LRUCache = class {
|
|
553
|
+
constructor(capacity = 100) {
|
|
554
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
555
|
+
this.hits = 0;
|
|
556
|
+
this.misses = 0;
|
|
557
|
+
this.evictions = 0;
|
|
558
|
+
if (capacity <= 0) {
|
|
559
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
560
|
+
}
|
|
561
|
+
this.capacity = capacity;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Get a value from the cache
|
|
565
|
+
*/
|
|
566
|
+
get(key) {
|
|
567
|
+
const entry = this.cache.get(key);
|
|
568
|
+
if (entry) {
|
|
569
|
+
entry.timestamp = Date.now();
|
|
570
|
+
entry.accessCount++;
|
|
571
|
+
this.hits++;
|
|
572
|
+
this.cache.delete(key);
|
|
573
|
+
this.cache.set(key, entry);
|
|
574
|
+
return entry.value;
|
|
575
|
+
}
|
|
576
|
+
this.misses++;
|
|
577
|
+
return void 0;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Set a value in the cache
|
|
581
|
+
*/
|
|
582
|
+
set(key, value) {
|
|
583
|
+
if (this.cache.has(key)) {
|
|
584
|
+
this.cache.delete(key);
|
|
585
|
+
} else if (this.cache.size >= this.capacity) {
|
|
586
|
+
this.evictLRU();
|
|
587
|
+
}
|
|
588
|
+
this.cache.set(key, {
|
|
589
|
+
value,
|
|
590
|
+
timestamp: Date.now(),
|
|
591
|
+
accessCount: 0
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Check if a key exists in the cache
|
|
596
|
+
*/
|
|
597
|
+
has(key) {
|
|
598
|
+
return this.cache.has(key);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Delete a specific key from the cache
|
|
602
|
+
*/
|
|
603
|
+
delete(key) {
|
|
604
|
+
return this.cache.delete(key);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Clear all entries from the cache
|
|
608
|
+
*/
|
|
609
|
+
clear() {
|
|
610
|
+
this.cache.clear();
|
|
611
|
+
this.hits = 0;
|
|
612
|
+
this.misses = 0;
|
|
613
|
+
this.evictions = 0;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get the current size of the cache
|
|
617
|
+
*/
|
|
618
|
+
get size() {
|
|
619
|
+
return this.cache.size;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get cache statistics
|
|
623
|
+
*/
|
|
624
|
+
getStats() {
|
|
625
|
+
const totalRequests = this.hits + this.misses;
|
|
626
|
+
return {
|
|
627
|
+
size: this.cache.size,
|
|
628
|
+
capacity: this.capacity,
|
|
629
|
+
hits: this.hits,
|
|
630
|
+
misses: this.misses,
|
|
631
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
632
|
+
evictions: this.evictions
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Reset cache statistics
|
|
637
|
+
*/
|
|
638
|
+
resetStats() {
|
|
639
|
+
this.hits = 0;
|
|
640
|
+
this.misses = 0;
|
|
641
|
+
this.evictions = 0;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Get all keys in the cache
|
|
645
|
+
*/
|
|
646
|
+
keys() {
|
|
647
|
+
return Array.from(this.cache.keys());
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get all values in the cache
|
|
651
|
+
*/
|
|
652
|
+
values() {
|
|
653
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Update cache capacity and evict if necessary
|
|
657
|
+
*/
|
|
658
|
+
setCapacity(newCapacity) {
|
|
659
|
+
if (newCapacity <= 0) {
|
|
660
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
661
|
+
}
|
|
662
|
+
this.capacity = newCapacity;
|
|
663
|
+
while (this.cache.size > this.capacity) {
|
|
664
|
+
this.evictLRU();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Evict the least recently used entry
|
|
669
|
+
*/
|
|
670
|
+
evictLRU() {
|
|
671
|
+
const firstKey = this.cache.keys().next().value;
|
|
672
|
+
if (firstKey !== void 0) {
|
|
673
|
+
this.cache.delete(firstKey);
|
|
674
|
+
this.evictions++;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
function hashContent(content) {
|
|
679
|
+
if (content.length > 1e4) {
|
|
680
|
+
const start = content.slice(0, 1e3);
|
|
681
|
+
const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
|
|
682
|
+
const end = content.slice(-1e3);
|
|
683
|
+
const sample = content.length + "|" + start + middle + end;
|
|
684
|
+
let hash2 = 2166136261;
|
|
685
|
+
for (let i = 0; i < sample.length; i++) {
|
|
686
|
+
hash2 ^= sample.charCodeAt(i);
|
|
687
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
688
|
+
}
|
|
689
|
+
return (hash2 >>> 0).toString(36);
|
|
690
|
+
}
|
|
691
|
+
let hash = 2166136261;
|
|
692
|
+
for (let i = 0; i < content.length; i++) {
|
|
693
|
+
hash ^= content.charCodeAt(i);
|
|
694
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
695
|
+
}
|
|
696
|
+
return (hash >>> 0).toString(36);
|
|
697
|
+
}
|
|
698
|
+
|
|
538
699
|
// src/extensions/core/blockquote.ts
|
|
539
700
|
var BlockquoteExtension = {
|
|
540
701
|
name: "blockquote",
|
|
@@ -1507,8 +1668,13 @@ function extractVimeoId(url) {
|
|
|
1507
1668
|
var ChangerawrMarkdown = class {
|
|
1508
1669
|
constructor(config) {
|
|
1509
1670
|
this.extensions = /* @__PURE__ */ new Map();
|
|
1671
|
+
this.parseTime = 0;
|
|
1672
|
+
this.renderTime = 0;
|
|
1673
|
+
this.lastTokenCount = 0;
|
|
1510
1674
|
this.parser = new MarkdownParser(config?.parser);
|
|
1511
1675
|
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1676
|
+
this.parseCache = new LRUCache(100);
|
|
1677
|
+
this.renderCache = new LRUCache(100);
|
|
1512
1678
|
this.registerCoreExtensions();
|
|
1513
1679
|
this.registerFeatureExtensions();
|
|
1514
1680
|
if (config?.extensions) {
|
|
@@ -1591,14 +1757,83 @@ var ChangerawrMarkdown = class {
|
|
|
1591
1757
|
}
|
|
1592
1758
|
}
|
|
1593
1759
|
parse(markdown2) {
|
|
1594
|
-
|
|
1760
|
+
const cacheKey = hashContent(markdown2);
|
|
1761
|
+
const cached = this.parseCache.get(cacheKey);
|
|
1762
|
+
if (cached) {
|
|
1763
|
+
this.lastTokenCount = cached.length;
|
|
1764
|
+
return cached;
|
|
1765
|
+
}
|
|
1766
|
+
const startTime = performance.now();
|
|
1767
|
+
const tokens = this.parser.parse(markdown2);
|
|
1768
|
+
this.parseTime = performance.now() - startTime;
|
|
1769
|
+
this.lastTokenCount = tokens.length;
|
|
1770
|
+
this.parseCache.set(cacheKey, tokens);
|
|
1771
|
+
return tokens;
|
|
1595
1772
|
}
|
|
1596
|
-
render(tokens) {
|
|
1597
|
-
|
|
1773
|
+
render(tokens, cacheKey) {
|
|
1774
|
+
if (cacheKey) {
|
|
1775
|
+
const cached = this.renderCache.get(cacheKey);
|
|
1776
|
+
if (cached) {
|
|
1777
|
+
return cached;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
const startTime = performance.now();
|
|
1781
|
+
const html = this.renderer.render(tokens);
|
|
1782
|
+
this.renderTime = performance.now() - startTime;
|
|
1783
|
+
if (cacheKey) {
|
|
1784
|
+
this.renderCache.set(cacheKey, html);
|
|
1785
|
+
}
|
|
1786
|
+
return html;
|
|
1598
1787
|
}
|
|
1599
1788
|
toHtml(markdown2) {
|
|
1789
|
+
const cacheKey = hashContent(markdown2);
|
|
1790
|
+
const cachedHtml = this.renderCache.get(cacheKey);
|
|
1791
|
+
if (cachedHtml) {
|
|
1792
|
+
return cachedHtml;
|
|
1793
|
+
}
|
|
1600
1794
|
const tokens = this.parse(markdown2);
|
|
1601
|
-
return this.render(tokens);
|
|
1795
|
+
return this.render(tokens, cacheKey);
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Render markdown with performance metrics
|
|
1799
|
+
*/
|
|
1800
|
+
toHtmlWithMetrics(markdown2) {
|
|
1801
|
+
const startTotal = performance.now();
|
|
1802
|
+
const parseCacheKey = hashContent(markdown2);
|
|
1803
|
+
const parseCacheHit = this.parseCache.has(parseCacheKey);
|
|
1804
|
+
const html = this.toHtml(markdown2);
|
|
1805
|
+
const totalTime = performance.now() - startTotal;
|
|
1806
|
+
const metrics = {
|
|
1807
|
+
inputSize: markdown2.length,
|
|
1808
|
+
parseTime: this.parseTime,
|
|
1809
|
+
renderTime: this.renderTime,
|
|
1810
|
+
totalTime,
|
|
1811
|
+
tokenCount: this.lastTokenCount,
|
|
1812
|
+
cacheHit: parseCacheHit
|
|
1813
|
+
};
|
|
1814
|
+
return { html, metrics };
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Stream-render large documents in chunks for better performance
|
|
1818
|
+
*/
|
|
1819
|
+
async toHtmlStreamed(markdown2, options = {}) {
|
|
1820
|
+
const chunkSize = options.chunkSize || 50;
|
|
1821
|
+
const tokens = this.parse(markdown2);
|
|
1822
|
+
const totalTokens = tokens.length;
|
|
1823
|
+
const chunks = [];
|
|
1824
|
+
for (let i = 0; i < tokens.length; i += chunkSize) {
|
|
1825
|
+
const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
|
|
1826
|
+
const chunkHtml = this.render(chunkTokens);
|
|
1827
|
+
chunks.push(chunkHtml);
|
|
1828
|
+
if (options.onChunk) {
|
|
1829
|
+
options.onChunk({
|
|
1830
|
+
html: chunkHtml,
|
|
1831
|
+
progress: Math.min(i + chunkSize, tokens.length) / totalTokens
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1835
|
+
}
|
|
1836
|
+
return chunks.join("");
|
|
1602
1837
|
}
|
|
1603
1838
|
getExtensions() {
|
|
1604
1839
|
return Array.from(this.extensions.keys());
|
|
@@ -1612,25 +1847,50 @@ var ChangerawrMarkdown = class {
|
|
|
1612
1847
|
getDebugInfo() {
|
|
1613
1848
|
return {
|
|
1614
1849
|
warnings: this.getWarnings(),
|
|
1615
|
-
parseTime:
|
|
1616
|
-
renderTime:
|
|
1617
|
-
tokenCount:
|
|
1850
|
+
parseTime: this.parseTime,
|
|
1851
|
+
renderTime: this.renderTime,
|
|
1852
|
+
tokenCount: this.lastTokenCount,
|
|
1618
1853
|
iterationCount: 0
|
|
1619
1854
|
};
|
|
1620
1855
|
}
|
|
1621
1856
|
getPerformanceMetrics() {
|
|
1622
1857
|
return {
|
|
1623
|
-
parseTime:
|
|
1624
|
-
renderTime:
|
|
1625
|
-
totalTime:
|
|
1626
|
-
tokenCount:
|
|
1858
|
+
parseTime: this.parseTime,
|
|
1859
|
+
renderTime: this.renderTime,
|
|
1860
|
+
totalTime: this.parseTime + this.renderTime,
|
|
1861
|
+
tokenCount: this.lastTokenCount
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Get cache statistics
|
|
1866
|
+
*/
|
|
1867
|
+
getCacheStats() {
|
|
1868
|
+
return {
|
|
1869
|
+
parse: this.parseCache.getStats(),
|
|
1870
|
+
render: this.renderCache.getStats()
|
|
1627
1871
|
};
|
|
1628
1872
|
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Clear all caches
|
|
1875
|
+
*/
|
|
1876
|
+
clearCaches() {
|
|
1877
|
+
this.parseCache.clear();
|
|
1878
|
+
this.renderCache.clear();
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Update cache capacity
|
|
1882
|
+
*/
|
|
1883
|
+
setCacheSize(size) {
|
|
1884
|
+
this.parseCache.setCapacity(size);
|
|
1885
|
+
this.renderCache.setCapacity(size);
|
|
1886
|
+
}
|
|
1629
1887
|
rebuildParserAndRenderer() {
|
|
1630
1888
|
const parserConfig = this.parser.getConfig();
|
|
1631
1889
|
const rendererConfig = this.renderer.getConfig();
|
|
1632
1890
|
this.parser = new MarkdownParser(parserConfig);
|
|
1633
1891
|
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
1892
|
+
this.parseCache.clear();
|
|
1893
|
+
this.renderCache.clear();
|
|
1634
1894
|
const extensionsToRegister = Array.from(this.extensions.values());
|
|
1635
1895
|
const featureExtensions = extensionsToRegister.filter(
|
|
1636
1896
|
(ext) => ["alert", "button", "embed"].includes(ext.name)
|