@conduction/nextcloud-vue 0.1.0-beta.14 → 0.1.0-beta.16

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 (46) hide show
  1. package/dist/nextcloud-vue.cjs.js +7282 -3443
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +719 -100
  4. package/dist/nextcloud-vue.esm.js +7120 -3300
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +3 -2
  7. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +36 -3
  8. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +34 -19
  9. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +312 -36
  10. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +983 -64
  11. package/src/components/CnAdvancedFormDialog/index.js +3 -0
  12. package/src/components/CnAppLoading/CnAppLoading.vue +93 -0
  13. package/src/components/CnAppLoading/index.js +3 -0
  14. package/src/components/CnAppNav/CnAppNav.vue +269 -0
  15. package/src/components/CnAppNav/index.js +3 -0
  16. package/src/components/CnAppRoot/CnAppRoot.vue +201 -0
  17. package/src/components/CnAppRoot/index.js +3 -0
  18. package/src/components/CnColorPicker/CnColorPicker.vue +251 -0
  19. package/src/components/CnColorPicker/index.js +1 -0
  20. package/src/components/CnContextMenu/CnContextMenu.vue +41 -4
  21. package/src/components/CnDashboardPage/CnDashboardPage.vue +8 -0
  22. package/src/components/CnDependencyMissing/CnDependencyMissing.vue +152 -0
  23. package/src/components/CnDependencyMissing/index.js +3 -0
  24. package/src/components/CnDetailPage/CnDetailPage.vue +27 -16
  25. package/src/components/CnIndexPage/CnIndexPage.vue +36 -6
  26. package/src/components/CnPageRenderer/CnPageRenderer.vue +278 -0
  27. package/src/components/CnPageRenderer/index.js +4 -0
  28. package/src/components/CnPageRenderer/pageTypes.js +37 -0
  29. package/src/components/CnRowActions/CnRowActions.vue +44 -3
  30. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +4 -0
  31. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +103 -74
  32. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +30 -2
  33. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +16 -12
  34. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +9 -4
  35. package/src/components/index.js +7 -1
  36. package/src/composables/index.js +2 -0
  37. package/src/composables/useAppManifest.js +115 -0
  38. package/src/composables/useAppStatus.js +107 -0
  39. package/src/css/CnSchemaFormDialog.css +22 -0
  40. package/src/index.js +24 -2
  41. package/src/schemas/app-manifest.schema.json +153 -0
  42. package/src/types/index.d.ts +9 -0
  43. package/src/types/manifest.d.ts +88 -0
  44. package/src/utils/index.js +1 -1
  45. package/src/utils/schema.js +157 -2
  46. package/src/utils/validateManifest.js +113 -0
@@ -1 +1,4 @@
1
1
  export { default as CnAdvancedFormDialog } from './CnAdvancedFormDialog.vue'
2
+ export { default as CnPropertiesTab } from './CnPropertiesTab.vue'
3
+ export { default as CnMetadataTab } from './CnMetadataTab.vue'
4
+ export { default as CnPropertyValueCell } from './CnPropertyValueCell.vue'
@@ -0,0 +1,93 @@
1
+ <!--
2
+ CnAppLoading — full-page loading screen used during the loading phase
3
+ of CnAppRoot (while useAppManifest is fetching).
4
+
5
+ Apps can override CnAppRoot's #loading slot to swap branding /
6
+ messaging. The default rendering shows an optional logo, the
7
+ Nextcloud loading spinner, and a message.
8
+
9
+ See REQ-JMR-010 of the json-manifest-renderer specification.
10
+ -->
11
+ <template>
12
+ <div class="cn-app-loading">
13
+ <div class="cn-app-loading__inner">
14
+ <slot name="logo">
15
+ <img
16
+ v-if="logoUrl"
17
+ :src="logoUrl"
18
+ alt=""
19
+ class="cn-app-loading__logo">
20
+ </slot>
21
+ <NcLoadingIcon :size="48" />
22
+ <p class="cn-app-loading__message">
23
+ {{ message }}
24
+ </p>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script>
30
+ import { NcLoadingIcon } from '@nextcloud/vue'
31
+
32
+ export default {
33
+ name: 'CnAppLoading',
34
+
35
+ components: {
36
+ NcLoadingIcon,
37
+ },
38
+
39
+ props: {
40
+ /**
41
+ * Loading message displayed below the spinner. Plain string —
42
+ * the consuming app pre-translates if needed (this library
43
+ * never imports `t()` from a specific app).
44
+ *
45
+ * @type {string}
46
+ */
47
+ message: {
48
+ type: String,
49
+ default: 'Loading...',
50
+ },
51
+ /**
52
+ * Optional logo image URL displayed above the spinner. Apps
53
+ * with custom branding can also override the `#logo` slot.
54
+ *
55
+ * @type {string}
56
+ */
57
+ logoUrl: {
58
+ type: String,
59
+ default: '',
60
+ },
61
+ },
62
+ }
63
+ </script>
64
+
65
+ <style>
66
+ .cn-app-loading {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ width: 100%;
71
+ min-height: 100vh;
72
+ background: var(--color-main-background);
73
+ color: var(--color-main-text);
74
+ }
75
+
76
+ .cn-app-loading__inner {
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: center;
80
+ gap: calc(2 * var(--default-grid-baseline));
81
+ }
82
+
83
+ .cn-app-loading__logo {
84
+ max-width: 96px;
85
+ max-height: 96px;
86
+ }
87
+
88
+ .cn-app-loading__message {
89
+ margin: 0;
90
+ color: var(--color-text-maxcontrast);
91
+ font-size: var(--default-font-size);
92
+ }
93
+ </style>
@@ -0,0 +1,3 @@
1
+ import CnAppLoading from './CnAppLoading.vue'
2
+ export default CnAppLoading
3
+ export { CnAppLoading }
@@ -0,0 +1,269 @@
1
+ <!--
2
+ CnAppNav — manifest-driven app navigation.
3
+
4
+ Renders the manifest's `menu[]` array as a Nextcloud app navigation
5
+ (NcAppNavigation + NcAppNavigationItem). One level of nested
6
+ `children[]` is supported. Items are sorted by `order`; items without
7
+ an order render last. Items with a `permission` are filtered against
8
+ the `permissions` prop — when the prop is omitted, all items render.
9
+
10
+ Items split into two groups by `section`:
11
+ - `section: "main"` (default) — top of the navigation, scrollable.
12
+ - `section: "settings"` — pinned to the bottom inside NcAppNavigation's
13
+ `#footer` slot, always visible above the close-toggle, separated
14
+ from the main list by a thin border. Use for documentation links,
15
+ settings entries, or anything that should sit at the bottom.
16
+
17
+ Manifest and translate are injected from CnAppRoot by default but can
18
+ also be passed as props for standalone use without CnAppRoot. Props
19
+ win over inject when both are present.
20
+
21
+ See REQ-JMR-004 of the json-manifest-renderer specification.
22
+ -->
23
+ <template>
24
+ <NcAppNavigation>
25
+ <template #list>
26
+ <NcAppNavigationItem
27
+ v-for="item in mainItems"
28
+ :key="item.id"
29
+ :name="resolveLabel(item)"
30
+ :to="itemTo(item)"
31
+ :exact="isExact(item)"
32
+ :icon="item.icon"
33
+ :active="isActive(item)"
34
+ @click="onItemClick(item, $event)">
35
+ <NcAppNavigationItem
36
+ v-for="child in visibleChildren(item)"
37
+ :key="child.id"
38
+ :name="resolveLabel(child)"
39
+ :to="itemTo(child)"
40
+ :exact="isExact(child)"
41
+ :icon="child.icon"
42
+ :active="isActive(child)"
43
+ @click="onItemClick(child, $event)" />
44
+ </NcAppNavigationItem>
45
+ </template>
46
+ <template v-if="settingsItems.length" #footer>
47
+ <ul class="cn-app-nav__footer-list">
48
+ <NcAppNavigationItem
49
+ v-for="item in settingsItems"
50
+ :key="item.id"
51
+ :name="resolveLabel(item)"
52
+ :to="itemTo(item)"
53
+ :exact="isExact(item)"
54
+ :icon="item.icon"
55
+ :active="isActive(item)"
56
+ @click="onItemClick(item, $event)" />
57
+ </ul>
58
+ </template>
59
+ </NcAppNavigation>
60
+ </template>
61
+
62
+ <script>
63
+ import { NcAppNavigation, NcAppNavigationItem } from '@nextcloud/vue'
64
+
65
+ export default {
66
+ name: 'CnAppNav',
67
+
68
+ components: {
69
+ NcAppNavigation,
70
+ NcAppNavigationItem,
71
+ },
72
+
73
+ inject: {
74
+ cnManifest: { default: null },
75
+ cnTranslate: { default: () => (key) => key },
76
+ },
77
+
78
+ props: {
79
+ /**
80
+ * Manifest object. Falls back to injected `cnManifest`. Provide
81
+ * explicitly when mounting CnAppNav outside of CnAppRoot.
82
+ *
83
+ * @type {object|null}
84
+ */
85
+ manifest: {
86
+ type: Object,
87
+ default: null,
88
+ },
89
+ /**
90
+ * Translate function. Falls back to injected `cnTranslate`,
91
+ * which itself defaults to an identity function.
92
+ *
93
+ * @type {Function|null}
94
+ */
95
+ translate: {
96
+ type: Function,
97
+ default: null,
98
+ },
99
+ /**
100
+ * List of permission strings the current user holds. Items
101
+ * declaring a `permission` only render when their permission
102
+ * appears in this list. When the prop is omitted (or empty),
103
+ * all items are visible regardless of their permission field.
104
+ *
105
+ * @type {Array<string>}
106
+ */
107
+ permissions: {
108
+ type: Array,
109
+ default: () => [],
110
+ },
111
+ },
112
+
113
+ computed: {
114
+ effectiveManifest() {
115
+ return this.manifest ?? this.cnManifest
116
+ },
117
+ effectiveTranslate() {
118
+ return this.translate ?? this.cnTranslate
119
+ },
120
+ /**
121
+ * All visible items (filtered by permission, sorted by order).
122
+ * Retained for backwards-compat with the previous public API and
123
+ * tests that read this computed; new code should use
124
+ * `mainItems` / `settingsItems` instead.
125
+ */
126
+ visibleItems() {
127
+ const items = this.effectiveManifest?.menu ?? []
128
+ return items
129
+ .filter((item) => this.passesPermission(item))
130
+ .slice()
131
+ .sort((a, b) => {
132
+ const aHas = typeof a.order === 'number'
133
+ const bHas = typeof b.order === 'number'
134
+ if (aHas && !bHas) return -1
135
+ if (!aHas && bHas) return 1
136
+ if (!aHas && !bHas) return 0
137
+ return a.order - b.order
138
+ })
139
+ },
140
+ /** Items that render in the top list (default placement). */
141
+ mainItems() {
142
+ return this.visibleItems.filter((item) => (item.section ?? 'main') === 'main')
143
+ },
144
+ /**
145
+ * Items that render inside the `#footer` slot of NcAppNavigation
146
+ * — always visible at the bottom of the navigation, above the
147
+ * close-toggle. Use for help / docs / settings entries that
148
+ * should anchor to the bottom rather than scroll with the main
149
+ * list.
150
+ */
151
+ settingsItems() {
152
+ return this.visibleItems.filter((item) => item.section === 'settings')
153
+ },
154
+ },
155
+
156
+ methods: {
157
+ passesPermission(item) {
158
+ if (!item.permission) return true
159
+ if (!this.permissions || this.permissions.length === 0) return true
160
+ return this.permissions.includes(item.permission)
161
+ },
162
+ visibleChildren(item) {
163
+ if (!Array.isArray(item.children)) return []
164
+ return item.children.filter((c) => this.passesPermission(c))
165
+ },
166
+ resolveLabel(item) {
167
+ return this.effectiveTranslate(item.label)
168
+ },
169
+ isActive(item) {
170
+ if (item.href || !item.route) return false
171
+ return this.$route?.name === item.route
172
+ },
173
+ /**
174
+ * Look up an item's resolved page (`pages[]` entry whose `id`
175
+ * matches the menu item's `route`) — used to decide whether the
176
+ * NcAppNavigationItem should match its router-link `exact`.
177
+ *
178
+ * @param {object} item Menu item to resolve.
179
+ * @return {object|null} Matching page entry, or null when the item
180
+ * has no `route` or no page matches.
181
+ */
182
+ pageForItem(item) {
183
+ if (!item.route) return null
184
+ const pages = this.effectiveManifest?.pages ?? []
185
+ return pages.find((p) => p.id === item.route) ?? null
186
+ },
187
+ /**
188
+ * Pass-through for `NcAppNavigationItem`'s router-link `exact`.
189
+ * Root paths (`/`) match every nested route by default, which
190
+ * makes the root item permanently look active. Returning true
191
+ * for `route === '/'` restores the expected behaviour.
192
+ *
193
+ * @param {object} item Menu item being rendered.
194
+ * @return {boolean} Whether to enable exact router-link matching.
195
+ */
196
+ isExact(item) {
197
+ const page = this.pageForItem(item)
198
+ return page?.route === '/'
199
+ },
200
+ /**
201
+ * Build the `:to` value for an `NcAppNavigationItem`. External
202
+ * (`href`) items return `null` so the underlying anchor falls
203
+ * through to a click handler instead of vue-router; route items
204
+ * return a named route.
205
+ *
206
+ * @param {object} item Menu item being rendered.
207
+ * @return {object|null} A `{ name }` route object, or null for
208
+ * external / route-less items.
209
+ */
210
+ itemTo(item) {
211
+ if (item.href) return null
212
+ return item.route ? { name: item.route } : null
213
+ },
214
+ /**
215
+ * Click handler. For external (`href`) items, opens the URL in a
216
+ * new tab with safe rel attributes. Route items are handled by
217
+ * `:to` and skip this path.
218
+ *
219
+ * @param {object} item Menu item being clicked.
220
+ * @param {Event} [event] Native click event (used to call
221
+ * preventDefault for external links).
222
+ */
223
+ onItemClick(item, event) {
224
+ if (!item.href) return
225
+ if (event && typeof event.preventDefault === 'function') {
226
+ event.preventDefault()
227
+ }
228
+ window.open(item.href, '_blank', 'noopener,noreferrer')
229
+ },
230
+ },
231
+ }
232
+ </script>
233
+
234
+ <style>
235
+ /*
236
+ * The legacy `icon-*` classes in Nextcloud render a background-image
237
+ * with a hardcoded dark fill, so they stay grey when an entry becomes
238
+ * active (text turns white against the primary-element background).
239
+ * Force the icon to white in the active state to match the label.
240
+ * Only applies when the icon is provided via the `icon` prop (CSS
241
+ * class) — items using a `<template #icon>` MDI component already
242
+ * inherit `currentColor`.
243
+ */
244
+ .app-navigation-entry.active .app-navigation-entry-icon[class*="icon-"] {
245
+ filter: brightness(0) invert(1);
246
+ }
247
+
248
+ /*
249
+ * Footer-list (section: "settings" items rendered in NcAppNavigation's
250
+ * `#footer` slot). Reset list defaults so the entries align with the
251
+ * main list, and add a thin separator above the group so it visually
252
+ * detaches from the scrollable list when the two meet.
253
+ *
254
+ * NcAppNavigation's scoped style targets `.app-navigation__content >
255
+ * ul` with `overflow-y: auto` + flex padding, and Vue 2 propagates the
256
+ * parent's data-v attribute onto slot-root elements — so without these
257
+ * overrides the footer list renders its own scrollbar even though the
258
+ * footer area has plenty of room. The `!important` flags force-beat
259
+ * the parent rule's specificity (which includes the data-v attribute).
260
+ */
261
+ .cn-app-nav__footer-list {
262
+ list-style: none;
263
+ margin: 0;
264
+ padding: 0 !important;
265
+ border-top: 1px solid var(--color-border);
266
+ overflow: visible !important;
267
+ flex: 0 0 auto;
268
+ }
269
+ </style>
@@ -0,0 +1,3 @@
1
+ import CnAppNav from './CnAppNav.vue'
2
+ export default CnAppNav
3
+ export { CnAppNav }
@@ -0,0 +1,201 @@
1
+ <!--
2
+ CnAppRoot — top-level wrapper for manifest-driven Conduction apps.
3
+
4
+ Provides the manifest, custom-component registry, and translate
5
+ function to descendants via Vue's `provide`/`inject`. Orchestrates
6
+ three rendering phases:
7
+
8
+ 1. Loading — while useAppManifest.isLoading is true.
9
+ Default: <CnAppLoading />. Override: #loading slot.
10
+ 2. Dependency — after loading; when any entry in
11
+ manifest.dependencies is not installed/enabled.
12
+ Default: <CnDependencyMissing />. Override:
13
+ #dependency-missing slot.
14
+ 3. Shell — manifest loaded + dependencies satisfied.
15
+ Renders #menu (default <CnAppNav />) + the
16
+ consuming app's <router-view>, plus optional
17
+ #header-actions, #sidebar, and #footer slots.
18
+
19
+ Consuming apps that want manifest-driven pages but their own root
20
+ layout can skip CnAppRoot entirely and use CnPageRenderer / CnAppNav
21
+ with explicit props (the props-vs-inject fallback). CnAppRoot is the
22
+ full-shell convenience.
23
+
24
+ See REQ-JMR-003 and REQ-JMR-013 of the json-manifest-renderer spec.
25
+ -->
26
+ <template>
27
+ <NcContent :app-name="appId">
28
+ <!-- Loading phase -->
29
+ <template v-if="phase === 'loading'">
30
+ <slot name="loading">
31
+ <CnAppLoading />
32
+ </slot>
33
+ </template>
34
+
35
+ <!-- Dependency-check phase -->
36
+ <template v-else-if="phase === 'dependency-missing'">
37
+ <slot name="dependency-missing" :dependencies="unresolvedDependencies">
38
+ <CnDependencyMissing
39
+ :dependencies="unresolvedDependencies"
40
+ :app-name="appId" />
41
+ </slot>
42
+ </template>
43
+
44
+ <!-- Shell phase -->
45
+ <template v-else>
46
+ <slot name="menu">
47
+ <CnAppNav :permissions="permissions" />
48
+ </slot>
49
+ <NcAppContent>
50
+ <router-view />
51
+ <slot name="header-actions" />
52
+ <slot name="footer" />
53
+ </NcAppContent>
54
+ <slot name="sidebar" />
55
+ </template>
56
+ </NcContent>
57
+ </template>
58
+
59
+ <script>
60
+ import { NcAppContent, NcContent } from '@nextcloud/vue'
61
+ import CnAppNav from '../CnAppNav/CnAppNav.vue'
62
+ import CnAppLoading from '../CnAppLoading/CnAppLoading.vue'
63
+ import CnDependencyMissing from '../CnDependencyMissing/CnDependencyMissing.vue'
64
+ import { useAppStatus } from '../../composables/useAppStatus.js'
65
+
66
+ export default {
67
+ name: 'CnAppRoot',
68
+
69
+ components: {
70
+ NcAppContent,
71
+ NcContent,
72
+ CnAppNav,
73
+ CnAppLoading,
74
+ CnDependencyMissing,
75
+ },
76
+
77
+ provide() {
78
+ return {
79
+ cnManifest: this.manifest,
80
+ cnCustomComponents: this.customComponents,
81
+ cnTranslate: this.translate,
82
+ cnPageTypes: this.pageTypes,
83
+ }
84
+ },
85
+
86
+ props: {
87
+ /**
88
+ * Reactive manifest object (from useAppManifest). The renderer
89
+ * reads `manifest.dependencies`, `manifest.menu`, and is
90
+ * propagated to descendants via provide/inject.
91
+ *
92
+ * @type {object}
93
+ */
94
+ manifest: {
95
+ type: Object,
96
+ required: true,
97
+ },
98
+ /**
99
+ * Nextcloud app id. Forwarded to NcContent as `app-name` and
100
+ * to CnDependencyMissing for the heading.
101
+ *
102
+ * @type {string}
103
+ */
104
+ appId: {
105
+ type: String,
106
+ required: true,
107
+ },
108
+ /**
109
+ * Whether the manifest is still loading from the backend.
110
+ * Typically wired to `useAppManifest().isLoading`. Defaults to
111
+ * false so that apps using only the bundled manifest skip the
112
+ * loading phase.
113
+ *
114
+ * @type {boolean}
115
+ */
116
+ isLoading: {
117
+ type: Boolean,
118
+ default: false,
119
+ },
120
+ /**
121
+ * Custom-component registry consumed by CnPageRenderer for
122
+ * `type: "custom"` pages and slot overrides. Empty by default.
123
+ *
124
+ * @type {object}
125
+ */
126
+ customComponents: {
127
+ type: Object,
128
+ default: () => ({}),
129
+ },
130
+ /**
131
+ * Translate function provided by the consuming app. The library
132
+ * never imports `t()` from a specific app, so the consumer
133
+ * passes its own translator. Typically a closure over the
134
+ * Nextcloud `t()` mixin pre-bound to the app id, e.g.
135
+ * `(key) => t(appId, key)`.
136
+ *
137
+ * Defaults to an identity function so untranslated keys surface
138
+ * visibly rather than crashing.
139
+ *
140
+ * Note: the prop is named `translate` (not `t`) to avoid
141
+ * shadowing the global `t()` method that Conduction apps
142
+ * install via `Vue.mixin({ methods: { t, n } })`. The provide
143
+ * key is `cnTranslate`.
144
+ *
145
+ * @type {Function}
146
+ */
147
+ translate: {
148
+ type: Function,
149
+ default: (key) => key,
150
+ },
151
+ /**
152
+ * List of permission strings the current user holds. Forwarded
153
+ * to CnAppNav's permission filter.
154
+ *
155
+ * @type {Array<string>}
156
+ */
157
+ permissions: {
158
+ type: Array,
159
+ default: () => [],
160
+ },
161
+ /**
162
+ * Page-type registry. Map of `pages[].type` → Vue component.
163
+ * Provided to descendant CnPageRenderer instances via inject.
164
+ * When omitted, the renderer falls back to the library's
165
+ * `defaultPageTypes`. Apps with custom page types pass a merged
166
+ * map: `{ ...defaultPageTypes, report: MyReportPage }`.
167
+ *
168
+ * @type {object|null}
169
+ */
170
+ pageTypes: {
171
+ type: Object,
172
+ default: null,
173
+ },
174
+ },
175
+
176
+ computed: {
177
+ /**
178
+ * Per-dependency status, computed once per `appId` declared in
179
+ * `manifest.dependencies`. Reading the value here triggers the
180
+ * useAppStatus composable for each id; results are cached
181
+ * module-side so subsequent reads are free.
182
+ */
183
+ dependencyStatuses() {
184
+ const deps = Array.isArray(this.manifest?.dependencies)
185
+ ? this.manifest.dependencies
186
+ : []
187
+ return deps.map((id) => ({ id, status: useAppStatus(id) }))
188
+ },
189
+ unresolvedDependencies() {
190
+ return this.dependencyStatuses
191
+ .filter(({ status }) => !status.installed.value || !status.enabled.value)
192
+ .map(({ id }) => ({ id, name: id, enabled: false }))
193
+ },
194
+ phase() {
195
+ if (this.isLoading) return 'loading'
196
+ if (this.unresolvedDependencies.length > 0) return 'dependency-missing'
197
+ return 'shell'
198
+ },
199
+ },
200
+ }
201
+ </script>
@@ -0,0 +1,3 @@
1
+ import CnAppRoot from './CnAppRoot.vue'
2
+ export default CnAppRoot
3
+ export { CnAppRoot }