@conduction/nextcloud-vue 2.110.0 → 2.112.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 (142) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +2 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
  11. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
  23. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  25. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  29. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +52 -8
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +98 -63
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
  44. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +203 -14
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  50. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -1
  51. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  52. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
  53. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
  54. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
  55. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
  56. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  57. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
  58. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  59. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
  60. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  61. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
  62. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
  63. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
  64. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  65. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
  66. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  67. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
  68. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
  69. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
  70. package/dist/esm/composables/useRefLabels.js +26 -6
  71. package/dist/esm/composables/useRefLabels.js.map +1 -1
  72. package/dist/esm/integrations/registry.js +4 -0
  73. package/dist/esm/integrations/registry.js.map +1 -1
  74. package/dist/esm/l10n/en.json.js +3 -1
  75. package/dist/esm/l10n/en.json.js.map +1 -1
  76. package/dist/esm/l10n/nl.json.js +3 -1
  77. package/dist/esm/l10n/nl.json.js.map +1 -1
  78. package/dist/esm/library-style.js +1 -1
  79. package/dist/esm/nextcloud-vue.css +630 -303
  80. package/dist/esm/store/plugins/liveUpdates.js +3 -1
  81. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  82. package/dist/esm/store/plugins/logs.js +2 -1
  83. package/dist/esm/store/plugins/logs.js.map +1 -1
  84. package/dist/esm/utils/detailActionModel.js +28 -1
  85. package/dist/esm/utils/detailActionModel.js.map +1 -1
  86. package/dist/esm/utils/fillTemplate.js +13 -5
  87. package/dist/esm/utils/fillTemplate.js.map +1 -1
  88. package/dist/esm/utils/headerFieldChips.js +21 -2
  89. package/dist/esm/utils/headerFieldChips.js.map +1 -1
  90. package/dist/esm/utils/mapList.js +26 -11
  91. package/dist/esm/utils/mapList.js.map +1 -1
  92. package/dist/esm/utils/savedViewHelpers.js +40 -3
  93. package/dist/esm/utils/savedViewHelpers.js.map +1 -1
  94. package/dist/esm/utils/savedViewMenu.js +12 -5
  95. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  96. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  97. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  98. package/dist/nextcloud-vue.cjs.js +2760 -1995
  99. package/dist/nextcloud-vue.cjs.js.map +2 -2
  100. package/dist/nextcloud-vue.css +630 -303
  101. package/l10n/en.json +3 -1
  102. package/l10n/nl.json +3 -1
  103. package/package.json +1 -1
  104. package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
  105. package/src/components/CnAppRoot/CnAppRoot.vue +2 -1
  106. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
  107. package/src/components/CnBoardView/CnBoardView.vue +58 -5
  108. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  109. package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
  110. package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
  111. package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
  112. package/src/components/CnDetailPage/CnDetailPage.vue +77 -20
  113. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
  114. package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
  115. package/src/components/CnIndexPage/CnIndexPage.md +1 -1
  116. package/src/components/CnIndexPage/CnIndexPage.vue +211 -16
  117. package/src/components/CnIndexSidebar/CnIndexSidebar.md +1 -1
  118. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +2 -2
  119. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
  120. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  121. package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
  122. package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
  123. package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
  124. package/src/composables/useRefLabels.js +25 -5
  125. package/src/css/board.css +5 -2
  126. package/src/css/dialog.css +53 -3
  127. package/src/css/index-page.css +26 -1
  128. package/src/css/look-board-detail.css +55 -22
  129. package/src/css/look-board-index.css +82 -12
  130. package/src/css/look-board-reports.css +1 -1
  131. package/src/css/look-board.css +84 -11
  132. package/src/integrations/registry.js +4 -0
  133. package/src/schemas/app-manifest-v2.schema.json +11 -5
  134. package/src/store/plugins/liveUpdates.js +3 -1
  135. package/src/store/plugins/logs.js +2 -1
  136. package/src/utils/detailActionModel.js +27 -0
  137. package/src/utils/fillTemplate.js +13 -5
  138. package/src/utils/headerFieldChips.js +21 -2
  139. package/src/utils/mapList.js +26 -11
  140. package/src/utils/savedViewHelpers.js +39 -2
  141. package/src/utils/savedViewMenu.js +12 -5
  142. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.133.0",
6
+ "version": "2.134.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -1212,6 +1212,7 @@
1212
1212
  ]
1213
1213
  },
1214
1214
  "pill": { "type": "string", "description": "The field shown as a status pill." },
1215
+ "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the `pill` field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } },
1215
1216
  "pillColors": {
1216
1217
  "type": "object",
1217
1218
  "description": "Pill value to badge variant. A value without an entry takes the default variant.",
@@ -1378,7 +1379,7 @@
1378
1379
  "filter": { "type": "object", "additionalProperties": true }
1379
1380
  }
1380
1381
  },
1381
- "resolve": { "type": "object", "additionalProperties": false, "required": ["register", "schema"], "description": "detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "labelField": { "type": "string", "minLength": 1 } } },
1382
+ "resolve": { "type": "object", "additionalProperties": false, "required": ["register", "schema"], "description": "detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "labelField": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1, "description": "pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto." }, "iconField": { "type": "string", "minLength": 1, "description": "pill-color-field: the referenced record's property holding a registered icon name, shown before the label." } } },
1382
1383
  "countLabel": {
1383
1384
  "type": "object",
1384
1385
  "additionalProperties": false,
@@ -4694,6 +4695,11 @@
4694
4695
  "type": "string",
4695
4696
  "description": "One label for the control's label filter."
4696
4697
  },
4698
+ "icon": {
4699
+ "type": "string",
4700
+ "minLength": 1,
4701
+ "description": "pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."
4702
+ },
4697
4703
  "overrides": {
4698
4704
  "type": "object",
4699
4705
  "additionalProperties": false,
@@ -5339,14 +5345,14 @@
5339
5345
  "additionalProperties": false,
5340
5346
  "description": "type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.",
5341
5347
  "properties": {
5342
- "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5348
+ "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5343
5349
  "number": { "type": "string", "minLength": 1 },
5344
5350
  "title": { "type": "string", "minLength": 1 },
5345
5351
  "meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } } ] } },
5346
- "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5352
+ "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5347
5353
  "footerDate": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "overdueVariant": { "type": "string", "enum": ["error", "warning"], "description": "The date's tone once it has passed (2.132.0)." } } },
5348
5354
  "description": { "description": "A line under the meta line: a field, or `{ field, maxLength? }` cut at maxLength characters (140 by default) with an ellipsis (2.132.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "maxLength": { "type": "integer", "minimum": 1 } } }] },
5349
- "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } }
5355
+ "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } }
5350
5356
  }
5351
5357
  },
5352
5358
  "afterList": { "type": "array", "description": "type='index': cards after the list, stacked full width in the listed order, the counterpart of statRow above it (index-scope-presentation). Each entry is a widget definition resolved like statRow's. A query preset or a folder scope can set its own. Maps to CnIndexPage `afterList`.", "items": { "type": "object", "required": ["id", "type"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "subtitle": { "type": "string" }, "content": { "type": "object" }, "headerLink": { "type": "object" } } } },
@@ -399,6 +399,8 @@ export function liveUpdatesPlugin(opts = {}) {
399
399
  // Plain (non-reactive) Map: type → last params object
400
400
  store.__lastCollectionParams = new Map()
401
401
 
402
+ // Detached: the store outlives the component whose setup first
403
+ // used it, and an attached observer is removed with that scope.
402
404
  store.$onAction(({ name, args, after }) => {
403
405
  if (name === 'fetchCollection') {
404
406
  const [type, params = {}] = args
@@ -406,7 +408,7 @@ export function liveUpdatesPlugin(opts = {}) {
406
408
  store.__lastCollectionParams.set(type, params)
407
409
  })
408
410
  }
409
- })
411
+ }, true)
410
412
 
411
413
  // -- In-flight dedup for fetchObject --
412
414
  // Plain Map: dedupKey → Promise
@@ -156,6 +156,7 @@ export function logsPlugin(options = {}) {
156
156
 
157
157
  if (autoRefreshOnItemChange) {
158
158
  plugin.setup = function setup(store) {
159
+ // Detached: the store outlives the component whose setup first used it.
159
160
  store.$onAction(({ name, after }) => {
160
161
  if (name !== 'setItem') {
161
162
  return
@@ -170,7 +171,7 @@ export function logsPlugin(options = {}) {
170
171
  store.clearLogs()
171
172
  }
172
173
  })
173
- })
174
+ }, true)
174
175
  }
175
176
  }
176
177
 
@@ -16,6 +16,7 @@
16
16
  * @module utils/detailActionModel
17
17
  */
18
18
 
19
+ import { variantForColor } from './headerFieldChips.js'
19
20
  import { readPath } from './readPath.js'
20
21
  import { evaluateVisibleWhenLocal } from './visibleWhen.js'
21
22
 
@@ -239,6 +240,32 @@ export function resolvePill(config, object) {
239
240
  }
240
241
  }
241
242
 
243
+ /**
244
+ * What a pill takes from the record its `resolve` points at
245
+ * (`resolve.colorField`, `resolve.iconField`): the tone its colour name maps
246
+ * onto (an NL Design System hue or a badge role, see `variantForColor`), which
247
+ * replaces the pill's own `variant` and `colorMap`, and an icon name. An empty
248
+ * colour or icon leaves the pill as declared.
249
+ *
250
+ * @param {{color?: string|null, icon?: string|null}|null|undefined} look The names read off the record.
251
+ * @return {{variant?: string, colorMap?: null, icon?: string}} The keys to lay over the pill.
252
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-pill-takes-its-tone-and-icon-from-the-referenced-record
253
+ */
254
+ export function pillLook(look) {
255
+ const out = {}
256
+ if (!look || typeof look !== 'object') {
257
+ return out
258
+ }
259
+ if (typeof look.color === 'string' && look.color !== '') {
260
+ out.variant = variantForColor(look.color)
261
+ out.colorMap = null
262
+ }
263
+ if (typeof look.icon === 'string' && look.icon !== '') {
264
+ out.icon = look.icon
265
+ }
266
+ return out
267
+ }
268
+
242
269
  /**
243
270
  * Resolve a tab's count.
244
271
  *
@@ -7,7 +7,7 @@ import { formatMoney } from './formatMetric.js'
7
7
  import { readPath } from './readPath.js'
8
8
  import { userDisplayName } from './userDisplayName.js'
9
9
 
10
- const TOKEN = /\{([^{}\s|]+)(?:\|([a-z]+)(?::([^{}\s]+))?)?\}/g
10
+ const TOKEN = /\{([^{}\s|]+)(?:\|([A-Za-z]+)(?::([^{}\s]+))?)?\}/g
11
11
 
12
12
  /**
13
13
  * A moment in the board's short form with the time: "4 okt, 10:52", or
@@ -68,12 +68,18 @@ export function formatLongDate(value, now = new Date(), withYear = false) {
68
68
  * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).
69
69
  * @param {object|null} [object] The row, for a currency read from a field.
70
70
  * @param {object|null} [config] The app config map, for the reporting currency.
71
+ * @param {object|null} [formatters] The app's named formatters (`cnFormatters`), for any other format name.
71
72
  * @return {string} The text, '' when nothing can be shown.
73
+ * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters
72
74
  */
73
- function formatValue(value, format, now, arg = '', object = null, config = null) {
75
+ function formatValue(value, format, now, arg = '', object = null, config = null, formatters = null) {
74
76
  if (value === null || value === undefined || value === '' || typeof value === 'object') {
75
77
  return ''
76
78
  }
79
+ if (format !== '' && formatters && typeof formatters[format] === 'function') {
80
+ const shown = formatters[format](value, object)
81
+ return shown === null || shown === undefined ? '' : String(shown)
82
+ }
77
83
  if (format === 'date') {
78
84
  return formatBoardDate(value, now)
79
85
  }
@@ -130,14 +136,16 @@ function formatValue(value, format, now, arg = '', object = null, config = null)
130
136
  * the row field is empty, the app's reporting currency), `{path|relative}` and
131
137
  * `{path|relativetime}` say "today", "yesterday" and then the short date
132
138
  * ("vandaag, 09.42"), and `{path|days}` is the
133
- * whole days since that date ("has waited {createdAt|days} days"). A token without a
139
+ * whole days since that date ("has waited {createdAt|days} days"). Any other
140
+ * name is one of the app's formatters, passed as `context.formatters`
141
+ * (`{id|caseAddress}`). A token without a
134
142
  * value empties the WHOLE template, because "Termijn " is worse than nothing.
135
143
  * A template without tokens is returned as written (a constant label).
136
144
  *
137
145
  * @param {string} template The template, already translated.
138
146
  * @param {object|null} object The object to read.
139
147
  * @param {Date} [now] The reference moment for dates.
140
- * @param {{config?: object}|null} [context] The app config map, for the reporting currency of a money token.
148
+ * @param {{config?: object, formatters?: object}|null} [context] The app config map, for the reporting currency of a money token, and the app's named formatters.
141
149
  * @return {string} The filled text, or ''.
142
150
  * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
143
151
  */
@@ -148,7 +156,7 @@ export function fillTemplate(template, object, now = new Date(), context = null)
148
156
  let missing = false
149
157
  const text = template.replace(TOKEN, (_match, path, format, arg) => {
150
158
  const value = object && typeof object === 'object' ? readPath(object, path) : undefined
151
- const shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null)
159
+ const shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null, context && context.formatters ? context.formatters : null)
152
160
  if (shown === '') {
153
161
  missing = true
154
162
  }
@@ -11,7 +11,12 @@
11
11
  /** The renderers a header chip can use. */
12
12
  export const HEADER_CHIP_FORMATS = Object.freeze(['text', 'mono', 'badge', 'user', 'date'])
13
13
 
14
- /** The variants a colour name maps onto; anything else is neutral. */
14
+ /**
15
+ * The variants a colour name maps onto; anything else is neutral. A name is a
16
+ * badge role (`success`, `warning`, ...) or an NL Design System hue as a
17
+ * register stores it (`red`, `blue-light`, ...): a hue and its light tint
18
+ * take the same tone.
19
+ */
15
20
  const COLOR_VARIANTS = Object.freeze({
16
21
  success: 'success',
17
22
  warning: 'warning',
@@ -19,6 +24,20 @@ const COLOR_VARIANTS = Object.freeze({
19
24
  info: 'info',
20
25
  primary: 'primary',
21
26
  neutral: 'default',
27
+ blue: 'info',
28
+ 'blue-light': 'info',
29
+ green: 'success',
30
+ 'green-light': 'success',
31
+ orange: 'warning',
32
+ 'orange-light': 'warning',
33
+ red: 'error',
34
+ 'red-light': 'error',
35
+ purple: 'purple',
36
+ 'purple-light': 'purple',
37
+ teal: 'teal',
38
+ 'teal-light': 'teal',
39
+ grey: 'default',
40
+ 'grey-light': 'default',
22
41
  })
23
42
 
24
43
  /**
@@ -69,7 +88,7 @@ export function isEmptyChipValue(value) {
69
88
  /**
70
89
  * Map a colour name read off a referenced object onto a badge variant.
71
90
  *
72
- * @param {*} name The colour name (`success`, `warning`, `error`, `info`, `neutral`).
91
+ * @param {*} name The colour name: a badge role (`success`, `warning`, `error`, `info`, `neutral`) or an NL Design System hue (`red`, `blue-light`, ...).
73
92
  * @return {string} A `CnStatusBadge` variant; `default` (neutral) when unknown.
74
93
  */
75
94
  export function variantForColor(name) {
@@ -3,7 +3,8 @@
3
3
  * SPDX-License-Identifier: EUPL-1.2
4
4
  */
5
5
  import { isBadgeVariant } from './badgeVariants.js'
6
- import { fillTemplate } from './fillTemplate.js'
6
+ import { pillLook } from './detailActionModel.js'
7
+ import { fillTemplate, firstFilled } from './fillTemplate.js'
7
8
  import { readPath } from './readPath.js'
8
9
 
9
10
  /**
@@ -12,10 +13,14 @@ import { readPath } from './readPath.js'
12
13
  * pin and the number in the list are the same, and the rows without one.
13
14
  *
14
15
  * `spec` is `mapConfig.list`: `{ title, sub?, pills?: [{ path?, text?, map?,
15
- * colorMap? }], due? }`. `title`, `sub` and `due` are field templates
16
- * (`"{title}"`, `"{address} · {identifier}"`, `"{deadline|date}"`), already
17
- * translated by the caller; a pill reads `path` (or shows `text`), maps it
18
- * through `map`, and takes its tone from `colorMap` by the raw value.
16
+ * formatter?, colorMap?, variant?, resolve? }], due? }`. `title`, `sub` and `due` are
17
+ * field templates (`"{title}"`, `"{address} · {identifier}"`,
18
+ * `"{deadline|date}"`), already translated by the caller; `sub` may list
19
+ * several, the first that fills wins. A pill reads `path` (or shows `text`),
20
+ * maps it through `map` or the app formatter `formatter` (a reference's
21
+ * display name), and takes its tone from `colorMap` by the raw value, or,
22
+ * with `resolve`, the tone and icon of the record the value points at
23
+ * (`options.looks`, read by CnIndexPage as for its other pills).
19
24
  *
20
25
  * @param {Array<object>} rows The rows, in display order.
21
26
  * @param {object} spec The list declaration.
@@ -23,9 +28,14 @@ import { readPath } from './readPath.js'
23
28
  * @param {string} options.rowKey The row's key field.
24
29
  * @param {Function} options.located Whether a row has a place on the map.
25
30
  * @param {Function} [options.translate] The host's translate, for pill labels.
31
+ * @param {object} [options.formatters] The app's named formatters, for pills and template tokens.
32
+ * @param {object} [options.looks] The referenced records' looks by field and id (`{ [path]: { [id]: { color, icon } } }`).
26
33
  * @return {{items: Array<{key: string, n: number, row: object, title: string, sub: string, due: string, pills: Array<{label: string, variant: string}>}>, missing: Array<object>}}
34
+ * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters
35
+ * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-pill-takes-its-tone-and-icon-from-the-referenced-record
27
36
  */
28
- export function mapListItems(rows, spec, { rowKey, located, translate = (text) => text }) {
37
+ export function mapListItems(rows, spec, { rowKey, located, translate = (text) => text, formatters = null, looks = null }) {
38
+ const context = formatters ? { formatters } : null
29
39
  const items = []
30
40
  const missing = []
31
41
  const list = spec && typeof spec === 'object' ? spec : {}
@@ -40,7 +50,9 @@ export function mapListItems(rows, spec, { rowKey, located, translate = (text) =
40
50
  return null
41
51
  }
42
52
  const raw = typeof pill.path === 'string' && pill.path !== '' ? readPath(row, pill.path) : undefined
43
- const mapped = pill.map && (typeof raw === 'string' || typeof raw === 'number') && Object.hasOwn(pill.map, raw) ? pill.map[raw] : undefined
53
+ const formatter = typeof pill.formatter === 'string' && formatters ? formatters[pill.formatter] : null
54
+ const formatted = typeof formatter === 'function' && (typeof raw === 'string' || typeof raw === 'number') && raw !== '' ? formatter(raw, row) : undefined
55
+ const mapped = pill.map && (typeof raw === 'string' || typeof raw === 'number') && Object.hasOwn(pill.map, raw) ? pill.map[raw] : formatted
44
56
  const text = mapped ?? pill.text ?? raw
45
57
  if (typeof text !== 'string' && typeof text !== 'number') {
46
58
  return null
@@ -50,16 +62,19 @@ export function mapListItems(rows, spec, { rowKey, located, translate = (text) =
50
62
  return null
51
63
  }
52
64
  const tone = pill.colorMap && (typeof raw === 'string' || typeof raw === 'number') ? pill.colorMap[raw] : pill.variant
53
- return { label, variant: isBadgeVariant(tone) ? tone : 'default' }
65
+ const fieldLooks = pill.resolve && looks && typeof pill.path === 'string' ? looks[pill.path] : null
66
+ const look = pillLook(fieldLooks && (typeof raw === 'string' || typeof raw === 'number') ? fieldLooks[String(raw)] : null)
67
+ const variant = look.variant || tone
68
+ return { label, variant: isBadgeVariant(variant) ? variant : 'default', ...(look.icon ? { icon: look.icon } : {}) }
54
69
  })
55
70
  .filter(Boolean)
56
71
  items.push({
57
72
  key: String(row[rowKey]),
58
73
  n: items.length + 1,
59
74
  row,
60
- title: fillTemplate(list.title || '', row),
61
- sub: fillTemplate(list.sub || '', row),
62
- due: fillTemplate(list.due || '', row),
75
+ title: fillTemplate(list.title || '', row, undefined, context),
76
+ sub: list.sub ? firstFilled(list.sub, row, undefined, undefined, context) : '',
77
+ due: fillTemplate(list.due || '', row, undefined, context),
63
78
  pills,
64
79
  })
65
80
  }
@@ -284,15 +284,18 @@ export function schemaSlug(schema) {
284
284
  * it: three case indexes share their views, the organisations index does
285
285
  * not. `savedViewsScope` on a page overrides that, for pages that should
286
286
  * share views although they read different sources, or keep them apart
287
- * although they read the same one.
287
+ * although they read the same one. A page over a named source
288
+ * (`entitySource`) and no schema shares its views with the pages over that
289
+ * source.
288
290
  *
289
291
  * @param {object} page The page.
290
292
  * @param {string} [page.scope] The explicit `savedViewsScope`, when set.
291
293
  * @param {string} [page.register] The page's register.
292
294
  * @param {object|string} [page.schema] The page's schema, slug or object.
295
+ * @param {string} [page.source] The page's `entitySource`, when it reads one.
293
296
  * @return {string} The scope key, or the empty string for a page with no source.
294
297
  */
295
- export function savedViewScope({ scope, register, schema } = {}) {
298
+ export function savedViewScope({ scope, register, schema, source } = {}) {
296
299
  if (typeof scope === 'string' && scope !== '') {
297
300
  return scope
298
301
  }
@@ -300,9 +303,43 @@ export function savedViewScope({ scope, register, schema } = {}) {
300
303
  if (typeof register === 'string' && register !== '' && slug !== '') {
301
304
  return `${register}/${slug}`
302
305
  }
306
+ if (typeof source === 'string' && source !== '') {
307
+ return `source:${source}`
308
+ }
303
309
  return ''
304
310
  }
305
311
 
312
+ /**
313
+ * Whether an index page offers saved views in its sidebar.
314
+ *
315
+ * `allowSavedViews` decides when it is set. Unset, every index page that has
316
+ * a sidebar and a source a view can be scoped to (a register and schema, a
317
+ * named source, or `savedViewsScope`) offers them, so managing views is
318
+ * there on every list without each page opting in.
319
+ *
320
+ * @param {object} config The page's index config, or CnIndexPage's props.
321
+ * @param {boolean|null} [config.allowSavedViews] The explicit choice, when made.
322
+ * @param {object} [config.sidebar] The sidebar config.
323
+ * @param {string} [config.savedViewsScope] The explicit scope.
324
+ * @param {string} [config.register] The register.
325
+ * @param {object|string} [config.schema] The schema.
326
+ * @param {string} [config.entitySource] The named source.
327
+ * @return {boolean}
328
+ */
329
+ export function pageOffersSavedViews(config) {
330
+ if (!config || typeof config !== 'object') {
331
+ return false
332
+ }
333
+ if (typeof config.allowSavedViews === 'boolean') {
334
+ return config.allowSavedViews
335
+ }
336
+ const sidebar = config.sidebar
337
+ if (!sidebar || typeof sidebar !== 'object' || sidebar.enabled !== true || sidebar.show === false) {
338
+ return false
339
+ }
340
+ return savedViewScope({ scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource }) !== ''
341
+ }
342
+
306
343
  /**
307
344
  * Whether a saved view belongs on a page.
308
345
  *
@@ -64,6 +64,9 @@ export function normalizeSavedViewMenu(entries) {
64
64
  }
65
65
  seen.add(`${page}|${id}`)
66
66
  const entry = { id, page, name: e && typeof e.name === 'string' && e.name !== '' ? e.name : id }
67
+ if (typeof e.icon === 'string' && e.icon !== '') {
68
+ entry.icon = e.icon
69
+ }
67
70
  const badge = normalizeBadge(e.badge)
68
71
  if (badge) {
69
72
  entry.badge = badge
@@ -87,12 +90,12 @@ export function toggleSavedViewMenuEntry(entries, entry) {
87
90
  if (present) {
88
91
  return list.filter((e) => !(e.id === id && e.page === entry.page))
89
92
  }
90
- return normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, badge: entry.badge }])
93
+ return normalizeSavedViewMenu([...list, { id, page: entry.page, name: entry.name, icon: entry.icon, badge: entry.badge }])
91
94
  }
92
95
 
93
96
  /**
94
97
  * Bring one page's entries in line with the views that page can show:
95
- * current names and badges, and no entry for a view that was deleted or is
98
+ * current names, icons and badges, and no entry for a view that was deleted or is
96
99
  * no longer shared with the user. Other pages' entries are left alone. A
97
100
  * view's badge is read from its `menuBadge` (see `withSavedViewMenu`).
98
101
  *
@@ -110,9 +113,10 @@ export function syncSavedViewMenu(entries, page, views) {
110
113
  return e
111
114
  }
112
115
  const view = byId.get(e.id)
113
- const { badge, ...rest } = e
116
+ const { badge, icon, ...rest } = e
114
117
  const next = normalizeBadge(view.menuBadge)
115
- return normalizeSavedViewMenu([{ ...rest, name: view.name || e.name, ...(next ? { badge: next } : {}) }])[0]
118
+ const nextIcon = typeof view.icon === 'string' && view.icon !== '' ? view.icon : icon
119
+ return normalizeSavedViewMenu([{ ...rest, name: view.name || e.name, ...(nextIcon ? { icon: nextIcon } : {}), ...(next ? { badge: next } : {}) }])[0]
116
120
  })
117
121
  }
118
122
 
@@ -127,6 +131,9 @@ export const SAVED_VIEW_MENU_EMPTY = 'saved-views-empty'
127
131
  * same place (Ruben's design rule, 19 Oct); it replaces app-specific groups
128
132
  * such as dossiq's "My case types".
129
133
  *
134
+ * An entry shows its view's `icon` (a registered icon name; a case type's
135
+ * own icon, say), else the filter icon.
136
+ *
130
137
  * An entry with a badge carries it as the menu entry's `count` (and
131
138
  * `counterVariant`), which CnAppRoot fetches like any filtered menu count.
132
139
  *
@@ -154,7 +161,7 @@ export function withSavedViewMenu(menu, entries, options = {}) {
154
161
  label: e.name,
155
162
  route: e.page,
156
163
  query: { savedView: e.id },
157
- icon: 'FilterOutline',
164
+ icon: e.icon || 'FilterOutline',
158
165
  ...(e.badge || {}),
159
166
  }))
160
167
  : [{ id: SAVED_VIEW_MENU_EMPTY, type: 'hint', label: t('nextcloud-vue', 'Add a view to the menu from Filters') }]),