@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/index.js
CHANGED
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
ImageExtension: () => ImageExtension,
|
|
44
44
|
InlineCodeExtension: () => InlineCodeExtension,
|
|
45
45
|
ItalicExtension: () => ItalicExtension,
|
|
46
|
+
LRUCache: () => LRUCache,
|
|
46
47
|
LineBreakExtension: () => LineBreakExtension,
|
|
47
48
|
LinkExtension: () => LinkExtension,
|
|
48
49
|
ListExtension: () => ListExtension,
|
|
@@ -75,10 +76,12 @@ __export(index_exports, {
|
|
|
75
76
|
generateId: () => generateId,
|
|
76
77
|
getASTStats: () => getASTStats,
|
|
77
78
|
getTokenStats: () => getTokenStats,
|
|
79
|
+
hashContent: () => hashContent,
|
|
78
80
|
isBrowser: () => isBrowser,
|
|
79
81
|
isNode: () => isNode,
|
|
80
82
|
isValidUrl: () => isValidUrl,
|
|
81
83
|
markdown: () => markdown2,
|
|
84
|
+
memoize: () => memoize,
|
|
82
85
|
minimalClasses: () => minimalClasses,
|
|
83
86
|
parseASTFromJSON: () => parseASTFromJSON,
|
|
84
87
|
parseCum: () => parseCum,
|
|
@@ -108,9 +111,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
108
111
|
|
|
109
112
|
// src/parser.ts
|
|
110
113
|
var MarkdownParser = class {
|
|
114
|
+
// Cache compiled regexes
|
|
111
115
|
constructor(config) {
|
|
112
116
|
this.rules = [];
|
|
113
117
|
this.warnings = [];
|
|
118
|
+
this.compiledPatterns = /* @__PURE__ */ new Map();
|
|
114
119
|
this.config = {
|
|
115
120
|
debugMode: false,
|
|
116
121
|
maxIterations: 1e4,
|
|
@@ -120,6 +125,10 @@ var MarkdownParser = class {
|
|
|
120
125
|
}
|
|
121
126
|
addRule(rule) {
|
|
122
127
|
this.rules.push(rule);
|
|
128
|
+
this.compiledPatterns.set(
|
|
129
|
+
rule,
|
|
130
|
+
new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
|
|
131
|
+
);
|
|
123
132
|
this.rules.sort((a, b) => {
|
|
124
133
|
const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
|
|
125
134
|
const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
|
|
@@ -165,12 +174,20 @@ var MarkdownParser = class {
|
|
|
165
174
|
iterationCount++;
|
|
166
175
|
let matched = false;
|
|
167
176
|
let bestMatch = null;
|
|
177
|
+
let nextBestMatchIndex = null;
|
|
168
178
|
for (const rule of this.rules) {
|
|
169
179
|
try {
|
|
170
|
-
const pattern =
|
|
180
|
+
const pattern = this.compiledPatterns.get(rule);
|
|
171
181
|
const match = remaining.match(pattern);
|
|
172
182
|
if (match && match.index !== void 0) {
|
|
173
|
-
|
|
183
|
+
if (match.index === 0) {
|
|
184
|
+
bestMatch = { rule, match, priority: 1e3 };
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
|
|
188
|
+
nextBestMatchIndex = match.index;
|
|
189
|
+
}
|
|
190
|
+
const priority = 1e3 - match.index;
|
|
174
191
|
if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
|
|
175
192
|
bestMatch = { rule, match, priority };
|
|
176
193
|
}
|
|
@@ -217,15 +234,14 @@ var MarkdownParser = class {
|
|
|
217
234
|
}
|
|
218
235
|
}
|
|
219
236
|
if (!matched) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
237
|
+
const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
|
|
238
|
+
const textChunk = remaining.slice(0, chunkSize);
|
|
239
|
+
tokens.push({
|
|
240
|
+
type: "text",
|
|
241
|
+
content: textChunk,
|
|
242
|
+
raw: textChunk
|
|
243
|
+
});
|
|
244
|
+
remaining = remaining.slice(chunkSize);
|
|
229
245
|
}
|
|
230
246
|
}
|
|
231
247
|
if (iterationCount >= maxIterations) {
|
|
@@ -723,6 +739,171 @@ var MarkdownRenderer = class {
|
|
|
723
739
|
}
|
|
724
740
|
};
|
|
725
741
|
|
|
742
|
+
// src/cache.ts
|
|
743
|
+
var LRUCache = class {
|
|
744
|
+
constructor(capacity = 100) {
|
|
745
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
746
|
+
this.hits = 0;
|
|
747
|
+
this.misses = 0;
|
|
748
|
+
this.evictions = 0;
|
|
749
|
+
if (capacity <= 0) {
|
|
750
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
751
|
+
}
|
|
752
|
+
this.capacity = capacity;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Get a value from the cache
|
|
756
|
+
*/
|
|
757
|
+
get(key) {
|
|
758
|
+
const entry = this.cache.get(key);
|
|
759
|
+
if (entry) {
|
|
760
|
+
entry.timestamp = Date.now();
|
|
761
|
+
entry.accessCount++;
|
|
762
|
+
this.hits++;
|
|
763
|
+
this.cache.delete(key);
|
|
764
|
+
this.cache.set(key, entry);
|
|
765
|
+
return entry.value;
|
|
766
|
+
}
|
|
767
|
+
this.misses++;
|
|
768
|
+
return void 0;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Set a value in the cache
|
|
772
|
+
*/
|
|
773
|
+
set(key, value) {
|
|
774
|
+
if (this.cache.has(key)) {
|
|
775
|
+
this.cache.delete(key);
|
|
776
|
+
} else if (this.cache.size >= this.capacity) {
|
|
777
|
+
this.evictLRU();
|
|
778
|
+
}
|
|
779
|
+
this.cache.set(key, {
|
|
780
|
+
value,
|
|
781
|
+
timestamp: Date.now(),
|
|
782
|
+
accessCount: 0
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Check if a key exists in the cache
|
|
787
|
+
*/
|
|
788
|
+
has(key) {
|
|
789
|
+
return this.cache.has(key);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Delete a specific key from the cache
|
|
793
|
+
*/
|
|
794
|
+
delete(key) {
|
|
795
|
+
return this.cache.delete(key);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Clear all entries from the cache
|
|
799
|
+
*/
|
|
800
|
+
clear() {
|
|
801
|
+
this.cache.clear();
|
|
802
|
+
this.hits = 0;
|
|
803
|
+
this.misses = 0;
|
|
804
|
+
this.evictions = 0;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get the current size of the cache
|
|
808
|
+
*/
|
|
809
|
+
get size() {
|
|
810
|
+
return this.cache.size;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Get cache statistics
|
|
814
|
+
*/
|
|
815
|
+
getStats() {
|
|
816
|
+
const totalRequests = this.hits + this.misses;
|
|
817
|
+
return {
|
|
818
|
+
size: this.cache.size,
|
|
819
|
+
capacity: this.capacity,
|
|
820
|
+
hits: this.hits,
|
|
821
|
+
misses: this.misses,
|
|
822
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
823
|
+
evictions: this.evictions
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Reset cache statistics
|
|
828
|
+
*/
|
|
829
|
+
resetStats() {
|
|
830
|
+
this.hits = 0;
|
|
831
|
+
this.misses = 0;
|
|
832
|
+
this.evictions = 0;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Get all keys in the cache
|
|
836
|
+
*/
|
|
837
|
+
keys() {
|
|
838
|
+
return Array.from(this.cache.keys());
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Get all values in the cache
|
|
842
|
+
*/
|
|
843
|
+
values() {
|
|
844
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Update cache capacity and evict if necessary
|
|
848
|
+
*/
|
|
849
|
+
setCapacity(newCapacity) {
|
|
850
|
+
if (newCapacity <= 0) {
|
|
851
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
852
|
+
}
|
|
853
|
+
this.capacity = newCapacity;
|
|
854
|
+
while (this.cache.size > this.capacity) {
|
|
855
|
+
this.evictLRU();
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Evict the least recently used entry
|
|
860
|
+
*/
|
|
861
|
+
evictLRU() {
|
|
862
|
+
const firstKey = this.cache.keys().next().value;
|
|
863
|
+
if (firstKey !== void 0) {
|
|
864
|
+
this.cache.delete(firstKey);
|
|
865
|
+
this.evictions++;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
function hashContent(content) {
|
|
870
|
+
if (content.length > 1e4) {
|
|
871
|
+
const start = content.slice(0, 1e3);
|
|
872
|
+
const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
|
|
873
|
+
const end = content.slice(-1e3);
|
|
874
|
+
const sample = content.length + "|" + start + middle + end;
|
|
875
|
+
let hash2 = 2166136261;
|
|
876
|
+
for (let i = 0; i < sample.length; i++) {
|
|
877
|
+
hash2 ^= sample.charCodeAt(i);
|
|
878
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
879
|
+
}
|
|
880
|
+
return (hash2 >>> 0).toString(36);
|
|
881
|
+
}
|
|
882
|
+
let hash = 2166136261;
|
|
883
|
+
for (let i = 0; i < content.length; i++) {
|
|
884
|
+
hash ^= content.charCodeAt(i);
|
|
885
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
886
|
+
}
|
|
887
|
+
return (hash >>> 0).toString(36);
|
|
888
|
+
}
|
|
889
|
+
function memoize(fn, options = {}) {
|
|
890
|
+
const cache = options.cache || new LRUCache(options.maxSize || 100);
|
|
891
|
+
const keyGenerator = options.keyGenerator || ((...args) => JSON.stringify(args));
|
|
892
|
+
const memoized = function(...args) {
|
|
893
|
+
const key = keyGenerator(...args);
|
|
894
|
+
const cached = cache.get(key);
|
|
895
|
+
if (cached !== void 0) {
|
|
896
|
+
return cached;
|
|
897
|
+
}
|
|
898
|
+
const result = fn.apply(this, args);
|
|
899
|
+
cache.set(key, result);
|
|
900
|
+
return result;
|
|
901
|
+
};
|
|
902
|
+
memoized.cache = cache;
|
|
903
|
+
memoized.clearCache = () => cache.clear();
|
|
904
|
+
return memoized;
|
|
905
|
+
}
|
|
906
|
+
|
|
726
907
|
// src/extensions/core/blockquote.ts
|
|
727
908
|
var BlockquoteExtension = {
|
|
728
909
|
name: "blockquote",
|
|
@@ -1695,8 +1876,13 @@ function extractVimeoId(url) {
|
|
|
1695
1876
|
var ChangerawrMarkdown = class {
|
|
1696
1877
|
constructor(config) {
|
|
1697
1878
|
this.extensions = /* @__PURE__ */ new Map();
|
|
1879
|
+
this.parseTime = 0;
|
|
1880
|
+
this.renderTime = 0;
|
|
1881
|
+
this.lastTokenCount = 0;
|
|
1698
1882
|
this.parser = new MarkdownParser(config?.parser);
|
|
1699
1883
|
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1884
|
+
this.parseCache = new LRUCache(100);
|
|
1885
|
+
this.renderCache = new LRUCache(100);
|
|
1700
1886
|
this.registerCoreExtensions();
|
|
1701
1887
|
this.registerFeatureExtensions();
|
|
1702
1888
|
if (config?.extensions) {
|
|
@@ -1779,14 +1965,83 @@ var ChangerawrMarkdown = class {
|
|
|
1779
1965
|
}
|
|
1780
1966
|
}
|
|
1781
1967
|
parse(markdown3) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1968
|
+
const cacheKey = hashContent(markdown3);
|
|
1969
|
+
const cached = this.parseCache.get(cacheKey);
|
|
1970
|
+
if (cached) {
|
|
1971
|
+
this.lastTokenCount = cached.length;
|
|
1972
|
+
return cached;
|
|
1973
|
+
}
|
|
1974
|
+
const startTime = performance.now();
|
|
1975
|
+
const tokens = this.parser.parse(markdown3);
|
|
1976
|
+
this.parseTime = performance.now() - startTime;
|
|
1977
|
+
this.lastTokenCount = tokens.length;
|
|
1978
|
+
this.parseCache.set(cacheKey, tokens);
|
|
1979
|
+
return tokens;
|
|
1980
|
+
}
|
|
1981
|
+
render(tokens, cacheKey) {
|
|
1982
|
+
if (cacheKey) {
|
|
1983
|
+
const cached = this.renderCache.get(cacheKey);
|
|
1984
|
+
if (cached) {
|
|
1985
|
+
return cached;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
const startTime = performance.now();
|
|
1989
|
+
const html = this.renderer.render(tokens);
|
|
1990
|
+
this.renderTime = performance.now() - startTime;
|
|
1991
|
+
if (cacheKey) {
|
|
1992
|
+
this.renderCache.set(cacheKey, html);
|
|
1993
|
+
}
|
|
1994
|
+
return html;
|
|
1786
1995
|
}
|
|
1787
1996
|
toHtml(markdown3) {
|
|
1997
|
+
const cacheKey = hashContent(markdown3);
|
|
1998
|
+
const cachedHtml = this.renderCache.get(cacheKey);
|
|
1999
|
+
if (cachedHtml) {
|
|
2000
|
+
return cachedHtml;
|
|
2001
|
+
}
|
|
2002
|
+
const tokens = this.parse(markdown3);
|
|
2003
|
+
return this.render(tokens, cacheKey);
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Render markdown with performance metrics
|
|
2007
|
+
*/
|
|
2008
|
+
toHtmlWithMetrics(markdown3) {
|
|
2009
|
+
const startTotal = performance.now();
|
|
2010
|
+
const parseCacheKey = hashContent(markdown3);
|
|
2011
|
+
const parseCacheHit = this.parseCache.has(parseCacheKey);
|
|
2012
|
+
const html = this.toHtml(markdown3);
|
|
2013
|
+
const totalTime = performance.now() - startTotal;
|
|
2014
|
+
const metrics = {
|
|
2015
|
+
inputSize: markdown3.length,
|
|
2016
|
+
parseTime: this.parseTime,
|
|
2017
|
+
renderTime: this.renderTime,
|
|
2018
|
+
totalTime,
|
|
2019
|
+
tokenCount: this.lastTokenCount,
|
|
2020
|
+
cacheHit: parseCacheHit
|
|
2021
|
+
};
|
|
2022
|
+
return { html, metrics };
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Stream-render large documents in chunks for better performance
|
|
2026
|
+
*/
|
|
2027
|
+
async toHtmlStreamed(markdown3, options = {}) {
|
|
2028
|
+
const chunkSize = options.chunkSize || 50;
|
|
1788
2029
|
const tokens = this.parse(markdown3);
|
|
1789
|
-
|
|
2030
|
+
const totalTokens = tokens.length;
|
|
2031
|
+
const chunks = [];
|
|
2032
|
+
for (let i = 0; i < tokens.length; i += chunkSize) {
|
|
2033
|
+
const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
|
|
2034
|
+
const chunkHtml = this.render(chunkTokens);
|
|
2035
|
+
chunks.push(chunkHtml);
|
|
2036
|
+
if (options.onChunk) {
|
|
2037
|
+
options.onChunk({
|
|
2038
|
+
html: chunkHtml,
|
|
2039
|
+
progress: Math.min(i + chunkSize, tokens.length) / totalTokens
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2043
|
+
}
|
|
2044
|
+
return chunks.join("");
|
|
1790
2045
|
}
|
|
1791
2046
|
getExtensions() {
|
|
1792
2047
|
return Array.from(this.extensions.keys());
|
|
@@ -1800,25 +2055,50 @@ var ChangerawrMarkdown = class {
|
|
|
1800
2055
|
getDebugInfo() {
|
|
1801
2056
|
return {
|
|
1802
2057
|
warnings: this.getWarnings(),
|
|
1803
|
-
parseTime:
|
|
1804
|
-
renderTime:
|
|
1805
|
-
tokenCount:
|
|
2058
|
+
parseTime: this.parseTime,
|
|
2059
|
+
renderTime: this.renderTime,
|
|
2060
|
+
tokenCount: this.lastTokenCount,
|
|
1806
2061
|
iterationCount: 0
|
|
1807
2062
|
};
|
|
1808
2063
|
}
|
|
1809
2064
|
getPerformanceMetrics() {
|
|
1810
2065
|
return {
|
|
1811
|
-
parseTime:
|
|
1812
|
-
renderTime:
|
|
1813
|
-
totalTime:
|
|
1814
|
-
tokenCount:
|
|
2066
|
+
parseTime: this.parseTime,
|
|
2067
|
+
renderTime: this.renderTime,
|
|
2068
|
+
totalTime: this.parseTime + this.renderTime,
|
|
2069
|
+
tokenCount: this.lastTokenCount
|
|
1815
2070
|
};
|
|
1816
2071
|
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Get cache statistics
|
|
2074
|
+
*/
|
|
2075
|
+
getCacheStats() {
|
|
2076
|
+
return {
|
|
2077
|
+
parse: this.parseCache.getStats(),
|
|
2078
|
+
render: this.renderCache.getStats()
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Clear all caches
|
|
2083
|
+
*/
|
|
2084
|
+
clearCaches() {
|
|
2085
|
+
this.parseCache.clear();
|
|
2086
|
+
this.renderCache.clear();
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Update cache capacity
|
|
2090
|
+
*/
|
|
2091
|
+
setCacheSize(size) {
|
|
2092
|
+
this.parseCache.setCapacity(size);
|
|
2093
|
+
this.renderCache.setCapacity(size);
|
|
2094
|
+
}
|
|
1817
2095
|
rebuildParserAndRenderer() {
|
|
1818
2096
|
const parserConfig = this.parser.getConfig();
|
|
1819
2097
|
const rendererConfig = this.renderer.getConfig();
|
|
1820
2098
|
this.parser = new MarkdownParser(parserConfig);
|
|
1821
2099
|
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
2100
|
+
this.parseCache.clear();
|
|
2101
|
+
this.renderCache.clear();
|
|
1822
2102
|
const extensionsToRegister = Array.from(this.extensions.values());
|
|
1823
2103
|
const featureExtensions = extensionsToRegister.filter(
|
|
1824
2104
|
(ext) => ["alert", "button", "embed"].includes(ext.name)
|
|
@@ -2464,6 +2744,7 @@ function createEngineWithPreset(presetName, additionalConfig) {
|
|
|
2464
2744
|
ImageExtension,
|
|
2465
2745
|
InlineCodeExtension,
|
|
2466
2746
|
ItalicExtension,
|
|
2747
|
+
LRUCache,
|
|
2467
2748
|
LineBreakExtension,
|
|
2468
2749
|
LinkExtension,
|
|
2469
2750
|
ListExtension,
|
|
@@ -2495,10 +2776,12 @@ function createEngineWithPreset(presetName, additionalConfig) {
|
|
|
2495
2776
|
generateId,
|
|
2496
2777
|
getASTStats,
|
|
2497
2778
|
getTokenStats,
|
|
2779
|
+
hashContent,
|
|
2498
2780
|
isBrowser,
|
|
2499
2781
|
isNode,
|
|
2500
2782
|
isValidUrl,
|
|
2501
2783
|
markdown,
|
|
2784
|
+
memoize,
|
|
2502
2785
|
minimalClasses,
|
|
2503
2786
|
parseASTFromJSON,
|
|
2504
2787
|
parseCum,
|