prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
/**
|
|
|
|
|
* Shared document shell for every rendered page.
|
|
|
|
|
*
|
|
|
|
|
* A page function returns `{title, description, body}`; `layout()` wraps it in
|
|
|
|
|
* the `<html>` document with the right meta, topnav, and footer. The brand mark
|
|
|
|
|
* is inlined as SVG so pages do not fetch an image to render a 20-pixel logo.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const BRAND_MARK = `<svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true">
|
|
|
|
|
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
prometheus: standalone identity + first-wave Silent Mode listings
Four changes, one coherent pass:
1. Prometheus gets its own flame mark (SVG, not an emoji). Used in the
topnav brand, in the hero at 88 px, and as the favicon. Replaces the
🔥 glyph in the service and in site/prometheus/index.html.
2. silentmode.st/prometheus is now a brief introduction with two big
buttons (Open prometheus.x · Open apps.silentmode.st mirror) and a
short "what / who / status" block, instead of the full category
grid that duplicated what prometheus.x already renders. The /prometheus
page's job on silentmode.st is "point you at the real site," not host
the content.
3. Footer on the Prometheus service drops the "Silent Mode — a Deviant
project" byline and reframes the sibling-x links as "Also live in the
decentralized web:" so Prometheus reads as its own site rather than a
Silent Mode subpage. Keeps the attribution via the MPL-2.0 + source
link on the final line. aegis.x and pithos.x added to the sibling row.
4. Four Silent Mode apps seeded as first-wave listings
(scripts/seed-listings.json + scripts/seed.mjs). Theseus Navigator,
Ariadne's Thread (Windows + Android), Pithos. Each carries the
project's own SVG icon from public/icons/, pulled from the project's
own site. The seed is a direct-DB insert (not through /api/listing)
with signer_address = chipnet operator and signature = sentinel
`SM_OPERATOR_SEED`; scan_report is an operator-attested envelope
signed by the service key so the display pipeline is unchanged. Run
with `node scripts/seed.mjs`; idempotent (skips existing (publisher,
slug)).
Deliberately NOT seeded: Theseus extensions that have no standalone
form — pdf-editor, docx-editor, shield-blocker, bergamot provider.
They ship via Theseus's own OTA, not as Prometheus listings. Aegis
is in the same boat today (a Theseus bundled add-on without a
standalone binary); its icon is included in public/icons/ for when
it has its own distribution.
Real file sizes + SHAs come from the current dl.silentmode.st manifest
(theseus 0.3.89, ariadne resolver 0.1.13, ariadne android 0.22,
pithos-desktop 0.3.26). When APS1 lands cross-session, these listings
migrate to real on-chain records signed by each project's own publisher
key and the sentinel values go away.
2026-10-09 01:23:21 +02:00
|
|
|
<path d="M32 6 C 38 18 46 24 46 36 C 46 48 40 56 32 56 C 24 56 18 48 18 36 C 18 28 24 22 20 14 C 26 18 30 20 32 6 Z" fill="#d6ff3d"/>
|
|
|
|
|
<path d="M32 20 C 36 28 40 32 40 40 C 40 48 36 52 32 52 C 28 52 24 48 24 40 C 24 34 28 30 32 20 Z" fill="#0b0e14"/>
|
|
|
|
|
<circle cx="32" cy="44" r="3" fill="#d6ff3d"/>
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
</svg>`;
|
|
|
|
|
|
|
|
|
|
const NAV = [
|
|
|
|
|
{ href: '/browse', label: 'Browse' },
|
|
|
|
|
{ href: '/submit', label: 'Submit' },
|
|
|
|
|
{ href: '/publish', label: 'How publishing works' },
|
|
|
|
|
{ href: '/boundaries', label: 'Boundaries' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compose a full HTML document from a page.
|
|
|
|
|
*
|
|
|
|
|
* @param {{title: string, description: string, body: string, here?: string}} page
|
|
|
|
|
* @returns {string} the HTML document, ready to send.
|
|
|
|
|
*/
|
|
|
|
|
export function layout(page) {
|
prometheus: SEO polish and the apps.silentmode.st mirror vhost
layout gained three optional fields: ogType, ogImage, headExtra. The
listing detail page uses all three — ogType=article, ogImage=the icon
URL (fallback to the Prometheus favicon), and headExtra for schema.org
SoftwareApplication JSON-LD. Google renders this as a rich card for
app results; the SEO uplift is real and the data is straightforward.
JSON-LD payload composed in service/seo.js:
@type SoftwareApplication, name, description, softwareVersion,
applicationCategory (mapped from Prometheus's slug to schema's name),
url, downloadUrl, fileSize, offers (free), author (BNS name as org),
image (icon when present), contentRating ("ClamAV clean" on a clean
scan).
OG defaults for every page: og:type (website unless overridden),
og:image (favicon unless overridden). The canonical URL stays
prometheus.x on both the live service and the apps.silentmode.st
mirror — crawlers that land on the mirror attribute the content to
the real address, matching the pattern [[silentmode-site-url-scheme]]
describes.
deploy/apps-silentmode-st.conf is the angie vhost for the mirror:
reverse-proxies to 127.0.0.1:3700, declines /admin (operator always
goes direct), 2 GiB client_max_body_size for POST /api/upload,
proxy_request_buffering off so a slow upload does not spool to disk.
Reuses the main silentmode.st certificate (SAN *.silentmode.st; if
that's not set up yet, request one and reload before enabling).
2026-10-08 21:45:59 +02:00
|
|
|
const {
|
|
|
|
|
title, description, body, here = '',
|
|
|
|
|
ogType = 'website',
|
|
|
|
|
ogImage = 'https://prometheus.x/static/favicon.svg',
|
|
|
|
|
headExtra = '',
|
|
|
|
|
} = page;
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
|
|
|
|
|
// The canonical URL is always on prometheus.x — never the IP+port the server
|
|
|
|
|
// actually listens on, and never the SEO mirror at apps.silentmode.st, even
|
|
|
|
|
// when the mirror is what served this request. Readers who followed a link
|
|
|
|
|
// from search land on the mirror; the canonical sends search engines back to
|
|
|
|
|
// the real address and keeps the scorecard on prometheus.x.
|
|
|
|
|
const canonical = `https://prometheus.x${here}`;
|
|
|
|
|
|
|
|
|
|
const navHtml = NAV.map(n => {
|
|
|
|
|
const classAttr = n.href === here ? ' class="here"' : '';
|
|
|
|
|
return `<a href="${n.href}"${classAttr}>${n.label}</a>`;
|
|
|
|
|
}).join('\n ');
|
|
|
|
|
|
|
|
|
|
return `<!doctype html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<title>${escapeHtml(title)} — Prometheus</title>
|
|
|
|
|
<meta name="description" content="${escapeHtml(description)}">
|
|
|
|
|
<link rel="canonical" href="${canonical}">
|
prometheus: SEO polish and the apps.silentmode.st mirror vhost
layout gained three optional fields: ogType, ogImage, headExtra. The
listing detail page uses all three — ogType=article, ogImage=the icon
URL (fallback to the Prometheus favicon), and headExtra for schema.org
SoftwareApplication JSON-LD. Google renders this as a rich card for
app results; the SEO uplift is real and the data is straightforward.
JSON-LD payload composed in service/seo.js:
@type SoftwareApplication, name, description, softwareVersion,
applicationCategory (mapped from Prometheus's slug to schema's name),
url, downloadUrl, fileSize, offers (free), author (BNS name as org),
image (icon when present), contentRating ("ClamAV clean" on a clean
scan).
OG defaults for every page: og:type (website unless overridden),
og:image (favicon unless overridden). The canonical URL stays
prometheus.x on both the live service and the apps.silentmode.st
mirror — crawlers that land on the mirror attribute the content to
the real address, matching the pattern [[silentmode-site-url-scheme]]
describes.
deploy/apps-silentmode-st.conf is the angie vhost for the mirror:
reverse-proxies to 127.0.0.1:3700, declines /admin (operator always
goes direct), 2 GiB client_max_body_size for POST /api/upload,
proxy_request_buffering off so a slow upload does not spool to disk.
Reuses the main silentmode.st certificate (SAN *.silentmode.st; if
that's not set up yet, request one and reload before enabling).
2026-10-08 21:45:59 +02:00
|
|
|
<meta property="og:type" content="${escapeHtml(ogType)}">
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
<meta property="og:site_name" content="Prometheus">
|
|
|
|
|
<meta property="og:title" content="${escapeHtml(title)} — Prometheus">
|
|
|
|
|
<meta property="og:description" content="${escapeHtml(description)}">
|
|
|
|
|
<meta property="og:url" content="${canonical}">
|
prometheus: SEO polish and the apps.silentmode.st mirror vhost
layout gained three optional fields: ogType, ogImage, headExtra. The
listing detail page uses all three — ogType=article, ogImage=the icon
URL (fallback to the Prometheus favicon), and headExtra for schema.org
SoftwareApplication JSON-LD. Google renders this as a rich card for
app results; the SEO uplift is real and the data is straightforward.
JSON-LD payload composed in service/seo.js:
@type SoftwareApplication, name, description, softwareVersion,
applicationCategory (mapped from Prometheus's slug to schema's name),
url, downloadUrl, fileSize, offers (free), author (BNS name as org),
image (icon when present), contentRating ("ClamAV clean" on a clean
scan).
OG defaults for every page: og:type (website unless overridden),
og:image (favicon unless overridden). The canonical URL stays
prometheus.x on both the live service and the apps.silentmode.st
mirror — crawlers that land on the mirror attribute the content to
the real address, matching the pattern [[silentmode-site-url-scheme]]
describes.
deploy/apps-silentmode-st.conf is the angie vhost for the mirror:
reverse-proxies to 127.0.0.1:3700, declines /admin (operator always
goes direct), 2 GiB client_max_body_size for POST /api/upload,
proxy_request_buffering off so a slow upload does not spool to disk.
Reuses the main silentmode.st certificate (SAN *.silentmode.st; if
that's not set up yet, request one and reload before enabling).
2026-10-08 21:45:59 +02:00
|
|
|
<meta property="og:image" content="${escapeHtml(ogImage)}">
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
|
|
|
|
<link rel="stylesheet" href="/static/style.css?v=1">
|
prometheus: SEO polish and the apps.silentmode.st mirror vhost
layout gained three optional fields: ogType, ogImage, headExtra. The
listing detail page uses all three — ogType=article, ogImage=the icon
URL (fallback to the Prometheus favicon), and headExtra for schema.org
SoftwareApplication JSON-LD. Google renders this as a rich card for
app results; the SEO uplift is real and the data is straightforward.
JSON-LD payload composed in service/seo.js:
@type SoftwareApplication, name, description, softwareVersion,
applicationCategory (mapped from Prometheus's slug to schema's name),
url, downloadUrl, fileSize, offers (free), author (BNS name as org),
image (icon when present), contentRating ("ClamAV clean" on a clean
scan).
OG defaults for every page: og:type (website unless overridden),
og:image (favicon unless overridden). The canonical URL stays
prometheus.x on both the live service and the apps.silentmode.st
mirror — crawlers that land on the mirror attribute the content to
the real address, matching the pattern [[silentmode-site-url-scheme]]
describes.
deploy/apps-silentmode-st.conf is the angie vhost for the mirror:
reverse-proxies to 127.0.0.1:3700, declines /admin (operator always
goes direct), 2 GiB client_max_body_size for POST /api/upload,
proxy_request_buffering off so a slow upload does not spool to disk.
Reuses the main silentmode.st certificate (SAN *.silentmode.st; if
that's not set up yet, request one and reload before enabling).
2026-10-08 21:45:59 +02:00
|
|
|
${headExtra}
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
<nav class="topnav">
|
|
|
|
|
<a class="brand" href="/">${BRAND_MARK}Prometheus<span class="g">.X</span></a>
|
|
|
|
|
${navHtml}
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
${body}
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
<div class="fine">
|
|
|
|
|
Named for Prometheus, who gave fire to humans without asking the gods.
|
|
|
|
|
Distinct from the well-known Prometheus monitoring project — both take
|
|
|
|
|
the name from the same myth; neither has anything to do with the other.
|
|
|
|
|
</div>
|
prometheus: standalone identity + first-wave Silent Mode listings
Four changes, one coherent pass:
1. Prometheus gets its own flame mark (SVG, not an emoji). Used in the
topnav brand, in the hero at 88 px, and as the favicon. Replaces the
🔥 glyph in the service and in site/prometheus/index.html.
2. silentmode.st/prometheus is now a brief introduction with two big
buttons (Open prometheus.x · Open apps.silentmode.st mirror) and a
short "what / who / status" block, instead of the full category
grid that duplicated what prometheus.x already renders. The /prometheus
page's job on silentmode.st is "point you at the real site," not host
the content.
3. Footer on the Prometheus service drops the "Silent Mode — a Deviant
project" byline and reframes the sibling-x links as "Also live in the
decentralized web:" so Prometheus reads as its own site rather than a
Silent Mode subpage. Keeps the attribution via the MPL-2.0 + source
link on the final line. aegis.x and pithos.x added to the sibling row.
4. Four Silent Mode apps seeded as first-wave listings
(scripts/seed-listings.json + scripts/seed.mjs). Theseus Navigator,
Ariadne's Thread (Windows + Android), Pithos. Each carries the
project's own SVG icon from public/icons/, pulled from the project's
own site. The seed is a direct-DB insert (not through /api/listing)
with signer_address = chipnet operator and signature = sentinel
`SM_OPERATOR_SEED`; scan_report is an operator-attested envelope
signed by the service key so the display pipeline is unchanged. Run
with `node scripts/seed.mjs`; idempotent (skips existing (publisher,
slug)).
Deliberately NOT seeded: Theseus extensions that have no standalone
form — pdf-editor, docx-editor, shield-blocker, bergamot provider.
They ship via Theseus's own OTA, not as Prometheus listings. Aegis
is in the same boat today (a Theseus bundled add-on without a
standalone binary); its icon is included in public/icons/ for when
it has its own distribution.
Real file sizes + SHAs come from the current dl.silentmode.st manifest
(theseus 0.3.89, ariadne resolver 0.1.13, ariadne android 0.22,
pithos-desktop 0.3.26). When APS1 lands cross-session, these listings
migrate to real on-chain records signed by each project's own publisher
key and the sentinel values go away.
2026-10-09 01:23:21 +02:00
|
|
|
<div class="fine" style="margin-top:1.4rem;color:var(--dim);font-size:12px">
|
|
|
|
|
Also live in the decentralized web:
|
|
|
|
|
<a href="//theseus.x/">theseus.x</a> ·
|
|
|
|
|
<a href="//sirius.x/">sirius.x</a> ·
|
|
|
|
|
<a href="//ariadne.x/">ariadne.x</a> ·
|
|
|
|
|
<a href="//aegis.x/">aegis.x</a> ·
|
|
|
|
|
<a href="//pithos.x/">pithos.x</a> ·
|
|
|
|
|
<a href="//hephaestus.x/">hephaestus.x</a>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fine" style="color:var(--dim);font-size:11px;margin-top:.6rem">
|
|
|
|
|
Source at <a href="https://code.silentmode.st/silentmode/prometheus">code.silentmode.st/silentmode/prometheus</a> · MPL-2.0
|
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
|
|
|
</div>
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
</body>
|
|
|
|
|
</html>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Minimal HTML escape for text that goes into attributes or text nodes.
|
|
|
|
|
* Not for anything that must survive as markup; page bodies ship as-is.
|
|
|
|
|
*/
|
|
|
|
|
export function escapeHtml(s) {
|
|
|
|
|
return String(s)
|
|
|
|
|
.replaceAll('&', '&')
|
|
|
|
|
.replaceAll('<', '<')
|
|
|
|
|
.replaceAll('>', '>')
|
|
|
|
|
.replaceAll('"', '"')
|
|
|
|
|
.replaceAll("'", ''');
|
|
|
|
|
}
|