@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.
- package/README.md +94 -9
- package/dist/index.d.mts +129 -2
- package/dist/index.d.ts +129 -2
- package/dist/index.js +319 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +316 -25
- 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 +295 -24
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +295 -24
- package/dist/react/index.mjs.map +1 -1
- package/dist/standalone.browser.js +317 -24
- package/dist/standalone.d.mts +55 -1
- package/dist/standalone.d.ts +55 -1
- package/dist/standalone.js +295 -24
- package/dist/standalone.js.map +1 -1
- package/dist/standalone.mjs +295 -24
- package/dist/standalone.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -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 =
|
|
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) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1622
|
-
renderTime:
|
|
1623
|
-
tokenCount:
|
|
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:
|
|
1630
|
-
renderTime:
|
|
1631
|
-
totalTime:
|
|
1632
|
-
tokenCount:
|
|
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)
|