@conduction/nextcloud-vue 2.75.0 → 2.77.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.
Files changed (158) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +6 -0
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +42 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +228 -203
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
  14. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
  15. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
  16. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
  17. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
  18. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
  19. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
  20. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +41 -4
  23. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +24 -33
  27. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  30. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
  31. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
  34. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  35. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  37. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
  38. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
  40. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +242 -19
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -20
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
  46. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  47. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
  48. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  49. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
  50. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  51. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
  52. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  53. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
  54. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
  56. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -3
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
  61. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  64. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
  65. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  66. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
  67. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  69. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  70. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  71. package/dist/esm/composables/useListView.js +4 -0
  72. package/dist/esm/composables/useListView.js.map +1 -1
  73. package/dist/esm/composables/useObjectLock.js +41 -10
  74. package/dist/esm/composables/useObjectLock.js.map +1 -1
  75. package/dist/esm/index.js +3 -1
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/l10n/en.json.js +31 -1
  78. package/dist/esm/l10n/en.json.js.map +1 -1
  79. package/dist/esm/l10n/nl.json.js +32 -2
  80. package/dist/esm/l10n/nl.json.js.map +1 -1
  81. package/dist/esm/nextcloud-vue.css +376 -75
  82. package/dist/esm/store/plugins/interactions.js +20 -10
  83. package/dist/esm/store/plugins/interactions.js.map +1 -1
  84. package/dist/esm/store/useObjectStore.js +54 -9
  85. package/dist/esm/store/useObjectStore.js.map +1 -1
  86. package/dist/esm/utils/builtInFormatters.js +15 -7
  87. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  88. package/dist/esm/utils/groupAutocomplete.js +63 -1
  89. package/dist/esm/utils/groupAutocomplete.js.map +1 -1
  90. package/dist/esm/utils/lensAvailability.js +90 -0
  91. package/dist/esm/utils/lensAvailability.js.map +1 -0
  92. package/dist/esm/utils/objectSignals.js +44 -0
  93. package/dist/esm/utils/objectSignals.js.map +1 -0
  94. package/dist/esm/utils/personalLenses.js +13 -6
  95. package/dist/esm/utils/personalLenses.js.map +1 -1
  96. package/dist/esm/utils/recordInteractions.js +31 -13
  97. package/dist/esm/utils/recordInteractions.js.map +1 -1
  98. package/dist/esm/utils/resolveFilterTokens.js +46 -3
  99. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  100. package/dist/esm/utils/schema.js +1 -1
  101. package/dist/esm/utils/schema.js.map +1 -1
  102. package/dist/esm/utils/sentinelTokens.js +4 -4
  103. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  104. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  105. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  106. package/dist/esm/utils/widgetVisibility.js +46 -1
  107. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  108. package/dist/nextcloud-vue.cjs.js +4732 -2990
  109. package/dist/nextcloud-vue.cjs.js.map +2 -2
  110. package/dist/nextcloud-vue.css +376 -75
  111. package/l10n/en.json +31 -1
  112. package/l10n/nl.json +32 -2
  113. package/package.json +1 -1
  114. package/src/components/CnAppNav/CnAppNav.vue +44 -5
  115. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  116. package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
  117. package/src/components/CnCellRenderer/CnCellRenderer.vue +56 -1
  118. package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
  119. package/src/components/CnCredentials/CnCredentials.vue +17 -16
  120. package/src/components/CnDataTable/CnDataTable.md +9 -0
  121. package/src/components/CnDataTable/CnDataTable.vue +80 -20
  122. package/src/components/CnDetailPage/CnDetailPage.vue +18 -32
  123. package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
  124. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
  125. package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
  126. package/src/components/CnIndexPage/CnIndexPage.md +51 -0
  127. package/src/components/CnIndexPage/CnIndexPage.vue +261 -35
  128. package/src/components/CnIndexPage/selfModeActions.js +89 -13
  129. package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
  130. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  131. package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
  132. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
  133. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
  134. package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
  135. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
  136. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  137. package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
  138. package/src/composables/useListView.js +4 -0
  139. package/src/composables/useObjectLock.js +41 -10
  140. package/src/css/look-board-detail.css +40 -2
  141. package/src/css/look-board-index.css +35 -1
  142. package/src/css/table.css +82 -17
  143. package/src/index.js +3 -1
  144. package/src/schemas/app-manifest-v2.schema.json +11 -7
  145. package/src/store/plugins/interactions.js +20 -10
  146. package/src/store/useObjectStore.js +54 -9
  147. package/src/utils/builtInFormatters.js +15 -7
  148. package/src/utils/groupAutocomplete.js +75 -0
  149. package/src/utils/index.js +2 -1
  150. package/src/utils/lensAvailability.js +86 -0
  151. package/src/utils/objectSignals.js +41 -0
  152. package/src/utils/personalLenses.js +13 -6
  153. package/src/utils/recordInteractions.js +31 -13
  154. package/src/utils/resolveFilterTokens.js +45 -2
  155. package/src/utils/schema.js +1 -1
  156. package/src/utils/sentinelTokens.js +4 -4
  157. package/src/utils/validateManifestV2.compiled.js +2 -2
  158. package/src/utils/widgetVisibility.js +45 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useListView.js","sources":["../../../src/composables/useListView.js"],"sourcesContent":["import { computed, isRef, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useObjectStore } from '../store/index.js'\n\n/**\n * Composable for managing list view state with full objectStore integration.\n *\n * When called with an `objectType` string, connects to the objectStore and handles\n * schema loading, collection fetching, sidebar wiring, and all event handlers\n * automatically. Everything a `CnIndexPage`-based list view needs is returned\n * directly — no additional computed properties or methods required in the component.\n *\n * Backward-compatible: existing `useListView(options)` and `useListView()` calls\n * continue to work without modification.\n *\n * @param {string|Function|object} [objectTypeOrOptions] Object type slug (new API), a getter or ref returning one (the list follows it when it changes), or a legacy options object\n * @param {object} [options] Options (new API only)\n * @param {object|null} [options.objectStore] Custom object store instance (from createObjectStore). When provided, uses this store instead of the default useObjectStore(). Required when the app uses createObjectStore with a custom store ID.\n * @param {object|null} [options.sidebarState] Sidebar state object from `inject('sidebarState')`. When provided, the composable wires and unwires the sidebar automatically on mount/unmount.\n * @param {number} [options.defaultPageSize] Default `_limit` sent to the API\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {object} [options.defaultSort] Default sort applied on mount e.g. `{ key: 'createdAt', order: 'desc' }`\n * @param {Array<{key: string, order: 'asc'|'desc'}>} [options.defaultSortKeys] Default MULTI-column sort applied on mount (ordered priority list). Takes precedence over `defaultSort` when both are given; used e.g. to restore a sort persisted in the route query.\n * @param {Array<string>|null} [options.defaultVisibleColumns] Initial visible-column key set. Seeds the sidebar's Columns tab with the curated default (e.g. a manifest `columns` list) so toggles add/remove from it. Omit (or `null`) for schema-driven tables where every column starts visible.\n * @param {object} [options.defaultActiveFilters] Initial facet-filter map, e.g. to restore filters persisted in the route query on mount.\n * @param {string} [options.defaultSearchTerm] Initial search term, e.g. to restore `_search` from the route query on mount.\n * @param {object|(() => object)} [options.fixedFilters] A filter map (or getter returning one) merged into every fetch AFTER the user's facet filters, so the fixed entries always win. Used e.g. by `CnIndexPage` to apply a route-param-scoped `pages[].config.filter`. Default `{}` — omitting it is behaviourally identical to before.\n * @param {Array<string>|(() => Array<string>)} [options.extend] OpenRegister `_extend[]` values (or a getter returning them) forwarded on every fetch, e.g. `['calculations']` so a schema's `materialise: false` calculations are evaluated and can be shown as columns. Default `[]` — omitting it sends no `_extend` at all, exactly as before.\n * @return {object} Reactive state and event handlers\n *\n * @example\n * // New API — minimal\n * const { schema, objects, loading, pagination,\n * onSearch, onSort, onFilterChange, onPageChange, refresh } = useListView('client')\n *\n * @example\n * // New API — with sidebar wiring\n * const list = useListView('client', {\n * sidebarState: inject('sidebarState', null),\n * })\n *\n * @example\n * // Legacy API — still works\n * const { searchTerm, filters, onSearchInput, toggleSort } = useListView({\n * objectType: 'client',\n * fetchFn: (type, params) => objectStore.fetchCollection(type, params),\n * })\n */\nexport function useListView(objectTypeOrOptions, options) {\n\t// Backward compat: if first arg is an object or absent, delegate to legacy implementation\n\tif (!objectTypeOrOptions || (typeof objectTypeOrOptions === 'object' && !isRef(objectTypeOrOptions))) {\n\t\treturn useLegacyListView(objectTypeOrOptions || {})\n\t}\n\n\t// ── New API ──────────────────────────────────────────────────────────\n\t// A string, a ref or a getter: a getter lets the page switch object type\n\t// (a folder carrying its own schema) without re-creating the list.\n\tconst typeSource = objectTypeOrOptions\n\tconst isDynamicType = typeof typeSource !== 'string'\n\tconst getType = () => (isRef(typeSource) ? typeSource.value : (typeof typeSource === 'function' ? typeSource() : typeSource))\n\tconst opts = options || {}\n\tconst sidebarState = opts.sidebarState || null\n\n\tconst objectStore = opts.objectStore || useObjectStore()\n\n\t// ── State refs ───────────────────────────────────────────────────────\n\tconst schema = ref(null)\n\tconst searchTerm = ref(opts.defaultSearchTerm || '')\n\t// `sortKeys` is the source of truth (ordered multi-column priority list);\n\t// `sortKey`/`sortOrder` are kept as a primary-entry mirror so every\n\t// existing reader of the pre-multi-sort refs keeps working unchanged.\n\tconst initialSortKeys = Array.isArray(opts.defaultSortKeys) && opts.defaultSortKeys.length > 0\n\t\t? opts.defaultSortKeys\n\t\t: (opts.defaultSort?.key ? [{ key: opts.defaultSort.key, order: opts.defaultSort.order || 'asc' }] : [])\n\tconst sortKeys = ref(initialSortKeys)\n\tconst sortKey = ref(initialSortKeys[0]?.key || null)\n\tconst sortOrder = ref(initialSortKeys[0]?.order || 'asc')\n\tconst activeFilters = ref((opts.defaultActiveFilters && typeof opts.defaultActiveFilters === 'object') ? opts.defaultActiveFilters : {})\n\t// Seed the visible-column set to the configured columns so the sidebar's\n\t// Columns tab reflects the curated default and toggles add/remove from it.\n\t// `null` (no seed) means \"all columns visible\" (schema-driven tables).\n\tconst visibleColumns = ref(opts.defaultVisibleColumns ?? null)\n\tconst pageSize = ref(opts.defaultPageSize || 20)\n\n\t// ── Computed refs from the store ─────────────────────────────────────\n\tconst objects = computed(() => objectStore.collections[getType()] || [])\n\t// True from creation until the mount sequence below has issued its first\n\t// fetch. The store's own flag only goes up once `fetchCollection` runs, and\n\t// `onMounted` awaits `fetchSchema()` first — so for the length of that round\n\t// trip the list was \"not loading\" with zero rows, and every consumer\n\t// rendered its empty state (\"No log entries to show\") before any data had\n\t// been asked for. Folded into `loading` rather than exposed separately so\n\t// existing consumers get the fix without touching their templates.\n\tconst bootstrapping = ref(true)\n\tconst loading = computed(() => bootstrapping.value || objectStore.loading[getType()] || false)\n\tconst pagination = computed(() => objectStore.pagination[getType()] || { total: 0, page: 1, pages: 1, limit: 20 })\n\t// Facets are computed by the platform over the whole query rather than the\n\t// loaded page, so anything that needs the complete set of values for a\n\t// field (a folder pane grouping by it, say) reads them here rather than\n\t// deriving them from `objects`, which is only the current page.\n\tconst facets = computed(() => objectStore.facets[getType()] || {})\n\t// Outcome of the latest `refresh()`: the store's error for it, or null once\n\t// it succeeded or a later fetch stored new rows. The store keeps the previous\n\t// rows on a failed fetch, so this is how a consumer tells stale rows from\n\t// current ones.\n\tconst error = ref(null)\n\tlet refreshSeq = 0\n\tlet pendingRefreshes = 0\n\n\tlet searchTimeout = null\n\n\t// ── Param construction ───────────────────────────────────────────────\n\n\t/**\n\t * Resolve `opts.fixedFilters` to a plain map. Accepts a plain object OR a\n\t * function/getter returning one (so callers can derive it from reactive\n\t * sources — e.g. route params — and have it re-read on every fetch).\n\t *\n\t * @return {object} The fixed-filter map (may be empty).\n\t */\n\tfunction resolveFixedFilters() {\n\t\tconst f = typeof opts.fixedFilters === 'function' ? opts.fixedFilters() : opts.fixedFilters\n\t\treturn (f && typeof f === 'object') ? f : {}\n\t}\n\n\t/**\n\t * Resolve `opts.extend` to a list of non-empty strings.\n\t *\n\t * Accepts an array or a getter returning one, so a caller can re-scope it\n\t * between fetches the way `fixedFilters` allows.\n\t *\n\t * @return {Array<string>} The `_extend[]` values, possibly empty.\n\t */\n\tfunction resolveExtend() {\n\t\tconst e = typeof opts.extend === 'function' ? opts.extend() : opts.extend\n\t\tif (!Array.isArray(e)) {\n\t\t\treturn []\n\t\t}\n\t\treturn e.filter((v) => typeof v === 'string' && v !== '')\n\t}\n\n\t/**\n\t * Build API fetch params from current reactive state.\n\t *\n\t * @param {number} page Page number to request\n\t * @return {object} Params object ready to pass to fetchCollection\n\t */\n\tfunction buildParams(page) {\n\t\tconst params = { _limit: pageSize.value, _page: page }\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKeys.value.length > 0) {\n\t\t\t// Object key insertion order = sort priority order, matching\n\t\t\t// OpenRegister's `_order` format exactly (`ObjectsController::\n\t\t\t// normalizeOrderParameter` / `MagicMapper`'s ordering loop). A\n\t\t\t// single active key produces byte-identical output to before.\n\t\t\tparams._order = Object.fromEntries(sortKeys.value.map((k) => [k.key, k.order]))\n\t\t}\n\n\t\tfor (const [key, values] of Object.entries(activeFilters.value)) {\n\t\t\tif (values && values.length > 0) {\n\t\t\t\t// Single-value arrays are unwrapped to scalar params\n\t\t\t\tparams[key] = values.length === 1 ? values[0] : values\n\t\t\t}\n\t\t}\n\n\t\t// Fixed filters (e.g. a route-param-scoped `pages[].config.filter`) are\n\t\t// merged LAST so they always win over a colliding facet `activeFilter`.\n\t\t// Repeated `_extend[]`, serialized by the shared query builder. Set\n\t\t// before the fixed filters so an explicit `_extend` in a filter map\n\t\t// still wins, matching every other key's precedence here.\n\t\tconst extend = resolveExtend()\n\t\tif (extend.length > 0) {\n\t\t\tparams._extend = extend\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(resolveFixedFilters())) {\n\t\t\tif (value !== undefined && value !== null && value !== '') {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\t// ── Fetch ────────────────────────────────────────────────────────────\n\n\t/**\n\t * Fetch the collection using current state params and update sidebar facet data.\n\t * Only the most recently started refresh sets `error`, so an older response\n\t * settling late cannot override a newer one's outcome.\n\t *\n\t * @param {number} [page] Page to fetch\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh(page = 1) {\n\t\tconst seq = ++refreshSeq\n\t\t// This call's own result. `errors[getType()]` is shared by every call\n\t\t// for the type and is not cleared on success, so it cannot say which\n\t\t// request failed; it is only the fallback for a store that ignores\n\t\t// `options.outcome`.\n\t\tconst outcome = {}\n\t\tpendingRefreshes++\n\t\ttry {\n\t\t\tawait objectStore.fetchCollection(getType(), buildParams(page), { outcome })\n\t\t} catch (e) {\n\t\t\tif (seq === refreshSeq) {\n\t\t\t\terror.value = e\n\t\t\t}\n\t\t\tthrow e\n\t\t} finally {\n\t\t\tpendingRefreshes--\n\t\t}\n\t\tif (seq === refreshSeq) {\n\t\t\terror.value = ('error' in outcome) ? outcome.error : (objectStore.errors?.[getType()] || null)\n\t\t}\n\t}\n\n\t// A fetch outside `refresh()` (e.g. the live-updates refetch) that stores\n\t// new rows recovers the list from an earlier failure. Rows written while\n\t// a refresh is pending are left to that refresh, which decides `error`.\n\twatch(\n\t\t() => objectStore.collections[getType()],\n\t\t() => {\n\t\t\tif (pendingRefreshes === 0) {\n\t\t\t\terror.value = null\n\t\t\t}\n\t\t},\n\t\t{ flush: 'sync' },\n\t)\n\n\t// ── Event handlers ───────────────────────────────────────────────────\n\n\t/**\n\t * Handle search input. Debounced by `options.debounceMs` (default 300 ms).\n\t *\n\t * @param {string} value New search string\n\t */\n\tfunction onSearch(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => refresh(1), opts.debounceMs || 300)\n\t}\n\n\t/**\n\t * Handle sort change. Updates sort state and triggers refresh.\n\t *\n\t * Accepts CnDataTable's extended `sort` event payload: `keys` (the full\n\t * ordered multi-column list) is used when present; a legacy caller\n\t * passing only `{ key, order }` (no `keys`) still works exactly as\n\t * before — it's normalised to a single-entry `sortKeys` list.\n\t *\n\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort New sort definition.\n\t */\n\tfunction onSort({ key, order, keys }) {\n\t\tconst nextKeys = Array.isArray(keys) ? keys : (key ? [{ key, order: order || 'asc' }] : [])\n\t\tsortKeys.value = nextKeys\n\t\tsortKey.value = nextKeys[0]?.key ?? null\n\t\tsortOrder.value = nextKeys[0]?.order ?? 'asc'\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle filter change for a single key. Empty arrays remove the key.\n\t *\n\t * @param {string} key Filter key (maps to API param name)\n\t * @param {Array} values Selected filter values\n\t */\n\tfunction onFilterChange(key, values) {\n\t\tif (!values || values.length === 0) {\n\t\t\tconst updated = { ...activeFilters.value }\n\t\t\tdelete updated[key]\n\t\t\tactiveFilters.value = updated\n\t\t} else {\n\t\t\tactiveFilters.value = { ...activeFilters.value, [key]: values }\n\t\t}\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle page navigation.\n\t *\n\t * @param {number} page Page number to navigate to\n\t */\n\tfunction onPageChange(page) {\n\t\trefresh(page)\n\t}\n\n\t/**\n\t * Handle page-size change. Resets to page 1.\n\t *\n\t * @param {number} size New page size\n\t */\n\tfunction onPageSizeChange(size) {\n\t\tpageSize.value = size\n\t\trefresh(1)\n\t}\n\n\t// ── Sidebar wiring ───────────────────────────────────────────────────\n\n\tfunction setupSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = true\n\t\tsidebarState.schema = schema.value\n\t\tsidebarState.searchValue = searchTerm.value\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.onSearch = onSearch\n\t\tsidebarState.onColumnsChange = (cols) => {\n\t\t\tvisibleColumns.value = cols\n\t\t}\n\t\tsidebarState.onFilterChange = ({ key, values }) => onFilterChange(key, values)\n\t}\n\n\tfunction teardownSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = false\n\t\tsidebarState.schema = null\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.facetData = {}\n\t\tsidebarState.onSearch = null\n\t\tsidebarState.onColumnsChange = null\n\t\tsidebarState.onFilterChange = null\n\t}\n\n\t// Push facet data to sidebar after each store update\n\tif (sidebarState) {\n\t\twatch(\n\t\t\t() => objectStore.facets[getType()],\n\t\t\t(facets) => {\n\t\t\t\tsidebarState.facetData = facets || {}\n\t\t\t},\n\t\t)\n\t}\n\n\t// A dynamic object type that changes starts that type's list from page 1\n\t// with its own schema.\n\tif (isDynamicType) {\n\t\twatch(getType, async (next, prev) => {\n\t\t\tif (!next || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tschema.value = null\n\t\t\tschema.value = await objectStore.fetchSchema(next)\n\t\t\tawait refresh(1)\n\t\t})\n\t}\n\n\t// ── Lifecycle ────────────────────────────────────────────────────────\n\n\tonMounted(async () => {\n\t\ttry {\n\t\t\tschema.value = await objectStore.fetchSchema(getType())\n\t\t\tif (sidebarState) {\n\t\t\t\tsetupSidebar()\n\t\t\t}\n\t\t\tawait refresh(1)\n\t\t} finally {\n\t\t\t// Hand the loading state back to the store either way: a schema\n\t\t\t// fetch that rejects must not leave the list spinning forever.\n\t\t\tbootstrapping.value = false\n\t\t}\n\t})\n\n\tonBeforeUnmount(() => {\n\t\tclearTimeout(searchTimeout)\n\t\tteardownSidebar()\n\t})\n\n\t// ── Return value ─────────────────────────────────────────────────────\n\n\treturn {\n\t\t// Store-derived\n\t\tschema,\n\t\tobjects,\n\t\tloading,\n\t\tpagination,\n\t\tfacets,\n\t\terror,\n\t\t// Local state\n\t\tsearchTerm,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tsortKeys,\n\t\tactiveFilters,\n\t\tvisibleColumns,\n\t\tpageSize,\n\t\t// Event handlers\n\t\tonSearch,\n\t\tonSort,\n\t\tonFilterChange,\n\t\tonPageChange,\n\t\tonPageSizeChange,\n\t\t// Explicit fetch\n\t\trefresh,\n\t\t// The query the list sends (search, sort, facet filters, fixed filters),\n\t\t// built from the current state, for an export that must follow the list.\n\t\tbuildParams,\n\t}\n}\n\n// ── Legacy implementation ─────────────────────────────────────────────────────\n\n/**\n * Legacy `useListView(options)` implementation.\n * Preserved verbatim for backward compatibility.\n *\n * @param {object} options Legacy options object\n * @param {string} [options.objectType] The registered object type slug\n * @param {(type: string, params: object) => Promise<Array<object>>} [options.fetchFn] Function to call: (type, params) => Promise<Array>\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {number} [options.pageSize] Default page size\n * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }\n * @return {object} Reactive state and methods\n */\nfunction useLegacyListView(options) {\n\tconst searchTerm = ref('')\n\tconst filters = ref({})\n\tconst sortKey = ref(options.defaultSort?.key || null)\n\tconst sortOrder = ref(options.defaultSort?.order || 'asc')\n\tconst currentPage = ref(1)\n\tconst pageSize = ref(options.pageSize || 20)\n\n\tlet searchTimeout = null\n\n\tfunction buildFetchParams() {\n\t\tconst params = {\n\t\t\t_limit: pageSize.value,\n\t\t\t_page: currentPage.value,\n\t\t}\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKey.value) {\n\t\t\tparams._order = { [sortKey.value]: sortOrder.value }\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(filters.value)) {\n\t\t\tif (value !== null && value !== '' && value !== false) {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\tasync function fetchData(page) {\n\t\tif (page !== undefined) {\n\t\t\tcurrentPage.value = page\n\t\t}\n\t\tconst params = buildFetchParams()\n\t\tif (options.fetchFn) {\n\t\t\treturn options.fetchFn(options.objectType, params)\n\t\t}\n\t}\n\n\tfunction onSearchInput(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)\n\t}\n\n\tfunction toggleSort(key) {\n\t\tif (sortKey.value === key) {\n\t\t\tif (sortOrder.value === 'asc') {\n\t\t\t\tsortOrder.value = 'desc'\n\t\t\t} else {\n\t\t\t\tsortKey.value = null\n\t\t\t\tsortOrder.value = 'asc'\n\t\t\t}\n\t\t} else {\n\t\t\tsortKey.value = key\n\t\t\tsortOrder.value = 'asc'\n\t\t}\n\t\tfetchData(1)\n\t}\n\n\tfunction setFilter(key, value) {\n\t\tfilters.value = { ...filters.value, [key]: value }\n\t\tfetchData(1)\n\t}\n\n\tfunction clearAllFilters() {\n\t\tsearchTerm.value = ''\n\t\tfilters.value = {}\n\t\tsortKey.value = options.defaultSort?.key || null\n\t\tsortOrder.value = options.defaultSort?.order || 'asc'\n\t\tfetchData(1)\n\t}\n\n\tfunction goToPage(page) {\n\t\tcurrentPage.value = page\n\t\tfetchData()\n\t}\n\n\tonBeforeUnmount(() => clearTimeout(searchTimeout))\n\n\treturn {\n\t\tsearchTerm,\n\t\tfilters,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tcurrentPage,\n\t\tpageSize,\n\t\tonSearchInput,\n\t\ttoggleSort,\n\t\tsetFilter,\n\t\tclearAllFilters,\n\t\tgoToPage,\n\t\tfetch: fetchData,\n\t\tbuildFetchParams,\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useListView.js","sources":["../../../src/composables/useListView.js"],"sourcesContent":["import { computed, isRef, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useObjectStore } from '../store/index.js'\n\n/**\n * Composable for managing list view state with full objectStore integration.\n *\n * When called with an `objectType` string, connects to the objectStore and handles\n * schema loading, collection fetching, sidebar wiring, and all event handlers\n * automatically. Everything a `CnIndexPage`-based list view needs is returned\n * directly — no additional computed properties or methods required in the component.\n *\n * Backward-compatible: existing `useListView(options)` and `useListView()` calls\n * continue to work without modification.\n *\n * @param {string|Function|object} [objectTypeOrOptions] Object type slug (new API), a getter or ref returning one (the list follows it when it changes), or a legacy options object\n * @param {object} [options] Options (new API only)\n * @param {object|null} [options.objectStore] Custom object store instance (from createObjectStore). When provided, uses this store instead of the default useObjectStore(). Required when the app uses createObjectStore with a custom store ID.\n * @param {object|null} [options.sidebarState] Sidebar state object from `inject('sidebarState')`. When provided, the composable wires and unwires the sidebar automatically on mount/unmount.\n * @param {number} [options.defaultPageSize] Default `_limit` sent to the API\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {object} [options.defaultSort] Default sort applied on mount e.g. `{ key: 'createdAt', order: 'desc' }`\n * @param {Array<{key: string, order: 'asc'|'desc'}>} [options.defaultSortKeys] Default MULTI-column sort applied on mount (ordered priority list). Takes precedence over `defaultSort` when both are given; used e.g. to restore a sort persisted in the route query.\n * @param {Array<string>|null} [options.defaultVisibleColumns] Initial visible-column key set. Seeds the sidebar's Columns tab with the curated default (e.g. a manifest `columns` list) so toggles add/remove from it. Omit (or `null`) for schema-driven tables where every column starts visible.\n * @param {object} [options.defaultActiveFilters] Initial facet-filter map, e.g. to restore filters persisted in the route query on mount.\n * @param {string} [options.defaultSearchTerm] Initial search term, e.g. to restore `_search` from the route query on mount.\n * @param {object|(() => object)} [options.fixedFilters] A filter map (or getter returning one) merged into every fetch AFTER the user's facet filters, so the fixed entries always win. Used e.g. by `CnIndexPage` to apply a route-param-scoped `pages[].config.filter`. Default `{}` — omitting it is behaviourally identical to before.\n * @param {Array<string>|(() => Array<string>)} [options.extend] OpenRegister `_extend[]` values (or a getter returning them) forwarded on every fetch, e.g. `['calculations']` so a schema's `materialise: false` calculations are evaluated and can be shown as columns. Default `[]` — omitting it sends no `_extend` at all, exactly as before.\n * @return {object} Reactive state and event handlers\n *\n * @example\n * // New API — minimal\n * const { schema, objects, loading, pagination,\n * onSearch, onSort, onFilterChange, onPageChange, refresh } = useListView('client')\n *\n * @example\n * // New API — with sidebar wiring\n * const list = useListView('client', {\n * sidebarState: inject('sidebarState', null),\n * })\n *\n * @example\n * // Legacy API — still works\n * const { searchTerm, filters, onSearchInput, toggleSort } = useListView({\n * objectType: 'client',\n * fetchFn: (type, params) => objectStore.fetchCollection(type, params),\n * })\n */\nexport function useListView(objectTypeOrOptions, options) {\n\t// Backward compat: if first arg is an object or absent, delegate to legacy implementation\n\tif (!objectTypeOrOptions || (typeof objectTypeOrOptions === 'object' && !isRef(objectTypeOrOptions))) {\n\t\treturn useLegacyListView(objectTypeOrOptions || {})\n\t}\n\n\t// ── New API ──────────────────────────────────────────────────────────\n\t// A string, a ref or a getter: a getter lets the page switch object type\n\t// (a folder carrying its own schema) without re-creating the list.\n\tconst typeSource = objectTypeOrOptions\n\tconst isDynamicType = typeof typeSource !== 'string'\n\tconst getType = () => (isRef(typeSource) ? typeSource.value : (typeof typeSource === 'function' ? typeSource() : typeSource))\n\tconst opts = options || {}\n\tconst sidebarState = opts.sidebarState || null\n\n\tconst objectStore = opts.objectStore || useObjectStore()\n\n\t// ── State refs ───────────────────────────────────────────────────────\n\tconst schema = ref(null)\n\tconst searchTerm = ref(opts.defaultSearchTerm || '')\n\t// `sortKeys` is the source of truth (ordered multi-column priority list);\n\t// `sortKey`/`sortOrder` are kept as a primary-entry mirror so every\n\t// existing reader of the pre-multi-sort refs keeps working unchanged.\n\tconst initialSortKeys = Array.isArray(opts.defaultSortKeys) && opts.defaultSortKeys.length > 0\n\t\t? opts.defaultSortKeys\n\t\t: (opts.defaultSort?.key ? [{ key: opts.defaultSort.key, order: opts.defaultSort.order || 'asc' }] : [])\n\tconst sortKeys = ref(initialSortKeys)\n\tconst sortKey = ref(initialSortKeys[0]?.key || null)\n\tconst sortOrder = ref(initialSortKeys[0]?.order || 'asc')\n\tconst activeFilters = ref((opts.defaultActiveFilters && typeof opts.defaultActiveFilters === 'object') ? opts.defaultActiveFilters : {})\n\t// Seed the visible-column set to the configured columns so the sidebar's\n\t// Columns tab reflects the curated default and toggles add/remove from it.\n\t// `null` (no seed) means \"all columns visible\" (schema-driven tables).\n\tconst visibleColumns = ref(opts.defaultVisibleColumns ?? null)\n\tconst pageSize = ref(opts.defaultPageSize || 20)\n\n\t// ── Computed refs from the store ─────────────────────────────────────\n\tconst objects = computed(() => objectStore.collections[getType()] || [])\n\t// True from creation until the mount sequence below has issued its first\n\t// fetch. The store's own flag only goes up once `fetchCollection` runs, and\n\t// `onMounted` awaits `fetchSchema()` first — so for the length of that round\n\t// trip the list was \"not loading\" with zero rows, and every consumer\n\t// rendered its empty state (\"No log entries to show\") before any data had\n\t// been asked for. Folded into `loading` rather than exposed separately so\n\t// existing consumers get the fix without touching their templates.\n\tconst bootstrapping = ref(true)\n\tconst loading = computed(() => bootstrapping.value || objectStore.loading[getType()] || false)\n\tconst pagination = computed(() => objectStore.pagination[getType()] || { total: 0, page: 1, pages: 1, limit: 20 })\n\t// Facets are computed by the platform over the whole query rather than the\n\t// loaded page, so anything that needs the complete set of values for a\n\t// field (a folder pane grouping by it, say) reads them here rather than\n\t// deriving them from `objects`, which is only the current page.\n\tconst facets = computed(() => objectStore.facets[getType()] || {})\n\t// Personal-lens reports of the latest response (`{ recent: { available, reason } }`).\n\t// @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-a-list-response-carries-the-report-of-the-lenses-it-was-asked-for\n\tconst lenses = computed(() => (objectStore.lenses && objectStore.lenses[getType()]) || {})\n\t// Outcome of the latest `refresh()`: the store's error for it, or null once\n\t// it succeeded or a later fetch stored new rows. The store keeps the previous\n\t// rows on a failed fetch, so this is how a consumer tells stale rows from\n\t// current ones.\n\tconst error = ref(null)\n\tlet refreshSeq = 0\n\tlet pendingRefreshes = 0\n\n\tlet searchTimeout = null\n\n\t// ── Param construction ───────────────────────────────────────────────\n\n\t/**\n\t * Resolve `opts.fixedFilters` to a plain map. Accepts a plain object OR a\n\t * function/getter returning one (so callers can derive it from reactive\n\t * sources — e.g. route params — and have it re-read on every fetch).\n\t *\n\t * @return {object} The fixed-filter map (may be empty).\n\t */\n\tfunction resolveFixedFilters() {\n\t\tconst f = typeof opts.fixedFilters === 'function' ? opts.fixedFilters() : opts.fixedFilters\n\t\treturn (f && typeof f === 'object') ? f : {}\n\t}\n\n\t/**\n\t * Resolve `opts.extend` to a list of non-empty strings.\n\t *\n\t * Accepts an array or a getter returning one, so a caller can re-scope it\n\t * between fetches the way `fixedFilters` allows.\n\t *\n\t * @return {Array<string>} The `_extend[]` values, possibly empty.\n\t */\n\tfunction resolveExtend() {\n\t\tconst e = typeof opts.extend === 'function' ? opts.extend() : opts.extend\n\t\tif (!Array.isArray(e)) {\n\t\t\treturn []\n\t\t}\n\t\treturn e.filter((v) => typeof v === 'string' && v !== '')\n\t}\n\n\t/**\n\t * Build API fetch params from current reactive state.\n\t *\n\t * @param {number} page Page number to request\n\t * @return {object} Params object ready to pass to fetchCollection\n\t */\n\tfunction buildParams(page) {\n\t\tconst params = { _limit: pageSize.value, _page: page }\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKeys.value.length > 0) {\n\t\t\t// Object key insertion order = sort priority order, matching\n\t\t\t// OpenRegister's `_order` format exactly (`ObjectsController::\n\t\t\t// normalizeOrderParameter` / `MagicMapper`'s ordering loop). A\n\t\t\t// single active key produces byte-identical output to before.\n\t\t\tparams._order = Object.fromEntries(sortKeys.value.map((k) => [k.key, k.order]))\n\t\t}\n\n\t\tfor (const [key, values] of Object.entries(activeFilters.value)) {\n\t\t\tif (values && values.length > 0) {\n\t\t\t\t// Single-value arrays are unwrapped to scalar params\n\t\t\t\tparams[key] = values.length === 1 ? values[0] : values\n\t\t\t}\n\t\t}\n\n\t\t// Fixed filters (e.g. a route-param-scoped `pages[].config.filter`) are\n\t\t// merged LAST so they always win over a colliding facet `activeFilter`.\n\t\t// Repeated `_extend[]`, serialized by the shared query builder. Set\n\t\t// before the fixed filters so an explicit `_extend` in a filter map\n\t\t// still wins, matching every other key's precedence here.\n\t\tconst extend = resolveExtend()\n\t\tif (extend.length > 0) {\n\t\t\tparams._extend = extend\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(resolveFixedFilters())) {\n\t\t\tif (value !== undefined && value !== null && value !== '') {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\t// ── Fetch ────────────────────────────────────────────────────────────\n\n\t/**\n\t * Fetch the collection using current state params and update sidebar facet data.\n\t * Only the most recently started refresh sets `error`, so an older response\n\t * settling late cannot override a newer one's outcome.\n\t *\n\t * @param {number} [page] Page to fetch\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh(page = 1) {\n\t\tconst seq = ++refreshSeq\n\t\t// This call's own result. `errors[getType()]` is shared by every call\n\t\t// for the type and is not cleared on success, so it cannot say which\n\t\t// request failed; it is only the fallback for a store that ignores\n\t\t// `options.outcome`.\n\t\tconst outcome = {}\n\t\tpendingRefreshes++\n\t\ttry {\n\t\t\tawait objectStore.fetchCollection(getType(), buildParams(page), { outcome })\n\t\t} catch (e) {\n\t\t\tif (seq === refreshSeq) {\n\t\t\t\terror.value = e\n\t\t\t}\n\t\t\tthrow e\n\t\t} finally {\n\t\t\tpendingRefreshes--\n\t\t}\n\t\tif (seq === refreshSeq) {\n\t\t\terror.value = ('error' in outcome) ? outcome.error : (objectStore.errors?.[getType()] || null)\n\t\t}\n\t}\n\n\t// A fetch outside `refresh()` (e.g. the live-updates refetch) that stores\n\t// new rows recovers the list from an earlier failure. Rows written while\n\t// a refresh is pending are left to that refresh, which decides `error`.\n\twatch(\n\t\t() => objectStore.collections[getType()],\n\t\t() => {\n\t\t\tif (pendingRefreshes === 0) {\n\t\t\t\terror.value = null\n\t\t\t}\n\t\t},\n\t\t{ flush: 'sync' },\n\t)\n\n\t// ── Event handlers ───────────────────────────────────────────────────\n\n\t/**\n\t * Handle search input. Debounced by `options.debounceMs` (default 300 ms).\n\t *\n\t * @param {string} value New search string\n\t */\n\tfunction onSearch(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => refresh(1), opts.debounceMs || 300)\n\t}\n\n\t/**\n\t * Handle sort change. Updates sort state and triggers refresh.\n\t *\n\t * Accepts CnDataTable's extended `sort` event payload: `keys` (the full\n\t * ordered multi-column list) is used when present; a legacy caller\n\t * passing only `{ key, order }` (no `keys`) still works exactly as\n\t * before — it's normalised to a single-entry `sortKeys` list.\n\t *\n\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort New sort definition.\n\t */\n\tfunction onSort({ key, order, keys }) {\n\t\tconst nextKeys = Array.isArray(keys) ? keys : (key ? [{ key, order: order || 'asc' }] : [])\n\t\tsortKeys.value = nextKeys\n\t\tsortKey.value = nextKeys[0]?.key ?? null\n\t\tsortOrder.value = nextKeys[0]?.order ?? 'asc'\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle filter change for a single key. Empty arrays remove the key.\n\t *\n\t * @param {string} key Filter key (maps to API param name)\n\t * @param {Array} values Selected filter values\n\t */\n\tfunction onFilterChange(key, values) {\n\t\tif (!values || values.length === 0) {\n\t\t\tconst updated = { ...activeFilters.value }\n\t\t\tdelete updated[key]\n\t\t\tactiveFilters.value = updated\n\t\t} else {\n\t\t\tactiveFilters.value = { ...activeFilters.value, [key]: values }\n\t\t}\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle page navigation.\n\t *\n\t * @param {number} page Page number to navigate to\n\t */\n\tfunction onPageChange(page) {\n\t\trefresh(page)\n\t}\n\n\t/**\n\t * Handle page-size change. Resets to page 1.\n\t *\n\t * @param {number} size New page size\n\t */\n\tfunction onPageSizeChange(size) {\n\t\tpageSize.value = size\n\t\trefresh(1)\n\t}\n\n\t// ── Sidebar wiring ───────────────────────────────────────────────────\n\n\tfunction setupSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = true\n\t\tsidebarState.schema = schema.value\n\t\tsidebarState.searchValue = searchTerm.value\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.onSearch = onSearch\n\t\tsidebarState.onColumnsChange = (cols) => {\n\t\t\tvisibleColumns.value = cols\n\t\t}\n\t\tsidebarState.onFilterChange = ({ key, values }) => onFilterChange(key, values)\n\t}\n\n\tfunction teardownSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = false\n\t\tsidebarState.schema = null\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.facetData = {}\n\t\tsidebarState.onSearch = null\n\t\tsidebarState.onColumnsChange = null\n\t\tsidebarState.onFilterChange = null\n\t}\n\n\t// Push facet data to sidebar after each store update\n\tif (sidebarState) {\n\t\twatch(\n\t\t\t() => objectStore.facets[getType()],\n\t\t\t(facets) => {\n\t\t\t\tsidebarState.facetData = facets || {}\n\t\t\t},\n\t\t)\n\t}\n\n\t// A dynamic object type that changes starts that type's list from page 1\n\t// with its own schema.\n\tif (isDynamicType) {\n\t\twatch(getType, async (next, prev) => {\n\t\t\tif (!next || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tschema.value = null\n\t\t\tschema.value = await objectStore.fetchSchema(next)\n\t\t\tawait refresh(1)\n\t\t})\n\t}\n\n\t// ── Lifecycle ────────────────────────────────────────────────────────\n\n\tonMounted(async () => {\n\t\ttry {\n\t\t\tschema.value = await objectStore.fetchSchema(getType())\n\t\t\tif (sidebarState) {\n\t\t\t\tsetupSidebar()\n\t\t\t}\n\t\t\tawait refresh(1)\n\t\t} finally {\n\t\t\t// Hand the loading state back to the store either way: a schema\n\t\t\t// fetch that rejects must not leave the list spinning forever.\n\t\t\tbootstrapping.value = false\n\t\t}\n\t})\n\n\tonBeforeUnmount(() => {\n\t\tclearTimeout(searchTimeout)\n\t\tteardownSidebar()\n\t})\n\n\t// ── Return value ─────────────────────────────────────────────────────\n\n\treturn {\n\t\t// Store-derived\n\t\tschema,\n\t\tobjects,\n\t\tloading,\n\t\tpagination,\n\t\tfacets,\n\t\tlenses,\n\t\terror,\n\t\t// Local state\n\t\tsearchTerm,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tsortKeys,\n\t\tactiveFilters,\n\t\tvisibleColumns,\n\t\tpageSize,\n\t\t// Event handlers\n\t\tonSearch,\n\t\tonSort,\n\t\tonFilterChange,\n\t\tonPageChange,\n\t\tonPageSizeChange,\n\t\t// Explicit fetch\n\t\trefresh,\n\t\t// The query the list sends (search, sort, facet filters, fixed filters),\n\t\t// built from the current state, for an export that must follow the list.\n\t\tbuildParams,\n\t}\n}\n\n// ── Legacy implementation ─────────────────────────────────────────────────────\n\n/**\n * Legacy `useListView(options)` implementation.\n * Preserved verbatim for backward compatibility.\n *\n * @param {object} options Legacy options object\n * @param {string} [options.objectType] The registered object type slug\n * @param {(type: string, params: object) => Promise<Array<object>>} [options.fetchFn] Function to call: (type, params) => Promise<Array>\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {number} [options.pageSize] Default page size\n * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }\n * @return {object} Reactive state and methods\n */\nfunction useLegacyListView(options) {\n\tconst searchTerm = ref('')\n\tconst filters = ref({})\n\tconst sortKey = ref(options.defaultSort?.key || null)\n\tconst sortOrder = ref(options.defaultSort?.order || 'asc')\n\tconst currentPage = ref(1)\n\tconst pageSize = ref(options.pageSize || 20)\n\n\tlet searchTimeout = null\n\n\tfunction buildFetchParams() {\n\t\tconst params = {\n\t\t\t_limit: pageSize.value,\n\t\t\t_page: currentPage.value,\n\t\t}\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKey.value) {\n\t\t\tparams._order = { [sortKey.value]: sortOrder.value }\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(filters.value)) {\n\t\t\tif (value !== null && value !== '' && value !== false) {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\tasync function fetchData(page) {\n\t\tif (page !== undefined) {\n\t\t\tcurrentPage.value = page\n\t\t}\n\t\tconst params = buildFetchParams()\n\t\tif (options.fetchFn) {\n\t\t\treturn options.fetchFn(options.objectType, params)\n\t\t}\n\t}\n\n\tfunction onSearchInput(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)\n\t}\n\n\tfunction toggleSort(key) {\n\t\tif (sortKey.value === key) {\n\t\t\tif (sortOrder.value === 'asc') {\n\t\t\t\tsortOrder.value = 'desc'\n\t\t\t} else {\n\t\t\t\tsortKey.value = null\n\t\t\t\tsortOrder.value = 'asc'\n\t\t\t}\n\t\t} else {\n\t\t\tsortKey.value = key\n\t\t\tsortOrder.value = 'asc'\n\t\t}\n\t\tfetchData(1)\n\t}\n\n\tfunction setFilter(key, value) {\n\t\tfilters.value = { ...filters.value, [key]: value }\n\t\tfetchData(1)\n\t}\n\n\tfunction clearAllFilters() {\n\t\tsearchTerm.value = ''\n\t\tfilters.value = {}\n\t\tsortKey.value = options.defaultSort?.key || null\n\t\tsortOrder.value = options.defaultSort?.order || 'asc'\n\t\tfetchData(1)\n\t}\n\n\tfunction goToPage(page) {\n\t\tcurrentPage.value = page\n\t\tfetchData()\n\t}\n\n\tonBeforeUnmount(() => clearTimeout(searchTimeout))\n\n\treturn {\n\t\tsearchTerm,\n\t\tfilters,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tcurrentPage,\n\t\tpageSize,\n\t\tonSearchInput,\n\t\ttoggleSort,\n\t\tsetFilter,\n\t\tclearAllFilters,\n\t\tgoToPage,\n\t\tfetch: fetchData,\n\t\tbuildFetchParams,\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { getCurrentUser } from '@nextcloud/auth';
2
2
  import axios__default from '@nextcloud/axios';
3
3
  import { generateUrl } from '@nextcloud/router';
4
4
  import { tryOnScopeDispose } from '@vueuse/core';
5
- import { computed, onMounted, onBeforeUnmount, unref } from 'vue';
5
+ import { computed, onMounted, onBeforeUnmount, toValue } from 'vue';
6
6
 
7
7
  // SPDX-License-Identifier: EUPL-1.2
8
8
  // Copyright (C) 2026 Conduction B.V.
@@ -47,14 +47,14 @@ class PermissionError extends Error {
47
47
  * other path that keeps `objectStore.objects[type][id]` fresh).
48
48
  *
49
49
  * @param {object} objectStore Pinia store instance (`useObjectStore()` result).
50
- * @param {string|import('vue').Ref<string>} register OR register slug.
51
- * @param {string|import('vue').Ref<string>} schema OR schema slug (also the GraphQL field name).
52
- * @param {string|import('vue').Ref<string>} id Object UUID.
50
+ * @param {string|import('vue').Ref<string>|function(): string} register OR register slug.
51
+ * @param {string|import('vue').Ref<string>|function(): string} schema OR schema slug (also the GraphQL field name).
52
+ * @param {string|import('vue').Ref<string>|function(): string} id Object UUID.
53
53
  * @param {object} [options] Optional config.
54
54
  * @param {boolean} [options.autoRenew] Renew the lock on a fixed interval while editing + visible.
55
55
  * @param {number} [options.renewIntervalMs] Renew every N ms (default 10 min).
56
56
  * @param {number} [options.lockDurationSec] Server-side TTL requested on acquire (default 30 min).
57
- * @param {string|import('vue').Ref<string>} [options.schemaSlug] The schema slug for the LOCK URL, when `schema` is the object-cache key rather than the slug (CnDetailPage passes `<register>-<schema>` as the key). Defaults to `schema`.
57
+ * @param {string|import('vue').Ref<string>|function(): string} [options.schemaSlug] The schema slug for the LOCK URL, when `schema` is the object-cache key rather than the slug (CnDetailPage passes `<register>-<schema>` as the key). Defaults to `schema`.
58
58
  * @return {{
59
59
  * locked: import('vue').ComputedRef<boolean>,
60
60
  * lockedByMe: import('vue').ComputedRef<boolean>,
@@ -63,6 +63,7 @@ class PermissionError extends Error {
63
63
  * acquire: () => Promise<void>,
64
64
  * release: () => Promise<void>
65
65
  * }} Reactive lock state + actions.
66
+ * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/object-lock/spec.md
66
67
  */
67
68
  function useObjectLock(objectStore, register, schema, id, options = {}) {
68
69
  const autoRenew = options.autoRenew !== false;
@@ -75,14 +76,23 @@ function useObjectLock(objectStore, register, schema, id, options = {}) {
75
76
  const isVisible = () => typeof document === 'undefined' || document.visibilityState !== 'hidden';
76
77
  let renewTimer = null;
77
78
 
79
+ /*
80
+ * 🔴 `toValue`, NOT `unref`. CnDetailPage passes GETTER FUNCTIONS
81
+ * (`() => props.objectId`) so the lock follows the route. `unref` returns a
82
+ * function untouched, so the getter's SOURCE was interpolated into the URL:
83
+ * every detail page sent `/api/objects/()=>pn/()=>Ct.schema||Bn/()=>Ct.objectId/lock`
84
+ * and got a 404, and the cache lookup used the function as its key, so the
85
+ * banner never saw a lock either. `toValue` reads a plain value, a ref and a
86
+ * getter alike.
87
+ */
78
88
  function readType() {
79
- return unref(schema)
89
+ return toValue(schema)
80
90
  }
81
91
  function readId() {
82
- return unref(id)
92
+ return toValue(id)
83
93
  }
84
94
  function readRegister() {
85
- return unref(register)
95
+ return toValue(register)
86
96
  }
87
97
 
88
98
  function readSelfLock() {
@@ -128,7 +138,22 @@ function useObjectLock(objectStore, register, schema, id, options = {}) {
128
138
  });
129
139
 
130
140
  function readSchemaSlug() {
131
- return unref(options.schemaSlug ?? schema)
141
+ return toValue(options.schemaSlug ?? schema)
142
+ }
143
+
144
+ /**
145
+ * Whether register, schema slug and object id are all known.
146
+ *
147
+ * A detail page mounts before its route params or object have resolved.
148
+ * Without this a lock call would go out with `undefined` in the path, a
149
+ * request OpenRegister can only answer with a 404.
150
+ *
151
+ * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/object-lock/spec.md
152
+ * @return {boolean} True when a lock URL can be built.
153
+ */
154
+ function hasTarget() {
155
+ return [readRegister(), readSchemaSlug(), readId()]
156
+ .every((part) => typeof part === 'string' ? part !== '' : typeof part === 'number')
132
157
  }
133
158
 
134
159
  /**
@@ -177,6 +202,9 @@ function useObjectLock(objectStore, register, schema, id, options = {}) {
177
202
  }
178
203
 
179
204
  async function acquire() {
205
+ if (!hasTarget()) {
206
+ return
207
+ }
180
208
  try {
181
209
  await axios__default.post(endpoint(), { duration: lockDurationSec });
182
210
  } catch (e) {
@@ -209,6 +237,9 @@ function useObjectLock(objectStore, register, schema, id, options = {}) {
209
237
 
210
238
  async function release() {
211
239
  stopRenewTimer();
240
+ if (!hasTarget()) {
241
+ return
242
+ }
212
243
  try {
213
244
  await axios__default.post(unlockEndpoint());
214
245
  } catch (e) {
@@ -252,7 +283,7 @@ function useObjectLock(objectStore, register, schema, id, options = {}) {
252
283
  }
253
284
 
254
285
  function beaconRelease() {
255
- if (!lockedByMe.value) {
286
+ if (!lockedByMe.value || !hasTarget()) {
256
287
  return
257
288
  }
258
289
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"useObjectLock.js","sources":["../../../src/composables/useObjectLock.js"],"sourcesContent":["// SPDX-License-Identifier: EUPL-1.2\n// Copyright (C) 2026 Conduction B.V.\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { tryOnScopeDispose } from '@vueuse/core'\nimport { computed, onBeforeUnmount, onMounted, unref } from 'vue'\n\n/**\n * Thrown when an `acquire()` POST returns 409 (Conflict) or 423\n * (Locked). Carries the conflicting user info so the UI can render\n * a \"Locked by X\" banner without a second fetch.\n */\nexport class LockConflictError extends Error {\n\t/**\n\t * @param {string} message Human-readable description.\n\t * @param {object|null} info Lock metadata (`{ user, displayName, expiresAt }`) parsed from the response.\n\t */\n\tconstructor(message, info = null) {\n\t\tsuper(message)\n\t\tthis.name = 'LockConflictError'\n\t\tthis.lockedBy = info?.user ?? info?.displayName ?? null\n\t\tthis.expiresAt = info?.expiresAt ?? null\n\t}\n}\n\n/**\n * Thrown when an `acquire()` / `release()` call returns 401 / 403.\n * Consumers may fall back to \"edit without lock\" UX.\n */\nexport class PermissionError extends Error {\n\tconstructor(message) {\n\t\tsuper(message)\n\t\tthis.name = 'PermissionError'\n\t}\n}\n\n/**\n * useObjectLock — manage the OR pessimistic lock for a single object.\n *\n * Reactive lock state is **read from the object cache** populated by\n * `liveUpdatesPlugin`, NOT from a polling lock endpoint. This means\n * remote lock acquisitions are visible without explicit refetch as\n * long as the consumer also uses `useObjectSubscription` (or any\n * other path that keeps `objectStore.objects[type][id]` fresh).\n *\n * @param {object} objectStore Pinia store instance (`useObjectStore()` result).\n * @param {string|import('vue').Ref<string>} register OR register slug.\n * @param {string|import('vue').Ref<string>} schema OR schema slug (also the GraphQL field name).\n * @param {string|import('vue').Ref<string>} id Object UUID.\n * @param {object} [options] Optional config.\n * @param {boolean} [options.autoRenew] Renew the lock on a fixed interval while editing + visible.\n * @param {number} [options.renewIntervalMs] Renew every N ms (default 10 min).\n * @param {number} [options.lockDurationSec] Server-side TTL requested on acquire (default 30 min).\n * @param {string|import('vue').Ref<string>} [options.schemaSlug] The schema slug for the LOCK URL, when `schema` is the object-cache key rather than the slug (CnDetailPage passes `<register>-<schema>` as the key). Defaults to `schema`.\n * @return {{\n * locked: import('vue').ComputedRef<boolean>,\n * lockedByMe: import('vue').ComputedRef<boolean>,\n * lockedBy: import('vue').ComputedRef<string|null>,\n * expiresAt: import('vue').ComputedRef<Date|null>,\n * acquire: () => Promise<void>,\n * release: () => Promise<void>\n * }} Reactive lock state + actions.\n */\nexport function useObjectLock(objectStore, register, schema, id, options = {}) {\n\tconst autoRenew = options.autoRenew !== false\n\tconst renewIntervalMs = options.renewIntervalMs ?? 600000\n\tconst lockDurationSec = options.lockDurationSec ?? 1800\n\n\t// Use native document.visibilityState directly to avoid pinning a\n\t// minimum @vueuse/core version (`useDocumentVisibility` landed in a\n\t// later cut and isn't available everywhere).\n\tconst isVisible = () => typeof document === 'undefined' || document.visibilityState !== 'hidden'\n\tlet renewTimer = null\n\n\tfunction readType() {\n\t\treturn unref(schema)\n\t}\n\tfunction readId() {\n\t\treturn unref(id)\n\t}\n\tfunction readRegister() {\n\t\treturn unref(register)\n\t}\n\n\tfunction readSelfLock() {\n\t\tconst t = readType()\n\t\tconst i = readId()\n\t\tif (!t || !i) {\n\t\t\treturn null\n\t\t}\n\t\tconst obj = objectStore.objects?.[t]?.[i]\n\t\tconst self = obj?.['@self'] ?? obj\n\t\treturn self?.locked ?? null\n\t}\n\n\tconst locked = computed(() => {\n\t\tconst l = readSelfLock()\n\t\tif (!l) {\n\t\t\treturn false\n\t\t}\n\t\tif (l.expiresAt) {\n\t\t\tconst exp = new Date(l.expiresAt).getTime()\n\t\t\tif (Number.isFinite(exp) && exp <= Date.now()) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tconst lockedBy = computed(() => readSelfLock()?.user ?? readSelfLock()?.displayName ?? null)\n\n\tconst lockedByMe = computed(() => {\n\t\tconst me = getCurrentUser()?.uid\n\t\tconst lb = readSelfLock()?.user\n\t\treturn Boolean(me && lb && me === lb)\n\t})\n\n\tconst expiresAt = computed(() => {\n\t\tconst raw = readSelfLock()?.expiresAt\n\t\tif (!raw) {\n\t\t\treturn null\n\t\t}\n\t\tconst d = new Date(raw)\n\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t})\n\n\tfunction readSchemaSlug() {\n\t\treturn unref(options.schemaSlug ?? schema)\n\t}\n\n\t/**\n\t * The object's own URL, without the lock verb.\n\t *\n\t * 🔴 THE SLUG AND THE STORE KEY ARE NOT THE SAME STRING, AND THEY USED TO\n\t * BE. `schema` doubles as the key the object cache is read under, and\n\t * CnDetailPage passes it `objectType`, which is `<register>-<schema>`. Put\n\t * straight into the URL that produced\n\t * `/api/objects/dossiq/dossiq-case/<id>/lock`, a schema slug no register\n\t * has, so the acquire 404ed on every manifest-driven detail page and the\n\t * lock was never taken. `options.schemaSlug` is the URL half and defaults\n\t * to `schema`, so a caller that passes one string for both is unaffected.\n\t *\n\t * @return {string} The object's base url.\n\t */\n\tfunction objectUrl() {\n\t\tconst r = readRegister()\n\t\tconst s = readSchemaSlug()\n\t\tconst i = readId()\n\t\treturn generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(r)}/${encodeURIComponent(s)}/${encodeURIComponent(i)}`)\n\t}\n\n\tfunction endpoint() {\n\t\treturn `${objectUrl()}/lock`\n\t}\n\n\t/**\n\t * Where a lock is handed back.\n\t *\n\t * 🔴 IT IS A POST TO `/unlock`, AND IT USED TO BE `DELETE /lock`.\n\t * OpenRegister declares exactly two lock routes, `objects#lock` and\n\t * `objects#unlock`, both POST (`appinfo/routes.php`, verified 2026-09-18\n\t * against `development`); there is no DELETE on `/lock` and there never\n\t * was. So every release 404ed, and `release()` reads a 404 as \"already\n\t * released; idempotent\" and returns without a word. The lock outlived the\n\t * editor, the holder was told nothing, and the next person was refused by a\n\t * lock whose owner had closed the page twenty minutes earlier. That is the\n\t * silent no-op this line ends, and it is why the release test asserts the\n\t * URL and the METHOD rather than only that the promise resolved.\n\t *\n\t * @return {string} The unlock url.\n\t */\n\tfunction unlockEndpoint() {\n\t\treturn `${objectUrl()}/unlock`\n\t}\n\n\tasync function acquire() {\n\t\ttry {\n\t\t\tawait axios.post(endpoint(), { duration: lockDurationSec })\n\t\t} catch (e) {\n\t\t\tconst status = e?.response?.status\n\t\t\tconst data = e?.response?.data\n\t\t\tif (status === 409 || status === 423) {\n\t\t\t\tthrow new LockConflictError(\n\t\t\t\t\tdata?.message || 'Object is locked by another user',\n\t\t\t\t\tdata?.lock || data,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (status === 401 || status === 403) {\n\t\t\t\tthrow new PermissionError(data?.message || 'No permission to lock this object')\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\t\t// Refresh the cached object so `@self.locked` is populated;\n\t\t// liveUpdatesPlugin will also fire a refetch on the\n\t\t// or-object-{uuid} event, but doing it here avoids a render\n\t\t// gap between acquire() resolution and the push frame.\n\t\ttry {\n\t\t\tawait objectStore.fetchObject(readType(), readId())\n\t\t} catch {\n\t\t\t// swallow — the lock is held; the cache will catch up\n\t\t}\n\t\tif (autoRenew) {\n\t\t\tstartRenewTimer()\n\t\t}\n\t}\n\n\tasync function release() {\n\t\tstopRenewTimer()\n\t\ttry {\n\t\t\tawait axios.post(unlockEndpoint())\n\t\t} catch (e) {\n\t\t\tconst status = e?.response?.status\n\t\t\tif (status === 404) {\n\t\t\t\treturn\n\t\t\t} // already released; idempotent\n\t\t\tif (status === 401 || status === 403) {\n\t\t\t\tthrow new PermissionError(e?.response?.data?.message || 'No permission to release this lock')\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\t\ttry {\n\t\t\tawait objectStore.fetchObject(readType(), readId())\n\t\t} catch { /* ignore */ }\n\t}\n\n\tfunction startRenewTimer() {\n\t\tstopRenewTimer()\n\t\tif (!autoRenew) {\n\t\t\treturn\n\t\t}\n\t\trenewTimer = setInterval(() => {\n\t\t\tif (!isVisible()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!lockedByMe.value) {\n\t\t\t\tstopRenewTimer()\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Re-issue acquire; idempotent on the server (resets TTL).\n\t\t\tacquire().catch(() => stopRenewTimer())\n\t\t}, renewIntervalMs)\n\t}\n\n\tfunction stopRenewTimer() {\n\t\tif (renewTimer) {\n\t\t\tclearInterval(renewTimer)\n\t\t\trenewTimer = null\n\t\t}\n\t}\n\n\tfunction beaconRelease() {\n\t\tif (!lockedByMe.value) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\t// A beacon is always a POST, which is what `/unlock` takes: the\n\t\t\t// old `?_method=DELETE` was a query parameter on a URL that had no\n\t\t\t// DELETE route to reach in the first place.\n\t\t\tnavigator.sendBeacon?.(unlockEndpoint())\n\t\t} catch { /* best-effort */ }\n\t}\n\n\tonMounted(() => {\n\t\twindow.addEventListener('beforeunload', beaconRelease)\n\t})\n\n\tonBeforeUnmount(() => {\n\t\twindow.removeEventListener('beforeunload', beaconRelease)\n\t})\n\n\ttryOnScopeDispose(() => {\n\t\tstopRenewTimer()\n\t\tif (lockedByMe.value) {\n\t\t\t// Fire-and-forget; the Vue scope is gone, no point awaiting.\n\t\t\trelease().catch(() => {})\n\t\t}\n\t})\n\n\treturn { locked, lockedByMe, lockedBy, expiresAt, acquire, release }\n}\n"],"names":["axios"],"mappings":"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"}
1
+ {"version":3,"file":"useObjectLock.js","sources":["../../../src/composables/useObjectLock.js"],"sourcesContent":["// SPDX-License-Identifier: EUPL-1.2\n// Copyright (C) 2026 Conduction B.V.\n\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { tryOnScopeDispose } from '@vueuse/core'\nimport { computed, onBeforeUnmount, onMounted, toValue } from 'vue'\n\n/**\n * Thrown when an `acquire()` POST returns 409 (Conflict) or 423\n * (Locked). Carries the conflicting user info so the UI can render\n * a \"Locked by X\" banner without a second fetch.\n */\nexport class LockConflictError extends Error {\n\t/**\n\t * @param {string} message Human-readable description.\n\t * @param {object|null} info Lock metadata (`{ user, displayName, expiresAt }`) parsed from the response.\n\t */\n\tconstructor(message, info = null) {\n\t\tsuper(message)\n\t\tthis.name = 'LockConflictError'\n\t\tthis.lockedBy = info?.user ?? info?.displayName ?? null\n\t\tthis.expiresAt = info?.expiresAt ?? null\n\t}\n}\n\n/**\n * Thrown when an `acquire()` / `release()` call returns 401 / 403.\n * Consumers may fall back to \"edit without lock\" UX.\n */\nexport class PermissionError extends Error {\n\tconstructor(message) {\n\t\tsuper(message)\n\t\tthis.name = 'PermissionError'\n\t}\n}\n\n/**\n * useObjectLock — manage the OR pessimistic lock for a single object.\n *\n * Reactive lock state is **read from the object cache** populated by\n * `liveUpdatesPlugin`, NOT from a polling lock endpoint. This means\n * remote lock acquisitions are visible without explicit refetch as\n * long as the consumer also uses `useObjectSubscription` (or any\n * other path that keeps `objectStore.objects[type][id]` fresh).\n *\n * @param {object} objectStore Pinia store instance (`useObjectStore()` result).\n * @param {string|import('vue').Ref<string>|function(): string} register OR register slug.\n * @param {string|import('vue').Ref<string>|function(): string} schema OR schema slug (also the GraphQL field name).\n * @param {string|import('vue').Ref<string>|function(): string} id Object UUID.\n * @param {object} [options] Optional config.\n * @param {boolean} [options.autoRenew] Renew the lock on a fixed interval while editing + visible.\n * @param {number} [options.renewIntervalMs] Renew every N ms (default 10 min).\n * @param {number} [options.lockDurationSec] Server-side TTL requested on acquire (default 30 min).\n * @param {string|import('vue').Ref<string>|function(): string} [options.schemaSlug] The schema slug for the LOCK URL, when `schema` is the object-cache key rather than the slug (CnDetailPage passes `<register>-<schema>` as the key). Defaults to `schema`.\n * @return {{\n * locked: import('vue').ComputedRef<boolean>,\n * lockedByMe: import('vue').ComputedRef<boolean>,\n * lockedBy: import('vue').ComputedRef<string|null>,\n * expiresAt: import('vue').ComputedRef<Date|null>,\n * acquire: () => Promise<void>,\n * release: () => Promise<void>\n * }} Reactive lock state + actions.\n * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/object-lock/spec.md\n */\nexport function useObjectLock(objectStore, register, schema, id, options = {}) {\n\tconst autoRenew = options.autoRenew !== false\n\tconst renewIntervalMs = options.renewIntervalMs ?? 600000\n\tconst lockDurationSec = options.lockDurationSec ?? 1800\n\n\t// Use native document.visibilityState directly to avoid pinning a\n\t// minimum @vueuse/core version (`useDocumentVisibility` landed in a\n\t// later cut and isn't available everywhere).\n\tconst isVisible = () => typeof document === 'undefined' || document.visibilityState !== 'hidden'\n\tlet renewTimer = null\n\n\t/*\n\t * 🔴 `toValue`, NOT `unref`. CnDetailPage passes GETTER FUNCTIONS\n\t * (`() => props.objectId`) so the lock follows the route. `unref` returns a\n\t * function untouched, so the getter's SOURCE was interpolated into the URL:\n\t * every detail page sent `/api/objects/()=>pn/()=>Ct.schema||Bn/()=>Ct.objectId/lock`\n\t * and got a 404, and the cache lookup used the function as its key, so the\n\t * banner never saw a lock either. `toValue` reads a plain value, a ref and a\n\t * getter alike.\n\t */\n\tfunction readType() {\n\t\treturn toValue(schema)\n\t}\n\tfunction readId() {\n\t\treturn toValue(id)\n\t}\n\tfunction readRegister() {\n\t\treturn toValue(register)\n\t}\n\n\tfunction readSelfLock() {\n\t\tconst t = readType()\n\t\tconst i = readId()\n\t\tif (!t || !i) {\n\t\t\treturn null\n\t\t}\n\t\tconst obj = objectStore.objects?.[t]?.[i]\n\t\tconst self = obj?.['@self'] ?? obj\n\t\treturn self?.locked ?? null\n\t}\n\n\tconst locked = computed(() => {\n\t\tconst l = readSelfLock()\n\t\tif (!l) {\n\t\t\treturn false\n\t\t}\n\t\tif (l.expiresAt) {\n\t\t\tconst exp = new Date(l.expiresAt).getTime()\n\t\t\tif (Number.isFinite(exp) && exp <= Date.now()) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tconst lockedBy = computed(() => readSelfLock()?.user ?? readSelfLock()?.displayName ?? null)\n\n\tconst lockedByMe = computed(() => {\n\t\tconst me = getCurrentUser()?.uid\n\t\tconst lb = readSelfLock()?.user\n\t\treturn Boolean(me && lb && me === lb)\n\t})\n\n\tconst expiresAt = computed(() => {\n\t\tconst raw = readSelfLock()?.expiresAt\n\t\tif (!raw) {\n\t\t\treturn null\n\t\t}\n\t\tconst d = new Date(raw)\n\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t})\n\n\tfunction readSchemaSlug() {\n\t\treturn toValue(options.schemaSlug ?? schema)\n\t}\n\n\t/**\n\t * Whether register, schema slug and object id are all known.\n\t *\n\t * A detail page mounts before its route params or object have resolved.\n\t * Without this a lock call would go out with `undefined` in the path, a\n\t * request OpenRegister can only answer with a 404.\n\t *\n\t * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/object-lock/spec.md\n\t * @return {boolean} True when a lock URL can be built.\n\t */\n\tfunction hasTarget() {\n\t\treturn [readRegister(), readSchemaSlug(), readId()]\n\t\t\t.every((part) => typeof part === 'string' ? part !== '' : typeof part === 'number')\n\t}\n\n\t/**\n\t * The object's own URL, without the lock verb.\n\t *\n\t * 🔴 THE SLUG AND THE STORE KEY ARE NOT THE SAME STRING, AND THEY USED TO\n\t * BE. `schema` doubles as the key the object cache is read under, and\n\t * CnDetailPage passes it `objectType`, which is `<register>-<schema>`. Put\n\t * straight into the URL that produced\n\t * `/api/objects/dossiq/dossiq-case/<id>/lock`, a schema slug no register\n\t * has, so the acquire 404ed on every manifest-driven detail page and the\n\t * lock was never taken. `options.schemaSlug` is the URL half and defaults\n\t * to `schema`, so a caller that passes one string for both is unaffected.\n\t *\n\t * @return {string} The object's base url.\n\t */\n\tfunction objectUrl() {\n\t\tconst r = readRegister()\n\t\tconst s = readSchemaSlug()\n\t\tconst i = readId()\n\t\treturn generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(r)}/${encodeURIComponent(s)}/${encodeURIComponent(i)}`)\n\t}\n\n\tfunction endpoint() {\n\t\treturn `${objectUrl()}/lock`\n\t}\n\n\t/**\n\t * Where a lock is handed back.\n\t *\n\t * 🔴 IT IS A POST TO `/unlock`, AND IT USED TO BE `DELETE /lock`.\n\t * OpenRegister declares exactly two lock routes, `objects#lock` and\n\t * `objects#unlock`, both POST (`appinfo/routes.php`, verified 2026-09-18\n\t * against `development`); there is no DELETE on `/lock` and there never\n\t * was. So every release 404ed, and `release()` reads a 404 as \"already\n\t * released; idempotent\" and returns without a word. The lock outlived the\n\t * editor, the holder was told nothing, and the next person was refused by a\n\t * lock whose owner had closed the page twenty minutes earlier. That is the\n\t * silent no-op this line ends, and it is why the release test asserts the\n\t * URL and the METHOD rather than only that the promise resolved.\n\t *\n\t * @return {string} The unlock url.\n\t */\n\tfunction unlockEndpoint() {\n\t\treturn `${objectUrl()}/unlock`\n\t}\n\n\tasync function acquire() {\n\t\tif (!hasTarget()) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tawait axios.post(endpoint(), { duration: lockDurationSec })\n\t\t} catch (e) {\n\t\t\tconst status = e?.response?.status\n\t\t\tconst data = e?.response?.data\n\t\t\tif (status === 409 || status === 423) {\n\t\t\t\tthrow new LockConflictError(\n\t\t\t\t\tdata?.message || 'Object is locked by another user',\n\t\t\t\t\tdata?.lock || data,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (status === 401 || status === 403) {\n\t\t\t\tthrow new PermissionError(data?.message || 'No permission to lock this object')\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\t\t// Refresh the cached object so `@self.locked` is populated;\n\t\t// liveUpdatesPlugin will also fire a refetch on the\n\t\t// or-object-{uuid} event, but doing it here avoids a render\n\t\t// gap between acquire() resolution and the push frame.\n\t\ttry {\n\t\t\tawait objectStore.fetchObject(readType(), readId())\n\t\t} catch {\n\t\t\t// swallow — the lock is held; the cache will catch up\n\t\t}\n\t\tif (autoRenew) {\n\t\t\tstartRenewTimer()\n\t\t}\n\t}\n\n\tasync function release() {\n\t\tstopRenewTimer()\n\t\tif (!hasTarget()) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tawait axios.post(unlockEndpoint())\n\t\t} catch (e) {\n\t\t\tconst status = e?.response?.status\n\t\t\tif (status === 404) {\n\t\t\t\treturn\n\t\t\t} // already released; idempotent\n\t\t\tif (status === 401 || status === 403) {\n\t\t\t\tthrow new PermissionError(e?.response?.data?.message || 'No permission to release this lock')\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\t\ttry {\n\t\t\tawait objectStore.fetchObject(readType(), readId())\n\t\t} catch { /* ignore */ }\n\t}\n\n\tfunction startRenewTimer() {\n\t\tstopRenewTimer()\n\t\tif (!autoRenew) {\n\t\t\treturn\n\t\t}\n\t\trenewTimer = setInterval(() => {\n\t\t\tif (!isVisible()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!lockedByMe.value) {\n\t\t\t\tstopRenewTimer()\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Re-issue acquire; idempotent on the server (resets TTL).\n\t\t\tacquire().catch(() => stopRenewTimer())\n\t\t}, renewIntervalMs)\n\t}\n\n\tfunction stopRenewTimer() {\n\t\tif (renewTimer) {\n\t\t\tclearInterval(renewTimer)\n\t\t\trenewTimer = null\n\t\t}\n\t}\n\n\tfunction beaconRelease() {\n\t\tif (!lockedByMe.value || !hasTarget()) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\t// A beacon is always a POST, which is what `/unlock` takes: the\n\t\t\t// old `?_method=DELETE` was a query parameter on a URL that had no\n\t\t\t// DELETE route to reach in the first place.\n\t\t\tnavigator.sendBeacon?.(unlockEndpoint())\n\t\t} catch { /* best-effort */ }\n\t}\n\n\tonMounted(() => {\n\t\twindow.addEventListener('beforeunload', beaconRelease)\n\t})\n\n\tonBeforeUnmount(() => {\n\t\twindow.removeEventListener('beforeunload', beaconRelease)\n\t})\n\n\ttryOnScopeDispose(() => {\n\t\tstopRenewTimer()\n\t\tif (lockedByMe.value) {\n\t\t\t// Fire-and-forget; the Vue scope is gone, no point awaiting.\n\t\t\trelease().catch(() => {})\n\t\t}\n\t})\n\n\treturn { locked, lockedByMe, lockedBy, expiresAt, acquire, release }\n}\n"],"names":["axios"],"mappings":";;;;;;AAAA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACO,MAAM,iBAAiB,SAAS,KAAK,CAAC;AAC7C;AACA;AACA;AACA;AACA,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE;AACnC,EAAE,KAAK,CAAC,OAAO,EAAC;AAChB,EAAE,IAAI,CAAC,IAAI,GAAG,oBAAmB;AACjC,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,WAAW,IAAI,KAAI;AACzD,EAAE,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,KAAI;AAC1C,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACO,MAAM,eAAe,SAAS,KAAK,CAAC;AAC3C,CAAC,WAAW,CAAC,OAAO,EAAE;AACtB,EAAE,KAAK,CAAC,OAAO,EAAC;AAChB,EAAE,IAAI,CAAC,IAAI,GAAG,kBAAiB;AAC/B,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE;AAC/E,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,MAAK;AAC9C,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,OAAM;AAC1D,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,KAAI;AACxD;AACA;AACA;AACA;AACA,CAAC,MAAM,SAAS,GAAG,MAAM,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAQ;AACjG,CAAC,IAAI,UAAU,GAAG,KAAI;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,SAAS,QAAQ,GAAG;AACrB,EAAE,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,EAAE;AACF,CAAC,SAAS,MAAM,GAAG;AACnB,EAAE,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,EAAE;AACF,CAAC,SAAS,YAAY,GAAG;AACzB,EAAE,OAAO,OAAO,CAAC,QAAQ,CAAC;AAC1B,EAAE;AACF;AACA,CAAC,SAAS,YAAY,GAAG;AACzB,EAAE,MAAM,CAAC,GAAG,QAAQ,GAAE;AACtB,EAAE,MAAM,CAAC,GAAG,MAAM,GAAE;AACpB,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE;AAChB,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,EAAC;AAC3C,EAAE,MAAM,IAAI,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,IAAG;AACpC,EAAE,OAAO,IAAI,EAAE,MAAM,IAAI,IAAI;AAC7B,EAAE;AACF;AACA,CAAC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM;AAC/B,EAAE,MAAM,CAAC,GAAG,YAAY,GAAE;AAC1B,EAAE,IAAI,CAAC,CAAC,EAAE;AACV,GAAG,OAAO,KAAK;AACf,GAAG;AACH,EAAE,IAAI,CAAC,CAAC,SAAS,EAAE;AACnB,GAAG,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,GAAE;AAC9C,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE;AAClD,IAAI,OAAO,KAAK;AAChB,IAAI;AACJ,GAAG;AACH,EAAE,OAAO,IAAI;AACb,EAAE,EAAC;AACH;AACA,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,YAAY,EAAE,EAAE,IAAI,IAAI,YAAY,EAAE,EAAE,WAAW,IAAI,IAAI,EAAC;AAC7F;AACA,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM;AACnC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,IAAG;AAClC,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,EAAE,KAAI;AACjC,EAAE,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACvC,EAAE,EAAC;AACH;AACA,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM;AAClC,EAAE,MAAM,GAAG,GAAG,YAAY,EAAE,EAAE,UAAS;AACvC,EAAE,IAAI,CAAC,GAAG,EAAE;AACZ,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAC;AACzB,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC;AAC7C,EAAE,EAAC;AACH;AACA,CAAC,SAAS,cAAc,GAAG;AAC3B,EAAE,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC;AAC9C,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,SAAS,SAAS,GAAG;AACtB,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,CAAC;AACrD,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,KAAK,EAAE,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC;AACtF,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,SAAS,SAAS,GAAG;AACtB,EAAE,MAAM,CAAC,GAAG,YAAY,GAAE;AAC1B,EAAE,MAAM,CAAC,GAAG,cAAc,GAAE;AAC5B,EAAE,MAAM,CAAC,GAAG,MAAM,GAAE;AACpB,EAAE,OAAO,WAAW,CAAC,CAAC,+BAA+B,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjI,EAAE;AACF;AACA,CAAC,SAAS,QAAQ,GAAG;AACrB,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC;AAC9B,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,SAAS,cAAc,GAAG;AAC3B,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC;AAChC,EAAE;AACF;AACA,CAAC,eAAe,OAAO,GAAG;AAC1B,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;AACpB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI;AACN,GAAG,MAAMA,cAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAC;AAC9D,GAAG,CAAC,OAAO,CAAC,EAAE;AACd,GAAG,MAAM,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,OAAM;AACrC,GAAG,MAAM,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,KAAI;AACjC,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE;AACzC,IAAI,MAAM,IAAI,iBAAiB;AAC/B,KAAK,IAAI,EAAE,OAAO,IAAI,kCAAkC;AACxD,KAAK,IAAI,EAAE,IAAI,IAAI,IAAI;AACvB,KAAK;AACL,IAAI;AACJ,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE;AACzC,IAAI,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,OAAO,IAAI,mCAAmC,CAAC;AACnF,IAAI;AACJ,GAAG,MAAM,CAAC;AACV,GAAG;AACH;AACA;AACA;AACA;AACA,EAAE,IAAI;AACN,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAC;AACtD,GAAG,CAAC,MAAM;AACV;AACA,GAAG;AACH,EAAE,IAAI,SAAS,EAAE;AACjB,GAAG,eAAe,GAAE;AACpB,GAAG;AACH,EAAE;AACF;AACA,CAAC,eAAe,OAAO,GAAG;AAC1B,EAAE,cAAc,GAAE;AAClB,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;AACpB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI;AACN,GAAG,MAAMA,cAAK,CAAC,IAAI,CAAC,cAAc,EAAE,EAAC;AACrC,GAAG,CAAC,OAAO,CAAC,EAAE;AACd,GAAG,MAAM,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,OAAM;AACrC,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE;AACvB,IAAI,MAAM;AACV,IAAI;AACJ,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE;AACzC,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,IAAI,oCAAoC,CAAC;AACjG,IAAI;AACJ,GAAG,MAAM,CAAC;AACV,GAAG;AACH,EAAE,IAAI;AACN,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAC;AACtD,GAAG,CAAC,MAAM,gBAAgB;AAC1B,EAAE;AACF;AACA,CAAC,SAAS,eAAe,GAAG;AAC5B,EAAE,cAAc,GAAE;AAClB,EAAE,IAAI,CAAC,SAAS,EAAE;AAClB,GAAG,MAAM;AACT,GAAG;AACH,EAAE,UAAU,GAAG,WAAW,CAAC,MAAM;AACjC,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE;AACrB,IAAI,MAAM;AACV,IAAI;AACJ,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE;AAC1B,IAAI,cAAc,GAAE;AACpB,IAAI,MAAM;AACV,IAAI;AACJ;AACA,GAAG,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,cAAc,EAAE,EAAC;AAC1C,GAAG,EAAE,eAAe,EAAC;AACrB,EAAE;AACF;AACA,CAAC,SAAS,cAAc,GAAG;AAC3B,EAAE,IAAI,UAAU,EAAE;AAClB,GAAG,aAAa,CAAC,UAAU,EAAC;AAC5B,GAAG,UAAU,GAAG,KAAI;AACpB,GAAG;AACH,EAAE;AACF;AACA,CAAC,SAAS,aAAa,GAAG;AAC1B,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,EAAE;AACzC,GAAG,MAAM;AACT,GAAG;AACH,EAAE,IAAI;AACN;AACA;AACA;AACA,GAAG,SAAS,CAAC,UAAU,GAAG,cAAc,EAAE,EAAC;AAC3C,GAAG,CAAC,MAAM,qBAAqB;AAC/B,EAAE;AACF;AACA,CAAC,SAAS,CAAC,MAAM;AACjB,EAAE,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,aAAa,EAAC;AACxD,EAAE,EAAC;AACH;AACA,CAAC,eAAe,CAAC,MAAM;AACvB,EAAE,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,aAAa,EAAC;AAC3D,EAAE,EAAC;AACH;AACA,CAAC,iBAAiB,CAAC,MAAM;AACzB,EAAE,cAAc,GAAE;AAClB,EAAE,IAAI,UAAU,CAAC,KAAK,EAAE;AACxB;AACA,GAAG,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,EAAC;AAC5B,GAAG;AACH,EAAE,EAAC;AACH;AACA,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE;AACrE;;;;"}
package/dist/esm/index.js CHANGED
@@ -27,6 +27,8 @@ export { BSN_ERROR_CHECKSUM, BSN_ERROR_LENGTH, maskBsn, validateBsn } from './ut
27
27
  export { registerTranslations } from './l10n/index.js';
28
28
  export { CnHttpError, cnFetch, cnFetchJson, configureCnFetch } from './utils/cnFetch.js';
29
29
  export { isLockedByCurrentUser, isObjectLocked, lockHolder, readLockPayload, resolveObjectLock } from './utils/objectLock.js';
30
+ export { dispatchObjectsChanged } from './utils/objectSignals.js';
31
+ export { lensUnavailableText, readLensReports } from './utils/lensAvailability.js';
30
32
  export { validateManifest, validateManifestV2 } from './utils/validateManifest.js';
31
33
  export { clearResolveCache, resolveManifestSentinels } from './utils/resolveManifestSentinels.js';
32
34
  export { clearRouteSentinelWarnings, resolveRouteSentinels } from './utils/resolveRouteSentinels.js';
@@ -456,7 +458,7 @@ export { columnsFromSchema, fieldsFromSchema, filtersFromSchema, formatValue, va
456
458
  export { findRepeatingWrite, journeyItemTargets } from './utils/journeyRepeatingWrite.js';
457
459
  export { DYNAMIC_KEY_PREFIX, EXTENDS_FORM_KEY, PREFILL_KEY, splitDynamicFormData, usesArrayValues, valueArrayFor, valueRecordsFor } from './utils/dynamicProperties.js';
458
460
  export { SchemaBreakingChangeError, SchemaHasObjectsError, deleteSchema, describeSchemaChange, saveSchema } from './utils/schemaApi.js';
459
- export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, resetVisibilityCache } from './utils/widgetVisibility.js';
461
+ export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, peekCurrentUserGroups, resetVisibilityCache } from './utils/widgetVisibility.js';
460
462
  export { safeHref, safeImageSrc, safeSvgPath } from './utils/safeHref.js';
461
463
  export { followLinkClick, isModifiedClick, openRowTarget, resolveHref } from './utils/linkNavigation.js';
462
464
  export { isNewTabHandled, isRowMiddleClick } from './utils/rowAuxClick.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,9 @@
1
1
  var translations = {
2
+ "Linked items": "Linked items",
3
+ "Still loading: {sections}": "Still loading: {sections}",
4
+ "Could not load {section}.": "Could not load {section}.",
5
+ "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.": "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.",
6
+ "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.": "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.",
2
7
  "Show as": "Show as",
3
8
  Board: "Board",
4
9
  Calendar: "Calendar",
@@ -1283,7 +1288,20 @@ var translations = {
1283
1288
  "Development environment": "Development environment",
1284
1289
  "Test environment": "Test environment",
1285
1290
  "Acceptance environment": "Acceptance environment",
1286
- "About {label}": "About {label}"
1291
+ "About {label}": "About {label}",
1292
+ "Could not load the form for this item.": "Could not load the form for this item.",
1293
+ "Drop to add": "Drop to add",
1294
+ "Or drag files onto this list.": "Or drag files onto this list.",
1295
+ "This server does not keep track of what you open.": "This server does not keep track of what you open.",
1296
+ "Log in to see what you opened recently.": "Log in to see what you opened recently.",
1297
+ "Your recent items are not available right now.": "Your recent items are not available right now.",
1298
+ yesterday: "yesterday",
1299
+ tomorrow: "tomorrow",
1300
+ "Filter by {column}": "Filter by {column}",
1301
+ "Filter by {column}, active": "Filter by {column}, active",
1302
+ "Turn notifications off": "Turn notifications off",
1303
+ "Turn notifications on": "Turn notifications on",
1304
+ "Could not change notifications.": "Could not change notifications."
1287
1305
  };
1288
1306
  var plurals = {
1289
1307
  "%n year": [
@@ -1337,6 +1355,18 @@ var plurals = {
1337
1355
  "You see {count} capability of {total}.": [
1338
1356
  "You see {count} capability of {total}.",
1339
1357
  "You see {count} capabilities of {total}."
1358
+ ],
1359
+ "%n file added": [
1360
+ "%n file added",
1361
+ "%n files added"
1362
+ ],
1363
+ "{count} day ago": [
1364
+ "{count} day ago",
1365
+ "{count} days ago"
1366
+ ],
1367
+ "in {count} day": [
1368
+ "in {count} day",
1369
+ "in {count} days"
1340
1370
  ]
1341
1371
  };
1342
1372
  var en = {
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,9 @@
1
1
  var translations = {
2
+ "Linked items": "Gekoppelde items",
3
+ "Still loading: {sections}": "Nog aan het laden: {sections}",
4
+ "Could not load {section}.": "{section} kon niet worden geladen.",
5
+ "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.": "Met inloggegevens van de organisatie handelen apps namens je organisatie. Keepiq, de kluis voor inloggegevens van Nextcloud, bewaart het geheim. De app krijgt het nooit in handen. Je kiest welke apps elk inloggegeven mogen gebruiken.",
6
+ "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.": "Apps handelen soms namens jou bij een externe dienst. Je geeft het geheim één keer aan Nextcloud, en Keepiq bewaart het in een versleutelde kluis. Apps doen de aanvraag via Keepiq en zien het geheim nooit. Je kiest welke apps elk inloggegeven mogen gebruiken. Deel er één tussen apps, of houd er één per app.",
2
7
  "Show as": "Toon als",
3
8
  Board: "Bord",
4
9
  Calendar: "Kalender",
@@ -170,7 +175,7 @@ var translations = {
170
175
  "This integration is not set up yet. Configure a connection to start using it.": "Deze integratie is nog niet ingesteld. Configureer een verbinding om hem te gebruiken.",
171
176
  "This integration is not set up yet. Install and enable the {app} app to start using it.": "Deze integratie is nog niet ingesteld. Installeer en activeer de app {app} om hem te gebruiken.",
172
177
  "Set up {app}": "{app} instellen",
173
- "Add {type}": "Nieuwe {type}",
178
+ "Add {type}": "{type} toevoegen",
174
179
  "Add new contact": "Nieuw contact toevoegen",
175
180
  "Link contact": "Contact koppelen",
176
181
  "Search contacts": "Contacten zoeken",
@@ -1334,7 +1339,20 @@ var translations = {
1334
1339
  "Development environment": "Ontwikkelomgeving",
1335
1340
  "Test environment": "Testomgeving",
1336
1341
  "Acceptance environment": "Acceptatieomgeving",
1337
- "About {label}": "Over {label}"
1342
+ "About {label}": "Over {label}",
1343
+ "Could not load the form for this item.": "Kon het formulier voor dit item niet laden.",
1344
+ "Drop to add": "Laat los om toe te voegen",
1345
+ "Or drag files onto this list.": "Of sleep bestanden op deze lijst.",
1346
+ "This server does not keep track of what you open.": "Deze server houdt niet bij welke items je opent.",
1347
+ "Log in to see what you opened recently.": "Log in om te zien wat je onlangs opende.",
1348
+ "Your recent items are not available right now.": "Je recente items zijn nu niet beschikbaar.",
1349
+ yesterday: "gisteren",
1350
+ tomorrow: "morgen",
1351
+ "Filter by {column}": "Filteren op {column}",
1352
+ "Filter by {column}, active": "Filteren op {column}, actief",
1353
+ "Turn notifications off": "Meldingen uitzetten",
1354
+ "Turn notifications on": "Meldingen aanzetten",
1355
+ "Could not change notifications.": "Meldingen konden niet worden gewijzigd."
1338
1356
  };
1339
1357
  var plurals = {
1340
1358
  "%n year": [
@@ -1388,6 +1406,18 @@ var plurals = {
1388
1406
  "You see {count} capability of {total}.": [
1389
1407
  "Je ziet {count} functionaliteit van {total}.",
1390
1408
  "Je ziet {count} functionaliteiten van {total}."
1409
+ ],
1410
+ "%n file added": [
1411
+ "%n bestand toegevoegd",
1412
+ "%n bestanden toegevoegd"
1413
+ ],
1414
+ "{count} day ago": [
1415
+ "{count} dag geleden",
1416
+ "{count} dagen geleden"
1417
+ ],
1418
+ "in {count} day": [
1419
+ "over {count} dag",
1420
+ "over {count} dagen"
1391
1421
  ]
1392
1422
  };
1393
1423
  var nl = {
@@ -1 +1 @@
1
- {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}