@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/react/index.mjs
CHANGED
|
@@ -6,9 +6,11 @@ import { useState, useCallback, useMemo, useRef, useEffect } from "react";
|
|
|
6
6
|
|
|
7
7
|
// src/parser.ts
|
|
8
8
|
var MarkdownParser = class {
|
|
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 =
|
|
75
|
+
const pattern = this.compiledPatterns.get(rule);
|
|
69
76
|
const match = remaining.match(pattern);
|
|
70
77
|
if (match && match.index !== void 0) {
|
|
71
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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) {
|
|
@@ -541,6 +554,154 @@ var MarkdownRenderer = class {
|
|
|
541
554
|
}
|
|
542
555
|
};
|
|
543
556
|
|
|
557
|
+
// src/cache.ts
|
|
558
|
+
var LRUCache = class {
|
|
559
|
+
constructor(capacity = 100) {
|
|
560
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
561
|
+
this.hits = 0;
|
|
562
|
+
this.misses = 0;
|
|
563
|
+
this.evictions = 0;
|
|
564
|
+
if (capacity <= 0) {
|
|
565
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
566
|
+
}
|
|
567
|
+
this.capacity = capacity;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get a value from the cache
|
|
571
|
+
*/
|
|
572
|
+
get(key) {
|
|
573
|
+
const entry = this.cache.get(key);
|
|
574
|
+
if (entry) {
|
|
575
|
+
entry.timestamp = Date.now();
|
|
576
|
+
entry.accessCount++;
|
|
577
|
+
this.hits++;
|
|
578
|
+
this.cache.delete(key);
|
|
579
|
+
this.cache.set(key, entry);
|
|
580
|
+
return entry.value;
|
|
581
|
+
}
|
|
582
|
+
this.misses++;
|
|
583
|
+
return void 0;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Set a value in the cache
|
|
587
|
+
*/
|
|
588
|
+
set(key, value) {
|
|
589
|
+
if (this.cache.has(key)) {
|
|
590
|
+
this.cache.delete(key);
|
|
591
|
+
} else if (this.cache.size >= this.capacity) {
|
|
592
|
+
this.evictLRU();
|
|
593
|
+
}
|
|
594
|
+
this.cache.set(key, {
|
|
595
|
+
value,
|
|
596
|
+
timestamp: Date.now(),
|
|
597
|
+
accessCount: 0
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Check if a key exists in the cache
|
|
602
|
+
*/
|
|
603
|
+
has(key) {
|
|
604
|
+
return this.cache.has(key);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Delete a specific key from the cache
|
|
608
|
+
*/
|
|
609
|
+
delete(key) {
|
|
610
|
+
return this.cache.delete(key);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Clear all entries from the cache
|
|
614
|
+
*/
|
|
615
|
+
clear() {
|
|
616
|
+
this.cache.clear();
|
|
617
|
+
this.hits = 0;
|
|
618
|
+
this.misses = 0;
|
|
619
|
+
this.evictions = 0;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get the current size of the cache
|
|
623
|
+
*/
|
|
624
|
+
get size() {
|
|
625
|
+
return this.cache.size;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get cache statistics
|
|
629
|
+
*/
|
|
630
|
+
getStats() {
|
|
631
|
+
const totalRequests = this.hits + this.misses;
|
|
632
|
+
return {
|
|
633
|
+
size: this.cache.size,
|
|
634
|
+
capacity: this.capacity,
|
|
635
|
+
hits: this.hits,
|
|
636
|
+
misses: this.misses,
|
|
637
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
638
|
+
evictions: this.evictions
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Reset cache statistics
|
|
643
|
+
*/
|
|
644
|
+
resetStats() {
|
|
645
|
+
this.hits = 0;
|
|
646
|
+
this.misses = 0;
|
|
647
|
+
this.evictions = 0;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get all keys in the cache
|
|
651
|
+
*/
|
|
652
|
+
keys() {
|
|
653
|
+
return Array.from(this.cache.keys());
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Get all values in the cache
|
|
657
|
+
*/
|
|
658
|
+
values() {
|
|
659
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Update cache capacity and evict if necessary
|
|
663
|
+
*/
|
|
664
|
+
setCapacity(newCapacity) {
|
|
665
|
+
if (newCapacity <= 0) {
|
|
666
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
667
|
+
}
|
|
668
|
+
this.capacity = newCapacity;
|
|
669
|
+
while (this.cache.size > this.capacity) {
|
|
670
|
+
this.evictLRU();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Evict the least recently used entry
|
|
675
|
+
*/
|
|
676
|
+
evictLRU() {
|
|
677
|
+
const firstKey = this.cache.keys().next().value;
|
|
678
|
+
if (firstKey !== void 0) {
|
|
679
|
+
this.cache.delete(firstKey);
|
|
680
|
+
this.evictions++;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
function hashContent(content) {
|
|
685
|
+
if (content.length > 1e4) {
|
|
686
|
+
const start = content.slice(0, 1e3);
|
|
687
|
+
const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
|
|
688
|
+
const end = content.slice(-1e3);
|
|
689
|
+
const sample = content.length + "|" + start + middle + end;
|
|
690
|
+
let hash2 = 2166136261;
|
|
691
|
+
for (let i = 0; i < sample.length; i++) {
|
|
692
|
+
hash2 ^= sample.charCodeAt(i);
|
|
693
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
694
|
+
}
|
|
695
|
+
return (hash2 >>> 0).toString(36);
|
|
696
|
+
}
|
|
697
|
+
let hash = 2166136261;
|
|
698
|
+
for (let i = 0; i < content.length; i++) {
|
|
699
|
+
hash ^= content.charCodeAt(i);
|
|
700
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
701
|
+
}
|
|
702
|
+
return (hash >>> 0).toString(36);
|
|
703
|
+
}
|
|
704
|
+
|
|
544
705
|
// src/extensions/core/blockquote.ts
|
|
545
706
|
var BlockquoteExtension = {
|
|
546
707
|
name: "blockquote",
|
|
@@ -1513,8 +1674,13 @@ function extractVimeoId(url) {
|
|
|
1513
1674
|
var ChangerawrMarkdown = class {
|
|
1514
1675
|
constructor(config) {
|
|
1515
1676
|
this.extensions = /* @__PURE__ */ new Map();
|
|
1677
|
+
this.parseTime = 0;
|
|
1678
|
+
this.renderTime = 0;
|
|
1679
|
+
this.lastTokenCount = 0;
|
|
1516
1680
|
this.parser = new MarkdownParser(config?.parser);
|
|
1517
1681
|
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1682
|
+
this.parseCache = new LRUCache(100);
|
|
1683
|
+
this.renderCache = new LRUCache(100);
|
|
1518
1684
|
this.registerCoreExtensions();
|
|
1519
1685
|
this.registerFeatureExtensions();
|
|
1520
1686
|
if (config?.extensions) {
|
|
@@ -1597,14 +1763,83 @@ var ChangerawrMarkdown = class {
|
|
|
1597
1763
|
}
|
|
1598
1764
|
}
|
|
1599
1765
|
parse(markdown2) {
|
|
1600
|
-
|
|
1766
|
+
const cacheKey = hashContent(markdown2);
|
|
1767
|
+
const cached = this.parseCache.get(cacheKey);
|
|
1768
|
+
if (cached) {
|
|
1769
|
+
this.lastTokenCount = cached.length;
|
|
1770
|
+
return cached;
|
|
1771
|
+
}
|
|
1772
|
+
const startTime = performance.now();
|
|
1773
|
+
const tokens = this.parser.parse(markdown2);
|
|
1774
|
+
this.parseTime = performance.now() - startTime;
|
|
1775
|
+
this.lastTokenCount = tokens.length;
|
|
1776
|
+
this.parseCache.set(cacheKey, tokens);
|
|
1777
|
+
return tokens;
|
|
1601
1778
|
}
|
|
1602
|
-
render(tokens) {
|
|
1603
|
-
|
|
1779
|
+
render(tokens, cacheKey) {
|
|
1780
|
+
if (cacheKey) {
|
|
1781
|
+
const cached = this.renderCache.get(cacheKey);
|
|
1782
|
+
if (cached) {
|
|
1783
|
+
return cached;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
const startTime = performance.now();
|
|
1787
|
+
const html = this.renderer.render(tokens);
|
|
1788
|
+
this.renderTime = performance.now() - startTime;
|
|
1789
|
+
if (cacheKey) {
|
|
1790
|
+
this.renderCache.set(cacheKey, html);
|
|
1791
|
+
}
|
|
1792
|
+
return html;
|
|
1604
1793
|
}
|
|
1605
1794
|
toHtml(markdown2) {
|
|
1795
|
+
const cacheKey = hashContent(markdown2);
|
|
1796
|
+
const cachedHtml = this.renderCache.get(cacheKey);
|
|
1797
|
+
if (cachedHtml) {
|
|
1798
|
+
return cachedHtml;
|
|
1799
|
+
}
|
|
1606
1800
|
const tokens = this.parse(markdown2);
|
|
1607
|
-
return this.render(tokens);
|
|
1801
|
+
return this.render(tokens, cacheKey);
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Render markdown with performance metrics
|
|
1805
|
+
*/
|
|
1806
|
+
toHtmlWithMetrics(markdown2) {
|
|
1807
|
+
const startTotal = performance.now();
|
|
1808
|
+
const parseCacheKey = hashContent(markdown2);
|
|
1809
|
+
const parseCacheHit = this.parseCache.has(parseCacheKey);
|
|
1810
|
+
const html = this.toHtml(markdown2);
|
|
1811
|
+
const totalTime = performance.now() - startTotal;
|
|
1812
|
+
const metrics = {
|
|
1813
|
+
inputSize: markdown2.length,
|
|
1814
|
+
parseTime: this.parseTime,
|
|
1815
|
+
renderTime: this.renderTime,
|
|
1816
|
+
totalTime,
|
|
1817
|
+
tokenCount: this.lastTokenCount,
|
|
1818
|
+
cacheHit: parseCacheHit
|
|
1819
|
+
};
|
|
1820
|
+
return { html, metrics };
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Stream-render large documents in chunks for better performance
|
|
1824
|
+
*/
|
|
1825
|
+
async toHtmlStreamed(markdown2, options = {}) {
|
|
1826
|
+
const chunkSize = options.chunkSize || 50;
|
|
1827
|
+
const tokens = this.parse(markdown2);
|
|
1828
|
+
const totalTokens = tokens.length;
|
|
1829
|
+
const chunks = [];
|
|
1830
|
+
for (let i = 0; i < tokens.length; i += chunkSize) {
|
|
1831
|
+
const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
|
|
1832
|
+
const chunkHtml = this.render(chunkTokens);
|
|
1833
|
+
chunks.push(chunkHtml);
|
|
1834
|
+
if (options.onChunk) {
|
|
1835
|
+
options.onChunk({
|
|
1836
|
+
html: chunkHtml,
|
|
1837
|
+
progress: Math.min(i + chunkSize, tokens.length) / totalTokens
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1841
|
+
}
|
|
1842
|
+
return chunks.join("");
|
|
1608
1843
|
}
|
|
1609
1844
|
getExtensions() {
|
|
1610
1845
|
return Array.from(this.extensions.keys());
|
|
@@ -1618,25 +1853,50 @@ var ChangerawrMarkdown = class {
|
|
|
1618
1853
|
getDebugInfo() {
|
|
1619
1854
|
return {
|
|
1620
1855
|
warnings: this.getWarnings(),
|
|
1621
|
-
parseTime:
|
|
1622
|
-
renderTime:
|
|
1623
|
-
tokenCount:
|
|
1856
|
+
parseTime: this.parseTime,
|
|
1857
|
+
renderTime: this.renderTime,
|
|
1858
|
+
tokenCount: this.lastTokenCount,
|
|
1624
1859
|
iterationCount: 0
|
|
1625
1860
|
};
|
|
1626
1861
|
}
|
|
1627
1862
|
getPerformanceMetrics() {
|
|
1628
1863
|
return {
|
|
1629
|
-
parseTime:
|
|
1630
|
-
renderTime:
|
|
1631
|
-
totalTime:
|
|
1632
|
-
tokenCount:
|
|
1864
|
+
parseTime: this.parseTime,
|
|
1865
|
+
renderTime: this.renderTime,
|
|
1866
|
+
totalTime: this.parseTime + this.renderTime,
|
|
1867
|
+
tokenCount: this.lastTokenCount
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Get cache statistics
|
|
1872
|
+
*/
|
|
1873
|
+
getCacheStats() {
|
|
1874
|
+
return {
|
|
1875
|
+
parse: this.parseCache.getStats(),
|
|
1876
|
+
render: this.renderCache.getStats()
|
|
1633
1877
|
};
|
|
1634
1878
|
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Clear all caches
|
|
1881
|
+
*/
|
|
1882
|
+
clearCaches() {
|
|
1883
|
+
this.parseCache.clear();
|
|
1884
|
+
this.renderCache.clear();
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Update cache capacity
|
|
1888
|
+
*/
|
|
1889
|
+
setCacheSize(size) {
|
|
1890
|
+
this.parseCache.setCapacity(size);
|
|
1891
|
+
this.renderCache.setCapacity(size);
|
|
1892
|
+
}
|
|
1635
1893
|
rebuildParserAndRenderer() {
|
|
1636
1894
|
const parserConfig = this.parser.getConfig();
|
|
1637
1895
|
const rendererConfig = this.renderer.getConfig();
|
|
1638
1896
|
this.parser = new MarkdownParser(parserConfig);
|
|
1639
1897
|
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
1898
|
+
this.parseCache.clear();
|
|
1899
|
+
this.renderCache.clear();
|
|
1640
1900
|
const extensionsToRegister = Array.from(this.extensions.values());
|
|
1641
1901
|
const featureExtensions = extensionsToRegister.filter(
|
|
1642
1902
|
(ext) => ["alert", "button", "embed"].includes(ext.name)
|