@ecency/render-helper 2.4.33 → 2.4.35

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.
@@ -4,11 +4,11 @@ var xmldom = require('@xmldom/xmldom');
4
4
  var xss = require('xss');
5
5
  var multihash = require('multihashes');
6
6
  var querystring = require('querystring');
7
+ var lruCache = require('lru-cache');
7
8
  var remarkable = require('remarkable');
8
9
  var linkify$1 = require('remarkable/linkify');
9
10
  var htmlparser2 = require('htmlparser2');
10
11
  var domSerializerModule = require('dom-serializer');
11
- var lruCache = require('lru-cache');
12
12
  var he = require('he');
13
13
 
14
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -206,23 +206,112 @@ function createParser() {
206
206
  var DOMParser = createParser();
207
207
 
208
208
  // src/helper.ts
209
+ function isSpaceChar(c) {
210
+ return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
211
+ }
212
+ function isAsciiLetter(c) {
213
+ return c >= 65 && c <= 90 || c >= 97 && c <= 122;
214
+ }
215
+ function isTagNameChar(c) {
216
+ return isAsciiLetter(c) || c >= 48 && c <= 57;
217
+ }
218
+ function isAttrNameChar(c) {
219
+ return isAsciiLetter(c) || c >= 48 && c <= 57 || c === 45 || c === 95 || c === 58 || c === 46;
220
+ }
209
221
  function removeDuplicateAttributes(html) {
210
- const tagRegex = /<([a-zA-Z][a-zA-Z0-9]*)\s+((?:[^>"']+|"[^"]*"|'[^']*')*?)\s*(\/?)>/g;
211
- return html.replace(tagRegex, (match, tagName, attrsString, selfClose) => {
212
- const seenAttrs = /* @__PURE__ */ new Set();
213
- const cleanedAttrs = [];
214
- const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"[^"]*"|'[^']*'|[^\s/>]+))?/g;
215
- let attrMatch;
216
- while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
217
- const attrName = attrMatch[1].toLowerCase();
218
- if (!seenAttrs.has(attrName)) {
219
- seenAttrs.add(attrName);
220
- cleanedAttrs.push(attrMatch[0]);
222
+ const n = html.length;
223
+ let out = "";
224
+ let i = 0;
225
+ while (i < n) {
226
+ const lt = html.indexOf("<", i);
227
+ if (lt < 0) {
228
+ out += html.slice(i);
229
+ break;
230
+ }
231
+ out += html.slice(i, lt);
232
+ if (lt + 1 >= n || !isAsciiLetter(html.charCodeAt(lt + 1))) {
233
+ out += "<";
234
+ i = lt + 1;
235
+ continue;
236
+ }
237
+ let p2 = lt + 1;
238
+ while (p2 < n && isTagNameChar(html.charCodeAt(p2))) p2++;
239
+ const tagName = html.slice(lt + 1, p2);
240
+ if (p2 >= n || !isSpaceChar(html.charCodeAt(p2))) {
241
+ out += "<";
242
+ i = lt + 1;
243
+ continue;
244
+ }
245
+ const attrs = [];
246
+ const seen = /* @__PURE__ */ new Set();
247
+ let q = p2;
248
+ while (q < n) {
249
+ while (q < n && isSpaceChar(html.charCodeAt(q))) q++;
250
+ if (q >= n) break;
251
+ const ch = html.charCodeAt(q);
252
+ if (ch === 62) break;
253
+ if (ch === 47 && q + 1 < n && html.charCodeAt(q + 1) === 62) break;
254
+ const nameStart = q;
255
+ while (q < n && isAttrNameChar(html.charCodeAt(q))) q++;
256
+ if (q === nameStart) {
257
+ q++;
258
+ continue;
259
+ }
260
+ const attrName = html.slice(nameStart, q);
261
+ let r = q;
262
+ while (r < n && isSpaceChar(html.charCodeAt(r))) r++;
263
+ let valueEnd = q;
264
+ if (r < n && html.charCodeAt(r) === 61) {
265
+ r++;
266
+ while (r < n && isSpaceChar(html.charCodeAt(r))) r++;
267
+ if (r < n) {
268
+ const v = html.charCodeAt(r);
269
+ if (v === 34 || v === 39) {
270
+ const quote = html[r];
271
+ const end = html.indexOf(quote, r + 1);
272
+ if (end < 0) {
273
+ const gt = html.indexOf(">", r + 1);
274
+ valueEnd = gt < 0 ? n : gt;
275
+ } else {
276
+ valueEnd = end + 1;
277
+ }
278
+ } else {
279
+ let s = r;
280
+ while (s < n) {
281
+ const k = html.charCodeAt(s);
282
+ if (isSpaceChar(k) || k === 62) break;
283
+ s++;
284
+ }
285
+ valueEnd = s;
286
+ }
287
+ } else {
288
+ valueEnd = r;
289
+ }
290
+ }
291
+ const fullAttr = html.slice(nameStart, valueEnd);
292
+ q = valueEnd;
293
+ const key = attrName.toLowerCase();
294
+ if (!seen.has(key)) {
295
+ seen.add(key);
296
+ attrs.push(fullAttr);
221
297
  }
222
298
  }
223
- const attrsJoined = cleanedAttrs.length > 0 ? ` ${cleanedAttrs.join(" ")}` : "";
224
- return `<${tagName}${attrsJoined}${selfClose ? " /" : ""}>`;
225
- });
299
+ let selfClose = false;
300
+ if (q < n && html.charCodeAt(q) === 47) {
301
+ selfClose = true;
302
+ q++;
303
+ }
304
+ if (q >= n || html.charCodeAt(q) !== 62) {
305
+ out += "<";
306
+ i = lt + 1;
307
+ continue;
308
+ }
309
+ q++;
310
+ const attrsJoined = attrs.length > 0 ? " " + attrs.join(" ") : "";
311
+ out += "<" + tagName + attrsJoined + (selfClose ? " /" : "") + ">";
312
+ i = q;
313
+ }
314
+ return out;
226
315
  }
227
316
  function createDoc(html) {
228
317
  if (html.trim() === "") {
@@ -325,6 +414,14 @@ function sanitizeHtml(html) {
325
414
  });
326
415
  }
327
416
  var proxyBase = "https://images.ecency.com";
417
+ var urlHashCache = new lruCache.LRUCache({ max: 500 });
418
+ function getUrlHash(url) {
419
+ const cached = urlHashCache.get(url);
420
+ if (cached) return cached;
421
+ const hash = multihash__default.default.toB58String(Buffer.from(url));
422
+ urlHashCache.set(url, hash);
423
+ return hash;
424
+ }
328
425
  function setProxyBase(p2) {
329
426
  proxyBase = p2;
330
427
  }
@@ -375,7 +472,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match") {
375
472
  if (pHash) {
376
473
  return `${proxyBase}/p/${pHash}?${qs}`;
377
474
  }
378
- const b58url = multihash__default.default.toB58String(Buffer.from(realUrl.toString()));
475
+ const b58url = getUrlHash(realUrl.toString());
379
476
  return `${proxyBase}/p/${b58url}?${qs}`;
380
477
  }
381
478
  var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
@@ -1607,7 +1704,6 @@ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext,
1607
1704
  output = serializer.serializeToString(doc);
1608
1705
  } catch (error) {
1609
1706
  try {
1610
- output = md.render(input);
1611
1707
  const preSanitized = sanitizeHtml(output);
1612
1708
  const dom = htmlparser2__namespace.parseDocument(preSanitized, {
1613
1709
  // lenient options - don't throw on malformed HTML
@@ -1647,7 +1743,7 @@ function simpleMarkdownToHTML(input) {
1647
1743
  const html = getMd().render(input);
1648
1744
  return sanitizeHtml(html);
1649
1745
  }
1650
- var cache = new lruCache.LRUCache({ max: 60 });
1746
+ var cache = new lruCache.LRUCache({ max: 500 });
1651
1747
  function setCacheSize(size) {
1652
1748
  cache = new lruCache.LRUCache({ max: size });
1653
1749
  }
@@ -1678,6 +1774,40 @@ var gifLinkRegex = /\.(gif)$/i;
1678
1774
  function isGifLink(link) {
1679
1775
  return gifLinkRegex.test(link);
1680
1776
  }
1777
+ var BACKTICK_FENCE_RE = /```[\s\S]*?```/g;
1778
+ var TILDE_FENCE_RE = /~~~[\s\S]*?~~~/g;
1779
+ var INLINE_CODE_RE = /`[^`\n]*`/g;
1780
+ var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
1781
+ var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
1782
+ var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
1783
+ var SAFE_URL_RE = /^https?:\/\//i;
1784
+ function findFirstImageUrl(body) {
1785
+ if (!body) return null;
1786
+ const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
1787
+ const mdMatch = cleaned.match(MD_IMAGE_RE);
1788
+ const htmlMatch = cleaned.match(HTML_IMAGE_RE);
1789
+ if (mdMatch) {
1790
+ const url = mdMatch[1];
1791
+ if (!url || !SAFE_URL_RE.test(url) || url.includes("(")) {
1792
+ return null;
1793
+ }
1794
+ }
1795
+ const mdValid = !!mdMatch;
1796
+ const htmlValid = !!(htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1]));
1797
+ if (mdValid && htmlValid) {
1798
+ return (mdMatch.index ?? 0) < (htmlMatch.index ?? 0) ? mdMatch[1] : htmlMatch[1];
1799
+ }
1800
+ if (mdValid) return mdMatch[1];
1801
+ if (htmlValid) return htmlMatch[1];
1802
+ return null;
1803
+ }
1804
+ function proxifyFound(src, width, height, format) {
1805
+ const decoded = he__default.default.decode(src);
1806
+ if (isGifLink(decoded)) {
1807
+ return proxifyImageSrc(decoded, 0, 0, format);
1808
+ }
1809
+ return proxifyImageSrc(decoded, width, height, format);
1810
+ }
1681
1811
  function getImage(entry, width = 0, height = 0, format = "match") {
1682
1812
  let meta;
1683
1813
  if (typeof entry.json_metadata === "object") {
@@ -1709,6 +1839,10 @@ function getImage(entry, width = 0, height = 0, format = "match") {
1709
1839
  }
1710
1840
  return proxifyImageSrc(meta.image[0], width, height, format);
1711
1841
  }
1842
+ const fast = findFirstImageUrl(entry.body);
1843
+ if (fast) {
1844
+ return proxifyFound(fast, width, height, format);
1845
+ }
1712
1846
  const html = markdown2Html(entry);
1713
1847
  const doc = createDoc(html);
1714
1848
  if (!doc) {
@@ -1720,16 +1854,16 @@ function getImage(entry, width = 0, height = 0, format = "match") {
1720
1854
  if (!src) {
1721
1855
  return null;
1722
1856
  }
1723
- const decodedSrc = he__default.default.decode(src);
1724
- if (isGifLink(decodedSrc)) {
1725
- return proxifyImageSrc(decodedSrc, 0, 0, format);
1726
- }
1727
- return proxifyImageSrc(decodedSrc, width, height, format);
1857
+ return proxifyFound(src, width, height, format);
1728
1858
  }
1729
1859
  return null;
1730
1860
  }
1731
1861
  function catchPostImage(obj, width = 0, height = 0, format = "match") {
1732
1862
  if (typeof obj === "string") {
1863
+ const fast = findFirstImageUrl(obj);
1864
+ if (fast) {
1865
+ return proxifyFound(fast, width, height, format);
1866
+ }
1733
1867
  const html = markdown2Html(obj);
1734
1868
  const doc = createDoc(html);
1735
1869
  if (!doc) {
@@ -1741,11 +1875,7 @@ function catchPostImage(obj, width = 0, height = 0, format = "match") {
1741
1875
  if (!src) {
1742
1876
  return null;
1743
1877
  }
1744
- const decodedSrc = he__default.default.decode(src);
1745
- if (isGifLink(decodedSrc)) {
1746
- return proxifyImageSrc(decodedSrc, 0, 0, format);
1747
- }
1748
- return proxifyImageSrc(decodedSrc, width, height, format);
1878
+ return proxifyFound(src, width, height, format);
1749
1879
  }
1750
1880
  return null;
1751
1881
  }
@@ -1758,6 +1888,20 @@ function catchPostImage(obj, width = 0, height = 0, format = "match") {
1758
1888
  cacheSet(key, res);
1759
1889
  return res;
1760
1890
  }
1891
+ var summaryRenderer = new remarkable.Remarkable({
1892
+ html: true,
1893
+ breaks: true,
1894
+ typographer: false
1895
+ });
1896
+ summaryRenderer.core.ruler.enable(["abbr"]);
1897
+ summaryRenderer.block.ruler.enable(["footnote", "deflist"]);
1898
+ summaryRenderer.inline.ruler.enable([
1899
+ "footnote_inline",
1900
+ "ins",
1901
+ "mark",
1902
+ "sub",
1903
+ "sup"
1904
+ ]);
1761
1905
  var joint = (arr, limit = 200) => {
1762
1906
  let result = "";
1763
1907
  if (arr) {
@@ -1783,25 +1927,6 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
1783
1927
  return "";
1784
1928
  }
1785
1929
  entryBody = cleanReply(entryBody);
1786
- const mdd = new remarkable.Remarkable({
1787
- html: true,
1788
- breaks: true,
1789
- typographer: false
1790
- }).use(linkify$1.linkify);
1791
- mdd.core.ruler.enable([
1792
- "abbr"
1793
- ]);
1794
- mdd.block.ruler.enable([
1795
- "footnote",
1796
- "deflist"
1797
- ]);
1798
- mdd.inline.ruler.enable([
1799
- "footnote_inline",
1800
- "ins",
1801
- "mark",
1802
- "sub",
1803
- "sup"
1804
- ]);
1805
1930
  const entities = entryBody.match(ENTITY_REGEX);
1806
1931
  const entityPlaceholders = [];
1807
1932
  if (entities && platform !== "web") {
@@ -1814,7 +1939,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
1814
1939
  }
1815
1940
  let text2 = "";
1816
1941
  try {
1817
- text2 = mdd.render(entryBody);
1942
+ text2 = summaryRenderer.render(entryBody);
1818
1943
  } catch (err) {
1819
1944
  console.error("[postBodySummary] Failed to render markdown:", {
1820
1945
  error: err instanceof Error ? err.message : String(err),