@conduction/nextcloud-vue 2.142.0 → 2.144.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/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +27 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js +2 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js +9 -0
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js +21 -22
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +29 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +28 -13
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +29 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +4 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +17 -4
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +21 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +18 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +6 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +12 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +2 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +4 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +18 -8
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
- package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
- package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
- package/dist/esm/l10n/en.json.js +6 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +7 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1264 -597
- package/dist/esm/utils/fetchFilterCounts.js +5 -2
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/nextcloudDirectory.js +19 -6
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1619 -849
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1264 -597
- package/l10n/en.json +6 -0
- package/l10n/nl.json +7 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +28 -1
- package/src/components/CnAiCompanion/CnAiChatPanel.vue +12 -0
- package/src/components/CnAiCompanion/CnAiInput.vue +32 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +34 -4
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +1 -0
- package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +8 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +1 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
- package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +43 -4
- package/src/components/CnFilesBrowser/filesBrowser.js +17 -4
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +20 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +18 -2
- package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
- package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
- package/src/components/CnPageHeader/CnHeaderTrail.vue +6 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +21 -1
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +6 -0
- package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
- package/src/components/CnTimelineWidget/CnTimelineManage.vue +10 -1
- package/src/css/dialog.css +25 -1
- package/src/css/kpi-card.css +3 -0
- package/src/css/look-board-detail.css +348 -0
- package/src/css/look-board-index.css +56 -1
- package/src/css/look-board.css +98 -1
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
- package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
- package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
- package/src/integrations/builtin/calendar/eventStatus.js +63 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
- package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
- package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
- package/src/integrations/builtin/deck/deckCardParts.js +56 -0
- package/src/utils/fetchFilterCounts.js +5 -2
- package/src/utils/nextcloudDirectory.js +19 -6
package/l10n/en.json
CHANGED
|
@@ -1706,6 +1706,12 @@
|
|
|
1706
1706
|
"expires {date}": "expires {date}",
|
|
1707
1707
|
"Loading …": "Loading …",
|
|
1708
1708
|
"Reorder {item}: drag, or press the up and down arrow keys": "Reorder {item}: drag, or press the up and down arrow keys",
|
|
1709
|
+
"Board {name}": "Board {name}",
|
|
1710
|
+
"list {name}": "list {name}",
|
|
1711
|
+
"Tentative": "Tentative",
|
|
1712
|
+
"Confirmed": "Confirmed",
|
|
1713
|
+
"Cancelled": "Cancelled",
|
|
1714
|
+
"{start} until {end}": "{start} until {end}",
|
|
1709
1715
|
"No contacts linked": "No contacts linked",
|
|
1710
1716
|
"No relations yet": "No relations yet",
|
|
1711
1717
|
"Period": "Period",
|
package/l10n/nl.json
CHANGED
|
@@ -1910,7 +1910,7 @@
|
|
|
1910
1910
|
"Now": "Nu",
|
|
1911
1911
|
"Tell me above a number": "Melden vanaf een aantal",
|
|
1912
1912
|
"Threshold": "Grens",
|
|
1913
|
-
"A message to you when there are {threshold} or more": "Bericht aan u als er {threshold} of meer zijn",
|
|
1913
|
+
"A message to you when there are {threshold} or more": "Bericht aan u als er {threshold} of meer open zijn",
|
|
1914
1914
|
"Save this search or open a saved one to set an alert on it.": "Sla deze zoekopdracht op of open een opgeslagen zoekopdracht om er een melding op te zetten.",
|
|
1915
1915
|
"The alert could not be saved.": "De melding kon niet worden opgeslagen.",
|
|
1916
1916
|
"Your exports could not be loaded.": "Uw exports konden niet worden geladen.",
|
|
@@ -1919,6 +1919,12 @@
|
|
|
1919
1919
|
"Made {date}": "Gemaakt {date}",
|
|
1920
1920
|
"expires {date}": "verloopt {date}",
|
|
1921
1921
|
"Reorder {item}: drag, or press the up and down arrow keys": "{item} verplaatsen: slepen, of de pijltoetsen omhoog en omlaag",
|
|
1922
|
+
"Board {name}": "Bord {name}",
|
|
1923
|
+
"list {name}": "lijst {name}",
|
|
1924
|
+
"Tentative": "Voorlopig",
|
|
1925
|
+
"Confirmed": "Bevestigd",
|
|
1926
|
+
"Cancelled": "Geannuleerd",
|
|
1927
|
+
"{start} until {end}": "{start} tot {end}",
|
|
1922
1928
|
"No contacts linked": "Geen contacten gekoppeld",
|
|
1923
1929
|
"No relations yet": "Nog geen relaties",
|
|
1924
1930
|
"Period": "Periode",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.144.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -191,6 +191,7 @@ import CircleOutline from 'vue-material-design-icons/CircleOutline.vue'
|
|
|
191
191
|
import HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'
|
|
192
192
|
import CnFormField from '../CnFormField/CnFormField.vue'
|
|
193
193
|
import { fetchEndpointSource, interpolateUrlTokens } from '../../composables/useEndpointSource.js'
|
|
194
|
+
import { BUILT_IN_FORMATTERS } from '../../utils/builtInFormatters.js'
|
|
194
195
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
195
196
|
import { readPath } from '../../utils/readPath.js'
|
|
196
197
|
import { CnDialog } from '../CnDialog/index.js'
|
|
@@ -219,7 +220,8 @@ let reasonSeq = 0
|
|
|
219
220
|
* `eyebrow` a template filled from the record ("Close off · {identifier}").
|
|
220
221
|
* - `summary`: `{ source?, title, meta?, badge?, icon? }`: templates filled from
|
|
221
222
|
* the record (`source` an endpointSource to read it from, else `object`).
|
|
222
|
-
* - `facts`: `[{ title?, items: [{ label, value }] }]`, `value` a template
|
|
223
|
+
* - `facts`: `[{ title?, items: [{ label, value }] }]`, `value` a template; a
|
|
224
|
+
* `{path|<formatter>}` token reads through the built-in and app formatters.
|
|
223
225
|
* - `checklist`: `{ title?, source, itemsPath?, labels?, required? }`: items
|
|
224
226
|
* `{ id, met, label?, detail? }` read from `source`; `labels[id]` gives an
|
|
225
227
|
* item's `{ label, detail?, unmetDetail? }`; the ids in `required` must be
|
|
@@ -262,6 +264,8 @@ export default {
|
|
|
262
264
|
cnTranslate: { default: () => (key) => key },
|
|
263
265
|
/** The page's pre-bound action dispatcher (CnPageRenderer), for `confirm.action`. */
|
|
264
266
|
cnDispatchAction: { default: null },
|
|
267
|
+
/** The app's named formatters (CnAppRoot), for `{path|<formatter>}` in a template. */
|
|
268
|
+
cnFormatters: { default: () => ({}) },
|
|
265
269
|
},
|
|
266
270
|
|
|
267
271
|
props: {
|
|
@@ -594,7 +598,8 @@ export default {
|
|
|
594
598
|
if (typeof template !== 'string' || template === '') {
|
|
595
599
|
return ''
|
|
596
600
|
}
|
|
597
|
-
|
|
601
|
+
const formatters = { ...BUILT_IN_FORMATTERS, ...(this.cnFormatters || {}) }
|
|
602
|
+
return fillTemplate(this.cnTranslate(template), row || this.record || {}, new Date(), { formatters }).trim()
|
|
598
603
|
},
|
|
599
604
|
|
|
600
605
|
/**
|
|
@@ -836,6 +841,7 @@ export default {
|
|
|
836
841
|
.cn-action-dialog__summary-text strong {
|
|
837
842
|
font-size: 15px;
|
|
838
843
|
font-weight: 600;
|
|
844
|
+
line-height: 1.2;
|
|
839
845
|
}
|
|
840
846
|
|
|
841
847
|
.cn-action-dialog__summary-text .cn-action-dialog__muted {
|
|
@@ -949,7 +955,25 @@ export default {
|
|
|
949
955
|
padding: 12px 14px;
|
|
950
956
|
border: 2px solid var(--color-primary-element);
|
|
951
957
|
border-radius: var(--border-radius-element, 8px);
|
|
952
|
-
background: var(--color-
|
|
958
|
+
background: var(--color-main-background);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/* The box sits at the card's inset, 12px before the label, the label on
|
|
962
|
+
its first line (DqArchiveren "Sanne de Vries laten weten ..."):
|
|
963
|
+
NcCheckboxRadioSwitch pads its row and centres a 34px target. */
|
|
964
|
+
.cn-action-dialog__checkbox :deep(.checkbox-content) {
|
|
965
|
+
padding: 0;
|
|
966
|
+
gap: 12px;
|
|
967
|
+
align-items: flex-start;
|
|
968
|
+
min-height: 0;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.cn-action-dialog__checkbox :deep(.checkbox-content__icon) {
|
|
972
|
+
margin: 0;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.cn-action-dialog__checkbox :deep(.checkbox-radio-switch__input) {
|
|
976
|
+
min-height: 0;
|
|
953
977
|
}
|
|
954
978
|
|
|
955
979
|
.cn-action-dialog__checkbox-text {
|
|
@@ -961,6 +985,7 @@ export default {
|
|
|
961
985
|
.cn-action-dialog__checkbox-label {
|
|
962
986
|
font-size: 15px;
|
|
963
987
|
font-weight: 600;
|
|
988
|
+
line-height: 1.2;
|
|
964
989
|
}
|
|
965
990
|
|
|
966
991
|
.cn-action-dialog__checkbox .cn-action-dialog__muted {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div
|
|
3
3
|
class="cn-admin-settings-shell"
|
|
4
4
|
:class="[ownLookClass, sectionColumns ? `cn-admin-settings-shell--columns-${sectionColumns}` : '']"
|
|
5
|
-
:style="
|
|
5
|
+
:style="shellStyle">
|
|
6
6
|
<!-- Names a development, test or acceptance environment; cannot be dismissed. Production shows nothing. -->
|
|
7
7
|
<CnEnvironmentBanner v-if="environmentName" :environment="environmentName" />
|
|
8
8
|
|
|
@@ -780,6 +780,16 @@ export default {
|
|
|
780
780
|
type: Number,
|
|
781
781
|
default: 0,
|
|
782
782
|
},
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* The board look's gap in px between a card's title and its
|
|
786
|
+
* description, when the app's board draws it other than 4px; the
|
|
787
|
+
* content follows 10px further (DqBeheerinstellingen 6, so 16).
|
|
788
|
+
*/
|
|
789
|
+
descGap: {
|
|
790
|
+
type: Number,
|
|
791
|
+
default: 0,
|
|
792
|
+
},
|
|
783
793
|
},
|
|
784
794
|
|
|
785
795
|
emits: ['update', 'reimported', 'reimport-error', 'tab-change'],
|
|
@@ -818,6 +828,23 @@ export default {
|
|
|
818
828
|
},
|
|
819
829
|
|
|
820
830
|
computed: {
|
|
831
|
+
/**
|
|
832
|
+
* The board look sizes an app's board sets on the shell
|
|
833
|
+
* (`versionTitleSize`, `descGap`), as CSS custom properties.
|
|
834
|
+
*
|
|
835
|
+
* @return {object|null} The style, or null when none is set.
|
|
836
|
+
*/
|
|
837
|
+
shellStyle() {
|
|
838
|
+
const style = {}
|
|
839
|
+
if (this.versionTitleSize) {
|
|
840
|
+
style['--cn-board-version-title-size'] = `${this.versionTitleSize}px`
|
|
841
|
+
}
|
|
842
|
+
if (this.descGap) {
|
|
843
|
+
style['--cn-board-settings-desc-gap'] = `${this.descGap}px`
|
|
844
|
+
}
|
|
845
|
+
return Object.keys(style).length > 0 ? style : null
|
|
846
|
+
},
|
|
847
|
+
|
|
821
848
|
/**
|
|
822
849
|
* The tabs that have an id and a label.
|
|
823
850
|
*
|
|
@@ -310,6 +310,7 @@
|
|
|
310
310
|
:disabled="streamState.isStreaming"
|
|
311
311
|
:placeholder="inputPlaceholder"
|
|
312
312
|
:sendLabel="sendLabel"
|
|
313
|
+
:stacked="titled"
|
|
313
314
|
:chatAppId="chatAppId"
|
|
314
315
|
:speechInputEngine="speechPolicy.inputEngine"
|
|
315
316
|
:dictationSilenceTimeout="speechPolicy.silenceTimeout"
|
|
@@ -1599,10 +1600,21 @@ export default {
|
|
|
1599
1600
|
}
|
|
1600
1601
|
|
|
1601
1602
|
.cn-ai-chat-window--titled .cn-ai-chat-window__input {
|
|
1603
|
+
padding: 0;
|
|
1604
|
+
border-top: 0;
|
|
1602
1605
|
border-radius: 0;
|
|
1603
1606
|
background: transparent;
|
|
1604
1607
|
}
|
|
1605
1608
|
|
|
1609
|
+
/* In a card the panel's body takes the card's 16px 18px inset. */
|
|
1610
|
+
.cn-ai-chat-window--titled .cn-ai-chat-window__messages {
|
|
1611
|
+
padding: 16px 18px;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.cn-ai-chat-window--titled .cn-ai-chat-window__note {
|
|
1615
|
+
margin: 0 18px 16px;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1606
1618
|
.cn-ai-chat-window__input {
|
|
1607
1619
|
flex: 0 0 auto;
|
|
1608
1620
|
padding: 8px;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
for the parent's async request to resolve before clearing either).
|
|
24
24
|
-->
|
|
25
25
|
<template>
|
|
26
|
-
<div class="cn-ai-input" data-testid="cn-ai-input">
|
|
26
|
+
<div class="cn-ai-input" :class="{ 'cn-ai-input--stacked': stacked }" data-testid="cn-ai-input">
|
|
27
27
|
<NcNoteCard
|
|
28
28
|
v-if="uploadError"
|
|
29
29
|
type="error"
|
|
@@ -298,6 +298,15 @@ export default {
|
|
|
298
298
|
default: '',
|
|
299
299
|
},
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* The narrow layout of a card: the text box and the send button on the
|
|
303
|
+
* first line, dictation and attach on a line under it.
|
|
304
|
+
*/
|
|
305
|
+
stacked: {
|
|
306
|
+
type: Boolean,
|
|
307
|
+
default: false,
|
|
308
|
+
},
|
|
309
|
+
|
|
301
310
|
/**
|
|
302
311
|
* Backend app id the attach control uploads files to
|
|
303
312
|
* (`POST /index.php/apps/{chatAppId}/api/chat/attachments`). See
|
|
@@ -1184,6 +1193,28 @@ export default {
|
|
|
1184
1193
|
align-items: flex-end;
|
|
1185
1194
|
}
|
|
1186
1195
|
|
|
1196
|
+
/* `stacked`: in a card the box keeps the line, dictation and attach wrap
|
|
1197
|
+
under it (DqZaakAssistent "Vraag iets over deze zaak" + "Vragen"). */
|
|
1198
|
+
.cn-ai-input--stacked {
|
|
1199
|
+
padding: 12px 18px;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.cn-ai-input--stacked .cn-ai-input__row {
|
|
1203
|
+
flex-wrap: wrap;
|
|
1204
|
+
align-items: center;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
.cn-ai-input--stacked .cn-ai-input__textarea {
|
|
1208
|
+
order: -2;
|
|
1209
|
+
flex: 1 1 calc(100% - 104px);
|
|
1210
|
+
min-width: 0;
|
|
1211
|
+
min-height: 40px;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.cn-ai-input--stacked .cn-ai-input__send-button {
|
|
1215
|
+
order: -1;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1187
1218
|
.cn-ai-input__file-input {
|
|
1188
1219
|
display: none;
|
|
1189
1220
|
}
|
|
@@ -30,13 +30,15 @@
|
|
|
30
30
|
<li
|
|
31
31
|
v-for="entry in displayedEntries"
|
|
32
32
|
:key="entry.id"
|
|
33
|
-
class="cn-audit-card__row"
|
|
33
|
+
class="cn-audit-card__row"
|
|
34
|
+
:class="{ 'cn-audit-card__row--stacked': layout === 'stacked' }">
|
|
34
35
|
<div class="cn-audit-card__row-head">
|
|
35
36
|
<strong class="cn-audit-card__action">{{ formatAction(entry) }}</strong>
|
|
36
|
-
<span class="cn-audit-card__when">{{ formatWhen(entry) }}</span>
|
|
37
|
+
<span v-if="layout !== 'stacked'" class="cn-audit-card__when">{{ formatWhen(entry) }}</span>
|
|
37
38
|
</div>
|
|
38
39
|
<div class="cn-audit-card__row-body">
|
|
39
|
-
<span class="cn-audit-
|
|
40
|
+
<span v-if="layout === 'stacked'" class="cn-audit-card__when" data-testid="cn-audit-card-when-below">{{ stackedLine(entry) }}</span>
|
|
41
|
+
<span v-else class="cn-audit-card__actor">{{ formatActor(entry) }}</span>
|
|
40
42
|
<span
|
|
41
43
|
v-if="entry.authorized === false"
|
|
42
44
|
class="cn-audit-card__refused"
|
|
@@ -64,8 +66,10 @@ import { NcLoadingIcon } from '@nextcloud/vue'
|
|
|
64
66
|
import { markRaw } from 'vue'
|
|
65
67
|
import History from 'vue-material-design-icons/History.vue'
|
|
66
68
|
import CnDetailCard from '../CnDetailCard/CnDetailCard.vue'
|
|
69
|
+
import { formatBoardDate, formatTimeOfDay } from '../../utils/boardDate.js'
|
|
67
70
|
import { buildHeaders, prefixUrl } from '../../utils/index.js'
|
|
68
71
|
import { leafSourceUrl } from '../../utils/leafSourceUrl.js'
|
|
72
|
+
import { userDisplayName } from '../../utils/userDisplayName.js'
|
|
69
73
|
import { userLocale } from '../../utils/userLocale.js'
|
|
70
74
|
|
|
71
75
|
/**
|
|
@@ -154,6 +158,12 @@ export default {
|
|
|
154
158
|
|
|
155
159
|
/** Maximum rows to render. */
|
|
156
160
|
maxDisplay: { type: Number, default: 5 },
|
|
161
|
+
/**
|
|
162
|
+
* `stacked` (DqTaak "Historie"): the action, then one muted line under
|
|
163
|
+
* it with the moment ("6 okt 09:05") and who, when the action does not
|
|
164
|
+
* already name them. Default: the moment beside the action, who under it.
|
|
165
|
+
*/
|
|
166
|
+
layout: { type: String, default: '' },
|
|
157
167
|
/**
|
|
158
168
|
* Actions the card leaves out (case-insensitive), such as `['read']`
|
|
159
169
|
* for a history of what changed. It then reads more entries than it
|
|
@@ -440,7 +450,27 @@ export default {
|
|
|
440
450
|
* @return {string} Who did it, by name where the entry carries one.
|
|
441
451
|
*/
|
|
442
452
|
actorName(entry) {
|
|
443
|
-
|
|
453
|
+
if (entry.actorDisplayName) {
|
|
454
|
+
return entry.actorDisplayName
|
|
455
|
+
}
|
|
456
|
+
const id = entry.actor || entry.userId || entry.user || ''
|
|
457
|
+
// A person's user id reads as their display name once it is known.
|
|
458
|
+
return (id && (!entry.performerType || entry.performerType === 'user') && userDisplayName(String(id))) || id
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The muted line of a stacked row: the moment in the board's short
|
|
463
|
+
* form ("6 okt 09:05") and who, unless the action names them already.
|
|
464
|
+
*
|
|
465
|
+
* @param {object} entry The entry.
|
|
466
|
+
* @return {string} The line.
|
|
467
|
+
* @spec openspec/changes/audit-card-stacked-rows/specs/audit-trail/spec.md#requirement-an-audit-row-can-put-its-moment-under-the-action
|
|
468
|
+
*/
|
|
469
|
+
stackedLine(entry) {
|
|
470
|
+
const raw = entry.creationDateTime || entry.created || entry.timestamp
|
|
471
|
+
const date = raw ? new Date(raw) : null
|
|
472
|
+
const when = date && !Number.isNaN(date.getTime()) ? `${formatBoardDate(date)} ${formatTimeOfDay(date)}` : ''
|
|
473
|
+
return [when, this.formatActor(entry)].filter((part) => part !== '').join(' · ')
|
|
444
474
|
},
|
|
445
475
|
|
|
446
476
|
/**
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
:scope="resolvedScope"
|
|
17
17
|
:title="resolvedTitle"
|
|
18
18
|
:maxDisplay="resolvedMaxDisplay"
|
|
19
|
+
:layout="content && typeof content.layout === 'string' ? content.layout : ''"
|
|
19
20
|
:excludeActions="resolvedExcludeActions"
|
|
20
21
|
:actionLabels="actionLabels"
|
|
21
22
|
:valueLabels="valueLabels"
|
|
@@ -1195,18 +1195,25 @@ export default {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
|
|
1197
1197
|
.cn-checklist-run__legend {
|
|
1198
|
+
font-size: 15px;
|
|
1198
1199
|
font-weight: 600;
|
|
1200
|
+
line-height: 1.2;
|
|
1199
1201
|
padding: 0 4px;
|
|
1200
1202
|
}
|
|
1201
1203
|
|
|
1202
1204
|
.cn-checklist-run__help {
|
|
1203
1205
|
color: var(--color-text-maxcontrast);
|
|
1206
|
+
font-size: 14px;
|
|
1204
1207
|
margin: 0;
|
|
1205
1208
|
}
|
|
1206
1209
|
|
|
1210
|
+
/* An option reads as text, not as a field label (a theme makes every label
|
|
1211
|
+
bold); 15px as the board draws it (DqInspectie). */
|
|
1207
1212
|
.cn-checklist-run__choice {
|
|
1208
1213
|
align-items: center;
|
|
1209
1214
|
display: flex;
|
|
1215
|
+
font-size: 15px;
|
|
1216
|
+
font-weight: 400;
|
|
1210
1217
|
gap: 10px;
|
|
1211
1218
|
min-height: 34px;
|
|
1212
1219
|
}
|
|
@@ -1225,6 +1232,7 @@ export default {
|
|
|
1225
1232
|
}
|
|
1226
1233
|
|
|
1227
1234
|
.cn-checklist-run__group {
|
|
1235
|
+
font-size: 14px;
|
|
1228
1236
|
font-weight: 600;
|
|
1229
1237
|
margin: 0;
|
|
1230
1238
|
}
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
class="cn-conversation-thread__title">
|
|
18
18
|
{{ title }}
|
|
19
19
|
</component>
|
|
20
|
-
<p
|
|
20
|
+
<!-- A div, not a <p>: a theme that sets every paragraph's size with
|
|
21
|
+
!important would otherwise override the 14px subtitle. -->
|
|
22
|
+
<div v-if="subtitle" class="cn-conversation-thread__subtitle" data-testid="cn-conversation-subtitle">
|
|
21
23
|
{{ subtitle }}
|
|
22
|
-
</
|
|
24
|
+
</div>
|
|
23
25
|
</div>
|
|
24
26
|
<div v-if="$slots['header-actions']" class="cn-conversation-thread__header-actions">
|
|
25
27
|
<!-- @slot header-actions Buttons beside the title, for example "Log contact". -->
|
|
@@ -633,6 +635,7 @@ export default {
|
|
|
633
635
|
.cn-conversation-thread__subtitle {
|
|
634
636
|
color: var(--color-text-maxcontrast);
|
|
635
637
|
font-size: 14px;
|
|
638
|
+
line-height: normal;
|
|
636
639
|
margin: 0;
|
|
637
640
|
}
|
|
638
641
|
|
|
@@ -28,9 +28,12 @@
|
|
|
28
28
|
:key="keyOf(event, index)"
|
|
29
29
|
class="cn-events-card__event"
|
|
30
30
|
data-testid="cn-events-card-event">
|
|
31
|
-
<span class="cn-events-
|
|
32
|
-
<span class="cn-events-
|
|
33
|
-
|
|
31
|
+
<span v-if="dayOf(event)" class="cn-events-card__day" aria-hidden="true">{{ dayOf(event) }}</span>
|
|
32
|
+
<span class="cn-events-card__text">
|
|
33
|
+
<span class="cn-events-card__summary">{{ summaryOf(event) }}</span>
|
|
34
|
+
<span class="cn-events-card__when">{{ whenOf(event) }}</span>
|
|
35
|
+
<span v-if="event.location" class="cn-events-card__where">{{ event.location }}</span>
|
|
36
|
+
</span>
|
|
34
37
|
</li>
|
|
35
38
|
</ul>
|
|
36
39
|
<p v-else class="cn-events-card__muted" data-testid="cn-events-card-empty">
|
|
@@ -292,6 +295,15 @@ export default {
|
|
|
292
295
|
return summary === '' ? t('nextcloud-vue', 'Appointment') : summary
|
|
293
296
|
},
|
|
294
297
|
|
|
298
|
+
/**
|
|
299
|
+
* @param {object} event The event.
|
|
300
|
+
* @return {string} The day of the month it starts on, '' when it has no readable start.
|
|
301
|
+
*/
|
|
302
|
+
dayOf(event) {
|
|
303
|
+
const parsed = new Date(String(event?.dtstart ?? '').trim())
|
|
304
|
+
return Number.isNaN(parsed.getTime()) ? '' : String(parsed.getDate())
|
|
305
|
+
},
|
|
306
|
+
|
|
295
307
|
/**
|
|
296
308
|
* @param {object} event The event.
|
|
297
309
|
* @return {string} When it is: its start and end, or whichever it has.
|
|
@@ -333,8 +345,7 @@ function formatMoment(value) {
|
|
|
333
345
|
|
|
334
346
|
.cn-events-card__event {
|
|
335
347
|
display: flex;
|
|
336
|
-
|
|
337
|
-
gap: 2px;
|
|
348
|
+
gap: 12px;
|
|
338
349
|
padding-bottom: 8px;
|
|
339
350
|
border-bottom: 1px solid var(--color-border);
|
|
340
351
|
}
|
|
@@ -344,6 +355,17 @@ function formatMoment(value) {
|
|
|
344
355
|
border-bottom: none;
|
|
345
356
|
}
|
|
346
357
|
|
|
358
|
+
.cn-events-card__day {
|
|
359
|
+
display: none;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.cn-events-card__text {
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
gap: 2px;
|
|
366
|
+
min-width: 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
347
369
|
.cn-events-card__summary {
|
|
348
370
|
font-weight: 600;
|
|
349
371
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
Files app and its plugins registered for this folder (a new folder,
|
|
39
39
|
a template, a file request) beside a plain upload. -->
|
|
40
40
|
<div class="cn-files-browser__bar">
|
|
41
|
-
<NcBreadcrumbs class="cn-files-browser__crumbs">
|
|
41
|
+
<NcBreadcrumbs class="cn-files-browser__crumbs" :class="{ 'cn-files-browser__crumbs--at-root': atRoot }">
|
|
42
42
|
<!-- The whole trail from the user's files root, as the Files app
|
|
43
43
|
draws it. A crumb above the browser's root links into the Files
|
|
44
44
|
app (the browser never leaves its root); the root and everything
|
|
@@ -1471,6 +1471,19 @@ export default {
|
|
|
1471
1471
|
return crumbsFor(this.rootPath, this.currentPath, this.rootLabel)
|
|
1472
1472
|
},
|
|
1473
1473
|
|
|
1474
|
+
/**
|
|
1475
|
+
* Whether the open folder is the browser's root: the trail then ends on
|
|
1476
|
+
* the root itself (the board look draws no trail there).
|
|
1477
|
+
*
|
|
1478
|
+
* @return {boolean}
|
|
1479
|
+
* @spec openspec/changes/board-files-root-crumb/specs/board-files-root-crumb/spec.md#requirement-the-board-files-bar-draws-no-trail-at-the-root
|
|
1480
|
+
*/
|
|
1481
|
+
atRoot() {
|
|
1482
|
+
const root = String(this.rootPath || '/').replace(/\/+$/, '') || '/'
|
|
1483
|
+
const last = this.crumbs[this.crumbs.length - 1]
|
|
1484
|
+
return Boolean(last) && last.path === root
|
|
1485
|
+
},
|
|
1486
|
+
|
|
1474
1487
|
/**
|
|
1475
1488
|
* The children in the chosen order, folders first the way the Files app
|
|
1476
1489
|
* lists them.
|
|
@@ -2165,7 +2178,8 @@ export default {
|
|
|
2165
2178
|
const object = {
|
|
2166
2179
|
...entry,
|
|
2167
2180
|
name: node.basename,
|
|
2168
|
-
|
|
2181
|
+
// A template drops an object value, so the Date is handed over as ISO text.
|
|
2182
|
+
modified: node.mtime instanceof Date ? node.mtime.toISOString() : node.mtime,
|
|
2169
2183
|
size: this.formatSize(node.size),
|
|
2170
2184
|
type: this.typeLabelOf(node),
|
|
2171
2185
|
...(owner ? { owner } : {}),
|
|
@@ -2768,7 +2782,8 @@ export default {
|
|
|
2768
2782
|
}
|
|
2769
2783
|
})
|
|
2770
2784
|
.filter((field) => field.value !== null && field.value !== undefined && field.value !== ''),
|
|
2771
|
-
actions: (Array.isArray(card.actions) ? card.actions : [])
|
|
2785
|
+
actions: (Array.isArray(card.actions) ? card.actions : [])
|
|
2786
|
+
.filter((action) => action && action.id && hostActionApplies(action, node, this.rowValueOf(node))),
|
|
2772
2787
|
}))
|
|
2773
2788
|
},
|
|
2774
2789
|
|
|
@@ -3064,7 +3079,18 @@ export default {
|
|
|
3064
3079
|
* @spec openspec/changes/files-browser-hosts-a-documents-list/specs/files-browser/spec.md
|
|
3065
3080
|
*/
|
|
3066
3081
|
hostActionsFor(node) {
|
|
3067
|
-
return this.rowActions.filter((action) => hostActionApplies(action, node))
|
|
3082
|
+
return this.rowActions.filter((action) => hostActionApplies(action, node, this.rowValueOf(node)))
|
|
3083
|
+
},
|
|
3084
|
+
|
|
3085
|
+
/**
|
|
3086
|
+
* A reader of the host's row fields for one file, for `visibleIf.row`.
|
|
3087
|
+
*
|
|
3088
|
+
* @param {object} node The file.
|
|
3089
|
+
* @return {(key: string) => unknown} `(key) => value` over the row the host gave for the file.
|
|
3090
|
+
* @spec openspec/changes/files-browser-action-visible-if-row/specs/files-browser/spec.md#requirement-a-host-action-can-be-offered-by-a-field-of-the-files-row
|
|
3091
|
+
*/
|
|
3092
|
+
rowValueOf(node) {
|
|
3093
|
+
return (key) => fileColumnValue({ key, source: 'row' }, node, this.resolvedRowData)
|
|
3068
3094
|
},
|
|
3069
3095
|
|
|
3070
3096
|
/**
|
|
@@ -3623,6 +3649,19 @@ export default {
|
|
|
3623
3649
|
width: auto;
|
|
3624
3650
|
}
|
|
3625
3651
|
|
|
3652
|
+
/* The row button and the row menu stand side by side on one line, not
|
|
3653
|
+
stacked (DqZaakDocumenten: Beoordelen, then the menu, 14px apart).
|
|
3654
|
+
@spec openspec/changes/files-row-button-beside-menu/specs/files-row-button-beside-menu/spec.md#requirement-a-files-row-button-stands-beside-the-row-menu */
|
|
3655
|
+
.cn-files-browser__col-actions > .cn-files-browser__row-button,
|
|
3656
|
+
.cn-files-browser__col-actions > .cn-files-browser__row-menu {
|
|
3657
|
+
display: inline-flex;
|
|
3658
|
+
vertical-align: middle;
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
.cn-files-browser__col-actions > .cn-files-browser__row-button + .cn-files-browser__row-menu {
|
|
3662
|
+
margin-inline-start: 14px;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3626
3665
|
.cn-files-browser__col-name {
|
|
3627
3666
|
min-width: 0;
|
|
3628
3667
|
}
|
|
@@ -284,15 +284,19 @@ export function indexRowData(data, { path = '', key = 'fileId' } = {}) {
|
|
|
284
284
|
*
|
|
285
285
|
* `visibleIf.extension` lists file extensions (no case, a leading dot is
|
|
286
286
|
* allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
|
|
287
|
-
* `/*` names the whole family.
|
|
288
|
-
*
|
|
289
|
-
* `
|
|
287
|
+
* `/*` names the whole family; `visibleIf.row` maps fields of the host's row
|
|
288
|
+
* for the file to the value each must have (compared strictly, so `true` is
|
|
289
|
+
* not `"true"`). Every condition named must hold. An action without
|
|
290
|
+
* `visibleIf` is offered on every file, as before. An action with `when` is
|
|
291
|
+
* offered only while `when` is `true`.
|
|
290
292
|
*
|
|
291
293
|
* @param {object} action The declared action.
|
|
292
294
|
* @param {object} node The file (`basename`, `mime`).
|
|
295
|
+
* @param {((key: string) => unknown)|null} rowValue Reads a field of the host's row for the file; without it a `row` condition fails.
|
|
293
296
|
* @return {boolean} True when the action belongs on this file.
|
|
297
|
+
* @spec openspec/changes/files-browser-action-visible-if-row/specs/files-browser/spec.md#requirement-a-host-action-can-be-offered-by-a-field-of-the-files-row
|
|
294
298
|
*/
|
|
295
|
-
export function hostActionApplies(action, node) {
|
|
299
|
+
export function hostActionApplies(action, node, rowValue = null) {
|
|
296
300
|
// `when`: the action applies only while it is true (a case type flag).
|
|
297
301
|
// @spec openspec/changes/files-review-row-when/specs/files-review-row-when/spec.md#requirement-a-files-browser-entry-can-apply-only-when-a-condition-holds
|
|
298
302
|
if (action && typeof action === 'object' && 'when' in action && action.when !== true) {
|
|
@@ -324,5 +328,14 @@ export function hostActionApplies(action, node) {
|
|
|
324
328
|
return false
|
|
325
329
|
}
|
|
326
330
|
}
|
|
331
|
+
if (condition.row && typeof condition.row === 'object') {
|
|
332
|
+
if (typeof rowValue !== 'function') {
|
|
333
|
+
return false
|
|
334
|
+
}
|
|
335
|
+
const holds = Object.entries(condition.row).every(([key, wanted]) => rowValue(key) === wanted)
|
|
336
|
+
if (!holds) {
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
}
|
|
327
340
|
return true
|
|
328
341
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* The built-in columns a host may name by string, and what each one reads.
|
|
8
10
|
*
|
|
@@ -53,12 +55,26 @@ export function isBuiltInFileColumn(name) {
|
|
|
53
55
|
return typeof name === 'string' && Object.hasOwn(BUILT_IN_FILE_COLUMNS, name)
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Whether the app a column leans on is enabled: one app id, or a list of which
|
|
60
|
+
* any one will do. A column without `requiredApp` always applies.
|
|
61
|
+
*
|
|
62
|
+
* @param {(string|string[]|undefined)} requiredApp The app id(s).
|
|
63
|
+
* @return {boolean} True when the column applies.
|
|
64
|
+
* @spec openspec/changes/files-column-required-app/specs/files-column-required-app/spec.md#requirement-a-file-column-can-require-another-app
|
|
65
|
+
*/
|
|
66
|
+
function requiredAppPresent(requiredApp) {
|
|
67
|
+
const ids = (Array.isArray(requiredApp) ? requiredApp : [requiredApp]).filter((id) => typeof id === 'string' && id !== '')
|
|
68
|
+
return ids.length === 0 || ids.some((id) => isAppInstalled(id))
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
/**
|
|
57
72
|
* Resolve a host's `columns` declaration into column objects.
|
|
58
73
|
*
|
|
59
74
|
* A string names a built-in. An object declares its own: `key`, `label`,
|
|
60
75
|
* `source` (`node`, `attribute` or `row`), `attribute`, `formatter` and
|
|
61
|
-
* `sortable
|
|
76
|
+
* `sortable`, and `requiredApp` (an app id, or a list of which any one will
|
|
77
|
+
* do; without it the column is left out). An entry naming an unknown built-in is dropped here and refused
|
|
62
78
|
* by the manifest validator, so an author hears about it at authoring time
|
|
63
79
|
* rather than finding a column missing at runtime.
|
|
64
80
|
*
|
|
@@ -83,6 +99,9 @@ export function normaliseFileColumns(columns) {
|
|
|
83
99
|
if ('when' in entry && entry.when !== true) {
|
|
84
100
|
return
|
|
85
101
|
}
|
|
102
|
+
if (!requiredAppPresent(entry.requiredApp)) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
86
105
|
if (isBuiltInFileColumn(entry.key) && entry.source === undefined) {
|
|
87
106
|
out.push({ ...BUILT_IN_FILE_COLUMNS[entry.key], ...entry, builtIn: true })
|
|
88
107
|
return
|