@conduction/nextcloud-vue 2.101.0 → 2.103.0
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/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +73 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +99 -64
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +46 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +37 -2
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +49 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +113 -20
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +387 -359
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +84 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +8 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +26 -2
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +10 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +97 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +46 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +9 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js +165 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js +85 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -0
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +54 -6
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +3 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +8 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/pageViews.js +8 -1
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +593 -459
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +32 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +10093 -8858
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +593 -459
- package/l10n/en.json +5 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
- package/src/components/CnConversationThread/CnConversationThread.md +17 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +113 -8
- package/src/components/CnConversationThread/CnConversationWidget.vue +60 -1
- package/src/components/CnDashboardPage/CnDashboardPage.md +4 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +50 -7
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +127 -0
- package/src/components/CnIndexPage/CnIndexPage.md +15 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +92 -9
- package/src/components/CnIndexPage/useNamedSource.js +26 -2
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +10 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +134 -2
- package/src/components/CnSearchSelect/CnSearchSelect.vue +283 -0
- package/src/components/CnSearchSelect/index.js +12 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +56 -6
- package/src/components/CnTabsWidget/CnTabsWidget.vue +8 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +5 -3
- package/src/css/look-board-index.css +10 -0
- package/src/index.js +1 -0
- package/src/mixins/pageViews.js +8 -1
- package/src/schemas/app-manifest-v2.schema.json +107 -8
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +32 -4
|
@@ -2,7 +2,7 @@ import script from './CnConversationThread.vue2.js';
|
|
|
2
2
|
import { render } from './CnConversationThread.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-ac0e8d2a";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios__default from '@nextcloud/axios';
|
|
2
2
|
import { translate } from '@nextcloud/l10n';
|
|
3
3
|
import { NcButton } from '@nextcloud/vue';
|
|
4
|
+
import __sfc_main from '../CnQuickFilterBar/CnQuickFilterBar.vue.js';
|
|
4
5
|
import { prefixUrl } from '../../utils/headers.js';
|
|
5
6
|
import { userLocale } from '../../utils/userLocale.js';
|
|
6
7
|
|
|
@@ -34,6 +35,7 @@ var script = {
|
|
|
34
35
|
name: 'CnConversationThread',
|
|
35
36
|
|
|
36
37
|
components: {
|
|
38
|
+
CnQuickFilterBar: __sfc_main,
|
|
37
39
|
NcButton,
|
|
38
40
|
},
|
|
39
41
|
|
|
@@ -112,6 +114,24 @@ var script = {
|
|
|
112
114
|
default: false,
|
|
113
115
|
},
|
|
114
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Draw channel chips above the messages: All, then one per channel
|
|
119
|
+
* with its count. Choosing one shows only that channel's messages.
|
|
120
|
+
*/
|
|
121
|
+
channelFilter: {
|
|
122
|
+
type: Boolean,
|
|
123
|
+
default: false,
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Channel labels that always get a chip, in this order, also when no
|
|
128
|
+
* message uses them yet. Channels the messages use follow.
|
|
129
|
+
*/
|
|
130
|
+
channels: {
|
|
131
|
+
type: Array,
|
|
132
|
+
default: () => [],
|
|
133
|
+
},
|
|
134
|
+
|
|
115
135
|
/** Shown when there are no messages yet. */
|
|
116
136
|
emptyText: {
|
|
117
137
|
type: String,
|
|
@@ -132,6 +152,8 @@ var script = {
|
|
|
132
152
|
conversationThreadUid += 1;
|
|
133
153
|
return {
|
|
134
154
|
draft: '',
|
|
155
|
+
/** The channel the chips narrow the thread to; '' is All. */
|
|
156
|
+
activeChannel: '',
|
|
135
157
|
sending: false,
|
|
136
158
|
errorMessage: '',
|
|
137
159
|
titleId: `cn-conversation-title-${conversationThreadUid}`,
|
|
@@ -170,6 +192,47 @@ var script = {
|
|
|
170
192
|
})
|
|
171
193
|
},
|
|
172
194
|
|
|
195
|
+
/**
|
|
196
|
+
* The channel labels with a chip: the declared ones, then the ones
|
|
197
|
+
* the messages use.
|
|
198
|
+
*
|
|
199
|
+
* @return {Array<string>} The labels, each once.
|
|
200
|
+
*/
|
|
201
|
+
channelLabels() {
|
|
202
|
+
const declared = (Array.isArray(this.channels) ? this.channels : []).filter((label) => typeof label === 'string' && label !== '');
|
|
203
|
+
return [...new Set([...declared, ...this.rows.map((row) => row.channel).filter(Boolean)])]
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The chips: All, then each channel, each with its count. Empty
|
|
208
|
+
* without `channelFilter` or without channels.
|
|
209
|
+
*
|
|
210
|
+
* @return {Array<{label: string, count: number}>} The chips.
|
|
211
|
+
*/
|
|
212
|
+
channelChips() {
|
|
213
|
+
if (!this.channelFilter || this.channelLabels.length === 0) {
|
|
214
|
+
return []
|
|
215
|
+
}
|
|
216
|
+
return [
|
|
217
|
+
{ label: translate('nextcloud-vue', 'All'), count: this.rows.length },
|
|
218
|
+
...this.channelLabels.map((label) => ({ label, count: this.rows.filter((row) => row.channel === label).length })),
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/** @return {number} The pressed chip: 0 for All. */
|
|
223
|
+
activeChannelIndex() {
|
|
224
|
+
const index = this.channelLabels.indexOf(this.activeChannel);
|
|
225
|
+
return index < 0 ? 0 : index + 1
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/** @return {Array<object>} The rows on the chosen channel, or all of them. */
|
|
229
|
+
shownRows() {
|
|
230
|
+
if (this.channelChips.length === 0 || this.activeChannelIndex === 0) {
|
|
231
|
+
return this.rows
|
|
232
|
+
}
|
|
233
|
+
return this.rows.filter((row) => row.channel === this.activeChannel)
|
|
234
|
+
},
|
|
235
|
+
|
|
173
236
|
/**
|
|
174
237
|
* Whether there is something to send and nothing in flight.
|
|
175
238
|
*
|
|
@@ -197,6 +260,16 @@ var script = {
|
|
|
197
260
|
},
|
|
198
261
|
|
|
199
262
|
methods: {
|
|
263
|
+
/**
|
|
264
|
+
* Narrow the thread to the chip's channel; the All chip (0) clears it.
|
|
265
|
+
*
|
|
266
|
+
* @param {number|null} index The chip index.
|
|
267
|
+
* @return {void}
|
|
268
|
+
*/
|
|
269
|
+
onChannelChip(index) {
|
|
270
|
+
this.activeChannel = index > 0 ? (this.channelLabels[index - 1] || '') : '';
|
|
271
|
+
},
|
|
272
|
+
|
|
200
273
|
/**
|
|
201
274
|
* Up to two initials of a name, for the avatar.
|
|
202
275
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnConversationThread.vue2.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tclass=\"cn-conversation-thread\"\n\t\tdata-testid=\"cn-conversation-thread\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<component\n\t\t\t:is=\"titleTag\"\n\t\t\tv-if=\"title\"\n\t\t\t:id=\"titleId\"\n\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t{{ title }}\n\t\t</component>\n\n\t\t<p v-if=\"loading && rows.length === 0\"\n\t\t\tclass=\"cn-conversation-thread__empty\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-conversation-loading\">\n\t\t\t{{ loadingText }}\n\t\t</p>\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<ol v-else class=\"cn-conversation-thread__messages\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-conversation-thread__message\"\n\t\t\t\t:class=\"`cn-conversation-thread__message--${row.side}`\"\n\t\t\t\tdata-testid=\"cn-conversation-message\"\n\t\t\t\t:data-side=\"row.side\">\n\t\t\t\t<span class=\"cn-conversation-thread__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t<div class=\"cn-conversation-thread__bubble\">\n\t\t\t\t\t<span class=\"cn-conversation-thread__meta\">\n\t\t\t\t\t\t<span class=\"hidden-visually\">{{ row.directionLabel }}</span>\n\t\t\t\t\t\t<span class=\"cn-conversation-thread__author\">{{ row.author }}</span>\n\t\t\t\t\t\t<time v-if=\"row.timeLabel\" :datetime=\"row.datetime || null\">{{ row.timeLabel }}</time>\n\t\t\t\t\t\t<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ol>\n\n\t\t<form\n\t\t\tv-if=\"allowReply\"\n\t\t\tclass=\"cn-conversation-thread__reply\"\n\t\t\tdata-testid=\"cn-conversation-reply\"\n\t\t\t@submit.prevent=\"onSend\">\n\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t<textarea\n\t\t\t\t:id=\"inputId\"\n\t\t\t\tv-model=\"draft\"\n\t\t\t\tclass=\"cn-conversation-thread__input\"\n\t\t\t\trows=\"2\"\n\t\t\t\t:placeholder=\"placeholder || null\"\n\t\t\t\t:disabled=\"sending\"\n\t\t\t\t:aria-describedby=\"errorMessage ? errorId : null\"\n\t\t\t\tdata-testid=\"cn-conversation-input\"\n\t\t\t\t@keydown.ctrl.enter.prevent=\"onSend\"\n\t\t\t\t@keydown.meta.enter.prevent=\"onSend\" />\n\t\t\t<p\n\t\t\t\tv-if=\"errorMessage\"\n\t\t\t\t:id=\"errorId\"\n\t\t\t\tclass=\"cn-conversation-thread__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-conversation-error\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nlet conversationThreadUid = 0\n\n/**\n * CnConversationThread: the messages between you and the other party, and a box to answer.\n *\n * Each message has an author, a time and a side: `us` for the organisation,\n * `them` for the resident or customer. The reply box emits `send`, and also\n * posts to `endpoint` when one is set. It needs no Talk and no other app:\n * where the messages are stored is the host's business.\n *\n * ```vue\n * <CnConversationThread\n * title=\"Contact with the resident\"\n * reply-label=\"Answer to the resident\"\n * :messages=\"[\n * { id: 1, author: 'Sanne de Vries', time: '2026-10-04T10:52:00', side: 'them',\n * text: 'Thank you. I would like to see all documents.' },\n * { id: 2, time: '2026-10-04T10:24:00', side: 'us',\n * text: 'Construction starts in March.' },\n * ]\"\n * @send=\"saveReply\" />\n * ```\n *\n * The `conversation` detail widget type renders this thread from a field on\n * the record.\n */\nexport default {\n\tname: 'CnConversationThread',\n\n\tcomponents: {\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel? }`.\n\t\t * `side` is `us` or `them` (default `them`). `time` is an ISO\n\t\t * date, or any text to show as is. `channel` is shown after the time\n\t\t * (\"E-mail\", \"Phone note\").\n\t\t */\n\t\tmessages: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Heading of the thread. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/** Whether the reply box renders. Turn it off for a closed or read-only record. */\n\t\tallowReply: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label of the reply box. */\n\t\treplyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Reply'),\n\t\t},\n\n\t\t/** Placeholder inside the reply box. */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Label of the send button. */\n\t\tsendLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Send'),\n\t\t},\n\n\t\t/** Author shown on an `us` message that names none. */\n\t\tusLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'You'),\n\t\t},\n\n\t\t/**\n\t\t * URL the reply is POSTed to as `{ message }`. Empty means the host\n\t\t * handles `send` itself and nothing is requested.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Extra fields sent along with `message` when `endpoint` is set. */\n\t\tpayload: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the messages are still loading; with none yet the thread says so. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Shown when there are no messages yet. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No messages yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** A reply was submitted. Payload: the text. */\n\t\t'send',\n\t\t/** The endpoint accepted the reply. Payload: the response body. */\n\t\t'sent',\n\t\t/** The endpoint refused the reply. Payload: the error. */\n\t\t'error',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\tsending: false,\n\t\t\terrorMessage: '',\n\t\t\ttitleId: `cn-conversation-title-${conversationThreadUid}`,\n\t\t\tinputId: `cn-conversation-input-${conversationThreadUid}`,\n\t\t\terrorId: `cn-conversation-error-${conversationThreadUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The messages, with author, side, time and initials resolved.\n\t\t *\n\t\t * @return {Array<{key: string, author: string, initials: string, side: string, text: string, timeLabel: string, datetime: string, channel: string, directionLabel: string}>} The rows.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\trows() {\n\t\t\treturn (Array.isArray(this.messages) ? this.messages : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message, index) => {\n\t\t\t\t\tconst side = message.side === 'us' ? 'us' : 'them'\n\t\t\t\t\tconst author = typeof message.author === 'string' && message.author !== ''\n\t\t\t\t\t\t? message.author\n\t\t\t\t\t\t: (side === 'us' ? this.usLabel : '')\n\t\t\t\t\tconst time = this.formatTime(message.time)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: message.id === undefined || message.id === null ? `message-${index}` : String(message.id),\n\t\t\t\t\t\tauthor,\n\t\t\t\t\t\tinitials: this.initialsOf(author),\n\t\t\t\t\t\tside,\n\t\t\t\t\t\ttext: message.text === undefined || message.text === null ? '' : String(message.text),\n\t\t\t\t\t\ttimeLabel: time.label,\n\t\t\t\t\t\tdatetime: time.datetime,\n\t\t\t\t\t\tchannel: typeof message.channel === 'string' ? message.channel : '',\n\t\t\t\t\t\tdirectionLabel: side === 'us' ? t('nextcloud-vue', 'Sent message') : t('nextcloud-vue', 'Received message'),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether there is something to send and nothing in flight.\n\t\t *\n\t\t * @return {boolean} True when Send is enabled.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tcanSend() {\n\t\t\treturn !this.sending && this.draft.trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a thread without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Conversation')\n\t\t},\n\n\t\t/** @return {string} What the thread says while its messages load. */\n\t\tloadingText() {\n\t\t\treturn t('nextcloud-vue', 'Loading messages…')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Up to two initials of a name, for the avatar.\n\t\t *\n\t\t * @param {string} name The author.\n\t\t * @return {string} The initials, upper case.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst parts = String(name || '').trim().split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = parts[0].charAt(0)\n\t\t\tconst last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''\n\t\t\treturn (first + last).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Turn a message time into what the reader sees and what `<time>` carries.\n\t\t *\n\t\t * @param {unknown} value An ISO date, a Date, or text to show as is.\n\t\t * @return {{label: string, datetime: string}} The visible label and the machine value.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tformatTime(value) {\n\t\t\tif (value === null || value === undefined || value === '') {\n\t\t\t\treturn { label: '', datetime: '' }\n\t\t\t}\n\t\t\tconst date = value instanceof Date ? value : new Date(value)\n\t\t\tconst looksLikeDate = value instanceof Date || typeof value === 'number' || /^\\d{4}-\\d{2}-\\d{2}/.test(String(value))\n\t\t\tif (!looksLikeDate || Number.isNaN(date.getTime())) {\n\t\t\t\treturn { label: String(value), datetime: '' }\n\t\t\t}\n\t\t\tconst label = date.toLocaleString(userLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\treturn { label, datetime: date.toISOString() }\n\t\t},\n\n\t\t/**\n\t\t * Submit the reply: tell the host, and post it when an endpoint is set.\n\t\t *\n\t\t * Without an endpoint the box clears straight away, because the host\n\t\t * owns the send. With one it clears only when the request succeeds, so\n\t\t * a refused reply is still there to try again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tasync onSend() {\n\t\t\tif (!this.canSend) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst text = this.draft.trim()\n\t\t\tthis.errorMessage = ''\n\t\t\tthis.$emit('send', text)\n\t\t\tif (!this.endpoint) {\n\t\t\t\tthis.draft = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sending = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.endpoint), { ...this.payload, message: text })\n\t\t\t\tthis.draft = ''\n\t\t\t\tthis.$emit('sent', response?.data)\n\t\t\t} catch (error) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'The message could not be sent. Try again.')\n\t\t\t\tthis.$emit('error', error)\n\t\t\t} finally {\n\t\t\t\tthis.sending = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-conversation-thread {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__title {\n\tfont-size: 1.1em;\n\tfont-weight: 700;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-conversation-thread__messages {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-conversation-thread__message {\n\tdisplay: flex;\n\tgap: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us {\n\tflex-direction: row-reverse;\n}\n\n.cn-conversation-thread__avatar {\n\talign-items: center;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 50%;\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: none;\n\tfont-size: 0.8em;\n\tfont-weight: 700;\n\theight: 34px;\n\tjustify-content: center;\n\twidth: 34px;\n}\n\n.cn-conversation-thread__bubble {\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tborder-start-start-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: 1;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline);\n\tmin-width: 0;\n\tpadding: calc(3 * var(--default-grid-baseline)) calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us .cn-conversation-thread__bubble {\n\tbackground: var(--color-primary-element-light);\n\tborder-start-end-radius: var(--border-radius-small, 4px);\n\tborder-start-start-radius: var(--border-radius-large);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-conversation-thread__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tfont-size: 0.9em;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__author {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__text {\n\toverflow-wrap: anywhere;\n\twhite-space: pre-wrap;\n}\n\n.cn-conversation-thread__reply {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__input {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tresize: vertical;\n\twidth: 100%;\n}\n\n.cn-conversation-thread__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n\n.cn-conversation-thread__reply-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: flex-end;\n}\n\n.cn-conversation-thread__channel {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t","axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnConversationThread.vue2.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tclass=\"cn-conversation-thread\"\n\t\tdata-testid=\"cn-conversation-thread\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<div v-if=\"title || $slots['header-actions']\" class=\"cn-conversation-thread__header\">\n\t\t\t<component\n\t\t\t\t:is=\"titleTag\"\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:id=\"titleId\"\n\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t{{ title }}\n\t\t\t</component>\n\t\t\t<div v-if=\"$slots['header-actions']\" class=\"cn-conversation-thread__header-actions\">\n\t\t\t\t<!-- @slot header-actions Buttons beside the title, for example \"Log contact\". -->\n\t\t\t\t<slot name=\"header-actions\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<CnQuickFilterBar\n\t\t\tv-if=\"channelChips.length > 0\"\n\t\t\tinline\n\t\t\tclass=\"cn-conversation-thread__channels\"\n\t\t\t:tabs=\"channelChips\"\n\t\t\t:activeIndex=\"activeChannelIndex\"\n\t\t\t@update:activeIndex=\"onChannelChip\" />\n\n\t\t<p v-if=\"loading && rows.length === 0\"\n\t\t\tclass=\"cn-conversation-thread__empty\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-conversation-loading\">\n\t\t\t{{ loadingText }}\n\t\t</p>\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<p v-else-if=\"shownRows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-channel-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<ol v-else class=\"cn-conversation-thread__messages\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in shownRows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-conversation-thread__message\"\n\t\t\t\t:class=\"`cn-conversation-thread__message--${row.side}`\"\n\t\t\t\tdata-testid=\"cn-conversation-message\"\n\t\t\t\t:data-side=\"row.side\">\n\t\t\t\t<span class=\"cn-conversation-thread__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t<div class=\"cn-conversation-thread__bubble\">\n\t\t\t\t\t<span class=\"cn-conversation-thread__meta\">\n\t\t\t\t\t\t<span class=\"hidden-visually\">{{ row.directionLabel }}</span>\n\t\t\t\t\t\t<span class=\"cn-conversation-thread__author\">{{ row.author }}</span>\n\t\t\t\t\t\t<time v-if=\"row.timeLabel\" :datetime=\"row.datetime || null\">{{ row.timeLabel }}</time>\n\t\t\t\t\t\t<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ol>\n\n\t\t<form\n\t\t\tv-if=\"allowReply\"\n\t\t\tclass=\"cn-conversation-thread__reply\"\n\t\t\tdata-testid=\"cn-conversation-reply\"\n\t\t\t@submit.prevent=\"onSend\">\n\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t<textarea\n\t\t\t\t:id=\"inputId\"\n\t\t\t\tv-model=\"draft\"\n\t\t\t\tclass=\"cn-conversation-thread__input\"\n\t\t\t\trows=\"2\"\n\t\t\t\t:placeholder=\"placeholder || null\"\n\t\t\t\t:disabled=\"sending\"\n\t\t\t\t:aria-describedby=\"errorMessage ? errorId : null\"\n\t\t\t\tdata-testid=\"cn-conversation-input\"\n\t\t\t\t@keydown.ctrl.enter.prevent=\"onSend\"\n\t\t\t\t@keydown.meta.enter.prevent=\"onSend\" />\n\t\t\t<p\n\t\t\t\tv-if=\"errorMessage\"\n\t\t\t\t:id=\"errorId\"\n\t\t\t\tclass=\"cn-conversation-thread__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-conversation-error\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport CnQuickFilterBar from '../CnQuickFilterBar/CnQuickFilterBar.vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nlet conversationThreadUid = 0\n\n/**\n * CnConversationThread: the messages between you and the other party, and a box to answer.\n *\n * Each message has an author, a time and a side: `us` for the organisation,\n * `them` for the resident or customer. The reply box emits `send`, and also\n * posts to `endpoint` when one is set. It needs no Talk and no other app:\n * where the messages are stored is the host's business.\n *\n * ```vue\n * <CnConversationThread\n * title=\"Contact with the resident\"\n * reply-label=\"Answer to the resident\"\n * :messages=\"[\n * { id: 1, author: 'Sanne de Vries', time: '2026-10-04T10:52:00', side: 'them',\n * text: 'Thank you. I would like to see all documents.' },\n * { id: 2, time: '2026-10-04T10:24:00', side: 'us',\n * text: 'Construction starts in March.' },\n * ]\"\n * @send=\"saveReply\" />\n * ```\n *\n * The `conversation` detail widget type renders this thread from a field on\n * the record.\n */\nexport default {\n\tname: 'CnConversationThread',\n\n\tcomponents: {\n\t\tCnQuickFilterBar,\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel? }`.\n\t\t * `side` is `us` or `them` (default `them`). `time` is an ISO\n\t\t * date, or any text to show as is. `channel` is shown after the time\n\t\t * (\"E-mail\", \"Phone note\").\n\t\t */\n\t\tmessages: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Heading of the thread. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/** Whether the reply box renders. Turn it off for a closed or read-only record. */\n\t\tallowReply: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label of the reply box. */\n\t\treplyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Reply'),\n\t\t},\n\n\t\t/** Placeholder inside the reply box. */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Label of the send button. */\n\t\tsendLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Send'),\n\t\t},\n\n\t\t/** Author shown on an `us` message that names none. */\n\t\tusLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'You'),\n\t\t},\n\n\t\t/**\n\t\t * URL the reply is POSTed to as `{ message }`. Empty means the host\n\t\t * handles `send` itself and nothing is requested.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Extra fields sent along with `message` when `endpoint` is set. */\n\t\tpayload: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the messages are still loading; with none yet the thread says so. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw channel chips above the messages: All, then one per channel\n\t\t * with its count. Choosing one shows only that channel's messages.\n\t\t */\n\t\tchannelFilter: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Channel labels that always get a chip, in this order, also when no\n\t\t * message uses them yet. Channels the messages use follow.\n\t\t */\n\t\tchannels: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Shown when there are no messages yet. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No messages yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** A reply was submitted. Payload: the text. */\n\t\t'send',\n\t\t/** The endpoint accepted the reply. Payload: the response body. */\n\t\t'sent',\n\t\t/** The endpoint refused the reply. Payload: the error. */\n\t\t'error',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The channel the chips narrow the thread to; '' is All. */\n\t\t\tactiveChannel: '',\n\t\t\tsending: false,\n\t\t\terrorMessage: '',\n\t\t\ttitleId: `cn-conversation-title-${conversationThreadUid}`,\n\t\t\tinputId: `cn-conversation-input-${conversationThreadUid}`,\n\t\t\terrorId: `cn-conversation-error-${conversationThreadUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The messages, with author, side, time and initials resolved.\n\t\t *\n\t\t * @return {Array<{key: string, author: string, initials: string, side: string, text: string, timeLabel: string, datetime: string, channel: string, directionLabel: string}>} The rows.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\trows() {\n\t\t\treturn (Array.isArray(this.messages) ? this.messages : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message, index) => {\n\t\t\t\t\tconst side = message.side === 'us' ? 'us' : 'them'\n\t\t\t\t\tconst author = typeof message.author === 'string' && message.author !== ''\n\t\t\t\t\t\t? message.author\n\t\t\t\t\t\t: (side === 'us' ? this.usLabel : '')\n\t\t\t\t\tconst time = this.formatTime(message.time)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: message.id === undefined || message.id === null ? `message-${index}` : String(message.id),\n\t\t\t\t\t\tauthor,\n\t\t\t\t\t\tinitials: this.initialsOf(author),\n\t\t\t\t\t\tside,\n\t\t\t\t\t\ttext: message.text === undefined || message.text === null ? '' : String(message.text),\n\t\t\t\t\t\ttimeLabel: time.label,\n\t\t\t\t\t\tdatetime: time.datetime,\n\t\t\t\t\t\tchannel: typeof message.channel === 'string' ? message.channel : '',\n\t\t\t\t\t\tdirectionLabel: side === 'us' ? t('nextcloud-vue', 'Sent message') : t('nextcloud-vue', 'Received message'),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The channel labels with a chip: the declared ones, then the ones\n\t\t * the messages use.\n\t\t *\n\t\t * @return {Array<string>} The labels, each once.\n\t\t */\n\t\tchannelLabels() {\n\t\t\tconst declared = (Array.isArray(this.channels) ? this.channels : []).filter((label) => typeof label === 'string' && label !== '')\n\t\t\treturn [...new Set([...declared, ...this.rows.map((row) => row.channel).filter(Boolean)])]\n\t\t},\n\n\t\t/**\n\t\t * The chips: All, then each channel, each with its count. Empty\n\t\t * without `channelFilter` or without channels.\n\t\t *\n\t\t * @return {Array<{label: string, count: number}>} The chips.\n\t\t */\n\t\tchannelChips() {\n\t\t\tif (!this.channelFilter || this.channelLabels.length === 0) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ label: t('nextcloud-vue', 'All'), count: this.rows.length },\n\t\t\t\t...this.channelLabels.map((label) => ({ label, count: this.rows.filter((row) => row.channel === label).length })),\n\t\t\t]\n\t\t},\n\n\t\t/** @return {number} The pressed chip: 0 for All. */\n\t\tactiveChannelIndex() {\n\t\t\tconst index = this.channelLabels.indexOf(this.activeChannel)\n\t\t\treturn index < 0 ? 0 : index + 1\n\t\t},\n\n\t\t/** @return {Array<object>} The rows on the chosen channel, or all of them. */\n\t\tshownRows() {\n\t\t\tif (this.channelChips.length === 0 || this.activeChannelIndex === 0) {\n\t\t\t\treturn this.rows\n\t\t\t}\n\t\t\treturn this.rows.filter((row) => row.channel === this.activeChannel)\n\t\t},\n\n\t\t/**\n\t\t * Whether there is something to send and nothing in flight.\n\t\t *\n\t\t * @return {boolean} True when Send is enabled.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tcanSend() {\n\t\t\treturn !this.sending && this.draft.trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a thread without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Conversation')\n\t\t},\n\n\t\t/** @return {string} What the thread says while its messages load. */\n\t\tloadingText() {\n\t\t\treturn t('nextcloud-vue', 'Loading messages…')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Narrow the thread to the chip's channel; the All chip (0) clears it.\n\t\t *\n\t\t * @param {number|null} index The chip index.\n\t\t * @return {void}\n\t\t */\n\t\tonChannelChip(index) {\n\t\t\tthis.activeChannel = index > 0 ? (this.channelLabels[index - 1] || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Up to two initials of a name, for the avatar.\n\t\t *\n\t\t * @param {string} name The author.\n\t\t * @return {string} The initials, upper case.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst parts = String(name || '').trim().split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = parts[0].charAt(0)\n\t\t\tconst last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''\n\t\t\treturn (first + last).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Turn a message time into what the reader sees and what `<time>` carries.\n\t\t *\n\t\t * @param {unknown} value An ISO date, a Date, or text to show as is.\n\t\t * @return {{label: string, datetime: string}} The visible label and the machine value.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tformatTime(value) {\n\t\t\tif (value === null || value === undefined || value === '') {\n\t\t\t\treturn { label: '', datetime: '' }\n\t\t\t}\n\t\t\tconst date = value instanceof Date ? value : new Date(value)\n\t\t\tconst looksLikeDate = value instanceof Date || typeof value === 'number' || /^\\d{4}-\\d{2}-\\d{2}/.test(String(value))\n\t\t\tif (!looksLikeDate || Number.isNaN(date.getTime())) {\n\t\t\t\treturn { label: String(value), datetime: '' }\n\t\t\t}\n\t\t\tconst label = date.toLocaleString(userLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\treturn { label, datetime: date.toISOString() }\n\t\t},\n\n\t\t/**\n\t\t * Submit the reply: tell the host, and post it when an endpoint is set.\n\t\t *\n\t\t * Without an endpoint the box clears straight away, because the host\n\t\t * owns the send. With one it clears only when the request succeeds, so\n\t\t * a refused reply is still there to try again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tasync onSend() {\n\t\t\tif (!this.canSend) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst text = this.draft.trim()\n\t\t\tthis.errorMessage = ''\n\t\t\tthis.$emit('send', text)\n\t\t\tif (!this.endpoint) {\n\t\t\t\tthis.draft = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sending = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.endpoint), { ...this.payload, message: text })\n\t\t\t\tthis.draft = ''\n\t\t\t\tthis.$emit('sent', response?.data)\n\t\t\t} catch (error) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'The message could not be sent. Try again.')\n\t\t\t\tthis.$emit('error', error)\n\t\t\t} finally {\n\t\t\t\tthis.sending = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-conversation-thread {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__header {\n\talign-items: flex-start;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(3 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__header-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__title {\n\tfont-size: 1.1em;\n\tfont-weight: 700;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-conversation-thread__messages {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-conversation-thread__message {\n\tdisplay: flex;\n\tgap: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us {\n\tflex-direction: row-reverse;\n}\n\n.cn-conversation-thread__avatar {\n\talign-items: center;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 50%;\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: none;\n\tfont-size: 0.8em;\n\tfont-weight: 700;\n\theight: 34px;\n\tjustify-content: center;\n\twidth: 34px;\n}\n\n.cn-conversation-thread__bubble {\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tborder-start-start-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: 1;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline);\n\tmin-width: 0;\n\tpadding: calc(3 * var(--default-grid-baseline)) calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us .cn-conversation-thread__bubble {\n\tbackground: var(--color-primary-element-light);\n\tborder-start-end-radius: var(--border-radius-small, 4px);\n\tborder-start-start-radius: var(--border-radius-large);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-conversation-thread__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tfont-size: 0.9em;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__author {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__text {\n\toverflow-wrap: anywhere;\n\twhite-space: pre-wrap;\n}\n\n.cn-conversation-thread__reply {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__input {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tresize: vertical;\n\twidth: 100%;\n}\n\n.cn-conversation-thread__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n\n.cn-conversation-thread__reply-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: flex-end;\n}\n\n.cn-conversation-thread__channel {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnQuickFilterBar","t","axios"],"mappings":"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"}
|
|
@@ -1,43 +1,57 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createElementBlock, createBlock, resolveDynamicComponent, withCtx, createTextVNode, toDisplayString, createCommentVNode, Fragment, renderList, normalizeClass, createElementVNode, withModifiers, withDirectives, withKeys, vModelText,
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, createBlock, resolveDynamicComponent, withCtx, createTextVNode, toDisplayString, createCommentVNode, renderSlot, Fragment, renderList, normalizeClass, createElementVNode, withModifiers, withDirectives, withKeys, vModelText, createVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = ["aria-labelledby", "aria-label"];
|
|
4
4
|
const _hoisted_2 = {
|
|
5
|
+
key: 0,
|
|
6
|
+
class: "cn-conversation-thread__header"
|
|
7
|
+
};
|
|
8
|
+
const _hoisted_3 = {
|
|
5
9
|
key: 1,
|
|
10
|
+
class: "cn-conversation-thread__header-actions"
|
|
11
|
+
};
|
|
12
|
+
const _hoisted_4 = {
|
|
13
|
+
key: 2,
|
|
6
14
|
class: "cn-conversation-thread__empty",
|
|
7
15
|
role: "status",
|
|
8
16
|
"data-testid": "cn-conversation-loading"
|
|
9
17
|
};
|
|
10
|
-
const
|
|
11
|
-
key:
|
|
18
|
+
const _hoisted_5 = {
|
|
19
|
+
key: 3,
|
|
12
20
|
class: "cn-conversation-thread__empty",
|
|
13
21
|
"data-testid": "cn-conversation-empty"
|
|
14
22
|
};
|
|
15
|
-
const
|
|
16
|
-
key:
|
|
23
|
+
const _hoisted_6 = {
|
|
24
|
+
key: 4,
|
|
25
|
+
class: "cn-conversation-thread__empty",
|
|
26
|
+
"data-testid": "cn-conversation-channel-empty"
|
|
27
|
+
};
|
|
28
|
+
const _hoisted_7 = {
|
|
29
|
+
key: 5,
|
|
17
30
|
class: "cn-conversation-thread__messages"
|
|
18
31
|
};
|
|
19
|
-
const
|
|
20
|
-
const
|
|
32
|
+
const _hoisted_8 = ["data-side"];
|
|
33
|
+
const _hoisted_9 = {
|
|
21
34
|
class: "cn-conversation-thread__avatar",
|
|
22
35
|
"aria-hidden": "true"
|
|
23
36
|
};
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
37
|
+
const _hoisted_10 = { class: "cn-conversation-thread__bubble" };
|
|
38
|
+
const _hoisted_11 = { class: "cn-conversation-thread__meta" };
|
|
39
|
+
const _hoisted_12 = { class: "hidden-visually" };
|
|
40
|
+
const _hoisted_13 = { class: "cn-conversation-thread__author" };
|
|
41
|
+
const _hoisted_14 = ["datetime"];
|
|
42
|
+
const _hoisted_15 = {
|
|
30
43
|
key: 1,
|
|
31
44
|
class: "cn-conversation-thread__channel",
|
|
32
45
|
"data-testid": "cn-conversation-channel"
|
|
33
46
|
};
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
47
|
+
const _hoisted_16 = { class: "cn-conversation-thread__text" };
|
|
48
|
+
const _hoisted_17 = ["for"];
|
|
49
|
+
const _hoisted_18 = ["id", "placeholder", "disabled", "aria-describedby"];
|
|
50
|
+
const _hoisted_19 = ["id"];
|
|
51
|
+
const _hoisted_20 = { class: "cn-conversation-thread__reply-actions" };
|
|
39
52
|
|
|
40
53
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
54
|
+
const _component_CnQuickFilterBar = resolveComponent("CnQuickFilterBar");
|
|
41
55
|
const _component_NcButton = resolveComponent("NcButton");
|
|
42
56
|
|
|
43
57
|
return (openBlock(), createElementBlock("section", {
|
|
@@ -46,53 +60,74 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
46
60
|
"aria-labelledby": $props.title ? $data.titleId : null,
|
|
47
61
|
"aria-label": $props.title ? null : $options.fallbackLabel
|
|
48
62
|
}, [
|
|
49
|
-
($props.title)
|
|
50
|
-
? (openBlock(),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
($props.title || _ctx.$slots['header-actions'])
|
|
64
|
+
? (openBlock(), createElementBlock("div", _hoisted_2, [
|
|
65
|
+
($props.title)
|
|
66
|
+
? (openBlock(), createBlock(resolveDynamicComponent($props.titleTag), {
|
|
67
|
+
key: 0,
|
|
68
|
+
id: $data.titleId,
|
|
69
|
+
class: "cn-conversation-thread__title"
|
|
70
|
+
}, {
|
|
71
|
+
default: withCtx(() => [
|
|
72
|
+
createTextVNode(toDisplayString($props.title), 1 /* TEXT */)
|
|
73
|
+
]),
|
|
74
|
+
_: 1 /* STABLE */
|
|
75
|
+
}, 8 /* PROPS */, ["id"]))
|
|
76
|
+
: createCommentVNode("v-if", true),
|
|
77
|
+
(_ctx.$slots['header-actions'])
|
|
78
|
+
? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
79
|
+
renderSlot(_ctx.$slots, "header-actions")
|
|
80
|
+
]))
|
|
81
|
+
: createCommentVNode("v-if", true)
|
|
82
|
+
]))
|
|
83
|
+
: createCommentVNode("v-if", true),
|
|
84
|
+
($options.channelChips.length > 0)
|
|
85
|
+
? (openBlock(), createBlock(_component_CnQuickFilterBar, {
|
|
86
|
+
key: 1,
|
|
87
|
+
inline: "",
|
|
88
|
+
class: "cn-conversation-thread__channels",
|
|
89
|
+
tabs: $options.channelChips,
|
|
90
|
+
activeIndex: $options.activeChannelIndex,
|
|
91
|
+
"onUpdate:activeIndex": $options.onChannelChip
|
|
92
|
+
}, null, 8 /* PROPS */, ["tabs", "activeIndex", "onUpdate:activeIndex"]))
|
|
60
93
|
: createCommentVNode("v-if", true),
|
|
61
94
|
($props.loading && $options.rows.length === 0)
|
|
62
|
-
? (openBlock(), createElementBlock("p",
|
|
95
|
+
? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString($options.loadingText), 1 /* TEXT */))
|
|
63
96
|
: ($options.rows.length === 0)
|
|
64
|
-
? (openBlock(), createElementBlock("p",
|
|
65
|
-
: (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
createElementVNode("span",
|
|
76
|
-
|
|
77
|
-
createElementVNode("span",
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
? (openBlock(), createElementBlock("p", _hoisted_5, toDisplayString($props.emptyText), 1 /* TEXT */))
|
|
98
|
+
: ($options.shownRows.length === 0)
|
|
99
|
+
? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString($props.emptyText), 1 /* TEXT */))
|
|
100
|
+
: (openBlock(), createElementBlock("ol", _hoisted_7, [
|
|
101
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.shownRows, (row) => {
|
|
102
|
+
return (openBlock(), createElementBlock("li", {
|
|
103
|
+
key: row.key,
|
|
104
|
+
class: normalizeClass(["cn-conversation-thread__message", `cn-conversation-thread__message--${row.side}`]),
|
|
105
|
+
"data-testid": "cn-conversation-message",
|
|
106
|
+
"data-side": row.side
|
|
107
|
+
}, [
|
|
108
|
+
createElementVNode("span", _hoisted_9, toDisplayString(row.initials), 1 /* TEXT */),
|
|
109
|
+
createElementVNode("div", _hoisted_10, [
|
|
110
|
+
createElementVNode("span", _hoisted_11, [
|
|
111
|
+
createElementVNode("span", _hoisted_12, toDisplayString(row.directionLabel), 1 /* TEXT */),
|
|
112
|
+
createElementVNode("span", _hoisted_13, toDisplayString(row.author), 1 /* TEXT */),
|
|
113
|
+
(row.timeLabel)
|
|
114
|
+
? (openBlock(), createElementBlock("time", {
|
|
115
|
+
key: 0,
|
|
116
|
+
datetime: row.datetime || null
|
|
117
|
+
}, toDisplayString(row.timeLabel), 9 /* TEXT, PROPS */, _hoisted_14))
|
|
118
|
+
: createCommentVNode("v-if", true),
|
|
119
|
+
(row.channel)
|
|
120
|
+
? (openBlock(), createElementBlock("span", _hoisted_15, toDisplayString(row.channel), 1 /* TEXT */))
|
|
121
|
+
: createCommentVNode("v-if", true)
|
|
122
|
+
]),
|
|
123
|
+
createElementVNode("span", _hoisted_16, toDisplayString(row.text), 1 /* TEXT */)
|
|
124
|
+
])
|
|
125
|
+
], 10 /* CLASS, PROPS */, _hoisted_8))
|
|
126
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
127
|
+
])),
|
|
93
128
|
($props.allowReply)
|
|
94
129
|
? (openBlock(), createElementBlock("form", {
|
|
95
|
-
key:
|
|
130
|
+
key: 6,
|
|
96
131
|
class: "cn-conversation-thread__reply",
|
|
97
132
|
"data-testid": "cn-conversation-reply",
|
|
98
133
|
onSubmit: _cache[3] || (_cache[3] = withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["prevent"]))
|
|
@@ -100,7 +135,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
100
135
|
createElementVNode("label", {
|
|
101
136
|
for: $data.inputId,
|
|
102
137
|
class: "cn-conversation-thread__reply-label"
|
|
103
|
-
}, toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */,
|
|
138
|
+
}, toDisplayString($props.replyLabel), 9 /* TEXT, PROPS */, _hoisted_17),
|
|
104
139
|
withDirectives(createElementVNode("textarea", {
|
|
105
140
|
id: $data.inputId,
|
|
106
141
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($data.draft) = $event)),
|
|
@@ -114,7 +149,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
114
149
|
_cache[1] || (_cache[1] = withKeys(withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["ctrl","prevent"]), ["enter"])),
|
|
115
150
|
_cache[2] || (_cache[2] = withKeys(withModifiers((...args) => ($options.onSend && $options.onSend(...args)), ["meta","prevent"]), ["enter"]))
|
|
116
151
|
]
|
|
117
|
-
}, null, 40 /* PROPS, NEED_HYDRATION */,
|
|
152
|
+
}, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_18), [
|
|
118
153
|
[vModelText, $data.draft]
|
|
119
154
|
]),
|
|
120
155
|
($data.errorMessage)
|
|
@@ -124,9 +159,9 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
124
159
|
class: "cn-conversation-thread__error",
|
|
125
160
|
role: "alert",
|
|
126
161
|
"data-testid": "cn-conversation-error"
|
|
127
|
-
}, toDisplayString($data.errorMessage), 9 /* TEXT, PROPS */,
|
|
162
|
+
}, toDisplayString($data.errorMessage), 9 /* TEXT, PROPS */, _hoisted_19))
|
|
128
163
|
: createCommentVNode("v-if", true),
|
|
129
|
-
createElementVNode("div",
|
|
164
|
+
createElementVNode("div", _hoisted_20, [
|
|
130
165
|
renderSlot(_ctx.$slots, "reply-actions", { draft: $data.draft }),
|
|
131
166
|
createVNode(_component_NcButton, {
|
|
132
167
|
variant: "primary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnConversationThread.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue?vue&type=template&id=40a059c8&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tclass=\"cn-conversation-thread\"\n\t\tdata-testid=\"cn-conversation-thread\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<component\n\t\t\t:is=\"titleTag\"\n\t\t\tv-if=\"title\"\n\t\t\t:id=\"titleId\"\n\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t{{ title }}\n\t\t</component>\n\n\t\t<p v-if=\"loading && rows.length === 0\"\n\t\t\tclass=\"cn-conversation-thread__empty\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-conversation-loading\">\n\t\t\t{{ loadingText }}\n\t\t</p>\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<ol v-else class=\"cn-conversation-thread__messages\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-conversation-thread__message\"\n\t\t\t\t:class=\"`cn-conversation-thread__message--${row.side}`\"\n\t\t\t\tdata-testid=\"cn-conversation-message\"\n\t\t\t\t:data-side=\"row.side\">\n\t\t\t\t<span class=\"cn-conversation-thread__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t<div class=\"cn-conversation-thread__bubble\">\n\t\t\t\t\t<span class=\"cn-conversation-thread__meta\">\n\t\t\t\t\t\t<span class=\"hidden-visually\">{{ row.directionLabel }}</span>\n\t\t\t\t\t\t<span class=\"cn-conversation-thread__author\">{{ row.author }}</span>\n\t\t\t\t\t\t<time v-if=\"row.timeLabel\" :datetime=\"row.datetime || null\">{{ row.timeLabel }}</time>\n\t\t\t\t\t\t<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ol>\n\n\t\t<form\n\t\t\tv-if=\"allowReply\"\n\t\t\tclass=\"cn-conversation-thread__reply\"\n\t\t\tdata-testid=\"cn-conversation-reply\"\n\t\t\t@submit.prevent=\"onSend\">\n\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t<textarea\n\t\t\t\t:id=\"inputId\"\n\t\t\t\tv-model=\"draft\"\n\t\t\t\tclass=\"cn-conversation-thread__input\"\n\t\t\t\trows=\"2\"\n\t\t\t\t:placeholder=\"placeholder || null\"\n\t\t\t\t:disabled=\"sending\"\n\t\t\t\t:aria-describedby=\"errorMessage ? errorId : null\"\n\t\t\t\tdata-testid=\"cn-conversation-input\"\n\t\t\t\t@keydown.ctrl.enter.prevent=\"onSend\"\n\t\t\t\t@keydown.meta.enter.prevent=\"onSend\" />\n\t\t\t<p\n\t\t\t\tv-if=\"errorMessage\"\n\t\t\t\t:id=\"errorId\"\n\t\t\t\tclass=\"cn-conversation-thread__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-conversation-error\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nlet conversationThreadUid = 0\n\n/**\n * CnConversationThread: the messages between you and the other party, and a box to answer.\n *\n * Each message has an author, a time and a side: `us` for the organisation,\n * `them` for the resident or customer. The reply box emits `send`, and also\n * posts to `endpoint` when one is set. It needs no Talk and no other app:\n * where the messages are stored is the host's business.\n *\n * ```vue\n * <CnConversationThread\n * title=\"Contact with the resident\"\n * reply-label=\"Answer to the resident\"\n * :messages=\"[\n * { id: 1, author: 'Sanne de Vries', time: '2026-10-04T10:52:00', side: 'them',\n * text: 'Thank you. I would like to see all documents.' },\n * { id: 2, time: '2026-10-04T10:24:00', side: 'us',\n * text: 'Construction starts in March.' },\n * ]\"\n * @send=\"saveReply\" />\n * ```\n *\n * The `conversation` detail widget type renders this thread from a field on\n * the record.\n */\nexport default {\n\tname: 'CnConversationThread',\n\n\tcomponents: {\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel? }`.\n\t\t * `side` is `us` or `them` (default `them`). `time` is an ISO\n\t\t * date, or any text to show as is. `channel` is shown after the time\n\t\t * (\"E-mail\", \"Phone note\").\n\t\t */\n\t\tmessages: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Heading of the thread. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/** Whether the reply box renders. Turn it off for a closed or read-only record. */\n\t\tallowReply: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label of the reply box. */\n\t\treplyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Reply'),\n\t\t},\n\n\t\t/** Placeholder inside the reply box. */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Label of the send button. */\n\t\tsendLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Send'),\n\t\t},\n\n\t\t/** Author shown on an `us` message that names none. */\n\t\tusLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'You'),\n\t\t},\n\n\t\t/**\n\t\t * URL the reply is POSTed to as `{ message }`. Empty means the host\n\t\t * handles `send` itself and nothing is requested.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Extra fields sent along with `message` when `endpoint` is set. */\n\t\tpayload: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the messages are still loading; with none yet the thread says so. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Shown when there are no messages yet. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No messages yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** A reply was submitted. Payload: the text. */\n\t\t'send',\n\t\t/** The endpoint accepted the reply. Payload: the response body. */\n\t\t'sent',\n\t\t/** The endpoint refused the reply. Payload: the error. */\n\t\t'error',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\tsending: false,\n\t\t\terrorMessage: '',\n\t\t\ttitleId: `cn-conversation-title-${conversationThreadUid}`,\n\t\t\tinputId: `cn-conversation-input-${conversationThreadUid}`,\n\t\t\terrorId: `cn-conversation-error-${conversationThreadUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The messages, with author, side, time and initials resolved.\n\t\t *\n\t\t * @return {Array<{key: string, author: string, initials: string, side: string, text: string, timeLabel: string, datetime: string, channel: string, directionLabel: string}>} The rows.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\trows() {\n\t\t\treturn (Array.isArray(this.messages) ? this.messages : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message, index) => {\n\t\t\t\t\tconst side = message.side === 'us' ? 'us' : 'them'\n\t\t\t\t\tconst author = typeof message.author === 'string' && message.author !== ''\n\t\t\t\t\t\t? message.author\n\t\t\t\t\t\t: (side === 'us' ? this.usLabel : '')\n\t\t\t\t\tconst time = this.formatTime(message.time)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: message.id === undefined || message.id === null ? `message-${index}` : String(message.id),\n\t\t\t\t\t\tauthor,\n\t\t\t\t\t\tinitials: this.initialsOf(author),\n\t\t\t\t\t\tside,\n\t\t\t\t\t\ttext: message.text === undefined || message.text === null ? '' : String(message.text),\n\t\t\t\t\t\ttimeLabel: time.label,\n\t\t\t\t\t\tdatetime: time.datetime,\n\t\t\t\t\t\tchannel: typeof message.channel === 'string' ? message.channel : '',\n\t\t\t\t\t\tdirectionLabel: side === 'us' ? t('nextcloud-vue', 'Sent message') : t('nextcloud-vue', 'Received message'),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether there is something to send and nothing in flight.\n\t\t *\n\t\t * @return {boolean} True when Send is enabled.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tcanSend() {\n\t\t\treturn !this.sending && this.draft.trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a thread without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Conversation')\n\t\t},\n\n\t\t/** @return {string} What the thread says while its messages load. */\n\t\tloadingText() {\n\t\t\treturn t('nextcloud-vue', 'Loading messages…')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Up to two initials of a name, for the avatar.\n\t\t *\n\t\t * @param {string} name The author.\n\t\t * @return {string} The initials, upper case.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst parts = String(name || '').trim().split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = parts[0].charAt(0)\n\t\t\tconst last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''\n\t\t\treturn (first + last).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Turn a message time into what the reader sees and what `<time>` carries.\n\t\t *\n\t\t * @param {unknown} value An ISO date, a Date, or text to show as is.\n\t\t * @return {{label: string, datetime: string}} The visible label and the machine value.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tformatTime(value) {\n\t\t\tif (value === null || value === undefined || value === '') {\n\t\t\t\treturn { label: '', datetime: '' }\n\t\t\t}\n\t\t\tconst date = value instanceof Date ? value : new Date(value)\n\t\t\tconst looksLikeDate = value instanceof Date || typeof value === 'number' || /^\\d{4}-\\d{2}-\\d{2}/.test(String(value))\n\t\t\tif (!looksLikeDate || Number.isNaN(date.getTime())) {\n\t\t\t\treturn { label: String(value), datetime: '' }\n\t\t\t}\n\t\t\tconst label = date.toLocaleString(userLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\treturn { label, datetime: date.toISOString() }\n\t\t},\n\n\t\t/**\n\t\t * Submit the reply: tell the host, and post it when an endpoint is set.\n\t\t *\n\t\t * Without an endpoint the box clears straight away, because the host\n\t\t * owns the send. With one it clears only when the request succeeds, so\n\t\t * a refused reply is still there to try again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tasync onSend() {\n\t\t\tif (!this.canSend) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst text = this.draft.trim()\n\t\t\tthis.errorMessage = ''\n\t\t\tthis.$emit('send', text)\n\t\t\tif (!this.endpoint) {\n\t\t\t\tthis.draft = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sending = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.endpoint), { ...this.payload, message: text })\n\t\t\t\tthis.draft = ''\n\t\t\t\tthis.$emit('sent', response?.data)\n\t\t\t} catch (error) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'The message could not be sent. Try again.')\n\t\t\t\tthis.$emit('error', error)\n\t\t\t} finally {\n\t\t\t\tthis.sending = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-conversation-thread {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__title {\n\tfont-size: 1.1em;\n\tfont-weight: 700;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-conversation-thread__messages {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-conversation-thread__message {\n\tdisplay: flex;\n\tgap: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us {\n\tflex-direction: row-reverse;\n}\n\n.cn-conversation-thread__avatar {\n\talign-items: center;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 50%;\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: none;\n\tfont-size: 0.8em;\n\tfont-weight: 700;\n\theight: 34px;\n\tjustify-content: center;\n\twidth: 34px;\n}\n\n.cn-conversation-thread__bubble {\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tborder-start-start-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: 1;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline);\n\tmin-width: 0;\n\tpadding: calc(3 * var(--default-grid-baseline)) calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us .cn-conversation-thread__bubble {\n\tbackground: var(--color-primary-element-light);\n\tborder-start-end-radius: var(--border-radius-small, 4px);\n\tborder-start-start-radius: var(--border-radius-large);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-conversation-thread__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tfont-size: 0.9em;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__author {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__text {\n\toverflow-wrap: anywhere;\n\twhite-space: pre-wrap;\n}\n\n.cn-conversation-thread__reply {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__input {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tresize: vertical;\n\twidth: 100%;\n}\n\n.cn-conversation-thread__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n\n.cn-conversation-thread__reply-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: flex-end;\n}\n\n.cn-conversation-thread__channel {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createElementBlock","_openBlock","_createBlock","_resolveDynamicComponent","_toDisplayString","_Fragment","_renderList","_normalizeClass","_createElementVNode","_renderSlot"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnConversationThread.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue?vue&type=template&id=ac0e8d2a&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tclass=\"cn-conversation-thread\"\n\t\tdata-testid=\"cn-conversation-thread\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<div v-if=\"title || $slots['header-actions']\" class=\"cn-conversation-thread__header\">\n\t\t\t<component\n\t\t\t\t:is=\"titleTag\"\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:id=\"titleId\"\n\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t{{ title }}\n\t\t\t</component>\n\t\t\t<div v-if=\"$slots['header-actions']\" class=\"cn-conversation-thread__header-actions\">\n\t\t\t\t<!-- @slot header-actions Buttons beside the title, for example \"Log contact\". -->\n\t\t\t\t<slot name=\"header-actions\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<CnQuickFilterBar\n\t\t\tv-if=\"channelChips.length > 0\"\n\t\t\tinline\n\t\t\tclass=\"cn-conversation-thread__channels\"\n\t\t\t:tabs=\"channelChips\"\n\t\t\t:activeIndex=\"activeChannelIndex\"\n\t\t\t@update:activeIndex=\"onChannelChip\" />\n\n\t\t<p v-if=\"loading && rows.length === 0\"\n\t\t\tclass=\"cn-conversation-thread__empty\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-conversation-loading\">\n\t\t\t{{ loadingText }}\n\t\t</p>\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<p v-else-if=\"shownRows.length === 0\" class=\"cn-conversation-thread__empty\" data-testid=\"cn-conversation-channel-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\n\t\t<ol v-else class=\"cn-conversation-thread__messages\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in shownRows\"\n\t\t\t\t:key=\"row.key\"\n\t\t\t\tclass=\"cn-conversation-thread__message\"\n\t\t\t\t:class=\"`cn-conversation-thread__message--${row.side}`\"\n\t\t\t\tdata-testid=\"cn-conversation-message\"\n\t\t\t\t:data-side=\"row.side\">\n\t\t\t\t<span class=\"cn-conversation-thread__avatar\" aria-hidden=\"true\">{{ row.initials }}</span>\n\t\t\t\t<div class=\"cn-conversation-thread__bubble\">\n\t\t\t\t\t<span class=\"cn-conversation-thread__meta\">\n\t\t\t\t\t\t<span class=\"hidden-visually\">{{ row.directionLabel }}</span>\n\t\t\t\t\t\t<span class=\"cn-conversation-thread__author\">{{ row.author }}</span>\n\t\t\t\t\t\t<time v-if=\"row.timeLabel\" :datetime=\"row.datetime || null\">{{ row.timeLabel }}</time>\n\t\t\t\t\t\t<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ol>\n\n\t\t<form\n\t\t\tv-if=\"allowReply\"\n\t\t\tclass=\"cn-conversation-thread__reply\"\n\t\t\tdata-testid=\"cn-conversation-reply\"\n\t\t\t@submit.prevent=\"onSend\">\n\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t<textarea\n\t\t\t\t:id=\"inputId\"\n\t\t\t\tv-model=\"draft\"\n\t\t\t\tclass=\"cn-conversation-thread__input\"\n\t\t\t\trows=\"2\"\n\t\t\t\t:placeholder=\"placeholder || null\"\n\t\t\t\t:disabled=\"sending\"\n\t\t\t\t:aria-describedby=\"errorMessage ? errorId : null\"\n\t\t\t\tdata-testid=\"cn-conversation-input\"\n\t\t\t\t@keydown.ctrl.enter.prevent=\"onSend\"\n\t\t\t\t@keydown.meta.enter.prevent=\"onSend\" />\n\t\t\t<p\n\t\t\t\tv-if=\"errorMessage\"\n\t\t\t\t:id=\"errorId\"\n\t\t\t\tclass=\"cn-conversation-thread__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-conversation-error\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport CnQuickFilterBar from '../CnQuickFilterBar/CnQuickFilterBar.vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nlet conversationThreadUid = 0\n\n/**\n * CnConversationThread: the messages between you and the other party, and a box to answer.\n *\n * Each message has an author, a time and a side: `us` for the organisation,\n * `them` for the resident or customer. The reply box emits `send`, and also\n * posts to `endpoint` when one is set. It needs no Talk and no other app:\n * where the messages are stored is the host's business.\n *\n * ```vue\n * <CnConversationThread\n * title=\"Contact with the resident\"\n * reply-label=\"Answer to the resident\"\n * :messages=\"[\n * { id: 1, author: 'Sanne de Vries', time: '2026-10-04T10:52:00', side: 'them',\n * text: 'Thank you. I would like to see all documents.' },\n * { id: 2, time: '2026-10-04T10:24:00', side: 'us',\n * text: 'Construction starts in March.' },\n * ]\"\n * @send=\"saveReply\" />\n * ```\n *\n * The `conversation` detail widget type renders this thread from a field on\n * the record.\n */\nexport default {\n\tname: 'CnConversationThread',\n\n\tcomponents: {\n\t\tCnQuickFilterBar,\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel? }`.\n\t\t * `side` is `us` or `them` (default `them`). `time` is an ISO\n\t\t * date, or any text to show as is. `channel` is shown after the time\n\t\t * (\"E-mail\", \"Phone note\").\n\t\t */\n\t\tmessages: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Heading of the thread. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/** Whether the reply box renders. Turn it off for a closed or read-only record. */\n\t\tallowReply: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label of the reply box. */\n\t\treplyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Reply'),\n\t\t},\n\n\t\t/** Placeholder inside the reply box. */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Label of the send button. */\n\t\tsendLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Send'),\n\t\t},\n\n\t\t/** Author shown on an `us` message that names none. */\n\t\tusLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'You'),\n\t\t},\n\n\t\t/**\n\t\t * URL the reply is POSTed to as `{ message }`. Empty means the host\n\t\t * handles `send` itself and nothing is requested.\n\t\t */\n\t\tendpoint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Extra fields sent along with `message` when `endpoint` is set. */\n\t\tpayload: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the messages are still loading; with none yet the thread says so. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw channel chips above the messages: All, then one per channel\n\t\t * with its count. Choosing one shows only that channel's messages.\n\t\t */\n\t\tchannelFilter: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Channel labels that always get a chip, in this order, also when no\n\t\t * message uses them yet. Channels the messages use follow.\n\t\t */\n\t\tchannels: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Shown when there are no messages yet. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No messages yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** A reply was submitted. Payload: the text. */\n\t\t'send',\n\t\t/** The endpoint accepted the reply. Payload: the response body. */\n\t\t'sent',\n\t\t/** The endpoint refused the reply. Payload: the error. */\n\t\t'error',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The channel the chips narrow the thread to; '' is All. */\n\t\t\tactiveChannel: '',\n\t\t\tsending: false,\n\t\t\terrorMessage: '',\n\t\t\ttitleId: `cn-conversation-title-${conversationThreadUid}`,\n\t\t\tinputId: `cn-conversation-input-${conversationThreadUid}`,\n\t\t\terrorId: `cn-conversation-error-${conversationThreadUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The messages, with author, side, time and initials resolved.\n\t\t *\n\t\t * @return {Array<{key: string, author: string, initials: string, side: string, text: string, timeLabel: string, datetime: string, channel: string, directionLabel: string}>} The rows.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\trows() {\n\t\t\treturn (Array.isArray(this.messages) ? this.messages : [])\n\t\t\t\t.filter((message) => message && typeof message === 'object')\n\t\t\t\t.map((message, index) => {\n\t\t\t\t\tconst side = message.side === 'us' ? 'us' : 'them'\n\t\t\t\t\tconst author = typeof message.author === 'string' && message.author !== ''\n\t\t\t\t\t\t? message.author\n\t\t\t\t\t\t: (side === 'us' ? this.usLabel : '')\n\t\t\t\t\tconst time = this.formatTime(message.time)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: message.id === undefined || message.id === null ? `message-${index}` : String(message.id),\n\t\t\t\t\t\tauthor,\n\t\t\t\t\t\tinitials: this.initialsOf(author),\n\t\t\t\t\t\tside,\n\t\t\t\t\t\ttext: message.text === undefined || message.text === null ? '' : String(message.text),\n\t\t\t\t\t\ttimeLabel: time.label,\n\t\t\t\t\t\tdatetime: time.datetime,\n\t\t\t\t\t\tchannel: typeof message.channel === 'string' ? message.channel : '',\n\t\t\t\t\t\tdirectionLabel: side === 'us' ? t('nextcloud-vue', 'Sent message') : t('nextcloud-vue', 'Received message'),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The channel labels with a chip: the declared ones, then the ones\n\t\t * the messages use.\n\t\t *\n\t\t * @return {Array<string>} The labels, each once.\n\t\t */\n\t\tchannelLabels() {\n\t\t\tconst declared = (Array.isArray(this.channels) ? this.channels : []).filter((label) => typeof label === 'string' && label !== '')\n\t\t\treturn [...new Set([...declared, ...this.rows.map((row) => row.channel).filter(Boolean)])]\n\t\t},\n\n\t\t/**\n\t\t * The chips: All, then each channel, each with its count. Empty\n\t\t * without `channelFilter` or without channels.\n\t\t *\n\t\t * @return {Array<{label: string, count: number}>} The chips.\n\t\t */\n\t\tchannelChips() {\n\t\t\tif (!this.channelFilter || this.channelLabels.length === 0) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ label: t('nextcloud-vue', 'All'), count: this.rows.length },\n\t\t\t\t...this.channelLabels.map((label) => ({ label, count: this.rows.filter((row) => row.channel === label).length })),\n\t\t\t]\n\t\t},\n\n\t\t/** @return {number} The pressed chip: 0 for All. */\n\t\tactiveChannelIndex() {\n\t\t\tconst index = this.channelLabels.indexOf(this.activeChannel)\n\t\t\treturn index < 0 ? 0 : index + 1\n\t\t},\n\n\t\t/** @return {Array<object>} The rows on the chosen channel, or all of them. */\n\t\tshownRows() {\n\t\t\tif (this.channelChips.length === 0 || this.activeChannelIndex === 0) {\n\t\t\t\treturn this.rows\n\t\t\t}\n\t\t\treturn this.rows.filter((row) => row.channel === this.activeChannel)\n\t\t},\n\n\t\t/**\n\t\t * Whether there is something to send and nothing in flight.\n\t\t *\n\t\t * @return {boolean} True when Send is enabled.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tcanSend() {\n\t\t\treturn !this.sending && this.draft.trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a thread without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Conversation')\n\t\t},\n\n\t\t/** @return {string} What the thread says while its messages load. */\n\t\tloadingText() {\n\t\t\treturn t('nextcloud-vue', 'Loading messages…')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Narrow the thread to the chip's channel; the All chip (0) clears it.\n\t\t *\n\t\t * @param {number|null} index The chip index.\n\t\t * @return {void}\n\t\t */\n\t\tonChannelChip(index) {\n\t\t\tthis.activeChannel = index > 0 ? (this.channelLabels[index - 1] || '') : ''\n\t\t},\n\n\t\t/**\n\t\t * Up to two initials of a name, for the avatar.\n\t\t *\n\t\t * @param {string} name The author.\n\t\t * @return {string} The initials, upper case.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tinitialsOf(name) {\n\t\t\tconst parts = String(name || '').trim().split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst first = parts[0].charAt(0)\n\t\t\tconst last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''\n\t\t\treturn (first + last).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Turn a message time into what the reader sees and what `<time>` carries.\n\t\t *\n\t\t * @param {unknown} value An ISO date, a Date, or text to show as is.\n\t\t * @return {{label: string, datetime: string}} The visible label and the machine value.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tformatTime(value) {\n\t\t\tif (value === null || value === undefined || value === '') {\n\t\t\t\treturn { label: '', datetime: '' }\n\t\t\t}\n\t\t\tconst date = value instanceof Date ? value : new Date(value)\n\t\t\tconst looksLikeDate = value instanceof Date || typeof value === 'number' || /^\\d{4}-\\d{2}-\\d{2}/.test(String(value))\n\t\t\tif (!looksLikeDate || Number.isNaN(date.getTime())) {\n\t\t\t\treturn { label: String(value), datetime: '' }\n\t\t\t}\n\t\t\tconst label = date.toLocaleString(userLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\treturn { label, datetime: date.toISOString() }\n\t\t},\n\n\t\t/**\n\t\t * Submit the reply: tell the host, and post it when an endpoint is set.\n\t\t *\n\t\t * Without an endpoint the box clears straight away, because the host\n\t\t * owns the send. With one it clears only when the request succeeds, so\n\t\t * a refused reply is still there to try again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread\n\t\t */\n\t\tasync onSend() {\n\t\t\tif (!this.canSend) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst text = this.draft.trim()\n\t\t\tthis.errorMessage = ''\n\t\t\tthis.$emit('send', text)\n\t\t\tif (!this.endpoint) {\n\t\t\t\tthis.draft = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.sending = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.endpoint), { ...this.payload, message: text })\n\t\t\t\tthis.draft = ''\n\t\t\t\tthis.$emit('sent', response?.data)\n\t\t\t} catch (error) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'The message could not be sent. Try again.')\n\t\t\t\tthis.$emit('error', error)\n\t\t\t} finally {\n\t\t\t\tthis.sending = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-conversation-thread {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__header {\n\talign-items: flex-start;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(3 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__header-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__title {\n\tfont-size: 1.1em;\n\tfont-weight: 700;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-conversation-thread__messages {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(3.5 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-conversation-thread__message {\n\tdisplay: flex;\n\tgap: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us {\n\tflex-direction: row-reverse;\n}\n\n.cn-conversation-thread__avatar {\n\talign-items: center;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 50%;\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: none;\n\tfont-size: 0.8em;\n\tfont-weight: 700;\n\theight: 34px;\n\tjustify-content: center;\n\twidth: 34px;\n}\n\n.cn-conversation-thread__bubble {\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tborder-start-start-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-main-text);\n\tdisplay: flex;\n\tflex: 1;\n\tflex-direction: column;\n\tgap: var(--default-grid-baseline);\n\tmin-width: 0;\n\tpadding: calc(3 * var(--default-grid-baseline)) calc(3.5 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__message--us .cn-conversation-thread__bubble {\n\tbackground: var(--color-primary-element-light);\n\tborder-start-end-radius: var(--border-radius-small, 4px);\n\tborder-start-start-radius: var(--border-radius-large);\n\tcolor: var(--color-primary-element-light-text);\n}\n\n.cn-conversation-thread__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tfont-size: 0.9em;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__author {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__text {\n\toverflow-wrap: anywhere;\n\twhite-space: pre-wrap;\n}\n\n.cn-conversation-thread__reply {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__input {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tresize: vertical;\n\twidth: 100%;\n}\n\n.cn-conversation-thread__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n\n.cn-conversation-thread__reply-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: flex-end;\n}\n\n.cn-conversation-thread__channel {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createElementBlock","_openBlock","_createBlock","_resolveDynamicComponent","_renderSlot","_toDisplayString","_Fragment","_renderList","_normalizeClass","_createElementVNode"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnConversationWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnConversationWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-0533e246";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|