@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.
- package/README.md +226 -0
- package/css/index.css +5 -0
- package/dist/nextcloud-vue.cjs +67614 -0
- package/dist/nextcloud-vue.cjs.js +76311 -5905
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.map +1 -0
- package/dist/nextcloud-vue.css +3279 -203
- package/dist/nextcloud-vue.esm.js +76240 -5882
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +89 -63
- package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
- package/src/components/CnActionsBar/index.js +1 -0
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +569 -0
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
- package/src/components/CnAdvancedFormDialog/index.js +1 -0
- package/src/components/CnCard/CnCard.vue +415 -0
- package/src/components/CnCard/index.js +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +23 -20
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/index.js +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +318 -0
- package/src/components/CnChartWidget/index.js +1 -0
- package/src/components/CnConfigurationCard/index.js +1 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
- package/src/components/CnContextMenu/index.js +1 -0
- package/src/components/CnCopyDialog/CnCopyDialog.vue +257 -0
- package/src/components/CnCopyDialog/index.js +1 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
- package/src/components/CnDashboardGrid/index.js +1 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +396 -0
- package/src/components/CnDashboardPage/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +24 -16
- package/src/components/CnDataTable/index.js +1 -1
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
- package/src/components/CnDeleteDialog/index.js +1 -0
- package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
- package/src/components/CnDetailCard/index.js +1 -0
- package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
- package/src/components/CnDetailGrid/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +431 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +12 -2
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +934 -0
- package/src/components/CnFormDialog/index.js +1 -0
- package/src/components/CnIcon/CnIcon.vue +89 -0
- package/src/components/CnIcon/index.js +1 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +589 -291
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +535 -0
- package/src/components/CnIndexSidebar/index.js +1 -0
- package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
- package/src/components/CnInfoWidget/index.js +1 -0
- package/src/components/CnItemCard/CnItemCard.vue +134 -0
- package/src/components/CnItemCard/index.js +1 -0
- package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
- package/src/components/CnJsonViewer/index.js +1 -0
- package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/CnMassActionBar.vue +6 -5
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +16 -9
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +16 -9
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +8 -7
- package/src/components/CnMassExportDialog/index.js +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +20 -17
- package/src/components/CnMassImportDialog/index.js +1 -1
- package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
- package/src/components/CnNoteCard/index.js +1 -0
- package/src/components/CnNotesCard/CnNotesCard.vue +415 -0
- package/src/components/CnNotesCard/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +3 -1
- package/src/components/CnObjectCard/index.js +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +853 -0
- package/src/components/CnObjectDataWidget/index.js +1 -0
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +288 -0
- package/src/components/CnObjectMetadataWidget/index.js +1 -0
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
- package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +254 -0
- package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
- package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
- package/src/components/CnObjectSidebar/index.js +6 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
- package/src/components/CnPageHeader/index.js +1 -0
- package/src/components/CnPagination/CnPagination.vue +7 -6
- package/src/components/CnPagination/index.js +1 -1
- package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
- package/src/components/CnProgressBar/index.js +1 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -0
- package/src/components/CnRegisterMapping/index.js +1 -0
- package/src/components/CnRowActions/CnRowActions.vue +25 -3
- package/src/components/CnRowActions/index.js +1 -1
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
- package/src/components/CnSchemaFormDialog/index.js +1 -0
- package/src/components/CnSettingsCard/index.js +1 -1
- package/src/components/CnSettingsSection/index.js +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +89 -19
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
- package/src/components/CnStatsPanel/index.js +1 -0
- package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
- package/src/components/CnStatusBadge/index.js +1 -1
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +544 -0
- package/src/components/CnTabbedFormDialog/index.js +1 -0
- package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
- package/src/components/CnTableWidget/index.js +1 -0
- package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
- package/src/components/CnTasksCard/index.js +1 -0
- package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
- package/src/components/CnTileWidget/index.js +1 -0
- package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
- package/src/components/CnTimelineStages/index.js +1 -0
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
- package/src/components/CnUserActionMenu/index.js +1 -0
- package/src/components/CnVersionInfoCard/index.js +1 -1
- package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
- package/src/components/CnWidgetRenderer/index.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +246 -0
- package/src/components/CnWidgetWrapper/index.js +1 -0
- package/src/components/index.js +57 -25
- package/src/composables/index.js +5 -3
- package/src/composables/useContextMenu.js +126 -0
- package/src/composables/useDashboardView.js +286 -0
- package/src/composables/useDetailView.js +290 -132
- package/src/composables/useListView.js +364 -153
- package/src/composables/useSubResource.js +142 -142
- package/src/constants/metadata.js +30 -0
- package/src/css/CnSchemaFormDialog.css +546 -0
- package/src/css/__sample_nextcloud_tokens.css +110 -0
- package/src/css/actions-bar.css +54 -0
- package/src/css/badge.css +83 -51
- package/src/css/card.css +129 -128
- package/src/css/context-menu.css +20 -0
- package/src/css/dashboard.css +70 -0
- package/src/css/detail-page.css +235 -0
- package/src/css/detail.css +68 -68
- package/src/css/index-page.css +44 -0
- package/src/css/index-sidebar.css +193 -0
- package/src/css/index.css +17 -8
- package/src/css/layout.css +90 -90
- package/src/css/page-header.css +35 -0
- package/src/css/pagination.css +72 -72
- package/src/css/table.css +142 -143
- package/src/css/timeline-stages.css +220 -0
- package/src/css/utilities.css +46 -46
- package/src/index.js +91 -50
- package/src/mixins/gridLayout.js +118 -0
- package/src/store/createCrudStore.js +360 -0
- package/src/store/createSubResourcePlugin.js +125 -135
- package/src/store/index.js +4 -3
- package/src/store/plugins/auditTrails.js +357 -17
- package/src/store/plugins/files.js +250 -186
- package/src/store/plugins/index.js +7 -4
- package/src/store/plugins/lifecycle.js +180 -180
- package/src/store/plugins/registerMapping.js +195 -0
- package/src/store/plugins/relations.js +68 -68
- package/src/store/plugins/search.js +385 -0
- package/src/store/plugins/selection.js +104 -0
- package/src/store/useObjectStore.js +823 -625
- package/src/types/auditTrail.d.ts +32 -32
- package/src/types/file.d.ts +23 -23
- package/src/types/index.d.ts +35 -35
- package/src/types/notification.d.ts +36 -36
- package/src/types/object.d.ts +40 -40
- package/src/types/organisation.d.ts +41 -41
- package/src/types/register.d.ts +25 -25
- package/src/types/schema.d.ts +39 -39
- package/src/types/shared.d.ts +79 -79
- package/src/types/source.d.ts +14 -14
- package/src/types/task.d.ts +31 -31
- package/src/utils/errors.js +96 -96
- package/src/utils/getTheme.js +9 -0
- package/src/utils/headers.js +80 -44
- package/src/utils/id.js +13 -0
- package/src/utils/index.js +4 -3
- package/src/utils/schema.js +422 -287
- package/src/utils/widgetVisibility.js +162 -0
- package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
- package/src/components/CnDetailViewLayout/index.js +0 -1
- package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
- package/src/components/CnEmptyState/index.js +0 -1
- package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
- package/src/components/CnListViewLayout/index.js +0 -1
- package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
- package/src/components/CnViewModeToggle/index.js +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
|
|
3
|
+
|
|
4
|
+
Supports two modes:
|
|
5
|
+
1. Manual: provide `fields` prop with `[{ label, value }]` objects
|
|
6
|
+
2. Auto-generated: provide `object` + `schema` props to generate from JSON Schema properties
|
|
7
|
+
|
|
8
|
+
Used in dashboard and detail page grid layouts to display entity metadata.
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
class="cn-info-widget"
|
|
13
|
+
:style="gridStyle">
|
|
14
|
+
<div
|
|
15
|
+
v-for="(field, index) in displayFields"
|
|
16
|
+
:key="index"
|
|
17
|
+
class="cn-info-widget__field">
|
|
18
|
+
<dt class="cn-info-widget__label">
|
|
19
|
+
{{ field.label }}
|
|
20
|
+
</dt>
|
|
21
|
+
<dd class="cn-info-widget__value">
|
|
22
|
+
{{ field.value || '—' }}
|
|
23
|
+
</dd>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
/**
|
|
30
|
+
* CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
|
|
31
|
+
*
|
|
32
|
+
* @example Manual fields
|
|
33
|
+
* <CnInfoWidget :fields="[
|
|
34
|
+
* { label: 'Email', value: 'test@example.com' },
|
|
35
|
+
* { label: 'Phone', value: '+31 6 12345678' },
|
|
36
|
+
* ]" :columns="2" />
|
|
37
|
+
*
|
|
38
|
+
* @example Auto-generated from schema
|
|
39
|
+
* <CnInfoWidget :object="myObject" :schema="mySchema" :columns="3" />
|
|
40
|
+
*/
|
|
41
|
+
export default {
|
|
42
|
+
name: 'CnInfoWidget',
|
|
43
|
+
|
|
44
|
+
props: {
|
|
45
|
+
/**
|
|
46
|
+
* Manual field definitions. Array of `{ label, value }` objects.
|
|
47
|
+
* Takes precedence over object+schema auto-generation.
|
|
48
|
+
*
|
|
49
|
+
* @type {{ label: string, value: string|number }[]}
|
|
50
|
+
*/
|
|
51
|
+
fields: {
|
|
52
|
+
type: Array,
|
|
53
|
+
default: null,
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* Object data for auto-generation mode. Properties are extracted
|
|
57
|
+
* based on the schema definition.
|
|
58
|
+
*
|
|
59
|
+
* @type {object}
|
|
60
|
+
*/
|
|
61
|
+
object: {
|
|
62
|
+
type: Object,
|
|
63
|
+
default: null,
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* JSON Schema for auto-generation mode. Each schema property
|
|
67
|
+
* generates a label:value pair using the property title as label.
|
|
68
|
+
*
|
|
69
|
+
* @type {object}
|
|
70
|
+
*/
|
|
71
|
+
schema: {
|
|
72
|
+
type: Object,
|
|
73
|
+
default: null,
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Number of columns for the grid layout.
|
|
77
|
+
*
|
|
78
|
+
* @type {number}
|
|
79
|
+
*/
|
|
80
|
+
columns: {
|
|
81
|
+
type: Number,
|
|
82
|
+
default: 2,
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Fields to include (by key). If provided, only these fields are shown.
|
|
86
|
+
* Only applies in auto-generation mode.
|
|
87
|
+
*
|
|
88
|
+
* @type {string[]}
|
|
89
|
+
*/
|
|
90
|
+
includeFields: {
|
|
91
|
+
type: Array,
|
|
92
|
+
default: null,
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* Fields to exclude (by key). Only applies in auto-generation mode.
|
|
96
|
+
*
|
|
97
|
+
* @type {string[]}
|
|
98
|
+
*/
|
|
99
|
+
excludeFields: {
|
|
100
|
+
type: Array,
|
|
101
|
+
default: () => [],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
computed: {
|
|
106
|
+
/**
|
|
107
|
+
* Resolved fields to display. Manual fields take precedence;
|
|
108
|
+
* otherwise generates from object + schema.
|
|
109
|
+
*
|
|
110
|
+
* @return {{ label: string, value: string|number }[]}
|
|
111
|
+
*/
|
|
112
|
+
displayFields() {
|
|
113
|
+
if (this.fields) {
|
|
114
|
+
return this.fields
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this.object && this.schema && this.schema.properties) {
|
|
118
|
+
return this.generateFieldsFromSchema()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return []
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* CSS grid style based on column count.
|
|
126
|
+
*
|
|
127
|
+
* @return {object}
|
|
128
|
+
*/
|
|
129
|
+
gridStyle() {
|
|
130
|
+
return {
|
|
131
|
+
gridTemplateColumns: `repeat(${this.columns}, 1fr)`,
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
methods: {
|
|
137
|
+
/**
|
|
138
|
+
* Generate label-value field definitions from object + schema.
|
|
139
|
+
*
|
|
140
|
+
* @return {{ label: string, value: string|number }[]}
|
|
141
|
+
*/
|
|
142
|
+
generateFieldsFromSchema() {
|
|
143
|
+
const properties = this.schema.properties || {}
|
|
144
|
+
const keys = this.includeFields || Object.keys(properties)
|
|
145
|
+
|
|
146
|
+
return keys
|
|
147
|
+
.filter(key => !this.excludeFields.includes(key))
|
|
148
|
+
.filter(key => properties[key])
|
|
149
|
+
.map(key => ({
|
|
150
|
+
label: properties[key].title || key,
|
|
151
|
+
value: this.formatFieldValue(this.object[key], properties[key]),
|
|
152
|
+
}))
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format a field value for display based on its schema type.
|
|
157
|
+
*
|
|
158
|
+
* @param {*} value - The raw value.
|
|
159
|
+
* @param {object} schemaProp - The JSON Schema property definition.
|
|
160
|
+
* @return {string} Formatted display value.
|
|
161
|
+
*/
|
|
162
|
+
formatFieldValue(value) {
|
|
163
|
+
if (value === null || value === undefined) {
|
|
164
|
+
return ''
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
return value.join(', ')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (typeof value === 'object') {
|
|
172
|
+
return JSON.stringify(value)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof value === 'boolean') {
|
|
176
|
+
return value ? 'Yes' : 'No'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return String(value)
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<style scoped>
|
|
186
|
+
.cn-info-widget {
|
|
187
|
+
display: grid;
|
|
188
|
+
gap: 12px 24px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cn-info-widget__field {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
gap: 2px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.cn-info-widget__label {
|
|
198
|
+
font-size: 12px;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
color: var(--color-text-maxcontrast);
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
letter-spacing: 0.5px;
|
|
203
|
+
margin: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.cn-info-widget__value {
|
|
207
|
+
font-size: 14px;
|
|
208
|
+
color: var(--color-main-text);
|
|
209
|
+
margin: 0;
|
|
210
|
+
word-break: break-word;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Responsive: single column on small screens */
|
|
214
|
+
@media (max-width: 600px) {
|
|
215
|
+
.cn-info-widget {
|
|
216
|
+
grid-template-columns: 1fr !important;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnInfoWidget } from './CnInfoWidget.vue'
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cn-item-card" @click="$emit('click', $event)">
|
|
3
|
+
<div class="cn-item-card__header">
|
|
4
|
+
<div class="cn-item-card__title-row">
|
|
5
|
+
<slot name="icon">
|
|
6
|
+
<component :is="icon" v-if="icon" :size="iconSize" />
|
|
7
|
+
</slot>
|
|
8
|
+
<div class="cn-item-card__title-content">
|
|
9
|
+
<h3 class="cn-item-card__title">
|
|
10
|
+
{{ title }}
|
|
11
|
+
</h3>
|
|
12
|
+
<span v-if="subtitle" class="cn-item-card__subtitle">{{ subtitle }}</span>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div v-if="$slots.actions || $scopedSlots.actions" class="cn-item-card__actions">
|
|
16
|
+
<slot name="actions" />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div v-if="$slots.default || $scopedSlots.default" class="cn-item-card__content">
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
/**
|
|
27
|
+
* CnItemCard — Compact card for displaying an item in a sidebar list.
|
|
28
|
+
*
|
|
29
|
+
* Provides a card with a header (icon + title + optional actions) and
|
|
30
|
+
* a flexible content area. Designed for use in sidebar lists such as
|
|
31
|
+
* schema listings, source listings, etc.
|
|
32
|
+
*
|
|
33
|
+
* @example Basic usage
|
|
34
|
+
* <CnItemCard title="My Schema" :icon="FileCodeOutline">
|
|
35
|
+
* <p>Schema content here</p>
|
|
36
|
+
* </CnItemCard>
|
|
37
|
+
*
|
|
38
|
+
* @example With actions and stats
|
|
39
|
+
* <CnItemCard title="My Schema" :icon="FileCodeOutline" subtitle="v1.0">
|
|
40
|
+
* <template #actions>
|
|
41
|
+
* <NcActions>
|
|
42
|
+
* <NcActionButton @click="edit">Edit</NcActionButton>
|
|
43
|
+
* </NcActions>
|
|
44
|
+
* </template>
|
|
45
|
+
* <CnKpiGrid :columns="2">
|
|
46
|
+
* <CnStatsBlock title="Objects" :count="42" />
|
|
47
|
+
* <CnStatsBlock title="Size" :count="0" :breakdown="{ size: '1.2 MB' }" />
|
|
48
|
+
* </CnKpiGrid>
|
|
49
|
+
* </CnItemCard>
|
|
50
|
+
*/
|
|
51
|
+
export default {
|
|
52
|
+
name: 'CnItemCard',
|
|
53
|
+
|
|
54
|
+
props: {
|
|
55
|
+
/** Card title */
|
|
56
|
+
title: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
/** Optional subtitle below the title */
|
|
61
|
+
subtitle: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: '',
|
|
64
|
+
},
|
|
65
|
+
/** Icon component (e.g., imported MDI icon) */
|
|
66
|
+
icon: {
|
|
67
|
+
type: [Object, Function],
|
|
68
|
+
default: null,
|
|
69
|
+
},
|
|
70
|
+
/** Icon size in pixels */
|
|
71
|
+
iconSize: {
|
|
72
|
+
type: Number,
|
|
73
|
+
default: 20,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
.cn-item-card {
|
|
81
|
+
background: var(--color-main-background);
|
|
82
|
+
border: 1px solid var(--color-border);
|
|
83
|
+
border-radius: var(--border-radius-large, 10px);
|
|
84
|
+
padding: 12px;
|
|
85
|
+
margin-bottom: 12px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cn-item-card:last-child {
|
|
89
|
+
margin-bottom: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.cn-item-card__header {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.cn-item-card__title-row {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 8px;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
flex: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.cn-item-card__title-content {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
min-width: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.cn-item-card__title {
|
|
113
|
+
margin: 0;
|
|
114
|
+
font-size: 1em;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
color: var(--color-main-text);
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.cn-item-card__subtitle {
|
|
123
|
+
font-size: 0.85em;
|
|
124
|
+
color: var(--color-text-maxcontrast);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.cn-item-card__actions {
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.cn-item-card__content {
|
|
132
|
+
margin-top: 8px;
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnItemCard } from './CnItemCard.vue'
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnJsonViewer — Syntax-highlighted code viewer/editor powered by CodeMirror.
|
|
3
|
+
|
|
4
|
+
Supports multiple languages (JSON, XML, HTML, plain text) with optional
|
|
5
|
+
auto-detection. Use `readOnly` for display-only mode.
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<div class="cn-json-viewer">
|
|
9
|
+
<div :class="['cn-json-viewer__codemirror', isDark ? 'cn-json-viewer__codemirror--dark' : 'cn-json-viewer__codemirror--light']">
|
|
10
|
+
<CodeMirror
|
|
11
|
+
v-model="localValue"
|
|
12
|
+
:basic="true"
|
|
13
|
+
:placeholder="resolvedLanguage === 'json' ? '{ "key": "value" }' : ''"
|
|
14
|
+
:dark="isDark"
|
|
15
|
+
:readonly="readOnly"
|
|
16
|
+
:linter="linterExtension"
|
|
17
|
+
:lang="langExtension"
|
|
18
|
+
:extensions="editorExtensions"
|
|
19
|
+
:tab-size="2"
|
|
20
|
+
:style="{ height }" />
|
|
21
|
+
<NcButton
|
|
22
|
+
v-if="!readOnly && resolvedLanguage === 'json'"
|
|
23
|
+
class="cn-json-viewer__format-btn"
|
|
24
|
+
type="secondary"
|
|
25
|
+
size="small"
|
|
26
|
+
@click="formatJson">
|
|
27
|
+
Format JSON
|
|
28
|
+
</NcButton>
|
|
29
|
+
</div>
|
|
30
|
+
<span v-if="!readOnly && resolvedLanguage === 'json' && !isValidJson(value)" class="cn-json-viewer__error">
|
|
31
|
+
Invalid JSON format
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
import { NcButton } from '@nextcloud/vue'
|
|
38
|
+
import CodeMirror from 'vue-codemirror6'
|
|
39
|
+
import { githubLight, githubDark } from '@uiw/codemirror-theme-github'
|
|
40
|
+
import { json as jsonLang, jsonParseLinter as jsonLinter } from '@codemirror/lang-json'
|
|
41
|
+
import { xml as xmlLang } from '@codemirror/lang-xml'
|
|
42
|
+
import { html as htmlLang } from '@codemirror/lang-html'
|
|
43
|
+
import { getTheme } from '../../utils/getTheme.js'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* CnJsonViewer — Syntax-highlighted code viewer/editor.
|
|
47
|
+
*
|
|
48
|
+
* Wraps CodeMirror 6 with support for JSON, XML, HTML, and plain text.
|
|
49
|
+
* Includes syntax highlighting, and optional formatting/validation for JSON.
|
|
50
|
+
* Use `readOnly` for display-only mode.
|
|
51
|
+
*
|
|
52
|
+
* @example Read-only JSON display (default)
|
|
53
|
+
* <CnJsonViewer :value="jsonString" :read-only="true" />
|
|
54
|
+
*
|
|
55
|
+
* @example Auto-detect language from content
|
|
56
|
+
* <CnJsonViewer :value="responseBody" :read-only="true" language="auto" />
|
|
57
|
+
*
|
|
58
|
+
* @example Explicit XML mode
|
|
59
|
+
* <CnJsonViewer :value="xmlString" :read-only="true" language="xml" />
|
|
60
|
+
*
|
|
61
|
+
* @example Editable JSON with custom height
|
|
62
|
+
* <CnJsonViewer :value="jsonString" height="500px" @update:value="onUpdate" />
|
|
63
|
+
*/
|
|
64
|
+
export default {
|
|
65
|
+
name: 'CnJsonViewer',
|
|
66
|
+
|
|
67
|
+
components: {
|
|
68
|
+
NcButton,
|
|
69
|
+
CodeMirror,
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
props: {
|
|
73
|
+
/** JSON string to display or edit */
|
|
74
|
+
value: { type: String, default: '' },
|
|
75
|
+
/** When true, the editor is non-editable and hides format button and validation */
|
|
76
|
+
readOnly: { type: Boolean, default: false },
|
|
77
|
+
/** CSS height for the editor container */
|
|
78
|
+
height: { type: String, default: '300px' },
|
|
79
|
+
/**
|
|
80
|
+
* Content language for syntax highlighting.
|
|
81
|
+
* - 'auto': Auto-detect from content (JSON → xml → text) (default)
|
|
82
|
+
* - 'json': JSON with validation and formatting
|
|
83
|
+
* - 'xml': XML/HTML tag highlighting
|
|
84
|
+
* - 'html': Alias for XML highlighting
|
|
85
|
+
* - 'text': Plain text, no highlighting
|
|
86
|
+
*/
|
|
87
|
+
language: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: 'auto',
|
|
90
|
+
validator: (v) => ['json', 'xml', 'html', 'text', 'auto'].includes(v),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
data() {
|
|
95
|
+
return {
|
|
96
|
+
githubLight,
|
|
97
|
+
githubDark,
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
computed: {
|
|
102
|
+
localValue: {
|
|
103
|
+
get() { return this.value },
|
|
104
|
+
set(v) { this.$emit('update:value', v) },
|
|
105
|
+
},
|
|
106
|
+
isDark: {
|
|
107
|
+
get() { return getTheme() === 'dark' },
|
|
108
|
+
},
|
|
109
|
+
theme: {
|
|
110
|
+
get() { return this.isDark ? githubDark : githubLight },
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* Resolve 'auto' language to a concrete language based on content.
|
|
114
|
+
* @return {string} Resolved language: 'json', 'xml', or 'text'
|
|
115
|
+
*/
|
|
116
|
+
resolvedLanguage() {
|
|
117
|
+
if (this.language !== 'auto') {
|
|
118
|
+
return this.language
|
|
119
|
+
}
|
|
120
|
+
const trimmed = (this.value || '').trim()
|
|
121
|
+
if (!trimmed) return 'text'
|
|
122
|
+
try {
|
|
123
|
+
JSON.parse(trimmed)
|
|
124
|
+
return 'json'
|
|
125
|
+
} catch {
|
|
126
|
+
// not JSON
|
|
127
|
+
}
|
|
128
|
+
if (trimmed.charAt(0) === '<' && trimmed.includes('>')) {
|
|
129
|
+
// Detect HTML by doctype or common HTML tags
|
|
130
|
+
if (/<!doctype\s+html/i.test(trimmed) || /<(?:html|head|body|div|span|p|a|script|style|link|meta|form|input|button|table|ul|ol|li|h[1-6]|img|nav|header|footer|main|section|article)\b/i.test(trimmed)) {
|
|
131
|
+
return 'html'
|
|
132
|
+
}
|
|
133
|
+
return 'xml'
|
|
134
|
+
}
|
|
135
|
+
return 'text'
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* CodeMirror language extension based on resolved language.
|
|
139
|
+
* @return {object|null} Language extension or null for plain text
|
|
140
|
+
*/
|
|
141
|
+
langExtension() {
|
|
142
|
+
switch (this.resolvedLanguage) {
|
|
143
|
+
case 'json':
|
|
144
|
+
return jsonLang()
|
|
145
|
+
case 'html':
|
|
146
|
+
return htmlLang()
|
|
147
|
+
case 'xml':
|
|
148
|
+
return xmlLang()
|
|
149
|
+
case 'text':
|
|
150
|
+
default:
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* CodeMirror linter extension (only active for JSON in edit mode).
|
|
156
|
+
* @return {object|null} Linter extension or null
|
|
157
|
+
*/
|
|
158
|
+
linterExtension() {
|
|
159
|
+
if (this.readOnly) return null
|
|
160
|
+
if (this.resolvedLanguage === 'json') return jsonLinter()
|
|
161
|
+
return null
|
|
162
|
+
},
|
|
163
|
+
/**
|
|
164
|
+
* Combined CodeMirror extensions array.
|
|
165
|
+
* @return {Array} Extensions including theme and optional language
|
|
166
|
+
*/
|
|
167
|
+
editorExtensions() {
|
|
168
|
+
const exts = [this.theme]
|
|
169
|
+
if (this.langExtension) exts.push(this.langExtension)
|
|
170
|
+
return exts
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
methods: {
|
|
175
|
+
/**
|
|
176
|
+
* Format the current JSON value with 2-space indentation.
|
|
177
|
+
* Emits 'update:value' with the formatted string and 'format' with the parsed object.
|
|
178
|
+
*/
|
|
179
|
+
formatJson() {
|
|
180
|
+
try {
|
|
181
|
+
if (this.value) {
|
|
182
|
+
const parsed = JSON.parse(this.value)
|
|
183
|
+
this.$emit('update:value', JSON.stringify(parsed, null, 2))
|
|
184
|
+
this.$emit('format', parsed)
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// Keep invalid JSON as-is
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if a string is valid JSON.
|
|
193
|
+
* @param {string} str - String to validate
|
|
194
|
+
* @return {boolean} True if valid JSON
|
|
195
|
+
*/
|
|
196
|
+
isValidJson(str) {
|
|
197
|
+
if (!str || !str.trim()) return false
|
|
198
|
+
try {
|
|
199
|
+
JSON.parse(str)
|
|
200
|
+
return true
|
|
201
|
+
} catch {
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
</script>
|
|
208
|
+
|
|
209
|
+
<style scoped>
|
|
210
|
+
.cn-json-viewer {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.cn-json-viewer__codemirror {
|
|
217
|
+
border: 1px solid var(--color-border);
|
|
218
|
+
border-radius: var(--border-radius);
|
|
219
|
+
position: relative;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.cn-json-viewer__codemirror :deep(.cm-editor) {
|
|
223
|
+
height: 100%;
|
|
224
|
+
outline: none !important;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.cn-json-viewer__codemirror :deep(.cm-scroller) {
|
|
228
|
+
overflow: auto;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.cn-json-viewer__codemirror :deep(.cm-content) {
|
|
232
|
+
border-radius: 0 !important;
|
|
233
|
+
border: none !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.cn-json-viewer__codemirror--light > .vue-codemirror {
|
|
237
|
+
border: 1px dotted silver;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.cn-json-viewer__codemirror--dark > .vue-codemirror {
|
|
241
|
+
border: 1px dotted grey;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Text cursor */
|
|
245
|
+
.cn-json-viewer__codemirror :deep(.cm-content) * {
|
|
246
|
+
cursor: text !important;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* PATCH SELECTION COLOR - default selection system does not work */
|
|
250
|
+
/* Selection background — CodeMirror uses .cm-selectionBackground instead of ::selection */
|
|
251
|
+
.cn-json-viewer__codemirror--light :deep(.cm-selectionBackground) {
|
|
252
|
+
background: #3390ff !important;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.cn-json-viewer__codemirror--light :deep(.cm-selectionBackground) + .cm-selectionBackground,
|
|
256
|
+
.cn-json-viewer__codemirror--light :deep(.cm-line ::selection) {
|
|
257
|
+
color: white !important;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.cn-json-viewer__codemirror--dark :deep(.cm-selectionBackground) {
|
|
261
|
+
background: #3390ff !important;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Selected text color */
|
|
265
|
+
.cn-json-viewer__codemirror--light :deep(.cm-content ::selection) {
|
|
266
|
+
background: #3390ff !important;
|
|
267
|
+
color: white !important;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.cn-json-viewer__codemirror--dark :deep(.cm-content ::selection) {
|
|
271
|
+
background: #3390ff !important;
|
|
272
|
+
color: white !important;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.cn-json-viewer__format-btn {
|
|
276
|
+
margin-top: 8px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.cn-json-viewer__error {
|
|
280
|
+
color: var(--color-error);
|
|
281
|
+
font-size: 14px;
|
|
282
|
+
}
|
|
283
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnJsonViewer } from './CnJsonViewer.vue'
|
|
@@ -35,12 +35,16 @@ export default {
|
|
|
35
35
|
default: 4,
|
|
36
36
|
validator: (v) => [2, 3, 4].includes(v),
|
|
37
37
|
},
|
|
38
|
+
gridClass: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: '',
|
|
41
|
+
},
|
|
38
42
|
},
|
|
39
43
|
|
|
40
44
|
computed: {
|
|
41
45
|
gridClasses() {
|
|
42
46
|
return {
|
|
43
|
-
[`cn-kpi-grid--cols-${this.columns}`]: true,
|
|
47
|
+
[`cn-kpi-grid--cols-${this.columns} ${this.gridClass}`]: true,
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as CnKpiGrid } from './CnKpiGrid.vue'
|
|
1
|
+
export { default as CnKpiGrid } from './CnKpiGrid.vue'
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
</template>
|
|
54
54
|
|
|
55
55
|
<script>
|
|
56
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
56
57
|
import { NcActions, NcActionButton } from '@nextcloud/vue'
|
|
57
58
|
import TuneVariant from 'vue-material-design-icons/TuneVariant.vue'
|
|
58
59
|
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
|
|
@@ -143,12 +144,12 @@ export default {
|
|
|
143
144
|
/** Label template for the menu button. Use {count} for the count. */
|
|
144
145
|
menuLabelTemplate: {
|
|
145
146
|
type: String,
|
|
146
|
-
default: 'Mass Actions ({count})',
|
|
147
|
+
default: () => t('nextcloud-vue', 'Mass Actions ({count})'),
|
|
147
148
|
},
|
|
148
|
-
importLabel: { type: String, default: 'Import' },
|
|
149
|
-
exportLabel: { type: String, default: 'Export' },
|
|
150
|
-
copyLabel: { type: String, default: 'Copy' },
|
|
151
|
-
deleteLabel: { type: String, default: 'Delete' },
|
|
149
|
+
importLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },
|
|
150
|
+
exportLabel: { type: String, default: () => t('nextcloud-vue', 'Export') },
|
|
151
|
+
copyLabel: { type: String, default: () => t('nextcloud-vue', 'Copy') },
|
|
152
|
+
deleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },
|
|
152
153
|
},
|
|
153
154
|
|
|
154
155
|
computed: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as CnMassActionBar } from './CnMassActionBar.vue'
|
|
1
|
+
export { default as CnMassActionBar } from './CnMassActionBar.vue'
|