@conduction/nextcloud-vue 2.143.0 → 2.144.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/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +2 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +3 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
- package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
- package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
- package/dist/esm/l10n/en.json.js +6 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +6 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +815 -363
- package/dist/esm/utils/fetchFilterCounts.js +5 -2
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/nextcloudDirectory.js +19 -6
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1347 -758
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +815 -363
- package/l10n/en.json +6 -0
- package/l10n/nl.json +6 -0
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
- package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
- package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +15 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +3 -1
- package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
- package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
- package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
- package/src/css/dialog.css +7 -1
- package/src/css/kpi-card.css +3 -0
- package/src/css/look-board-detail.css +317 -0
- package/src/css/look-board-index.css +14 -0
- package/src/css/look-board.css +25 -0
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
- package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
- package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
- package/src/integrations/builtin/calendar/eventStatus.js +63 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
- package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
- package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
- package/src/integrations/builtin/deck/deckCardParts.js +56 -0
- package/src/utils/fetchFilterCounts.js +5 -2
- package/src/utils/nextcloudDirectory.js +19 -6
|
@@ -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<div v-if=\"title || $slots['header-actions']\" class=\"cn-conversation-thread__header\">\n\t\t\t<div v-if=\"title\" class=\"cn-conversation-thread__heading\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"titleTag\"\n\t\t\t\t\t:id=\"titleId\"\n\t\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</component>\n\t\t\t\t<p v-if=\"subtitle\" class=\"cn-conversation-thread__subtitle\" data-testid=\"cn-conversation-subtitle\">\n\t\t\t\t\t{{ subtitle }}\n\t\t\t\t</p>\n\t\t\t</div>\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<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t\t<span v-if=\"row.status\" class=\"cn-conversation-thread__status\" data-testid=\"cn-conversation-status\">{{ row.status }}</span>\n\t\t\t\t\t\t<span v-if=\"showDirection\"\n\t\t\t\t\t\t\tclass=\"cn-conversation-thread__direction\"\n\t\t\t\t\t\t\tdata-testid=\"cn-conversation-direction\"\n\t\t\t\t\t\t\taria-hidden=\"true\">{{ row.directionWord }}</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</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t\t<span v-if=\"row.note\" class=\"cn-conversation-thread__note\" data-testid=\"cn-conversation-note\">{{ row.note }}</span>\n\t\t\t\t\t<span v-if=\"row.actions.length > 0\" class=\"cn-conversation-thread__message-actions\">\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:disabled=\"action.disabled === true\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-conversation-action-${action.id}`\"\n\t\t\t\t\t\t\t@click=\"$emit('message-action', { action: action.id, message: row.key })\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</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<!-- @slot reply-tools Tools above the reply box, for example a template picker. -->\n\t\t\t<!-- @binding {Function} setDraft Puts a text in the reply box. -->\n\t\t\t<slot name=\"reply-tools\" :setDraft=\"setDraft\" />\n\t\t\t<div class=\"cn-conversation-thread__reply-head\">\n\t\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t\t<span v-if=\"replyHint\" class=\"cn-conversation-thread__reply-hint\" data-testid=\"cn-conversation-reply-hint\">{{ replyHint }}</span>\n\t\t\t</div>\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<!-- The option at the start and the buttons at the end of one row. -->\n\t\t\t<div class=\"cn-conversation-thread__reply-foot\">\n\t\t\t\t<label v-if=\"optionShown\" class=\"cn-conversation-thread__reply-option\" data-testid=\"cn-conversation-reply-option\">\n\t\t\t\t\t<input v-model=\"optionOn\" type=\"checkbox\" :disabled=\"sending\">\n\t\t\t\t\t<span>{{ replyOption.label }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Send :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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 Send from 'vue-material-design-icons/Send.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\tSend,\n\t},\n\n\tinject: {\n\t\t/** The host app's translate, for the All chip in the host's word. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel?, status?, actions?, note? }`.\n\t\t * `status` is shown after the channel (\"Failed\"); `actions` are\n\t\t * `{ id, label, disabled? }` buttons under the text, each emitting\n\t\t * `message-action`.\n\t\t * `note` is a muted line under the text.\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\n\t\t * author (\"E-mail\", \"Phone note\"); `note` is a muted line under the text.\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/** A line under the title, such as \"Oldest first, with the channel of each message\". */\n\t\tsubtitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Say each message's direction in words before its time (\"Incoming · 3 Oct\"), not only to a screen reader. */\n\t\tshowDirection: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\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/** A line beside the reply label saying where the answer goes, such as \"Goes to the message box in the portal\". */\n\t\treplyHint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A switch under the reply box whose state is sent with the reply:\n\t\t * `{ label, key, value? }`, `value` the starting state (on by default).\n\t\t * The reply's `send` event carries `{ [key]: state }` after the text.\n\t\t *\n\t\t * @type {{label: string, key: string, value?: boolean}|null}\n\t\t */\n\t\treplyOption: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\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\t/** A message action was pressed. Payload: `{ action, message }`, the action id and the message key. */\n\t\t'message-action',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The state of the reply option's switch. */\n\t\t\toptionOn: this.replyOption?.value !== false,\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, directionWord: string, note: 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\tstatus: typeof message.status === 'string' ? message.status : '',\n\t\t\t\t\t\tactions: Array.isArray(message.actions) ? message.actions.filter((action) => action && action.id && action.label) : [],\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\tdirectionWord: side === 'us' ? t('nextcloud-vue', 'Outgoing') : t('nextcloud-vue', 'Incoming'),\n\t\t\t\t\t\tnote: typeof message.note === 'string' ? message.note : '',\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: this.allLabel, 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/**\n\t\t * Whether the reply option renders: it has a label and a key.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\toptionShown() {\n\t\t\tconst option = this.replyOption\n\t\t\treturn Boolean(option && typeof option.label === 'string' && option.label !== '' && typeof option.key === 'string' && option.key !== '')\n\t\t},\n\n\t\t/**\n\t\t * What travels with the reply besides its text.\n\t\t *\n\t\t * @return {object} `{ [key]: state }`, or {} without an option.\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\treplyExtras() {\n\t\t\treturn this.optionShown ? { [this.replyOption.key]: this.optionOn === true } : {}\n\t\t},\n\n\t\t/**\n\t\t * The All chip: the host's word first, as its own filter chips read\n\t\t * (\"Alles\"), then the library's.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/conversation-all-chip-host-word/specs/conversation-source/spec.md#requirement-the-all-chip-reads-the-hosts-word\n\t\t */\n\t\tallLabel() {\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate('All') : 'All'\n\t\t\treturn typeof host === 'string' && host !== '' && host !== 'All' ? host : t('nextcloud-vue', 'All')\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 * Put a text in the reply box (a template a host tool picked).\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(text) {\n\t\t\tthis.draft = typeof text === 'string' ? text : ''\n\t\t},\n\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, this.replyExtras)\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, ...this.replyExtras, 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__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 700;\n}\n\n.cn-conversation-thread__message-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tmargin-top: calc(2 * 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__heading {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 3px;\n\tmin-width: 0;\n}\n\n.cn-conversation-thread__subtitle {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__direction,\n.cn-conversation-thread__meta time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-conversation-thread__direction::after {\n\tcontent: ' ·';\n}\n\n.cn-conversation-thread__note {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\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\talign-items: center;\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-head {\n\talign-items: baseline;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__reply-hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-conversation-thread__reply-option {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: calc(2 * var(--default-grid-baseline));\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-foot {\n\talign-items: center;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-actions {\n\tmargin-inline-start: auto;\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
|
+
{"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<div v-if=\"title\" class=\"cn-conversation-thread__heading\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"titleTag\"\n\t\t\t\t\t:id=\"titleId\"\n\t\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</component>\n\t\t\t\t<!-- A div, not a <p>: a theme that sets every paragraph's size with\n\t\t\t\t !important would otherwise override the 14px subtitle. -->\n\t\t\t\t<div v-if=\"subtitle\" class=\"cn-conversation-thread__subtitle\" data-testid=\"cn-conversation-subtitle\">\n\t\t\t\t\t{{ subtitle }}\n\t\t\t\t</div>\n\t\t\t</div>\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<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t\t<span v-if=\"row.status\" class=\"cn-conversation-thread__status\" data-testid=\"cn-conversation-status\">{{ row.status }}</span>\n\t\t\t\t\t\t<span v-if=\"showDirection\"\n\t\t\t\t\t\t\tclass=\"cn-conversation-thread__direction\"\n\t\t\t\t\t\t\tdata-testid=\"cn-conversation-direction\"\n\t\t\t\t\t\t\taria-hidden=\"true\">{{ row.directionWord }}</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</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t\t<span v-if=\"row.note\" class=\"cn-conversation-thread__note\" data-testid=\"cn-conversation-note\">{{ row.note }}</span>\n\t\t\t\t\t<span v-if=\"row.actions.length > 0\" class=\"cn-conversation-thread__message-actions\">\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:disabled=\"action.disabled === true\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-conversation-action-${action.id}`\"\n\t\t\t\t\t\t\t@click=\"$emit('message-action', { action: action.id, message: row.key })\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</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<!-- @slot reply-tools Tools above the reply box, for example a template picker. -->\n\t\t\t<!-- @binding {Function} setDraft Puts a text in the reply box. -->\n\t\t\t<slot name=\"reply-tools\" :setDraft=\"setDraft\" />\n\t\t\t<div class=\"cn-conversation-thread__reply-head\">\n\t\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t\t<span v-if=\"replyHint\" class=\"cn-conversation-thread__reply-hint\" data-testid=\"cn-conversation-reply-hint\">{{ replyHint }}</span>\n\t\t\t</div>\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<!-- The option at the start and the buttons at the end of one row. -->\n\t\t\t<div class=\"cn-conversation-thread__reply-foot\">\n\t\t\t\t<label v-if=\"optionShown\" class=\"cn-conversation-thread__reply-option\" data-testid=\"cn-conversation-reply-option\">\n\t\t\t\t\t<input v-model=\"optionOn\" type=\"checkbox\" :disabled=\"sending\">\n\t\t\t\t\t<span>{{ replyOption.label }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Send :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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 Send from 'vue-material-design-icons/Send.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\tSend,\n\t},\n\n\tinject: {\n\t\t/** The host app's translate, for the All chip in the host's word. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel?, status?, actions?, note? }`.\n\t\t * `status` is shown after the channel (\"Failed\"); `actions` are\n\t\t * `{ id, label, disabled? }` buttons under the text, each emitting\n\t\t * `message-action`.\n\t\t * `note` is a muted line under the text.\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\n\t\t * author (\"E-mail\", \"Phone note\"); `note` is a muted line under the text.\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/** A line under the title, such as \"Oldest first, with the channel of each message\". */\n\t\tsubtitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Say each message's direction in words before its time (\"Incoming · 3 Oct\"), not only to a screen reader. */\n\t\tshowDirection: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\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/** A line beside the reply label saying where the answer goes, such as \"Goes to the message box in the portal\". */\n\t\treplyHint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A switch under the reply box whose state is sent with the reply:\n\t\t * `{ label, key, value? }`, `value` the starting state (on by default).\n\t\t * The reply's `send` event carries `{ [key]: state }` after the text.\n\t\t *\n\t\t * @type {{label: string, key: string, value?: boolean}|null}\n\t\t */\n\t\treplyOption: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\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\t/** A message action was pressed. Payload: `{ action, message }`, the action id and the message key. */\n\t\t'message-action',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The state of the reply option's switch. */\n\t\t\toptionOn: this.replyOption?.value !== false,\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, directionWord: string, note: 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\tstatus: typeof message.status === 'string' ? message.status : '',\n\t\t\t\t\t\tactions: Array.isArray(message.actions) ? message.actions.filter((action) => action && action.id && action.label) : [],\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\tdirectionWord: side === 'us' ? t('nextcloud-vue', 'Outgoing') : t('nextcloud-vue', 'Incoming'),\n\t\t\t\t\t\tnote: typeof message.note === 'string' ? message.note : '',\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: this.allLabel, 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/**\n\t\t * Whether the reply option renders: it has a label and a key.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\toptionShown() {\n\t\t\tconst option = this.replyOption\n\t\t\treturn Boolean(option && typeof option.label === 'string' && option.label !== '' && typeof option.key === 'string' && option.key !== '')\n\t\t},\n\n\t\t/**\n\t\t * What travels with the reply besides its text.\n\t\t *\n\t\t * @return {object} `{ [key]: state }`, or {} without an option.\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\treplyExtras() {\n\t\t\treturn this.optionShown ? { [this.replyOption.key]: this.optionOn === true } : {}\n\t\t},\n\n\t\t/**\n\t\t * The All chip: the host's word first, as its own filter chips read\n\t\t * (\"Alles\"), then the library's.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/conversation-all-chip-host-word/specs/conversation-source/spec.md#requirement-the-all-chip-reads-the-hosts-word\n\t\t */\n\t\tallLabel() {\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate('All') : 'All'\n\t\t\treturn typeof host === 'string' && host !== '' && host !== 'All' ? host : t('nextcloud-vue', 'All')\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 * Put a text in the reply box (a template a host tool picked).\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(text) {\n\t\t\tthis.draft = typeof text === 'string' ? text : ''\n\t\t},\n\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, this.replyExtras)\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, ...this.replyExtras, 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__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 700;\n}\n\n.cn-conversation-thread__message-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tmargin-top: calc(2 * 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__heading {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 3px;\n\tmin-width: 0;\n}\n\n.cn-conversation-thread__subtitle {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tline-height: normal;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__direction,\n.cn-conversation-thread__meta time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-conversation-thread__direction::after {\n\tcontent: ' ·';\n}\n\n.cn-conversation-thread__note {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\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\talign-items: center;\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-head {\n\talign-items: baseline;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__reply-hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-conversation-thread__reply-option {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: calc(2 * var(--default-grid-baseline));\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-foot {\n\talign-items: center;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-actions {\n\tmargin-inline-start: auto;\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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"}
|
|
@@ -117,7 +117,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
117
117
|
_: 1 /* STABLE */
|
|
118
118
|
}, 8 /* PROPS */, ["id"])),
|
|
119
119
|
($props.subtitle)
|
|
120
|
-
? (openBlock(), createElementBlock("
|
|
120
|
+
? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString($props.subtitle), 1 /* TEXT */))
|
|
121
121
|
: createCommentVNode("v-if", true)
|
|
122
122
|
]))
|
|
123
123
|
: createCommentVNode("v-if", true),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnConversationThread.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue?vue&type=template&id=0d461ee4&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<div v-if=\"title\" class=\"cn-conversation-thread__heading\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"titleTag\"\n\t\t\t\t\t:id=\"titleId\"\n\t\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</component>\n\t\t\t\t<p v-if=\"subtitle\" class=\"cn-conversation-thread__subtitle\" data-testid=\"cn-conversation-subtitle\">\n\t\t\t\t\t{{ subtitle }}\n\t\t\t\t</p>\n\t\t\t</div>\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<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t\t<span v-if=\"row.status\" class=\"cn-conversation-thread__status\" data-testid=\"cn-conversation-status\">{{ row.status }}</span>\n\t\t\t\t\t\t<span v-if=\"showDirection\"\n\t\t\t\t\t\t\tclass=\"cn-conversation-thread__direction\"\n\t\t\t\t\t\t\tdata-testid=\"cn-conversation-direction\"\n\t\t\t\t\t\t\taria-hidden=\"true\">{{ row.directionWord }}</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</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t\t<span v-if=\"row.note\" class=\"cn-conversation-thread__note\" data-testid=\"cn-conversation-note\">{{ row.note }}</span>\n\t\t\t\t\t<span v-if=\"row.actions.length > 0\" class=\"cn-conversation-thread__message-actions\">\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:disabled=\"action.disabled === true\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-conversation-action-${action.id}`\"\n\t\t\t\t\t\t\t@click=\"$emit('message-action', { action: action.id, message: row.key })\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</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<!-- @slot reply-tools Tools above the reply box, for example a template picker. -->\n\t\t\t<!-- @binding {Function} setDraft Puts a text in the reply box. -->\n\t\t\t<slot name=\"reply-tools\" :setDraft=\"setDraft\" />\n\t\t\t<div class=\"cn-conversation-thread__reply-head\">\n\t\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t\t<span v-if=\"replyHint\" class=\"cn-conversation-thread__reply-hint\" data-testid=\"cn-conversation-reply-hint\">{{ replyHint }}</span>\n\t\t\t</div>\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<!-- The option at the start and the buttons at the end of one row. -->\n\t\t\t<div class=\"cn-conversation-thread__reply-foot\">\n\t\t\t\t<label v-if=\"optionShown\" class=\"cn-conversation-thread__reply-option\" data-testid=\"cn-conversation-reply-option\">\n\t\t\t\t\t<input v-model=\"optionOn\" type=\"checkbox\" :disabled=\"sending\">\n\t\t\t\t\t<span>{{ replyOption.label }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Send :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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 Send from 'vue-material-design-icons/Send.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\tSend,\n\t},\n\n\tinject: {\n\t\t/** The host app's translate, for the All chip in the host's word. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel?, status?, actions?, note? }`.\n\t\t * `status` is shown after the channel (\"Failed\"); `actions` are\n\t\t * `{ id, label, disabled? }` buttons under the text, each emitting\n\t\t * `message-action`.\n\t\t * `note` is a muted line under the text.\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\n\t\t * author (\"E-mail\", \"Phone note\"); `note` is a muted line under the text.\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/** A line under the title, such as \"Oldest first, with the channel of each message\". */\n\t\tsubtitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Say each message's direction in words before its time (\"Incoming · 3 Oct\"), not only to a screen reader. */\n\t\tshowDirection: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\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/** A line beside the reply label saying where the answer goes, such as \"Goes to the message box in the portal\". */\n\t\treplyHint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A switch under the reply box whose state is sent with the reply:\n\t\t * `{ label, key, value? }`, `value` the starting state (on by default).\n\t\t * The reply's `send` event carries `{ [key]: state }` after the text.\n\t\t *\n\t\t * @type {{label: string, key: string, value?: boolean}|null}\n\t\t */\n\t\treplyOption: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\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\t/** A message action was pressed. Payload: `{ action, message }`, the action id and the message key. */\n\t\t'message-action',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The state of the reply option's switch. */\n\t\t\toptionOn: this.replyOption?.value !== false,\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, directionWord: string, note: 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\tstatus: typeof message.status === 'string' ? message.status : '',\n\t\t\t\t\t\tactions: Array.isArray(message.actions) ? message.actions.filter((action) => action && action.id && action.label) : [],\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\tdirectionWord: side === 'us' ? t('nextcloud-vue', 'Outgoing') : t('nextcloud-vue', 'Incoming'),\n\t\t\t\t\t\tnote: typeof message.note === 'string' ? message.note : '',\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: this.allLabel, 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/**\n\t\t * Whether the reply option renders: it has a label and a key.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\toptionShown() {\n\t\t\tconst option = this.replyOption\n\t\t\treturn Boolean(option && typeof option.label === 'string' && option.label !== '' && typeof option.key === 'string' && option.key !== '')\n\t\t},\n\n\t\t/**\n\t\t * What travels with the reply besides its text.\n\t\t *\n\t\t * @return {object} `{ [key]: state }`, or {} without an option.\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\treplyExtras() {\n\t\t\treturn this.optionShown ? { [this.replyOption.key]: this.optionOn === true } : {}\n\t\t},\n\n\t\t/**\n\t\t * The All chip: the host's word first, as its own filter chips read\n\t\t * (\"Alles\"), then the library's.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/conversation-all-chip-host-word/specs/conversation-source/spec.md#requirement-the-all-chip-reads-the-hosts-word\n\t\t */\n\t\tallLabel() {\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate('All') : 'All'\n\t\t\treturn typeof host === 'string' && host !== '' && host !== 'All' ? host : t('nextcloud-vue', 'All')\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 * Put a text in the reply box (a template a host tool picked).\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(text) {\n\t\t\tthis.draft = typeof text === 'string' ? text : ''\n\t\t},\n\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, this.replyExtras)\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, ...this.replyExtras, 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__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 700;\n}\n\n.cn-conversation-thread__message-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tmargin-top: calc(2 * 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__heading {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 3px;\n\tmin-width: 0;\n}\n\n.cn-conversation-thread__subtitle {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__direction,\n.cn-conversation-thread__meta time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-conversation-thread__direction::after {\n\tcontent: ' ·';\n}\n\n.cn-conversation-thread__note {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\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\talign-items: center;\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-head {\n\talign-items: baseline;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__reply-hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-conversation-thread__reply-option {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: calc(2 * var(--default-grid-baseline));\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-foot {\n\talign-items: center;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-actions {\n\tmargin-inline-start: auto;\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","_renderSlot","_Fragment","_renderList","_normalizeClass","_createElementVNode","_createTextVNode","_createVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnConversationThread.vue3.js","sources":["../../../../src/components/CnConversationThread/CnConversationThread.vue?vue&type=template&id=e2600a68&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<div v-if=\"title\" class=\"cn-conversation-thread__heading\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"titleTag\"\n\t\t\t\t\t:id=\"titleId\"\n\t\t\t\t\tclass=\"cn-conversation-thread__title\">\n\t\t\t\t\t{{ title }}\n\t\t\t\t</component>\n\t\t\t\t<!-- A div, not a <p>: a theme that sets every paragraph's size with\n\t\t\t\t !important would otherwise override the 14px subtitle. -->\n\t\t\t\t<div v-if=\"subtitle\" class=\"cn-conversation-thread__subtitle\" data-testid=\"cn-conversation-subtitle\">\n\t\t\t\t\t{{ subtitle }}\n\t\t\t\t</div>\n\t\t\t</div>\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<span v-if=\"row.channel\" class=\"cn-conversation-thread__channel\" data-testid=\"cn-conversation-channel\">{{ row.channel }}</span>\n\t\t\t\t\t\t<span v-if=\"row.status\" class=\"cn-conversation-thread__status\" data-testid=\"cn-conversation-status\">{{ row.status }}</span>\n\t\t\t\t\t\t<span v-if=\"showDirection\"\n\t\t\t\t\t\t\tclass=\"cn-conversation-thread__direction\"\n\t\t\t\t\t\t\tdata-testid=\"cn-conversation-direction\"\n\t\t\t\t\t\t\taria-hidden=\"true\">{{ row.directionWord }}</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</span>\n\t\t\t\t\t<span class=\"cn-conversation-thread__text\">{{ row.text }}</span>\n\t\t\t\t\t<span v-if=\"row.note\" class=\"cn-conversation-thread__note\" data-testid=\"cn-conversation-note\">{{ row.note }}</span>\n\t\t\t\t\t<span v-if=\"row.actions.length > 0\" class=\"cn-conversation-thread__message-actions\">\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-for=\"action in row.actions\"\n\t\t\t\t\t\t\t:key=\"action.id\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:disabled=\"action.disabled === true\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-conversation-action-${action.id}`\"\n\t\t\t\t\t\t\t@click=\"$emit('message-action', { action: action.id, message: row.key })\">\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</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<!-- @slot reply-tools Tools above the reply box, for example a template picker. -->\n\t\t\t<!-- @binding {Function} setDraft Puts a text in the reply box. -->\n\t\t\t<slot name=\"reply-tools\" :setDraft=\"setDraft\" />\n\t\t\t<div class=\"cn-conversation-thread__reply-head\">\n\t\t\t\t<label :for=\"inputId\" class=\"cn-conversation-thread__reply-label\">{{ replyLabel }}</label>\n\t\t\t\t<span v-if=\"replyHint\" class=\"cn-conversation-thread__reply-hint\" data-testid=\"cn-conversation-reply-hint\">{{ replyHint }}</span>\n\t\t\t</div>\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<!-- The option at the start and the buttons at the end of one row. -->\n\t\t\t<div class=\"cn-conversation-thread__reply-foot\">\n\t\t\t\t<label v-if=\"optionShown\" class=\"cn-conversation-thread__reply-option\" data-testid=\"cn-conversation-reply-option\">\n\t\t\t\t\t<input v-model=\"optionOn\" type=\"checkbox\" :disabled=\"sending\">\n\t\t\t\t\t<span>{{ replyOption.label }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-conversation-thread__reply-actions\">\n\t\t\t\t\t<!-- @slot reply-actions Extra buttons before Send, for example \"Save draft\". -->\n\t\t\t\t\t<!-- @binding {string} draft The text in the reply box. -->\n\t\t\t\t\t<slot name=\"reply-actions\" :draft=\"draft\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"!canSend\"\n\t\t\t\t\t\tdata-testid=\"cn-conversation-send\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Send :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ sendLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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 Send from 'vue-material-design-icons/Send.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\tSend,\n\t},\n\n\tinject: {\n\t\t/** The host app's translate, for the All chip in the host's word. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The messages, oldest first: `{ id?, author?, time?, text, side?, channel?, status?, actions?, note? }`.\n\t\t * `status` is shown after the channel (\"Failed\"); `actions` are\n\t\t * `{ id, label, disabled? }` buttons under the text, each emitting\n\t\t * `message-action`.\n\t\t * `note` is a muted line under the text.\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\n\t\t * author (\"E-mail\", \"Phone note\"); `note` is a muted line under the text.\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/** A line under the title, such as \"Oldest first, with the channel of each message\". */\n\t\tsubtitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Say each message's direction in words before its time (\"Incoming · 3 Oct\"), not only to a screen reader. */\n\t\tshowDirection: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\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/** A line beside the reply label saying where the answer goes, such as \"Goes to the message box in the portal\". */\n\t\treplyHint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A switch under the reply box whose state is sent with the reply:\n\t\t * `{ label, key, value? }`, `value` the starting state (on by default).\n\t\t * The reply's `send` event carries `{ [key]: state }` after the text.\n\t\t *\n\t\t * @type {{label: string, key: string, value?: boolean}|null}\n\t\t */\n\t\treplyOption: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\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\t/** A message action was pressed. Payload: `{ action, message }`, the action id and the message key. */\n\t\t'message-action',\n\t],\n\n\tdata() {\n\t\tconversationThreadUid += 1\n\t\treturn {\n\t\t\tdraft: '',\n\t\t\t/** The state of the reply option's switch. */\n\t\t\toptionOn: this.replyOption?.value !== false,\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, directionWord: string, note: 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\tstatus: typeof message.status === 'string' ? message.status : '',\n\t\t\t\t\t\tactions: Array.isArray(message.actions) ? message.actions.filter((action) => action && action.id && action.label) : [],\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\tdirectionWord: side === 'us' ? t('nextcloud-vue', 'Outgoing') : t('nextcloud-vue', 'Incoming'),\n\t\t\t\t\t\tnote: typeof message.note === 'string' ? message.note : '',\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: this.allLabel, 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/**\n\t\t * Whether the reply option renders: it has a label and a key.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\toptionShown() {\n\t\t\tconst option = this.replyOption\n\t\t\treturn Boolean(option && typeof option.label === 'string' && option.label !== '' && typeof option.key === 'string' && option.key !== '')\n\t\t},\n\n\t\t/**\n\t\t * What travels with the reply besides its text.\n\t\t *\n\t\t * @return {object} `{ [key]: state }`, or {} without an option.\n\t\t * @spec openspec/changes/conversation-reply-option/specs/conversation-source/spec.md#requirement-a-reply-can-carry-a-hint-and-an-option\n\t\t */\n\t\treplyExtras() {\n\t\t\treturn this.optionShown ? { [this.replyOption.key]: this.optionOn === true } : {}\n\t\t},\n\n\t\t/**\n\t\t * The All chip: the host's word first, as its own filter chips read\n\t\t * (\"Alles\"), then the library's.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/conversation-all-chip-host-word/specs/conversation-source/spec.md#requirement-the-all-chip-reads-the-hosts-word\n\t\t */\n\t\tallLabel() {\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate('All') : 'All'\n\t\t\treturn typeof host === 'string' && host !== '' && host !== 'All' ? host : t('nextcloud-vue', 'All')\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 * Put a text in the reply box (a template a host tool picked).\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(text) {\n\t\t\tthis.draft = typeof text === 'string' ? text : ''\n\t\t},\n\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, this.replyExtras)\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, ...this.replyExtras, 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__status {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 700;\n}\n\n.cn-conversation-thread__message-actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tmargin-top: calc(2 * 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__heading {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 3px;\n\tmin-width: 0;\n}\n\n.cn-conversation-thread__subtitle {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tline-height: normal;\n\tmargin: 0;\n}\n\n.cn-conversation-thread__direction,\n.cn-conversation-thread__meta time {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-conversation-thread__direction::after {\n\tcontent: ' ·';\n}\n\n.cn-conversation-thread__note {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\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\talign-items: center;\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-head {\n\talign-items: baseline;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-label {\n\tfont-weight: 600;\n}\n\n.cn-conversation-thread__reply-hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-conversation-thread__reply-option {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: calc(2 * var(--default-grid-baseline));\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-foot {\n\talign-items: center;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tjustify-content: space-between;\n}\n\n.cn-conversation-thread__reply-actions {\n\tmargin-inline-start: auto;\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","_renderSlot","_Fragment","_renderList","_normalizeClass","_createElementVNode","_createTextVNode","_createVNode"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnEventsCard.vue2.js';
|
|
|
2
2
|
import { render } from './CnEventsCard.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-bb3ee7b4";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -231,6 +231,15 @@ var script = {
|
|
|
231
231
|
return summary === '' ? translate('nextcloud-vue', 'Appointment') : summary
|
|
232
232
|
},
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* @param {object} event The event.
|
|
236
|
+
* @return {string} The day of the month it starts on, '' when it has no readable start.
|
|
237
|
+
*/
|
|
238
|
+
dayOf(event) {
|
|
239
|
+
const parsed = new Date(String(event?.dtstart ?? '').trim());
|
|
240
|
+
return Number.isNaN(parsed.getTime()) ? '' : String(parsed.getDate())
|
|
241
|
+
},
|
|
242
|
+
|
|
234
243
|
/**
|
|
235
244
|
* @param {object} event The event.
|
|
236
245
|
* @return {string} When it is: its start and end, or whichever it has.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEventsCard.vue2.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnCalendarEventCreate","CnCalendarEventPicker","t"],"mappings":";;;;;;;;;AAsEA;;AAEC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,cAAc;;AAEpB,CAAA,UAAU,EAAE,yBAAEA,UAAqB,yBAAEC,YAAqB,EAAE,eAAe;;AAE3E,CAAA,MAAM,EAAE;;AAEP,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,KAAK,EAAE;AACN;;;AAGC;AACA;AACA;;AAED,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;AAEvC;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;;;AAGvC,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI;;;EAGjD,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;;AAE5D;;AAEC;AACA;AACA;;;;AAIA;AACA;;AAED,EAAA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG;;AAE9C;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;;;AAGzC,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;EAC1C;;AAED,CAAA,KAAK,EAAE,CAAC,QAAQ,CAAC;;AAEjB,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,MAAM,EAAE,EAAE;GACV,OAAO,EAAE,KAAK;GACd,MAAM,EAAE,KAAK;AACb,GAAA,UAAU,EAAE,KAAK;AACjB,GAAA,UAAU,EAAE,KAAK;GACjB,SAAS,EAAE,EAAE;AACd,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;;EAED,GAAG,GAAG;GACL,MAAM,OAAM,GAAI,IAAI,CAAC,OAAM,IAAK,GAAC;AACjC,GAAA,MAAM,SAAS,IAAI,CAAC,WAAW,OAAO,CAAC,MAAK,IAAK,OAAO,OAAO,CAAC,WAAW,QAAO,GAAI,OAAO,CAAC,MAAK,GAAI,IAAI,EAAA;AAC3G,GAAA,MAAM,QAAS,GAAE,IAAI,CAAC,QAAO,KAAM,OAAO,OAAO,CAAC,QAAS,KAAI,QAAS,GAAE,OAAO,CAAC,QAAS,GAAE,EAAE,EAAA;GAC/F,OAAO,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAO,EAAG,EAAE,QAAQ,CAAE,IAAG,EAAC;GACzG;;AAED;EACA,SAAS,GAAG;AACX,GAAA,IAAI,IAAI,CAAC,UAAU,EAAE;AACpB,IAAA,OAAO,IAAI,CAAC,UAAS;AACtB,IAAA;GACA,MAAM,QAAS,GAAE,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAO,CAAC,UAAQ;AACtD,GAAA,IAAI,OAAO,aAAa,YAAY,QAAS,KAAI,EAAE,EAAE;AACpD,IAAA,OAAO,OAAO,IAAI,CAAC,WAAU,KAAM,UAAW,GAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAE,GAAE,QAAO;AACrF,IAAA;AACA,GAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,qBAAqB,CAAA;GAC/C;;AAED;EACA,UAAU,GAAG;AACZ,GAAA,OAAO,IAAI,CAAC,WAAU,IAAKA,SAAC,CAAC,eAAe,EAAE,sCAAsC,CAAA;GACpF;EACD;;AAED,CAAA,KAAK,EAAE;EACN,GAAG,EAAE;AACJ,GAAA,SAAS,EAAE,IAAI;AACf,GAAA,OAAO,GAAG;IACT,IAAI,CAAC,IAAI,GAAC;AACV,IAAA,IAAI,CAAC,YAAY,GAAC;IAClB;GACD;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;;AAIC;;;AAGD,EAAA,YAAY,GAAG;AACd,GAAA,MAAM,KAAM,GAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAA,GAAI,IAAI,CAAC,OAAO,CAAC,aAAY,GAAI,GAAC;GACzF,MAAM,KAAI,GAAI;IACb,IAAI,EAAE,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,sBAAsB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,UAAW,GAAE,KAAK,EAAA,EAAG;AACvH,IAAA,MAAM,EAAE;AACP,KAAA,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,qBAAqB,CAAC;KAChD,IAAI,EAAE,OAAO,IAAI,CAAC,OAAO,EAAE,UAAS,KAAM,QAAO,IAAK,IAAI,CAAC,OAAO,CAAC,UAAS,KAAM,EAAC,GAAI,IAAI,CAAC,OAAO,CAAC,UAAW,GAAE,MAAM;AACvH,KAAA,IAAI,IAAI,CAAC,OAAO,EAAE,aAAY,KAAM,WAAU,GAAI,EAAG,GAAE,EAAE,OAAO,EAAE,SAAQ,EAAG,CAAC;AAC9E,KAAA,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,UAAW,GAAE,KAAG,EAAG;KACrC;AACF,KAAA;GACA,MAAM,IAAK,GAAE,CAAC,IAAI,MAAM,IAAI,CAAC,GAAI,KAAI,EAAG,GAAE,EAAC,GAAI,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK,CAAC,GAAG,CAAE,IAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,EAAA;GAC9I,MAAM,UAAU,IAAI,CAAC,KAAK,EAAA;GAC1B,MAAM,MAAK,GAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAE,GAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAE,GAAE,GAAC;AAC5H;;;GAGA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAA,EAAG,EAAA;GAC3D;;AAED;;AAEC;;;AAGD,EAAA,cAAc,GAAG;AAChB,GAAA,IAAI,CAAC,UAAS,GAAI,MAAI;GACtB,IAAI,CAAC,IAAI,GAAC;GACV;;AAED;AACC;AACA;AACA;;;EAGD,MAAM,YAAY,CAAC,OAAO,EAAE;AAC3B,GAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACtB,GAAA,MAAM,MAAK,GAAI,MAAM,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAA;AACnE,GAAA,IAAI,CAAC,SAAQ,GAAI,MAAM,CAAC,EAAC,GAAI,KAAK,MAAM,CAAC,MAAI;GAC7C,IAAI,MAAM,CAAC,EAAE,EAAE;IACd,IAAI,CAAC,IAAI,GAAC;AACX,IAAA;GACA;;AAED;;AAEC;AACA;;;EAGD,MAAM,IAAI,GAAG;AACZ,GAAA,MAAM,GAAE,GAAI,IAAI,CAAC,IAAE;GACnB,IAAI,GAAE,KAAM,EAAE,EAAE;IACf,IAAI,CAAC,MAAO,GAAE,GAAC;IACf,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,MAAO,GAAE,MAAI;AAClB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACxE,IAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;KACjB,IAAI,CAAC,MAAO,GAAE,GAAC;AACf,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;KACjB,MAAK;AACN,KAAA;IACA,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;IACjC,MAAM,IAAK,GAAE,IAAG,IAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAA,GAAI,IAAI,CAAC,OAAQ,GAAE,KAAG;AACrE,IAAA,IAAI,CAAC,MAAK,GAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAA,GAAI,IAAG,GAAI,GAAC;IAC3C,CAAA,OAAO,KAAK,EAAE;AACf;IACA,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,EAAA;IAC/D,IAAI,CAAC,MAAO,GAAE,GAAC;AACf,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED;;;;;AAKA,EAAA,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE;AACnB,GAAA,OAAO,MAAM,CAAC,KAAK,EAAE,EAAC,IAAK,KAAK,EAAE,GAAE,IAAK,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,OAAM,IAAK,EAAE,CAAC,CAAC,CAAA;GAC3E;;AAED;;;;AAIA,EAAA,SAAS,CAAC,KAAK,EAAE;AAChB,GAAA,MAAM,OAAQ,GAAE,MAAM,CAAC,KAAK,EAAE,OAAM,IAAK,EAAE,CAAC,CAAC,IAAI,GAAC;GAClD,OAAO,OAAQ,KAAI,EAAG,GAAEA,SAAC,CAAC,eAAe,EAAE,aAAa,CAAA,GAAI,OAAM;GAClE;;AAED;;;;EAIA,MAAM,CAAC,KAAK,EAAE;AACb,GAAA,MAAM,KAAM,GAAE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAA;AACzC,GAAA,MAAM,GAAE,GAAI,YAAY,CAAC,KAAK,EAAE,KAAK,EAAA;GACrC,IAAI,KAAI,KAAM,MAAM,GAAE,KAAM,EAAE,EAAE;AAC/B,IAAA,OAAO,KAAI,IAAK,GAAE;AACnB,IAAA;GACA,OAAO,CAAC,EAAE,KAAK,IAAI,EAAE,GAAG,CAAC,CAAA;GACzB;EACD;AACF,EAAA;;AAEA;AACC;AACA;;AAED,SAAS,YAAY,CAAC,KAAK,EAAE;AAC5B,CAAA,MAAM,GAAI,GAAE,MAAM,CAAC,KAAM,IAAG,EAAE,CAAC,CAAC,IAAI,GAAC;CACrC,IAAI,GAAE,KAAM,EAAE,EAAE;AACf,EAAA,OAAO,EAAC;AACT,EAAA;CACA,MAAM,MAAK,GAAI,IAAI,IAAI,CAAC,GAAG,EAAA;AAC3B,CAAA,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAE,GAAE,GAAI,GAAE,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,CAAA;AACjF;;;;"}
|
|
1
|
+
{"version":3,"file":"CnEventsCard.vue2.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span v-if=\"dayOf(event)\" class=\"cn-events-card__day\" aria-hidden=\"true\">{{ dayOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__text\">\n\t\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t\t</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} The day of the month it starts on, '' when it has no readable start.\n\t\t */\n\t\tdayOf(event) {\n\t\t\tconst parsed = new Date(String(event?.dtstart ?? '').trim())\n\t\t\treturn Number.isNaN(parsed.getTime()) ? '' : String(parsed.getDate())\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__day {\n\tdisplay: none;\n}\n\n.cn-events-card__text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnCalendarEventCreate","CnCalendarEventPicker","t"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createElementBlock, createBlock, toDisplayString, Fragment, renderList,
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, createBlock, toDisplayString, Fragment, renderList, createCommentVNode, createElementVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = {
|
|
4
4
|
class: "cn-events-card",
|
|
@@ -14,18 +14,24 @@ const _hoisted_3 = {
|
|
|
14
14
|
key: 2,
|
|
15
15
|
class: "cn-events-card__list"
|
|
16
16
|
};
|
|
17
|
-
const _hoisted_4 = {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const _hoisted_4 = {
|
|
18
|
+
key: 0,
|
|
19
|
+
class: "cn-events-card__day",
|
|
20
|
+
"aria-hidden": "true"
|
|
21
|
+
};
|
|
22
|
+
const _hoisted_5 = { class: "cn-events-card__text" };
|
|
23
|
+
const _hoisted_6 = { class: "cn-events-card__summary" };
|
|
24
|
+
const _hoisted_7 = { class: "cn-events-card__when" };
|
|
25
|
+
const _hoisted_8 = {
|
|
20
26
|
key: 0,
|
|
21
27
|
class: "cn-events-card__where"
|
|
22
28
|
};
|
|
23
|
-
const
|
|
29
|
+
const _hoisted_9 = {
|
|
24
30
|
key: 3,
|
|
25
31
|
class: "cn-events-card__muted",
|
|
26
32
|
"data-testid": "cn-events-card-empty"
|
|
27
33
|
};
|
|
28
|
-
const
|
|
34
|
+
const _hoisted_10 = {
|
|
29
35
|
key: 4,
|
|
30
36
|
class: "cn-events-card__muted",
|
|
31
37
|
role: "alert",
|
|
@@ -53,17 +59,22 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
53
59
|
class: "cn-events-card__event",
|
|
54
60
|
"data-testid": "cn-events-card-event"
|
|
55
61
|
}, [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
($options.dayOf(event))
|
|
63
|
+
? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString($options.dayOf(event)), 1 /* TEXT */))
|
|
64
|
+
: createCommentVNode("v-if", true),
|
|
65
|
+
createElementVNode("span", _hoisted_5, [
|
|
66
|
+
createElementVNode("span", _hoisted_6, toDisplayString($options.summaryOf(event)), 1 /* TEXT */),
|
|
67
|
+
createElementVNode("span", _hoisted_7, toDisplayString($options.whenOf(event)), 1 /* TEXT */),
|
|
68
|
+
(event.location)
|
|
69
|
+
? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(event.location), 1 /* TEXT */))
|
|
70
|
+
: createCommentVNode("v-if", true)
|
|
71
|
+
])
|
|
61
72
|
]))
|
|
62
73
|
}), 128 /* KEYED_FRAGMENT */))
|
|
63
74
|
]))
|
|
64
|
-
: (openBlock(), createElementBlock("p",
|
|
75
|
+
: (openBlock(), createElementBlock("p", _hoisted_9, toDisplayString($options.emptyText), 1 /* TEXT */)),
|
|
65
76
|
($data.linkError)
|
|
66
|
-
? (openBlock(), createElementBlock("p",
|
|
77
|
+
? (openBlock(), createElementBlock("p", _hoisted_10, toDisplayString($data.linkError), 1 /* TEXT */))
|
|
67
78
|
: createCommentVNode("v-if", true),
|
|
68
79
|
($data.showPicker)
|
|
69
80
|
? (openBlock(), createBlock(_component_CnCalendarEventPicker, {
|