@conduction/nextcloud-vue 0.1.0-beta.3 → 0.1.0-beta.5
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 -226
- package/dist/nextcloud-vue.cjs +67614 -0
- package/dist/nextcloud-vue.cjs.js +58386 -6112
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.map +1 -0
- package/dist/nextcloud-vue.css +1819 -285
- package/dist/nextcloud-vue.esm.js +58342 -6088
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +82 -62
- package/src/components/CnActionsBar/CnActionsBar.vue +17 -7
- package/src/components/CnActionsBar/index.js +1 -1
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
- package/src/components/CnAdvancedFormDialog/index.js +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +1 -1
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/index.js +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
- package/src/components/CnChartWidget/index.js +1 -0
- package/src/components/CnConfigurationCard/index.js +1 -1
- package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -250
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
- package/src/components/CnDashboardGrid/index.js +1 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
- package/src/components/CnDashboardPage/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +1 -1
- package/src/components/CnDataTable/index.js +1 -1
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -170
- package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
- package/src/components/CnDetailCard/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +285 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +9 -1
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +302 -11
- package/src/components/CnIcon/index.js +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +71 -3
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +121 -102
- package/src/components/CnIndexSidebar/index.js +1 -1
- package/src/components/CnItemCard/CnItemCard.vue +132 -0
- package/src/components/CnItemCard/index.js +1 -0
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/index.js +1 -1
- 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 +413 -0
- package/src/components/CnNotesCard/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
- package/src/components/CnObjectCard/index.js +1 -1
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
- package/src/components/CnObjectSidebar/index.js +1 -0
- package/src/components/CnPageHeader/index.js +1 -1
- package/src/components/CnPagination/index.js +1 -1
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
- 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 +62 -8
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatusBadge/index.js +1 -1
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
- package/src/components/CnTabbedFormDialog/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 +211 -0
- package/src/components/CnWidgetWrapper/index.js +1 -0
- package/src/components/index.js +43 -29
- package/src/composables/index.js +4 -3
- package/src/composables/useDashboardView.js +240 -0
- package/src/composables/useDetailView.js +289 -132
- package/src/composables/useListView.js +363 -362
- package/src/composables/useSubResource.js +142 -142
- package/src/constants/metadata.js +30 -30
- package/src/css/CnSchemaFormDialog.css +546 -0
- package/src/css/__sample_nextcloud_tokens.css +110 -0
- package/src/css/actions-bar.css +48 -48
- package/src/css/badge.css +51 -51
- package/src/css/card.css +128 -128
- package/src/css/dashboard.css +70 -0
- package/src/css/detail-page.css +168 -0
- package/src/css/detail.css +68 -68
- package/src/css/index-page.css +44 -32
- package/src/css/index-sidebar.css +193 -187
- package/src/css/index.css +16 -12
- package/src/css/layout.css +90 -90
- package/src/css/page-header.css +33 -33
- package/src/css/pagination.css +72 -72
- package/src/css/table.css +142 -142
- package/src/css/timeline-stages.css +218 -0
- package/src/css/utilities.css +46 -46
- package/src/index.js +72 -53
- package/src/store/createSubResourcePlugin.js +135 -135
- package/src/store/index.js +3 -3
- package/src/store/plugins/auditTrails.js +17 -17
- package/src/store/plugins/files.js +250 -186
- package/src/store/plugins/index.js +7 -5
- package/src/store/plugins/lifecycle.js +180 -180
- package/src/store/plugins/relations.js +68 -68
- package/src/store/plugins/search.js +372 -0
- package/src/store/plugins/selection.js +104 -0
- package/src/store/useObjectStore.js +829 -686
- 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/headers.js +68 -50
- package/src/utils/id.js +13 -0
- package/src/utils/index.js +3 -3
- package/src/utils/schema.js +422 -419
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnUserActionMenu — Popover menu triggered by clicking a user name.
|
|
3
|
+
|
|
4
|
+
Shows avatar, display name, and contextual actions (message, chat, email, meeting)
|
|
5
|
+
based on which Nextcloud apps are installed (Talk, Mail, Calendar).
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<span class="cn-user-action-menu">
|
|
9
|
+
<span
|
|
10
|
+
ref="trigger"
|
|
11
|
+
class="cn-user-action-menu__trigger"
|
|
12
|
+
:class="{ 'cn-user-action-menu__trigger--interactive': interactive }"
|
|
13
|
+
role="button"
|
|
14
|
+
:tabindex="interactive ? 0 : -1"
|
|
15
|
+
:aria-haspopup="interactive ? 'menu' : undefined"
|
|
16
|
+
@click="interactive && openMenu()"
|
|
17
|
+
@keydown.enter.prevent="interactive && openMenu()"
|
|
18
|
+
@keydown.space.prevent="interactive && openMenu()">
|
|
19
|
+
<slot>{{ displayName }}</slot>
|
|
20
|
+
</span>
|
|
21
|
+
|
|
22
|
+
<NcPopover
|
|
23
|
+
v-if="interactive"
|
|
24
|
+
:shown.sync="isOpen"
|
|
25
|
+
:trigger="triggerElements"
|
|
26
|
+
placement="bottom-start"
|
|
27
|
+
@after-hide="onClose">
|
|
28
|
+
<div
|
|
29
|
+
class="cn-user-action-menu__popover"
|
|
30
|
+
role="menu"
|
|
31
|
+
:aria-label="'Actions for ' + displayName"
|
|
32
|
+
@keydown.escape.prevent="closeMenu">
|
|
33
|
+
<!-- User info header -->
|
|
34
|
+
<div class="cn-user-action-menu__header">
|
|
35
|
+
<NcAvatar
|
|
36
|
+
:user="userId"
|
|
37
|
+
:display-name="displayName"
|
|
38
|
+
:size="36"
|
|
39
|
+
:show-user-status="false" />
|
|
40
|
+
<div class="cn-user-action-menu__user-info">
|
|
41
|
+
<span class="cn-user-action-menu__display-name">{{ displayName }}</span>
|
|
42
|
+
<span v-if="userEmail" class="cn-user-action-menu__email">{{ userEmail }}</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Action buttons -->
|
|
47
|
+
<div class="cn-user-action-menu__actions">
|
|
48
|
+
<NcActionButton
|
|
49
|
+
v-if="hasTalk"
|
|
50
|
+
role="menuitem"
|
|
51
|
+
@click="sendMessage">
|
|
52
|
+
<template #icon>
|
|
53
|
+
<MessageTextOutline :size="20" />
|
|
54
|
+
</template>
|
|
55
|
+
{{ sendMessageLabel }}
|
|
56
|
+
</NcActionButton>
|
|
57
|
+
|
|
58
|
+
<NcActionButton
|
|
59
|
+
v-if="hasTalk"
|
|
60
|
+
role="menuitem"
|
|
61
|
+
@click="startChat">
|
|
62
|
+
<template #icon>
|
|
63
|
+
<ChatOutline :size="20" />
|
|
64
|
+
</template>
|
|
65
|
+
{{ startChatLabel }}
|
|
66
|
+
</NcActionButton>
|
|
67
|
+
|
|
68
|
+
<NcActionButton
|
|
69
|
+
v-if="showEmailAction"
|
|
70
|
+
role="menuitem"
|
|
71
|
+
@click="sendEmail">
|
|
72
|
+
<template #icon>
|
|
73
|
+
<EmailOutline :size="20" />
|
|
74
|
+
</template>
|
|
75
|
+
{{ sendEmailLabel }}
|
|
76
|
+
</NcActionButton>
|
|
77
|
+
|
|
78
|
+
<NcActionButton
|
|
79
|
+
v-if="hasCalendar"
|
|
80
|
+
role="menuitem"
|
|
81
|
+
@click="planMeeting">
|
|
82
|
+
<template #icon>
|
|
83
|
+
<CalendarOutline :size="20" />
|
|
84
|
+
</template>
|
|
85
|
+
{{ planMeetingLabel }}
|
|
86
|
+
</NcActionButton>
|
|
87
|
+
|
|
88
|
+
<div
|
|
89
|
+
v-if="!hasTalk && !showEmailAction && !hasCalendar"
|
|
90
|
+
class="cn-user-action-menu__no-actions">
|
|
91
|
+
{{ noActionsLabel }}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</NcPopover>
|
|
96
|
+
</span>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script>
|
|
100
|
+
import { NcPopover, NcActionButton, NcAvatar } from '@nextcloud/vue'
|
|
101
|
+
|
|
102
|
+
import MessageTextOutline from 'vue-material-design-icons/MessageTextOutline.vue'
|
|
103
|
+
import ChatOutline from 'vue-material-design-icons/ChatOutline.vue'
|
|
104
|
+
import EmailOutline from 'vue-material-design-icons/EmailOutline.vue'
|
|
105
|
+
import CalendarOutline from 'vue-material-design-icons/CalendarOutline.vue'
|
|
106
|
+
|
|
107
|
+
import { buildHeaders } from '../../utils/index.js'
|
|
108
|
+
|
|
109
|
+
// Module-level capabilities cache (shared across all instances, fetched once per session)
|
|
110
|
+
let _capabilitiesCache = null
|
|
111
|
+
let _capabilitiesPromise = null
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* CnUserActionMenu — Popover with user communication actions.
|
|
115
|
+
*
|
|
116
|
+
* Shows contextual actions based on installed Nextcloud apps (Talk, Mail, Calendar).
|
|
117
|
+
* Uses @nextcloud/capabilities when available, falls back to OCS API.
|
|
118
|
+
*
|
|
119
|
+
* @example Usage in notes/tasks cards
|
|
120
|
+
* <CnUserActionMenu
|
|
121
|
+
* :user-id="note.actorId || note.author"
|
|
122
|
+
* :display-name="note.actorDisplayName || note.author || 'Unknown'" />
|
|
123
|
+
*/
|
|
124
|
+
export default {
|
|
125
|
+
name: 'CnUserActionMenu',
|
|
126
|
+
|
|
127
|
+
components: {
|
|
128
|
+
NcPopover,
|
|
129
|
+
NcActionButton,
|
|
130
|
+
NcAvatar,
|
|
131
|
+
MessageTextOutline,
|
|
132
|
+
ChatOutline,
|
|
133
|
+
EmailOutline,
|
|
134
|
+
CalendarOutline,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
props: {
|
|
138
|
+
/** The Nextcloud user ID */
|
|
139
|
+
userId: {
|
|
140
|
+
type: String,
|
|
141
|
+
required: true,
|
|
142
|
+
},
|
|
143
|
+
/** The user's display name */
|
|
144
|
+
displayName: {
|
|
145
|
+
type: String,
|
|
146
|
+
default: 'Unknown',
|
|
147
|
+
},
|
|
148
|
+
/** Whether the menu is interactive (false for current user or system accounts) */
|
|
149
|
+
interactive: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
default: true,
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// --- Pre-translated labels ---
|
|
155
|
+
sendMessageLabel: { type: String, default: 'Send message' },
|
|
156
|
+
startChatLabel: { type: String, default: 'Start chat' },
|
|
157
|
+
sendEmailLabel: { type: String, default: 'Send email' },
|
|
158
|
+
planMeetingLabel: { type: String, default: 'Plan meeting' },
|
|
159
|
+
noActionsLabel: { type: String, default: 'No communication apps available' },
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
emits: ['action'],
|
|
163
|
+
|
|
164
|
+
data() {
|
|
165
|
+
return {
|
|
166
|
+
isOpen: false,
|
|
167
|
+
hasTalk: false,
|
|
168
|
+
hasMail: false,
|
|
169
|
+
hasCalendar: false,
|
|
170
|
+
userEmail: '',
|
|
171
|
+
emailResolved: false,
|
|
172
|
+
triggerElements: [],
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
computed: {
|
|
177
|
+
showEmailAction() {
|
|
178
|
+
return !!this.userEmail
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
mounted() {
|
|
183
|
+
this.triggerElements = [this.$refs.trigger]
|
|
184
|
+
this.detectCapabilities()
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
methods: {
|
|
188
|
+
openMenu() {
|
|
189
|
+
if (!this.interactive) return
|
|
190
|
+
this.isOpen = true
|
|
191
|
+
// Resolve email on first open if not yet done
|
|
192
|
+
if (!this.emailResolved) {
|
|
193
|
+
this.resolveUserEmail()
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
closeMenu() {
|
|
198
|
+
this.isOpen = false
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
onClose() {
|
|
202
|
+
this.isOpen = false
|
|
203
|
+
// Return focus to trigger
|
|
204
|
+
if (this.$refs.trigger) {
|
|
205
|
+
this.$refs.trigger.focus()
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
async detectCapabilities() {
|
|
210
|
+
if (_capabilitiesCache) {
|
|
211
|
+
this.applyCapabilities(_capabilitiesCache)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Try @nextcloud/capabilities first (synchronous, from initial state)
|
|
216
|
+
try {
|
|
217
|
+
// eslint-disable-next-line n/no-missing-import
|
|
218
|
+
const { getCapabilities } = await import('@nextcloud/capabilities')
|
|
219
|
+
const caps = getCapabilities()
|
|
220
|
+
if (caps) {
|
|
221
|
+
_capabilitiesCache = caps
|
|
222
|
+
this.applyCapabilities(caps)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Package not available, fall back to API
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Fallback: fetch from OCS API (once per session)
|
|
230
|
+
if (!_capabilitiesPromise) {
|
|
231
|
+
_capabilitiesPromise = this.fetchCapabilities()
|
|
232
|
+
}
|
|
233
|
+
const caps = await _capabilitiesPromise
|
|
234
|
+
if (caps) {
|
|
235
|
+
_capabilitiesCache = caps
|
|
236
|
+
this.applyCapabilities(caps)
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async fetchCapabilities() {
|
|
241
|
+
try {
|
|
242
|
+
const response = await fetch('/ocs/v2.php/cloud/capabilities?format=json', {
|
|
243
|
+
headers: buildHeaders(),
|
|
244
|
+
})
|
|
245
|
+
if (response.ok) {
|
|
246
|
+
const data = await response.json()
|
|
247
|
+
return data?.ocs?.data?.capabilities || {}
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error('CnUserActionMenu: Failed to fetch capabilities', err)
|
|
251
|
+
}
|
|
252
|
+
return {}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
applyCapabilities(caps) {
|
|
256
|
+
this.hasTalk = !!caps?.spreed
|
|
257
|
+
this.hasCalendar = !!caps?.dav
|
|
258
|
+
this.hasMail = !!caps?.mail
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
async resolveUserEmail() {
|
|
262
|
+
this.emailResolved = true
|
|
263
|
+
try {
|
|
264
|
+
const response = await fetch(
|
|
265
|
+
`/ocs/v2.php/cloud/users/${encodeURIComponent(this.userId)}?format=json`,
|
|
266
|
+
{
|
|
267
|
+
headers: {
|
|
268
|
+
...buildHeaders(),
|
|
269
|
+
'OCS-APIREQUEST': 'true',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
if (response.ok) {
|
|
274
|
+
const data = await response.json()
|
|
275
|
+
this.userEmail = data?.ocs?.data?.email || ''
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.error('CnUserActionMenu: Failed to resolve user email', err)
|
|
279
|
+
this.userEmail = ''
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
async sendMessage() {
|
|
284
|
+
try {
|
|
285
|
+
const response = await fetch('/ocs/v2.php/apps/spreed/api/v4/room', {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers: {
|
|
288
|
+
...buildHeaders(),
|
|
289
|
+
'OCS-APIREQUEST': 'true',
|
|
290
|
+
'Content-Type': 'application/json',
|
|
291
|
+
},
|
|
292
|
+
body: JSON.stringify({ roomType: 1, invite: this.userId }),
|
|
293
|
+
})
|
|
294
|
+
if (response.ok) {
|
|
295
|
+
const data = await response.json()
|
|
296
|
+
const token = data?.ocs?.data?.token
|
|
297
|
+
if (token) {
|
|
298
|
+
window.location.href = `/apps/spreed/#/call/${token}`
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
this.showActionError('Failed to create conversation')
|
|
302
|
+
}
|
|
303
|
+
} catch (err) {
|
|
304
|
+
console.error('CnUserActionMenu: Failed to send message', err)
|
|
305
|
+
this.showActionError('Failed to create conversation')
|
|
306
|
+
}
|
|
307
|
+
this.$emit('action', { type: 'message', userId: this.userId })
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
async startChat() {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch('/ocs/v2.php/apps/spreed/api/v4/room', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: {
|
|
315
|
+
...buildHeaders(),
|
|
316
|
+
'OCS-APIREQUEST': 'true',
|
|
317
|
+
'Content-Type': 'application/json',
|
|
318
|
+
},
|
|
319
|
+
body: JSON.stringify({ roomType: 1, invite: this.userId }),
|
|
320
|
+
})
|
|
321
|
+
if (response.ok) {
|
|
322
|
+
const data = await response.json()
|
|
323
|
+
const token = data?.ocs?.data?.token
|
|
324
|
+
if (token) {
|
|
325
|
+
window.open(`/apps/spreed/#/call/${token}`, '_blank')
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
this.showActionError('Failed to create conversation')
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
console.error('CnUserActionMenu: Failed to start chat', err)
|
|
332
|
+
this.showActionError('Failed to create conversation')
|
|
333
|
+
}
|
|
334
|
+
this.$emit('action', { type: 'chat', userId: this.userId })
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
sendEmail() {
|
|
338
|
+
if (!this.userEmail) return
|
|
339
|
+
if (this.hasMail) {
|
|
340
|
+
window.location.href = `/apps/mail/compose?to=${encodeURIComponent(this.userEmail)}`
|
|
341
|
+
} else {
|
|
342
|
+
window.location.href = `mailto:${this.userEmail}`
|
|
343
|
+
}
|
|
344
|
+
this.closeMenu()
|
|
345
|
+
this.$emit('action', { type: 'email', userId: this.userId })
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
planMeeting() {
|
|
349
|
+
window.location.href = `/apps/calendar/new?attendees=${encodeURIComponent(this.userId)}&title=Meeting`
|
|
350
|
+
this.closeMenu()
|
|
351
|
+
this.$emit('action', { type: 'meeting', userId: this.userId })
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
showActionError(message) {
|
|
355
|
+
try {
|
|
356
|
+
// eslint-disable-next-line n/no-missing-import
|
|
357
|
+
import('@nextcloud/dialogs').then(({ showError }) => {
|
|
358
|
+
showError(message)
|
|
359
|
+
})
|
|
360
|
+
} catch {
|
|
361
|
+
console.error(message)
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
</script>
|
|
367
|
+
|
|
368
|
+
<style scoped>
|
|
369
|
+
.cn-user-action-menu {
|
|
370
|
+
display: inline;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.cn-user-action-menu__trigger--interactive {
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
color: var(--color-primary-element);
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.cn-user-action-menu__trigger--interactive:hover {
|
|
380
|
+
text-decoration: underline;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.cn-user-action-menu__trigger--interactive:focus-visible {
|
|
384
|
+
outline: 2px solid var(--color-primary-element);
|
|
385
|
+
outline-offset: 2px;
|
|
386
|
+
border-radius: var(--border-radius);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.cn-user-action-menu__popover {
|
|
390
|
+
min-width: 220px;
|
|
391
|
+
padding: 8px 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.cn-user-action-menu__header {
|
|
395
|
+
display: flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
gap: 10px;
|
|
398
|
+
padding: 8px 12px 12px;
|
|
399
|
+
border-bottom: 1px solid var(--color-border);
|
|
400
|
+
margin-bottom: 4px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.cn-user-action-menu__user-info {
|
|
404
|
+
display: flex;
|
|
405
|
+
flex-direction: column;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.cn-user-action-menu__display-name {
|
|
410
|
+
font-weight: 600;
|
|
411
|
+
font-size: 14px;
|
|
412
|
+
white-space: nowrap;
|
|
413
|
+
overflow: hidden;
|
|
414
|
+
text-overflow: ellipsis;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.cn-user-action-menu__email {
|
|
418
|
+
font-size: 12px;
|
|
419
|
+
color: var(--color-text-maxcontrast);
|
|
420
|
+
white-space: nowrap;
|
|
421
|
+
overflow: hidden;
|
|
422
|
+
text-overflow: ellipsis;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.cn-user-action-menu__actions {
|
|
426
|
+
padding: 4px 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.cn-user-action-menu__no-actions {
|
|
430
|
+
padding: 12px 16px;
|
|
431
|
+
text-align: center;
|
|
432
|
+
color: var(--color-text-maxcontrast);
|
|
433
|
+
font-size: 13px;
|
|
434
|
+
}
|
|
435
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnUserActionMenu } from './CnUserActionMenu.vue'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as CnVersionInfoCard } from './CnVersionInfoCard.vue'
|
|
1
|
+
export { default as CnVersionInfoCard } from './CnVersionInfoCard.vue'
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnWidgetRenderer — Renders Nextcloud Dashboard API widgets (v1/v2).
|
|
3
|
+
|
|
4
|
+
Loads widget items from the Nextcloud widget API and displays them
|
|
5
|
+
using NcDashboardWidget. Supports auto-refresh for widgets that
|
|
6
|
+
define a reload interval.
|
|
7
|
+
-->
|
|
8
|
+
<template>
|
|
9
|
+
<div class="cn-widget-renderer">
|
|
10
|
+
<!-- API Widget V1 or V2 -->
|
|
11
|
+
<NcDashboardWidget
|
|
12
|
+
v-if="isApiWidget"
|
|
13
|
+
:items="widgetItems"
|
|
14
|
+
:show-more-url="widget.widgetUrl"
|
|
15
|
+
:loading="loading"
|
|
16
|
+
:item-menu="false"
|
|
17
|
+
:round-icons="widget.itemIconsRound">
|
|
18
|
+
<template #empty-content>
|
|
19
|
+
<NcEmptyContent
|
|
20
|
+
v-if="emptyMessage"
|
|
21
|
+
:description="emptyMessage">
|
|
22
|
+
<template #icon>
|
|
23
|
+
<span v-if="widget.iconClass" :class="widget.iconClass" />
|
|
24
|
+
</template>
|
|
25
|
+
</NcEmptyContent>
|
|
26
|
+
</template>
|
|
27
|
+
</NcDashboardWidget>
|
|
28
|
+
|
|
29
|
+
<!-- Loading state -->
|
|
30
|
+
<div v-else-if="loading" class="cn-widget-renderer__loading">
|
|
31
|
+
<NcLoadingIcon :size="32" />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Fallback for unknown widget types -->
|
|
35
|
+
<NcEmptyContent
|
|
36
|
+
v-else
|
|
37
|
+
:description="unavailableText">
|
|
38
|
+
<template #icon>
|
|
39
|
+
<AlertCircleOutline :size="48" />
|
|
40
|
+
</template>
|
|
41
|
+
</NcEmptyContent>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
import { NcDashboardWidget, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
47
|
+
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
48
|
+
import axios from '@nextcloud/axios'
|
|
49
|
+
import { generateOcsUrl } from '@nextcloud/router'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* CnWidgetRenderer — Renders Nextcloud Dashboard API widgets.
|
|
53
|
+
*
|
|
54
|
+
* Fetches widget items from the OCS Dashboard API and displays them
|
|
55
|
+
* using the standard NcDashboardWidget component.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* <CnWidgetRenderer :widget="ncWidget" />
|
|
59
|
+
*/
|
|
60
|
+
export default {
|
|
61
|
+
name: 'CnWidgetRenderer',
|
|
62
|
+
|
|
63
|
+
components: {
|
|
64
|
+
NcDashboardWidget,
|
|
65
|
+
NcEmptyContent,
|
|
66
|
+
NcLoadingIcon,
|
|
67
|
+
AlertCircleOutline,
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
props: {
|
|
71
|
+
/**
|
|
72
|
+
* Nextcloud widget object from the Dashboard API.
|
|
73
|
+
* @type {{ id: string, title: string, iconClass?: string, iconUrl?: string, widgetUrl?: string, itemIconsRound?: boolean, itemApiVersions?: number[], reloadInterval?: number, buttons?: Array }}
|
|
74
|
+
*/
|
|
75
|
+
widget: {
|
|
76
|
+
type: Object,
|
|
77
|
+
required: true,
|
|
78
|
+
},
|
|
79
|
+
/** Text shown when widget is not available */
|
|
80
|
+
unavailableText: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: 'Widget not available',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
data() {
|
|
87
|
+
return {
|
|
88
|
+
loading: false,
|
|
89
|
+
items: [],
|
|
90
|
+
emptyMessage: '',
|
|
91
|
+
refreshInterval: null,
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
computed: {
|
|
96
|
+
isApiWidgetV2() {
|
|
97
|
+
return this.widget?.itemApiVersions?.includes(2)
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
isApiWidgetV1() {
|
|
101
|
+
return this.widget?.itemApiVersions?.includes(1)
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
isApiWidget() {
|
|
105
|
+
return this.isApiWidgetV1 || this.isApiWidgetV2
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
widgetItems() {
|
|
109
|
+
return this.items.map(item => ({
|
|
110
|
+
id: item.sinceId || item.id || String(Math.random()),
|
|
111
|
+
targetUrl: item.link || item.targetUrl || '',
|
|
112
|
+
avatarUrl: item.iconUrl || item.avatarUrl || '',
|
|
113
|
+
avatarUsername: item.avatarUsername || '',
|
|
114
|
+
overlayIconUrl: item.overlayIconUrl || '',
|
|
115
|
+
mainText: item.title || item.mainText || '',
|
|
116
|
+
subText: item.subtitle || item.subText || '',
|
|
117
|
+
}))
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async mounted() {
|
|
122
|
+
if (this.isApiWidget) {
|
|
123
|
+
await this.loadItems()
|
|
124
|
+
|
|
125
|
+
if (this.widget.reloadInterval && this.widget.reloadInterval > 0) {
|
|
126
|
+
this.refreshInterval = setInterval(
|
|
127
|
+
() => this.loadItems(),
|
|
128
|
+
this.widget.reloadInterval * 1000,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
beforeDestroy() {
|
|
135
|
+
if (this.refreshInterval) {
|
|
136
|
+
clearInterval(this.refreshInterval)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
methods: {
|
|
141
|
+
async loadItems() {
|
|
142
|
+
this.loading = true
|
|
143
|
+
try {
|
|
144
|
+
const version = this.isApiWidgetV2 ? 2 : 1
|
|
145
|
+
const url = generateOcsUrl(
|
|
146
|
+
`/apps/dashboard/api/v${version}/widget-items`,
|
|
147
|
+
)
|
|
148
|
+
const response = await axios.get(url, {
|
|
149
|
+
params: { widgets: [this.widget.id] },
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const data = response.data?.ocs?.data
|
|
153
|
+
if (data && data[this.widget.id]) {
|
|
154
|
+
const widgetData = data[this.widget.id]
|
|
155
|
+
this.items = widgetData.items || widgetData || []
|
|
156
|
+
this.emptyMessage = widgetData.emptyContentMessage || ''
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`[CnWidgetRenderer] Failed to load items for ${this.widget.id}:`, error)
|
|
160
|
+
} finally {
|
|
161
|
+
this.loading = false
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<style scoped>
|
|
169
|
+
.cn-widget-renderer {
|
|
170
|
+
height: 100%;
|
|
171
|
+
padding: 8px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.cn-widget-renderer__loading {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
height: 100%;
|
|
179
|
+
}
|
|
180
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnWidgetRenderer } from './CnWidgetRenderer.vue'
|