@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,31 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
|
3
|
+
import { ToggleButton } from './ui';
|
|
4
|
+
import { Icon } from './ui/Icon';
|
|
5
|
+
|
|
6
|
+
export type DocumentView = 'gallery' | 'list';
|
|
7
|
+
const storageKey = 'opendoc-document-view';
|
|
8
|
+
function readView(): DocumentView {
|
|
9
|
+
try { if (localStorage.getItem(storageKey) === 'list') return 'list'; } catch { /* Keep gallery available without browser storage. */ }
|
|
10
|
+
return 'gallery';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useDocumentView() {
|
|
14
|
+
const [view, setView] = useState<DocumentView>(readView);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const sync = (event: StorageEvent) => { if (event.key === storageKey || event.key === null) setView(readView()); };
|
|
17
|
+
window.addEventListener('storage', sync);
|
|
18
|
+
return () => window.removeEventListener('storage', sync);
|
|
19
|
+
}, []);
|
|
20
|
+
return { view, changeView: (next: DocumentView) => {
|
|
21
|
+
setView(next);
|
|
22
|
+
try { localStorage.setItem(storageKey, next); } catch { /* Keep the selection for this session. */ }
|
|
23
|
+
} };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DocumentViewControl({ value, onChange }: { value: DocumentView; onChange: (value: DocumentView) => void }) {
|
|
27
|
+
return <ToggleGroup className="document-view-toggle" aria-label="Document view" value={[value]} onValueChange={values => { if (values[0]) onChange(values[0]); }}>
|
|
28
|
+
<ToggleButton static label="Gallery view" value="gallery"><Icon name="grid" size={16} /><span>Gallery</span></ToggleButton>
|
|
29
|
+
<ToggleButton static label="List view" value="list"><Icon name="list" size={16} /><span>List</span></ToggleButton>
|
|
30
|
+
</ToggleGroup>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useState, type ReactNode } from 'react';
|
|
2
|
+
import { Menu } from '@base-ui/react/menu';
|
|
3
|
+
import type { Project } from '../shared/projects';
|
|
4
|
+
import { documentName, documentFormat, type DocumentFormat, type DocumentSummary } from '../shared/types';
|
|
5
|
+
import { DocumentCard } from './DocumentCard';
|
|
6
|
+
import type { DocumentActionHandler } from './DocumentActions';
|
|
7
|
+
import { DocumentViewControl, type DocumentView } from './DocumentViewControl';
|
|
8
|
+
import { Button, IconButton, SelectControl } from './ui';
|
|
9
|
+
import { SearchField } from './SearchField';
|
|
10
|
+
import { Icon } from './ui/Icon';
|
|
11
|
+
import './projects.css';
|
|
12
|
+
|
|
13
|
+
type ViewProps = { view: DocumentView; onViewChange: (view: DocumentView) => void };
|
|
14
|
+
|
|
15
|
+
function DocumentTools({ noun = 'document', query, onQueryChange, view, onViewChange, filter }: ViewProps & { noun?: string; query: string; onQueryChange: (query: string) => void; filter?: ReactNode }) {
|
|
16
|
+
return <div className="library-tools document-browser-tools">
|
|
17
|
+
<SearchField label={`Search ${noun}s`} value={query} onValueChange={onQueryChange} />
|
|
18
|
+
{filter}
|
|
19
|
+
<div className="document-view-tools"><DocumentViewControl value={view} onChange={onViewChange} /></div>
|
|
20
|
+
</div>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function DocumentsBrowser({ format = 'document', projects, documents, loaded, view, onViewChange, onCreate, onMove, onAction, disabled }: ViewProps & {
|
|
24
|
+
format?: DocumentFormat; projects: Project[]; documents: DocumentSummary[]; loaded: boolean; onCreate: () => void; onMove: (document: DocumentSummary) => void; onAction: DocumentActionHandler; disabled: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
const [query, setQuery] = useState('');
|
|
27
|
+
const plural = format === 'presentation' ? 'Presentations' : 'Documents';
|
|
28
|
+
const projectById = new Map(projects.map(project => [project.id, project]));
|
|
29
|
+
const visible = documents.filter(document => `${documentName(document)} ${document.artifact?.meta.description ?? ''} ${projectById.get(document.projectId ?? '')?.name ?? ''}`.toLowerCase().includes(query.toLowerCase()));
|
|
30
|
+
return <section className="library-content documents-content">
|
|
31
|
+
<div className="library-heading"><h1>{plural}</h1><Button className="primary" aria-label={`Create ${format}`} onClick={onCreate} disabled={!loaded || disabled}><Icon name="plus" size={17} /><span>Create {format}</span></Button></div>
|
|
32
|
+
<DocumentTools noun={format} query={query} onQueryChange={setQuery} view={view} onViewChange={onViewChange} />
|
|
33
|
+
<div className={view === 'list' ? 'documents-list' : 'document-grid'} role="list">{visible.map(document => {
|
|
34
|
+
const project = projectById.get(document.projectId ?? '');
|
|
35
|
+
return <article className="project-document" key={document.id} role="listitem">
|
|
36
|
+
<DocumentCard document={document} view={view} onAction={onAction} disabled={disabled} onOpen={() => { location.hash = `document/${document.id}`; }} />
|
|
37
|
+
{project ? <a className="document-project-link" href={`#project/${project.id}`}><Icon name="folder" size={15} /><span>{project.name}</span></a>
|
|
38
|
+
: <Button className="text-button document-move" onClick={() => onMove(document)} disabled={!projects.length}>Choose a project</Button>}
|
|
39
|
+
</article>;
|
|
40
|
+
})}</div>
|
|
41
|
+
{!visible.length && <div className="empty-state"><h2>{!loaded ? `Loading ${plural.toLowerCase()}…` : query ? `No matching ${plural.toLowerCase()}` : `Your ${plural.toLowerCase()} start here`}</h2><p>{!loaded ? 'Opening your local workspace.' : query ? 'Try a different title or project name.' : projects.length ? format === 'presentation' ? 'Create a presentation with your agent and review every slide here.' : 'Create a document in a project, or choose a layout from Templates.' : 'Create your first project using the + beside Projects in the sidebar.'}</p></div>}
|
|
42
|
+
</section>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ProjectDocuments({ project, documents, loaded, view, onViewChange, onCreate, onCreatePresentation, onSettings, onAction, disabled }: ViewProps & {
|
|
46
|
+
project: Project; documents: DocumentSummary[]; loaded: boolean; onCreate: () => void; onCreatePresentation: () => void; onSettings: () => void; onAction: DocumentActionHandler; disabled: boolean;
|
|
47
|
+
}) {
|
|
48
|
+
const [query, setQuery] = useState('');
|
|
49
|
+
const [format, setFormat] = useState<'all' | DocumentFormat>('all');
|
|
50
|
+
const hasFilters = Boolean(query) || format !== 'all';
|
|
51
|
+
const visible = documents.filter(document => (format === 'all' || documentFormat(document) === format)
|
|
52
|
+
&& `${documentName(document)} ${document.artifact?.meta.description ?? ''}`.toLowerCase().includes(query.toLowerCase()));
|
|
53
|
+
return <section className="library-content project-documents">
|
|
54
|
+
<div className="library-heading"><h1>{project.name}</h1><div className="project-actions">
|
|
55
|
+
<IconButton label="Project settings" onClick={onSettings}><Icon name="gear" size={18} /></IconButton>
|
|
56
|
+
<Menu.Root><Menu.Trigger render={<Button className="primary project-create-trigger" disabled={!loaded || disabled} />}>
|
|
57
|
+
<Icon name="plus" size={16} /><span>Create</span><Icon name="down" size={14} />
|
|
58
|
+
</Menu.Trigger><Menu.Portal><Menu.Positioner className="ui-positioner" sideOffset={6} align="end"><Menu.Popup className="ui-menu-popup">
|
|
59
|
+
<Menu.Item className="ui-menu-item" onClick={onCreate}><Icon name="document" size={16} />Document</Menu.Item>
|
|
60
|
+
<Menu.Item className="ui-menu-item" onClick={onCreatePresentation}><Icon name="monitor" size={16} />Presentation</Menu.Item>
|
|
61
|
+
</Menu.Popup></Menu.Positioner></Menu.Portal></Menu.Root>
|
|
62
|
+
</div></div>
|
|
63
|
+
<DocumentTools noun="item" query={query} onQueryChange={setQuery} view={view} onViewChange={onViewChange}
|
|
64
|
+
filter={<SelectControl label="Filter by format" value={format} onValueChange={value => { if (value === 'all' || value === 'document' || value === 'presentation') setFormat(value); }} items={[
|
|
65
|
+
{ label: 'All formats', value: 'all' }, { label: 'Documents', value: 'document' }, { label: 'Presentations', value: 'presentation' },
|
|
66
|
+
]} />} />
|
|
67
|
+
<div className={view === 'list' ? 'documents-list' : 'document-grid'} role="list">{visible.map(document => <article className="project-document" key={document.id} role="listitem"><DocumentCard document={document} view={view} onAction={onAction} disabled={disabled} onOpen={() => { location.hash = `document/${document.id}`; }} /></article>)}</div>
|
|
68
|
+
{!visible.length && <div className="empty-state"><h2>{!loaded ? 'Loading project…' : hasFilters ? 'No matching items' : 'Your project is ready'}</h2><p>{!loaded ? 'Opening your local workspace.' : hasFilters ? 'Try another search or choose a different format.' : 'Create a document or presentation with your agent.'}</p>{loaded && (hasFilters ? <Button onClick={() => { setQuery(''); setFormat('all'); }}>Clear filters</Button> : <a href="#templates" className="project-templates-link">Browse templates<Icon name="arrow" size={16} /></a>)}</div>}
|
|
69
|
+
</section>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { documentName, type DocumentSummary } from '../shared/types';
|
|
3
|
+
import { exportInfo, type ExportHistoryEntry } from '../shared/export';
|
|
4
|
+
import { api } from './api';
|
|
5
|
+
import { Button, Dialog, IconButton, useNotifications } from './ui';
|
|
6
|
+
import { Icon } from './ui/Icon';
|
|
7
|
+
import './export-history.css';
|
|
8
|
+
|
|
9
|
+
const date = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' });
|
|
10
|
+
function fileSize(bytes: number) { return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(bytes / (bytes >= 1048576 ? 1048576 : 1024))} ${bytes >= 1048576 ? 'MB' : 'KB'}`; }
|
|
11
|
+
|
|
12
|
+
export function ExportHistoryDialog({ document, connected, onClose }: { document: DocumentSummary | null; connected: boolean; onClose: () => void }) {
|
|
13
|
+
const [entries, setEntries] = useState<ExportHistoryEntry[]>([]);
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
const [canCopy, setCanCopy] = useState(false);
|
|
17
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
18
|
+
const working = useRef(false);
|
|
19
|
+
const request = useRef(0);
|
|
20
|
+
const selected = useRef(document?.id);
|
|
21
|
+
selected.current = document?.id;
|
|
22
|
+
const notifications = useNotifications();
|
|
23
|
+
const refresh = useCallback(async () => {
|
|
24
|
+
if (!document || !connected) return;
|
|
25
|
+
const sequence = ++request.current;
|
|
26
|
+
setLoading(true); setError('');
|
|
27
|
+
try {
|
|
28
|
+
const rows = await api<ExportHistoryEntry[]>(`/api/documents/${document.id}/exports`, { signal: AbortSignal.timeout(12_000) });
|
|
29
|
+
if (sequence === request.current) setEntries(rows);
|
|
30
|
+
} catch (failure) { if (sequence === request.current) setError((failure as Error).message); }
|
|
31
|
+
finally { if (sequence === request.current) setLoading(false); }
|
|
32
|
+
}, [document?.id, connected]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setEntries([]); setError(''); setCanCopy(false);
|
|
35
|
+
void refresh();
|
|
36
|
+
if (document && connected) {
|
|
37
|
+
api<{ copyFile: boolean }>('/api/exports/capabilities', { signal: AbortSignal.timeout(5_000) }).then(value => { if (selected.current === document.id) setCanCopy(value.copyFile); }).catch(() => {});
|
|
38
|
+
}
|
|
39
|
+
const focus = () => { if (!working.current) void refresh(); };
|
|
40
|
+
window.addEventListener('focus', focus);
|
|
41
|
+
return () => { request.current++; window.removeEventListener('focus', focus); };
|
|
42
|
+
}, [refresh]);
|
|
43
|
+
async function act(entry: ExportHistoryEntry, action: 'copy' | 'reveal' | 'delete') {
|
|
44
|
+
if (working.current || !connected) return;
|
|
45
|
+
working.current = true; setBusy(entry.id); setError('');
|
|
46
|
+
try {
|
|
47
|
+
await api(`/api/exports/${entry.id}${action === 'delete' ? '' : `/${action}`}`, { method: action === 'delete' ? 'DELETE' : 'POST', signal: AbortSignal.timeout(12_000) });
|
|
48
|
+
if (action === 'delete') {
|
|
49
|
+
request.current++; setLoading(false);
|
|
50
|
+
setEntries(rows => rows.filter(row => row.id !== entry.id));
|
|
51
|
+
notifications.success(`${entry.filename} removed`, { label: 'Undo', onClick: async () => {
|
|
52
|
+
await api(`/api/exports/${entry.id}/restore`, { method: 'POST', signal: AbortSignal.timeout(12_000) });
|
|
53
|
+
if (selected.current === entry.documentId) await refresh();
|
|
54
|
+
notifications.success('Export restored');
|
|
55
|
+
} });
|
|
56
|
+
} else if (action === 'copy') notifications.success(`${exportInfo(entry.format).label} file copied to clipboard`);
|
|
57
|
+
} catch (failure) { setError((failure as Error).message); }
|
|
58
|
+
finally { working.current = false; setBusy(null); }
|
|
59
|
+
}
|
|
60
|
+
return <Dialog.Root open={!!document} onOpenChange={open => { if (!open && !working.current) onClose(); }}>
|
|
61
|
+
<Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog export-history-dialog">
|
|
62
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close export history" disabled={!!busy} />}><Icon name="close" /></Dialog.Close>
|
|
63
|
+
<div className="export-history-heading"><span className="export-history-symbol"><Icon name="history" size={22} /></span><div><Dialog.Title>Previous exports</Dialog.Title><Dialog.Description>{document ? documentName(document) : ''}</Dialog.Description></div></div>
|
|
64
|
+
<div className="export-history-summary"><span>{loading ? 'Checking saved files…' : `${entries.length} ${entries.length === 1 ? 'export' : 'exports'} · Newest first`}</span><Button className="text-button" disabled={loading || !!busy || !connected} onClick={() => void refresh()}>Refresh</Button></div>
|
|
65
|
+
{!connected && <p className="field-error" role="status">Reconnect to view and manage exports.</p>}
|
|
66
|
+
{error && <p className="field-error" role="alert">{error}</p>}
|
|
67
|
+
<div className="export-history-content" aria-busy={loading || !!busy}>
|
|
68
|
+
{!entries.length && !loading && !error && connected && <div className="export-history-empty"><Icon name="document" size={34} /><strong>No exports yet</strong><p>Export this document and it will appear here.</p></div>}
|
|
69
|
+
{!!entries.length && <ul className="export-history-list">{entries.map(entry => <li key={entry.id} className={`export-history-row${entry.available ? '' : ' unavailable'}`}>
|
|
70
|
+
<span className="export-history-paper" aria-hidden="true"><Icon name="document" size={24} /><span>{(entry.format ?? 'pdf').toUpperCase()}</span></span>
|
|
71
|
+
<div className="export-history-details">
|
|
72
|
+
{entry.available ? <a href={`/api/exports/${entry.id}/open`} target="_blank" rel="noreferrer" title={`Open ${entry.filename}`}>{entry.filename}</a> : <strong>{entry.filename}</strong>}
|
|
73
|
+
<span><time dateTime={entry.createdAt}>{date.format(new Date(entry.createdAt))}</time><span aria-hidden="true"> · </span>{fileSize(entry.bytes)}</span>
|
|
74
|
+
{!entry.available && <span className="export-history-unavailable">File moved, removed, or changed</span>}
|
|
75
|
+
</div>
|
|
76
|
+
<div className="export-history-actions" aria-label={`Actions for ${entry.filename}`}>
|
|
77
|
+
{canCopy && <IconButton label={`Copy ${entry.filename}`} disabled={!!busy || !connected || !entry.available} onClick={() => void act(entry, 'copy')}><Icon name="copy" size={17} /></IconButton>}
|
|
78
|
+
<IconButton label={`${canCopy ? 'Show in Finder' : 'Show in folder'}: ${entry.filename}`} disabled={!!busy || !connected || !entry.available} onClick={() => void act(entry, 'reveal')}><Icon name="folder" size={17} /></IconButton>
|
|
79
|
+
<IconButton label={`Delete export: ${entry.filename}`} className="export-history-delete" disabled={!!busy || !connected} onClick={() => void act(entry, 'delete')}><Icon name="trash" size={17} /></IconButton>
|
|
80
|
+
</div>
|
|
81
|
+
</li>)}</ul>}
|
|
82
|
+
</div>
|
|
83
|
+
<p className="export-history-footnote">Saved files are independent of later document edits. Deleting an export offers Undo.</p>
|
|
84
|
+
</Dialog.Popup></Dialog.Portal>
|
|
85
|
+
</Dialog.Root>;
|
|
86
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Popover } from '@base-ui/react/popover';
|
|
3
|
+
import { documentName, documentFormat, type DocumentState } from '../shared/types';
|
|
4
|
+
import { suggestedExportName, exportInfo, type ExportFormat, type SavedExport } from '../shared/export';
|
|
5
|
+
import { api, ApiError } from './api';
|
|
6
|
+
import { Button, Input, SelectControl } from './ui';
|
|
7
|
+
import { Icon } from './ui/Icon';
|
|
8
|
+
import './export.css';
|
|
9
|
+
|
|
10
|
+
type Attempt = { id: string; hash: string; filename: string; format?: ExportFormat };
|
|
11
|
+
function remembered(id: string): Attempt | null {
|
|
12
|
+
try {
|
|
13
|
+
const value = JSON.parse(sessionStorage.getItem(`opendoc:export:${id}`) ?? 'null');
|
|
14
|
+
return value && typeof value.id === 'string' && typeof value.hash === 'string' && typeof value.filename === 'string' && (value.format === undefined || value.format === 'pdf' || value.format === 'pptx') ? value : null;
|
|
15
|
+
} catch { return null; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ExportMenu({ state, connected, ready, unsaved, saving, correctionError, canSave, onSave, onShowExports }: {
|
|
19
|
+
state: DocumentState; connected: boolean; ready: boolean; unsaved: number; saving: boolean; correctionError: string;
|
|
20
|
+
canSave: boolean; onSave: () => Promise<boolean | undefined>; onShowExports: () => void;
|
|
21
|
+
}) {
|
|
22
|
+
const [open, setOpen] = useState(false);
|
|
23
|
+
const [format, setFormat] = useState<ExportFormat>(() => remembered(state.id)?.format ?? 'pdf');
|
|
24
|
+
const [filename, setFilename] = useState(() => suggestedExportName(documentName(state), format));
|
|
25
|
+
const [attempt, setAttempt] = useState<Attempt | null>(() => remembered(state.id));
|
|
26
|
+
const [result, setResult] = useState<SavedExport | null>(null);
|
|
27
|
+
const [busy, setBusy] = useState(false);
|
|
28
|
+
const working = useRef(false);
|
|
29
|
+
const [error, setError] = useState('');
|
|
30
|
+
const [notice, setNotice] = useState('');
|
|
31
|
+
const [canCopy, setCanCopy] = useState(false);
|
|
32
|
+
const [copying, setCopying] = useState(false);
|
|
33
|
+
const input = useRef<HTMLInputElement>(null);
|
|
34
|
+
const showFile = useRef<HTMLButtonElement>(null);
|
|
35
|
+
const showHistory = useRef(false);
|
|
36
|
+
const activeFormat = result?.format ?? attempt?.format ?? format;
|
|
37
|
+
const formatLabel = exportInfo(activeFormat).label;
|
|
38
|
+
useEffect(() => { if (open && result) showFile.current?.focus(); }, [open, result]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!open || !connected) return;
|
|
41
|
+
let current = true;
|
|
42
|
+
api<{ copyFile: boolean }>('/api/exports/capabilities', { signal: AbortSignal.timeout(5_000) })
|
|
43
|
+
.then(value => { if (current) setCanCopy(value.copyFile); })
|
|
44
|
+
.catch(() => { if (current) setCanCopy(false); });
|
|
45
|
+
return () => { current = false; };
|
|
46
|
+
}, [open, connected]);
|
|
47
|
+
function remember(value: Attempt | null) {
|
|
48
|
+
setAttempt(value);
|
|
49
|
+
try { if (value) sessionStorage.setItem(`opendoc:export:${state.id}`, JSON.stringify(value)); else sessionStorage.removeItem(`opendoc:export:${state.id}`); } catch { /* The open reader retains its request. */ }
|
|
50
|
+
}
|
|
51
|
+
async function perform() {
|
|
52
|
+
if (working.current || !connected || (!attempt && (!ready || unsaved || saving || correctionError))) return;
|
|
53
|
+
working.current = true; setBusy(true); setError(''); setNotice('');
|
|
54
|
+
const request = attempt ?? { id: crypto.randomUUID(), hash: state.artifact!.hash, filename, format };
|
|
55
|
+
remember(request);
|
|
56
|
+
try {
|
|
57
|
+
// Retrying the same receipt never creates a second output, including after a restart.
|
|
58
|
+
const saved = await api<SavedExport>(`/api/documents/${state.id}/exports`, { method: 'POST', body: JSON.stringify(request), signal: AbortSignal.timeout(20_000) });
|
|
59
|
+
setResult(saved); remember(null);
|
|
60
|
+
} catch (failure) {
|
|
61
|
+
if (failure instanceof ApiError && failure.status >= 400 && failure.status < 500) { remember(null); setError(failure.message); }
|
|
62
|
+
else setError('OpenDoc could not confirm whether the file was saved. Check the export to recover it without creating another copy.');
|
|
63
|
+
} finally { working.current = false; setBusy(false); }
|
|
64
|
+
}
|
|
65
|
+
async function reveal() {
|
|
66
|
+
if (!result || working.current) return;
|
|
67
|
+
working.current = true; setBusy(true); setError('');
|
|
68
|
+
try { await api(`/api/exports/${result.id}/reveal`, { method: 'POST', signal: AbortSignal.timeout(12_000) }); }
|
|
69
|
+
catch (failure) { setError((failure as Error).message); }
|
|
70
|
+
finally { working.current = false; setBusy(false); }
|
|
71
|
+
}
|
|
72
|
+
async function saveChanges() {
|
|
73
|
+
if (await onSave()) setOpen(false);
|
|
74
|
+
}
|
|
75
|
+
async function copy() {
|
|
76
|
+
if (!result || working.current || !connected || !canCopy) return;
|
|
77
|
+
working.current = true; setBusy(true); setCopying(true); setError(''); setNotice('');
|
|
78
|
+
try {
|
|
79
|
+
await api(`/api/exports/${result.id}/copy`, { method: 'POST', signal: AbortSignal.timeout(12_000) });
|
|
80
|
+
setNotice(`${formatLabel} file copied. Paste it into Finder or an app that accepts file attachments.`);
|
|
81
|
+
} catch (failure) { setError(failure instanceof ApiError ? failure.message : 'OpenDoc could not confirm the copy. Check your connection and try again.'); }
|
|
82
|
+
finally { working.current = false; setBusy(false); setCopying(false); }
|
|
83
|
+
}
|
|
84
|
+
return <Popover.Root open={open} onOpenChange={next => {
|
|
85
|
+
setOpen(next);
|
|
86
|
+
if (!next && result && !working.current) { setResult(null); setError(''); setNotice(''); setFilename(suggestedExportName(documentName(state), format)); }
|
|
87
|
+
}} onOpenChangeComplete={next => {
|
|
88
|
+
// Let the dropdown restore focus to Export before the history dialog opens.
|
|
89
|
+
if (!next && showHistory.current) { showHistory.current = false; onShowExports(); }
|
|
90
|
+
}}>
|
|
91
|
+
<Popover.Trigger render={<Button className="primary export-button" aria-label={`Export ${documentFormat(state)}`} />}><Icon name="download" size={16} /><span>Export</span><Icon name="down" size={12} /></Popover.Trigger>
|
|
92
|
+
<Popover.Portal><Popover.Positioner className="ui-positioner" align="end" sideOffset={8} collisionPadding={8}>
|
|
93
|
+
<Popover.Popup className="ui-popover-popup export-dropdown" initialFocus={interaction => interaction === 'touch' ? true : result || attempt ? true : input.current}>
|
|
94
|
+
<form onSubmit={event => { event.preventDefault(); if (!result) void perform(); }}>
|
|
95
|
+
<div className="export-dropdown-body">
|
|
96
|
+
<div className="export-dropdown-heading">
|
|
97
|
+
<Popover.Title>{result ? `${formatLabel} saved` : `Export ${formatLabel}`}</Popover.Title>
|
|
98
|
+
<Popover.Close render={<Button className="icon-button" aria-label="Close export menu" />}><Icon name="close" size={16} /></Popover.Close>
|
|
99
|
+
</div>
|
|
100
|
+
<Popover.Description>{result ? 'Saved in your workspace’s output folder.' : activeFormat === 'pptx' ? 'Editable text and shapes · Embedded fonts' : `Original quality · All ${documentFormat(state) === 'presentation' ? 'slides' : 'pages'}`}</Popover.Description>
|
|
101
|
+
{result ? <>
|
|
102
|
+
<div className="export-file" role="status"><Icon name="document" size={24} /><div><strong>{result.filename}</strong><span>{new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(result.bytes / 1024)} KB · {activeFormat.toUpperCase()}</span></div><Icon name="check" size={18} /></div>
|
|
103
|
+
</> : <>
|
|
104
|
+
{documentFormat(state) === 'presentation' && !attempt && <div className="create-field export-format"><span>Format</span><SelectControl label="Export format" value={format} disabled={busy} items={[{ value: 'pdf', label: 'PDF' }, { value: 'pptx', label: 'PowerPoint (.pptx)' }]} onValueChange={value => { const next = value as ExportFormat; setFormat(next); setFilename(name => name.trim() ? name.replace(/\.(pdf|pptx)$/i, '') + exportInfo(next).extension : suggestedExportName(documentName(state), next)); setError(''); }} /></div>}
|
|
105
|
+
<label className="create-field export-filename" htmlFor="export-filename"><span>Filename</span><Input ref={input} id="export-filename" value={attempt?.filename ?? filename} onChange={event => { setFilename(event.target.value); setError(''); }} disabled={busy || !!attempt} maxLength={120} autoComplete="off" /></label>
|
|
106
|
+
{attempt && !busy && <p className="export-status" role="status">An export is awaiting confirmation. Check it to recover the saved file or finish the same request.</p>}
|
|
107
|
+
{!attempt && (unsaved > 0 || correctionError || saving) && <div className="export-status" role="status"><strong>{saving ? 'Saving your changes…' : 'Save your corrections first'}</strong><p>{correctionError || 'Save your changes and review the updated PDF before exporting.'}</p>{unsaved > 0 && <Button disabled={!canSave || saving} onClick={() => void saveChanges()}>Save changes first</Button>}</div>}
|
|
108
|
+
{!attempt && !unsaved && !saving && !correctionError && !ready && <p className="export-status" role="status">{!connected ? 'Reconnect to the local workspace before exporting.' : state.status === 'error' ? 'Fix the document’s render error before exporting.' : 'Waiting for the current PDF preview…'}</p>}
|
|
109
|
+
</>}
|
|
110
|
+
{error && <p className="field-error" role="alert">{error}</p>}
|
|
111
|
+
{notice && <p className="export-notice" role="status">{notice}</p>}
|
|
112
|
+
{busy && <p className="export-notice" role="status">{copying ? 'Copying the file…' : result ? 'Opening the export folder…' : `Saving ${formatLabel}… You can close this menu and return to check it.`}</p>}
|
|
113
|
+
{!result && <Button type="submit" className="primary export-save" disabled={busy || !connected || (!attempt && (!ready || !!unsaved || !!correctionError || saving || !filename.trim()))}>{busy ? 'Saving…' : attempt ? 'Check export' : `Save ${formatLabel}`}</Button>}
|
|
114
|
+
</div>
|
|
115
|
+
</form>
|
|
116
|
+
{result && <div className="ui-menu-group">
|
|
117
|
+
<Button ref={showFile} className="ui-menu-item" onClick={() => void reveal()} disabled={busy || !connected}><Icon name="folder" size={16} />Show in folder</Button>
|
|
118
|
+
<a className="ui-menu-item" href={`/api/exports/${result.id}/download`} download={result.filename} onClick={() => setNotice('Download requested. Your browser handles where the copy is saved.')}><Icon name="download" size={16} />Download a copy</a>
|
|
119
|
+
{canCopy && <Button className="ui-menu-item" onClick={() => void copy()} disabled={busy || !connected} focusableWhenDisabled={copying}><Icon name="copy" size={16} />Copy to clipboard</Button>}
|
|
120
|
+
{activeFormat === 'pdf' && <a className="ui-menu-item" href={`/api/exports/${result.id}/open`} target="_blank" rel="noreferrer"><Icon name="arrow" size={16} />Open PDF to print</a>}
|
|
121
|
+
<Button className="ui-menu-item" disabled={busy} onClick={() => { setResult(null); setError(''); setNotice(''); setFilename(suggestedExportName(documentName(state), format)); }}><Icon name="plus" size={16} />Export another copy</Button>
|
|
122
|
+
</div>}
|
|
123
|
+
<div className="ui-menu-separator" />
|
|
124
|
+
<Button className="ui-menu-item" onClick={() => { showHistory.current = true; setOpen(false); }}><Icon name="history" size={16} />Previous exports</Button>
|
|
125
|
+
</Popover.Popup></Popover.Positioner></Popover.Portal>
|
|
126
|
+
</Popover.Root>;
|
|
127
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { api } from './api';
|
|
3
|
+
import { Button, Dialog } from './ui';
|
|
4
|
+
import { Icon } from './ui/Icon';
|
|
5
|
+
import type { GuideLocation } from './guideLinks';
|
|
6
|
+
import './guides.css';
|
|
7
|
+
|
|
8
|
+
let markdownModule: Promise<typeof import('./Markdown')> | undefined;
|
|
9
|
+
function loadMarkdown() {
|
|
10
|
+
markdownModule ??= import('./Markdown').catch(error => { markdownModule = undefined; throw error; });
|
|
11
|
+
return markdownModule;
|
|
12
|
+
}
|
|
13
|
+
type GuideVisit = GuideLocation & { scrollTop?: number };
|
|
14
|
+
|
|
15
|
+
export function GuideDialog({ kind, id, name, generation }: { kind: 'template' | 'theme'; id: string; name: string; generation: number | string }) {
|
|
16
|
+
const template = kind === 'template';
|
|
17
|
+
const initialFile = template ? `templates/${id}/AGENTS.md` : `themes/${id}/design.md`;
|
|
18
|
+
const title = template ? 'AGENTS.md' : 'Design guide';
|
|
19
|
+
const body = useRef<HTMLDivElement>(null);
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const [visits, setVisits] = useState<GuideVisit[]>([{ file: initialFile }]);
|
|
22
|
+
const visit = visits.at(-1)!;
|
|
23
|
+
const [content, setContent] = useState<{ key: string; markdown: string; file: string; Renderer: typeof import('./Markdown')['Markdown'] }>();
|
|
24
|
+
const [error, setError] = useState('');
|
|
25
|
+
const [attempt, setAttempt] = useState(0);
|
|
26
|
+
const key = JSON.stringify([visit.file, generation, attempt]);
|
|
27
|
+
const guide = content?.key === key ? content : undefined;
|
|
28
|
+
const Markdown = guide?.Renderer;
|
|
29
|
+
const initial = visit.file === initialFile;
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!open) return;
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
setError(''); setContent(undefined);
|
|
35
|
+
void api<{ markdown: string; file: string }>(`/api/guides?path=${encodeURIComponent(visit.file)}`, { signal: controller.signal })
|
|
36
|
+
.then(async result => {
|
|
37
|
+
if (controller.signal.aborted) return;
|
|
38
|
+
const { Markdown } = await loadMarkdown();
|
|
39
|
+
if (!controller.signal.aborted) setContent({ key, ...result, Renderer: Markdown });
|
|
40
|
+
})
|
|
41
|
+
.catch(error => { if (!controller.signal.aborted) { setError(error.message); body.current?.focus({ preventScroll: true }); } });
|
|
42
|
+
return () => controller.abort();
|
|
43
|
+
}, [open, visit.file, key]);
|
|
44
|
+
|
|
45
|
+
const restorePosition = useCallback(() => {
|
|
46
|
+
const container = body.current;
|
|
47
|
+
if (!container) return;
|
|
48
|
+
const heading = visit.fragment ? container.querySelector<HTMLElement>(`[data-guide-heading="${CSS.escape(visit.fragment)}"]`) : null;
|
|
49
|
+
if (visit.scrollTop !== undefined) container.scrollTop = visit.scrollTop;
|
|
50
|
+
else if (heading) heading.scrollIntoView({ block: 'start' });
|
|
51
|
+
else container.scrollTop = 0;
|
|
52
|
+
container.focus({ preventScroll: true });
|
|
53
|
+
}, [visit.file, visit.fragment, visit.scrollTop, visits.length]);
|
|
54
|
+
|
|
55
|
+
function navigate(next: GuideLocation) {
|
|
56
|
+
setError('');
|
|
57
|
+
const scrollTop = body.current?.scrollTop ?? 0;
|
|
58
|
+
setVisits(previous => [...previous.slice(0, -1), { ...previous.at(-1)!, scrollTop }, next]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return <Dialog.Root open={open} onOpenChange={next => { if (next) { setVisits([{ file: initialFile }]); setError(''); } setOpen(next); }}>
|
|
62
|
+
<Dialog.Trigger render={<Button className={`text-button${template ? ' template-guide-trigger' : ''}`} />}>{template ? 'View AGENTS.md' : <>Design guide<Icon name="arrow" size={16} /></>}</Dialog.Trigger>
|
|
63
|
+
<Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" />
|
|
64
|
+
<Dialog.Popup className="help-dialog template-guide-dialog">
|
|
65
|
+
<header className="template-guide-heading">
|
|
66
|
+
<Dialog.Title>{initial ? title : visit.file.split('/').at(-1)}</Dialog.Title>
|
|
67
|
+
<Dialog.Description>{initial ? name : visit.file}</Dialog.Description>
|
|
68
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label={template ? 'Close template guide' : 'Close design guide'} />}><Icon name="close" /></Dialog.Close>
|
|
69
|
+
{visits.length > 1 && <Button className="text-button guide-back" onClick={() => { setError(''); setVisits(previous => previous.length > 1 ? previous.slice(0, -1) : previous); }}><Icon name="left" size={15} />Back to {visits.at(-2)!.file === initialFile ? title : visits.at(-2)!.file.split('/').at(-1)}</Button>}
|
|
70
|
+
</header>
|
|
71
|
+
<div ref={body} className="template-guide-body" tabIndex={0} role="region" aria-label={template ? 'Template guide content' : `${name} design guide`} aria-busy={!guide && !error}>
|
|
72
|
+
{error ? <div role="alert"><p>{initial ? template ? 'Could not load this template’s AGENTS.md.' : 'Could not load this theme’s design guide.' : 'Could not load this guide.'}</p><p>{error}</p><Button onClick={() => { body.current?.focus(); setAttempt(value => value + 1); }}>Try again</Button></div>
|
|
73
|
+
: !guide ? <p role="status">Loading guide…</p>
|
|
74
|
+
: guide.markdown.trim() && Markdown ? <Markdown file={guide.file} onNavigate={navigate} onReady={restorePosition}>{guide.markdown}</Markdown>
|
|
75
|
+
: <p>{initial ? template ? 'This template’s AGENTS.md is empty.' : 'This theme’s design guide is empty.' : 'This guide is empty.'}</p>}
|
|
76
|
+
</div>
|
|
77
|
+
</Dialog.Popup>
|
|
78
|
+
</Dialog.Portal>
|
|
79
|
+
</Dialog.Root>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState, type PointerEvent, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type HoverTarget = { left: number; top: number; width: number; height: number; label: string };
|
|
4
|
+
|
|
5
|
+
// Keep one full-size rectangle alive. Enlarging one-pixel strips magnifies
|
|
6
|
+
// raster rounding at fractional display scales and separates the frame edges.
|
|
7
|
+
export function InspectionLayer({ children }: { children: ReactNode }) {
|
|
8
|
+
const [target, setTarget] = useState<HoverTarget | null>(null);
|
|
9
|
+
const [visible, setVisible] = useState(false);
|
|
10
|
+
const [morph, setMorph] = useState(false);
|
|
11
|
+
const hovered = useRef<HTMLElement | null>(null);
|
|
12
|
+
const hiddenAt = useRef(-Infinity);
|
|
13
|
+
const box = useRef<HTMLDivElement>(null);
|
|
14
|
+
|
|
15
|
+
useLayoutEffect(() => {
|
|
16
|
+
const element = box.current;
|
|
17
|
+
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
18
|
+
const cancel = () => element?.getAnimations().forEach(animation => animation.cancel());
|
|
19
|
+
const onChange = () => { if (reduced.matches) cancel(); };
|
|
20
|
+
reduced.addEventListener('change', onChange);
|
|
21
|
+
return () => { reduced.removeEventListener('change', onChange); cancel(); };
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
const element = box.current;
|
|
26
|
+
if (!element || !target) return;
|
|
27
|
+
// Read the in-flight visual bounds before retargeting, then animate back
|
|
28
|
+
// from those bounds to the new geometry using a single transform.
|
|
29
|
+
const previous = element.getBoundingClientRect();
|
|
30
|
+
element.getAnimations().forEach(animation => animation.cancel());
|
|
31
|
+
Object.assign(element.style, {
|
|
32
|
+
left: `${target.left}px`, top: `${target.top}px`,
|
|
33
|
+
width: `${target.width}px`, height: `${target.height}px`,
|
|
34
|
+
});
|
|
35
|
+
const next = element.getBoundingClientRect();
|
|
36
|
+
if (!morph || !previous.width || !previous.height || !next.width || !next.height ||
|
|
37
|
+
!window.matchMedia('(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)').matches) return;
|
|
38
|
+
element.animate([
|
|
39
|
+
{ transform: `translate(${(previous.left - next.left) * target.width / next.width}px, ${(previous.top - next.top) * target.height / next.height}px) scale(${previous.width / next.width}, ${previous.height / next.height})` },
|
|
40
|
+
{ transform: 'none' },
|
|
41
|
+
], { duration: 180, easing: 'cubic-bezier(0.23, 1, 0.32, 1)' });
|
|
42
|
+
}, [target, morph]);
|
|
43
|
+
|
|
44
|
+
function hide() {
|
|
45
|
+
if (!hovered.current) return;
|
|
46
|
+
hovered.current = null;
|
|
47
|
+
hiddenAt.current = performance.now();
|
|
48
|
+
setVisible(false);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hover(event: PointerEvent<HTMLDivElement>) {
|
|
52
|
+
if (event.pointerType !== 'mouse' || !window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;
|
|
53
|
+
const element = event.target instanceof Element ? event.target.closest<HTMLElement>('.component-target') : null;
|
|
54
|
+
if (!element || element.classList.contains('selected')) { hide(); return; }
|
|
55
|
+
if (hovered.current === element) return;
|
|
56
|
+
setMorph(!!hovered.current || performance.now() - hiddenAt.current < 150);
|
|
57
|
+
hovered.current = element;
|
|
58
|
+
setTarget({
|
|
59
|
+
left: parseFloat(element.style.left), top: parseFloat(element.style.top),
|
|
60
|
+
width: parseFloat(element.style.width), height: parseFloat(element.style.height),
|
|
61
|
+
label: element.querySelector('.block-label')?.textContent ?? 'Component',
|
|
62
|
+
});
|
|
63
|
+
setVisible(true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { left: x, top: y } = target ?? { left: 0, top: 0 };
|
|
67
|
+
return <div className="inspection-layer" onPointerOver={hover} onPointerLeave={hide}
|
|
68
|
+
onPointerOut={event => {
|
|
69
|
+
if (!(event.relatedTarget instanceof Element) || !event.relatedTarget.closest('.component-target')) hide();
|
|
70
|
+
}} onPointerDownCapture={hide} onKeyDownCapture={hide}>
|
|
71
|
+
{children}
|
|
72
|
+
<div className="inspection-hover-frame" aria-hidden="true" data-visible={visible} data-morph={morph}>
|
|
73
|
+
<div ref={box} className="inspection-hover-box" />
|
|
74
|
+
<span className="block-label" style={{ transform: `translate(${x}px, ${y}px)` }}>{target?.label}</span>
|
|
75
|
+
</div>
|
|
76
|
+
</div>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type FormEvent } from 'react';
|
|
2
|
+
import type { AssetInspection } from '../shared/assets';
|
|
3
|
+
import { api } from './api';
|
|
4
|
+
import { Button, Dialog, Input } from './ui';
|
|
5
|
+
import { Icon } from './ui/Icon';
|
|
6
|
+
|
|
7
|
+
function UploadPreview({ file }: { file: File }) {
|
|
8
|
+
const [url, setUrl] = useState('');
|
|
9
|
+
useEffect(() => { const next = URL.createObjectURL(file); setUrl(next); return () => URL.revokeObjectURL(next); }, [file]);
|
|
10
|
+
return <span className="logo-upload-preview asset-background-transparent">{url && <img src={url} alt="" />}</span>;
|
|
11
|
+
}
|
|
12
|
+
const accept = '.svg,.png,image/svg+xml,image/png';
|
|
13
|
+
|
|
14
|
+
/** Creation asks only for the folder name and its default artwork. */
|
|
15
|
+
export function CreateLogoDialog({ connected, onClose, onSaved }: { connected: boolean; onClose: () => void; onSaved: (value: AssetInspection) => void }) {
|
|
16
|
+
const [name, setName] = useState('');
|
|
17
|
+
const [step, setStep] = useState<'name' | 'default'>('name');
|
|
18
|
+
const [file, setFile] = useState<File>();
|
|
19
|
+
const [error, setError] = useState('');
|
|
20
|
+
const [busy, setBusy] = useState(false);
|
|
21
|
+
const working = useRef(false);
|
|
22
|
+
const nameInput = useRef<HTMLInputElement>(null);
|
|
23
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
24
|
+
useEffect(() => { if (step === 'default') { if (file && fileInput.current) { const selection = new DataTransfer(); selection.items.add(file); fileInput.current.files = selection.files; } fileInput.current?.focus(); } else nameInput.current?.focus(); }, [step, file]);
|
|
25
|
+
async function submit(event: FormEvent) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
if (working.current || !connected || !name.trim()) return;
|
|
28
|
+
if (step === 'name') { setStep('default'); return; }
|
|
29
|
+
if (!file) return;
|
|
30
|
+
working.current = true; setBusy(true); setError('');
|
|
31
|
+
try {
|
|
32
|
+
const form = new FormData(); form.set('metadata', JSON.stringify({ name: name.trim() })); form.set('file', file);
|
|
33
|
+
const result = await api<AssetInspection>('/api/assets/logo', { method: 'POST', body: form });
|
|
34
|
+
onSaved(result); onClose();
|
|
35
|
+
} catch (failure) { setError((failure as Error).message); }
|
|
36
|
+
finally { working.current = false; setBusy(false); }
|
|
37
|
+
}
|
|
38
|
+
return <Dialog.Root open onOpenChange={open => { if (!open && !working.current) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog asset-dialog logo-create-dialog" initialFocus={nameInput}>
|
|
39
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close add logo" disabled={busy} />}><Icon name="close" /></Dialog.Close>
|
|
40
|
+
<Dialog.Title>{step === 'name' ? 'Add logo' : 'Upload default logo'}</Dialog.Title>
|
|
41
|
+
<Dialog.Description>{step === 'name' ? 'Give this logo a name. Its variations will live together inside.' : `${name.trim()} · You can add more variations after this.`}</Dialog.Description>
|
|
42
|
+
<form onSubmit={event => void submit(event)} aria-busy={busy}>
|
|
43
|
+
{step === 'name' ? <label className="create-field"><span>Name</span><Input ref={nameInput} value={name} onChange={event => setName(event.target.value)} maxLength={120} required autoComplete="off" placeholder="For example, Northstar" /></label> : <label className="create-field asset-upload logo-default-upload"><span>Default logo</span>{file && <UploadPreview file={file} />}<input ref={fileInput} type="file" accept={accept} required={!file} disabled={busy} onChange={event => { setFile(event.target.files?.[0]); setError(''); }} /><span className="field-hint">SVG or PNG</span></label>}
|
|
44
|
+
{error && <p className="field-error" role="alert">{error}</p>}
|
|
45
|
+
<div className="dialog-actions"><Button disabled={busy} onClick={() => { if (step === 'name') onClose(); else { setStep('name'); setError(''); } }}>{step === 'name' ? 'Cancel' : 'Back'}</Button><Button type="submit" className="primary" disabled={busy || !connected || !name.trim() || (step === 'default' && !file)}>{busy ? 'Uploading…' : step === 'name' ? 'Next' : 'Create logo'}</Button></div>
|
|
46
|
+
</form>
|
|
47
|
+
</Dialog.Popup></Dialog.Portal></Dialog.Root>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type PendingVariation = { id: number; file: File; name: string };
|
|
51
|
+
export function AddLogoVariationsDialog({ inspection, connected, onClose, onSaved }: { inspection: AssetInspection; connected: boolean; onClose: () => void; onSaved: (value: AssetInspection) => void }) {
|
|
52
|
+
const [pending, setPending] = useState<PendingVariation[]>([]);
|
|
53
|
+
const [error, setError] = useState('');
|
|
54
|
+
const [busy, setBusy] = useState(false);
|
|
55
|
+
const [progress, setProgress] = useState('');
|
|
56
|
+
const current = useRef(inspection);
|
|
57
|
+
const counter = useRef(0);
|
|
58
|
+
const working = useRef(false);
|
|
59
|
+
const input = useRef<HTMLInputElement>(null);
|
|
60
|
+
const count = current.current.asset.kind === 'logo' ? current.current.asset.variations.length : 0;
|
|
61
|
+
async function submit(event: FormEvent) {
|
|
62
|
+
event.preventDefault(); if (!connected || working.current || !pending.length || pending.some(item => !item.name.trim())) return;
|
|
63
|
+
working.current = true; setBusy(true); setError('');
|
|
64
|
+
let saved = 0;
|
|
65
|
+
try {
|
|
66
|
+
for (const item of pending) {
|
|
67
|
+
setProgress(`Uploading ${saved + 1} of ${pending.length}…`);
|
|
68
|
+
const form = new FormData(); form.set('metadata', JSON.stringify({ expectedRevision: current.current.head.revision, name: item.name.trim() })); form.set('file', item.file);
|
|
69
|
+
current.current = await api<AssetInspection>(`/api/assets/logo/${encodeURIComponent(inspection.asset.id)}/variations`, { method: 'POST', body: form });
|
|
70
|
+
saved++; setPending(list => list.filter(value => value.id !== item.id));
|
|
71
|
+
}
|
|
72
|
+
onSaved(current.current); onClose();
|
|
73
|
+
} catch (failure) {
|
|
74
|
+
if (saved) onSaved(current.current);
|
|
75
|
+
setError(`${saved ? `${saved} ${saved === 1 ? 'variation was' : 'variations were'} added. The remaining files are still here. ` : ''}${(failure as Error).message}`);
|
|
76
|
+
} finally { working.current = false; setBusy(false); setProgress(''); }
|
|
77
|
+
}
|
|
78
|
+
return <Dialog.Root open onOpenChange={open => { if (!open && !working.current) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog asset-dialog" initialFocus={input}>
|
|
79
|
+
<Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close add variations" disabled={busy} />}><Icon name="close" /></Dialog.Close>
|
|
80
|
+
<Dialog.Title>Add variations</Dialog.Title><Dialog.Description>{inspection.asset.name} · Select one or more SVG or PNG files.</Dialog.Description>
|
|
81
|
+
<form onSubmit={event => void submit(event)} aria-busy={busy}>
|
|
82
|
+
<label className="create-field asset-upload"><span>Upload variations</span><input ref={input} type="file" accept={accept} multiple disabled={busy} onChange={event => {
|
|
83
|
+
const files = Array.from(event.target.files ?? []); setError('');
|
|
84
|
+
if (count + pending.length + files.length > 64) { setError('A logo can hold up to 64 variations. Choose fewer files.'); return; }
|
|
85
|
+
setPending(list => [...list, ...files.map(file => ({ id: counter.current++, file, name: file.name.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').slice(0, 120) || 'Variation' }))]); event.target.value = '';
|
|
86
|
+
}} /></label>
|
|
87
|
+
{pending.length > 0 && <div className="logo-upload-list">{pending.map(item => <div className="logo-upload-row" key={item.id}><UploadPreview file={item.file} /><label className="create-field"><span>Variation name</span><Input value={item.name} required maxLength={120} disabled={busy} onChange={event => setPending(list => list.map(value => value.id === item.id ? { ...value, name: event.target.value } : value))} /><small>{item.file.name}</small></label><Button className="icon-button" aria-label={`Remove ${item.file.name} from upload`} disabled={busy} onClick={() => { setPending(list => list.filter(value => value.id !== item.id)); setError(''); }}><Icon name="close" size={16} /></Button></div>)}</div>}
|
|
88
|
+
{error && <p className="field-error" role="alert">{error}</p>}
|
|
89
|
+
<div className="dialog-actions"><Button onClick={onClose} disabled={busy}>Cancel</Button><Button type="submit" className="primary" disabled={busy || !connected || !pending.length || pending.some(item => !item.name.trim())}>{busy ? progress : pending.length ? `Add ${pending.length} ${pending.length === 1 ? 'variation' : 'variations'}` : 'Add variations'}</Button></div>
|
|
90
|
+
</form>
|
|
91
|
+
</Dialog.Popup></Dialog.Portal></Dialog.Root>;
|
|
92
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Children, isValidElement, useEffect, type ReactNode } from 'react';
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
import { guideHeadingSlug, resolveGuideLink, type GuideLocation } from './guideLinks';
|
|
5
|
+
|
|
6
|
+
function plainText(children: ReactNode): string {
|
|
7
|
+
return Children.toArray(children).map(child => typeof child === 'string' || typeof child === 'number' ? String(child) : isValidElement<{ children?: ReactNode }>(child) ? plainText(child.props.children) : '').join('');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Markdown({ children, file, onNavigate, onReady }: { children: string; file: string; onNavigate: (location: GuideLocation) => void; onReady?: () => void }) {
|
|
11
|
+
useEffect(() => { onReady?.(); }, [children, file, onReady]);
|
|
12
|
+
const headings = new Set<string>();
|
|
13
|
+
function heading(level: 1 | 2 | 3 | 4 | 5 | 6, children: ReactNode) {
|
|
14
|
+
const slug = guideHeadingSlug(plainText(children));
|
|
15
|
+
let unique = slug;
|
|
16
|
+
for (let count = 1; headings.has(unique); count += 1) unique = `${slug}-${count}`;
|
|
17
|
+
headings.add(unique);
|
|
18
|
+
const Tag = `h${level}` as const;
|
|
19
|
+
return <Tag id={`guide-${unique}`} data-guide-heading={unique}>{children}</Tag>;
|
|
20
|
+
}
|
|
21
|
+
return <div className="markdown-body">
|
|
22
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]} skipHtml components={{
|
|
23
|
+
a: ({ node: _node, href, ...props }) => {
|
|
24
|
+
const link = href && resolveGuideLink(file, href);
|
|
25
|
+
return !link ? <span title={href}>{props.children}</span> : link.kind === 'external'
|
|
26
|
+
? <a {...props} href={link.href} target="_blank" rel="noopener noreferrer" />
|
|
27
|
+
: <a {...props} href={`/api/guides?path=${encodeURIComponent(link.location.file)}${link.location.fragment ? `#${encodeURIComponent(link.location.fragment)}` : ''}`} onClick={event => { event.preventDefault(); onNavigate(link.location); }} />;
|
|
28
|
+
},
|
|
29
|
+
h1: ({ children }) => heading(1, children), h2: ({ children }) => heading(2, children), h3: ({ children }) => heading(3, children),
|
|
30
|
+
h4: ({ children }) => heading(4, children), h5: ({ children }) => heading(5, children), h6: ({ children }) => heading(6, children),
|
|
31
|
+
table: ({ node: _node, ...props }) => <div className="markdown-table"><table {...props} /></div>,
|
|
32
|
+
}}>{children}</ReactMarkdown>
|
|
33
|
+
</div>;
|
|
34
|
+
}
|