@ofidj/generator-fidj 3.21.1 → 3.22.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/generators/app/templates/typescript/package.json +2 -2
- package/generators/app/templates/typescript/scripts/render-content.mjs +5 -2
- package/generators/app/templates/typescript/src/content.ts +14 -10
- package/generators/app/templates/typescript/src/main.ts +1 -1
- package/lib/module-mount.cjs +3 -1
- package/package.json +1 -1
|
@@ -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.22.0",
|
|
19
|
+
"@ofidj/node": "^3.22.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|
|
@@ -30,8 +30,11 @@ function mountedHead(config) {
|
|
|
30
30
|
].join("");
|
|
31
31
|
}
|
|
32
32
|
export function renderContent(config) {
|
|
33
|
+
const footer = config.moduleMount
|
|
34
|
+
? ""
|
|
35
|
+
: "<footer>Built with Fidj · Your choices belong to this app.</footer>";
|
|
33
36
|
// HTML is supplied by the developer at generation time, never by an app visitor.
|
|
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>
|
|
37
|
+
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"><meta name="description" content="${escape(config.description)}"><title>${escape(config.title)}</title><link rel="icon" href="${escape(config.favicon)}"><link rel="preload" href="./fonts/InstrumentSerif-400-latin.woff2" as="font" type="font/woff2" crossorigin><link rel="stylesheet" href="./main.css">${mountedHead(config)}</head><body>
|
|
35
38
|
<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
|
-
<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
|
|
39
|
+
<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}</main><script type="module" src="./main.js"></script></body></html>`;
|
|
37
40
|
}
|
|
@@ -97,14 +97,10 @@ function banner() {
|
|
|
97
97
|
// Signing out is not a place, so it is not a tab: it is a thing you do to an
|
|
98
98
|
// account, and it lives on that account's screen.
|
|
99
99
|
function appNav(current: "content" | "account") {
|
|
100
|
-
const tab = (id: string, label: string, selected: boolean) =>
|
|
101
|
-
`<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}><span class="tab-label">${label}</span></button>`;
|
|
100
|
+
const tab = (id: string, label: string, selected: boolean, profile = false) =>
|
|
101
|
+
`<button id="${id}"${selected ? ' class="selected" aria-current="page"' : ""}>${profile ? '<span class="profile-mark" aria-hidden="true"><svg viewBox="0 0 20 20"><circle cx="10" cy="7" r="3"/><path d="M4.5 16c.7-3 2.5-4.5 5.5-4.5s4.8 1.5 5.5 4.5"/></svg></span>' : ""}<span class="tab-label">${label}</span></button>`;
|
|
102
102
|
const account = signedIn
|
|
103
|
-
? tab(
|
|
104
|
-
"account-tab",
|
|
105
|
-
accountEmail ? `Profile (${escape(accountEmail)})` : "Profile",
|
|
106
|
-
current === "account",
|
|
107
|
-
)
|
|
103
|
+
? tab("account-tab", "Profile", current === "account", true)
|
|
108
104
|
: tab("account-tab", "Sign in", false);
|
|
109
105
|
return tab("content-tab", "Content", current === "content") + account;
|
|
110
106
|
}
|
|
@@ -112,7 +108,7 @@ function appNav(current: "content" | "account") {
|
|
|
112
108
|
function profileSummary() {
|
|
113
109
|
const publicUrl = `${config.dashboardUrl}/#/pub/${encodeURIComponent(config.appId)}`;
|
|
114
110
|
const badgeUrl = `${config.apiEndpoint}/apps/${encodeURIComponent(config.appId)}/badge`;
|
|
115
|
-
return `<header class="profile-summary"><div class="profile-summary-copy"><
|
|
111
|
+
return `<header class="profile-summary"><div class="profile-summary-copy"><h1 class="eyebrow">Profile</h1><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="sign-out">Sign out</button></header>`;
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
// The bar belongs to the document, not to the screen being drawn: it survives
|
|
@@ -521,6 +517,9 @@ function startModule() {
|
|
|
521
517
|
} | null;
|
|
522
518
|
if (!mount) return;
|
|
523
519
|
moduleStarted = true;
|
|
520
|
+
// The entry may have been on screen a moment ago (a refused request, a
|
|
521
|
+
// redirect); its class drops the page margins the app relies on.
|
|
522
|
+
document.body.classList.remove("signin-view");
|
|
524
523
|
document.body.classList.add("has-module");
|
|
525
524
|
// The app takes the whole document, not a corner of the shell's. A built
|
|
526
525
|
// single-page app positions itself against the body — Ionic, for one, fixes
|
|
@@ -674,7 +673,7 @@ function render() {
|
|
|
674
673
|
root.innerHTML = `<section class="signin-shell"><div class="signin-intro${config.highlights?.length ? "" : " is-plain"}">${masthead(config.logo, config.title)}
|
|
675
674
|
<div class="signin-identity"><h1>${escape(config.welcome)}</h1><p class="signin-description">${escape(config.description)}</p></div>
|
|
676
675
|
${highlightCells(config.highlights)}</div>
|
|
677
|
-
<div class="signin-form"><div>${banner()}<h2>Sign in to ${escape(config.title)}</h2><form id="signin">${credentialFields({email: signInEmail, password: signInPassword}, {passkey: passkeyHere})}</form>${config.allowAnonymous ? `<div class="signin-divider"><span>or explore first</span></div><button class="anonymous-entry" id="anonymous">Enter anonymously <span aria-hidden="true">→</span></button><p class="signin-footnote">No account needed to view the content.</p>` : ""}
|
|
676
|
+
<div class="signin-form"><div><div class="signin-mobile-masthead">${masthead(config.logo, config.title)}</div>${banner()}<h2>Sign in to ${escape(config.title)}</h2><form id="signin">${credentialFields({email: signInEmail, password: signInPassword}, {passkey: passkeyHere})}</form>${config.allowAnonymous ? `<div class="signin-divider"><span>or explore first</span></div><button class="anonymous-entry" id="anonymous">Enter anonymously <span aria-hidden="true">→</span></button><p class="signin-footnote">No account needed to view the content.</p>` : ""}
|
|
678
677
|
${config.title === "Fidj" ? walletDoor() : ""}</div>
|
|
679
678
|
${badgeStrip(config.badges)}</div></section>`;
|
|
680
679
|
wireNav();
|
|
@@ -809,7 +808,12 @@ function render() {
|
|
|
809
808
|
signInThroughProvider(submitter);
|
|
810
809
|
return;
|
|
811
810
|
}
|
|
812
|
-
|
|
811
|
+
// Enter arrives through the unseen twin of Continue; the wait is shown on
|
|
812
|
+
// the Continue a person can see.
|
|
813
|
+
const shown = submitter?.classList.contains("implicit-submit")
|
|
814
|
+
? root.querySelector<HTMLButtonElement>('button.primary[value="credentials"]')
|
|
815
|
+
: submitter;
|
|
816
|
+
shown?.setAttribute("data-busy", "true");
|
|
813
817
|
void action(async () => {
|
|
814
818
|
if (oidc && (!email || !password)) {
|
|
815
819
|
throw new Error("Enter your email and password, or sign in with Fidj.");
|
|
@@ -208,7 +208,7 @@ function render() {
|
|
|
208
208
|
<main>${notice ? `<p class="notice" role="status">${escape(notice)}</p>` : ""}${error ? `<p class="error" role="alert">${escape(error)}</p>` : ""}
|
|
209
209
|
${
|
|
210
210
|
!session
|
|
211
|
-
? `<section class="welcome"><div><p class="eyebrow">A LITTLE SPACE FOR YOUR IDEAS</p><h1>Good ideas<br>start here.</h1><p>Keep your notes together, with access you understand and privacy you control.</p><div class="promise"><img src="/fidj-logo.png" alt=""><span>Your account connects through Fidj.<br>Your choices belong to this app.</span></div></div><form id="signin" class="card"><h2>Welcome to ${escape(settings.title)}</h2>${oidc ? providerEntry(settings.title, settings.appId, settings.signin === "button" ? "" : credentialFields(), false, settings.signin || "button") : `<p>Sign in with your Fidj account.</p>${credentialFields()}<button class="primary" type="submit">Sign in</button>${settings.localDemo ? `<div class="demo"><strong>Try the local example</strong><p>Alex owns the app. Maya and Sam start
|
|
211
|
+
? `<section class="welcome"><div><p class="eyebrow">A LITTLE SPACE FOR YOUR IDEAS</p><h1>Good ideas<br>start here.</h1><p>Keep your notes together, with access you understand and privacy you control.</p><div class="promise"><img src="/fidj-logo.png" alt=""><span>Your account connects through Fidj.<br>Your choices belong to this app.</span></div></div><form id="signin" class="card"><h2>Welcome to ${escape(settings.title)}</h2>${oidc ? providerEntry(settings.title, settings.appId, settings.signin === "button" ? "" : credentialFields(), false, settings.signin || "button") : `<p>Sign in with your Fidj account.</p>${credentialFields()}<button class="primary" type="submit">Sign in</button>${settings.localDemo ? `<div class="demo"><strong>Try the local example</strong><p>Alex owns the app. Maya and Sam start as members.</p><button type="button" data-demo="alex">Alex · owner</button><button type="button" data-demo="maya">Maya · member</button><button type="button" data-demo="sam">Sam · member</button></div>` : ""}`}</form></section>`
|
|
212
212
|
: `
|
|
213
213
|
<div class="page-heading"><div><p class="eyebrow">YOUR WORKSPACE</p><h1>A place to think.</h1><p>${escape(session.username)} <span class="roles">${session.roles.map(escape).join(" · ") || "No assigned roles"}</span></p></div><button id="signout">Sign out</button></div>
|
|
214
214
|
<nav><button id="workspace-tab" class="${view === "workspace" ? "selected" : ""}">My notes</button><button id="privacy-tab" class="${view === "privacy" ? "selected" : ""}">My privacy</button><button id="refresh">Refresh access</button></nav>
|
package/lib/module-mount.cjs
CHANGED
|
@@ -27,7 +27,9 @@ function describeMount(html, file) {
|
|
|
27
27
|
for (const tag of head.match(/<link\b[^>]*>/gi) || []) {
|
|
28
28
|
const rel = (attribute(tag, "rel") || "").toLowerCase();
|
|
29
29
|
const href = attribute(tag, "href");
|
|
30
|
-
|
|
30
|
+
// Once each: Angular repeats its deferred stylesheet inside <noscript>.
|
|
31
|
+
if (rel === "stylesheet" && href && !styles.includes(resolve(href)))
|
|
32
|
+
styles.push(resolve(href));
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const scripts = [];
|