@overcrow/mcp 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +90 -2
- package/dist/audit/index.js +103 -0
- package/dist/audit/lexer.js +417 -0
- package/dist/audit/project.js +200 -0
- package/dist/audit/rules.js +661 -0
- package/dist/bootstrap/download.js +229 -0
- package/dist/bootstrap/install.js +280 -0
- package/dist/bootstrap/pin.js +85 -0
- package/dist/bootstrap/pin.json +119 -0
- package/dist/bootstrap/zip.js +258 -0
- package/dist/cli.js +200 -0
- package/dist/confine.js +244 -0
- package/dist/content/docs/en/cli.md +353 -0
- package/dist/content/docs/en/dev-channel.md +130 -0
- package/dist/content/docs/en/elements.md +469 -0
- package/dist/content/docs/en/forms.md +328 -0
- package/dist/content/docs/en/guide.md +253 -0
- package/dist/content/docs/en/index.md +85 -0
- package/dist/content/docs/en/limits.md +241 -0
- package/dist/content/docs/en/logic.md +446 -0
- package/dist/content/docs/en/manifest.md +381 -0
- package/dist/content/docs/en/mcp.md +165 -0
- package/dist/content/docs/en/package.md +131 -0
- package/dist/content/docs/en/publishing.md +162 -0
- package/dist/content/docs/en/reference.md +55 -0
- package/dist/content/docs/en/result-shapes.md +332 -0
- package/dist/content/docs/en/sdk.md +295 -0
- package/dist/content/docs/en/security.md +115 -0
- package/dist/content/docs/en/service-reference.md +344 -0
- package/dist/content/docs/en/services.md +517 -0
- package/dist/content/docs/en/style-properties.md +270 -0
- package/dist/content/docs/en/style.md +266 -0
- package/dist/content/docs/en/testing.md +400 -0
- package/dist/content/docs/en/view.md +338 -0
- package/dist/content/docs/en/widgets.md +76 -0
- package/dist/content/docs/fr/cli.md +367 -0
- package/dist/content/docs/fr/dev-channel.md +140 -0
- package/dist/content/docs/fr/elements.md +475 -0
- package/dist/content/docs/fr/forms.md +341 -0
- package/dist/content/docs/fr/guide.md +264 -0
- package/dist/content/docs/fr/index.md +90 -0
- package/dist/content/docs/fr/limits.md +244 -0
- package/dist/content/docs/fr/logic.md +461 -0
- package/dist/content/docs/fr/manifest.md +392 -0
- package/dist/content/docs/fr/mcp.md +173 -0
- package/dist/content/docs/fr/package.md +139 -0
- package/dist/content/docs/fr/publishing.md +172 -0
- package/dist/content/docs/fr/reference.md +59 -0
- package/dist/content/docs/fr/result-shapes.md +333 -0
- package/dist/content/docs/fr/sdk.md +301 -0
- package/dist/content/docs/fr/security.md +129 -0
- package/dist/content/docs/fr/service-reference.md +346 -0
- package/dist/content/docs/fr/services.md +540 -0
- package/dist/content/docs/fr/style-properties.md +272 -0
- package/dist/content/docs/fr/style.md +281 -0
- package/dist/content/docs/fr/testing.md +417 -0
- package/dist/content/docs/fr/view.md +352 -0
- package/dist/content/docs/fr/widgets.md +81 -0
- package/dist/content/examples/clock/LICENSE +21 -0
- package/dist/content/examples/clock/README.md +75 -0
- package/dist/content/examples/clock/listing.json +19 -0
- package/dist/content/examples/clock/locales/en.json +4 -0
- package/dist/content/examples/clock/locales/fr.json +4 -0
- package/dist/content/examples/clock/logic.ts +85 -0
- package/dist/content/examples/clock/manifest.json +44 -0
- package/dist/content/examples/clock/package.json +16 -0
- package/dist/content/examples/clock/style.ocss +26 -0
- package/dist/content/examples/clock/tests/hidden.scenario.json +68 -0
- package/dist/content/examples/clock/tests/load-logic.mjs +22 -0
- package/dist/content/examples/clock/tests/logic.test.mjs +89 -0
- package/dist/content/examples/clock/tests/options.scenario.json +108 -0
- package/dist/content/examples/clock/tests/preview.scenario.json +25 -0
- package/dist/content/examples/clock/tests/render.scenario.json +94 -0
- package/dist/content/examples/clock/tests/summer-time.scenario.json +74 -0
- package/dist/content/examples/clock/tests/ticks.scenario.json +120 -0
- package/dist/content/examples/clock/tsconfig.json +16 -0
- package/dist/content/examples/clock/view.ocml +8 -0
- package/dist/content/examples/countdown/LICENSE +20 -0
- package/dist/content/examples/countdown/listing.json +10 -0
- package/dist/content/examples/countdown/locales/en.json +15 -0
- package/dist/content/examples/countdown/locales/fr.json +15 -0
- package/dist/content/examples/countdown/logic.ts +179 -0
- package/dist/content/examples/countdown/manifest.json +34 -0
- package/dist/content/examples/countdown/package.json +15 -0
- package/dist/content/examples/countdown/style.ocss +94 -0
- package/dist/content/examples/countdown/tsconfig.json +16 -0
- package/dist/content/examples/countdown/view.ocml +42 -0
- package/dist/content/examples/fps/LICENSE +21 -0
- package/dist/content/examples/fps/README.md +64 -0
- package/dist/content/examples/fps/listing.json +19 -0
- package/dist/content/examples/fps/locales/en.json +11 -0
- package/dist/content/examples/fps/locales/fr.json +11 -0
- package/dist/content/examples/fps/logic.ts +64 -0
- package/dist/content/examples/fps/manifest.json +30 -0
- package/dist/content/examples/fps/package.json +16 -0
- package/dist/content/examples/fps/style.ocss +31 -0
- package/dist/content/examples/fps/tests/label.scenario.json +63 -0
- package/dist/content/examples/fps/tests/load-logic.mjs +22 -0
- package/dist/content/examples/fps/tests/logic.test.mjs +60 -0
- package/dist/content/examples/fps/tests/preview.scenario.json +29 -0
- package/dist/content/examples/fps/tests/refused.scenario.json +20 -0
- package/dist/content/examples/fps/tests/render.scenario.json +96 -0
- package/dist/content/examples/fps/tests/stale.scenario.json +91 -0
- package/dist/content/examples/fps/tests/unavailable.scenario.json +111 -0
- package/dist/content/examples/fps/tsconfig.json +16 -0
- package/dist/content/examples/fps/view.ocml +11 -0
- package/dist/content/examples/media/LICENSE +21 -0
- package/dist/content/examples/media/README.md +147 -0
- package/dist/content/examples/media/listing.json +19 -0
- package/dist/content/examples/media/locales/en.json +11 -0
- package/dist/content/examples/media/locales/fr.json +11 -0
- package/dist/content/examples/media/logic.ts +191 -0
- package/dist/content/examples/media/manifest.json +35 -0
- package/dist/content/examples/media/package.json +16 -0
- package/dist/content/examples/media/style.ocss +148 -0
- package/dist/content/examples/media/tests/covers.scenario.json +144 -0
- package/dist/content/examples/media/tests/gesture.scenario.json +173 -0
- package/dist/content/examples/media/tests/load-logic.mjs +22 -0
- package/dist/content/examples/media/tests/logic.test.mjs +176 -0
- package/dist/content/examples/media/tests/marquee.scenario.json +127 -0
- package/dist/content/examples/media/tests/preview.scenario.json +52 -0
- package/dist/content/examples/media/tests/read-only.scenario.json +53 -0
- package/dist/content/examples/media/tests/refused.scenario.json +22 -0
- package/dist/content/examples/media/tests/refused.test.mjs +31 -0
- package/dist/content/examples/media/tests/render.scenario.json +136 -0
- package/dist/content/examples/media/tests/states.scenario.json +257 -0
- package/dist/content/examples/media/tests/unavailable.scenario.json +120 -0
- package/dist/content/examples/media/tsconfig.json +16 -0
- package/dist/content/examples/media/view.ocml +41 -0
- package/dist/content/examples/notes/LICENSE +21 -0
- package/dist/content/examples/notes/README.md +219 -0
- package/dist/content/examples/notes/listing.json +19 -0
- package/dist/content/examples/notes/locales/en.json +25 -0
- package/dist/content/examples/notes/locales/fr.json +25 -0
- package/dist/content/examples/notes/logic.ts +799 -0
- package/dist/content/examples/notes/manifest.json +40 -0
- package/dist/content/examples/notes/package.json +16 -0
- package/dist/content/examples/notes/style.ocss +469 -0
- package/dist/content/examples/notes/tests/checks.scenario.json +237 -0
- package/dist/content/examples/notes/tests/create.scenario.json +260 -0
- package/dist/content/examples/notes/tests/delete.scenario.json +342 -0
- package/dist/content/examples/notes/tests/drafts.scenario.json +714 -0
- package/dist/content/examples/notes/tests/editor.scenario.json +232 -0
- package/dist/content/examples/notes/tests/frame.scenario.json +93 -0
- package/dist/content/examples/notes/tests/interactive.scenario.json +344 -0
- package/dist/content/examples/notes/tests/limits.scenario.json +1351 -0
- package/dist/content/examples/notes/tests/load-logic.mjs +22 -0
- package/dist/content/examples/notes/tests/logic.test.mjs +514 -0
- package/dist/content/examples/notes/tests/preview.scenario.json +99 -0
- package/dist/content/examples/notes/tests/read-only.scenario.json +105 -0
- package/dist/content/examples/notes/tests/refused.scenario.json +33 -0
- package/dist/content/examples/notes/tests/save.scenario.json +378 -0
- package/dist/content/examples/notes/tests/states.scenario.json +640 -0
- package/dist/content/examples/notes/tests/typing.scenario.json +562 -0
- package/dist/content/examples/notes/tests/unavailable.scenario.json +246 -0
- package/dist/content/examples/notes/tsconfig.json +16 -0
- package/dist/content/examples/notes/view.ocml +154 -0
- package/dist/content/examples/performance/LICENSE +21 -0
- package/dist/content/examples/performance/README.md +134 -0
- package/dist/content/examples/performance/listing.json +19 -0
- package/dist/content/examples/performance/locales/en.json +20 -0
- package/dist/content/examples/performance/locales/fr.json +20 -0
- package/dist/content/examples/performance/logic.ts +210 -0
- package/dist/content/examples/performance/manifest.json +83 -0
- package/dist/content/examples/performance/package.json +16 -0
- package/dist/content/examples/performance/style.ocss +75 -0
- package/dist/content/examples/performance/tests/fahrenheit.scenario.json +110 -0
- package/dist/content/examples/performance/tests/horizontal.scenario.json +96 -0
- package/dist/content/examples/performance/tests/load-logic.mjs +22 -0
- package/dist/content/examples/performance/tests/logic.test.mjs +226 -0
- package/dist/content/examples/performance/tests/menu.scenario.json +170 -0
- package/dist/content/examples/performance/tests/no-fps-source.scenario.json +47 -0
- package/dist/content/examples/performance/tests/preview.scenario.json +45 -0
- package/dist/content/examples/performance/tests/refused.scenario.json +20 -0
- package/dist/content/examples/performance/tests/refused.test.mjs +19 -0
- package/dist/content/examples/performance/tests/region.scenario.json +89 -0
- package/dist/content/examples/performance/tests/render.scenario.json +112 -0
- package/dist/content/examples/performance/tests/sources.scenario.json +151 -0
- package/dist/content/examples/performance/tests/stale.scenario.json +111 -0
- package/dist/content/examples/performance/tests/telemetry-only.scenario.json +44 -0
- package/dist/content/examples/performance/tests/thresholds.scenario.json +147 -0
- package/dist/content/examples/performance/tests/waiting.scenario.json +105 -0
- package/dist/content/examples/performance/tsconfig.json +16 -0
- package/dist/content/examples/performance/view.ocml +18 -0
- package/dist/content/examples/playervox-journal/LICENSE +21 -0
- package/dist/content/examples/playervox-journal/README.md +150 -0
- package/dist/content/examples/playervox-journal/listing.json +19 -0
- package/dist/content/examples/playervox-journal/locales/en.json +25 -0
- package/dist/content/examples/playervox-journal/locales/fr.json +25 -0
- package/dist/content/examples/playervox-journal/logic.ts +556 -0
- package/dist/content/examples/playervox-journal/manifest.json +19 -0
- package/dist/content/examples/playervox-journal/package.json +16 -0
- package/dist/content/examples/playervox-journal/style.ocss +142 -0
- package/dist/content/examples/playervox-journal/tests/changes.scenario.json +482 -0
- package/dist/content/examples/playervox-journal/tests/delete.scenario.json +432 -0
- package/dist/content/examples/playervox-journal/tests/frame.scenario.json +169 -0
- package/dist/content/examples/playervox-journal/tests/load-logic.mjs +22 -0
- package/dist/content/examples/playervox-journal/tests/logic.test.mjs +349 -0
- package/dist/content/examples/playervox-journal/tests/paging.scenario.json +428 -0
- package/dist/content/examples/playervox-journal/tests/preview.scenario.json +184 -0
- package/dist/content/examples/playervox-journal/tests/read-only.scenario.json +157 -0
- package/dist/content/examples/playervox-journal/tests/refused.scenario.json +39 -0
- package/dist/content/examples/playervox-journal/tests/scale.scenario.json +286 -0
- package/dist/content/examples/playervox-journal/tests/signed-out.scenario.json +134 -0
- package/dist/content/examples/playervox-journal/tests/states.scenario.json +741 -0
- package/dist/content/examples/playervox-journal/tests/unavailable.scenario.json +183 -0
- package/dist/content/examples/playervox-journal/tsconfig.json +16 -0
- package/dist/content/examples/playervox-journal/view.ocml +52 -0
- package/dist/content/examples/playervox-rating/LICENSE +21 -0
- package/dist/content/examples/playervox-rating/README.md +166 -0
- package/dist/content/examples/playervox-rating/listing.json +19 -0
- package/dist/content/examples/playervox-rating/locales/en.json +35 -0
- package/dist/content/examples/playervox-rating/locales/fr.json +35 -0
- package/dist/content/examples/playervox-rating/logic.ts +581 -0
- package/dist/content/examples/playervox-rating/manifest.json +19 -0
- package/dist/content/examples/playervox-rating/package.json +16 -0
- package/dist/content/examples/playervox-rating/style.ocss +333 -0
- package/dist/content/examples/playervox-rating/tests/account.scenario.json +125 -0
- package/dist/content/examples/playervox-rating/tests/bounds.scenario.json +73 -0
- package/dist/content/examples/playervox-rating/tests/draft.scenario.json +199 -0
- package/dist/content/examples/playervox-rating/tests/form.scenario.json +142 -0
- package/dist/content/examples/playervox-rating/tests/load-logic.mjs +22 -0
- package/dist/content/examples/playervox-rating/tests/logic.test.mjs +346 -0
- package/dist/content/examples/playervox-rating/tests/offline.scenario.json +142 -0
- package/dist/content/examples/playervox-rating/tests/preview.scenario.json +44 -0
- package/dist/content/examples/playervox-rating/tests/publish-keyboard.scenario.json +221 -0
- package/dist/content/examples/playervox-rating/tests/publish-pointer.scenario.json +314 -0
- package/dist/content/examples/playervox-rating/tests/read-only.scenario.json +70 -0
- package/dist/content/examples/playervox-rating/tests/refused.scenario.json +45 -0
- package/dist/content/examples/playervox-rating/tests/states.scenario.json +362 -0
- package/dist/content/examples/playervox-rating/tsconfig.json +16 -0
- package/dist/content/examples/playervox-rating/view.ocml +62 -0
- package/dist/content/examples/playervox-reviews/LICENSE +21 -0
- package/dist/content/examples/playervox-reviews/README.md +194 -0
- package/dist/content/examples/playervox-reviews/listing.json +19 -0
- package/dist/content/examples/playervox-reviews/locales/en.json +32 -0
- package/dist/content/examples/playervox-reviews/locales/fr.json +32 -0
- package/dist/content/examples/playervox-reviews/logic.ts +822 -0
- package/dist/content/examples/playervox-reviews/manifest.json +30 -0
- package/dist/content/examples/playervox-reviews/package.json +16 -0
- package/dist/content/examples/playervox-reviews/style.ocss +260 -0
- package/dist/content/examples/playervox-reviews/tests/account.scenario.json +249 -0
- package/dist/content/examples/playervox-reviews/tests/bounds.scenario.json +336 -0
- package/dist/content/examples/playervox-reviews/tests/changes.scenario.json +515 -0
- package/dist/content/examples/playervox-reviews/tests/filter.scenario.json +400 -0
- package/dist/content/examples/playervox-reviews/tests/frame.scenario.json +185 -0
- package/dist/content/examples/playervox-reviews/tests/keyboard.scenario.json +188 -0
- package/dist/content/examples/playervox-reviews/tests/load-logic.mjs +22 -0
- package/dist/content/examples/playervox-reviews/tests/logic.test.mjs +559 -0
- package/dist/content/examples/playervox-reviews/tests/offline-start.scenario.json +152 -0
- package/dist/content/examples/playervox-reviews/tests/offline.scenario.json +311 -0
- package/dist/content/examples/playervox-reviews/tests/paging.scenario.json +497 -0
- package/dist/content/examples/playervox-reviews/tests/preview.scenario.json +286 -0
- package/dist/content/examples/playervox-reviews/tests/reading.scenario.json +591 -0
- package/dist/content/examples/playervox-reviews/tests/refused.scenario.json +54 -0
- package/dist/content/examples/playervox-reviews/tests/scale.scenario.json +303 -0
- package/dist/content/examples/playervox-reviews/tests/states.scenario.json +700 -0
- package/dist/content/examples/playervox-reviews/tests/unavailable.scenario.json +192 -0
- package/dist/content/examples/playervox-reviews/tsconfig.json +16 -0
- package/dist/content/examples/playervox-reviews/view.ocml +110 -0
- package/dist/content/examples/playervox-score/LICENSE +21 -0
- package/dist/content/examples/playervox-score/README.md +131 -0
- package/dist/content/examples/playervox-score/listing.json +19 -0
- package/dist/content/examples/playervox-score/locales/en.json +23 -0
- package/dist/content/examples/playervox-score/locales/fr.json +23 -0
- package/dist/content/examples/playervox-score/logic.ts +430 -0
- package/dist/content/examples/playervox-score/manifest.json +47 -0
- package/dist/content/examples/playervox-score/package.json +16 -0
- package/dist/content/examples/playervox-score/style.ocss +247 -0
- package/dist/content/examples/playervox-score/tests/grades.scenario.json +279 -0
- package/dist/content/examples/playervox-score/tests/load-logic.mjs +22 -0
- package/dist/content/examples/playervox-score/tests/logic.test.mjs +229 -0
- package/dist/content/examples/playervox-score/tests/options.scenario.json +302 -0
- package/dist/content/examples/playervox-score/tests/preview.scenario.json +68 -0
- package/dist/content/examples/playervox-score/tests/pulse.scenario.json +157 -0
- package/dist/content/examples/playervox-score/tests/refused.scenario.json +21 -0
- package/dist/content/examples/playervox-score/tests/signed-out.scenario.json +46 -0
- package/dist/content/examples/playervox-score/tests/states.scenario.json +450 -0
- package/dist/content/examples/playervox-score/tests/unavailable.scenario.json +84 -0
- package/dist/content/examples/playervox-score/tsconfig.json +16 -0
- package/dist/content/examples/playervox-score/view.ocml +51 -0
- package/dist/content/examples/session/LICENSE +21 -0
- package/dist/content/examples/session/README.md +57 -0
- package/dist/content/examples/session/listing.json +19 -0
- package/dist/content/examples/session/locales/en.json +3 -0
- package/dist/content/examples/session/locales/fr.json +3 -0
- package/dist/content/examples/session/logic.ts +26 -0
- package/dist/content/examples/session/manifest.json +19 -0
- package/dist/content/examples/session/package.json +16 -0
- package/dist/content/examples/session/style.ocss +16 -0
- package/dist/content/examples/session/tests/hidden.scenario.json +45 -0
- package/dist/content/examples/session/tests/load-logic.mjs +22 -0
- package/dist/content/examples/session/tests/logic.test.mjs +37 -0
- package/dist/content/examples/session/tests/preview.scenario.json +28 -0
- package/dist/content/examples/session/tests/refused.scenario.json +20 -0
- package/dist/content/examples/session/tests/refused.test.mjs +16 -0
- package/dist/content/examples/session/tests/render.scenario.json +95 -0
- package/dist/content/examples/session/tests/ticks.scenario.json +139 -0
- package/dist/content/examples/session/tests/unknown.scenario.json +37 -0
- package/dist/content/examples/session/tsconfig.json +16 -0
- package/dist/content/examples/session/view.ocml +11 -0
- package/dist/content/examples/stopwatch/LICENSE +21 -0
- package/dist/content/examples/stopwatch/README.md +99 -0
- package/dist/content/examples/stopwatch/listing.json +19 -0
- package/dist/content/examples/stopwatch/locales/en.json +8 -0
- package/dist/content/examples/stopwatch/locales/fr.json +8 -0
- package/dist/content/examples/stopwatch/logic.ts +93 -0
- package/dist/content/examples/stopwatch/manifest.json +19 -0
- package/dist/content/examples/stopwatch/package.json +16 -0
- package/dist/content/examples/stopwatch/style.ocss +54 -0
- package/dist/content/examples/stopwatch/tests/errors.scenario.json +121 -0
- package/dist/content/examples/stopwatch/tests/gesture.scenario.json +154 -0
- package/dist/content/examples/stopwatch/tests/hidden.scenario.json +57 -0
- package/dist/content/examples/stopwatch/tests/hundredths.scenario.json +173 -0
- package/dist/content/examples/stopwatch/tests/load-logic.mjs +22 -0
- package/dist/content/examples/stopwatch/tests/logic.test.mjs +99 -0
- package/dist/content/examples/stopwatch/tests/preview.scenario.json +36 -0
- package/dist/content/examples/stopwatch/tests/read-only.scenario.json +42 -0
- package/dist/content/examples/stopwatch/tests/read-only.test.mjs +18 -0
- package/dist/content/examples/stopwatch/tests/refused.scenario.json +22 -0
- package/dist/content/examples/stopwatch/tests/refused.test.mjs +18 -0
- package/dist/content/examples/stopwatch/tests/render.scenario.json +105 -0
- package/dist/content/examples/stopwatch/tests/states.scenario.json +246 -0
- package/dist/content/examples/stopwatch/tests/unknown.scenario.json +40 -0
- package/dist/content/examples/stopwatch/tsconfig.json +16 -0
- package/dist/content/examples/stopwatch/view.ocml +29 -0
- package/dist/content/examples/twitch-chat/LICENSE +21 -0
- package/dist/content/examples/twitch-chat/README.md +283 -0
- package/dist/content/examples/twitch-chat/listing.json +19 -0
- package/dist/content/examples/twitch-chat/locales/en.json +52 -0
- package/dist/content/examples/twitch-chat/locales/fr.json +52 -0
- package/dist/content/examples/twitch-chat/logic.ts +887 -0
- package/dist/content/examples/twitch-chat/manifest.json +33 -0
- package/dist/content/examples/twitch-chat/package.json +16 -0
- package/dist/content/examples/twitch-chat/style.ocss +544 -0
- package/dist/content/examples/twitch-chat/tests/account.scenario.json +325 -0
- package/dist/content/examples/twitch-chat/tests/burst.scenario.json +1866 -0
- package/dist/content/examples/twitch-chat/tests/channel-refused.scenario.json +372 -0
- package/dist/content/examples/twitch-chat/tests/channel.scenario.json +480 -0
- package/dist/content/examples/twitch-chat/tests/draft.scenario.json +279 -0
- package/dist/content/examples/twitch-chat/tests/fade.scenario.json +269 -0
- package/dist/content/examples/twitch-chat/tests/failures.scenario.json +420 -0
- package/dist/content/examples/twitch-chat/tests/favorites.scenario.json +376 -0
- package/dist/content/examples/twitch-chat/tests/frame.scenario.json +152 -0
- package/dist/content/examples/twitch-chat/tests/lifetime.scenario.json +186 -0
- package/dist/content/examples/twitch-chat/tests/load-logic.mjs +22 -0
- package/dist/content/examples/twitch-chat/tests/logic.test.mjs +1053 -0
- package/dist/content/examples/twitch-chat/tests/passive.scenario.json +536 -0
- package/dist/content/examples/twitch-chat/tests/preview.scenario.json +147 -0
- package/dist/content/examples/twitch-chat/tests/read-only.scenario.json +202 -0
- package/dist/content/examples/twitch-chat/tests/read-only.test.mjs +63 -0
- package/dist/content/examples/twitch-chat/tests/refused.scenario.json +65 -0
- package/dist/content/examples/twitch-chat/tests/refused.test.mjs +43 -0
- package/dist/content/examples/twitch-chat/tests/render.scenario.json +347 -0
- package/dist/content/examples/twitch-chat/tests/reply.scenario.json +384 -0
- package/dist/content/examples/twitch-chat/tests/send-refused.scenario.json +383 -0
- package/dist/content/examples/twitch-chat/tests/send.scenario.json +300 -0
- package/dist/content/examples/twitch-chat/tests/states.scenario.json +455 -0
- package/dist/content/examples/twitch-chat/tests/unavailable.scenario.json +256 -0
- package/dist/content/examples/twitch-chat/tests/unread.scenario.json +997 -0
- package/dist/content/examples/twitch-chat/tsconfig.json +16 -0
- package/dist/content/examples/twitch-chat/view.ocml +141 -0
- package/dist/content/examples/warframe-market/LICENSE +21 -0
- package/dist/content/examples/warframe-market/README.md +176 -0
- package/dist/content/examples/warframe-market/listing.json +19 -0
- package/dist/content/examples/warframe-market/locales/en.json +44 -0
- package/dist/content/examples/warframe-market/locales/fr.json +44 -0
- package/dist/content/examples/warframe-market/logic.ts +1265 -0
- package/dist/content/examples/warframe-market/manifest.json +92 -0
- package/dist/content/examples/warframe-market/package.json +16 -0
- package/dist/content/examples/warframe-market/style.ocss +422 -0
- package/dist/content/examples/warframe-market/tests/bounds.scenario.json +60 -0
- package/dist/content/examples/warframe-market/tests/cached.scenario.json +70 -0
- package/dist/content/examples/warframe-market/tests/detail.scenario.json +251 -0
- package/dist/content/examples/warframe-market/tests/errors.scenario.json +105 -0
- package/dist/content/examples/warframe-market/tests/fixtures.json +295 -0
- package/dist/content/examples/warframe-market/tests/load-logic.mjs +22 -0
- package/dist/content/examples/warframe-market/tests/logic.test.mjs +505 -0
- package/dist/content/examples/warframe-market/tests/no-clipboard.scenario.json +104 -0
- package/dist/content/examples/warframe-market/tests/passive.scenario.json +87 -0
- package/dist/content/examples/warframe-market/tests/preview.scenario.json +146 -0
- package/dist/content/examples/warframe-market/tests/refresh.scenario.json +56 -0
- package/dist/content/examples/warframe-market/tests/refused.scenario.json +74 -0
- package/dist/content/examples/warframe-market/tests/render.scenario.json +210 -0
- package/dist/content/examples/warframe-market/tests/search.scenario.json +189 -0
- package/dist/content/examples/warframe-market/tests/unavailable.scenario.json +54 -0
- package/dist/content/examples/warframe-market/tsconfig.json +16 -0
- package/dist/content/examples/warframe-market/view.ocml +116 -0
- package/dist/content/examples/weather/LICENSE +20 -0
- package/dist/content/examples/weather/listing.json +10 -0
- package/dist/content/examples/weather/locales/en.json +9 -0
- package/dist/content/examples/weather/locales/fr.json +9 -0
- package/dist/content/examples/weather/logic.ts +62 -0
- package/dist/content/examples/weather/manifest.json +51 -0
- package/dist/content/examples/weather/package.json +15 -0
- package/dist/content/examples/weather/style.ocss +29 -0
- package/dist/content/examples/weather/tsconfig.json +16 -0
- package/dist/content/examples/weather/view.ocml +12 -0
- package/dist/content/index.json +3601 -0
- package/dist/content/reference/errors.json +1712 -0
- package/dist/content/reference/limits.json +1154 -0
- package/dist/content/reference/manifest.md +223 -0
- package/dist/content/reference/permissions.json +413 -0
- package/dist/content/templates/blank/locales/en.json +3 -0
- package/dist/content/templates/blank/locales/fr.json +3 -0
- package/dist/content/templates/blank/logic.ts +7 -0
- package/dist/content/templates/blank/manifest.json +16 -0
- package/dist/content/templates/blank/style.ocss +10 -0
- package/dist/content/templates/blank/tests/example.scenario.json +10 -0
- package/dist/content/templates/blank/view.ocml +6 -0
- package/dist/content/templates/chart/locales/en.json +3 -0
- package/dist/content/templates/chart/locales/fr.json +3 -0
- package/dist/content/templates/chart/logic.ts +27 -0
- package/dist/content/templates/chart/manifest.json +16 -0
- package/dist/content/templates/chart/style.ocss +27 -0
- package/dist/content/templates/chart/tests/example.scenario.json +10 -0
- package/dist/content/templates/chart/view.ocml +10 -0
- package/dist/content/templates/counter/locales/en.json +4 -0
- package/dist/content/templates/counter/locales/fr.json +4 -0
- package/dist/content/templates/counter/logic.ts +20 -0
- package/dist/content/templates/counter/manifest.json +16 -0
- package/dist/content/templates/counter/style.ocss +26 -0
- package/dist/content/templates/counter/tests/example.scenario.json +11 -0
- package/dist/content/templates/counter/view.ocml +13 -0
- package/dist/content/templates/list/locales/en.json +8 -0
- package/dist/content/templates/list/locales/fr.json +8 -0
- package/dist/content/templates/list/logic.ts +35 -0
- package/dist/content/templates/list/manifest.json +16 -0
- package/dist/content/templates/list/style.ocss +42 -0
- package/dist/content/templates/list/tests/example.scenario.json +10 -0
- package/dist/content/templates/list/view.ocml +17 -0
- package/dist/content/templates/shared/LICENSE +20 -0
- package/dist/content/templates/shared/package.json +14 -0
- package/dist/content/templates/shared/tsconfig.json +16 -0
- package/dist/content.js +68 -0
- package/dist/env.js +156 -0
- package/dist/fingerprint.js +32 -0
- package/dist/ids.js +27 -0
- package/dist/index.js +82 -0
- package/dist/knowledge.js +222 -0
- package/dist/npm.js +149 -0
- package/dist/prompts.js +47 -0
- package/dist/resources.js +82 -0
- package/dist/run.js +120 -0
- package/dist/server.js +43 -0
- package/dist/session.js +205 -0
- package/dist/sources.js +218 -0
- package/dist/text.js +80 -0
- package/dist/texts.js +44 -0
- package/dist/tools/audit.js +105 -0
- package/dist/tools/build.js +453 -0
- package/dist/tools/common.js +122 -0
- package/dist/tools/docs.js +196 -0
- package/dist/tools/preview.js +87 -0
- package/dist/tools/project.js +178 -0
- package/dist/tools/setup.js +146 -0
- package/dist/tools/submission.js +369 -0
- package/dist/version.js +3 -0
- package/npm-shrinkwrap.json +402 -0
- package/package.json +52 -4
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
// Notes: the user's notes and checklists, one document for every game.
|
|
2
|
+
// OverCrow stores them; this widget shows the active note, and in
|
|
3
|
+
// Interactive mode lets the user choose, create, delete, check and edit
|
|
4
|
+
// notes. The editor is a form bound to the `notes.save` intent: its title,
|
|
5
|
+
// body and checklist rows belong to OverCrow, which fills them, keeps the
|
|
6
|
+
// draft and sends their values itself when the user saves. The logic only
|
|
7
|
+
// follows them and decides which rows the form has.
|
|
8
|
+
import {
|
|
9
|
+
MAX_NOTES,
|
|
10
|
+
MAX_NOTE_ITEMS,
|
|
11
|
+
hasGrant,
|
|
12
|
+
host,
|
|
13
|
+
initState,
|
|
14
|
+
notes,
|
|
15
|
+
onHost,
|
|
16
|
+
option,
|
|
17
|
+
t,
|
|
18
|
+
timers,
|
|
19
|
+
type Note,
|
|
20
|
+
type NoteItem,
|
|
21
|
+
type Notes,
|
|
22
|
+
type ServiceError,
|
|
23
|
+
type ServiceErrorPayload,
|
|
24
|
+
type Subscription,
|
|
25
|
+
type Timer,
|
|
26
|
+
} from "@overcrow/sdk";
|
|
27
|
+
|
|
28
|
+
/** One checklist row of an editor. */
|
|
29
|
+
export interface Row {
|
|
30
|
+
/** Stable key of the row in the view. */
|
|
31
|
+
key: string;
|
|
32
|
+
/** The stored item the row was opened with; `null` for a new row. */
|
|
33
|
+
id: string | null;
|
|
34
|
+
/** The row's text as OverCrow last reported it. */
|
|
35
|
+
text: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The editor of one note: what OverCrow's form holds, as its `input`
|
|
40
|
+
* events reported it. The form stays in the view while the editor exists,
|
|
41
|
+
* hidden when it is not the one shown: OverCrow keeps the draft.
|
|
42
|
+
*/
|
|
43
|
+
export interface Editor {
|
|
44
|
+
/**
|
|
45
|
+
* Stable key of the form in the view: the note's ID, or a key of its
|
|
46
|
+
* own for the editor of a note being created.
|
|
47
|
+
*/
|
|
48
|
+
key: string;
|
|
49
|
+
/**
|
|
50
|
+
* The note the form saves to. `null` while a new note is being created:
|
|
51
|
+
* the form is already there, so that its title takes the focus in the
|
|
52
|
+
* turn of the user's click, and OverCrow fills it once it has its note.
|
|
53
|
+
*/
|
|
54
|
+
note: string | null;
|
|
55
|
+
/** The user opened it and has not saved or cancelled since. */
|
|
56
|
+
open: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The editor of a note just created: it shows until its note is the
|
|
59
|
+
* active one, which the document says a moment after the click.
|
|
60
|
+
*/
|
|
61
|
+
fresh: boolean;
|
|
62
|
+
/** `null` until OverCrow filled the form. */
|
|
63
|
+
title: string | null;
|
|
64
|
+
body: string | null;
|
|
65
|
+
rows: Row[];
|
|
66
|
+
/** Number of the next row key. */
|
|
67
|
+
next: number;
|
|
68
|
+
/** A save was sent and not answered yet. */
|
|
69
|
+
pending: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Which parts of the active note show (menu row `sections`). */
|
|
73
|
+
export type Sections = "both" | "note" | "checklist";
|
|
74
|
+
|
|
75
|
+
declare module "@overcrow/sdk" {
|
|
76
|
+
interface WidgetState {
|
|
77
|
+
/** The stored document; `null` until the host's first answer. */
|
|
78
|
+
doc: Notes | null;
|
|
79
|
+
/** The host's source failed: the widget asks again later. */
|
|
80
|
+
unavailable: boolean;
|
|
81
|
+
/** Interactive mode: selector, checks and editor. */
|
|
82
|
+
interactive: boolean;
|
|
83
|
+
sections: Sections;
|
|
84
|
+
moveChecked: boolean;
|
|
85
|
+
/** The note list is open. */
|
|
86
|
+
menu: boolean;
|
|
87
|
+
editors: Editor[];
|
|
88
|
+
/** Checks sent and not stored yet, by item ID. */
|
|
89
|
+
checks: Record<string, boolean>;
|
|
90
|
+
/** Calls and saves sent and not answered yet. */
|
|
91
|
+
busy: number;
|
|
92
|
+
/** Message key of the last failure, or `""`. */
|
|
93
|
+
error: string;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* After the host ends the subscription with a failure, the widget
|
|
99
|
+
* subscribes again this much later.
|
|
100
|
+
*/
|
|
101
|
+
export const RETRY_MS = 5_000;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* OverCrow answers a save once the note is stored. Should no answer come
|
|
105
|
+
* at all, the editor is given back after this long.
|
|
106
|
+
*/
|
|
107
|
+
export const ANSWER_MS = 40_000;
|
|
108
|
+
|
|
109
|
+
/** `label` bound (`MAX_LABEL_BYTES`). */
|
|
110
|
+
const MAX_LABEL_BYTES = 256;
|
|
111
|
+
|
|
112
|
+
const canRead = hasGrant("notes.read");
|
|
113
|
+
const canWrite = hasGrant("notes.write");
|
|
114
|
+
|
|
115
|
+
function sectionsOption(): Sections {
|
|
116
|
+
const value = option("sections", "both");
|
|
117
|
+
return value === "note" || value === "checklist" ? value : "both";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const state = initState({
|
|
121
|
+
doc: null as Notes | null,
|
|
122
|
+
unavailable: false,
|
|
123
|
+
interactive: host.mode === "interactive",
|
|
124
|
+
sections: sectionsOption(),
|
|
125
|
+
moveChecked: option("move-checked", true),
|
|
126
|
+
menu: false,
|
|
127
|
+
editors: [] as Editor[],
|
|
128
|
+
checks: {} as Record<string, boolean>,
|
|
129
|
+
busy: 0,
|
|
130
|
+
error: "",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/** What the view reads of the state. */
|
|
134
|
+
export interface View {
|
|
135
|
+
doc: Notes | null;
|
|
136
|
+
unavailable: boolean;
|
|
137
|
+
interactive: boolean;
|
|
138
|
+
sections: Sections;
|
|
139
|
+
moveChecked: boolean;
|
|
140
|
+
menu: boolean;
|
|
141
|
+
editors: Editor[];
|
|
142
|
+
checks: Record<string, boolean>;
|
|
143
|
+
busy: number;
|
|
144
|
+
error: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** `text` cut to `limit` UTF-8 bytes on a character boundary. */
|
|
148
|
+
export function cutBytes(text: string, limit: number): string {
|
|
149
|
+
let bytes = 0;
|
|
150
|
+
let end = 0;
|
|
151
|
+
for (const character of text) {
|
|
152
|
+
const code = character.codePointAt(0) ?? 0;
|
|
153
|
+
const size = code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
|
|
154
|
+
if (bytes + size > limit) {
|
|
155
|
+
return text.slice(0, end);
|
|
156
|
+
}
|
|
157
|
+
bytes += size;
|
|
158
|
+
end += character.length;
|
|
159
|
+
}
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A label of `key` with the note's title, within the label bound. */
|
|
164
|
+
export function titled(key: string, title: string): string {
|
|
165
|
+
return cutBytes(t(key, { title }), MAX_LABEL_BYTES);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The first shown state: nothing known yet, the source failed, or ready. */
|
|
169
|
+
export function phase(view: View): "loading" | "unavailable" | "ready" {
|
|
170
|
+
if (!canRead || view.unavailable) {
|
|
171
|
+
return "unavailable";
|
|
172
|
+
}
|
|
173
|
+
return view.doc === null ? "loading" : "ready";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The selector, the checks and the editor show: Interactive, with the write grant. */
|
|
177
|
+
export function editable(view: View): boolean {
|
|
178
|
+
return view.interactive && canWrite;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function noteOf(view: View, id: string | null): Note | null {
|
|
182
|
+
return id === null ? null : (view.doc?.notes.find((note) => note.id === id) ?? null);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The note shown: the document's active one. */
|
|
186
|
+
export function active(view: View): Note | null {
|
|
187
|
+
return view.doc === null ? null : noteOf(view, view.doc.active);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function noteCount(view: View): number {
|
|
191
|
+
return view.doc?.notes.length ?? 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** No more note can be created. */
|
|
195
|
+
export function full(view: View): boolean {
|
|
196
|
+
return noteCount(view) >= MAX_NOTES;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function showsNote(view: View): boolean {
|
|
200
|
+
return view.sections !== "checklist";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function showsChecklist(view: View): boolean {
|
|
204
|
+
return view.sections !== "note";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function editorOf(view: View, id: string | null): Editor | null {
|
|
208
|
+
return id === null ? null : (view.editors.find((editor) => editor.note === id) ?? null);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The editor whose form has this key. */
|
|
212
|
+
export function editorAt(view: View, key: string): Editor | null {
|
|
213
|
+
return view.editors.find((editor) => editor.key === key) ?? null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The rows a save keeps: trimmed, the empty ones left out. */
|
|
217
|
+
function keptRows(editor: Editor): Row[] {
|
|
218
|
+
return editor.rows.filter((row) => row.text.trim() !== "");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The editor's form differs from the stored note. Unknown before OverCrow
|
|
223
|
+
* filled the form: not a draft yet.
|
|
224
|
+
*/
|
|
225
|
+
export function dirty(editor: Editor, note: Note | null): boolean {
|
|
226
|
+
if (note === null || editor.title === null || editor.body === null) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
// As a save reads the form: the title and the rows trimmed, the empty
|
|
230
|
+
// rows left out, the text as written. Stored texts are compared trimmed
|
|
231
|
+
// too: a note written by an older version may hold a padded entry.
|
|
232
|
+
const rows = keptRows(editor);
|
|
233
|
+
const items = note.items.filter((item) => item.text.trim() !== "");
|
|
234
|
+
return (
|
|
235
|
+
editor.title.trim() !== note.title.trim() ||
|
|
236
|
+
editor.body !== note.body ||
|
|
237
|
+
rows.length !== items.length ||
|
|
238
|
+
rows.some((row, index) => {
|
|
239
|
+
const item = items[index];
|
|
240
|
+
return item === undefined || row.id !== item.id || row.text.trim() !== item.text.trim();
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The note has an unsaved draft. */
|
|
246
|
+
export function drafted(view: View, id: string | null): boolean {
|
|
247
|
+
const editor = editorOf(view, id);
|
|
248
|
+
return editor !== null && dirty(editor, noteOf(view, id));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A note's title in the selector and the list, " •" with a draft. */
|
|
252
|
+
export function noteLabel(view: View, note: Note): string {
|
|
253
|
+
return drafted(view, note.id) ? `${note.title} •` : note.title;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** The ID of the note shown, `""` without one. */
|
|
257
|
+
export function activeId(view: View): string {
|
|
258
|
+
return active(view)?.id ?? "";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function activeTitle(view: View): string {
|
|
262
|
+
return active(view)?.title ?? "";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function activeBody(view: View): string {
|
|
266
|
+
return active(view)?.body ?? "";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** The selector's text: the active note's title, " •" with a draft. */
|
|
270
|
+
export function activeLabel(view: View): string {
|
|
271
|
+
const note = active(view);
|
|
272
|
+
return note === null ? "" : noteLabel(view, note);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The chevron shows when there is another note to choose. */
|
|
276
|
+
export function chevronClass(view: View): string {
|
|
277
|
+
return noteCount(view) > 1 ? "selector-icon" : "selector-icon none";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function listItemClass(view: View, note: Note): string {
|
|
281
|
+
return note.id === view.doc?.active ? "list-item current" : "list-item";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Message key of the state without notes to show. */
|
|
285
|
+
export function unavailableKey(view: View): string {
|
|
286
|
+
return canRead && view.unavailable ? "error-unavailable" : "error-permission";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The editor that shows, in Interactive mode: the one of a note just
|
|
291
|
+
* created, else the open one of the active note.
|
|
292
|
+
*/
|
|
293
|
+
export function shownEditor(view: View): Editor | null {
|
|
294
|
+
if (!editable(view)) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const fresh = view.editors.find((editor) => editor.open && editor.fresh);
|
|
298
|
+
if (fresh !== undefined) {
|
|
299
|
+
return fresh;
|
|
300
|
+
}
|
|
301
|
+
const editor = editorOf(view, view.doc?.active ?? null);
|
|
302
|
+
return editor !== null && editor.open ? editor : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** An editor shows in place of the active note's preview. */
|
|
306
|
+
export function editing(view: View): boolean {
|
|
307
|
+
return shownEditor(view) !== null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The classes of an editor's form: shown for the editor that shows,
|
|
312
|
+
* hidden otherwise. Hidden, it stays in the view and OverCrow keeps the
|
|
313
|
+
* draft its controls hold.
|
|
314
|
+
*/
|
|
315
|
+
export function editorClass(view: View, editor: Editor): string {
|
|
316
|
+
return shownEditor(view) === editor ? "editor" : "editor hidden";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Whether the item shows checked: a check just sent, else the stored one. */
|
|
320
|
+
export function checked(view: View, item: NoteItem): boolean {
|
|
321
|
+
return (item.id !== null ? view.checks[item.id] : undefined) ?? item.checked;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** A stored item's check by its ID, for an editor's row. */
|
|
325
|
+
export function rowChecked(view: View, editor: Editor, row: Row): boolean {
|
|
326
|
+
const item = noteOf(view, editor.note)?.items.find((candidate) => candidate.id === row.id);
|
|
327
|
+
return item !== undefined && checked(view, item);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** A row whose item is stored: it has a check. */
|
|
331
|
+
export function rowStored(view: View, editor: Editor, row: Row): boolean {
|
|
332
|
+
return row.id !== null && noteOf(view, editor.note)?.items.some((item) => item.id === row.id) === true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The checklist of the preview: in the stored order, or with the checked
|
|
337
|
+
* entries after the open ones, each group in the stored order. The note is
|
|
338
|
+
* never reordered.
|
|
339
|
+
*/
|
|
340
|
+
export function previewItems(view: View): NoteItem[] {
|
|
341
|
+
const items = active(view)?.items ?? [];
|
|
342
|
+
if (!view.moveChecked) {
|
|
343
|
+
return [...items];
|
|
344
|
+
}
|
|
345
|
+
return [
|
|
346
|
+
...items.filter((item) => !checked(view, item)),
|
|
347
|
+
...items.filter((item) => checked(view, item)),
|
|
348
|
+
];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function itemClass(view: View, item: NoteItem): string {
|
|
352
|
+
return checked(view, item) ? "item-text done" : "item-text";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function itemLabel(item: NoteItem): string {
|
|
356
|
+
return cutBytes(item.text, MAX_LABEL_BYTES);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The accessible name of a row's check: the row's text. */
|
|
360
|
+
export function rowLabel(row: Row): string {
|
|
361
|
+
return cutBytes(row.text, MAX_LABEL_BYTES);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** The rows of an editor's form hold as many entries as a note can. */
|
|
365
|
+
export function rowsFull(editor: Editor): boolean {
|
|
366
|
+
return keptRows(editor).length >= MAX_NOTE_ITEMS;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** The row is the empty one at the end, where a new entry is typed. */
|
|
370
|
+
export function lastEmpty(editor: Editor, row: Row): boolean {
|
|
371
|
+
return row.text === "" && editor.rows[editor.rows.length - 1] === row;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function rowHint(editor: Editor, row: Row): string {
|
|
375
|
+
return t(lastEmpty(editor, row) ? "new-item-hint" : "item-hint");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The body's visible rows: its lines, from 3 to 10. OverCrow scrolls a
|
|
380
|
+
* longer text to the caret.
|
|
381
|
+
*/
|
|
382
|
+
export function bodyRows(editor: Editor): number {
|
|
383
|
+
const lines = (editor.body ?? "").split("\n").length;
|
|
384
|
+
return Math.min(10, Math.max(3, lines));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The title is empty: nothing to save under. */
|
|
388
|
+
export function untitled(editor: Editor): boolean {
|
|
389
|
+
return editor.title !== null && editor.title.trim() === "";
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Save is active once the form has its note and OverCrow filled it, with a
|
|
394
|
+
* title, while no save is in flight.
|
|
395
|
+
*/
|
|
396
|
+
export function canSave(view: View, editor: Editor): boolean {
|
|
397
|
+
return editable(view) && editor.note !== null && editor.title !== null && !untitled(editor) && !editor.pending;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function saveClass(view: View, editor: Editor): string {
|
|
401
|
+
return canSave(view, editor) ? "action primary" : "action";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Message key of a refused call or save. */
|
|
405
|
+
export function errorKey(code: string | null): string {
|
|
406
|
+
switch (code) {
|
|
407
|
+
case "quota_exceeded":
|
|
408
|
+
return "error-full";
|
|
409
|
+
case "permission_denied":
|
|
410
|
+
return "error-permission";
|
|
411
|
+
default:
|
|
412
|
+
// `unavailable`, `busy`, `stale_context`, `timeout` and the rest: the
|
|
413
|
+
// notes could not be saved.
|
|
414
|
+
return "error-unavailable";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---------------------------------------------------------------------
|
|
419
|
+
// Editors.
|
|
420
|
+
|
|
421
|
+
/** A new empty row at the end, unless the form holds its last one. */
|
|
422
|
+
function addEmptyRow(editor: Editor): void {
|
|
423
|
+
const last = editor.rows[editor.rows.length - 1];
|
|
424
|
+
if (editor.rows.length < MAX_NOTE_ITEMS && (last === undefined || last.text !== "")) {
|
|
425
|
+
editor.rows.push({ key: `row-${editor.next}`, id: null, text: "" });
|
|
426
|
+
editor.next += 1;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* The editor of `note`, opened: its rows are the note's items in their
|
|
432
|
+
* stored order, which is the order OverCrow fills the form's fields in,
|
|
433
|
+
* and an empty row to type a new entry.
|
|
434
|
+
*/
|
|
435
|
+
function openEditor(note: Note): void {
|
|
436
|
+
if (note.id === null) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
let editor = editorOf(state, note.id);
|
|
440
|
+
if (editor === null) {
|
|
441
|
+
editor = {
|
|
442
|
+
key: note.id,
|
|
443
|
+
note: note.id,
|
|
444
|
+
open: true,
|
|
445
|
+
fresh: false,
|
|
446
|
+
title: null,
|
|
447
|
+
body: null,
|
|
448
|
+
rows: note.items.map((item, index) => ({ key: `row-${index}`, id: item.id, text: item.text })),
|
|
449
|
+
next: note.items.length,
|
|
450
|
+
pending: false,
|
|
451
|
+
};
|
|
452
|
+
addEmptyRow(editor);
|
|
453
|
+
state.editors.push(editor);
|
|
454
|
+
}
|
|
455
|
+
editor.open = true;
|
|
456
|
+
state.menu = false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Removes an editor: OverCrow forgets its form's draft with it. */
|
|
460
|
+
function closeEditor(key: string): void {
|
|
461
|
+
state.editors = state.editors.filter((editor) => editor.key !== key);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* The editors leave the screen: a draft stays, hidden, with its form; an
|
|
466
|
+
* editor without a change is closed, and so is the editor of a note that
|
|
467
|
+
* is still being created.
|
|
468
|
+
*/
|
|
469
|
+
function setEditorsAside(): void {
|
|
470
|
+
state.editors = state.editors.filter((editor) => editor.pending || dirty(editor, noteOf(state, editor.note)));
|
|
471
|
+
for (const editor of state.editors) {
|
|
472
|
+
editor.open = false;
|
|
473
|
+
editor.fresh = false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** The title's text, from the user's typing or from OverCrow's fill. */
|
|
478
|
+
export function titleInput(key: string, text: string): void {
|
|
479
|
+
const editor = editorAt(state, key);
|
|
480
|
+
if (editor !== null) {
|
|
481
|
+
editor.title = text;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** The body's text, from the user's typing or from OverCrow's fill. */
|
|
486
|
+
export function bodyInput(key: string, text: string): void {
|
|
487
|
+
const editor = editorAt(state, key);
|
|
488
|
+
if (editor !== null) {
|
|
489
|
+
editor.body = text;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* A row's text. Typing in the empty row at the end adds the next one, so
|
|
495
|
+
* that Enter, which OverCrow moves to the next field, continues the list.
|
|
496
|
+
*/
|
|
497
|
+
export function rowInput(form: string, key: string, text: string): void {
|
|
498
|
+
const editor = editorAt(state, form);
|
|
499
|
+
const row = editor?.rows.find((candidate) => candidate.key === key);
|
|
500
|
+
if (editor === null || row === undefined) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
row.text = text;
|
|
504
|
+
addEmptyRow(editor);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Removes a row from the form: its entry leaves the note at the next save. */
|
|
508
|
+
export function dropRow(form: string, key: string): void {
|
|
509
|
+
const editor = editorAt(state, form);
|
|
510
|
+
if (editor === null) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
editor.rows = editor.rows.filter((row) => row.key !== key);
|
|
514
|
+
addEmptyRow(editor);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const answers = new Map<string, Timer>();
|
|
518
|
+
|
|
519
|
+
/** OverCrow is sending the form: the editor waits for the outcome. */
|
|
520
|
+
function sending(key: string): void {
|
|
521
|
+
const editor = editorAt(state, key);
|
|
522
|
+
// Without its note yet, the form has nothing to save to.
|
|
523
|
+
if (editor === null || editor.note === null || editor.pending) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
editor.pending = true;
|
|
527
|
+
state.busy += 1;
|
|
528
|
+
state.error = "";
|
|
529
|
+
answers.set(
|
|
530
|
+
key,
|
|
531
|
+
timers.after(ANSWER_MS, () => {
|
|
532
|
+
answers.delete(key);
|
|
533
|
+
const waiting = editorAt(state, key);
|
|
534
|
+
if (waiting !== null && waiting.pending) {
|
|
535
|
+
waiting.pending = false;
|
|
536
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
537
|
+
state.error = "error-unavailable";
|
|
538
|
+
}
|
|
539
|
+
}),
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** The Save button: OverCrow submits the form on the same gesture. */
|
|
544
|
+
export function saving(key: string): void {
|
|
545
|
+
sending(key);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** Ctrl+Enter in a field or the body submits the form too. */
|
|
549
|
+
export function keyed(form: string, key: string, ctrl: boolean): void {
|
|
550
|
+
if (key === "Enter" && ctrl) {
|
|
551
|
+
sending(form);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* The outcome of a save OverCrow sent: the form's `submit` event, with the
|
|
557
|
+
* failure's code when it was refused. Stored, the editor closes and the
|
|
558
|
+
* subscription shows the note; refused, the form keeps the draft.
|
|
559
|
+
*/
|
|
560
|
+
export function saved(key: string, outcome: string, error: ServiceErrorPayload | undefined): void {
|
|
561
|
+
const editor = editorAt(state, key);
|
|
562
|
+
if (editor !== null && editor.pending) {
|
|
563
|
+
editor.pending = false;
|
|
564
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
565
|
+
}
|
|
566
|
+
answers.get(key)?.cancel();
|
|
567
|
+
answers.delete(key);
|
|
568
|
+
if (outcome === "accepted") {
|
|
569
|
+
state.error = "";
|
|
570
|
+
closeEditor(key);
|
|
571
|
+
} else if (outcome !== "cancelled" && (editor === null || editor.note !== null)) {
|
|
572
|
+
// (A save asked before the new note exists had no note to go to:
|
|
573
|
+
// nothing to report, the form is filled a moment later.)
|
|
574
|
+
state.error = editor !== null && untitled(editor) ? "add-title" : errorKey(error?.code ?? null);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** Cancel: the draft is discarded with its form. */
|
|
579
|
+
export function cancel(key: string): void {
|
|
580
|
+
closeEditor(key);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ---------------------------------------------------------------------
|
|
584
|
+
// Calls. Each one is made while handling the user's gesture.
|
|
585
|
+
|
|
586
|
+
function sent<T>(promise: Promise<T>, done: (value: T) => void, refused: () => void = () => {}): void {
|
|
587
|
+
state.busy += 1;
|
|
588
|
+
promise.then(
|
|
589
|
+
(value) => {
|
|
590
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
591
|
+
state.error = "";
|
|
592
|
+
done(value);
|
|
593
|
+
},
|
|
594
|
+
(error: ServiceError) => {
|
|
595
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
596
|
+
refused();
|
|
597
|
+
// The user declined the host's confirmation: nothing to say.
|
|
598
|
+
if (error.code !== "cancelled") {
|
|
599
|
+
state.error = errorKey(error.code);
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Opens or closes the note list. */
|
|
606
|
+
export function toggleMenu(): void {
|
|
607
|
+
state.menu = !state.menu;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** A click beside the list closes it. */
|
|
611
|
+
export function closeMenu(): void {
|
|
612
|
+
state.menu = false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** Shows another note: OverCrow stores the active note. */
|
|
616
|
+
export function select(id: string): void {
|
|
617
|
+
state.menu = false;
|
|
618
|
+
if (state.doc?.active === id) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
setEditorsAside();
|
|
622
|
+
sent(notes.select({ note: id }), () => {});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** Opens the editor of a note, showing that note first. */
|
|
626
|
+
export function edit(id: string): void {
|
|
627
|
+
const note = noteOf(state, id);
|
|
628
|
+
if (note === null) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (state.doc?.active !== id) {
|
|
632
|
+
setEditorsAside();
|
|
633
|
+
sent(notes.select({ note: id }), () => {});
|
|
634
|
+
}
|
|
635
|
+
openEditor(note);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Number of the next new note's editor. */
|
|
639
|
+
let created = 0;
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* A new note, titled by OverCrow, opened in its editor. The editor is in
|
|
643
|
+
* the view at once, in the turn of the user's click, so that its title
|
|
644
|
+
* takes the focus; its note comes with OverCrow's answer, and OverCrow
|
|
645
|
+
* fills the form then. If the note cannot be created (the notes are full,
|
|
646
|
+
* the store refuses), the editor goes and the failure is said.
|
|
647
|
+
*/
|
|
648
|
+
export function create(): void {
|
|
649
|
+
state.menu = false;
|
|
650
|
+
setEditorsAside();
|
|
651
|
+
// A key no note and no editor has.
|
|
652
|
+
let key = "";
|
|
653
|
+
do {
|
|
654
|
+
created += 1;
|
|
655
|
+
key = `new:${created}`;
|
|
656
|
+
} while (noteOf(state, key) !== null || editorAt(state, key) !== null);
|
|
657
|
+
const editor: Editor = {
|
|
658
|
+
key,
|
|
659
|
+
note: null,
|
|
660
|
+
open: true,
|
|
661
|
+
fresh: true,
|
|
662
|
+
title: null,
|
|
663
|
+
body: null,
|
|
664
|
+
rows: [],
|
|
665
|
+
next: 0,
|
|
666
|
+
pending: false,
|
|
667
|
+
};
|
|
668
|
+
addEmptyRow(editor);
|
|
669
|
+
state.editors.push(editor);
|
|
670
|
+
sent(
|
|
671
|
+
notes.create(),
|
|
672
|
+
(answer) => {
|
|
673
|
+
const waiting = editorAt(state, key);
|
|
674
|
+
// The user went elsewhere meanwhile: the note exists, without editor.
|
|
675
|
+
if (waiting === null) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (answer.note.id === null) {
|
|
679
|
+
closeEditor(key);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
waiting.note = answer.note.id;
|
|
683
|
+
},
|
|
684
|
+
() => {
|
|
685
|
+
closeEditor(key);
|
|
686
|
+
},
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** Deletes a note: OverCrow asks the user to confirm first. */
|
|
691
|
+
export function remove(id: string): void {
|
|
692
|
+
state.menu = false;
|
|
693
|
+
sent(notes.delete({ note: id }), () => {
|
|
694
|
+
state.editors = state.editors.filter((editor) => editor.note !== id);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Checks or unchecks a stored item at once: the box shows the new state
|
|
700
|
+
* until OverCrow stored it, and goes back if it could not.
|
|
701
|
+
*/
|
|
702
|
+
export function check(note: string | null, item: string | null, value: boolean): void {
|
|
703
|
+
if (note === null || item === null) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
state.checks[item] = value;
|
|
707
|
+
state.busy += 1;
|
|
708
|
+
notes.setItem({ note, item, checked: value }).then(
|
|
709
|
+
() => {
|
|
710
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
711
|
+
state.error = "";
|
|
712
|
+
settleChecks();
|
|
713
|
+
},
|
|
714
|
+
(error: ServiceError) => {
|
|
715
|
+
state.busy = Math.max(0, state.busy - 1);
|
|
716
|
+
delete state.checks[item];
|
|
717
|
+
state.error = errorKey(error.code);
|
|
718
|
+
},
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** Forgets the checks the stored document now holds. */
|
|
723
|
+
function settleChecks(): void {
|
|
724
|
+
const stored = new Map<string, boolean>();
|
|
725
|
+
for (const note of state.doc?.notes ?? []) {
|
|
726
|
+
for (const item of note.items) {
|
|
727
|
+
if (item.id !== null) {
|
|
728
|
+
stored.set(item.id, item.checked);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
for (const [id, value] of Object.entries(state.checks)) {
|
|
733
|
+
if (stored.get(id) === value || !stored.has(id)) {
|
|
734
|
+
delete state.checks[id];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// ---------------------------------------------------------------------
|
|
740
|
+
// The document.
|
|
741
|
+
|
|
742
|
+
let subscription: Subscription | null = null;
|
|
743
|
+
|
|
744
|
+
function show(doc: Notes): void {
|
|
745
|
+
state.doc = doc;
|
|
746
|
+
state.unavailable = false;
|
|
747
|
+
// What is stored changed: the last failure is no longer the news. A
|
|
748
|
+
// refused write changes nothing, so its message stays.
|
|
749
|
+
state.error = "";
|
|
750
|
+
// A note deleted, here or elsewhere, takes its editor with it. The
|
|
751
|
+
// editor of a note being created has none yet and stays.
|
|
752
|
+
state.editors = state.editors.filter((editor) => editor.note === null || noteOf(state, editor.note) !== null);
|
|
753
|
+
// A new note is now the active one: its editor shows as any other.
|
|
754
|
+
for (const editor of state.editors) {
|
|
755
|
+
if (editor.fresh && editor.note !== null && editor.note === doc.active) {
|
|
756
|
+
editor.fresh = false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (doc.notes.length < 2) {
|
|
760
|
+
state.menu = false;
|
|
761
|
+
}
|
|
762
|
+
settleChecks();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function subscribe(): void {
|
|
766
|
+
const current = notes.subscribe((update) => {
|
|
767
|
+
if (update.ok) {
|
|
768
|
+
show(update.value);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
// The subscription ended: the host's source failed. Say so and ask
|
|
772
|
+
// again later; hidden, the timer waits until the widget shows.
|
|
773
|
+
if (subscription === current) {
|
|
774
|
+
subscription = null;
|
|
775
|
+
state.unavailable = true;
|
|
776
|
+
timers.after(RETRY_MS, subscribe);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
subscription = current;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
onHost((changed) => {
|
|
783
|
+
if (changed.includes("mode")) {
|
|
784
|
+
state.interactive = host.mode === "interactive";
|
|
785
|
+
if (!state.interactive) {
|
|
786
|
+
state.menu = false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (changed.includes("options")) {
|
|
790
|
+
state.sections = sectionsOption();
|
|
791
|
+
state.moveChecked = option("move-checked", true);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Without the grant the host would refuse the subscription: say so instead
|
|
796
|
+
// of asking.
|
|
797
|
+
if (canRead) {
|
|
798
|
+
subscribe();
|
|
799
|
+
}
|