@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.11

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 (208) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +79416 -7715
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +3583 -504
  6. package/dist/nextcloud-vue.esm.js +79343 -7692
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/l10n/en.json +164 -0
  9. package/l10n/nl.json +164 -0
  10. package/package.json +104 -63
  11. package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
  12. package/src/components/CnActionsBar/index.js +1 -0
  13. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +570 -0
  14. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  15. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  16. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
  17. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  18. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  19. package/src/components/CnCard/CnCard.vue +415 -0
  20. package/src/components/CnCard/index.js +1 -0
  21. package/src/components/CnCardGrid/CnCardGrid.vue +156 -152
  22. package/src/components/CnCardGrid/index.js +1 -1
  23. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  24. package/src/components/CnCellRenderer/index.js +1 -1
  25. package/src/components/CnChartWidget/CnChartWidget.vue +346 -0
  26. package/src/components/CnChartWidget/index.js +1 -0
  27. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  28. package/src/components/CnConfigurationCard/index.js +1 -1
  29. package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
  30. package/src/components/CnContextMenu/index.js +1 -0
  31. package/src/components/CnCopyDialog/CnCopyDialog.vue +266 -0
  32. package/src/components/CnCopyDialog/index.js +1 -0
  33. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
  34. package/src/components/CnDashboardGrid/index.js +1 -0
  35. package/src/components/CnDashboardPage/CnDashboardPage.vue +397 -0
  36. package/src/components/CnDashboardPage/index.js +1 -0
  37. package/src/components/CnDataTable/CnDataTable.vue +362 -354
  38. package/src/components/CnDataTable/index.js +1 -1
  39. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
  40. package/src/components/CnDeleteDialog/index.js +1 -0
  41. package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
  42. package/src/components/CnDetailCard/index.js +1 -0
  43. package/src/components/CnDetailGrid/CnDetailGrid.vue +256 -0
  44. package/src/components/CnDetailGrid/index.js +1 -0
  45. package/src/components/CnDetailPage/CnDetailPage.vue +432 -0
  46. package/src/components/CnDetailPage/index.js +1 -0
  47. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +234 -223
  48. package/src/components/CnFacetSidebar/index.js +1 -1
  49. package/src/components/CnFilterBar/CnFilterBar.vue +153 -152
  50. package/src/components/CnFilterBar/index.js +1 -1
  51. package/src/components/CnFormDialog/CnFormDialog.vue +1047 -0
  52. package/src/components/CnFormDialog/index.js +1 -0
  53. package/src/components/CnIcon/CnIcon.vue +89 -0
  54. package/src/components/CnIcon/index.js +1 -0
  55. package/src/components/CnIndexPage/CnIndexPage.vue +980 -682
  56. package/src/components/CnIndexPage/index.js +1 -1
  57. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +536 -0
  58. package/src/components/CnIndexSidebar/index.js +1 -0
  59. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  60. package/src/components/CnInfoWidget/index.js +1 -0
  61. package/src/components/CnItemCard/CnItemCard.vue +134 -0
  62. package/src/components/CnItemCard/index.js +1 -0
  63. package/src/components/CnJsonViewer/CnJsonViewer.vue +312 -0
  64. package/src/components/CnJsonViewer/index.js +1 -0
  65. package/src/components/CnKpiGrid/CnKpiGrid.vue +93 -89
  66. package/src/components/CnKpiGrid/index.js +1 -1
  67. package/src/components/CnMassActionBar/CnMassActionBar.vue +161 -160
  68. package/src/components/CnMassActionBar/index.js +1 -1
  69. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +327 -320
  70. package/src/components/CnMassCopyDialog/index.js +1 -1
  71. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +245 -238
  72. package/src/components/CnMassDeleteDialog/index.js +1 -1
  73. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +191 -190
  74. package/src/components/CnMassExportDialog/index.js +1 -1
  75. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +494 -491
  76. package/src/components/CnMassImportDialog/index.js +1 -1
  77. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  78. package/src/components/CnNoteCard/index.js +1 -0
  79. package/src/components/CnNotesCard/CnNotesCard.vue +416 -0
  80. package/src/components/CnNotesCard/index.js +1 -0
  81. package/src/components/CnObjectCard/CnObjectCard.vue +294 -292
  82. package/src/components/CnObjectCard/index.js +1 -1
  83. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +854 -0
  84. package/src/components/CnObjectDataWidget/index.js +1 -0
  85. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +289 -0
  86. package/src/components/CnObjectMetadataWidget/index.js +1 -0
  87. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +369 -0
  88. package/src/components/CnObjectSidebar/CnFilesTab.vue +287 -0
  89. package/src/components/CnObjectSidebar/CnNotesTab.vue +250 -0
  90. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +255 -0
  91. package/src/components/CnObjectSidebar/CnTagsTab.vue +259 -0
  92. package/src/components/CnObjectSidebar/CnTasksTab.vue +483 -0
  93. package/src/components/CnObjectSidebar/index.js +6 -0
  94. package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
  95. package/src/components/CnPageHeader/index.js +1 -0
  96. package/src/components/CnPagination/CnPagination.vue +253 -252
  97. package/src/components/CnPagination/index.js +1 -1
  98. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  99. package/src/components/CnProgressBar/index.js +1 -0
  100. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +793 -0
  101. package/src/components/CnRegisterMapping/index.js +1 -0
  102. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  103. package/src/components/CnRowActions/index.js +1 -1
  104. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  105. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +788 -0
  106. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  107. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  108. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  109. package/src/components/CnSchemaFormDialog/index.js +1 -0
  110. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  111. package/src/components/CnSettingsCard/index.js +1 -1
  112. package/src/components/CnSettingsSection/CnSettingsSection.vue +267 -266
  113. package/src/components/CnSettingsSection/index.js +1 -1
  114. package/src/components/CnStatsBlock/CnStatsBlock.vue +437 -366
  115. package/src/components/CnStatsBlock/index.js +1 -1
  116. package/src/components/CnStatsPanel/CnStatsPanel.vue +321 -0
  117. package/src/components/CnStatsPanel/index.js +1 -0
  118. package/src/components/CnStatusBadge/CnStatusBadge.vue +90 -77
  119. package/src/components/CnStatusBadge/index.js +1 -1
  120. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +545 -0
  121. package/src/components/CnTabbedFormDialog/index.js +1 -0
  122. package/src/components/CnTableWidget/CnTableWidget.vue +333 -0
  123. package/src/components/CnTableWidget/index.js +1 -0
  124. package/src/components/CnTasksCard/CnTasksCard.vue +374 -0
  125. package/src/components/CnTasksCard/index.js +1 -0
  126. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  127. package/src/components/CnTileWidget/index.js +1 -0
  128. package/src/components/CnTimelineStages/CnTimelineStages.vue +294 -0
  129. package/src/components/CnTimelineStages/index.js +1 -0
  130. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +436 -0
  131. package/src/components/CnUserActionMenu/index.js +1 -0
  132. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +313 -312
  133. package/src/components/CnVersionInfoCard/index.js +1 -1
  134. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  135. package/src/components/CnWidgetRenderer/index.js +1 -0
  136. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +248 -0
  137. package/src/components/CnWidgetWrapper/index.js +1 -0
  138. package/src/components/index.js +57 -25
  139. package/src/composables/index.js +5 -3
  140. package/src/composables/useContextMenu.js +126 -0
  141. package/src/composables/useDashboardView.js +286 -0
  142. package/src/composables/useDetailView.js +290 -132
  143. package/src/composables/useListView.js +364 -153
  144. package/src/composables/useSubResource.js +142 -142
  145. package/src/constants/metadata.js +30 -0
  146. package/src/css/CnSchemaFormDialog.css +546 -0
  147. package/src/css/__sample_nextcloud_tokens.css +110 -0
  148. package/src/css/actions-bar.css +54 -0
  149. package/src/css/badge.css +83 -51
  150. package/src/css/card.css +129 -128
  151. package/src/css/context-menu.css +20 -0
  152. package/src/css/dashboard.css +70 -0
  153. package/src/css/detail-page.css +235 -0
  154. package/src/css/detail.css +68 -68
  155. package/src/css/index-page.css +44 -0
  156. package/src/css/index-sidebar.css +193 -0
  157. package/src/css/index.css +17 -8
  158. package/src/css/layout.css +90 -90
  159. package/src/css/page-header.css +35 -0
  160. package/src/css/pagination.css +72 -72
  161. package/src/css/table.css +142 -143
  162. package/src/css/timeline-stages.css +220 -0
  163. package/src/css/utilities.css +46 -46
  164. package/src/index.js +95 -50
  165. package/src/l10n/index.js +12 -0
  166. package/src/mixins/gridLayout.js +118 -0
  167. package/src/store/createCrudStore.d.ts +350 -0
  168. package/src/store/createCrudStore.js +413 -0
  169. package/src/store/createSubResourcePlugin.js +125 -135
  170. package/src/store/index.js +4 -3
  171. package/src/store/pluginMerge.js +55 -0
  172. package/src/store/plugins/auditTrails.js +357 -17
  173. package/src/store/plugins/files.js +250 -186
  174. package/src/store/plugins/index.js +8 -4
  175. package/src/store/plugins/lifecycle.js +180 -180
  176. package/src/store/plugins/logs.d.ts +22 -0
  177. package/src/store/plugins/logs.js +172 -0
  178. package/src/store/plugins/registerMapping.js +195 -0
  179. package/src/store/plugins/relations.js +68 -68
  180. package/src/store/plugins/search.js +385 -0
  181. package/src/store/plugins/selection.js +104 -0
  182. package/src/store/useObjectStore.js +793 -625
  183. package/src/types/auditTrail.d.ts +32 -32
  184. package/src/types/file.d.ts +23 -23
  185. package/src/types/index.d.ts +67 -35
  186. package/src/types/notification.d.ts +36 -36
  187. package/src/types/object.d.ts +40 -40
  188. package/src/types/organisation.d.ts +41 -41
  189. package/src/types/register.d.ts +25 -25
  190. package/src/types/schema.d.ts +39 -39
  191. package/src/types/shared.d.ts +79 -79
  192. package/src/types/source.d.ts +14 -14
  193. package/src/types/task.d.ts +31 -31
  194. package/src/utils/errors.js +96 -96
  195. package/src/utils/getTheme.js +9 -0
  196. package/src/utils/headers.js +80 -44
  197. package/src/utils/id.js +13 -0
  198. package/src/utils/index.js +4 -3
  199. package/src/utils/schema.js +423 -287
  200. package/src/utils/widgetVisibility.js +162 -0
  201. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  202. package/src/components/CnDetailViewLayout/index.js +0 -1
  203. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  204. package/src/components/CnEmptyState/index.js +0 -1
  205. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  206. package/src/components/CnListViewLayout/index.js +0 -1
  207. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  208. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -0,0 +1,255 @@
1
+ <!--
2
+ CnObjectSidebar — Right sidebar with standardized tabs for generic object functionality.
3
+
4
+ Provides Files, Notes, Tags, Tasks, and Audit Trail tabs that integrate with
5
+ OpenRegister API endpoints (which bridge to Nextcloud-native APIs).
6
+ All tabs are optional and overridable via props and slots.
7
+ -->
8
+ <template>
9
+ <NcAppSidebar
10
+ :name="sidebarTitle"
11
+ :title="sidebarTitle"
12
+ :subtitle="sidebarSubtitle"
13
+ :active.sync="activeTab"
14
+ @update:open="$emit('update:open', $event)"
15
+ @close="$emit('update:open', false)">
16
+ <!-- Files Tab -->
17
+ <NcAppSidebarTab
18
+ v-if="!isTabHidden('files')"
19
+ id="files"
20
+ :name="filesLabel"
21
+ :order="1">
22
+ <template #icon>
23
+ <Paperclip :size="20" />
24
+ </template>
25
+ <slot name="tab-files" :object-id="objectId" :object-type="objectType">
26
+ <CnFilesTab
27
+ :object-id="objectId"
28
+ :register="register"
29
+ :schema="schema"
30
+ :api-base="apiBase" />
31
+ </slot>
32
+ </NcAppSidebarTab>
33
+
34
+ <!-- Notes Tab -->
35
+ <NcAppSidebarTab
36
+ v-if="!isTabHidden('notes')"
37
+ id="notes"
38
+ :name="notesLabel"
39
+ :order="2">
40
+ <template #icon>
41
+ <CommentTextOutline :size="20" />
42
+ </template>
43
+ <slot name="tab-notes" :object-id="objectId" :object-type="objectType">
44
+ <CnNotesTab
45
+ :object-id="objectId"
46
+ :register="register"
47
+ :schema="schema"
48
+ :api-base="apiBase" />
49
+ </slot>
50
+ </NcAppSidebarTab>
51
+
52
+ <!-- Tags Tab -->
53
+ <NcAppSidebarTab
54
+ v-if="!isTabHidden('tags')"
55
+ id="tags"
56
+ :name="tagsLabel"
57
+ :order="3">
58
+ <template #icon>
59
+ <TagOutline :size="20" />
60
+ </template>
61
+ <slot name="tab-tags" :object-id="objectId" :object-type="objectType">
62
+ <CnTagsTab
63
+ :object-id="objectId"
64
+ :register="register"
65
+ :schema="schema"
66
+ :api-base="apiBase" />
67
+ </slot>
68
+ </NcAppSidebarTab>
69
+
70
+ <!-- Tasks Tab -->
71
+ <NcAppSidebarTab
72
+ v-if="!isTabHidden('tasks')"
73
+ id="tasks"
74
+ :name="tasksLabel"
75
+ :order="4">
76
+ <template #icon>
77
+ <CheckboxMarkedOutline :size="20" />
78
+ </template>
79
+ <slot name="tab-tasks" :object-id="objectId" :object-type="objectType">
80
+ <CnTasksTab
81
+ :object-id="objectId"
82
+ :register="register"
83
+ :schema="schema"
84
+ :api-base="apiBase" />
85
+ </slot>
86
+ </NcAppSidebarTab>
87
+
88
+ <!-- Audit Trail Tab -->
89
+ <NcAppSidebarTab
90
+ v-if="!isTabHidden('auditTrail')"
91
+ id="auditTrail"
92
+ :name="auditTrailLabel"
93
+ :order="5">
94
+ <template #icon>
95
+ <History :size="20" />
96
+ </template>
97
+ <slot name="tab-audit-trail" :object-id="objectId" :object-type="objectType">
98
+ <CnAuditTrailTab
99
+ :object-id="objectId"
100
+ :register="register"
101
+ :schema="schema"
102
+ :api-base="apiBase" />
103
+ </slot>
104
+ </NcAppSidebarTab>
105
+
106
+ <!-- Custom tabs slot -->
107
+ <slot name="extra-tabs" />
108
+ </NcAppSidebar>
109
+ </template>
110
+
111
+ <script>
112
+ import { translate as t } from '@nextcloud/l10n'
113
+ import { NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue'
114
+
115
+ import Paperclip from 'vue-material-design-icons/Paperclip.vue'
116
+ import CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'
117
+ import TagOutline from 'vue-material-design-icons/TagOutline.vue'
118
+ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'
119
+ import History from 'vue-material-design-icons/History.vue'
120
+
121
+ import CnFilesTab from './CnFilesTab.vue'
122
+ import CnNotesTab from './CnNotesTab.vue'
123
+ import CnTagsTab from './CnTagsTab.vue'
124
+ import CnTasksTab from './CnTasksTab.vue'
125
+ import CnAuditTrailTab from './CnAuditTrailTab.vue'
126
+
127
+ /**
128
+ * CnObjectSidebar — Right sidebar for entity detail pages.
129
+ *
130
+ * Provides standardized tabs for generic object functionality (Files, Notes, Tags,
131
+ * Tasks, Audit Trail) that integrate with OpenRegister API endpoints bridging to
132
+ * Nextcloud-native APIs. Each tab is a self-contained component.
133
+ *
134
+ * @example Basic usage
135
+ * <CnObjectSidebar
136
+ * object-type="pipelinq_lead"
137
+ * :object-id="leadId"
138
+ * :register="registerConfig.register"
139
+ * :schema="registerConfig.schema" />
140
+ *
141
+ * @example Hide specific tabs
142
+ * <CnObjectSidebar
143
+ * object-type="pipelinq_lead"
144
+ * :object-id="leadId"
145
+ * :hidden-tabs="['tasks', 'tags']" />
146
+ *
147
+ * @example Override a tab
148
+ * <CnObjectSidebar object-type="pipelinq_lead" :object-id="leadId">
149
+ * <template #tab-notes="{ objectId }">
150
+ * <MyCustomNotesComponent :id="objectId" />
151
+ * </template>
152
+ * </CnObjectSidebar>
153
+ */
154
+ export default {
155
+ name: 'CnObjectSidebar',
156
+
157
+ components: {
158
+ NcAppSidebar,
159
+ NcAppSidebarTab,
160
+ Paperclip,
161
+ CommentTextOutline,
162
+ TagOutline,
163
+ CheckboxMarkedOutline,
164
+ History,
165
+ CnFilesTab,
166
+ CnNotesTab,
167
+ CnTagsTab,
168
+ CnTasksTab,
169
+ CnAuditTrailTab,
170
+ },
171
+
172
+ props: {
173
+ /** The entity type (e.g., "pipelinq_lead", "procest_case") */
174
+ objectType: {
175
+ type: String,
176
+ required: true,
177
+ },
178
+ /** The object UUID */
179
+ objectId: {
180
+ type: String,
181
+ required: true,
182
+ },
183
+ /** OpenRegister register ID */
184
+ register: {
185
+ type: String,
186
+ default: '',
187
+ },
188
+ /** OpenRegister schema ID */
189
+ schema: {
190
+ type: String,
191
+ default: '',
192
+ },
193
+ /** Array of tab IDs to hide: 'files', 'notes', 'tags', 'tasks', 'auditTrail' */
194
+ hiddenTabs: {
195
+ type: Array,
196
+ default: () => [],
197
+ },
198
+ /** Whether the sidebar is open */
199
+ open: {
200
+ type: Boolean,
201
+ default: true,
202
+ },
203
+ /** Sidebar title (defaults to objectType) */
204
+ title: {
205
+ type: String,
206
+ default: '',
207
+ },
208
+ /** Sidebar subtitle */
209
+ subtitle: {
210
+ type: String,
211
+ default: '',
212
+ },
213
+ /** @deprecated Use subtitle instead */
214
+ subtitleProp: {
215
+ type: String,
216
+ default: '',
217
+ },
218
+ /** Base API URL for OpenRegister */
219
+ apiBase: {
220
+ type: String,
221
+ default: '/apps/openregister/api',
222
+ },
223
+
224
+ // --- Pre-translated labels ---
225
+ filesLabel: { type: String, default: () => t('nextcloud-vue', 'Files') },
226
+ notesLabel: { type: String, default: () => t('nextcloud-vue', 'Notes') },
227
+ tagsLabel: { type: String, default: () => t('nextcloud-vue', 'Tags') },
228
+ tasksLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },
229
+ auditTrailLabel: { type: String, default: () => t('nextcloud-vue', 'Audit trail') },
230
+ },
231
+
232
+ emits: ['update:open'],
233
+
234
+ data() {
235
+ return {
236
+ activeTab: 'files',
237
+ }
238
+ },
239
+
240
+ computed: {
241
+ sidebarTitle() {
242
+ return this.title || this.objectType || 'Details'
243
+ },
244
+ sidebarSubtitle() {
245
+ return this.subtitle || this.subtitleProp || ''
246
+ },
247
+ },
248
+
249
+ methods: {
250
+ isTabHidden(tabId) {
251
+ return this.hiddenTabs.includes(tabId)
252
+ },
253
+ },
254
+ }
255
+ </script>
@@ -0,0 +1,259 @@
1
+ <template>
2
+ <div class="cn-sidebar-tab">
3
+ <!-- Add tag -->
4
+ <div class="cn-sidebar-tab__action">
5
+ <div class="cn-sidebar-tab__action--row">
6
+ <NcTextField
7
+ v-model="newTagName"
8
+ :label="addTagPlaceholder"
9
+ @input="filterSuggestions"
10
+ @keyup.enter="addTag"
11
+ @focus="showSuggestions = true" />
12
+ <NcButton
13
+ type="primary"
14
+ :disabled="!newTagName.trim() || saving"
15
+ @click="addTag">
16
+ <template #icon>
17
+ <Plus :size="20" />
18
+ </template>
19
+ </NcButton>
20
+ </div>
21
+ <!-- Tag suggestions dropdown -->
22
+ <div
23
+ v-if="showSuggestions && filtered.length > 0"
24
+ class="cn-sidebar-tab__tag-suggestions">
25
+ <button
26
+ v-for="suggestion in filtered"
27
+ :key="suggestion"
28
+ class="cn-sidebar-tab__tag-suggestion"
29
+ @mousedown.prevent="selectSuggestion(suggestion)">
30
+ <TagOutline :size="16" />
31
+ {{ suggestion }}
32
+ </button>
33
+ </div>
34
+ </div>
35
+
36
+ <!-- Tags list -->
37
+ <NcLoadingIcon v-if="loading" />
38
+ <div v-else-if="tags.length === 0" class="cn-sidebar-tab__empty">
39
+ {{ noTagsLabel }}
40
+ </div>
41
+ <div v-else class="cn-sidebar-tab__tags">
42
+ <span
43
+ v-for="tag in tags"
44
+ :key="tag"
45
+ class="cn-sidebar-tab__tag">
46
+ {{ tag }}
47
+ <button
48
+ class="cn-sidebar-tab__tag-remove"
49
+ :aria-label="'Remove ' + tag"
50
+ @click="removeTag(tag)">
51
+ <Close :size="14" />
52
+ </button>
53
+ </span>
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <script>
59
+ import { translate as t } from '@nextcloud/l10n'
60
+ import { NcButton, NcTextField, NcLoadingIcon } from '@nextcloud/vue'
61
+ import TagOutline from 'vue-material-design-icons/TagOutline.vue'
62
+ import Plus from 'vue-material-design-icons/Plus.vue'
63
+ import Close from 'vue-material-design-icons/Close.vue'
64
+ import { buildHeaders } from '../../utils/index.js'
65
+
66
+ export default {
67
+ name: 'CnTagsTab',
68
+
69
+ components: { NcButton, NcTextField, NcLoadingIcon, TagOutline, Plus, Close },
70
+
71
+ props: {
72
+ objectId: { type: String, required: true },
73
+ register: { type: String, default: '' },
74
+ schema: { type: String, default: '' },
75
+ apiBase: { type: String, default: '/apps/openregister/api' },
76
+ addTagPlaceholder: { type: String, default: () => t('nextcloud-vue', 'Add tag...') },
77
+ noTagsLabel: { type: String, default: () => t('nextcloud-vue', 'No tags') },
78
+ },
79
+
80
+ data() {
81
+ return {
82
+ tags: [],
83
+ loading: false,
84
+ newTagName: '',
85
+ saving: false,
86
+ availableTags: [],
87
+ filtered: [],
88
+ showSuggestions: false,
89
+ }
90
+ },
91
+
92
+ watch: {
93
+ objectId: {
94
+ immediate: true,
95
+ handler(id) {
96
+ if (id) {
97
+ this.fetchTags()
98
+ this.fetchAvailableTags()
99
+ }
100
+ },
101
+ },
102
+ },
103
+
104
+ methods: {
105
+ async fetchTags() {
106
+ if (!this.register || !this.schema) return
107
+ this.loading = true
108
+ try {
109
+ const response = await fetch(
110
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tags`,
111
+ { headers: buildHeaders() },
112
+ )
113
+ if (response.ok) {
114
+ this.tags = await response.json()
115
+ }
116
+ } catch (err) {
117
+ console.error('CnTagsTab: Failed to fetch tags', err)
118
+ } finally {
119
+ this.loading = false
120
+ }
121
+ },
122
+
123
+ async fetchAvailableTags() {
124
+ try {
125
+ const response = await fetch(`${this.apiBase}/tags`, { headers: buildHeaders() })
126
+ if (response.ok) {
127
+ this.availableTags = await response.json()
128
+ }
129
+ } catch (err) {
130
+ console.error('CnTagsTab: Failed to fetch available tags', err)
131
+ }
132
+ },
133
+
134
+ filterSuggestions() {
135
+ const query = this.newTagName.trim().toLowerCase()
136
+ if (!query) {
137
+ this.filtered = this.availableTags.filter(t => !this.tags.includes(t))
138
+ return
139
+ }
140
+ this.filtered = this.availableTags.filter(
141
+ t => t.toLowerCase().includes(query) && !this.tags.includes(t),
142
+ )
143
+ },
144
+
145
+ selectSuggestion(tagName) {
146
+ this.newTagName = tagName
147
+ this.showSuggestions = false
148
+ this.addTag()
149
+ },
150
+
151
+ async addTag() {
152
+ if (!this.newTagName.trim() || !this.register || !this.schema) return
153
+ this.saving = true
154
+ this.showSuggestions = false
155
+ try {
156
+ const response = await fetch(
157
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tags`,
158
+ {
159
+ method: 'POST',
160
+ headers: buildHeaders(),
161
+ body: JSON.stringify({ tag: this.newTagName.trim() }),
162
+ },
163
+ )
164
+ if (response.ok) {
165
+ this.tags = await response.json()
166
+ }
167
+ this.newTagName = ''
168
+ this.fetchAvailableTags()
169
+ } catch (err) {
170
+ console.error('CnTagsTab: Failed to add tag', err)
171
+ } finally {
172
+ this.saving = false
173
+ }
174
+ },
175
+
176
+ async removeTag(tagName) {
177
+ if (!this.register || !this.schema) return
178
+ try {
179
+ const response = await fetch(
180
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tags/${encodeURIComponent(tagName)}`,
181
+ { method: 'DELETE', headers: buildHeaders() },
182
+ )
183
+ if (response.ok) {
184
+ this.tags = await response.json()
185
+ }
186
+ } catch (err) {
187
+ console.error('CnTagsTab: Failed to remove tag', err)
188
+ }
189
+ },
190
+ },
191
+ }
192
+ </script>
193
+
194
+ <style scoped>
195
+ .cn-sidebar-tab { padding: 12px; }
196
+ .cn-sidebar-tab__action { margin-bottom: 16px; }
197
+ .cn-sidebar-tab__action--row { display: flex; gap: 8px; align-items: flex-end; }
198
+
199
+ .cn-sidebar-tab__empty {
200
+ text-align: center;
201
+ padding: 24px 12px;
202
+ color: var(--color-text-maxcontrast);
203
+ font-size: 13px;
204
+ }
205
+
206
+ .cn-sidebar-tab__tags { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0; }
207
+
208
+ .cn-sidebar-tab__tag {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ gap: 4px;
212
+ padding: 3px 8px;
213
+ border-radius: var(--border-radius-pill, 16px);
214
+ background: var(--color-primary-element-light, rgba(0, 130, 201, 0.1));
215
+ color: var(--color-primary-element);
216
+ font-size: 12px;
217
+ font-weight: 500;
218
+ }
219
+
220
+ .cn-sidebar-tab__tag-remove {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ background: none;
225
+ border: none;
226
+ padding: 0;
227
+ cursor: pointer;
228
+ color: var(--color-primary-element);
229
+ opacity: 0.6;
230
+ border-radius: 50%;
231
+ }
232
+
233
+ .cn-sidebar-tab__tag-remove:hover { opacity: 1; background: rgba(0, 0, 0, 0.08); }
234
+
235
+ .cn-sidebar-tab__tag-suggestions {
236
+ margin-top: 4px;
237
+ border: 1px solid var(--color-border);
238
+ border-radius: var(--border-radius);
239
+ background: var(--color-main-background);
240
+ max-height: 160px;
241
+ overflow-y: auto;
242
+ }
243
+
244
+ .cn-sidebar-tab__tag-suggestion {
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 8px;
248
+ width: 100%;
249
+ padding: 6px 12px;
250
+ border: none;
251
+ background: none;
252
+ cursor: pointer;
253
+ font-size: 13px;
254
+ color: var(--color-main-text);
255
+ text-align: left;
256
+ }
257
+
258
+ .cn-sidebar-tab__tag-suggestion:hover { background: var(--color-background-hover); }
259
+ </style>