@ecency/render-helper 2.5.24 → 2.5.26
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 +3 -3
- package/dist/browser/index.d.ts +26 -0
- package/dist/browser/index.js +34 -12
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +34 -12
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +34 -12
- package/dist/node/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Built and maintained by [Ecency](https://ecency.com), an open-source social plat
|
|
|
19
19
|
- `pnpm publish:render-helper` - Manually publish to npm
|
|
20
20
|
|
|
21
21
|
### CI/CD
|
|
22
|
-
- Part of vision-
|
|
22
|
+
- Part of vision-web monorepo with centralized GitHub Actions (`.github/workflows/packages.yml`)
|
|
23
23
|
- Automatically detects changes to `packages/render-helper/**` using git diff
|
|
24
24
|
- On push to `develop` branch, builds and publishes to npm
|
|
25
25
|
- Uses OIDC (OpenID Connect) for secure npm authentication
|
|
@@ -128,7 +128,7 @@ src/
|
|
|
128
128
|
|
|
129
129
|
## Build System
|
|
130
130
|
|
|
131
|
-
- Uses **tsup** for bundling (shared with other vision-
|
|
131
|
+
- Uses **tsup** for bundling (shared with other vision-web packages)
|
|
132
132
|
- TypeScript 5.6+ with strict mode enabled
|
|
133
133
|
- Outputs three bundles:
|
|
134
134
|
- `dist/browser/index.js` - Browser ESM with TypeScript declarations
|
|
@@ -173,6 +173,6 @@ const summary = postBodySummary(entry, 200, 'react-native')
|
|
|
173
173
|
- When adding embed support for new platforms, implement server-side rendering only
|
|
174
174
|
- All external content must go through sanitization
|
|
175
175
|
- Version bumps required for auto-publish via CI
|
|
176
|
-
- Part of vision-
|
|
176
|
+
- Part of vision-web monorepo - changes require updating across dependent packages
|
|
177
177
|
- The `@ecency/renderer` package uses this as a workspace dependency
|
|
178
178
|
- Exports are optimized for tree-shaking with proper ESM structure
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -12,6 +12,32 @@ interface Entry {
|
|
|
12
12
|
interface RenderOptions {
|
|
13
13
|
/** When true, video embeds (3Speak, YouTube, etc.) render as iframes directly without a play button overlay. */
|
|
14
14
|
embedVideosDirectly?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* When true, auto-linkified `@user` and `#tag` chips render as inert `<span>`
|
|
17
|
+
* instead of `<a>`. Only affects the non-app render path, which is the only
|
|
18
|
+
* one that emits these chips. For consumers that have no profile or tag
|
|
19
|
+
* routes to send a reader to (a single-author self-hosted blog), an `<a>`
|
|
20
|
+
* pointing at `/@user` or `/trending/tag` is a dead link that is still
|
|
21
|
+
* focusable, announced as a link, and crawlable. Classes are unchanged so
|
|
22
|
+
* existing chip styling still applies.
|
|
23
|
+
*/
|
|
24
|
+
inertAuthorAndTagChips?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Absolute origin for links a consumer has no route to serve itself, e.g.
|
|
27
|
+
* `https://ecency.com`. Only affects the non-app render path.
|
|
28
|
+
*
|
|
29
|
+
* This is for PROFILE SECTION links, `/@user/wallet`, `/@user/followers` and
|
|
30
|
+
* the rest of SECTION_LIST. They are emitted as ordinary links, so a
|
|
31
|
+
* consumer whose only matching route is `/:author/:permlink` routes them as a
|
|
32
|
+
* post and tries to load one whose permlink is `wallet`. That is not a dead
|
|
33
|
+
* route, so no route guard catches it, and it is not a chip, so
|
|
34
|
+
* `inertAuthorAndTagChips` does not either.
|
|
35
|
+
*
|
|
36
|
+
* Post links themselves are deliberately NOT rewritten: `/@author/permlink`
|
|
37
|
+
* is real content the consumer can resolve from the chain, and keeping it
|
|
38
|
+
* internal is the point of a self-hosted blog.
|
|
39
|
+
*/
|
|
40
|
+
externalProfileBase?: string;
|
|
15
41
|
}
|
|
16
42
|
|
|
17
43
|
/**
|
package/dist/browser/index.js
CHANGED
|
@@ -1069,9 +1069,9 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
1069
1069
|
if (el.textContent === href) {
|
|
1070
1070
|
el.textContent = `@${author}/${section}`;
|
|
1071
1071
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
el.setAttribute("href",
|
|
1072
|
+
const external1 = renderOptions?.externalProfileBase;
|
|
1073
|
+
if (forApp || external1) {
|
|
1074
|
+
el.setAttribute("href", `${external1 ?? "https://ecency.com"}/@${author}/${section}`);
|
|
1075
1075
|
} else {
|
|
1076
1076
|
const h = `/@${author}/${section}`;
|
|
1077
1077
|
el.setAttribute("href", h);
|
|
@@ -1139,9 +1139,9 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
1139
1139
|
if (el.textContent === href) {
|
|
1140
1140
|
el.textContent = `@${author}/${section}`;
|
|
1141
1141
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
el.setAttribute("href",
|
|
1142
|
+
const external2 = renderOptions?.externalProfileBase;
|
|
1143
|
+
if (forApp || external2) {
|
|
1144
|
+
el.setAttribute("href", `${external2 ?? "https://ecency.com"}/@${author}/${section}`);
|
|
1145
1145
|
} else {
|
|
1146
1146
|
const h = `/@${author}/${section}`;
|
|
1147
1147
|
el.setAttribute("href", h);
|
|
@@ -1763,6 +1763,9 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1763
1763
|
const tag2 = tag.trim().substring(1);
|
|
1764
1764
|
const tagLower = tag2.toLowerCase();
|
|
1765
1765
|
if (!forApp) {
|
|
1766
|
+
if (renderOptions?.inertAuthorAndTagChips) {
|
|
1767
|
+
return `${preceding}<span class="er-tag er-tag-link">${tag.trim()}</span>`;
|
|
1768
|
+
}
|
|
1766
1769
|
return `${preceding}<a class="er-tag er-tag-link" href="/trending/${tagLower}">${tag.trim()}</a>`;
|
|
1767
1770
|
}
|
|
1768
1771
|
return `${preceding}<a class="markdown-tag-link" data-tag="${tagLower}">${tag.trim()}</a>`;
|
|
@@ -1776,7 +1779,8 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1776
1779
|
if (userLower.indexOf("/") === -1 && isValidUsername(user)) {
|
|
1777
1780
|
if (!forApp) {
|
|
1778
1781
|
const avatarSrc = `${getProxyBase()}/u/${userLower}/avatar/small`;
|
|
1779
|
-
const
|
|
1782
|
+
const inner = `<img class="er-author-link-image" src="${avatarSrc}" alt="${userLower}"/>@${userLower}`;
|
|
1783
|
+
const html = renderOptions?.inertAuthorAndTagChips ? `${preceedings}<span class="er-author er-author-link">${inner}</span>` : `${preceedings}<a class="er-author er-author-link" href="/@${userLower}">${inner}</a>`;
|
|
1780
1784
|
const placeholder = `\u200C${authorPlaceholders.length}\u200C`;
|
|
1781
1785
|
authorPlaceholders.push({ placeholder, html });
|
|
1782
1786
|
return placeholder;
|
|
@@ -1795,7 +1799,8 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1795
1799
|
const permlink = sanitizePermlink(p3);
|
|
1796
1800
|
if (!isValidPermlink(permlink)) return match;
|
|
1797
1801
|
if (SECTION_LIST.includes(permlink)) {
|
|
1798
|
-
const
|
|
1802
|
+
const external = renderOptions?.externalProfileBase;
|
|
1803
|
+
const attrs = forApp || external ? `href="${external ?? "https://ecency.com"}/@${authorLower}/${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
1799
1804
|
return `${preceding}<a class="markdown-profile-link" ${attrs}>@${authorLower}/${permlink}</a>`;
|
|
1800
1805
|
} else {
|
|
1801
1806
|
const attrs = forApp ? `data-author="${authorLower}" data-tag="${tag}" data-permlink="${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
@@ -1810,7 +1815,8 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1810
1815
|
const permlink = sanitizePermlink(p3);
|
|
1811
1816
|
if (!isValidPermlink(permlink)) return match;
|
|
1812
1817
|
if (SECTION_LIST.includes(permlink)) {
|
|
1813
|
-
const
|
|
1818
|
+
const external = renderOptions?.externalProfileBase;
|
|
1819
|
+
const attrs = forApp || external ? `href="${external ?? "https://ecency.com"}/@${uu}/${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
1814
1820
|
return ` <a class="markdown-profile-link" ${attrs}>@${uu}/${permlink}</a>`;
|
|
1815
1821
|
} else {
|
|
1816
1822
|
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
@@ -1841,6 +1847,19 @@ function hasAncestor(node, tagNames) {
|
|
|
1841
1847
|
}
|
|
1842
1848
|
return false;
|
|
1843
1849
|
}
|
|
1850
|
+
var CHIP_CLASSES = ["er-author-link", "er-tag-link"];
|
|
1851
|
+
function hasChipAncestor(node) {
|
|
1852
|
+
let current = node.parentNode;
|
|
1853
|
+
while (current) {
|
|
1854
|
+
const el = current;
|
|
1855
|
+
const className = typeof el.getAttribute === "function" ? el.getAttribute("class") : null;
|
|
1856
|
+
if (className && className.split(/\s+/).some((token) => CHIP_CLASSES.includes(token))) {
|
|
1857
|
+
return true;
|
|
1858
|
+
}
|
|
1859
|
+
current = current.parentNode;
|
|
1860
|
+
}
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1844
1863
|
function text(node, forApp, renderOptions) {
|
|
1845
1864
|
if (!node || !node.parentNode) {
|
|
1846
1865
|
return;
|
|
@@ -1848,8 +1867,11 @@ function text(node, forApp, renderOptions) {
|
|
|
1848
1867
|
if (hasAncestor(node, ["a", "code", "pre"])) {
|
|
1849
1868
|
return;
|
|
1850
1869
|
}
|
|
1870
|
+
if (renderOptions?.inertAuthorAndTagChips && hasChipAncestor(node)) {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1851
1873
|
const nodeValue = node.nodeValue || "";
|
|
1852
|
-
const linkified = linkify(nodeValue, forApp);
|
|
1874
|
+
const linkified = linkify(nodeValue, forApp, renderOptions);
|
|
1853
1875
|
if (linkified !== nodeValue) {
|
|
1854
1876
|
const doc = DOMParser.parseFromString(
|
|
1855
1877
|
`<span class="wr">${linkified}</span>`,
|
|
@@ -1937,7 +1959,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1937
1959
|
iframe(child, parentDomain, forApp, renderOptions);
|
|
1938
1960
|
}
|
|
1939
1961
|
if (child.nodeName === "#text") {
|
|
1940
|
-
text(child, forApp);
|
|
1962
|
+
text(child, forApp, renderOptions);
|
|
1941
1963
|
}
|
|
1942
1964
|
if (child.nodeName.toLowerCase() === "img") {
|
|
1943
1965
|
img(child, state, forApp);
|
|
@@ -2141,7 +2163,7 @@ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency
|
|
|
2141
2163
|
logIfSlow(performance.now() - t02, `body_len=${obj.length}`);
|
|
2142
2164
|
return res2;
|
|
2143
2165
|
}
|
|
2144
|
-
const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}${renderOptions?.embedVideosDirectly ? "-embed" : ""}`;
|
|
2166
|
+
const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}${renderOptions?.embedVideosDirectly ? "-embed" : ""}${renderOptions?.inertAuthorAndTagChips ? "-inert" : ""}${renderOptions?.externalProfileBase ? "-ext" + renderOptions.externalProfileBase : ""}`;
|
|
2145
2167
|
const item = cacheGet(key);
|
|
2146
2168
|
if (item) {
|
|
2147
2169
|
return item;
|