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

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 (56) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +7039 -2409
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +237 -52
  6. package/dist/nextcloud-vue.esm.js +7012 -2386
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +4 -5
  9. package/src/components/CnActionsBar/CnActionsBar.vue +225 -0
  10. package/src/components/CnActionsBar/index.js +1 -0
  11. package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -0
  12. package/src/components/CnCopyDialog/index.js +1 -0
  13. package/src/components/CnDataTable/CnDataTable.vue +0 -5
  14. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -0
  15. package/src/components/CnDeleteDialog/index.js +1 -0
  16. package/src/components/CnFormDialog/CnFormDialog.vue +629 -0
  17. package/src/components/CnFormDialog/index.js +1 -0
  18. package/src/components/CnIcon/CnIcon.vue +89 -0
  19. package/src/components/CnIcon/index.js +1 -0
  20. package/src/components/CnIndexPage/CnIndexPage.vue +434 -300
  21. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +484 -0
  22. package/src/components/CnIndexSidebar/index.js +1 -0
  23. package/src/components/CnPageHeader/CnPageHeader.vue +57 -0
  24. package/src/components/CnPageHeader/index.js +1 -0
  25. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -0
  26. package/src/components/CnRegisterMapping/index.js +1 -0
  27. package/src/components/index.js +8 -4
  28. package/src/composables/useListView.js +254 -45
  29. package/src/constants/metadata.js +30 -0
  30. package/src/css/actions-bar.css +48 -0
  31. package/src/css/badge.css +4 -4
  32. package/src/css/card.css +23 -23
  33. package/src/css/detail.css +13 -13
  34. package/src/css/index-page.css +32 -0
  35. package/src/css/index-sidebar.css +187 -0
  36. package/src/css/index.css +4 -0
  37. package/src/css/layout.css +14 -14
  38. package/src/css/page-header.css +33 -0
  39. package/src/css/pagination.css +12 -12
  40. package/src/css/table.css +21 -22
  41. package/src/css/utilities.css +2 -2
  42. package/src/index.js +11 -8
  43. package/src/store/plugins/index.js +1 -0
  44. package/src/store/plugins/registerMapping.js +185 -0
  45. package/src/store/useObjectStore.js +122 -61
  46. package/src/utils/headers.js +7 -1
  47. package/src/utils/index.js +1 -1
  48. package/src/utils/schema.js +133 -1
  49. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  50. package/src/components/CnDetailViewLayout/index.js +0 -1
  51. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  52. package/src/components/CnEmptyState/index.js +0 -1
  53. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  54. package/src/components/CnListViewLayout/index.js +0 -1
  55. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  56. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Schema utility functions for auto-generating table columns, cell formatting,
3
- * and faceted filter definitions from OpenRegister schema property definitions.
3
+ * form field definitions, and faceted filter definitions from OpenRegister
4
+ * schema property definitions.
4
5
  *
5
6
  * @module utils/schema
6
7
  */
@@ -236,6 +237,136 @@ function truncateString(str, maxLength) {
236
237
  return str.substring(0, maxLength) + '...'
237
238
  }
238
239
 
240
+ /**
241
+ * Resolve the form widget type for a JSON Schema property.
242
+ *
243
+ * Resolution priority (first match wins):
244
+ * 1. Explicit `prop.widget` — pass-through custom widget name
245
+ * 2. `prop.enum` → `'select'`
246
+ * 3. Type-based: `boolean` → `'checkbox'`, `integer`/`number` → `'number'`,
247
+ * `array` + `items.enum` → `'multiselect'`, `array` → `'tags'`
248
+ * 4. Format-based: `date-time` → `'datetime'`, `date` → `'date'`,
249
+ * `email` → `'email'`, `uri`/`url` → `'url'`,
250
+ * `markdown`/`textarea` → `'textarea'`
251
+ * 5. Long text: `maxLength > 255` → `'textarea'`
252
+ * 6. Fallback → `'text'`
253
+ *
254
+ * @param {object} prop The schema property definition (type, format, enum, widget, items, maxLength)
255
+ * @return {string} Widget identifier: 'text'|'email'|'url'|'number'|'checkbox'|'select'|'multiselect'|'tags'|'textarea'|'date'|'datetime' or a custom string
256
+ */
257
+ function resolveWidget(prop) {
258
+ // Explicit widget hint takes priority
259
+ if (prop.widget) return prop.widget
260
+
261
+ // Enum → select
262
+ if (prop.enum) return 'select'
263
+
264
+ const type = prop.type || 'string'
265
+ const format = prop.format || ''
266
+
267
+ // Boolean → switch/checkbox
268
+ if (type === 'boolean') return 'checkbox'
269
+
270
+ // Number types
271
+ if (type === 'integer' || type === 'number') return 'number'
272
+
273
+ // Array types
274
+ if (type === 'array') {
275
+ if (prop.items && prop.items.enum) return 'multiselect'
276
+ return 'tags'
277
+ }
278
+
279
+ // Format-based widgets
280
+ if (format === 'date-time') return 'datetime'
281
+ if (format === 'date') return 'date'
282
+ if (format === 'email') return 'email'
283
+ if (format === 'uri' || format === 'url') return 'url'
284
+ if (format === 'markdown' || format === 'textarea') return 'textarea'
285
+
286
+ // Long text → textarea
287
+ if (prop.maxLength && prop.maxLength > 255) return 'textarea'
288
+
289
+ return 'text'
290
+ }
291
+
292
+ /**
293
+ * Generate form field definitions from a schema's properties.
294
+ *
295
+ * Reads `schema.properties` and creates field descriptor objects suitable
296
+ * for auto-generating form UIs. Follows the same pattern as
297
+ * `columnsFromSchema()` — filters, sorts, and supports overrides.
298
+ *
299
+ * @param {object} schema The schema object with a `properties` field
300
+ * @param {object} [options] Configuration options
301
+ * @param {string[]} [options.exclude] Property keys to exclude
302
+ * @param {string[]} [options.include] Property keys to include (whitelist mode)
303
+ * @param {object} [options.overrides] Per-key field overrides, e.g. `{ status: { widget: 'select' } }`
304
+ * @param {boolean} [options.includeReadOnly=false] Whether to include readOnly properties
305
+ * @return {Array<{key: string, label: string, description: string, type: string, format: string|null, widget: string, required: boolean, readOnly: boolean, default: *, enum: Array|null, items: object|null, validation: object, order: number}>}
306
+ */
307
+ export function fieldsFromSchema(schema, options = {}) {
308
+ const { exclude = [], include = null, overrides = {}, includeReadOnly = false } = options
309
+
310
+ if (!schema || !schema.properties) {
311
+ return []
312
+ }
313
+
314
+ const requiredKeys = Array.isArray(schema.required) ? schema.required : []
315
+
316
+ const entries = Object.entries(schema.properties)
317
+ .filter(([key, prop]) => {
318
+ // Skip properties marked as not visible
319
+ if (prop.visible === false) return false
320
+ // Skip readOnly properties by default
321
+ if (prop.readOnly === true && !includeReadOnly) return false
322
+ // Apply exclude list
323
+ if (exclude.includes(key)) return false
324
+ // Apply include whitelist
325
+ if (include && !include.includes(key)) return false
326
+ // Skip complex object types (not supported in auto-form)
327
+ if (prop.type === 'object') return false
328
+ return true
329
+ })
330
+ .sort(([keyA, propA], [keyB, propB]) => {
331
+ // Sort by order hint first, then alphabetically
332
+ const orderA = typeof propA.order === 'number' ? propA.order : Infinity
333
+ const orderB = typeof propB.order === 'number' ? propB.order : Infinity
334
+ if (orderA !== orderB) return orderA - orderB
335
+ return keyA.localeCompare(keyB)
336
+ })
337
+
338
+ return entries.map(([key, prop]) => {
339
+ const field = {
340
+ key,
341
+ label: prop.title || key,
342
+ description: prop.description || '',
343
+ type: prop.type || 'string',
344
+ format: prop.format || null,
345
+ widget: resolveWidget(prop),
346
+ required: requiredKeys.includes(key),
347
+ readOnly: prop.readOnly || false,
348
+ default: prop.default !== undefined ? prop.default : null,
349
+ enum: prop.enum || null,
350
+ items: prop.items || null,
351
+ validation: {
352
+ minLength: prop.minLength,
353
+ maxLength: prop.maxLength,
354
+ minimum: prop.minimum,
355
+ maximum: prop.maximum,
356
+ pattern: prop.pattern,
357
+ },
358
+ order: typeof prop.order === 'number' ? prop.order : Infinity,
359
+ }
360
+
361
+ // Apply per-field overrides
362
+ if (overrides[key]) {
363
+ Object.assign(field, overrides[key])
364
+ }
365
+
366
+ return field
367
+ })
368
+ }
369
+
239
370
  /**
240
371
  * Generate faceted filter definitions from a schema's facetable properties.
241
372
  *
@@ -263,6 +394,7 @@ export function filtersFromSchema(schema) {
263
394
  const filter = {
264
395
  key,
265
396
  label: prop.title || key,
397
+ description: prop.description || '',
266
398
  propertyType: prop.type || 'string',
267
399
  options: [],
268
400
  value: null,
@@ -1,88 +0,0 @@
1
- <template>
2
- <div class="cn-detail-layout">
3
- <!-- Header -->
4
- <div class="cn-detail-layout__header">
5
- <NcButton @click="$emit('back')">
6
- <template #icon>
7
- <ArrowLeft :size="20" />
8
- </template>
9
- {{ backLabel }}
10
- </NcButton>
11
-
12
- <h2 class="cn-detail-layout__title">
13
- <slot name="title">{{ title }}</slot>
14
- </h2>
15
-
16
- <div class="cn-detail-layout__actions">
17
- <slot name="actions" />
18
- </div>
19
- </div>
20
-
21
- <!-- Loading state -->
22
- <div v-if="loading" class="cn-loading-container">
23
- <NcLoadingIcon :size="32" />
24
- </div>
25
-
26
- <!-- Main content -->
27
- <div v-else class="cn-detail-layout__content">
28
- <slot />
29
- </div>
30
-
31
- <!-- Delete confirmation dialog -->
32
- <slot name="dialogs" />
33
- </div>
34
- </template>
35
-
36
- <script>
37
- import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
38
- import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
39
-
40
- /**
41
- * CnDetailViewLayout — Detail page layout with back button, title, actions, and content.
42
- *
43
- * Provides the standard structure for detail/edit views: back navigation,
44
- * page title, action buttons, and a content area. Supports loading state
45
- * and a dialogs slot for modals.
46
- *
47
- * @example
48
- * <CnDetailViewLayout
49
- * title="Client: Acme Corp"
50
- * :loading="isLoading"
51
- * @back="goBack">
52
- * <template #actions>
53
- * <NcButton @click="edit">Edit</NcButton>
54
- * <NcButton type="error" @click="confirmDelete">Delete</NcButton>
55
- * </template>
56
- * <div class="cn-detail-grid">
57
- * <div class="cn-detail-item">...</div>
58
- * </div>
59
- * </CnDetailViewLayout>
60
- */
61
- export default {
62
- name: 'CnDetailViewLayout',
63
-
64
- components: {
65
- NcButton,
66
- NcLoadingIcon,
67
- ArrowLeft,
68
- },
69
-
70
- props: {
71
- /** Page title */
72
- title: {
73
- type: String,
74
- default: '',
75
- },
76
- /** Whether data is loading */
77
- loading: {
78
- type: Boolean,
79
- default: false,
80
- },
81
- /** Back button label */
82
- backLabel: {
83
- type: String,
84
- default: 'Back',
85
- },
86
- },
87
- }
88
- </script>
@@ -1 +0,0 @@
1
- export { default as CnDetailViewLayout } from './CnDetailViewLayout.vue'
@@ -1,78 +0,0 @@
1
- <template>
2
- <NcEmptyContent :name="title" :description="description">
3
- <template #icon>
4
- <slot name="icon">
5
- <component :is="icon" v-if="icon" :size="64" />
6
- </slot>
7
- </template>
8
- <template v-if="actionLabel" #action>
9
- <slot name="action">
10
- <NcButton :type="actionType" @click="$emit('action')">
11
- {{ actionLabel }}
12
- </NcButton>
13
- </slot>
14
- </template>
15
- </NcEmptyContent>
16
- </template>
17
-
18
- <script>
19
- import { NcEmptyContent, NcButton } from '@nextcloud/vue'
20
-
21
- /**
22
- * CnEmptyState — Consistent empty state display wrapping NcEmptyContent.
23
- *
24
- * Provides a unified empty state pattern with icon, title, description,
25
- * and optional action button. Used across all list views.
26
- *
27
- * @example
28
- * <CnEmptyState
29
- * title="No clients yet"
30
- * description="Create your first client to get started"
31
- * action-label="New Client"
32
- * @action="createClient" />
33
- *
34
- * @example
35
- * <!-- With custom icon -->
36
- * <CnEmptyState title="No results">
37
- * <template #icon>
38
- * <Magnify :size="64" />
39
- * </template>
40
- * </CnEmptyState>
41
- */
42
- export default {
43
- name: 'CnEmptyState',
44
-
45
- components: {
46
- NcEmptyContent,
47
- NcButton,
48
- },
49
-
50
- props: {
51
- /** Main title text */
52
- title: {
53
- type: String,
54
- required: true,
55
- },
56
- /** Description text below the title */
57
- description: {
58
- type: String,
59
- default: '',
60
- },
61
- /** Vue component for the icon (e.g., imported material design icon) */
62
- icon: {
63
- type: [Object, null],
64
- default: null,
65
- },
66
- /** Action button label. If empty, no button is shown. */
67
- actionLabel: {
68
- type: String,
69
- default: '',
70
- },
71
- /** NcButton type for the action button */
72
- actionType: {
73
- type: String,
74
- default: 'primary',
75
- },
76
- },
77
- }
78
- </script>
@@ -1 +0,0 @@
1
- export { default as CnEmptyState } from './CnEmptyState.vue'
@@ -1,80 +0,0 @@
1
- <template>
2
- <div class="cn-list-layout">
3
- <!-- Header -->
4
- <div class="cn-list-layout__header">
5
- <div class="cn-list-layout__title">
6
- <h2>{{ title }}</h2>
7
- <span v-if="totalItems > 0" class="cn-list-layout__count">({{ totalItems }})</span>
8
- </div>
9
- <div class="cn-list-layout__actions">
10
- <slot name="actions" />
11
- </div>
12
- </div>
13
-
14
- <!-- Filters slot -->
15
- <slot name="filters" />
16
-
17
- <!-- Loading state -->
18
- <div v-if="loading" class="cn-loading-container">
19
- <NcLoadingIcon :size="32" />
20
- </div>
21
-
22
- <!-- Main content (table area) -->
23
- <template v-else>
24
- <slot />
25
- </template>
26
-
27
- <!-- Pagination slot -->
28
- <slot name="pagination" />
29
- </div>
30
- </template>
31
-
32
- <script>
33
- import { NcLoadingIcon } from '@nextcloud/vue'
34
-
35
- /**
36
- * CnListViewLayout — Full list page layout wrapping header, filters, table, and pagination.
37
- *
38
- * Provides the standard page structure used by every list view: a header with
39
- * title + action buttons, a filter/search area, the main content (table), and pagination.
40
- *
41
- * @example
42
- * <CnListViewLayout title="Clients" :total-items="clients.length" :loading="isLoading">
43
- * <template #actions>
44
- * <NcButton type="primary" @click="createClient">New client</NcButton>
45
- * </template>
46
- * <template #filters>
47
- * <CnFilterBar ... />
48
- * </template>
49
- * <CnDataTable :columns="columns" :rows="clients" />
50
- * <template #pagination>
51
- * <CnPagination ... />
52
- * </template>
53
- * </CnListViewLayout>
54
- */
55
- export default {
56
- name: 'CnListViewLayout',
57
-
58
- components: {
59
- NcLoadingIcon,
60
- },
61
-
62
- props: {
63
- /** Page title */
64
- title: {
65
- type: String,
66
- required: true,
67
- },
68
- /** Total items count (shown next to title) */
69
- totalItems: {
70
- type: Number,
71
- default: 0,
72
- },
73
- /** Whether data is loading */
74
- loading: {
75
- type: Boolean,
76
- default: false,
77
- },
78
- },
79
- }
80
- </script>
@@ -1 +0,0 @@
1
- export { default as CnListViewLayout } from './CnListViewLayout.vue'
@@ -1,77 +0,0 @@
1
- <template>
2
- <div class="cn-view-mode-toggle" role="group" :aria-label="ariaLabel">
3
- <NcButton
4
- :type="value === 'cards' ? 'primary' : 'secondary'"
5
- :aria-pressed="String(value === 'cards')"
6
- @click="$emit('input', 'cards')">
7
- <template #icon>
8
- <ViewGrid :size="20" />
9
- </template>
10
- {{ cardsLabel }}
11
- </NcButton>
12
- <NcButton
13
- :type="value === 'table' ? 'primary' : 'secondary'"
14
- :aria-pressed="String(value === 'table')"
15
- @click="$emit('input', 'table')">
16
- <template #icon>
17
- <ViewList :size="20" />
18
- </template>
19
- {{ tableLabel }}
20
- </NcButton>
21
- </div>
22
- </template>
23
-
24
- <script>
25
- import { NcButton } from '@nextcloud/vue'
26
- import ViewGrid from 'vue-material-design-icons/ViewGrid.vue'
27
- import ViewList from 'vue-material-design-icons/ViewList.vue'
28
-
29
- /**
30
- * CnViewModeToggle — Cards/Table view mode toggle.
31
- *
32
- * @example
33
- * <CnViewModeToggle v-model="viewMode" />
34
- */
35
- export default {
36
- name: 'CnViewModeToggle',
37
-
38
- components: {
39
- NcButton,
40
- ViewGrid,
41
- ViewList,
42
- },
43
-
44
- props: {
45
- /** Current view mode: 'cards' or 'table' */
46
- value: {
47
- type: String,
48
- default: 'table',
49
- validator: (v) => ['cards', 'table'].includes(v),
50
- },
51
- /** Label for cards button */
52
- cardsLabel: {
53
- type: String,
54
- default: 'Cards',
55
- },
56
- /** Label for table button */
57
- tableLabel: {
58
- type: String,
59
- default: 'Table',
60
- },
61
- /** Aria label for the toggle group */
62
- ariaLabel: {
63
- type: String,
64
- default: 'View mode',
65
- },
66
- },
67
- }
68
- </script>
69
-
70
- <style scoped>
71
- .cn-view-mode-toggle {
72
- display: inline-flex;
73
- gap: 0;
74
- border-radius: var(--border-radius-pill, 20px);
75
- overflow: hidden;
76
- }
77
- </style>
@@ -1 +0,0 @@
1
- export { default as CnViewModeToggle } from './CnViewModeToggle.vue'