@symbo.ls/mermaid 3.14.0 → 3.14.4
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 +7 -3
- package/src/bundle.js +116 -17
- package/src/render.js +103 -12
- package/src/template.html +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/mermaid",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "CC-BY-NC-4.0",
|
|
6
6
|
"description": "Self-sustaining server runner for Symbols projects from JSON. Renders DOMQL projects to HTML with optional brender SSR.",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"./utils/logger": "./src/utils/logger.js",
|
|
18
18
|
"./template.html": "./src/template.html"
|
|
19
19
|
},
|
|
20
|
-
"files": [
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"src",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
21
25
|
"scripts": {
|
|
22
26
|
"start": "node bin/start.js"
|
|
23
27
|
},
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
"mermaid": "./bin/start.js"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@symbo.ls/utils": "^3.14.
|
|
32
|
+
"@symbo.ls/utils": "^3.14.2",
|
|
29
33
|
"cors": "^2.8.5",
|
|
30
34
|
"express": "^4.21.2"
|
|
31
35
|
},
|
package/src/bundle.js
CHANGED
|
@@ -6,6 +6,15 @@ import smblsIife from 'smbls/iife-string';
|
|
|
6
6
|
|
|
7
7
|
log.debug(`[BUNDLE] smbls IIFE loaded from smbls/iife-string (${smblsIife.length} bytes)`);
|
|
8
8
|
|
|
9
|
+
// FRAMEWORK-6: any `<` in JSON that gets inlined into a `<script>` block can
|
|
10
|
+
// terminate the wrapper (`</script>`) or trigger HTML-comment / CDATA modes
|
|
11
|
+
// (`<!--`, `<![CDATA[`) inside the script-data state. Replacing every `<`
|
|
12
|
+
// with the JS unicode escape `<` neutralizes HTML interpretation while
|
|
13
|
+
// preserving the runtime string value — `<` decodes to `<` at JS parse
|
|
14
|
+
// time, so the embedded object is identical to the original. Standard
|
|
15
|
+
// mitigation: see OWASP §RULE-3.1, React's renderToString, Rails json_escape.
|
|
16
|
+
const escapeJsonForScript = (s) => s.replace(/</g, '\\u003c');
|
|
17
|
+
|
|
9
18
|
export function getSmblsIife() {
|
|
10
19
|
return smblsIife;
|
|
11
20
|
}
|
|
@@ -45,17 +54,63 @@ export function getBundleScript(appKey, data, req, { ProjectDataService } = {})
|
|
|
45
54
|
log.error(`[BUNDLE] Failed to ensure merged data: ${mergeError.message}`);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
// Serialize the project data as JSON (same as templates __mockedData approach)
|
|
49
|
-
|
|
57
|
+
// Serialize the project data as JSON (same as templates __mockedData approach).
|
|
58
|
+
// escapeJsonForScript: see FRAMEWORK-6 — any `</script>` (or `<!--`) inside a
|
|
59
|
+
// string value would break out of the inline `<script>` wrapper.
|
|
60
|
+
const jsonData = escapeJsonForScript(JSON.stringify(data));
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
/*
|
|
63
|
+
* Determine liveSync setting.
|
|
64
|
+
*
|
|
65
|
+
* Realtime is enabled on Symbols-managed dev hosts (any *.symbo.ls env,
|
|
66
|
+
* *.preview.symbols.app, and localhost / *.localhost for local dev) and
|
|
67
|
+
* disabled on custom domains so we don't leak the realtime backend to
|
|
68
|
+
* third-party hosts. Post-§45 the appkey is a bare `owner/key` slug, so
|
|
69
|
+
* gating on `appKey.endsWith('symbo.ls')` would always evaluate false
|
|
70
|
+
* (rendering all browsers blind). The signal we want is the request host.
|
|
71
|
+
*/
|
|
72
|
+
// Resolve incoming host across Express (req.headers.host),
|
|
73
|
+
// Cloudflare Workers / Fetch (req.headers is a Headers instance with .get),
|
|
74
|
+
// and plain options bags ({ host }). Falls back to req.url parsing so a
|
|
75
|
+
// missing Host header still degrades to a URL-based read instead of ''.
|
|
76
|
+
const rawHost =
|
|
77
|
+
(req?.headers && typeof req.headers.get === 'function' && req.headers.get('host')) ||
|
|
78
|
+
req?.headers?.host ||
|
|
79
|
+
req?.host ||
|
|
80
|
+
(req?.url && (() => { try { return new URL(req.url).host; } catch { return ''; } })()) ||
|
|
81
|
+
'';
|
|
82
|
+
const host = String(rawHost).toLowerCase().split(':')[0];
|
|
83
|
+
// Express exposes parsed query on req.query; CF Workers expose req.url only —
|
|
84
|
+
// pull `release` / `version` from a parsed URL when the structured form is
|
|
85
|
+
// absent so feature flags work in both runtimes.
|
|
86
|
+
const queryFrom = (() => {
|
|
87
|
+
if (req?.query) return req.query;
|
|
88
|
+
try { return Object.fromEntries(new URL(req?.url || '', 'http://x').searchParams); } catch { return {}; }
|
|
89
|
+
})();
|
|
90
|
+
const release = queryFrom.release || false;
|
|
91
|
+
const version = queryFrom.version;
|
|
92
|
+
const isSymboLs =
|
|
93
|
+
host.endsWith('.symbo.ls') ||
|
|
94
|
+
host.endsWith('.preview.symbols.app') ||
|
|
95
|
+
host === 'localhost' ||
|
|
96
|
+
host.endsWith('.localhost');
|
|
55
97
|
const shouldDisableLiveSync = Boolean(release) || Boolean(version) || !isSymboLs;
|
|
56
98
|
|
|
99
|
+
// Inject the persisted `_id` so the realtime client can hand the socket
|
|
100
|
+
// server a concrete projectId (mongo _id) on handshake. Without this, the
|
|
101
|
+
// server falls back to a `/v1/projects/by-key/...` lookup that 404s on the
|
|
102
|
+
// current API and drops the connection mid-join.
|
|
103
|
+
const projectId =
|
|
104
|
+
(data && (data._id || data.projectId || data.id)) ||
|
|
105
|
+
null;
|
|
57
106
|
const settingsStr = isSymboLs
|
|
58
|
-
? JSON.stringify({
|
|
107
|
+
? escapeJsonForScript(JSON.stringify({
|
|
108
|
+
...data.settings,
|
|
109
|
+
liveSync: !shouldDisableLiveSync,
|
|
110
|
+
inspect: true,
|
|
111
|
+
verbose: !shouldDisableLiveSync,
|
|
112
|
+
projectId,
|
|
113
|
+
}))
|
|
59
114
|
: 'null';
|
|
60
115
|
|
|
61
116
|
// Generate the client script - same pattern as templates/start.js runFromJson
|
|
@@ -80,24 +135,68 @@ export function getBundleScript(appKey, data, req, { ProjectDataService } = {})
|
|
|
80
135
|
var settings = ${settingsStr};
|
|
81
136
|
if (settings) context.editor = settings;
|
|
82
137
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
138
|
+
// Theme initialization: set data-theme before app renders to avoid FOUC.
|
|
139
|
+
// Mirrors the precedence in resolveAndApplyTheme (packages/smbls/src/index.js):
|
|
140
|
+
// 1. URL param ?globalTheme=<name> — embedded-iframe override
|
|
141
|
+
// 2. localStorage[themeStorageKey] — persisted user choice
|
|
142
|
+
// 3. context.globalTheme — project's configured value
|
|
143
|
+
// 4. matchMedia(prefers-color-scheme: dark) — OS fallback (when 'auto')
|
|
144
|
+
//
|
|
145
|
+
// Writing the literal 'auto' as data-theme would leave the
|
|
146
|
+
// [data-theme="dark"] / [data-theme="light"] CSS selectors unmatched and
|
|
147
|
+
// the page would render with system defaults instead of the chosen
|
|
148
|
+
// theme — so 'auto' is always resolved to a concrete light/dark value.
|
|
149
|
+
//
|
|
150
|
+
// PRIOR BUG: this script previously ignored context.globalTheme entirely
|
|
151
|
+
// and went straight to matchMedia after localStorage. A project with
|
|
152
|
+
// globalTheme: light in config.js would render in OS dark on a
|
|
153
|
+
// dark-mode device because the inline boot ran first and locked in
|
|
154
|
+
// dark before resolveAndApplyTheme could correct it.
|
|
155
|
+
//
|
|
156
|
+
// IDEMPOTENT GUARD: the SSR-side render in @symbo.ls/mermaid/render.js
|
|
157
|
+
// now bakes data-theme onto <html> for concrete-theme projects and
|
|
158
|
+
// emits an inline <head> script that runs synchronously before paint.
|
|
159
|
+
// When that path runs, data-theme is already set by the time the bundle
|
|
160
|
+
// executes — skip this block to avoid re-resolving and overwriting
|
|
161
|
+
// (which would race with URL/localStorage values the head script
|
|
162
|
+
// already honored). Kept as a safety net for older renders / cases
|
|
163
|
+
// where the head script couldn't run.
|
|
164
|
+
if (
|
|
165
|
+
context.useDocumentTheme !== false &&
|
|
166
|
+
!document.documentElement.hasAttribute('data-theme')
|
|
167
|
+
) {
|
|
90
168
|
(function() {
|
|
91
169
|
try {
|
|
92
170
|
var t = null;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
171
|
+
// 1. URL param wins (used by preview/canvas iframe embedding).
|
|
172
|
+
try {
|
|
173
|
+
var p = new URLSearchParams(window.location.search).get('globalTheme');
|
|
174
|
+
if (p) t = p;
|
|
175
|
+
} catch(e) {}
|
|
176
|
+
// 2. localStorage — only consulted when no forcing source above.
|
|
177
|
+
if (!t) {
|
|
178
|
+
var key = context.themeStorageKey;
|
|
179
|
+
if (key) { try { t = localStorage.getItem(key) } catch(e) {} }
|
|
180
|
+
}
|
|
181
|
+
// 3. Project's configured globalTheme (config.js export). Honors an
|
|
182
|
+
// explicit 'light'/'dark'/<scheme> instead of falling through to OS.
|
|
183
|
+
if (!t && context.globalTheme && context.globalTheme !== 'auto') {
|
|
184
|
+
t = context.globalTheme;
|
|
185
|
+
}
|
|
186
|
+
// 4. OS preference fallback — only when nothing concrete chose.
|
|
187
|
+
if (!t || t === 'auto') {
|
|
188
|
+
t = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
189
|
+
}
|
|
96
190
|
if (t) document.documentElement.setAttribute('data-theme', t);
|
|
97
191
|
} catch(e) {}
|
|
98
192
|
})();
|
|
99
193
|
}
|
|
100
194
|
|
|
195
|
+
// When brender pre-rendered this page, adopt the existing DOM nodes
|
|
196
|
+
// instead of tearing them down. The [data-ssr] wrapper is moved out of
|
|
197
|
+
// the way so pre-rendered children land at document.body level — matching
|
|
198
|
+
// the level at which DOMQL creates its own elements. The __BRENDER__ flag
|
|
199
|
+
// and __BR_REGISTRY__ map were injected into <head> by the server.
|
|
101
200
|
var ssrShell = document.querySelector('[data-ssr]');
|
|
102
201
|
if (window.__BRENDER__ && ssrShell) {
|
|
103
202
|
// Move pre-rendered children to body before DOMQL skeleton creation
|
package/src/render.js
CHANGED
|
@@ -37,12 +37,22 @@ export { generateSitemap, prefetchPageData };
|
|
|
37
37
|
// ── Default HTML template ───────────────────────────────────────────────────
|
|
38
38
|
// Inlined so the plugin works in any runtime (Node, CF Workers, browser).
|
|
39
39
|
// Mirrors src/template.html — keep in sync.
|
|
40
|
+
//
|
|
41
|
+
// ROOT_ATTRS + HEAD_BOOT eliminate the FOUC that used to happen between
|
|
42
|
+
// HTML-parse-finish and the end-of-body bundle IIFE running (~1s on a
|
|
43
|
+
// 2.5 MB bundle). For projects with a concrete `globalTheme` (e.g. 'light'),
|
|
44
|
+
// SSR bakes `<html data-theme="light">` so first paint is on the right
|
|
45
|
+
// theme. For 'auto' projects, the inline HEAD_BOOT script resolves URL
|
|
46
|
+
// override / localStorage / matchMedia synchronously before any CSS
|
|
47
|
+
// applies. The end-of-body IIFE in bundle.js is kept as an idempotent
|
|
48
|
+
// safety net.
|
|
40
49
|
export const DEFAULT_TEMPLATE_HTML = `<!doctype html>
|
|
41
|
-
<html>
|
|
50
|
+
<html'\${{{ ROOT_ATTRS }}}'>
|
|
42
51
|
<head>
|
|
52
|
+
<meta charset="UTF-8" />
|
|
53
|
+
'\${{{ HEAD_BOOT }}}'
|
|
43
54
|
'\${{{ META }}}'
|
|
44
55
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
45
|
-
<meta charset="UTF-8" />
|
|
46
56
|
'\${{{ IMPORTMAP }}}'
|
|
47
57
|
</head>
|
|
48
58
|
<body>
|
|
@@ -113,7 +123,12 @@ export function transpileFunctionStrings(obj, target = Array.isArray(obj) ? [] :
|
|
|
113
123
|
* @param {object} options
|
|
114
124
|
* @param {string} options.appkey — appkey identifier (for metadata, comments)
|
|
115
125
|
* @param {string} options.pathname — request pathname
|
|
116
|
-
* @param {string} [options.channel='production'] —
|
|
126
|
+
* @param {string} [options.channel='production'] — sysEnv label (production|upcoming|next|...);
|
|
127
|
+
* informational, used for metadata. NOT the same axis as userEnv — sysEnv is which
|
|
128
|
+
* deployment served the request, userEnv is which version of project content.
|
|
129
|
+
* @param {string} [options.userEnv='production'] — projectEnv (production|staging|development|...);
|
|
130
|
+
* drives the SSR-vs-CSR decision and the `<!-- symbols | … -->` comment. Defaults
|
|
131
|
+
* to channel for back-compat with callers that haven't been updated.
|
|
117
132
|
* @param {boolean} [options.isProd=false] — controls SEO indexing (custom domains only)
|
|
118
133
|
* @param {object} [options.req] — full request object (Express-like) for getBundleScript context
|
|
119
134
|
* @param {object} [options.query] — query params (when no req available)
|
|
@@ -131,6 +146,7 @@ export async function renderProjectHtml(data, options = {}) {
|
|
|
131
146
|
appkey,
|
|
132
147
|
pathname = '/',
|
|
133
148
|
channel = 'production',
|
|
149
|
+
userEnv = channel,
|
|
134
150
|
isProd = false,
|
|
135
151
|
req,
|
|
136
152
|
query,
|
|
@@ -156,7 +172,12 @@ export async function renderProjectHtml(data, options = {}) {
|
|
|
156
172
|
|
|
157
173
|
const route = normalizeRoute(pathname);
|
|
158
174
|
const resolved = resolvePageRoute(data.pages, pathname);
|
|
159
|
-
|
|
175
|
+
// SSR is a projectEnv decision — render published (production) and pre-published
|
|
176
|
+
// (staging) slots server-side; render dev slot client-side. Was previously
|
|
177
|
+
// checking `channel` (sysEnv), which gave wrong answers on canonical hosts
|
|
178
|
+
// like *.upcoming.symbo.ls (sysEnv=upcoming, userEnv=production → was CSR
|
|
179
|
+
// when it should have been SSR).
|
|
180
|
+
const shouldBrender = userEnv === 'production' || userEnv === 'staging';
|
|
160
181
|
|
|
161
182
|
// ── Brender / SSR ─────────────────────────────────────────────────────────
|
|
162
183
|
let prerenderedBody = '';
|
|
@@ -309,7 +330,10 @@ export async function renderProjectHtml(data, options = {}) {
|
|
|
309
330
|
const bundle = getBundleScript(appkey, data, req, { ProjectDataService });
|
|
310
331
|
|
|
311
332
|
// ── Importmap ─────────────────────────────────────────────────────────────
|
|
312
|
-
|
|
333
|
+
// esm.sh is the default — it resolves transitive deps correctly and matches
|
|
334
|
+
// the `context.packageManager = 'esm.sh'` set by bundle.js at runtime.
|
|
335
|
+
// Skypack frequently 500s on transitive deps (e.g. lit-html via ninja-keys).
|
|
336
|
+
const importmap = getImportMapScript(data, data?.settings?.cdnProvider || 'esmsh');
|
|
313
337
|
|
|
314
338
|
// ── HTML template ─────────────────────────────────────────────────────────
|
|
315
339
|
const indexHtmlFromFiles = data?.files && data.files['index.html'];
|
|
@@ -356,13 +380,68 @@ export async function renderProjectHtml(data, options = {}) {
|
|
|
356
380
|
? `<script defer type="text/javascript">${editorBundle}</script>`
|
|
357
381
|
: '';
|
|
358
382
|
|
|
383
|
+
// Comment label is the projectEnv (which version of the content is being
|
|
384
|
+
// served). Was previously `${channel}` (sysEnv) which mislabelled canonical
|
|
385
|
+
// *.upcoming.symbo.ls hosts as "upcoming" when the actual projectEnv is
|
|
386
|
+
// "production".
|
|
359
387
|
const envComment = isProd
|
|
360
388
|
? `<!-- built with symbols.app -->\n`
|
|
361
|
-
: `<!-- symbols | ${appkey} | ${
|
|
389
|
+
: `<!-- symbols | ${appkey} | ${userEnv} | ${shouldBrender ? 'ssr' : 'csr'} | noindex -->\n`;
|
|
390
|
+
|
|
391
|
+
// ── Theme: zero-flash boot ─────────────────────────────────────────────────
|
|
392
|
+
// Eliminates the dark→light (or light→dark) flash that used to happen
|
|
393
|
+
// between first paint and the end-of-body bundle IIFE setting data-theme.
|
|
394
|
+
// Two layers:
|
|
395
|
+
// 1. Concrete `globalTheme` ('light'|'dark'|<custom>) → bake `data-theme`
|
|
396
|
+
// onto <html> server-side. Zero JS, perfect first paint.
|
|
397
|
+
// 2. URL ?globalTheme= override / localStorage user choice / 'auto' OS
|
|
398
|
+
// pref → small inline <head> script resolves synchronously before
|
|
399
|
+
// any CSS applies. Browser renders first paint with the resolved
|
|
400
|
+
// data-theme, no flash.
|
|
401
|
+
// Layer 3 (existing end-of-body IIFE in bundle.js) is the back-compat
|
|
402
|
+
// safety net for cases the head script didn't reach.
|
|
403
|
+
const useDocumentTheme = data.useDocumentTheme !== false;
|
|
404
|
+
const concreteTheme =
|
|
405
|
+
data.globalTheme && data.globalTheme !== 'auto' ? String(data.globalTheme) : null;
|
|
406
|
+
const themeStorageKey = data.themeStorageKey ? String(data.themeStorageKey) : '';
|
|
407
|
+
|
|
408
|
+
// SSR-baked attribute on <html>. Only when the project has a concrete
|
|
409
|
+
// theme — for 'auto' we leave <html> bare so the head script (or
|
|
410
|
+
// CSS @media) can decide. Custom schemes (e.g. 'ocean') flow through.
|
|
411
|
+
const rootAttrs =
|
|
412
|
+
useDocumentTheme && concreteTheme ? ' data-theme="' + concreteTheme + '"' : '';
|
|
413
|
+
|
|
414
|
+
// Inline head boot. Runs synchronously during HTML parse, before any
|
|
415
|
+
// CSS applies. Order matches resolveAndApplyTheme in
|
|
416
|
+
// smbls/packages/smbls/src/index.js:
|
|
417
|
+
// 1. ?globalTheme= URL param (preview/editor iframes push themes here)
|
|
418
|
+
// 2. localStorage[themeStorageKey] (persisted user toggle)
|
|
419
|
+
// 3. concreteTheme baked from project config (for 'auto' projects this
|
|
420
|
+
// is null and we fall to step 4)
|
|
421
|
+
// 4. matchMedia(prefers-color-scheme: dark) — OS fallback
|
|
422
|
+
// Skipped entirely when useDocumentTheme is false.
|
|
423
|
+
const headBoot = useDocumentTheme
|
|
424
|
+
? '<script>(function(){try{var t=null;' +
|
|
425
|
+
"try{t=new URLSearchParams(location.search).get('globalTheme')}catch(e){}" +
|
|
426
|
+
(themeStorageKey
|
|
427
|
+
? 'if(!t){try{t=localStorage.getItem(' +
|
|
428
|
+
JSON.stringify(themeStorageKey) +
|
|
429
|
+
')}catch(e){}}'
|
|
430
|
+
: '') +
|
|
431
|
+
"if(!t||t==='auto'){" +
|
|
432
|
+
(concreteTheme
|
|
433
|
+
? 't=' + JSON.stringify(concreteTheme) + ';'
|
|
434
|
+
: "try{t=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}catch(e){}") +
|
|
435
|
+
'}' +
|
|
436
|
+
"if(t&&t!=='auto')document.documentElement.setAttribute('data-theme',t);" +
|
|
437
|
+
'}catch(e){}})();</script>\n <meta name="color-scheme" content="light dark">'
|
|
438
|
+
: '';
|
|
362
439
|
|
|
363
440
|
const html = replaceVariablesInFileSync(
|
|
364
441
|
envComment + indexHtml,
|
|
365
442
|
{
|
|
443
|
+
ROOT_ATTRS: rootAttrs,
|
|
444
|
+
HEAD_BOOT: headBoot,
|
|
366
445
|
META: metaTags + '\n' + brenderStyles + '\n' + hydrationScript + '\n' + authScript,
|
|
367
446
|
BUNDLE: bundle,
|
|
368
447
|
BODY: prerenderedBody,
|
|
@@ -378,23 +457,35 @@ export async function renderProjectHtml(data, options = {}) {
|
|
|
378
457
|
}
|
|
379
458
|
|
|
380
459
|
/**
|
|
381
|
-
* Render the "project not published" 404 fallback page.
|
|
460
|
+
* Render the "project not published" 404 fallback page. When `reason` is
|
|
461
|
+
* 'not_public', renders a tailored "site not yet public" message —
|
|
462
|
+
* specifically for paid custom domains pointing at a project whose
|
|
463
|
+
* visibility is still private.
|
|
382
464
|
*/
|
|
383
|
-
export function renderNotPublishedHtml(appkey, channel = 'production') {
|
|
465
|
+
export function renderNotPublishedHtml(appkey, channel = 'production', reason = null) {
|
|
384
466
|
const envLabel = channel !== 'production' ? ` (${channel})` : '';
|
|
467
|
+
const isNotPublic = reason === 'not_public';
|
|
468
|
+
const title = isNotPublic ? 'Site Not Yet Public' : 'Not Published';
|
|
469
|
+
const heading = isNotPublic
|
|
470
|
+
? `This site isn't public yet${envLabel}`
|
|
471
|
+
: `This project hasn't been published yet${envLabel}`;
|
|
472
|
+
const body = isNotPublic
|
|
473
|
+
? `The owner has connected this domain but the project's visibility is set to private. Once they switch it to public in the workspace settings, the site will appear here automatically.`
|
|
474
|
+
: `Run <code>smbls publish</code> to deploy your project, or visit <a href="https://symbols.app">symbols.app</a> to get started.`;
|
|
385
475
|
return `<!doctype html>
|
|
386
476
|
<html>
|
|
387
477
|
<head>
|
|
388
478
|
<meta charset="UTF-8">
|
|
389
479
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
390
480
|
<meta name="robots" content="noindex">
|
|
391
|
-
<title
|
|
481
|
+
<title>${title}</title>
|
|
392
482
|
<style>
|
|
393
483
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
394
484
|
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0a0a0a; color: #fafafa; }
|
|
395
|
-
.container { text-align: center; max-width:
|
|
485
|
+
.container { text-align: center; max-width: 520px; padding: 2rem; }
|
|
396
486
|
h1 { font-size: 1.25rem; font-weight: 500; margin-bottom: 0.75rem; }
|
|
397
487
|
p { color: #888; font-size: 0.875rem; line-height: 1.6; }
|
|
488
|
+
code { background: #1a1a1a; padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.8rem; color: #ccc; }
|
|
398
489
|
a { color: #888; text-decoration: underline; text-underline-offset: 2px; }
|
|
399
490
|
a:hover { color: #fafafa; }
|
|
400
491
|
.env { display: inline-block; font-size: 0.75rem; color: #555; background: #1a1a1a; padding: 0.2rem 0.5rem; border-radius: 4px; margin-top: 1rem; }
|
|
@@ -402,8 +493,8 @@ export function renderNotPublishedHtml(appkey, channel = 'production') {
|
|
|
402
493
|
</head>
|
|
403
494
|
<body>
|
|
404
495
|
<div class="container">
|
|
405
|
-
<h1
|
|
406
|
-
<p
|
|
496
|
+
<h1>${heading}</h1>
|
|
497
|
+
<p>${body}</p>
|
|
407
498
|
<span class="env">${appkey}${envLabel}</span>
|
|
408
499
|
</div>
|
|
409
500
|
</body>
|
package/src/template.html
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html>
|
|
2
|
+
<html'${{{ ROOT_ATTRS }}}'>
|
|
3
3
|
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
'${{{ HEAD_BOOT }}}'
|
|
4
6
|
'${{{ META }}}'
|
|
5
7
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<meta charset="UTF-8" />
|
|
7
8
|
'${{{ IMPORTMAP }}}'
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|