@open-press/core 1.1.0 → 1.1.2
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/engine/commands/dev.mjs +1 -1
- package/engine/commands/typecheck.mjs +88 -3
- package/package.json +1 -1
- package/src/openpress/app/OpenPressApp.tsx +19 -6
- package/src/openpress/reader/PageThumbnailsPanel.tsx +7 -0
- package/src/openpress/shared/runtimeMode.ts +10 -2
- package/src/styles/openpress/workbench-panels.css +17 -5
package/engine/commands/dev.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export async function run({ root, options }) {
|
|
|
17
17
|
}
|
|
18
18
|
const host = options.host ?? "127.0.0.1";
|
|
19
19
|
const port = options.port ?? "5173";
|
|
20
|
-
const url = `http://${host}:${port}
|
|
20
|
+
const url = `http://${host}:${port}/workspace`;
|
|
21
21
|
if (options.dryRun) {
|
|
22
22
|
console.log(`OpenPress dev URL: ${url}`);
|
|
23
23
|
if (!options.noBuild) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
|
-
import { runCommand } from "./_shared.mjs";
|
|
5
|
+
import { FRAMEWORK_ROOT, runCommand } from "./_shared.mjs";
|
|
6
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
5
7
|
|
|
6
8
|
// Run typecheck via the locally installed typescript. The previous
|
|
7
9
|
// implementation used `npx tsc`; npm 11 + Node 24 (our CI / release
|
|
@@ -17,14 +19,15 @@ import { runCommand } from "./_shared.mjs";
|
|
|
17
19
|
// even when bare require.resolve doesn't, which is what CI hits.
|
|
18
20
|
export async function run({ root }) {
|
|
19
21
|
const absoluteRoot = path.resolve(root);
|
|
22
|
+
const projectPath = await resolveTypecheckProject(absoluteRoot);
|
|
20
23
|
|
|
21
24
|
const tscBin = resolveTscBin(absoluteRoot);
|
|
22
25
|
if (tscBin) {
|
|
23
|
-
return runCommand("node", [tscBin, "--noEmit", "-p",
|
|
26
|
+
return runCommand("node", [tscBin, "--noEmit", "-p", projectPath], absoluteRoot);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
if (hasCommand("pnpm")) {
|
|
27
|
-
return runCommand("pnpm", ["exec", "tsc", "--noEmit", "-p",
|
|
30
|
+
return runCommand("pnpm", ["exec", "tsc", "--noEmit", "-p", projectPath], absoluteRoot);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
console.error("[openpress] typescript is not installed in this workspace.");
|
|
@@ -32,6 +35,88 @@ export async function run({ root }) {
|
|
|
32
35
|
return 1;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
async function resolveTypecheckProject(absoluteRoot) {
|
|
39
|
+
const workspaceProject = path.join(absoluteRoot, "tsconfig.json");
|
|
40
|
+
if (fs.existsSync(workspaceProject)) return workspaceProject;
|
|
41
|
+
return await writeGeneratedTypecheckProject(absoluteRoot);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeGeneratedTypecheckProject(absoluteRoot) {
|
|
45
|
+
const config = await loadConfig(absoluteRoot);
|
|
46
|
+
const outDir = path.join(absoluteRoot, ".openpress");
|
|
47
|
+
await mkdir(outDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
const projectPath = path.join(outDir, "typecheck.tsconfig.json");
|
|
50
|
+
const fromProjectDir = (target) => normalizePath(path.relative(outDir, target)) || ".";
|
|
51
|
+
const fromWorkspace = (target) => normalizePath(path.relative(absoluteRoot, target)) || ".";
|
|
52
|
+
const includeRoot = (target) => {
|
|
53
|
+
const relative = fromProjectDir(target);
|
|
54
|
+
return relative === "." ? "." : relative;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const typeRoots = [
|
|
58
|
+
path.join(absoluteRoot, "node_modules", "@types"),
|
|
59
|
+
path.join(FRAMEWORK_ROOT, "node_modules", "@types"),
|
|
60
|
+
]
|
|
61
|
+
.filter((dir) => fs.existsSync(dir))
|
|
62
|
+
.map((dir) => fromProjectDir(dir));
|
|
63
|
+
|
|
64
|
+
const typecheckConfig = {
|
|
65
|
+
compilerOptions: {
|
|
66
|
+
target: "ES2022",
|
|
67
|
+
useDefineForClassFields: true,
|
|
68
|
+
lib: ["DOM", "DOM.Iterable", "ES2022"],
|
|
69
|
+
allowJs: false,
|
|
70
|
+
skipLibCheck: true,
|
|
71
|
+
esModuleInterop: true,
|
|
72
|
+
allowSyntheticDefaultImports: true,
|
|
73
|
+
strict: true,
|
|
74
|
+
forceConsistentCasingInFileNames: true,
|
|
75
|
+
module: "ESNext",
|
|
76
|
+
moduleResolution: "Bundler",
|
|
77
|
+
types: ["node"],
|
|
78
|
+
...(typeRoots.length > 0 ? { typeRoots } : {}),
|
|
79
|
+
resolveJsonModule: true,
|
|
80
|
+
isolatedModules: true,
|
|
81
|
+
noEmit: true,
|
|
82
|
+
jsx: "react-jsx",
|
|
83
|
+
ignoreDeprecations: "6.0",
|
|
84
|
+
baseUrl: "..",
|
|
85
|
+
paths: {
|
|
86
|
+
"@open-press/core": [fromWorkspace(path.join(FRAMEWORK_ROOT, "src", "openpress", "core", "index.tsx"))],
|
|
87
|
+
"@open-press/core/mdx": [fromWorkspace(path.join(FRAMEWORK_ROOT, "src", "openpress", "mdx", "index.ts"))],
|
|
88
|
+
"@open-press/core/manuscript": [fromWorkspace(path.join(FRAMEWORK_ROOT, "src", "openpress", "manuscript", "index.tsx"))],
|
|
89
|
+
"@open-press/core/numbering": [fromWorkspace(path.join(FRAMEWORK_ROOT, "src", "openpress", "numbering", "index.ts"))],
|
|
90
|
+
"@/components": [
|
|
91
|
+
`${fromWorkspace(config.paths.componentsDir)}/index.ts`,
|
|
92
|
+
`${fromWorkspace(config.paths.componentsDir)}/index.tsx`,
|
|
93
|
+
],
|
|
94
|
+
"@/components/*": [`${fromWorkspace(config.paths.componentsDir)}/*`],
|
|
95
|
+
"@workspace/content": [fromWorkspace(config.paths.sourceDir)],
|
|
96
|
+
"@workspace/content/*": [`${fromWorkspace(config.paths.sourceDir)}/*`],
|
|
97
|
+
"@workspace/media": [fromWorkspace(config.paths.mediaDir)],
|
|
98
|
+
"@workspace/media/*": [`${fromWorkspace(config.paths.mediaDir)}/*`],
|
|
99
|
+
"@workspace/components": [fromWorkspace(config.paths.componentsDir)],
|
|
100
|
+
"@workspace/components/*": [`${fromWorkspace(config.paths.componentsDir)}/*`],
|
|
101
|
+
"@/*": [`${fromWorkspace(path.join(FRAMEWORK_ROOT, "src"))}/*`],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
include: [
|
|
105
|
+
`${includeRoot(config.paths.documentRoot)}/**/*.ts`,
|
|
106
|
+
`${includeRoot(config.paths.documentRoot)}/**/*.tsx`,
|
|
107
|
+
"../tests/**/*.test.ts",
|
|
108
|
+
"../tests/**/*.test.tsx",
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
await writeFile(projectPath, `${JSON.stringify(typecheckConfig, null, 2)}\n`, "utf8");
|
|
113
|
+
return projectPath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizePath(value) {
|
|
117
|
+
return value.replaceAll("\\", "/");
|
|
118
|
+
}
|
|
119
|
+
|
|
35
120
|
function resolveTscBin(absoluteRoot) {
|
|
36
121
|
try {
|
|
37
122
|
const require = createRequire(path.join(absoluteRoot, "package.json"));
|
package/package.json
CHANGED
|
@@ -218,20 +218,33 @@ export function OpenPressApp() {
|
|
|
218
218
|
|
|
219
219
|
function currentSlugFromLocation(): string {
|
|
220
220
|
if (typeof window === "undefined") return "";
|
|
221
|
-
return
|
|
221
|
+
return slugFromWorkspacePathname(window.location.pathname);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
function normalizeSlug(raw: string): string {
|
|
225
225
|
return raw.replace(/^\/+|\/+$/g, "");
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
function slugFromWorkspacePathname(pathname: string): string {
|
|
229
|
+
const normalized = normalizeSlug(pathname);
|
|
230
|
+
if (!normalized || normalized === "workspace") return "";
|
|
231
|
+
|
|
232
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
233
|
+
if (segments.length === 2 && segments[1] === "preview") {
|
|
234
|
+
return segments[0] ?? "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Legacy static/public route compatibility. New workspace navigation
|
|
238
|
+
// writes /workspace and /<press-slug>/preview.
|
|
239
|
+
return normalized;
|
|
240
|
+
}
|
|
241
|
+
|
|
228
242
|
function pushSlug(slug: string) {
|
|
229
243
|
if (typeof window === "undefined") return;
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const target = `${pathname}${window.location.search}`;
|
|
244
|
+
// Drop query + hash: workbench routing is path-based, and page anchors
|
|
245
|
+
// do not transfer across documents.
|
|
246
|
+
const pathname = slug ? `/${normalizeSlug(slug)}/preview` : "/workspace";
|
|
247
|
+
const target = pathname;
|
|
235
248
|
if (window.location.pathname === pathname) return;
|
|
236
249
|
window.history.pushState({}, "", target);
|
|
237
250
|
}
|
|
@@ -69,6 +69,7 @@ function ThumbnailCard({
|
|
|
69
69
|
aspectRatio: string;
|
|
70
70
|
}) {
|
|
71
71
|
const surfaceRef = useRef<HTMLDivElement>(null);
|
|
72
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
72
73
|
const [scale, setScale] = useState<number | null>(null);
|
|
73
74
|
|
|
74
75
|
useEffect(() => {
|
|
@@ -86,6 +87,11 @@ function ThumbnailCard({
|
|
|
86
87
|
return () => ro.disconnect();
|
|
87
88
|
}, [pageWidthPx, pageHeightPx]);
|
|
88
89
|
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!active) return;
|
|
92
|
+
cardRef.current?.scrollIntoView({ block: "nearest" });
|
|
93
|
+
}, [active]);
|
|
94
|
+
|
|
89
95
|
const className = `openpress-thumb-card${active ? " is-active" : ""}`;
|
|
90
96
|
// Wrap the page HTML using the same class structure as the main
|
|
91
97
|
// reader (`.openpress-html-page > .openpress-html-page__html`) so
|
|
@@ -117,6 +123,7 @@ function ThumbnailCard({
|
|
|
117
123
|
|
|
118
124
|
return (
|
|
119
125
|
<div
|
|
126
|
+
ref={cardRef}
|
|
120
127
|
role="button"
|
|
121
128
|
tabIndex={0}
|
|
122
129
|
className={className}
|
|
@@ -2,10 +2,18 @@ export function isLocalWorkspaceHost(hostname: string) {
|
|
|
2
2
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function isWorkspaceModeLocation(location: Pick<Location, "hostname" | "
|
|
6
|
-
|
|
5
|
+
export function isWorkspaceModeLocation(location: Pick<Location, "hostname" | "pathname">) {
|
|
6
|
+
if (!isLocalWorkspaceHost(location.hostname)) return false;
|
|
7
|
+
const pathname = normalizePathname(location.pathname);
|
|
8
|
+
if (pathname === "workspace") return true;
|
|
9
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
10
|
+
return segments.length === 2 && segments[1] === "preview";
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export function isPrintModeLocation(location: Pick<Location, "search">) {
|
|
10
14
|
return new URLSearchParams(location.search).has("print");
|
|
11
15
|
}
|
|
16
|
+
|
|
17
|
+
function normalizePathname(pathname: string) {
|
|
18
|
+
return pathname.replace(/^\/+|\/+$/g, "");
|
|
19
|
+
}
|
|
@@ -597,24 +597,36 @@
|
|
|
597
597
|
(canvas-style Press: social posts, slides). Renders the actual page
|
|
598
598
|
HTML scaled down so the user can navigate by visual recognition. */
|
|
599
599
|
.openpress-reader-app .openpress-panel-section--thumbnails {
|
|
600
|
-
display:
|
|
601
|
-
|
|
602
|
-
gap: 10px;
|
|
603
|
-
padding: 8px 14px 16px;
|
|
600
|
+
display: grid;
|
|
601
|
+
grid-template-rows: minmax(0, 1fr);
|
|
604
602
|
min-height: 0;
|
|
603
|
+
overflow: hidden;
|
|
604
|
+
padding: 8px 14px 12px;
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
.openpress-reader-app .openpress-thumb-list {
|
|
608
608
|
display: flex;
|
|
609
|
+
min-height: 0;
|
|
609
610
|
flex-direction: column;
|
|
610
611
|
gap: 10px;
|
|
611
612
|
margin: 0;
|
|
612
|
-
|
|
613
|
+
overflow: auto;
|
|
614
|
+
padding: 0 0 10px;
|
|
613
615
|
list-style: none;
|
|
616
|
+
overscroll-behavior: contain;
|
|
617
|
+
scrollbar-width: none;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.openpress-reader-app .openpress-thumb-list::-webkit-scrollbar {
|
|
621
|
+
width: 0;
|
|
622
|
+
height: 0;
|
|
623
|
+
display: none;
|
|
614
624
|
}
|
|
615
625
|
|
|
616
626
|
.openpress-reader-app .openpress-thumb-card {
|
|
617
627
|
display: grid;
|
|
628
|
+
width: 100%;
|
|
629
|
+
min-width: 0;
|
|
618
630
|
grid-template-columns: 20px minmax(0, 1fr);
|
|
619
631
|
align-items: stretch;
|
|
620
632
|
gap: 6px;
|