@seamlet/astro 0.11.2 → 0.12.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.
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  formatFieldPath,
3
+ isJsonObject,
4
+ linesOf,
3
5
  normalizedLine,
4
6
  readRichText,
5
- resolveFieldPath
6
- } from "./chunk-DRDPKFHZ.js";
7
+ resolveFieldPath,
8
+ safeHref
9
+ } from "./chunk-7GYJS7ZT.js";
7
10
 
8
11
  // src/bridge/editor-shortcuts.ts
9
12
  function shortcutOf(event) {
@@ -32,28 +35,20 @@ function imageFieldAt(schema, path) {
32
35
 
33
36
  // src/bridge/messages.ts
34
37
  var PROTOCOL_VERSION = 2;
35
- var EDITOR_KEYS = /* @__PURE__ */ new Set([
36
- "save",
37
- "undo",
38
- "redo",
39
- "escape"
40
- ]);
41
- var TEXT_FORMATS = /* @__PURE__ */ new Set([
42
- "bold",
43
- "italic",
44
- "list"
45
- ]);
46
- var TYPING_OUTCOMES = /* @__PURE__ */ new Set([
47
- "kept",
48
- "reverted"
49
- ]);
50
- function readPageMessage(data) {
51
- if (!isRecord(data)) return void 0;
52
- return isPageMessage(data) ? data : void 0;
53
- }
54
- function readEditorMessage(data) {
55
- if (!isRecord(data)) return void 0;
56
- return isEditorMessage(data) ? data : void 0;
38
+ var SHORTCUTS = ["save", "undo", "redo"];
39
+ var EDITOR_KEY_NAMES = [...SHORTCUTS, "escape"];
40
+ var TEXT_FORMAT_NAMES = ["bold", "italic", "list"];
41
+ var TYPING_OUTCOME_NAMES = ["kept", "reverted"];
42
+ var EDITOR_KEYS = new Set(EDITOR_KEY_NAMES);
43
+ var TEXT_FORMATS = new Set(TEXT_FORMAT_NAMES);
44
+ var TYPING_OUTCOMES = new Set(TYPING_OUTCOME_NAMES);
45
+ function readPageMessage(posted) {
46
+ if (!isJsonObject(posted)) return void 0;
47
+ return isPageMessage(posted) ? posted : void 0;
48
+ }
49
+ function readEditorMessage(posted) {
50
+ if (!isJsonObject(posted)) return void 0;
51
+ return isEditorMessage(posted) ? posted : void 0;
57
52
  }
58
53
  function isPageMessage(message) {
59
54
  switch (message["type"]) {
@@ -94,9 +89,9 @@ function isPageMessage(message) {
94
89
  function isEditorMessage(message) {
95
90
  switch (message["type"]) {
96
91
  case "connect":
97
- return isRecord(message["schema"]);
92
+ return isJsonObject(message["schema"]);
98
93
  case "render":
99
- return isRecord(message["content"]);
94
+ return isJsonObject(message["content"]);
100
95
  case "start-typing":
101
96
  return isString(message["path"]) && isString(message["text"]) && typeof message["isSingleLine"] === "boolean" && isPoint(message["point"]);
102
97
  case "start-rich-typing":
@@ -114,36 +109,36 @@ function isEditorMessage(message) {
114
109
  }
115
110
  }
116
111
  function isLayout(value) {
117
- if (!isRecord(value)) return false;
112
+ if (!isJsonObject(value)) return false;
118
113
  const markers = value["markers"];
119
114
  return Array.isArray(markers) && markers.every(isMarkerBox) && isViewport(value["viewport"]) && isOptional(value["link"], isLinkBox);
120
115
  }
121
116
  function isMarkerBox(value) {
122
- return isRecord(value) && isString(value["path"]) && isBox(value["box"]);
117
+ return isJsonObject(value) && isString(value["path"]) && isBox(value["box"]);
123
118
  }
124
119
  function isLinkBox(value) {
125
- return isRecord(value) && isString(value["href"]) && isBox(value["box"]);
120
+ return isJsonObject(value) && isString(value["href"]) && isBox(value["box"]);
126
121
  }
127
122
  function isClickTarget(value) {
128
- return isRecord(value) && isString(value["path"]) && isPoint(value["point"]);
123
+ return isJsonObject(value) && isString(value["path"]) && isPoint(value["point"]);
129
124
  }
130
125
  function isRichText(value) {
131
126
  return readRichText(value) !== void 0;
132
127
  }
133
128
  function isTypedSelection(value) {
134
- return isRecord(value) && isBox(value["box"]) && typeof value["isCollapsed"] === "boolean" && isTextFormats(value["formats"]);
129
+ return isJsonObject(value) && isBox(value["box"]) && typeof value["isCollapsed"] === "boolean" && isTextFormats(value["formats"]);
135
130
  }
136
131
  function isTextFormats(value) {
137
- return isRecord(value) && typeof value["isBold"] === "boolean" && typeof value["isItalic"] === "boolean" && typeof value["isList"] === "boolean" && isOptional(value["href"], isString);
132
+ return isJsonObject(value) && typeof value["isBold"] === "boolean" && typeof value["isItalic"] === "boolean" && typeof value["isList"] === "boolean" && isOptional(value["href"], isString);
138
133
  }
139
134
  function isBox(value) {
140
- return isRecord(value) && isNumber(value["top"]) && isNumber(value["left"]) && isNumber(value["width"]) && isNumber(value["height"]);
135
+ return isJsonObject(value) && isNumber(value["top"]) && isNumber(value["left"]) && isNumber(value["width"]) && isNumber(value["height"]);
141
136
  }
142
137
  function isViewport(value) {
143
- return isRecord(value) && isNumber(value["width"]) && isNumber(value["height"]);
138
+ return isJsonObject(value) && isNumber(value["width"]) && isNumber(value["height"]);
144
139
  }
145
140
  function isPoint(value) {
146
- return isRecord(value) && isNumber(value["x"]) && isNumber(value["y"]);
141
+ return isJsonObject(value) && isNumber(value["x"]) && isNumber(value["y"]);
147
142
  }
148
143
  function isFieldPath(value) {
149
144
  return Array.isArray(value) && value.every((segment) => isString(segment) || isNumber(segment));
@@ -151,9 +146,6 @@ function isFieldPath(value) {
151
146
  function isOptional(value, isPresent) {
152
147
  return value === void 0 || isPresent(value);
153
148
  }
154
- function isRecord(value) {
155
- return typeof value === "object" && value !== null && !Array.isArray(value);
156
- }
157
149
  function isString(value) {
158
150
  return typeof value === "string";
159
151
  }
@@ -377,9 +369,7 @@ function renderRichText(element, text) {
377
369
  }
378
370
  function renderInlineRichText(element, text) {
379
371
  const document = element.ownerDocument;
380
- const lines = text.flatMap(
381
- (block) => block.kind === "paragraph" ? [block.spans] : block.items
382
- );
372
+ const lines = linesOf(text);
383
373
  const nodes = lines.flatMap((line, index) => [
384
374
  ...index > 0 ? [document.createElement("br")] : [],
385
375
  ...lineNodes(document, line)
@@ -461,9 +451,10 @@ function spanNode(document, span) {
461
451
  const words = wordsNode(document, span.text);
462
452
  const italic = span.italic === true ? wrapped(document, "em", words) : words;
463
453
  const bold = span.bold === true ? wrapped(document, "strong", italic) : italic;
464
- if (span.href === void 0) return bold;
454
+ const href = span.href === void 0 ? void 0 : safeHref(span.href);
455
+ if (href === void 0) return bold;
465
456
  const link = wrapped(document, "a", bold);
466
- link.setAttribute("href", span.href);
457
+ link.setAttribute("href", href);
467
458
  return link;
468
459
  }
469
460
  function wordsNode(document, text) {
@@ -0,0 +1,14 @@
1
+ // src/site-addresses.ts
2
+ var SITE_MODULE_FILE = "src/seamlet.ts";
3
+ var PREVIEW_PATH = "/preview";
4
+ var UPLOADS_PATH = "/media";
5
+ function uploadedPhotoPath(siteId, fileName) {
6
+ return `${UPLOADS_PATH}/${siteId}/${fileName}`;
7
+ }
8
+
9
+ export {
10
+ SITE_MODULE_FILE,
11
+ PREVIEW_PATH,
12
+ UPLOADS_PATH,
13
+ uploadedPhotoPath
14
+ };
@@ -4,6 +4,13 @@ var BAD_REQUEST = 400;
4
4
  var PAYLOAD_TOO_LARGE = 413;
5
5
  var UNSUPPORTED_MEDIA_TYPE = 415;
6
6
  var UPLOADED_PHOTO = /^[A-Za-z0-9_-]+\.webp$/u;
7
+ function previewHeadersOf(cmsOrigin) {
8
+ return {
9
+ "Content-Security-Policy": `frame-ancestors ${cmsOrigin}`,
10
+ "X-Robots-Tag": "noindex",
11
+ "Cache-Control": "no-store"
12
+ };
13
+ }
7
14
  async function readDraft(request) {
8
15
  const isJson = request.headers.get("Content-Type")?.startsWith("application/json") === true;
9
16
  if (!isJson) return refused(UNSUPPORTED_MEDIA_TYPE);
@@ -50,6 +57,7 @@ function refused(status) {
50
57
 
51
58
  export {
52
59
  MAX_DRAFT_BYTES,
60
+ previewHeadersOf,
53
61
  readDraft,
54
62
  isUploadedPhoto
55
63
  };
@@ -1,10 +1,14 @@
1
+ import {
2
+ UPLOADS_PATH
3
+ } from "./chunk-QCVWHHJC.js";
4
+
1
5
  // src/own-photos.ts
2
6
  var OWN_PHOTOS_PATH = "/seamlet-photos";
3
- var UPLOADS_PATH = "/media/";
7
+ var UPLOADS_FOLDER = `${UPLOADS_PATH}/`;
4
8
  var PARENT_SEGMENT = "..";
5
9
  function isOwnPhotoSrc(src) {
6
10
  const isOnSite = src.startsWith("/") && !src.startsWith("//");
7
- const isUpload = src.startsWith(UPLOADS_PATH);
11
+ const isUpload = src.startsWith(UPLOADS_FOLDER);
8
12
  const isPlainPath = !/[?#\\]/u.test(src);
9
13
  const climbsUp = src.split("/").includes(PARENT_SEGMENT);
10
14
  return isOnSite && !isUpload && isPlainPath && !climbsUp;
package/dist/cli.js CHANGED
@@ -3,7 +3,10 @@ import {
3
3
  SITE_CONFIG_FILE,
4
4
  isSiteId,
5
5
  readSiteConfig
6
- } from "./chunk-DRDPKFHZ.js";
6
+ } from "./chunk-7GYJS7ZT.js";
7
+ import {
8
+ SITE_MODULE_FILE
9
+ } from "./chunk-QCVWHHJC.js";
7
10
 
8
11
  // src/cli.ts
9
12
  import process from "process";
@@ -14,20 +17,21 @@ import { join as join2 } from "path";
14
17
  import { parseArgs } from "util";
15
18
 
16
19
  // src/init/site-files.ts
17
- var SITE_MODULE_PATH = "src/seamlet.ts";
18
20
  var SCHEMA_PATH = "src/schema.ts";
19
- var HOME_PAGE_PATH = "src/components/HomePage.astro";
21
+ var HOME_PAGE_FILE = "src/components/HomePage.astro";
20
22
  var JSON_INDENT = 2;
21
- function siteFilesOf(config) {
22
- return [
23
- {
24
- path: SITE_CONFIG_FILE,
25
- text: `${JSON.stringify(config, null, JSON_INDENT)}
23
+ function configFileOf(config) {
24
+ return {
25
+ path: SITE_CONFIG_FILE,
26
+ text: `${JSON.stringify(config, null, JSON_INDENT)}
26
27
  `
27
- },
28
+ };
29
+ }
30
+ function moduleFilesOf(config) {
31
+ return [
28
32
  { path: SCHEMA_PATH, text: schemaModuleOf(config.title) },
29
- { path: SITE_MODULE_PATH, text: SITE_MODULE },
30
- { path: HOME_PAGE_PATH, text: HOME_PAGE }
33
+ { path: SITE_MODULE_FILE, text: SITE_MODULE },
34
+ { path: HOME_PAGE_FILE, text: HOME_PAGE }
31
35
  ];
32
36
  }
33
37
  function siteIdOf(packageName) {
@@ -41,7 +45,7 @@ function schemaModuleOf(title) {
41
45
  // What the site's owner edits in the CMS. Add a field here, show it on a page and mark its element
42
46
  // with edit(...): the CMS builds its form from this schema alone.
43
47
  export const schema = defineSchema({
44
- title: text({ label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A", maxLength: 80 }),
48
+ title: text({ label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A" }),
45
49
  });
46
50
 
47
51
  export type Content = ContentOf<typeof schema>;
@@ -83,30 +87,46 @@ const edit = editable<Content>();
83
87
  `;
84
88
 
85
89
  // src/init/write-site-files.ts
86
- import { mkdir, writeFile } from "fs/promises";
90
+ import { access, mkdir, writeFile } from "fs/promises";
87
91
  import { dirname, join } from "path";
88
92
  var ALREADY_EXISTS = "EEXIST";
93
+ var MISSING = "ENOENT";
89
94
  async function writeSiteFiles(root, files) {
90
- const written = [];
91
- const kept = [];
95
+ const paths = files.map((file) => file.path);
96
+ if (await isAnyThere(root, paths)) return { written: [], kept: paths };
92
97
  for (const file of files) {
93
- const isWritten = await writeNewFile(join(root, file.path), file.text);
94
- (isWritten ? written : kept).push(file.path);
98
+ await writeNewFile(join(root, file.path), file.text);
99
+ }
100
+ return { written: paths, kept: [] };
101
+ }
102
+ async function isAnyThere(root, paths) {
103
+ for (const path of paths) {
104
+ if (await isThere(join(root, path))) return true;
105
+ }
106
+ return false;
107
+ }
108
+ async function isThere(path) {
109
+ try {
110
+ await access(path);
111
+ return true;
112
+ } catch (error) {
113
+ if (hasCode(error, MISSING)) return false;
114
+ throw new Error(`@seamlet/astro: cannot read ${path}`, { cause: error });
95
115
  }
96
- return { written, kept };
97
116
  }
98
117
  async function writeNewFile(path, text) {
99
- await mkdir(dirname(path), { recursive: true });
100
118
  try {
119
+ await mkdir(dirname(path), { recursive: true });
101
120
  await writeFile(path, text, { flag: "wx" });
102
- return true;
103
121
  } catch (error) {
104
- if (isAlreadyThere(error)) return false;
105
- throw new Error(`@seamlet/astro: cannot write ${path}`, { cause: error });
122
+ const reason = hasCode(error, ALREADY_EXISTS) ? " (it appeared meanwhile)" : "";
123
+ throw new Error(`@seamlet/astro: cannot write ${path}${reason}`, {
124
+ cause: error
125
+ });
106
126
  }
107
127
  }
108
- function isAlreadyThere(error) {
109
- return error instanceof Error && "code" in error && error.code === ALREADY_EXISTS;
128
+ function hasCode(error, code2) {
129
+ return error instanceof Error && "code" in error && error.code === code2;
110
130
  }
111
131
 
112
132
  // src/init/init-command.ts
@@ -114,6 +134,7 @@ var DEFAULT_CMS = "https://cms.seamlet.ru";
114
134
  var SUCCESS = 0;
115
135
  var FAILURE = 1;
116
136
  var MISUSE = 2;
137
+ var MISSING2 = "ENOENT";
117
138
  var USAGE = `Usage: npx @seamlet/astro init [--id <id>] [--title <title>] [--cms <address>]
118
139
 
119
140
  Writes ${SITE_CONFIG_FILE} and a first module of the site in the Astro project here. The id is the
@@ -135,26 +156,64 @@ async function runInit(run) {
135
156
  run.log(USAGE);
136
157
  return MISUSE;
137
158
  }
138
- const packageName = await packageNameOf(run.root);
159
+ const existing = await existingConfigOf(run.root);
160
+ const reading = existing ?? await configOfOptions(run.root, options);
161
+ if (reading.status === "invalid") {
162
+ run.log(invalidConfigMessage(reading, existing !== void 0));
163
+ return FAILURE;
164
+ }
165
+ const config = reading.config;
166
+ const configFile = await writeSiteFiles(run.root, [configFileOf(config)]);
167
+ const moduleFiles = await writeSiteFiles(run.root, moduleFilesOf(config));
168
+ logWritten(run, configFile);
169
+ logWritten(run, moduleFiles);
170
+ run.log(NEXT_STEPS);
171
+ return SUCCESS;
172
+ }
173
+ async function configOfOptions(root, options) {
174
+ const packageName = await packageNameOf(root);
139
175
  const id = options.id ?? siteIdOf(packageName ?? "");
140
- const reading = readSiteConfig({
176
+ return readSiteConfig({
141
177
  id,
142
178
  title: options.title ?? id,
143
179
  cms: options.cms ?? DEFAULT_CMS
144
180
  });
145
- if (reading.status === "invalid") {
146
- const fields = reading.invalidFields.map((field) => `--${field}`).join(", ");
147
- run.log(`Give the site ${fields}.
148
-
149
- ${USAGE}`);
150
- return FAILURE;
181
+ }
182
+ async function existingConfigOf(root) {
183
+ const path = join2(root, SITE_CONFIG_FILE);
184
+ let text;
185
+ try {
186
+ text = await readFile(path, "utf8");
187
+ } catch (error) {
188
+ if (hasCode(error, MISSING2)) return void 0;
189
+ throw new Error(`@seamlet/astro: cannot read ${path}`, { cause: error });
151
190
  }
152
- const files = await writeSiteFiles(run.root, siteFilesOf(reading.config));
191
+ return readSiteConfig(parsedOrNothing(text));
192
+ }
193
+ function parsedOrNothing(text) {
194
+ try {
195
+ return JSON.parse(text);
196
+ } catch {
197
+ return void 0;
198
+ }
199
+ }
200
+ function invalidConfigMessage(reading, isExisting) {
201
+ if (isExisting) {
202
+ const fields = reading.invalidFields.map((field) => `"${field}"`).join(", ");
203
+ return `The site's ${SITE_CONFIG_FILE} needs ${fields}: fix it, or remove it to write a new one.`;
204
+ }
205
+ const options = reading.invalidFields.map((field) => `--${field}`).join(", ");
206
+ return `Give the site ${options}.
207
+
208
+ ${USAGE}`;
209
+ }
210
+ function logWritten(run, files) {
153
211
  for (const path of files.written) run.log(`wrote ${path}`);
154
- for (const path of files.kept)
155
- run.log(`kept ${path}: the site has it already`);
156
- run.log(NEXT_STEPS);
157
- return SUCCESS;
212
+ for (const path of files.kept) {
213
+ run.log(
214
+ `kept ${path}: the site has it, or a file that goes with it, already`
215
+ );
216
+ }
158
217
  }
159
218
  function optionsOf(argv) {
160
219
  try {
package/dist/client.js CHANGED
@@ -1,6 +1,9 @@
1
+ import {
2
+ PREVIEW_PATH
3
+ } from "./chunk-QCVWHHJC.js";
4
+
1
5
  // src/client.ts
2
6
  import { cmsOrigin } from "virtual:seamlet/config";
3
- var PREVIEW_PATH = "/preview";
4
7
  var isFramed = window.parent !== window;
5
8
  var { pathname } = window.location;
6
9
  var isPreview = pathname === PREVIEW_PATH || pathname.startsWith(`${PREVIEW_PATH}/`);
package/dist/index.js CHANGED
@@ -1,14 +1,20 @@
1
1
  import {
2
2
  OWN_PHOTOS_PATH
3
- } from "./chunk-3BS5CE26.js";
3
+ } from "./chunk-W2LIOR2C.js";
4
4
  import {
5
5
  SITE_CONFIG_FILE,
6
6
  SITE_DESCRIPTION_PATH,
7
+ isJsonObject,
7
8
  readSiteConfig
8
- } from "./chunk-DRDPKFHZ.js";
9
+ } from "./chunk-7GYJS7ZT.js";
10
+ import {
11
+ PREVIEW_PATH,
12
+ SITE_MODULE_FILE,
13
+ UPLOADS_PATH
14
+ } from "./chunk-QCVWHHJC.js";
9
15
  import {
10
16
  isUploadedPhoto
11
- } from "./chunk-P2OQOZBC.js";
17
+ } from "./chunk-QL4K4HR6.js";
12
18
 
13
19
  // src/index.ts
14
20
  import process from "process";
@@ -17,28 +23,35 @@ import { fileURLToPath as fileURLToPath2 } from "url";
17
23
  // src/uploaded-photos.ts
18
24
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
19
25
  import { dirname, join } from "path";
20
- var PHOTO_ADDRESS = /\/media\/([a-z0-9-]+)\/([A-Za-z0-9_-]+\.webp)/gu;
26
+ var PHOTO_ADDRESS = new RegExp(
27
+ `${UPLOADS_PATH}/([^/\\s"'<>]+)/([^/\\s"'<>?#]+)`,
28
+ "gu"
29
+ );
21
30
  var HTML_EXTENSION = ".html";
22
- function uploadedPhotoAddresses(html) {
31
+ var PHOTOS_COPIED_AT_ONCE = 6;
32
+ var PHOTO_TIMEOUT_MS = 6e4;
33
+ function uploadedPhotoAddresses(html, siteId) {
23
34
  const addresses = [...html.matchAll(PHOTO_ADDRESS)].flatMap(
24
- ([address, , fileName]) => isUploadedPhoto(fileName) ? [address] : []
35
+ ([address, site, fileName]) => site === siteId && isUploadedPhoto(fileName) ? [address] : []
25
36
  );
26
37
  return [...new Set(addresses)];
27
38
  }
28
39
  async function copyUploadedPhotos(options) {
29
40
  const pages = await htmlFilesIn(options.directory);
30
41
  const texts = await Promise.all(pages.map((page) => readFile(page, "utf8")));
31
- const addresses = new Set(texts.flatMap(uploadedPhotoAddresses));
32
- await Promise.all(
33
- [...addresses].map(
34
- (address) => copyPhoto(address, options.directory, options.cmsOrigin)
35
- )
42
+ const addresses = new Set(
43
+ texts.flatMap((text) => uploadedPhotoAddresses(text, options.siteId))
44
+ );
45
+ await inTurns(
46
+ [...addresses],
47
+ PHOTOS_COPIED_AT_ONCE,
48
+ (address) => copyPhoto(address, options.directory, options.cmsOrigin)
36
49
  );
37
50
  return addresses.size;
38
51
  }
39
52
  async function copyPhoto(address, directory, cmsOrigin) {
40
53
  const url = new URL(address, cmsOrigin);
41
- const response = await fetch(url);
54
+ const response = await fetchPhoto(url);
42
55
  if (!response.ok) {
43
56
  throw new Error(
44
57
  `Copying the photo ${url.href} into the build failed with status ${String(response.status)}`
@@ -48,6 +61,29 @@ async function copyPhoto(address, directory, cmsOrigin) {
48
61
  await mkdir(dirname(target), { recursive: true });
49
62
  await writeFile(target, new Uint8Array(await response.arrayBuffer()));
50
63
  }
64
+ async function fetchPhoto(url) {
65
+ try {
66
+ return await fetch(url, { signal: AbortSignal.timeout(PHOTO_TIMEOUT_MS) });
67
+ } catch (error) {
68
+ throw new Error(
69
+ `Copying the photo ${url.href} into the build failed: the CMS did not answer`,
70
+ { cause: error }
71
+ );
72
+ }
73
+ }
74
+ async function inTurns(items, limit, task) {
75
+ const waiting = [...items];
76
+ const runInTurn = async () => {
77
+ for (let item = waiting.shift(); item !== void 0; item = waiting.shift()) {
78
+ await task(item);
79
+ }
80
+ };
81
+ const runners = Array.from(
82
+ { length: Math.min(limit, waiting.length) },
83
+ runInTurn
84
+ );
85
+ await Promise.all(runners);
86
+ }
51
87
  async function htmlFilesIn(directory) {
52
88
  const entries = await readdir(directory, {
53
89
  recursive: true,
@@ -68,7 +104,7 @@ async function loadSiteConfig(root, cmsOverride) {
68
104
  const file = new URL(SITE_CONFIG_FILE, root);
69
105
  const parsed = await readJsonFile(file);
70
106
  const isOverridden = cmsOverride !== void 0 && cmsOverride !== "";
71
- const input = isOverridden ? { ...asObject(parsed), cms: cmsOverride } : parsed;
107
+ const input = isOverridden ? { ...isJsonObject(parsed) ? parsed : {}, cms: cmsOverride } : parsed;
72
108
  const reading = readSiteConfig(input);
73
109
  if (reading.status === "read") return reading.config;
74
110
  throw new Error(invalidFieldsMessage(file, reading.invalidFields));
@@ -91,9 +127,6 @@ async function readJsonFile(file) {
91
127
  });
92
128
  }
93
129
  }
94
- function asObject(value) {
95
- return typeof value === "object" && value !== null ? value : {};
96
- }
97
130
  function invalidFieldsMessage(file, fields) {
98
131
  const lines = fields.map((field) => ` "${field}": ${FIELD_RULES[field]}`);
99
132
  return [
@@ -106,7 +139,6 @@ function invalidFieldsMessage(file, fields) {
106
139
  var CONFIG_MODULE = "virtual:seamlet/config";
107
140
  var SITE_MODULE = "virtual:seamlet/site";
108
141
  var RESOLVED_CONFIG_MODULE = `\0${CONFIG_MODULE}`;
109
- var SITE_MODULE_FILE = "src/seamlet.ts";
110
142
  var CMS_ORIGIN_VARIABLE = "CMS_ORIGIN";
111
143
  function seamlet() {
112
144
  let siteConfig;
@@ -146,12 +178,12 @@ function seamlet() {
146
178
  });
147
179
  injectScript("page", `import "@seamlet/astro/client";`);
148
180
  injectRoute({
149
- pattern: "/preview/[...page]",
181
+ pattern: `${PREVIEW_PATH}/[...page]`,
150
182
  entrypoint: "@seamlet/astro/routes/preview.astro",
151
183
  prerender: false
152
184
  });
153
185
  injectRoute({
154
- pattern: "/media/[site]/[file]",
186
+ pattern: `${UPLOADS_PATH}/[site]/[file]`,
155
187
  entrypoint: "@seamlet/astro/routes/media",
156
188
  prerender: false
157
189
  });
@@ -173,6 +205,7 @@ function seamlet() {
173
205
  if (siteConfig === void 0) return;
174
206
  const copied = await copyUploadedPhotos({
175
207
  directory: fileURLToPath2(dir),
208
+ siteId: siteConfig.id,
176
209
  cmsOrigin: siteConfig.cms
177
210
  });
178
211
  if (copied > 0) logger.info(`copied ${String(copied)} uploaded photos`);
@@ -1,5 +1,4 @@
1
- import { R as RichText, F as Fields, a as Content } from './content-6k-E7NWf.js';
2
- import { F as FieldPath } from './parse-content-C9ldnmvj.js';
1
+ import { R as RichText, F as Fields, a as Content, b as FieldPath } from './parse-content-Eg8yRq_t.js';
3
2
 
4
3
  interface Box {
5
4
  readonly top: number;
@@ -12,52 +11,6 @@ interface Viewport {
12
11
  readonly height: number;
13
12
  }
14
13
 
15
- type Shortcut = "save" | "undo" | "redo";
16
- declare function shortcutOf(event: KeyboardEvent): Shortcut | undefined;
17
-
18
- type InlineEditingOutcome = "kept" | "reverted";
19
-
20
- interface TextPoint {
21
- /** The paragraph or the list item, counted from the start of the text. */
22
- readonly line: number;
23
- /** Characters from the start of the line; a line break is one character. */
24
- readonly offset: number;
25
- }
26
- interface TextSelection {
27
- readonly start: TextPoint;
28
- readonly end: TextPoint;
29
- }
30
- type Mark = "bold" | "italic";
31
- /** What the selected words have, or the words at the cursor when nothing is selected. */
32
- interface TextFormats {
33
- readonly isBold: boolean;
34
- readonly isItalic: boolean;
35
- readonly isList: boolean;
36
- /** The address of the link the selection is in, when all of it is in one link. */
37
- readonly href: string | undefined;
38
- }
39
- /** Whether the selection holds no characters: it is a cursor. */
40
- declare function isCollapsed(selection: TextSelection): boolean;
41
- /** The marks every selected word has, and the link they are in. */
42
- declare function formatsAt(text: RichText, selection: TextSelection): TextFormats;
43
- /** The words of the link the cursor is in, or undefined outside a link. */
44
- declare function linkSelectionAt(text: RichText, point: TextPoint): TextSelection | undefined;
45
- /** The mark on every selected word, or off them all when every one has it already. */
46
- declare function toggleMark(text: RichText, selection: TextSelection, mark: Mark): RichText;
47
- /** The selected words lead to the address, or lead nowhere when it is undefined. */
48
- declare function setLink(text: RichText, selection: TextSelection, href: string | undefined): RichText;
49
- /** The selected lines become a list, or paragraphs again when they all are a list already. */
50
- declare function toggleList(text: RichText, selection: TextSelection): RichText;
51
-
52
- /** A change of formatting the editor's toolbar asks for. */
53
- type TextFormat = "bold" | "italic" | "list";
54
- /** The selection in the typed text: where it is on the page and what its words have. */
55
- interface TypedSelection {
56
- readonly box: Box;
57
- readonly isCollapsed: boolean;
58
- readonly formats: TextFormats;
59
- }
60
-
61
14
  /**
62
15
  * The version of what the two say to each other. A site updates @seamlet/astro when it likes, so the
63
16
  * editor may meet a bridge of another version: it tells by this number, said with `ready`, instead
@@ -87,8 +40,32 @@ interface PageLayout {
87
40
  /** The link under the pointer, if it is not a field. */
88
41
  readonly link: LinkBox | undefined;
89
42
  }
43
+ declare const SHORTCUTS: readonly ["save", "undo", "redo"];
44
+ declare const EDITOR_KEY_NAMES: readonly ["save", "undo", "redo", "escape"];
45
+ declare const TEXT_FORMAT_NAMES: readonly ["bold", "italic", "list"];
46
+ declare const TYPING_OUTCOME_NAMES: readonly ["kept", "reverted"];
47
+ /** A keyboard shortcut of the editor, pressed on the page. */
48
+ type Shortcut = (typeof SHORTCUTS)[number];
90
49
  /** A key the editor acts on: its shortcuts, and Escape that closes what is open. */
91
- type EditorKey = Shortcut | "escape";
50
+ type EditorKey = (typeof EDITOR_KEY_NAMES)[number];
51
+ /** A change of formatting the editor's toolbar asks for. */
52
+ type TextFormat = (typeof TEXT_FORMAT_NAMES)[number];
53
+ /** How a typing in place ended: the typed text kept, or brought back as it was. */
54
+ type InlineEditingOutcome = (typeof TYPING_OUTCOME_NAMES)[number];
55
+ /** The marks of the selected words of a formatted text. */
56
+ interface TextFormats {
57
+ readonly isBold: boolean;
58
+ readonly isItalic: boolean;
59
+ readonly isList: boolean;
60
+ /** The address of the link the selection is in, when all of it is in one link. */
61
+ readonly href: string | undefined;
62
+ }
63
+ /** The selection in the typed text: where it is on the page and what its words have. */
64
+ interface TypedSelection {
65
+ readonly box: Box;
66
+ readonly isCollapsed: boolean;
67
+ readonly formats: TextFormats;
68
+ }
92
69
  /** What the page tells the editor. */
93
70
  type PageMessage =
94
71
  /** The bridge runs and waits for the editor to connect; it speaks this version of the protocol. */
@@ -208,8 +185,8 @@ type EditorMessage =
208
185
  readonly path: FieldPath;
209
186
  };
210
187
  /** The message the page sent, or undefined for anything that is not one. */
211
- declare function readPageMessage(data: unknown): PageMessage | undefined;
188
+ declare function readPageMessage(posted: unknown): PageMessage | undefined;
212
189
  /** The message the editor sent, or undefined for anything that is not one. */
213
- declare function readEditorMessage(data: unknown): EditorMessage | undefined;
190
+ declare function readEditorMessage(posted: unknown): EditorMessage | undefined;
214
191
 
215
- export { type Box as B, type EditorMessage as E, type InlineEditingOutcome as I, type LinkBox as L, type Mark as M, type PageMessage as P, type Shortcut as S, type TextSelection as T, type Viewport as V, type EditorKey as a, type MarkerBox as b, PROTOCOL_VERSION as c, type PageLayout as d, type Point as e, type TextFormat as f, type TextFormats as g, type TextPoint as h, type TypedSelection as i, formatsAt as j, isCollapsed as k, linkSelectionAt as l, readPageMessage as m, shortcutOf as n, toggleMark as o, readEditorMessage as r, setLink as s, toggleList as t };
192
+ export { type Box as B, type EditorMessage as E, type InlineEditingOutcome as I, type LinkBox as L, type MarkerBox as M, type PageMessage as P, type Shortcut as S, type TextFormats as T, type Viewport as V, type EditorKey as a, PROTOCOL_VERSION as b, type PageLayout as c, type Point as d, type TextFormat as e, type TypedSelection as f, readPageMessage as g, readEditorMessage as r };