waybetter 0.1.0 → 0.6.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 +143 -9
- package/bin/waybetter.mjs +46 -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/privacy.mjs +768 -0
- package/cli/release.mjs +444 -0
- package/cli/remote.mjs +260 -0
- package/cli/store.mjs +326 -0
- package/cli/strings.mjs +121 -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 +59 -0
- package/dist/runtime/stringPackTranslate.js +264 -0
- package/dist/runtime/stringPackTranslate.js.map +1 -0
- package/dist/runtime/stringPacks.d.ts +200 -0
- package/dist/runtime/stringPacks.js +474 -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/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 +463 -0
- package/dist/runtime/themeComponents.js.map +1 -0
- package/dist/runtime/themeDecor.d.ts +73 -0
- package/dist/runtime/themeDecor.js +131 -0
- package/dist/runtime/themeDecor.js.map +1 -0
- package/dist/runtime/themeDoctor.d.ts +11 -0
- package/dist/runtime/themeDoctor.js +51 -0
- package/dist/runtime/themeDoctor.js.map +1 -0
- package/dist/runtime/themeIcons.d.ts +130 -0
- package/dist/runtime/themeIcons.js +263 -0
- package/dist/runtime/themeIcons.js.map +1 -0
- package/dist/runtime/themeRuntime.d.ts +100 -0
- package/dist/runtime/themeRuntime.js +497 -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 +259 -0
- package/dist/runtime/themeSpec.js +439 -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 +170 -0
- package/dist/shell/InstallDialog.js.map +1 -0
- package/dist/shell/StoreView.d.ts +66 -0
- package/dist/shell/StoreView.js +103 -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 +21 -0
- package/dist/shell/frame/AppMenu.js +642 -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 +8 -0
- package/dist/shell/frame/Shell.js +1131 -0
- package/dist/shell/frame/Shell.js.map +1 -0
- package/dist/shell/frame/Titlebar.d.ts +16 -0
- package/dist/shell/frame/Titlebar.js +77 -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 +182 -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 +121 -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 +35 -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 +29 -0
- package/dist/shell/frame/chromeTheme.js +81 -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/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 +34 -0
- package/dist/shell/frame/mediaModel.js +53 -0
- package/dist/shell/frame/mediaModel.js.map +1 -0
- package/dist/shell/frame/menuMessages.en.json +54 -0
- package/dist/shell/frame/menuModel.d.ts +277 -0
- package/dist/shell/frame/menuModel.js +293 -0
- package/dist/shell/frame/menuModel.js.map +1 -0
- package/dist/shell/frame/paletteModel.d.ts +139 -0
- package/dist/shell/frame/paletteModel.js +112 -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 +629 -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 +99 -0
- package/dist/shell/frame/state.js +284 -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 +252 -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 +122 -0
- package/dist/shell/installController.js +146 -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 +256 -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 +116 -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 +6 -0
- package/dist/shell/panel/StoreSection.js +48 -0
- package/dist/shell/panel/StoreSection.js.map +1 -0
- package/dist/shell/panel/StringPacks.d.ts +11 -0
- package/dist/shell/panel/StringPacks.js +106 -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 +232 -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 +72 -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 +763 -0
- package/dist/shell/panel/panelMessages.js +767 -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 +230 -0
- package/dist/shell/storeMessages.d.ts +169 -0
- package/dist/shell/storeMessages.js +172 -0
- package/dist/shell/storeMessages.js.map +1 -0
- package/dist/shell/storeModel.d.ts +181 -0
- package/dist/shell/storeModel.js +133 -0
- package/dist/shell/storeModel.js.map +1 -0
- package/dist/shell/stringPacksModel.d.ts +44 -0
- package/dist/shell/stringPacksModel.js +94 -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 +646 -0
- package/languages.schema.json +23 -0
- package/locales/ar-x-fm.json +11 -0
- package/locales/ar.json +1281 -0
- package/locales/bg.json +1280 -0
- package/locales/bn.json +1280 -0
- package/locales/ca.json +1280 -0
- package/locales/cs.json +1280 -0
- package/locales/da.json +1280 -0
- package/locales/de.json +1280 -0
- package/locales/el.json +1280 -0
- package/locales/en-GB.json +27 -0
- package/locales/en.json +1280 -0
- package/locales/en.meta.json +51 -0
- package/locales/es.json +1280 -0
- package/locales/eu.json +1280 -0
- package/locales/fa.json +1281 -0
- package/locales/fi.json +1280 -0
- package/locales/fil.json +1280 -0
- package/locales/fr.json +1280 -0
- package/locales/ga.json +1280 -0
- package/locales/gl.json +1280 -0
- package/locales/gu.json +1280 -0
- package/locales/ha.json +1280 -0
- package/locales/he.json +1281 -0
- package/locales/hi.json +1280 -0
- package/locales/hr.json +1280 -0
- package/locales/hu.json +1279 -0
- package/locales/id.json +1280 -0
- package/locales/ig.json +1280 -0
- package/locales/it.json +1280 -0
- package/locales/ja.json +1280 -0
- package/locales/kn.json +1280 -0
- package/locales/ko.json +1280 -0
- package/locales/mr.json +1280 -0
- package/locales/ms.json +1280 -0
- package/locales/nb.json +1280 -0
- package/locales/nl.json +1280 -0
- package/locales/pl.json +1280 -0
- package/locales/pt.json +1280 -0
- package/locales/ro.json +1280 -0
- package/locales/ru.json +1280 -0
- package/locales/sk.json +1280 -0
- package/locales/sr.json +1280 -0
- package/locales/sv.json +1280 -0
- package/locales/ta.json +1280 -0
- package/locales/th.json +1280 -0
- package/locales/tr.json +1280 -0
- package/locales/uk.json +1280 -0
- package/locales/ur.json +1281 -0
- package/locales/vi.json +1280 -0
- package/locales/yo.json +1280 -0
- package/locales/zh-Hans.json +1280 -0
- package/locales/zh-Hant.json +1280 -0
- package/package.json +148 -6
- package/store-catalog.schema.json +65 -0
- package/theme.schema.json +4659 -0
- package/windows/installer.nsi +926 -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,103 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// The wayBetter Store view, shared by every wayBetter app: themes and plugins from the signed
|
|
3
|
+
// catalog (native code verifies its Ed25519 signature, keeps this app's items, then checks each
|
|
4
|
+
// download's sha256). Search, kinds, categories, cards with previews and permission badges, and
|
|
5
|
+
// Install / Update / Remove. Installing always goes through the install screen (InstallHost), never
|
|
6
|
+
// straight from here. Styles: `store.css` (framework `--wb-*` tokens). The app may pass its own
|
|
7
|
+
// controls (buttons, search field, segmented control) so the view matches its settings.
|
|
8
|
+
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
|
9
|
+
import { categoryLabel, errorText, filterItems, itemState, permissionBadges } from "./storeModel.js";
|
|
10
|
+
export const DEFAULT_CONTROLS = {
|
|
11
|
+
Button: ({ children, onClick, tone = 'plain', size = 'md', disabled, title }) => _jsx("button", { type: "button", className: `wb-btn is-${tone} is-${size}`, disabled: disabled, title: title, onClick: onClick, children: children }),
|
|
12
|
+
SearchField: ({ value, onChange, placeholder, label }) => _jsx("input", { className: "wb-field", type: "search", "aria-label": label, placeholder: placeholder, value: value, onChange: event => onChange(event.target.value), onKeyDown: event => { if (event.key === 'Escape' && value) {
|
|
13
|
+
event.stopPropagation();
|
|
14
|
+
onChange('');
|
|
15
|
+
} } }),
|
|
16
|
+
Segmented: ({ value, options, onChange, label }) => _jsx("div", { className: "wb-segmented", role: "radiogroup", "aria-label": label, children: options.map(option => _jsx("button", { type: "button", role: "radio", "aria-checked": option.value === value, className: option.value === value ? 'is-on' : '', onClick: () => onChange(option.value), children: option.label }, option.value)) }),
|
|
17
|
+
EmptyState: ({ icon, title, children, action }) => _jsxs("div", { className: "wb-empty", children: [icon && _jsx("div", { "aria-hidden": "true", children: icon }), _jsx("strong", { children: title }), children && _jsx("p", { children: children }), action] }),
|
|
18
|
+
Badge: ({ children }) => _jsx("span", { className: "wb-badge", children: children }),
|
|
19
|
+
};
|
|
20
|
+
const STORE_ICON = _jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M4 9l1.5-5h13L20 9M4 9h16v11H4zM4 9a2.7 2.7 0 0 0 5.3 0 2.7 2.7 0 0 0 5.4 0 2.7 2.7 0 0 0 5.3 0M10 20v-5h4v5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round" }) });
|
|
21
|
+
function Preview({ item }) {
|
|
22
|
+
const [broken, setBroken] = useState(false);
|
|
23
|
+
const c = item.colors;
|
|
24
|
+
if (item.preview && !broken)
|
|
25
|
+
return _jsx("img", { className: "wbs-img", src: item.preview, alt: "", loading: "lazy", onError: () => setBroken(true) });
|
|
26
|
+
// Offline or not published yet: swatches from the signed catalog (themes) or a plain tile.
|
|
27
|
+
return _jsx("div", { className: "wbs-img wbs-swatch", style: c ? { background: c.bg } : undefined, "aria-hidden": "true", children: c ? _jsxs(_Fragment, { children: [_jsx("span", { style: { background: c.surface ?? c.bg, borderColor: c.text } }), _jsx("i", { style: { background: c.text } }), _jsx("b", { style: { background: c.accent } })] }) : _jsx("strong", { children: item.name.slice(0, 1) }) });
|
|
28
|
+
}
|
|
29
|
+
function Action({ item, state, busy, t, ui, onInstall, onRemove, onUse }) {
|
|
30
|
+
const { Button } = ui;
|
|
31
|
+
const [asking, setAsking] = useState(false);
|
|
32
|
+
if (state.state === 'builtin')
|
|
33
|
+
return _jsxs("div", { className: "wbs-actions", children: [_jsx("span", { className: "wbs-state", children: t('wb.store.builtIn') }), _jsx(Button, { size: "sm", onClick: onUse, children: t('wb.store.use') })] });
|
|
34
|
+
if (state.state === 'install')
|
|
35
|
+
return _jsx("div", { className: "wbs-actions", children: _jsx(Button, { size: "sm", tone: "primary", disabled: busy, onClick: onInstall, children: t('wb.store.install') }) });
|
|
36
|
+
const remove = asking
|
|
37
|
+
? _jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", tone: "danger", onClick: () => { setAsking(false); onRemove(); }, children: t('wb.store.remove') }), _jsx(Button, { size: "sm", tone: "ghost", onClick: () => setAsking(false), children: t('wb.store.keep') })] })
|
|
38
|
+
: _jsx(Button, { size: "sm", tone: "ghost", onClick: () => setAsking(true), children: t('wb.store.remove') });
|
|
39
|
+
if (state.state === 'update')
|
|
40
|
+
return _jsxs("div", { className: "wbs-actions", children: [_jsx(Button, { size: "sm", tone: "primary", disabled: busy, onClick: onInstall, children: t('wb.store.updateTo', { version: item.version }) }), remove] });
|
|
41
|
+
if (state.state === 'modified')
|
|
42
|
+
return _jsxs("div", { className: "wbs-actions", children: [_jsx("span", { className: "wbs-state", title: t('wb.store.modifiedTitle'), children: t('wb.store.modified') }), _jsx(Button, { size: "sm", disabled: busy, onClick: onInstall, children: t('wb.store.replace') }), remove] });
|
|
43
|
+
return _jsxs("div", { className: "wbs-actions", children: [_jsx("span", { className: "wbs-state is-ok", children: state.version ? t('wb.store.installedVersion', { version: state.version }) : t('wb.store.installed') }), remove] });
|
|
44
|
+
}
|
|
45
|
+
export function StoreView(props) {
|
|
46
|
+
const { t, site, controller, notify } = props;
|
|
47
|
+
const ui = { ...DEFAULT_CONTROLS, ...props.controls };
|
|
48
|
+
const { Button, SearchField, Segmented, EmptyState, Badge } = ui;
|
|
49
|
+
const [view, setView] = useState(null);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [query, setQuery] = useState('');
|
|
52
|
+
const [kind, setKind] = useState('all');
|
|
53
|
+
const [category, setCategory] = useState('all');
|
|
54
|
+
const request = useSyncExternalStore(controller.subscribe, controller.current, controller.current);
|
|
55
|
+
const load = (refresh) => {
|
|
56
|
+
setLoading(true);
|
|
57
|
+
return props.load(refresh).then(setView).catch(error => notify(errorText(error), true)).finally(() => setLoading(false));
|
|
58
|
+
};
|
|
59
|
+
useEffect(() => { void load(false); }, []);
|
|
60
|
+
// Installs finish in the install screen; refresh what is installed afterwards.
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const again = () => void load(false);
|
|
63
|
+
const events = props.reloadOn ?? [];
|
|
64
|
+
for (const name of events)
|
|
65
|
+
window.addEventListener(name, again);
|
|
66
|
+
return () => { for (const name of events)
|
|
67
|
+
window.removeEventListener(name, again); };
|
|
68
|
+
}, [(props.reloadOn ?? []).join(' ')]);
|
|
69
|
+
useEffect(() => { if (request === null)
|
|
70
|
+
void load(false); }, [request === null]);
|
|
71
|
+
const items = view?.catalog?.items ?? [];
|
|
72
|
+
const context = { plugins: view?.plugins ?? {}, records: view?.records.items ?? {}, builtinThemes: props.builtinThemes, userThemes: props.userThemes };
|
|
73
|
+
const categories = useMemo(() => [...new Set(items.filter(item => kind === 'all' || item.kind === kind).map(item => item.category))].sort(), [items, kind]);
|
|
74
|
+
const shown = filterItems(items, query, kind, category);
|
|
75
|
+
const updates = items.filter(item => itemState(item, context).state === 'update');
|
|
76
|
+
const ago = (iso) => {
|
|
77
|
+
if (!iso)
|
|
78
|
+
return '';
|
|
79
|
+
const at = Date.parse(iso);
|
|
80
|
+
return Number.isFinite(at) ? props.relativeTime(Math.min(at, Date.now())) : '';
|
|
81
|
+
};
|
|
82
|
+
async function removeItem(item) {
|
|
83
|
+
if (!view)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
if (!(await props.onRemove(item, view)))
|
|
87
|
+
return;
|
|
88
|
+
notify(t('wb.store.removed', { name: item.name }));
|
|
89
|
+
await load(false);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
notify(errorText(error), true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const status = view?.error
|
|
96
|
+
? _jsx("span", { className: "wbs-status is-warn", role: "status", children: view.catalog ? t('wb.store.offline', { when: ago(view.fetchedAt), error: errorText(view.error) }) : t('wb.store.unreachableShort') })
|
|
97
|
+
: view?.catalog ? _jsxs("span", { className: "wbs-status is-ok", role: "status", children: [_jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 3l7 3v5c0 4.6-3 8.5-7 10-4-1.5-7-5.4-7-10V6zM8.5 12l2.5 2.5 4.5-5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), t('wb.store.verified', { when: ago(view.fetchedAt) })] }) : null;
|
|
98
|
+
return _jsxs("div", { className: "wbs", children: [_jsx("p", { className: "wbs-intro", children: t('wb.store.intro') }), _jsxs("div", { className: "wbs-toolbar", children: [_jsx(SearchField, { label: t('wb.store.search'), placeholder: t('wb.store.searchPlaceholder'), value: query, onChange: setQuery }), _jsx(Segmented, { label: t('wb.store.kind'), size: "sm", value: kind, onChange: next => { setKind(next); setCategory('all'); }, options: [{ value: 'all', label: t('wb.store.all') }, { value: 'theme', label: t('wb.store.themes') }, { value: 'plugin', label: t('wb.store.pluginsTab') }, ...(items.some(item => item.kind === 'strings') ? [{ value: 'strings', label: t('wb.store.strings') }] : [])] }), _jsx(Button, { size: "sm", tone: "ghost", disabled: loading, onClick: () => void load(true), children: loading ? t('wb.store.checking') : t('wb.store.refresh') })] }), _jsxs("div", { className: "wbs-cats", role: "group", "aria-label": t('wb.store.categories'), children: [['all', ...categories].map(id => _jsx("button", { className: category === id ? 'is-on' : '', "aria-pressed": category === id, onClick: () => setCategory(id), children: id === 'all' ? t('wb.store.everything') : categoryLabel(id, t) }, id)), _jsx("span", { className: "wbs-cats-end" }), status] }), updates.length > 0 && _jsxs("div", { className: "wbs-updates", role: "status", children: [_jsx("strong", { children: t('wb.store.updates', { count: updates.length }) }), _jsx("span", { children: updates.map(item => item.name).join(', ') })] }), !view && loading && _jsx("div", { className: "wbs-grid", "aria-busy": "true", children: Array.from({ length: 6 }, (_, i) => _jsx("div", { className: "wbs-card is-skeleton" }, i)) }), view && !view.catalog && _jsxs(EmptyState, { icon: STORE_ICON, title: t('wb.store.unreachable'), action: _jsx(Button, { size: "sm", onClick: () => void load(true), children: t('wb.store.tryAgain') }), children: [view.error ? errorText(view.error).replace(/\.?$/, '.') : t('wb.store.noCatalog'), " ", t('wb.store.nothingChanged')] }), view?.catalog && shown.length === 0 && _jsx(EmptyState, { icon: STORE_ICON, title: t('wb.store.noMatch'), children: t('wb.store.noMatchHint') }), _jsx("div", { className: "wbs-grid", children: shown.map(item => {
|
|
99
|
+
const state = itemState(item, context);
|
|
100
|
+
return _jsxs("article", { className: `wbs-card is-${item.kind}`, children: [_jsx(Preview, { item: item }), _jsxs("div", { className: "wbs-card-body", children: [_jsxs("header", { children: [_jsx("h3", { children: item.name }), _jsx(Badge, { children: item.kind === 'theme' ? t('wb.store.theme') : item.kind === 'strings' ? t('wb.store.stringsItem') : t('wb.store.plugin') })] }), _jsxs("p", { className: "wbs-meta", children: [item.author && `${t('wb.store.by', { author: item.author })} · `, "v", item.version, " \u00B7 ", item.kind === 'strings' && item.language ? item.language : categoryLabel(item.category, t), item.license ? ` · ${item.license}` : ''] }), _jsx("p", { className: "wbs-desc", children: item.description }), item.kind === 'plugin' && _jsx("ul", { className: "wbs-perms", "aria-label": t('wb.store.permissions'), children: permissionBadges(item.permissions, t, site).map(badge => _jsx("li", { className: `is-${badge.tone}`, title: badge.detail, children: badge.label }, badge.id)) }), _jsx(Action, { item: item, state: state, busy: request !== null, t: t, ui: ui, onInstall: () => void controller.requestStoreItem(item), onRemove: () => void removeItem(item), onUse: () => void props.onUse(item).then(() => notify(t('wb.store.themeNow', { name: item.name }))).catch(error => notify(errorText(error), true)) })] })] }, `${item.kind}:${item.id}`);
|
|
101
|
+
}) }), _jsx("p", { className: "wbs-foot", children: t('wb.store.foot') })] });
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=StoreView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StoreView.js","sourceRoot":"","sources":["../../../../../shell/src/StoreView.tsx"],"names":[],"mappings":"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","sourcesContent":["// The wayBetter Store view, shared by every wayBetter app: themes and plugins from the signed\n// catalog (native code verifies its Ed25519 signature, keeps this app's items, then checks each\n// download's sha256). Search, kinds, categories, cards with previews and permission badges, and\n// Install / Update / Remove. Installing always goes through the install screen (InstallHost), never\n// straight from here. Styles: `store.css` (framework `--wb-*` tokens). The app may pass its own\n// controls (buttons, search field, segmented control) so the view matches its settings.\n\nimport { useEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport type { InstallController } from './installController.ts';\nimport type { StoreText } from './storeMessages.ts';\nimport { categoryLabel, errorText, filterItems, itemState, permissionBadges, type ItemState, type StoreCatalogView, type StoreItem } from './storeModel.ts';\n\ntype ButtonProps = { children: ReactNode; onClick?(): void; tone?: 'plain' | 'primary' | 'ghost' | 'danger'; size?: 'sm' | 'md'; disabled?: boolean; title?: string };\ntype Option<T extends string> = { value: T; label: string };\nexport type StoreControls = {\n Button(props: ButtonProps): ReactNode;\n SearchField(props: { value: string; onChange(next: string): void; placeholder: string; label: string }): ReactNode;\n Segmented<T extends string>(props: { value: T; options: Array<Option<T>>; onChange(next: T): void; label: string; size?: 'sm' | 'md' }): ReactNode;\n EmptyState(props: { icon?: ReactNode; title: string; children?: ReactNode; action?: ReactNode }): ReactNode;\n Badge(props: { children: ReactNode }): ReactNode;\n};\n\nexport const DEFAULT_CONTROLS: StoreControls = {\n Button: ({ children, onClick, tone = 'plain', size = 'md', disabled, title }) =>\n <button type=\"button\" className={`wb-btn is-${tone} is-${size}`} disabled={disabled} title={title} onClick={onClick}>{children}</button>,\n SearchField: ({ value, onChange, placeholder, label }) =>\n <input className=\"wb-field\" type=\"search\" aria-label={label} placeholder={placeholder} value={value}\n onChange={event => onChange(event.target.value)} onKeyDown={event => { if (event.key === 'Escape' && value) { event.stopPropagation(); onChange(''); } }}/>,\n Segmented: ({ value, options, onChange, label }) =>\n <div className=\"wb-segmented\" role=\"radiogroup\" aria-label={label}>\n {options.map(option => <button key={option.value} type=\"button\" role=\"radio\" aria-checked={option.value === value} className={option.value === value ? 'is-on' : ''} onClick={() => onChange(option.value)}>{option.label}</button>)}\n </div>,\n EmptyState: ({ icon, title, children, action }) =>\n <div className=\"wb-empty\">{icon && <div aria-hidden=\"true\">{icon}</div>}<strong>{title}</strong>{children && <p>{children}</p>}{action}</div>,\n Badge: ({ children }) => <span className=\"wb-badge\">{children}</span>,\n};\n\nexport type StoreViewProps<Theme> = {\n t: StoreText;\n /** The site's name in messages (\"X\"). */\n site: string;\n /** `store_catalog` (the app passes `refresh => invoke('store_catalog', { refresh })`). */\n load(refresh: boolean): Promise<StoreCatalogView>;\n /** The install screen's controller: Install opens it; the list refreshes when it closes. */\n controller: InstallController<Theme>;\n /** Theme ids that ship with the app, and the owner's own theme ids now. */\n builtinThemes: readonly string[];\n userThemes: readonly string[];\n /** Removes an installed item (plugin file, or the owner's copy of a store theme); false: nothing to remove. */\n onRemove(item: StoreItem, view: StoreCatalogView): Promise<boolean>;\n /** Makes a built-in theme the current one. */\n onUse(item: StoreItem): Promise<void>;\n notify(text: string, error?: boolean): void;\n /** \"3 minutes ago\" for a timestamp (the app's relative-time formatter). */\n relativeTime(at: number): string;\n /** Window events after which the list reloads (e.g. the app's \"plugins changed\"). */\n reloadOn?: readonly string[];\n controls?: Partial<StoreControls>;\n};\n\nconst STORE_ICON = <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 9l1.5-5h13L20 9M4 9h16v11H4zM4 9a2.7 2.7 0 0 0 5.3 0 2.7 2.7 0 0 0 5.4 0 2.7 2.7 0 0 0 5.3 0M10 20v-5h4v5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinejoin=\"round\"/></svg>;\n\nfunction Preview({ item }: { item: StoreItem }) {\n const [broken, setBroken] = useState(false);\n const c = item.colors;\n if (item.preview && !broken) return <img className=\"wbs-img\" src={item.preview} alt=\"\" loading=\"lazy\" onError={() => setBroken(true)}/>;\n // Offline or not published yet: swatches from the signed catalog (themes) or a plain tile.\n return <div className=\"wbs-img wbs-swatch\" style={c ? { background: c.bg } : undefined} aria-hidden=\"true\">\n {c ? <><span style={{ background: c.surface ?? c.bg, borderColor: c.text }}/><i style={{ background: c.text }}/><b style={{ background: c.accent }}/></> : <strong>{item.name.slice(0, 1)}</strong>}\n </div>;\n}\n\nfunction Action({ item, state, busy, t, ui, onInstall, onRemove, onUse }: { item: StoreItem; state: ItemState; busy: boolean; t: StoreText; ui: StoreControls; onInstall(): void; onRemove(): void; onUse(): void }) {\n const { Button } = ui;\n const [asking, setAsking] = useState(false);\n if (state.state === 'builtin') return <div className=\"wbs-actions\"><span className=\"wbs-state\">{t('wb.store.builtIn')}</span><Button size=\"sm\" onClick={onUse}>{t('wb.store.use')}</Button></div>;\n if (state.state === 'install') return <div className=\"wbs-actions\"><Button size=\"sm\" tone=\"primary\" disabled={busy} onClick={onInstall}>{t('wb.store.install')}</Button></div>;\n const remove = asking\n ? <><Button size=\"sm\" tone=\"danger\" onClick={() => { setAsking(false); onRemove(); }}>{t('wb.store.remove')}</Button><Button size=\"sm\" tone=\"ghost\" onClick={() => setAsking(false)}>{t('wb.store.keep')}</Button></>\n : <Button size=\"sm\" tone=\"ghost\" onClick={() => setAsking(true)}>{t('wb.store.remove')}</Button>;\n if (state.state === 'update') return <div className=\"wbs-actions\"><Button size=\"sm\" tone=\"primary\" disabled={busy} onClick={onInstall}>{t('wb.store.updateTo', { version: item.version })}</Button>{remove}</div>;\n if (state.state === 'modified') return <div className=\"wbs-actions\"><span className=\"wbs-state\" title={t('wb.store.modifiedTitle')}>{t('wb.store.modified')}</span><Button size=\"sm\" disabled={busy} onClick={onInstall}>{t('wb.store.replace')}</Button>{remove}</div>;\n return <div className=\"wbs-actions\"><span className=\"wbs-state is-ok\">{state.version ? t('wb.store.installedVersion', { version: state.version }) : t('wb.store.installed')}</span>{remove}</div>;\n}\n\nexport function StoreView<Theme>(props: StoreViewProps<Theme>) {\n const { t, site, controller, notify } = props;\n const ui: StoreControls = { ...DEFAULT_CONTROLS, ...props.controls };\n const { Button, SearchField, Segmented, EmptyState, Badge } = ui;\n const [view, setView] = useState<StoreCatalogView | null>(null);\n const [loading, setLoading] = useState(false);\n const [query, setQuery] = useState('');\n const [kind, setKind] = useState<'all' | 'theme' | 'plugin' | 'strings'>('all');\n const [category, setCategory] = useState<string>('all');\n const request = useSyncExternalStore(controller.subscribe, controller.current, controller.current);\n\n const load = (refresh: boolean) => {\n setLoading(true);\n return props.load(refresh).then(setView).catch(error => notify(errorText(error), true)).finally(() => setLoading(false));\n };\n useEffect(() => { void load(false); }, []);\n // Installs finish in the install screen; refresh what is installed afterwards.\n useEffect(() => {\n const again = () => void load(false);\n const events = props.reloadOn ?? [];\n for (const name of events) window.addEventListener(name, again);\n return () => { for (const name of events) window.removeEventListener(name, again); };\n }, [(props.reloadOn ?? []).join(' ')]);\n useEffect(() => { if (request === null) void load(false); }, [request === null]);\n\n const items = view?.catalog?.items ?? [];\n const context = { plugins: view?.plugins ?? {}, records: view?.records.items ?? {}, builtinThemes: props.builtinThemes, userThemes: props.userThemes };\n const categories = useMemo(() => [...new Set(items.filter(item => kind === 'all' || item.kind === kind).map(item => item.category))].sort(), [items, kind]);\n const shown = filterItems(items, query, kind, category);\n const updates = items.filter(item => itemState(item, context).state === 'update');\n\n const ago = (iso: string | null) => {\n if (!iso) return '';\n const at = Date.parse(iso);\n return Number.isFinite(at) ? props.relativeTime(Math.min(at, Date.now())) : '';\n };\n\n async function removeItem(item: StoreItem) {\n if (!view) return;\n try {\n if (!(await props.onRemove(item, view))) return;\n notify(t('wb.store.removed', { name: item.name }));\n await load(false);\n } catch (error) { notify(errorText(error), true); }\n }\n\n const status = view?.error\n ? <span className=\"wbs-status is-warn\" role=\"status\">{view.catalog ? t('wb.store.offline', { when: ago(view.fetchedAt), error: errorText(view.error) }) : t('wb.store.unreachableShort')}</span>\n : view?.catalog ? <span className=\"wbs-status is-ok\" role=\"status\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M12 3l7 3v5c0 4.6-3 8.5-7 10-4-1.5-7-5.4-7-10V6zM8.5 12l2.5 2.5 4.5-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/></svg>{t('wb.store.verified', { when: ago(view.fetchedAt) })}</span> : null;\n\n return <div className=\"wbs\">\n <p className=\"wbs-intro\">{t('wb.store.intro')}</p>\n <div className=\"wbs-toolbar\">\n <SearchField label={t('wb.store.search')} placeholder={t('wb.store.searchPlaceholder')} value={query} onChange={setQuery}/>\n <Segmented label={t('wb.store.kind')} size=\"sm\" value={kind} onChange={next => { setKind(next); setCategory('all'); }} options={[{ value: 'all', label: t('wb.store.all') }, { value: 'theme', label: t('wb.store.themes') }, { value: 'plugin', label: t('wb.store.pluginsTab') }, ...(items.some(item => item.kind === 'strings') ? [{ value: 'strings' as const, label: t('wb.store.strings') }] : [])]}/>\n <Button size=\"sm\" tone=\"ghost\" disabled={loading} onClick={() => void load(true)}>{loading ? t('wb.store.checking') : t('wb.store.refresh')}</Button>\n </div>\n <div className=\"wbs-cats\" role=\"group\" aria-label={t('wb.store.categories')}>\n {['all', ...categories].map(id => <button key={id} className={category === id ? 'is-on' : ''} aria-pressed={category === id} onClick={() => setCategory(id)}>{id === 'all' ? t('wb.store.everything') : categoryLabel(id, t)}</button>)}\n <span className=\"wbs-cats-end\"/>\n {status}\n </div>\n {updates.length > 0 && <div className=\"wbs-updates\" role=\"status\"><strong>{t('wb.store.updates', { count: updates.length })}</strong><span>{updates.map(item => item.name).join(', ')}</span></div>}\n {!view && loading && <div className=\"wbs-grid\" aria-busy=\"true\">{Array.from({ length: 6 }, (_, i) => <div key={i} className=\"wbs-card is-skeleton\"/>)}</div>}\n {view && !view.catalog && <EmptyState icon={STORE_ICON} title={t('wb.store.unreachable')} action={<Button size=\"sm\" onClick={() => void load(true)}>{t('wb.store.tryAgain')}</Button>}>\n {view.error ? errorText(view.error).replace(/\\.?$/, '.') : t('wb.store.noCatalog')} {t('wb.store.nothingChanged')}\n </EmptyState>}\n {view?.catalog && shown.length === 0 && <EmptyState icon={STORE_ICON} title={t('wb.store.noMatch')}>{t('wb.store.noMatchHint')}</EmptyState>}\n <div className=\"wbs-grid\">\n {shown.map(item => {\n const state = itemState(item, context);\n return <article key={`${item.kind}:${item.id}`} className={`wbs-card is-${item.kind}`}>\n <Preview item={item}/>\n <div className=\"wbs-card-body\">\n <header>\n <h3>{item.name}</h3>\n <Badge>{item.kind === 'theme' ? t('wb.store.theme') : item.kind === 'strings' ? t('wb.store.stringsItem') : t('wb.store.plugin')}</Badge>\n </header>\n <p className=\"wbs-meta\">{item.author && `${t('wb.store.by', { author: item.author })} · `}v{item.version} · {item.kind === 'strings' && item.language ? item.language : categoryLabel(item.category, t)}{item.license ? ` · ${item.license}` : ''}</p>\n <p className=\"wbs-desc\">{item.description}</p>\n {item.kind === 'plugin' && <ul className=\"wbs-perms\" aria-label={t('wb.store.permissions')}>\n {permissionBadges(item.permissions, t, site).map(badge => <li key={badge.id} className={`is-${badge.tone}`} title={badge.detail}>{badge.label}</li>)}\n </ul>}\n <Action item={item} state={state} busy={request !== null} t={t} ui={ui}\n onInstall={() => void controller.requestStoreItem(item)}\n onRemove={() => void removeItem(item)}\n onUse={() => void props.onUse(item).then(() => notify(t('wb.store.themeNow', { name: item.name }))).catch(error => notify(errorText(error), true))}/>\n </div>\n </article>;\n })}\n </div>\n <p className=\"wbs-foot\">{t('wb.store.foot')}</p>\n </div>;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addressModel.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"addressModel.js","sourceRoot":"","sources":["../../../../../shell/src/addressModel.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure, unit-testable model for the address bar's breadcrumb chips.\n// No React, no Tauri: parseAddress(url, site) -> AddressModel | null. The site adapter turns its\n// own URLs into named chips; any other site gets host + path chips.\n\nexport type Segment = { label: string; href: string; mono?: boolean; quoted?: boolean };\nexport type AddressModel<Kind extends string = string> = {\n site: { kind: Kind | 'host'; label: string; href: string };\n segments: Segment[];\n insecure: boolean;\n};\n\nexport type AddressSite<Kind extends string = string> = {\n /** `site.kind` for the adapter's own pages, e.g. 'x'. */\n kind: Kind;\n /** Hostnames (lowercase) that are the site. */\n hosts: ReadonlySet<string>;\n /** The site chip's label. */\n label: string;\n /** Where the site chip links: its home page on this origin. */\n home(origin: string): string;\n /** Named chips for one of the site's URLs, or null to show no chips (an internal page). */\n segments(url: URL): Segment[] | null;\n};\n\nexport function truncate(value: string, max: number): string {\n return value.length > max ? `${value.slice(0, max - 1)}…` : value;\n}\n\n/** Long numeric ids shown by their last 6 characters. */\nexport function last6(id: string): string {\n return `…${id.slice(-6)}`;\n}\n\nexport function titleCase(part: string): string {\n return part\n .split(/[-_]/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/** Generic chips: one per path segment, then `?…` for a query. */\nexport function hostSegments(url: URL): Segment[] {\n const origin = `${url.protocol}//${url.hostname}`;\n const parts = url.pathname.split('/').filter(Boolean);\n const segments: Segment[] = [];\n parts.forEach((part, index) => {\n const href = `${origin}/${parts.slice(0, index + 1).join('/')}`;\n segments.push({ label: truncate(decodeURIComponent(part), 24), href });\n });\n if (url.search) {\n segments.push({ label: '?…', href: `${origin}${url.pathname}${url.search}` });\n }\n return segments;\n}\n\nexport function parseAddress<Kind extends string>(url: string, site?: AddressSite<Kind>): AddressModel<Kind> | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return null;\n }\n const insecure = parsed.protocol === 'http:';\n const hostname = parsed.hostname.replace(/^www\\./, '');\n\n if (site && site.hosts.has(parsed.hostname.toLowerCase())) {\n const origin = `${parsed.protocol}//${parsed.hostname}`;\n const segments = site.segments(parsed);\n if (segments === null) {\n return null;\n }\n const last = segments[segments.length - 1];\n if (last) (last as Segment & { current?: boolean }).current = true;\n return {\n site: { kind: site.kind, label: site.label, href: site.home(origin) },\n segments,\n insecure,\n };\n }\n\n return {\n site: { kind: 'host', label: hostname, href: `${parsed.protocol}//${parsed.hostname}` },\n segments: hostSegments(parsed),\n insecure,\n };\n}\n"]}
|
package/dist/shell/chrome.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chrome.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../../../../shell/src/chrome.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,6FAA6F;AAC7F,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC","sourcesContent":["// Layout constants for the custom window chrome (titlebar + toolbar), in logical px. TITLEBAR_H and\n// RESIZE_BORDER must match `policy::TITLEBAR_H` / `policy::RESIZE_BORDER` in waybetter-host.\nexport const TITLEBAR_H = 44;\nexport const TOOLBAR_H = 50;\nexport const RESIZE_BORDER = 5;\n"]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** A column's content, named by the site adapter (e.g. `{ kind: 'search', value: 'from:me' }`). */
|
|
2
|
+
export type ColumnSource = {
|
|
3
|
+
kind: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
};
|
|
6
|
+
/** Per-column filters, applied by the page runtime to the site's items (runtime/column.ts). */
|
|
7
|
+
export type ColumnFilters = {
|
|
8
|
+
/** Keywords (case-insensitive) or `/regex/flags`; a match hides the item. */
|
|
9
|
+
terms: string[];
|
|
10
|
+
hideReplies: boolean;
|
|
11
|
+
hideReposts: boolean;
|
|
12
|
+
mediaOnly: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type DeckColumn = {
|
|
15
|
+
id: string;
|
|
16
|
+
/** The tab webview id this column runs in (`tab-<tabId>` natively). */
|
|
17
|
+
tabId: string;
|
|
18
|
+
/** Account profile of the column's webview. */
|
|
19
|
+
profile: string;
|
|
20
|
+
source: ColumnSource;
|
|
21
|
+
/** Preferred width in px; a page stretches its columns to fill the viewport. */
|
|
22
|
+
width: number;
|
|
23
|
+
/** Per-column feature flags over the global ones (allow-listed natively). */
|
|
24
|
+
flags: Record<string, boolean>;
|
|
25
|
+
filters: ColumnFilters;
|
|
26
|
+
/** Optional title the user gave the column. */
|
|
27
|
+
title?: string;
|
|
28
|
+
};
|
|
29
|
+
export type Deck = {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
columns: DeckColumn[];
|
|
33
|
+
};
|
|
34
|
+
export declare const COLUMN_WIDTHS: readonly [320, 360, 400, 480];
|
|
35
|
+
export declare const DEFAULT_COLUMN_WIDTH = 360;
|
|
36
|
+
export declare const MIN_COLUMN_WIDTH = 280;
|
|
37
|
+
export declare const MAX_COLUMN_WIDTH = 640;
|
|
38
|
+
export declare const MAX_COLUMNS = 24;
|
|
39
|
+
export declare const MAX_DECKS = 12;
|
|
40
|
+
export declare const MAX_TERMS = 32;
|
|
41
|
+
export declare const MAX_TERM_CHARS = 200;
|
|
42
|
+
export declare const MAX_FLAGS = 16;
|
|
43
|
+
/** Visible columns per page before the memory guard warns. */
|
|
44
|
+
export declare const DEFAULT_MAX_VISIBLE = 6;
|
|
45
|
+
/** Gap between columns in px. */
|
|
46
|
+
export declare const COLUMN_GAP = 8;
|
|
47
|
+
export declare const emptyFilters: () => ColumnFilters;
|
|
48
|
+
export declare const validColumnId: (id: unknown) => id is string;
|
|
49
|
+
export declare function normalizeFilters(value: unknown): ColumnFilters;
|
|
50
|
+
export declare function normalizeFlags(value: unknown, allowed?: readonly string[]): Record<string, boolean>;
|
|
51
|
+
export declare const clampWidth: (width: unknown) => number;
|
|
52
|
+
export type NormalizeOptions = {
|
|
53
|
+
/** Source kinds the site adapter knows; others are dropped. */
|
|
54
|
+
kinds?: readonly string[];
|
|
55
|
+
/** Profile ids that exist; a column of a removed profile moves to `fallbackProfile`. */
|
|
56
|
+
profiles?: readonly string[];
|
|
57
|
+
fallbackProfile?: string;
|
|
58
|
+
/** Allow-listed per-column flags. */
|
|
59
|
+
flags?: readonly string[];
|
|
60
|
+
};
|
|
61
|
+
export declare function normalizeColumn(value: unknown, options?: NormalizeOptions): DeckColumn | null;
|
|
62
|
+
/** Anything from storage → a bounded deck (unique column and tab ids, at most MAX_COLUMNS). */
|
|
63
|
+
export declare function normalizeDeck(value: unknown, options?: NormalizeOptions): Deck | null;
|
|
64
|
+
export declare function normalizeDecks(value: unknown, options?: NormalizeOptions): Deck[];
|
|
65
|
+
export type PlacedColumn = {
|
|
66
|
+
id: string;
|
|
67
|
+
x: number;
|
|
68
|
+
width: number;
|
|
69
|
+
};
|
|
70
|
+
export type DeckLayout = {
|
|
71
|
+
/** The page shown (clamped into range). */
|
|
72
|
+
page: number;
|
|
73
|
+
pages: number;
|
|
74
|
+
visible: PlacedColumn[];
|
|
75
|
+
/** Columns on other pages: their webviews are hidden (and parked when the setting says so). */
|
|
76
|
+
parked: string[];
|
|
77
|
+
};
|
|
78
|
+
export type LayoutOptions = {
|
|
79
|
+
gap?: number;
|
|
80
|
+
maxVisible?: number;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Splits columns into pages of whole columns that fit `viewportW` (at least one per page). Pages
|
|
84
|
+
* are filled from the left; the last page is filled from the right end instead, so it is as full as
|
|
85
|
+
* the others (it may repeat columns of the page before) rather than one lone stretched column.
|
|
86
|
+
*/
|
|
87
|
+
export declare function paginate(viewportW: number, columns: readonly Pick<DeckColumn, 'id' | 'width'>[], options?: LayoutOptions): string[][];
|
|
88
|
+
/** A page's columns stretch to fill the viewport, but never beyond this factor of their width. */
|
|
89
|
+
export declare const MAX_STRETCH = 2;
|
|
90
|
+
/**
|
|
91
|
+
* Where each column of `page` goes in a viewport `viewportW` px wide. The page's columns stretch
|
|
92
|
+
* evenly to fill the width (no ragged gap on the right); a lone column wider than the viewport
|
|
93
|
+
* is squeezed to it. Columns on other pages are `parked`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function layoutColumns(viewportW: number, columns: readonly Pick<DeckColumn, 'id' | 'width'>[], page: number, options?: LayoutOptions): DeckLayout;
|
|
96
|
+
/**
|
|
97
|
+
* The page that shows column `id`: `current` when it is on it already (so focusing a column never
|
|
98
|
+
* flips pages needlessly), else the first page that holds it (0 when it is unknown).
|
|
99
|
+
*/
|
|
100
|
+
export declare function pageOf(viewportW: number, columns: readonly Pick<DeckColumn, 'id' | 'width'>[], id: string, options?: LayoutOptions, current?: number): number;
|
|
101
|
+
/** The column `delta` steps from `focused` (clamped at the ends; the first one when none is focused). */
|
|
102
|
+
export declare function moveFocus(columns: readonly Pick<DeckColumn, 'id'>[], focused: string | null | undefined, delta: number): string | null;
|
|
103
|
+
/** The URL a column loads: the adapter's `urlFor`, or null for a source it does not know. */
|
|
104
|
+
export declare function columnUrl(source: ColumnSource, urlFor: (source: ColumnSource) => string | null): string | null;
|
|
105
|
+
/** Unread count to show for a deck: the sum over its columns, capped like the native report (999). */
|
|
106
|
+
export declare const MAX_UNREAD = 999;
|
|
107
|
+
export declare function unreadTotal(unread: ReadonlyMap<string, number> | Record<string, number>, columns: readonly Pick<DeckColumn, 'tabId'>[]): number;
|
|
108
|
+
/** Moves column `id` by `delta` places (clamped); returns a new array. */
|
|
109
|
+
export declare function moveColumn<T extends {
|
|
110
|
+
id: string;
|
|
111
|
+
}>(columns: readonly T[], id: string, delta: number): T[];
|
|
112
|
+
/**
|
|
113
|
+
* A column still shows its source page (origin, path with the trailing slash ignored, and query);
|
|
114
|
+
* otherwise it navigated away (a post, a profile) and its header offers the way back. The page
|
|
115
|
+
* runtime uses the same rule (runtime/column.ts `isSourcePage`).
|
|
116
|
+
*/
|
|
117
|
+
export declare function isSourcePage(href: string, source: string): boolean;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Power User Mode: a deck of narrow site columns (TweetDeck-style). Pure model (no Tauri/DOM), so
|
|
2
|
+
// it is unit-tested under node. Every column is a real site tab webview in its own account profile;
|
|
3
|
+
// this model only says which columns exist, which page of them is on screen and where each one
|
|
4
|
+
// goes. What a column shows is the site adapter's business (`urlFor`), what it hides is the page
|
|
5
|
+
// runtime's (runtime/column.ts). Paging, not pixel scrolling: a page is as many whole columns as
|
|
6
|
+
// fit the viewport; the rest are parked off-page (hidden, low memory).
|
|
7
|
+
export const COLUMN_WIDTHS = [320, 360, 400, 480];
|
|
8
|
+
export const DEFAULT_COLUMN_WIDTH = 360;
|
|
9
|
+
export const MIN_COLUMN_WIDTH = 280;
|
|
10
|
+
export const MAX_COLUMN_WIDTH = 640;
|
|
11
|
+
export const MAX_COLUMNS = 24;
|
|
12
|
+
export const MAX_DECKS = 12;
|
|
13
|
+
export const MAX_TERMS = 32;
|
|
14
|
+
export const MAX_TERM_CHARS = 200;
|
|
15
|
+
export const MAX_FLAGS = 16;
|
|
16
|
+
/** Visible columns per page before the memory guard warns. */
|
|
17
|
+
export const DEFAULT_MAX_VISIBLE = 6;
|
|
18
|
+
/** Gap between columns in px. */
|
|
19
|
+
export const COLUMN_GAP = 8;
|
|
20
|
+
export const emptyFilters = () => ({ terms: [], hideReplies: false, hideReposts: false, mediaOnly: false });
|
|
21
|
+
const ID = /^[a-z0-9][a-z0-9-]{0,39}$/i;
|
|
22
|
+
export const validColumnId = (id) => typeof id === 'string' && ID.test(id);
|
|
23
|
+
// eslint-disable-next-line no-control-regex
|
|
24
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
25
|
+
function cleanText(value, max) {
|
|
26
|
+
if (typeof value !== 'string')
|
|
27
|
+
return null;
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!trimmed || [...trimmed].length > max || CONTROL.test(trimmed))
|
|
30
|
+
return null;
|
|
31
|
+
return trimmed;
|
|
32
|
+
}
|
|
33
|
+
export function normalizeFilters(value) {
|
|
34
|
+
const raw = (value && typeof value === 'object' ? value : {});
|
|
35
|
+
const terms = [];
|
|
36
|
+
for (const term of Array.isArray(raw.terms) ? raw.terms : []) {
|
|
37
|
+
const clean = cleanText(term, MAX_TERM_CHARS);
|
|
38
|
+
if (clean && !terms.includes(clean) && terms.length < MAX_TERMS)
|
|
39
|
+
terms.push(clean);
|
|
40
|
+
}
|
|
41
|
+
return { terms, hideReplies: raw.hideReplies === true, hideReposts: raw.hideReposts === true, mediaOnly: raw.mediaOnly === true };
|
|
42
|
+
}
|
|
43
|
+
export function normalizeFlags(value, allowed) {
|
|
44
|
+
const flags = {};
|
|
45
|
+
if (!value || typeof value !== 'object')
|
|
46
|
+
return flags;
|
|
47
|
+
for (const [key, flag] of Object.entries(value)) {
|
|
48
|
+
if (typeof flag !== 'boolean' || !/^[a-zA-Z][a-zA-Z0-9]{0,47}$/.test(key))
|
|
49
|
+
continue;
|
|
50
|
+
if (allowed && !allowed.includes(key))
|
|
51
|
+
continue;
|
|
52
|
+
if (Object.keys(flags).length >= MAX_FLAGS)
|
|
53
|
+
break;
|
|
54
|
+
flags[key] = flag;
|
|
55
|
+
}
|
|
56
|
+
return flags;
|
|
57
|
+
}
|
|
58
|
+
export const clampWidth = (width) => typeof width === 'number' && Number.isFinite(width) ? Math.round(Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, width))) : DEFAULT_COLUMN_WIDTH;
|
|
59
|
+
export function normalizeColumn(value, options = {}) {
|
|
60
|
+
const raw = (value && typeof value === 'object' ? value : {});
|
|
61
|
+
if (!validColumnId(raw.id) || !validColumnId(raw.tabId))
|
|
62
|
+
return null;
|
|
63
|
+
const source = raw.source;
|
|
64
|
+
const kind = typeof source?.kind === 'string' && /^[a-z][a-z0-9-]{0,23}$/.test(source.kind) ? source.kind : null;
|
|
65
|
+
if (!kind || (options.kinds && !options.kinds.includes(kind)))
|
|
66
|
+
return null;
|
|
67
|
+
const sourceValue = source?.value === undefined ? undefined : cleanText(source.value, 512);
|
|
68
|
+
if (source?.value !== undefined && sourceValue === null)
|
|
69
|
+
return null;
|
|
70
|
+
let profile = typeof raw.profile === 'string' && raw.profile ? raw.profile : options.fallbackProfile ?? 'default';
|
|
71
|
+
if (options.profiles && !options.profiles.includes(profile))
|
|
72
|
+
profile = options.fallbackProfile ?? options.profiles[0] ?? 'default';
|
|
73
|
+
const title = cleanText(raw.title, 60);
|
|
74
|
+
return {
|
|
75
|
+
id: raw.id,
|
|
76
|
+
tabId: raw.tabId,
|
|
77
|
+
profile,
|
|
78
|
+
source: sourceValue ? { kind, value: sourceValue } : { kind },
|
|
79
|
+
width: clampWidth(raw.width),
|
|
80
|
+
flags: normalizeFlags(raw.flags, options.flags),
|
|
81
|
+
filters: normalizeFilters(raw.filters),
|
|
82
|
+
...(title ? { title } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Anything from storage → a bounded deck (unique column and tab ids, at most MAX_COLUMNS). */
|
|
86
|
+
export function normalizeDeck(value, options = {}) {
|
|
87
|
+
const raw = (value && typeof value === 'object' ? value : {});
|
|
88
|
+
if (!validColumnId(raw.id))
|
|
89
|
+
return null;
|
|
90
|
+
const name = cleanText(raw.name, 40) ?? 'Deck';
|
|
91
|
+
const columns = [];
|
|
92
|
+
const ids = new Set();
|
|
93
|
+
for (const item of Array.isArray(raw.columns) ? raw.columns : []) {
|
|
94
|
+
if (columns.length >= MAX_COLUMNS)
|
|
95
|
+
break;
|
|
96
|
+
const column = normalizeColumn(item, options);
|
|
97
|
+
if (!column || ids.has(column.id) || ids.has(`tab:${column.tabId}`))
|
|
98
|
+
continue;
|
|
99
|
+
ids.add(column.id);
|
|
100
|
+
ids.add(`tab:${column.tabId}`);
|
|
101
|
+
columns.push(column);
|
|
102
|
+
}
|
|
103
|
+
return { id: raw.id, name, columns };
|
|
104
|
+
}
|
|
105
|
+
export function normalizeDecks(value, options = {}) {
|
|
106
|
+
const decks = [];
|
|
107
|
+
const tabs = new Set();
|
|
108
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
109
|
+
if (decks.length >= MAX_DECKS)
|
|
110
|
+
break;
|
|
111
|
+
const deck = normalizeDeck(item, options);
|
|
112
|
+
if (!deck || decks.some(other => other.id === deck.id))
|
|
113
|
+
continue;
|
|
114
|
+
// A tab webview belongs to exactly one column of one deck.
|
|
115
|
+
deck.columns = deck.columns.filter(column => !tabs.has(column.tabId));
|
|
116
|
+
for (const column of deck.columns)
|
|
117
|
+
tabs.add(column.tabId);
|
|
118
|
+
decks.push(deck);
|
|
119
|
+
}
|
|
120
|
+
return decks;
|
|
121
|
+
}
|
|
122
|
+
/** How many columns from `start` (forward, or backward with `step` -1) fit `viewportW` (at least one). */
|
|
123
|
+
function fitCount(viewportW, widths, start, step, gap, maxVisible) {
|
|
124
|
+
let used = 0;
|
|
125
|
+
let count = 0;
|
|
126
|
+
for (let i = start; i >= 0 && i < widths.length && count < maxVisible; i += step) {
|
|
127
|
+
const needed = count ? used + gap + widths[i] : widths[i];
|
|
128
|
+
if (count && needed > viewportW)
|
|
129
|
+
break;
|
|
130
|
+
used = needed;
|
|
131
|
+
count++;
|
|
132
|
+
}
|
|
133
|
+
return Math.max(1, count);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Splits columns into pages of whole columns that fit `viewportW` (at least one per page). Pages
|
|
137
|
+
* are filled from the left; the last page is filled from the right end instead, so it is as full as
|
|
138
|
+
* the others (it may repeat columns of the page before) rather than one lone stretched column.
|
|
139
|
+
*/
|
|
140
|
+
export function paginate(viewportW, columns, options = {}) {
|
|
141
|
+
const gap = options.gap ?? COLUMN_GAP;
|
|
142
|
+
const maxVisible = Math.max(1, Math.floor(options.maxVisible ?? DEFAULT_MAX_VISIBLE));
|
|
143
|
+
const widths = columns.map(column => clampWidth(column.width));
|
|
144
|
+
const pages = [];
|
|
145
|
+
let start = 0;
|
|
146
|
+
while (start < columns.length) {
|
|
147
|
+
const count = fitCount(viewportW, widths, start, 1, gap, maxVisible);
|
|
148
|
+
if (start + count >= columns.length && pages.length) {
|
|
149
|
+
// The last page: as many columns as fit, counted back from the end.
|
|
150
|
+
const back = fitCount(viewportW, widths, columns.length - 1, -1, gap, maxVisible);
|
|
151
|
+
pages.push(columns.slice(columns.length - back).map(column => column.id));
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
pages.push(columns.slice(start, start + count).map(column => column.id));
|
|
155
|
+
start += count;
|
|
156
|
+
}
|
|
157
|
+
return pages;
|
|
158
|
+
}
|
|
159
|
+
/** A page's columns stretch to fill the viewport, but never beyond this factor of their width. */
|
|
160
|
+
export const MAX_STRETCH = 2;
|
|
161
|
+
/**
|
|
162
|
+
* Where each column of `page` goes in a viewport `viewportW` px wide. The page's columns stretch
|
|
163
|
+
* evenly to fill the width (no ragged gap on the right); a lone column wider than the viewport
|
|
164
|
+
* is squeezed to it. Columns on other pages are `parked`.
|
|
165
|
+
*/
|
|
166
|
+
export function layoutColumns(viewportW, columns, page, options = {}) {
|
|
167
|
+
const gap = options.gap ?? COLUMN_GAP;
|
|
168
|
+
const width = Math.max(0, Math.floor(viewportW));
|
|
169
|
+
const pages = paginate(width, columns, options);
|
|
170
|
+
if (!pages.length)
|
|
171
|
+
return { page: 0, pages: 0, visible: [], parked: [] };
|
|
172
|
+
const index = Math.min(pages.length - 1, Math.max(0, Math.floor(Number.isFinite(page) ? page : 0)));
|
|
173
|
+
const ids = pages[index];
|
|
174
|
+
const byId = new Map(columns.map(column => [column.id, clampWidth(column.width)]));
|
|
175
|
+
const preferred = ids.map(id => byId.get(id));
|
|
176
|
+
const sum = preferred.reduce((total, w) => total + w, 0);
|
|
177
|
+
const room = Math.max(ids.length, width - gap * (ids.length - 1));
|
|
178
|
+
// Integer px in proportion to the preferred widths; the rounding remainder goes 1 px per column
|
|
179
|
+
// from the left, so the row ends exactly at the viewport edge.
|
|
180
|
+
// Stretch to fill, up to MAX_STRETCH; a page of few columns on a wide screen leaves room at the end.
|
|
181
|
+
const target = Math.min(room, Math.floor(sum * MAX_STRETCH));
|
|
182
|
+
const widths = preferred.map(w => Math.max(1, Math.floor((w * target) / sum)));
|
|
183
|
+
let remainder = target - widths.reduce((total, w) => total + w, 0);
|
|
184
|
+
for (let i = 0; remainder > 0; i = (i + 1) % widths.length, remainder--)
|
|
185
|
+
widths[i] += 1;
|
|
186
|
+
const visible = [];
|
|
187
|
+
let x = 0;
|
|
188
|
+
ids.forEach((id, i) => {
|
|
189
|
+
visible.push({ id, x, width: widths[i] });
|
|
190
|
+
x += widths[i] + gap;
|
|
191
|
+
});
|
|
192
|
+
const shown = new Set(ids);
|
|
193
|
+
return { page: index, pages: pages.length, visible, parked: columns.map(c => c.id).filter(id => !shown.has(id)) };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The page that shows column `id`: `current` when it is on it already (so focusing a column never
|
|
197
|
+
* flips pages needlessly), else the first page that holds it (0 when it is unknown).
|
|
198
|
+
*/
|
|
199
|
+
export function pageOf(viewportW, columns, id, options = {}, current) {
|
|
200
|
+
const pages = paginate(Math.max(0, Math.floor(viewportW)), columns, options);
|
|
201
|
+
if (current !== undefined && pages[current]?.includes(id))
|
|
202
|
+
return current;
|
|
203
|
+
const index = pages.findIndex(page => page.includes(id));
|
|
204
|
+
return index < 0 ? 0 : index;
|
|
205
|
+
}
|
|
206
|
+
/** The column `delta` steps from `focused` (clamped at the ends; the first one when none is focused). */
|
|
207
|
+
export function moveFocus(columns, focused, delta) {
|
|
208
|
+
if (!columns.length)
|
|
209
|
+
return null;
|
|
210
|
+
const index = columns.findIndex(column => column.id === focused);
|
|
211
|
+
if (index < 0)
|
|
212
|
+
return columns[delta < 0 ? columns.length - 1 : 0].id;
|
|
213
|
+
return columns[Math.min(columns.length - 1, Math.max(0, index + Math.trunc(delta)))].id;
|
|
214
|
+
}
|
|
215
|
+
/** The URL a column loads: the adapter's `urlFor`, or null for a source it does not know. */
|
|
216
|
+
export function columnUrl(source, urlFor) {
|
|
217
|
+
try {
|
|
218
|
+
const url = urlFor(source);
|
|
219
|
+
if (!url)
|
|
220
|
+
return null;
|
|
221
|
+
const parsed = new URL(url);
|
|
222
|
+
return parsed.protocol === 'https:' && !parsed.username && !parsed.password ? parsed.href : null;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Unread count to show for a deck: the sum over its columns, capped like the native report (999). */
|
|
229
|
+
export const MAX_UNREAD = 999;
|
|
230
|
+
export function unreadTotal(unread, columns) {
|
|
231
|
+
const get = (id) => (unread instanceof Map ? unread.get(id) : unread[id]) ?? 0;
|
|
232
|
+
let total = 0;
|
|
233
|
+
for (const column of columns) {
|
|
234
|
+
const value = get(column.tabId);
|
|
235
|
+
if (Number.isFinite(value) && value > 0)
|
|
236
|
+
total += Math.floor(value);
|
|
237
|
+
}
|
|
238
|
+
return Math.min(MAX_UNREAD, total);
|
|
239
|
+
}
|
|
240
|
+
/** Moves column `id` by `delta` places (clamped); returns a new array. */
|
|
241
|
+
export function moveColumn(columns, id, delta) {
|
|
242
|
+
const index = columns.findIndex(column => column.id === id);
|
|
243
|
+
if (index < 0)
|
|
244
|
+
return [...columns];
|
|
245
|
+
const target = Math.min(columns.length - 1, Math.max(0, index + Math.trunc(delta)));
|
|
246
|
+
const next = [...columns];
|
|
247
|
+
const [item] = next.splice(index, 1);
|
|
248
|
+
next.splice(target, 0, item);
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* A column still shows its source page (origin, path with the trailing slash ignored, and query);
|
|
253
|
+
* otherwise it navigated away (a post, a profile) and its header offers the way back. The page
|
|
254
|
+
* runtime uses the same rule (runtime/column.ts `isSourcePage`).
|
|
255
|
+
*/
|
|
256
|
+
export function isSourcePage(href, source) {
|
|
257
|
+
try {
|
|
258
|
+
const a = new URL(href);
|
|
259
|
+
const b = new URL(source);
|
|
260
|
+
const path = (url) => url.pathname.replace(/\/+$/, '') || '/';
|
|
261
|
+
return a.origin === b.origin && path(a) === path(b) && a.search === b.search;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=deckModel.js.map
|