@conduction/nextcloud-vue 2.72.0 → 2.73.1

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 (66) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +29 -0
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +18 -0
  5. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  6. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +9 -6
  7. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +16 -1
  9. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  10. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +5 -4
  11. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  12. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +25 -0
  13. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
  14. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +3 -1
  15. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
  16. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js +36 -1
  17. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js.map +1 -1
  18. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue3.js.map +1 -1
  19. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +37 -0
  20. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
  21. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -2
  22. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +16 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -6
  26. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +53 -5
  28. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
  29. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +1 -1
  30. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
  31. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +133 -0
  32. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  33. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +22 -14
  34. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  35. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +15 -1
  36. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  37. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +3 -2
  38. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js +15 -1
  40. package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js.map +1 -1
  41. package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js +2 -2
  42. package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js.map +1 -1
  43. package/dist/esm/index.js +1 -0
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/utils/notificationRuleLabel.js +132 -0
  46. package/dist/esm/utils/notificationRuleLabel.js.map +1 -0
  47. package/dist/nextcloud-vue.cjs.js +578 -47
  48. package/dist/nextcloud-vue.cjs.js.map +2 -2
  49. package/package.json +1 -1
  50. package/scripts/openregister-reliance.js +206 -0
  51. package/src/components/CnAppRoot/CnAppRoot.md +1 -0
  52. package/src/components/CnAppRoot/CnAppRoot.vue +29 -0
  53. package/src/components/CnDetailPage/CnDetailPage.vue +24 -3
  54. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +22 -4
  55. package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
  56. package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
  57. package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
  58. package/src/components/CnIndexPage/CnIndexPage.vue +17 -2
  59. package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +54 -6
  60. package/src/components/CnPageRenderer/CnPageRenderer.vue +147 -6
  61. package/src/components/CnTabsWidget/CnTabsWidget.vue +17 -2
  62. package/src/components/CnWidgetGrid/CnWidgetGrid.vue +19 -3
  63. package/src/index.js +2 -0
  64. package/src/utils/notificationRuleLabel.js +129 -0
  65. package/webpack/index.d.ts +16 -0
  66. package/webpack/index.js +70 -0
@@ -136551,7 +136551,7 @@ var script$3W = {
136551
136551
  },
136552
136552
  },
136553
136553
 
136554
- emits: ['geo-saved', 'open-integration'],
136554
+ emits: ['geo-saved', 'open-integration', 'select-object'],
136555
136555
 
136556
136556
  setup() {
136557
136557
  // Every integration component this host renders goes through the
@@ -137056,6 +137056,21 @@ var script$3W = {
137056
137056
  this.$emit('open-integration', integrationId);
137057
137057
  },
137058
137058
 
137059
+ /**
137060
+ * Pass a related-object click up; the page decides where it opens. Heard
137061
+ * from the Related widget itself and from a registry widget that nests a
137062
+ * host of its own (a Tabs widget with a Related tab).
137063
+ *
137064
+ * @param {object} raw The clicked object as the related widget holds it.
137065
+ */
137066
+ onSelectObject(raw) {
137067
+ /**
137068
+ * @event select-object A related object was clicked in the related widget. Payload is the raw object; its `@self` names the register and schema.
137069
+ * @type {object}
137070
+ */
137071
+ this.$emit('select-object', raw);
137072
+ },
137073
+
137059
137074
  /**
137060
137075
  * Delegate to the rendered list widget's own create affordance, so the
137061
137076
  * card's Add action and the widget's footer button open the same form.
@@ -137158,8 +137173,9 @@ function render$3W(_ctx, _cache, $props, $setup, $data, $options) {
137158
137173
  includeGroups: $options.content.groups || [],
137159
137174
  hideSingleTabTitle: $options.content.hideSingleTabTitle !== false,
137160
137175
  showTotalCount: $options.content.showTotalCount !== false,
137161
- onOpenIntegration: $options.onOpenIntegration
137162
- }, null, 8 /* PROPS */, ["title", "bare", "objectType", "objectId", "objectData", "register", "schema", "store", "includeGroups", "hideSingleTabTitle", "showTotalCount", "onOpenIntegration"]))
137176
+ onOpenIntegration: $options.onOpenIntegration,
137177
+ onSelectObject: $options.onSelectObject
137178
+ }, null, 8 /* PROPS */, ["title", "bare", "objectType", "objectId", "objectData", "register", "schema", "store", "includeGroups", "hideSingleTabTitle", "showTotalCount", "onOpenIntegration", "onSelectObject"]))
137163
137179
  : ($options.isGeo)
137164
137180
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnObjectGeoWidget, {
137165
137181
  key: 3,
@@ -137198,7 +137214,7 @@ function render$3W(_ctx, _cache, $props, $setup, $data, $options) {
137198
137214
  class: "cn-detail-page__catalog-card"
137199
137215
  }, vue$1.createSlots({
137200
137216
  default: vue$1.withCtx(() => [
137201
- (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.renderer), vue$1.mergeProps({ ref: "renderer" }, $options.rendererProps), null, 16 /* FULL_PROPS */))
137217
+ (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.renderer), vue$1.mergeProps({ ref: "renderer" }, $options.rendererProps, { onSelectObject: $options.onSelectObject }), null, 16 /* FULL_PROPS */, ["onSelectObject"]))
137202
137218
  ]),
137203
137219
  _: 2 /* DYNAMIC */
137204
137220
  }, [
@@ -137266,7 +137282,7 @@ function render$3W(_ctx, _cache, $props, $setup, $data, $options) {
137266
137282
  ? (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.renderer), vue$1.mergeProps({
137267
137283
  key: 8,
137268
137284
  ref: "renderer"
137269
- }, $options.rendererProps), null, 16 /* FULL_PROPS */))
137285
+ }, $options.rendererProps, { onSelectObject: $options.onSelectObject }), null, 16 /* FULL_PROPS */, ["onSelectObject"]))
137270
137286
  : vue$1.createCommentVNode("v-if", true)
137271
137287
  ], 2 /* CLASS */))
137272
137288
  }
@@ -138248,7 +138264,7 @@ var script$3T = {
138248
138264
  },
138249
138265
  },
138250
138266
 
138251
- emits: ['geo-saved', 'open-integration'],
138267
+ emits: ['geo-saved', 'open-integration', 'select-object'],
138252
138268
 
138253
138269
  data() {
138254
138270
  return {
@@ -138421,6 +138437,20 @@ var script$3T = {
138421
138437
  this.$emit('open-integration', integrationId);
138422
138438
  },
138423
138439
 
138440
+ /**
138441
+ * Re-emit a related child's object click; the page opens the object.
138442
+ *
138443
+ * @param {object} raw The clicked object.
138444
+ * @return {void}
138445
+ */
138446
+ onSelectObject(raw) {
138447
+ /**
138448
+ * @event select-object Re-emitted from a related child whose object row was clicked. Payload is the raw object.
138449
+ * @type {object}
138450
+ */
138451
+ this.$emit('select-object', raw);
138452
+ },
138453
+
138424
138454
  /**
138425
138455
  * Follow the strip's own selection, so keyboard navigation moves the
138426
138456
  * hoisted Actions menu too.
@@ -138547,8 +138577,9 @@ function render$3T(_ctx, _cache, $props, $setup, $data, $options) {
138547
138577
  integrationContext: $props.integrationContext,
138548
138578
  cnRegistry: $props.cnRegistry,
138549
138579
  onGeoSaved: $options.onGeoSaved,
138550
- onOpenIntegration: $options.onOpenIntegration
138551
- }, null, 8 /* PROPS */, ["widget", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "cnRegistry", "onGeoSaved", "onOpenIntegration"]))
138580
+ onOpenIntegration: $options.onOpenIntegration,
138581
+ onSelectObject: $options.onSelectObject
138582
+ }, null, 8 /* PROPS */, ["widget", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "cnRegistry", "onGeoSaved", "onOpenIntegration", "onSelectObject"]))
138552
138583
  : (vue$1.openBlock(), vue$1.createBlock(_component_NcEmptyContent, {
138553
138584
  key: 1,
138554
138585
  name: $options.missingLabel(entry)
@@ -231610,6 +231641,19 @@ var script$22 = {
231610
231641
  * edges, its undo stack, its persistence — so nothing is removed here.
231611
231642
  */
231612
231643
  'remove',
231644
+
231645
+ /**
231646
+ * @event activate Enter or Space on the focused node: open this step.
231647
+ * Carries the node id. The pointer equivalent is a double-click.
231648
+ */
231649
+ 'activate',
231650
+
231651
+ /**
231652
+ * @event menu Shift+F10 or the menu key on the focused node: show this
231653
+ * step's actions. Carries `{ id, clientX, clientY }`, a point at the
231654
+ * node, so a host positions the menu as it would for a click.
231655
+ */
231656
+ 'menu',
231613
231657
  ],
231614
231658
 
231615
231659
  setup() {
@@ -231864,7 +231908,8 @@ var script$22 = {
231864
231908
  * The keyboard contract.
231865
231909
  *
231866
231910
  * Arrow keys move; `c` arms an exit and, on a second node, completes the
231867
- * connection; `Escape` cancels. Repeated `c` on the SAME node steps
231911
+ * connection; `Escape` cancels. Enter or Space opens the step, Shift+F10
231912
+ * or the menu key opens its actions. Repeated `c` on the SAME node steps
231868
231913
  * through its exits rather than re-arming the first — the behaviour a
231869
231914
  * mouse gets for free by pointing.
231870
231915
  *
@@ -231913,6 +231958,27 @@ var script$22 = {
231913
231958
  return
231914
231959
  }
231915
231960
 
231961
+ // Opening the step, and its menu, from the keyboard. Only for a key
231962
+ // pressed ON the node: a port inside it is a control of its own.
231963
+ if (event.target === event.currentTarget) {
231964
+ if (event.key === 'Enter' || event.key === ' ') {
231965
+ event.preventDefault();
231966
+ this.$emit('activate', this.id);
231967
+ return
231968
+ }
231969
+
231970
+ if ((event.key === 'F10' && event.shiftKey === true) || event.key === 'ContextMenu') {
231971
+ event.preventDefault();
231972
+ const rect = event.currentTarget.getBoundingClientRect();
231973
+ this.$emit('menu', {
231974
+ id: this.id,
231975
+ clientX: rect.left + rect.width / 2,
231976
+ clientY: rect.bottom,
231977
+ });
231978
+ return
231979
+ }
231980
+ }
231981
+
231916
231982
  // Delete and Backspace both, because which one removes a thing is a
231917
231983
  // platform habit rather than a preference: Backspace is the delete
231918
231984
  // key on a Mac keyboard, and a user who learned one does not think
@@ -232270,6 +232336,8 @@ var script$21 = {
232270
232336
  'canvas-drop',
232271
232337
 
232272
232338
  'node-remove',
232339
+ 'node-activate',
232340
+ 'node-menu',
232273
232341
 
232274
232342
  /**
232275
232343
  * The canvas zoom changed, by any route — a control here, the host's own
@@ -232419,6 +232487,41 @@ var script$21 = {
232419
232487
  return port === 'in' ? null : port
232420
232488
  },
232421
232489
 
232490
+ /**
232491
+ * Forward Enter or Space on a node: the keyboard double-click.
232492
+ *
232493
+ * @param {string} id The node id.
232494
+ * @return {void}
232495
+ */
232496
+ onNodeActivate(id) {
232497
+ if (this.readOnly === true) {
232498
+ return
232499
+ }
232500
+
232501
+ /**
232502
+ * @event node-activate Enter or Space on a node. Payload is the id.
232503
+ */
232504
+ this.$emit('node-activate', id);
232505
+ },
232506
+
232507
+ /**
232508
+ * Forward Shift+F10 or the menu key on a node: the keyboard click menu.
232509
+ *
232510
+ * @param {object} request `{ id, clientX, clientY }`, a point at the node.
232511
+ * @return {void}
232512
+ */
232513
+ onNodeMenu(request) {
232514
+ if (this.readOnly === true) {
232515
+ return
232516
+ }
232517
+
232518
+ /**
232519
+ * @event node-menu Shift+F10 or the menu key on a node. Payload is
232520
+ * `{ id, clientX, clientY }`.
232521
+ */
232522
+ this.$emit('node-menu', request);
232523
+ },
232524
+
232422
232525
  /**
232423
232526
  * Forward a node click.
232424
232527
  *
@@ -232583,13 +232686,15 @@ function render$21(_ctx, _cache, $props, $setup, $data, $options) {
232583
232686
  vue$1.createVNode(_component_CnFlowNode, vue$1.mergeProps(nodeProps, {
232584
232687
  deletable: !$props.readOnly,
232585
232688
  onConnect: $options.onConnect,
232586
- onRemove: $options.onNodeRemove
232689
+ onRemove: $options.onNodeRemove,
232690
+ onActivate: $options.onNodeActivate,
232691
+ onMenu: $options.onNodeMenu
232587
232692
  }), {
232588
232693
  default: vue$1.withCtx((slotProps) => [
232589
232694
  vue$1.renderSlot(_ctx.$slots, "node", vue$1.normalizeProps(vue$1.guardReactiveProps(slotProps)))
232590
232695
  ]),
232591
232696
  _: 3 /* FORWARDED */
232592
- }, 16 /* FULL_PROPS */, ["deletable", "onConnect", "onRemove"])
232697
+ }, 16 /* FULL_PROPS */, ["deletable", "onConnect", "onRemove", "onActivate", "onMenu"])
232593
232698
  ]),
232594
232699
  "edge-default": vue$1.withCtx((edgeProps) => [
232595
232700
  vue$1.createVNode(_component_CnFlowEdge, vue$1.mergeProps(edgeProps, {
@@ -234359,6 +234464,31 @@ var script$1$ = {
234359
234464
  this.openNodeMenu({ item: id, event: mouse });
234360
234465
  },
234361
234466
 
234467
+ /**
234468
+ * Enter or Space on a step: open its editor, as a double-click does.
234469
+ *
234470
+ * @param {string} id The step id.
234471
+ * @return {void}
234472
+ */
234473
+ onNodeActivate(id) {
234474
+ this.store.selectedNodeId = id;
234475
+ this.store.editingNodeId = id;
234476
+ },
234477
+
234478
+ /**
234479
+ * Shift+F10 or the menu key on a step: its menu, at the step.
234480
+ *
234481
+ * @param {object} request The node's request.
234482
+ * @param {string} request.id The step id.
234483
+ * @param {number} request.clientX Where to place the menu.
234484
+ * @param {number} request.clientY Where to place the menu.
234485
+ * @return {void}
234486
+ */
234487
+ onNodeMenu({ id, clientX, clientY }) {
234488
+ this.store.selectedNodeId = id;
234489
+ this.openNodeMenu({ item: id, event: { clientX, clientY } });
234490
+ },
234491
+
234362
234492
  /**
234363
234493
  * Place a palette node where it was dropped.
234364
234494
  *
@@ -234973,6 +235103,8 @@ function render$1$(_ctx, _cache, $props, $setup, $data, $options) {
234973
235103
  maxZoom: $data.maxZoom,
234974
235104
  onZoomChange: _cache[8] || (_cache[8] = $event => ($data.zoom = $event)),
234975
235105
  onNodeSelect: $options.onNodeSelect,
235106
+ onNodeActivate: $options.onNodeActivate,
235107
+ onNodeMenu: $options.onNodeMenu,
234976
235108
  onEdgeSelect: $options.onEdgeSelect,
234977
235109
  onEdgeLabelClick: $options.onEdgeLabelClick,
234978
235110
  onEdgeLabelContext: $options.onEdgeLabelContext,
@@ -235018,7 +235150,7 @@ function render$1$(_ctx, _cache, $props, $setup, $data, $options) {
235018
235150
  : vue$1.createCommentVNode("v-if", true)
235019
235151
  ]),
235020
235152
  _: 1 /* STABLE */
235021
- }, 8 /* PROPS */, ["nodes", "edges", "minZoom", "maxZoom", "onNodeSelect", "onEdgeSelect", "onEdgeLabelClick", "onEdgeLabelContext", "onEdgeLabelMove", "onCanvasClick", "onNodesChange", "onCanvasDrop"]),
235153
+ }, 8 /* PROPS */, ["nodes", "edges", "minZoom", "maxZoom", "onNodeSelect", "onNodeActivate", "onNodeMenu", "onEdgeSelect", "onEdgeLabelClick", "onEdgeLabelContext", "onEdgeLabelMove", "onCanvasClick", "onNodesChange", "onCanvasDrop"]),
235022
235154
  vue$1.createVNode(_component_CnContextMenu, {
235023
235155
  open: $setup.nodeMenuOpen,
235024
235156
  "onUpdate:open": _cache[11] || (_cache[11] = $event => (($setup.nodeMenuOpen) = $event)),
@@ -248243,7 +248375,7 @@ function namedFilterToQuery(values) {
248243
248375
  * @event {object} row-aux-click — Table row or card middle-clicked. Payload: (row, event) — the row object and the native auxclick event
248244
248376
  * @event {{ key: string, order: string }} sort — Column sort changed
248245
248377
  * @event {number} page-changed — Pagination page changed
248246
- * @event {number} page-size-changed — Pagination page size changed
248378
+ * @event {number} page-size-changed — Pagination page size changed. In self-fetch mode the list refetches page 1 at the new size first.
248247
248379
  * @event {string[]} select — Selection changed. Payload: array of selected IDs
248248
248380
  * @event {object} action — Row action triggered. Payload: { action, row }
248249
248381
  * @event {{ action: string, id: string, selectedIds: Array, count: number }} bulk-action — A declarative bulk action was triggered from the selection strip. The SELECTION travels with it: an action that has to go and find out what was selected is one re-render away from acting on a different set than the user saw highlighted.
@@ -252668,6 +252800,21 @@ var script$1A = {
252668
252800
  this.$emit('page-changed', page);
252669
252801
  },
252670
252802
 
252803
+ /**
252804
+ * Page-size pick from CnPagination. In self-fetch mode the list refetches
252805
+ * page 1 at the new size — the event alone left the select changing and
252806
+ * the table not, since a manifest page has no host listening.
252807
+ *
252808
+ * @param {number} size Requested page size.
252809
+ * @return {void}
252810
+ */
252811
+ onPageSizeEvent(size) {
252812
+ if (this.isSelfFetchMode && typeof this.list.onPageSizeChange === 'function') {
252813
+ this.list.onPageSizeChange(size);
252814
+ }
252815
+ this.$emit('page-size-changed', size);
252816
+ },
252817
+
252671
252818
  /**
252672
252819
  * @param {{key: string, values: Array}} payload Facet-filter change from the sidebar.
252673
252820
  * @return {void}
@@ -254275,7 +254422,7 @@ function render$1A(_ctx, _cache, $props, $setup, $data, $options) {
254275
254422
  return (vue$1.openBlock(), vue$1.createElementBlock("div", {
254276
254423
  class: "cn-index-page",
254277
254424
  "data-testid": "cn-index-page",
254278
- onKeydown: _cache[26] || (_cache[26] = (...args) => ($options.onListKeydown && $options.onListKeydown(...args)))
254425
+ onKeydown: _cache[25] || (_cache[25] = (...args) => ($options.onListKeydown && $options.onListKeydown(...args)))
254279
254426
  }, [
254280
254427
  vue$1.renderSlot(_ctx.$slots, "header", {
254281
254428
  title: $props.title,
@@ -254765,7 +254912,7 @@ function render$1A(_ctx, _cache, $props, $setup, $data, $options) {
254765
254912
  'cn-index-page__main--table': $data.currentViewMode === 'table',
254766
254913
  'cn-index-page__main--split': $options.splitLayout === 'split',
254767
254914
  }]),
254768
- onScroll: _cache[23] || (_cache[23] = (...args) => ($options.onListScroll && $options.onListScroll(...args)))
254915
+ onScroll: _cache[22] || (_cache[22] = (...args) => ($options.onListScroll && $options.onListScroll(...args)))
254769
254916
  }, [
254770
254917
  vue$1.renderSlot(_ctx.$slots, "before-collection"),
254771
254918
  ($options.showInitialLoader)
@@ -255134,8 +255281,8 @@ function render$1A(_ctx, _cache, $props, $setup, $data, $options) {
255134
255281
  currentPageSize: $options.effectivePagination.limit || 20,
255135
255282
  class: "cn-index-page__pagination",
255136
255283
  onPageChanged: $options.onPageEvent,
255137
- onPageSizeChanged: _cache[22] || (_cache[22] = $event => (_ctx.$emit('page-size-changed', $event)))
255138
- }, null, 8 /* PROPS */, ["currentPage", "totalPages", "totalItems", "currentPageSize", "onPageChanged"]))
255284
+ onPageSizeChanged: $options.onPageSizeEvent
255285
+ }, null, 8 /* PROPS */, ["currentPage", "totalPages", "totalItems", "currentPageSize", "onPageChanged", "onPageSizeChanged"]))
255139
255286
  : vue$1.createCommentVNode("v-if", true)
255140
255287
  ], 34 /* CLASS, NEED_HYDRATION */), [
255141
255288
  [vue$1.vShow, $options.splitLayout !== 'detail']
@@ -255155,7 +255302,7 @@ function render$1A(_ctx, _cache, $props, $setup, $data, $options) {
255155
255302
  href: $options.splitCloseHref,
255156
255303
  title: $options.splitCloseTitle,
255157
255304
  "data-testid": "cn-index-page-split-close",
255158
- onClick: _cache[24] || (_cache[24] = vue$1.withModifiers((...args) => ($options.closeSplitPane && $options.closeSplitPane(...args)), ["prevent"]))
255305
+ onClick: _cache[23] || (_cache[23] = vue$1.withModifiers((...args) => ($options.closeSplitPane && $options.closeSplitPane(...args)), ["prevent"]))
255159
255306
  }, [
255160
255307
  vue$1.createVNode(_component_Close, { size: 16 }),
255161
255308
  vue$1.createElementVNode("span", null, vue$1.toDisplayString($options.splitCloseTitle), 1 /* TEXT */)
@@ -255185,7 +255332,7 @@ function render$1A(_ctx, _cache, $props, $setup, $data, $options) {
255185
255332
  facetData: $options.effectiveFacetData,
255186
255333
  showMetadata: $options.resolvedSidebar.showMetadata !== false
255187
255334
  }, $options.sidebarSearchProps, {
255188
- "onUpdate:open": _cache[25] || (_cache[25] = $event => ($data.sidebarOpen = $event)),
255335
+ "onUpdate:open": _cache[24] || (_cache[24] = $event => ($data.sidebarOpen = $event)),
255189
255336
  onSearch: $options.onSearchEvent,
255190
255337
  onColumnsChange: $options.onColumnsEvent,
255191
255338
  onFilterChange: $options.onFilterEvent,
@@ -268485,6 +268632,7 @@ var script$1c = {
268485
268632
  'layout-change',
268486
268633
  'open-integration',
268487
268634
  'refresh',
268635
+ 'related-object-click',
268488
268636
  'related-row-click',
268489
268637
  'relation-linked',
268490
268638
  'request-feature',
@@ -270466,6 +270614,23 @@ var script$1c = {
270466
270614
  this.$emit('open-integration', integrationId);
270467
270615
  },
270468
270616
 
270617
+ /**
270618
+ * Re-emit a click on an object in the Related widget. The page does not
270619
+ * know the routes; the host (CnPageRenderer on a manifest page) opens
270620
+ * the object's own detail page.
270621
+ *
270622
+ * @param {object} raw The clicked object; `@self` names its register and schema.
270623
+ */
270624
+ onRelatedObjectSelect(raw) {
270625
+ /**
270626
+ * @event related-object-click An object in the Related widget was
270627
+ * clicked. Payload is the raw object; `@self` carries its register,
270628
+ * schema and id.
270629
+ * @type {object}
270630
+ */
270631
+ this.$emit('related-object-click', raw);
270632
+ },
270633
+
270469
270634
  /**
270470
270635
  * Re-fetch the object after the geo widget persists `@self.geo` (so the
270471
270636
  * rest of the page — metadata, other widgets — reflects the new value),
@@ -272276,8 +272441,9 @@ function render$1c(_ctx, _cache, $props, $setup, $data, $options) {
272276
272441
  cnRegistry: $options.cnRegistry,
272277
272442
  availableWidgets: _ctx.widgets,
272278
272443
  onGeoSaved: $options.onGeoSaved,
272279
- onOpenIntegration: $options.onAutoBodyOpenIntegration
272280
- }, null, 8 /* PROPS */, ["widget", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration"])
272444
+ onOpenIntegration: $options.onAutoBodyOpenIntegration,
272445
+ onSelectObject: $options.onRelatedObjectSelect
272446
+ }, null, 8 /* PROPS */, ["widget", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration", "onSelectObject"])
272281
272447
  ])
272282
272448
  ]))
272283
272449
  : vue$1.createCommentVNode("v-if", true)
@@ -272501,8 +272667,9 @@ function render$1c(_ctx, _cache, $props, $setup, $data, $options) {
272501
272667
  cnRegistry: $options.cnRegistry,
272502
272668
  availableWidgets: $options.bodyGridWidgets,
272503
272669
  onGeoSaved: $options.onGeoSaved,
272504
- onOpenIntegration: $options.onAutoBodyOpenIntegration
272505
- }, null, 8 /* PROPS */, ["widget", "showCardTitle", "showActions", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration"])
272670
+ onOpenIntegration: $options.onAutoBodyOpenIntegration,
272671
+ onSelectObject: $options.onRelatedObjectSelect
272672
+ }, null, 8 /* PROPS */, ["widget", "showCardTitle", "showActions", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration", "onSelectObject"])
272506
272673
  ])
272507
272674
  ], 10 /* CLASS, PROPS */, _hoisted_19$8)
272508
272675
  ]),
@@ -272683,8 +272850,9 @@ function render$1c(_ctx, _cache, $props, $setup, $data, $options) {
272683
272850
  cnRegistry: $options.cnRegistry,
272684
272851
  availableWidgets: _ctx.widgets,
272685
272852
  onGeoSaved: $options.onGeoSaved,
272686
- onOpenIntegration: $options.onAutoBodyOpenIntegration
272687
- }, null, 8 /* PROPS */, ["widget", "showActions", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration"])
272853
+ onOpenIntegration: $options.onAutoBodyOpenIntegration,
272854
+ onSelectObject: $options.onRelatedObjectSelect
272855
+ }, null, 8 /* PROPS */, ["widget", "showActions", "objectId", "object", "objectType", "schemaObject", "register", "schema", "store", "surface", "integrationContext", "hideEmpty", "cnRegistry", "availableWidgets", "onGeoSaved", "onOpenIntegration", "onSelectObject"])
272688
272856
  ])
272689
272857
  ], 8 /* PROPS */, _hoisted_34$1))
272690
272858
  }), 128 /* KEYED_FRAGMENT */))
@@ -295213,7 +295381,7 @@ var script$s = {
295213
295381
  },
295214
295382
  },
295215
295383
 
295216
- emits: ['layout-change'],
295384
+ emits: ['layout-change', 'select-object'],
295217
295385
 
295218
295386
  data() {
295219
295387
  return {
@@ -295423,6 +295591,20 @@ var script$s = {
295423
295591
  },
295424
295592
 
295425
295593
  methods: {
295594
+ /**
295595
+ * Pass a related widget's object click up; the page opens the object.
295596
+ *
295597
+ * @param {object} raw The clicked object.
295598
+ * @return {void}
295599
+ */
295600
+ onSelectObject(raw) {
295601
+ /**
295602
+ * @event select-object An object row in a related widget of this grid was clicked. Payload is the raw object.
295603
+ * @type {object}
295604
+ */
295605
+ this.$emit('select-object', raw);
295606
+ },
295607
+
295426
295608
  // Expose the shared grid helpers to the template.
295427
295609
  cnGridCellStyle,
295428
295610
  hasGridRow,
@@ -295517,7 +295699,7 @@ function render$s(_ctx, _cache, $props, $setup, $data, $options) {
295517
295699
  "gs-h": widget.gridHeight
295518
295700
  }, [
295519
295701
  vue$1.createElementVNode("div", _hoisted_4$v, [
295520
- (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(widget.component), vue$1.mergeProps({ ref_for: true }, widget.props), null, 16 /* FULL_PROPS */))
295702
+ (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(widget.component), vue$1.mergeProps({ ref_for: true }, widget.props, { onSelectObject: $options.onSelectObject }), null, 16 /* FULL_PROPS */, ["onSelectObject"]))
295521
295703
  ])
295522
295704
  ], 8 /* PROPS */, _hoisted_3$w))
295523
295705
  }), 128 /* KEYED_FRAGMENT */))
@@ -295535,7 +295717,7 @@ function render$s(_ctx, _cache, $props, $setup, $data, $options) {
295535
295717
  class: vue$1.normalizeClass(["cn-grid__item", { 'cn-grid__item--row': $options.hasGridRow(widget) }]),
295536
295718
  style: vue$1.normalizeStyle($options.cnGridCellStyle(widget, $options.gridColumns))
295537
295719
  }, [
295538
- (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(widget.component), vue$1.mergeProps({ ref_for: true }, widget.props), null, 16 /* FULL_PROPS */))
295720
+ (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent(widget.component), vue$1.mergeProps({ ref_for: true }, widget.props, { onSelectObject: $options.onSelectObject }), null, 16 /* FULL_PROPS */, ["onSelectObject"]))
295539
295721
  ], 6 /* CLASS, STYLE */))
295540
295722
  }), 128 /* KEYED_FRAGMENT */))
295541
295723
  ], 14 /* CLASS, STYLE, PROPS */, _hoisted_5$j))
@@ -296038,6 +296220,53 @@ const defaultPageTypes = {
296038
296220
  'flow-detail': vue$1.defineAsyncComponent(() => Promise.resolve().then(function () { return CnFlowEditorPage; }).then((m) => m.default)),
296039
296221
  };
296040
296222
 
296223
+ /**
296224
+ * OpenRegister registers and schemas already described by id, so a click on
296225
+ * a related object costs at most one request per register or schema for as
296226
+ * long as this module stays loaded (the SPA's lifetime, across page
296227
+ * changes). Keyed by `${kind}:${id}`; a failed lookup is dropped again so the
296228
+ * next click retries it.
296229
+ *
296230
+ * @type {Map<string, Promise<{ id: string, slug: string }>>}
296231
+ */
296232
+ const entityDescriptorCache = new Map();
296233
+
296234
+ /**
296235
+ * Describe an OpenRegister register or schema (`{ id, slug }`) by its id or
296236
+ * slug, or null when it cannot be fetched. An object's `@self` names its
296237
+ * register and schema by id while a manifest may name them by slug; this is
296238
+ * the bridge. Lazy-imports axios and the router helper like the widgets do.
296239
+ *
296240
+ * @param {'registers'|'schemas'} kind The collection to ask.
296241
+ * @param {string|number} idOrSlug The id (or slug) to describe.
296242
+ * @return {Promise<{ id: string, slug: string }|null>} The descriptor.
296243
+ */
296244
+ function describeOpenRegisterEntity(kind, idOrSlug) {
296245
+ const key = `${kind}:${idOrSlug}`;
296246
+ if (!entityDescriptorCache.has(key)) {
296247
+ entityDescriptorCache.set(key, (async () => {
296248
+ try {
296249
+ const [{ default: axios }, { generateUrl }] = await Promise.all([
296250
+ import('@nextcloud/axios'),
296251
+ import('@nextcloud/router'),
296252
+ ]);
296253
+ const url = generateUrl('/apps/openregister/api/{kind}/{id}', { kind, id: String(idOrSlug) });
296254
+ const res = await axios.get(url);
296255
+ const data = res && res.data;
296256
+ if (!data || typeof data !== 'object') {
296257
+ throw new Error(`no descriptor for ${key}`)
296258
+ }
296259
+ return { id: String(data.id ?? idOrSlug), slug: String(data.slug ?? '') }
296260
+ } catch (error) {
296261
+ // One failed request must not pin every later click to nowhere.
296262
+ entityDescriptorCache.delete(key);
296263
+ throw error
296264
+ }
296265
+ })());
296266
+ }
296267
+ return entityDescriptorCache.get(key).catch(() => null)
296268
+ }
296269
+
296041
296270
  /** Recognised fixed slot names for v2 manifests. */
296042
296271
  const KNOWN_SLOTS = new Set(['body', 'sidebar', 'header-actions', 'footer', 'modal']);
296043
296272
 
@@ -297565,6 +297794,92 @@ var script$r = {
297565
297794
  })
297566
297795
  },
297567
297796
 
297797
+ /**
297798
+ * Open the detail page of an object clicked in a Related widget —
297799
+ * reaching here as `CnDetailPage`'s `related-object-click`, or as
297800
+ * `CnWidgetGrid`'s `select-object` on a v2 widget-grid page. Nothing
297801
+ * listened to either, so the rows looked like links and went nowhere.
297802
+ *
297803
+ * @param {object} raw The clicked object as the Related widget holds it.
297804
+ * @return {Promise<void>}
297805
+ */
297806
+ async onRelatedObjectOpen(raw) {
297807
+ const target = await this.relatedObjectTarget(raw);
297808
+ if (target) {
297809
+ this.$router.push(target).catch(() => {});
297810
+ }
297811
+ },
297812
+
297813
+ /**
297814
+ * Where a related object opens: the first `type: 'detail'` page bound
297815
+ * to the object's register and schema, with the id in whichever param
297816
+ * that page's route declares. The object's `@self` names register and
297817
+ * schema by id and a manifest may name them by slug, so a miss on the
297818
+ * literal pair describes both (one request each, cached) and matches
297819
+ * again. Null, silently, when there is no router or the object carries
297820
+ * no register, schema or id (nothing to open); null with a warning,
297821
+ * like a dead row click, when no detail page matches or the router
297822
+ * lacks the route.
297823
+ *
297824
+ * @param {object} raw The clicked object.
297825
+ * @return {Promise<object|null>} The router location, or null.
297826
+ */
297827
+ async relatedObjectTarget(raw) {
297828
+ const router = this.$router;
297829
+ if (!router || !raw || typeof raw !== 'object') {
297830
+ return null
297831
+ }
297832
+ const self = raw['@self'] || {};
297833
+ const id = raw.id ?? self.id ?? self.uuid ?? raw.uuid;
297834
+ const register = self.register ?? raw.register;
297835
+ const schema = self.schema ?? raw.schema;
297836
+ const missing = (v) => v === undefined || v === null || v === '';
297837
+ if (missing(id) || missing(register) || missing(schema)) {
297838
+ return null
297839
+ }
297840
+ let page = this.detailPageFor([String(register)], [String(schema)]);
297841
+ if (!page) {
297842
+ const [reg, sch] = await Promise.all([
297843
+ describeOpenRegisterEntity('registers', register),
297844
+ describeOpenRegisterEntity('schemas', schema),
297845
+ ]);
297846
+ page = this.detailPageFor(
297847
+ [String(register), reg?.id, reg?.slug].filter(Boolean),
297848
+ [String(schema), sch?.id, sch?.slug].filter(Boolean),
297849
+ );
297850
+ }
297851
+ if (!page) {
297852
+ // eslint-disable-next-line no-console
297853
+ console.warn(`[CnPageRenderer] No detail page is bound to register "${register}" and schema "${schema}", so related object "${id}" opens nowhere.`);
297854
+ return null
297855
+ }
297856
+ if (this.routeNameIsKnown(page.id) === false) {
297857
+ // eslint-disable-next-line no-console
297858
+ console.warn(`[CnPageRenderer] Detail page "${page.id}" is not a route the router has, so related object "${id}" opens nowhere.`);
297859
+ return null
297860
+ }
297861
+ const path = page.route ?? routePathFor(router, page.id);
297862
+ return { name: page.id, params: buildRouteParams(path, id, this.$route?.params) }
297863
+ },
297864
+
297865
+ /**
297866
+ * The first `type: 'detail'` page whose register and schema are both
297867
+ * among the given names (ids or slugs, compared as strings).
297868
+ *
297869
+ * @param {Array<string>} registerNames Names the register goes by.
297870
+ * @param {Array<string>} schemaNames Names the schema goes by.
297871
+ * @return {object|null} The page, or null.
297872
+ */
297873
+ detailPageFor(registerNames, schemaNames) {
297874
+ for (const page of this.detailPageByRegisterSchema.values()) {
297875
+ const cfg = page.config || {};
297876
+ if (registerNames.includes(String(cfg.register)) && schemaNames.includes(String(cfg.schema))) {
297877
+ return page
297878
+ }
297879
+ }
297880
+ return null
297881
+ },
297882
+
297568
297883
  /**
297569
297884
  * Whether the router has a route by this name.
297570
297885
  *
@@ -297974,14 +298289,16 @@ function render$r(_ctx, _cache, $props, $setup, $data, $options) {
297974
298289
  key: 0,
297975
298290
  widgets: $options.widgetsBySlot.get('body'),
297976
298291
  editable: $options.bodyEditable,
297977
- slotName: "body"
297978
- }, null, 8 /* PROPS */, ["widgets", "editable"]))
298292
+ slotName: "body",
298293
+ onSelectObject: $options.onRelatedObjectOpen
298294
+ }, null, 8 /* PROPS */, ["widgets", "editable", "onSelectObject"]))
297979
298295
  : ($options.resolvedComponent)
297980
298296
  ? (vue$1.openBlock(), vue$1.createBlock(vue$1.resolveDynamicComponent($options.resolvedComponent), vue$1.mergeProps({ key: $options.pageRenderKey }, { ..._ctx.$attrs, ...$options.resolvedProps }, {
297981
298297
  onView: $options.onRowOpen,
297982
298298
  onRowClick: $options.onRowOpen,
297983
298299
  onRowAuxClick: $options.onRowOpen,
297984
298300
  onEditOpen: $options.onRowOpen,
298301
+ onRelatedObjectClick: $options.onRelatedObjectOpen,
297985
298302
  onConfigure: _cache[0] || (_cache[0] = $event => ($data.showConfigModal = true))
297986
298303
  }), vue$1.createSlots({ _: 2 /* DYNAMIC */ }, [
297987
298304
  vue$1.renderList($options.resolvedSlotEntries, (entry) => {
@@ -298017,42 +298334,47 @@ function render$r(_ctx, _cache, $props, $setup, $data, $options) {
298017
298334
  key: "0"
298018
298335
  }
298019
298336
  : undefined
298020
- ]), 1040 /* FULL_PROPS, DYNAMIC_SLOTS */, ["onView", "onRowClick", "onRowAuxClick", "onEditOpen"]))
298337
+ ]), 1040 /* FULL_PROPS, DYNAMIC_SLOTS */, ["onView", "onRowClick", "onRowAuxClick", "onEditOpen", "onRelatedObjectClick"]))
298021
298338
  : vue$1.createCommentVNode("v-if", true),
298022
298339
  ($options.widgetsBySlot.has('header-actions'))
298023
298340
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnWidgetGrid, {
298024
298341
  key: 2,
298025
298342
  widgets: $options.widgetsBySlot.get('header-actions'),
298026
- slotName: "header-actions"
298027
- }, null, 8 /* PROPS */, ["widgets"]))
298343
+ slotName: "header-actions",
298344
+ onSelectObject: $options.onRelatedObjectOpen
298345
+ }, null, 8 /* PROPS */, ["widgets", "onSelectObject"]))
298028
298346
  : vue$1.createCommentVNode("v-if", true),
298029
298347
  ($options.widgetsBySlot.has('footer'))
298030
298348
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnWidgetGrid, {
298031
298349
  key: 3,
298032
298350
  widgets: $options.widgetsBySlot.get('footer'),
298033
- slotName: "footer"
298034
- }, null, 8 /* PROPS */, ["widgets"]))
298351
+ slotName: "footer",
298352
+ onSelectObject: $options.onRelatedObjectOpen
298353
+ }, null, 8 /* PROPS */, ["widgets", "onSelectObject"]))
298035
298354
  : vue$1.createCommentVNode("v-if", true),
298036
298355
  ($options.widgetsBySlot.has('modal'))
298037
298356
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnWidgetGrid, {
298038
298357
  key: 4,
298039
298358
  widgets: $options.widgetsBySlot.get('modal'),
298040
- slotName: "modal"
298041
- }, null, 8 /* PROPS */, ["widgets"]))
298359
+ slotName: "modal",
298360
+ onSelectObject: $options.onRelatedObjectOpen
298361
+ }, null, 8 /* PROPS */, ["widgets", "onSelectObject"]))
298042
298362
  : vue$1.createCommentVNode("v-if", true),
298043
298363
  ($options.widgetsBySlot.has('sidebar') && $options.pageSidebarVisibleValue)
298044
298364
  ? (vue$1.openBlock(), vue$1.createBlock(_component_CnWidgetGrid, {
298045
298365
  key: 5,
298046
298366
  widgets: $options.widgetsBySlot.get('sidebar'),
298047
- slotName: "sidebar"
298048
- }, null, 8 /* PROPS */, ["widgets"]))
298367
+ slotName: "sidebar",
298368
+ onSelectObject: $options.onRelatedObjectOpen
298369
+ }, null, 8 /* PROPS */, ["widgets", "onSelectObject"]))
298049
298370
  : vue$1.createCommentVNode("v-if", true),
298050
298371
  (vue$1.openBlock(true), vue$1.createElementBlock(vue$1.Fragment, null, vue$1.renderList($options.dynamicSlotKeys, (dynamicSlot) => {
298051
298372
  return (vue$1.openBlock(), vue$1.createBlock(_component_CnWidgetGrid, {
298052
298373
  key: dynamicSlot,
298053
298374
  widgets: $options.widgetsBySlot.get(dynamicSlot),
298054
- slotName: dynamicSlot
298055
- }, null, 8 /* PROPS */, ["widgets", "slotName"]))
298375
+ slotName: dynamicSlot,
298376
+ onSelectObject: $options.onRelatedObjectOpen
298377
+ }, null, 8 /* PROPS */, ["widgets", "slotName", "onSelectObject"]))
298056
298378
  }), 128 /* KEYED_FRAGMENT */))
298057
298379
  ], 64 /* STABLE_FRAGMENT */))
298058
298380
  : ($options.resolvedComponent)
@@ -298061,6 +298383,7 @@ function render$r(_ctx, _cache, $props, $setup, $data, $options) {
298061
298383
  onRowClick: $options.onRowOpen,
298062
298384
  onRowAuxClick: $options.onRowOpen,
298063
298385
  onEditOpen: $options.onRowOpen,
298386
+ onRelatedObjectClick: $options.onRelatedObjectOpen,
298064
298387
  onConfigure: _cache[1] || (_cache[1] = $event => ($data.showConfigModal = true))
298065
298388
  }), vue$1.createSlots({ _: 2 /* DYNAMIC */ }, [
298066
298389
  vue$1.renderList($options.resolvedSlotEntries, (entry) => {
@@ -298096,7 +298419,7 @@ function render$r(_ctx, _cache, $props, $setup, $data, $options) {
298096
298419
  key: "0"
298097
298420
  }
298098
298421
  : undefined
298099
- ]), 1040 /* FULL_PROPS, DYNAMIC_SLOTS */, ["onView", "onRowClick", "onRowAuxClick", "onEditOpen"]))
298422
+ ]), 1040 /* FULL_PROPS, DYNAMIC_SLOTS */, ["onView", "onRowClick", "onRowAuxClick", "onEditOpen", "onRelatedObjectClick"]))
298100
298423
  : vue$1.createCommentVNode("v-if", true),
298101
298424
  (!$options.hasRenderableBody)
298102
298425
  ? (vue$1.openBlock(), vue$1.createElementBlock("div", _hoisted_2$A, [
@@ -304420,6 +304743,136 @@ script$j.render = render$j;
304420
304743
  script$j.__file = "src/components/CnAiCompanion/CnAiCompanion.vue";
304421
304744
  const __sfc_main$j = script$j;
304422
304745
 
304746
+ /**
304747
+ * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
304748
+ * SPDX-License-Identifier: EUPL-1.2
304749
+ *
304750
+ * The label a person reads for one notification rule in their settings.
304751
+ *
304752
+ * A rule is declared in a schema's `x-openregister-notifications` map under a
304753
+ * KEY (`caseAssigned`, `clientUpdated`, `onCreate`). The key is an identifier
304754
+ * for a developer. The settings pane used to print it as is, so a person saw
304755
+ * "substitutionRegisteredForSubstitute" next to a switch and had to guess.
304756
+ *
304757
+ * The label is taken from the first source that has one, most specific first:
304758
+ *
304759
+ * 1. the rule's own words, when OpenRegister passes them on the preference
304760
+ * entry (`label`, then `title`), as a plain string or a per-locale map
304761
+ * like the dialect's `subject` (`{ "nl": "…", "en": "…" }`);
304762
+ * 2. the app's own label for the rule, from the `labels` map the app hands
304763
+ * to CnAppRoot (`notificationLabels`), keyed `<schema>.<key>` or `<key>`;
304764
+ * 3. the library's wording for the generic object-event keys;
304765
+ * 4. the key made readable: `caseAssigned` becomes "Case assigned".
304766
+ *
304767
+ * The rule's `subject` is used only when it carries no `{{placeholder}}`: it
304768
+ * is the notification TITLE template, and "Case "{{title}}" assigned to you"
304769
+ * reads worse as a setting than the readable key does.
304770
+ *
304771
+ * Pure: no store, no fetch, no Vue.
304772
+ *
304773
+ * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
304774
+ */
304775
+
304776
+ /**
304777
+ * Pick the string for the current language out of a string or per-locale map.
304778
+ *
304779
+ * @param {string|object|null|undefined} value A string, or `{ nl, en, … }`.
304780
+ * @param {string} [language] The current language, e.g. `nl` or `de_DE`.
304781
+ * @return {string} The resolved text, or '' when there is none.
304782
+ */
304783
+ function resolveLocalised(value, language = 'en') {
304784
+ if (typeof value === 'string') {
304785
+ return value.trim()
304786
+ }
304787
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
304788
+ return ''
304789
+ }
304790
+ const lang = String(language || 'en');
304791
+ const base = lang.split(/[-_]/)[0];
304792
+ for (const candidate of [lang, base, 'en']) {
304793
+ if (typeof value[candidate] === 'string' && value[candidate].trim() !== '') {
304794
+ return value[candidate].trim()
304795
+ }
304796
+ }
304797
+ const first = Object.values(value).find((text) => typeof text === 'string' && text.trim() !== '');
304798
+ return first ? first.trim() : ''
304799
+ }
304800
+
304801
+ /**
304802
+ * Make a rule key readable: split camelCase, snake_case and kebab-case into
304803
+ * words, keep acronyms, and capitalise only the first word.
304804
+ *
304805
+ * `caseAssigned` → "Case assigned", `object_created` → "Object created",
304806
+ * `newBRPRecord` → "New BRP record".
304807
+ *
304808
+ * @param {string} key The rule key.
304809
+ * @return {string} A readable sentence-case label, or the key when it has no words.
304810
+ */
304811
+ function humaniseRuleKey(key) {
304812
+ const words = String(key || '')
304813
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
304814
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
304815
+ .split(/[\s_\-.]+/)
304816
+ .filter(Boolean);
304817
+ if (words.length === 0) {
304818
+ return String(key || '')
304819
+ }
304820
+ return words
304821
+ .map((word, index) => {
304822
+ const isAcronym = word.length > 1 && word === word.toUpperCase() && /[A-Z]/.test(word);
304823
+ if (isAcronym) {
304824
+ return word
304825
+ }
304826
+ const lower = word.toLowerCase();
304827
+ return index === 0 ? lower.charAt(0).toUpperCase() + lower.slice(1) : lower
304828
+ })
304829
+ .join(' ')
304830
+ }
304831
+
304832
+ /**
304833
+ * The label for one preference entry.
304834
+ *
304835
+ * @param {object} entry A preference entry: `{ schema, notification, label?, title?, subject? }`.
304836
+ * @param {object} [options] Where else a label may come from.
304837
+ * @param {object} [options.labels] App-supplied labels keyed `<schema>.<key>` or `<key>`;
304838
+ * values are strings or per-locale maps.
304839
+ * @param {object} [options.known] Library wording for generic keys, keyed by rule key.
304840
+ * @param {string} [options.language] The current language.
304841
+ * @return {string} The label to show.
304842
+ */
304843
+ function notificationRuleLabel(entry, { labels = {}, known = {}, language = 'en' } = {}) {
304844
+ const key = String(entry?.notification ?? '');
304845
+ const schema = String(entry?.schema ?? '');
304846
+
304847
+ for (const field of ['label', 'title']) {
304848
+ const own = resolveLocalised(entry?.[field], language);
304849
+ if (own) {
304850
+ return own
304851
+ }
304852
+ }
304853
+
304854
+ const map = labels && typeof labels === 'object' ? labels : {};
304855
+ for (const lookup of [schema ? `${schema}.${key}` : '', key]) {
304856
+ if (lookup && Object.hasOwn(map, lookup)) {
304857
+ const fromApp = resolveLocalised(map[lookup], language);
304858
+ if (fromApp) {
304859
+ return fromApp
304860
+ }
304861
+ }
304862
+ }
304863
+
304864
+ if (known && typeof known[key] === 'string' && known[key]) {
304865
+ return known[key]
304866
+ }
304867
+
304868
+ const subject = resolveLocalised(entry?.subject, language);
304869
+ if (subject && !subject.includes('{{')) {
304870
+ return subject
304871
+ }
304872
+
304873
+ return humaniseRuleKey(key)
304874
+ }
304875
+
304423
304876
  const PREFS_PATH = '/apps/openregister/api/notification-preferences';
304424
304877
 
304425
304878
  var script$i = {
@@ -304443,6 +304896,28 @@ var script$i = {
304443
304896
  * than an always-empty pane.
304444
304897
  */
304445
304898
  cnAppId: { default: () => '' },
304899
+ /**
304900
+ * App-supplied labels for its notification rules, provided by the
304901
+ * CnAppRoot ancestor from its `notificationLabels` prop. Keyed
304902
+ * `<schema>.<key>` or `<key>`.
304903
+ */
304904
+ cnNotificationLabels: { default: () => ({}) },
304905
+ },
304906
+
304907
+ props: {
304908
+ /**
304909
+ * Labels for the notification rules, keyed `<schema>.<key>` or
304910
+ * `<key>`; values are strings or per-locale maps (`{ nl, en }`).
304911
+ * Optional: inside CnAppRoot the app's `notificationLabels` arrive by
304912
+ * inject, and a rule without a label still reads as words, never as
304913
+ * its raw key. A prop given here wins over the injected map.
304914
+ *
304915
+ * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
304916
+ */
304917
+ labels: {
304918
+ type: Object,
304919
+ default: null,
304920
+ },
304446
304921
  },
304447
304922
 
304448
304923
  data() {
@@ -304568,18 +305043,43 @@ var script$i = {
304568
305043
  },
304569
305044
 
304570
305045
  /**
304571
- * Human-readable label for a notification key, falling back to the key.
305046
+ * Human-readable label for a notification rule. Never the raw key: the
305047
+ * rule's own label, then the app's, then the library's wording for the
305048
+ * generic keys, then the key split into words.
304572
305049
  *
304573
- * @param {string} key The notification annotation key.
305050
+ * @param {object} entry The preference row.
304574
305051
  * @return {string} A display label.
305052
+ *
305053
+ * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
304575
305054
  */
304576
- notificationLabel(key) {
305055
+ notificationLabel(entry) {
304577
305056
  const known = {
304578
305057
  object_created: l10n.translate('nextcloud-vue', 'When an item is created'),
304579
305058
  object_updated: l10n.translate('nextcloud-vue', 'When an item is updated'),
304580
305059
  object_transitioned: l10n.translate('nextcloud-vue', 'When an item is assigned or changes status'),
304581
305060
  };
304582
- return known[key] || key
305061
+ const language = this.currentLanguage();
305062
+ const injected = typeof this.cnNotificationLabels === 'function'
305063
+ ? this.cnNotificationLabels()
305064
+ : this.cnNotificationLabels;
305065
+ return notificationRuleLabel(entry, {
305066
+ labels: { ...(injected || {}), ...(this.labels || {}) },
305067
+ known,
305068
+ language,
305069
+ })
305070
+ },
305071
+
305072
+ /**
305073
+ * The person's language, `en` when it cannot be read.
305074
+ *
305075
+ * @return {string} The language code.
305076
+ */
305077
+ currentLanguage() {
305078
+ try {
305079
+ return (typeof l10n.getLanguage === 'function' && l10n.getLanguage()) || 'en'
305080
+ } catch {
305081
+ return 'en'
305082
+ }
304583
305083
  },
304584
305084
 
304585
305085
  /**
@@ -304669,7 +305169,7 @@ function render$i(_ctx, _cache, $props, $setup, $data, $options) {
304669
305169
  "onUpdate:modelValue": $event => ($options.onToggle(entry, $event))
304670
305170
  }, {
304671
305171
  default: vue$1.withCtx(() => [
304672
- vue$1.createTextVNode(vue$1.toDisplayString($options.notificationLabel(entry.notification)), 1 /* TEXT */)
305172
+ vue$1.createTextVNode(vue$1.toDisplayString($options.notificationLabel(entry)), 1 /* TEXT */)
304673
305173
  ]),
304674
305174
  _: 2 /* DYNAMIC */
304675
305175
  }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["modelValue", "disabled", "onUpdate:modelValue"]),
@@ -306487,6 +306987,18 @@ var script$f = {
306487
306987
  * wiring.
306488
306988
  */
306489
306989
  cnAppId: this.appId,
306990
+ /**
306991
+ * The app's labels for its notification rules, read by the
306992
+ * notification-preferences pane in the user-settings modal. A
306993
+ * getter so a late prop update is still seen by a pane that
306994
+ * mounts after it.
306995
+ *
306996
+ * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
306997
+ */
306998
+ get cnNotificationLabels() {
306999
+ return self.notificationLabels || {}
307000
+ },
307001
+
306490
307002
  /**
306491
307003
  * Target repo slug for the in-product feature-request deep
306492
307004
  * link (e.g. `ConductionNL/pipelinq`). Read from the
@@ -306902,6 +307414,23 @@ var script$f = {
306902
307414
  default: (key) => key,
306903
307415
  },
306904
307416
 
307417
+ /**
307418
+ * Labels for the app's notification rules, shown in the
307419
+ * user-settings notification pane instead of the rule keys. Keyed
307420
+ * `<schema>.<key>` (or just `<key>`); values are already translated
307421
+ * strings, e.g. `{ 'case.caseAssigned': t('dossiq', 'A case is
307422
+ * assigned to me') }`, or per-locale maps `{ nl, en }`. A rule's own
307423
+ * `label` from OpenRegister wins; a rule with no label anywhere reads
307424
+ * as its key split into words, never as the raw key.
307425
+ *
307426
+ * @type {{[key: string]: (string|object)}}
307427
+ * @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
307428
+ */
307429
+ notificationLabels: {
307430
+ type: Object,
307431
+ default: () => ({}),
307432
+ },
307433
+
306905
307434
  /**
306906
307435
  * List of permission strings the current user holds. Forwarded
306907
307436
  * to CnAppNav's permission filter.
@@ -349472,6 +350001,7 @@ exports.getRegisterApiUrl = getRegisterApiUrl;
349472
350001
  exports.getSchemaApiUrl = getSchemaApiUrl;
349473
350002
  exports.getSharedRegistry = getSharedRegistry;
349474
350003
  exports.getWidgetTypeEntry = getWidgetTypeEntry;
350004
+ exports.humaniseRuleKey = humaniseRuleKey;
349475
350005
  exports.installIntegrationRegistry = installIntegrationRegistry;
349476
350006
  exports.installModalStack = installModalStack;
349477
350007
  exports.integrations = integrations;
@@ -349503,6 +350033,7 @@ exports.mergePages = mergePages;
349503
350033
  exports.mergeUserLayout = mergeUserLayout;
349504
350034
  exports.networkError = networkError;
349505
350035
  exports.notesIntegration = notesIntegration;
350036
+ exports.notificationRuleLabel = notificationRuleLabel;
349506
350037
  exports.offlineCollection = index;
349507
350038
  exports.openRowTarget = openRowTarget;
349508
350039
  exports.openprojectIntegration = openprojectIntegration;