gutterpress 0.10.1-beta.1 → 0.10.1-beta.3
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/dist/api/index.js +2 -2
- package/dist/{audit-7a37g51p.js → audit-sse9r5rm.js} +4 -4
- package/dist/{build-kdaet3pd.js → build-9dn6r6ak.js} +4 -4
- package/dist/{cli-ak1tagkr.js → cli-eewynen9.js} +3 -3
- package/dist/{cli-dx6sxj2t.js → cli-jhvkcwvf.js} +1 -1
- package/dist/{cli-mcb484g3.js → cli-qxebvmrq.js} +1 -1
- package/dist/{cli-df7gcyns.js → cli-wtyzagaq.js} +4 -4
- package/dist/cli.js +14 -14
- package/dist/{doctor-vajd5h45.js → doctor-pswwk1nm.js} +2 -2
- package/dist/{engine-gkcqdye5.js → engine-57vw8zpd.js} +1 -1
- package/dist/{engine-0nt35svz.js → engine-8kdwy0jz.js} +2 -2
- package/dist/{index-6g72h2n1.js → index-7faw7w8v.js} +3 -3
- package/dist/{index-b2rqw41a.js → index-7hrvksrb.js} +3 -3
- package/dist/{index-kvgq4q3q.js → index-p0ebr7dh.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{lint-p6a4bkvh.js → lint-bbc9qk1h.js} +4 -4
- package/dist/{new-4cdzfbsj.js → new-aj8be9p9.js} +4 -4
- package/dist/{plugin-a89tqspy.js → plugin-raq1arqq.js} +4 -4
- package/dist/{preflight-r4y3f8f9.js → preflight-hq060mas.js} +4 -4
- package/dist/{preview-bridge-fz7vpk8m.js → preview-bridge-2nnrs2hy.js} +15 -0
- package/dist/{preview-k8n2kew7.js → preview-c4d698wn.js} +4 -4
- package/dist/{preview-interface-0ssk8bmm.js → preview-interface-2f5v96hd.js} +411 -88
- package/dist/{preview-shell-c5mfa3q0.js → preview-shell-kyjq59d9.js} +80 -25
- package/dist/{publish-jnhjp2vd.js → publish-ft5x1e5v.js} +4 -4
- package/dist/schema/manifest.types.d.ts +6 -3
- package/dist/{validate-prnybpkx.js → validate-wp7rgg1y.js} +4 -4
- package/package.json +1 -1
package/dist/api/index.js
CHANGED
|
@@ -95,12 +95,12 @@ import {
|
|
|
95
95
|
syncProject,
|
|
96
96
|
testRemoteAccess,
|
|
97
97
|
validateProjectPlugins
|
|
98
|
-
} from "../index-
|
|
98
|
+
} from "../index-7faw7w8v.js";
|
|
99
99
|
import {
|
|
100
100
|
BuildError,
|
|
101
101
|
DEFAULT_PRINT_OPTS,
|
|
102
102
|
readyProbeExpr
|
|
103
|
-
} from "../index-
|
|
103
|
+
} from "../index-7hrvksrb.js";
|
|
104
104
|
import {
|
|
105
105
|
AUTO_SNAPSHOT_MESSAGE,
|
|
106
106
|
HISTORY_PAGE_LIMIT,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-wtyzagaq.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-qxebvmrq.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
11
|
+
} from "./cli-jhvkcwvf.js";
|
|
12
|
+
import"./cli-eewynen9.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuild,
|
|
3
3
|
splitOutPath
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-wtyzagaq.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-qxebvmrq.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
parseEngine,
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
parsePdfxFlavor,
|
|
13
13
|
rejectExtraPositionals,
|
|
14
14
|
rejectUnknownFlags
|
|
15
|
-
} from "./cli-
|
|
16
|
-
import"./cli-
|
|
15
|
+
} from "./cli-jhvkcwvf.js";
|
|
16
|
+
import"./cli-eewynen9.js";
|
|
17
17
|
import"./cli-c41yr7he.js";
|
|
18
18
|
import {
|
|
19
19
|
BuildError
|
|
@@ -1422,13 +1422,13 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
|
1422
1422
|
var manifest_schema_default = "./manifest.schema-zxgxnbg7.json";
|
|
1423
1423
|
|
|
1424
1424
|
// src/assets/preview/scripts/preview-interface.js
|
|
1425
|
-
var preview_interface_default = "./preview-interface-
|
|
1425
|
+
var preview_interface_default = "./preview-interface-2f5v96hd.js";
|
|
1426
1426
|
|
|
1427
1427
|
// src/assets/preview/scripts/preview-bridge.js
|
|
1428
|
-
var preview_bridge_default = "./preview-bridge-
|
|
1428
|
+
var preview_bridge_default = "./preview-bridge-2nnrs2hy.js";
|
|
1429
1429
|
|
|
1430
1430
|
// src/assets/preview/scripts/preview-shell.js
|
|
1431
|
-
var preview_shell_default = "./preview-shell-
|
|
1431
|
+
var preview_shell_default = "./preview-shell-kyjq59d9.js";
|
|
1432
1432
|
|
|
1433
1433
|
// profiles/CGATS21_CRPC1.icc
|
|
1434
1434
|
var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "gutterpress",
|
|
7
|
-
version: "0.10.1-beta.
|
|
7
|
+
version: "0.10.1-beta.3",
|
|
8
8
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
9
9
|
author: "itlackey",
|
|
10
10
|
license: "MPL-2.0",
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-qxebvmrq.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-jhvkcwvf.js";
|
|
22
22
|
import {
|
|
23
23
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
24
24
|
RENDER_TIMEOUT_MS,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getAssetPath,
|
|
28
28
|
getBrowser,
|
|
29
29
|
prewarmBrowser
|
|
30
|
-
} from "./cli-
|
|
30
|
+
} from "./cli-eewynen9.js";
|
|
31
31
|
import {
|
|
32
32
|
INSTALL_HINTS,
|
|
33
33
|
execCapture,
|
|
@@ -7521,7 +7521,7 @@ class PdfOutput {
|
|
|
7521
7521
|
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
7522
7522
|
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
7523
7523
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
7524
|
-
const { buildNativePdf } = await import("./engine-
|
|
7524
|
+
const { buildNativePdf } = await import("./engine-57vw8zpd.js");
|
|
7525
7525
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
7526
7526
|
title: config.title,
|
|
7527
7527
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
UsageError,
|
|
7
7
|
package_default,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-jhvkcwvf.js";
|
|
10
10
|
import {
|
|
11
11
|
EXIT_CODES
|
|
12
12
|
} from "./cli-46ycxe6r.js";
|
|
@@ -18,16 +18,16 @@ import {
|
|
|
18
18
|
import { defineCommand, parseArgs, runMain } from "citty";
|
|
19
19
|
import { statSync } from "node:fs";
|
|
20
20
|
var SUBCOMMANDS = {
|
|
21
|
-
new: () => import("./new-
|
|
22
|
-
preview: () => import("./preview-
|
|
23
|
-
build: () => import("./build-
|
|
24
|
-
publish: () => import("./publish-
|
|
25
|
-
lint: () => import("./lint-
|
|
26
|
-
validate: () => import("./validate-
|
|
27
|
-
audit: () => import("./audit-
|
|
28
|
-
preflight: () => import("./preflight-
|
|
29
|
-
doctor: () => import("./doctor-
|
|
30
|
-
plugin: () => import("./plugin-
|
|
21
|
+
new: () => import("./new-aj8be9p9.js").then((m) => m.default),
|
|
22
|
+
preview: () => import("./preview-c4d698wn.js").then((m) => m.default),
|
|
23
|
+
build: () => import("./build-9dn6r6ak.js").then((m) => m.default),
|
|
24
|
+
publish: () => import("./publish-ft5x1e5v.js").then((m) => m.default),
|
|
25
|
+
lint: () => import("./lint-bbc9qk1h.js").then((m) => m.default),
|
|
26
|
+
validate: () => import("./validate-wp7rgg1y.js").then((m) => m.default),
|
|
27
|
+
audit: () => import("./audit-sse9r5rm.js").then((m) => m.default),
|
|
28
|
+
preflight: () => import("./preflight-hq060mas.js").then((m) => m.default),
|
|
29
|
+
doctor: () => import("./doctor-pswwk1nm.js").then((m) => m.default),
|
|
30
|
+
plugin: () => import("./plugin-raq1arqq.js").then((m) => m.default)
|
|
31
31
|
};
|
|
32
32
|
var VERSION = package_default.version;
|
|
33
33
|
var main = defineCommand({
|
|
@@ -52,7 +52,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
52
52
|
const [command, ...commandArgs] = rawArgs;
|
|
53
53
|
try {
|
|
54
54
|
if (command === "new") {
|
|
55
|
-
const { newArgs } = await import("./new-
|
|
55
|
+
const { newArgs } = await import("./new-aj8be9p9.js");
|
|
56
56
|
rejectUnknownFlags(commandArgs, newArgs, "new");
|
|
57
57
|
const parsed2 = parseArgs(commandArgs, {
|
|
58
58
|
...newArgs,
|
|
@@ -64,7 +64,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
if (command === "preflight") {
|
|
67
|
-
const { preflightArgs } = await import("./preflight-
|
|
67
|
+
const { preflightArgs } = await import("./preflight-hq060mas.js");
|
|
68
68
|
rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
|
|
69
69
|
const parsed2 = parseArgs(commandArgs, {
|
|
70
70
|
...preflightArgs,
|
|
@@ -88,7 +88,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
88
88
|
if (subcommand !== "add") {
|
|
89
89
|
throw new UsageError(`gutterpress plugin: unknown command "${subcommand}"`);
|
|
90
90
|
}
|
|
91
|
-
const { pluginAddArgs } = await import("./plugin-
|
|
91
|
+
const { pluginAddArgs } = await import("./plugin-raq1arqq.js");
|
|
92
92
|
rejectUnknownFlags(subcommandArgs, pluginAddArgs, "plugin add");
|
|
93
93
|
const parsed = parseArgs(subcommandArgs, {
|
|
94
94
|
...pluginAddArgs,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSystemDiagnostics,
|
|
3
3
|
log
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-qxebvmrq.js";
|
|
5
5
|
import {
|
|
6
6
|
UsageError,
|
|
7
7
|
rejectExtraPositionals,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-jhvkcwvf.js";
|
|
10
10
|
import"./cli-c41yr7he.js";
|
|
11
11
|
import"./cli-46ycxe6r.js";
|
|
12
12
|
import"./cli-37x76zdn.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
build
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-p0ebr7dh.js";
|
|
4
4
|
import {
|
|
5
5
|
BuildError,
|
|
6
6
|
RENDER_TIMEOUT_MS,
|
|
7
7
|
assertMilestone,
|
|
8
8
|
connectChromium,
|
|
9
9
|
getBrowser
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-7hrvksrb.js";
|
|
11
11
|
import"./index-37x76zdn.js";
|
|
12
12
|
|
|
13
13
|
// src/lib/engine.ts
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
resolveChromiumExecutable,
|
|
18
18
|
run,
|
|
19
19
|
spawnCapture
|
|
20
|
-
} from "./index-
|
|
20
|
+
} from "./index-7hrvksrb.js";
|
|
21
21
|
import {
|
|
22
22
|
gitFs,
|
|
23
23
|
gitScopeFor,
|
|
@@ -3980,7 +3980,7 @@ import git from "isomorphic-git";
|
|
|
3980
3980
|
// package.json
|
|
3981
3981
|
var package_default = {
|
|
3982
3982
|
name: "gutterpress",
|
|
3983
|
-
version: "0.10.1-beta.
|
|
3983
|
+
version: "0.10.1-beta.3",
|
|
3984
3984
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
3985
3985
|
author: "itlackey",
|
|
3986
3986
|
license: "MPL-2.0",
|
|
@@ -8016,7 +8016,7 @@ class PdfOutput {
|
|
|
8016
8016
|
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
8017
8017
|
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
8018
8018
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
8019
|
-
const { buildNativePdf } = await import("./engine-
|
|
8019
|
+
const { buildNativePdf } = await import("./engine-8kdwy0jz.js");
|
|
8020
8020
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
8021
8021
|
title: config.title,
|
|
8022
8022
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -596,13 +596,13 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
|
596
596
|
var manifest_schema_default = "./manifest.schema-zxgxnbg7.json";
|
|
597
597
|
|
|
598
598
|
// src/assets/preview/scripts/preview-interface.js
|
|
599
|
-
var preview_interface_default = "./preview-interface-
|
|
599
|
+
var preview_interface_default = "./preview-interface-2f5v96hd.js";
|
|
600
600
|
|
|
601
601
|
// src/assets/preview/scripts/preview-bridge.js
|
|
602
|
-
var preview_bridge_default = "./preview-bridge-
|
|
602
|
+
var preview_bridge_default = "./preview-bridge-2nnrs2hy.js";
|
|
603
603
|
|
|
604
604
|
// src/assets/preview/scripts/preview-shell.js
|
|
605
|
-
var preview_shell_default = "./preview-shell-
|
|
605
|
+
var preview_shell_default = "./preview-shell-kyjq59d9.js";
|
|
606
606
|
|
|
607
607
|
// profiles/CGATS21_CRPC1.icc
|
|
608
608
|
var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
|
package/dist/index.js
CHANGED
|
@@ -113,15 +113,15 @@ import {
|
|
|
113
113
|
syncProject,
|
|
114
114
|
testRemoteAccess,
|
|
115
115
|
validateProjectPlugins
|
|
116
|
-
} from "./index-
|
|
116
|
+
} from "./index-7faw7w8v.js";
|
|
117
117
|
import {
|
|
118
118
|
BUILD_DIAGNOSTIC_CODES
|
|
119
|
-
} from "./index-
|
|
119
|
+
} from "./index-p0ebr7dh.js";
|
|
120
120
|
import {
|
|
121
121
|
BuildError,
|
|
122
122
|
DEFAULT_PRINT_OPTS,
|
|
123
123
|
readyProbeExpr
|
|
124
|
-
} from "./index-
|
|
124
|
+
} from "./index-7hrvksrb.js";
|
|
125
125
|
import {
|
|
126
126
|
AUTO_SNAPSHOT_MESSAGE,
|
|
127
127
|
HISTORY_PAGE_LIMIT,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_FILENAMES,
|
|
3
3
|
runLint
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-wtyzagaq.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-qxebvmrq.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
rejectExtraPositionals,
|
|
11
11
|
rejectUnknownFlags
|
|
12
|
-
} from "./cli-
|
|
13
|
-
import"./cli-
|
|
12
|
+
} from "./cli-jhvkcwvf.js";
|
|
13
|
+
import"./cli-eewynen9.js";
|
|
14
14
|
import"./cli-c41yr7he.js";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_CODES
|
|
@@ -5,16 +5,16 @@ import {
|
|
|
5
5
|
TARGETS,
|
|
6
6
|
TARGET_IDS,
|
|
7
7
|
scaffoldProject
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-wtyzagaq.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveGhostscript
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-qxebvmrq.js";
|
|
12
12
|
import {
|
|
13
13
|
UsageError,
|
|
14
14
|
rejectExtraPositionals,
|
|
15
15
|
rejectUnknownFlags
|
|
16
|
-
} from "./cli-
|
|
17
|
-
import"./cli-
|
|
16
|
+
} from "./cli-jhvkcwvf.js";
|
|
17
|
+
import"./cli-eewynen9.js";
|
|
18
18
|
import {
|
|
19
19
|
isToolAvailable
|
|
20
20
|
} from "./cli-c41yr7he.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addNpmPlugin,
|
|
3
3
|
parseNpmPluginSpec
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-wtyzagaq.js";
|
|
5
|
+
import"./cli-qxebvmrq.js";
|
|
6
6
|
import {
|
|
7
7
|
UsageError,
|
|
8
8
|
rejectExtraPositionals,
|
|
9
9
|
rejectUnknownFlags
|
|
10
|
-
} from "./cli-
|
|
11
|
-
import"./cli-
|
|
10
|
+
} from "./cli-jhvkcwvf.js";
|
|
11
|
+
import"./cli-eewynen9.js";
|
|
12
12
|
import"./cli-c41yr7he.js";
|
|
13
13
|
import {
|
|
14
14
|
EXIT_CODES
|
|
@@ -2,16 +2,16 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-wtyzagaq.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-qxebvmrq.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-jhvkcwvf.js";
|
|
14
|
+
import"./cli-eewynen9.js";
|
|
15
15
|
import"./cli-c41yr7he.js";
|
|
16
16
|
import {
|
|
17
17
|
EXIT_CODES
|
|
@@ -82,6 +82,21 @@
|
|
|
82
82
|
window.addEventListener('contextMenuRequested', function (e) {
|
|
83
83
|
post({ type: 'gutterpress:event', name: 'contextMenuRequested', detail: e.detail });
|
|
84
84
|
});
|
|
85
|
+
// In-flow block editing (protocol v8). `Requested` is the double-click entry
|
|
86
|
+
// point — the SPA answers it with a beginBlockEdit command. `Finished`
|
|
87
|
+
// carries the edited text back for an end the AUTHOR initiated inside the
|
|
88
|
+
// book (Escape / Cmd+Enter / blur), which the SPA cannot observe. `State`
|
|
89
|
+
// fires on every open and close, including SPA-initiated ones, and is what
|
|
90
|
+
// preview-shell.js holds hot-reload swaps on.
|
|
91
|
+
window.addEventListener('blockEditRequested', function (e) {
|
|
92
|
+
post({ type: 'gutterpress:event', name: 'blockEditRequested', detail: e.detail });
|
|
93
|
+
});
|
|
94
|
+
window.addEventListener('blockEditFinished', function (e) {
|
|
95
|
+
post({ type: 'gutterpress:event', name: 'blockEditFinished', detail: e.detail });
|
|
96
|
+
});
|
|
97
|
+
window.addEventListener('blockEditStateChanged', function (e) {
|
|
98
|
+
post({ type: 'gutterpress:event', name: 'blockEditStateChanged', detail: e.detail });
|
|
99
|
+
});
|
|
85
100
|
|
|
86
101
|
// Announce readiness as soon as previewAPI is defined.
|
|
87
102
|
function announceReady() {
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
runBuild,
|
|
7
7
|
splitOutPath,
|
|
8
8
|
startPreviewServer
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-wtyzagaq.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-qxebvmrq.js";
|
|
13
13
|
import {
|
|
14
14
|
UsageError,
|
|
15
15
|
parseEngine,
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
rejectExtraPositionals,
|
|
19
19
|
rejectUnknownFlags,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
22
|
-
import"./cli-
|
|
21
|
+
} from "./cli-jhvkcwvf.js";
|
|
22
|
+
import"./cli-eewynen9.js";
|
|
23
23
|
import"./cli-c41yr7he.js";
|
|
24
24
|
import {
|
|
25
25
|
BuildError
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// window.Gutterpress.pageOf(el) (0-based); it fires 'gp:layout' when its
|
|
6
6
|
// pagination completes.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// single
|
|
8
|
+
// Blocks are addressed by `{chapter, range}` (data-source-range) — see
|
|
9
|
+
// blocksMatchingRange below. The native viewer never clones an element across
|
|
10
|
+
// pages (an element that visually spans pages is still ONE element), so such a
|
|
11
|
+
// spec resolves to AT MOST ONE element. That is what lets in-flow editing put
|
|
12
|
+
// a single contenteditable on a block that spans a page break.
|
|
13
13
|
|
|
14
14
|
(function () {
|
|
15
15
|
'use strict';
|
|
@@ -625,15 +625,39 @@
|
|
|
625
625
|
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
// ──
|
|
629
|
-
// docs/inline-editing-plan.md §
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
//
|
|
628
|
+
// ── In-flow block editing (protocol v8) ────────────────────────────────────
|
|
629
|
+
// docs/inline-editing-plan.md §3.1 / ADR 0009 decision 4 (as revised).
|
|
630
|
+
//
|
|
631
|
+
// The block's OWN element becomes the editing surface: its rendered HTML is
|
|
632
|
+
// swapped for that block's markdown source under
|
|
633
|
+
// `contenteditable="plaintext-only"`, so the caret sits in the real page, in
|
|
634
|
+
// the book's own typography, and Chromium's fragmenter re-flows the pages
|
|
635
|
+
// around it as the author types. This replaced a floating CodeMirror panel
|
|
636
|
+
// positioned in host-SPA coordinates; `getRectsFor()`/`setEditMask()` existed
|
|
637
|
+
// ONLY to serve that panel and went with it (protocol v8).
|
|
638
|
+
//
|
|
639
|
+
// Three properties of the native viewer are what make this work, each
|
|
640
|
+
// spike-verified rather than assumed (plan §2) — under Paged.js none of them
|
|
641
|
+
// held, which is why the panel existed:
|
|
642
|
+
// 1. A block spanning a page break is ONE element with several client
|
|
643
|
+
// rects, so it takes ONE contenteditable and the caret crosses the
|
|
644
|
+
// break natively (ArrowDown walks into the next page).
|
|
645
|
+
// 2. `white-space: pre-wrap` + `plaintext-only` round-trips multi-line
|
|
646
|
+
// markdown EXACTLY through `textContent` (lists, tables, fences); Enter
|
|
647
|
+
// inserts a real "\n" rather than a <div>/<br>, and a rich-HTML paste
|
|
648
|
+
// is stripped to text by the browser. So there is no serializer and no
|
|
649
|
+
// sanitiser here — text in, text out.
|
|
650
|
+
// 3. `Gutterpress.refresh()` -> `relayout()` rebuilds the strips from
|
|
651
|
+
// scratch and re-measures, so a growing block gets a correct page
|
|
652
|
+
// count. This is MANDATORY, not cosmetic: `.gp-strip` is
|
|
653
|
+
// `column-fill: auto` and its `.gp-run` clips to the last measured page,
|
|
654
|
+
// so content that grows past that is silently invisible rather than
|
|
655
|
+
// overlapping (measured: a strip needing 2400px still clipped at its
|
|
656
|
+
// 900px run width). Never mutate this DOM without scheduling a refresh.
|
|
657
|
+
//
|
|
658
|
+
// Nothing here writes to disk. `endBlockEdit()` hands text back and the SPA
|
|
659
|
+
// decides whether it becomes a patch, behind the commit engine's clean-buffer
|
|
660
|
+
// gate (ADR 0009 decision 3, unchanged).
|
|
637
661
|
|
|
638
662
|
function rangedBlocks() {
|
|
639
663
|
return Array.from(document.querySelectorAll('[data-source-range]'));
|
|
@@ -644,6 +668,10 @@
|
|
|
644
668
|
return rangedBlocks().filter(function (el) { return chapterOf(el) === chapter; });
|
|
645
669
|
}
|
|
646
670
|
|
|
671
|
+
// The native viewer never clones, so a `{chapter, range}` spec resolves to AT
|
|
672
|
+
// MOST ONE element — this returns an array only because callers still guard
|
|
673
|
+
// on emptiness (a range that no longer matches anything: block deleted or
|
|
674
|
+
// moved since the target was captured).
|
|
647
675
|
function blocksMatchingRange(chapter, range) {
|
|
648
676
|
if (!range) return [];
|
|
649
677
|
return rangedBlocksInChapter(chapter).filter(function (el) {
|
|
@@ -652,42 +680,287 @@
|
|
|
652
680
|
});
|
|
653
681
|
}
|
|
654
682
|
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
683
|
+
// Re-pagination debounce while typing. Tuned by measurement, not taste:
|
|
684
|
+
// `relayout()` costs "the same order as mount (tens of ms on a real book)"
|
|
685
|
+
// per fragment.ts, so it cannot run per keystroke on a long book.
|
|
686
|
+
var EDIT_REFRESH_MS = 120;
|
|
687
|
+
|
|
688
|
+
// The one live edit, or null. At most one at a time — `beginBlockEdit`
|
|
689
|
+
// commits any predecessor before opening (an author double-clicking straight
|
|
690
|
+
// from one block to another must not silently drop the first edit).
|
|
691
|
+
var edit = null;
|
|
692
|
+
var editRefreshTimer = null;
|
|
693
|
+
|
|
694
|
+
function scheduleEditRefresh() {
|
|
695
|
+
if (editRefreshTimer !== null) clearTimeout(editRefreshTimer);
|
|
696
|
+
editRefreshTimer = setTimeout(function () {
|
|
697
|
+
editRefreshTimer = null;
|
|
698
|
+
repaginate();
|
|
699
|
+
}, EDIT_REFRESH_MS);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Character offset of the caret within the edit box, or null when the caret
|
|
703
|
+
// is not in it. Text-space, not node-space, so it survives the box being
|
|
704
|
+
// re-parented and its text nodes being renormalized.
|
|
705
|
+
function caretOffset() {
|
|
706
|
+
if (!edit) return null;
|
|
707
|
+
var sel = typeof window.getSelection === 'function' ? window.getSelection() : null;
|
|
708
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
709
|
+
var r = sel.getRangeAt(0);
|
|
710
|
+
if (!edit.el.contains(r.startContainer)) return null;
|
|
711
|
+
var pre = document.createRange();
|
|
712
|
+
pre.selectNodeContents(edit.el);
|
|
713
|
+
pre.setEnd(r.startContainer, r.startOffset);
|
|
714
|
+
return pre.toString().length;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Depth-first text nodes, by `nodeType === 3` rather than TreeWalker +
|
|
718
|
+
// NodeFilter: the preview scripts run in hosts that do not expose every DOM
|
|
719
|
+
// global (the iframe test harness evaluates this file with an explicit,
|
|
720
|
+
// small global list), and a caret helper is not worth a host requirement.
|
|
721
|
+
function textNodesIn(root) {
|
|
722
|
+
var out = [];
|
|
723
|
+
var stack = [root];
|
|
724
|
+
while (stack.length) {
|
|
725
|
+
var node = stack.pop();
|
|
726
|
+
if (node.nodeType === 3) { out.push(node); continue; }
|
|
727
|
+
for (var i = node.childNodes.length - 1; i >= 0; i--) stack.push(node.childNodes[i]);
|
|
674
728
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
729
|
+
return out;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function restoreCaret(offset) {
|
|
733
|
+
if (!edit || offset === null || offset === undefined) return;
|
|
734
|
+
var nodes = textNodesIn(edit.el);
|
|
735
|
+
var seen = 0;
|
|
736
|
+
for (var n = 0; n < nodes.length; n++) {
|
|
737
|
+
var node = nodes[n];
|
|
738
|
+
var len = node.nodeValue ? node.nodeValue.length : 0;
|
|
739
|
+
if (seen + len >= offset) {
|
|
740
|
+
var range = document.createRange();
|
|
741
|
+
range.setStart(node, Math.max(0, Math.min(len, offset - seen)));
|
|
742
|
+
range.collapse(true);
|
|
743
|
+
var sel = typeof window.getSelection === 'function' ? window.getSelection() : null;
|
|
744
|
+
if (!sel) return;
|
|
745
|
+
try { sel.removeAllRanges(); sel.addRange(range); } catch (_e) { /* best effort */ }
|
|
746
|
+
return;
|
|
687
747
|
}
|
|
688
|
-
|
|
748
|
+
seen += len;
|
|
749
|
+
}
|
|
750
|
+
// Offset past the end (the text shrank): land at the end.
|
|
751
|
+
var end = document.createRange();
|
|
752
|
+
end.selectNodeContents(edit.el);
|
|
753
|
+
end.collapse(false);
|
|
754
|
+
var sel2 = typeof window.getSelection === 'function' ? window.getSelection() : null;
|
|
755
|
+
if (!sel2) return;
|
|
756
|
+
try { sel2.removeAllRanges(); sel2.addRange(end); } catch (_e) { /* best effort */ }
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function repaginate() {
|
|
760
|
+
if (!window.Gutterpress || typeof window.Gutterpress.refresh !== 'function') return;
|
|
761
|
+
// `relayout()` unwraps every strip and rebuilds it, which RE-PARENTS the
|
|
762
|
+
// edit box — and re-parenting a focused element drops both its focus and
|
|
763
|
+
// the selection. Unhandled, that means the caret dies on the first
|
|
764
|
+
// debounced refresh after the author starts typing and every keystroke
|
|
765
|
+
// after it goes nowhere. Capture in text space, restore after.
|
|
766
|
+
var offset = caretOffset();
|
|
767
|
+
try {
|
|
768
|
+
window.Gutterpress.refresh();
|
|
769
|
+
} catch (_e) {
|
|
770
|
+
// A refresh failure must not trap the author inside a broken edit; the
|
|
771
|
+
// authoritative render still arrives on commit.
|
|
772
|
+
}
|
|
773
|
+
// The strips are rebuilt, so cached sheet nodes are stale.
|
|
774
|
+
pages = [];
|
|
775
|
+
if (edit && offset !== null) {
|
|
776
|
+
try { edit.el.focus({ preventScroll: true }); } catch (_e2) { /* best effort */ }
|
|
777
|
+
restoreCaret(offset);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Place the caret from the click point, on the SOURCE text now in the box.
|
|
782
|
+
// Approximate by construction (the point was measured against the RENDERED
|
|
783
|
+
// text) and that is fine — it puts the caret near what the author aimed at,
|
|
784
|
+
// which is the whole affordance. Falls back to end-of-block.
|
|
785
|
+
function placeCaret(el, point) {
|
|
786
|
+
var range = null;
|
|
787
|
+
if (point && typeof document.caretRangeFromPoint === 'function') {
|
|
788
|
+
try {
|
|
789
|
+
var hit = document.caretRangeFromPoint(point.x, point.y);
|
|
790
|
+
if (hit && el.contains(hit.startContainer)) range = hit;
|
|
791
|
+
} catch (_e) { /* fall through to end-of-block */ }
|
|
792
|
+
}
|
|
793
|
+
if (!range) {
|
|
794
|
+
range = document.createRange();
|
|
795
|
+
range.selectNodeContents(el);
|
|
796
|
+
range.collapse(false);
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
var sel = typeof window.getSelection === 'function' ? window.getSelection() : null;
|
|
800
|
+
if (!sel) return;
|
|
801
|
+
sel.removeAllRanges();
|
|
802
|
+
sel.addRange(range);
|
|
803
|
+
} catch (_e) {
|
|
804
|
+
// Focus is already set, so the box is usable even if seating the caret
|
|
805
|
+
// failed (a detached selection, a host without Selection support). The
|
|
806
|
+
// author clicks once more; nothing is lost.
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function finishEdit(commit, notify) {
|
|
811
|
+
if (!edit) return { ended: false, text: null, commit: false };
|
|
812
|
+
if (editRefreshTimer !== null) {
|
|
813
|
+
clearTimeout(editRefreshTimer);
|
|
814
|
+
editRefreshTimer = null;
|
|
815
|
+
}
|
|
816
|
+
var el = edit.el;
|
|
817
|
+
var text = el.textContent || '';
|
|
818
|
+
var done = { ended: true, text: text, commit: commit === true, chapter: edit.chapter, range: edit.range };
|
|
819
|
+
|
|
820
|
+
// Restore the rendered HTML on BOTH paths. On cancel it is the final
|
|
821
|
+
// state; on commit it avoids showing raw markdown for the ~500ms until the
|
|
822
|
+
// authoritative re-render swaps the frame.
|
|
823
|
+
el.removeAttribute('contenteditable');
|
|
824
|
+
if (edit.hadWhiteSpace) el.style.whiteSpace = edit.whiteSpace;
|
|
825
|
+
else el.style.removeProperty('white-space');
|
|
826
|
+
if (!el.getAttribute('style')) el.removeAttribute('style');
|
|
827
|
+
el.classList.remove('gutterpress-editing');
|
|
828
|
+
el.innerHTML = edit.html;
|
|
829
|
+
el.removeEventListener('input', onEditInput);
|
|
830
|
+
document.removeEventListener('mousedown', onDocumentPointerDown, true);
|
|
831
|
+
window.removeEventListener('focus', onWindowFocus);
|
|
832
|
+
edit = null;
|
|
833
|
+
repaginate();
|
|
834
|
+
|
|
835
|
+
if (notify) {
|
|
836
|
+
window.dispatchEvent(new CustomEvent('blockEditFinished', { detail: done }));
|
|
837
|
+
}
|
|
838
|
+
// ALWAYS emitted, on both paths, unlike blockEditFinished above — the
|
|
839
|
+
// shell holds hot-reload swaps on this and a missed close would freeze the
|
|
840
|
+
// preview until the next manual action.
|
|
841
|
+
window.dispatchEvent(new CustomEvent('blockEditStateChanged', { detail: { open: false } }));
|
|
842
|
+
return done;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function onEditInput() {
|
|
846
|
+
scheduleEditRefresh();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// "Clicked away" commits — driven by a POINTER PRESS in the book, never by
|
|
850
|
+
// `blur`.
|
|
851
|
+
//
|
|
852
|
+
// Blur was the obvious signal and it is the wrong one here. The open request
|
|
853
|
+
// arrives by postMessage from a click in the host SPA (the context menu's
|
|
854
|
+
// "Edit this block"), so this frame does not hold browsing-context focus when
|
|
855
|
+
// `startEdit` runs. Focusing the box then races the frame-focus transition:
|
|
856
|
+
// Chromium settles `activeElement` back to BODY and the element receives a
|
|
857
|
+
// blur it never earned. Measured end-to-end in the packaged app — the editor
|
|
858
|
+
// opened and closed 7ms later, committing content the author never saw, so
|
|
859
|
+
// both entry points looked like they did nothing at all. Guarding on
|
|
860
|
+
// `document.hasFocus()` does not separate the two cases either: a real click
|
|
861
|
+
// on another paragraph ALSO lands on BODY with the document focused.
|
|
862
|
+
//
|
|
863
|
+
// A pointer press outside the box is unambiguous and needs no focus
|
|
864
|
+
// bookkeeping. Focus leaving the frame entirely (the author reaches for the
|
|
865
|
+
// editor pane or another window) is deliberately NOT a commit — the host
|
|
866
|
+
// drives those ends explicitly through `endBlockEdit`, and the box stays
|
|
867
|
+
// visibly open meanwhile, which is honest about the state.
|
|
868
|
+
function onDocumentPointerDown(e) {
|
|
869
|
+
if (!edit) return;
|
|
870
|
+
if (e.target && edit.el.contains(e.target)) return;
|
|
871
|
+
finishEdit(true, true);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Put the caret in the box, and KEEP putting it there until it sticks.
|
|
875
|
+
//
|
|
876
|
+
// Opening from the context menu is a postMessage with no user activation, so
|
|
877
|
+
// this frame cannot focus itself; the host focuses the preview and
|
|
878
|
+
// preview-shell.js hands that down to this frame (see both). That transition
|
|
879
|
+
// lands AFTER `startEdit` runs, and Chromium resets `activeElement` to BODY
|
|
880
|
+
// as it completes — so the `el.focus()` inside `startEdit` is silently undone
|
|
881
|
+
// and the author's keystrokes go to the app behind the preview. Measured
|
|
882
|
+
// end-to-end in the packaged app: the box opened with the whole frame chain
|
|
883
|
+
// focused, `document.activeElement` sat on `BODY.gp-stage`, and typing landed
|
|
884
|
+
// outside the book. The same `el.focus()` issued a moment later worked, which
|
|
885
|
+
// is what makes this a race rather than a permission problem.
|
|
886
|
+
//
|
|
887
|
+
// So: seat it now, then re-seat across the transition until `activeElement`
|
|
888
|
+
// IS the box. Bounded and idempotent — it stops on the first success, when
|
|
889
|
+
// the edit closes, or after the last attempt, and never fights the author
|
|
890
|
+
// (any element inside the box already counts as seated).
|
|
891
|
+
var SEAT_DELAYS_MS = [0, 16, 50, 150];
|
|
892
|
+
|
|
893
|
+
function seated() {
|
|
894
|
+
return !!edit && (document.activeElement === edit.el || edit.el.contains(document.activeElement));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function seatCaret() {
|
|
898
|
+
if (!edit || seated()) return;
|
|
899
|
+
try { edit.el.focus({ preventScroll: true }); } catch (_e) { /* best effort */ }
|
|
900
|
+
placeCaret(edit.el, edit.caret);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function seatCaretRepeatedly() {
|
|
904
|
+
var target = edit;
|
|
905
|
+
SEAT_DELAYS_MS.forEach(function (ms) {
|
|
906
|
+
setTimeout(function () {
|
|
907
|
+
if (edit !== target) return;
|
|
908
|
+
seatCaret();
|
|
909
|
+
}, ms);
|
|
689
910
|
});
|
|
690
|
-
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Whenever this frame gains focus while an edit is open, the caret belongs in
|
|
914
|
+
// the box — covers orderings where focus arrives later still.
|
|
915
|
+
function onWindowFocus() {
|
|
916
|
+
seatCaret();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function startEdit(spec) {
|
|
920
|
+
var el = blocksMatchingRange(spec.chapter, spec.range)[0] || null;
|
|
921
|
+
if (!el) return { ok: false, reason: 'unresolved' };
|
|
922
|
+
if (edit) finishEdit(true, true);
|
|
923
|
+
|
|
924
|
+
var style = el.style;
|
|
925
|
+
edit = {
|
|
926
|
+
el: el,
|
|
927
|
+
chapter: spec.chapter,
|
|
928
|
+
range: spec.range,
|
|
929
|
+
caret: spec.caret,
|
|
930
|
+
html: el.innerHTML,
|
|
931
|
+
hadWhiteSpace: !!style.whiteSpace,
|
|
932
|
+
whiteSpace: style.whiteSpace
|
|
933
|
+
};
|
|
934
|
+
// pre-wrap is load-bearing, not cosmetic: without it the source's newlines
|
|
935
|
+
// collapse and a multi-line block (list, table, fence) is unreadable AND
|
|
936
|
+
// uneditable line-by-line.
|
|
937
|
+
style.whiteSpace = 'pre-wrap';
|
|
938
|
+
el.classList.add('gutterpress-editing');
|
|
939
|
+
el.setAttribute('contenteditable', 'plaintext-only');
|
|
940
|
+
el.textContent = typeof spec.text === 'string' ? spec.text : '';
|
|
941
|
+
el.addEventListener('input', onEditInput);
|
|
942
|
+
document.addEventListener('mousedown', onDocumentPointerDown, true);
|
|
943
|
+
window.addEventListener('focus', onWindowFocus);
|
|
944
|
+
// Pull browsing-context focus into THIS frame first. Without it the caret
|
|
945
|
+
// is nowhere the author can type: the open request came from a click in
|
|
946
|
+
// the host document, so focus is still there.
|
|
947
|
+
try { window.focus(); } catch (_e) { /* focus stays with the host */ }
|
|
948
|
+
// Focus and caret seating are best-effort. A throw here must not escape:
|
|
949
|
+
// the edit state is already installed, so an exception would leave the
|
|
950
|
+
// block stuck in editing mode with the host believing nothing opened.
|
|
951
|
+
try {
|
|
952
|
+
el.focus({ preventScroll: true });
|
|
953
|
+
} catch (_e) {
|
|
954
|
+
try { el.focus(); } catch (_e2) { /* unfocusable host; the box still edits */ }
|
|
955
|
+
}
|
|
956
|
+
placeCaret(el, spec.caret);
|
|
957
|
+
// The swap from rendered HTML to source text changes the block's extent
|
|
958
|
+
// immediately, so pagination is already stale before a single keystroke.
|
|
959
|
+
// This re-parents the box, so seat the caret AFTER it, never before.
|
|
960
|
+
repaginate();
|
|
961
|
+
seatCaretRepeatedly();
|
|
962
|
+
window.dispatchEvent(new CustomEvent('blockEditStateChanged', { detail: { open: true } }));
|
|
963
|
+
return { ok: true };
|
|
691
964
|
}
|
|
692
965
|
|
|
693
966
|
var api = {
|
|
@@ -802,7 +1075,14 @@
|
|
|
802
1075
|
// (a point inside a .gp-sheet but outside every author box resolves to
|
|
803
1076
|
// the sheet's owning .page/.spread). An older SPA simply ignores the
|
|
804
1077
|
// extra field; a newer SPA feature-detects by the field's presence.
|
|
805
|
-
|
|
1078
|
+
// v8: in-flow block editing. ADDED beginBlockEdit()/endBlockEdit() and the
|
|
1079
|
+
// blockEditRequested/blockEditFinished/blockEditStateChanged events;
|
|
1080
|
+
// REMOVED getRectsFor() and
|
|
1081
|
+
// setEditMask(), which existed only to place and de-clutter behind the
|
|
1082
|
+
// floating edit panel this replaces. A v8 lib with a pre-v8 SPA loses the
|
|
1083
|
+
// "Edit this block" action and nothing else; a v8 SPA feature-detects on
|
|
1084
|
+
// the version before offering it.
|
|
1085
|
+
getProtocolVersion: function () { return 8; },
|
|
806
1086
|
|
|
807
1087
|
// Resolve the annotated element/selection at a viewport point (protocol
|
|
808
1088
|
// v4). Pure read; see buildContextTarget() above for the full contract.
|
|
@@ -814,34 +1094,26 @@
|
|
|
814
1094
|
return buildContextTarget(contextPointEl(spec.x, spec.y, spec.topmostOnly));
|
|
815
1095
|
},
|
|
816
1096
|
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
1097
|
+
// Open the in-flow editor on one block (protocol v8). `text` is that
|
|
1098
|
+
// block's markdown source, read SPA-side from the authoritative buffer —
|
|
1099
|
+
// this function never derives source from the DOM. `caret` is the optional
|
|
1100
|
+
// viewport point to seat the caret near. Returns
|
|
1101
|
+
// `{ok: false, reason: 'unresolved'}` when the range no longer matches a
|
|
1102
|
+
// live block, so the SPA can drop the request instead of hanging.
|
|
1103
|
+
beginBlockEdit: function (spec) {
|
|
1104
|
+
spec = spec || {};
|
|
1105
|
+
return startEdit(spec);
|
|
822
1106
|
},
|
|
823
1107
|
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
|
|
831
|
-
// scoped per-block, and there is at most one overlay open at a time.
|
|
832
|
-
setEditMask: function (spec) {
|
|
1108
|
+
// Close it and hand back the current text (protocol v8). Idempotent:
|
|
1109
|
+
// `{ended: false}` when nothing is open. The SPA calls this to force an end
|
|
1110
|
+
// it initiated (a dialog opening over the workspace); ends the author
|
|
1111
|
+
// initiates from inside the book — Escape, Cmd/Ctrl+Enter, blur — arrive
|
|
1112
|
+
// as the `blockEditFinished` event instead, so a keystroke the SPA cannot
|
|
1113
|
+
// see still resolves the edit. Both paths restore the rendered HTML.
|
|
1114
|
+
endBlockEdit: function (spec) {
|
|
833
1115
|
spec = spec || {};
|
|
834
|
-
|
|
835
|
-
for (var i = 0; i < els.length; i++) {
|
|
836
|
-
if (spec.masked) els[i].classList.add('gutterpress-edit-mask');
|
|
837
|
-
else els[i].classList.remove('gutterpress-edit-mask');
|
|
838
|
-
}
|
|
839
|
-
var root = document.documentElement;
|
|
840
|
-
if (root && root.classList) {
|
|
841
|
-
if (spec.masked) root.classList.add('gutterpress-edit-scroll-lock');
|
|
842
|
-
else root.classList.remove('gutterpress-edit-scroll-lock');
|
|
843
|
-
}
|
|
844
|
-
return { count: els.length };
|
|
1116
|
+
return finishEdit(spec.commit !== false, false);
|
|
845
1117
|
},
|
|
846
1118
|
|
|
847
1119
|
// Publish any debounced reader movement before a host atomically replaces
|
|
@@ -1061,23 +1333,29 @@
|
|
|
1061
1333
|
} catch (_e) { /* non-fatal: highlight just renders unstyled */ }
|
|
1062
1334
|
}
|
|
1063
1335
|
|
|
1064
|
-
//
|
|
1065
|
-
// Preview-only, never part of the PDF build path.
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1336
|
+
// In-flow editing style (protocol v8, plan §3.1).
|
|
1337
|
+
// Preview-only, never part of the PDF build path. Marks the block being
|
|
1338
|
+
// edited without moving it: `outline` and `box-shadow` are chosen because
|
|
1339
|
+
// they take NO space in layout — a border or padding here would change the
|
|
1340
|
+
// block's extent and repaginate the book on entering edit mode, which is
|
|
1341
|
+
// exactly the divergence this project cares most about. Nothing in this
|
|
1342
|
+
// rule may become layout-affecting.
|
|
1343
|
+
//
|
|
1344
|
+
// No scroll lock and no dimming of the surrounding page: the caret is in the
|
|
1345
|
+
// flow now, so the page can scroll freely and the neighbouring text stays
|
|
1346
|
+
// legible while the author works. Both existed only to prop up the floating
|
|
1347
|
+
// panel this replaced. Exact visual treatment is a design-review item
|
|
1348
|
+
// (plan §6), same as the mask treatment was.
|
|
1072
1349
|
if (typeof document.createElement === 'function') {
|
|
1073
1350
|
try {
|
|
1074
|
-
var
|
|
1075
|
-
|
|
1076
|
-
'.gutterpress-
|
|
1077
|
-
'
|
|
1078
|
-
'
|
|
1079
|
-
|
|
1080
|
-
|
|
1351
|
+
var editStyle = document.createElement('style');
|
|
1352
|
+
editStyle.textContent =
|
|
1353
|
+
'.gutterpress-editing{outline:2px solid Highlight;outline-offset:2px;' +
|
|
1354
|
+
'box-shadow:0 0 0 2px color-mix(in srgb, Highlight 25%, transparent);' +
|
|
1355
|
+
'border-radius:2px;caret-color:currentColor;}' +
|
|
1356
|
+
'.gutterpress-editing:focus{outline-style:solid;}';
|
|
1357
|
+
(document.head || document.documentElement).appendChild(editStyle);
|
|
1358
|
+
} catch (_e) { /* non-fatal: the edit box just renders unmarked */ }
|
|
1081
1359
|
}
|
|
1082
1360
|
|
|
1083
1361
|
// Click-to-source: emit elementActivated when the user clicks a source-mapped
|
|
@@ -1103,6 +1381,10 @@
|
|
|
1103
1381
|
// with no replacement menu would be a strict regression. No event is
|
|
1104
1382
|
// dispatched for 'none' either.
|
|
1105
1383
|
document.addEventListener('contextmenu', function (e) {
|
|
1384
|
+
// Inside the live in-flow editor the native menu is the useful one
|
|
1385
|
+
// (cut/copy/paste on the source text). Ours offers block actions that
|
|
1386
|
+
// make no sense mid-edit.
|
|
1387
|
+
if (edit && edit.el.contains(e.target)) return;
|
|
1106
1388
|
var detail = api.getContextTargetAt({ x: e.clientX, y: e.clientY });
|
|
1107
1389
|
if (detail.kind === 'none') return;
|
|
1108
1390
|
e.preventDefault();
|
|
@@ -1112,6 +1394,47 @@
|
|
|
1112
1394
|
window.dispatchEvent(new CustomEvent('contextMenuRequested', { detail: detail }));
|
|
1113
1395
|
}, true);
|
|
1114
1396
|
|
|
1397
|
+
// Double-click to edit (protocol v8, plan §6). The SECOND entry point
|
|
1398
|
+
// alongside the context menu's "Edit this block" — both land on the same
|
|
1399
|
+
// SPA handler, which reads the source slice and calls beginBlockEdit().
|
|
1400
|
+
// Requesting rather than starting is deliberate: only the SPA can read the
|
|
1401
|
+
// authoritative buffer, so the book document never sources its own text.
|
|
1402
|
+
//
|
|
1403
|
+
// Bails while an edit is live so a double-click INSIDE the box keeps its
|
|
1404
|
+
// native meaning (select word). Never preventDefault: on a block that does
|
|
1405
|
+
// not resolve, double-click must keep selecting text as it always has.
|
|
1406
|
+
document.addEventListener('dblclick', function (e) {
|
|
1407
|
+
if (edit) return;
|
|
1408
|
+
var el = e.target && e.target.closest ? e.target.closest('[data-source-range]') : null;
|
|
1409
|
+
if (!el) return;
|
|
1410
|
+
var range = sourceRangeOf(el);
|
|
1411
|
+
var chapter = chapterOf(el);
|
|
1412
|
+
if (!range || !chapter) return;
|
|
1413
|
+
window.dispatchEvent(new CustomEvent('blockEditRequested', {
|
|
1414
|
+
detail: { chapter: chapter, range: range, x: e.clientX, y: e.clientY, via: 'dblclick' }
|
|
1415
|
+
}));
|
|
1416
|
+
}, true);
|
|
1417
|
+
|
|
1418
|
+
// Edit-mode keys. These MUST live inside the book iframe for the same
|
|
1419
|
+
// physical reason the Shift+F10 listener below does: the caret is in a
|
|
1420
|
+
// cross-origin document, so its keystrokes never reach the SPA.
|
|
1421
|
+
// Cmd/Ctrl+Enter commits (Enter alone is a newline — a markdown block can
|
|
1422
|
+
// be multi-line); Escape cancels and restores.
|
|
1423
|
+
document.addEventListener('keydown', function (e) {
|
|
1424
|
+
if (!edit) return;
|
|
1425
|
+
if (e.key === 'Escape') {
|
|
1426
|
+
e.preventDefault();
|
|
1427
|
+
e.stopPropagation();
|
|
1428
|
+
finishEdit(false, true);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
1432
|
+
e.preventDefault();
|
|
1433
|
+
e.stopPropagation();
|
|
1434
|
+
finishEdit(true, true);
|
|
1435
|
+
}
|
|
1436
|
+
}, true);
|
|
1437
|
+
|
|
1115
1438
|
// Shift+F10 / the dedicated ContextMenu key. This listener MUST live
|
|
1116
1439
|
// inside the book iframe: keyboard events targeted at a focused element
|
|
1117
1440
|
// in a cross-origin iframe never reach the parent SPA, so an SPA-side
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
var pendingSwap = null;
|
|
20
20
|
var pendingSwapTimer = null;
|
|
21
21
|
var SCROLL_IDLE_MS = 250;
|
|
22
|
+
// True while the book iframe has a live in-flow block editor open
|
|
23
|
+
// (preview-interface.js's blockEditStateChanged, protocol v8). A swap
|
|
24
|
+
// replaces the whole iframe, so swapping mid-edit would destroy the caret
|
|
25
|
+
// and the author's uncommitted typing with it — see holdForEdit below.
|
|
26
|
+
var blockEditOpen = false;
|
|
22
27
|
if (!active) return;
|
|
23
28
|
|
|
24
29
|
// Transparent bridge relay: forward host-toolbar commands to the active book
|
|
@@ -27,26 +32,36 @@
|
|
|
27
32
|
try {
|
|
28
33
|
if (window.parent !== window && e.source === window.parent) {
|
|
29
34
|
if (active && active.contentWindow) active.contentWindow.postMessage(e.data, '*');
|
|
35
|
+
// Opening the in-flow editor needs KEYBOARD focus in the book, and a
|
|
36
|
+
// postMessage carries no user activation the book could focus itself
|
|
37
|
+
// with. The host focuses this shell frame; this hands it the rest of
|
|
38
|
+
// the way down to the active book iframe, whose own window-focus
|
|
39
|
+
// listener then seats the caret.
|
|
40
|
+
if (e.data && e.data.type === 'gutterpress:cmd' && e.data.cmd === 'beginBlockEdit' && active) {
|
|
41
|
+
try { active.focus(); } catch (_f) {}
|
|
42
|
+
}
|
|
30
43
|
} else if (active && e.source === active.contentWindow) {
|
|
31
44
|
var data = e.data;
|
|
32
45
|
if (data && data.type === 'gutterpress:event' && data.name === 'viewportChanged') {
|
|
33
46
|
lastViewportChangeAt = Date.now();
|
|
34
47
|
if (pendingSwap) armPendingSwap();
|
|
35
48
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
detail.hotReload = true;
|
|
43
|
-
var startedAt = hotReloadFrame.__gutterpressReloadStartedAt;
|
|
44
|
-
detail.hotReloadMs = typeof startedAt === 'number' ? Math.max(0, Date.now() - startedAt) : 0;
|
|
45
|
-
detail.revision = hotReloadFrame.__gutterpressRevision;
|
|
46
|
-
detail.updateMode = 'full-reload';
|
|
47
|
-
data = { type: data.type, name: data.name, detail: detail };
|
|
48
|
-
hotReloadFrame = null;
|
|
49
|
+
if (data && data.type === 'gutterpress:event' && data.name === 'blockEditStateChanged') {
|
|
50
|
+
blockEditOpen = !!(data.detail && data.detail.open);
|
|
51
|
+
// Releasing the hold: a swap that arrived mid-edit is still queued,
|
|
52
|
+
// so start it now that the caret is gone.
|
|
53
|
+
if (!blockEditOpen && pendingSwap) armPendingSwap();
|
|
49
54
|
}
|
|
55
|
+
// A swapped-in frame's own lifecycle events are the shell's business,
|
|
56
|
+
// not the host's: finish() reports the swap (see reportSwapComplete).
|
|
57
|
+
// Relaying them too would either double-report or — when the frame
|
|
58
|
+
// paginates before its `load` event, which is the common case — report
|
|
59
|
+
// nothing at all, because the message arrives while the frame is still
|
|
60
|
+
// `building` and matches no branch here.
|
|
61
|
+
if (
|
|
62
|
+
active === hotReloadFrame && data && data.type === 'gutterpress:event' &&
|
|
63
|
+
(data.name === 'ready' || data.name === 'renderingComplete')
|
|
64
|
+
) return;
|
|
50
65
|
if (window.parent !== window) window.parent.postMessage(data, '*');
|
|
51
66
|
} else if (retiring && e.source === retiring.contentWindow && window.parent !== window) {
|
|
52
67
|
var retiringData = e.data;
|
|
@@ -135,7 +150,33 @@
|
|
|
135
150
|
building = null;
|
|
136
151
|
}
|
|
137
152
|
|
|
153
|
+
// Tell the host the swap is done. This is a synchronous fact the shell knows
|
|
154
|
+
// — the replacement is paginated (onReady resolved), promoted, and refreshed
|
|
155
|
+
// — rather than a relayed message whose arrival depends on whether the
|
|
156
|
+
// frame's pagination beat its own `load` event.
|
|
157
|
+
function reportSwapComplete(frame, pageState) {
|
|
158
|
+
if (window.parent === window) return;
|
|
159
|
+
var startedAt = frame.__gutterpressReloadStartedAt;
|
|
160
|
+
window.parent.postMessage({
|
|
161
|
+
type: 'gutterpress:event',
|
|
162
|
+
name: 'renderingComplete',
|
|
163
|
+
detail: {
|
|
164
|
+
totalPages: pageState ? pageState.totalPages : 0,
|
|
165
|
+
hotReload: true,
|
|
166
|
+
hotReloadMs: typeof startedAt === 'number' ? Math.max(0, Date.now() - startedAt) : 0,
|
|
167
|
+
revision: frame.__gutterpressRevision,
|
|
168
|
+
updateMode: 'full-reload'
|
|
169
|
+
}
|
|
170
|
+
}, '*');
|
|
171
|
+
}
|
|
172
|
+
|
|
138
173
|
function swap(instance, revision) {
|
|
174
|
+
// The editor lived in the frame we are about to retire, so the hold is
|
|
175
|
+
// void whatever the book document last reported. Every update path reaches
|
|
176
|
+
// swap() through armPendingSwap(), which refuses while an edit is open, so
|
|
177
|
+
// this only fires when the frame goes away some other way — but a flag
|
|
178
|
+
// left stuck true would freeze hot reload for the session.
|
|
179
|
+
blockEditOpen = false;
|
|
139
180
|
discardBuilding();
|
|
140
181
|
var frame = document.createElement('iframe');
|
|
141
182
|
frame.style.visibility = 'hidden';
|
|
@@ -175,7 +216,8 @@
|
|
|
175
216
|
hotReloadFrame = frame;
|
|
176
217
|
building = null;
|
|
177
218
|
var api = fwin(frame) && fwin(frame).previewAPI;
|
|
178
|
-
|
|
219
|
+
var pageState = api && typeof api.refresh === 'function' ? api.refresh() : null;
|
|
220
|
+
reportSwapComplete(frame, pageState);
|
|
179
221
|
frame.style.visibility = 'visible';
|
|
180
222
|
frame.removeAttribute('aria-hidden');
|
|
181
223
|
appliedInstance = instance;
|
|
@@ -229,6 +271,13 @@
|
|
|
229
271
|
|
|
230
272
|
function armPendingSwap() {
|
|
231
273
|
if (pendingSwapTimer !== null) clearTimeout(pendingSwapTimer);
|
|
274
|
+
// An open in-flow editor holds the swap indefinitely — there is no timeout
|
|
275
|
+
// to race, because an author may legitimately sit in one block for
|
|
276
|
+
// minutes. The hold is released by blockEditStateChanged{open:false},
|
|
277
|
+
// which preview-interface.js emits on EVERY close path (commit, cancel,
|
|
278
|
+
// blur, SPA-forced), and defensively by swap() below in case the frame
|
|
279
|
+
// carrying the editor goes away first.
|
|
280
|
+
if (blockEditOpen) return;
|
|
232
281
|
var delay = Math.max(0, SCROLL_IDLE_MS - (Date.now() - lastViewportChangeAt));
|
|
233
282
|
if (delay === 0) beginPendingSwap();
|
|
234
283
|
else pendingSwapTimer = setTimeout(beginPendingSwap, delay);
|
|
@@ -243,17 +292,23 @@
|
|
|
243
292
|
// Paged.js's `.pagedjs_page` DOM to find a chapter's live page range and
|
|
244
293
|
// graft a freshly-paginated replacement into it. Paged.js has been removed
|
|
245
294
|
// (native-only-migration-plan.md Phase 6). A native in-place splice was
|
|
246
|
-
// also tried and removed (2026-08-08 review)
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
295
|
+
// also tried and removed (2026-08-08 review), and the standing reason is
|
|
296
|
+
// PERFORMANCE, not soundness: measured end-to-end (file write -> change
|
|
297
|
+
// visible, 5 samples, 34pp field guide) the plain full reload (`swap`,
|
|
298
|
+
// below) is 509ms avg vs the incremental splice's 998ms avg. Every
|
|
299
|
+
// content-update goes straight to `swap()`.
|
|
300
|
+
//
|
|
301
|
+
// CORRECTED 2026-08-24: the 2026-08-08 review ALSO recorded a soundness
|
|
302
|
+
// objection — that `refresh()` -> `relayout()` "only re-measures the
|
|
303
|
+
// EXISTING strips", silently dropping any page context the edit
|
|
304
|
+
// introduces. That is false against the current `fragment.ts`, whose
|
|
305
|
+
// `relayout()` unwraps the strips and re-runs `buildStrips()` from
|
|
306
|
+
// scratch before re-measuring, precisely so a mutation that adds or
|
|
307
|
+
// removes a page-context run is seen. Do not cite the old objection as a
|
|
308
|
+
// reason a DOM mutation cannot be re-paginated — it can, and the inline
|
|
309
|
+
// editing work depends on it (docs/inline-editing-plan.md, ADR 0009
|
|
310
|
+
// decision 4 as revised). The perf comparison above still stands on its
|
|
311
|
+
// own for the full-document reload path.
|
|
257
312
|
|
|
258
313
|
function markActiveReady() {
|
|
259
314
|
onReady(active, function () {
|
|
@@ -7,17 +7,17 @@ import {
|
|
|
7
7
|
publishCredentialKey,
|
|
8
8
|
publishProviderFor,
|
|
9
9
|
runPublish
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-wtyzagaq.js";
|
|
11
11
|
import {
|
|
12
12
|
FileTokenStore,
|
|
13
13
|
log
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-qxebvmrq.js";
|
|
15
15
|
import {
|
|
16
16
|
UsageError,
|
|
17
17
|
rejectExtraPositionals,
|
|
18
18
|
rejectUnknownFlags
|
|
19
|
-
} from "./cli-
|
|
20
|
-
import"./cli-
|
|
19
|
+
} from "./cli-jhvkcwvf.js";
|
|
20
|
+
import"./cli-eewynen9.js";
|
|
21
21
|
import"./cli-c41yr7he.js";
|
|
22
22
|
import {
|
|
23
23
|
EXIT_CODES
|
|
@@ -194,10 +194,13 @@ export interface ResolvedConfig {
|
|
|
194
194
|
title: string;
|
|
195
195
|
authors: string[];
|
|
196
196
|
/**
|
|
197
|
-
* Resolved pagination engine
|
|
198
|
-
*
|
|
197
|
+
* Resolved pagination engine. Always `"native"` — `manifest.ts` assigns the
|
|
198
|
+
* literal, warning and ignoring an author's `engine: paged`. Typed as the
|
|
199
|
+
* single value it can hold so no consumer can branch on an engine that
|
|
200
|
+
* cannot run. The INPUT union ({@link GutterpressManifest.engine}) keeps
|
|
201
|
+
* both spellings, because old manifests must go on parsing.
|
|
199
202
|
*/
|
|
200
|
-
engine: "
|
|
203
|
+
engine: "native";
|
|
201
204
|
/** Validated publish-target ids for this book (may be empty). */
|
|
202
205
|
targets: string[];
|
|
203
206
|
styles?: string[];
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-wtyzagaq.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-qxebvmrq.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
11
|
+
} from "./cli-jhvkcwvf.js";
|
|
12
|
+
import"./cli-eewynen9.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
package/package.json
CHANGED