@open-press/core 1.2.0 → 1.3.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.
Files changed (76) hide show
  1. package/README.md +2 -2
  2. package/engine/cli.mjs +1 -1
  3. package/engine/commands/_shared.mjs +10 -5
  4. package/engine/commands/deploy.mjs +19 -4
  5. package/engine/commands/typecheck.mjs +1 -1
  6. package/engine/document-export.mjs +1 -1
  7. package/engine/output/page-block.mjs +11 -2
  8. package/engine/output/public-assets.mjs +41 -6
  9. package/engine/output/static-server.mjs +84 -24
  10. package/engine/react/caption-numbering.mjs +2 -2
  11. package/engine/react/comment-marker.mjs +1 -2
  12. package/engine/react/document-entry.mjs +64 -11
  13. package/engine/react/document-export.d.mts +6 -0
  14. package/engine/react/document-export.mjs +158 -28
  15. package/engine/react/mdx-compile.mjs +4 -4
  16. package/engine/react/measurement-css.mjs +3 -3
  17. package/engine/react/page-folio.mjs +37 -0
  18. package/engine/react/pagination/allocator.mjs +4 -4
  19. package/engine/react/pipeline/frame-measurement.mjs +34 -16
  20. package/engine/react/press-tree-inspection.mjs +43 -13
  21. package/engine/react/project-asset-endpoint.mjs +45 -11
  22. package/engine/react/sources/heading-numbering.mjs +2 -2
  23. package/engine/react/sources/mdx-resolver.mjs +3 -3
  24. package/engine/react/style-discovery.mjs +60 -11
  25. package/engine/react/text-source-transform.mjs +18 -4
  26. package/engine/runtime/config.mjs +22 -22
  27. package/engine/runtime/file-utils.mjs +57 -13
  28. package/engine/runtime/inspection.mjs +40 -15
  29. package/engine/runtime/page-geometry.mjs +6 -6
  30. package/engine/runtime/source-text-tools.mjs +28 -4
  31. package/engine/runtime/source-workspace.mjs +6 -9
  32. package/engine/runtime/validation.mjs +42 -24
  33. package/package.json +1 -1
  34. package/src/openpress/app/OpenPressApp.tsx +10 -16
  35. package/src/openpress/app/OpenPressRuntime.tsx +29 -4
  36. package/src/openpress/app/WorkspaceGalleryPage.tsx +1 -1
  37. package/src/openpress/core/PageFolio.tsx +115 -0
  38. package/src/openpress/core/Press.tsx +5 -10
  39. package/src/openpress/core/Slide.tsx +11 -0
  40. package/src/openpress/core/index.tsx +4 -0
  41. package/src/openpress/core/types.ts +21 -13
  42. package/src/openpress/core/useSource.ts +1 -1
  43. package/src/openpress/document-model/workspaceManifestModel.ts +4 -9
  44. package/src/openpress/reader/PageThumbnailsPanel.tsx +28 -5
  45. package/src/openpress/reader/SlidePresentationPage.tsx +36 -19
  46. package/src/openpress/reader/SlidePublicPage.tsx +332 -0
  47. package/src/openpress/reader/index.ts +1 -0
  48. package/src/openpress/reader/pageViewportScaleModel.ts +5 -3
  49. package/src/openpress/reader/usePageViewportScale.ts +9 -5
  50. package/src/openpress/workbench/Workbench.tsx +46 -164
  51. package/src/openpress/workbench/actions/DeploymentControl.tsx +1 -1
  52. package/src/openpress/workbench/actions/ExportControl.tsx +267 -0
  53. package/src/openpress/workbench/actions/index.ts +1 -1
  54. package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +7 -2
  55. package/src/openpress/workbench/hooks/useWorkbenchNavigation.ts +42 -0
  56. package/src/openpress/workbench/project/ProjectEntryPanel.tsx +2 -278
  57. package/src/openpress/workbench/shell/WorkbenchToolbarActions.tsx +206 -0
  58. package/src/styles/openpress/app-shell.css +0 -83
  59. package/src/styles/openpress/print-route.css +1 -3
  60. package/src/styles/openpress/project-preview-panel.css +5 -783
  61. package/src/styles/openpress/public-viewer.css +7 -249
  62. package/src/styles/openpress/reader-runtime.css +0 -274
  63. package/src/styles/openpress/slide-presenter.css +150 -0
  64. package/src/styles/openpress/slide-public-viewer.css +222 -0
  65. package/src/styles/openpress/workbench-dialog.css +267 -0
  66. package/src/styles/openpress/workbench-export.css +154 -0
  67. package/src/styles/openpress/workbench-inline-editor.css +128 -0
  68. package/src/styles/openpress/workbench-panels.css +0 -88
  69. package/src/styles/openpress/workbench-search.css +257 -0
  70. package/src/styles/openpress/workbench-toolbar.css +422 -0
  71. package/src/styles/openpress/workbench.css +34 -1263
  72. package/src/styles/openpress/workspace-gallery.css +0 -5
  73. package/src/styles/openpress.css +7 -1
  74. package/vite.config.ts +98 -25
  75. package/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  76. package/src/styles/openpress/media-workspace.css +0 -230
@@ -262,11 +262,6 @@
262
262
  white-space: nowrap;
263
263
  }
264
264
 
265
- .openpress-workspace-gallery__dot {
266
- color: color-mix(in srgb, var(--workspace-card-muted) 55%, transparent);
267
- }
268
-
269
- .openpress-workspace-gallery__pages,
270
265
  .openpress-workspace-gallery__geom {
271
266
  color: var(--workspace-card-muted);
272
267
  }
@@ -1,9 +1,15 @@
1
1
  @import "./openpress/app-shell.css";
2
2
  @import "./openpress/workspace-gallery.css";
3
3
  @import "./openpress/workbench.css";
4
+ @import "./openpress/slide-presenter.css";
5
+ @import "./openpress/workbench-toolbar.css";
6
+ @import "./openpress/slide-public-viewer.css";
7
+ @import "./openpress/workbench-inline-editor.css";
8
+ @import "./openpress/workbench-dialog.css";
9
+ @import "./openpress/workbench-search.css";
10
+ @import "./openpress/workbench-export.css";
4
11
  @import "./openpress/workbench-panels.css";
5
12
  @import "./openpress/project-preview-panel.css";
6
- @import "./openpress/media-workspace.css";
7
13
  @import "./openpress/reader-runtime.css";
8
14
  @import "./openpress/public-viewer.css";
9
15
  @import "./openpress/responsive.css";
package/vite.config.ts CHANGED
@@ -10,6 +10,7 @@ import { searchSourceText } from "./engine/runtime/source-text-tools.mjs";
10
10
  import { handleCommentRequest } from "./engine/react/comment-endpoint.mjs";
11
11
  import { handleProjectAssetRequest } from "./engine/react/project-asset-endpoint.mjs";
12
12
  import { handleSourceEditRequest } from "./engine/react/source-edit-endpoint.mjs";
13
+ import { exportReactDocument } from "./engine/react/document-export.mjs";
13
14
 
14
15
  const frameworkRoot = fileURLToPath(new URL("./", import.meta.url));
15
16
  const workspaceRoot = process.env.OPENPRESS_WORKSPACE_ROOT
@@ -26,10 +27,7 @@ const openpressConfig = await loadConfig(workspaceRoot);
26
27
  const outputDir = openpressConfig.paths.outputDir;
27
28
  const reactDocumentRoot = openpressConfig.paths.documentRoot;
28
29
  const reactDocumentComponentsRoot = openpressConfig.paths.componentsDir;
29
- const reactDocumentEntry = path.join(reactDocumentRoot, "index.tsx");
30
- const activeContentDir = await fileExists(reactDocumentEntry)
31
- ? path.join(reactDocumentRoot, "chapters")
32
- : openpressConfig.paths.sourceDir;
30
+ const activeContentDir = reactDocumentRoot;
33
31
 
34
32
  // Workspace directories — Vite resolves these at build time so that
35
33
  // `import.meta.glob("@workspace/content/**")` and friends follow the active
@@ -112,6 +110,11 @@ export default defineConfig({
112
110
  });
113
111
 
114
112
  function openpressLocalDeployPlugin() {
113
+ // Suppress auto-reload when source-edit endpoint triggers an export (avoids double reload).
114
+ let watcherSuppressedUntil = 0;
115
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
116
+ let exporting = false;
117
+
115
118
  return {
116
119
  name: "openpress-local-deploy-endpoint",
117
120
  configureServer(server: { middlewares: { use: (path: string, handler: (req: IncomingMessage, res: ServerResponse) => void) => void } }) {
@@ -128,6 +131,7 @@ function openpressLocalDeployPlugin() {
128
131
  void handleLocalSearchRequest(req, res);
129
132
  });
130
133
  server.middlewares.use("/__openpress/source-edit", (req, res) => {
134
+ if (req.method === "POST") watcherSuppressedUntil = Date.now() + 5000;
131
135
  void handleSourceEditRequest(req, res, { root: workspaceRoot });
132
136
  });
133
137
  server.middlewares.use("/__openpress/deploy", (req, res) => {
@@ -146,6 +150,31 @@ function openpressLocalDeployPlugin() {
146
150
  void handleLocalMediaFileRequest(req, res);
147
151
  });
148
152
  },
153
+ async handleHotUpdate({ file, server }: { file: string; server: { ws: { send: (payload: unknown) => void } } }) {
154
+ // Only react to changes inside the press/document directory.
155
+ const inDocumentRoot = file.startsWith(reactDocumentRoot + path.sep) || file === reactDocumentRoot;
156
+ const inContentDir = file.startsWith(activeContentDir + path.sep) || file === activeContentDir;
157
+ if (!inDocumentRoot && !inContentDir) return;
158
+
159
+ // Skip when source-edit already handled the export to avoid a double reload.
160
+ if (Date.now() < watcherSuppressedUntil) return [];
161
+
162
+ if (debounceTimer) clearTimeout(debounceTimer);
163
+ debounceTimer = setTimeout(async () => {
164
+ if (exporting) return;
165
+ exporting = true;
166
+ try {
167
+ await exportReactDocument(workspaceRoot, { syncAssets: false });
168
+ } catch {
169
+ // Export failure must not crash the dev server.
170
+ } finally {
171
+ exporting = false;
172
+ }
173
+ server.ws.send({ type: "full-reload" });
174
+ }, 300);
175
+
176
+ return []; // Suppress Vite's premature HMR until our export finishes.
177
+ },
149
178
  };
150
179
  }
151
180
 
@@ -205,14 +234,12 @@ async function handleLocalMediaFileRequest(req: IncomingMessage, res: ServerResp
205
234
  writeJson(res, 404, { ok: false, message: "Media file not found." });
206
235
  return;
207
236
  }
208
- const targetPath = path.join(openpressConfig.paths.mediaDir, fileName);
209
- const resolvedTarget = path.resolve(targetPath);
210
- const mediaRoot = path.resolve(openpressConfig.paths.mediaDir);
211
- if (!resolvedTarget.startsWith(`${mediaRoot}${path.sep}`) && resolvedTarget !== mediaRoot) {
212
- writeJson(res, 403, { ok: false, message: "Forbidden." });
237
+ const mediaPath = await findLocalMediaFile(fileName);
238
+ if (!mediaPath) {
239
+ writeJson(res, 404, { ok: false, message: "Media file not found." });
213
240
  return;
214
241
  }
215
- const body = await fs.readFile(resolvedTarget);
242
+ const body = await fs.readFile(mediaPath);
216
243
  res.writeHead(200, {
217
244
  "Content-Type": mediaMimeType(fileName),
218
245
  "Cache-Control": "no-store",
@@ -361,6 +388,11 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
361
388
  return;
362
389
  }
363
390
 
391
+ const body = await readJsonRequestBody(req);
392
+ const slug = normalizePressSlug(body?.press);
393
+ const cliArgs = slug ? ["deploy", ".", "--confirm", "--press", slug] : ["deploy", ".", "--confirm"];
394
+ const pdfFilename = pressFilename(openpressConfig.pdf.filename, slug);
395
+
364
396
  if (!isLocalDeployConfigured()) {
365
397
  writeJson(res, 400, {
366
398
  ok: false,
@@ -370,15 +402,15 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
370
402
  deploy_adapter: openpressConfig.deploy.adapter,
371
403
  deploy_source: openpressConfig.deploy.source,
372
404
  deploy_project_name: openpressConfig.deploy.projectName,
373
- command: openpressCliCommand(["deploy", ".", "--confirm"]),
405
+ command: openpressCliCommand(cliArgs),
374
406
  });
375
407
  return;
376
408
  }
377
409
 
378
- const result = await runLocalDeploy();
410
+ const result = await runLocalDeploy(slug);
379
411
  const deployedUrl = extractDeployUrl(result.stdout);
380
412
  if (result.code === 0 && deployedUrl) {
381
- await writeLocalDeploymentPublicUrl(deployedUrl);
413
+ await writeLocalDeploymentPublicUrl(deployedUrl, pdfFilename);
382
414
  }
383
415
  const deploymentInfo = await readLocalDeploymentInfo();
384
416
  const publicUrl = deployedUrl ?? deploymentInfo.public_url;
@@ -386,10 +418,10 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
386
418
  ok: result.code === 0,
387
419
  code: result.code,
388
420
  deployed_at: deploymentInfo.deployed_at,
389
- pdf: deployedUrl ? `${deployedUrl}/${openpressConfig.pdf.filename}` : deploymentInfo.pdf,
421
+ pdf: deployedUrl ? `${deployedUrl}/${pdfFilename}` : deploymentInfo.pdf,
390
422
  public_url: publicUrl,
391
423
  dirty: false,
392
- command: openpressCliCommand(["deploy", ".", "--confirm"]),
424
+ command: openpressCliCommand(cliArgs),
393
425
  stdout: result.stdout,
394
426
  stderr: result.stderr,
395
427
  });
@@ -420,9 +452,11 @@ function runLocalPdfExport(slug = "") {
420
452
  });
421
453
  }
422
454
 
423
- function runLocalDeploy() {
455
+ function runLocalDeploy(slug = "") {
456
+ const args = [openpressCliPath, "deploy", ".", "--confirm"];
457
+ if (slug) args.push("--press", slug);
424
458
  return new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
425
- const child = spawn("node", [openpressCliPath, "deploy", ".", "--confirm"], {
459
+ const child = spawn("node", args, {
426
460
  cwd: workspaceRoot,
427
461
  shell: false,
428
462
  });
@@ -485,7 +519,7 @@ async function readLocalDeploymentInfo() {
485
519
  }
486
520
  }
487
521
 
488
- async function writeLocalDeploymentPublicUrl(publicUrl: string) {
522
+ async function writeLocalDeploymentPublicUrl(publicUrl: string, pdfFilename = openpressConfig.pdf.filename) {
489
523
  let deployConfig: Record<string, unknown> = {};
490
524
  try {
491
525
  deployConfig = JSON.parse(await fs.readFile(openpressConfig.paths.deployMetadata, "utf8")) as Record<string, unknown>;
@@ -495,7 +529,7 @@ async function writeLocalDeploymentPublicUrl(publicUrl: string) {
495
529
  await fs.mkdir(path.dirname(openpressConfig.paths.deployMetadata), { recursive: true });
496
530
  await fs.writeFile(
497
531
  openpressConfig.paths.deployMetadata,
498
- `${JSON.stringify({ ...deployConfig, pdf: `${publicUrl}/${openpressConfig.pdf.filename}`, public_url: publicUrl }, null, 2)}\n`,
532
+ `${JSON.stringify({ ...deployConfig, pdf: `${publicUrl}/${pdfFilename}`, public_url: publicUrl }, null, 2)}\n`,
499
533
  "utf8",
500
534
  );
501
535
  }
@@ -510,16 +544,11 @@ async function isLocalDeploymentDirty(deployedAt: string | undefined) {
510
544
 
511
545
  function getLocalDeploymentSourcePaths() {
512
546
  return [
513
- openpressConfig.paths.sourceDir,
514
- openpressConfig.paths.mediaDir,
515
- openpressConfig.paths.themeDir,
516
- openpressConfig.paths.designDoc,
517
- openpressConfig.paths.componentsDir,
547
+ openpressConfig.paths.documentRoot,
518
548
  path.join(frameworkRoot, "src"),
519
549
  path.join(frameworkRoot, "index.html"),
520
550
  path.join(frameworkRoot, "vite.config.ts"),
521
551
  path.join(workspaceRoot, "package.json"),
522
- path.join(workspaceRoot, "openpress.config.mjs"),
523
552
  openpressConfig.configPath,
524
553
  ];
525
554
  }
@@ -596,6 +625,50 @@ async function uniqueMediaFileName(mediaDir: string, fileName: string) {
596
625
  return candidate;
597
626
  }
598
627
 
628
+ async function findLocalMediaFile(fileName: string): Promise<string | null> {
629
+ for (const mediaRoot of await collectLocalMediaRoots()) {
630
+ const resolvedRoot = path.resolve(mediaRoot);
631
+ const candidate = path.resolve(mediaRoot, fileName);
632
+ if (!isInsideRoot(candidate, resolvedRoot)) continue;
633
+ if (await fileExists(candidate)) return candidate;
634
+ }
635
+ return null;
636
+ }
637
+
638
+ async function collectLocalMediaRoots(): Promise<string[]> {
639
+ const roots = [
640
+ openpressConfig.paths.mediaDir,
641
+ path.join(openpressConfig.paths.publicDir, "media"),
642
+ ];
643
+ try {
644
+ const entries = await fs.readdir(openpressConfig.paths.documentRoot, { withFileTypes: true });
645
+ for (const entry of entries) {
646
+ if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "shared") continue;
647
+ roots.push(path.join(openpressConfig.paths.documentRoot, entry.name, "media"));
648
+ }
649
+ } catch {
650
+ // Missing press/ is handled by the render/validate commands.
651
+ }
652
+ return uniquePaths(roots);
653
+ }
654
+
655
+ function uniquePaths(paths: string[]): string[] {
656
+ const out: string[] = [];
657
+ const seen = new Set<string>();
658
+ for (const candidate of paths) {
659
+ const normalized = path.resolve(candidate);
660
+ if (seen.has(normalized)) continue;
661
+ seen.add(normalized);
662
+ out.push(normalized);
663
+ }
664
+ return out;
665
+ }
666
+
667
+ function isInsideRoot(candidate: string, rootDir: string): boolean {
668
+ const relative = path.relative(rootDir, candidate);
669
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
670
+ }
671
+
599
672
  function readRequestBuffer(req: IncomingMessage, maxBytes: number) {
600
673
  return new Promise<Buffer>((resolve, reject) => {
601
674
  const chunks: Buffer[] = [];
@@ -1,96 +0,0 @@
1
- import { useCallback, useState } from "react";
2
- import { Camera } from "lucide-react";
3
- import { toPng } from "html-to-image";
4
-
5
- type ExportStatus = "idle" | "exporting" | "done" | "error";
6
-
7
- // Exports the currently visible page as a PNG. Locates the page DOM via
8
- // the data-openpress-page-index attribute (set in PublicReaderPage) and
9
- // hands it to html-to-image, then triggers a browser download.
10
- //
11
- // Lives in the workbench toolbar so it's reachable for any Press shape
12
- // (manuscript / canvas / slide); for multi-page Press the user navigates
13
- // to the page first, then exports.
14
- export function ExportImageControl({
15
- currentPageIndex,
16
- currentPageLabel,
17
- pressTitle,
18
- }: {
19
- currentPageIndex: number;
20
- currentPageLabel: string;
21
- pressTitle: string;
22
- }) {
23
- const [status, setStatus] = useState<ExportStatus>("idle");
24
-
25
- const handleExport = useCallback(async () => {
26
- if (status === "exporting") return;
27
- setStatus("exporting");
28
-
29
- try {
30
- const pageEl = typeof window === "undefined"
31
- ? null
32
- : window.document.querySelector<HTMLElement>(
33
- `[data-openpress-page-index="${currentPageIndex}"]`,
34
- );
35
- if (!pageEl) throw new Error("找不到目前頁面");
36
-
37
- // pixelRatio: 2 — retina-ish; keeps text crisp without blowing the file size.
38
- // cacheBust: true — force re-fetch of images so stale CORS doesn't taint the canvas.
39
- const dataUrl = await toPng(pageEl, {
40
- pixelRatio: 2,
41
- cacheBust: true,
42
- backgroundColor: "#ffffff",
43
- });
44
-
45
- const safeTitle = sanitizeFilename(pressTitle) || "openpress";
46
- const safePage = sanitizeFilename(currentPageLabel) || String(currentPageIndex + 1);
47
- const link = window.document.createElement("a");
48
- link.href = dataUrl;
49
- link.download = `${safeTitle}-${safePage}.png`;
50
- window.document.body.appendChild(link);
51
- link.click();
52
- link.remove();
53
-
54
- setStatus("done");
55
- window.setTimeout(() => setStatus("idle"), 1600);
56
- } catch (error) {
57
- console.error("[openpress] page PNG export failed", error);
58
- setStatus("error");
59
- window.setTimeout(() => setStatus("idle"), 2400);
60
- }
61
- }, [currentPageIndex, currentPageLabel, pressTitle, status]);
62
-
63
- const label = status === "exporting"
64
- ? "匯出中…"
65
- : status === "done"
66
- ? "已下載"
67
- : status === "error"
68
- ? "匯出失敗"
69
- : "PNG";
70
- const title = "將目前頁面匯出為 PNG";
71
-
72
- return (
73
- <button
74
- type="button"
75
- className="openpress-workbench-toolbar-action"
76
- data-openpress-page-png-export
77
- data-openpress-export-status={status}
78
- disabled={status === "exporting"}
79
- onClick={handleExport}
80
- title={title}
81
- aria-label={title}
82
- >
83
- <Camera aria-hidden="true" />
84
- <span className="openpress-workbench-toolbar-action__label">{label}</span>
85
- </button>
86
- );
87
- }
88
-
89
- function sanitizeFilename(value: string): string {
90
- return value
91
- .replace(/[\\/:*?"<>|]+/g, "-")
92
- .replace(/\s+/g, "-")
93
- .replace(/-+/g, "-")
94
- .replace(/^-+|-+$/g, "")
95
- .slice(0, 80);
96
- }
@@ -1,230 +0,0 @@
1
- .openpress-media-assets {
2
- display: grid;
3
- grid-template-rows: auto minmax(120px, 1fr) auto auto auto;
4
- min-height: 0;
5
- overflow: hidden;
6
- }
7
-
8
- .openpress-media-workspace-page {
9
- position: absolute;
10
- inset: 0;
11
- z-index: 8;
12
- padding: 28px clamp(24px, 4vw, 48px) 40px;
13
- background: #141414;
14
- color: #dfe1dd;
15
- overflow: auto;
16
- }
17
-
18
- .openpress-media-workspace-header {
19
- display: flex;
20
- gap: 18px;
21
- align-items: start;
22
- justify-content: space-between;
23
- border-bottom: 1px solid rgb(255 255 255 / 9%);
24
- padding-bottom: 18px;
25
- }
26
-
27
- .openpress-media-workspace-header .openpress-panel-heading {
28
- padding: 0 0 8px;
29
- }
30
-
31
- .openpress-media-workspace-header h2 {
32
- margin: 0;
33
- color: #f2f2f0;
34
- font-size: 22px;
35
- font-weight: 500;
36
- line-height: 1.2;
37
- }
38
-
39
- .openpress-media-workspace-header p {
40
- margin: 8px 0 0;
41
- color: #858c93;
42
- font-size: 12px;
43
- }
44
-
45
- .openpress-media-workspace-back {
46
- flex: 0 0 auto;
47
- }
48
-
49
- .openpress-media-asset-list {
50
- display: grid;
51
- min-height: 0;
52
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
53
- gap: 10px;
54
- overflow: auto;
55
- padding: 18px 0;
56
- scrollbar-width: none;
57
- }
58
-
59
- .openpress-media-asset-list::-webkit-scrollbar {
60
- width: 0;
61
- height: 0;
62
- display: none;
63
- }
64
-
65
- .openpress-media-asset {
66
- display: grid;
67
- grid-template-columns: 54px minmax(0, 1fr);
68
- gap: 12px;
69
- align-items: center;
70
- min-height: 66px;
71
- border: 1px solid rgb(255 255 255 / 9%);
72
- padding: 10px;
73
- background: rgb(255 255 255 / 3%);
74
- color: #a2a8ae;
75
- text-decoration: none;
76
- transition:
77
- border-color 160ms ease,
78
- background 160ms ease,
79
- color 160ms ease;
80
- }
81
-
82
- .openpress-media-asset:hover {
83
- border-color: rgb(255 255 255 / 18%);
84
- background: rgb(255 255 255 / 6%);
85
- color: #f3f3ef;
86
- }
87
-
88
- .openpress-media-asset__thumb {
89
- display: grid;
90
- width: 54px;
91
- height: 54px;
92
- place-items: center;
93
- overflow: hidden;
94
- border: 1px solid rgb(255 255 255 / 10%);
95
- background: #101010;
96
- }
97
-
98
- .openpress-media-asset__thumb img {
99
- display: block;
100
- width: 100%;
101
- height: 100%;
102
- object-fit: cover;
103
- }
104
-
105
- .openpress-media-asset--svg .openpress-media-asset__thumb img {
106
- object-fit: contain;
107
- padding: 5px;
108
- background: #f4f4f0;
109
- }
110
-
111
- .openpress-media-asset__meta {
112
- min-width: 0;
113
- }
114
-
115
- .openpress-media-asset__meta strong {
116
- display: block;
117
- overflow: hidden;
118
- color: #e2e4e1;
119
- font-size: 12px;
120
- font-weight: 500;
121
- line-height: 1.2;
122
- text-overflow: ellipsis;
123
- white-space: nowrap;
124
- }
125
-
126
- .openpress-media-asset__meta small {
127
- display: block;
128
- overflow: hidden;
129
- margin-top: 4px;
130
- color: #6b7178;
131
- font-size: 10px;
132
- line-height: 1.2;
133
- text-overflow: ellipsis;
134
- white-space: nowrap;
135
- }
136
-
137
- .openpress-media-dropzone {
138
- display: grid;
139
- gap: 7px;
140
- margin: 0 0 14px;
141
- border: 1px dashed rgb(255 255 255 / 18%);
142
- padding: 12px;
143
- background: rgb(255 255 255 / 2%);
144
- color: #7f868d;
145
- font-size: 11px;
146
- line-height: 1.45;
147
- transition:
148
- border-color 160ms ease,
149
- background 160ms ease,
150
- color 160ms ease;
151
- }
152
-
153
- .openpress-media-dropzone[data-drag-active="true"] {
154
- border-color: rgb(223 75 33 / 70%);
155
- background: rgb(223 75 33 / 9%);
156
- color: #ece7df;
157
- }
158
-
159
- .openpress-media-dropzone__action,
160
- .openpress-staged-asset button {
161
- width: fit-content;
162
- border: 1px solid rgb(255 255 255 / 14%);
163
- padding: 6px 9px;
164
- background: rgb(255 255 255 / 4%);
165
- color: #ececea;
166
- font: inherit;
167
- font-size: 11px;
168
- cursor: pointer;
169
- }
170
-
171
- .openpress-media-dropzone__action:hover,
172
- .openpress-staged-asset button:hover {
173
- border-color: rgb(255 255 255 / 24%);
174
- background: rgb(255 255 255 / 8%);
175
- }
176
-
177
- .openpress-staged-assets {
178
- display: grid;
179
- max-height: 180px;
180
- gap: 8px;
181
- overflow: auto;
182
- padding: 0 0 12px;
183
- scrollbar-width: none;
184
- }
185
-
186
- .openpress-staged-asset {
187
- display: grid;
188
- grid-template-columns: 34px minmax(0, 1fr) auto;
189
- gap: 9px;
190
- align-items: center;
191
- }
192
-
193
- .openpress-staged-asset img {
194
- width: 34px;
195
- height: 34px;
196
- object-fit: cover;
197
- border: 1px solid rgb(255 255 255 / 10%);
198
- background: #111;
199
- }
200
-
201
- .openpress-staged-asset span {
202
- min-width: 0;
203
- }
204
-
205
- .openpress-staged-asset strong,
206
- .openpress-staged-asset small {
207
- display: block;
208
- overflow: hidden;
209
- text-overflow: ellipsis;
210
- white-space: nowrap;
211
- }
212
-
213
- .openpress-staged-asset strong {
214
- color: #dedfdd;
215
- font-size: 11px;
216
- font-weight: 500;
217
- }
218
-
219
- .openpress-staged-asset small {
220
- margin-top: 3px;
221
- color: #70767d;
222
- font-size: 10px;
223
- }
224
-
225
- body::-webkit-scrollbar,
226
- html::-webkit-scrollbar {
227
- width: 0;
228
- height: 0;
229
- display: none;
230
- }