@profullstack/footer 0.2.3 → 0.2.5
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/package.json +1 -1
- package/src/index.js +37 -0
- package/src/template.js +4 -1
- package/template.json +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/footer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One footer for every Profullstack site: links, copyright and the << Profullstack >> webring nav with Random. Sites render the @latest template from the CDN at runtime, so a release updates every site without a redeploy.",
|
|
6
6
|
"keywords": ["footer", "webring", "openwebring", "copyright", "react", "nextjs", "hono", "ssr"],
|
package/src/index.js
CHANGED
|
@@ -54,6 +54,11 @@ export async function latestTemplate(opts = {}) {
|
|
|
54
54
|
const env = typeof process !== 'undefined' ? process.env ?? {} : {};
|
|
55
55
|
if (env.PROFULLSTACK_FOOTER_OFFLINE === '1') return bundled;
|
|
56
56
|
const now = (opts.now ?? Date.now)();
|
|
57
|
+
// Under Next.js, always go through fetch: Next caches it (revalidate 300 s)
|
|
58
|
+
// AND marks the route as revalidating. Answering from memory instead left
|
|
59
|
+
// every page rendered after the first at build time fully static
|
|
60
|
+
// (s-maxage=31536000), so it never saw a template release.
|
|
61
|
+
if (!opts.fetch && underNext()) return fetchFresh(opts, env, now);
|
|
57
62
|
if (cache.template && now < cache.until) return cache.template;
|
|
58
63
|
// Stale while revalidating: hand back what we have, refresh once.
|
|
59
64
|
if (cache.template && cache.pending) return cache.template;
|
|
@@ -87,6 +92,38 @@ export async function latestTemplate(opts = {}) {
|
|
|
87
92
|
return cache.template ?? cache.pending;
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
/** Next.js patches fetch (and marks it) and sets NEXT_RUNTIME in its servers. */
|
|
96
|
+
function underNext() {
|
|
97
|
+
// @ts-ignore Next's marker on its patched fetch
|
|
98
|
+
if (globalThis.fetch && globalThis.fetch.__nextPatched) return true;
|
|
99
|
+
// Written out literally so Next's compile-time define can replace it.
|
|
100
|
+
return typeof process !== 'undefined' && !!process.env.NEXT_RUNTIME;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One uncached-by-us fetch, for runtimes (Next) that cache fetch themselves. */
|
|
104
|
+
async function fetchFresh(opts, env, now) {
|
|
105
|
+
try {
|
|
106
|
+
const ctrl = typeof AbortController === 'function' ? new AbortController() : null;
|
|
107
|
+
const timer = ctrl ? setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 1500) : null;
|
|
108
|
+
const res = await globalThis
|
|
109
|
+
.fetch(opts.url ?? env.PROFULLSTACK_FOOTER_TEMPLATE_URL ?? TEMPLATE_URL, {
|
|
110
|
+
signal: ctrl?.signal,
|
|
111
|
+
headers: { accept: 'application/json' },
|
|
112
|
+
// @ts-ignore Next's fetch extension
|
|
113
|
+
next: { revalidate: NEXT_REVALIDATE_S },
|
|
114
|
+
})
|
|
115
|
+
.finally(() => timer && clearTimeout(timer));
|
|
116
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
117
|
+
const t = await res.json();
|
|
118
|
+
if (!usable(t)) throw new Error('template not usable by this renderer');
|
|
119
|
+
cache.template = t;
|
|
120
|
+
cache.until = now + (opts.ttlMs ?? DEFAULT_TTL_MS);
|
|
121
|
+
return t;
|
|
122
|
+
} catch {
|
|
123
|
+
return cache.template ?? bundled;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
90
127
|
/** Forget the cached template (tests, or a manual refresh hook). */
|
|
91
128
|
export function resetTemplateCache() {
|
|
92
129
|
cache.template = null;
|
package/src/template.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// render.js does not fill; older installs then keep their bundled template.
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
|
-
version: '0.2.
|
|
12
|
+
version: '0.2.5',
|
|
13
13
|
renderer: 1,
|
|
14
14
|
css: [
|
|
15
15
|
'.pfs-footer{--pfs-footer-muted:color-mix(in srgb,currentColor 62%,transparent);--pfs-footer-gap:.5rem 1.25rem;--pfs-footer-size:.875rem;',
|
|
@@ -19,6 +19,8 @@ export default {
|
|
|
19
19
|
'.pfs-footer a:hover,.pfs-footer a:focus-visible{color:inherit;text-decoration:underline}',
|
|
20
20
|
'.pfs-footer__links{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--pfs-footer-gap)}',
|
|
21
21
|
'.pfs-footer__ring{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:.6rem}',
|
|
22
|
+
'.pfs-footer a.pfs-footer__badge{display:inline-block;line-height:0}',
|
|
23
|
+
'.pfs-footer__badge img{display:block;width:200px;height:44px;max-width:100%}',
|
|
22
24
|
'.pfs-footer__copy{margin:0;color:var(--pfs-footer-muted)}',
|
|
23
25
|
'.pfs-footer__copy a{color:inherit}',
|
|
24
26
|
].join(''),
|
|
@@ -33,6 +35,7 @@ export default {
|
|
|
33
35
|
'<a href="{{ring}}">{{ring_name}}</a>',
|
|
34
36
|
'<a href="{{next}}" rel="next" title="Next site">>></a>',
|
|
35
37
|
'</nav>',
|
|
38
|
+
'<a class="pfs-footer__badge" href="https://built.indie.pit.world/for?site={{site}}"><img src="https://built.indie.pit.world/for.svg?site={{site}}" alt="Listed on built.indie" width="200" height="44" loading="lazy"></a>',
|
|
36
39
|
'<p class="pfs-footer__copy">© {{years}} <a href="{{owner_url}}">{{owner_name}}</a>{{#tagline}} · {{tagline}}{{/tagline}}</p>',
|
|
37
40
|
'</div>',
|
|
38
41
|
'</footer>',
|
package/template.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.5",
|
|
3
3
|
"renderer": 1,
|
|
4
|
-
"css": ".pfs-footer{--pfs-footer-muted:color-mix(in srgb,currentColor 62%,transparent);--pfs-footer-gap:.5rem 1.25rem;--pfs-footer-size:.875rem;border-top:1px solid color-mix(in srgb,currentColor 14%,transparent);font-size:var(--pfs-footer-size);line-height:1.5;color:inherit}.pfs-footer__inner{max-width:72rem;margin:0 auto;padding:1.5rem 1rem;display:flex;flex-direction:column;align-items:center;gap:.75rem;text-align:center}.pfs-footer a{color:var(--pfs-footer-muted);text-decoration:none}.pfs-footer a:hover,.pfs-footer a:focus-visible{color:inherit;text-decoration:underline}.pfs-footer__links{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--pfs-footer-gap)}.pfs-footer__ring{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:.6rem}.pfs-footer__copy{margin:0;color:var(--pfs-footer-muted)}.pfs-footer__copy a{color:inherit}",
|
|
4
|
+
"css": ".pfs-footer{--pfs-footer-muted:color-mix(in srgb,currentColor 62%,transparent);--pfs-footer-gap:.5rem 1.25rem;--pfs-footer-size:.875rem;border-top:1px solid color-mix(in srgb,currentColor 14%,transparent);font-size:var(--pfs-footer-size);line-height:1.5;color:inherit}.pfs-footer__inner{max-width:72rem;margin:0 auto;padding:1.5rem 1rem;display:flex;flex-direction:column;align-items:center;gap:.75rem;text-align:center}.pfs-footer a{color:var(--pfs-footer-muted);text-decoration:none}.pfs-footer a:hover,.pfs-footer a:focus-visible{color:inherit;text-decoration:underline}.pfs-footer__links{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--pfs-footer-gap)}.pfs-footer__ring{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:.6rem}.pfs-footer a.pfs-footer__badge{display:inline-block;line-height:0}.pfs-footer__badge img{display:block;width:200px;height:44px;max-width:100%}.pfs-footer__copy{margin:0;color:var(--pfs-footer-muted)}.pfs-footer__copy a{color:inherit}",
|
|
5
5
|
"link": "<a href=\"{{href}}\"{{#rel}} rel=\"{{rel}}\"{{/rel}}>{{label}}</a>",
|
|
6
|
-
"footer": "<footer class=\"pfs-footer\" data-pfs-footer=\"{{version}}\">{{#css}}<style{{#nonce}} nonce=\"{{nonce}}\"{{/nonce}}>{{{css}}}</style>{{/css}}<div class=\"pfs-footer__inner\">{{#links}}<nav class=\"pfs-footer__links\" aria-label=\"Footer\">{{{links}}}</nav>{{/links}}<nav class=\"webring pfs-footer__ring\" aria-label=\"{{ring_name}} webring\"><a href=\"{{previous}}\" rel=\"prev\" title=\"Previous site\"><<</a><a href=\"{{ring}}\">{{ring_name}}</a><a href=\"{{next}}\" rel=\"next\" title=\"Next site\">>></a></nav><p class=\"pfs-footer__copy\">© {{years}} <a href=\"{{owner_url}}\">{{owner_name}}</a>{{#tagline}} · {{tagline}}{{/tagline}}</p></div></footer>",
|
|
6
|
+
"footer": "<footer class=\"pfs-footer\" data-pfs-footer=\"{{version}}\">{{#css}}<style{{#nonce}} nonce=\"{{nonce}}\"{{/nonce}}>{{{css}}}</style>{{/css}}<div class=\"pfs-footer__inner\">{{#links}}<nav class=\"pfs-footer__links\" aria-label=\"Footer\">{{{links}}}</nav>{{/links}}<nav class=\"webring pfs-footer__ring\" aria-label=\"{{ring_name}} webring\"><a href=\"{{previous}}\" rel=\"prev\" title=\"Previous site\"><<</a><a href=\"{{ring}}\">{{ring_name}}</a><a href=\"{{next}}\" rel=\"next\" title=\"Next site\">>></a></nav><a class=\"pfs-footer__badge\" href=\"https://built.indie.pit.world/for?site={{site}}\"><img src=\"https://built.indie.pit.world/for.svg?site={{site}}\" alt=\"Listed on built.indie\" width=\"200\" height=\"44\" loading=\"lazy\"></a><p class=\"pfs-footer__copy\">© {{years}} <a href=\"{{owner_url}}\">{{owner_name}}</a>{{#tagline}} · {{tagline}}{{/tagline}}</p></div></footer>",
|
|
7
7
|
"houseLinks": []
|
|
8
8
|
}
|