@agent-cms/agentcms 0.0.0-stage → 0.1.0
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/LICENSE +143 -0
- package/README.md +1119 -2
- package/changelog.json +20 -0
- package/dist/admin/admin-files.json +48 -0
- package/dist/admin/assets/CodeEditor-DcFzGUt8.js +24 -0
- package/dist/admin/assets/flexweg-logo-CdtuqAFA.png +0 -0
- package/dist/admin/assets/index-CLZ1gQZl.js +5460 -0
- package/dist/admin/assets/index-DMcGogR2.css +1 -0
- package/dist/admin/assets/sql-wasm-C1U8OeUW.wasm +0 -0
- package/dist/admin/assets/sql-wasm-CYl3LXH_.js +2 -0
- package/dist/admin/assets/sql-wasm-V5jTDcrT.js +1 -0
- package/dist/admin/config.js +11 -0
- package/dist/admin/external.default.json +72 -0
- package/dist/admin/index.html +116 -0
- package/dist/admin/plugins/core-seo/bundle.js +55 -0
- package/dist/admin/plugins/core-seo/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-archives/bundle.js +1414 -0
- package/dist/admin/plugins/flexweg-archives/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-multilang/bundle.js +1187 -0
- package/dist/admin/plugins/flexweg-multilang/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-rss/bundle.js +1354 -0
- package/dist/admin/plugins/flexweg-rss/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-search/bundle.js +1243 -0
- package/dist/admin/plugins/flexweg-search/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-sitemaps/bundle.js +1293 -0
- package/dist/admin/plugins/flexweg-sitemaps/manifest.json +7 -0
- package/dist/admin/runtime/cms-runtime.js +164 -0
- package/dist/admin/runtime/react-dom-client.js +13 -0
- package/dist/admin/runtime/react-dom.js +16 -0
- package/dist/admin/runtime/react-i18next.js +21 -0
- package/dist/admin/runtime/react-jsx-runtime.js +17 -0
- package/dist/admin/runtime/react.js +55 -0
- package/dist/admin/themes/corporate/bundle.js +14465 -0
- package/dist/admin/themes/corporate/manifest.json +7 -0
- package/dist/admin/themes/corporate/theme.css +2 -0
- package/dist/admin/themes/magazine/bundle.js +10229 -0
- package/dist/admin/themes/magazine/manifest.json +7 -0
- package/dist/admin/themes/magazine/theme.css +2 -0
- package/dist/admin/themes/marketplace-core/bundle.js +11213 -0
- package/dist/admin/themes/marketplace-core/manifest.json +7 -0
- package/dist/admin/themes/marketplace-core/theme.css +1613 -0
- package/dist/admin/themes/portfolio/bundle.js +9195 -0
- package/dist/admin/themes/portfolio/manifest.json +7 -0
- package/dist/admin/themes/portfolio/theme.css +2 -0
- package/dist/admin/themes/storefront/bundle.js +13172 -0
- package/dist/admin/themes/storefront/manifest.json +7 -0
- package/dist/admin/themes/storefront/theme.css +2 -0
- package/dist/agent/agentcms.mjs +1711 -0
- package/dist/agent/chunks/hosting-D_uj-Mt1.mjs +117 -0
- package/dist/agent/chunks/index-CrvEoyi0.mjs +3608 -0
- package/dist/agent/chunks/install-DkCCcKAO.mjs +577 -0
- package/dist/agent/chunks/plan-EYKegfhp.mjs +245 -0
- package/dist/agent/chunks/registry-lkfb_E9O.mjs +53517 -0
- package/dist/agent/chunks/siteAdd-BqcYt8nu.mjs +118 -0
- package/dist/agent/chunks/sql-wasm-GjJDiefO.mjs +10 -0
- package/dist/agent/chunks/sqljs-DjSCStJ0.mjs +20 -0
- package/dist/agent/vendor/sql-wasm.cjs +188 -0
- package/dist/theme-assets/corporate-menu.js +266 -0
- package/dist/theme-assets/corporate-posts.js +571 -0
- package/dist/theme-assets/corporate.css +2 -0
- package/dist/theme-assets/default-menu.js +266 -0
- package/dist/theme-assets/default-posts.js +378 -0
- package/dist/theme-assets/default.css +1 -0
- package/dist/theme-assets/magazine-menu.js +227 -0
- package/dist/theme-assets/magazine-posts.js +361 -0
- package/dist/theme-assets/magazine.css +2 -0
- package/dist/theme-assets/portfolio-filters.js +104 -0
- package/dist/theme-assets/portfolio-menu.js +240 -0
- package/dist/theme-assets/portfolio-posts.js +358 -0
- package/dist/theme-assets/portfolio.css +2 -0
- package/dist/theme-assets/storefront-catalog.js +504 -0
- package/dist/theme-assets/storefront-menu.js +239 -0
- package/dist/theme-assets/storefront-posts.js +404 -0
- package/dist/theme-assets/storefront.css +2 -0
- package/dist/version.json +9 -0
- package/package.json +101 -4
- package/plugin/.claude-plugin/plugin.json +16 -0
- package/plugin/skills/agentcms-install/SKILL.md +110 -0
- package/plugin/skills/agentcms-install/references/cloudflare.md +15 -0
- package/plugin/skills/agentcms-install/references/flexweg.md +11 -0
- package/plugin/skills/agentcms-install/references/github.md +67 -0
- package/plugin/skills/agentcms-install/references/gitlab.md +15 -0
- package/plugin/skills/agentcms-install/references/s3.md +81 -0
- package/plugin/skills/agentcms-manage/SKILL.md +92 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corporate theme — runtime sidebar loader.
|
|
3
|
+
*
|
|
4
|
+
* Populates `[data-cms-related]` and `[data-cms-author-bio]` hosts
|
|
5
|
+
* from `/data/posts.json` and `/data/authors.json`. Same lifecycle as
|
|
6
|
+
* the magazine + default themes: fetched once on DOMContentLoaded;
|
|
7
|
+
* paint passes are independent and tolerate each other's failure.
|
|
8
|
+
*
|
|
9
|
+
* The injected DOM uses corporate-specific class names
|
|
10
|
+
* (`corporate-bio-*`, `corporate-related-*`) — matching CSS rules
|
|
11
|
+
* live in `theme.css` under `@layer components`.
|
|
12
|
+
*
|
|
13
|
+
* Failure is silent: missing data leaves the host hidden (related)
|
|
14
|
+
* or untouched (author-bio's host stays `hidden` from publish time).
|
|
15
|
+
*/
|
|
16
|
+
(function () {
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
// ─── Base path ────────────────────────────────────────────────────
|
|
20
|
+
// Sites served from a sub-folder (e.g. GitHub Pages project sites)
|
|
21
|
+
// carry <meta name="x-cms-base" content="/repo/"> in <head>; no meta
|
|
22
|
+
// means the site lives at the domain root. URLs built at runtime from
|
|
23
|
+
// JSON data go through withBase() so they land under that folder.
|
|
24
|
+
var BASE = (function () {
|
|
25
|
+
var meta = document.querySelector('meta[name="x-cms-base"]');
|
|
26
|
+
var b = (meta && meta.getAttribute("content")) || "/";
|
|
27
|
+
if (b.charAt(0) !== "/") b = "/" + b;
|
|
28
|
+
if (b.charAt(b.length - 1) !== "/") b += "/";
|
|
29
|
+
return b;
|
|
30
|
+
})();
|
|
31
|
+
|
|
32
|
+
function withBase(url) {
|
|
33
|
+
if (BASE === "/" || !url) return url;
|
|
34
|
+
// Absolute URLs (http:, mailto:, data:, …), protocol-relative and
|
|
35
|
+
// fragment-only links pass through.
|
|
36
|
+
if (/^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url)) return url;
|
|
37
|
+
if (url.indexOf(BASE) === 0 || url === BASE.slice(0, -1)) return url;
|
|
38
|
+
return BASE + (url.charAt(0) === "/" ? url.slice(1) : url);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// "/repo/fr/x.html" → "/fr/x.html" so path parsing ignores the base.
|
|
42
|
+
function stripBase(pathname) {
|
|
43
|
+
if (BASE === "/") return pathname;
|
|
44
|
+
if (pathname.indexOf(BASE) === 0) return "/" + pathname.slice(BASE.length);
|
|
45
|
+
if (pathname === BASE.slice(0, -1)) return "/";
|
|
46
|
+
return pathname;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Social icons ─────────────────────────────────────────────────
|
|
50
|
+
// Brand SVG paths + display labels for the curated SocialNetwork
|
|
51
|
+
// list. Mirror in core/socialIcons.tsx (and each other theme's
|
|
52
|
+
// posts-loader.js) when adding a network.
|
|
53
|
+
var SOCIAL_ICON_PATHS = {
|
|
54
|
+
twitter: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
|
|
55
|
+
linkedin: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z",
|
|
56
|
+
instagram: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z",
|
|
57
|
+
mastodon: "M23.193 7.88c0-5.207-3.411-6.733-3.411-6.733C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.412 1.526-3.412 6.733 0 1.193-.023 2.619.015 4.13.124 5.092.934 10.11 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.151 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.52-2.667.477-6.508.477-6.508zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.484v-3.35c0-1.536-.601-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.945v6.119H4.831V8.285c0-1.29.328-2.313.987-3.07.68-.756 1.57-1.144 2.674-1.144 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.04c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.144.658.757.986 1.78.986 3.07v6.304z",
|
|
58
|
+
bluesky: "M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.137-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.952 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z",
|
|
59
|
+
github: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
|
|
60
|
+
website: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95a15.65 15.65 0 0 0-1.38-3.56A8.03 8.03 0 0 1 18.92 8zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56A7.987 7.987 0 0 1 5.08 16zm2.95-8H5.08a7.987 7.987 0 0 1 4.33-3.56A15.65 15.65 0 0 0 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z",
|
|
61
|
+
};
|
|
62
|
+
var SOCIAL_ICON_LABELS = {
|
|
63
|
+
twitter: "Twitter / X",
|
|
64
|
+
linkedin: "LinkedIn",
|
|
65
|
+
instagram: "Instagram",
|
|
66
|
+
mastodon: "Mastodon",
|
|
67
|
+
bluesky: "Bluesky",
|
|
68
|
+
github: "GitHub",
|
|
69
|
+
website: "Website",
|
|
70
|
+
};
|
|
71
|
+
function socialIconSvg(network) {
|
|
72
|
+
var path = SOCIAL_ICON_PATHS[network];
|
|
73
|
+
if (!path) return "";
|
|
74
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="' + path + '"/></svg>';
|
|
75
|
+
}
|
|
76
|
+
function socialIconLabel(network) {
|
|
77
|
+
return SOCIAL_ICON_LABELS[network] || network;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fetchJson(path) {
|
|
81
|
+
return fetch(withBase(path), { credentials: "omit" })
|
|
82
|
+
.then(function (res) {
|
|
83
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
84
|
+
return res.json();
|
|
85
|
+
})
|
|
86
|
+
.catch(function (err) {
|
|
87
|
+
console.warn("[cms] " + path + " load failed:", err);
|
|
88
|
+
return null;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Related / popular articles ───────────────────────────────────
|
|
93
|
+
//
|
|
94
|
+
// DOM emitted matches the corporate single-post mockup — a card with
|
|
95
|
+
// a heading and a list of items; each item is an <a> with a colored
|
|
96
|
+
// category eyebrow, line-clamped title, and a small date meta line.
|
|
97
|
+
//
|
|
98
|
+
// <div class="corporate-related-card">
|
|
99
|
+
// <h4 class="corporate-related-heading">Articles populaires</h4>
|
|
100
|
+
// <div class="corporate-related-list">
|
|
101
|
+
// <a class="corporate-related-item" href="/...">
|
|
102
|
+
// <p class="corporate-related-eyebrow">Category</p>
|
|
103
|
+
// <p class="corporate-related-title">Title</p>
|
|
104
|
+
// <p class="corporate-related-meta">Jan 12 • 5 min read</p>
|
|
105
|
+
// </a>
|
|
106
|
+
// …
|
|
107
|
+
// </div>
|
|
108
|
+
// </div>
|
|
109
|
+
//
|
|
110
|
+
// The card chrome (background/border/padding) lives in the host
|
|
111
|
+
// element itself via `.corporate-related-card` — see the
|
|
112
|
+
// SingleTemplate sidebar markup.
|
|
113
|
+
|
|
114
|
+
function renderRelatedItem(entry) {
|
|
115
|
+
var a = document.createElement("a");
|
|
116
|
+
a.href = withBase("/" + entry.url);
|
|
117
|
+
a.className = "corporate-related-item";
|
|
118
|
+
if (entry.category && entry.category.name) {
|
|
119
|
+
var eyebrow = document.createElement("p");
|
|
120
|
+
eyebrow.className = "corporate-related-eyebrow";
|
|
121
|
+
eyebrow.textContent = entry.category.name;
|
|
122
|
+
a.appendChild(eyebrow);
|
|
123
|
+
}
|
|
124
|
+
var title = document.createElement("p");
|
|
125
|
+
title.className = "corporate-related-title";
|
|
126
|
+
title.textContent = entry.title || "";
|
|
127
|
+
a.appendChild(title);
|
|
128
|
+
if (entry.dateLabel) {
|
|
129
|
+
var meta = document.createElement("p");
|
|
130
|
+
meta.className = "corporate-related-meta";
|
|
131
|
+
meta.textContent = entry.dateLabel;
|
|
132
|
+
a.appendChild(meta);
|
|
133
|
+
}
|
|
134
|
+
return a;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderRelated(host, posts) {
|
|
138
|
+
var termId = host.getAttribute("data-cms-term-id") || "";
|
|
139
|
+
var currentId = host.getAttribute("data-cms-current-id") || "";
|
|
140
|
+
var limit = parseInt(host.getAttribute("data-cms-limit") || "3", 10);
|
|
141
|
+
var label = host.getAttribute("data-cms-label") || "Popular articles";
|
|
142
|
+
var fallbackLabel =
|
|
143
|
+
host.getAttribute("data-cms-fallback-label") || "Latest articles";
|
|
144
|
+
|
|
145
|
+
var matching = [];
|
|
146
|
+
var heading = label;
|
|
147
|
+
|
|
148
|
+
if (termId) {
|
|
149
|
+
for (var i = 0; i < posts.length && matching.length < limit; i++) {
|
|
150
|
+
var p = posts[i];
|
|
151
|
+
if (p.id === currentId) continue;
|
|
152
|
+
if (p.type !== "post") continue;
|
|
153
|
+
if (p.primaryTermId !== termId) continue;
|
|
154
|
+
matching.push(p);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (matching.length === 0) {
|
|
159
|
+
heading = fallbackLabel;
|
|
160
|
+
for (var j = 0; j < posts.length && matching.length < limit; j++) {
|
|
161
|
+
var pp = posts[j];
|
|
162
|
+
if (pp.id === currentId) continue;
|
|
163
|
+
if (pp.type !== "post") continue;
|
|
164
|
+
matching.push(pp);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (matching.length === 0) {
|
|
169
|
+
host.setAttribute("hidden", "");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
host.removeAttribute("hidden");
|
|
174
|
+
host.innerHTML = "";
|
|
175
|
+
var headingEl = document.createElement("h4");
|
|
176
|
+
headingEl.className = "corporate-related-heading";
|
|
177
|
+
headingEl.textContent = heading;
|
|
178
|
+
host.appendChild(headingEl);
|
|
179
|
+
var list = document.createElement("div");
|
|
180
|
+
list.className = "corporate-related-list";
|
|
181
|
+
matching.forEach(function (entry) {
|
|
182
|
+
list.appendChild(renderRelatedItem(entry));
|
|
183
|
+
});
|
|
184
|
+
host.appendChild(list);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── Author bio ───────────────────────────────────────────────────
|
|
188
|
+
//
|
|
189
|
+
// DOM emitted (matches the corporate single-post sidebar mockup —
|
|
190
|
+
// round avatar + name in headline weight + label-caps title +
|
|
191
|
+
// bio paragraph + circular social icon row):
|
|
192
|
+
//
|
|
193
|
+
// <h4 class="corporate-bio-eyebrow">Author</h4>
|
|
194
|
+
// <div class="corporate-bio-row">
|
|
195
|
+
// <img class="corporate-bio-avatar" />
|
|
196
|
+
// <div>
|
|
197
|
+
// <p class="corporate-bio-name">…</p>
|
|
198
|
+
// <p class="corporate-bio-title">Senior Strategist</p>
|
|
199
|
+
// </div>
|
|
200
|
+
// </div>
|
|
201
|
+
// <p class="corporate-bio-bio">…</p>
|
|
202
|
+
// <div class="corporate-bio-socials">
|
|
203
|
+
// <a href="…" class="corporate-bio-social"> [icon] </a>
|
|
204
|
+
// </div>
|
|
205
|
+
|
|
206
|
+
function authorAvatar(name, url) {
|
|
207
|
+
if (!url) {
|
|
208
|
+
// Fallback initials box — same color tokens as the avatar
|
|
209
|
+
// when the author hasn't uploaded an image.
|
|
210
|
+
var span = document.createElement("span");
|
|
211
|
+
span.className = "corporate-bio-avatar";
|
|
212
|
+
span.style.display = "inline-flex";
|
|
213
|
+
span.style.alignItems = "center";
|
|
214
|
+
span.style.justifyContent = "center";
|
|
215
|
+
span.style.background = "rgb(var(--color-secondary-fixed))";
|
|
216
|
+
span.style.color = "rgb(var(--color-secondary))";
|
|
217
|
+
span.style.fontWeight = "700";
|
|
218
|
+
span.setAttribute("aria-hidden", "true");
|
|
219
|
+
span.textContent = (function () {
|
|
220
|
+
if (!name) return "?";
|
|
221
|
+
var parts = name.trim().split(/\s+/);
|
|
222
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
223
|
+
return ((parts[0][0] || "") + (parts[parts.length - 1][0] || "")).toUpperCase();
|
|
224
|
+
})();
|
|
225
|
+
return span;
|
|
226
|
+
}
|
|
227
|
+
var img = document.createElement("img");
|
|
228
|
+
img.className = "corporate-bio-avatar";
|
|
229
|
+
img.src = withBase(url);
|
|
230
|
+
img.alt = name || "";
|
|
231
|
+
img.loading = "lazy";
|
|
232
|
+
return img;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function renderAuthorBio(host, authors) {
|
|
236
|
+
var authorId = host.getAttribute("data-cms-author-id") || "";
|
|
237
|
+
if (!authorId) return;
|
|
238
|
+
var entry = authors[authorId];
|
|
239
|
+
if (!entry) return;
|
|
240
|
+
|
|
241
|
+
host.innerHTML = "";
|
|
242
|
+
|
|
243
|
+
// Eyebrow label — "Author" or whatever the host configured. The
|
|
244
|
+
// host can override it via data-cms-bio-label for i18n.
|
|
245
|
+
var eyebrow = document.createElement("h4");
|
|
246
|
+
eyebrow.className = "corporate-bio-eyebrow";
|
|
247
|
+
eyebrow.textContent = host.getAttribute("data-cms-bio-label") || "Author";
|
|
248
|
+
host.appendChild(eyebrow);
|
|
249
|
+
|
|
250
|
+
var row = document.createElement("div");
|
|
251
|
+
row.className = "corporate-bio-row";
|
|
252
|
+
row.appendChild(authorAvatar(entry.displayName, entry.avatar));
|
|
253
|
+
|
|
254
|
+
var nameWrap = document.createElement("div");
|
|
255
|
+
var nameEl;
|
|
256
|
+
if (entry.url) {
|
|
257
|
+
nameEl = document.createElement("a");
|
|
258
|
+
nameEl.href = withBase("/" + entry.url);
|
|
259
|
+
} else {
|
|
260
|
+
nameEl = document.createElement("p");
|
|
261
|
+
}
|
|
262
|
+
nameEl.className = "corporate-bio-name";
|
|
263
|
+
nameEl.textContent = entry.displayName || "";
|
|
264
|
+
nameWrap.appendChild(nameEl);
|
|
265
|
+
|
|
266
|
+
if (entry.title) {
|
|
267
|
+
var role = document.createElement("p");
|
|
268
|
+
role.className = "corporate-bio-title";
|
|
269
|
+
role.textContent = entry.title;
|
|
270
|
+
nameWrap.appendChild(role);
|
|
271
|
+
}
|
|
272
|
+
row.appendChild(nameWrap);
|
|
273
|
+
host.appendChild(row);
|
|
274
|
+
|
|
275
|
+
if (entry.bio) {
|
|
276
|
+
var bio = document.createElement("p");
|
|
277
|
+
bio.className = "corporate-bio-bio";
|
|
278
|
+
bio.textContent = entry.bio;
|
|
279
|
+
host.appendChild(bio);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Visible socials — already filtered server-side. Each entry
|
|
283
|
+
// produces an icon link to the user's external profile, opened in
|
|
284
|
+
// a new tab with rel=noopener noreferrer.
|
|
285
|
+
if (entry.socials && entry.socials.length) {
|
|
286
|
+
var socials = document.createElement("div");
|
|
287
|
+
socials.className = "corporate-bio-socials";
|
|
288
|
+
for (var i = 0; i < entry.socials.length; i++) {
|
|
289
|
+
var s = entry.socials[i];
|
|
290
|
+
var a = document.createElement("a");
|
|
291
|
+
a.className = "corporate-bio-social";
|
|
292
|
+
a.href = s.url;
|
|
293
|
+
a.target = "_blank";
|
|
294
|
+
a.rel = "noopener noreferrer";
|
|
295
|
+
a.setAttribute("aria-label", socialIconLabel(s.network));
|
|
296
|
+
a.title = socialIconLabel(s.network);
|
|
297
|
+
a.innerHTML = socialIconSvg(s.network);
|
|
298
|
+
socials.appendChild(a);
|
|
299
|
+
}
|
|
300
|
+
host.appendChild(socials);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
host.removeAttribute("hidden");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Share buttons ────────────────────────────────────────────────
|
|
307
|
+
//
|
|
308
|
+
// Wires up `[data-cms-share]` buttons in the body. The single-post
|
|
309
|
+
// template emits two: `data-cms-share="native"` (Web Share API
|
|
310
|
+
// fallback to copy-link when unsupported) and `data-cms-share="copy"`
|
|
311
|
+
// (clipboard write of the page URL). Visual feedback is a brief
|
|
312
|
+
// text/title swap so the user knows the click registered without
|
|
313
|
+
// needing a toast library.
|
|
314
|
+
|
|
315
|
+
function showFlash(button, glyphName) {
|
|
316
|
+
var icon = button.querySelector(".material-symbols-outlined");
|
|
317
|
+
if (!icon) return;
|
|
318
|
+
var original = icon.textContent;
|
|
319
|
+
icon.textContent = glyphName;
|
|
320
|
+
setTimeout(function () {
|
|
321
|
+
icon.textContent = original;
|
|
322
|
+
}, 1400);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function copyLink(button) {
|
|
326
|
+
var url = window.location.href;
|
|
327
|
+
var done = function () { showFlash(button, "check"); };
|
|
328
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
329
|
+
navigator.clipboard.writeText(url).then(done, function () {
|
|
330
|
+
// Fallback when the clipboard write is denied (e.g. insecure
|
|
331
|
+
// context) — select-and-copy the URL via a temporary textarea.
|
|
332
|
+
legacyCopy(url);
|
|
333
|
+
done();
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
legacyCopy(url);
|
|
337
|
+
done();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function legacyCopy(text) {
|
|
342
|
+
try {
|
|
343
|
+
var ta = document.createElement("textarea");
|
|
344
|
+
ta.value = text;
|
|
345
|
+
ta.setAttribute("readonly", "");
|
|
346
|
+
ta.style.position = "absolute";
|
|
347
|
+
ta.style.left = "-9999px";
|
|
348
|
+
document.body.appendChild(ta);
|
|
349
|
+
ta.select();
|
|
350
|
+
document.execCommand("copy");
|
|
351
|
+
document.body.removeChild(ta);
|
|
352
|
+
} catch (e) {
|
|
353
|
+
console.warn("[cms] clipboard copy failed:", e);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function shareNative(button) {
|
|
358
|
+
var url = window.location.href;
|
|
359
|
+
var title = document.title || "";
|
|
360
|
+
if (navigator.share) {
|
|
361
|
+
navigator.share({ title: title, url: url }).catch(function () {
|
|
362
|
+
// User cancelled or the share sheet failed — silent.
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
copyLink(button);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function wireShareButtons() {
|
|
370
|
+
document.querySelectorAll("[data-cms-share]").forEach(function (button) {
|
|
371
|
+
var mode = button.getAttribute("data-cms-share");
|
|
372
|
+
button.addEventListener("click", function () {
|
|
373
|
+
if (mode === "copy") copyLink(button);
|
|
374
|
+
else shareNative(button);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ─── Contact form ─────────────────────────────────────────────────
|
|
380
|
+
//
|
|
381
|
+
// Intercepts submit on every `[data-cms-form]` form. Two modes are
|
|
382
|
+
// surfaced in the contact-form block's inspector:
|
|
383
|
+
// - "endpoint" (default) → POST a JSON body to a third-party
|
|
384
|
+
// form service (Formspree, Web3Forms, etc.). Success / error
|
|
385
|
+
// messages from `[data-cms-form-success]` / `[data-cms-form-error]`
|
|
386
|
+
// toggle visibility based on the response status.
|
|
387
|
+
// - "mailto" → build a `mailto:` URL with the subject + body
|
|
388
|
+
// pre-filled, then `window.location.href` to it. No JS-only
|
|
389
|
+
// state to manage.
|
|
390
|
+
// When the form has neither a configured endpoint nor a mailto
|
|
391
|
+
// address (most likely during initial setup), submit is left to
|
|
392
|
+
// the browser's default behavior so the user notices the missing
|
|
393
|
+
// configuration.
|
|
394
|
+
|
|
395
|
+
function asUrlEncoded(formData) {
|
|
396
|
+
var pairs = [];
|
|
397
|
+
formData.forEach(function (value, key) {
|
|
398
|
+
pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
|
|
399
|
+
});
|
|
400
|
+
return pairs.join("&");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function showFormStatus(form, kind) {
|
|
404
|
+
["success", "error"].forEach(function (k) {
|
|
405
|
+
var el = form.querySelector("[data-cms-form-" + k + "]");
|
|
406
|
+
if (el) {
|
|
407
|
+
if (k === kind) el.removeAttribute("hidden");
|
|
408
|
+
else el.setAttribute("hidden", "");
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function buildMailto(form, address) {
|
|
414
|
+
var data = new FormData(form);
|
|
415
|
+
var subject = data.get("subject") || "Contact form submission";
|
|
416
|
+
var lines = [];
|
|
417
|
+
data.forEach(function (value, key) {
|
|
418
|
+
if (key === "subject" || key === "consent") return;
|
|
419
|
+
lines.push(key + ": " + value);
|
|
420
|
+
});
|
|
421
|
+
return (
|
|
422
|
+
"mailto:" +
|
|
423
|
+
encodeURIComponent(address) +
|
|
424
|
+
"?subject=" +
|
|
425
|
+
encodeURIComponent(String(subject)) +
|
|
426
|
+
"&body=" +
|
|
427
|
+
encodeURIComponent(lines.join("\n\n"))
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function handleFormSubmit(form, mode, endpoint, mailtoAddress, event) {
|
|
432
|
+
if (mode === "mailto") {
|
|
433
|
+
if (!mailtoAddress) return; // No config — let the browser handle.
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
window.location.href = buildMailto(form, mailtoAddress);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (!endpoint) return; // No endpoint configured — fall through.
|
|
439
|
+
event.preventDefault();
|
|
440
|
+
var data = new FormData(form);
|
|
441
|
+
var submitButton = form.querySelector("button[type='submit']");
|
|
442
|
+
if (submitButton) submitButton.disabled = true;
|
|
443
|
+
fetch(withBase(endpoint), {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: {
|
|
446
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
447
|
+
Accept: "application/json",
|
|
448
|
+
},
|
|
449
|
+
body: asUrlEncoded(data),
|
|
450
|
+
})
|
|
451
|
+
.then(function (res) {
|
|
452
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
453
|
+
showFormStatus(form, "success");
|
|
454
|
+
form.reset();
|
|
455
|
+
})
|
|
456
|
+
.catch(function (err) {
|
|
457
|
+
console.warn("[cms] contact form submit failed:", err);
|
|
458
|
+
showFormStatus(form, "error");
|
|
459
|
+
})
|
|
460
|
+
.finally(function () {
|
|
461
|
+
if (submitButton) submitButton.disabled = false;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ─── Featured posts chevrons ──────────────────────────────────────
|
|
466
|
+
//
|
|
467
|
+
// The home's featured-posts section ships decorative prev/next
|
|
468
|
+
// chevrons (matching the mockup). On md+ the layout is a static
|
|
469
|
+
// 3-col grid — the chevrons scroll smoothly to the next/previous
|
|
470
|
+
// card slot. On mobile (single column with horizontal overflow if
|
|
471
|
+
// the grid is repurposed as a slider in a future iteration), they
|
|
472
|
+
// page through one card width at a time.
|
|
473
|
+
function wireFeaturedChevrons() {
|
|
474
|
+
var track = document.querySelector("[data-cms-featured-track]");
|
|
475
|
+
if (!track) return;
|
|
476
|
+
var prev = document.querySelector("[data-cms-featured-prev]");
|
|
477
|
+
var next = document.querySelector("[data-cms-featured-next]");
|
|
478
|
+
function step(dir) {
|
|
479
|
+
var cards = track.children;
|
|
480
|
+
if (!cards || cards.length === 0) return;
|
|
481
|
+
var card = cards[0];
|
|
482
|
+
var width = card.getBoundingClientRect().width;
|
|
483
|
+
// Read the actual computed gap from the flex container so the
|
|
484
|
+
// scroll step matches the visual rhythm. The `gap` shorthand
|
|
485
|
+
// resolves to a px value in the computed style (e.g. "32px"
|
|
486
|
+
// when Tailwind's gap-stack-lg = 2rem at 16px root font-size).
|
|
487
|
+
var styles = window.getComputedStyle(track);
|
|
488
|
+
var gap = parseFloat(styles.columnGap || styles.gap || "0") || 0;
|
|
489
|
+
track.scrollBy({ left: dir * (width + gap), behavior: "smooth" });
|
|
490
|
+
}
|
|
491
|
+
if (prev) prev.addEventListener("click", function () { step(-1); });
|
|
492
|
+
if (next) next.addEventListener("click", function () { step(1); });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function wireContactForms() {
|
|
496
|
+
document.querySelectorAll("[data-cms-form]").forEach(function (form) {
|
|
497
|
+
var mode = form.getAttribute("data-cms-form") || "endpoint";
|
|
498
|
+
var endpoint = form.getAttribute("data-cms-form-endpoint") || "";
|
|
499
|
+
var mailtoAddress = form.getAttribute("data-cms-form-mailto") || "";
|
|
500
|
+
form.addEventListener("submit", function (event) {
|
|
501
|
+
handleFormSubmit(form, mode, endpoint, mailtoAddress, event);
|
|
502
|
+
});
|
|
503
|
+
// Marks each input as filled so the floating-label CSS keeps
|
|
504
|
+
// the label in its raised position even when the field has a
|
|
505
|
+
// pre-filled value (browser autofill, back-button restore).
|
|
506
|
+
form.querySelectorAll("input, textarea, select").forEach(function (input) {
|
|
507
|
+
function update() {
|
|
508
|
+
var group = input.closest(".floating-label-group");
|
|
509
|
+
if (!group) return;
|
|
510
|
+
if (input.value && input.value.length > 0) group.classList.add("is-filled");
|
|
511
|
+
else group.classList.remove("is-filled");
|
|
512
|
+
}
|
|
513
|
+
input.addEventListener("input", update);
|
|
514
|
+
input.addEventListener("change", update);
|
|
515
|
+
update();
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ─── Paint passes ─────────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
function paintRelated(postsBlob) {
|
|
523
|
+
if (!postsBlob || !Array.isArray(postsBlob.posts)) return;
|
|
524
|
+
document.querySelectorAll("[data-cms-related]").forEach(function (host) {
|
|
525
|
+
renderRelated(host, postsBlob.posts);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function paintAuthorBios(authorsBlob) {
|
|
530
|
+
if (!authorsBlob || !authorsBlob.authors) return;
|
|
531
|
+
document.querySelectorAll("[data-cms-author-bio]").forEach(function (host) {
|
|
532
|
+
renderAuthorBio(host, authorsBlob.authors);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function ready(fn) {
|
|
537
|
+
if (document.readyState !== "loading") fn();
|
|
538
|
+
else document.addEventListener("DOMContentLoaded", fn);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Multi-language helper — same locale prefix detection as the
|
|
542
|
+
// other themes (see default/posts-loader.js for the rationale).
|
|
543
|
+
function detectLocalePrefix() {
|
|
544
|
+
var path = stripBase(window.location.pathname).replace(/^\/+/, "");
|
|
545
|
+
var first = path.split("/")[0];
|
|
546
|
+
if (!first) return null;
|
|
547
|
+
if (/^[a-z]{2}(-[a-z]{2})?$/.test(first.toLowerCase())) return first.toLowerCase();
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
function fetchWithLocaleFallback(filename) {
|
|
551
|
+
var locale = detectLocalePrefix();
|
|
552
|
+
if (!locale) return fetchJson("/data/" + filename);
|
|
553
|
+
return fetchJson("/" + locale + "/data/" + filename).then(function (data) {
|
|
554
|
+
if (data) return data;
|
|
555
|
+
return fetchJson("/data/" + filename);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
ready(function () {
|
|
560
|
+
wireShareButtons();
|
|
561
|
+
wireContactForms();
|
|
562
|
+
wireFeaturedChevrons();
|
|
563
|
+
Promise.all([
|
|
564
|
+
fetchWithLocaleFallback("posts.json"),
|
|
565
|
+
fetchWithLocaleFallback("authors.json"),
|
|
566
|
+
]).then(function (results) {
|
|
567
|
+
paintRelated(results[0]);
|
|
568
|
+
paintAuthorBios(results[1]);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
})();
|