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

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