@conduction/nextcloud-vue 2.115.0 → 2.116.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/CnActionButtons/CnActionButtons.vue.js +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +92 -0
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +2 -2
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js +9 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js +145 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js +58 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/index.js +28 -0
- package/dist/esm/components/CnChecklistWidget/index.js.map +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +376 -51
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +364 -285
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +6 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +48 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +11 -2
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue.js +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js +11 -3
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js +8 -3
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +55 -2
- 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/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +94 -4
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +248 -239
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +9 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +282 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +202 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +10 -5
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +64 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +85 -0
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +9 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js.map +1 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +107 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +110 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +19 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +23 -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 +1054 -911
- package/dist/esm/utils/actionsDispatcher.js +10 -5
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +6 -2
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +17078 -15224
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1054 -911
- package/l10n/en.json +19 -1
- package/l10n/nl.json +23 -1
- package/package.json +1 -1
- package/src/components/CnActionButtons/CnActionButtons.vue +93 -1
- package/src/components/CnChecklistWidget/CnChecklistWidget.vue +206 -0
- package/src/components/CnChecklistWidget/index.js +27 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +26 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +377 -10
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +15 -2
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +59 -1
- package/src/components/CnMacrosWidget/CnMacrosWidget.vue +16 -6
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +55 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +101 -3
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +508 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +10 -5
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +64 -0
- package/src/composables/useTaskInboxStore.js +85 -0
- package/src/dialogs/CnAccessPurposeDialog.vue +183 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +49 -3
- package/src/utils/actionsDispatcher.js +10 -5
- package/src/utils/detailActionModel.js +6 -2
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +1 -1
|
@@ -18,23 +18,24 @@ import Plus from 'vue-material-design-icons/Plus.vue';
|
|
|
18
18
|
import Refresh from 'vue-material-design-icons/Refresh.vue';
|
|
19
19
|
import Restore from 'vue-material-design-icons/Restore.vue';
|
|
20
20
|
import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue';
|
|
21
|
-
import __sfc_main
|
|
22
|
-
import __sfc_main$
|
|
23
|
-
import __sfc_main$
|
|
24
|
-
import __sfc_main$
|
|
25
|
-
import __sfc_main$
|
|
26
|
-
import __sfc_main$
|
|
27
|
-
import __sfc_main$
|
|
28
|
-
import __sfc_main$
|
|
29
|
-
import __sfc_main$
|
|
30
|
-
import __sfc_main$
|
|
31
|
-
import __sfc_main$
|
|
32
|
-
import __sfc_main$4 from '../
|
|
33
|
-
import __sfc_main$
|
|
34
|
-
import __sfc_main$
|
|
35
|
-
import __sfc_main$
|
|
36
|
-
import __sfc_main$
|
|
37
|
-
import __sfc_main$
|
|
21
|
+
import __sfc_main from '../../dialogs/CnAccessPurposeDialog.vue.js';
|
|
22
|
+
import __sfc_main$l from '../../dialogs/CnRelationLinkModal.vue.js';
|
|
23
|
+
import __sfc_main$k from '../../dialogs/CnWidgetStyleEditorModal.vue.js';
|
|
24
|
+
import __sfc_main$2 from '../CnActionsMenu/CnActionsMenu.vue.js';
|
|
25
|
+
import __sfc_main$i from '../CnBodySections/CnBodySections.vue.js';
|
|
26
|
+
import __sfc_main$3 from '../CnBuildiqEditButton/CnBuildiqEditButton.vue.js';
|
|
27
|
+
import __sfc_main$8 from '../CnDashboardGrid/CnDashboardGrid.vue.js';
|
|
28
|
+
import __sfc_main$6 from '../CnDetailWidgetHost/CnDetailWidgetHost.vue.js';
|
|
29
|
+
import __sfc_main$g from '../CnFollowToggle/CnFollowToggle.vue.js';
|
|
30
|
+
import __sfc_main$7 from '../CnFormDialog/CnFormDialog.vue.js';
|
|
31
|
+
import __sfc_main$a from '../CnLifecycleActions/CnLifecycleActions.vue.js';
|
|
32
|
+
import __sfc_main$4 from '../CnLockedBanner/CnLockedBanner.vue.js';
|
|
33
|
+
import __sfc_main$5 from '../CnNextStepCard/CnNextStepCard.vue.js';
|
|
34
|
+
import __sfc_main$h from '../CnRelatedCollections/CnRelatedCollections.vue.js';
|
|
35
|
+
import __sfc_main$9 from '../CnSegmentedControl/CnSegmentedControl.vue.js';
|
|
36
|
+
import __sfc_main$e from '../CnSummaryAggregates/CnSummaryAggregates.vue.js';
|
|
37
|
+
import __sfc_main$j from '../CnTranslatedBadge/CnTranslatedBadge.vue.js';
|
|
38
|
+
import __sfc_main$f from './CnDetailHeaderChips.vue.js';
|
|
38
39
|
import { resolveRecordSource } from '../../composables/indexSources.js';
|
|
39
40
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
|
|
40
41
|
import { normalizeLook } from '../../composables/useLook.js';
|
|
@@ -49,22 +50,23 @@ import { isAppInstalled } from '../../utils/appInstalled.js';
|
|
|
49
50
|
import { compactLayoutRows } from '../../utils/dashboardPlacement.js';
|
|
50
51
|
import { defaultDetailGrid } from '../../utils/defaultDetailGrid.js';
|
|
51
52
|
import { stageOf, normalisePinnedAction, stageEntry, MAX_QUICK_ACTIONS, groupMenuEntries, resolveNextStep, pillCountSource, resolveCountPill, resolvePill, pillLook } from '../../utils/detailActionModel.js';
|
|
52
|
-
import { visibilityContextRefs, widgetConditionData, fetchVisibilityRows
|
|
53
|
+
import { detailWidgetVisible, visibilityContextRefs, widgetConditionData, fetchVisibilityRows } from '../../utils/detailWidgetVisibility.js';
|
|
53
54
|
import { reportBindingProblems } from '../../utils/diagnostics.js';
|
|
54
55
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js';
|
|
55
56
|
import { firstFilled } from '../../utils/fillTemplate.js';
|
|
56
57
|
import { cnGridCellStyle, hasGridRow } from '../../utils/grid.js';
|
|
57
58
|
import { resolveHeaderMeta, isActivityWidget } from '../../utils/headerMeta.js';
|
|
59
|
+
import { prefixUrl } from '../../utils/headers.js';
|
|
58
60
|
import { patchStoredSelf } from '../../utils/patchStoredSelf.js';
|
|
59
61
|
import { setReadState } from '../../utils/recordInteractions.js';
|
|
60
62
|
import { saveEdited } from '../../utils/saveEdited.js';
|
|
61
63
|
import { slotRenders } from '../../utils/slotContent.js';
|
|
62
64
|
import { readVisibleWhenPath, isLocallyDecidableVisibleWhen, evaluateVisibleWhen } from '../../utils/visibleWhen.js';
|
|
63
65
|
import { isDataWidgetDef, isRelatedWidgetDef, isGeoWidgetDef, isContentOnlyWidgetDef, isCardWidgetDef, widgetContentOf, resolveRegistryRenderer, widgetTitleOf } from '../../utils/widgetDispatch.js';
|
|
64
|
-
import __sfc_main$
|
|
65
|
-
import __sfc_main$
|
|
66
|
-
import __sfc_main from '../CnIcon/CnIcon.vue.js';
|
|
67
|
-
import __sfc_main$
|
|
66
|
+
import __sfc_main$b from '../CnActionButtons/CnActionButtons.vue.js';
|
|
67
|
+
import __sfc_main$c from '../CnBreadcrumbs/CnBreadcrumbs.vue.js';
|
|
68
|
+
import __sfc_main$1 from '../CnIcon/CnIcon.vue.js';
|
|
69
|
+
import __sfc_main$d from '../CnStatusBadge/CnStatusBadge.vue.js';
|
|
68
70
|
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
|
|
69
71
|
import '../CnWidgetGrid/registerDashboardWidgets.js';
|
|
70
72
|
import { useObjectStore } from '../../store/useObjectStore.js';
|
|
@@ -140,6 +142,25 @@ const PAGE_REFRESH_CHANNEL = 'cn:page:refresh';
|
|
|
140
142
|
/** Surfaces understood by the pluggable integration registry (AD-19). */
|
|
141
143
|
const INTEGRATION_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
142
144
|
|
|
145
|
+
/** The session storage key that lists the records whose access purpose is recorded. */
|
|
146
|
+
const ACCESS_PURPOSE_SESSION_KEY = 'cn-access-purposes';
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The records whose access purpose this browser session recorded. Empty when
|
|
150
|
+
* session storage cannot be read (a private window, blocked site data).
|
|
151
|
+
*
|
|
152
|
+
* @return {Array<string>} The record keys.
|
|
153
|
+
*/
|
|
154
|
+
function readGrantedPurposes() {
|
|
155
|
+
try {
|
|
156
|
+
const raw = window.sessionStorage.getItem(ACCESS_PURPOSE_SESSION_KEY);
|
|
157
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
158
|
+
return Array.isArray(list) ? list.filter((key) => typeof key === 'string') : []
|
|
159
|
+
} catch {
|
|
160
|
+
return []
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
143
164
|
/**
|
|
144
165
|
* CnDetailPage — Generic detail/overview page.
|
|
145
166
|
*
|
|
@@ -216,6 +237,7 @@ var script = {
|
|
|
216
237
|
|
|
217
238
|
components: {
|
|
218
239
|
ChevronLeft,
|
|
240
|
+
CnAccessPurposeDialog: __sfc_main,
|
|
219
241
|
ChevronRight,
|
|
220
242
|
NcActionButton,
|
|
221
243
|
NcActionCaption,
|
|
@@ -224,35 +246,35 @@ var script = {
|
|
|
224
246
|
NcButton,
|
|
225
247
|
NcEmptyContent,
|
|
226
248
|
NcLoadingIcon,
|
|
227
|
-
CnIcon: __sfc_main,
|
|
249
|
+
CnIcon: __sfc_main$1,
|
|
228
250
|
AlertCircleOutline,
|
|
229
251
|
ArrowLeft,
|
|
230
252
|
FileQuestionOutline,
|
|
231
253
|
InformationOutline,
|
|
232
254
|
Refresh,
|
|
233
|
-
CnActionsMenu: __sfc_main$
|
|
234
|
-
CnBuildiqEditButton: __sfc_main$
|
|
235
|
-
CnLockedBanner: __sfc_main$
|
|
236
|
-
CnNextStepCard: __sfc_main$
|
|
237
|
-
CnDetailWidgetHost: __sfc_main$
|
|
238
|
-
CnFormDialog: __sfc_main$
|
|
239
|
-
CnDashboardGrid: __sfc_main$
|
|
240
|
-
CnSegmentedControl: __sfc_main$
|
|
255
|
+
CnActionsMenu: __sfc_main$2,
|
|
256
|
+
CnBuildiqEditButton: __sfc_main$3,
|
|
257
|
+
CnLockedBanner: __sfc_main$4,
|
|
258
|
+
CnNextStepCard: __sfc_main$5,
|
|
259
|
+
CnDetailWidgetHost: __sfc_main$6,
|
|
260
|
+
CnFormDialog: __sfc_main$7,
|
|
261
|
+
CnDashboardGrid: __sfc_main$8,
|
|
262
|
+
CnSegmentedControl: __sfc_main$9,
|
|
241
263
|
ViewDashboardOutline,
|
|
242
|
-
CnLifecycleActions: __sfc_main$
|
|
243
|
-
CnActionButtons: __sfc_main$
|
|
244
|
-
CnBreadcrumbs: __sfc_main$
|
|
245
|
-
CnStatusBadge: __sfc_main$
|
|
246
|
-
CnSummaryAggregates: __sfc_main$
|
|
247
|
-
CnDetailHeaderChips: __sfc_main$
|
|
248
|
-
CnFollowToggle: __sfc_main$
|
|
264
|
+
CnLifecycleActions: __sfc_main$a,
|
|
265
|
+
CnActionButtons: __sfc_main$b,
|
|
266
|
+
CnBreadcrumbs: __sfc_main$c,
|
|
267
|
+
CnStatusBadge: __sfc_main$d,
|
|
268
|
+
CnSummaryAggregates: __sfc_main$e,
|
|
269
|
+
CnDetailHeaderChips: __sfc_main$f,
|
|
270
|
+
CnFollowToggle: __sfc_main$g,
|
|
249
271
|
DockRight,
|
|
250
272
|
EyeOffOutline: EyeOffOutlineIcon,
|
|
251
|
-
CnRelatedCollections: __sfc_main$
|
|
252
|
-
CnBodySections: __sfc_main$
|
|
253
|
-
CnTranslatedBadge: __sfc_main$
|
|
254
|
-
CnWidgetStyleEditorModal: __sfc_main$
|
|
255
|
-
CnRelationLinkModal: __sfc_main$
|
|
273
|
+
CnRelatedCollections: __sfc_main$h,
|
|
274
|
+
CnBodySections: __sfc_main$i,
|
|
275
|
+
CnTranslatedBadge: __sfc_main$j,
|
|
276
|
+
CnWidgetStyleEditorModal: __sfc_main$k,
|
|
277
|
+
CnRelationLinkModal: __sfc_main$l,
|
|
256
278
|
Check,
|
|
257
279
|
Cog,
|
|
258
280
|
Plus,
|
|
@@ -313,6 +335,7 @@ var script = {
|
|
|
313
335
|
const isBoard = computed(() => this.isBoard);
|
|
314
336
|
const tabsLabel = computed(() => this.resolvedTabsLabel);
|
|
315
337
|
const visibilityRows = computed(() => (this.visibilityRowsPending ? null : this.visibilityRows));
|
|
338
|
+
const canCheckItems = computed(() => this.recordCanCheckItems);
|
|
316
339
|
return {
|
|
317
340
|
// The page's own look reaches the tabs, the cards and the activity
|
|
318
341
|
// below it, also when it came from the `look` prop and not the app.
|
|
@@ -323,6 +346,15 @@ var script = {
|
|
|
323
346
|
},
|
|
324
347
|
},
|
|
325
348
|
|
|
349
|
+
// The record source's checklist writes, for a `checklist` widget.
|
|
350
|
+
cnRecordChecklist: {
|
|
351
|
+
get canCheck() {
|
|
352
|
+
return canCheckItems.value
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
check: (itemId, checked) => this.checkSourceItem(itemId, checked),
|
|
356
|
+
},
|
|
357
|
+
|
|
326
358
|
// The open body tab, reported by a `tabs` widget, so a side entry
|
|
327
359
|
// with `tabs` shows only beside those tabs.
|
|
328
360
|
cnDetailReportTab: (id) => {
|
|
@@ -720,6 +752,20 @@ var script = {
|
|
|
720
752
|
default: '',
|
|
721
753
|
},
|
|
722
754
|
|
|
755
|
+
/**
|
|
756
|
+
* App routes that replace a record source's own route for a verb, by
|
|
757
|
+
* verb id (manifest `config.verbEndpoints`), such as
|
|
758
|
+
* `{ "complete": "/apps/dossiq/api/case-tasks/{id}/complete" }` for an
|
|
759
|
+
* app that checks something of its own before the engine completes the
|
|
760
|
+
* task. `{id}` is the record id. Only read with `entitySource`.
|
|
761
|
+
*
|
|
762
|
+
* @type {object|null}
|
|
763
|
+
*/
|
|
764
|
+
verbEndpoints: {
|
|
765
|
+
type: Object,
|
|
766
|
+
default: null,
|
|
767
|
+
},
|
|
768
|
+
|
|
723
769
|
/**
|
|
724
770
|
* OpenRegister register slug. Pair with `schema` to opt into the
|
|
725
771
|
* schema-driven mode: the page fuses the two into an internal
|
|
@@ -1283,9 +1329,11 @@ var script = {
|
|
|
1283
1329
|
* shows nothing at 0. With `resolve: { register, schema, labelField?,
|
|
1284
1330
|
* colorField?, iconField? }` the pill shows the referenced record's
|
|
1285
1331
|
* label, takes its tone from that record's colour (an NL Design System
|
|
1286
|
-
* hue such as `red`, or a badge role) and shows its icon.
|
|
1332
|
+
* hue such as `red`, or a badge role) and shows its icon. `values`
|
|
1333
|
+
* draws a `field` pill only for those raw values (a priority pill for
|
|
1334
|
+
* `high` and `urgent` only).
|
|
1287
1335
|
*
|
|
1288
|
-
* @type {{field?: string, text?: string, countFrom?: object, countLabel?: object, colorMap?: object, labels?: object, variant?: string, resolve?: object}|null}
|
|
1336
|
+
* @type {{field?: string, text?: string, countFrom?: object, countLabel?: object, colorMap?: object, labels?: object, variant?: string, resolve?: object, values?: Array<string>}|null}
|
|
1289
1337
|
*/
|
|
1290
1338
|
typePill: {
|
|
1291
1339
|
type: Object,
|
|
@@ -1463,6 +1511,28 @@ var script = {
|
|
|
1463
1511
|
default: () => ({}),
|
|
1464
1512
|
},
|
|
1465
1513
|
|
|
1514
|
+
/**
|
|
1515
|
+
* Ask why the reader opens the record before the page shows it, and
|
|
1516
|
+
* record the answer (manifest `config.accessPurpose`). `{ when, options,
|
|
1517
|
+
* url, title?, description? }`: `when` is a condition in the
|
|
1518
|
+
* `visibleWhen` grammar on the record and its `visibilityContext`
|
|
1519
|
+
* records (DqSociaalDomein: the case type lists special categories of
|
|
1520
|
+
* personal data); `options` the purposes offered (`{ value, label }`,
|
|
1521
|
+
* "Other" with a written purpose is always added); `url` the app
|
|
1522
|
+
* endpoint that records the purpose (POST `{ purpose, note }`, `{objectId}`
|
|
1523
|
+
* filled in). The page shows nothing of the record until the endpoint
|
|
1524
|
+
* accepts, and does not ask again for the same record in the same
|
|
1525
|
+
* browser session. A refusal keeps the record closed. `null` (the
|
|
1526
|
+
* default) asks nothing.
|
|
1527
|
+
*
|
|
1528
|
+
* @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens
|
|
1529
|
+
* @type {object|null}
|
|
1530
|
+
*/
|
|
1531
|
+
accessPurpose: {
|
|
1532
|
+
type: Object,
|
|
1533
|
+
default: null,
|
|
1534
|
+
},
|
|
1535
|
+
|
|
1466
1536
|
/**
|
|
1467
1537
|
* Whether the current user administers this instance. Shows the
|
|
1468
1538
|
* `adminOnly` header actions. `null` (the default) reads it from
|
|
@@ -1705,6 +1775,14 @@ var script = {
|
|
|
1705
1775
|
|
|
1706
1776
|
data() {
|
|
1707
1777
|
return {
|
|
1778
|
+
/** The session keys of the records whose access purpose is recorded. */
|
|
1779
|
+
accessPurposeGranted: readGrantedPurposes(),
|
|
1780
|
+
/** Whether the purpose dialog is open; closing it without a purpose keeps the record closed. */
|
|
1781
|
+
accessPurposeAsking: true,
|
|
1782
|
+
/** Whether a purpose is being recorded. */
|
|
1783
|
+
accessPurposeBusy: false,
|
|
1784
|
+
/** Why the last purpose was refused. */
|
|
1785
|
+
accessPurposeError: '',
|
|
1708
1786
|
/** The id of the open body tab a `tabs` widget reported, '' before one did. */
|
|
1709
1787
|
activeBodyTab: '',
|
|
1710
1788
|
/**
|
|
@@ -1755,6 +1833,8 @@ var script = {
|
|
|
1755
1833
|
* menu dispatches without reaching back into that component.
|
|
1756
1834
|
*/
|
|
1757
1835
|
menuHeaderActions: [],
|
|
1836
|
+
/** Whether a record-source verb is in flight, so a double click cannot run it twice. */
|
|
1837
|
+
sourceVerbBusy: false,
|
|
1758
1838
|
/** Lifecycle transitions CnLifecycleActions hands the Actions menu. */
|
|
1759
1839
|
lifecycleMenuEntries: [],
|
|
1760
1840
|
/** Whether the per-widget style/config editor modal is open. */
|
|
@@ -2206,7 +2286,64 @@ var script = {
|
|
|
2206
2286
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
2207
2287
|
*/
|
|
2208
2288
|
showsBody() {
|
|
2209
|
-
return !this.objectNotFound && !this.showLoadingState && !this.error && !this.empty
|
|
2289
|
+
return !this.objectNotFound && !this.contentWithheld && !this.showLoadingState && !this.error && !this.empty
|
|
2290
|
+
},
|
|
2291
|
+
|
|
2292
|
+
/**
|
|
2293
|
+
* Whether the loaded record asks for a purpose before it opens
|
|
2294
|
+
* (`accessPurpose.when` holds for it). Undecided, and so withheld,
|
|
2295
|
+
* while the record or the records its condition reads are on their way.
|
|
2296
|
+
*
|
|
2297
|
+
* @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens
|
|
2298
|
+
* @return {boolean}
|
|
2299
|
+
*/
|
|
2300
|
+
accessPurposeRequired() {
|
|
2301
|
+
const gate = this.accessPurpose;
|
|
2302
|
+
if (!gate || typeof gate !== 'object' || !gate.when || !this.widgetVisibilityData || this.visibilityRowsPending) {
|
|
2303
|
+
return false
|
|
2304
|
+
}
|
|
2305
|
+
return detailWidgetVisible({ visibleWhen: gate.when }, this.widgetVisibilityData)
|
|
2306
|
+
},
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Whether the page holds the record back: it asks for a purpose and
|
|
2310
|
+
* none is recorded for this record in this session yet, or it cannot
|
|
2311
|
+
* tell yet whether it asks.
|
|
2312
|
+
*
|
|
2313
|
+
* @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens
|
|
2314
|
+
* @return {boolean}
|
|
2315
|
+
*/
|
|
2316
|
+
contentWithheld() {
|
|
2317
|
+
const gate = this.accessPurpose;
|
|
2318
|
+
if (!gate || typeof gate !== 'object' || !gate.when || this.objectNotFound) {
|
|
2319
|
+
return false
|
|
2320
|
+
}
|
|
2321
|
+
if (this.accessPurposeGranted.includes(this.accessPurposeKey)) {
|
|
2322
|
+
return false
|
|
2323
|
+
}
|
|
2324
|
+
const undecided = !this.widgetVisibilityData || this.visibilityRowsPending;
|
|
2325
|
+
return undecided || this.accessPurposeRequired
|
|
2326
|
+
},
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* The session key of the purpose recorded for this record.
|
|
2330
|
+
*
|
|
2331
|
+
* @return {string}
|
|
2332
|
+
*/
|
|
2333
|
+
accessPurposeKey() {
|
|
2334
|
+
return `cn-access-purpose:${this.resolvedObjectType || ''}:${this.objectId || ''}`
|
|
2335
|
+
},
|
|
2336
|
+
|
|
2337
|
+
/**
|
|
2338
|
+
* The purposes offered, through the app's translate.
|
|
2339
|
+
*
|
|
2340
|
+
* @return {Array<{value: string, label: string}>}
|
|
2341
|
+
*/
|
|
2342
|
+
accessPurposeOptions() {
|
|
2343
|
+
const options = Array.isArray(this.accessPurpose?.options) ? this.accessPurpose.options : [];
|
|
2344
|
+
return options
|
|
2345
|
+
.filter((option) => option && typeof option.value === 'string' && option.value !== '')
|
|
2346
|
+
.map((option) => ({ value: option.value, label: this.cnTranslate(option.label || option.value) }))
|
|
2210
2347
|
},
|
|
2211
2348
|
|
|
2212
2349
|
/**
|
|
@@ -2695,7 +2832,70 @@ var script = {
|
|
|
2695
2832
|
const inMenu = pinned.size > 0
|
|
2696
2833
|
? this.menuHeaderActions.filter((entry) => !pinned.has(entry.id))
|
|
2697
2834
|
: this.menuHeaderActions;
|
|
2698
|
-
return [...lifecycle, ...inMenu]
|
|
2835
|
+
return [...this.sourceVerbMenuEntries, ...lifecycle, ...inMenu]
|
|
2836
|
+
},
|
|
2837
|
+
|
|
2838
|
+
/**
|
|
2839
|
+
* The verbs the record source offers on the record in view, read off
|
|
2840
|
+
* the record's own state by the source. Empty without a source that
|
|
2841
|
+
* offers verbs, before the record is read, and when it was not found.
|
|
2842
|
+
*
|
|
2843
|
+
* @return {Array<object>} The verbs.
|
|
2844
|
+
* @spec openspec/changes/record-source-verbs/specs/record-source-verbs/spec.md#requirement-a-record-source-offers-header-verbs
|
|
2845
|
+
*/
|
|
2846
|
+
sourceVerbs() {
|
|
2847
|
+
const source = this.recordSource;
|
|
2848
|
+
if (!source || typeof source.verbs !== 'function' || typeof source.runVerb !== 'function') {
|
|
2849
|
+
return []
|
|
2850
|
+
}
|
|
2851
|
+
if (this.objectNotFound || !this.currentObject) {
|
|
2852
|
+
return []
|
|
2853
|
+
}
|
|
2854
|
+
const verbs = source.verbs(this.currentObject);
|
|
2855
|
+
return Array.isArray(verbs) ? verbs.filter((verb) => verb && typeof verb.id === 'string' && verb.id !== '') : []
|
|
2856
|
+
},
|
|
2857
|
+
|
|
2858
|
+
/**
|
|
2859
|
+
* Whether the record source can tick the checklist of the record in
|
|
2860
|
+
* view: it writes items, and says this record can still be ticked.
|
|
2861
|
+
*
|
|
2862
|
+
* @return {boolean}
|
|
2863
|
+
* @spec openspec/changes/record-checklist-widget/specs/record-checklist-widget/spec.md#requirement-a-checklist-widget-ticks-items-through-the-record-source
|
|
2864
|
+
*/
|
|
2865
|
+
recordCanCheckItems() {
|
|
2866
|
+
const source = this.recordSource;
|
|
2867
|
+
if (!source || typeof source.checkItem !== 'function' || !this.currentObject || this.objectNotFound) {
|
|
2868
|
+
return false
|
|
2869
|
+
}
|
|
2870
|
+
return typeof source.canCheckItems !== 'function' || source.canCheckItems(this.currentObject) === true
|
|
2871
|
+
},
|
|
2872
|
+
|
|
2873
|
+
/**
|
|
2874
|
+
* The source verbs drawn as header buttons.
|
|
2875
|
+
*
|
|
2876
|
+
* @return {Array<object>} The verbs.
|
|
2877
|
+
*/
|
|
2878
|
+
sourceVerbButtons() {
|
|
2879
|
+
return this.sourceVerbs.filter((verb) => verb.menu !== true)
|
|
2880
|
+
},
|
|
2881
|
+
|
|
2882
|
+
/**
|
|
2883
|
+
* The source verbs listed in the Actions menu, as menu entries.
|
|
2884
|
+
*
|
|
2885
|
+
* @return {Array<object>} Menu-ready entries.
|
|
2886
|
+
*/
|
|
2887
|
+
sourceVerbMenuEntries() {
|
|
2888
|
+
return this.sourceVerbs
|
|
2889
|
+
.filter((verb) => verb.menu === true)
|
|
2890
|
+
.map((verb) => ({
|
|
2891
|
+
id: `cn-source-verb-${verb.id}`,
|
|
2892
|
+
label: verb.label,
|
|
2893
|
+
testid: `cn-detail-page-verb-${verb.id}`,
|
|
2894
|
+
iconName: typeof verb.icon === 'string' ? verb.icon : '',
|
|
2895
|
+
disabled: this.sourceVerbBusy,
|
|
2896
|
+
pressed: null,
|
|
2897
|
+
run: () => this.runSourceVerb(verb),
|
|
2898
|
+
}))
|
|
2699
2899
|
},
|
|
2700
2900
|
|
|
2701
2901
|
/**
|
|
@@ -3685,6 +3885,16 @@ var script = {
|
|
|
3685
3885
|
this.syncSidebarState();
|
|
3686
3886
|
},
|
|
3687
3887
|
|
|
3888
|
+
contentWithheld() {
|
|
3889
|
+
this.syncSidebarState();
|
|
3890
|
+
},
|
|
3891
|
+
|
|
3892
|
+
/** Another record asks its own purpose: the dialog opens again for it. */
|
|
3893
|
+
accessPurposeKey() {
|
|
3894
|
+
this.accessPurposeAsking = true;
|
|
3895
|
+
this.accessPurposeError = '';
|
|
3896
|
+
},
|
|
3897
|
+
|
|
3688
3898
|
// Deep, like `sidebarTabs` / `sidebarProps` below: the manifest editors
|
|
3689
3899
|
// mutate `page.config.sidebar` IN PLACE, so the reference CnPageRenderer
|
|
3690
3900
|
// forwards never changes. A shallow watcher missed every such edit —
|
|
@@ -3861,6 +4071,52 @@ var script = {
|
|
|
3861
4071
|
},
|
|
3862
4072
|
|
|
3863
4073
|
methods: {
|
|
4074
|
+
/**
|
|
4075
|
+
* A text of the access purpose gate through the app's translate, '' when unset.
|
|
4076
|
+
*
|
|
4077
|
+
* @param {string} key `title` or `description`.
|
|
4078
|
+
* @return {string}
|
|
4079
|
+
*/
|
|
4080
|
+
accessPurposeText(key) {
|
|
4081
|
+
const text = this.accessPurpose && this.accessPurpose[key];
|
|
4082
|
+
return typeof text === 'string' && text !== '' ? this.cnTranslate(text) : ''
|
|
4083
|
+
},
|
|
4084
|
+
|
|
4085
|
+
/**
|
|
4086
|
+
* Record the reader's purpose with the app's endpoint, then open the
|
|
4087
|
+
* record and remember it for this browser session. A refusal (or a
|
|
4088
|
+
* failure to record) keeps the record closed and says why.
|
|
4089
|
+
*
|
|
4090
|
+
* @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens
|
|
4091
|
+
* @param {{purpose: string, note: string}} answer The chosen purpose.
|
|
4092
|
+
* @return {Promise<void>}
|
|
4093
|
+
*/
|
|
4094
|
+
async recordAccessPurpose(answer) {
|
|
4095
|
+
const url = String(this.accessPurpose?.url || '').replace(/\{objectId\}/g, encodeURIComponent(String(this.objectId || '')));
|
|
4096
|
+
if (url === '') {
|
|
4097
|
+
this.accessPurposeError = translate('nextcloud-vue', 'This page cannot record a purpose, so the record stays closed.');
|
|
4098
|
+
return
|
|
4099
|
+
}
|
|
4100
|
+
this.accessPurposeBusy = true;
|
|
4101
|
+
this.accessPurposeError = '';
|
|
4102
|
+
try {
|
|
4103
|
+
const { default: axios } = await import('@nextcloud/axios');
|
|
4104
|
+
await axios.post(prefixUrl(url), { purpose: answer.purpose, note: answer.note });
|
|
4105
|
+
const key = this.accessPurposeKey;
|
|
4106
|
+
this.accessPurposeGranted = [...new Set([...this.accessPurposeGranted, key])];
|
|
4107
|
+
try {
|
|
4108
|
+
window.sessionStorage.setItem(ACCESS_PURPOSE_SESSION_KEY, JSON.stringify(this.accessPurposeGranted));
|
|
4109
|
+
} catch {
|
|
4110
|
+
// Without session storage the purpose is asked again on the next visit.
|
|
4111
|
+
}
|
|
4112
|
+
} catch (error) {
|
|
4113
|
+
const data = error?.response?.data;
|
|
4114
|
+
this.accessPurposeError = (data && (data.message || data.error)) || translate('nextcloud-vue', 'Your purpose could not be recorded, so the record stays closed.');
|
|
4115
|
+
} finally {
|
|
4116
|
+
this.accessPurposeBusy = false;
|
|
4117
|
+
}
|
|
4118
|
+
},
|
|
4119
|
+
|
|
3864
4120
|
/**
|
|
3865
4121
|
* The widget definitions with each `tabs` widget's tabs narrowed and
|
|
3866
4122
|
* ordered by `pickedTabIds`; the definitions as given without a pick.
|
|
@@ -4318,6 +4574,65 @@ var script = {
|
|
|
4318
4574
|
this.$emit('transitioned', payload);
|
|
4319
4575
|
},
|
|
4320
4576
|
|
|
4577
|
+
/**
|
|
4578
|
+
* Tick or untick one checklist item through the record source. The
|
|
4579
|
+
* source replaces its record with the answer; a refusal is shown with
|
|
4580
|
+
* the server's message and leaves the item as it was.
|
|
4581
|
+
*
|
|
4582
|
+
* @param {string} itemId The item id.
|
|
4583
|
+
* @param {boolean} checked The new state.
|
|
4584
|
+
* @return {Promise<boolean>} Whether the write was taken.
|
|
4585
|
+
* @spec openspec/changes/record-checklist-widget/specs/record-checklist-widget/spec.md#requirement-a-checklist-widget-ticks-items-through-the-record-source
|
|
4586
|
+
*/
|
|
4587
|
+
async checkSourceItem(itemId, checked) {
|
|
4588
|
+
const source = this.recordSource;
|
|
4589
|
+
const objectId = this.objectId;
|
|
4590
|
+
if (!this.recordCanCheckItems || !objectId) {
|
|
4591
|
+
return false
|
|
4592
|
+
}
|
|
4593
|
+
const outcome = await source.checkItem(String(objectId), String(itemId), checked === true);
|
|
4594
|
+
if (!outcome || outcome.ok !== true) {
|
|
4595
|
+
const { showError } = await import('../../node_modules/@nextcloud/dialogs/dist/index.js');
|
|
4596
|
+
showError(String(outcome?.message || '').trim() || translate('nextcloud-vue', 'This could not be done. Try again.'));
|
|
4597
|
+
return false
|
|
4598
|
+
}
|
|
4599
|
+
return true
|
|
4600
|
+
},
|
|
4601
|
+
|
|
4602
|
+
/**
|
|
4603
|
+
* Run a record-source verb on the record in view. The source keeps its
|
|
4604
|
+
* record in step with the answer, so the header, pills and widgets
|
|
4605
|
+
* redraw from it. A refusal is shown with the server's own message,
|
|
4606
|
+
* which names the verb and the reason.
|
|
4607
|
+
*
|
|
4608
|
+
* @param {{id: string, label: string, successMessage?: string}} verb The verb.
|
|
4609
|
+
* @return {Promise<void>}
|
|
4610
|
+
* @spec openspec/changes/record-source-verbs/specs/record-source-verbs/spec.md#requirement-a-record-source-offers-header-verbs
|
|
4611
|
+
*/
|
|
4612
|
+
async runSourceVerb(verb) {
|
|
4613
|
+
const source = this.recordSource;
|
|
4614
|
+
const objectId = this.objectId;
|
|
4615
|
+
if (!source || !objectId || this.sourceVerbBusy) {
|
|
4616
|
+
return
|
|
4617
|
+
}
|
|
4618
|
+
this.sourceVerbBusy = true;
|
|
4619
|
+
try {
|
|
4620
|
+
const url = this.verbEndpoints && typeof this.verbEndpoints[verb.id] === 'string' ? this.verbEndpoints[verb.id] : undefined;
|
|
4621
|
+
const outcome = await source.runVerb(String(objectId), verb.id, url ? { url } : {});
|
|
4622
|
+
const { showError, showSuccess } = await import('../../node_modules/@nextcloud/dialogs/dist/index.js');
|
|
4623
|
+
if (!outcome || outcome.ok !== true) {
|
|
4624
|
+
showError(String(outcome?.message || '').trim() || translate('nextcloud-vue', 'This could not be done. Try again.'));
|
|
4625
|
+
return
|
|
4626
|
+
}
|
|
4627
|
+
if (verb.successMessage) {
|
|
4628
|
+
showSuccess(verb.successMessage);
|
|
4629
|
+
}
|
|
4630
|
+
this.$emit('transitioned', { action: verb.id, to: String(outcome.record?.state ?? ''), object: outcome.record || null });
|
|
4631
|
+
} finally {
|
|
4632
|
+
this.sourceVerbBusy = false;
|
|
4633
|
+
}
|
|
4634
|
+
},
|
|
4635
|
+
|
|
4321
4636
|
/**
|
|
4322
4637
|
* Re-fetch the page's object after a lifecycle transition so the new state
|
|
4323
4638
|
* (and any other server-side mutations the guard applied) render.
|
|
@@ -5094,15 +5409,24 @@ var script = {
|
|
|
5094
5409
|
|
|
5095
5410
|
/**
|
|
5096
5411
|
* Whether a side entry belongs beside the open body tab: an entry with
|
|
5097
|
-
* `tabs` (tab ids) shows only while one of them is open; an entry
|
|
5098
|
-
*
|
|
5412
|
+
* `tabs` (tab ids) shows only while one of them is open; an entry with
|
|
5413
|
+
* `notTabs` stands aside while one of those is open (a tab that draws
|
|
5414
|
+
* the side column as its own, DqZaakAssistent); an entry with neither
|
|
5415
|
+
* shows beside every tab. Edit mode shows them all.
|
|
5099
5416
|
*
|
|
5100
5417
|
* @spec openspec/changes/detail-side-cards-per-tab/specs/detail-page/spec.md#requirement-a-side-card-can-follow-the-open-tab
|
|
5418
|
+
* @spec openspec/changes/side-card-not-on-tabs/specs/detail-page/spec.md#requirement-a-side-card-can-stand-aside-for-a-tab
|
|
5101
5419
|
* @param {object} widget The side entry's definition.
|
|
5102
5420
|
* @return {boolean}
|
|
5103
5421
|
*/
|
|
5104
5422
|
sideWidgetOnTab(widget) {
|
|
5105
|
-
if (this.editingBody
|
|
5423
|
+
if (this.editingBody) {
|
|
5424
|
+
return true
|
|
5425
|
+
}
|
|
5426
|
+
if (Array.isArray(widget.notTabs) && widget.notTabs.map((id) => String(id)).includes(this.activeBodyTab)) {
|
|
5427
|
+
return false
|
|
5428
|
+
}
|
|
5429
|
+
if (!Array.isArray(widget.tabs) || widget.tabs.length === 0) {
|
|
5106
5430
|
return true
|
|
5107
5431
|
}
|
|
5108
5432
|
return widget.tabs.map((id) => String(id)).includes(this.activeBodyTab)
|
|
@@ -5538,7 +5862,8 @@ var script = {
|
|
|
5538
5862
|
return
|
|
5539
5863
|
}
|
|
5540
5864
|
const r = this.resolvedSidebar;
|
|
5541
|
-
|
|
5865
|
+
// A record held back for its access purpose shows nothing in the sidebar either.
|
|
5866
|
+
const hasRecord = !this.objectNotFound && !this.awaitingObject && !this.contentWithheld;
|
|
5542
5867
|
if (this.sidebarActive && hasRecord && this.resolvedObjectType && this.objectId) {
|
|
5543
5868
|
const merged = this.mergeSidebarSources(r);
|
|
5544
5869
|
// Seed `open` only on the inactive→active edge (first activation
|