@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.31
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/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +85 -6
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +4 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +40 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +43 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +125 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +3 -2
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +13 -6
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +94 -50
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -4
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/index.js +10 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +8 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -8
- package/dist/esm/utils/facets.js +75 -3
- package/dist/esm/utils/facets.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/pageControls.js +81 -0
- package/dist/esm/utils/pageControls.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +40 -3
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +1 -1
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +2 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +19 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/userGroupsPeek.js +91 -0
- package/dist/esm/utils/userGroupsPeek.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +57 -5
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +4 -57
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4309 -2233
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -8
- package/l10n/en.json +8 -1
- package/l10n/nl.json +54 -1
- package/package.json +2 -1
- package/scripts/check-docs-mdx.mjs +108 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAppRoot/CnAppRoot.md +4 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
- package/src/components/CnBannerWidget/CnBannerWidget.vue +112 -7
- package/src/components/CnCellRenderer/CnCellRenderer.vue +4 -1
- package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +40 -3
- package/src/components/CnDataTable/CnDataTable.vue +19 -11
- package/src/components/CnDetailPage/CnDetailPage.vue +44 -1
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
- package/src/components/CnFactsWidget/CnFactsWidget.vue +125 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +3 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
- package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
- package/src/components/CnFormPage/CnFormPage.vue +81 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +56 -3
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +13 -6
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
- package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +281 -10
- package/src/components/index.js +8 -0
- package/src/composables/useListView.js +4 -4
- package/src/css/look-board-index.css +16 -8
- package/src/index.js +8 -0
- package/src/schemas/app-manifest-v2.schema.json +93 -9
- package/src/utils/facets.js +74 -2
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/pageControls.js +78 -0
- package/src/utils/personalLenses.js +39 -2
- package/src/utils/resolveFilterTokens.js +3 -1
- package/src/utils/resolveManifestTokens.js +2 -0
- package/src/utils/sentinelTokens.js +19 -1
- package/src/utils/userGroupsPeek.js +87 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +56 -4
- package/src/utils/widgetVisibility.js +7 -56
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
|
|
32
32
|
Hosts a per-user `NcAppSettingsDialog` that any descendant can open
|
|
33
33
|
via the injected `cnOpenUserSettings()` method. CnAppNav binds the
|
|
34
|
-
inject to manifest entries with `action: "user-settings"`.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
inject to manifest entries with `action: "user-settings"`. The modal
|
|
35
|
+
draws the library's default sections (notifications, credentials,
|
|
36
|
+
walkthrough replay, integrations). Apps add their own
|
|
37
|
+
`NcAppSettingsSection`s through `#user-settings-sections` and place,
|
|
38
|
+
order or hide the defaults with the `userSettingsSections` prop. The
|
|
39
|
+
older `#user-settings` slot replaces the defaults instead.
|
|
38
40
|
|
|
39
41
|
A SECOND, distinct `NcAppSettingsDialog` hosts admin-only, app-level
|
|
40
42
|
(not per-user) settings, driven GENERICALLY by `manifest.adminSettings[]`
|
|
@@ -534,18 +536,33 @@
|
|
|
534
536
|
User-settings modal. Always mounted so descendants can
|
|
535
537
|
open it via the `cnOpenUserSettings` inject (CnAppNav
|
|
536
538
|
wires this to manifest entries with
|
|
537
|
-
`action: "user-settings"`).
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
`action: "user-settings"`).
|
|
540
|
+
|
|
541
|
+
The dialog draws its sections in the order
|
|
542
|
+
`userSettingsSectionOrder()` returns: `app` (the app's own
|
|
543
|
+
sections from the slots), `notifications`, `credentials`,
|
|
544
|
+
`walkthrough` and `integrations`. A key that is left out is
|
|
545
|
+
not drawn. Two slots carry the app's sections:
|
|
546
|
+
- `#user-settings-sections` adds them: the library's default
|
|
547
|
+
sections stay unless `userSettingsSections` leaves them out.
|
|
548
|
+
- `#user-settings` (the original slot) replaces the defaults,
|
|
549
|
+
as it always did: only the app's content and the
|
|
550
|
+
integrations section are drawn, unless `userSettingsSections`
|
|
551
|
+
names more.
|
|
540
552
|
-->
|
|
541
553
|
<NcAppSettingsDialog
|
|
542
554
|
:open="userSettingsOpen"
|
|
543
555
|
:showNavigation="true"
|
|
544
556
|
:name="resolvedUserSettingsTitle"
|
|
545
557
|
@update:open="userSettingsOpen = $event">
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
558
|
+
<template v-for="section in userSettingsSectionOrder()" :key="section">
|
|
559
|
+
<template v-if="section === 'app'">
|
|
560
|
+
<!-- @slot user-settings The app's own sections (NcAppSettingsSection children), REPLACING the library's default sections: filling it draws only this content and the integrations section, unless the `userSettingsSections` prop names more. Prefer `#user-settings-sections`. -->
|
|
561
|
+
<slot name="user-settings" />
|
|
562
|
+
<!-- @slot user-settings-sections The app's own sections (NcAppSettingsSection children), ADDED to the library's default sections (notifications, credentials, walkthrough, integrations). Place, order or hide the defaults with the `userSettingsSections` prop. -->
|
|
563
|
+
<slot name="user-settings-sections" />
|
|
564
|
+
</template>
|
|
565
|
+
<CnNotificationPreferences v-else-if="section === 'notifications' && userSettingsOpen" />
|
|
549
566
|
<!--
|
|
550
567
|
Credential broker (OpenRegister). Lets the user manage the
|
|
551
568
|
secrets OR holds on their behalf; apps call external providers
|
|
@@ -553,9 +570,9 @@
|
|
|
553
570
|
`credentials[]` declarations drive the informational "Apps
|
|
554
571
|
requesting credentials" list.
|
|
555
572
|
-->
|
|
556
|
-
<NcAppSettingsSection v-if="userSettingsOpen"
|
|
573
|
+
<NcAppSettingsSection v-else-if="section === 'credentials' && userSettingsOpen"
|
|
557
574
|
id="credentials"
|
|
558
|
-
:name="
|
|
575
|
+
:name="libraryCopy('Credentials')">
|
|
559
576
|
<CnCredentials
|
|
560
577
|
scope="personal"
|
|
561
578
|
:appId="appId"
|
|
@@ -567,7 +584,7 @@
|
|
|
567
584
|
when the manifest declares an enabled tour, so apps
|
|
568
585
|
without a walkthrough never show an empty section.
|
|
569
586
|
-->
|
|
570
|
-
<NcAppSettingsSection v-if="walkthroughEnabled"
|
|
587
|
+
<NcAppSettingsSection v-else-if="section === 'walkthrough' && walkthroughEnabled"
|
|
571
588
|
id="cn-walkthrough"
|
|
572
589
|
:name="restartWalkthroughSectionName">
|
|
573
590
|
<p class="cn-app-root__walkthrough-hint">
|
|
@@ -595,48 +612,47 @@
|
|
|
595
612
|
</NcButton>
|
|
596
613
|
</div>
|
|
597
614
|
</NcAppSettingsSection>
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
</
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
</NcAppSettingsSection>
|
|
615
|
+
<!--
|
|
616
|
+
Integrations (ADR-110): the links that leave this app for
|
|
617
|
+
another one, gathered out of the navigation into one place.
|
|
618
|
+
|
|
619
|
+
Drawn whether or not the app fills `#user-settings`: six
|
|
620
|
+
fleet apps fill that slot, and a section placed inside its
|
|
621
|
+
fallback disappeared for all of them in silence. Only an
|
|
622
|
+
explicit `userSettingsSections` without `integrations`
|
|
623
|
+
hides it, for an app whose board does not draw it.
|
|
624
|
+
|
|
625
|
+
Renders nothing when the app declares no integration
|
|
626
|
+
entries, so apps without any never show an empty section.
|
|
627
|
+
-->
|
|
628
|
+
<NcAppSettingsSection v-else-if="section === 'integrations' && userSettingsOpen && integrationEntries.length"
|
|
629
|
+
id="cn-integrations"
|
|
630
|
+
:name="translate('Integrations')">
|
|
631
|
+
<p class="cn-app-root__integrations-hint">
|
|
632
|
+
{{ libraryCopy('These open in another app.') }}
|
|
633
|
+
</p>
|
|
634
|
+
<ul class="cn-app-root__integrations">
|
|
635
|
+
<li v-for="entry in integrationEntries" :key="entry.id">
|
|
636
|
+
<!--
|
|
637
|
+
One uniform external-link icon rather than the
|
|
638
|
+
entry's own: every row here goes to a different
|
|
639
|
+
app, which is the thing worth signalling, and
|
|
640
|
+
CnIcon falls back to a question mark for any
|
|
641
|
+
name an app has not registered, a broken-looking
|
|
642
|
+
glyph on a link that works fine.
|
|
643
|
+
-->
|
|
644
|
+
<NcButton variant="tertiary"
|
|
645
|
+
:href="entry.href"
|
|
646
|
+
:aria-label="entry.label">
|
|
647
|
+
<template #icon>
|
|
648
|
+
<OpenInNew :size="20" />
|
|
649
|
+
</template>
|
|
650
|
+
{{ entry.label }}
|
|
651
|
+
</NcButton>
|
|
652
|
+
</li>
|
|
653
|
+
</ul>
|
|
654
|
+
</NcAppSettingsSection>
|
|
655
|
+
</template>
|
|
640
656
|
</NcAppSettingsDialog>
|
|
641
657
|
|
|
642
658
|
<!--
|
|
@@ -781,6 +797,12 @@ const KNOWN_REGISTRY_KINDS = Object.keys(REGISTRY_KIND_REQUIRED_FIELDS)
|
|
|
781
797
|
*/
|
|
782
798
|
const OR_STORE_LINK = '/index.php/settings/apps/integration/openregister'
|
|
783
799
|
|
|
800
|
+
/**
|
|
801
|
+
* The sections of the user-settings modal, in their default order. `app`
|
|
802
|
+
* stands for the app's own slot content.
|
|
803
|
+
*/
|
|
804
|
+
const USER_SETTINGS_SECTIONS = ['app', 'notifications', 'credentials', 'walkthrough', 'integrations']
|
|
805
|
+
|
|
784
806
|
export default {
|
|
785
807
|
name: 'CnAppRoot',
|
|
786
808
|
|
|
@@ -1625,6 +1647,28 @@ export default {
|
|
|
1625
1647
|
default: '',
|
|
1626
1648
|
},
|
|
1627
1649
|
|
|
1650
|
+
/**
|
|
1651
|
+
* The sections of the user-settings modal, in the order they are
|
|
1652
|
+
* drawn. Keys: `app` (the content of the `#user-settings-sections`
|
|
1653
|
+
* and `#user-settings` slots), `notifications`, `credentials`,
|
|
1654
|
+
* `walkthrough` and `integrations`. A key that is left out is not
|
|
1655
|
+
* drawn, so `['app', 'credentials', 'walkthrough']` hides the
|
|
1656
|
+
* notifications pane and the integrations section. Unknown keys are
|
|
1657
|
+
* ignored. The app's slot content is always drawn: without `app` in
|
|
1658
|
+
* the list it comes first.
|
|
1659
|
+
*
|
|
1660
|
+
* `null` (the default) keeps what each slot always did: all
|
|
1661
|
+
* defaults after the `#user-settings-sections` content, or only the
|
|
1662
|
+
* `#user-settings` content and the integrations section when an app
|
|
1663
|
+
* fills that older slot.
|
|
1664
|
+
*
|
|
1665
|
+
* @type {Array<string>|null}
|
|
1666
|
+
*/
|
|
1667
|
+
userSettingsSections: {
|
|
1668
|
+
type: Array,
|
|
1669
|
+
default: null,
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1628
1672
|
/**
|
|
1629
1673
|
* Title rendered at the top of the admin-settings modal
|
|
1630
1674
|
* (NcAppSettingsDialog `name` prop). Defaults to the
|
|
@@ -3017,7 +3061,7 @@ export default {
|
|
|
3017
3061
|
},
|
|
3018
3062
|
|
|
3019
3063
|
resolvedUserSettingsTitle() {
|
|
3020
|
-
return this.userSettingsTitle || this.
|
|
3064
|
+
return this.userSettingsTitle || this.libraryCopy('User settings')
|
|
3021
3065
|
},
|
|
3022
3066
|
|
|
3023
3067
|
/**
|
|
@@ -3036,7 +3080,7 @@ export default {
|
|
|
3036
3080
|
* @return {string}
|
|
3037
3081
|
*/
|
|
3038
3082
|
restartWalkthroughSectionName() {
|
|
3039
|
-
return this.
|
|
3083
|
+
return this.libraryCopy('Walkthrough')
|
|
3040
3084
|
},
|
|
3041
3085
|
|
|
3042
3086
|
/**
|
|
@@ -3045,7 +3089,7 @@ export default {
|
|
|
3045
3089
|
* @return {string}
|
|
3046
3090
|
*/
|
|
3047
3091
|
restartWalkthroughHint() {
|
|
3048
|
-
return this.
|
|
3092
|
+
return this.libraryCopy('Take the guided tour of this app again.')
|
|
3049
3093
|
},
|
|
3050
3094
|
|
|
3051
3095
|
/**
|
|
@@ -3054,7 +3098,7 @@ export default {
|
|
|
3054
3098
|
* @return {string}
|
|
3055
3099
|
*/
|
|
3056
3100
|
restartWalkthroughLabel() {
|
|
3057
|
-
return this.
|
|
3101
|
+
return this.libraryCopy('Restart walkthrough')
|
|
3058
3102
|
},
|
|
3059
3103
|
|
|
3060
3104
|
/**
|
|
@@ -3389,6 +3433,54 @@ export default {
|
|
|
3389
3433
|
return legacy || null
|
|
3390
3434
|
},
|
|
3391
3435
|
|
|
3436
|
+
/**
|
|
3437
|
+
* The keys of the user-settings sections to draw, in order. A method
|
|
3438
|
+
* rather than a computed, because which slots an app fills is read
|
|
3439
|
+
* from `$slots`, which a computed does not track.
|
|
3440
|
+
*
|
|
3441
|
+
* @return {Array<string>} Keys out of `USER_SETTINGS_SECTIONS`.
|
|
3442
|
+
* @spec openspec/changes/user-settings-sections/specs/user-settings-sections/spec.md#requirement-the-user-settings-dialog-keeps-its-default-sections-next-to-the-apps-own
|
|
3443
|
+
*/
|
|
3444
|
+
userSettingsSectionOrder() {
|
|
3445
|
+
const replaces = Boolean(this.$slots['user-settings'])
|
|
3446
|
+
const adds = Boolean(this.$slots['user-settings-sections'])
|
|
3447
|
+
let order
|
|
3448
|
+
if (Array.isArray(this.userSettingsSections)) {
|
|
3449
|
+
order = [...new Set(this.userSettingsSections.filter((key) => USER_SETTINGS_SECTIONS.includes(key)))]
|
|
3450
|
+
} else if (replaces) {
|
|
3451
|
+
order = ['app', 'integrations']
|
|
3452
|
+
} else {
|
|
3453
|
+
order = [...USER_SETTINGS_SECTIONS]
|
|
3454
|
+
}
|
|
3455
|
+
if ((replaces || adds) && !order.includes('app')) {
|
|
3456
|
+
order.unshift('app')
|
|
3457
|
+
}
|
|
3458
|
+
return order
|
|
3459
|
+
},
|
|
3460
|
+
|
|
3461
|
+
/**
|
|
3462
|
+
* Copy for a part of the user settings dialog the library draws itself
|
|
3463
|
+
* (its title, the credentials and walkthrough sections). The library's
|
|
3464
|
+
* own catalogue answers first, because the app's catalogue may give
|
|
3465
|
+
* the same English word another meaning: learniq translates
|
|
3466
|
+
* "Credentials" as "Diploma's" for its own records, and the dialog
|
|
3467
|
+
* showed that above the vault pane. Only when the library has no
|
|
3468
|
+
* translation in the user's language does the host `translate` get a
|
|
3469
|
+
* turn, then the text as written.
|
|
3470
|
+
*
|
|
3471
|
+
* @param {string} key The English source string.
|
|
3472
|
+
* @return {string} The translated copy.
|
|
3473
|
+
* @spec openspec/changes/dutch-user-settings-dialog/specs/dutch-user-settings-dialog/spec.md#requirement-the-user-settings-dialog-reads-dutch-for-a-dutch-reader
|
|
3474
|
+
*/
|
|
3475
|
+
libraryCopy(key) {
|
|
3476
|
+
const own = t('nextcloud-vue', key)
|
|
3477
|
+
if (typeof own === 'string' && own !== '' && own !== key) {
|
|
3478
|
+
return own
|
|
3479
|
+
}
|
|
3480
|
+
const host = this.translate(key)
|
|
3481
|
+
return typeof host === 'string' && host !== '' ? host : key
|
|
3482
|
+
},
|
|
3483
|
+
|
|
3392
3484
|
/**
|
|
3393
3485
|
* Return the translated copy for `key`, or `fallback` when the
|
|
3394
3486
|
* `translate` prop leaves the key unchanged (its default is the
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<section
|
|
8
8
|
v-if="visible && isAttention"
|
|
9
9
|
class="cn-banner-widget cn-banner-widget--attention"
|
|
10
|
-
:class="'cn-banner-widget--' + resolvedVariant"
|
|
10
|
+
:class="['cn-banner-widget--' + resolvedVariant, { 'cn-banner-widget--stacked': isStacked }]"
|
|
11
11
|
:aria-labelledby="headingId"
|
|
12
12
|
data-testid="cn-banner-widget-attention">
|
|
13
13
|
<div class="cn-banner-widget__body">
|
|
@@ -65,7 +65,9 @@
|
|
|
65
65
|
import { translate as t } from '@nextcloud/l10n'
|
|
66
66
|
import { NcNoteCard } from '@nextcloud/vue'
|
|
67
67
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
68
|
+
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
68
69
|
import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
|
|
70
|
+
import { readPath } from '../../utils/readPath.js'
|
|
69
71
|
import { safeHref } from '../../utils/safeHref.js'
|
|
70
72
|
import { nextUid } from '../../utils/uid.js'
|
|
71
73
|
import { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
@@ -266,6 +268,18 @@ export default {
|
|
|
266
268
|
default: null,
|
|
267
269
|
},
|
|
268
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Attention card: stack the actions under the text instead of beside it,
|
|
273
|
+
* the "Begin hier" card of PqMijnWerk (16px title, the action under the
|
|
274
|
+
* reason). Falls back to the `content` blob.
|
|
275
|
+
*
|
|
276
|
+
* @spec openspec/changes/screens-start-here-banner/specs/start-here-banner/spec.md#requirement-a-card-can-stack-its-action-under-the-text
|
|
277
|
+
*/
|
|
278
|
+
stacked: {
|
|
279
|
+
type: Boolean,
|
|
280
|
+
default: false,
|
|
281
|
+
},
|
|
282
|
+
|
|
269
283
|
/**
|
|
270
284
|
* Stored content blob (CnDashboardPage registry branch) carrying the
|
|
271
285
|
* same keys as the flat props: `{ variant, text, visibleWhen, route,
|
|
@@ -326,6 +340,12 @@ export default {
|
|
|
326
340
|
* the text wherever it says `{value}`.
|
|
327
341
|
*/
|
|
328
342
|
conditionValue: null,
|
|
343
|
+
/**
|
|
344
|
+
* The first row of the `visibleWhen` source query (its `order`
|
|
345
|
+
* applied), the card's subject: `{subject.<field>}` in the texts
|
|
346
|
+
* and `@subject.<field>` in an action route read it.
|
|
347
|
+
*/
|
|
348
|
+
conditionRow: null,
|
|
329
349
|
/** Whether the visibleWhen request itself failed (not: answered "no"). */
|
|
330
350
|
conditionFailed: false,
|
|
331
351
|
/** Why it failed, as the request reported it. */
|
|
@@ -383,6 +403,15 @@ export default {
|
|
|
383
403
|
return (this.layout || (this.content && this.content.layout) || 'banner') === 'attention'
|
|
384
404
|
},
|
|
385
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Whether the actions sit under the text.
|
|
408
|
+
*
|
|
409
|
+
* @return {boolean}
|
|
410
|
+
*/
|
|
411
|
+
isStacked() {
|
|
412
|
+
return this.stacked || Boolean(this.content && this.content.stacked)
|
|
413
|
+
},
|
|
414
|
+
|
|
386
415
|
/**
|
|
387
416
|
* The attention card's kicker (prop, else content).
|
|
388
417
|
*
|
|
@@ -391,7 +420,7 @@ export default {
|
|
|
391
420
|
*/
|
|
392
421
|
resolvedKicker() {
|
|
393
422
|
const kicker = this.kicker || (this.content && this.content.kicker) || ''
|
|
394
|
-
return kicker ? this.tl(kicker) : ''
|
|
423
|
+
return kicker ? this.fillValue(this.tl(kicker)) : ''
|
|
395
424
|
},
|
|
396
425
|
|
|
397
426
|
/**
|
|
@@ -459,7 +488,7 @@ export default {
|
|
|
459
488
|
let target = null
|
|
460
489
|
let href = ''
|
|
461
490
|
if (action.route) {
|
|
462
|
-
target = typeof action.route === 'string' ? { name: action.route } : action.route
|
|
491
|
+
target = typeof action.route === 'string' ? { name: action.route } : this.resolveSubject(action.route)
|
|
463
492
|
href = resolveHref(target, this.$router)
|
|
464
493
|
} else if (typeof action.href === 'string' && action.href !== '') {
|
|
465
494
|
const safe = safeHref(action.href)
|
|
@@ -518,7 +547,7 @@ export default {
|
|
|
518
547
|
*/
|
|
519
548
|
uncheckedText() {
|
|
520
549
|
const title = this.title || (this.content && this.content.title) || this.resolvedText
|
|
521
|
-
if (!title || title.includes('{value}')) {
|
|
550
|
+
if (!title || title.includes('{value}') || title.includes('{subject.')) {
|
|
522
551
|
return t('nextcloud-vue', 'Could not check')
|
|
523
552
|
}
|
|
524
553
|
// `escape: false`: the result is rendered through `{{ }}`, which escapes.
|
|
@@ -590,10 +619,55 @@ export default {
|
|
|
590
619
|
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card
|
|
591
620
|
*/
|
|
592
621
|
fillValue(text) {
|
|
593
|
-
|
|
622
|
+
let out = text
|
|
623
|
+
if (this.conditionValue !== null && out.includes('{value}')) {
|
|
624
|
+
out = out.replaceAll('{value}', String(this.conditionValue))
|
|
625
|
+
}
|
|
626
|
+
return this.fillSubject(out)
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Fill `{subject.<field>}` (and `{subject.<field>|date}`, `|days`) from
|
|
631
|
+
* the card's subject row. A token with no value empties the whole text,
|
|
632
|
+
* as in a record list. Text without a subject token is untouched.
|
|
633
|
+
*
|
|
634
|
+
* @param {string} text The text.
|
|
635
|
+
* @return {string} The filled text.
|
|
636
|
+
* @spec openspec/changes/screens-start-here-banner/specs/start-here-banner/spec.md#requirement-a-banner-can-name-the-first-row-of-a-query
|
|
637
|
+
*/
|
|
638
|
+
fillSubject(text) {
|
|
639
|
+
if (typeof text !== 'string' || !text.includes('{subject.')) {
|
|
594
640
|
return text
|
|
595
641
|
}
|
|
596
|
-
|
|
642
|
+
// `{value}` is not a subject token: keep it out of the template fill.
|
|
643
|
+
const guard = '\u0000value\u0000'
|
|
644
|
+
const filled = fillTemplate(text.replaceAll('{value}', guard), { subject: this.conditionRow || {} })
|
|
645
|
+
return filled.replaceAll(guard, '{value}')
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Replace `@subject.<field>` strings anywhere in an action route
|
|
650
|
+
* (params, query) by the subject's value.
|
|
651
|
+
*
|
|
652
|
+
* @param {unknown} node A route name, location or any nested part of one.
|
|
653
|
+
* @return {unknown} The same shape with the tokens resolved.
|
|
654
|
+
* @spec openspec/changes/screens-start-here-banner/specs/start-here-banner/spec.md#requirement-a-banner-can-name-the-first-row-of-a-query
|
|
655
|
+
*/
|
|
656
|
+
resolveSubject(node) {
|
|
657
|
+
if (typeof node === 'string') {
|
|
658
|
+
if (!node.startsWith('@subject.')) {
|
|
659
|
+
return node
|
|
660
|
+
}
|
|
661
|
+
const value = this.conditionRow ? readPath(this.conditionRow, node.slice('@subject.'.length)) : undefined
|
|
662
|
+
return value === undefined || value === null ? '' : String(value)
|
|
663
|
+
}
|
|
664
|
+
if (Array.isArray(node)) {
|
|
665
|
+
return node.map((item) => this.resolveSubject(item))
|
|
666
|
+
}
|
|
667
|
+
if (node && typeof node === 'object') {
|
|
668
|
+
return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, this.resolveSubject(value)]))
|
|
669
|
+
}
|
|
670
|
+
return node
|
|
597
671
|
},
|
|
598
672
|
|
|
599
673
|
/**
|
|
@@ -636,6 +710,7 @@ export default {
|
|
|
636
710
|
if (outcome) {
|
|
637
711
|
this.conditionMet = outcome.met === true
|
|
638
712
|
this.conditionValue = outcome.value !== undefined ? outcome.value : null
|
|
713
|
+
this.conditionRow = outcome.row && typeof outcome.row === 'object' ? outcome.row : null
|
|
639
714
|
this.conditionFailed = outcome.failed === true
|
|
640
715
|
this.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''
|
|
641
716
|
return
|
|
@@ -644,12 +719,15 @@ export default {
|
|
|
644
719
|
if (!cond) {
|
|
645
720
|
this.conditionMet = null
|
|
646
721
|
this.conditionValue = null
|
|
722
|
+
this.conditionRow = null
|
|
647
723
|
this.conditionFailed = false
|
|
648
724
|
this.conditionFailureReason = ''
|
|
649
725
|
return
|
|
650
726
|
}
|
|
651
727
|
try {
|
|
652
|
-
const
|
|
728
|
+
const sink = {}
|
|
729
|
+
const value = await readVisibleWhenValue(cond, undefined, sink)
|
|
730
|
+
this.conditionRow = sink.row || null
|
|
653
731
|
this.conditionValue = value
|
|
654
732
|
this.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)
|
|
655
733
|
this.conditionFailed = false
|
|
@@ -662,6 +740,7 @@ export default {
|
|
|
662
740
|
console.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)
|
|
663
741
|
this.conditionMet = false
|
|
664
742
|
this.conditionValue = null
|
|
743
|
+
this.conditionRow = null
|
|
665
744
|
this.conditionFailed = true
|
|
666
745
|
this.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')
|
|
667
746
|
}
|
|
@@ -784,6 +863,32 @@ export default {
|
|
|
784
863
|
color: var(--color-text-maxcontrast);
|
|
785
864
|
}
|
|
786
865
|
|
|
866
|
+
/* Stacked (PqMijnWerk "Begin hier"): the text, then the action under it. */
|
|
867
|
+
.cn-banner-widget--stacked {
|
|
868
|
+
flex-direction: column;
|
|
869
|
+
align-items: flex-start;
|
|
870
|
+
gap: 10px;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.cn-banner-widget--stacked .cn-banner-widget__body {
|
|
874
|
+
flex: 0 0 auto;
|
|
875
|
+
gap: 10px;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.cn-banner-widget--stacked .cn-banner-widget__title {
|
|
879
|
+
font-size: 16px;
|
|
880
|
+
line-height: 1.4;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.cn-banner-widget--stacked .cn-banner-widget__reason {
|
|
884
|
+
font-size: 14px;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.cn-banner-widget--stacked .cn-banner-widget__action {
|
|
888
|
+
min-height: var(--cn-board-control-height, 40px);
|
|
889
|
+
font-size: 14px;
|
|
890
|
+
}
|
|
891
|
+
|
|
787
892
|
.cn-banner-widget__actions {
|
|
788
893
|
display: flex;
|
|
789
894
|
flex-wrap: wrap;
|
|
@@ -882,11 +882,14 @@ export default {
|
|
|
882
882
|
* a `{ value: variant }` map (e.g. `{ placed: 'primary', delivered: 'success' }`)
|
|
883
883
|
* that CnStatusBadge resolves per label. Null when unset (falls back to
|
|
884
884
|
* `badgeVariant`). Lets a manifest colour a status column without code.
|
|
885
|
+
* A column-level `colorMap` (it reaches the cell as the property's
|
|
886
|
+
* `colorMap`) works too, so `{ widget: 'badge', colorMap }` and
|
|
887
|
+
* `{ widget: 'badge', widgetProps: { colorMap } }` read the same.
|
|
885
888
|
*
|
|
886
889
|
* @return {object|null}
|
|
887
890
|
*/
|
|
888
891
|
badgeColorMap() {
|
|
889
|
-
return (this.widgetProps && this.widgetProps.colorMap) || null
|
|
892
|
+
return (this.widgetProps && this.widgetProps.colorMap) || this.enumColorMap || null
|
|
890
893
|
},
|
|
891
894
|
|
|
892
895
|
/**
|
|
@@ -29,7 +29,7 @@ import { readPath } from '../../utils/readPath.js'
|
|
|
29
29
|
* `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.
|
|
30
30
|
* `@objectId` in `endpoint` is replaced by the record's id. A reply the
|
|
31
31
|
* endpoint accepts is shown at once, until the record is read again.
|
|
32
|
-
* Resolved by its registry key,
|
|
32
|
+
* Resolved by its registry key, and exported for an app that renders it itself.
|
|
33
33
|
*/
|
|
34
34
|
export default {
|
|
35
35
|
name: 'CnConversationWidget',
|
|
@@ -424,14 +424,14 @@ export default {
|
|
|
424
424
|
* anything whose height is whatever it turned out to have.
|
|
425
425
|
*
|
|
426
426
|
* @param {object} item the layout item.
|
|
427
|
-
* @return {string|undefined} `'true'`, a row
|
|
427
|
+
* @return {string|undefined} `'true'`, a row maximum as a string, or undefined.
|
|
428
428
|
*/
|
|
429
429
|
sizeToContentAttr(item) {
|
|
430
430
|
const v = item && item.sizeToContent
|
|
431
431
|
if (v === true) {
|
|
432
432
|
return 'true'
|
|
433
433
|
}
|
|
434
|
-
// A number is GridStack's "size to content, but never
|
|
434
|
+
// A number is GridStack's "size to content, but never taller than N rows" (a maximum).
|
|
435
435
|
if (Number.isFinite(v) && v > 0) {
|
|
436
436
|
return String(v)
|
|
437
437
|
}
|
|
@@ -933,7 +933,7 @@ import { normalizeLook } from '../../composables/useLook.js'
|
|
|
933
933
|
import { pageViews } from '../../mixins/pageViews.js'
|
|
934
934
|
import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashboardLayouts.js'
|
|
935
935
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
936
|
-
import { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
936
|
+
import { compareVisibleWhen, evaluateWorkspaceCondition, isWorkspaceCondition, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
937
937
|
import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
|
|
938
938
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
939
939
|
import { CnActionsMenu } from '../CnActionsMenu/index.js'
|
|
@@ -2256,6 +2256,10 @@ export default {
|
|
|
2256
2256
|
const items = []
|
|
2257
2257
|
for (const id of ids) {
|
|
2258
2258
|
if (this.getWidgetDef(id) || this.hasWidgetSlot(id)) {
|
|
2259
|
+
// A tile a page control hides surrenders its cell.
|
|
2260
|
+
if (!this.gridEditable && this.isHiddenByWorkspace(id)) {
|
|
2261
|
+
continue
|
|
2262
|
+
}
|
|
2259
2263
|
items.push({ widgetId: id })
|
|
2260
2264
|
} else if (process.env.NODE_ENV !== 'production') {
|
|
2261
2265
|
// eslint-disable-next-line no-console
|
|
@@ -3435,6 +3439,11 @@ export default {
|
|
|
3435
3439
|
if (!visibleWhen) {
|
|
3436
3440
|
return false
|
|
3437
3441
|
}
|
|
3442
|
+
// A workspace condition (a page control's choice) is answered at
|
|
3443
|
+
// once from the reactive bag; nothing is fetched for it.
|
|
3444
|
+
if (isWorkspaceCondition(visibleWhen)) {
|
|
3445
|
+
return !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
|
|
3446
|
+
}
|
|
3438
3447
|
const outcome = this.widgetConditionOutcome[item.widgetId]
|
|
3439
3448
|
if (outcome && outcome.failed === true && layout === 'attention' && this.isBannerDef(def)) {
|
|
3440
3449
|
return false
|
|
@@ -3442,6 +3451,23 @@ export default {
|
|
|
3442
3451
|
return !outcome || outcome.met !== true
|
|
3443
3452
|
},
|
|
3444
3453
|
|
|
3454
|
+
/**
|
|
3455
|
+
* Whether a page control (a workspace condition on `visibleWhen`)
|
|
3456
|
+
* hides this widget right now. Other conditions are not looked at.
|
|
3457
|
+
*
|
|
3458
|
+
* @param {string} widgetId The widget id.
|
|
3459
|
+
* @return {boolean} True when the widget must not draw.
|
|
3460
|
+
* @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-widget-can-be-shown-by-a-workspace-value
|
|
3461
|
+
*/
|
|
3462
|
+
isHiddenByWorkspace(widgetId) {
|
|
3463
|
+
const def = this.getWidgetDef(widgetId)
|
|
3464
|
+
if (!def || !def.type) {
|
|
3465
|
+
return false
|
|
3466
|
+
}
|
|
3467
|
+
const { visibleWhen } = this.widgetDisplayConfig(def)
|
|
3468
|
+
return isWorkspaceCondition(visibleWhen) && !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
|
|
3469
|
+
},
|
|
3470
|
+
|
|
3445
3471
|
/**
|
|
3446
3472
|
* Evaluate every conditional widget's `visibleWhen` into
|
|
3447
3473
|
* `widgetConditionOutcome` — the verdict AND the raw field value,
|
|
@@ -3464,7 +3490,8 @@ export default {
|
|
|
3464
3490
|
async evaluateWidgetConditions() {
|
|
3465
3491
|
const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)]
|
|
3466
3492
|
const conditional = defs.filter((def) => def && def.id && def.type
|
|
3467
|
-
&& this.widgetDisplayConfig(def).visibleWhen
|
|
3493
|
+
&& this.widgetDisplayConfig(def).visibleWhen
|
|
3494
|
+
&& !isWorkspaceCondition(this.widgetDisplayConfig(def).visibleWhen))
|
|
3468
3495
|
// Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
|
|
3469
3496
|
// watch fires on any def edit, but only an added/removed/edited
|
|
3470
3497
|
// predicate warrants refetching every conditional endpoint.
|
|
@@ -3484,8 +3511,13 @@ export default {
|
|
|
3484
3511
|
const cond = this.widgetDisplayConfig(def).visibleWhen
|
|
3485
3512
|
let outcome
|
|
3486
3513
|
try {
|
|
3487
|
-
|
|
3514
|
+
// `sink` keeps the first row of a source query, for a banner that names it.
|
|
3515
|
+
const sink = {}
|
|
3516
|
+
const value = await readVisibleWhenValue(cond, undefined, sink)
|
|
3488
3517
|
outcome = { met: compareVisibleWhen(value, cond.op || 'eq', cond.value), value }
|
|
3518
|
+
if (sink.row) {
|
|
3519
|
+
outcome.row = sink.row
|
|
3520
|
+
}
|
|
3489
3521
|
} catch (error) {
|
|
3490
3522
|
// Fail-safe: not met. But the failure is kept, and said
|
|
3491
3523
|
// out loud: a failed count and "nothing to report" look
|
|
@@ -3985,6 +4017,11 @@ export default {
|
|
|
3985
4017
|
const def = this.getWidgetDef(item.widgetId)
|
|
3986
4018
|
const value = item.showTitle !== undefined ? item.showTitle : def?.showTitle
|
|
3987
4019
|
if (value === undefined || value === null) {
|
|
4020
|
+
// A banner that names no title draws no title row: it used to show
|
|
4021
|
+
// its widget id ("attention-first") as the card title.
|
|
4022
|
+
if (this.isBannerDef(def) && !item.customTitle && !def?.customTitle && !def?.title) {
|
|
4023
|
+
return false
|
|
4024
|
+
}
|
|
3988
4025
|
return !this.isCardWidget(item)
|
|
3989
4026
|
}
|
|
3990
4027
|
return value !== false
|