@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/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// src/parser.ts
|
|
2
|
-
var MarkdownParser = class {
|
|
2
|
+
var MarkdownParser = class _MarkdownParser {
|
|
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) {
|
|
@@ -199,7 +212,18 @@ var MarkdownParser = class {
|
|
|
199
212
|
recursivelyParseBlockContent(token) {
|
|
200
213
|
const blockTypes = ["alert", "blockquote", "list-item", "task-item"];
|
|
201
214
|
if (blockTypes.includes(token.type) && token.content && token.content.trim()) {
|
|
202
|
-
|
|
215
|
+
let children;
|
|
216
|
+
if ((token.type === "list-item" || token.type === "task-item") && this.rules.some((r) => r.name === "list-item")) {
|
|
217
|
+
const parserWithoutListRule = new _MarkdownParser(this.config);
|
|
218
|
+
this.rules.forEach((rule) => {
|
|
219
|
+
if (rule.name !== "list-item" && rule.name !== "task-item") {
|
|
220
|
+
parserWithoutListRule.addRule(rule);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
children = parserWithoutListRule.parse(token.content);
|
|
224
|
+
} else {
|
|
225
|
+
children = this.parse(token.content);
|
|
226
|
+
}
|
|
203
227
|
return {
|
|
204
228
|
...token,
|
|
205
229
|
children
|
|
@@ -615,6 +639,171 @@ var MarkdownRenderer = class {
|
|
|
615
639
|
}
|
|
616
640
|
};
|
|
617
641
|
|
|
642
|
+
// src/cache.ts
|
|
643
|
+
var LRUCache = class {
|
|
644
|
+
constructor(capacity = 100) {
|
|
645
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
646
|
+
this.hits = 0;
|
|
647
|
+
this.misses = 0;
|
|
648
|
+
this.evictions = 0;
|
|
649
|
+
if (capacity <= 0) {
|
|
650
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
651
|
+
}
|
|
652
|
+
this.capacity = capacity;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get a value from the cache
|
|
656
|
+
*/
|
|
657
|
+
get(key) {
|
|
658
|
+
const entry = this.cache.get(key);
|
|
659
|
+
if (entry) {
|
|
660
|
+
entry.timestamp = Date.now();
|
|
661
|
+
entry.accessCount++;
|
|
662
|
+
this.hits++;
|
|
663
|
+
this.cache.delete(key);
|
|
664
|
+
this.cache.set(key, entry);
|
|
665
|
+
return entry.value;
|
|
666
|
+
}
|
|
667
|
+
this.misses++;
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Set a value in the cache
|
|
672
|
+
*/
|
|
673
|
+
set(key, value) {
|
|
674
|
+
if (this.cache.has(key)) {
|
|
675
|
+
this.cache.delete(key);
|
|
676
|
+
} else if (this.cache.size >= this.capacity) {
|
|
677
|
+
this.evictLRU();
|
|
678
|
+
}
|
|
679
|
+
this.cache.set(key, {
|
|
680
|
+
value,
|
|
681
|
+
timestamp: Date.now(),
|
|
682
|
+
accessCount: 0
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Check if a key exists in the cache
|
|
687
|
+
*/
|
|
688
|
+
has(key) {
|
|
689
|
+
return this.cache.has(key);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Delete a specific key from the cache
|
|
693
|
+
*/
|
|
694
|
+
delete(key) {
|
|
695
|
+
return this.cache.delete(key);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Clear all entries from the cache
|
|
699
|
+
*/
|
|
700
|
+
clear() {
|
|
701
|
+
this.cache.clear();
|
|
702
|
+
this.hits = 0;
|
|
703
|
+
this.misses = 0;
|
|
704
|
+
this.evictions = 0;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get the current size of the cache
|
|
708
|
+
*/
|
|
709
|
+
get size() {
|
|
710
|
+
return this.cache.size;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Get cache statistics
|
|
714
|
+
*/
|
|
715
|
+
getStats() {
|
|
716
|
+
const totalRequests = this.hits + this.misses;
|
|
717
|
+
return {
|
|
718
|
+
size: this.cache.size,
|
|
719
|
+
capacity: this.capacity,
|
|
720
|
+
hits: this.hits,
|
|
721
|
+
misses: this.misses,
|
|
722
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
723
|
+
evictions: this.evictions
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Reset cache statistics
|
|
728
|
+
*/
|
|
729
|
+
resetStats() {
|
|
730
|
+
this.hits = 0;
|
|
731
|
+
this.misses = 0;
|
|
732
|
+
this.evictions = 0;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Get all keys in the cache
|
|
736
|
+
*/
|
|
737
|
+
keys() {
|
|
738
|
+
return Array.from(this.cache.keys());
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Get all values in the cache
|
|
742
|
+
*/
|
|
743
|
+
values() {
|
|
744
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Update cache capacity and evict if necessary
|
|
748
|
+
*/
|
|
749
|
+
setCapacity(newCapacity) {
|
|
750
|
+
if (newCapacity <= 0) {
|
|
751
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
752
|
+
}
|
|
753
|
+
this.capacity = newCapacity;
|
|
754
|
+
while (this.cache.size > this.capacity) {
|
|
755
|
+
this.evictLRU();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Evict the least recently used entry
|
|
760
|
+
*/
|
|
761
|
+
evictLRU() {
|
|
762
|
+
const firstKey = this.cache.keys().next().value;
|
|
763
|
+
if (firstKey !== void 0) {
|
|
764
|
+
this.cache.delete(firstKey);
|
|
765
|
+
this.evictions++;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
function hashContent(content) {
|
|
770
|
+
if (content.length > 1e4) {
|
|
771
|
+
const start = content.slice(0, 1e3);
|
|
772
|
+
const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
|
|
773
|
+
const end = content.slice(-1e3);
|
|
774
|
+
const sample = content.length + "|" + start + middle + end;
|
|
775
|
+
let hash2 = 2166136261;
|
|
776
|
+
for (let i = 0; i < sample.length; i++) {
|
|
777
|
+
hash2 ^= sample.charCodeAt(i);
|
|
778
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
779
|
+
}
|
|
780
|
+
return (hash2 >>> 0).toString(36);
|
|
781
|
+
}
|
|
782
|
+
let hash = 2166136261;
|
|
783
|
+
for (let i = 0; i < content.length; i++) {
|
|
784
|
+
hash ^= content.charCodeAt(i);
|
|
785
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
786
|
+
}
|
|
787
|
+
return (hash >>> 0).toString(36);
|
|
788
|
+
}
|
|
789
|
+
function memoize(fn, options = {}) {
|
|
790
|
+
const cache = options.cache || new LRUCache(options.maxSize || 100);
|
|
791
|
+
const keyGenerator = options.keyGenerator || ((...args) => JSON.stringify(args));
|
|
792
|
+
const memoized = function(...args) {
|
|
793
|
+
const key = keyGenerator(...args);
|
|
794
|
+
const cached = cache.get(key);
|
|
795
|
+
if (cached !== void 0) {
|
|
796
|
+
return cached;
|
|
797
|
+
}
|
|
798
|
+
const result = fn.apply(this, args);
|
|
799
|
+
cache.set(key, result);
|
|
800
|
+
return result;
|
|
801
|
+
};
|
|
802
|
+
memoized.cache = cache;
|
|
803
|
+
memoized.clearCache = () => cache.clear();
|
|
804
|
+
return memoized;
|
|
805
|
+
}
|
|
806
|
+
|
|
618
807
|
// src/extensions/core/blockquote.ts
|
|
619
808
|
var BlockquoteExtension = {
|
|
620
809
|
name: "blockquote",
|
|
@@ -1587,8 +1776,13 @@ function extractVimeoId(url) {
|
|
|
1587
1776
|
var ChangerawrMarkdown = class {
|
|
1588
1777
|
constructor(config) {
|
|
1589
1778
|
this.extensions = /* @__PURE__ */ new Map();
|
|
1779
|
+
this.parseTime = 0;
|
|
1780
|
+
this.renderTime = 0;
|
|
1781
|
+
this.lastTokenCount = 0;
|
|
1590
1782
|
this.parser = new MarkdownParser(config?.parser);
|
|
1591
1783
|
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1784
|
+
this.parseCache = new LRUCache(100);
|
|
1785
|
+
this.renderCache = new LRUCache(100);
|
|
1592
1786
|
this.registerCoreExtensions();
|
|
1593
1787
|
this.registerFeatureExtensions();
|
|
1594
1788
|
if (config?.extensions) {
|
|
@@ -1671,14 +1865,83 @@ var ChangerawrMarkdown = class {
|
|
|
1671
1865
|
}
|
|
1672
1866
|
}
|
|
1673
1867
|
parse(markdown3) {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1868
|
+
const cacheKey = hashContent(markdown3);
|
|
1869
|
+
const cached = this.parseCache.get(cacheKey);
|
|
1870
|
+
if (cached) {
|
|
1871
|
+
this.lastTokenCount = cached.length;
|
|
1872
|
+
return cached;
|
|
1873
|
+
}
|
|
1874
|
+
const startTime = performance.now();
|
|
1875
|
+
const tokens = this.parser.parse(markdown3);
|
|
1876
|
+
this.parseTime = performance.now() - startTime;
|
|
1877
|
+
this.lastTokenCount = tokens.length;
|
|
1878
|
+
this.parseCache.set(cacheKey, tokens);
|
|
1879
|
+
return tokens;
|
|
1880
|
+
}
|
|
1881
|
+
render(tokens, cacheKey) {
|
|
1882
|
+
if (cacheKey) {
|
|
1883
|
+
const cached = this.renderCache.get(cacheKey);
|
|
1884
|
+
if (cached) {
|
|
1885
|
+
return cached;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const startTime = performance.now();
|
|
1889
|
+
const html = this.renderer.render(tokens);
|
|
1890
|
+
this.renderTime = performance.now() - startTime;
|
|
1891
|
+
if (cacheKey) {
|
|
1892
|
+
this.renderCache.set(cacheKey, html);
|
|
1893
|
+
}
|
|
1894
|
+
return html;
|
|
1678
1895
|
}
|
|
1679
1896
|
toHtml(markdown3) {
|
|
1897
|
+
const cacheKey = hashContent(markdown3);
|
|
1898
|
+
const cachedHtml = this.renderCache.get(cacheKey);
|
|
1899
|
+
if (cachedHtml) {
|
|
1900
|
+
return cachedHtml;
|
|
1901
|
+
}
|
|
1680
1902
|
const tokens = this.parse(markdown3);
|
|
1681
|
-
return this.render(tokens);
|
|
1903
|
+
return this.render(tokens, cacheKey);
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Render markdown with performance metrics
|
|
1907
|
+
*/
|
|
1908
|
+
toHtmlWithMetrics(markdown3) {
|
|
1909
|
+
const startTotal = performance.now();
|
|
1910
|
+
const parseCacheKey = hashContent(markdown3);
|
|
1911
|
+
const parseCacheHit = this.parseCache.has(parseCacheKey);
|
|
1912
|
+
const html = this.toHtml(markdown3);
|
|
1913
|
+
const totalTime = performance.now() - startTotal;
|
|
1914
|
+
const metrics = {
|
|
1915
|
+
inputSize: markdown3.length,
|
|
1916
|
+
parseTime: this.parseTime,
|
|
1917
|
+
renderTime: this.renderTime,
|
|
1918
|
+
totalTime,
|
|
1919
|
+
tokenCount: this.lastTokenCount,
|
|
1920
|
+
cacheHit: parseCacheHit
|
|
1921
|
+
};
|
|
1922
|
+
return { html, metrics };
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Stream-render large documents in chunks for better performance
|
|
1926
|
+
*/
|
|
1927
|
+
async toHtmlStreamed(markdown3, options = {}) {
|
|
1928
|
+
const chunkSize = options.chunkSize || 50;
|
|
1929
|
+
const tokens = this.parse(markdown3);
|
|
1930
|
+
const totalTokens = tokens.length;
|
|
1931
|
+
const chunks = [];
|
|
1932
|
+
for (let i = 0; i < tokens.length; i += chunkSize) {
|
|
1933
|
+
const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
|
|
1934
|
+
const chunkHtml = this.render(chunkTokens);
|
|
1935
|
+
chunks.push(chunkHtml);
|
|
1936
|
+
if (options.onChunk) {
|
|
1937
|
+
options.onChunk({
|
|
1938
|
+
html: chunkHtml,
|
|
1939
|
+
progress: Math.min(i + chunkSize, tokens.length) / totalTokens
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1943
|
+
}
|
|
1944
|
+
return chunks.join("");
|
|
1682
1945
|
}
|
|
1683
1946
|
getExtensions() {
|
|
1684
1947
|
return Array.from(this.extensions.keys());
|
|
@@ -1692,25 +1955,50 @@ var ChangerawrMarkdown = class {
|
|
|
1692
1955
|
getDebugInfo() {
|
|
1693
1956
|
return {
|
|
1694
1957
|
warnings: this.getWarnings(),
|
|
1695
|
-
parseTime:
|
|
1696
|
-
renderTime:
|
|
1697
|
-
tokenCount:
|
|
1958
|
+
parseTime: this.parseTime,
|
|
1959
|
+
renderTime: this.renderTime,
|
|
1960
|
+
tokenCount: this.lastTokenCount,
|
|
1698
1961
|
iterationCount: 0
|
|
1699
1962
|
};
|
|
1700
1963
|
}
|
|
1701
1964
|
getPerformanceMetrics() {
|
|
1702
1965
|
return {
|
|
1703
|
-
parseTime:
|
|
1704
|
-
renderTime:
|
|
1705
|
-
totalTime:
|
|
1706
|
-
tokenCount:
|
|
1966
|
+
parseTime: this.parseTime,
|
|
1967
|
+
renderTime: this.renderTime,
|
|
1968
|
+
totalTime: this.parseTime + this.renderTime,
|
|
1969
|
+
tokenCount: this.lastTokenCount
|
|
1707
1970
|
};
|
|
1708
1971
|
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Get cache statistics
|
|
1974
|
+
*/
|
|
1975
|
+
getCacheStats() {
|
|
1976
|
+
return {
|
|
1977
|
+
parse: this.parseCache.getStats(),
|
|
1978
|
+
render: this.renderCache.getStats()
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Clear all caches
|
|
1983
|
+
*/
|
|
1984
|
+
clearCaches() {
|
|
1985
|
+
this.parseCache.clear();
|
|
1986
|
+
this.renderCache.clear();
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Update cache capacity
|
|
1990
|
+
*/
|
|
1991
|
+
setCacheSize(size) {
|
|
1992
|
+
this.parseCache.setCapacity(size);
|
|
1993
|
+
this.renderCache.setCapacity(size);
|
|
1994
|
+
}
|
|
1709
1995
|
rebuildParserAndRenderer() {
|
|
1710
1996
|
const parserConfig = this.parser.getConfig();
|
|
1711
1997
|
const rendererConfig = this.renderer.getConfig();
|
|
1712
1998
|
this.parser = new MarkdownParser(parserConfig);
|
|
1713
1999
|
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
2000
|
+
this.parseCache.clear();
|
|
2001
|
+
this.renderCache.clear();
|
|
1714
2002
|
const extensionsToRegister = Array.from(this.extensions.values());
|
|
1715
2003
|
const featureExtensions = extensionsToRegister.filter(
|
|
1716
2004
|
(ext) => ["alert", "button", "embed"].includes(ext.name)
|
|
@@ -2355,6 +2643,7 @@ export {
|
|
|
2355
2643
|
ImageExtension,
|
|
2356
2644
|
InlineCodeExtension,
|
|
2357
2645
|
ItalicExtension,
|
|
2646
|
+
LRUCache,
|
|
2358
2647
|
LineBreakExtension,
|
|
2359
2648
|
LinkExtension,
|
|
2360
2649
|
ListExtension,
|
|
@@ -2387,10 +2676,12 @@ export {
|
|
|
2387
2676
|
generateId,
|
|
2388
2677
|
getASTStats,
|
|
2389
2678
|
getTokenStats,
|
|
2679
|
+
hashContent,
|
|
2390
2680
|
isBrowser,
|
|
2391
2681
|
isNode,
|
|
2392
2682
|
isValidUrl,
|
|
2393
2683
|
markdown2 as markdown,
|
|
2684
|
+
memoize,
|
|
2394
2685
|
minimalClasses,
|
|
2395
2686
|
parseASTFromJSON,
|
|
2396
2687
|
parseCum,
|