@seamlet/astro 0.11.3 → 0.12.1
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 +19 -7
- 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-QCVWHHJC.js +14 -0
- package/dist/{chunk-P2OQOZBC.js → chunk-QL4K4HR6.js} +8 -0
- package/dist/{chunk-2E7VDSUK.js → chunk-SSECNMAD.js} +34 -43
- package/dist/{chunk-3BS5CE26.js → chunk-W2LIOR2C.js} +6 -2
- package/dist/{chunk-DRDPKFHZ.js → chunk-ZS32CW5F.js} +396 -207
- 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
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-ZS32CW5F.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-ZS32CW5F.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 };
|
|
@@ -168,6 +168,8 @@ declare function asOneParagraph(text: RichText): RichText;
|
|
|
168
168
|
declare function readRichText(input: unknown): RichText | undefined;
|
|
169
169
|
/** The same line with empty pieces left out and neighbors with the same marks joined. */
|
|
170
170
|
declare function normalizedLine(spans: readonly RichSpan[]): RichLine;
|
|
171
|
+
/** The lines of a text: its paragraphs and the items of its lists, in their order. */
|
|
172
|
+
declare function linesOf(text: RichText): readonly RichLine[];
|
|
171
173
|
|
|
172
174
|
/** The point of a photo that must stay in view when a site crops it: 0 to 1 from the left and the top. */
|
|
173
175
|
interface ImageFocus {
|
|
@@ -211,4 +213,70 @@ type FieldValue = string | number | boolean | null | readonly string[] | ImageVa
|
|
|
211
213
|
declare function defaultValue(field: Field): FieldValue;
|
|
212
214
|
declare function defaultContent(fields: Fields): Content;
|
|
213
215
|
|
|
214
|
-
|
|
216
|
+
/** Where a value lives in the content: field names and, inside a collection, record indexes. */
|
|
217
|
+
type FieldPath = readonly (string | number)[];
|
|
218
|
+
type ContentIssue = {
|
|
219
|
+
readonly path: FieldPath;
|
|
220
|
+
readonly code: "invalid-type";
|
|
221
|
+
} | {
|
|
222
|
+
readonly path: FieldPath;
|
|
223
|
+
readonly code: "invalid-date";
|
|
224
|
+
} | {
|
|
225
|
+
readonly path: FieldPath;
|
|
226
|
+
readonly code: "invalid-time";
|
|
227
|
+
} | {
|
|
228
|
+
readonly path: FieldPath;
|
|
229
|
+
readonly code: "too-long";
|
|
230
|
+
readonly maxLength: number;
|
|
231
|
+
} | {
|
|
232
|
+
readonly path: FieldPath;
|
|
233
|
+
readonly code: "below-min";
|
|
234
|
+
readonly min: number;
|
|
235
|
+
} | {
|
|
236
|
+
readonly path: FieldPath;
|
|
237
|
+
readonly code: "above-max";
|
|
238
|
+
readonly max: number;
|
|
239
|
+
} | {
|
|
240
|
+
readonly path: FieldPath;
|
|
241
|
+
readonly code: "missing-image-src";
|
|
242
|
+
} | {
|
|
243
|
+
readonly path: FieldPath;
|
|
244
|
+
readonly code: "missing-link-href";
|
|
245
|
+
} | {
|
|
246
|
+
readonly path: FieldPath;
|
|
247
|
+
readonly code: "unsupported-link-href";
|
|
248
|
+
} | {
|
|
249
|
+
readonly path: FieldPath;
|
|
250
|
+
readonly code: "unsupported-url";
|
|
251
|
+
} | {
|
|
252
|
+
readonly path: FieldPath;
|
|
253
|
+
readonly code: "unknown-choice";
|
|
254
|
+
readonly value: string;
|
|
255
|
+
} | {
|
|
256
|
+
readonly path: FieldPath;
|
|
257
|
+
readonly code: "missing-record";
|
|
258
|
+
readonly id: string;
|
|
259
|
+
};
|
|
260
|
+
interface ParsedContent<C> {
|
|
261
|
+
readonly content: C;
|
|
262
|
+
readonly issues: readonly ContentIssue[];
|
|
263
|
+
}
|
|
264
|
+
declare function formatFieldPath(path: FieldPath): string;
|
|
265
|
+
/**
|
|
266
|
+
* The length that `maxLength` limits: characters as people see them (grapheme clusters), so
|
|
267
|
+
* "й" and "👍🏽" are one character each, whatever number of code units they take.
|
|
268
|
+
*/
|
|
269
|
+
declare function countCharacters(text: string): number;
|
|
270
|
+
/**
|
|
271
|
+
* Brings any input to the shape of the schema, so the result is always content of that schema:
|
|
272
|
+
* - a field missing from the input (the schema grew after the content was saved) gets its default;
|
|
273
|
+
* - a value of the wrong type or format gets the default too, and an issue;
|
|
274
|
+
* - a value of the right type that breaks a rule of its field (too long, below the minimum) is
|
|
275
|
+
* kept, with an issue: nothing a person typed is lost;
|
|
276
|
+
* - keys the schema does not know are dropped; a record keeps its id (see withRecordIds);
|
|
277
|
+
* - a reference to a record its collection does not have (removed since) is kept, with an issue.
|
|
278
|
+
* Content with issues is valid to show and edit, but not to save.
|
|
279
|
+
*/
|
|
280
|
+
declare function parseContent<F extends Fields>(schema: F, input: unknown): ParsedContent<ContentOf<F>>;
|
|
281
|
+
|
|
282
|
+
export { plainTextOf as $, collection as A, countCharacters as B, type ContentOf as C, type DateField as D, date as E, type Fields as F, type GroupField as G, defaultContent as H, type ImageField as I, defaultValue as J, defineSchema as K, type LinkValue as L, formatFieldPath as M, type NumberField as N, type OptionValue as O, type ParsedContent as P, group as Q, type RichText as R, image as S, type TextField as T, type UrlField as U, type ValueOf as V, linesOf as W, link as X, normalizedLine as Y, number as Z, parseContent as _, type Content as a, readRichText as a0, reference as a1, references as a2, richText as a3, richTextOf as a4, text as a5, time as a6, toggle as a7, url as a8, type FieldPath as b, type ContentIssue as c, type ImageValue as d, type Field as e, type CollectionField as f, type RecordFields as g, type LinkField as h, type ChoicesField as i, type TimeField as j, type ChoiceField as k, type ChoiceOption as l, type FieldKind as m, type FieldValue as n, type ImageFocus as o, type RecordOf as p, type ReferenceField as q, type ReferencesField as r, type RichBlock as s, type RichLine as t, type RichSpan as u, type RichTextField as v, type ToggleField as w, asOneParagraph as x, choice as y, choices as z };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** Far more than the content of a site ever takes; a bigger body is not a draft. */
|
|
2
2
|
declare const MAX_DRAFT_BYTES: number;
|
|
3
|
+
/**
|
|
4
|
+
* What every answer of the preview says, a refusal too: only the CMS's editor frames it, and
|
|
5
|
+
* nobody indexes or keeps it.
|
|
6
|
+
*/
|
|
7
|
+
declare function previewHeadersOf(cmsOrigin: string): Readonly<Record<string, string>>;
|
|
3
8
|
type DraftReading = {
|
|
4
9
|
readonly isRead: true;
|
|
5
10
|
readonly draft: unknown;
|
|
@@ -15,4 +20,4 @@ declare function readDraft(request: Request): Promise<DraftReading>;
|
|
|
15
20
|
/** Whether a name can be a photo uploaded to the CMS: the hash of its bytes, as WebP. */
|
|
16
21
|
declare function isUploadedPhoto(fileName: string | undefined): fileName is string;
|
|
17
22
|
|
|
18
|
-
export { type DraftReading, MAX_DRAFT_BYTES, isUploadedPhoto, readDraft };
|
|
23
|
+
export { type DraftReading, MAX_DRAFT_BYTES, isUploadedPhoto, previewHeadersOf, readDraft };
|
package/dist/preview-requests.js
CHANGED
package/dist/protocol.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { T as
|
|
2
|
-
export { B as Box, a as EditorKey, E as EditorMessage, I as InlineEditingOutcome, L as LinkBox, M as
|
|
3
|
-
import { F as Fields, I as ImageField, R as RichText } from './content-
|
|
4
|
-
|
|
1
|
+
import { S as Shortcut, T as TextFormats } from './messages-CvZhLRSE.js';
|
|
2
|
+
export { B as Box, a as EditorKey, E as EditorMessage, I as InlineEditingOutcome, L as LinkBox, M as MarkerBox, b as PROTOCOL_VERSION, c as PageLayout, P as PageMessage, d as Point, e as TextFormat, f as TypedSelection, V as Viewport, r as readEditorMessage, g as readPageMessage } from './messages-CvZhLRSE.js';
|
|
3
|
+
import { F as Fields, b as FieldPath, I as ImageField, R as RichText } from './parse-content-Eg8yRq_t.js';
|
|
4
|
+
|
|
5
|
+
declare function shortcutOf(event: KeyboardEvent): Shortcut | undefined;
|
|
5
6
|
|
|
6
7
|
declare function carriesFiles(event: DragEvent): boolean;
|
|
7
8
|
|
|
@@ -17,6 +18,31 @@ declare function imageFieldAt(schema: Fields, path: string | undefined): {
|
|
|
17
18
|
*/
|
|
18
19
|
declare function closestMarkedPath(path: FieldPath, isMarked: (path: string) => boolean): string | undefined;
|
|
19
20
|
|
|
21
|
+
interface TextPoint {
|
|
22
|
+
/** The paragraph or the list item, counted from the start of the text. */
|
|
23
|
+
readonly line: number;
|
|
24
|
+
/** Characters from the start of the line; a line break is one character. */
|
|
25
|
+
readonly offset: number;
|
|
26
|
+
}
|
|
27
|
+
interface TextSelection {
|
|
28
|
+
readonly start: TextPoint;
|
|
29
|
+
readonly end: TextPoint;
|
|
30
|
+
}
|
|
31
|
+
type Mark = "bold" | "italic";
|
|
32
|
+
/** What the selected words have, or the words at the cursor when nothing is selected. */
|
|
33
|
+
/** Whether the selection holds no characters: it is a cursor. */
|
|
34
|
+
declare function isCollapsed(selection: TextSelection): boolean;
|
|
35
|
+
/** The marks every selected word has, and the link they are in. */
|
|
36
|
+
declare function formatsAt(text: RichText, selection: TextSelection): TextFormats;
|
|
37
|
+
/** The words of the link the cursor is in, or undefined outside a link. */
|
|
38
|
+
declare function linkSelectionAt(text: RichText, point: TextPoint): TextSelection | undefined;
|
|
39
|
+
/** The mark on every selected word, or off them all when every one has it already. */
|
|
40
|
+
declare function toggleMark(text: RichText, selection: TextSelection, mark: Mark): RichText;
|
|
41
|
+
/** The selected words lead to the address, or lead nowhere when it is undefined. */
|
|
42
|
+
declare function setLink(text: RichText, selection: TextSelection, href: string | undefined): RichText;
|
|
43
|
+
/** The selected lines become a list, or paragraphs again when they all are a list already. */
|
|
44
|
+
declare function toggleList(text: RichText, selection: TextSelection): RichText;
|
|
45
|
+
|
|
20
46
|
/** The elements of a text, put into the editable element in place of what it held. */
|
|
21
47
|
declare function renderRichText(element: HTMLElement, text: RichText): void;
|
|
22
48
|
/** What the editable element holds now, as formatted text. */
|
|
@@ -35,4 +61,4 @@ declare function isOwnPhotoSrc(src: string): boolean;
|
|
|
35
61
|
/** Where the site hands out its own photo; undefined for an upload or another site's address. */
|
|
36
62
|
declare function ownPhotoPath(src: string): string | undefined;
|
|
37
63
|
|
|
38
|
-
export { OWN_PHOTOS_PATH, TextSelection, carriesFiles, closestMarkedPath, imageFieldAt, insertPlainText, isOwnPhotoSrc, ownPhotoPath, readRichTextFrom, readSelectionFrom, renderRichText, selectIn };
|
|
64
|
+
export { type Mark, OWN_PHOTOS_PATH, Shortcut, TextFormats, type TextPoint, type TextSelection, carriesFiles, closestMarkedPath, formatsAt, imageFieldAt, insertPlainText, isCollapsed, isOwnPhotoSrc, linkSelectionAt, ownPhotoPath, readRichTextFrom, readSelectionFrom, renderRichText, selectIn, setLink, shortcutOf, toggleList, toggleMark };
|
package/dist/protocol.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
OWN_PHOTOS_PATH,
|
|
3
3
|
isOwnPhotoSrc,
|
|
4
4
|
ownPhotoPath
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-W2LIOR2C.js";
|
|
6
6
|
import {
|
|
7
7
|
PROTOCOL_VERSION,
|
|
8
8
|
carriesFiles,
|
|
@@ -22,8 +22,9 @@ import {
|
|
|
22
22
|
shortcutOf,
|
|
23
23
|
toggleList,
|
|
24
24
|
toggleMark
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-SSECNMAD.js";
|
|
26
|
+
import "./chunk-ZS32CW5F.js";
|
|
27
|
+
import "./chunk-QCVWHHJC.js";
|
|
27
28
|
export {
|
|
28
29
|
OWN_PHOTOS_PATH,
|
|
29
30
|
PROTOCOL_VERSION,
|