@seamlet/astro 0.11.3 → 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.
- package/README.md +18 -6
- package/components/RichLine.astro +2 -5
- package/components/RichText.astro +2 -4
- package/dist/bridge.d.ts +2 -3
- package/dist/bridge.js +557 -393
- package/dist/{chunk-DRDPKFHZ.js → chunk-7GYJS7ZT.js} +386 -203
- package/dist/{chunk-2E7VDSUK.js → chunk-MW3BE5O6.js} +34 -43
- package/dist/chunk-QCVWHHJC.js +14 -0
- package/dist/{chunk-P2OQOZBC.js → chunk-QL4K4HR6.js} +8 -0
- package/dist/{chunk-3BS5CE26.js → chunk-W2LIOR2C.js} +6 -2
- package/dist/cli.js +96 -37
- package/dist/client.js +4 -1
- package/dist/index.js +52 -19
- package/dist/{messages-Dx0jNy3N.d.ts → messages-CvZhLRSE.d.ts} +29 -52
- package/dist/{content-6k-E7NWf.d.ts → parse-content-Eg8yRq_t.d.ts} +69 -1
- package/dist/preview-requests.d.ts +6 -1
- package/dist/preview-requests.js +3 -1
- package/dist/protocol.d.ts +31 -5
- package/dist/protocol.js +4 -3
- package/dist/routes/media.js +26 -5
- package/dist/routes/own-photos.js +2 -1
- package/dist/schema.d.ts +13 -8
- package/dist/schema.js +13 -1
- package/dist/site-description-Cg5KNhhG.d.ts +112 -0
- package/dist/site.d.ts +2 -2
- package/dist/site.js +83 -9
- package/package.json +1 -1
- package/routes/preview.astro +8 -11
- package/dist/parse-content-C9ldnmvj.d.ts +0 -69
- package/dist/site-description-4Jx6Nq6X.d.ts +0 -36
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatFieldPath,
|
|
3
|
+
isJsonObject,
|
|
4
|
+
linesOf,
|
|
3
5
|
normalizedLine,
|
|
4
6
|
readRichText,
|
|
5
|
-
resolveFieldPath
|
|
6
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
var
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
92
|
+
return isJsonObject(message["schema"]);
|
|
98
93
|
case "render":
|
|
99
|
-
return
|
|
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 (!
|
|
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
|
|
117
|
+
return isJsonObject(value) && isString(value["path"]) && isBox(value["box"]);
|
|
123
118
|
}
|
|
124
119
|
function isLinkBox(value) {
|
|
125
|
-
return
|
|
120
|
+
return isJsonObject(value) && isString(value["href"]) && isBox(value["box"]);
|
|
126
121
|
}
|
|
127
122
|
function isClickTarget(value) {
|
|
128
|
-
return
|
|
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
|
|
129
|
+
return isJsonObject(value) && isBox(value["box"]) && typeof value["isCollapsed"] === "boolean" && isTextFormats(value["formats"]);
|
|
135
130
|
}
|
|
136
131
|
function isTextFormats(value) {
|
|
137
|
-
return
|
|
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
|
|
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
|
|
138
|
+
return isJsonObject(value) && isNumber(value["width"]) && isNumber(value["height"]);
|
|
144
139
|
}
|
|
145
140
|
function isPoint(value) {
|
|
146
|
-
return
|
|
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
|
|
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
|
-
|
|
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",
|
|
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
|
|
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(
|
|
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-
|
|
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
|
|
21
|
+
var HOME_PAGE_FILE = "src/components/HomePage.astro";
|
|
20
22
|
var JSON_INDENT = 2;
|
|
21
|
-
function
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
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:
|
|
30
|
-
{ path:
|
|
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"
|
|
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
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
105
|
-
throw new Error(`@seamlet/astro: cannot write ${path}`, {
|
|
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
|
|
109
|
-
return error instanceof Error && "code" in error && error.code ===
|
|
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
|
|
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
|
-
|
|
176
|
+
return readSiteConfig({
|
|
141
177
|
id,
|
|
142
178
|
title: options.title ?? id,
|
|
143
179
|
cms: options.cms ?? DEFAULT_CMS
|
|
144
180
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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(
|
|
156
|
-
|
|
157
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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 =
|
|
26
|
+
var PHOTO_ADDRESS = new RegExp(
|
|
27
|
+
`${UPLOADS_PATH}/([^/\\s"'<>]+)/([^/\\s"'<>?#]+)`,
|
|
28
|
+
"gu"
|
|
29
|
+
);
|
|
21
30
|
var HTML_EXTENSION = ".html";
|
|
22
|
-
|
|
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(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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 ? { ...
|
|
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:
|
|
181
|
+
pattern: `${PREVIEW_PATH}/[...page]`,
|
|
150
182
|
entrypoint: "@seamlet/astro/routes/preview.astro",
|
|
151
183
|
prerender: false
|
|
152
184
|
});
|
|
153
185
|
injectRoute({
|
|
154
|
-
pattern:
|
|
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-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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 };
|