waybetter 0.1.1 → 0.7.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/README.md +139 -6
- package/bin/waybetter.mjs +43 -0
- package/build/runtime.mjs +105 -0
- package/cli/buildenv.mjs +130 -0
- package/cli/dev.mjs +97 -0
- package/cli/i18n.mjs +382 -0
- package/cli/manifests.mjs +383 -0
- package/cli/release.mjs +437 -0
- package/cli/releasecheck.mjs +15 -0
- package/cli/remote.mjs +260 -0
- package/cli/store.mjs +378 -0
- package/cli/strings.mjs +123 -0
- package/dist/runtime/column.d.ts +93 -0
- package/dist/runtime/column.js +451 -0
- package/dist/runtime/column.js.map +1 -0
- package/dist/runtime/commandReport.d.ts +19 -0
- package/dist/runtime/commandReport.js +47 -0
- package/dist/runtime/commandReport.js.map +1 -0
- package/dist/runtime/commands.d.ts +72 -0
- package/dist/runtime/commands.js +214 -0
- package/dist/runtime/commands.js.map +1 -0
- package/dist/runtime/devErrors.d.ts +16 -0
- package/dist/runtime/devErrors.js +30 -0
- package/dist/runtime/devErrors.js.map +1 -0
- package/dist/runtime/domHub.d.ts +21 -0
- package/dist/runtime/domHub.js +139 -4
- package/dist/runtime/domHub.js.map +1 -1
- package/dist/runtime/featureModules.d.ts +157 -0
- package/dist/runtime/featureModules.js +484 -0
- package/dist/runtime/featureModules.js.map +1 -0
- package/dist/runtime/featureTable.d.ts +144 -0
- package/dist/runtime/featureTable.js +223 -0
- package/dist/runtime/featureTable.js.map +1 -0
- package/dist/runtime/fonts.d.ts +22 -0
- package/dist/runtime/fonts.js +72 -0
- package/dist/runtime/fonts.js.map +1 -0
- package/dist/runtime/i18n.d.ts +138 -0
- package/dist/runtime/i18n.js +614 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/i18nCheck.d.ts +30 -0
- package/dist/runtime/i18nCheck.js +224 -0
- package/dist/runtime/i18nCheck.js.map +1 -0
- package/dist/runtime/pageLang.d.ts +8 -0
- package/dist/runtime/pageLang.js +64 -0
- package/dist/runtime/pageLang.js.map +1 -0
- package/dist/runtime/pageStrings.d.ts +35 -0
- package/dist/runtime/pageStrings.js +76 -0
- package/dist/runtime/pageStrings.js.map +1 -0
- package/dist/runtime/panelButton.d.ts +33 -0
- package/dist/runtime/panelButton.js +83 -0
- package/dist/runtime/panelButton.js.map +1 -0
- package/dist/runtime/permissions.d.ts +47 -0
- package/dist/runtime/permissions.js +141 -0
- package/dist/runtime/permissions.js.map +1 -0
- package/dist/runtime/pluginApi.d.ts +51 -2
- package/dist/runtime/pluginApi.js +137 -22
- package/dist/runtime/pluginApi.js.map +1 -1
- package/dist/runtime/pluginI18n.d.ts +29 -0
- package/dist/runtime/pluginI18n.js +119 -0
- package/dist/runtime/pluginI18n.js.map +1 -0
- package/dist/runtime/settingsForm.d.ts +66 -0
- package/dist/runtime/settingsForm.js +114 -0
- package/dist/runtime/settingsForm.js.map +1 -0
- package/dist/runtime/site.d.ts +5 -0
- package/dist/runtime/site.js +1 -1
- package/dist/runtime/site.js.map +1 -1
- package/dist/runtime/siteRuntime.d.ts +144 -0
- package/dist/runtime/siteRuntime.js +175 -0
- package/dist/runtime/siteRuntime.js.map +1 -0
- package/dist/runtime/stringPackHook.d.ts +84 -0
- package/dist/runtime/stringPackHook.js +220 -0
- package/dist/runtime/stringPackHook.js.map +1 -0
- package/dist/runtime/stringPackTranslate.d.ts +61 -0
- package/dist/runtime/stringPackTranslate.js +267 -0
- package/dist/runtime/stringPackTranslate.js.map +1 -0
- package/dist/runtime/stringPacks.d.ts +248 -0
- package/dist/runtime/stringPacks.js +610 -0
- package/dist/runtime/stringPacks.js.map +1 -0
- package/dist/runtime/styles.d.ts +15 -0
- package/dist/runtime/styles.js +81 -0
- package/dist/runtime/styles.js.map +1 -0
- package/dist/runtime/textNet.d.ts +15 -0
- package/dist/runtime/textNet.js +131 -0
- package/dist/runtime/textNet.js.map +1 -0
- package/dist/runtime/themeApi.d.ts +121 -0
- package/dist/runtime/themeApi.js +220 -0
- package/dist/runtime/themeApi.js.map +1 -0
- package/dist/runtime/themeBar.d.ts +28 -0
- package/dist/runtime/themeBar.js +82 -0
- package/dist/runtime/themeBar.js.map +1 -0
- package/dist/runtime/themeChoice.d.ts +52 -0
- package/dist/runtime/themeChoice.js +69 -0
- package/dist/runtime/themeChoice.js.map +1 -0
- package/dist/runtime/themeComponents.d.ts +435 -0
- package/dist/runtime/themeComponents.js +476 -0
- package/dist/runtime/themeComponents.js.map +1 -0
- package/dist/runtime/themeDecor.d.ts +73 -0
- package/dist/runtime/themeDecor.js +133 -0
- package/dist/runtime/themeDecor.js.map +1 -0
- package/dist/runtime/themeDerive.d.ts +69 -0
- package/dist/runtime/themeDerive.js +117 -0
- package/dist/runtime/themeDerive.js.map +1 -0
- package/dist/runtime/themeDoctor.d.ts +52 -0
- package/dist/runtime/themeDoctor.js +112 -0
- package/dist/runtime/themeDoctor.js.map +1 -0
- package/dist/runtime/themeGuards.d.ts +60 -0
- package/dist/runtime/themeGuards.js +235 -0
- package/dist/runtime/themeGuards.js.map +1 -0
- package/dist/runtime/themeIcons.d.ts +130 -0
- package/dist/runtime/themeIcons.js +271 -0
- package/dist/runtime/themeIcons.js.map +1 -0
- package/dist/runtime/themeInk.d.ts +37 -0
- package/dist/runtime/themeInk.js +111 -0
- package/dist/runtime/themeInk.js.map +1 -0
- package/dist/runtime/themeInvariants.d.ts +39 -0
- package/dist/runtime/themeInvariants.js +273 -0
- package/dist/runtime/themeInvariants.js.map +1 -0
- package/dist/runtime/themeRuntime.d.ts +114 -0
- package/dist/runtime/themeRuntime.js +570 -0
- package/dist/runtime/themeRuntime.js.map +1 -0
- package/dist/runtime/themeSchema.d.ts +10 -0
- package/dist/runtime/themeSchema.js +150 -0
- package/dist/runtime/themeSchema.js.map +1 -0
- package/dist/runtime/themeSpec.d.ts +263 -0
- package/dist/runtime/themeSpec.js +435 -0
- package/dist/runtime/themeSpec.js.map +1 -0
- package/dist/runtime/themeTokens.d.ts +12 -0
- package/dist/runtime/themeTokens.js +14 -6
- package/dist/runtime/themeTokens.js.map +1 -1
- package/dist/runtime/themeVariants.d.ts +30 -0
- package/dist/runtime/themeVariants.js +53 -0
- package/dist/runtime/themeVariants.js.map +1 -0
- package/dist/runtime/ui.d.ts +192 -0
- package/dist/runtime/ui.js +926 -0
- package/dist/runtime/ui.js.map +1 -0
- package/dist/shell/InstallDialog.d.ts +40 -0
- package/dist/shell/InstallDialog.js +176 -0
- package/dist/shell/InstallDialog.js.map +1 -0
- package/dist/shell/StoreView.d.ts +78 -0
- package/dist/shell/StoreView.js +108 -0
- package/dist/shell/StoreView.js.map +1 -0
- package/dist/shell/addressModel.js.map +1 -1
- package/dist/shell/chrome.js.map +1 -1
- package/dist/shell/deckModel.d.ts +117 -0
- package/dist/shell/deckModel.js +267 -0
- package/dist/shell/deckModel.js.map +1 -0
- package/dist/shell/frame/AccountSwitcher.d.ts +42 -0
- package/dist/shell/frame/AccountSwitcher.js +243 -0
- package/dist/shell/frame/AccountSwitcher.js.map +1 -0
- package/dist/shell/frame/AddColumnDialog.d.ts +8 -0
- package/dist/shell/frame/AddColumnDialog.js +81 -0
- package/dist/shell/frame/AddColumnDialog.js.map +1 -0
- package/dist/shell/frame/AppMenu.d.ts +44 -0
- package/dist/shell/frame/AppMenu.js +721 -0
- package/dist/shell/frame/AppMenu.js.map +1 -0
- package/dist/shell/frame/CommandPalette.d.ts +7 -0
- package/dist/shell/frame/CommandPalette.js +118 -0
- package/dist/shell/frame/CommandPalette.js.map +1 -0
- package/dist/shell/frame/DeckView.d.ts +13 -0
- package/dist/shell/frame/DeckView.js +287 -0
- package/dist/shell/frame/DeckView.js.map +1 -0
- package/dist/shell/frame/FindBar.d.ts +10 -0
- package/dist/shell/frame/FindBar.js +108 -0
- package/dist/shell/frame/FindBar.js.map +1 -0
- package/dist/shell/frame/MediaPanel.d.ts +8 -0
- package/dist/shell/frame/MediaPanel.js +76 -0
- package/dist/shell/frame/MediaPanel.js.map +1 -0
- package/dist/shell/frame/NewTabPage.d.ts +9 -0
- package/dist/shell/frame/NewTabPage.js +71 -0
- package/dist/shell/frame/NewTabPage.js.map +1 -0
- package/dist/shell/frame/PaneTheme.d.ts +16 -0
- package/dist/shell/frame/PaneTheme.js +67 -0
- package/dist/shell/frame/PaneTheme.js.map +1 -0
- package/dist/shell/frame/ReportDialog.d.ts +3 -0
- package/dist/shell/frame/ReportDialog.js +95 -0
- package/dist/shell/frame/ReportDialog.js.map +1 -0
- package/dist/shell/frame/Shell.d.ts +9 -0
- package/dist/shell/frame/Shell.js +1139 -0
- package/dist/shell/frame/Shell.js.map +1 -0
- package/dist/shell/frame/Titlebar.d.ts +17 -0
- package/dist/shell/frame/Titlebar.js +79 -0
- package/dist/shell/frame/Titlebar.js.map +1 -0
- package/dist/shell/frame/TitlebarExtras.d.ts +4 -0
- package/dist/shell/frame/TitlebarExtras.js +202 -0
- package/dist/shell/frame/TitlebarExtras.js.map +1 -0
- package/dist/shell/frame/WebPanel.d.ts +4 -0
- package/dist/shell/frame/WebPanel.js +185 -0
- package/dist/shell/frame/WebPanel.js.map +1 -0
- package/dist/shell/frame/accountModel.d.ts +49 -0
- package/dist/shell/frame/accountModel.js +67 -0
- package/dist/shell/frame/accountModel.js.map +1 -0
- package/dist/shell/frame/accountSwitcher.css +91 -0
- package/dist/shell/frame/appI18n.d.ts +44 -0
- package/dist/shell/frame/appI18n.js +64 -0
- package/dist/shell/frame/appI18n.js.map +1 -0
- package/dist/shell/frame/appMenu.css +127 -0
- package/dist/shell/frame/backupModel.d.ts +38 -0
- package/dist/shell/frame/backupModel.js +192 -0
- package/dist/shell/frame/backupModel.js.map +1 -0
- package/dist/shell/frame/boot.d.ts +8 -0
- package/dist/shell/frame/boot.js +37 -0
- package/dist/shell/frame/boot.js.map +1 -0
- package/dist/shell/frame/brand.d.ts +1 -0
- package/dist/shell/frame/brand.js +4 -0
- package/dist/shell/frame/brand.js.map +1 -0
- package/dist/shell/frame/bridge.d.ts +35 -0
- package/dist/shell/frame/bridge.js +70 -0
- package/dist/shell/frame/bridge.js.map +1 -0
- package/dist/shell/frame/chromeTheme.d.ts +47 -0
- package/dist/shell/frame/chromeTheme.js +118 -0
- package/dist/shell/frame/chromeTheme.js.map +1 -0
- package/dist/shell/frame/deck.css +209 -0
- package/dist/shell/frame/deckStore.d.ts +89 -0
- package/dist/shell/frame/deckStore.js +239 -0
- package/dist/shell/frame/deckStore.js.map +1 -0
- package/dist/shell/frame/devHost.d.ts +35 -0
- package/dist/shell/frame/devHost.js +80 -0
- package/dist/shell/frame/devHost.js.map +1 -0
- package/dist/shell/frame/devModel.d.ts +7 -0
- package/dist/shell/frame/devModel.js +17 -0
- package/dist/shell/frame/devModel.js.map +1 -0
- package/dist/shell/frame/diag.d.ts +7 -0
- package/dist/shell/frame/diag.js +39 -0
- package/dist/shell/frame/diag.js.map +1 -0
- package/dist/shell/frame/edges.css +159 -0
- package/dist/shell/frame/findBar.css +14 -0
- package/dist/shell/frame/frameMessages.d.ts +248 -0
- package/dist/shell/frame/frameMessages.js +252 -0
- package/dist/shell/frame/frameMessages.js.map +1 -0
- package/dist/shell/frame/hooks.d.ts +39 -0
- package/dist/shell/frame/hooks.js +97 -0
- package/dist/shell/frame/hooks.js.map +1 -0
- package/dist/shell/frame/i18n.d.ts +27 -0
- package/dist/shell/frame/i18n.js +66 -0
- package/dist/shell/frame/i18n.js.map +1 -0
- package/dist/shell/frame/iconPack.d.ts +11 -0
- package/dist/shell/frame/iconPack.js +45 -0
- package/dist/shell/frame/iconPack.js.map +1 -0
- package/dist/shell/frame/icons.d.ts +71 -0
- package/dist/shell/frame/icons.js +75 -0
- package/dist/shell/frame/icons.js.map +1 -0
- package/dist/shell/frame/index.d.ts +34 -0
- package/dist/shell/frame/index.js +43 -0
- package/dist/shell/frame/index.js.map +1 -0
- package/dist/shell/frame/language.d.ts +58 -0
- package/dist/shell/frame/language.js +165 -0
- package/dist/shell/frame/language.js.map +1 -0
- package/dist/shell/frame/mediaModel.d.ts +47 -0
- package/dist/shell/frame/mediaModel.js +67 -0
- package/dist/shell/frame/mediaModel.js.map +1 -0
- package/dist/shell/frame/menuMessages.en.json +96 -0
- package/dist/shell/frame/menuModel.d.ts +319 -0
- package/dist/shell/frame/menuModel.js +301 -0
- package/dist/shell/frame/menuModel.js.map +1 -0
- package/dist/shell/frame/nativeMenu.d.ts +5 -0
- package/dist/shell/frame/nativeMenu.js +178 -0
- package/dist/shell/frame/nativeMenu.js.map +1 -0
- package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
- package/dist/shell/frame/nativeMenuModel.js +186 -0
- package/dist/shell/frame/nativeMenuModel.js.map +1 -0
- package/dist/shell/frame/paletteModel.d.ts +139 -0
- package/dist/shell/frame/paletteModel.js +114 -0
- package/dist/shell/frame/paletteModel.js.map +1 -0
- package/dist/shell/frame/paneTheme.css +9 -0
- package/dist/shell/frame/pluginCommandStore.d.ts +23 -0
- package/dist/shell/frame/pluginCommandStore.js +70 -0
- package/dist/shell/frame/pluginCommandStore.js.map +1 -0
- package/dist/shell/frame/reportModel.d.ts +52 -0
- package/dist/shell/frame/reportModel.js +105 -0
- package/dist/shell/frame/reportModel.js.map +1 -0
- package/dist/shell/frame/safeStore.d.ts +7 -0
- package/dist/shell/frame/safeStore.js +40 -0
- package/dist/shell/frame/safeStore.js.map +1 -0
- package/dist/shell/frame/searchInput.d.ts +2 -0
- package/dist/shell/frame/searchInput.js +11 -0
- package/dist/shell/frame/searchInput.js.map +1 -0
- package/dist/shell/frame/shell.css +634 -0
- package/dist/shell/frame/site.d.ts +206 -0
- package/dist/shell/frame/site.js +22 -0
- package/dist/shell/frame/site.js.map +1 -0
- package/dist/shell/frame/state.d.ts +101 -0
- package/dist/shell/frame/state.js +286 -0
- package/dist/shell/frame/state.js.map +1 -0
- package/dist/shell/frame/syncGate.d.ts +10 -0
- package/dist/shell/frame/syncGate.js +36 -0
- package/dist/shell/frame/syncGate.js.map +1 -0
- package/dist/shell/frame/syncHost.d.ts +35 -0
- package/dist/shell/frame/syncHost.js +85 -0
- package/dist/shell/frame/syncHost.js.map +1 -0
- package/dist/shell/frame/tabStill.d.ts +2 -0
- package/dist/shell/frame/tabStill.js +62 -0
- package/dist/shell/frame/tabStill.js.map +1 -0
- package/dist/shell/frame/themeScopes.d.ts +29 -0
- package/dist/shell/frame/themeScopes.js +61 -0
- package/dist/shell/frame/themeScopes.js.map +1 -0
- package/dist/shell/frame/themeStore.d.ts +99 -0
- package/dist/shell/frame/themeStore.js +267 -0
- package/dist/shell/frame/themeStore.js.map +1 -0
- package/dist/shell/frame/windowBinding.d.ts +2 -0
- package/dist/shell/frame/windowBinding.js +7 -0
- package/dist/shell/frame/windowBinding.js.map +1 -0
- package/dist/shell/frame/workspaceModel.d.ts +82 -0
- package/dist/shell/frame/workspaceModel.js +250 -0
- package/dist/shell/frame/workspaceModel.js.map +1 -0
- package/dist/shell/frame/workspaces.css +84 -0
- package/dist/shell/frame/workspaces.d.ts +22 -0
- package/dist/shell/frame/workspaces.js +125 -0
- package/dist/shell/frame/workspaces.js.map +1 -0
- package/dist/shell/frame/zoomStore.d.ts +10 -0
- package/dist/shell/frame/zoomStore.js +65 -0
- package/dist/shell/frame/zoomStore.js.map +1 -0
- package/dist/shell/installController.d.ts +137 -0
- package/dist/shell/installController.js +172 -0
- package/dist/shell/installController.js.map +1 -0
- package/dist/shell/installerMessages.d.ts +30 -0
- package/dist/shell/installerMessages.js +36 -0
- package/dist/shell/installerMessages.js.map +1 -0
- package/dist/shell/linkDrop.js.map +1 -1
- package/dist/shell/messages.d.ts +16 -0
- package/dist/shell/messages.js +27 -0
- package/dist/shell/messages.js.map +1 -0
- package/dist/shell/palette.js.map +1 -1
- package/dist/shell/paneModel.d.ts +22 -0
- package/dist/shell/paneModel.js +24 -1
- package/dist/shell/paneModel.js.map +1 -1
- package/dist/shell/panel/Appearance.d.ts +8 -0
- package/dist/shell/panel/Appearance.js +265 -0
- package/dist/shell/panel/Appearance.js.map +1 -0
- package/dist/shell/panel/BackupPanel.d.ts +5 -0
- package/dist/shell/panel/BackupPanel.js +198 -0
- package/dist/shell/panel/BackupPanel.js.map +1 -0
- package/dist/shell/panel/DeveloperPanel.d.ts +4 -0
- package/dist/shell/panel/DeveloperPanel.js +51 -0
- package/dist/shell/panel/DeveloperPanel.js.map +1 -0
- package/dist/shell/panel/FeatureToggles.d.ts +13 -0
- package/dist/shell/panel/FeatureToggles.js +42 -0
- package/dist/shell/panel/FeatureToggles.js.map +1 -0
- package/dist/shell/panel/Home.d.ts +3 -0
- package/dist/shell/panel/Home.js +23 -0
- package/dist/shell/panel/Home.js.map +1 -0
- package/dist/shell/panel/IconPackInstall.d.ts +15 -0
- package/dist/shell/panel/IconPackInstall.js +41 -0
- package/dist/shell/panel/IconPackInstall.js.map +1 -0
- package/dist/shell/panel/LanguageSettings.d.ts +1 -0
- package/dist/shell/panel/LanguageSettings.js +54 -0
- package/dist/shell/panel/LanguageSettings.js.map +1 -0
- package/dist/shell/panel/MakePanel.d.ts +3 -0
- package/dist/shell/panel/MakePanel.js +237 -0
- package/dist/shell/panel/MakePanel.js.map +1 -0
- package/dist/shell/panel/MarkdownDoc.d.ts +6 -0
- package/dist/shell/panel/MarkdownDoc.js +35 -0
- package/dist/shell/panel/MarkdownDoc.js.map +1 -0
- package/dist/shell/panel/Modes.d.ts +40 -0
- package/dist/shell/panel/Modes.js +270 -0
- package/dist/shell/panel/Modes.js.map +1 -0
- package/dist/shell/panel/NavIcon.d.ts +4 -0
- package/dist/shell/panel/NavIcon.js +6 -0
- package/dist/shell/panel/NavIcon.js.map +1 -0
- package/dist/shell/panel/Panel.d.ts +7 -0
- package/dist/shell/panel/Panel.js +139 -0
- package/dist/shell/panel/Panel.js.map +1 -0
- package/dist/shell/panel/PluginFeatures.d.ts +24 -0
- package/dist/shell/panel/PluginFeatures.js +165 -0
- package/dist/shell/panel/PluginFeatures.js.map +1 -0
- package/dist/shell/panel/Plugins.d.ts +33 -0
- package/dist/shell/panel/Plugins.js +112 -0
- package/dist/shell/panel/Plugins.js.map +1 -0
- package/dist/shell/panel/PowerSettings.d.ts +4 -0
- package/dist/shell/panel/PowerSettings.js +52 -0
- package/dist/shell/panel/PowerSettings.js.map +1 -0
- package/dist/shell/panel/Settings.d.ts +23 -0
- package/dist/shell/panel/Settings.js +117 -0
- package/dist/shell/panel/Settings.js.map +1 -0
- package/dist/shell/panel/ShareSetup.d.ts +8 -0
- package/dist/shell/panel/ShareSetup.js +130 -0
- package/dist/shell/panel/ShareSetup.js.map +1 -0
- package/dist/shell/panel/StoreSection.d.ts +7 -0
- package/dist/shell/panel/StoreSection.js +78 -0
- package/dist/shell/panel/StoreSection.js.map +1 -0
- package/dist/shell/panel/StringPacks.d.ts +20 -0
- package/dist/shell/panel/StringPacks.js +131 -0
- package/dist/shell/panel/StringPacks.js.map +1 -0
- package/dist/shell/panel/ThemeBuilder.d.ts +5 -0
- package/dist/shell/panel/ThemeBuilder.js +205 -0
- package/dist/shell/panel/ThemeBuilder.js.map +1 -0
- package/dist/shell/panel/backup.css +34 -0
- package/dist/shell/panel/builder.css +240 -0
- package/dist/shell/panel/builderModel.d.ts +55 -0
- package/dist/shell/panel/builderModel.js +95 -0
- package/dist/shell/panel/builderModel.js.map +1 -0
- package/dist/shell/panel/compare.d.ts +10 -0
- package/dist/shell/panel/compare.js +51 -0
- package/dist/shell/panel/compare.js.map +1 -0
- package/dist/shell/panel/controls.d.ts +135 -0
- package/dist/shell/panel/controls.js +213 -0
- package/dist/shell/panel/controls.js.map +1 -0
- package/dist/shell/panel/featureModel.d.ts +69 -0
- package/dist/shell/panel/featureModel.js +79 -0
- package/dist/shell/panel/featureModel.js.map +1 -0
- package/dist/shell/panel/fonts/FontBrowser.d.ts +10 -0
- package/dist/shell/panel/fonts/FontBrowser.js +189 -0
- package/dist/shell/panel/fonts/FontBrowser.js.map +1 -0
- package/dist/shell/panel/fonts/FontRoles.d.ts +20 -0
- package/dist/shell/panel/fonts/FontRoles.js +77 -0
- package/dist/shell/panel/fonts/FontRoles.js.map +1 -0
- package/dist/shell/panel/fonts/catalog.d.ts +37 -0
- package/dist/shell/panel/fonts/catalog.js +68 -0
- package/dist/shell/panel/fonts/catalog.js.map +1 -0
- package/dist/shell/panel/fonts/googleFontsCatalog.json +1 -0
- package/dist/shell/panel/index.d.ts +20 -0
- package/dist/shell/panel/index.js +34 -0
- package/dist/shell/panel/index.js.map +1 -0
- package/dist/shell/panel/languageModel.d.ts +5 -0
- package/dist/shell/panel/languageModel.js +7 -0
- package/dist/shell/panel/languageModel.js.map +1 -0
- package/dist/shell/panel/make.css +73 -0
- package/dist/shell/panel/makeModel.d.ts +32 -0
- package/dist/shell/panel/makeModel.js +84 -0
- package/dist/shell/panel/makeModel.js.map +1 -0
- package/dist/shell/panel/markdown.d.ts +46 -0
- package/dist/shell/panel/markdown.js +100 -0
- package/dist/shell/panel/markdown.js.map +1 -0
- package/dist/shell/panel/nav.d.ts +12 -0
- package/dist/shell/panel/nav.js +50 -0
- package/dist/shell/panel/nav.js.map +1 -0
- package/dist/shell/panel/panel.css +401 -0
- package/dist/shell/panel/panelMessages.d.ts +803 -0
- package/dist/shell/panel/panelMessages.js +779 -0
- package/dist/shell/panel/panelMessages.js.map +1 -0
- package/dist/shell/panel/plugins.css +59 -0
- package/dist/shell/panel/setupModel.d.ts +87 -0
- package/dist/shell/panel/setupModel.js +152 -0
- package/dist/shell/panel/setupModel.js.map +1 -0
- package/dist/shell/panel/site.d.ts +259 -0
- package/dist/shell/panel/site.js +25 -0
- package/dist/shell/panel/site.js.map +1 -0
- package/dist/shell/panel/strings.css +7 -0
- package/dist/shell/panel/themes.d.ts +10 -0
- package/dist/shell/panel/themes.js +8 -0
- package/dist/shell/panel/themes.js.map +1 -0
- package/dist/shell/profileModel.d.ts +3 -2
- package/dist/shell/profileModel.js +7 -5
- package/dist/shell/profileModel.js.map +1 -1
- package/dist/shell/store.css +250 -0
- package/dist/shell/storeMessages.d.ts +193 -0
- package/dist/shell/storeMessages.js +196 -0
- package/dist/shell/storeMessages.js.map +1 -0
- package/dist/shell/storeModel.d.ts +205 -0
- package/dist/shell/storeModel.js +146 -0
- package/dist/shell/storeModel.js.map +1 -0
- package/dist/shell/stringPacksModel.d.ts +44 -0
- package/dist/shell/stringPacksModel.js +99 -0
- package/dist/shell/stringPacksModel.js.map +1 -0
- package/dist/shell/updateModel.d.ts +2 -1
- package/dist/shell/updateModel.js +5 -4
- package/dist/shell/updateModel.js.map +1 -1
- package/dist/shell/windowControls.d.ts +4 -0
- package/dist/shell/windowControls.js +5 -0
- package/dist/shell/windowControls.js.map +1 -1
- package/i18n-config.schema.json +29 -0
- package/i18n-meta.schema.json +26 -0
- package/i18n.schema.json +42 -0
- package/icons.schema.json +655 -0
- package/languages.schema.json +23 -0
- package/locales/ar-x-fm.json +11 -0
- package/locales/ar.json +1388 -0
- package/locales/bg.json +1387 -0
- package/locales/bn.json +1387 -0
- package/locales/ca.json +1387 -0
- package/locales/cs.json +1387 -0
- package/locales/da.json +1387 -0
- package/locales/de.json +1387 -0
- package/locales/el.json +1387 -0
- package/locales/en-GB.json +27 -0
- package/locales/en.json +1386 -0
- package/locales/en.meta.json +51 -0
- package/locales/es.json +1387 -0
- package/locales/eu.json +1387 -0
- package/locales/fa.json +1388 -0
- package/locales/fi.json +1387 -0
- package/locales/fil.json +1387 -0
- package/locales/fr.json +1387 -0
- package/locales/ga.json +1387 -0
- package/locales/gl.json +1387 -0
- package/locales/gu.json +1387 -0
- package/locales/ha.json +1387 -0
- package/locales/he.json +1388 -0
- package/locales/hi.json +1387 -0
- package/locales/hr.json +1387 -0
- package/locales/hu.json +1387 -0
- package/locales/id.json +1387 -0
- package/locales/ig.json +1387 -0
- package/locales/it.json +1387 -0
- package/locales/ja.json +1387 -0
- package/locales/kn.json +1387 -0
- package/locales/ko.json +1387 -0
- package/locales/mr.json +1387 -0
- package/locales/ms.json +1387 -0
- package/locales/nb.json +1387 -0
- package/locales/nl.json +1387 -0
- package/locales/pl.json +1387 -0
- package/locales/pt.json +1387 -0
- package/locales/ro.json +1387 -0
- package/locales/ru.json +1387 -0
- package/locales/sk.json +1387 -0
- package/locales/sr.json +1387 -0
- package/locales/sv.json +1387 -0
- package/locales/ta.json +1387 -0
- package/locales/th.json +1387 -0
- package/locales/tr.json +1387 -0
- package/locales/uk.json +1387 -0
- package/locales/ur.json +1388 -0
- package/locales/vi.json +1387 -0
- package/locales/yo.json +1387 -0
- package/locales/zh-Hans.json +1387 -0
- package/locales/zh-Hant.json +1387 -0
- package/package.json +138 -5
- package/store-catalog.schema.json +78 -0
- package/theme.schema.json +4668 -0
- package/windows/installer.nsi +946 -0
- package/windows/languages/Basque.nsh +28 -0
- package/windows/languages/Catalan.nsh +28 -0
- package/windows/languages/Croatian.nsh +28 -0
- package/windows/languages/Czech.nsh +28 -0
- package/windows/languages/Danish.nsh +28 -0
- package/windows/languages/Finnish.nsh +28 -0
- package/windows/languages/Galician.nsh +28 -0
- package/windows/languages/Greek.nsh +28 -0
- package/windows/languages/Hungarian.nsh +28 -0
- package/windows/languages/Indonesian.nsh +28 -0
- package/windows/languages/Irish.nsh +28 -0
- package/windows/languages/Malay.nsh +28 -0
- package/windows/languages/Polish.nsh +28 -0
- package/windows/languages/Romanian.nsh +28 -0
- package/windows/languages/Serbian.nsh +28 -0
- package/windows/languages/Slovak.nsh +28 -0
- package/windows/languages/Thai.nsh +28 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The page runtime's language. Native code hands site tabs the owner's language and exactly the
|
|
2
|
+
// strings the page needs (the app's own page strings, the plugin UI kit's `kit.*` and the bundled
|
|
3
|
+
// plugins' `plugin.*`; waybetter-host `i18n::page_script`) as `<global>.i18n.set({ locale, dir,
|
|
4
|
+
// messages })`, queued in `__wbI18n` until the runtime exists. The messages arrive already resolved
|
|
5
|
+
// along the language's fallback chain by the shell; English is built into the runtime (the build
|
|
6
|
+
// keeps only those prefixes of the app's catalog). A switch applies live: `onChange` listeners
|
|
7
|
+
// re-render. Everything delivered is data: checked, bounded and inserted as text.
|
|
8
|
+
import { formatMessage, normalizeLocale } from "./i18n.js";
|
|
9
|
+
import { pluginNamespace } from "./pluginI18n.js";
|
|
10
|
+
/** A delivery holds at most this many messages, each at most this long. */
|
|
11
|
+
export const PAGE_STRING_LIMITS = Object.freeze({ keys: 4000, value: 2000 });
|
|
12
|
+
export function createPageStrings(options) {
|
|
13
|
+
const { english, prefixes, label, bundledPrefix } = options;
|
|
14
|
+
let delivered = {};
|
|
15
|
+
let active = 'en';
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
const message = (key) => delivered[key] ?? english[key];
|
|
18
|
+
const t = (key, params) => {
|
|
19
|
+
const text = message(key);
|
|
20
|
+
return text === undefined ? key : formatMessage(text, params ?? {}, active);
|
|
21
|
+
};
|
|
22
|
+
function set(value) {
|
|
23
|
+
if (!value || typeof value !== 'object')
|
|
24
|
+
return;
|
|
25
|
+
const input = value;
|
|
26
|
+
const tag = normalizeLocale(input.locale);
|
|
27
|
+
if (!tag || !input.messages || typeof input.messages !== 'object')
|
|
28
|
+
return;
|
|
29
|
+
const next = {};
|
|
30
|
+
let count = 0;
|
|
31
|
+
for (const [key, text] of Object.entries(input.messages)) {
|
|
32
|
+
if (count >= PAGE_STRING_LIMITS.keys)
|
|
33
|
+
break;
|
|
34
|
+
if (typeof text !== 'string' || text.length > PAGE_STRING_LIMITS.value || !prefixes.some(prefix => key.startsWith(prefix)))
|
|
35
|
+
continue;
|
|
36
|
+
next[key] = text;
|
|
37
|
+
count += 1;
|
|
38
|
+
}
|
|
39
|
+
const changed = tag !== active || JSON.stringify(next) !== JSON.stringify(delivered);
|
|
40
|
+
delivered = next;
|
|
41
|
+
active = tag;
|
|
42
|
+
if (!changed)
|
|
43
|
+
return;
|
|
44
|
+
for (const listener of [...listeners]) {
|
|
45
|
+
try {
|
|
46
|
+
listener();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`[${label}] language listener failed`, error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const onChange = (listener) => {
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
return () => { listeners.delete(listener); };
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
message,
|
|
59
|
+
t,
|
|
60
|
+
locale: () => active,
|
|
61
|
+
set,
|
|
62
|
+
onChange,
|
|
63
|
+
source: { locale: () => active, message, onChange },
|
|
64
|
+
// Bundled plugins are installed as `<bundledPrefix><name>.plugin.js`, but their strings are keyed
|
|
65
|
+
// by their source name: without this, every bundled plugin would stay English.
|
|
66
|
+
pluginNamespace(id) {
|
|
67
|
+
const namespace = pluginNamespace(id);
|
|
68
|
+
if (!bundledPrefix || !namespace.startsWith(bundledPrefix))
|
|
69
|
+
return namespace;
|
|
70
|
+
const bare = namespace.slice(bundledPrefix.length);
|
|
71
|
+
const prefix = `plugin.${bare}.`;
|
|
72
|
+
return Object.keys(english).some(key => key.startsWith(prefix)) ? bare : namespace;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=pageStrings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageStrings.js","sourceRoot":"","sources":["../../../runtime/src/pageStrings.ts"],"names":[],"mappings":"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","sourcesContent":["// The page runtime's language. Native code hands site tabs the owner's language and exactly the\n// strings the page needs (the app's own page strings, the plugin UI kit's `kit.*` and the bundled\n// plugins' `plugin.*`; waybetter-host `i18n::page_script`) as `<global>.i18n.set({ locale, dir,\n// messages })`, queued in `__wbI18n` until the runtime exists. The messages arrive already resolved\n// along the language's fallback chain by the shell; English is built into the runtime (the build\n// keeps only those prefixes of the app's catalog). A switch applies live: `onChange` listeners\n// re-render. Everything delivered is data: checked, bounded and inserted as text.\n\nimport { formatMessage, normalizeLocale, type Params } from './i18n.ts';\nimport { pluginNamespace, type I18nSource } from './pluginI18n.ts';\n\nexport type PageStringsOptions = {\n /** English for every string the page shows (the app's page strings and the framework's kit words). */\n english: Readonly<Record<string, string>>;\n /** Key prefixes a delivery may carry (`['x.', 'kit.', 'plugin.']`); other keys are dropped. */\n prefixes: readonly string[];\n /** Console label (the app's name). */\n label: string;\n /**\n * The file-name prefix the app installs its bundled plugins with (`bt-` for `bt-<name>.plugin.js`)\n * when their strings are keyed by the source name (`plugin.<name>.*`).\n */\n bundledPrefix?: string;\n};\n\nexport type PageStrings = {\n /** The active language's message for `key`, else English. */\n message(key: string): string | undefined;\n /** A runtime string: `t('x.media.download')`. Placeholder values are inserted as text. */\n t(key: string, params?: Params): string;\n locale(): string;\n /** Native code delivers `{ locale, dir, messages }`; anything malformed is ignored (English stays). */\n set(value: unknown): void;\n onChange(listener: () => void): () => void;\n /** The plugin API's view of the language (pluginI18n.ts): `api.i18n` and the UI kit's words. */\n source: I18nSource;\n /** A plugin file's catalog namespace (`plugin.<namespace>.*`). */\n pluginNamespace(id: string): string;\n};\n\n/** A delivery holds at most this many messages, each at most this long. */\nexport const PAGE_STRING_LIMITS = Object.freeze({ keys: 4000, value: 2000 });\n\nexport function createPageStrings(options: PageStringsOptions): PageStrings {\n const { english, prefixes, label, bundledPrefix } = options;\n let delivered: Record<string, string> = {};\n let active = 'en';\n const listeners = new Set<() => void>();\n\n const message = (key: string) => delivered[key] ?? english[key];\n const t = (key: string, params?: Params) => {\n const text = message(key);\n return text === undefined ? key : formatMessage(text, params ?? {}, active);\n };\n\n function set(value: unknown) {\n if (!value || typeof value !== 'object') return;\n const input = value as { locale?: unknown; messages?: unknown };\n const tag = normalizeLocale(input.locale);\n if (!tag || !input.messages || typeof input.messages !== 'object') return;\n const next: Record<string, string> = {};\n let count = 0;\n for (const [key, text] of Object.entries(input.messages as Record<string, unknown>)) {\n if (count >= PAGE_STRING_LIMITS.keys) break;\n if (typeof text !== 'string' || text.length > PAGE_STRING_LIMITS.value || !prefixes.some(prefix => key.startsWith(prefix))) continue;\n next[key] = text;\n count += 1;\n }\n const changed = tag !== active || JSON.stringify(next) !== JSON.stringify(delivered);\n delivered = next;\n active = tag;\n if (!changed) return;\n for (const listener of [...listeners]) {\n try { listener(); } catch (error) { console.error(`[${label}] language listener failed`, error); }\n }\n }\n\n const onChange = (listener: () => void) => {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n };\n\n return {\n message,\n t,\n locale: () => active,\n set,\n onChange,\n source: { locale: () => active, message, onChange },\n // Bundled plugins are installed as `<bundledPrefix><name>.plugin.js`, but their strings are keyed\n // by their source name: without this, every bundled plugin would stay English.\n pluginNamespace(id) {\n const namespace = pluginNamespace(id);\n if (!bundledPrefix || !namespace.startsWith(bundledPrefix)) return namespace;\n const bare = namespace.slice(bundledPrefix.length);\n const prefix = `plugin.${bare}.`;\n return Object.keys(english).some(key => key.startsWith(prefix)) ? bare : namespace;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Gap between the button and the pane edge or the site's bottom bar. */
|
|
2
|
+
export declare const BUTTON_GAP = 16;
|
|
3
|
+
/**
|
|
4
|
+
* Pure: the button's `bottom` offset. `barTop` is the top edge of the site's bottom bar, null when
|
|
5
|
+
* it has none; a zero-height or implausibly tall bar (a desktop layout may keep an empty one at the
|
|
6
|
+
* bottom edge) adds nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buttonBottom(barTop: number | null, viewportHeight: number, gap?: number): number;
|
|
9
|
+
export type PanelButtonSite = {
|
|
10
|
+
/** The floating button's DOM id. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The app's logo (an image URL, usually a data URI: the site's CSP may block others). */
|
|
13
|
+
logo: string;
|
|
14
|
+
/** The button's tooltip and accessible name, in the owner's language. */
|
|
15
|
+
title(): string;
|
|
16
|
+
/** Puts the entry into the site's own navigation (re-attaching it when the site re-renders); true while it is there. */
|
|
17
|
+
mountNav?(open: () => void): boolean;
|
|
18
|
+
/** Pages where the button stays hidden (a full-pane photo or video viewer whose actions sit at the bottom). */
|
|
19
|
+
hiddenOn?(pathname: string): boolean;
|
|
20
|
+
/** The site is showing a modal sheet or a dialog layer: the button hides under it. */
|
|
21
|
+
overlaySelector?: string;
|
|
22
|
+
/** The site's bottom bar (a mobile layout): the button sits above it. */
|
|
23
|
+
bottomBarSelector?: string;
|
|
24
|
+
};
|
|
25
|
+
/** Pure: whether the button stays hidden on this route / with a site overlay open. */
|
|
26
|
+
export declare function buttonHidden(site: Pick<PanelButtonSite, 'hiddenOn'>, pathname: string, overlayOpen: boolean): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Keeps the panel entry on the page: the site's navigation item when `mountNav` places one, else
|
|
29
|
+
* the floating button. `open` opens the panel (the runtime's `open()`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function installPanelButton(win: Window & typeof globalThis, site: PanelButtonSite, open: () => void): {
|
|
32
|
+
sync(): void;
|
|
33
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// The way from a site page into the app's panel. The site adapter puts an entry into the site's own
|
|
2
|
+
// navigation when it has one (`mountNav`: X clones its "More" item); where the page draws no
|
|
3
|
+
// navigation (a full-pane viewer opened by URL, signed-out pages, a mobile layout) a small floating
|
|
4
|
+
// button stands in. The button must never cover the site's own UI: it hides while the site layers
|
|
5
|
+
// something over the page and sits above the site's bottom bar. Both are re-checked on every DOM
|
|
6
|
+
// change through the shared DOM hub.
|
|
7
|
+
import { watchDom } from "./domHub.js";
|
|
8
|
+
/** Gap between the button and the pane edge or the site's bottom bar. */
|
|
9
|
+
export const BUTTON_GAP = 16;
|
|
10
|
+
/**
|
|
11
|
+
* Pure: the button's `bottom` offset. `barTop` is the top edge of the site's bottom bar, null when
|
|
12
|
+
* it has none; a zero-height or implausibly tall bar (a desktop layout may keep an empty one at the
|
|
13
|
+
* bottom edge) adds nothing.
|
|
14
|
+
*/
|
|
15
|
+
export function buttonBottom(barTop, viewportHeight, gap = BUTTON_GAP) {
|
|
16
|
+
if (barTop === null)
|
|
17
|
+
return gap;
|
|
18
|
+
const bar = viewportHeight - barTop;
|
|
19
|
+
return bar > 0 && bar < viewportHeight / 3 ? Math.round(bar) + gap : gap;
|
|
20
|
+
}
|
|
21
|
+
/** Pure: whether the button stays hidden on this route / with a site overlay open. */
|
|
22
|
+
export function buttonHidden(site, pathname, overlayOpen) {
|
|
23
|
+
return overlayOpen || !!site.hiddenOn?.(pathname);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Keeps the panel entry on the page: the site's navigation item when `mountNav` places one, else
|
|
27
|
+
* the floating button. `open` opens the panel (the runtime's `open()`).
|
|
28
|
+
*/
|
|
29
|
+
export function installPanelButton(win, site, open) {
|
|
30
|
+
const doc = win.document;
|
|
31
|
+
const create = () => {
|
|
32
|
+
const button = doc.createElement('button');
|
|
33
|
+
button.id = site.id;
|
|
34
|
+
button.type = 'button';
|
|
35
|
+
button.title = site.title();
|
|
36
|
+
button.setAttribute('aria-label', button.title);
|
|
37
|
+
const img = doc.createElement('img');
|
|
38
|
+
img.src = site.logo;
|
|
39
|
+
img.alt = '';
|
|
40
|
+
Object.assign(img.style, { width: '100%', height: '100%', borderRadius: '22%', display: 'block' });
|
|
41
|
+
button.appendChild(img);
|
|
42
|
+
Object.assign(button.style, {
|
|
43
|
+
position: 'fixed', left: '16px', bottom: '16px', zIndex: '2147483646', width: '40px', height: '40px',
|
|
44
|
+
padding: '0', border: '0', background: 'transparent', cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.35)', borderRadius: '22%',
|
|
45
|
+
});
|
|
46
|
+
button.addEventListener('click', () => open());
|
|
47
|
+
return button;
|
|
48
|
+
};
|
|
49
|
+
const syncButton = (navMounted) => {
|
|
50
|
+
const existing = doc.getElementById(site.id);
|
|
51
|
+
if (navMounted) {
|
|
52
|
+
existing?.remove();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const button = existing ?? create();
|
|
56
|
+
const overlay = site.overlaySelector ? !!doc.querySelector(site.overlaySelector) : false;
|
|
57
|
+
const barTop = site.bottomBarSelector ? doc.querySelector(site.bottomBarSelector)?.getBoundingClientRect().top ?? null : null;
|
|
58
|
+
const display = buttonHidden(site, win.location.pathname, overlay) ? 'none' : 'block';
|
|
59
|
+
const bottom = `${buttonBottom(barTop, win.innerHeight)}px`;
|
|
60
|
+
if (button.style.display !== display)
|
|
61
|
+
button.style.display = display;
|
|
62
|
+
if (button.style.bottom !== bottom)
|
|
63
|
+
button.style.bottom = bottom;
|
|
64
|
+
if (!existing)
|
|
65
|
+
doc.body.appendChild(button);
|
|
66
|
+
};
|
|
67
|
+
const sync = () => {
|
|
68
|
+
if (!doc.body)
|
|
69
|
+
return;
|
|
70
|
+
syncButton(site.mountNav?.(open) ?? false);
|
|
71
|
+
};
|
|
72
|
+
const start = () => {
|
|
73
|
+
sync();
|
|
74
|
+
// Sites re-render their navigation on route and layout changes; re-attach when the entry disappears.
|
|
75
|
+
watchDom(win, sync);
|
|
76
|
+
};
|
|
77
|
+
if (doc.readyState === 'loading')
|
|
78
|
+
doc.addEventListener('DOMContentLoaded', start, { once: true });
|
|
79
|
+
else
|
|
80
|
+
start();
|
|
81
|
+
return { sync };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=panelButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panelButton.js","sourceRoot":"","sources":["../../../runtime/src/panelButton.ts"],"names":[],"mappings":"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","sourcesContent":["// The way from a site page into the app's panel. The site adapter puts an entry into the site's own\n// navigation when it has one (`mountNav`: X clones its \"More\" item); where the page draws no\n// navigation (a full-pane viewer opened by URL, signed-out pages, a mobile layout) a small floating\n// button stands in. The button must never cover the site's own UI: it hides while the site layers\n// something over the page and sits above the site's bottom bar. Both are re-checked on every DOM\n// change through the shared DOM hub.\n\nimport { watchDom, type DomWindow } from './domHub.ts';\n\n/** Gap between the button and the pane edge or the site's bottom bar. */\nexport const BUTTON_GAP = 16;\n\n/**\n * Pure: the button's `bottom` offset. `barTop` is the top edge of the site's bottom bar, null when\n * it has none; a zero-height or implausibly tall bar (a desktop layout may keep an empty one at the\n * bottom edge) adds nothing.\n */\nexport function buttonBottom(barTop: number | null, viewportHeight: number, gap = BUTTON_GAP): number {\n if (barTop === null) return gap;\n const bar = viewportHeight - barTop;\n return bar > 0 && bar < viewportHeight / 3 ? Math.round(bar) + gap : gap;\n}\n\nexport type PanelButtonSite = {\n /** The floating button's DOM id. */\n id: string;\n /** The app's logo (an image URL, usually a data URI: the site's CSP may block others). */\n logo: string;\n /** The button's tooltip and accessible name, in the owner's language. */\n title(): string;\n /** Puts the entry into the site's own navigation (re-attaching it when the site re-renders); true while it is there. */\n mountNav?(open: () => void): boolean;\n /** Pages where the button stays hidden (a full-pane photo or video viewer whose actions sit at the bottom). */\n hiddenOn?(pathname: string): boolean;\n /** The site is showing a modal sheet or a dialog layer: the button hides under it. */\n overlaySelector?: string;\n /** The site's bottom bar (a mobile layout): the button sits above it. */\n bottomBarSelector?: string;\n};\n\n/** Pure: whether the button stays hidden on this route / with a site overlay open. */\nexport function buttonHidden(site: Pick<PanelButtonSite, 'hiddenOn'>, pathname: string, overlayOpen: boolean): boolean {\n return overlayOpen || !!site.hiddenOn?.(pathname);\n}\n\n/**\n * Keeps the panel entry on the page: the site's navigation item when `mountNav` places one, else\n * the floating button. `open` opens the panel (the runtime's `open()`).\n */\nexport function installPanelButton(win: Window & typeof globalThis, site: PanelButtonSite, open: () => void): { sync(): void } {\n const doc = win.document;\n\n const create = (): HTMLButtonElement => {\n const button = doc.createElement('button');\n button.id = site.id;\n button.type = 'button';\n button.title = site.title();\n button.setAttribute('aria-label', button.title);\n const img = doc.createElement('img');\n img.src = site.logo;\n img.alt = '';\n Object.assign(img.style, { width: '100%', height: '100%', borderRadius: '22%', display: 'block' });\n button.appendChild(img);\n Object.assign(button.style, {\n position: 'fixed', left: '16px', bottom: '16px', zIndex: '2147483646', width: '40px', height: '40px',\n padding: '0', border: '0', background: 'transparent', cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.35)', borderRadius: '22%',\n });\n button.addEventListener('click', () => open());\n return button;\n };\n\n const syncButton = (navMounted: boolean) => {\n const existing = doc.getElementById(site.id);\n if (navMounted) { existing?.remove(); return; }\n const button = existing ?? create();\n const overlay = site.overlaySelector ? !!doc.querySelector(site.overlaySelector) : false;\n const barTop = site.bottomBarSelector ? doc.querySelector(site.bottomBarSelector)?.getBoundingClientRect().top ?? null : null;\n const display = buttonHidden(site, win.location.pathname, overlay) ? 'none' : 'block';\n const bottom = `${buttonBottom(barTop, win.innerHeight)}px`;\n if (button.style.display !== display) button.style.display = display;\n if (button.style.bottom !== bottom) button.style.bottom = bottom;\n if (!existing) doc.body.appendChild(button);\n };\n\n const sync = () => {\n if (!doc.body) return;\n syncButton(site.mountNav?.(open) ?? false);\n };\n const start = () => {\n sync();\n // Sites re-render their navigation on route and layout changes; re-attach when the entry disappears.\n watchDom(win as unknown as DomWindow, sync);\n };\n if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', start, { once: true });\n else start();\n return { sync };\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Grants a plugin can declare. `network` / `network:none` are declarations only (see above). */
|
|
2
|
+
export declare const PLUGIN_PERMISSIONS: readonly ["storage", "ui", "clipboard", "account", "users", "commands", "network", "network:none"];
|
|
3
|
+
export type PluginPermission = (typeof PLUGIN_PERMISSIONS)[number];
|
|
4
|
+
/** Which permission each gated API member needs. Members not listed need none (DOM helpers, theme). */
|
|
5
|
+
export type PermissionGates = Readonly<Record<string, PluginPermission>>;
|
|
6
|
+
/** The core API's gated members. A site adds its own (e.g. `account`, `users`). */
|
|
7
|
+
export declare const CORE_GATES: PermissionGates;
|
|
8
|
+
/**
|
|
9
|
+
* Parses the value of a `@permissions` header line (comma and/or space separated, case-insensitive).
|
|
10
|
+
* Same rules as the native host's `parse_permissions`: unknown names and `network` together with
|
|
11
|
+
* `network:none` are errors; duplicates collapse; `none` alone means "no gated API".
|
|
12
|
+
*/
|
|
13
|
+
export declare function parsePermissions(value: string): {
|
|
14
|
+
permissions: PluginPermission[];
|
|
15
|
+
} | {
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
18
|
+
export declare class PluginPermissionError extends Error {
|
|
19
|
+
readonly plugin: string;
|
|
20
|
+
readonly member: string;
|
|
21
|
+
readonly permission: PluginPermission;
|
|
22
|
+
constructor(plugin: string, member: string, permission: PluginPermission);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The API as plugin `plugin` sees it with `grants`: gated members it was not granted throw a
|
|
26
|
+
* `PluginPermissionError` when called; everything else is the shared API. Frozen, like the API.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scopePluginApi<T extends object>(api: T, grants: readonly string[], plugin: string, gates?: PermissionGates): T;
|
|
29
|
+
export type PluginContext<T extends object> = {
|
|
30
|
+
/**
|
|
31
|
+
* Runs `fn` as plugin `id` (its module body, constructor, `start()` and `stop()`): while it runs,
|
|
32
|
+
* `current()` is that plugin's API. `grants`: a list = the plugin declared these; `null` = legacy
|
|
33
|
+
* plugin without a `@permissions` line (the full API); `undefined` = keep what `id` registered.
|
|
34
|
+
*/
|
|
35
|
+
run<R>(id: string, grants: readonly string[] | null | undefined, fn: () => R): R;
|
|
36
|
+
/** The API for the code running now: a plugin's view inside `run`, else the full API. */
|
|
37
|
+
current(): T;
|
|
38
|
+
/** What `id` registered: its grants, `null` for legacy, `undefined` when never run. */
|
|
39
|
+
grants(id: string): readonly string[] | null | undefined;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Members bound to the plugin a view belongs to (its `i18n`, keyed by the plugin file), applied to
|
|
43
|
+
* every plugin's view, legacy plugins included. Not a permission: bound members are never gated.
|
|
44
|
+
*/
|
|
45
|
+
export type PluginBindings<T extends object> = (full: T, plugin: string) => Partial<T>;
|
|
46
|
+
/** Per-plugin API views for a runtime whose `api` getter returns `context.current()`. */
|
|
47
|
+
export declare function createPluginContext<T extends object>(full: T, gates?: PermissionGates, bind?: PluginBindings<T>): PluginContext<T>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Plugin permissions: what a `*.plugin.js` declares in its header (`@permissions storage, ui`) and
|
|
2
|
+
// the per-plugin view of the plugin API that holds it to that declaration.
|
|
3
|
+
//
|
|
4
|
+
// Be clear about what this is. Plugins are JavaScript running in the site's page: they can read
|
|
5
|
+
// and change the page, call `fetch`, and reach `window.<Runtime>` directly. No page-level check can
|
|
6
|
+
// stop code that wants to get around it. Permissions are
|
|
7
|
+
// - a declaration the owner reviews before installing (the install screen and the Plugins list
|
|
8
|
+
// show them), checked again by the store and by reviewers against the source, and
|
|
9
|
+
// - a guard rail for honest code: the `api` a plugin receives throws on members it did not
|
|
10
|
+
// declare, so an undeclared use fails loudly in development instead of shipping silently.
|
|
11
|
+
// A plugin without a `@permissions` line is "legacy": it gets the whole API, as before, and the app
|
|
12
|
+
// labels it "legacy, full page access". `network:none` is a promise (checked by review and a source
|
|
13
|
+
// scan at install time), not something the page can enforce.
|
|
14
|
+
/** Grants a plugin can declare. `network` / `network:none` are declarations only (see above). */
|
|
15
|
+
export const PLUGIN_PERMISSIONS = ['storage', 'ui', 'clipboard', 'account', 'users', 'commands', 'network', 'network:none'];
|
|
16
|
+
/** The core API's gated members. A site adds its own (e.g. `account`, `users`). */
|
|
17
|
+
export const CORE_GATES = Object.freeze({
|
|
18
|
+
storage: 'storage',
|
|
19
|
+
ui: 'ui',
|
|
20
|
+
toast: 'ui',
|
|
21
|
+
copyText: 'clipboard',
|
|
22
|
+
// Command palette entries (commands.ts); also gates `scope().commands`.
|
|
23
|
+
commands: 'commands',
|
|
24
|
+
});
|
|
25
|
+
const KNOWN = new Set(PLUGIN_PERMISSIONS);
|
|
26
|
+
/**
|
|
27
|
+
* Parses the value of a `@permissions` header line (comma and/or space separated, case-insensitive).
|
|
28
|
+
* Same rules as the native host's `parse_permissions`: unknown names and `network` together with
|
|
29
|
+
* `network:none` are errors; duplicates collapse; `none` alone means "no gated API".
|
|
30
|
+
*/
|
|
31
|
+
export function parsePermissions(value) {
|
|
32
|
+
const tokens = value.split(/[\s,]+/).map(token => token.trim().toLowerCase()).filter(Boolean);
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const token of tokens) {
|
|
35
|
+
if (token === 'none')
|
|
36
|
+
continue;
|
|
37
|
+
if (!KNOWN.has(token))
|
|
38
|
+
return { error: `unknown permission "${token.slice(0, 40)}"` };
|
|
39
|
+
if (!out.includes(token))
|
|
40
|
+
out.push(token);
|
|
41
|
+
}
|
|
42
|
+
if (out.includes('network') && out.includes('network:none'))
|
|
43
|
+
return { error: '"network" and "network:none" contradict each other' };
|
|
44
|
+
return { permissions: out };
|
|
45
|
+
}
|
|
46
|
+
export class PluginPermissionError extends Error {
|
|
47
|
+
plugin;
|
|
48
|
+
member;
|
|
49
|
+
permission;
|
|
50
|
+
constructor(plugin, member, permission) {
|
|
51
|
+
super(`${plugin}: api.${member} needs the "${permission}" permission. Add it to the plugin's header: @permissions ${permission}`);
|
|
52
|
+
this.name = 'PluginPermissionError';
|
|
53
|
+
this.plugin = plugin;
|
|
54
|
+
this.member = member;
|
|
55
|
+
this.permission = permission;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function denied(plugin, member, permission) {
|
|
59
|
+
return () => { throw new PluginPermissionError(plugin, member, permission); };
|
|
60
|
+
}
|
|
61
|
+
/** An object whose every function member throws (`api.ui.toast`, `api.users.get`, ...). */
|
|
62
|
+
function deniedObject(value, plugin, member, permission) {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const key of Object.keys(value)) {
|
|
65
|
+
out[key] = typeof value[key] === 'function' ? denied(plugin, `${member}.${key}`, permission) : undefined;
|
|
66
|
+
}
|
|
67
|
+
return Object.freeze(out);
|
|
68
|
+
}
|
|
69
|
+
/** A plugin's scope (`api.scope()`) whose `ui` is gated like `api.ui`. */
|
|
70
|
+
function scopeView(scope, granted, plugin, gates) {
|
|
71
|
+
const view = {};
|
|
72
|
+
for (const key of Object.getOwnPropertyNames(scope)) {
|
|
73
|
+
const gate = gates[key];
|
|
74
|
+
const descriptor = Object.getOwnPropertyDescriptor(scope, key);
|
|
75
|
+
if (gate && !granted.has(gate)) {
|
|
76
|
+
Object.defineProperty(view, key, {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
get: () => {
|
|
79
|
+
const value = scope[key];
|
|
80
|
+
return typeof value === 'function' ? denied(plugin, `scope().${key}`, gate) : deniedObject(value, plugin, `scope().${key}`, gate);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (descriptor.get) {
|
|
85
|
+
Object.defineProperty(view, key, { enumerable: true, get: () => scope[key] });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const value = descriptor.value;
|
|
89
|
+
view[key] = typeof value === 'function' ? value.bind(scope) : value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return view;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The API as plugin `plugin` sees it with `grants`: gated members it was not granted throw a
|
|
96
|
+
* `PluginPermissionError` when called; everything else is the shared API. Frozen, like the API.
|
|
97
|
+
*/
|
|
98
|
+
export function scopePluginApi(api, grants, plugin, gates = CORE_GATES) {
|
|
99
|
+
const granted = new Set(grants);
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const key of Object.keys(api)) {
|
|
102
|
+
const value = api[key];
|
|
103
|
+
const gate = gates[key];
|
|
104
|
+
if (gate && !granted.has(gate)) {
|
|
105
|
+
out[key] = typeof value === 'function' ? denied(plugin, key, gate) : value && typeof value === 'object' ? deniedObject(value, plugin, key, gate) : undefined;
|
|
106
|
+
}
|
|
107
|
+
else if (key === 'scope' && typeof value === 'function') {
|
|
108
|
+
out[key] = () => scopeView(value.call(api), granted, plugin, gates);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
out[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return Object.freeze(out);
|
|
115
|
+
}
|
|
116
|
+
/** Per-plugin API views for a runtime whose `api` getter returns `context.current()`. */
|
|
117
|
+
export function createPluginContext(full, gates = CORE_GATES, bind) {
|
|
118
|
+
const registered = new Map();
|
|
119
|
+
const stack = [];
|
|
120
|
+
return {
|
|
121
|
+
run(id, grants, fn) {
|
|
122
|
+
let entry = registered.get(id);
|
|
123
|
+
if (grants !== undefined || !entry) {
|
|
124
|
+
const list = Array.isArray(grants) ? Object.freeze(grants.filter(grant => typeof grant === 'string' && KNOWN.has(grant))) : null;
|
|
125
|
+
const view = list ? scopePluginApi(full, list, id, gates) : full;
|
|
126
|
+
entry = { grants: list, api: bind ? Object.freeze({ ...view, ...bind(full, id) }) : view };
|
|
127
|
+
registered.set(id, entry);
|
|
128
|
+
}
|
|
129
|
+
stack.push(entry.api);
|
|
130
|
+
try {
|
|
131
|
+
return fn();
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
stack.pop();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
current: () => stack[stack.length - 1] ?? full,
|
|
138
|
+
grants: id => registered.get(id)?.grants,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../../runtime/src/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["// Plugin permissions: what a `*.plugin.js` declares in its header (`@permissions storage, ui`) and\n// the per-plugin view of the plugin API that holds it to that declaration.\n//\n// Be clear about what this is. Plugins are JavaScript running in the site's page: they can read\n// and change the page, call `fetch`, and reach `window.<Runtime>` directly. No page-level check can\n// stop code that wants to get around it. Permissions are\n// - a declaration the owner reviews before installing (the install screen and the Plugins list\n// show them), checked again by the store and by reviewers against the source, and\n// - a guard rail for honest code: the `api` a plugin receives throws on members it did not\n// declare, so an undeclared use fails loudly in development instead of shipping silently.\n// A plugin without a `@permissions` line is \"legacy\": it gets the whole API, as before, and the app\n// labels it \"legacy, full page access\". `network:none` is a promise (checked by review and a source\n// scan at install time), not something the page can enforce.\n\n/** Grants a plugin can declare. `network` / `network:none` are declarations only (see above). */\nexport const PLUGIN_PERMISSIONS = ['storage', 'ui', 'clipboard', 'account', 'users', 'commands', 'network', 'network:none'] as const;\nexport type PluginPermission = (typeof PLUGIN_PERMISSIONS)[number];\n\n/** Which permission each gated API member needs. Members not listed need none (DOM helpers, theme). */\nexport type PermissionGates = Readonly<Record<string, PluginPermission>>;\n\n/** The core API's gated members. A site adds its own (e.g. `account`, `users`). */\nexport const CORE_GATES: PermissionGates = Object.freeze({\n storage: 'storage',\n ui: 'ui',\n toast: 'ui',\n copyText: 'clipboard',\n // Command palette entries (commands.ts); also gates `scope().commands`.\n commands: 'commands',\n});\n\nconst KNOWN = new Set<string>(PLUGIN_PERMISSIONS);\n\n/**\n * Parses the value of a `@permissions` header line (comma and/or space separated, case-insensitive).\n * Same rules as the native host's `parse_permissions`: unknown names and `network` together with\n * `network:none` are errors; duplicates collapse; `none` alone means \"no gated API\".\n */\nexport function parsePermissions(value: string): { permissions: PluginPermission[] } | { error: string } {\n const tokens = value.split(/[\\s,]+/).map(token => token.trim().toLowerCase()).filter(Boolean);\n const out: PluginPermission[] = [];\n for (const token of tokens) {\n if (token === 'none') continue;\n if (!KNOWN.has(token)) return { error: `unknown permission \"${token.slice(0, 40)}\"` };\n if (!out.includes(token as PluginPermission)) out.push(token as PluginPermission);\n }\n if (out.includes('network') && out.includes('network:none')) return { error: '\"network\" and \"network:none\" contradict each other' };\n return { permissions: out };\n}\n\nexport class PluginPermissionError extends Error {\n readonly plugin: string;\n readonly member: string;\n readonly permission: PluginPermission;\n constructor(plugin: string, member: string, permission: PluginPermission) {\n super(`${plugin}: api.${member} needs the \"${permission}\" permission. Add it to the plugin's header: @permissions ${permission}`);\n this.name = 'PluginPermissionError';\n this.plugin = plugin;\n this.member = member;\n this.permission = permission;\n }\n}\n\ntype Fn = (...args: unknown[]) => unknown;\n\nfunction denied(plugin: string, member: string, permission: PluginPermission): Fn {\n return () => { throw new PluginPermissionError(plugin, member, permission); };\n}\n\n/** An object whose every function member throws (`api.ui.toast`, `api.users.get`, ...). */\nfunction deniedObject(value: object, plugin: string, member: string, permission: PluginPermission): object {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n out[key] = typeof (value as Record<string, unknown>)[key] === 'function' ? denied(plugin, `${member}.${key}`, permission) : undefined;\n }\n return Object.freeze(out);\n}\n\n/** A plugin's scope (`api.scope()`) whose `ui` is gated like `api.ui`. */\nfunction scopeView(scope: object, granted: ReadonlySet<string>, plugin: string, gates: PermissionGates): object {\n const view: Record<string, unknown> = {};\n for (const key of Object.getOwnPropertyNames(scope)) {\n const gate = gates[key];\n const descriptor = Object.getOwnPropertyDescriptor(scope, key)!;\n if (gate && !granted.has(gate)) {\n Object.defineProperty(view, key, {\n enumerable: true,\n get: () => {\n const value = (scope as Record<string, unknown>)[key];\n return typeof value === 'function' ? denied(plugin, `scope().${key}`, gate) : deniedObject(value as object, plugin, `scope().${key}`, gate);\n },\n });\n } else if (descriptor.get) {\n Object.defineProperty(view, key, { enumerable: true, get: () => (scope as Record<string, unknown>)[key] });\n } else {\n const value = descriptor.value;\n view[key] = typeof value === 'function' ? (value as Fn).bind(scope) : value;\n }\n }\n return view;\n}\n\n/**\n * The API as plugin `plugin` sees it with `grants`: gated members it was not granted throw a\n * `PluginPermissionError` when called; everything else is the shared API. Frozen, like the API.\n */\nexport function scopePluginApi<T extends object>(api: T, grants: readonly string[], plugin: string, gates: PermissionGates = CORE_GATES): T {\n const granted = new Set(grants);\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(api)) {\n const value = (api as Record<string, unknown>)[key];\n const gate = gates[key];\n if (gate && !granted.has(gate)) {\n out[key] = typeof value === 'function' ? denied(plugin, key, gate) : value && typeof value === 'object' ? deniedObject(value, plugin, key, gate) : undefined;\n } else if (key === 'scope' && typeof value === 'function') {\n out[key] = () => scopeView((value as Fn).call(api) as object, granted, plugin, gates);\n } else {\n out[key] = value;\n }\n }\n return Object.freeze(out) as T;\n}\n\nexport type PluginContext<T extends object> = {\n /**\n * Runs `fn` as plugin `id` (its module body, constructor, `start()` and `stop()`): while it runs,\n * `current()` is that plugin's API. `grants`: a list = the plugin declared these; `null` = legacy\n * plugin without a `@permissions` line (the full API); `undefined` = keep what `id` registered.\n */\n run<R>(id: string, grants: readonly string[] | null | undefined, fn: () => R): R;\n /** The API for the code running now: a plugin's view inside `run`, else the full API. */\n current(): T;\n /** What `id` registered: its grants, `null` for legacy, `undefined` when never run. */\n grants(id: string): readonly string[] | null | undefined;\n};\n\n/**\n * Members bound to the plugin a view belongs to (its `i18n`, keyed by the plugin file), applied to\n * every plugin's view, legacy plugins included. Not a permission: bound members are never gated.\n */\nexport type PluginBindings<T extends object> = (full: T, plugin: string) => Partial<T>;\n\n/** Per-plugin API views for a runtime whose `api` getter returns `context.current()`. */\nexport function createPluginContext<T extends object>(full: T, gates: PermissionGates = CORE_GATES, bind?: PluginBindings<T>): PluginContext<T> {\n const registered = new Map<string, { grants: readonly string[] | null; api: T }>();\n const stack: T[] = [];\n return {\n run(id, grants, fn) {\n let entry = registered.get(id);\n if (grants !== undefined || !entry) {\n const list = Array.isArray(grants) ? Object.freeze(grants.filter(grant => typeof grant === 'string' && KNOWN.has(grant))) : null;\n const view = list ? scopePluginApi(full, list, id, gates) : full;\n entry = { grants: list, api: bind ? Object.freeze({ ...view, ...bind(full, id) }) as T : view };\n registered.set(id, entry);\n }\n stack.push(entry.api);\n try { return fn(); } finally { stack.pop(); }\n },\n current: () => stack[stack.length - 1] ?? full,\n grants: id => registered.get(id)?.grants,\n };\n}\n"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { type ThemeApi, type ThemeSource } from './themeApi.ts';
|
|
2
|
+
import { type UiKit } from './ui.ts';
|
|
3
|
+
import { type RootStyles } from './styles.ts';
|
|
4
|
+
import { type CommandDefinition, type CommandInfo, type CommandRegistry } from './commands.ts';
|
|
5
|
+
import { type I18nSource, type PluginI18n, type PluginMessages } from './pluginI18n.ts';
|
|
6
|
+
export type { ThemeApi, ThemeSource, UiKit, CommandDefinition, CommandInfo, CommandRegistry, RootStyles, I18nSource, PluginI18n, PluginMessages };
|
|
1
7
|
export type Dispose = () => void;
|
|
2
8
|
export type PluginStorage = {
|
|
3
9
|
get<T>(key: string, fallback: T): T;
|
|
@@ -9,6 +15,8 @@ export type PluginStorage = {
|
|
|
9
15
|
/** Collects disposers so a plugin's `stop()` is a single `scope.dispose()`. */
|
|
10
16
|
export type PluginScope = {
|
|
11
17
|
css(id: string, css: string): void;
|
|
18
|
+
/** `api.adoptCSS`, taken off the root on dispose. */
|
|
19
|
+
adoptCSS(root: ShadowRoot | Document, css: string): RootStyles;
|
|
12
20
|
on(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
13
21
|
observe(selector: string, callback: (element: Element) => void): void;
|
|
14
22
|
onRouteChange(callback: (href: string, previous: string) => void): void;
|
|
@@ -17,17 +25,34 @@ export type PluginScope = {
|
|
|
17
25
|
add<T extends Node>(node: T): T;
|
|
18
26
|
/** Runs `fn` on dispose (restore attributes, clear timers, ...). */
|
|
19
27
|
defer(fn: Dispose): void;
|
|
28
|
+
/** Calls `callback(theme)` after the theme changes, until dispose. */
|
|
29
|
+
onThemeChange(callback: Parameters<ThemeApi['onChange']>[0]): void;
|
|
30
|
+
/** The UI kit, scoped: dialogs and toasts it opened are closed on dispose. */
|
|
31
|
+
readonly ui: UiKit;
|
|
32
|
+
/** Command palette entries of the plugin that created this scope, removed on dispose. */
|
|
33
|
+
readonly commands: {
|
|
34
|
+
register(definition: CommandDefinition): Dispose;
|
|
35
|
+
};
|
|
20
36
|
dispose(): void;
|
|
21
37
|
};
|
|
22
38
|
export type CorePluginApi = {
|
|
23
39
|
injectCSS(id: string, css: string): void;
|
|
24
40
|
clearCSS(id: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Styles a shadow root the plugin created (or the document) with a constructable stylesheet:
|
|
43
|
+
* the page's Content-Security-Policy cannot refuse it, it is parsed once, and it stays with the
|
|
44
|
+
* root wherever its host is moved. Falls back to a `<style>` element where unsupported.
|
|
45
|
+
*/
|
|
46
|
+
adoptCSS(root: ShadowRoot | Document, css: string): RootStyles;
|
|
25
47
|
waitFor(selector: string, timeoutMs?: number): Promise<Element>;
|
|
26
48
|
/** Namespaced JSON storage in this tab's localStorage (`<storagePrefix><namespace>:<key>`). */
|
|
27
49
|
storage(namespace: string): PluginStorage;
|
|
28
50
|
/** Called after the page changes route (pushState/replaceState/back/forward). */
|
|
29
51
|
onRouteChange(callback: (href: string, previous: string) => void): Dispose;
|
|
30
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Calls `callback` once for every element matching `selector`, now and as the page renders more.
|
|
54
|
+
* Page-wide observers share one MutationObserver (domHub.ts); pass `root` only for a tree of your own.
|
|
55
|
+
*/
|
|
31
56
|
observe(selector: string, callback: (element: Element) => void, root?: ParentNode): Dispose;
|
|
32
57
|
/**
|
|
33
58
|
* Single-key shortcuts: `callback(event, focusedItem)` runs only when the user is not typing, no
|
|
@@ -35,9 +60,31 @@ export type CorePluginApi = {
|
|
|
35
60
|
*/
|
|
36
61
|
onKey(callback: (event: KeyboardEvent, item: Element | null) => void): Dispose;
|
|
37
62
|
copyText(text: string): Promise<boolean>;
|
|
63
|
+
/** A short status message (the UI kit's toast; `ms` is how long it stays). */
|
|
38
64
|
toast(message: string, ms?: number): void;
|
|
65
|
+
/** Toasts, alerts, confirmations, prompts and dialogs in the active theme (see ui.ts). */
|
|
66
|
+
ui: UiKit;
|
|
67
|
+
/** The active theme as values: `current()` and `onChange(fn)` (see themeApi.ts). */
|
|
68
|
+
theme: ThemeApi;
|
|
69
|
+
/**
|
|
70
|
+
* Commands in the shell's command palette (Ctrl+K), listed under the plugin's name and run in
|
|
71
|
+
* this page (see commands.ts). Removed when the plugin stops.
|
|
72
|
+
*/
|
|
73
|
+
commands: CommandRegistry;
|
|
74
|
+
/**
|
|
75
|
+
* The app's language and the plugin's own strings in it (see pluginI18n.ts). In a plugin's view
|
|
76
|
+
* it is bound to that plugin (`plugin.<file name>.*` in the app catalog); `namespace(name)` binds
|
|
77
|
+
* it explicitly.
|
|
78
|
+
*/
|
|
79
|
+
i18n: PluginI18n;
|
|
39
80
|
scope(): PluginScope;
|
|
40
81
|
};
|
|
82
|
+
/** What the site adapter adds to the core: its theme engine, when it has one. */
|
|
83
|
+
export type CorePluginExtras = {
|
|
84
|
+
theme?: ThemeSource;
|
|
85
|
+
/** The app's active locale and catalog (plugin strings, the UI kit's own words). English without it. */
|
|
86
|
+
i18n?: I18nSource;
|
|
87
|
+
};
|
|
41
88
|
export type PluginApiSite = {
|
|
42
89
|
/** The runtime global, for error messages (`<name>.waitFor timed out`). */
|
|
43
90
|
name: string;
|
|
@@ -47,7 +94,9 @@ export type PluginApiSite = {
|
|
|
47
94
|
storagePrefix: string;
|
|
48
95
|
/** `data-*` key (camelCase) marking plugin `<style>` elements with their id. */
|
|
49
96
|
styleDataKey: string;
|
|
97
|
+
/** Id of the kit's shadow host element (`<wb-ui id=…>`), kept from the old single toast. */
|
|
50
98
|
toastId: string;
|
|
99
|
+
/** Accent used when the site has no theme source (and the page shows none). */
|
|
51
100
|
toastBackground: string;
|
|
52
101
|
/** The item a single-key shortcut acts on: the focused element's closest match (a post, a message). */
|
|
53
102
|
itemSelector: string;
|
|
@@ -56,4 +105,4 @@ export type PluginApiSite = {
|
|
|
56
105
|
};
|
|
57
106
|
/** True when a key event belongs to text entry, not to page shortcuts. */
|
|
58
107
|
export declare function isTypingTarget(target: EventTarget | null): boolean;
|
|
59
|
-
export declare function createCorePluginApi(win: Window & typeof globalThis, site: PluginApiSite): CorePluginApi;
|
|
108
|
+
export declare function createCorePluginApi(win: Window & typeof globalThis, site: PluginApiSite, extras?: CorePluginExtras): CorePluginApi;
|