mulmoclaude 0.5.2 → 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/Dockerfile.sandbox +100 -0
- package/README.md +17 -4
- package/bin/mulmoclaude.js +46 -15
- package/bin/prepare-dist.js +18 -2
- package/client/assets/chunk-CernVdwh.js +1 -0
- package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
- package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
- package/client/assets/index-BbgSjFQ8.js +4968 -0
- package/client/assets/index-ECD0lgIv.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
- package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
- package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
- package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
- package/client/assets/vue-1e_vz2LW.js +1 -0
- package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
- package/client/index.html +33 -2
- package/package.json +20 -18
- package/sandbox-entrypoint.sh +106 -0
- package/server/accounting/accountNormalize.ts +32 -0
- package/server/accounting/defaultAccounts.ts +87 -0
- package/server/accounting/eventPublisher.ts +51 -0
- package/server/accounting/journal.ts +252 -0
- package/server/accounting/openingBalances.ts +114 -0
- package/server/accounting/report.ts +237 -0
- package/server/accounting/service.ts +718 -0
- package/server/accounting/snapshotCache.ts +333 -0
- package/server/accounting/timeSeries.ts +265 -0
- package/server/accounting/types.ts +148 -0
- package/server/agent/activeTools.ts +128 -0
- package/server/agent/attachmentConverter.ts +10 -5
- package/server/agent/backend/claude-code.ts +8 -2
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/config.ts +101 -31
- package/server/agent/index.ts +45 -33
- package/server/agent/mcp-server.ts +146 -69
- package/server/agent/mcp-tools/index.ts +1 -5
- package/server/agent/mcp-tools/notify.ts +2 -22
- package/server/agent/mcp-tools/x.ts +0 -4
- package/server/agent/mcpHealth.ts +168 -0
- package/server/agent/plugin-names.ts +20 -77
- package/server/agent/prompt.ts +259 -51
- package/server/agent/resumeFailover.ts +1 -1
- package/server/agent/stream.ts +0 -1
- package/server/api/auth/bearerAuth.ts +5 -5
- package/server/api/csrfGuard.ts +1 -1
- package/server/api/routes/accounting.ts +366 -0
- package/server/api/routes/agent.ts +509 -46
- package/server/api/routes/attachment.ts +104 -0
- package/server/api/routes/chart.ts +2 -1
- package/server/api/routes/config.ts +12 -12
- package/server/api/routes/files.ts +105 -48
- package/server/api/routes/image.ts +70 -25
- package/server/api/routes/journal.ts +35 -0
- package/server/api/routes/mulmo-script.ts +358 -118
- package/server/api/routes/mulmoScriptValidate.ts +1 -1
- package/server/api/routes/news.ts +1 -1
- package/server/api/routes/notifications.ts +92 -22
- package/server/api/routes/notifier.ts +98 -0
- package/server/api/routes/pdf.ts +188 -48
- package/server/api/routes/plugins.ts +34 -14
- package/server/api/routes/presentHtml.ts +58 -3
- package/server/api/routes/roles.ts +1 -8
- package/server/api/routes/runtime-plugin.ts +224 -0
- package/server/api/routes/scheduler.ts +7 -5
- package/server/api/routes/schedulerHandlers.ts +1 -1
- package/server/api/routes/schedulerTasks.ts +8 -7
- package/server/api/routes/sessions.ts +234 -121
- package/server/api/routes/skills.ts +56 -51
- package/server/api/routes/sources.ts +52 -45
- package/server/api/routes/translation.ts +44 -0
- package/server/api/routes/wiki/frontmatter.ts +13 -65
- package/server/api/routes/wiki/history.ts +261 -0
- package/server/api/routes/wiki/pageIndex.ts +1 -1
- package/server/api/routes/wiki.ts +50 -26
- package/server/events/file-change.ts +83 -0
- package/server/events/notifications.ts +247 -91
- package/server/events/pub-sub/index.ts +1 -1
- package/server/events/relay-client.ts +5 -5
- package/server/events/scheduler-adapter.ts +2 -2
- package/server/events/session-store/index.ts +110 -22
- package/server/events/task-manager/index.ts +10 -9
- package/server/index.ts +509 -33
- package/server/notifier/engine.ts +419 -0
- package/server/notifier/legacy-adapters.ts +76 -0
- package/server/notifier/runtime-api.ts +74 -0
- package/server/notifier/store.ts +70 -0
- package/server/notifier/types.ts +121 -0
- package/server/plugins/dev-loader.ts +171 -0
- package/server/plugins/dev-watcher.ts +150 -0
- package/server/plugins/diagnostics.ts +188 -0
- package/server/plugins/preset-list.ts +52 -0
- package/server/plugins/preset-loader.ts +112 -0
- package/server/plugins/runtime-chat-api.ts +38 -0
- package/server/plugins/runtime-loader.ts +430 -0
- package/server/plugins/runtime-registry.ts +112 -0
- package/server/plugins/runtime-tasks-api.ts +50 -0
- package/server/plugins/runtime.ts +378 -0
- package/server/services/translation/cache.ts +72 -0
- package/server/services/translation/index.ts +106 -0
- package/server/services/translation/llm.ts +140 -0
- package/server/services/translation/types.ts +35 -0
- package/server/system/credentials.ts +13 -2
- package/server/system/env.ts +6 -1
- package/server/system/logger/formatters.ts +46 -4
- package/server/system/logger/index.ts +4 -4
- package/server/system/logger/sinks.ts +26 -5
- package/server/system/logger/types.ts +2 -2
- package/server/utils/dev-plugin-args.d.mts +11 -0
- package/server/utils/dev-plugin-args.mjs +43 -0
- package/server/utils/errors.ts +13 -4
- package/server/utils/files/accounting-io.ts +295 -0
- package/server/utils/files/atomic.ts +17 -49
- package/server/utils/files/attachment-store.ts +182 -0
- package/server/utils/files/html-io.ts +1 -7
- package/server/utils/files/html-store.ts +19 -0
- package/server/utils/files/image-store.ts +20 -22
- package/server/utils/files/index.ts +5 -15
- package/server/utils/files/journal-io.ts +7 -35
- package/server/utils/files/json.ts +2 -29
- package/server/utils/files/markdown-image-fill.ts +6 -37
- package/server/utils/files/markdown-store.ts +6 -21
- package/server/utils/files/naming.ts +3 -39
- package/server/utils/files/plugins-io.ts +100 -0
- package/server/utils/files/reference-dirs-io.ts +1 -9
- package/server/utils/files/roles-io.ts +2 -10
- package/server/utils/files/safe.ts +17 -19
- package/server/utils/files/scheduler-io.ts +1 -7
- package/server/utils/files/scheduler-overrides-io.ts +3 -12
- package/server/utils/files/session-io.ts +21 -30
- package/server/utils/files/spreadsheet-store.ts +9 -22
- package/server/utils/files/translation-io.ts +46 -0
- package/server/utils/files/user-tasks-io.ts +1 -7
- package/server/utils/files/workspace-io.ts +3 -79
- package/server/utils/gemini.ts +33 -11
- package/server/utils/html/htmlArtifactSplicer.ts +41 -0
- package/server/utils/markdown/frontmatter.ts +112 -0
- package/server/utils/regex.ts +56 -0
- package/server/utils/router.ts +41 -0
- package/server/utils/slug.ts +5 -3
- package/server/utils/time.ts +12 -0
- package/server/workspace/chat-index/indexer.ts +15 -2
- package/server/workspace/chat-index/summarizer.ts +1 -1
- package/server/workspace/custom-dirs.ts +1 -1
- package/server/workspace/helps/gemini.md +1 -1
- package/server/workspace/helps/guide.md +61 -0
- package/server/workspace/helps/index.md +4 -0
- package/server/workspace/helps/presenthtml.md +80 -0
- package/server/workspace/helps/sandbox.md +7 -0
- package/server/workspace/helps/storyteller.md +101 -0
- package/server/workspace/helps/telegram.md +1 -0
- package/server/workspace/helps/wiki.md +9 -7
- package/server/workspace/journal/archivist-cli.ts +7 -33
- package/server/workspace/journal/archivist-schemas.ts +5 -43
- package/server/workspace/journal/dailyPass.ts +34 -187
- package/server/workspace/journal/diff.ts +3 -28
- package/server/workspace/journal/index.ts +10 -81
- package/server/workspace/journal/indexFile.ts +3 -24
- package/server/workspace/journal/latestDaily.ts +51 -0
- package/server/workspace/journal/memoryExtractor.ts +4 -20
- package/server/workspace/journal/optimizationPass.ts +4 -21
- package/server/workspace/journal/paths.ts +4 -23
- package/server/workspace/journal/state.ts +6 -29
- package/server/workspace/memory/io.ts +213 -0
- package/server/workspace/memory/llm-classifier.ts +158 -0
- package/server/workspace/memory/migrate.ts +263 -0
- package/server/workspace/memory/run.ts +84 -0
- package/server/workspace/memory/topic-cluster.ts +218 -0
- package/server/workspace/memory/topic-detect.ts +67 -0
- package/server/workspace/memory/topic-index-hook.ts +128 -0
- package/server/workspace/memory/topic-io.ts +180 -0
- package/server/workspace/memory/topic-migrate.ts +248 -0
- package/server/workspace/memory/topic-run.ts +172 -0
- package/server/workspace/memory/topic-swap.ts +135 -0
- package/server/workspace/memory/topic-types.ts +142 -0
- package/server/workspace/memory/types.ts +83 -0
- package/server/workspace/news/reader.ts +4 -5
- package/server/workspace/paths.ts +124 -47
- package/server/workspace/roles.ts +2 -11
- package/server/workspace/skills/parser.ts +38 -55
- package/server/workspace/skills/user-tasks.ts +1 -2
- package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
- package/server/workspace/skills-preset.ts +196 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
- package/server/workspace/sources/fetchers/index.ts +1 -1
- package/server/workspace/sources/fetchers/rssParser.ts +1 -1
- package/server/workspace/sources/pipeline/index.ts +2 -2
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/write.ts +2 -2
- package/server/workspace/sources/registry.ts +39 -61
- package/server/workspace/sources/robots.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +2 -1
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +6 -1
- package/server/workspace/wiki-backlinks/index.ts +19 -7
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
- package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
- package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
- package/server/workspace/wiki-history/provision.ts +181 -0
- package/server/workspace/wiki-pages/io.ts +217 -0
- package/server/workspace/wiki-pages/snapshot.ts +380 -0
- package/server/workspace/workspace.ts +75 -13
- package/src/App.vue +115 -40
- package/src/_runtime/protocol-vue.ts +21 -0
- package/src/_runtime/vue.ts +22 -0
- package/src/components/ChatInput.vue +14 -10
- package/src/components/CopyChatButton.vue +76 -0
- package/src/components/FileContentRenderer.vue +67 -14
- package/src/components/FileTree.vue +2 -2
- package/src/components/FilesView.vue +17 -1
- package/src/components/NewsView.vue +16 -2
- package/src/components/NotificationBell.vue +320 -93
- package/src/components/PageChatComposer.vue +5 -4
- package/src/components/PluginLauncher.vue +42 -6
- package/src/components/PluginScopedRoot.vue +87 -0
- package/src/components/RoleSelector.vue +12 -1
- package/src/components/RolesView.vue +562 -0
- package/src/components/SentAttachmentChip.vue +102 -0
- package/src/components/SessionHistoryPanel.vue +109 -20
- package/src/components/SessionRoleIcon.vue +7 -4
- package/src/components/SessionSidebar.vue +20 -7
- package/src/components/SessionTabBar.vue +1 -1
- package/src/components/SettingsMcpTab.vue +4 -4
- package/src/components/SettingsModal.vue +2 -0
- package/src/components/SidebarHeader.vue +16 -5
- package/src/components/SourcesManager.vue +23 -9
- package/src/components/SourcesView.vue +1 -1
- package/src/components/StackView.vue +102 -6
- package/src/components/SuggestionsPanel.vue +105 -16
- package/src/components/SystemFileBanner.vue +1 -1
- package/src/components/TodoExplorer.vue +4 -5
- package/src/components/todo/TodoAddDialog.vue +2 -3
- package/src/components/todo/TodoEditDialog.vue +1 -2
- package/src/components/todo/TodoEditPanel.vue +2 -3
- package/src/components/todo/TodoKanbanView.vue +8 -5
- package/src/components/todo/TodoListView.vue +3 -5
- package/src/components/todo/TodoTableView.vue +7 -5
- package/src/composables/useAccountingChannel.ts +58 -0
- package/src/composables/useActiveSession.ts +4 -25
- package/src/composables/useAppApi.ts +6 -44
- package/src/composables/useClipboardCopy.ts +3 -20
- package/src/composables/useContentDisplay.ts +33 -2
- package/src/composables/useDevPluginReload.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +5 -31
- package/src/composables/useEventListeners.ts +0 -20
- package/src/composables/useExpandedDirs.ts +4 -15
- package/src/composables/useFaviconState.ts +12 -46
- package/src/composables/useFileChange.ts +53 -0
- package/src/composables/useFreshPluginData.ts +6 -43
- package/src/composables/useHealth.ts +14 -43
- package/src/composables/useImageErrorRepair.ts +104 -0
- package/src/composables/useLatestDaily.ts +40 -0
- package/src/composables/useMarkdownDoc.ts +39 -0
- package/src/composables/useMarkdownLinkHandler.ts +1 -1
- package/src/composables/useMcpTools.ts +3 -16
- package/src/composables/useNotifications.ts +138 -112
- package/src/composables/usePdfDownload.ts +17 -3
- package/src/composables/usePendingCalls.ts +8 -26
- package/src/composables/usePluginErrorBoundary.ts +68 -0
- package/src/composables/usePubSub.ts +9 -17
- package/src/composables/useRunElapsed.ts +5 -22
- package/src/composables/useSandboxStatus.ts +4 -20
- package/src/composables/useSessionDerived.ts +7 -15
- package/src/composables/useSessionHistory.ts +70 -29
- package/src/composables/useSessionSync.ts +25 -3
- package/src/composables/useSkillsList.ts +59 -0
- package/src/composables/useTranslatedQueries.ts +109 -0
- package/src/config/apiRoutes.ts +181 -80
- package/src/config/historyFilters.ts +5 -3
- package/src/config/hostEvents.ts +17 -0
- package/src/config/mcpCatalog.ts +277 -5
- package/src/config/pubsubChannels.ts +134 -12
- package/src/config/roles.ts +212 -147
- package/src/config/systemFileDescriptors.ts +5 -5
- package/src/config/toolNames.ts +52 -30
- package/src/config/workspacePaths.ts +26 -2
- package/src/lang/de.ts +483 -27
- package/src/lang/en.ts +448 -27
- package/src/lang/es.ts +474 -27
- package/src/lang/fr.ts +476 -27
- package/src/lang/ja.ts +465 -27
- package/src/lang/ko.ts +466 -27
- package/src/lang/pt-BR.ts +473 -27
- package/src/lang/zh.ts +463 -27
- package/src/lib/vue-i18n.ts +1 -1
- package/src/lib/wiki-page/slug.ts +66 -0
- package/src/main.ts +85 -0
- package/src/plugins/_extras.ts +58 -0
- package/src/plugins/_generated/metas.ts +42 -0
- package/src/plugins/_generated/registrations.ts +44 -0
- package/src/plugins/_generated/server-bindings.ts +47 -0
- package/src/plugins/accounting/Preview.vue +106 -0
- package/src/plugins/accounting/View.vue +632 -0
- package/src/plugins/accounting/actions.ts +34 -0
- package/src/plugins/accounting/api.ts +301 -0
- package/src/plugins/accounting/components/AccountEditor.vue +250 -0
- package/src/plugins/accounting/components/AccountRow.vue +50 -0
- package/src/plugins/accounting/components/AccountsList.vue +102 -0
- package/src/plugins/accounting/components/AccountsModal.vue +300 -0
- package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
- package/src/plugins/accounting/components/BookSettings.vue +284 -0
- package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
- package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
- package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
- package/src/plugins/accounting/components/JournalList.vue +553 -0
- package/src/plugins/accounting/components/Ledger.vue +206 -0
- package/src/plugins/accounting/components/NewBookForm.vue +211 -0
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
- package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
- package/src/plugins/accounting/components/accountDraft.ts +13 -0
- package/src/plugins/accounting/components/accountNumbering.ts +103 -0
- package/src/plugins/accounting/components/accountValidation.ts +75 -0
- package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
- package/src/plugins/accounting/countries.ts +158 -0
- package/src/plugins/accounting/currencies.ts +64 -0
- package/src/plugins/accounting/dates.ts +51 -0
- package/src/plugins/accounting/definition.ts +199 -0
- package/src/plugins/accounting/fiscalYear.ts +136 -0
- package/src/plugins/accounting/index.ts +49 -0
- package/src/plugins/accounting/meta.ts +91 -0
- package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
- package/src/plugins/api.ts +125 -0
- package/src/plugins/canvas/View.vue +38 -28
- package/src/plugins/canvas/definition.ts +10 -8
- package/src/plugins/canvas/index.ts +15 -8
- package/src/plugins/canvas/meta.ts +12 -0
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +2 -2
- package/src/plugins/chart/definition.ts +12 -2
- package/src/plugins/chart/index.ts +15 -7
- package/src/plugins/chart/meta.ts +18 -0
- package/src/plugins/editImages/definition.ts +44 -0
- package/src/plugins/editImages/index.ts +43 -0
- package/src/plugins/editImages/meta.ts +5 -0
- package/src/plugins/generateImage/View.vue +3 -1
- package/src/plugins/generateImage/definition.ts +2 -0
- package/src/plugins/generateImage/index.ts +13 -5
- package/src/plugins/generateImage/meta.ts +5 -0
- package/src/plugins/index.ts +35 -0
- package/src/plugins/manageRoles/Preview.vue +7 -4
- package/src/plugins/manageRoles/View.vue +12 -8
- package/src/plugins/manageRoles/definition.ts +6 -0
- package/src/plugins/manageRoles/index.ts +7 -6
- package/src/plugins/manageSkills/View.vue +11 -7
- package/src/plugins/manageSkills/definition.ts +4 -1
- package/src/plugins/manageSkills/index.ts +14 -7
- package/src/plugins/manageSkills/meta.ts +21 -0
- package/src/plugins/manageSource/definition.ts +4 -1
- package/src/plugins/manageSource/index.ts +15 -7
- package/src/plugins/manageSource/meta.ts +21 -0
- package/src/plugins/markdown/Preview.vue +10 -8
- package/src/plugins/markdown/View.vue +84 -17
- package/src/plugins/markdown/definition.ts +7 -1
- package/src/plugins/markdown/index.ts +15 -8
- package/src/plugins/markdown/meta.ts +16 -0
- package/src/plugins/meta-types.ts +97 -0
- package/src/plugins/metas.ts +224 -0
- package/src/plugins/presentForm/Preview.vue +4 -15
- package/src/plugins/presentForm/View.vue +35 -78
- package/src/plugins/presentForm/definition.ts +7 -6
- package/src/plugins/presentForm/index.ts +12 -5
- package/src/plugins/presentForm/meta.ts +11 -0
- package/src/plugins/presentForm/plugin.ts +8 -9
- package/src/plugins/presentForm/types.ts +0 -24
- package/src/plugins/presentHtml/Preview.vue +1 -8
- package/src/plugins/presentHtml/View.vue +401 -30
- package/src/plugins/presentHtml/definition.ts +8 -5
- package/src/plugins/presentHtml/index.ts +15 -8
- package/src/plugins/presentHtml/meta.ts +14 -0
- package/src/plugins/presentMulmoScript/View.vue +327 -107
- package/src/plugins/presentMulmoScript/definition.ts +34 -7
- package/src/plugins/presentMulmoScript/helpers.ts +4 -5
- package/src/plugins/presentMulmoScript/index.ts +20 -7
- package/src/plugins/presentMulmoScript/meta.ts +52 -0
- package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
- package/src/plugins/scheduler/Preview.vue +5 -2
- package/src/plugins/scheduler/TasksTab.vue +16 -36
- package/src/plugins/scheduler/View.vue +22 -54
- package/src/plugins/scheduler/automationsDefinition.ts +14 -9
- package/src/plugins/scheduler/automationsMeta.ts +5 -0
- package/src/plugins/scheduler/calendarDefinition.ts +4 -7
- package/src/plugins/scheduler/calendarMeta.ts +28 -0
- package/src/plugins/scheduler/formatSchedule.ts +6 -24
- package/src/plugins/scheduler/index.ts +26 -52
- package/src/plugins/scope.ts +57 -0
- package/src/plugins/server-bindings-types.ts +38 -0
- package/src/plugins/server.ts +32 -0
- package/src/plugins/skill/Preview.vue +25 -0
- package/src/plugins/skill/View.vue +125 -0
- package/src/plugins/skill/definition.ts +23 -0
- package/src/plugins/skill/index.ts +36 -0
- package/src/plugins/skill/plugin.ts +31 -0
- package/src/plugins/skill/types.ts +21 -0
- package/src/plugins/spreadsheet/Preview.vue +1 -3
- package/src/plugins/spreadsheet/View.vue +29 -49
- package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
- package/src/plugins/spreadsheet/definition.ts +5 -2
- package/src/plugins/spreadsheet/index.ts +15 -8
- package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
- package/src/plugins/spreadsheet/meta.ts +14 -0
- package/src/plugins/textResponse/Preview.vue +9 -1
- package/src/plugins/textResponse/View.vue +59 -8
- package/src/plugins/textResponse/index.ts +11 -3
- package/src/plugins/textResponse/plugin.ts +8 -10
- package/src/plugins/textResponse/types.ts +28 -0
- package/src/plugins/wiki/Preview.vue +6 -4
- package/src/plugins/wiki/View.vue +463 -254
- package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
- package/src/plugins/wiki/helpers.ts +17 -0
- package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
- package/src/plugins/wiki/history/HistoryTab.vue +167 -0
- package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
- package/src/plugins/wiki/history/api.ts +52 -0
- package/src/plugins/wiki/history/diff.ts +145 -0
- package/src/plugins/wiki/index.ts +42 -32
- package/src/plugins/wiki/meta.ts +10 -0
- package/src/plugins/wiki/pageEditLoader.ts +53 -0
- package/src/plugins/wiki/route.ts +8 -0
- package/src/router/guards.ts +2 -1
- package/src/router/index.ts +19 -0
- package/src/router/pageRoutes.ts +1 -0
- package/src/tools/index.ts +50 -51
- package/src/tools/runtimeLoader.ts +141 -0
- package/src/tools/types.ts +44 -1
- package/src/types/notification.ts +23 -0
- package/src/types/pastedFile.ts +10 -0
- package/src/types/session.ts +61 -3
- package/src/types/sse.ts +21 -6
- package/src/utils/agent/eventDispatch.ts +12 -9
- package/src/utils/agent/pastedAttachment.ts +35 -0
- package/src/utils/agent/request.ts +32 -3
- package/src/utils/agent/toolCalls.ts +7 -1
- package/src/utils/api.ts +1 -1
- package/src/utils/chat/exportMarkdown.ts +243 -0
- package/src/utils/errors.ts +10 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/filesPreview/todoPreview.ts +13 -2
- package/src/utils/format/date.ts +1 -3
- package/src/utils/format/jsonSyntax.ts +5 -0
- package/src/utils/html/iframeHeightReporterScript.ts +62 -0
- package/src/utils/html/previewCsp.ts +29 -2
- package/src/utils/image/htmlSrcAttrs.ts +122 -0
- package/src/utils/image/imageRepairInlineScript.ts +115 -0
- package/src/utils/image/resolve.ts +17 -3
- package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
- package/src/utils/markdown/frontmatter.ts +125 -0
- package/src/utils/markdown/taskList.ts +7 -2
- package/src/utils/plugin/runtime.ts +132 -0
- package/src/utils/session/mergeSessions.ts +40 -37
- package/src/utils/session/sessionEntries.ts +74 -18
- package/src/utils/session/sessionHelpers.ts +54 -10
- package/src/utils/tools/result.ts +76 -14
- package/src/vite-env.d.ts +6 -0
- package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
- package/client/assets/index-CY-WpQUm.css +0 -2
- package/client/assets/index-DbTz2Mfs.js +0 -4911
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/server/api/routes/html.ts +0 -114
- package/server/api/routes/todos.ts +0 -293
- package/server/api/routes/todosColumnsHandlers.ts +0 -333
- package/server/api/routes/todosHandlers.ts +0 -274
- package/server/api/routes/todosItemsHandlers.ts +0 -386
- package/server/utils/files/todos-io.ts +0 -29
- package/src/components/NotificationToast.vue +0 -75
- package/src/plugins/editImage/definition.ts +0 -27
- package/src/plugins/editImage/index.ts +0 -37
- package/src/plugins/presentHtml/helpers.ts +0 -72
- package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
- package/src/plugins/scheduler/legacyShape.ts +0 -34
- package/src/plugins/todo/Preview.vue +0 -68
- package/src/plugins/todo/View.vue +0 -378
- package/src/plugins/todo/composables/useTodos.ts +0 -179
- package/src/plugins/todo/definition.ts +0 -45
- package/src/plugins/todo/index.ts +0 -62
- package/src/plugins/todo/labels.ts +0 -163
- package/src/plugins/todo/priority.ts +0 -98
- package/src/plugins/todo/viewModes.ts +0 -19
- package/src/plugins/wiki/definition.ts +0 -25
- package/src/tools/legacyPluginNames.ts +0 -13
- package/src/utils/format/frontmatter.ts +0 -80
- package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
- package/src/utils/notification/dispatch.ts +0 -58
- /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
- /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
- /package/src/plugins/{editImage → editImages}/View.vue +0 -0
- /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-4" data-testid="accounting-settings">
|
|
3
|
+
<section class="border border-gray-200 rounded p-3 flex flex-col gap-2">
|
|
4
|
+
<h4 class="text-sm font-semibold">{{ t("pluginAccounting.settings.bookInfo") }}</h4>
|
|
5
|
+
<p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.bookInfoExplain") }}</p>
|
|
6
|
+
<label class="text-sm flex flex-col gap-1">
|
|
7
|
+
{{ t("pluginAccounting.bookSwitcher.nameLabel") }}
|
|
8
|
+
<input
|
|
9
|
+
v-model="selectedName"
|
|
10
|
+
type="text"
|
|
11
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
|
|
12
|
+
data-testid="accounting-settings-name"
|
|
13
|
+
:disabled="updating"
|
|
14
|
+
maxlength="200"
|
|
15
|
+
/>
|
|
16
|
+
</label>
|
|
17
|
+
<dl class="text-xs text-gray-700 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1">
|
|
18
|
+
<dt class="text-gray-500">{{ t("pluginAccounting.bookSwitcher.currencyLabel") }}</dt>
|
|
19
|
+
<dd>{{ currency }}</dd>
|
|
20
|
+
</dl>
|
|
21
|
+
<label class="text-sm flex flex-col gap-1 mt-1">
|
|
22
|
+
{{ t("pluginAccounting.bookSwitcher.countryLabel") }}
|
|
23
|
+
<select
|
|
24
|
+
v-model="selectedCountry"
|
|
25
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
|
|
26
|
+
data-testid="accounting-settings-country"
|
|
27
|
+
:disabled="updating"
|
|
28
|
+
>
|
|
29
|
+
<option value="">{{ t("pluginAccounting.settings.countryUnset") }}</option>
|
|
30
|
+
<option v-for="opt in countryOptions" :key="opt.code" :value="opt.code">{{ opt.label }}</option>
|
|
31
|
+
</select>
|
|
32
|
+
</label>
|
|
33
|
+
<label class="text-sm flex flex-col gap-1 mt-1">
|
|
34
|
+
{{ t("pluginAccounting.bookSwitcher.fiscalYearEndLabel") }}
|
|
35
|
+
<select
|
|
36
|
+
v-model="selectedFiscalYearEnd"
|
|
37
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
|
|
38
|
+
data-testid="accounting-settings-fiscal-year-end"
|
|
39
|
+
:disabled="updating"
|
|
40
|
+
>
|
|
41
|
+
<option v-for="opt in fiscalYearEndOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
|
42
|
+
</select>
|
|
43
|
+
</label>
|
|
44
|
+
<p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.fiscalYearEndExplain") }}</p>
|
|
45
|
+
<p v-if="updateOk" class="text-xs text-green-600" data-testid="accounting-settings-update-ok">{{ updateOk }}</p>
|
|
46
|
+
<p v-if="updateError" class="text-xs text-red-500" data-testid="accounting-settings-update-error">{{ updateError }}</p>
|
|
47
|
+
<div>
|
|
48
|
+
<button
|
|
49
|
+
class="h-8 px-3 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-50"
|
|
50
|
+
:disabled="updating || !hasPendingChanges"
|
|
51
|
+
data-testid="accounting-settings-save"
|
|
52
|
+
@click="onSaveBookInfo"
|
|
53
|
+
>
|
|
54
|
+
{{ updating ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.saveChanges") }}
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
<section class="border border-gray-200 rounded p-3 flex flex-col gap-2">
|
|
59
|
+
<h4 class="text-sm font-semibold">{{ t("pluginAccounting.settings.rebuild") }}</h4>
|
|
60
|
+
<p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.rebuildExplain") }}</p>
|
|
61
|
+
<p v-if="rebuildOk" class="text-xs text-green-600" data-testid="accounting-settings-rebuild-ok">{{ rebuildOk }}</p>
|
|
62
|
+
<p v-if="rebuildError" class="text-xs text-red-500" data-testid="accounting-settings-rebuild-error">{{ rebuildError }}</p>
|
|
63
|
+
<div>
|
|
64
|
+
<button
|
|
65
|
+
class="h-8 px-3 rounded bg-blue-600 hover:bg-blue-700 text-white text-sm disabled:opacity-50"
|
|
66
|
+
:disabled="rebuilding"
|
|
67
|
+
data-testid="accounting-settings-rebuild"
|
|
68
|
+
@click="onRebuild"
|
|
69
|
+
>
|
|
70
|
+
{{ rebuilding ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.rebuild") }}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
<div v-if="!showAdvanced">
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class="h-8 px-2.5 flex items-center gap-1 rounded border border-gray-300 text-sm text-gray-600 hover:bg-gray-50"
|
|
78
|
+
data-testid="accounting-settings-advanced"
|
|
79
|
+
@click="showAdvanced = true"
|
|
80
|
+
>
|
|
81
|
+
<span class="material-icons text-base">expand_more</span>
|
|
82
|
+
<span>{{ t("pluginAccounting.settings.advanced") }}</span>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
<section v-if="showAdvanced" class="border border-red-300 rounded p-3 flex flex-col gap-2">
|
|
86
|
+
<h4 class="text-sm font-semibold text-red-700">{{ t("pluginAccounting.settings.deleteBook") }}</h4>
|
|
87
|
+
<p class="text-xs text-gray-500">{{ t("pluginAccounting.settings.deleteBookExplain") }}</p>
|
|
88
|
+
<p v-if="deleteError" class="text-xs text-red-500" data-testid="accounting-settings-delete-error">{{ deleteError }}</p>
|
|
89
|
+
<label class="text-xs text-gray-500 flex flex-col gap-1">
|
|
90
|
+
{{ t("pluginAccounting.settings.deleteBookConfirm", { bookName: bookName }) }}
|
|
91
|
+
<input v-model="confirmName" class="h-8 px-2 rounded border border-gray-300 text-sm" data-testid="accounting-settings-delete-confirm" />
|
|
92
|
+
</label>
|
|
93
|
+
<div>
|
|
94
|
+
<button
|
|
95
|
+
class="h-8 px-3 rounded bg-red-600 hover:bg-red-700 text-white text-sm disabled:opacity-50"
|
|
96
|
+
:disabled="confirmName !== bookName || deleting"
|
|
97
|
+
data-testid="accounting-settings-delete"
|
|
98
|
+
@click="onDelete"
|
|
99
|
+
>
|
|
100
|
+
{{ deleting ? t("pluginAccounting.common.loading") : t("pluginAccounting.settings.deleteBookButton") }}
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</section>
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|
|
106
|
+
|
|
107
|
+
<script setup lang="ts">
|
|
108
|
+
import { computed, ref, watch } from "vue";
|
|
109
|
+
import { useI18n } from "vue-i18n";
|
|
110
|
+
import { deleteBook, rebuildSnapshots, updateBook } from "../api";
|
|
111
|
+
import { SUPPORTED_COUNTRY_CODES, isSupportedCountryCode, localizedCountryName, type SupportedCountryCode } from "../countries";
|
|
112
|
+
import { DEFAULT_FISCAL_YEAR_END, FISCAL_YEAR_ENDS, resolveFiscalYearEnd, type FiscalYearEnd } from "../fiscalYear";
|
|
113
|
+
|
|
114
|
+
const { t, locale } = useI18n();
|
|
115
|
+
|
|
116
|
+
const props = defineProps<{
|
|
117
|
+
bookId: string;
|
|
118
|
+
bookName: string;
|
|
119
|
+
currency: string;
|
|
120
|
+
country?: SupportedCountryCode;
|
|
121
|
+
fiscalYearEnd?: FiscalYearEnd;
|
|
122
|
+
}>();
|
|
123
|
+
const emit = defineEmits<{ deleted: [bookName: string]; "books-changed": [] }>();
|
|
124
|
+
|
|
125
|
+
const rebuilding = ref(false);
|
|
126
|
+
const rebuildOk = ref<string | null>(null);
|
|
127
|
+
const rebuildError = ref<string | null>(null);
|
|
128
|
+
const deleting = ref(false);
|
|
129
|
+
const deleteError = ref<string | null>(null);
|
|
130
|
+
const confirmName = ref("");
|
|
131
|
+
const updating = ref(false);
|
|
132
|
+
const updateOk = ref<string | null>(null);
|
|
133
|
+
const updateError = ref<string | null>(null);
|
|
134
|
+
const showAdvanced = ref(false);
|
|
135
|
+
const selectedName = ref<string>(props.bookName);
|
|
136
|
+
const selectedCountry = ref<string>(props.country ?? "");
|
|
137
|
+
// Resolved at the boundary so the dropdown always shows a concrete
|
|
138
|
+
// value — books without a `fiscalYearEnd` field on disk land here as
|
|
139
|
+
// the default Q4 (matches the back-compat read policy).
|
|
140
|
+
const selectedFiscalYearEnd = ref<FiscalYearEnd>(props.fiscalYearEnd ?? DEFAULT_FISCAL_YEAR_END);
|
|
141
|
+
|
|
142
|
+
interface CountryOption {
|
|
143
|
+
code: string;
|
|
144
|
+
label: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const countryOptions = computed<CountryOption[]>(() =>
|
|
148
|
+
SUPPORTED_COUNTRY_CODES.map((code) => ({
|
|
149
|
+
code,
|
|
150
|
+
label: `${code} — ${localizedCountryName(code, locale.value)}`,
|
|
151
|
+
})),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
interface FiscalYearEndOption {
|
|
155
|
+
value: FiscalYearEnd;
|
|
156
|
+
label: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const fiscalYearEndOptions = computed<FiscalYearEndOption[]>(() =>
|
|
160
|
+
FISCAL_YEAR_ENDS.map((value) => ({
|
|
161
|
+
value,
|
|
162
|
+
label: t(`pluginAccounting.bookSwitcher.fiscalYearEnd${value}`),
|
|
163
|
+
})),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const hasPendingChanges = computed<boolean>(() => {
|
|
167
|
+
// Compare against the trimmed value so a no-op edit (typing then
|
|
168
|
+
// backspacing back to the original) doesn't keep the Save button
|
|
169
|
+
// hot. Server-side validateUpdateBookInput rejects empty / whitespace
|
|
170
|
+
// names with a 400 — the disabled binding below mirrors that contract
|
|
171
|
+
// so the button can't fire a doomed request.
|
|
172
|
+
const nameChanged = selectedName.value.trim() !== props.bookName;
|
|
173
|
+
const nameValid = selectedName.value.trim().length > 0;
|
|
174
|
+
const countryChanged = selectedCountry.value !== (props.country ?? "");
|
|
175
|
+
const fiscalChanged = selectedFiscalYearEnd.value !== resolveFiscalYearEnd(props.fiscalYearEnd);
|
|
176
|
+
return nameValid && (nameChanged || countryChanged || fiscalChanged);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
async function onRebuild(): Promise<void> {
|
|
180
|
+
rebuilding.value = true;
|
|
181
|
+
rebuildOk.value = null;
|
|
182
|
+
rebuildError.value = null;
|
|
183
|
+
try {
|
|
184
|
+
const result = await rebuildSnapshots(props.bookId);
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
rebuildError.value = result.error;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
rebuildOk.value = t("pluginAccounting.settings.rebuildOk", { count: result.data.rebuilt.length });
|
|
190
|
+
} finally {
|
|
191
|
+
rebuilding.value = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function onSaveBookInfo(): Promise<void> {
|
|
196
|
+
if (updating.value) return;
|
|
197
|
+
updating.value = true;
|
|
198
|
+
updateOk.value = null;
|
|
199
|
+
updateError.value = null;
|
|
200
|
+
try {
|
|
201
|
+
// The select v-model is a plain `string` (HTML form value); narrow
|
|
202
|
+
// it back to the union before handing it to the API helper. The
|
|
203
|
+
// empty string is the sentinel that clears the country server-side.
|
|
204
|
+
const rawCountry = selectedCountry.value;
|
|
205
|
+
const country: SupportedCountryCode | "" = rawCountry === "" || isSupportedCountryCode(rawCountry) ? rawCountry : "";
|
|
206
|
+
const result = await updateBook({
|
|
207
|
+
bookId: props.bookId,
|
|
208
|
+
name: selectedName.value.trim(),
|
|
209
|
+
country,
|
|
210
|
+
fiscalYearEnd: selectedFiscalYearEnd.value,
|
|
211
|
+
});
|
|
212
|
+
if (!result.ok) {
|
|
213
|
+
updateError.value = result.error;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
updateOk.value = t("pluginAccounting.settings.updateOk");
|
|
217
|
+
emit("books-changed");
|
|
218
|
+
} finally {
|
|
219
|
+
updating.value = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function onDelete(): Promise<void> {
|
|
224
|
+
if (deleting.value) return;
|
|
225
|
+
deleting.value = true;
|
|
226
|
+
deleteError.value = null;
|
|
227
|
+
try {
|
|
228
|
+
const result = await deleteBook(props.bookId);
|
|
229
|
+
if (!result.ok) {
|
|
230
|
+
deleteError.value = result.error;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
emit("deleted", props.bookName);
|
|
234
|
+
emit("books-changed");
|
|
235
|
+
} finally {
|
|
236
|
+
deleting.value = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Reset feedback / confirmation AND the dropdown selection when the
|
|
241
|
+
// user navigates between books while this tab is open. Without the
|
|
242
|
+
// `selectedCountry` reset, switching from book A (country=JP) to
|
|
243
|
+
// book B (also country=JP) leaves a previously-typed unsaved value
|
|
244
|
+
// staged on B — a save would then misattribute the edit.
|
|
245
|
+
watch(
|
|
246
|
+
() => props.bookId,
|
|
247
|
+
() => {
|
|
248
|
+
rebuildOk.value = null;
|
|
249
|
+
rebuildError.value = null;
|
|
250
|
+
deleteError.value = null;
|
|
251
|
+
confirmName.value = "";
|
|
252
|
+
updateOk.value = null;
|
|
253
|
+
updateError.value = null;
|
|
254
|
+
selectedName.value = props.bookName;
|
|
255
|
+
selectedCountry.value = props.country ?? "";
|
|
256
|
+
selectedFiscalYearEnd.value = props.fiscalYearEnd ?? DEFAULT_FISCAL_YEAR_END;
|
|
257
|
+
showAdvanced.value = false;
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Follow external bookName updates — e.g. an LLM-driven updateBook in
|
|
262
|
+
// another tab, or pubsub-driven refetch. Without this, an out-of-band
|
|
263
|
+
// rename leaves a stale draft staged in the input.
|
|
264
|
+
watch(
|
|
265
|
+
() => props.bookName,
|
|
266
|
+
(next) => {
|
|
267
|
+
selectedName.value = next;
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
watch(
|
|
272
|
+
() => props.country,
|
|
273
|
+
(next) => {
|
|
274
|
+
selectedCountry.value = next ?? "";
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
watch(
|
|
279
|
+
() => props.fiscalYearEnd,
|
|
280
|
+
(next) => {
|
|
281
|
+
selectedFiscalYearEnd.value = next ?? DEFAULT_FISCAL_YEAR_END;
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
</script>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center gap-2">
|
|
3
|
+
<label class="text-xs text-gray-500" for="accounting-book-select">{{ t("pluginAccounting.bookSwitcher.label") }}</label>
|
|
4
|
+
<select
|
|
5
|
+
id="accounting-book-select"
|
|
6
|
+
:value="modelValue"
|
|
7
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
|
|
8
|
+
data-testid="accounting-book-select"
|
|
9
|
+
@change="onSelect"
|
|
10
|
+
>
|
|
11
|
+
<option v-if="modelValue === ''" value="" disabled>{{ t("pluginAccounting.bookSwitcher.placeholder") }}</option>
|
|
12
|
+
<option v-for="book in books" :key="book.id" :value="book.id">{{ formatBookOption(book) }}</option>
|
|
13
|
+
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- decorative separator inside the books <select>, not user copy -->
|
|
14
|
+
<option disabled>──────────</option>
|
|
15
|
+
<option :value="NEW_BOOK_SENTINEL" data-testid="accounting-new-book-option">+ {{ t("pluginAccounting.bookSwitcher.newBook") }}</option>
|
|
16
|
+
</select>
|
|
17
|
+
<NewBookForm v-if="showNewBook" @cancel="showNewBook = false" @created="onCreated" />
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { ref } from "vue";
|
|
23
|
+
import { useI18n } from "vue-i18n";
|
|
24
|
+
import NewBookForm from "./NewBookForm.vue";
|
|
25
|
+
import type { BookSummary } from "../api";
|
|
26
|
+
|
|
27
|
+
const { t } = useI18n();
|
|
28
|
+
|
|
29
|
+
const props = defineProps<{ modelValue: string; books: BookSummary[] }>();
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
"update:modelValue": [bookId: string];
|
|
32
|
+
"books-changed": [];
|
|
33
|
+
"book-created": [book: BookSummary];
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
// Sentinel value for the "+ New book" option living inside the
|
|
37
|
+
// books <select>. Picking it opens the modal and reverts the
|
|
38
|
+
// select's displayed value to the current selection — the option
|
|
39
|
+
// must not collide with any real book id, which are nanoid-shaped.
|
|
40
|
+
const NEW_BOOK_SENTINEL = "__new__";
|
|
41
|
+
|
|
42
|
+
const showNewBook = ref(false);
|
|
43
|
+
|
|
44
|
+
function formatBookOption(book: BookSummary): string {
|
|
45
|
+
// `Name (CCY · Country)` when a country is set; otherwise fall back
|
|
46
|
+
// to `Name (CCY)`. Keeps the option label compact while surfacing
|
|
47
|
+
// the country so a multi-jurisdiction user can pick the right book
|
|
48
|
+
// by tax regime, not just currency.
|
|
49
|
+
const suffix = book.country ? `${book.currency} · ${book.country}` : book.currency;
|
|
50
|
+
return `${book.name} (${suffix})`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function onSelect(event: Event): void {
|
|
54
|
+
const target = event.target as HTMLSelectElement;
|
|
55
|
+
const bookId = target.value;
|
|
56
|
+
if (bookId === NEW_BOOK_SENTINEL) {
|
|
57
|
+
target.value = props.modelValue;
|
|
58
|
+
showNewBook.value = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (bookId === props.modelValue) return;
|
|
62
|
+
// The View persists the new selection to localStorage; no server
|
|
63
|
+
// round-trip needed since there's no shared "active book" state.
|
|
64
|
+
emit("update:modelValue", bookId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onCreated(book: BookSummary): void {
|
|
68
|
+
// Hand the new book to the parent in one event so it can await
|
|
69
|
+
// its own refetch before setting the active selection. Splitting
|
|
70
|
+
// this into separate `books-changed` + `update:modelValue` emits
|
|
71
|
+
// races: the parent's async refetch runs concurrently with the
|
|
72
|
+
// selection update, and the stillExists guard inside refetch can
|
|
73
|
+
// snap the selection back to books[0] if the fetch happens to
|
|
74
|
+
// resolve before the new book is in the list.
|
|
75
|
+
showNewBook.value = false;
|
|
76
|
+
emit("book-created", book);
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Reusable from/to + shortcut date range picker. Owns no state
|
|
3
|
+
beyond the v-model; the parent supplies an initial range and
|
|
4
|
+
the active book's fiscalYearEnd so quarter/year shortcuts
|
|
5
|
+
resolve under the right calendar. -->
|
|
6
|
+
<div class="flex flex-wrap items-end gap-2" data-testid="accounting-daterange">
|
|
7
|
+
<label class="text-xs text-gray-500 flex flex-col gap-1">
|
|
8
|
+
{{ t("pluginAccounting.dateRange.shortcutLabel") }}
|
|
9
|
+
<select
|
|
10
|
+
:value="selectedShortcut"
|
|
11
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm bg-white"
|
|
12
|
+
data-testid="accounting-daterange-shortcut"
|
|
13
|
+
@change="onShortcutChange(($event.target as HTMLSelectElement).value)"
|
|
14
|
+
>
|
|
15
|
+
<!-- Sentinel for the "custom" state. Hidden from the menu
|
|
16
|
+
but bound when the current range doesn't match any
|
|
17
|
+
preset, which leaves the trigger displaying blank
|
|
18
|
+
instead of forcing a wrong-looking match. -->
|
|
19
|
+
<option value="" hidden></option>
|
|
20
|
+
<option value="currentQuarter">{{ t("pluginAccounting.dateRange.currentQuarter") }}</option>
|
|
21
|
+
<option value="previousQuarter">{{ t("pluginAccounting.dateRange.previousQuarter") }}</option>
|
|
22
|
+
<option value="currentYear">{{ t("pluginAccounting.dateRange.currentYear") }}</option>
|
|
23
|
+
<option value="previousYear">{{ t("pluginAccounting.dateRange.previousYear") }}</option>
|
|
24
|
+
<option v-if="hasOpeningDate" value="lifetime">{{ t("pluginAccounting.dateRange.lifetime") }}</option>
|
|
25
|
+
<option value="all">{{ t("pluginAccounting.dateRange.all") }}</option>
|
|
26
|
+
</select>
|
|
27
|
+
</label>
|
|
28
|
+
<label class="text-xs text-gray-500 flex flex-col gap-1">
|
|
29
|
+
{{ t("pluginAccounting.dateRange.fromLabel") }}
|
|
30
|
+
<input
|
|
31
|
+
:value="modelValue.from"
|
|
32
|
+
type="date"
|
|
33
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm"
|
|
34
|
+
data-testid="accounting-daterange-from"
|
|
35
|
+
@input="onFromChange(($event.target as HTMLInputElement).value)"
|
|
36
|
+
/>
|
|
37
|
+
</label>
|
|
38
|
+
<label class="text-xs text-gray-500 flex flex-col gap-1">
|
|
39
|
+
{{ t("pluginAccounting.dateRange.toLabel") }}
|
|
40
|
+
<input
|
|
41
|
+
:value="modelValue.to"
|
|
42
|
+
type="date"
|
|
43
|
+
class="h-8 px-2 rounded border border-gray-300 text-sm"
|
|
44
|
+
data-testid="accounting-daterange-to"
|
|
45
|
+
@input="onToChange(($event.target as HTMLInputElement).value)"
|
|
46
|
+
/>
|
|
47
|
+
</label>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { computed } from "vue";
|
|
53
|
+
import { useI18n } from "vue-i18n";
|
|
54
|
+
import { currentFiscalYearRange, currentQuarterRange, previousFiscalYearRange, previousQuarterRange, type DateRange, type FiscalYearEnd } from "../fiscalYear";
|
|
55
|
+
import { localDateString } from "../dates";
|
|
56
|
+
|
|
57
|
+
const { t } = useI18n();
|
|
58
|
+
|
|
59
|
+
const props = defineProps<{
|
|
60
|
+
modelValue: DateRange;
|
|
61
|
+
fiscalYearEnd: FiscalYearEnd;
|
|
62
|
+
/** The active book's opening-balance date. Drives the "Lifetime"
|
|
63
|
+
* shortcut (from = openingDate, to = today). Optional — when
|
|
64
|
+
* absent the Lifetime option is hidden from the menu. The opening
|
|
65
|
+
* gate prevents the tabs that mount this picker from rendering
|
|
66
|
+
* before an opening exists, so in normal use this stays defined. */
|
|
67
|
+
openingDate?: string;
|
|
68
|
+
}>();
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits<{
|
|
71
|
+
"update:modelValue": [DateRange];
|
|
72
|
+
}>();
|
|
73
|
+
|
|
74
|
+
const hasOpeningDate = computed<boolean>(() => Boolean(props.openingDate));
|
|
75
|
+
|
|
76
|
+
const UNBOUNDED_RANGE: DateRange = { from: "", to: "" };
|
|
77
|
+
|
|
78
|
+
/** From the book's opening date through today. Hidden from the menu
|
|
79
|
+
* when the parent hasn't supplied an opening. */
|
|
80
|
+
function lifetimeRange(): DateRange | null {
|
|
81
|
+
if (!props.openingDate) return null;
|
|
82
|
+
return { from: props.openingDate, to: localDateString() };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type Shortcut = "currentQuarter" | "previousQuarter" | "currentYear" | "previousYear" | "lifetime" | "all";
|
|
86
|
+
/** Empty string is the sentinel "no preset matches" value bound to
|
|
87
|
+
* the hidden option in the template — the trigger shows blank. */
|
|
88
|
+
type SelectedShortcut = Shortcut | "";
|
|
89
|
+
|
|
90
|
+
function rangesEqual(left: DateRange, right: DateRange): boolean {
|
|
91
|
+
return left.from === right.from && left.to === right.to;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Resolve the dropdown's displayed value from the current
|
|
95
|
+
// modelValue. Re-evaluates today on every read — the picker is a
|
|
96
|
+
// short-lived UI surface so cache invalidation isn't a concern, and
|
|
97
|
+
// the user has no expectation that "current quarter" picked in the
|
|
98
|
+
// morning still labels correctly at midnight. Returns "" when no
|
|
99
|
+
// preset matches (custom range from manual from/to edits).
|
|
100
|
+
//
|
|
101
|
+
// Order matters when ranges collide: when no opening is on file the
|
|
102
|
+
// Lifetime option is hidden from the menu, but if it ever produced
|
|
103
|
+
// the same span as another preset (it can't — Lifetime spans years,
|
|
104
|
+
// presets span quarter/year), the earlier branch would win. We
|
|
105
|
+
// check the explicit ranges first and fall through to the unbounded
|
|
106
|
+
// "all" last so a manually-cleared input lands on "all" rather than
|
|
107
|
+
// blank when both sides happen to be empty.
|
|
108
|
+
const selectedShortcut = computed<SelectedShortcut>(() => {
|
|
109
|
+
const value = props.modelValue;
|
|
110
|
+
const today = new Date();
|
|
111
|
+
if (rangesEqual(value, currentQuarterRange(props.fiscalYearEnd, today))) return "currentQuarter";
|
|
112
|
+
if (rangesEqual(value, previousQuarterRange(props.fiscalYearEnd, today))) return "previousQuarter";
|
|
113
|
+
if (rangesEqual(value, currentFiscalYearRange(props.fiscalYearEnd, today))) return "currentYear";
|
|
114
|
+
if (rangesEqual(value, previousFiscalYearRange(props.fiscalYearEnd, today))) return "previousYear";
|
|
115
|
+
const lifetime = lifetimeRange();
|
|
116
|
+
if (lifetime && rangesEqual(value, lifetime)) return "lifetime";
|
|
117
|
+
if (rangesEqual(value, UNBOUNDED_RANGE)) return "all";
|
|
118
|
+
return "";
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
function onShortcutChange(raw: string): void {
|
|
122
|
+
const today = new Date();
|
|
123
|
+
if (raw === "currentQuarter") emit("update:modelValue", currentQuarterRange(props.fiscalYearEnd, today));
|
|
124
|
+
else if (raw === "previousQuarter") emit("update:modelValue", previousQuarterRange(props.fiscalYearEnd, today));
|
|
125
|
+
else if (raw === "currentYear") emit("update:modelValue", currentFiscalYearRange(props.fiscalYearEnd, today));
|
|
126
|
+
else if (raw === "previousYear") emit("update:modelValue", previousFiscalYearRange(props.fiscalYearEnd, today));
|
|
127
|
+
else if (raw === "lifetime") {
|
|
128
|
+
const lifetime = lifetimeRange();
|
|
129
|
+
if (lifetime) emit("update:modelValue", lifetime);
|
|
130
|
+
} else if (raw === "all") emit("update:modelValue", UNBOUNDED_RANGE);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onFromChange(value: string): void {
|
|
134
|
+
emit("update:modelValue", { from: value, to: props.modelValue.to });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onToChange(value: string): void {
|
|
138
|
+
emit("update:modelValue", { from: props.modelValue.from, to: value });
|
|
139
|
+
}
|
|
140
|
+
</script>
|