@conduction/nextcloud-vue 2.119.0 → 2.121.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/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js +9 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js +226 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js +211 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js +9 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js +64 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js +89 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js.map +1 -0
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +53 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +546 -523
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +9 -0
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js +9 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js.map +1 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js +257 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js.map +1 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js +266 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js +14 -7
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js +9 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js +278 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js +128 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js +9 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js +214 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js +125 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js +9 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js +163 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js +131 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js +127 -0
- package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js +9 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js +82 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js +61 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js +60 -0
- package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js +10 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js +226 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js +126 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js +10 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js +944 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js +276 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js +354 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js +392 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js +180 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js +208 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js +244 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js +76 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js +290 -0
- package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +9 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +448 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +303 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +19 -2
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +73 -62
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +6 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +42 -32
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +116 -16
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +207 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +207 -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 +951 -16
- package/dist/esm/utils/aiFeatures.js +245 -0
- package/dist/esm/utils/aiFeatures.js.map +1 -0
- package/dist/esm/utils/aiSuggestionTracker.js +182 -0
- package/dist/esm/utils/aiSuggestionTracker.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectPermissions.js +552 -0
- package/dist/esm/utils/objectPermissions.js.map +1 -0
- package/dist/esm/utils/objectTranslations.js +180 -0
- package/dist/esm/utils/objectTranslations.js.map +1 -0
- package/dist/esm/utils/stateMachineValidation.js +254 -0
- package/dist/esm/utils/stateMachineValidation.js.map +1 -0
- package/dist/esm/utils/widgetDispatch.js +3 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +23038 -14567
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +951 -16
- package/l10n/en.json +207 -1
- package/l10n/nl.json +207 -1
- package/package.json +1 -1
- package/src/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue +416 -0
- package/src/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue +136 -0
- package/src/components/CnAiFeaturesWidget/__tests__/CnAiFeaturesWidget.spec.js +213 -0
- package/src/components/CnAiFeaturesWidget/index.js +14 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +74 -3
- package/src/components/CnIndexPage/useNamedSource.js +9 -0
- package/src/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue +499 -0
- package/src/components/CnObjectPermissionsTab/__tests__/CnObjectPermissionsTab.spec.js +186 -0
- package/src/components/CnObjectPermissionsTab/index.js +13 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +8 -1
- package/src/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue +424 -0
- package/src/components/CnObjectTranslationsWidget/__tests__/CnObjectTranslationsWidget.spec.js +189 -0
- package/src/components/CnObjectTranslationsWidget/index.js +13 -0
- package/src/components/CnReadinessChecklist/CnReadinessChecklist.vue +335 -0
- package/src/components/CnReadinessChecklist/index.js +9 -0
- package/src/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue +300 -0
- package/src/components/CnSchemaRulesWidget/index.js +10 -0
- package/src/components/CnSchemaRulesWidget/schemaRules.js +123 -0
- package/src/components/CnSearchIndexStatus/CnSearchIndexStatus.vue +160 -0
- package/src/components/CnSearchIndexStatus/index.js +10 -0
- package/src/components/CnSearchIndexStatus/searchIndexStatus.js +56 -0
- package/src/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue +328 -0
- package/src/components/CnShippedConfigurationSection/index.js +9 -0
- package/src/components/CnStateMachineEditor/CnStateMachineEditor.vue +1332 -0
- package/src/components/CnStateMachineEditor/CnStateMachineFields.vue +791 -0
- package/src/components/CnStateMachineEditor/CnStateMachineNode.vue +442 -0
- package/src/components/CnStateMachineEditor/CnStateMachinePanel.vue +326 -0
- package/src/components/CnStateMachineEditor/index.js +8 -0
- package/src/components/CnStateMachineEditor/stateMachineFields.js +287 -0
- package/src/components/CnTimelineWidget/CnTimelineManage.vue +827 -0
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +29 -3
- package/src/components/CnWidgetGrid/builtInWidgets.js +6 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +10 -0
- package/src/components/index.js +8 -0
- package/src/composables/indexSources.js +116 -16
- package/src/css/index-page.css +7 -0
- package/src/index.js +9 -0
- package/src/utils/aiFeatures.js +241 -0
- package/src/utils/aiSuggestionTracker.js +178 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/objectPermissions.js +548 -0
- package/src/utils/objectTranslations.js +176 -0
- package/src/utils/stateMachineValidation.js +250 -0
- package/src/utils/widgetDispatch.js +3 -1
|
@@ -60,6 +60,9 @@ import { searchFieldParams } from '../../utils/searchFieldParams.js';
|
|
|
60
60
|
* chosen values, `{ fieldKey: values }`. Mapped through the source's
|
|
61
61
|
* `searchFields` declaration into loader arguments and merged into every
|
|
62
62
|
* load, so a manifest page's sidebar narrows the SERVER's query.
|
|
63
|
+
* @param {import('vue').Ref<string>} [options.searchTerm] The page's search
|
|
64
|
+
* term. A source that names a `searchParam` gets it as that argument, so the
|
|
65
|
+
* server searches; a new term reloads from the first page.
|
|
63
66
|
*
|
|
64
67
|
* @return {object} { isNamedSource, namedSource, namedRows, namedLoading, namedQuickFilters }
|
|
65
68
|
*/
|
|
@@ -107,6 +110,7 @@ function useNamedSource(props, options = {}) {
|
|
|
107
110
|
const tabs = manifestTabs || sourceTabs;
|
|
108
111
|
const activeIndex = options.activeQuickFilterIndex || null;
|
|
109
112
|
const activeFilters = options.activeFilters || null;
|
|
113
|
+
const searchTerm = (typeof source.searchParam === 'string' && source.searchParam !== '') ? (options.searchTerm || null) : null;
|
|
110
114
|
// A source that pages (`pages: true`) gets the page as `limit` and
|
|
111
115
|
// `offset`, from the pager the page draws for it.
|
|
112
116
|
const paging = source.pages === true ? (options.paging || null) : null;
|
|
@@ -170,6 +174,7 @@ function useNamedSource(props, options = {}) {
|
|
|
170
174
|
activeFilters ? activeFilters.value : null,
|
|
171
175
|
props.entitySource,
|
|
172
176
|
),
|
|
177
|
+
...(searchTerm && searchTerm.value ? { [source.searchParam]: searchTerm.value } : {}),
|
|
173
178
|
...(paging ? { limit: paging.value.limit, offset: (paging.value.page - 1) * paging.value.limit } : {}),
|
|
174
179
|
},
|
|
175
180
|
tokenCtx(),
|
|
@@ -219,6 +224,10 @@ function useNamedSource(props, options = {}) {
|
|
|
219
224
|
watch(activeFilters, reloadFromFirstPage, { deep: true });
|
|
220
225
|
}
|
|
221
226
|
|
|
227
|
+
if (searchTerm) {
|
|
228
|
+
watch(searchTerm, reloadFromFirstPage);
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
// A new page, or a new page size, is a new request. A new tab or filter
|
|
223
232
|
// first returns to page 1, which reloads through here.
|
|
224
233
|
if (paging) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNamedSource.js","sources":["../../../../src/components/CnIndexPage/useNamedSource.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n */\n\nimport { subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { computed, inject, onMounted, onUnmounted, watch } from 'vue'\nimport { resolveIndexSource } from '../../composables/indexSources.js'\nimport { withPersonalLenses } from '../../utils/personalLenses.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { searchFieldParams } from '../../utils/searchFieldParams.js'\n\n/**\n * Third data mode for CnIndexPage: a NAMED source.\n *\n * The page already knew two ways to get rows — fetch OpenRegister objects from\n * `register` + `schema`, or accept them as `:objects`. Neither reaches a list\n * that is not an OpenRegister object and not already in a parent's hands, so\n * those lists became `type: \"custom\"` pages whose only real job was loading\n * rows from somewhere else.\n *\n * `source` closes that: the manifest names a source and the index loads it.\n *\n * Precedence is deliberate and narrow. NON-EMPTY `:objects` still wins, so a\n * parent that hands rows down is never overridden by a source it did not ask\n * for; and a source wins over `register`/`schema`, because naming both is a\n * contradiction and silently self-fetching would render the WRONG list rather\n * than an obviously empty one.\n *\n * Note the test is \"has rows\", not \"was the prop passed\" — `useSelfFetchList`\n * uses the latter. The difference only shows for `:objects=\"[]\"` alongside a\n * source, where the source wins; that is the useful reading of an empty array\n * next to an explicit source name.\n *\n * Quick filters (cn-tasks-entity-source): a source may supply its own tabs\n * (`quickFilters`, same `{ label, filter, default? }` shape the manifest\n * uses). A manifest that declares `config.quickFilters` still wins. The\n * active tab's filter is merged OVER `sourceConfig` into every load, tab\n * winning on a colliding key, the same precedence self-fetch gives its tabs.\n * The default tab is seeded BEFORE the watcher registers, so mounting issues\n * exactly one request. Single mode only: `quickFilterMultiple` unions filter\n * maps for an OpenRegister query, which a source loader has no grammar for.\n *\n * 🔑 TOKENS RESOLVE HERE TOO. A tab filter is a filter, and a manifest author\n * writing `{ dueBefore: '@today+7d' }` has no way of knowing that the page\n * behind the label happens to be a named source rather than a self-fetch.\n * Without this the literal string `@today+7d` went over the wire, and a\n * source's server either rejected it or, worse, quietly answered the wrong\n * window: dossiq's \"Due this week\" lens is exactly that shape. Self-fetch\n * resolved tokens from the first day; this closes the gap rather than\n * teaching manifests which pages may use them.\n *\n * @param {object} props The CnIndexPage props.\n * @param {object} [options] Wiring handed down by CnIndexPage's setup.\n * @param {import('vue').Ref<number|null>} [options.activeQuickFilterIndex]\n * The shared tab-index ref `useSelfFetchList` owns (it exists even when\n * self-fetch is off); this composable seeds and watches it.\n * @param {import('vue').Ref<object>} [options.activeFilters] The sidebar's\n * chosen values, `{ fieldKey: values }`. Mapped through the source's\n * `searchFields` declaration into loader arguments and merged into every\n * load, so a manifest page's sidebar narrows the SERVER's query.\n *\n * @return {object} { isNamedSource, namedSource, namedRows, namedLoading, namedQuickFilters }\n */\nexport function useNamedSource(props, options = {}) {\n\tconst objectsProvided = !!(props.objects && props.objects.length > 0)\n\tconst isNamedSource = !!props.entitySource && !objectsProvided\n\n\tif (!isNamedSource) {\n\t\treturn {\n\t\t\tisNamedSource: false,\n\t\t\tnamedSource: null,\n\t\t\tnamedRows: computed(() => []),\n\t\t\tnamedLoading: computed(() => false),\n\t\t\tnamedQuickFilters: null,\n\t\t}\n\t}\n\n\tconst cnTranslate = inject('cnTranslate', null)\n\tconst source = resolveIndexSource(props.entitySource, props.sourceConfig, {\n\t\ttranslate: (text) => (typeof cnTranslate === 'function' ? cnTranslate(text) : text),\n\t})\n\n\t// An unknown source name resolves to null and was already warned about.\n\t// The page then behaves as an empty index rather than throwing.\n\tif (!source) {\n\t\treturn {\n\t\t\tisNamedSource: true,\n\t\t\tnamedSource: null,\n\t\t\tnamedRows: computed(() => []),\n\t\t\tnamedLoading: computed(() => false),\n\t\t\tnamedQuickFilters: null,\n\t\t}\n\t}\n\n\t// Effective tabs: the manifest's own strip wins, the source's is the\n\t// default. Both flow into the SAME load-merge below, so a manifest tab\n\t// can steer a source loader too.\n\tconst ownTabs = withPersonalLenses(props.quickFilters, props.personalLenses)\n\tconst manifestTabs = (Array.isArray(ownTabs) && ownTabs.length > 0)\n\t\t? ownTabs\n\t\t: null\n\tconst sourceTabs = (Array.isArray(source.quickFilters) && source.quickFilters.length > 0)\n\t\t? source.quickFilters\n\t\t: null\n\tconst tabs = manifestTabs || sourceTabs\n\tconst activeIndex = options.activeQuickFilterIndex || null\n\tconst activeFilters = options.activeFilters || null\n\t// A source that pages (`pages: true`) gets the page as `limit` and\n\t// `offset`, from the pager the page draws for it.\n\tconst paging = source.pages === true ? (options.paging || null) : null\n\n\t// Seed the default tab before the watcher below registers: the mount load\n\t// already carries this tab's filter, so the seed must not fire a second\n\t// request. `useSelfFetchList` resolved the index from the PROP only, so\n\t// source-supplied tabs arrive with the ref still null.\n\tif (tabs && activeIndex && activeIndex.value === null) {\n\t\tconst di = tabs.findIndex((tab) => tab && tab.default === true)\n\t\tactiveIndex.value = di >= 0 ? di : 0\n\t}\n\n\t// The same three bags `useSelfFetchList` injects, so a token means the\n\t// same thing on both kinds of page. They default to null/absent, so a\n\t// host that provides none is unaffected and `@today` still works: that\n\t// one needs no context at all.\n\tconst objectCtxRaw = inject('cnObjectContext', null)\n\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\tconst appConfigRaw = inject('cnAppConfig', null)\n\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\n\t/**\n\t * Build the token-resolution ctx from the injected bags, unwrapping refs.\n\t *\n\t * @return {object} The ctx `{ objectId?, object?, workspace, config }`.\n\t */\n\tconst tokenCtx = () => {\n\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\treturn base\n\t}\n\n\t/**\n\t * Load the source with the active tab's filter merged over the page's\n\t * `sourceConfig` (tab wins on a colliding key), tokens resolved.\n\t *\n\t * The ctx is rebuilt on every load rather than captured once, the same\n\t * way `useSelfFetchList` does it: `@today` has to mean today at the\n\t * moment of the fetch, not at the moment the page mounted, and a tab\n\t * left open across midnight would otherwise keep asking for yesterday.\n\t *\n\t * @return {Promise<void>} Resolves when the load settles.\n\t */\n\tconst loadActive = async () => {\n\t\tconst idx = activeIndex ? activeIndex.value : null\n\t\tconst tab = (tabs && idx !== null && idx !== undefined) ? tabs[idx] : null\n\t\t// 🔑 THE FIELD NARROWS WITHIN THE LENS, SO IT IS MERGED LAST. A lens\n\t\t// sets the question (\"unclaimed work\"); a field narrows the answer\n\t\t// (\"on this case\"). On the rare colliding key the field wins, because\n\t\t// it is the thing the person just chose and can see selected. Both\n\t\t// reach the SERVER: nothing here reduces a fetched page.\n\t\tconst config = resolveFilterTokens(\n\t\t\t{\n\t\t\t\t...(props.sourceConfig || {}),\n\t\t\t\t...((tab && tab.filter) || {}),\n\t\t\t\t...searchFieldParams(\n\t\t\t\t\tsource.searchFields,\n\t\t\t\t\tactiveFilters ? activeFilters.value : null,\n\t\t\t\t\tprops.entitySource,\n\t\t\t\t),\n\t\t\t\t...(paging ? { limit: paging.value.limit, offset: (paging.value.page - 1) * paging.value.limit } : {}),\n\t\t\t},\n\t\t\ttokenCtx(),\n\t\t)\n\t\ttry {\n\t\t\tawait source.load(config)\n\t\t} catch (error) {\n\t\t\t// Surfaced, not swallowed: a failed load and an empty source look\n\t\t\t// identical in the table, and only one of them is a problem.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(`[CnIndexPage] entitySource \"${props.entitySource}\" failed to load`, error)\n\t\t}\n\t}\n\n\tonMounted(loadActive)\n\n\t// A dialog that acted on a row (CnActionDialog, CnAssignDialog) signals\n\t// `cn:page:refresh`; the rows it changed are read again.\n\tconst onPageRefresh = () => loadActive()\n\tonMounted(() => subscribe('cn:page:refresh', onPageRefresh))\n\tonUnmounted(() => unsubscribe('cn:page:refresh', onPageRefresh))\n\n\t// Registered AFTER the seed above, so only a person switching tabs\n\t// reloads. Without tabs there is nothing to watch.\n\t/**\n\t * Reload for a new query: on a paging source back to page 1, which\n\t * reloads through the paging watcher below.\n\t *\n\t * @return {void}\n\t */\n\tfunction reloadFromFirstPage() {\n\t\tif (paging && paging.value.page !== 1) {\n\t\t\tpaging.value = { ...paging.value, page: 1 }\n\t\t\treturn\n\t\t}\n\t\tloadActive()\n\t}\n\n\tif (tabs && activeIndex) {\n\t\twatch(activeIndex, reloadFromFirstPage)\n\t}\n\n\t// A sidebar choice is a new QUERY, not a new view of the page already\n\t// fetched, so it reloads. `deep` because the map is rewritten key by key\n\t// and a shallow watch misses a second value added to the same field.\n\tif (activeFilters) {\n\t\twatch(activeFilters, reloadFromFirstPage, { deep: true })\n\t}\n\n\t// A new page, or a new page size, is a new request. A new tab or filter\n\t// first returns to page 1, which reloads through here.\n\tif (paging) {\n\t\twatch(paging, loadActive, { deep: true })\n\t}\n\n\t// No manual invalidation tick here on purpose. The adapters read from a\n\t// reactive store, so a computed that calls into them tracks it the ordinary\n\t// way. A hand-cranked counter would REPLACE that reactivity rather than add\n\t// to it, and would then miss every update that did not go through load() —\n\t// a flow renamed in the editor, for instance.\n\treturn {\n\t\tisNamedSource: true,\n\t\tnamedSource: source,\n\t\tnamedRows: computed(() => source.rows() || []),\n\t\tnamedLoading: computed(() => source.loading()),\n\t\t// The tabs the page should RENDER: the manifest's when it declared\n\t\t// any (the strip then also drives self-authored filters), else the\n\t\t// source's own. Null when neither exists.\n\t\tnamedQuickFilters: tabs,\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useNamedSource.js","sources":["../../../../src/components/CnIndexPage/useNamedSource.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n */\n\nimport { subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { computed, inject, onMounted, onUnmounted, watch } from 'vue'\nimport { resolveIndexSource } from '../../composables/indexSources.js'\nimport { withPersonalLenses } from '../../utils/personalLenses.js'\nimport { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { searchFieldParams } from '../../utils/searchFieldParams.js'\n\n/**\n * Third data mode for CnIndexPage: a NAMED source.\n *\n * The page already knew two ways to get rows — fetch OpenRegister objects from\n * `register` + `schema`, or accept them as `:objects`. Neither reaches a list\n * that is not an OpenRegister object and not already in a parent's hands, so\n * those lists became `type: \"custom\"` pages whose only real job was loading\n * rows from somewhere else.\n *\n * `source` closes that: the manifest names a source and the index loads it.\n *\n * Precedence is deliberate and narrow. NON-EMPTY `:objects` still wins, so a\n * parent that hands rows down is never overridden by a source it did not ask\n * for; and a source wins over `register`/`schema`, because naming both is a\n * contradiction and silently self-fetching would render the WRONG list rather\n * than an obviously empty one.\n *\n * Note the test is \"has rows\", not \"was the prop passed\" — `useSelfFetchList`\n * uses the latter. The difference only shows for `:objects=\"[]\"` alongside a\n * source, where the source wins; that is the useful reading of an empty array\n * next to an explicit source name.\n *\n * Quick filters (cn-tasks-entity-source): a source may supply its own tabs\n * (`quickFilters`, same `{ label, filter, default? }` shape the manifest\n * uses). A manifest that declares `config.quickFilters` still wins. The\n * active tab's filter is merged OVER `sourceConfig` into every load, tab\n * winning on a colliding key, the same precedence self-fetch gives its tabs.\n * The default tab is seeded BEFORE the watcher registers, so mounting issues\n * exactly one request. Single mode only: `quickFilterMultiple` unions filter\n * maps for an OpenRegister query, which a source loader has no grammar for.\n *\n * 🔑 TOKENS RESOLVE HERE TOO. A tab filter is a filter, and a manifest author\n * writing `{ dueBefore: '@today+7d' }` has no way of knowing that the page\n * behind the label happens to be a named source rather than a self-fetch.\n * Without this the literal string `@today+7d` went over the wire, and a\n * source's server either rejected it or, worse, quietly answered the wrong\n * window: dossiq's \"Due this week\" lens is exactly that shape. Self-fetch\n * resolved tokens from the first day; this closes the gap rather than\n * teaching manifests which pages may use them.\n *\n * @param {object} props The CnIndexPage props.\n * @param {object} [options] Wiring handed down by CnIndexPage's setup.\n * @param {import('vue').Ref<number|null>} [options.activeQuickFilterIndex]\n * The shared tab-index ref `useSelfFetchList` owns (it exists even when\n * self-fetch is off); this composable seeds and watches it.\n * @param {import('vue').Ref<object>} [options.activeFilters] The sidebar's\n * chosen values, `{ fieldKey: values }`. Mapped through the source's\n * `searchFields` declaration into loader arguments and merged into every\n * load, so a manifest page's sidebar narrows the SERVER's query.\n * @param {import('vue').Ref<string>} [options.searchTerm] The page's search\n * term. A source that names a `searchParam` gets it as that argument, so the\n * server searches; a new term reloads from the first page.\n *\n * @return {object} { isNamedSource, namedSource, namedRows, namedLoading, namedQuickFilters }\n */\nexport function useNamedSource(props, options = {}) {\n\tconst objectsProvided = !!(props.objects && props.objects.length > 0)\n\tconst isNamedSource = !!props.entitySource && !objectsProvided\n\n\tif (!isNamedSource) {\n\t\treturn {\n\t\t\tisNamedSource: false,\n\t\t\tnamedSource: null,\n\t\t\tnamedRows: computed(() => []),\n\t\t\tnamedLoading: computed(() => false),\n\t\t\tnamedQuickFilters: null,\n\t\t}\n\t}\n\n\tconst cnTranslate = inject('cnTranslate', null)\n\tconst source = resolveIndexSource(props.entitySource, props.sourceConfig, {\n\t\ttranslate: (text) => (typeof cnTranslate === 'function' ? cnTranslate(text) : text),\n\t})\n\n\t// An unknown source name resolves to null and was already warned about.\n\t// The page then behaves as an empty index rather than throwing.\n\tif (!source) {\n\t\treturn {\n\t\t\tisNamedSource: true,\n\t\t\tnamedSource: null,\n\t\t\tnamedRows: computed(() => []),\n\t\t\tnamedLoading: computed(() => false),\n\t\t\tnamedQuickFilters: null,\n\t\t}\n\t}\n\n\t// Effective tabs: the manifest's own strip wins, the source's is the\n\t// default. Both flow into the SAME load-merge below, so a manifest tab\n\t// can steer a source loader too.\n\tconst ownTabs = withPersonalLenses(props.quickFilters, props.personalLenses)\n\tconst manifestTabs = (Array.isArray(ownTabs) && ownTabs.length > 0)\n\t\t? ownTabs\n\t\t: null\n\tconst sourceTabs = (Array.isArray(source.quickFilters) && source.quickFilters.length > 0)\n\t\t? source.quickFilters\n\t\t: null\n\tconst tabs = manifestTabs || sourceTabs\n\tconst activeIndex = options.activeQuickFilterIndex || null\n\tconst activeFilters = options.activeFilters || null\n\tconst searchTerm = (typeof source.searchParam === 'string' && source.searchParam !== '') ? (options.searchTerm || null) : null\n\t// A source that pages (`pages: true`) gets the page as `limit` and\n\t// `offset`, from the pager the page draws for it.\n\tconst paging = source.pages === true ? (options.paging || null) : null\n\n\t// Seed the default tab before the watcher below registers: the mount load\n\t// already carries this tab's filter, so the seed must not fire a second\n\t// request. `useSelfFetchList` resolved the index from the PROP only, so\n\t// source-supplied tabs arrive with the ref still null.\n\tif (tabs && activeIndex && activeIndex.value === null) {\n\t\tconst di = tabs.findIndex((tab) => tab && tab.default === true)\n\t\tactiveIndex.value = di >= 0 ? di : 0\n\t}\n\n\t// The same three bags `useSelfFetchList` injects, so a token means the\n\t// same thing on both kinds of page. They default to null/absent, so a\n\t// host that provides none is unaffected and `@today` still works: that\n\t// one needs no context at all.\n\tconst objectCtxRaw = inject('cnObjectContext', null)\n\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\tconst appConfigRaw = inject('cnAppConfig', null)\n\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\n\t/**\n\t * Build the token-resolution ctx from the injected bags, unwrapping refs.\n\t *\n\t * @return {object} The ctx `{ objectId?, object?, workspace, config }`.\n\t */\n\tconst tokenCtx = () => {\n\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\treturn base\n\t}\n\n\t/**\n\t * Load the source with the active tab's filter merged over the page's\n\t * `sourceConfig` (tab wins on a colliding key), tokens resolved.\n\t *\n\t * The ctx is rebuilt on every load rather than captured once, the same\n\t * way `useSelfFetchList` does it: `@today` has to mean today at the\n\t * moment of the fetch, not at the moment the page mounted, and a tab\n\t * left open across midnight would otherwise keep asking for yesterday.\n\t *\n\t * @return {Promise<void>} Resolves when the load settles.\n\t */\n\tconst loadActive = async () => {\n\t\tconst idx = activeIndex ? activeIndex.value : null\n\t\tconst tab = (tabs && idx !== null && idx !== undefined) ? tabs[idx] : null\n\t\t// 🔑 THE FIELD NARROWS WITHIN THE LENS, SO IT IS MERGED LAST. A lens\n\t\t// sets the question (\"unclaimed work\"); a field narrows the answer\n\t\t// (\"on this case\"). On the rare colliding key the field wins, because\n\t\t// it is the thing the person just chose and can see selected. Both\n\t\t// reach the SERVER: nothing here reduces a fetched page.\n\t\tconst config = resolveFilterTokens(\n\t\t\t{\n\t\t\t\t...(props.sourceConfig || {}),\n\t\t\t\t...((tab && tab.filter) || {}),\n\t\t\t\t...searchFieldParams(\n\t\t\t\t\tsource.searchFields,\n\t\t\t\t\tactiveFilters ? activeFilters.value : null,\n\t\t\t\t\tprops.entitySource,\n\t\t\t\t),\n\t\t\t\t...(searchTerm && searchTerm.value ? { [source.searchParam]: searchTerm.value } : {}),\n\t\t\t\t...(paging ? { limit: paging.value.limit, offset: (paging.value.page - 1) * paging.value.limit } : {}),\n\t\t\t},\n\t\t\ttokenCtx(),\n\t\t)\n\t\ttry {\n\t\t\tawait source.load(config)\n\t\t} catch (error) {\n\t\t\t// Surfaced, not swallowed: a failed load and an empty source look\n\t\t\t// identical in the table, and only one of them is a problem.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(`[CnIndexPage] entitySource \"${props.entitySource}\" failed to load`, error)\n\t\t}\n\t}\n\n\tonMounted(loadActive)\n\n\t// A dialog that acted on a row (CnActionDialog, CnAssignDialog) signals\n\t// `cn:page:refresh`; the rows it changed are read again.\n\tconst onPageRefresh = () => loadActive()\n\tonMounted(() => subscribe('cn:page:refresh', onPageRefresh))\n\tonUnmounted(() => unsubscribe('cn:page:refresh', onPageRefresh))\n\n\t// Registered AFTER the seed above, so only a person switching tabs\n\t// reloads. Without tabs there is nothing to watch.\n\t/**\n\t * Reload for a new query: on a paging source back to page 1, which\n\t * reloads through the paging watcher below.\n\t *\n\t * @return {void}\n\t */\n\tfunction reloadFromFirstPage() {\n\t\tif (paging && paging.value.page !== 1) {\n\t\t\tpaging.value = { ...paging.value, page: 1 }\n\t\t\treturn\n\t\t}\n\t\tloadActive()\n\t}\n\n\tif (tabs && activeIndex) {\n\t\twatch(activeIndex, reloadFromFirstPage)\n\t}\n\n\t// A sidebar choice is a new QUERY, not a new view of the page already\n\t// fetched, so it reloads. `deep` because the map is rewritten key by key\n\t// and a shallow watch misses a second value added to the same field.\n\tif (activeFilters) {\n\t\twatch(activeFilters, reloadFromFirstPage, { deep: true })\n\t}\n\n\tif (searchTerm) {\n\t\twatch(searchTerm, reloadFromFirstPage)\n\t}\n\n\t// A new page, or a new page size, is a new request. A new tab or filter\n\t// first returns to page 1, which reloads through here.\n\tif (paging) {\n\t\twatch(paging, loadActive, { deep: true })\n\t}\n\n\t// No manual invalidation tick here on purpose. The adapters read from a\n\t// reactive store, so a computed that calls into them tracks it the ordinary\n\t// way. A hand-cranked counter would REPLACE that reactivity rather than add\n\t// to it, and would then miss every update that did not go through load() —\n\t// a flow renamed in the editor, for instance.\n\treturn {\n\t\tisNamedSource: true,\n\t\tnamedSource: source,\n\t\tnamedRows: computed(() => source.rows() || []),\n\t\tnamedLoading: computed(() => source.loading()),\n\t\t// The tabs the page should RENDER: the manifest's when it declared\n\t\t// any (the strip then also drives self-authored filters), else the\n\t\t// source's own. Null when neither exists.\n\t\tnamedQuickFilters: tabs,\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import script from './CnObjectPermissionsTab.vue2.js';
|
|
2
|
+
import { render } from './CnObjectPermissionsTab.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__scopeId = "data-v-7b7204e8";
|
|
6
|
+
const __sfc_main = script;
|
|
7
|
+
|
|
8
|
+
export { __sfc_main as default };
|
|
9
|
+
//# sourceMappingURL=CnObjectPermissionsTab.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnObjectPermissionsTab.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
import { NcLoadingIcon } from '@nextcloud/vue';
|
|
3
|
+
import { DEFAULT_API_BASE, roleRuleLabels, roleRuleSentence, fetchObjectPermissions, fetchPermissionCatalogue, fetchObjectGrants, fetchRoleGrants, fetchCallerScope, fetchDenyRules, fetchFieldRoleRules, fieldRoleRows, grantRows, fetchAccessHistory, asOfRows } from '../../utils/objectPermissions.js';
|
|
4
|
+
|
|
5
|
+
/** OpenRegister's answer to a reader who may open the object and not review it. */
|
|
6
|
+
const REVIEW_REFUSED = 403;
|
|
7
|
+
|
|
8
|
+
var script = {
|
|
9
|
+
name: 'CnObjectPermissionsTab',
|
|
10
|
+
components: { NcLoadingIcon },
|
|
11
|
+
|
|
12
|
+
props: {
|
|
13
|
+
/** The object uuid (injected by CnObjectSidebar). */
|
|
14
|
+
objectId: { type: String, default: '' },
|
|
15
|
+
/** Register slug or uuid (injected by CnObjectSidebar). */
|
|
16
|
+
register: { type: String, default: '' },
|
|
17
|
+
/** Schema slug or uuid (injected by CnObjectSidebar). */
|
|
18
|
+
schema: { type: String, default: '' },
|
|
19
|
+
/** OpenRegister API root. */
|
|
20
|
+
apiBase: { type: String, default: DEFAULT_API_BASE },
|
|
21
|
+
/** Offer "who held a right on a date" when OpenRegister answers the history read. */
|
|
22
|
+
historyEnabled: { type: Boolean, default: true },
|
|
23
|
+
/**
|
|
24
|
+
* Where the per-role field rules are declared:
|
|
25
|
+
* `{ typeField, typeSchema, declarationField? }`. The object's
|
|
26
|
+
* `typeField` references a `typeSchema` object in the same register
|
|
27
|
+
* whose `declarationField` (default `fieldRoleRules`) lists the rules.
|
|
28
|
+
* `null` leaves the field-rules section out.
|
|
29
|
+
*/
|
|
30
|
+
fieldRules: { type: Object, default: null },
|
|
31
|
+
/** Heading; empty uses the translated default. */
|
|
32
|
+
title: { type: String, default: '' },
|
|
33
|
+
/**
|
|
34
|
+
* Labels per source key, overriding the defaults (e.g. `{ group: '…' }`).
|
|
35
|
+
* An unknown key renders as itself.
|
|
36
|
+
*/
|
|
37
|
+
sourceLabels: { type: Object, default: () => ({}) },
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
loading: true,
|
|
43
|
+
rows: [],
|
|
44
|
+
catalogue: [],
|
|
45
|
+
enforcement: '',
|
|
46
|
+
unreadable: [],
|
|
47
|
+
reviewRefused: false,
|
|
48
|
+
historyReadable: false,
|
|
49
|
+
fieldRows: [],
|
|
50
|
+
moment: '',
|
|
51
|
+
momentLoading: false,
|
|
52
|
+
asOf: null,
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
computed: {
|
|
57
|
+
target() {
|
|
58
|
+
return { register: this.register, schema: this.schema, apiBase: this.apiBase }
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
ruleLabels() {
|
|
62
|
+
return roleRuleLabels()
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/** Today, so nobody asks the trail about a date that has not happened. */
|
|
66
|
+
today() {
|
|
67
|
+
return new Date().toISOString().slice(0, 10)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
setBySentence() {
|
|
71
|
+
if (!this.asOf?.setBy) {
|
|
72
|
+
return translate('nextcloud-vue', 'These rights stood on that date. Nobody is named as setting them.')
|
|
73
|
+
}
|
|
74
|
+
return translate('nextcloud-vue', '{who} set these rights.', { who: this.asOf.setBy })
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
changedAfterwardsSentence() {
|
|
78
|
+
const change = this.asOf?.changedAfterwardsBy;
|
|
79
|
+
if (!change) {
|
|
80
|
+
return ''
|
|
81
|
+
}
|
|
82
|
+
return translate('nextcloud-vue', '{who} changed them afterwards, on {date}.', {
|
|
83
|
+
who: change.by || translate('nextcloud-vue', 'Somebody'),
|
|
84
|
+
date: change.at || translate('nextcloud-vue', 'a date the trail does not give'),
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
enforcementSentence() {
|
|
89
|
+
if (this.enforcement === 'enforcing') {
|
|
90
|
+
return translate('nextcloud-vue', 'A refusal below is in force now.')
|
|
91
|
+
}
|
|
92
|
+
if (this.enforcement === 'off') {
|
|
93
|
+
return translate('nextcloud-vue', 'Refusals are switched off, so no rule below removes a right.')
|
|
94
|
+
}
|
|
95
|
+
return translate('nextcloud-vue', 'Refusals are staged: a rule below is recorded, and removes a right once it is switched on.')
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
watch: {
|
|
100
|
+
objectId() {
|
|
101
|
+
this.asOf = null;
|
|
102
|
+
this.moment = '';
|
|
103
|
+
this.load();
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
mounted() {
|
|
108
|
+
this.load();
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
methods: {
|
|
112
|
+
t: translate,
|
|
113
|
+
|
|
114
|
+
sentenceFor(row) {
|
|
115
|
+
return roleRuleSentence(row)
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Ask OpenRegister once and render what came back.
|
|
120
|
+
*
|
|
121
|
+
* @return {Promise<void>}
|
|
122
|
+
*/
|
|
123
|
+
async load() {
|
|
124
|
+
this.loading = true;
|
|
125
|
+
const permissions = this.objectId
|
|
126
|
+
? await fetchObjectPermissions(this.target, this.objectId)
|
|
127
|
+
: { status: 0, set: null };
|
|
128
|
+
|
|
129
|
+
// A 403 is OpenRegister answering "not you", not a failed read.
|
|
130
|
+
this.reviewRefused = permissions.status === REVIEW_REFUSED;
|
|
131
|
+
this.historyReadable = permissions.set !== null;
|
|
132
|
+
const fallback = permissions.set === null;
|
|
133
|
+
|
|
134
|
+
const [catalogue, objectGrants, roleGrants, callerScope, denyRules, fieldRules] = await Promise.all([
|
|
135
|
+
fetchPermissionCatalogue(this.target),
|
|
136
|
+
fallback && this.objectId ? fetchObjectGrants(this.target, this.objectId) : null,
|
|
137
|
+
fallback ? fetchRoleGrants(this.target) : null,
|
|
138
|
+
fetchCallerScope(this.target),
|
|
139
|
+
fallback ? fetchDenyRules(this.target) : null,
|
|
140
|
+
this.fieldRules ? fetchFieldRoleRules(this.target, this.objectId, this.fieldRules) : { decided: null, declared: null },
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
this.fieldRows = fieldRoleRows(fieldRules.declared, fieldRules.decided);
|
|
144
|
+
|
|
145
|
+
// Each failed read is named: an auditor reading a short list has to
|
|
146
|
+
// know which source is missing from it.
|
|
147
|
+
const missing = [];
|
|
148
|
+
if (catalogue === null) {
|
|
149
|
+
missing.push(translate('nextcloud-vue', 'the rights it publishes'));
|
|
150
|
+
}
|
|
151
|
+
if (permissions.set === null) {
|
|
152
|
+
if (objectGrants === null) {
|
|
153
|
+
missing.push(translate('nextcloud-vue', 'the shares on this record'));
|
|
154
|
+
}
|
|
155
|
+
if (roleGrants === null) {
|
|
156
|
+
missing.push(translate('nextcloud-vue', 'the roles'));
|
|
157
|
+
}
|
|
158
|
+
if (denyRules === null) {
|
|
159
|
+
missing.push(translate('nextcloud-vue', 'the refusals'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (callerScope === null) {
|
|
163
|
+
missing.push(translate('nextcloud-vue', 'your own rights'));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.catalogue = catalogue?.permissions || [];
|
|
167
|
+
this.enforcement = permissions.set?.denyEnforcement || catalogue?.denyEnforcement || denyRules?.denyEnforcement || '';
|
|
168
|
+
this.unreadable = this.reviewRefused ? [] : missing;
|
|
169
|
+
this.rows = grantRows({ objectPermissions: permissions.set, objectGrants, roleGrants, callerScope, denyRules });
|
|
170
|
+
this.loading = false;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Ask the trail who held a right at the end of the picked day.
|
|
175
|
+
*
|
|
176
|
+
* @return {Promise<void>}
|
|
177
|
+
*/
|
|
178
|
+
async loadMoment() {
|
|
179
|
+
if (this.moment === '' || this.objectId === '') {
|
|
180
|
+
this.asOf = null;
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
this.momentLoading = true;
|
|
184
|
+
// End of that day, so a grant written during it counts as standing on it.
|
|
185
|
+
const { history } = await fetchAccessHistory(this.target, this.objectId, `${this.moment}T23:59:59`);
|
|
186
|
+
this.asOf = asOfRows(history);
|
|
187
|
+
this.momentLoading = false;
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
roleHint(row) {
|
|
191
|
+
if (!row?.role) {
|
|
192
|
+
return row?.detail || ''
|
|
193
|
+
}
|
|
194
|
+
return translate('nextcloud-vue', 'Through the role {role}', { role: row.role })
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Which record handed this rule down, named inside a sentence, never bare.
|
|
199
|
+
*
|
|
200
|
+
* @param {object} row One row.
|
|
201
|
+
* @return {string} The sentence, or ''.
|
|
202
|
+
*/
|
|
203
|
+
inheritedHint(row) {
|
|
204
|
+
if (!row?.inheritedFrom) {
|
|
205
|
+
return ''
|
|
206
|
+
}
|
|
207
|
+
return translate('nextcloud-vue', 'Granted on record {record}, which this one hangs under', { record: row.inheritedFrom })
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The end as OpenRegister wrote it: printed, never judged against a clock.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} row One row.
|
|
214
|
+
* @return {string} The sentence.
|
|
215
|
+
*/
|
|
216
|
+
endsHint(row) {
|
|
217
|
+
return translate('nextcloud-vue', 'Written to end on {date}', { date: row.until })
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
areaHint(row) {
|
|
221
|
+
const named = [...(row?.scopedTo?.registers || []), ...(row?.scopedTo?.schemas || [])]
|
|
222
|
+
.filter((name) => typeof name === 'string' && name !== '');
|
|
223
|
+
if (named.length === 0) {
|
|
224
|
+
return ''
|
|
225
|
+
}
|
|
226
|
+
return translate('nextcloud-vue', 'Only in {areas}', { areas: named.join(', ') })
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
describe(right) {
|
|
230
|
+
const entry = this.catalogue.find((permission) => permission?.action === right);
|
|
231
|
+
return entry?.description || ''
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
sourceLabel(source) {
|
|
235
|
+
if (this.sourceLabels && this.sourceLabels[source]) {
|
|
236
|
+
return this.sourceLabels[source]
|
|
237
|
+
}
|
|
238
|
+
const labels = {
|
|
239
|
+
share: translate('nextcloud-vue', 'A share on this record'),
|
|
240
|
+
role: translate('nextcloud-vue', 'A role'),
|
|
241
|
+
group: translate('nextcloud-vue', 'A grant on a group of types'),
|
|
242
|
+
object: translate('nextcloud-vue', 'A rule on this record'),
|
|
243
|
+
schema: translate('nextcloud-vue', 'A rule on every record of this type'),
|
|
244
|
+
register: translate('nextcloud-vue', 'A rule on the register'),
|
|
245
|
+
deny: translate('nextcloud-vue', 'A rule that refuses'),
|
|
246
|
+
'staged-deny': translate('nextcloud-vue', 'A rule that will refuse'),
|
|
247
|
+
'default-open': translate('nextcloud-vue', 'No rule, so it is open'),
|
|
248
|
+
none: translate('nextcloud-vue', 'No rule grants it'),
|
|
249
|
+
inherited: translate('nextcloud-vue', 'A grant on a record this one hangs under'),
|
|
250
|
+
};
|
|
251
|
+
return labels[source] || source
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export { script as default };
|
|
257
|
+
//# sourceMappingURL=CnObjectPermissionsTab.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnObjectPermissionsTab.vue2.js","sources":["../../../../src/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n\n CnObjectPermissionsTab — who holds which right on one OpenRegister object, and\n where each grant came from. Read-only: it renders OpenRegister's answer and\n computes none of it. One row per rule; a deny is its own row beside the grant\n it removes. A failed read is named, never shown as an empty list.\n-->\n<template>\n\t<div class=\"cn-object-permissions-tab\">\n\t\t<h3>{{ title || t('nextcloud-vue', 'Who holds which right') }}</h3>\n\n\t\t<div v-if=\"loading\" class=\"cn-object-permissions-tab__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t{{ t('nextcloud-vue', 'Reading the grants on this record') }}\n\t\t</div>\n\n\t\t<template v-else>\n\t\t\t<p v-if=\"reviewRefused\" class=\"cn-object-permissions-tab__unreadable\">\n\t\t\t\t{{ t('nextcloud-vue', 'You may open this record. You may not review who else can.') }}\n\t\t\t</p>\n\n\t\t\t<p v-else-if=\"unreadable.length > 0\" class=\"cn-object-permissions-tab__unreadable\">\n\t\t\t\t{{ t('nextcloud-vue', 'OpenRegister could not be asked for {sources}. This list is incomplete, not empty.', { sources: unreadable.join(', ') }) }}\n\t\t\t</p>\n\n\t\t\t<p v-if=\"enforcement\" class=\"cn-object-permissions-tab__enforcement\">\n\t\t\t\t{{ enforcementSentence }}\n\t\t\t</p>\n\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-object-permissions-tab__empty\">\n\t\t\t\t{{ t('nextcloud-vue', 'OpenRegister reports no rule on this record.') }}\n\t\t\t</p>\n\n\t\t\t<table v-else class=\"cn-object-permissions-tab__table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Holder') }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Right') }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Where it comes from') }}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr v-for=\"(row, index) in rows\"\n\t\t\t\t\t\t:key=\"`${row.holder}-${row.right}-${row.source}-${index}`\"\n\t\t\t\t\t\tclass=\"cn-object-permissions-tab__row\">\n\t\t\t\t\t\t<td>{{ row.holder || t('nextcloud-vue', 'Not named') }}</td>\n\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t<span class=\"cn-object-permissions-tab__right\">{{ row.right }}</span>\n\t\t\t\t\t\t\t<span v-if=\"describe(row.right)\" class=\"cn-object-permissions-tab__hint\">{{ describe(row.right) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"row.declared === false\" class=\"cn-object-permissions-tab__hint\">{{ t('nextcloud-vue', 'OpenRegister does not publish this right.') }}</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t<span class=\"cn-object-permissions-tab__source\">{{ sourceLabel(row.source) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"roleHint(row)\" class=\"cn-object-permissions-tab__hint\">{{ roleHint(row) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"inheritedHint(row)\" class=\"cn-object-permissions-tab__hint cn-object-permissions-tab__inherited\">{{ inheritedHint(row) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"row.until\" class=\"cn-object-permissions-tab__hint\">{{ endsHint(row) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"areaHint(row)\" class=\"cn-object-permissions-tab__hint\">{{ areaHint(row) }}</span>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\n\t\t\t<section v-if=\"historyEnabled && historyReadable\" class=\"cn-object-permissions-tab__history\">\n\t\t\t\t<h4>{{ t('nextcloud-vue', 'Who held a right on a date') }}</h4>\n\n\t\t\t\t<label class=\"cn-object-permissions-tab__moment\">\n\t\t\t\t\t<span>{{ t('nextcloud-vue', 'Report the rights as they stood on') }}</span>\n\t\t\t\t\t<input v-model=\"moment\"\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\t:max=\"today\"\n\t\t\t\t\t\t@change=\"loadMoment\">\n\t\t\t\t</label>\n\n\t\t\t\t<p v-if=\"momentLoading\" class=\"cn-object-permissions-tab__empty\" role=\"status\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Reading the trail for that date') }}\n\t\t\t\t</p>\n\n\t\t\t\t<template v-else-if=\"asOf\">\n\t\t\t\t\t<p v-if=\"asOf.answered === false\" class=\"cn-object-permissions-tab__empty\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'The trail does not reach {date}, so nobody can be reported for it.', { date: moment }) }}\n\t\t\t\t\t</p>\n\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t<p class=\"cn-object-permissions-tab__enforcement\">\n\t\t\t\t\t\t\t{{ setBySentence }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"asOf.changedAfterwardsBy\" class=\"cn-object-permissions-tab__enforcement\">\n\t\t\t\t\t\t\t{{ changedAfterwardsSentence }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<table class=\"cn-object-permissions-tab__table\">\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Holder') }}\n\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Right') }}\n\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Where it came from') }}\n\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr v-for=\"(row, index) in asOf.rows\"\n\t\t\t\t\t\t\t\t\t:key=\"`asof-${row.holder}-${row.right}-${index}`\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-object-permissions-tab__row\">\n\t\t\t\t\t\t\t\t\t<td>{{ row.holder || t('nextcloud-vue', 'Not named') }}</td>\n\t\t\t\t\t\t\t\t\t<td>{{ row.right }}</td>\n\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t<span class=\"cn-object-permissions-tab__source\">{{ sourceLabel(row.source) }}</span>\n\t\t\t\t\t\t\t\t\t\t<span v-if=\"roleHint(row)\" class=\"cn-object-permissions-tab__hint\">{{ roleHint(row) }}</span>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</template>\n\t\t\t\t</template>\n\t\t\t</section>\n\n\t\t\t<section v-if=\"fieldRows.length\" class=\"cn-object-permissions-tab__fields\">\n\t\t\t\t<h4>{{ t('nextcloud-vue', 'Which fields each role reads') }}</h4>\n\t\t\t\t<p class=\"cn-object-permissions-tab__hint\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'A field missing from this record is missing because of a rule below, not because it is empty.') }}\n\t\t\t\t</p>\n\t\t\t\t<table class=\"cn-object-permissions-tab__table\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Field') }}\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Rule') }}\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t<th scope=\"col\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Who it affects') }}\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t<tr v-for=\"(row, index) in fieldRows\"\n\t\t\t\t\t\t\t:key=\"`field-${row.field}-${row.rule}-${index}`\"\n\t\t\t\t\t\t\tclass=\"cn-object-permissions-tab__row\">\n\t\t\t\t\t\t\t<td>{{ row.field }}</td>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t{{ ruleLabels[row.rule] }}\n\t\t\t\t\t\t\t\t<span v-if=\"row.appliesToMe\" class=\"cn-object-permissions-tab__source\">{{ t('nextcloud-vue', 'applies to you') }}</span>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t{{ sentenceFor(row) }}\n\t\t\t\t\t\t\t\t<span v-if=\"row.reason\" class=\"cn-object-permissions-tab__hint\">{{ row.reason }}</span>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</section>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport {\n\tasOfRows,\n\tDEFAULT_API_BASE,\n\tfetchAccessHistory,\n\tfetchCallerScope,\n\tfetchDenyRules,\n\tfetchFieldRoleRules,\n\tfetchObjectGrants,\n\tfetchObjectPermissions,\n\tfetchPermissionCatalogue,\n\tfetchRoleGrants,\n\tfieldRoleRows,\n\tgrantRows,\n\troleRuleLabels,\n\troleRuleSentence,\n} from '../../utils/objectPermissions.js'\n\n/** OpenRegister's answer to a reader who may open the object and not review it. */\nconst REVIEW_REFUSED = 403\n\nexport default {\n\tname: 'CnObjectPermissionsTab',\n\tcomponents: { NcLoadingIcon },\n\n\tprops: {\n\t\t/** The object uuid (injected by CnObjectSidebar). */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** Register slug or uuid (injected by CnObjectSidebar). */\n\t\tregister: { type: String, default: '' },\n\t\t/** Schema slug or uuid (injected by CnObjectSidebar). */\n\t\tschema: { type: String, default: '' },\n\t\t/** OpenRegister API root. */\n\t\tapiBase: { type: String, default: DEFAULT_API_BASE },\n\t\t/** Offer \"who held a right on a date\" when OpenRegister answers the history read. */\n\t\thistoryEnabled: { type: Boolean, default: true },\n\t\t/**\n\t\t * Where the per-role field rules are declared:\n\t\t * `{ typeField, typeSchema, declarationField? }`. The object's\n\t\t * `typeField` references a `typeSchema` object in the same register\n\t\t * whose `declarationField` (default `fieldRoleRules`) lists the rules.\n\t\t * `null` leaves the field-rules section out.\n\t\t */\n\t\tfieldRules: { type: Object, default: null },\n\t\t/** Heading; empty uses the translated default. */\n\t\ttitle: { type: String, default: '' },\n\t\t/**\n\t\t * Labels per source key, overriding the defaults (e.g. `{ group: '…' }`).\n\t\t * An unknown key renders as itself.\n\t\t */\n\t\tsourceLabels: { type: Object, default: () => ({}) },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\trows: [],\n\t\t\tcatalogue: [],\n\t\t\tenforcement: '',\n\t\t\tunreadable: [],\n\t\t\treviewRefused: false,\n\t\t\thistoryReadable: false,\n\t\t\tfieldRows: [],\n\t\t\tmoment: '',\n\t\t\tmomentLoading: false,\n\t\t\tasOf: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\ttarget() {\n\t\t\treturn { register: this.register, schema: this.schema, apiBase: this.apiBase }\n\t\t},\n\n\t\truleLabels() {\n\t\t\treturn roleRuleLabels()\n\t\t},\n\n\t\t/** Today, so nobody asks the trail about a date that has not happened. */\n\t\ttoday() {\n\t\t\treturn new Date().toISOString().slice(0, 10)\n\t\t},\n\n\t\tsetBySentence() {\n\t\t\tif (!this.asOf?.setBy) {\n\t\t\t\treturn t('nextcloud-vue', 'These rights stood on that date. Nobody is named as setting them.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{who} set these rights.', { who: this.asOf.setBy })\n\t\t},\n\n\t\tchangedAfterwardsSentence() {\n\t\t\tconst change = this.asOf?.changedAfterwardsBy\n\t\t\tif (!change) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{who} changed them afterwards, on {date}.', {\n\t\t\t\twho: change.by || t('nextcloud-vue', 'Somebody'),\n\t\t\t\tdate: change.at || t('nextcloud-vue', 'a date the trail does not give'),\n\t\t\t})\n\t\t},\n\n\t\tenforcementSentence() {\n\t\t\tif (this.enforcement === 'enforcing') {\n\t\t\t\treturn t('nextcloud-vue', 'A refusal below is in force now.')\n\t\t\t}\n\t\t\tif (this.enforcement === 'off') {\n\t\t\t\treturn t('nextcloud-vue', 'Refusals are switched off, so no rule below removes a right.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Refusals are staged: a rule below is recorded, and removes a right once it is switched on.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId() {\n\t\t\tthis.asOf = null\n\t\t\tthis.moment = ''\n\t\t\tthis.load()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tsentenceFor(row) {\n\t\t\treturn roleRuleSentence(row)\n\t\t},\n\n\t\t/**\n\t\t * Ask OpenRegister once and render what came back.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tconst permissions = this.objectId\n\t\t\t\t? await fetchObjectPermissions(this.target, this.objectId)\n\t\t\t\t: { status: 0, set: null }\n\n\t\t\t// A 403 is OpenRegister answering \"not you\", not a failed read.\n\t\t\tthis.reviewRefused = permissions.status === REVIEW_REFUSED\n\t\t\tthis.historyReadable = permissions.set !== null\n\t\t\tconst fallback = permissions.set === null\n\n\t\t\tconst [catalogue, objectGrants, roleGrants, callerScope, denyRules, fieldRules] = await Promise.all([\n\t\t\t\tfetchPermissionCatalogue(this.target),\n\t\t\t\tfallback && this.objectId ? fetchObjectGrants(this.target, this.objectId) : null,\n\t\t\t\tfallback ? fetchRoleGrants(this.target) : null,\n\t\t\t\tfetchCallerScope(this.target),\n\t\t\t\tfallback ? fetchDenyRules(this.target) : null,\n\t\t\t\tthis.fieldRules ? fetchFieldRoleRules(this.target, this.objectId, this.fieldRules) : { decided: null, declared: null },\n\t\t\t])\n\n\t\t\tthis.fieldRows = fieldRoleRows(fieldRules.declared, fieldRules.decided)\n\n\t\t\t// Each failed read is named: an auditor reading a short list has to\n\t\t\t// know which source is missing from it.\n\t\t\tconst missing = []\n\t\t\tif (catalogue === null) {\n\t\t\t\tmissing.push(t('nextcloud-vue', 'the rights it publishes'))\n\t\t\t}\n\t\t\tif (permissions.set === null) {\n\t\t\t\tif (objectGrants === null) {\n\t\t\t\t\tmissing.push(t('nextcloud-vue', 'the shares on this record'))\n\t\t\t\t}\n\t\t\t\tif (roleGrants === null) {\n\t\t\t\t\tmissing.push(t('nextcloud-vue', 'the roles'))\n\t\t\t\t}\n\t\t\t\tif (denyRules === null) {\n\t\t\t\t\tmissing.push(t('nextcloud-vue', 'the refusals'))\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (callerScope === null) {\n\t\t\t\tmissing.push(t('nextcloud-vue', 'your own rights'))\n\t\t\t}\n\n\t\t\tthis.catalogue = catalogue?.permissions || []\n\t\t\tthis.enforcement = permissions.set?.denyEnforcement || catalogue?.denyEnforcement || denyRules?.denyEnforcement || ''\n\t\t\tthis.unreadable = this.reviewRefused ? [] : missing\n\t\t\tthis.rows = grantRows({ objectPermissions: permissions.set, objectGrants, roleGrants, callerScope, denyRules })\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Ask the trail who held a right at the end of the picked day.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadMoment() {\n\t\t\tif (this.moment === '' || this.objectId === '') {\n\t\t\t\tthis.asOf = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.momentLoading = true\n\t\t\t// End of that day, so a grant written during it counts as standing on it.\n\t\t\tconst { history } = await fetchAccessHistory(this.target, this.objectId, `${this.moment}T23:59:59`)\n\t\t\tthis.asOf = asOfRows(history)\n\t\t\tthis.momentLoading = false\n\t\t},\n\n\t\troleHint(row) {\n\t\t\tif (!row?.role) {\n\t\t\t\treturn row?.detail || ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Through the role {role}', { role: row.role })\n\t\t},\n\n\t\t/**\n\t\t * Which record handed this rule down, named inside a sentence, never bare.\n\t\t *\n\t\t * @param {object} row One row.\n\t\t * @return {string} The sentence, or ''.\n\t\t */\n\t\tinheritedHint(row) {\n\t\t\tif (!row?.inheritedFrom) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Granted on record {record}, which this one hangs under', { record: row.inheritedFrom })\n\t\t},\n\n\t\t/**\n\t\t * The end as OpenRegister wrote it: printed, never judged against a clock.\n\t\t *\n\t\t * @param {object} row One row.\n\t\t * @return {string} The sentence.\n\t\t */\n\t\tendsHint(row) {\n\t\t\treturn t('nextcloud-vue', 'Written to end on {date}', { date: row.until })\n\t\t},\n\n\t\tareaHint(row) {\n\t\t\tconst named = [...(row?.scopedTo?.registers || []), ...(row?.scopedTo?.schemas || [])]\n\t\t\t\t.filter((name) => typeof name === 'string' && name !== '')\n\t\t\tif (named.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Only in {areas}', { areas: named.join(', ') })\n\t\t},\n\n\t\tdescribe(right) {\n\t\t\tconst entry = this.catalogue.find((permission) => permission?.action === right)\n\t\t\treturn entry?.description || ''\n\t\t},\n\n\t\tsourceLabel(source) {\n\t\t\tif (this.sourceLabels && this.sourceLabels[source]) {\n\t\t\t\treturn this.sourceLabels[source]\n\t\t\t}\n\t\t\tconst labels = {\n\t\t\t\tshare: t('nextcloud-vue', 'A share on this record'),\n\t\t\t\trole: t('nextcloud-vue', 'A role'),\n\t\t\t\tgroup: t('nextcloud-vue', 'A grant on a group of types'),\n\t\t\t\tobject: t('nextcloud-vue', 'A rule on this record'),\n\t\t\t\tschema: t('nextcloud-vue', 'A rule on every record of this type'),\n\t\t\t\tregister: t('nextcloud-vue', 'A rule on the register'),\n\t\t\t\tdeny: t('nextcloud-vue', 'A rule that refuses'),\n\t\t\t\t'staged-deny': t('nextcloud-vue', 'A rule that will refuse'),\n\t\t\t\t'default-open': t('nextcloud-vue', 'No rule, so it is open'),\n\t\t\t\tnone: t('nextcloud-vue', 'No rule grants it'),\n\t\t\t\tinherited: t('nextcloud-vue', 'A grant on a record this one hangs under'),\n\t\t\t}\n\t\t\treturn labels[source] || source\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-permissions-tab {\n\tpadding: 12px;\n}\n\n.cn-object-permissions-tab__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-object-permissions-tab__table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n}\n\n.cn-object-permissions-tab__table th,\n.cn-object-permissions-tab__table td {\n\ttext-align: start;\n\tpadding: 4px 8px;\n\tborder-bottom: 1px solid var(--color-border);\n\tvertical-align: top;\n}\n\n.cn-object-permissions-tab__right,\n.cn-object-permissions-tab__source {\n\tdisplay: block;\n}\n\n.cn-object-permissions-tab__hint {\n\tdisplay: block;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-object-permissions-tab__unreadable {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n}\n\n.cn-object-permissions-tab__enforcement,\n.cn-object-permissions-tab__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-permissions-tab__history,\n.cn-object-permissions-tab__fields {\n\tmargin-block-start: 24px;\n\tborder-top: 1px solid var(--color-border);\n\tpadding-block-start: 12px;\n}\n\n.cn-object-permissions-tab__moment {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-block-end: 8px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|