@ryanyahya/opendoc 0.4.0
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/.agents/skills/opendoc-apply-comments/SKILL.md +30 -0
- package/.agents/skills/opendoc-apply-comments/agents/openai.yaml +4 -0
- package/.agents/skills/opendoc-create/SKILL.md +63 -0
- package/.agents/skills/opendoc-create/agents/openai.yaml +6 -0
- package/.agents/skills/opendoc-create-template/SKILL.md +50 -0
- package/.agents/skills/opendoc-create-template/agents/openai.yaml +4 -0
- package/.agents/skills/opendoc-create-template/references/structured-reports.md +11 -0
- package/.agents/skills/opendoc-create-theme/SKILL.md +40 -0
- package/.agents/skills/opendoc-create-theme/agents/openai.yaml +4 -0
- package/.agents/skills/opendoc-current-document/SKILL.md +32 -0
- package/.agents/skills/opendoc-current-document/agents/openai.yaml +4 -0
- package/.agents/skills/opendoc-current-document/references/context-fields.md +20 -0
- package/.agents/skills/opendoc-review-document/SKILL.md +60 -0
- package/.agents/skills/opendoc-review-document/agents/openai.yaml +4 -0
- package/AGENTS.md +69 -0
- package/CONTRIBUTING.md +38 -0
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/SECURITY.md +9 -0
- package/THIRD_PARTY.md +49 -0
- package/VALIDATION.md +99 -0
- package/bin/opendoc.mjs +6 -0
- package/dist/assets/Markdown-sNZyjJi7.js +29 -0
- package/dist/assets/OpenDocSerif-Regular-D3OXs9A9.ttf +0 -0
- package/dist/assets/OpenDocSerif-Semibold-CjL9oDRo.ttf +0 -0
- package/dist/assets/geist-latin-ext-wght-italic-XgKbg5n7.woff2 +0 -0
- package/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/assets/geist-latin-wght-italic-dTnRTFFJ.woff2 +0 -0
- package/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/assets/index-DdrZ6ozO.css +1 -0
- package/dist/assets/index-DgNBz7mL.js +39 -0
- package/dist/assets/pdf-B7Emtyld.js +54 -0
- package/dist/assets/pdf.worker.min-Dswkl-cV.mjs +29 -0
- package/dist/assets/wordmark-DsqLlZpb.png +0 -0
- package/dist/index.html +17 -0
- package/docs/APPEARANCE.md +21 -0
- package/docs/ASSETS.md +176 -0
- package/docs/AUTHORING.md +178 -0
- package/docs/HEADLESS.md +120 -0
- package/docs/MEDIA.md +112 -0
- package/docs/PROJECTS.md +55 -0
- package/docs/PROSE.md +15 -0
- package/docs/SELECTION.md +59 -0
- package/docs/TEMPLATES.md +55 -0
- package/docs/THEMES.md +106 -0
- package/node_modules/@formepdf/core/LICENSE +21 -0
- package/node_modules/@formepdf/core/README.md +53 -0
- package/node_modules/@formepdf/core/dist/attachments.d.ts +55 -0
- package/node_modules/@formepdf/core/dist/attachments.js +59 -0
- package/node_modules/@formepdf/core/dist/extract.d.ts +8 -0
- package/node_modules/@formepdf/core/dist/extract.js +56 -0
- package/node_modules/@formepdf/core/dist/index.d.ts +311 -0
- package/node_modules/@formepdf/core/dist/index.js +167 -0
- package/node_modules/@formepdf/core/dist/layout.d.ts +132 -0
- package/node_modules/@formepdf/core/dist/layout.js +240 -0
- package/node_modules/@formepdf/core/dist/shared/browserHelpers.d.ts +19 -0
- package/node_modules/@formepdf/core/dist/shared/browserHelpers.js +128 -0
- package/node_modules/@formepdf/core/package.json +35 -0
- package/node_modules/@formepdf/core/pkg-node/LICENSE +21 -0
- package/node_modules/@formepdf/core/pkg-node/README.md +84 -0
- package/node_modules/@formepdf/core/pkg-node/forme.d.ts +39 -0
- package/node_modules/@formepdf/core/pkg-node/forme.js +394 -0
- package/node_modules/@formepdf/core/pkg-node/forme_bg.wasm +0 -0
- package/node_modules/@formepdf/core/pkg-node/forme_bg.wasm.d.ts +21 -0
- package/node_modules/@formepdf/core/pkg-node/package.json +25 -0
- package/node_modules/@formepdf/react/README.md +70 -0
- package/node_modules/@formepdf/react/dist/charts.d.ts +72 -0
- package/node_modules/@formepdf/react/dist/charts.js +338 -0
- package/node_modules/@formepdf/react/dist/components.d.ts +533 -0
- package/node_modules/@formepdf/react/dist/components.js +608 -0
- package/node_modules/@formepdf/react/dist/expr.d.ts +20 -0
- package/node_modules/@formepdf/react/dist/expr.js +39 -0
- package/node_modules/@formepdf/react/dist/font.d.ts +2 -0
- package/node_modules/@formepdf/react/dist/font.js +1 -0
- package/node_modules/@formepdf/react/dist/index.d.ts +11 -0
- package/node_modules/@formepdf/react/dist/index.js +15 -0
- package/node_modules/@formepdf/react/dist/render.d.ts +12 -0
- package/node_modules/@formepdf/react/dist/render.js +15 -0
- package/node_modules/@formepdf/react/dist/serialize.d.ts +12 -0
- package/node_modules/@formepdf/react/dist/serialize.js +1470 -0
- package/node_modules/@formepdf/react/dist/stylesheet.d.ts +4 -0
- package/node_modules/@formepdf/react/dist/stylesheet.js +5 -0
- package/node_modules/@formepdf/react/dist/template-proxy.d.ts +38 -0
- package/node_modules/@formepdf/react/dist/template-proxy.js +100 -0
- package/node_modules/@formepdf/react/dist/types.d.ts +287 -0
- package/node_modules/@formepdf/react/dist/types.js +1 -0
- package/node_modules/@formepdf/react/package.json +41 -0
- package/node_modules/@formepdf/shared/README.md +7 -0
- package/node_modules/@formepdf/shared/dist/canvas.d.ts +6 -0
- package/node_modules/@formepdf/shared/dist/canvas.js +43 -0
- package/node_modules/@formepdf/shared/dist/charts.d.ts +100 -0
- package/node_modules/@formepdf/shared/dist/charts.js +98 -0
- package/node_modules/@formepdf/shared/dist/encode.d.ts +27 -0
- package/node_modules/@formepdf/shared/dist/encode.js +93 -0
- package/node_modules/@formepdf/shared/dist/font.d.ts +13 -0
- package/node_modules/@formepdf/shared/dist/font.js +52 -0
- package/node_modules/@formepdf/shared/dist/index.d.ts +10 -0
- package/node_modules/@formepdf/shared/dist/index.js +15 -0
- package/node_modules/@formepdf/shared/dist/parser.d.ts +30 -0
- package/node_modules/@formepdf/shared/dist/parser.js +756 -0
- package/node_modules/@formepdf/shared/dist/semantics.d.ts +15 -0
- package/node_modules/@formepdf/shared/dist/semantics.js +44 -0
- package/node_modules/@formepdf/shared/dist/style.d.ts +7 -0
- package/node_modules/@formepdf/shared/dist/style.js +1024 -0
- package/node_modules/@formepdf/shared/dist/types.d.ts +743 -0
- package/node_modules/@formepdf/shared/dist/types.js +2 -0
- package/node_modules/@formepdf/shared/package.json +35 -0
- package/node_modules/@types/node/LICENSE +21 -0
- package/node_modules/@types/node/README.md +15 -0
- package/node_modules/@types/node/assert/strict.d.ts +111 -0
- package/node_modules/@types/node/assert.d.ts +1078 -0
- package/node_modules/@types/node/async_hooks.d.ts +603 -0
- package/node_modules/@types/node/buffer.buffer.d.ts +472 -0
- package/node_modules/@types/node/buffer.d.ts +1934 -0
- package/node_modules/@types/node/child_process.d.ts +1476 -0
- package/node_modules/@types/node/cluster.d.ts +578 -0
- package/node_modules/@types/node/compatibility/disposable.d.ts +14 -0
- package/node_modules/@types/node/compatibility/index.d.ts +9 -0
- package/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
- package/node_modules/@types/node/compatibility/iterators.d.ts +20 -0
- package/node_modules/@types/node/console.d.ts +452 -0
- package/node_modules/@types/node/constants.d.ts +21 -0
- package/node_modules/@types/node/crypto.d.ts +4545 -0
- package/node_modules/@types/node/dgram.d.ts +600 -0
- package/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
- package/node_modules/@types/node/dns/promises.d.ts +503 -0
- package/node_modules/@types/node/dns.d.ts +923 -0
- package/node_modules/@types/node/domain.d.ts +170 -0
- package/node_modules/@types/node/events.d.ts +976 -0
- package/node_modules/@types/node/fs/promises.d.ts +1306 -0
- package/node_modules/@types/node/fs.d.ts +4463 -0
- package/node_modules/@types/node/globals.d.ts +172 -0
- package/node_modules/@types/node/globals.typedarray.d.ts +38 -0
- package/node_modules/@types/node/http.d.ts +2097 -0
- package/node_modules/@types/node/http2.d.ts +2843 -0
- package/node_modules/@types/node/https.d.ts +579 -0
- package/node_modules/@types/node/index.d.ts +98 -0
- package/node_modules/@types/node/inspector.d.ts +253 -0
- package/node_modules/@types/node/inspector.generated.d.ts +4052 -0
- package/node_modules/@types/node/module.d.ts +891 -0
- package/node_modules/@types/node/net.d.ts +1076 -0
- package/node_modules/@types/node/os.d.ts +506 -0
- package/node_modules/@types/node/package.json +145 -0
- package/node_modules/@types/node/path.d.ts +200 -0
- package/node_modules/@types/node/perf_hooks.d.ts +968 -0
- package/node_modules/@types/node/process.d.ts +2084 -0
- package/node_modules/@types/node/punycode.d.ts +117 -0
- package/node_modules/@types/node/querystring.d.ts +152 -0
- package/node_modules/@types/node/readline/promises.d.ts +161 -0
- package/node_modules/@types/node/readline.d.ts +594 -0
- package/node_modules/@types/node/repl.d.ts +428 -0
- package/node_modules/@types/node/sea.d.ts +162 -0
- package/node_modules/@types/node/sqlite.d.ts +722 -0
- package/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/node_modules/@types/node/stream/promises.d.ts +90 -0
- package/node_modules/@types/node/stream/web.d.ts +573 -0
- package/node_modules/@types/node/stream.d.ts +1694 -0
- package/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/node_modules/@types/node/test.d.ts +2265 -0
- package/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/node_modules/@types/node/timers.d.ts +287 -0
- package/node_modules/@types/node/tls.d.ts +1319 -0
- package/node_modules/@types/node/trace_events.d.ts +197 -0
- package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
- package/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
- package/node_modules/@types/node/ts5.6/index.d.ts +98 -0
- package/node_modules/@types/node/tty.d.ts +208 -0
- package/node_modules/@types/node/url.d.ts +984 -0
- package/node_modules/@types/node/util.d.ts +2606 -0
- package/node_modules/@types/node/v8.d.ts +920 -0
- package/node_modules/@types/node/vm.d.ts +1053 -0
- package/node_modules/@types/node/wasi.d.ts +181 -0
- package/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
- package/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/node_modules/@types/node/web-globals/events.d.ts +97 -0
- package/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
- package/node_modules/@types/node/web-globals/navigator.d.ts +22 -0
- package/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- package/node_modules/@types/node/web-globals/streams.d.ts +22 -0
- package/node_modules/@types/node/worker_threads.d.ts +819 -0
- package/node_modules/@types/node/zlib.d.ts +747 -0
- package/node_modules/@types/react/LICENSE +21 -0
- package/node_modules/@types/react/README.md +15 -0
- package/node_modules/@types/react/canary.d.ts +129 -0
- package/node_modules/@types/react/compiler-runtime.d.ts +4 -0
- package/node_modules/@types/react/experimental.d.ts +184 -0
- package/node_modules/@types/react/global.d.ts +166 -0
- package/node_modules/@types/react/index.d.ts +4378 -0
- package/node_modules/@types/react/jsx-dev-runtime.d.ts +33 -0
- package/node_modules/@types/react/jsx-runtime.d.ts +24 -0
- package/node_modules/@types/react/package.json +210 -0
- package/node_modules/@types/react/ts5.0/canary.d.ts +129 -0
- package/node_modules/@types/react/ts5.0/experimental.d.ts +184 -0
- package/node_modules/@types/react/ts5.0/global.d.ts +166 -0
- package/node_modules/@types/react/ts5.0/index.d.ts +4365 -0
- package/node_modules/@types/react/ts5.0/jsx-dev-runtime.d.ts +33 -0
- package/node_modules/@types/react/ts5.0/jsx-runtime.d.ts +24 -0
- package/node_modules/csstype/LICENSE +19 -0
- package/node_modules/csstype/README.md +291 -0
- package/node_modules/csstype/index.d.ts +22569 -0
- package/node_modules/csstype/index.js.flow +6863 -0
- package/node_modules/csstype/package.json +70 -0
- package/node_modules/entities/LICENSE +11 -0
- package/node_modules/entities/decode.d.ts +1 -0
- package/node_modules/entities/decode.js +3 -0
- package/node_modules/entities/dist/commonjs/decode-codepoint.d.ts +19 -0
- package/node_modules/entities/dist/commonjs/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
- package/node_modules/entities/dist/commonjs/decode-codepoint.js.map +1 -0
- package/node_modules/entities/dist/commonjs/decode.d.ts +209 -0
- package/node_modules/entities/dist/commonjs/decode.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/decode.js +511 -0
- package/node_modules/entities/dist/commonjs/decode.js.map +1 -0
- package/node_modules/entities/dist/commonjs/encode.d.ts +22 -0
- package/node_modules/entities/dist/commonjs/encode.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/encode.js +73 -0
- package/node_modules/entities/dist/commonjs/encode.js.map +1 -0
- package/node_modules/entities/dist/commonjs/escape.d.ts +43 -0
- package/node_modules/entities/dist/commonjs/escape.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/escape.js +121 -0
- package/node_modules/entities/dist/commonjs/escape.js.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-html.d.ts +2 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-html.js +10 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-xml.d.ts +2 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +10 -0
- package/node_modules/entities/dist/commonjs/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/encode-html.d.ts +8 -0
- package/node_modules/entities/dist/commonjs/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/generated/encode-html.js +13 -0
- package/node_modules/entities/dist/commonjs/generated/encode-html.js.map +1 -0
- package/node_modules/entities/dist/commonjs/index.d.ts +96 -0
- package/node_modules/entities/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/entities/dist/commonjs/index.js +131 -0
- package/node_modules/entities/dist/commonjs/index.js.map +1 -0
- package/node_modules/entities/dist/commonjs/package.json +3 -0
- package/node_modules/entities/dist/esm/decode-codepoint.d.ts +19 -0
- package/node_modules/entities/dist/esm/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/decode-codepoint.js +72 -0
- package/node_modules/entities/dist/esm/decode-codepoint.js.map +1 -0
- package/node_modules/entities/dist/esm/decode.d.ts +209 -0
- package/node_modules/entities/dist/esm/decode.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/decode.js +497 -0
- package/node_modules/entities/dist/esm/decode.js.map +1 -0
- package/node_modules/entities/dist/esm/encode.d.ts +22 -0
- package/node_modules/entities/dist/esm/encode.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/encode.js +69 -0
- package/node_modules/entities/dist/esm/encode.js.map +1 -0
- package/node_modules/entities/dist/esm/escape.d.ts +43 -0
- package/node_modules/entities/dist/esm/escape.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/escape.js +117 -0
- package/node_modules/entities/dist/esm/escape.js.map +1 -0
- package/node_modules/entities/dist/esm/generated/decode-data-html.d.ts +2 -0
- package/node_modules/entities/dist/esm/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/generated/decode-data-html.js +7 -0
- package/node_modules/entities/dist/esm/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/dist/esm/generated/decode-data-xml.d.ts +2 -0
- package/node_modules/entities/dist/esm/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/generated/decode-data-xml.js +7 -0
- package/node_modules/entities/dist/esm/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/dist/esm/generated/encode-html.d.ts +8 -0
- package/node_modules/entities/dist/esm/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/generated/encode-html.js +10 -0
- package/node_modules/entities/dist/esm/generated/encode-html.js.map +1 -0
- package/node_modules/entities/dist/esm/index.d.ts +96 -0
- package/node_modules/entities/dist/esm/index.d.ts.map +1 -0
- package/node_modules/entities/dist/esm/index.js +107 -0
- package/node_modules/entities/dist/esm/index.js.map +1 -0
- package/node_modules/entities/dist/esm/package.json +3 -0
- package/node_modules/entities/escape.d.ts +1 -0
- package/node_modules/entities/escape.js +3 -0
- package/node_modules/entities/package.json +118 -0
- package/node_modules/entities/readme.md +122 -0
- package/node_modules/entities/src/decode-codepoint.ts +81 -0
- package/node_modules/entities/src/decode.spec.ts +320 -0
- package/node_modules/entities/src/decode.ts +620 -0
- package/node_modules/entities/src/encode.spec.ts +78 -0
- package/node_modules/entities/src/encode.ts +77 -0
- package/node_modules/entities/src/escape.spec.ts +14 -0
- package/node_modules/entities/src/escape.ts +148 -0
- package/node_modules/entities/src/generated/.eslintrc.json +10 -0
- package/node_modules/entities/src/generated/decode-data-html.ts +8 -0
- package/node_modules/entities/src/generated/decode-data-xml.ts +8 -0
- package/node_modules/entities/src/generated/encode-html.ts +17 -0
- package/node_modules/entities/src/index.spec.ts +125 -0
- package/node_modules/entities/src/index.ts +188 -0
- package/node_modules/parse5/LICENSE +19 -0
- package/node_modules/parse5/README.md +38 -0
- package/node_modules/parse5/dist/cjs/common/doctype.d.ts +4 -0
- package/node_modules/parse5/dist/cjs/common/doctype.js +118 -0
- package/node_modules/parse5/dist/cjs/common/error-codes.d.ts +67 -0
- package/node_modules/parse5/dist/cjs/common/error-codes.js +66 -0
- package/node_modules/parse5/dist/cjs/common/foreign-content.d.ts +9 -0
- package/node_modules/parse5/dist/cjs/common/foreign-content.js +237 -0
- package/node_modules/parse5/dist/cjs/common/html.d.ts +289 -0
- package/node_modules/parse5/dist/cjs/common/html.js +528 -0
- package/node_modules/parse5/dist/cjs/common/token.d.ts +84 -0
- package/node_modules/parse5/dist/cjs/common/token.js +24 -0
- package/node_modules/parse5/dist/cjs/common/unicode.d.ts +42 -0
- package/node_modules/parse5/dist/cjs/common/unicode.js +70 -0
- package/node_modules/parse5/dist/cjs/index.d.ts +71 -0
- package/node_modules/parse5/dist/cjs/index.js +54 -0
- package/node_modules/parse5/dist/cjs/package.json +1 -0
- package/node_modules/parse5/dist/cjs/parser/formatting-element-list.d.ts +36 -0
- package/node_modules/parse5/dist/cjs/parser/formatting-element-list.js +114 -0
- package/node_modules/parse5/dist/cjs/parser/index.d.ts +221 -0
- package/node_modules/parse5/dist/cjs/parser/index.js +3240 -0
- package/node_modules/parse5/dist/cjs/parser/open-element-stack.d.ts +53 -0
- package/node_modules/parse5/dist/cjs/parser/open-element-stack.js +328 -0
- package/node_modules/parse5/dist/cjs/serializer/index.d.ts +60 -0
- package/node_modules/parse5/dist/cjs/serializer/index.js +172 -0
- package/node_modules/parse5/dist/cjs/tokenizer/index.d.ts +247 -0
- package/node_modules/parse5/dist/cjs/tokenizer/index.js +2714 -0
- package/node_modules/parse5/dist/cjs/tokenizer/preprocessor.d.ts +36 -0
- package/node_modules/parse5/dist/cjs/tokenizer/preprocessor.js +200 -0
- package/node_modules/parse5/dist/cjs/tree-adapters/default.d.ts +84 -0
- package/node_modules/parse5/dist/cjs/tree-adapters/default.js +176 -0
- package/node_modules/parse5/dist/cjs/tree-adapters/interface.d.ts +255 -0
- package/node_modules/parse5/dist/cjs/tree-adapters/interface.js +2 -0
- package/node_modules/parse5/dist/common/doctype.d.ts +4 -0
- package/node_modules/parse5/dist/common/doctype.js +114 -0
- package/node_modules/parse5/dist/common/error-codes.d.ts +67 -0
- package/node_modules/parse5/dist/common/error-codes.js +63 -0
- package/node_modules/parse5/dist/common/foreign-content.d.ts +9 -0
- package/node_modules/parse5/dist/common/foreign-content.js +228 -0
- package/node_modules/parse5/dist/common/html.d.ts +289 -0
- package/node_modules/parse5/dist/common/html.js +523 -0
- package/node_modules/parse5/dist/common/token.d.ts +84 -0
- package/node_modules/parse5/dist/common/token.js +20 -0
- package/node_modules/parse5/dist/common/unicode.d.ts +42 -0
- package/node_modules/parse5/dist/common/unicode.js +62 -0
- package/node_modules/parse5/dist/index.d.ts +71 -0
- package/node_modules/parse5/dist/index.js +42 -0
- package/node_modules/parse5/dist/parser/formatting-element-list.d.ts +36 -0
- package/node_modules/parse5/dist/parser/formatting-element-list.js +110 -0
- package/node_modules/parse5/dist/parser/index.d.ts +221 -0
- package/node_modules/parse5/dist/parser/index.js +3245 -0
- package/node_modules/parse5/dist/parser/open-element-stack.d.ts +53 -0
- package/node_modules/parse5/dist/parser/open-element-stack.js +324 -0
- package/node_modules/parse5/dist/serializer/index.d.ts +60 -0
- package/node_modules/parse5/dist/serializer/index.js +168 -0
- package/node_modules/parse5/dist/tokenizer/index.d.ts +247 -0
- package/node_modules/parse5/dist/tokenizer/index.js +2710 -0
- package/node_modules/parse5/dist/tokenizer/preprocessor.d.ts +36 -0
- package/node_modules/parse5/dist/tokenizer/preprocessor.js +196 -0
- package/node_modules/parse5/dist/tree-adapters/default.d.ts +84 -0
- package/node_modules/parse5/dist/tree-adapters/default.js +173 -0
- package/node_modules/parse5/dist/tree-adapters/interface.d.ts +255 -0
- package/node_modules/parse5/dist/tree-adapters/interface.js +1 -0
- package/node_modules/parse5/package.json +50 -0
- package/node_modules/undici-types/LICENSE +21 -0
- package/node_modules/undici-types/README.md +6 -0
- package/node_modules/undici-types/agent.d.ts +31 -0
- package/node_modules/undici-types/api.d.ts +43 -0
- package/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/node_modules/undici-types/cache.d.ts +36 -0
- package/node_modules/undici-types/client.d.ts +108 -0
- package/node_modules/undici-types/connector.d.ts +34 -0
- package/node_modules/undici-types/content-type.d.ts +21 -0
- package/node_modules/undici-types/cookies.d.ts +28 -0
- package/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
- package/node_modules/undici-types/dispatcher.d.ts +256 -0
- package/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
- package/node_modules/undici-types/errors.d.ts +149 -0
- package/node_modules/undici-types/eventsource.d.ts +61 -0
- package/node_modules/undici-types/fetch.d.ts +209 -0
- package/node_modules/undici-types/file.d.ts +39 -0
- package/node_modules/undici-types/filereader.d.ts +54 -0
- package/node_modules/undici-types/formdata.d.ts +108 -0
- package/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/node_modules/undici-types/global-origin.d.ts +7 -0
- package/node_modules/undici-types/handlers.d.ts +15 -0
- package/node_modules/undici-types/header.d.ts +4 -0
- package/node_modules/undici-types/index.d.ts +71 -0
- package/node_modules/undici-types/interceptors.d.ts +17 -0
- package/node_modules/undici-types/mock-agent.d.ts +50 -0
- package/node_modules/undici-types/mock-client.d.ts +25 -0
- package/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/node_modules/undici-types/mock-interceptor.d.ts +93 -0
- package/node_modules/undici-types/mock-pool.d.ts +25 -0
- package/node_modules/undici-types/package.json +55 -0
- package/node_modules/undici-types/patch.d.ts +33 -0
- package/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/node_modules/undici-types/pool.d.ts +39 -0
- package/node_modules/undici-types/proxy-agent.d.ts +28 -0
- package/node_modules/undici-types/readable.d.ts +65 -0
- package/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/node_modules/undici-types/retry-handler.d.ts +116 -0
- package/node_modules/undici-types/util.d.ts +18 -0
- package/node_modules/undici-types/webidl.d.ts +228 -0
- package/node_modules/undici-types/websocket.d.ts +150 -0
- package/package.json +80 -0
- package/scripts/rebuild-forme.mjs +61 -0
- package/src/app/AppearanceControl.tsx +44 -0
- package/src/app/AssetThemeDefaults.tsx +87 -0
- package/src/app/AssetsBrowser.tsx +273 -0
- package/src/app/CommentDock.tsx +108 -0
- package/src/app/CreateDocumentDialog.tsx +51 -0
- package/src/app/DocumentActions.tsx +69 -0
- package/src/app/DocumentCard.tsx +50 -0
- package/src/app/DocumentViewControl.tsx +31 -0
- package/src/app/DocumentsBrowser.tsx +70 -0
- package/src/app/ExportHistoryDialog.tsx +86 -0
- package/src/app/ExportMenu.tsx +127 -0
- package/src/app/GuideDialog.tsx +80 -0
- package/src/app/InspectionLayer.tsx +77 -0
- package/src/app/LogoUploadDialog.tsx +92 -0
- package/src/app/Markdown.tsx +34 -0
- package/src/app/MaterialsBrowser.tsx +149 -0
- package/src/app/PageNumberInput.tsx +56 -0
- package/src/app/Pdf.tsx +561 -0
- package/src/app/ProjectDialogs.tsx +100 -0
- package/src/app/Reader.tsx +769 -0
- package/src/app/SearchField.tsx +13 -0
- package/src/app/Sidebar.tsx +86 -0
- package/src/app/SkillIndex.tsx +30 -0
- package/src/app/TemplatesBrowser.tsx +149 -0
- package/src/app/ThemesBrowser.tsx +159 -0
- package/src/app/api.ts +47 -0
- package/src/app/appearance.ts +35 -0
- package/src/app/assets.css +139 -0
- package/src/app/catalogPreview.ts +20 -0
- package/src/app/colors.css +167 -0
- package/src/app/comment-dock.css +53 -0
- package/src/app/commentDrafts.ts +67 -0
- package/src/app/componentCorrection.ts +17 -0
- package/src/app/edit-workbench.css +87 -0
- package/src/app/editSession.ts +238 -0
- package/src/app/editorRecovery.ts +25 -0
- package/src/app/export-history.css +31 -0
- package/src/app/export.css +17 -0
- package/src/app/guideLinks.ts +21 -0
- package/src/app/guides.css +40 -0
- package/src/app/main.tsx +221 -0
- package/src/app/materials.css +53 -0
- package/src/app/pdfLoading.ts +12 -0
- package/src/app/pdfSelection.ts +112 -0
- package/src/app/projects.css +63 -0
- package/src/app/reader-toolbar.css +65 -0
- package/src/app/selection.css +150 -0
- package/src/app/sidebar.css +60 -0
- package/src/app/skills.css +7 -0
- package/src/app/style.css +1061 -0
- package/src/app/templates.css +49 -0
- package/src/app/themes.css +80 -0
- package/src/app/ui/Icon.tsx +92 -0
- package/src/app/ui/README.md +27 -0
- package/src/app/ui/fonts.css +36 -0
- package/src/app/ui/index.tsx +223 -0
- package/src/app/ui/ui.css +388 -0
- package/src/app/useReaderPreview.ts +19 -0
- package/src/app/useTextEditing.ts +155 -0
- package/src/assets/adapter.ts +122 -0
- package/src/assets/files.ts +378 -0
- package/src/assets/import-worker.ts +294 -0
- package/src/assets/imports.ts +124 -0
- package/src/assets/index.ts +60 -0
- package/src/assets/store.ts +798 -0
- package/src/assets/usage.ts +118 -0
- package/src/cli/check.ts +25 -0
- package/src/cli/init.ts +119 -0
- package/src/cli/lock.ts +34 -0
- package/src/cli/main.ts +118 -0
- package/src/cli/process.ts +19 -0
- package/src/cli/start.ts +96 -0
- package/src/cli/update.ts +348 -0
- package/src/cli/workspace.ts +94 -0
- package/src/document/index.tsx +641 -0
- package/src/document/page.tsx +53 -0
- package/src/document/text-targets.tsx +162 -0
- package/src/media/files.ts +124 -0
- package/src/media/render.ts +63 -0
- package/src/rendering/resolve-sources.ts +84 -0
- package/src/runtime/jsx-dev-runtime.ts +2 -0
- package/src/runtime/jsx-runtime.ts +2 -0
- package/src/runtime/paths.ts +12 -0
- package/src/server/assets-cli.ts +162 -0
- package/src/server/assets-http.ts +100 -0
- package/src/server/client.ts +50 -0
- package/src/server/clipboard.ts +22 -0
- package/src/server/comments-cli.ts +64 -0
- package/src/server/comments.ts +130 -0
- package/src/server/create-cli.ts +33 -0
- package/src/server/create.ts +228 -0
- package/src/server/dependencies.ts +91 -0
- package/src/server/documents.ts +143 -0
- package/src/server/edits.ts +249 -0
- package/src/server/export-batch.ts +105 -0
- package/src/server/export-errors.ts +2 -0
- package/src/server/export-file.ts +57 -0
- package/src/server/export-inputs.ts +66 -0
- package/src/server/export-preview.ts +69 -0
- package/src/server/export.ts +32 -0
- package/src/server/exports.ts +283 -0
- package/src/server/files.ts +21 -0
- package/src/server/guides.ts +71 -0
- package/src/server/index.ts +432 -0
- package/src/server/materials.ts +39 -0
- package/src/server/media-cli.ts +73 -0
- package/src/server/media-mutations.ts +168 -0
- package/src/server/pptx.ts +160 -0
- package/src/server/preflight.ts +129 -0
- package/src/server/projects-cli.ts +29 -0
- package/src/server/projects.ts +187 -0
- package/src/server/render-error.ts +9 -0
- package/src/server/render-source.ts +24 -0
- package/src/server/render.ts +48 -0
- package/src/server/review-cli.ts +40 -0
- package/src/server/review.ts +161 -0
- package/src/server/selection.ts +23 -0
- package/src/server/source-overrides.ts +97 -0
- package/src/server/templates-cli.ts +51 -0
- package/src/server/templates.ts +186 -0
- package/src/server/text-layout.ts +41 -0
- package/src/server/text-source.ts +307 -0
- package/src/server/themes-cli.ts +59 -0
- package/src/server/themes.ts +215 -0
- package/src/server/ttf2eot.d.ts +3 -0
- package/src/server/worker.ts +166 -0
- package/src/server/workspace.ts +242 -0
- package/src/shared/anchors.ts +69 -0
- package/src/shared/assets.ts +154 -0
- package/src/shared/export.ts +34 -0
- package/src/shared/media.ts +33 -0
- package/src/shared/projects.ts +19 -0
- package/src/shared/selection.ts +94 -0
- package/src/shared/skill-index.ts +14 -0
- package/src/shared/starters.ts +19 -0
- package/src/shared/templates.ts +18 -0
- package/src/shared/themes.ts +24 -0
- package/src/shared/types.ts +39 -0
- package/src/template/index.ts +27 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/neutral.ts +20 -0
- package/src/themes/types.ts +110 -0
- package/starter/assets/brand/wordmark.png +0 -0
- package/starter/assets/fonts/InterUI-LICENSE.txt +93 -0
- package/starter/assets/fonts/InterUI-Medium.ttf +0 -0
- package/starter/assets/fonts/InterUI-Regular.ttf +0 -0
- package/starter/assets/fonts/InterUI-source.json +19 -0
- package/starter/assets/fonts/OpenDocMono-Regular.ttf +0 -0
- package/starter/assets/fonts/OpenDocSans-Italic.ttf +0 -0
- package/starter/assets/fonts/OpenDocSans-Regular.ttf +0 -0
- package/starter/assets/fonts/OpenDocSans-Semibold.ttf +0 -0
- package/starter/assets/fonts/OpenDocSans-SemiboldItalic.ttf +0 -0
- package/starter/assets/fonts/OpenDocSerif-Italic.ttf +0 -0
- package/starter/assets/fonts/OpenDocSerif-Regular.ttf +0 -0
- package/starter/assets/fonts/OpenDocSerif-Semibold.ttf +0 -0
- package/starter/assets/fonts/OpenDocSerif-SemiboldItalic.ttf +0 -0
- package/starter/assets/fonts/SourceCode-LICENSE.md +93 -0
- package/starter/assets/fonts/SourceSans-LICENSE.md +93 -0
- package/starter/assets/fonts/SourceSerif-LICENSE.md +93 -0
- package/starter/assets/fonts/geist/OFL.txt +93 -0
- package/starter/assets/fonts/geist/asset.json +7 -0
- package/starter/assets/fonts/geist/files/1a9874b3be0fc168692a3d2327bccb4d28835653d3b91537a755296a467a83e3.ttf +0 -0
- package/starter/assets/fonts/geist/files/a8eefb873ba9c9020665f8a98302697ce1f3b79ab2deef6ee508c0ca6a82e876.ttf +0 -0
- package/starter/assets/fonts/geist/files/ccce59465ae5c61930ada5453834f60500fbd95084d85db63e0e65fad0125455.ttf +0 -0
- package/starter/assets/fonts/geist/files/f5e95110d70593672515080bee9148463ada0a7f77b89ed65a3d43cb2b0ff83a.pdf +0 -0
- package/starter/assets/fonts/geist/revisions/9a2bc12fb2f23cb27bc227087e63e1407bb804ee0586480bd6b7966ec69fb474.json +60 -0
- package/starter/assets/fonts/inter/OFL.txt +93 -0
- package/starter/assets/fonts/inter/asset.json +7 -0
- package/starter/assets/fonts/inter/files/1b08e7fc267a5c7e1d614100f604b83e7e8a0be241f0f288faa2b3ac93a683ba.ttf +0 -0
- package/starter/assets/fonts/inter/files/5b6f0c6f26e5c554a64cdcc3a9b5d802b7f9aec6206f1dbcfcdcad7b5860a2a9.ttf +0 -0
- package/starter/assets/fonts/inter/files/957db8560d510a198a7d0333fae7b6b444b00c20801581126273978d778eb3c3.pdf +0 -0
- package/starter/assets/fonts/inter/files/b37284b5701b6b168dfc770aa1a4ac492106422fd3ba76bc7641e37434e8019c.ttf +0 -0
- package/starter/assets/fonts/inter/files/c1811d32ac0f6eed1bbf57529f2ecdfc6c717d48a969dd864942436315a1b198.ttf +0 -0
- package/starter/assets/fonts/inter/files/e7a1aaf7eda9f2fad4131725fa556265ec75ca7b2d756260173a040363e8d4f7.ttf +0 -0
- package/starter/assets/fonts/inter/files/ef8f755d933b9310804cc9152f1f18cb2ac460d6b05054ec913d889c9f11f79e.ttf +0 -0
- package/starter/assets/fonts/inter/revisions/600391517c413963b71e278a57c26c40366c81fa3939a6b9755f446206766d12.json +99 -0
- package/starter/assets/fonts/inter/source.json +57 -0
- package/starter/assets/fonts/lato/OFL.txt +93 -0
- package/starter/assets/fonts/lato/asset.json +7 -0
- package/starter/assets/fonts/lato/files/4a6b566dbb28adb473a8789ce5169306f42f0c8a070ac92bae93636f36348b0d.pdf +0 -0
- package/starter/assets/fonts/lato/files/5adeb6b334d8fb99ef053bc052b952773d544315e0e1d668a12c0ce8bd51375c.ttf +0 -0
- package/starter/assets/fonts/lato/files/62c1b7f0d2e74b45960154c3520efc337b553db0961bfdc950d5618334596cc8.ttf +0 -0
- package/starter/assets/fonts/lato/files/71b8b7decbe75a881ed267be539d402bd1e9420b799658aada4e0d1bd5af803c.ttf +0 -0
- package/starter/assets/fonts/lato/files/8a0aace75d33794eece4b28187bfc1df0bbd2888b5d8a56e01788c8d65d16be1.ttf +0 -0
- package/starter/assets/fonts/lato/files/d636e4683231f931eda222d588e944d082bfd3bdba02f928bee461c0f185b251.ttf +0 -0
- package/starter/assets/fonts/lato/files/e399c44efe1387100531d26c7e4800c5d12251b890d6654a3098c7c679cb1786.ttf +0 -0
- package/starter/assets/fonts/lato/revisions/49ccb93400a9d466fab7d9843b1ecf8916ec6d47a475dd79a597a22eb39616bc.json +99 -0
- package/starter/assets/fonts/lato/source.json +57 -0
- package/starter/assets/fonts/merriweather/OFL.txt +93 -0
- package/starter/assets/fonts/merriweather/asset.json +7 -0
- package/starter/assets/fonts/merriweather/files/1c54f91b910454251c4c2b7bd4e472e1abf265fba7ca3c0ef2eee2f41324099e.ttf +0 -0
- package/starter/assets/fonts/merriweather/files/8bde8da2bb7f0265e27ba0481f70bb48d0f98a976c145f2f239c648ddd6532e0.pdf +0 -0
- package/starter/assets/fonts/merriweather/files/90b7db4460fcd1a5ca49575c2668a793f3cd6447664d312a068d690e1c01e6bc.ttf +0 -0
- package/starter/assets/fonts/merriweather/files/afff81272b7653417bb6f8bef57add055b1f9fda36f27eba5f32b40752bd08da.ttf +0 -0
- package/starter/assets/fonts/merriweather/files/e029d3b71e5bae3877c73f4f2b15af28db16593d49b0aed2a328d37ff7075846.ttf +0 -0
- package/starter/assets/fonts/merriweather/files/e9864a6790bf712a04e20f3662b206a1535186ed3e877fd859dd5fb9682335df.ttf +0 -0
- package/starter/assets/fonts/merriweather/files/fa71baebdd20a7d0de2e5e445a5922530fe9e1d740efbe791f113b682d3cb093.ttf +0 -0
- package/starter/assets/fonts/merriweather/revisions/1888e390b3e4f38a3d7cd2dc1bb805aeb6ce13f986a97656f0265fd436bc91d2.json +99 -0
- package/starter/assets/fonts/merriweather/source.json +57 -0
- package/starter/assets/fonts/open-sans/OFL.txt +92 -0
- package/starter/assets/fonts/open-sans/asset.json +7 -0
- package/starter/assets/fonts/open-sans/files/1bc0594a83762b2c25d38915a34464b7fec6088298ca22d151757bf98d6700ec.ttf +0 -0
- package/starter/assets/fonts/open-sans/files/47ed1cfaa7ffca04a04ab850b2938fd52c09d1b44cfa3e2980878e166b0cbd87.ttf +0 -0
- package/starter/assets/fonts/open-sans/files/574939af301f07df8e21cfff279465c5981dd9d9cf62086f7515897df951c6f5.pdf +0 -0
- package/starter/assets/fonts/open-sans/files/6d3884d0d57c1b67e9c50c54eca7b69c535c114e76bd7a9cfda0258588185553.ttf +0 -0
- package/starter/assets/fonts/open-sans/files/733eb93b167e6d45de09aa768f6c0355647c2c26f5416dbc7407a08ed41e81c6.ttf +0 -0
- package/starter/assets/fonts/open-sans/files/75367e6da7d914eaed87883ba51b2a2b5816b5cdbb39f96ec5c81a5b032bfd5a.ttf +0 -0
- package/starter/assets/fonts/open-sans/files/ff4fdc11204e0edd14e9a0bb6c374b847a9093b0ec2c16702e03afca79ac4ef7.ttf +0 -0
- package/starter/assets/fonts/open-sans/revisions/bbbe3a4915b9bccbc883c0decb4097511c43b6bd09ae22a1516aff128c219bc5.json +99 -0
- package/starter/assets/fonts/open-sans/source.json +57 -0
- package/starter/assets/fonts/roboto/OFL.txt +93 -0
- package/starter/assets/fonts/roboto/asset.json +7 -0
- package/starter/assets/fonts/roboto/files/22d45d989248a624252b8382067ac56550212fa675dcdbaf12f03d297dd87a58.pdf +0 -0
- package/starter/assets/fonts/roboto/files/35757eaa996b3359022f47f164a67ee9eac04971af24c5b8fbd4b89e844ff190.ttf +0 -0
- package/starter/assets/fonts/roboto/files/4d3035883c531846bd1ce2dc89f36d932e72dc114f56062c7e3a9cdd74f3d3ea.ttf +0 -0
- package/starter/assets/fonts/roboto/files/be7639306a4fc4a02ce2ae2ff59eb1335e4db4a7379dd0a384ed7cc053817789.ttf +0 -0
- package/starter/assets/fonts/roboto/files/d2920cbb1b60a3fd11fbf22049286e43a6baf522d37fdf1e7c89562319bcfea5.ttf +0 -0
- package/starter/assets/fonts/roboto/files/dece7c71bbe61710787f0839c722c65e87355dd2eb4a823e6b5488121ff81e4b.ttf +0 -0
- package/starter/assets/fonts/roboto/files/f478ba88b0292e685e487901dfbb1401457d32d6ebee4030cdfd5698235bffa8.ttf +0 -0
- package/starter/assets/fonts/roboto/revisions/bda5e2d84c17313373dd2dbf3e64fe0dd450c7e1b59e6929e69557e122fe976e.json +99 -0
- package/starter/assets/fonts/roboto/source.json +57 -0
- package/starter/assets/logos/opendoc/asset.json +6 -0
- package/starter/assets/logos/opendoc/files/831d5ccd9eb6709cc8439ea257c03d81452e81a28a3d1eedbd6a3b7daff687b6.png +0 -0
- package/starter/assets/logos/opendoc/revisions/834dde8926ddb81de687254195a168bb13bfb77803307f1257de1f2228be63b2.json +33 -0
- package/starter/assets/ui-fonts/geist/OFL.txt +93 -0
- package/starter/assets/ui-fonts/geist/geist-latin-ext-wght-italic.woff2 +0 -0
- package/starter/assets/ui-fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
- package/starter/assets/ui-fonts/geist/geist-latin-wght-italic.woff2 +0 -0
- package/starter/assets/ui-fonts/geist/geist-latin-wght-normal.woff2 +0 -0
- package/starter/assets/ui-fonts/geist/source.json +26 -0
- package/starter/documents/welcome/assets.json +7 -0
- package/starter/documents/welcome/index.tsx +160 -0
- package/starter/documents/welcome/media/.gitkeep +0 -0
- package/starter/documents/welcome/media/paper-architecture/image.png +0 -0
- package/starter/documents/welcome/media/paper-architecture/meta.json +9 -0
- package/starter/documents/welcome/media/paper-architecture/prompt.txt +1 -0
- package/starter/documents/welcome/media/studio-rhythm/chart.svg +2343 -0
- package/starter/documents/welcome/media/studio-rhythm/data.json +234 -0
- package/starter/documents/welcome/media/studio-rhythm/generation.json +9 -0
- package/starter/documents/welcome/media/studio-rhythm/image.png +0 -0
- package/starter/documents/welcome/media/studio-rhythm/meta.json +11 -0
- package/starter/documents/welcome/media/studio-rhythm/recipe.py +79 -0
- package/starter/documents/welcome/theme.tsx +5 -0
- package/starter/documents/welcome-presentation/assets.json +7 -0
- package/starter/documents/welcome-presentation/index.tsx +166 -0
- package/starter/documents/welcome-presentation/media/.gitkeep +0 -0
- package/starter/documents/welcome-presentation/media/paper-architecture/image.png +0 -0
- package/starter/documents/welcome-presentation/media/paper-architecture/meta.json +9 -0
- package/starter/documents/welcome-presentation/media/paper-architecture/prompt.txt +1 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/chart.svg +1336 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/data.json +234 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/generation.json +9 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/image.png +0 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/meta.json +13 -0
- package/starter/documents/welcome-presentation/media/studio-rhythm/recipe.py +49 -0
- package/starter/documents/welcome-presentation/theme.tsx +5 -0
- package/starter/projects.json +17 -0
- package/starter/templates/AGENTS.md +9 -0
- package/starter/templates/_shared/BusinessLogo.tsx +14 -0
- package/starter/templates/_shared/Pricing.tsx +40 -0
- package/starter/templates/_shared/commerce.ts +93 -0
- package/starter/templates/brand-guidelines/AGENTS.md +32 -0
- package/starter/templates/brand-guidelines/index.tsx +62 -0
- package/starter/templates/brand-guidelines/preview.tsx +181 -0
- package/starter/templates/brand-guidelines/starter.tsx +179 -0
- package/starter/templates/brand-guidelines/template.json +15 -0
- package/starter/templates/business-proposal/AGENTS.md +13 -0
- package/starter/templates/business-proposal/README.md +36 -0
- package/starter/templates/business-proposal/data.json +37 -0
- package/starter/templates/business-proposal/examples/long.json +142 -0
- package/starter/templates/business-proposal/examples/sparse.json +17 -0
- package/starter/templates/business-proposal/examples/typical.json +37 -0
- package/starter/templates/business-proposal/index.tsx +59 -0
- package/starter/templates/business-proposal/preview.tsx +47 -0
- package/starter/templates/business-proposal/schema.ts +14 -0
- package/starter/templates/business-proposal/starter.tsx +18 -0
- package/starter/templates/business-proposal/template.json +11 -0
- package/starter/templates/company-profile/AGENTS.md +48 -0
- package/starter/templates/company-profile/index.tsx +69 -0
- package/starter/templates/company-profile/preview.tsx +185 -0
- package/starter/templates/company-profile/starter.tsx +186 -0
- package/starter/templates/company-profile/template.json +15 -0
- package/starter/templates/consulting-report/AGENTS.md +11 -0
- package/starter/templates/consulting-report/README.md +13 -0
- package/starter/templates/consulting-report/RESEARCH.md +10 -0
- package/starter/templates/consulting-report/index.tsx +44 -0
- package/starter/templates/consulting-report/preview.tsx +39 -0
- package/starter/templates/consulting-report/starter.tsx +15 -0
- package/starter/templates/consulting-report/template.json +11 -0
- package/starter/templates/editorial-essay/AGENTS.md +11 -0
- package/starter/templates/editorial-essay/README.md +11 -0
- package/starter/templates/editorial-essay/index.tsx +30 -0
- package/starter/templates/editorial-essay/preview.tsx +26 -0
- package/starter/templates/editorial-essay/starter.tsx +15 -0
- package/starter/templates/editorial-essay/template.json +11 -0
- package/starter/templates/executive-brief/AGENTS.md +9 -0
- package/starter/templates/executive-brief/README.md +11 -0
- package/starter/templates/executive-brief/index.tsx +33 -0
- package/starter/templates/executive-brief/preview.tsx +37 -0
- package/starter/templates/executive-brief/starter.tsx +11 -0
- package/starter/templates/executive-brief/template.json +11 -0
- package/starter/templates/image-story/AGENTS.md +37 -0
- package/starter/templates/image-story/index.tsx +106 -0
- package/starter/templates/image-story/preview.tsx +33 -0
- package/starter/templates/image-story/starter.tsx +24 -0
- package/starter/templates/image-story/template.json +13 -0
- package/starter/templates/invoice/AGENTS.md +13 -0
- package/starter/templates/invoice/README.md +40 -0
- package/starter/templates/invoice/data.json +61 -0
- package/starter/templates/invoice/examples/long.json +241 -0
- package/starter/templates/invoice/examples/sparse.json +33 -0
- package/starter/templates/invoice/examples/typical.json +61 -0
- package/starter/templates/invoice/index.tsx +57 -0
- package/starter/templates/invoice/preview.tsx +12 -0
- package/starter/templates/invoice/schema.ts +45 -0
- package/starter/templates/invoice/starter.tsx +12 -0
- package/starter/templates/invoice/template.json +11 -0
- package/starter/templates/literary-text/AGENTS.md +9 -0
- package/starter/templates/literary-text/README.md +11 -0
- package/starter/templates/literary-text/index.tsx +31 -0
- package/starter/templates/literary-text/preview.tsx +20 -0
- package/starter/templates/literary-text/starter.tsx +11 -0
- package/starter/templates/literary-text/template.json +11 -0
- package/starter/templates/magazine-feature/AGENTS.md +11 -0
- package/starter/templates/magazine-feature/README.md +13 -0
- package/starter/templates/magazine-feature/index.tsx +41 -0
- package/starter/templates/magazine-feature/preview.tsx +34 -0
- package/starter/templates/magazine-feature/starter.tsx +15 -0
- package/starter/templates/magazine-feature/template.json +11 -0
- package/starter/templates/monthly-report/AGENTS.md +11 -0
- package/starter/templates/monthly-report/README.md +24 -0
- package/starter/templates/monthly-report/data.json +131 -0
- package/starter/templates/monthly-report/examples/long.json +519 -0
- package/starter/templates/monthly-report/examples/sparse.json +20 -0
- package/starter/templates/monthly-report/examples/typical.json +37 -0
- package/starter/templates/monthly-report/index.tsx +92 -0
- package/starter/templates/monthly-report/preview.tsx +14 -0
- package/starter/templates/monthly-report/schema.ts +127 -0
- package/starter/templates/monthly-report/starter.tsx +13 -0
- package/starter/templates/monthly-report/template.json +11 -0
- package/starter/templates/pitch-deck/AGENTS.md +32 -0
- package/starter/templates/pitch-deck/index.tsx +46 -0
- package/starter/templates/pitch-deck/preview.tsx +142 -0
- package/starter/templates/pitch-deck/starter.tsx +140 -0
- package/starter/templates/pitch-deck/template.json +15 -0
- package/starter/templates/proposal-deck/AGENTS.md +25 -0
- package/starter/templates/proposal-deck/index.tsx +52 -0
- package/starter/templates/proposal-deck/preview.tsx +172 -0
- package/starter/templates/proposal-deck/starter.tsx +168 -0
- package/starter/templates/proposal-deck/template.json +7 -0
- package/starter/templates/quotation/AGENTS.md +13 -0
- package/starter/templates/quotation/README.md +42 -0
- package/starter/templates/quotation/data.json +64 -0
- package/starter/templates/quotation/examples/long.json +272 -0
- package/starter/templates/quotation/examples/sparse.json +31 -0
- package/starter/templates/quotation/examples/typical.json +64 -0
- package/starter/templates/quotation/index.tsx +51 -0
- package/starter/templates/quotation/preview.tsx +12 -0
- package/starter/templates/quotation/schema.ts +27 -0
- package/starter/templates/quotation/starter.tsx +12 -0
- package/starter/templates/quotation/template.json +11 -0
- package/starter/templates/scientific-paper/AGENTS.md +11 -0
- package/starter/templates/scientific-paper/README.md +13 -0
- package/starter/templates/scientific-paper/index.tsx +47 -0
- package/starter/templates/scientific-paper/preview.tsx +43 -0
- package/starter/templates/scientific-paper/starter.tsx +15 -0
- package/starter/templates/scientific-paper/template.json +11 -0
- package/starter/themes/Specimen.tsx +54 -0
- package/starter/themes/civic-spectrum/components.tsx +92 -0
- package/starter/themes/civic-spectrum/design.md +70 -0
- package/starter/themes/civic-spectrum/index.ts +57 -0
- package/starter/themes/civic-spectrum/preview.tsx +66 -0
- package/starter/themes/field-manual/components.tsx +106 -0
- package/starter/themes/field-manual/design.md +72 -0
- package/starter/themes/field-manual/index.ts +57 -0
- package/starter/themes/field-manual/preview.tsx +82 -0
- package/starter/themes/index.ts +3 -0
- package/starter/themes/mckinsey-consulting/components.tsx +69 -0
- package/starter/themes/mckinsey-consulting/design.md +68 -0
- package/starter/themes/mckinsey-consulting/index.ts +60 -0
- package/starter/themes/mckinsey-consulting/preview.tsx +53 -0
- package/starter/themes/neutral/design.md +35 -0
- package/starter/themes/neutral/index.ts +21 -0
- package/starter/themes/neutral/preview.tsx +4 -0
- package/starter/themes/opendoc-neutral/components.tsx +77 -0
- package/starter/themes/opendoc-neutral/design.md +70 -0
- package/starter/themes/opendoc-neutral/index.ts +54 -0
- package/starter/themes/opendoc-neutral/preview.tsx +43 -0
- package/starter/themes/types.ts +3 -0
- package/tsconfig.workspace.json +24 -0
- package/vendor/formepdf/LICENSE +21 -0
- package/vendor/formepdf/README.md +97 -0
- package/vendor/formepdf/font-subset.patch +228 -0
- package/vendor/formepdf/text-writer.patch +507 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type FormEvent } from 'react';
|
|
2
|
+
import { Menu } from '@base-ui/react/menu';
|
|
3
|
+
import type { AssetCatalog, AssetFile, AssetInspection, AssetKind, AssetSummary, FontRevision, LogoVariation } from '../shared/assets';
|
|
4
|
+
import type { DocumentSummary } from '../shared/types';
|
|
5
|
+
import type { ThemeSummary } from '../shared/themes';
|
|
6
|
+
import { api } from './api';
|
|
7
|
+
import { CreateLogoDialog, AddLogoVariationsDialog } from './LogoUploadDialog';
|
|
8
|
+
import { MaterialsBrowser } from './MaterialsBrowser';
|
|
9
|
+
import { AssetThemeDialog } from './AssetThemeDefaults';
|
|
10
|
+
import { PdfPage, usePdf } from './Pdf';
|
|
11
|
+
import { SearchField } from './SearchField';
|
|
12
|
+
import { Button, Dialog, Input, SelectControl, useNotifications } from './ui';
|
|
13
|
+
import { Icon } from './ui/Icon';
|
|
14
|
+
import './assets.css';
|
|
15
|
+
|
|
16
|
+
const collection = (kind: AssetKind) => kind === 'logo' ? 'logos' : 'fonts';
|
|
17
|
+
const endpoint = (kind: AssetKind, id: string) => `/api/assets/${kind}/${encodeURIComponent(id)}`;
|
|
18
|
+
export const assetFileUrl = (kind: AssetKind, id: string, revision: string, file: AssetFile) => `${endpoint(kind, id)}/file?revision=${encodeURIComponent(revision)}&file=${encodeURIComponent(file.file)}`;
|
|
19
|
+
const dateLabel = (value: string) => new Date(value).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
|
|
20
|
+
const weightLabel = (weight: number) => ({ 100: 'Thin', 200: 'Extra light', 300: 'Light', 400: 'Regular', 500: 'Medium', 600: 'Semibold', 700: 'Bold', 800: 'Extra bold', 900: 'Black' }[weight] ?? String(weight));
|
|
21
|
+
const fontStyle = (face: { weight: number; style: string }) => `${weightLabel(face.weight)}${face.style === 'italic' ? ' italic' : ''}`;
|
|
22
|
+
|
|
23
|
+
function LogoImage({ id, revision, file, name }: { id: string; revision: string; file?: AssetFile; name: string }) {
|
|
24
|
+
const [failed, setFailed] = useState(false);
|
|
25
|
+
useEffect(() => setFailed(false), [file?.hash]);
|
|
26
|
+
return file && !failed ? <img src={assetFileUrl('logo', id, revision, file)} alt={name} loading="lazy" decoding="async" onError={() => setFailed(true)} /> : <span className="asset-preview-missing"><Icon name="image" size={28} /><span>{failed ? 'Preview unavailable' : 'No preview'}</span></span>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function FontCardPreview({ item }: { item: AssetSummary }) {
|
|
30
|
+
const frame = useRef<HTMLDivElement>(null);
|
|
31
|
+
const [visible, setVisible] = useState(false);
|
|
32
|
+
const [family, setFamily] = useState('');
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!frame.current) return;
|
|
35
|
+
if (!('IntersectionObserver' in window)) { setVisible(true); return; }
|
|
36
|
+
const observer = new IntersectionObserver(entries => { if (entries.some(entry => entry.isIntersecting)) { setVisible(true); observer.disconnect(); } }, { rootMargin: '180px' });
|
|
37
|
+
observer.observe(frame.current); return () => observer.disconnect();
|
|
38
|
+
}, []);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!visible || item.error) return;
|
|
41
|
+
const controller = new AbortController(); let loaded: FontFace | undefined;
|
|
42
|
+
void api<AssetInspection>(`${endpoint('font', item.id)}?revision=${encodeURIComponent(item.revision)}`, { signal: controller.signal }).then(async value => {
|
|
43
|
+
if (value.asset.kind !== 'font') return;
|
|
44
|
+
const face = value.asset.faces.find(face => face.weight === 400 && face.style === 'normal') ?? value.asset.faces[0];
|
|
45
|
+
if (!face) return;
|
|
46
|
+
const name = `OpenDocCard-${face.file.hash}`;
|
|
47
|
+
const font = new FontFace(name, `url("${assetFileUrl('font', item.id, item.revision, face.file)}")`, { weight: '400', style: 'normal' });
|
|
48
|
+
await font.load(); if (controller.signal.aborted) return;
|
|
49
|
+
document.fonts.add(font); loaded = font; setFamily(name);
|
|
50
|
+
}).catch(() => {});
|
|
51
|
+
return () => { controller.abort(); if (loaded) document.fonts.delete(loaded); };
|
|
52
|
+
}, [visible, item.id, item.revision, item.error]);
|
|
53
|
+
return <div ref={frame} className="asset-font-card-preview"><span aria-hidden="true" style={family ? { fontFamily: family, letterSpacing: '-.04em' } : undefined}>{family ? 'Aa' : <Icon name="document" size={36} />}</span><small>{item.count} {item.count === 1 ? 'style' : 'styles'}</small></div>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function FontSpecimen({ asset }: { asset: FontRevision }) {
|
|
57
|
+
const frame = useRef<HTMLDivElement>(null);
|
|
58
|
+
const [width, setWidth] = useState(500);
|
|
59
|
+
const { pdf, error } = usePdf(asset.id, asset.specimen?.hash, true, 'documents', asset.specimen ? assetFileUrl('font', asset.id, asset.revision, asset.specimen) : undefined);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!frame.current) return;
|
|
62
|
+
const observer = new ResizeObserver(([entry]) => { if (entry.contentRect.width > 0) setWidth(Math.min(780, entry.contentRect.width)); });
|
|
63
|
+
observer.observe(frame.current); return () => observer.disconnect();
|
|
64
|
+
}, []);
|
|
65
|
+
return <section className="asset-specimen" aria-label={`${asset.name} PDF specimen`} ref={frame}>
|
|
66
|
+
<div className="asset-specimen-heading"><span>PDF specimen</span>{asset.specimen && <a href={assetFileUrl('font', asset.id, asset.revision, asset.specimen)} target="_blank" rel="noreferrer">Open PDF<Icon name="arrow" size={15} /></a>}</div>
|
|
67
|
+
{error ? <p className="asset-preview-message" role="alert">Could not display this specimen. {error}</p> : pdf ? Array.from({ length: pdf.numPages }, (_, index) => <PdfPage key={`${asset.revision}/${index}`} pdf={pdf} number={index + 1} width={width} thumbnail />) : <p className="asset-preview-message" role="status">{asset.specimen ? 'Opening specimen…' : 'No PDF specimen is available for this version.'}</p>}
|
|
68
|
+
</section>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type AssetFormEdit = { action: 'create'; kind: 'font' } | { action: 'edit' | 'replace' | 'faces' | 'archive' | 'remove'; inspection: AssetInspection; variation?: LogoVariation };
|
|
72
|
+
type AssetEdit = AssetFormEdit | { action: 'create'; kind: 'logo' } | { action: 'variation'; inspection: AssetInspection };
|
|
73
|
+
|
|
74
|
+
function AssetEditDialog({ edit, connected, onClose, onSaved, onArchived }: {
|
|
75
|
+
edit: AssetFormEdit | null; connected: boolean; onClose: () => void; onSaved: (value: AssetInspection) => void; onArchived: (value: AssetInspection) => void;
|
|
76
|
+
}) {
|
|
77
|
+
const [name, setName] = useState('');
|
|
78
|
+
const [description, setDescription] = useState('');
|
|
79
|
+
const [replacementDefault, setReplacementDefault] = useState('');
|
|
80
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
81
|
+
const [clearDefaults, setClearDefaults] = useState(false);
|
|
82
|
+
const [error, setError] = useState('');
|
|
83
|
+
const [busy, setBusy] = useState(false);
|
|
84
|
+
const working = useRef(false);
|
|
85
|
+
const input = useRef<HTMLInputElement>(null);
|
|
86
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!edit) return;
|
|
89
|
+
const source = edit.action === 'create' ? undefined : edit.inspection.asset;
|
|
90
|
+
setName(edit.action !== 'create' && edit.variation ? edit.variation.name : source?.name ?? '');
|
|
91
|
+
setDescription(edit.action !== 'create' && edit.variation ? edit.variation.description : source?.description ?? '');
|
|
92
|
+
setReplacementDefault(''); setFiles([]); setError(''); setClearDefaults(false);
|
|
93
|
+
}, [edit]);
|
|
94
|
+
if (!edit) return null;
|
|
95
|
+
const kind = edit.action === 'create' ? edit.kind : edit.inspection.asset.kind;
|
|
96
|
+
const adding = edit.action === 'create';
|
|
97
|
+
const archive = edit.action === 'archive';
|
|
98
|
+
const removing = edit.action === 'remove';
|
|
99
|
+
const logo = !adding && edit.inspection.asset.kind === 'logo' ? edit.inspection.asset : undefined;
|
|
100
|
+
const removingDefault = removing && edit.variation?.id === logo?.defaultVariation;
|
|
101
|
+
const upload = adding || edit.action === 'replace' || edit.action === 'faces';
|
|
102
|
+
const showName = edit.action === 'edit';
|
|
103
|
+
const nameRequired = showName;
|
|
104
|
+
const themes = !adding ? edit.inspection.usage.themes : [];
|
|
105
|
+
const title = removing ? `Remove ${edit.variation?.name}?` : adding ? 'Add a font family' : edit.action === 'replace' ? 'Replace logo artwork' : edit.action === 'faces' ? 'Add font styles' : archive ? `Archive ${edit.inspection.asset.name}?` : edit.variation ? 'Edit variation' : 'Edit name & guidance';
|
|
106
|
+
const hint = removing ? 'This variation will be removed from the logo. Existing documents keep their saved artwork.' : archive ? 'Existing documents keep using their saved versions. You can restore this asset at any time.' : adding ? 'Select TTF or OTF files from one family. The name, description, and styles are read from the files.' : edit.action === 'replace' ? 'The new artwork will be used by future choices. Documents already using this version keep the original.' : edit.action === 'faces' ? 'Add static TTF or OTF files from this family. Existing documents keep their saved styles.' : 'Give your agent practical guidance. This saves a new version; existing documents keep their saved guidance.';
|
|
107
|
+
async function submit(event: FormEvent) {
|
|
108
|
+
event.preventDefault(); if (!edit || working.current || !connected || (nameRequired && !name.trim()) || (upload && !files.length) || (removingDefault && !replacementDefault)) return;
|
|
109
|
+
working.current = true; setBusy(true); setError('');
|
|
110
|
+
try {
|
|
111
|
+
let value: AssetInspection;
|
|
112
|
+
if (upload) {
|
|
113
|
+
const form = new FormData();
|
|
114
|
+
const metadata = adding ? {}
|
|
115
|
+
: edit.action === 'faces' ? { expectedRevision: edit.inspection.head.revision }
|
|
116
|
+
: { expectedRevision: edit.inspection.head.revision, name: edit.action === 'replace' ? edit.variation?.name : name.trim(), description: edit.action === 'replace' ? edit.variation?.description : description, ...(edit.action === 'replace' ? { replaceId: edit.variation?.id } : {}) };
|
|
117
|
+
form.set('metadata', JSON.stringify(metadata)); files.forEach(file => form.append(kind === 'logo' ? 'file' : 'files', file));
|
|
118
|
+
const url = adding ? `/api/assets/${kind}` : `${endpoint(kind, edit.inspection.asset.id)}/${kind === 'logo' ? 'variations' : 'faces'}`;
|
|
119
|
+
value = await api<AssetInspection>(url, { method: 'POST', body: form });
|
|
120
|
+
} else if (edit.action === 'archive') {
|
|
121
|
+
value = await api<AssetInspection>(`${endpoint(kind, edit.inspection.asset.id)}/archive`, { method: 'POST', body: JSON.stringify({ expectedRevision: edit.inspection.head.revision, clearDefaults }) });
|
|
122
|
+
} else if (edit.action === 'remove') {
|
|
123
|
+
value = await api<AssetInspection>(endpoint(kind, edit.inspection.asset.id), { method: 'PATCH', body: JSON.stringify({ expectedRevision: edit.inspection.head.revision, removeVariation: edit.variation?.id, ...(removingDefault ? { defaultVariation: replacementDefault } : {}) }) });
|
|
124
|
+
} else if (edit.action === 'edit') {
|
|
125
|
+
value = await api<AssetInspection>(endpoint(kind, edit.inspection.asset.id), { method: 'PATCH', body: JSON.stringify({ expectedRevision: edit.inspection.head.revision, ...(edit.variation ? { variation: { id: edit.variation.id, name: name.trim(), description } } : { name: name.trim(), description }) }) });
|
|
126
|
+
} else { return; }
|
|
127
|
+
if (archive) onArchived(value); else onSaved(value);
|
|
128
|
+
onClose();
|
|
129
|
+
} catch (error) { setError((error as Error).message); }
|
|
130
|
+
finally { working.current = false; setBusy(false); }
|
|
131
|
+
}
|
|
132
|
+
return <Dialog.Root open onOpenChange={value => { if (!value && !working.current) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog asset-dialog" initialFocus={upload ? fileInput : archive || removing ? undefined : input}>
|
|
133
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close asset dialog" disabled={busy} />}><Icon name="close" /></Dialog.Close>
|
|
134
|
+
<Dialog.Title>{title}</Dialog.Title><Dialog.Description>{hint}</Dialog.Description>
|
|
135
|
+
<form onSubmit={event => void submit(event)} aria-busy={busy}>
|
|
136
|
+
{showName && <label className="create-field"><span>{edit.action === 'edit' && edit.variation ? 'Variation name' : kind === 'logo' ? 'Logo name' : 'Family name'}</span><Input ref={input} value={name} onChange={event => setName(event.target.value)} required={nameRequired} maxLength={120} disabled={busy} autoComplete="off" placeholder={edit.action === 'edit' && edit.variation ? 'For example, On dark backgrounds' : kind === 'logo' ? 'For example, Northstar' : 'Read from the font files'} /></label>}
|
|
137
|
+
{showName && <label className="create-field"><span>{edit.action === 'edit' && edit.variation ? 'When to use' : 'Guidance for your agent'} <span className="muted">(optional)</span></span><textarea value={description} onChange={event => setDescription(event.target.value)} disabled={busy} rows={3} placeholder="Describe where this asset fits, and anything to avoid." /></label>}
|
|
138
|
+
{removingDefault && <div className="create-field"><span>New default</span><SelectControl label="New default" value={replacementDefault} onValueChange={setReplacementDefault} items={[{ value: '', label: 'Choose a variation' }, ...(logo?.variations.filter(item => item.id !== edit.variation?.id).map(item => ({ value: item.id, label: item.name })) ?? [])]} /></div>}
|
|
139
|
+
{upload && <label className="create-field asset-upload"><span>{kind === 'logo' ? 'Logo artwork' : 'Font files'}</span><input ref={fileInput} type="file" accept={kind === 'logo' ? '.svg,.png,image/svg+xml,image/png' : '.ttf,.otf,font/ttf,font/otf'} multiple={kind === 'font'} onChange={event => setFiles(Array.from(event.target.files ?? []))} disabled={busy} required /><span className="field-hint">{kind === 'logo' ? 'SVG or PNG. Transparency and proportions are preserved.' : 'Static TTF or OTF. Select multiple styles together.'}</span>{files.length > 0 && <ul className="asset-upload-files">{files.map((file, index) => <li key={`${file.name}/${index}`}>{file.name}<span>{Math.max(1, Math.round(file.size / 1024))} KB</span></li>)}</ul>}</label>}
|
|
140
|
+
{archive && themes.length > 0 && <div className="asset-archive-relationships"><p>Used as a default by:</p><ul>{themes.map(theme => <li key={theme.id}><a href={`#themes/${theme.id}`}>{theme.name}</a><span>{theme.roles.join(', ')}</span></li>)}</ul><label className="asset-checkbox"><input type="checkbox" checked={clearDefaults} onChange={event => setClearDefaults(event.target.checked)} disabled={busy} /><span>Clear these theme defaults when archiving</span></label></div>}
|
|
141
|
+
{error && <p role="alert" className="field-error">{error}</p>}
|
|
142
|
+
<div className="dialog-actions"><Dialog.Close render={<Button disabled={busy} />}>Cancel</Dialog.Close><Button type="submit" className="primary" disabled={busy || !connected || (nameRequired && !name.trim()) || (upload && !files.length) || (archive && themes.length > 0 && !clearDefaults) || (removingDefault && !replacementDefault)}>{busy ? upload ? 'Checking & saving…' : archive ? 'Archiving…' : 'Saving…' : removing ? 'Remove variation' : archive ? 'Archive' : adding ? 'Add fonts' : edit.action === 'replace' ? 'Replace file' : edit.action === 'faces' ? 'Add styles' : 'Save changes'}</Button></div>
|
|
143
|
+
</form>
|
|
144
|
+
</Dialog.Popup></Dialog.Portal></Dialog.Root>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function AssetRelationships({ inspection }: { inspection: AssetInspection }) {
|
|
148
|
+
const { usage } = inspection;
|
|
149
|
+
return <section className="asset-relationships"><h2>Used with</h2>
|
|
150
|
+
{!usage.themes.length && !usage.documents.length ? <p className="muted small">No theme defaults or saved document choices yet.</p> : <>
|
|
151
|
+
{usage.themes.map(theme => <a className="asset-relationship" href={`#themes/${theme.id}`} key={`theme/${theme.id}`}><Icon name="theme" size={16} /><span><strong>{theme.name}</strong><small>For new documents · {theme.roles.join(', ')}</small></span><Icon name="arrow" size={15} /></a>)}
|
|
152
|
+
{usage.documents.map(document => <div className="asset-relationship" key={`document/${document.id}/${document.revision}`}><Icon name={document.trashed ? 'trash' : 'document'} size={16} /><span>{document.trashed ? <strong>{document.name}</strong> : <a href={`#document/${document.id}`}><strong>{document.name}</strong></a>}<small>{document.trashed ? 'In Trash · ' : ''}{document.revision !== inspection.asset.revision ? inspection.asset.revision === inspection.head.revision ? 'Earlier saved version · ' : 'Another saved version · ' : ''}{document.rendered ? document.current ? 'Used in current PDF' : 'Used in last successful PDF' : 'Available to document'} · {document.roles.join(', ')}</small></span></div>)}
|
|
153
|
+
</>}
|
|
154
|
+
</section>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function AssetVersions({ inspection, open, onClose, onSelect }: { inspection: AssetInspection; open: boolean; onClose: () => void; onSelect: (revision: string) => void }) {
|
|
158
|
+
return <Dialog.Root open={open} onOpenChange={value => { if (!value) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog asset-dialog asset-versions-dialog">
|
|
159
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close previous versions" />}><Icon name="close" /></Dialog.Close><Dialog.Title>Previous versions</Dialog.Title><Dialog.Description>{inspection.asset.name}. Saved artwork and guidance stay available to documents that use them.</Dialog.Description>
|
|
160
|
+
<div className="asset-version-list">{inspection.versions.map((version, index) => <Button className="asset-version" key={version.revision} onClick={() => { onSelect(version.revision); onClose(); }}><span><strong>Version {inspection.versions.length - index}</strong><small>{dateLabel(version.createdAt)} · {version.name}</small></span>{version.revision === inspection.head.revision ? <span className="asset-badge">Current</span> : <Icon name="arrow" size={16} />}</Button>)}</div>
|
|
161
|
+
</Dialog.Popup></Dialog.Portal></Dialog.Root>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function AssetCollection({ kind, catalog, query, setQuery, archived, setArchived, onAdd, error }: { kind: AssetKind; catalog?: AssetCatalog; error?: string; query: string; setQuery: (value: string) => void; archived: boolean; setArchived: (value: boolean) => void; onAdd: () => void }) {
|
|
165
|
+
const items = catalog?.items.filter(item => item.archived === archived && `${item.name} ${item.description}`.toLowerCase().includes(query.toLowerCase())) ?? [];
|
|
166
|
+
return <>
|
|
167
|
+
<div className="asset-collection-toolbar"><SearchField label={`Search ${collection(kind)}`} value={query} onValueChange={setQuery} /><SelectControl label="Asset status" value={archived ? 'archived' : 'active'} onValueChange={value => setArchived(value === 'archived')} items={[{ value: 'active', label: `All ${collection(kind)}` }, { value: 'archived', label: 'Archived' }]} /></div>
|
|
168
|
+
{!catalog && <p className="asset-collection-status" role="status">{error ? 'Could not read local assets.' : `Reading ${collection(kind)}…`}</p>}
|
|
169
|
+
<div className="asset-grid">{items.map(item => <a href={`#assets/${collection(kind)}/${item.id}`} className={`asset-card ${item.error ? 'asset-card-error' : ''}`} key={item.id}>
|
|
170
|
+
{kind === 'logo' ? <div className="asset-card-preview asset-background-transparent"><LogoImage id={item.id} revision={item.revision} file={item.preview} name={item.name} /></div> : <FontCardPreview item={item} />}
|
|
171
|
+
<div className="asset-card-caption"><strong>{item.name}<Icon name="arrow" size={16} /></strong><span>{(kind === 'logo' ? `${item.count} ${item.count === 1 ? 'variation' : 'variations'}` : item.builtIn ? 'Built-in family' : item.description || 'Local font family')}</span>{item.error ? <span className="media-problem">{item.error}</span> : kind === 'font' && item.compatibility && !item.compatibility.defaultEligible ? <span className="asset-health-note">{item.compatibility.status === 'needs-attention' ? 'Needs attention' : 'Additional styles needed for theme defaults'}</span> : null}{item.builtIn && kind === 'logo' && <span>Built in</span>}</div>
|
|
172
|
+
</a>)}</div>
|
|
173
|
+
{catalog && !items.length && <div className="empty-state"><div className="asset-empty-mark" aria-hidden="true">{kind === 'logo' ? <Icon name="image" size={28} /> : 'Aa'}</div><h2>{query ? `No matching ${collection(kind)}` : archived ? 'Nothing archived' : kind === 'logo' ? 'Your logos' : 'Typography, ready to use.'}</h2><p>{query ? 'Try another name or clear your search.' : archived ? 'Archived assets remain available to existing documents.' : kind === 'logo' ? 'Add a logo, then keep all its variations inside.' : 'Add a family’s font files together, then explore its styles in a real PDF.'}</p>{query ? <Button onClick={() => setQuery('')}>Clear search</Button> : !archived && <Button onClick={onAdd}><Icon name="plus" size={16} />{kind === 'logo' ? 'Add your first logo' : 'Add fonts'}</Button>}</div>}
|
|
174
|
+
</>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function AssetsBrowser({ selection, generation, documents, themes, connected }: { selection: string; generation: number; documents: DocumentSummary[]; themes: ThemeSummary[]; connected: boolean }) {
|
|
178
|
+
const [tabPart, ...parts] = selection.split('/');
|
|
179
|
+
const tab = tabPart === 'logos' || tabPart === 'fonts' ? tabPart : 'media';
|
|
180
|
+
const id = parts.join('/');
|
|
181
|
+
const kind: AssetKind = tab === 'fonts' ? 'font' : 'logo';
|
|
182
|
+
const [catalog, setCatalog] = useState<{ kind: AssetKind; value: AssetCatalog }>();
|
|
183
|
+
const [inspectionState, setInspectionState] = useState<{ key: string; value: AssetInspection }>();
|
|
184
|
+
const [selectedRevision, setSelectedRevision] = useState('');
|
|
185
|
+
const [variationId, setVariationId] = useState('');
|
|
186
|
+
const [background, setBackground] = useState('transparent');
|
|
187
|
+
const [query, setQuery] = useState('');
|
|
188
|
+
const [archived, setArchived] = useState(false);
|
|
189
|
+
const [error, setError] = useState('');
|
|
190
|
+
const [retry, setRetry] = useState(0);
|
|
191
|
+
const [edit, setEdit] = useState<AssetEdit | null>(null);
|
|
192
|
+
const [versionsOpen, setVersionsOpen] = useState(false);
|
|
193
|
+
const [themeOpen, setThemeOpen] = useState(false);
|
|
194
|
+
const [busy, setBusy] = useState(false);
|
|
195
|
+
const working = useRef(false);
|
|
196
|
+
const notifications = useNotifications();
|
|
197
|
+
const requestKey = `${kind}/${id}/${selectedRevision}`;
|
|
198
|
+
const inspection = inspectionState?.key === requestKey ? inspectionState.value : undefined;
|
|
199
|
+
useEffect(() => { setSelectedRevision(''); setVariationId(''); setError(''); setEdit(null); setVersionsOpen(false); setThemeOpen(false); setQuery(''); }, [tab, id]);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (tab === 'media') return;
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
void api<AssetCatalog>(`/api/assets?kind=${kind}&archived=true`, { signal: controller.signal }).then(value => { setCatalog({ kind, value }); if (!id) setError(''); }).catch(error => { if (!controller.signal.aborted) setError(error.message); });
|
|
204
|
+
return () => controller.abort();
|
|
205
|
+
}, [tab, kind, id, generation, retry]);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (tab === 'media' || !id) return;
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
void api<AssetInspection>(`${endpoint(kind, id)}${selectedRevision ? `?revision=${encodeURIComponent(selectedRevision)}` : ''}`, { signal: controller.signal }).then(value => { setInspectionState({ key: requestKey, value }); setError(''); }).catch(error => { if (!controller.signal.aborted) setError(error.message); });
|
|
210
|
+
return () => controller.abort();
|
|
211
|
+
}, [tab, kind, id, selectedRevision, requestKey, generation, retry]);
|
|
212
|
+
const asset = inspection?.asset;
|
|
213
|
+
const variation = asset?.kind === 'logo' ? asset.variations.find(item => item.id === variationId) ?? asset.variations.find(item => item.id === asset.defaultVariation) ?? asset.variations[0] : undefined;
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (tab === 'media') return;
|
|
216
|
+
void api('/api/context', { method: 'POST', body: JSON.stringify({ projectId: null, documentId: null, blockId: null, page: 1, mediaId: null, themeId: null, selectedAsset: asset ? { kind: asset.kind, id: asset.id, revision: asset.revision, ...(variation ? { variation: variation.id } : {}) } : null }) }).catch(() => {});
|
|
217
|
+
}, [tab, asset?.kind, asset?.id, asset?.revision, variation?.id]);
|
|
218
|
+
const historical = Boolean(inspection && inspection.asset.revision !== inspection.head.revision);
|
|
219
|
+
const editable = Boolean(inspection && !historical && !inspection.head.archived && !inspection.head.builtIn && connected && !busy);
|
|
220
|
+
function saved(value: AssetInspection) { setSelectedRevision(''); setInspectionState({ key: `${value.asset.kind}/${value.asset.id}/`, value }); setRetry(value => value + 1); notifications.success('Asset saved'); if (id !== value.asset.id || tab !== collection(value.asset.kind)) location.hash = `assets/${collection(value.asset.kind)}/${value.asset.id}`; }
|
|
221
|
+
function archivedAsset(value: AssetInspection) {
|
|
222
|
+
setInspectionState({ key: requestKey, value }); setRetry(value => value + 1);
|
|
223
|
+
notifications.success(`${value.asset.name} archived`, { label: 'Undo', onClick: async () => {
|
|
224
|
+
await api<AssetInspection>(`${endpoint(value.asset.kind, value.asset.id)}/restore`, { method: 'POST', body: JSON.stringify({ expectedRevision: value.head.revision }) });
|
|
225
|
+
setRetry(value => value + 1); notifications.success(`${value.asset.name} restored`);
|
|
226
|
+
} });
|
|
227
|
+
}
|
|
228
|
+
async function quickAction(action: 'default' | 'restore') {
|
|
229
|
+
if (!inspection || working.current || !connected) return;
|
|
230
|
+
working.current = true; setBusy(true); setError('');
|
|
231
|
+
try {
|
|
232
|
+
const result = await api<AssetInspection>(`${endpoint(kind, inspection.asset.id)}${action === 'restore' ? '/restore' : ''}`, { method: action === 'restore' ? 'POST' : 'PATCH', body: JSON.stringify({ expectedRevision: inspection.head.revision, ...(action === 'default' ? { defaultVariation: variation?.id } : {}) }) });
|
|
233
|
+
setInspectionState({ key: requestKey, value: result }); setRetry(value => value + 1); notifications.success(action === 'restore' ? 'Asset restored' : 'Default variation saved');
|
|
234
|
+
} catch (error) { setError((error as Error).message); }
|
|
235
|
+
finally { working.current = false; setBusy(false); }
|
|
236
|
+
}
|
|
237
|
+
return <section className="library-content assets-content">
|
|
238
|
+
<div className="library-heading assets-heading"><h1>Media & Assets</h1>{tab !== 'media' && !id && <Button onClick={() => setEdit({ action: 'create', kind })} disabled={!connected}><Icon name="plus" size={17} />{kind === 'logo' ? 'Add logo' : 'Add fonts'}</Button>}</div>
|
|
239
|
+
<nav className="assets-tabs" aria-label="Media and assets categories">{(['media', 'logos', 'fonts'] as const).map(value => <a key={value} href={`#assets/${value}`} aria-current={tab === value ? 'page' : undefined}>{value === 'media' ? 'Media' : value === 'logos' ? 'Logos' : 'Fonts'}</a>)}</nav>
|
|
240
|
+
{tab === 'media' ? <MaterialsBrowser connected={connected} selection={id} generation={generation} documents={documents} /> : <>
|
|
241
|
+
{error && <div className="error-banner" role="alert"><span>{error}</span><Button className="text-button" onClick={() => setRetry(value => value + 1)}>Try again</Button></div>}
|
|
242
|
+
{catalog?.kind === kind && catalog.value.issues.map(issue => <p className="media-problem materials-issue" role="status" key={issue}>{issue}</p>)}
|
|
243
|
+
{!id ? <AssetCollection error={error} kind={kind} catalog={catalog?.kind === kind ? catalog.value : undefined} query={query} setQuery={setQuery} archived={archived} setArchived={setArchived} onAdd={() => setEdit({ action: 'create', kind })} /> : <>
|
|
244
|
+
<a className="media-back" href={`#assets/${collection(kind)}`}><Icon name="left" size={16} />All {collection(kind)}</a>
|
|
245
|
+
{!asset || !inspection ? <div className="empty-state"><h2>{error ? 'Asset unavailable' : 'Loading asset…'}</h2><p>{error ? 'Check the local files or return to the library.' : 'Opening its saved artwork and guidance.'}</p></div> : <>
|
|
246
|
+
{historical && <div className="asset-state-banner" role="status"><span>Viewing a previous version · {dateLabel(asset.createdAt)}</span><Button className="text-button" onClick={() => setSelectedRevision('')}>View current version</Button></div>}
|
|
247
|
+
{inspection.head.archived && <div className="asset-state-banner" role="status"><span>Archived. Existing documents can still use this asset.</span><Button onClick={() => void quickAction('restore')} disabled={!connected || busy || inspection.head.builtIn}>{busy ? 'Restoring…' : 'Restore'}</Button></div>}
|
|
248
|
+
<header className="asset-detail-heading"><div><h2>{asset.name}</h2>{asset.description ? <p>{asset.description}</p> : <p className="muted">{asset.kind === 'logo' ? 'A shared logo for your documents.' : 'A shared font family for your documents.'}</p>}</div><div className="asset-detail-actions"><Button onClick={() => setThemeOpen(true)} disabled={!connected || inspection.head.archived || historical || (asset.kind === 'font' && !asset.compatibility.defaultEligible)}>Use with theme</Button><Menu.Root><Menu.Trigger render={<Button className="icon-button" aria-label={`Options for ${asset.name}`} />}><Icon name="more" /></Menu.Trigger><Menu.Portal><Menu.Positioner className="ui-positioner" align="end" sideOffset={6}><Menu.Popup className="ui-menu-popup"><Menu.Item className="ui-menu-item" disabled={!editable} onClick={() => setEdit({ action: 'edit', inspection })}><Icon name="edit" size={16} />Edit name & guidance</Menu.Item><Menu.Item className="ui-menu-item" onClick={() => setVersionsOpen(true)}><Icon name="history" size={16} />Previous versions</Menu.Item><Menu.Separator className="ui-menu-separator" /><Menu.Item className="ui-menu-item" disabled={!editable} onClick={() => setEdit({ action: 'archive', inspection })}><Icon name="trash" size={16} />Archive</Menu.Item></Menu.Popup></Menu.Positioner></Menu.Portal></Menu.Root></div></header>
|
|
249
|
+
{asset.kind === 'logo' && <>
|
|
250
|
+
<section className="asset-variations logo-folder-contents"><div className="asset-section-heading"><h3>Files <span>{asset.variations.length}</span></h3><Button className="text-button" disabled={!editable} onClick={() => setEdit({ action: 'variation', inspection })}><Icon name="plus" size={16} />Add variations</Button></div>
|
|
251
|
+
<div className="asset-variation-list">{asset.variations.map(item => <Button static className={`asset-variation-choice ${variation?.id === item.id ? 'selected' : ''}`} key={item.id} aria-pressed={variation?.id === item.id} onClick={() => setVariationId(item.id)}><span className={`asset-variation-thumb asset-background-${background}`} aria-hidden="true"><LogoImage id={asset.id} revision={asset.revision} file={item.image} name={item.name} /></span><span><strong>{item.name}</strong><small>{item.id === asset.defaultVariation ? 'Default variation' : item.original.mime === 'image/svg+xml' ? 'SVG' : 'PNG'}</small></span>{variation?.id === item.id && <Icon name="check" size={16} />}</Button>)}</div>
|
|
252
|
+
</section>
|
|
253
|
+
</>}
|
|
254
|
+
<div className="asset-detail-grid"><div className="asset-main-column">
|
|
255
|
+
{asset.kind === 'logo' ? <>
|
|
256
|
+
<div className={`asset-logo-stage asset-background-${background}`}><LogoImage id={asset.id} revision={asset.revision} file={variation?.image} name={`${asset.name} · ${variation?.name ?? ''}`} /></div>
|
|
257
|
+
<div className="asset-preview-tools"><div className="asset-backgrounds" role="group" aria-label="Preview background">{(['light', 'dark', 'transparent'] as const).map(value => <Button className={`asset-background-choice asset-background-${value}`} key={value} aria-label={`${value === 'transparent' ? 'Transparency' : value === 'light' ? 'Light' : 'Dark'} preview background`} aria-pressed={background === value} onClick={() => setBackground(value)}><span aria-hidden="true">{background === value ? <Icon name="check" size={14} /> : null}</span></Button>)}</div><span className="small muted">Preview background only</span>{variation && <a href={assetFileUrl('logo', asset.id, asset.revision, variation.original)} download>Original {variation.original.mime === 'image/svg+xml' ? 'SVG' : 'PNG'}<Icon name="download" size={15} /></a>}</div>
|
|
258
|
+
|
|
259
|
+
</> : <FontSpecimen key={`${asset.id}/${asset.revision}`} asset={asset} />}
|
|
260
|
+
</div><aside className="asset-side-column">
|
|
261
|
+
{asset.kind === 'logo' && variation && <section className="asset-variation-details"><div className="asset-section-heading"><h3>{variation.name}</h3>{variation.id === asset.defaultVariation && <span className="asset-badge">Default</span>}</div><h4>When to use</h4><p>{variation.description || 'No guidance yet. Describe which backgrounds or layouts suit this variation.'}</p><div className="asset-action-stack"><Button className="text-button" onClick={() => setEdit({ action: 'edit', inspection, variation })} disabled={!editable}><Icon name="edit" size={15} />Edit guidance</Button><Button className="text-button" onClick={() => setEdit({ action: 'replace', inspection, variation })} disabled={!editable}>Replace file</Button>{variation.id !== asset.defaultVariation && <Button className="text-button" disabled={!editable} onClick={() => void quickAction('default')}>Make default</Button>}{asset.variations.length > 1 && <Button className="text-button" disabled={!editable} onClick={() => setEdit({ action: 'remove', inspection, variation })}><Icon name="trash" size={15} />Remove variation</Button>}</div><p className="small muted asset-variation-size">{variation.image.width} × {variation.image.height} px · Prepared for PDF</p></section>}
|
|
262
|
+
{asset.kind === 'font' && <><section className="asset-font-health"><h3>{asset.compatibility.defaultEligible ? 'Ready for theme defaults' : 'Font compatibility'}</h3><p className={asset.compatibility.status === 'needs-attention' ? 'media-problem' : ''}>{asset.compatibility.message || (asset.compatibility.defaultEligible ? 'This family includes regular and semibold or bold, and passed the PDF checks.' : 'Add regular and semibold or bold before using this family as a theme default.')}</p></section><section className="asset-font-styles"><div className="asset-section-heading"><h3>Styles <span>{asset.faces.length}</span></h3><Button className="text-button" onClick={() => setEdit({ action: 'faces', inspection })} disabled={!editable}><Icon name="plus" size={15} />Add</Button></div>{!asset.faces.some(face => face.style === 'italic') && <p className="small muted asset-missing-style">No italic styles included. Add them if your documents use italics.</p>}{!asset.faces.some(face => face.weight === 600 && face.style === 'normal') && asset.faces.some(face => face.weight === 700 && face.style === 'normal') && <p className="small muted asset-missing-style">Semibold requests use the family’s Bold face.</p>}<ul>{asset.faces.map(face => <li key={face.id}><span><strong>{fontStyle(face)}</strong><small>{face.weight} · {face.file.file.toLowerCase().endsWith('.otf') ? 'OTF' : 'TTF'}</small></span><a href={assetFileUrl('font', asset.id, asset.revision, face.file)} aria-label={`Download ${fontStyle(face)}`} download><Icon name="download" size={16} /></a></li>)}</ul></section><Button className="text-button" disabled={!editable} onClick={() => setEdit({ action: 'edit', inspection })}><Icon name="edit" size={15} />Edit guidance</Button></>}
|
|
263
|
+
<AssetRelationships inspection={inspection} />
|
|
264
|
+
{inspection.head.builtIn && <p className="small muted">Built-in asset. Its original files stay in the workspace.</p>}
|
|
265
|
+
</aside></div>
|
|
266
|
+
<AssetVersions inspection={inspection} open={versionsOpen} onClose={() => setVersionsOpen(false)} onSelect={setSelectedRevision} />
|
|
267
|
+
<AssetThemeDialog open={themeOpen} onClose={() => setThemeOpen(false)} themes={themes} generation={generation + retry} connected={connected} asset={asset} />
|
|
268
|
+
</>}
|
|
269
|
+
</>}
|
|
270
|
+
{edit?.action === 'create' && edit.kind === 'logo' ? <CreateLogoDialog connected={connected} onClose={() => setEdit(null)} onSaved={saved} /> : edit?.action === 'variation' ? <AddLogoVariationsDialog inspection={edit.inspection} connected={connected} onClose={() => setEdit(null)} onSaved={saved} /> : <AssetEditDialog edit={edit} connected={connected} onClose={() => setEdit(null)} onSaved={saved} onArchived={archivedAsset} />}
|
|
271
|
+
</>}
|
|
272
|
+
</section>;
|
|
273
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useEffect, useRef, type Ref } from 'react';
|
|
2
|
+
import type { Comment } from '../shared/types';
|
|
3
|
+
import { Button, IconButton } from './ui';
|
|
4
|
+
import { Icon } from './ui/Icon';
|
|
5
|
+
import './comment-dock.css';
|
|
6
|
+
|
|
7
|
+
export interface CommentDockItem {
|
|
8
|
+
comment: Comment;
|
|
9
|
+
page?: number;
|
|
10
|
+
kind: string;
|
|
11
|
+
canJump: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CommentDockProps {
|
|
15
|
+
pageLabel?: string;
|
|
16
|
+
open: boolean;
|
|
17
|
+
heading: string;
|
|
18
|
+
count: number;
|
|
19
|
+
items: CommentDockItem[];
|
|
20
|
+
error?: string;
|
|
21
|
+
connected: boolean;
|
|
22
|
+
changing: string | null;
|
|
23
|
+
editing: { id: string; text: string } | null;
|
|
24
|
+
onEdit: (comment: Comment) => void;
|
|
25
|
+
onEditText: (text: string) => void;
|
|
26
|
+
onSaveEdit: (comment: Comment) => void | Promise<void>;
|
|
27
|
+
onCancelEdit: () => void;
|
|
28
|
+
onDelete: (comment: Comment) => void | Promise<void>;
|
|
29
|
+
onJump: (comment: Comment) => void;
|
|
30
|
+
onOpen: () => void;
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
triggerRef?: Ref<HTMLButtonElement>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The reader owns selection, drafts, and writes; this dock only presents them. */
|
|
36
|
+
export function CommentDock({
|
|
37
|
+
pageLabel = 'Page',
|
|
38
|
+
open, heading, count, items, error, connected, changing, editing,
|
|
39
|
+
onEdit, onEditText, onSaveEdit, onCancelEdit, onDelete, onJump, onOpen, onClose,
|
|
40
|
+
triggerRef,
|
|
41
|
+
}: CommentDockProps) {
|
|
42
|
+
const editField = useRef<HTMLTextAreaElement>(null);
|
|
43
|
+
const closeButton = useRef<HTMLButtonElement>(null);
|
|
44
|
+
const busy = changing !== null;
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!open) return;
|
|
48
|
+
const field = editing ? editField.current : closeButton.current;
|
|
49
|
+
field?.focus({ preventScroll: true });
|
|
50
|
+
}, [open, editing?.id]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!open) return;
|
|
54
|
+
const dismiss = (event: KeyboardEvent) => {
|
|
55
|
+
if (event.defaultPrevented || event.key !== 'Escape') return;
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
onClose();
|
|
59
|
+
};
|
|
60
|
+
document.addEventListener('keydown', dismiss);
|
|
61
|
+
return () => document.removeEventListener('keydown', dismiss);
|
|
62
|
+
}, [open, onClose]);
|
|
63
|
+
|
|
64
|
+
return <>
|
|
65
|
+
<Button static ref={triggerRef} className="comment-dock-trigger" aria-label="Show comments" aria-controls="reader-comments" aria-expanded={open} onClick={open ? onClose : onOpen}>
|
|
66
|
+
<Icon name="comment" size={17} /><span className="comment-dock-count" aria-hidden="true">{count}</span>
|
|
67
|
+
</Button>
|
|
68
|
+
{open && <section className="correction-editor comment-popover comment-dock-panel" id="reader-comments" role="dialog" aria-modal="false" aria-labelledby="comment-dock-heading" onKeyDown={event => {
|
|
69
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
70
|
+
const form = (event.target as HTMLElement).closest('form');
|
|
71
|
+
if (form) { event.preventDefault(); if (!busy) form.requestSubmit(); }
|
|
72
|
+
}
|
|
73
|
+
}}>
|
|
74
|
+
<div className="correction-heading">
|
|
75
|
+
<h2 id="comment-dock-heading">{heading}</h2>
|
|
76
|
+
<IconButton ref={closeButton} label="Close comments" onClick={onClose}><Icon name="close" size={15} /></IconButton>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="comment-dock-content">
|
|
79
|
+
{error && <p className="comment-error" role="alert">{error}</p>}
|
|
80
|
+
{items.length ? items.map(({ comment, page, kind, canJump }) => <article className={`comment-card ${comment.status}`} key={comment.id}>
|
|
81
|
+
{editing?.id === comment.id ? <form onSubmit={event => {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
if (!connected || busy || !editing.text.trim() || editing.text.trim() === comment.text) return;
|
|
84
|
+
void onSaveEdit(comment);
|
|
85
|
+
}}>
|
|
86
|
+
<textarea ref={editField} aria-label="Edit comment" value={editing.text} maxLength={8000} readOnly={busy} onChange={event => onEditText(event.target.value)} />
|
|
87
|
+
<div className="correction-actions">
|
|
88
|
+
<Button disabled={busy} onClick={onCancelEdit}>Cancel</Button>
|
|
89
|
+
<Button className="primary" type="submit" disabled={!connected || busy || !editing.text.trim() || editing.text.trim() === comment.text}>{changing === comment.id ? 'Saving…' : 'Save'}</Button>
|
|
90
|
+
</div>
|
|
91
|
+
</form> : <div className="comment-row">
|
|
92
|
+
<Button static className="comment-jump" disabled={!canJump || busy} onClick={() => onJump(comment)}>
|
|
93
|
+
<span className="comment-location">
|
|
94
|
+
{page !== undefined ? <><span>{pageLabel} {page}</span><span aria-hidden="true">·</span><span className="comment-kind">{kind}</span>{canJump && <Icon name="arrow" size={12} />}</> : 'Component unavailable'}
|
|
95
|
+
{comment.status === 'resolved' && <span>· Resolved</span>}
|
|
96
|
+
</span>
|
|
97
|
+
<span className="comment-text">{comment.text}</span>
|
|
98
|
+
</Button>
|
|
99
|
+
<div className="comment-row-actions">
|
|
100
|
+
<IconButton label="Edit comment" disabled={!connected || busy} onClick={() => onEdit(comment)}><Icon name="edit" size={15} /></IconButton>
|
|
101
|
+
<IconButton label={changing === comment.id ? 'Deleting comment…' : 'Delete comment'} className="delete-comment" disabled={!connected || busy} onClick={() => { void onDelete(comment); }}><Icon name="trash" size={15} /></IconButton>
|
|
102
|
+
</div>
|
|
103
|
+
</div>}
|
|
104
|
+
</article>) : <p className="no-comments">No comments here yet.</p>}
|
|
105
|
+
</div>
|
|
106
|
+
</section>}
|
|
107
|
+
</>;
|
|
108
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DocumentFormat } from '../shared/types';
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button, Dialog } from "./ui";
|
|
4
|
+
import { Icon } from "./ui/Icon";
|
|
5
|
+
import type { Project } from "../shared/projects";
|
|
6
|
+
import type { TemplateItem } from "../shared/templates";
|
|
7
|
+
import type { ThemeSummary } from "../shared/themes";
|
|
8
|
+
|
|
9
|
+
export function CreateDocumentDialog({ open, onOpenChange, project, theme, template, format }: {
|
|
10
|
+
format?: DocumentFormat;
|
|
11
|
+
open: boolean;
|
|
12
|
+
onOpenChange: (open: boolean) => void;
|
|
13
|
+
project?: Project;
|
|
14
|
+
theme?: ThemeSummary;
|
|
15
|
+
template?: TemplateItem;
|
|
16
|
+
}) {
|
|
17
|
+
const [copied, setCopied] = useState(false);
|
|
18
|
+
const [error, setError] = useState("");
|
|
19
|
+
const promptField = useRef<HTMLTextAreaElement>(null);
|
|
20
|
+
const outputFormat = template ? template.descriptor.documentFormat ?? 'document' : format;
|
|
21
|
+
const presentation = outputFormat === 'presentation';
|
|
22
|
+
const prompt = [
|
|
23
|
+
outputFormat ? `Create a ${presentation ? 'presentation' : 'document'} in this OpenDoc workspace.` : 'Create a document or presentation in this OpenDoc workspace, following my brief.',
|
|
24
|
+
'Read this workspace’s AGENTS.md and follow its opendoc-create workflow.',
|
|
25
|
+
project && `Project: ${project.name} (${project.id}).`,
|
|
26
|
+
template && `Use the ${template.descriptor.name} template (${template.id}).`,
|
|
27
|
+
theme && `Use the ${theme.name} theme (${theme.id}).`,
|
|
28
|
+
presentation ? 'Deliver a reviewed PDF and editable PowerPoint.' : outputFormat ? 'Deliver a reviewed PDF.' : 'Deliver a reviewed PDF, plus editable PowerPoint for a presentation.',
|
|
29
|
+
].filter(Boolean).join('\n');
|
|
30
|
+
useEffect(() => { if (open) { setCopied(false); setError(""); } }, [open, prompt]);
|
|
31
|
+
async function copy() {
|
|
32
|
+
try { await navigator.clipboard.writeText(prompt); setCopied(true); setError(""); }
|
|
33
|
+
catch { setError("Select the prompt and copy it manually."); promptField.current?.focus(); promptField.current?.select(); }
|
|
34
|
+
}
|
|
35
|
+
return <Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
36
|
+
<Dialog.Portal>
|
|
37
|
+
<Dialog.Backdrop className="ui-dialog-backdrop" />
|
|
38
|
+
<Dialog.Popup className="help-dialog">
|
|
39
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close" />}><Icon name="close" /></Dialog.Close>
|
|
40
|
+
<Dialog.Title>Create with your agent</Dialog.Title>
|
|
41
|
+
<Dialog.Description>Paste this prompt into your coding agent in the OpenDoc workspace. Add your brief, material, and any design preferences. The agent follows the workspace’s creation workflow.</Dialog.Description>
|
|
42
|
+
{project && <p className="field-hint">Project: {project.name}</p>}
|
|
43
|
+
{theme && <p className="field-hint">Theme: {theme.name}</p>}
|
|
44
|
+
<textarea ref={promptField} className="prompt-example skill-invocation" aria-label={presentation ? "Presentation creation prompt" : outputFormat ? "Document creation prompt" : "Creation prompt"} readOnly rows={Math.max(3, prompt.split('\n').length + 1)} value={prompt} />
|
|
45
|
+
<Button className="primary copy-prompt" onClick={() => void copy()}><Icon name={copied ? "check" : "copy"} size={16} />{copied ? "Copied" : "Copy prompt"}</Button>
|
|
46
|
+
<span className="sr-only" role="status">{copied ? "Prompt copied to clipboard." : ""}</span>
|
|
47
|
+
{error && <p className="comment-error" role="alert">{error}</p>}
|
|
48
|
+
</Dialog.Popup>
|
|
49
|
+
</Dialog.Portal>
|
|
50
|
+
</Dialog.Root>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type FormEvent } from 'react';
|
|
2
|
+
import { Menu } from '@base-ui/react/menu';
|
|
3
|
+
import { documentName, type DocumentSummary } from '../shared/types';
|
|
4
|
+
import { api } from './api';
|
|
5
|
+
import { Button, Dialog, Input } from './ui';
|
|
6
|
+
import { Icon } from './ui/Icon';
|
|
7
|
+
|
|
8
|
+
export type DocumentAction = 'rename' | 'duplicate' | 'move' | 'delete' | 'exports';
|
|
9
|
+
export type DocumentActionHandler = (document: DocumentSummary, action: DocumentAction) => void;
|
|
10
|
+
|
|
11
|
+
export function DocumentMenuItems({ document, disabled, onAction }: { document: DocumentSummary; disabled?: boolean; onAction: DocumentActionHandler }) {
|
|
12
|
+
return <>
|
|
13
|
+
<Menu.Item className="ui-menu-item" disabled={disabled} onClick={() => onAction(document, 'exports')}><Icon name="history" size={16} /><span>Previous exports</span></Menu.Item>
|
|
14
|
+
<Menu.Separator className="ui-menu-separator" />
|
|
15
|
+
<Menu.Item className="ui-menu-item" disabled={disabled} onClick={() => onAction(document, 'rename')}><Icon name="edit" size={16} /><span>Rename</span></Menu.Item>
|
|
16
|
+
<Menu.Item className="ui-menu-item" disabled={disabled || !document.projectId} onClick={() => onAction(document, 'duplicate')}><Icon name="copy" size={16} /><span>Duplicate</span></Menu.Item>
|
|
17
|
+
<Menu.Item className="ui-menu-item" disabled={disabled} onClick={() => onAction(document, 'move')}><Icon name="folder" size={16} /><span>Move to project…</span></Menu.Item>
|
|
18
|
+
<Menu.Separator className="ui-menu-separator" />
|
|
19
|
+
<Menu.Item className="ui-menu-item ui-menu-item-danger" disabled={disabled} onClick={() => onAction(document, 'delete')}><Icon name="trash" size={16} /><span>Delete</span></Menu.Item>
|
|
20
|
+
</>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function DocumentMenu({ document, disabled, onAction }: { document: DocumentSummary; disabled?: boolean; onAction: DocumentActionHandler }) {
|
|
24
|
+
return <Menu.Root><Menu.Trigger render={<Button className="icon-button document-menu-trigger" aria-label={`Options for ${documentName(document)}`} />}><Icon name="more" size={14} /></Menu.Trigger>
|
|
25
|
+
<Menu.Portal><Menu.Positioner sideOffset={5} align="end" className="document-menu-positioner"><Menu.Popup className="ui-menu-popup"><DocumentMenuItems document={document} disabled={disabled} onAction={onAction} /></Menu.Popup></Menu.Positioner></Menu.Portal>
|
|
26
|
+
</Menu.Root>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function DocumentActionDialog({ selection, connected, onClose, onRenamed, onDeleted }: {
|
|
30
|
+
selection: { document: DocumentSummary; action: 'rename' | 'delete' } | null; connected: boolean; onClose: () => void;
|
|
31
|
+
onRenamed: (id: string, name: string) => void; onDeleted: (document: DocumentSummary, restoreId: string) => void;
|
|
32
|
+
}) {
|
|
33
|
+
const [name, setName] = useState('');
|
|
34
|
+
const [error, setError] = useState('');
|
|
35
|
+
const [busy, setBusy] = useState(false);
|
|
36
|
+
const working = useRef(false);
|
|
37
|
+
const input = useRef<HTMLInputElement>(null);
|
|
38
|
+
const cancel = useRef<HTMLButtonElement>(null);
|
|
39
|
+
const deleting = selection?.action === 'delete';
|
|
40
|
+
useEffect(() => { setName(selection ? documentName(selection.document) : ''); setError(''); }, [selection]);
|
|
41
|
+
async function submit(event: FormEvent) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
if (!selection || working.current || !connected || (!deleting && !name.trim())) return;
|
|
44
|
+
working.current = true; setBusy(true); setError('');
|
|
45
|
+
try {
|
|
46
|
+
if (deleting) {
|
|
47
|
+
const result = await api<{ restoreId: string }>(`/api/documents/${selection.document.id}`, { method: 'DELETE' });
|
|
48
|
+
onDeleted(selection.document, result.restoreId);
|
|
49
|
+
} else {
|
|
50
|
+
const result = await api<{ name: string }>(`/api/documents/${selection.document.id}`, { method: 'PATCH', body: JSON.stringify({ name }) });
|
|
51
|
+
onRenamed(selection.document.id, result.name);
|
|
52
|
+
}
|
|
53
|
+
onClose();
|
|
54
|
+
} catch (error) { setError((error as Error).message); }
|
|
55
|
+
finally { working.current = false; setBusy(false); }
|
|
56
|
+
}
|
|
57
|
+
return <Dialog.Root open={Boolean(selection)} onOpenChange={open => { if (!open && !working.current) onClose(); }}>
|
|
58
|
+
<Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog" initialFocus={deleting ? cancel : input}>
|
|
59
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close document dialog" disabled={busy} />}><Icon name="close" /></Dialog.Close>
|
|
60
|
+
<Dialog.Title>{deleting ? 'Delete document?' : 'Rename document'}</Dialog.Title>
|
|
61
|
+
<Dialog.Description>{deleting ? `“${selection ? documentName(selection.document) : ''}” and its saved files will move to local Trash. You can undo this after deleting.` : 'Change the name shown in your library. The title inside the PDF stays as written.'}</Dialog.Description>
|
|
62
|
+
<form onSubmit={event => void submit(event)} aria-busy={busy}>
|
|
63
|
+
{!deleting && <label className="create-field" htmlFor="document-name"><span>Document name</span><Input ref={input} id="document-name" value={name} onChange={event => { setName(event.target.value); setError(''); }} disabled={busy} maxLength={160} required autoComplete="off" /></label>}
|
|
64
|
+
{error && <p className="field-error" role="alert">{error}</p>}
|
|
65
|
+
<div className="dialog-actions"><Dialog.Close render={<Button ref={cancel} disabled={busy} />}>Cancel</Dialog.Close><Button type="submit" className={deleting ? 'document-delete-button' : 'primary'} disabled={busy || !connected || (!deleting && !name.trim())}>{busy ? deleting ? 'Deleting…' : 'Saving…' : deleting ? 'Delete document' : 'Save name'}</Button></div>
|
|
66
|
+
</form>
|
|
67
|
+
</Dialog.Popup></Dialog.Portal>
|
|
68
|
+
</Dialog.Root>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { documentName, documentFormat, type DocumentSummary } from "../shared/types";
|
|
2
|
+
import { DocumentMenu, type DocumentActionHandler } from './DocumentActions';
|
|
3
|
+
import { CoverPreview } from "./Pdf";
|
|
4
|
+
import { Button, IconButton } from "./ui";
|
|
5
|
+
import { Icon } from "./ui/Icon";
|
|
6
|
+
import type { DocumentView } from './DocumentViewControl';
|
|
7
|
+
|
|
8
|
+
// Base UI owns the card interaction; PDF.js renders the actual document cover.
|
|
9
|
+
export function DocumentCard({
|
|
10
|
+
document,
|
|
11
|
+
view = 'gallery',
|
|
12
|
+
onOpen,
|
|
13
|
+
onAction,
|
|
14
|
+
disabled,
|
|
15
|
+
}: {
|
|
16
|
+
document: DocumentSummary;
|
|
17
|
+
view?: DocumentView;
|
|
18
|
+
onOpen: () => void;
|
|
19
|
+
onAction: DocumentActionHandler;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
}) {
|
|
22
|
+
const { id, artifact, status } = document;
|
|
23
|
+
const titleId = `document-title-${id}`;
|
|
24
|
+
const presentation = documentFormat(document) === 'presentation';
|
|
25
|
+
const pageCount = artifact?.pages.length ?? 0;
|
|
26
|
+
const cover = artifact?.pages[0];
|
|
27
|
+
const portrait = cover ? cover.height > cover.width : !presentation;
|
|
28
|
+
const statusId = `document-status-${id}`;
|
|
29
|
+
const statusLabel = status === 'ready'
|
|
30
|
+
? `${pageCount} ${presentation ? 'slide' : 'page'}${pageCount === 1 ? '' : 's'}`
|
|
31
|
+
: status === 'error' ? 'Needs attention' : 'Rendering…';
|
|
32
|
+
return (
|
|
33
|
+
<div className="document-card-wrap"><Button
|
|
34
|
+
static
|
|
35
|
+
className={`document-card ${view === 'list' ? 'document-card-list' : ''}`}
|
|
36
|
+
onClick={onOpen}
|
|
37
|
+
aria-labelledby={titleId}
|
|
38
|
+
aria-describedby={statusId}
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
className={`card-paper${portrait ? ' card-paper-portrait' : ''}`}
|
|
42
|
+
>
|
|
43
|
+
<CoverPreview format={documentFormat(document)} id={id} artifact={artifact} compact={view === 'list'} />
|
|
44
|
+
{view === 'gallery' && <span id={statusId} className="card-preview-status">{statusLabel}</span>}
|
|
45
|
+
</div>
|
|
46
|
+
<h2 id={titleId} title={documentName(document)}>{documentName(document)}</h2>
|
|
47
|
+
{view === 'list' && <span id={statusId} className="card-meta">{statusLabel}</span>}
|
|
48
|
+
</Button><IconButton className="document-history-trigger" label={`Previous exports for ${documentName(document)}`} disabled={disabled} onClick={() => onAction(document, 'exports')}><Icon name="history" size={14} /></IconButton><DocumentMenu document={document} onAction={onAction} disabled={disabled} /></div>
|
|
49
|
+
);
|
|
50
|
+
}
|