@ofidj/generator-fidj 3.13.0 → 3.14.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.
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"privacy:rehearse": "npm run build && node --test test/server.test.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@ofidj/entry": "^3.
|
|
19
|
-
"@ofidj/node": "^3.
|
|
18
|
+
"@ofidj/entry": "^3.14.0",
|
|
19
|
+
"@ofidj/node": "^3.14.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|
|
@@ -32,6 +32,6 @@ function mountedHead(config) {
|
|
|
32
32
|
export function renderContent(config) {
|
|
33
33
|
// HTML is supplied by the developer at generation time, never by an app visitor.
|
|
34
34
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${escape(config.title)}</title><link rel="icon" href="${escape(config.favicon)}"><link rel="stylesheet" href="./main.css">${mountedHead(config)}</head><body>
|
|
35
|
-
<header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt="">${escape(config.title)}</a><nav class="content-nav" id="app-nav" aria-label="App navigation" hidden></nav></header>
|
|
35
|
+
<header class="topbar"><a class="brand" href="#/content"><img src="${escape(config.logo)}" alt=""><span class="brand-name">${escape(config.title)}</span></a><nav class="content-nav" id="app-nav" aria-label="App navigation" hidden></nav></header>
|
|
36
36
|
<main><template id="public-content"><section class="card public-content"><h1>${escape(config.welcome)}</h1><div>${config.content}</div></section></template><div id="app" aria-live="polite"></div><footer>Built with Fidj · Your choices belong to this app.</footer></main><script type="module" src="./main.js"></script></body></html>`;
|
|
37
37
|
}
|
|
@@ -45,7 +45,7 @@ let accountEmail = "";
|
|
|
45
45
|
// with the agreement unchecked folded the form away and left the complaint
|
|
46
46
|
// floating above a door the person could no longer see.
|
|
47
47
|
let emailEntryOpen = false;
|
|
48
|
-
const accountRoutes = ["forgot", "reset", "verify", "
|
|
48
|
+
const accountRoutes = ["forgot", "reset", "verify", "profile"];
|
|
49
49
|
let linkToken = "";
|
|
50
50
|
let verificationConfirmed = false;
|
|
51
51
|
function currentRoute() {
|
|
@@ -88,17 +88,23 @@ function banner() {
|
|
|
88
88
|
// account, and it lives on that account's screen.
|
|
89
89
|
function appNav(current: "content" | "account") {
|
|
90
90
|
const tab = (id: string, label: string, selected: boolean) =>
|
|
91
|
-
`<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}>${label}</button>`;
|
|
91
|
+
`<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}><span class="tab-label">${label}</span></button>`;
|
|
92
92
|
const account = signedIn
|
|
93
93
|
? tab(
|
|
94
94
|
"account-tab",
|
|
95
|
-
accountEmail ? `
|
|
95
|
+
accountEmail ? `Profile (${escape(accountEmail)})` : "Profile",
|
|
96
96
|
current === "account",
|
|
97
97
|
)
|
|
98
98
|
: tab("account-tab", "Sign in", false);
|
|
99
99
|
return tab("content-tab", "Content", current === "content") + account;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function profileSummary() {
|
|
103
|
+
const publicUrl = `${config.dashboardUrl}/#/pub/${encodeURIComponent(config.appId)}`;
|
|
104
|
+
const badgeUrl = `${config.apiEndpoint}/apps/${encodeURIComponent(config.appId)}/badge`;
|
|
105
|
+
return `<header class="profile-summary"><div class="profile-summary-copy"><span class="eyebrow">Profile</span><strong>${escape(accountEmail)}</strong><span>${escape(config.title)}</span></div><a class="profile-public" href="${escape(publicUrl)}" target="_blank" rel="noopener"><img src="${escape(badgeUrl)}" alt="${escape(config.title)} public badge" width="133" height="20"></a><button id="exit" class="danger">Sign out</button></header>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
// The bar belongs to the document, not to the screen being drawn: it survives
|
|
103
109
|
// every route change, so it is filled rather than rebuilt with the page.
|
|
104
110
|
function renderNav(current: "content" | "account") {
|
|
@@ -112,7 +118,7 @@ function renderNav(current: "content" | "account") {
|
|
|
112
118
|
function wireNav() {
|
|
113
119
|
element("content-tab")?.addEventListener("click", () => navigate("content"));
|
|
114
120
|
element("account-tab")?.addEventListener("click", () =>
|
|
115
|
-
navigate(signedIn ? "
|
|
121
|
+
navigate(signedIn ? "profile" : "signin"),
|
|
116
122
|
);
|
|
117
123
|
}
|
|
118
124
|
|
|
@@ -557,15 +563,15 @@ function render() {
|
|
|
557
563
|
route = "signin";
|
|
558
564
|
else if (!["signin", "content", "privacy", ...accountRoutes].includes(route))
|
|
559
565
|
route = "content";
|
|
560
|
-
// The privacy screen and the
|
|
566
|
+
// The privacy screen and the profile screen became one. The address that
|
|
561
567
|
// named the first still means something to anyone who bookmarked it, so it
|
|
562
568
|
// arrives where that screen went rather than nowhere.
|
|
563
|
-
if (route === "privacy") route = signedIn ? "
|
|
569
|
+
if (route === "privacy") route = signedIn ? "profile" : "signin";
|
|
564
570
|
window.history.replaceState(null, "", "#/" + route);
|
|
565
|
-
//
|
|
571
|
+
// Profile is a signed-in screen and keeps the app's chrome. Recovery and
|
|
566
572
|
// verification are reached without a session, so they stand alone.
|
|
567
573
|
const standaloneAccount =
|
|
568
|
-
accountRoutes.includes(route) && !(route === "
|
|
574
|
+
accountRoutes.includes(route) && !(route === "profile" && signedIn);
|
|
569
575
|
document.body.classList.toggle(
|
|
570
576
|
"signin-view",
|
|
571
577
|
route === "signin" || standaloneAccount,
|
|
@@ -584,11 +590,11 @@ function render() {
|
|
|
584
590
|
renderAccount(route);
|
|
585
591
|
return;
|
|
586
592
|
}
|
|
587
|
-
if (route === "
|
|
593
|
+
if (route === "profile") {
|
|
588
594
|
// The two ways out, last and together: back to the app, or out of this
|
|
589
595
|
// session. Leaving the app itself is a different decision and stays where
|
|
590
596
|
// the things it erases are listed.
|
|
591
|
-
root.innerHTML = `<section class="
|
|
597
|
+
root.innerHTML = `<section class="content-account">${profileSummary()}<div class="card profile-body">${banner()}${accountForm("account", {linkToken, verificationConfirmed, emailVerified, accountEmail}, {compact: true})}${privacyBlock()}</div></section>`;
|
|
592
598
|
renderNav("account");
|
|
593
599
|
wireAccount("account");
|
|
594
600
|
wireSignOut();
|
|
@@ -213,7 +213,7 @@ function render() {
|
|
|
213
213
|
const canWrite = session?.roles.some((role) =>
|
|
214
214
|
["Owner", "Editor"].includes(role),
|
|
215
215
|
);
|
|
216
|
-
root.innerHTML = `<header class="topbar"><a class="brand" href="/"><span class="app-symbol">✳</span>${escape(settings.title)}</a><a class="fidj-brand" href="${escape(settings.dashboardUrl)}" target="_blank" rel="noopener"><img src="/fidj-logo.png" alt="Fidj">Privacy with Fidj ↗</a></header>
|
|
216
|
+
root.innerHTML = `<header class="topbar"><a class="brand" href="/"><span class="app-symbol">✳</span><span class="brand-name">${escape(settings.title)}</span></a><a class="fidj-brand" href="${escape(settings.dashboardUrl)}" target="_blank" rel="noopener"><img src="/fidj-logo.png" alt="Fidj">Privacy with Fidj ↗</a></header>
|
|
217
217
|
<main>${notice ? `<p class="notice" role="status">${escape(notice)}</p>` : ""}${error ? `<p class="error" role="alert">${escape(error)}</p>` : ""}
|
|
218
218
|
${
|
|
219
219
|
!session
|