OpenPress

@open-press/core

Press

單一份文件。<Press> 宣告其標題、頁面幾何、來源、來源根目錄,以及底下的 Frames + 輔助工具之 React 樹狀結構。遵循資料夾慣例的專案會從 press/<slug>/press.tsx 中匯出單一個 Press。

<Press> 元件定義單一文件之核心設定、內容來源與元件結構。引擎藉由此宣告建立 Workspace 內的隔離文件邊界。

1.0 契約: 系統依賴 press/*/press.tsx 資料夾慣例發現出版物。每個入口檔案必須具有預設匯出,回傳單一 <Press> 實例,且 slug 須與資料夾名稱相符。

Component Impl

# <Press>

設定並封裝單份文件之上下文、佈局與資料來源。渲染時作為 `Frame` 及輔助元件之宿主。

import { Press } from "@open-press/core";
<Press
  title="..."
  page="a4" | "social-square" | "slide-16-9" | PageGeometry
  sources={[ mdxSource({ id, preset, root }) ]}
  slug?
  theme?
  componentsDir?
  mediaDir?
>
  {/* Frames + 文稿輔助工具 */}
</Press>

文件設定 (Metadata & Routing)

Name Type Default Description
title required string 文件全名。自動綁定至 HTML ``、OG 標籤、PDF metadata 與 UI 介面。此屬性不直接輸出於內容畫布。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">slug</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">URL 路由及輸出目錄識別。預設自動推導自外層目錄名稱(如 `press/report` 對應 `report`)。</td> </tr> </tbody> </table> </div> </div><div class="min-w-0 max-w-full my-4 mb-6"> <p class="m-0 mb-2 text-subdued font-mono text-xs font-semibold tracking-[0.06em] uppercase">資源註冊 (Resource Registration)</p> <div class="max-w-full overflow-x-auto border border-hairline rounded-[6px]"> <table class="w-full min-w-[38rem] m-0 border-collapse text-sm leading-[1.55]"> <thead> <tr> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Name</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Type</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Default</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Description</th> </tr> </thead> <tbody class="[&_tr:last-child_td]:border-b-0"> <tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">page</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">"a4" | "social-square" | "slide-16-9" | PageGeometry</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">此 Press 的 page geometry。通用格式可用 preset;專案特定尺寸請傳入 `{ id, label, width, height }` custom object。Exporter 會把 resolved geometry 轉成 `--openpress-page-*` CSS variables。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">sources</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">SourceRegistration[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">由 `mdxSource()` 初始化之資料來源清單。其內部定義之 `id` 將供 `<MdxArea>` 及 `<Sections>` 消費。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">theme</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">此 Press 的 theme path override。新工作應把 tokens 與 font loading 放在 `press/<slug>/theme/`;只有刻意共享多 Press source 時才使用 shared theme path。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">componentsDir</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string | string[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">MDX 自動載入元件之實體目錄。預設包含 `./components`。目錄內元件免 `import` 即可於 MDX 使用。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">mediaDir</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string | string[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">局部媒體檔案目錄。預設包含 `./media`。</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">children</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">ReactNode</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">構成文件本體之 `<Frame>` 及輔助元件(如 `<Toc>`、`<Sections>`)樹狀結構。</td> </tr> </tbody> </table> </div> </div><h3 id="範例宣告單一文件">範例:宣告單一文件</h3><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { Press, Frame } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core"</span><span style="color:#E1E4E8">;</span></span> <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { mdxSource } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core/mdx"</span><span style="color:#E1E4E8">;</span></span> <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { Sections, Toc } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core/manuscript"</span><span style="color:#E1E4E8">;</span></span> <span class="line"></span> <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> ReportPress</span><span style="color:#E1E4E8">() {</span></span> <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Press</span></span> <span class="line"><span style="color:#B392F0"> title</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Transport models in dense networks"</span></span> <span class="line"><span style="color:#B392F0"> page</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"a4"</span></span> <span class="line"><span style="color:#B392F0"> sources</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{[</span></span> <span class="line"><span style="color:#B392F0"> mdxSource</span><span style="color:#E1E4E8">({ id: </span><span style="color:#9ECBFF">"story"</span><span style="color:#E1E4E8">, preset: </span><span style="color:#9ECBFF">"section-folders"</span><span style="color:#E1E4E8">, root: </span><span style="color:#9ECBFF">"report/chapters"</span><span style="color:#E1E4E8"> }),</span></span> <span class="line"><span style="color:#E1E4E8"> ]}</span></span> <span class="line"><span style="color:#E1E4E8"> ></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Frame</span><span style="color:#B392F0"> frameKey</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cover"</span><span style="color:#B392F0"> role</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"document.cover"</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Cover</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Frame</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Toc</span><span style="color:#B392F0"> source</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"story"</span><span style="color:#B392F0"> maxLevel</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">} /></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Sections</span><span style="color:#B392F0"> source</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"story"</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Press</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> );</span></span> <span class="line"><span style="color:#E1E4E8">}</span></span></code></pre><h3 id="範例自訂專案尺寸">範例:自訂專案尺寸</h3><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#E1E4E8"><</span><span style="color:#79B8FF">Press</span></span> <span class="line"><span style="color:#B392F0"> slug</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"campaign"</span></span> <span class="line"><span style="color:#B392F0"> title</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Campaign Card"</span></span> <span class="line"><span style="color:#B392F0"> page</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{{ id: </span><span style="color:#9ECBFF">"campaign-card"</span><span style="color:#E1E4E8">, label: </span><span style="color:#9ECBFF">"Campaign Card"</span><span style="color:#E1E4E8">, width: </span><span style="color:#9ECBFF">"1080px"</span><span style="color:#E1E4E8">, height: </span><span style="color:#9ECBFF">"1350px"</span><span style="color:#E1E4E8"> }}</span></span> <span class="line"><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Frame</span><span style="color:#B392F0"> frameKey</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cover"</span><span style="color:#B392F0"> role</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"social.card"</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">CampaignCard</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Frame</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"></</span><span style="color:#79B8FF">Press</span><span style="color:#E1E4E8">></span></span></code></pre> </div> </section> <h2 id="執行期契約與約束">執行期契約與約束</h2> <p>引擎於處理 <code><Press></code> 時依循下列不變法則:</p> <ul> <li><strong>單一根節點法則</strong>:文件之全部可視及結構內容必須為單一 <code><Press></code> 之子代。</li> <li><strong>配置一致性</strong>:文件設定全數封裝於 props,不存在平行之前端設定檔。</li> <li><strong>順序約束</strong>:樹狀結構之頂層元件順序(如封面、目錄、內文段落)直接對應至輸出分頁順序。</li> <li><strong>無狀態渲染 (Stateless Rendering)</strong>:渲染階段採多重遍歷(計算空間、安排區塊等),開發者不得於元件樹內部引發副作用(如讀寫網路、隨機數產生或快取操作)。</li> </ul> </div> </article> </main> <footer class="border-t border-hairline-strong bg-[linear-gradient(90deg,rgba(15,13,10,0.035)_1px,transparent_1px),var(--op-paper-soft)] bg-[size:3rem_3rem] py-10"> <div class="op-page grid grid-cols-[1fr_2fr_1fr] gap-8 items-center max-[720px]:grid-cols-1 max-[720px]:text-center"> <div> <p class="font-display text-xl text-ink-strong">OpenPress</p> <p class="op-meta">Agent-first document package · v1.0</p> </div> <ul class="flex flex-wrap justify-center gap-5 p-0 m-0 list-none text-sm [&>li>a]:text-ink [&>li>a]:no-underline hover:[&>li>a]:text-accent"> <li><a href="https://open-press-story.pages.dev">Document</a></li> <li><a href="/showcase">Showcase</a></li> <li><a href="https://github.com/quan0715/open-press">GitHub</a></li> <li><a href="https://www.npmjs.com/package/@open-press/cli">@open-press/cli</a></li> <li><a href="https://github.com/quan0715/open-press/issues">Issues</a></li> </ul> <p class="text-right max-[720px]:text-center op-meta">MIT License · 2026</p> </div> </footer> </body></html>