@conduction/nextcloud-vue 2.106.0 → 2.107.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 (135) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +9 -0
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js.map +1 -0
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +338 -0
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -0
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +249 -0
  6. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js +2 -2
  8. package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +145 -138
  11. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +102 -2
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +83 -6
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +4 -2
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  21. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +389 -20
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -35
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  26. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -2
  27. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  28. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +69 -5
  29. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +38 -5
  31. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  32. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  33. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +7 -1
  34. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +8 -0
  37. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -0
  38. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +297 -0
  39. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -0
  40. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +24 -0
  41. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -0
  42. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js +9 -0
  43. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js.map +1 -0
  44. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js +95 -0
  45. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js.map +1 -0
  46. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js +123 -0
  47. package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js.map +1 -0
  48. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js +1 -1
  49. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js +13 -5
  50. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js.map +1 -1
  51. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js +3 -1
  52. package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js.map +1 -1
  53. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue.js +1 -1
  54. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js +49 -23
  55. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js.map +1 -1
  56. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js +74 -27
  57. package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js.map +1 -1
  58. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js +0 -1
  59. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js.map +1 -1
  60. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +18 -413
  61. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  62. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +27 -214
  63. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  64. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js +9 -0
  65. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js.map +1 -0
  66. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js +190 -0
  67. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js.map +1 -0
  68. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js +270 -0
  69. package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js.map +1 -0
  70. package/dist/esm/index.js +4 -0
  71. package/dist/esm/index.js.map +1 -1
  72. package/dist/esm/l10n/en.json.js +56 -1
  73. package/dist/esm/l10n/en.json.js.map +1 -1
  74. package/dist/esm/l10n/nl.json.js +56 -1
  75. package/dist/esm/l10n/nl.json.js.map +1 -1
  76. package/dist/esm/library-style.js +1 -1
  77. package/dist/esm/mixins/savedViewsList.js +235 -0
  78. package/dist/esm/mixins/savedViewsList.js.map +1 -0
  79. package/dist/esm/nextcloud-vue.css +348 -108
  80. package/dist/esm/utils/actionsDispatcher.js +2 -2
  81. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  82. package/dist/esm/utils/builtInModals.js +39 -0
  83. package/dist/esm/utils/builtInModals.js.map +1 -0
  84. package/dist/esm/utils/mapMarkerColor.js +121 -0
  85. package/dist/esm/utils/mapMarkerColor.js.map +1 -0
  86. package/dist/esm/utils/mergeManifestDelta.js +4 -0
  87. package/dist/esm/utils/mergeManifestDelta.js.map +1 -1
  88. package/dist/esm/utils/savedViewHelpers.js +59 -7
  89. package/dist/esm/utils/savedViewHelpers.js.map +1 -1
  90. package/dist/esm/utils/savedViewMenu.js +154 -0
  91. package/dist/esm/utils/savedViewMenu.js.map +1 -0
  92. package/dist/esm/utils/searchGroupSharees.js +50 -15
  93. package/dist/esm/utils/searchGroupSharees.js.map +1 -1
  94. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  95. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  96. package/dist/nextcloud-vue.cjs.js +13351 -10855
  97. package/dist/nextcloud-vue.cjs.js.map +2 -2
  98. package/dist/nextcloud-vue.css +348 -108
  99. package/l10n/en.json +56 -1
  100. package/l10n/nl.json +56 -1
  101. package/package.json +1 -1
  102. package/src/components/CnActionDialog/CnActionDialog.vue +545 -0
  103. package/src/components/CnActionDialog/index.js +12 -0
  104. package/src/components/CnAppNav/CnAppNav.vue +17 -0
  105. package/src/components/CnAppRoot/CnAppRoot.vue +102 -2
  106. package/src/components/CnDashboardPage/CnDashboardPage.vue +86 -7
  107. package/src/components/CnIndexPage/CnIndexPage.vue +409 -23
  108. package/src/components/CnIndexPage/useSelfFetchList.js +21 -1
  109. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  110. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +91 -1
  111. package/src/components/CnMapWidget/CnMapWidget.vue +16 -1
  112. package/src/components/CnPartyPicker/CnPartyPicker.vue +317 -0
  113. package/src/components/CnPartyPicker/index.js +11 -0
  114. package/src/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue +192 -0
  115. package/src/components/CnSavedViewEditDialog/index.js +11 -0
  116. package/src/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue +21 -6
  117. package/src/components/CnSavedViewShareFields/CnSavedViewShareFields.vue +130 -37
  118. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +36 -599
  119. package/src/components/CnSavedViewsPanel/CnSavedViewsPanel.vue +459 -0
  120. package/src/components/CnSavedViewsPanel/index.js +11 -0
  121. package/src/components/index.js +4 -0
  122. package/src/css/index-page.css +16 -0
  123. package/src/css/look-board-index.css +5 -3
  124. package/src/css/look-board.css +18 -3
  125. package/src/index.js +4 -0
  126. package/src/mixins/savedViewsList.js +231 -0
  127. package/src/schemas/app-manifest-v2.schema.json +98 -3
  128. package/src/utils/actionsDispatcher.js +2 -2
  129. package/src/utils/builtInModals.js +35 -0
  130. package/src/utils/mapMarkerColor.js +117 -0
  131. package/src/utils/mergeManifestDelta.js +4 -0
  132. package/src/utils/savedViewHelpers.js +58 -6
  133. package/src/utils/savedViewMenu.js +150 -0
  134. package/src/utils/searchGroupSharees.js +49 -14
  135. package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json CHANGED
@@ -1743,7 +1743,62 @@
1743
1743
  "{message} until {time}": "{message} until {time}",
1744
1744
  "Away": "Away",
1745
1745
  "Busy": "Busy",
1746
- "No one in the team": "No one in the team"
1746
+ "No one in the team": "No one in the team",
1747
+ "Also replace the criteria with the current filters": "Also replace the criteria with the current filters",
1748
+ "Manage \"{name}\"": "Manage \"{name}\"",
1749
+ "Presentation": "Presentation",
1750
+ "Save a copy": "Save a copy",
1751
+ "Save the current filters to \"{name}\"": "Save the current filters to \"{name}\"",
1752
+ "Share": "Share",
1753
+ "Update with current filters": "Update with current filters",
1754
+ "Share with people and groups": "Share with people and groups",
1755
+ "Add to menu": "Add to menu",
1756
+ "Remove from menu": "Remove from menu",
1757
+ "Labels": "Labels",
1758
+ "Add a view to the menu from Filters": "Add a view to the menu from Filters",
1759
+ "Also for whoever it is shared with": "Also for whoever it is shared with",
1760
+ "By {owner}": "By {owner}",
1761
+ "Criteria": "Criteria",
1762
+ "From colleagues. You can use them and save a copy.": "From colleagues. You can use them and save a copy.",
1763
+ "It is shared with {names}; it leaves their lists too.": "It is shared with {names}; it leaves their lists too.",
1764
+ "Keep what is saved": "Keep what is saved",
1765
+ "Keeps the current search term, filters and sort under a name.": "Keeps the current search term, filters and sort under a name.",
1766
+ "May update": "May update",
1767
+ "May use": "May use",
1768
+ "Name, search term, filters and sort": "Name, search term, filters and sort",
1769
+ "People and groups from your Nextcloud, as when sharing a file.": "People and groups from your Nextcloud, as when sharing a file.",
1770
+ "Person": "Person",
1771
+ "Read only. Shipped with the app; a new version updates them.": "Read only. Shipped with the app; a new version updates them.",
1772
+ "Replace with the current search term, filters and sort": "Replace with the current search term, filters and sort",
1773
+ "Rights of {name}": "Rights of {name}",
1774
+ "Share with": "Share with",
1775
+ "Shared with": "Shared with",
1776
+ "Stop sharing with {name}": "Stop sharing with {name}",
1777
+ "The items themselves stay. This cannot be undone.": "The items themselves stay. This cannot be undone.",
1778
+ "With people and groups": "With people and groups",
1779
+ "Appears under My views in the menu on the left": "Appears under My views in the menu on the left",
1780
+ "Could not save the view": "Could not save the view",
1781
+ "Default views": "Default views",
1782
+ "Delete view": "Delete view",
1783
+ "Delete view?": "Delete view?",
1784
+ "Edit view: {name}": "Edit view: {name}",
1785
+ "View \"{name}\" saved": "View \"{name}\" saved",
1786
+ "View name": "View name",
1787
+ "Give the view another name, or replace its criteria with what the list shows now.": "Give the view another name, or replace its criteria with what the list shows now.",
1788
+ "Leaves the menu on the left; the view stays": "Leaves the menu on the left; the view stays",
1789
+ "May use: sees the view under Shared with me. May update: can also replace the criteria. Deleting and sharing stay with you.": "May use: sees the view under Shared with me. May update: can also replace the criteria. Deleting and sharing stay with you.",
1790
+ "My view": "My view",
1791
+ "Save current view": "Save current view",
1792
+ "Share view: {name}": "Share view: {name}",
1793
+ "The view \"{name}\" leaves your list and the menu on the left.": "The view \"{name}\" leaves your list and the menu on the left.",
1794
+ "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Whoever gets the view sees it under Shared with me. It is yours and stays yours.",
1795
+ "Whoever you share the view with sees the change at once.": "Whoever you share the view with sees the change at once.",
1796
+ "Reason": "Reason",
1797
+ "Could not be read": "Could not be read",
1798
+ "The action could not be completed.": "The action could not be completed.",
1799
+ "Whether this is ready could not be read.": "Whether this is ready could not be read.",
1800
+ "Showing {located} of {total} located": "Showing {located} of {total} located",
1801
+ "The map shows the first {limit} of {total}. Narrow the filters to see the rest.": "The map shows the first {limit} of {total}. Narrow the filters to see the rest."
1747
1802
  },
1748
1803
  "plurals": {
1749
1804
  "{count} member": [
package/l10n/nl.json CHANGED
@@ -1956,7 +1956,62 @@
1956
1956
  "{message} until {time}": "{message} tot {time}",
1957
1957
  "Away": "Afwezig",
1958
1958
  "Busy": "Bezet",
1959
- "No one in the team": "Niemand in het team"
1959
+ "No one in the team": "Niemand in het team",
1960
+ "Also replace the criteria with the current filters": "Vervang ook de criteria door de huidige filters",
1961
+ "Manage \"{name}\"": "Beheer {name}",
1962
+ "Presentation": "Presentatie",
1963
+ "Save a copy": "Kopie opslaan",
1964
+ "Save the current filters to \"{name}\"": "Huidige filters opslaan in \"{name}\"",
1965
+ "Share": "Delen",
1966
+ "Update with current filters": "Bijwerken met huidige filters",
1967
+ "Share with people and groups": "Delen met personen en groepen",
1968
+ "Add to menu": "Toevoegen aan menu",
1969
+ "Remove from menu": "Uit menu halen",
1970
+ "Labels": "Labels",
1971
+ "Add a view to the menu from Filters": "Zet een weergave in het menu via Filters",
1972
+ "Also for whoever it is shared with": "Ook bij wie het is gedeeld",
1973
+ "By {owner}": "Door {owner}",
1974
+ "Criteria": "Criteria",
1975
+ "From colleagues. You can use them and save a copy.": "Van collega's. U kunt ze gebruiken en een kopie opslaan.",
1976
+ "It is shared with {names}; it leaves their lists too.": "Het is gedeeld met {names}. Ook bij hen verdwijnt het.",
1977
+ "Keep what is saved": "Behouden wat opgeslagen is",
1978
+ "Keeps the current search term, filters and sort under a name.": "Bewaart de zoekterm, de filters en de sortering van nu onder een naam.",
1979
+ "May update": "Mag bijwerken",
1980
+ "May use": "Mag gebruiken",
1981
+ "Name, search term, filters and sort": "Naam, zoekterm, filters en sortering",
1982
+ "People and groups from your Nextcloud, as when sharing a file.": "Personen en groepen uit uw Nextcloud, zoals bij het delen van een bestand.",
1983
+ "Person": "Persoon",
1984
+ "Read only. Shipped with the app; a new version updates them.": "Alleen lezen. Geleverd door de app; een nieuwe versie werkt ze bij.",
1985
+ "Replace with the current search term, filters and sort": "Vervangen door de huidige zoekterm, filters en sortering",
1986
+ "Rights of {name}": "Rechten van {name}",
1987
+ "Share with": "Delen met",
1988
+ "Shared with": "Gedeeld met",
1989
+ "Stop sharing with {name}": "Niet meer delen met {name}",
1990
+ "The items themselves stay. This cannot be undone.": "Wat erin staat, blijft bestaan. Dit kunt u niet ongedaan maken.",
1991
+ "With people and groups": "Met personen en groepen",
1992
+ "Appears under My views in the menu on the left": "Komt onder Mijn weergaven in het menu links",
1993
+ "Could not save the view": "De weergave kon niet worden opgeslagen",
1994
+ "Default views": "Standaardweergaven",
1995
+ "Delete view": "Weergave verwijderen",
1996
+ "Delete view?": "Weergave verwijderen?",
1997
+ "Edit view: {name}": "Weergave bewerken: {name}",
1998
+ "View \"{name}\" saved": "Weergave \"{name}\" opgeslagen",
1999
+ "View name": "Naam van de weergave",
2000
+ "Give the view another name, or replace its criteria with what the list shows now.": "Geef de weergave een andere naam, of vervang de criteria door wat nu in de lijst staat.",
2001
+ "Leaves the menu on the left; the view stays": "Verdwijnt uit het menu links, de weergave blijft bestaan",
2002
+ "May use: sees the view under Shared with me. May update: can also replace the criteria. Deleting and sharing stay with you.": "Mag gebruiken: ziet de weergave onder Gedeeld met mij. Mag bijwerken: kan ook de criteria vervangen. Verwijderen en delen blijven bij u.",
2003
+ "My view": "Mijn weergave",
2004
+ "Save current view": "Huidige weergave opslaan",
2005
+ "Share view: {name}": "Weergave delen: {name}",
2006
+ "The view \"{name}\" leaves your list and the menu on the left.": "De weergave \"{name}\" verdwijnt uit uw lijst en uit het menu links.",
2007
+ "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Wie de weergave krijgt, ziet die onder Gedeeld met mij. Die is van u en blijft dat.",
2008
+ "Whoever you share the view with sees the change at once.": "Iedereen met wie u de weergave deelt, ziet de wijziging meteen.",
2009
+ "Reason": "Reden",
2010
+ "Could not be read": "Kon niet worden gelezen",
2011
+ "The action could not be completed.": "De actie kon niet worden uitgevoerd.",
2012
+ "Whether this is ready could not be read.": "Of dit klaar is, kon niet worden gelezen.",
2013
+ "Showing {located} of {total} located": "{located} van {total} met een locatie getoond",
2014
+ "The map shows the first {limit} of {total}. Narrow the filters to see the rest.": "De kaart toont de eerste {limit} van {total}. Verfijn de filters om de rest te zien."
1960
2015
  },
1961
2016
  "plurals": {
1962
2017
  "{count} member": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.106.0",
3
+ "version": "2.107.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -0,0 +1,545 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <CnDialog
7
+ :name="text(config.title) || t('nextcloud-vue', 'Confirm')"
8
+ :eyebrow="text(config.eyebrow)"
9
+ :subtitle="text(config.subtitle)"
10
+ width="form"
11
+ :noClose="busy"
12
+ data-testid="cn-action-dialog"
13
+ @closing="close">
14
+ <div v-if="!done" class="cn-action-dialog">
15
+ <!-- The record the action is about: its title, a meta line, a badge. -->
16
+ <div v-if="summary" class="cn-action-dialog__summary" data-testid="cn-action-dialog-summary">
17
+ <CnIcon :name="config.summary.icon || 'FolderOutline'" :size="20" class="cn-action-dialog__summary-icon" />
18
+ <span class="cn-action-dialog__summary-text">
19
+ <strong>{{ summary.title }}</strong>
20
+ <span v-if="summary.meta" class="cn-action-dialog__muted">{{ summary.meta }}</span>
21
+ </span>
22
+ <span v-if="summary.badge" class="cn-action-dialog__badge">{{ summary.badge }}</span>
23
+ </div>
24
+
25
+ <!-- Facts about the record, in titled groups ("Retention under the case type"). -->
26
+ <section v-for="(section, index) in factSections"
27
+ :key="`facts-${index}`"
28
+ class="cn-action-dialog__section"
29
+ data-testid="cn-action-dialog-facts">
30
+ <h3 v-if="section.title" class="cn-action-dialog__title">
31
+ {{ section.title }}
32
+ </h3>
33
+ <dl class="cn-action-dialog__facts">
34
+ <div v-for="fact in section.facts" :key="fact.label">
35
+ <dt>{{ fact.label }}</dt>
36
+ <dd>{{ fact.value }}</dd>
37
+ </div>
38
+ </dl>
39
+ </section>
40
+
41
+ <!-- The checklist, read from an endpoint: each item met, not met or
42
+ unknown. Items named in `required` decide whether the action may
43
+ run; the others inform. -->
44
+ <section v-if="config.checklist" class="cn-action-dialog__section" data-testid="cn-action-dialog-checklist">
45
+ <h3 v-if="config.checklist.title" class="cn-action-dialog__title">
46
+ {{ text(config.checklist.title) }}
47
+ </h3>
48
+ <p v-if="checklistError" class="cn-action-dialog__muted" role="status">
49
+ {{ checklistError }}
50
+ </p>
51
+ <ul class="cn-action-dialog__checks">
52
+ <li v-for="item in checklist"
53
+ :key="item.id"
54
+ class="cn-action-dialog__check"
55
+ :data-testid="`cn-action-dialog-check-${item.id}`"
56
+ :data-met="String(item.met)">
57
+ <CheckCircle v-if="item.met === true" :size="20" class="cn-action-dialog__check-icon cn-action-dialog__check-icon--met" />
58
+ <HelpCircleOutline v-else-if="item.met === null" :size="20" class="cn-action-dialog__check-icon" />
59
+ <CircleOutline v-else :size="20" class="cn-action-dialog__check-icon" />
60
+ <strong class="cn-action-dialog__check-label">{{ item.label }}</strong>
61
+ <span v-if="item.detail" class="cn-action-dialog__muted">{{ item.detail }}</span>
62
+ </li>
63
+ </ul>
64
+ </section>
65
+
66
+ <div v-if="config.reason" class="cn-action-dialog__field">
67
+ <NcTextArea
68
+ :id="reasonId"
69
+ v-model="reason"
70
+ :label="reasonLabel"
71
+ :labelOutside="false"
72
+ resize="vertical"
73
+ data-testid="cn-action-dialog-reason" />
74
+ <span v-if="config.reason.hint" class="cn-action-dialog__muted">{{ text(config.reason.hint) }}</span>
75
+ </div>
76
+
77
+ <NcNoteCard v-if="config.note"
78
+ :type="noteType"
79
+ :heading="text(config.note.title)"
80
+ data-testid="cn-action-dialog-note">
81
+ {{ text(config.note.text) }}
82
+ </NcNoteCard>
83
+
84
+ <p v-if="refusal"
85
+ class="cn-action-dialog__error"
86
+ role="alert"
87
+ data-testid="cn-action-dialog-refusal">
88
+ {{ refusal }}
89
+ </p>
90
+ </div>
91
+
92
+ <p v-else role="status" data-testid="cn-action-dialog-done">
93
+ {{ text(config.doneText) || t('nextcloud-vue', 'Done') }}
94
+ </p>
95
+
96
+ <template #actions>
97
+ <NcButton :disabled="busy" data-testid="cn-action-dialog-cancel" @click="close">
98
+ {{ done ? t('nextcloud-vue', 'Close') : t('nextcloud-vue', 'Cancel') }}
99
+ </NcButton>
100
+ <NcButton v-if="!done"
101
+ variant="primary"
102
+ :disabled="!canConfirm"
103
+ data-testid="cn-action-dialog-confirm"
104
+ @click="confirm">
105
+ <template v-if="confirmConfig.icon" #icon>
106
+ <CnIcon :name="confirmConfig.icon" :size="20" />
107
+ </template>
108
+ {{ text(confirmConfig.label) || t('nextcloud-vue', 'Confirm') }}
109
+ </NcButton>
110
+ </template>
111
+ </CnDialog>
112
+ </template>
113
+
114
+ <script>
115
+ import axios from '@nextcloud/axios'
116
+ import { emit } from '@nextcloud/event-bus'
117
+ import { translate as t } from '@nextcloud/l10n'
118
+ import { generateUrl } from '@nextcloud/router'
119
+ import { NcButton, NcNoteCard, NcTextArea } from '@nextcloud/vue'
120
+ import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
121
+ import CircleOutline from 'vue-material-design-icons/CircleOutline.vue'
122
+ import HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'
123
+ import { fetchEndpointSource, interpolateUrlTokens } from '../../composables/useEndpointSource.js'
124
+ import { fillTemplate } from '../../utils/fillTemplate.js'
125
+ import { readPath } from '../../utils/readPath.js'
126
+ import { CnDialog } from '../CnDialog/index.js'
127
+ import { CnIcon } from '../CnIcon/index.js'
128
+
129
+ /** The page refresh channel the built-in widgets listen on. */
130
+ const PAGE_REFRESH = 'cn:page:refresh'
131
+
132
+ /** Gives each dialog's reason field its own id. */
133
+ let reasonSeq = 0
134
+
135
+ /**
136
+ * CnActionDialog — the generic "summary, checklist, reason, confirm" dialog
137
+ * for an act on a record, configured from the manifest (an `open-modal`
138
+ * action's props), so an app does not hand-build each one. dossiq's archive
139
+ * dialog (board DqArchiveren) is the first: the case, what its case type
140
+ * keeps, a readiness checklist read from an endpoint, a reason, a read-only
141
+ * note and Archive it.
142
+ *
143
+ * `config`:
144
+ * - `title`, `eyebrow`, `subtitle`, `doneText`: texts, through the host translate.
145
+ * - `summary`: `{ source?, title, meta?, badge?, icon? }`: templates filled from
146
+ * the record (`source` an endpointSource to read it from, else `object`).
147
+ * - `facts`: `[{ title?, items: [{ label, value }] }]`, `value` a template.
148
+ * - `checklist`: `{ title?, source, itemsPath?, labels?, required? }`: items
149
+ * `{ id, met, label?, detail? }` read from `source`; `labels[id]` gives an
150
+ * item's `{ label, detail?, unmetDetail? }`; the ids in `required` must be
151
+ * met before the action can run. `met: null` reads as unknown.
152
+ * - `permission`: `{ source, allowedPath, reasonPath? }`: the action runs only
153
+ * while `allowedPath` reads true; `reasonPath` says why not.
154
+ * - `reason`: `{ label?, hint?, required?, field? }`: a text area; required by
155
+ * default; sent as `field` (default `reason`).
156
+ * - `note`: `{ variant?, title?, text }`: a read-only note.
157
+ * - `confirm`: `{ label, icon?, request?: { method?, url, body? }, action? }`.
158
+ * `request` posts itself (`body` templates filled with the record and
159
+ * `{reason}`); `action` dispatches a manifest action through the page.
160
+ *
161
+ * Tokens (`@objectId`, `@object.<field>`) resolve in every url.
162
+ */
163
+ export default {
164
+ name: 'CnActionDialog',
165
+
166
+ components: { CheckCircle, CircleOutline, CnDialog, CnIcon, HelpCircleOutline, NcButton, NcNoteCard, NcTextArea },
167
+
168
+ inject: {
169
+ /** The host app's translate (CnAppRoot `cnTranslate`). */
170
+ cnTranslate: { default: () => (key) => key },
171
+ /** The page's pre-bound action dispatcher (CnPageRenderer), for `confirm.action`. */
172
+ cnDispatchAction: { default: null },
173
+ },
174
+
175
+ props: {
176
+ /**
177
+ * The dialog's configuration (see the component docs).
178
+ *
179
+ * @type {object}
180
+ */
181
+ config: {
182
+ type: Object,
183
+ default: () => ({}),
184
+ },
185
+
186
+ /** The record's id, for the `@objectId` token. */
187
+ objectId: {
188
+ type: [String, Number],
189
+ default: '',
190
+ },
191
+
192
+ /**
193
+ * The record, when the host has it: the summary and facts read it.
194
+ *
195
+ * @type {object|null}
196
+ */
197
+ object: {
198
+ type: Object,
199
+ default: null,
200
+ },
201
+ },
202
+
203
+ emits: ['close', 'done'],
204
+
205
+ data() {
206
+ return {
207
+ record: this.object,
208
+ items: [],
209
+ checklistError: '',
210
+ allowed: true,
211
+ reason: '',
212
+ busy: false,
213
+ refusal: '',
214
+ done: false,
215
+ reasonId: `cn-action-dialog-reason-${++reasonSeq}`,
216
+ }
217
+ },
218
+
219
+ computed: {
220
+ /** @return {object} The token context. */
221
+ tokenCtx() {
222
+ return { objectId: this.objectId, object: this.record || {} }
223
+ },
224
+
225
+ /** @return {object} The confirm config. */
226
+ confirmConfig() {
227
+ return this.config.confirm && typeof this.config.confirm === 'object' ? this.config.confirm : {}
228
+ },
229
+
230
+ /**
231
+ * The summary row, or null without one or before the record is known.
232
+ *
233
+ * @spec openspec/changes/action-dialog/specs/action-dialog/spec.md#requirement-a-manifest-configures-an-action-dialog
234
+ * @return {{title: string, meta: string, badge: string}|null}
235
+ */
236
+ summary() {
237
+ const def = this.config.summary
238
+ if (!def || !this.record) {
239
+ return null
240
+ }
241
+ const title = this.fill(def.title)
242
+ return title ? { title, meta: this.fill(def.meta), badge: this.fill(def.badge) } : null
243
+ },
244
+
245
+ /** @return {Array<{title: string, facts: Array<{label: string, value: string}>}>} The fact groups. */
246
+ factSections() {
247
+ if (!Array.isArray(this.config.facts) || !this.record) {
248
+ return []
249
+ }
250
+ const empty = t('nextcloud-vue', 'Not set')
251
+ return this.config.facts.map((section) => ({
252
+ title: this.text(section.title),
253
+ facts: (Array.isArray(section.items) ? section.items : []).map((fact) => ({ label: this.text(fact.label), value: this.fill(fact.value) || empty })),
254
+ }))
255
+ },
256
+
257
+ /**
258
+ * The checklist rows: label and detail from `labels` by id, else the item's own.
259
+ *
260
+ * @spec openspec/changes/action-dialog/specs/action-dialog/spec.md#requirement-a-manifest-configures-an-action-dialog
261
+ * @return {Array<{id: string, met: (boolean|null), label: string, detail: string}>}
262
+ */
263
+ checklist() {
264
+ const labels = (this.config.checklist && this.config.checklist.labels) || {}
265
+ return this.items.map((item) => {
266
+ const met = item.met === null || item.met === undefined ? null : item.met === true
267
+ const own = labels[item.id] || {}
268
+ const detail = met === false && own.unmetDetail ? own.unmetDetail : (own.detail || item.detail || '')
269
+ return {
270
+ id: String(item.id),
271
+ met,
272
+ label: this.fill(own.label || item.label || String(item.id), item),
273
+ detail: met === null && !item.detail ? t('nextcloud-vue', 'Could not be read') : this.fill(detail, item),
274
+ }
275
+ })
276
+ },
277
+
278
+ /** @return {boolean} Whether every required item is met. */
279
+ requiredMet() {
280
+ const required = (this.config.checklist && Array.isArray(this.config.checklist.required)) ? this.config.checklist.required.map(String) : []
281
+ return required.every((id) => this.items.some((item) => String(item.id) === id && item.met === true))
282
+ },
283
+
284
+ /** @return {boolean} Whether a reason is asked and needed. */
285
+ reasonNeeded() {
286
+ return Boolean(this.config.reason) && this.config.reason.required !== false
287
+ },
288
+
289
+ /** @return {string} The reason field's label. */
290
+ reasonLabel() {
291
+ return this.text(this.config.reason && this.config.reason.label) || t('nextcloud-vue', 'Reason')
292
+ },
293
+
294
+ /** @return {string} The note's NcNoteCard type. */
295
+ noteType() {
296
+ const variant = this.config.note && this.config.note.variant
297
+ return ['info', 'warning', 'error', 'success'].includes(variant) ? variant : 'info'
298
+ },
299
+
300
+ /**
301
+ * Whether the action may run: allowed, required items met, a reason
302
+ * when one is needed, and nothing in flight.
303
+ *
304
+ * @spec openspec/changes/action-dialog/specs/action-dialog/spec.md#requirement-a-manifest-configures-an-action-dialog
305
+ * @return {boolean}
306
+ */
307
+ canConfirm() {
308
+ return !this.busy && this.allowed && this.requiredMet && (!this.reasonNeeded || this.reason.trim() !== '')
309
+ },
310
+ },
311
+
312
+ async mounted() {
313
+ await Promise.all([this.loadRecord(), this.loadChecklist(), this.loadPermission()])
314
+ },
315
+
316
+ methods: {
317
+ t,
318
+
319
+ /**
320
+ * A config text through the host translate, '' for anything else.
321
+ *
322
+ * @param {unknown} value The text.
323
+ * @return {string}
324
+ */
325
+ text(value) {
326
+ return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
327
+ },
328
+
329
+ /**
330
+ * A template filled from a row (the record by default), translated first.
331
+ *
332
+ * @param {unknown} template The template.
333
+ * @param {object} [row] The row.
334
+ * @return {string}
335
+ */
336
+ fill(template, row) {
337
+ if (typeof template !== 'string' || template === '') {
338
+ return ''
339
+ }
340
+ return fillTemplate(this.cnTranslate(template), row || this.record || {}).trim()
341
+ },
342
+
343
+ /**
344
+ * Read an endpoint, fresh.
345
+ *
346
+ * @param {object} source An endpointSource block.
347
+ * @return {Promise<unknown>}
348
+ */
349
+ read(source) {
350
+ return fetchEndpointSource(source, this.tokenCtx, { fresh: true })
351
+ },
352
+
353
+ /** @return {Promise<void>} */
354
+ async loadRecord() {
355
+ const source = this.config.summary && this.config.summary.source
356
+ if (!source || !source.url) {
357
+ return
358
+ }
359
+ try {
360
+ const data = await this.read(source)
361
+ this.record = data && typeof data === 'object' ? data : this.record
362
+ } catch {
363
+ // The summary stays empty; the checklist and the action still work.
364
+ }
365
+ },
366
+
367
+ /** @return {Promise<void>} */
368
+ async loadChecklist() {
369
+ const def = this.config.checklist
370
+ if (!def || !def.source || !def.source.url) {
371
+ return
372
+ }
373
+ try {
374
+ const data = await this.read(def.source)
375
+ const list = def.itemsPath ? readPath(data, def.itemsPath) : data
376
+ this.items = Array.isArray(list) ? list.filter((item) => item && item.id !== undefined) : []
377
+ } catch {
378
+ this.items = []
379
+ this.checklistError = t('nextcloud-vue', 'Whether this is ready could not be read.')
380
+ }
381
+ },
382
+
383
+ /** @return {Promise<void>} */
384
+ async loadPermission() {
385
+ const def = this.config.permission
386
+ if (!def || !def.source || !def.source.url) {
387
+ return
388
+ }
389
+ try {
390
+ const data = await this.read(def.source)
391
+ this.allowed = readPath(data, def.allowedPath || 'allowed') === true
392
+ this.refusal = this.allowed || !def.reasonPath ? '' : String(readPath(data, def.reasonPath) || '')
393
+ } catch {
394
+ this.allowed = false
395
+ }
396
+ },
397
+
398
+ /**
399
+ * Run the action once: post `confirm.request`, or dispatch
400
+ * `confirm.action`, then show the done text and refresh the page.
401
+ *
402
+ * @spec openspec/changes/action-dialog/specs/action-dialog/spec.md#requirement-a-manifest-configures-an-action-dialog
403
+ * @return {Promise<void>}
404
+ */
405
+ async confirm() {
406
+ if (!this.canConfirm) {
407
+ return
408
+ }
409
+ this.busy = true
410
+ this.refusal = ''
411
+ const reason = this.reason.trim()
412
+ try {
413
+ const request = this.confirmConfig.request
414
+ if (request && request.url) {
415
+ const url = interpolateUrlTokens(request.url, this.tokenCtx)
416
+ const body = {}
417
+ for (const [key, value] of Object.entries(request.body || {})) {
418
+ body[key] = typeof value === 'string' ? fillTemplate(value, { ...(this.record || {}), reason }) : value
419
+ }
420
+ if (this.config.reason) {
421
+ body[this.config.reason.field || 'reason'] = reason
422
+ }
423
+ const method = String(request.method || 'POST').toLowerCase()
424
+ await axios[['post', 'put', 'patch', 'delete'].includes(method) ? method : 'post'](/^https?:\/\//i.test(url) ? url : generateUrl(url), body)
425
+ } else if (this.confirmConfig.action && typeof this.cnDispatchAction === 'function') {
426
+ await this.cnDispatchAction({ ...this.confirmConfig.action, args: [...(this.confirmConfig.action.args || []), { reason }] })
427
+ }
428
+ this.done = true
429
+ emit(PAGE_REFRESH, {})
430
+ /** @event done The action ran. Payload: `{ reason }`. */
431
+ this.$emit('done', { reason })
432
+ } catch (failure) {
433
+ const data = failure && failure.response && failure.response.data
434
+ this.refusal = String((data && (data.message || data.error)) || t('nextcloud-vue', 'The action could not be completed.'))
435
+ } finally {
436
+ this.busy = false
437
+ }
438
+ },
439
+
440
+ /** Close the dialog, unless the action is running. */
441
+ close() {
442
+ if (this.busy) {
443
+ return
444
+ }
445
+ /** @event close The dialog was closed. */
446
+ this.$emit('close')
447
+ },
448
+ },
449
+ }
450
+ </script>
451
+
452
+ <style scoped>
453
+ .cn-action-dialog {
454
+ display: flex;
455
+ flex-direction: column;
456
+ gap: 16px;
457
+ }
458
+
459
+ .cn-action-dialog__summary {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 10px;
463
+ padding: 12px 14px;
464
+ border: 1px solid var(--color-border);
465
+ border-radius: var(--border-radius-large, 8px);
466
+ }
467
+
468
+ .cn-action-dialog__summary-text {
469
+ display: flex;
470
+ flex: 1 1 auto;
471
+ flex-direction: column;
472
+ min-width: 0;
473
+ }
474
+
475
+ .cn-action-dialog__badge {
476
+ padding: 3px 10px;
477
+ border-radius: 12px;
478
+ background: var(--color-background-dark);
479
+ font-size: 13px;
480
+ font-weight: 600;
481
+ white-space: nowrap;
482
+ }
483
+
484
+ .cn-action-dialog__title {
485
+ margin: 0 0 8px;
486
+ font-size: 15px;
487
+ font-weight: 700;
488
+ }
489
+
490
+ .cn-action-dialog__facts {
491
+ display: grid;
492
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
493
+ gap: 8px 16px;
494
+ margin: 0;
495
+ }
496
+
497
+ .cn-action-dialog__facts dt {
498
+ color: var(--color-text-maxcontrast);
499
+ font-size: 13px;
500
+ }
501
+
502
+ .cn-action-dialog__facts dd {
503
+ margin: 0;
504
+ }
505
+
506
+ .cn-action-dialog__checks {
507
+ display: flex;
508
+ flex-direction: column;
509
+ gap: 8px;
510
+ margin: 0;
511
+ padding: 0;
512
+ list-style: none;
513
+ }
514
+
515
+ .cn-action-dialog__check {
516
+ display: flex;
517
+ flex-wrap: wrap;
518
+ align-items: center;
519
+ gap: 8px;
520
+ }
521
+
522
+ .cn-action-dialog__check-icon {
523
+ color: var(--color-text-maxcontrast);
524
+ }
525
+
526
+ .cn-action-dialog__check-icon--met {
527
+ color: var(--color-success-text, var(--color-success));
528
+ }
529
+
530
+ .cn-action-dialog__muted {
531
+ color: var(--color-text-maxcontrast);
532
+ font-size: 14px;
533
+ }
534
+
535
+ .cn-action-dialog__field {
536
+ display: flex;
537
+ flex-direction: column;
538
+ gap: 4px;
539
+ }
540
+
541
+ .cn-action-dialog__error {
542
+ margin: 0;
543
+ color: var(--color-error-text, var(--color-error));
544
+ }
545
+ </style>