scribe-cms 0.0.17 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/cli/index.cjs +4013 -555
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4011 -553
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1502 -229
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1487 -228
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +435 -33
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +434 -32
- package/dist/runtime.js.map +1 -1
- package/dist/src/config/resolve-config.d.ts.map +1 -1
- package/dist/src/core/builtin-fields.d.ts +10 -1
- package/dist/src/core/builtin-fields.d.ts.map +1 -1
- package/dist/src/core/field.d.ts +70 -0
- package/dist/src/core/field.d.ts.map +1 -1
- package/dist/src/core/introspect-schema.d.ts +31 -1
- package/dist/src/core/introspect-schema.d.ts.map +1 -1
- package/dist/src/core/managed-roots.d.ts +15 -0
- package/dist/src/core/managed-roots.d.ts.map +1 -0
- package/dist/src/core/types.d.ts +56 -4
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/create-project.d.ts +11 -1
- package/dist/src/create-project.d.ts.map +1 -1
- package/dist/src/create-scribe.d.ts.map +1 -1
- package/dist/src/delete/execute.d.ts +21 -0
- package/dist/src/delete/execute.d.ts.map +1 -0
- package/dist/src/delete/plan.d.ts +85 -0
- package/dist/src/delete/plan.d.ts.map +1 -0
- package/dist/src/delete/render-text.d.ts +7 -0
- package/dist/src/delete/render-text.d.ts.map +1 -0
- package/dist/src/hash/page-hash.d.ts +9 -0
- package/dist/src/hash/page-hash.d.ts.map +1 -1
- package/dist/src/index.d.ts +12 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/inline/resolve-tokens.d.ts +31 -0
- package/dist/src/inline/resolve-tokens.d.ts.map +1 -0
- package/dist/src/inline/tokens.d.ts +95 -0
- package/dist/src/inline/tokens.d.ts.map +1 -0
- package/dist/src/loader/create-loader.d.ts +7 -1
- package/dist/src/loader/create-loader.d.ts.map +1 -1
- package/dist/src/loader/resolve-assets.d.ts +16 -0
- package/dist/src/loader/resolve-assets.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/storage/translations.d.ts +8 -0
- package/dist/src/storage/translations.d.ts.map +1 -1
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -1
- package/dist/src/translate/response-schema.d.ts +7 -1
- package/dist/src/translate/response-schema.d.ts.map +1 -1
- package/dist/src/translate/translate-core.d.ts +6 -0
- package/dist/src/translate/translate-core.d.ts.map +1 -1
- package/dist/src/translate/validate-translation.d.ts.map +1 -1
- package/dist/src/translate/worklist.d.ts +8 -1
- package/dist/src/translate/worklist.d.ts.map +1 -1
- package/dist/src/validate/validate-assets.d.ts +26 -2
- package/dist/src/validate/validate-assets.d.ts.map +1 -1
- package/dist/src/validate/validate-inline-tokens.d.ts +10 -0
- package/dist/src/validate/validate-inline-tokens.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts.map +1 -1
- package/dist/src/validate/validate-relations.d.ts +2 -1
- package/dist/src/validate/validate-relations.d.ts.map +1 -1
- package/dist/studio/asset-serve.d.ts +39 -0
- package/dist/studio/asset-serve.d.ts.map +1 -0
- package/dist/studio/asset-views.d.ts +4 -0
- package/dist/studio/asset-views.d.ts.map +1 -0
- package/dist/studio/content-views.d.ts +85 -0
- package/dist/studio/content-views.d.ts.map +1 -0
- package/dist/studio/introspect-fields.d.ts +111 -0
- package/dist/studio/introspect-fields.d.ts.map +1 -0
- package/dist/studio/mdx-preview.d.ts +3 -0
- package/dist/studio/mdx-preview.d.ts.map +1 -0
- package/dist/studio/preview-tokens.d.ts +16 -0
- package/dist/studio/preview-tokens.d.ts.map +1 -0
- package/dist/studio/search.d.ts +34 -0
- package/dist/studio/search.d.ts.map +1 -0
- package/dist/studio/server.cjs +4691 -304
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.d.ts.map +1 -1
- package/dist/studio/server.js +4689 -302
- package/dist/studio/server.js.map +1 -1
- package/dist/studio/shared.d.ts +20 -0
- package/dist/studio/shared.d.ts.map +1 -0
- package/dist/studio/studio-cache.d.ts +93 -0
- package/dist/studio/studio-cache.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ScribeProject } from "../src/core/types.js";
|
|
2
|
+
export declare function escapeHtml(value: string): string;
|
|
3
|
+
export declare function encodePathSegment(value: string): string;
|
|
4
|
+
/** URL for the studio asset-preview route (source file off disk, not publicPath). */
|
|
5
|
+
export declare function assetPreviewUrl(webPath: string): string;
|
|
6
|
+
export interface LayoutOptions {
|
|
7
|
+
/** Highlighted content type in the sidebar. */
|
|
8
|
+
activeTypeId?: string;
|
|
9
|
+
/** Highlighted top-level nav entry ("dashboard" | "assets" | typeId). */
|
|
10
|
+
activeNav?: string;
|
|
11
|
+
/** Current query to prefill the sidebar search box. */
|
|
12
|
+
searchQuery?: string;
|
|
13
|
+
}
|
|
14
|
+
/** The shared studio CSS. Extends the original stylesheet with content-management additions. */
|
|
15
|
+
export declare const STUDIO_CSS = "\n:root {\n --bg: #1e1e1e;\n --sidebar: #252526;\n --bar: #333333;\n --panel: #1e1e1e;\n --border: #3c3c3c;\n --text: #cccccc;\n --dim: #858585;\n --accent: #3794ff;\n --hover: #2a2d2e;\n --active: #37373d;\n --ok: #89d185;\n --stale: #cca700;\n --missing: #f48771;\n --source: #75beff;\n --mono: ui-monospace, \"Cascadia Code\", \"SF Mono\", Menlo, monospace;\n --ui: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --fs: 13px;\n --fs-sm: 11px;\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody { font: var(--fs)/1.4 var(--ui); background: var(--bg); color: var(--text); }\na { color: var(--accent); text-decoration: none; }\na:hover { text-decoration: underline; }\n.app { display: flex; height: 100vh; overflow: hidden; }\n\n/* activity bar */\n.actbar {\n width: 48px; flex-shrink: 0; background: var(--bar);\n display: flex; flex-direction: column; align-items: center;\n padding: 8px 0; gap: 4px; border-right: 1px solid var(--border);\n}\n.actbar a {\n width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;\n color: var(--dim); font-size: 18px; text-decoration: none; position: relative;\n}\n.actbar a:hover { color: var(--text); }\n.actbar a.active { color: var(--text); }\n.actbar a.active::before {\n content: \"\"; position: absolute; left: 0; top: 8px; bottom: 8px;\n width: 2px; background: var(--accent);\n}\n\n/* sidebar */\n.sidebar {\n width: 200px; flex-shrink: 0; background: var(--sidebar);\n border-right: 1px solid var(--border); display: flex; flex-direction: column;\n overflow: hidden;\n}\n.sidebar-head {\n padding: 8px 12px; font-size: var(--fs-sm); font-weight: 600;\n text-transform: uppercase; letter-spacing: 0.04em; color: var(--dim);\n}\n.sidebar-body { flex: 1; overflow-y: auto; padding: 2px 0; }\n.tree-item {\n display: flex; flex-direction: column; padding: 3px 12px 3px 20px;\n color: var(--text); text-decoration: none; line-height: 1.3;\n}\n.tree-item:hover { background: var(--hover); text-decoration: none; }\n.tree-item.active { background: var(--active); }\n.tree-label { font-size: var(--fs); }\n.tree-meta { font-size: var(--fs-sm); color: var(--dim); }\n.tree-item .tree-label .badge { float: right; }\n\n/* main */\n.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }\n.toolbar {\n display: flex; align-items: center; gap: 6px; padding: 0 12px;\n height: 35px; background: var(--sidebar); border-bottom: 1px solid var(--border);\n font-size: var(--fs-sm); color: var(--dim); flex-shrink: 0; overflow: hidden;\n}\n.toolbar a { color: var(--dim); }\n.toolbar a:hover { color: var(--text); }\n.toolbar .sep { color: var(--border); }\n.toolbar .spacer { flex: 1; }\n.content { flex: 1; overflow-y: auto; padding: 0; }\n\n/* tabs (locale switcher) */\n.tabs {\n display: flex; background: var(--sidebar); border-bottom: 1px solid var(--border);\n overflow-x: auto; flex-shrink: 0;\n}\n.tab {\n display: flex; align-items: center; gap: 6px; padding: 6px 12px;\n font-size: var(--fs-sm); color: var(--dim); border-right: 1px solid var(--border);\n text-decoration: none; white-space: nowrap;\n}\n.tab:hover { background: var(--hover); color: var(--text); text-decoration: none; }\n.tab.active {\n background: var(--bg); color: var(--text);\n border-bottom: 1px solid var(--bg); margin-bottom: -1px;\n}\n\n/* status dots */\n.status { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-sm); color: var(--dim); }\n.dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }\n.dot-source { background: var(--source); }\n.dot-up-to-date { background: var(--ok); }\n.dot-stale { background: var(--stale); }\n.dot-missing { background: var(--missing); opacity: 0.7; }\n.dot-not-translatable { background: var(--dim); opacity: 0.6; }\n.dots { display: inline-flex; gap: 3px; align-items: center; }\n\n/* sections */\n.section { border-bottom: 1px solid var(--border); }\n.section-head {\n padding: 4px 12px; font-size: var(--fs-sm); font-weight: 600;\n text-transform: uppercase; letter-spacing: 0.04em; color: var(--dim);\n background: var(--sidebar);\n}\n.section-body { padding: 0; }\n\n/* tables */\ntable { border-collapse: collapse; width: 100%; font-size: var(--fs); }\n.data th, .data td { padding: 2px 12px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }\n.data th { font-size: var(--fs-sm); font-weight: 600; color: var(--dim); background: var(--sidebar); height: 22px; }\n.data tr:hover td { background: var(--hover); }\n.data .mono { font-family: var(--mono); font-size: var(--fs-sm); }\n\n/* key-value (frontmatter) */\n.kv td { padding: 1px 12px; font-family: var(--mono); font-size: var(--fs-sm); vertical-align: top; border-bottom: 1px solid var(--border); }\n.kv .k { width: 160px; color: #9cdcfe; white-space: nowrap; }\n.kv .v { color: #ce9178; white-space: pre-wrap; word-break: break-word; }\n.flag { font-size: 9px; font-weight: 700; margin-right: 4px; opacity: 0.5; }\n.flag.t { color: var(--ok); }\n.flag.s { color: var(--dim); }\n\n/* code block */\n.code {\n margin: 0; padding: 8px 12px; font: var(--fs-sm)/1.5 var(--mono);\n white-space: pre-wrap; word-break: break-word; color: var(--text);\n background: var(--bg); border: none;\n}\n\n/* meta row */\n.meta { display: flex; flex-wrap: wrap; font-size: var(--fs-sm); border-bottom: 1px solid var(--border); padding: 4px 0; }\n.meta dt { padding: 0 4px 0 12px; color: var(--dim); }\n.meta dt::after { content: \":\"; }\n.meta dd { padding: 0 12px 0 0; font-family: var(--mono); }\n\n/* misc */\n.dim { color: var(--dim); }\n.page-title { padding: 8px 12px 4px; font-size: 14px; font-weight: 400; }\n.page-sub { padding: 0 12px 8px; font-size: var(--fs-sm); color: var(--dim); }\ndetails summary { cursor: pointer; color: var(--accent); font-size: var(--fs-sm); }\ndetails[open] summary { margin-bottom: 4px; }\n.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }\n.tag-warn { color: var(--stale); }\n.tag-err { color: var(--missing); }\n\n/* dashboard */\n.cards { display: flex; flex-wrap: wrap; gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }\n.card { flex: 1 1 120px; background: var(--bg); padding: 12px; min-width: 120px; }\n.card-value { font-size: 22px; font-weight: 300; line-height: 1.2; }\n.card-label { font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }\n.bar { display: inline-flex; width: 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; vertical-align: middle; margin-right: 8px; }\n.bar-fill { display: block; height: 100%; }\n\n/* ---- content management additions ---- */\n\n/* chips (enum / relation values) */\n.chip {\n display: inline-flex; align-items: center; gap: 3px; padding: 0 6px;\n height: 16px; border-radius: 8px; font-size: 10px; line-height: 16px;\n background: var(--active); color: var(--text); margin: 1px 2px 1px 0; white-space: nowrap;\n}\n.chip.rel { background: #22343f; color: var(--source); }\n.chip.rel-bad { background: #3f2222; color: var(--missing); }\n.chip.nt { background: var(--bar); color: var(--dim); }\n.chip a { color: inherit; }\n\n/* validation badges */\n.vbadge {\n display: inline-flex; align-items: center; padding: 0 6px; height: 15px;\n border-radius: 3px; font-size: 10px; font-weight: 600; line-height: 15px; margin: 0 2px;\n}\n.vbadge.err { background: #3f2222; color: var(--missing); }\n.vbadge.warn { background: #3a3320; color: var(--stale); }\n.vbadge.info { background: #22343f; color: var(--source); }\n.vbadge.ok { background: #22331f; color: var(--ok); }\n\n/* filter bar */\n.filters {\n display: flex; flex-wrap: wrap; gap: 8px; align-items: center;\n padding: 6px 12px; background: var(--sidebar); border-bottom: 1px solid var(--border);\n font-size: var(--fs-sm);\n}\n.filters label { display: inline-flex; align-items: center; gap: 4px; color: var(--dim); }\n.filters select, .filters input[type=text] {\n background: var(--bg); color: var(--text); border: 1px solid var(--border);\n border-radius: 3px; font: var(--fs-sm) var(--ui); padding: 2px 4px;\n}\n.filters .reset { margin-left: auto; }\n.viewtoggle { display: inline-flex; border: 1px solid var(--border); border-radius: 3px; overflow: hidden; }\n.viewtoggle a { padding: 2px 8px; color: var(--dim); border-right: 1px solid var(--border); }\n.viewtoggle a:last-child { border-right: none; }\n.viewtoggle a.active { background: var(--active); color: var(--text); text-decoration: none; }\n\n/* gallery */\n.gallery {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 10px; padding: 12px;\n}\n.gcard { background: var(--sidebar); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }\n.gcard .thumb {\n aspect-ratio: 1 / 1; background: #111 repeating-linear-gradient(45deg, #1a1a1a 0 8px, #161616 8px 16px);\n display: flex; align-items: center; justify-content: center; overflow: hidden;\n}\n.gcard .thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }\n.gcard .thumb .noimg { color: var(--dim); font-size: var(--fs-sm); }\n.gcard .body { padding: 6px 8px; }\n.gcard .gslug { font-family: var(--mono); font-size: var(--fs-sm); display: block; margin-bottom: 3px; }\n.gcard .gkey { font-size: 10px; color: var(--dim); }\n\n/* asset preview panel (inspector) */\n.asset-preview { display: flex; gap: 12px; padding: 8px 12px; align-items: flex-start; }\n.asset-preview .frame {\n width: 160px; height: 160px; flex-shrink: 0; border: 1px solid var(--border); border-radius: 4px;\n background: #111 repeating-linear-gradient(45deg, #1a1a1a 0 8px, #161616 8px 16px);\n display: flex; align-items: center; justify-content: center; overflow: hidden;\n}\n.asset-preview .frame img { max-width: 100%; max-height: 100%; object-fit: contain; }\n.asset-preview .info { font-size: var(--fs-sm); }\n.asset-preview .info .mono { font-family: var(--mono); color: #ce9178; }\n\n/* asset browser grid */\n.asset-grid {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 10px; padding: 12px;\n}\n.acard { background: var(--sidebar); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }\n.acard .athumb {\n aspect-ratio: 1 / 1;\n background: #111 repeating-linear-gradient(45deg, #1a1a1a 0 8px, #161616 8px 16px);\n display: flex; align-items: center; justify-content: center; overflow: hidden;\n}\n.acard .athumb img { width: 100%; height: 100%; object-fit: contain; }\n.acard .abody { padding: 6px 8px; font-size: 10px; }\n.acard .apath { font-family: var(--mono); font-size: 10px; color: var(--dim); word-break: break-all; display: block; margin-bottom: 3px; }\n\n/* ---- global search ---- */\n.sidebar-search { padding: 8px; border-bottom: 1px solid var(--border); }\n.sidebar-search input {\n width: 100%; background: var(--bg); color: var(--text); border: 1px solid var(--border);\n border-radius: 3px; font: var(--fs-sm) var(--ui); padding: 4px 6px;\n}\n.search-group { border-bottom: 1px solid var(--border); }\n.search-hit { padding: 6px 12px; border-bottom: 1px solid var(--border); }\n.search-hit:last-child { border-bottom: none; }\n.search-where { font-size: var(--fs-sm); color: var(--dim); margin-left: 8px; }\n.search-snip { display: block; margin-top: 3px; font-size: var(--fs-sm); color: var(--text); font-family: var(--mono); word-break: break-word; }\n.search-more { padding: 4px 12px; font-size: var(--fs-sm); color: var(--dim); }\nmark { background: #3a3320; color: var(--stale); border-radius: 3px; padding: 0 2px; }\n\n/* ---- validation tooltip ---- */\n.vtip { position: relative; display: inline-block; }\n.vtip-panel {\n position: absolute; right: 0; top: 100%; margin-top: 4px; z-index: 60;\n min-width: 280px; max-width: 420px; background: var(--panel, #1c1c1e);\n border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px;\n box-shadow: 0 8px 24px rgba(0,0,0,.5); text-align: left; white-space: normal; display: none;\n}\n.vtip:hover .vtip-panel, .vtip:focus-within .vtip-panel { display: block; }\n.vrow { display: flex; align-items: baseline; gap: 6px; padding: 3px 0; font-size: var(--fs-sm); line-height: 1.4; }\n.vrow .vbadge { flex-shrink: 0; }\n.vrow .vmeta { font-family: var(--mono); font-size: 10px; color: var(--dim); flex-shrink: 0; }\n.vrow .vmsg { color: var(--text); word-break: break-word; }\n.vrow.more { color: var(--dim); }\n/* The sidebar tree scrolls (overflow clips its children); pin the panel to the\n viewport so the type-badge tooltip escapes that clip. */\n.tree-item .vtip-panel { position: fixed; top: auto; right: auto; }\n\n/* ---- inspector toolbar: prev/next nav + delete ---- */\n.navgroup { display: inline-flex; gap: 2px; margin-left: 6px; }\n.navbtn {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 20px; height: 20px; padding: 0 5px; border: 1px solid var(--border);\n border-radius: 4px; color: var(--dim); font-size: 14px; line-height: 1; text-decoration: none;\n}\n.navbtn:hover { color: var(--text); background: var(--hover); text-decoration: none; }\n.navbtn.disabled { opacity: 0.35; pointer-events: none; }\n.btn-danger {\n color: var(--missing); border: 1px solid currentColor; border-radius: 4px; padding: 2px 8px;\n background: transparent; font: var(--fs-sm) var(--ui); text-decoration: none; cursor: pointer;\n margin-left: 6px;\n}\n.btn-danger:hover { background: rgba(244,135,113,0.12); text-decoration: none; }\n.btn {\n color: var(--text); border: 1px solid var(--border); border-radius: 4px; padding: 4px 12px;\n background: transparent; font: var(--fs) var(--ui); text-decoration: none; cursor: pointer;\n}\n.btn:hover { background: var(--hover); text-decoration: none; }\n.btn-lg { padding: 6px 16px; font-size: var(--fs); }\n\n/* ---- delete confirmation page ---- */\n.del-page { padding: 0 0 24px; }\n.del-banner { padding: 12px; margin: 12px; border: 1px solid var(--border); border-radius: 4px; background: var(--sidebar); font-size: var(--fs); }\n.del-banner.del-blocked { border-color: var(--missing); color: var(--missing); }\n.del-actions { display: flex; align-items: center; gap: 10px; padding: 16px 12px; }\n.del-actions form { margin: 0; }\n\n/* ---- body raw/preview tabs ---- */\n.bodytabs { margin-left: 8px; font-weight: 400; text-transform: none; }\n.bodytabs a { color: var(--dim); padding: 0 4px; }\n.bodytabs a.active { color: var(--text); }\n\n/* ---- rendered MDX approximation ---- */\n.mdx-preview { padding: 10px 14px; font-size: var(--fs); line-height: 1.6; }\n.mdx-preview .mdx-h { margin: 12px 0 6px; line-height: 1.3; font-weight: 600; }\n.mdx-preview h1.mdx-h { font-size: 20px; }\n.mdx-preview h2.mdx-h { font-size: 17px; }\n.mdx-preview h3.mdx-h { font-size: 15px; }\n.mdx-preview h4.mdx-h, .mdx-preview h5.mdx-h, .mdx-preview h6.mdx-h { font-size: 13px; }\n.mdx-preview .mdx-p { margin: 6px 0; }\n.mdx-preview .mdx-list { margin: 6px 0 6px 20px; }\n.mdx-preview .mdx-list li { margin: 2px 0; }\n.mdx-preview .mdx-quote { border-left: 3px solid var(--border); margin: 8px 0; padding: 2px 0 2px 12px; color: var(--dim); }\n.mdx-preview .mdx-hr { border: none; border-top: 1px solid var(--border); margin: 12px 0; }\n.mdx-preview .mdx-code { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 8px 12px; overflow-x: auto; margin: 8px 0; }\n.mdx-preview .mdx-code code { font: var(--fs-sm)/1.5 var(--mono); color: var(--text); }\n.mdx-preview .mdx-inline-code { font-family: var(--mono); font-size: 12px; background: var(--active); border-radius: 3px; padding: 0 4px; }\n.mdx-preview .mdx-link { color: var(--accent); text-decoration: underline; text-decoration-style: dotted; cursor: help; }\n.mdx-preview .mdx-table { width: auto; margin: 8px 0; border: 1px solid var(--border); }\n.mdx-preview .mdx-table th, .mdx-preview .mdx-table td { border: 1px solid var(--border); padding: 3px 8px; text-align: left; }\n.mdx-preview .mdx-table th { background: var(--sidebar); color: var(--text); }\n.mdx-preview .mdx-fallback, pre.mdx-fallback { margin: 8px 0; padding: 8px 12px; font: var(--fs-sm)/1.5 var(--mono); white-space: pre-wrap; word-break: break-word; color: var(--dim); background: var(--bg); border: 1px dashed var(--border); }\n.mdx-preview .mdx-jsx { border: 1px solid var(--border); border-radius: 4px; margin: 8px 0; background: var(--sidebar); }\n.mdx-preview .mdx-jsx-head { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--source); padding: 3px 8px; border-bottom: 1px solid var(--border); background: var(--bar); }\n.mdx-preview .mdx-jsx-props { display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 6px 8px; font-family: var(--mono); font-size: 10px; }\n.mdx-preview .mdx-jsx-props .k { color: #9cdcfe; }\n.mdx-preview .mdx-jsx-props .v { color: #ce9178; }\n.mdx-preview .mdx-jsx-children { padding: 4px 8px; }\n.mdx-preview .mdx-jsx-raw { background: var(--sidebar); border: 1px solid var(--border); border-radius: 4px; margin: 8px 0; padding: 8px 12px; overflow-x: auto; }\n.mdx-preview .mdx-jsx-raw code { font: var(--fs-sm)/1.5 var(--mono); color: var(--dim); white-space: pre; }\n.mdx-preview .mdx-img { max-width: 100%; height: auto; display: block; border-radius: 4px; margin: 8px 0; }\n.mdx-preview .mdx-relation-link { color: var(--accent); text-decoration: none; }\n.mdx-preview .mdx-relation-link:hover { text-decoration: underline; }\n.mdx-preview .mdx-relation-chip { display: inline-block; padding: 0 6px; border-radius: 3px; background: var(--active); color: var(--accent); text-decoration: none; font-size: var(--fs-sm); }\n.mdx-preview .mdx-relation-chip:hover { text-decoration: underline; }\n.mdx-preview .mdx-relation-broken { display: inline-block; padding: 0 6px; border-radius: 3px; background: rgba(244,135,113,0.12); color: var(--missing); text-decoration: line-through; font-size: var(--fs-sm); }\n";
|
|
16
|
+
/** Render the shared studio chrome (activity bar + type sidebar + content). */
|
|
17
|
+
export declare function renderLayout(title: string, body: string, project: ScribeProject, options?: LayoutOptions & {
|
|
18
|
+
typeBadges?: Map<string, string>;
|
|
19
|
+
}): string;
|
|
20
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../studio/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,gGAAgG;AAChG,eAAO,MAAM,UAAU,qljBAqVtB,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,aAAa,GAAG;IAAE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GACjE,MAAM,CAoDR"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ScribeConfig, ScribeProject } from "../src/core/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Content-fingerprint driven cache for the read-only studio.
|
|
4
|
+
*
|
|
5
|
+
* The studio derives three things from content that are far too expensive to
|
|
6
|
+
* recompute on every request against a real project (hundreds of docs × a dozen
|
|
7
|
+
* locales):
|
|
8
|
+
*
|
|
9
|
+
* - the back-reference index (relations)
|
|
10
|
+
* - the asset-reference graph
|
|
11
|
+
* - the validation report (drives the advisory badges)
|
|
12
|
+
*
|
|
13
|
+
* The validation report in particular parses every EN + translated MDX body
|
|
14
|
+
* through remark/unified, which is multiple seconds on a large project. Doing
|
|
15
|
+
* that synchronously inside the request that happened to arrive right after a
|
|
16
|
+
* content change made `/types/:id` block for ~5s — the reported slowness.
|
|
17
|
+
*
|
|
18
|
+
* This module rebuilds the derived data ONCE per content-change tick (a cheap
|
|
19
|
+
* `count:newestMtime:storeMtime` fingerprint) and serves it *stale-while-
|
|
20
|
+
* revalidate*: a request never blocks on a rebuild once a cache exists. When the
|
|
21
|
+
* fingerprint changes the current (stale) value is returned immediately and a
|
|
22
|
+
* single background rebuild is kicked off; the next request after it lands sees
|
|
23
|
+
* the fresh data. Only the very first build (no cache yet) runs synchronously so
|
|
24
|
+
* there is always something to render.
|
|
25
|
+
*
|
|
26
|
+
* The badges are advisory, so a few hundred ms of staleness after an edit is an
|
|
27
|
+
* acceptable trade for never blocking the browser. Edits are still reflected
|
|
28
|
+
* without a restart — the fingerprint invalidation drives the refresh.
|
|
29
|
+
*
|
|
30
|
+
* Everything is injectable (clock + builder) so the invalidation/refresh logic
|
|
31
|
+
* is unit-testable without a filesystem or a real remark parse.
|
|
32
|
+
*/
|
|
33
|
+
export interface StudioCacheData<T> {
|
|
34
|
+
fingerprint: string;
|
|
35
|
+
value: T;
|
|
36
|
+
}
|
|
37
|
+
export interface StudioCacheOptions<T> {
|
|
38
|
+
/** Compute the current content fingerprint (file counts + newest mtime + store mtime). */
|
|
39
|
+
fingerprint: () => string;
|
|
40
|
+
/** Build the derived value for the current content. Expensive; run off the hot path. */
|
|
41
|
+
build: () => T;
|
|
42
|
+
/**
|
|
43
|
+
* Cheap placeholder served on the very first request while the first real
|
|
44
|
+
* build runs in the background. When provided, no request ever blocks on a
|
|
45
|
+
* build; when omitted, the first `get()` builds synchronously so callers that
|
|
46
|
+
* need a fully-populated value on first paint still work.
|
|
47
|
+
*/
|
|
48
|
+
initial?: () => T;
|
|
49
|
+
/** Injectable error sink (defaults to console.error). */
|
|
50
|
+
onError?: (err: unknown) => void;
|
|
51
|
+
/** Schedule a background job (defaults to `queueMicrotask` → `setImmediate` fallback). */
|
|
52
|
+
schedule?: (job: () => void) => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A fingerprint-keyed value that is rebuilt lazily and refreshed in the
|
|
56
|
+
* background. `get()` is non-blocking whenever a value already exists.
|
|
57
|
+
*/
|
|
58
|
+
export declare class StudioCache<T> {
|
|
59
|
+
private readonly opts;
|
|
60
|
+
private current;
|
|
61
|
+
private building;
|
|
62
|
+
/** Fingerprint of the in-flight background build, to avoid redundant rebuilds. */
|
|
63
|
+
private pendingFingerprint;
|
|
64
|
+
constructor(opts: StudioCacheOptions<T>);
|
|
65
|
+
/**
|
|
66
|
+
* Return the freshest cached value without blocking (once a value exists).
|
|
67
|
+
*
|
|
68
|
+
* - No cache yet → build synchronously once (blocking, unavoidable).
|
|
69
|
+
* - Fingerprint unchanged → return cached value.
|
|
70
|
+
* - Fingerprint changed → return the stale value now, schedule a background
|
|
71
|
+
* rebuild (deduped by fingerprint) so the next request sees fresh data.
|
|
72
|
+
*/
|
|
73
|
+
get(): T;
|
|
74
|
+
/** True when a background rebuild is currently in flight (for tests/introspection). */
|
|
75
|
+
get isRefreshing(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Drop the cached value so the next `get()` rebuilds synchronously. Used after
|
|
78
|
+
* a mutation (e.g. an entry deletion) so derived data — back-refs, badges,
|
|
79
|
+
* validation — reflects the change on the very next request rather than after
|
|
80
|
+
* the next background tick.
|
|
81
|
+
*/
|
|
82
|
+
invalidate(): void;
|
|
83
|
+
private scheduleRefresh;
|
|
84
|
+
private refreshNow;
|
|
85
|
+
private runBuild;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Cheap content fingerprint: total file count across all type content dirs, the
|
|
89
|
+
* newest file mtime, and the store mtime. A change to any of these triggers a
|
|
90
|
+
* rebuild. Pure read of `fs.stat` — never throws.
|
|
91
|
+
*/
|
|
92
|
+
export declare function computeContentFingerprint(project: ScribeProject, config: ScribeConfig): string;
|
|
93
|
+
//# sourceMappingURL=studio-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-cache.d.ts","sourceRoot":"","sources":["../../studio/studio-cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,CAAC;CACV;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,0FAA0F;IAC1F,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC,CAAC;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAClB,yDAAyD;IACzD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACtC;AAUD;;;GAGG;AACH,qBAAa,WAAW,CAAC,CAAC;IAMZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,QAAQ,CAAS;IACzB,kFAAkF;IAClF,OAAO,CAAC,kBAAkB,CAAuB;gBAEpB,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAExD;;;;;;;OAOG;IACH,GAAG,IAAI,CAAC;IAuBR,uFAAuF;IACvF,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,QAAQ;CAGjB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAyB9F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribe-cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "Typed, file-based CMS for multilingual MDX sites",
|
|
5
5
|
"homepage": "https://scribe.genlook.app",
|
|
6
6
|
"repository": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"build": "tsup && tsc -p tsconfig.dts.json",
|
|
52
52
|
"dev": "tsup --watch",
|
|
53
53
|
"clean": "rm -rf dist",
|
|
54
|
-
"test": "pnpm build && node --import tsx --test \"src/**/*.test.ts\"",
|
|
54
|
+
"test": "pnpm build && node --import tsx --test \"src/**/*.test.ts\" \"studio/**/*.test.ts\"",
|
|
55
55
|
"prepublishOnly": "pnpm build",
|
|
56
56
|
"publish:npm": "npm publish --access public"
|
|
57
57
|
},
|