@conduction/nextcloud-vue 2.139.0 → 2.141.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/CnAppRoot/CnAppRoot.vue2.js +9 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +28 -4
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +64 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +76 -37
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +5 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +28 -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/CnFilesBrowser/filesBrowser.js +7 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -2
- 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/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +20 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +5 -2
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +4 -3
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +6 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +961 -605
- package/dist/esm/utils/builtInFormatters.js +23 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/esm/utils/myTeam.js +24 -4
- package/dist/esm/utils/myTeam.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +5 -4
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/transitionInputs.js +43 -2
- package/dist/esm/utils/transitionInputs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1731 -846
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +961 -605
- package/l10n/en.json +3 -1
- package/l10n/nl.json +6 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +30 -6
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +121 -5
- package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +6 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
- package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +43 -9
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
- package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
- package/src/composables/indexSources.js +5 -2
- package/src/composables/useTaskInboxStore.js +4 -3
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +12 -9
- package/src/css/look-board-index.css +90 -7
- package/src/css/look-board.css +102 -2
- package/src/css/table.css +25 -0
- package/src/utils/builtInFormatters.js +22 -0
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/groupSublines.js +125 -0
- package/src/utils/myTeam.js +24 -4
- package/src/utils/resolveFilterTokens.js +5 -4
- package/src/utils/transitionInputs.js +42 -1
package/l10n/en.json
CHANGED
|
@@ -2380,7 +2380,9 @@
|
|
|
2380
2380
|
"You save the order along with a view.": "You save the order along with a view.",
|
|
2381
2381
|
"me": "me",
|
|
2382
2382
|
"Open profile": "Open profile",
|
|
2383
|
-
"Link a contact from your address book.": "Link a contact from your address book."
|
|
2383
|
+
"Link a contact from your address book.": "Link a contact from your address book.",
|
|
2384
|
+
"The value could not be shown.": "The value could not be shown.",
|
|
2385
|
+
"No value": "No value"
|
|
2384
2386
|
},
|
|
2385
2387
|
"plurals": {
|
|
2386
2388
|
"{count} member": [
|
package/l10n/nl.json
CHANGED
|
@@ -2630,7 +2630,12 @@
|
|
|
2630
2630
|
"You save the order along with a view.": "De sortering bewaart u mee in een weergave.",
|
|
2631
2631
|
"me": "ik",
|
|
2632
2632
|
"Open profile": "Profiel openen",
|
|
2633
|
-
"Link a contact from your address book.": "Koppel een contact uit je adresboek."
|
|
2633
|
+
"Link a contact from your address book.": "Koppel een contact uit je adresboek.",
|
|
2634
|
+
"The value could not be shown.": "De waarde kon niet worden getoond.",
|
|
2635
|
+
"Save as draft": "Opslaan als concept",
|
|
2636
|
+
"Draft saved at {time}": "Concept opgeslagen om {time}",
|
|
2637
|
+
"The draft could not be saved": "Het concept kon niet worden opgeslagen",
|
|
2638
|
+
"No value": "Geen waarde"
|
|
2634
2639
|
},
|
|
2635
2640
|
"plurals": {
|
|
2636
2641
|
"{count} member": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.141.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -770,9 +770,10 @@ import { DEFAULT_FORGE, resolveForge } from '../../utils/forge.js'
|
|
|
770
770
|
import { BUILT_IN_KB_PROVIDERS } from '../../utils/kbSearchProviders.js'
|
|
771
771
|
import { createManifestTranslate } from '../../utils/manifestTranslate.js'
|
|
772
772
|
import { installModalStack, uninstallModalStack } from '../../utils/modalStack.js'
|
|
773
|
+
import { loadMyTeam } from '../../utils/myTeam.js'
|
|
773
774
|
import { OBJECTS_CHANGED_EVENT } from '../../utils/objectSignals.js'
|
|
774
775
|
import { resolveBrand } from '../../utils/resolveBrand.js'
|
|
775
|
-
import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
776
|
+
import { dropOptionalUnresolved, MY_TEAM_TOKEN, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
776
777
|
import { pageOffersSavedViews } from '../../utils/savedViewHelpers.js'
|
|
777
778
|
import { normalizeSavedViewMenu, SAVED_VIEW_MENU_KEY, SAVED_VIEW_MENU_PREFIX, syncSavedViewMenu, toggleSavedViewMenuEntry, withSavedViewMenu } from '../../utils/savedViewMenu.js'
|
|
778
779
|
import { passesContextPredicates } from '../../utils/visibleIfContext.js'
|
|
@@ -4475,7 +4476,13 @@ export default {
|
|
|
4475
4476
|
async _fetchMenuItemCount(item) {
|
|
4476
4477
|
try {
|
|
4477
4478
|
const { register, schema, filter } = item.count
|
|
4478
|
-
const
|
|
4479
|
+
const raw = filter && typeof filter === 'object' ? filter : {}
|
|
4480
|
+
// `@myTeam` counts as the list filters: once the team is read,
|
|
4481
|
+
// every team the app answers, and without one no team at all.
|
|
4482
|
+
if (Object.values(raw).some((value) => value === MY_TEAM_TOKEN || (Array.isArray(value) && value.includes(MY_TEAM_TOKEN)))) {
|
|
4483
|
+
await loadMyTeam()
|
|
4484
|
+
}
|
|
4485
|
+
const resolved = dropOptionalUnresolved(resolveFilterTokens(raw, {}))
|
|
4479
4486
|
const url = generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}`)
|
|
4480
4487
|
const { data } = await axios.get(url, { params: { _limit: 1, ...resolved } })
|
|
4481
4488
|
const total = data?.total
|
|
@@ -89,6 +89,7 @@ import { generateUrl } from '@nextcloud/router'
|
|
|
89
89
|
import { NcButton, NcCheckboxRadioSwitch, NcTextArea } from '@nextcloud/vue'
|
|
90
90
|
import { fetchEndpointSource, interpolateUrlTokens } from '../../composables/useEndpointSource.js'
|
|
91
91
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
92
|
+
import { groupSublines } from '../../utils/groupSublines.js'
|
|
92
93
|
import { readPath } from '../../utils/readPath.js'
|
|
93
94
|
import { searchNextcloudGroups, searchNextcloudUsers } from '../../utils/userAutocomplete.js'
|
|
94
95
|
import { CnDialog } from '../CnDialog/index.js'
|
|
@@ -134,7 +135,10 @@ export function withTypedAssignee(found, typed, label) {
|
|
|
134
135
|
* `eyebrow` a template filled from the record.
|
|
135
136
|
* - `source`: an endpointSource to read the record from, else `object`.
|
|
136
137
|
* - `assignee`: `{ kinds?, label?, placeholder?, hint?, currentPath?,
|
|
137
|
-
* currentHint?, currentAs?, allowTyped?, field?, kindField? }`.
|
|
138
|
+
* currentHint?, currentAs?, allowTyped?, field?, kindField?, describe? }`.
|
|
139
|
+
* `describe` (`{ register, schema, groupField?, textField? }`) names the
|
|
140
|
+
* records that hold a team's description; each team option then reads
|
|
141
|
+
* "{count} members · {description}", the count where the viewer may read it. `kinds` is `["group"]`
|
|
138
142
|
* (default), `["user"]` or both; `currentPath` reads who has it now from the
|
|
139
143
|
* record and `currentHint` ("Now with {current}.", the placeholder named
|
|
140
144
|
* by `currentAs`) says so, in place of `hint`; `allowTyped`
|
|
@@ -398,8 +402,12 @@ export default {
|
|
|
398
402
|
if (seq !== this.searchSeq) {
|
|
399
403
|
return
|
|
400
404
|
}
|
|
405
|
+
const lines = await groupSublines(groups.map((g) => g.id), typed, this.assigneeConfig.describe || null)
|
|
406
|
+
if (seq !== this.searchSeq) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
401
409
|
const found = [
|
|
402
|
-
...groups.map((g) => ({ id: g.id, label: g.label, secondary: g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
|
|
410
|
+
...groups.map((g) => ({ id: g.id, label: g.label, secondary: lines[g.id] || g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
|
|
403
411
|
...users.map((u) => ({ id: u.id, label: u.label, secondary: u.subline || '', avatar: true, kind: 'user' })),
|
|
404
412
|
]
|
|
405
413
|
this.options = this.assigneeConfig.allowTyped ? withTypedAssignee(found, typed, t('nextcloud-vue', 'Use "{name}"', { name: typed })) : found
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
<!-- Built-in "label" widget: the value's label (an enum's translated
|
|
57
57
|
label) as plain text, where a list wants words and no pill. -->
|
|
58
58
|
<template v-else-if="widget === 'label'">
|
|
59
|
-
<span v-if="hasValue" :title="rawTitle">{{
|
|
59
|
+
<span v-if="hasValue" :title="rawTitle">{{ shownLabel }}</span>
|
|
60
60
|
<span v-else class="cn-cell-renderer__dash">—</span>
|
|
61
61
|
</template>
|
|
62
62
|
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
shapes it; the colour stays keyed on the raw value. -->
|
|
66
66
|
<template v-else-if="widget === 'badge'">
|
|
67
67
|
<CnStatusBadge v-if="hasValue"
|
|
68
|
-
:label="
|
|
68
|
+
:label="shownLabel"
|
|
69
69
|
:variant="badgeVariant"
|
|
70
70
|
:colorMap="badgeColorMap"
|
|
71
71
|
:colorKey="badgeColorKey"
|
|
@@ -438,7 +438,7 @@ import { ageInDays, boardDateLocale, formatAge, formatBoardDate, formatRelativeD
|
|
|
438
438
|
import { daysUntil, parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
|
|
439
439
|
import { pillLook } from '../../utils/detailActionModel.js'
|
|
440
440
|
import { resolveDue } from '../../utils/dueText.js'
|
|
441
|
-
import { formatBoardDateTime } from '../../utils/fillTemplate.js'
|
|
441
|
+
import { fillTemplate, formatBoardDateTime } from '../../utils/fillTemplate.js'
|
|
442
442
|
import { formatMoney } from '../../utils/formatMetric.js'
|
|
443
443
|
import { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'
|
|
444
444
|
import { objectFieldValue } from '../../utils/objectName.js'
|
|
@@ -1455,6 +1455,22 @@ export default {
|
|
|
1455
1455
|
* @return {string}
|
|
1456
1456
|
* @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
|
|
1457
1457
|
*/
|
|
1458
|
+
/**
|
|
1459
|
+
* What a `label` or `badge` cell shows: its label, worded by
|
|
1460
|
+
* `widgetProps.textTemplate` when given (translated, filled from the
|
|
1461
|
+
* row, `{value}` the label; DqPostvak "Lijkt op 2026-0079").
|
|
1462
|
+
*
|
|
1463
|
+
* @return {string}
|
|
1464
|
+
* @spec openspec/changes/cell-text-template/specs/cell-text-template/spec.md
|
|
1465
|
+
*/
|
|
1466
|
+
shownLabel() {
|
|
1467
|
+
const template = this.widgetProps && this.widgetProps.textTemplate
|
|
1468
|
+
if (typeof template !== 'string' || template === '') {
|
|
1469
|
+
return this.labelledValue
|
|
1470
|
+
}
|
|
1471
|
+
return fillTemplate(this.cnTranslate(template), { ...(this.row || {}), value: this.labelledValue })
|
|
1472
|
+
},
|
|
1473
|
+
|
|
1458
1474
|
labelledValue() {
|
|
1459
1475
|
if (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {
|
|
1460
1476
|
return this.enumLabel
|
|
@@ -1721,8 +1737,9 @@ export default {
|
|
|
1721
1737
|
|
|
1722
1738
|
/**
|
|
1723
1739
|
* The swatch colour — read from the sibling row field named by
|
|
1724
|
-
* `format.colorField` (default `'color'`)
|
|
1725
|
-
*
|
|
1740
|
+
* `format.colorField` (default `'color'`), through `format.colorMap`
|
|
1741
|
+
* when given (a stored name such as `blue` to a CSS colour). Returns
|
|
1742
|
+
* null when no usable colour is present (the dot is then omitted).
|
|
1726
1743
|
*
|
|
1727
1744
|
* @return {string|null}
|
|
1728
1745
|
*/
|
|
@@ -1732,7 +1749,14 @@ export default {
|
|
|
1732
1749
|
}
|
|
1733
1750
|
const field = (this.format && this.format.colorField) || 'color'
|
|
1734
1751
|
const c = this.row && this.row[field]
|
|
1735
|
-
|
|
1752
|
+
if (typeof c !== 'string' || c.trim() === '') {
|
|
1753
|
+
return null
|
|
1754
|
+
}
|
|
1755
|
+
const map = this.format && this.format.colorMap
|
|
1756
|
+
if (map && typeof map === 'object') {
|
|
1757
|
+
return typeof map[c] === 'string' && map[c] !== '' ? map[c] : null
|
|
1758
|
+
}
|
|
1759
|
+
return c
|
|
1736
1760
|
},
|
|
1737
1761
|
|
|
1738
1762
|
formattedValue() {
|
|
@@ -109,6 +109,7 @@ Fallback slot — shown when ApexCharts is not available:
|
|
|
109
109
|
| `barMax` | Number | `null` | `bar-list`: the value a full track stands for (100 for percentages); null takes the largest value |
|
|
110
110
|
| `matrixLabelWidth` | Number | `null` | `matrix`: the row-label column's width in px; a long label wraps (DqProcesanalyse "Dagen per fase": 240). Null sizes the column to its longest label, at least 120px |
|
|
111
111
|
| `footnote` | String | `''` | A line under the chart, through the host's translate: under the rows of a `bar-list` or `matrix`, under the plot of a `line`, `area` or `bar` chart |
|
|
112
|
+
| `notice` | Object | `null` | A toned note card under the chart: `{ lead?, text?, values?, labels?, tone? }`. `{name}` in `lead` (bold) and `text` is filled from `values[name]`, a literal or a record token (`@object.<field>`); `labels[name]` (`"rows"`, `"columns"`, `"values"` or a map) turns a key into words. `tone`: info, success, warning, error. Hidden while a value is missing |
|
|
112
113
|
| `referenceLines` | Array | `[]` | Dashed horizontal reference lines on the value axis of a line, area or bar chart, such as a norm: `[{ value, label?, color? }]`. The label goes through the host's translate; the colour defaults to the theme's error colour (board DqDoorlooptijd). `valuePath` reads the value from the endpoint answer instead, `{value}` in the label writes it, and a line without a value is not drawn. |
|
|
113
114
|
| `categoryFormat` | String | `''` | How date categories read on the x-axis: `month` ("jan") or `month-year` ("jan 2026"), in the user's language, or `week` for ISO week keys ("2026-W40" as "w40"). Categories that are not dates are left as is; a bare year key stays a year. |
|
|
114
115
|
| `emptyCellLabel` | String | `''` | `matrix`: the text of a cell without a value ("geen"), through the host's translate. Empty leaves the cell blank |
|
|
@@ -134,3 +135,4 @@ The `dataSource` OBJECT-form `aggregate` (Wave 3, #91) — `{ groupBy, metric?:
|
|
|
134
135
|
| Prop | Type | Default | Description |
|
|
135
136
|
|------|------|---------|-------------|
|
|
136
137
|
| `widgetId` | String | `''` | Matches `cn:widget:refresh` event-bus events; on a matching id the chart re-queries its `dataSource`. Also exposes a ref-callable `refresh()`. |
|
|
138
|
+
| `barValueAtEnd` | Boolean | `false` | A `bar-list` without a track: each bar as long as its share and its value right after the bar's end (DqProcesanalyse "Doorlooptijd per status"). False keeps the track and the value column. |
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
share of `barMax` (default the largest value), the value at the
|
|
66
66
|
end. Rows navigate when the source declares a drilldown. -->
|
|
67
67
|
<div v-else-if="type === 'bar-list'" class="cn-chart-widget__html" data-testid="cn-chart-widget-bar-list">
|
|
68
|
-
<ul class="cn-chart-widget__bar-list">
|
|
68
|
+
<ul class="cn-chart-widget__bar-list" :class="{ 'cn-chart-widget__bar-list--value-at-end': barValueAtEnd }">
|
|
69
69
|
<li v-for="row in barListRows" :key="row.index" class="cn-chart-widget__bar-row">
|
|
70
70
|
<component
|
|
71
71
|
:is="row.drill ? 'button' : 'div'"
|
|
@@ -75,10 +75,16 @@
|
|
|
75
75
|
data-testid="cn-chart-widget-bar-row"
|
|
76
76
|
@click="row.drill ? onDataPointSelection(null, null, { dataPointIndex: row.index }) : null">
|
|
77
77
|
<span class="cn-chart-widget__bar-label">{{ row.label }}</span>
|
|
78
|
-
<span class="cn-chart-widget__bar-track"
|
|
79
|
-
<span class="cn-chart-widget__bar-fill" :style="{
|
|
78
|
+
<span v-if="barValueAtEnd" class="cn-chart-widget__bar-track">
|
|
79
|
+
<span class="cn-chart-widget__bar-fill" aria-hidden="true" :style="{ flexBasis: `${row.share}%` }" />
|
|
80
|
+
<span class="cn-chart-widget__bar-value">{{ row.text }}</span>
|
|
80
81
|
</span>
|
|
81
|
-
<
|
|
82
|
+
<template v-else>
|
|
83
|
+
<span class="cn-chart-widget__bar-track" aria-hidden="true">
|
|
84
|
+
<span class="cn-chart-widget__bar-fill" :style="{ width: `${row.share}%` }" />
|
|
85
|
+
</span>
|
|
86
|
+
<span class="cn-chart-widget__bar-value">{{ row.text }}</span>
|
|
87
|
+
</template>
|
|
82
88
|
</component>
|
|
83
89
|
</li>
|
|
84
90
|
</ul>
|
|
@@ -148,12 +154,25 @@
|
|
|
148
154
|
data-testid="cn-chart-widget-footnote">
|
|
149
155
|
{{ hostTranslate(footnote) }}
|
|
150
156
|
</p>
|
|
157
|
+
<!-- `notice`: a toned note card under the chart whose words carry
|
|
158
|
+
values from the page record (DqHandhaving "Advies: ..."). -->
|
|
159
|
+
<NcNoteCard
|
|
160
|
+
v-if="noticeParts"
|
|
161
|
+
:type="noticeParts.type"
|
|
162
|
+
:data-tone="noticeParts.type"
|
|
163
|
+
class="cn-chart-widget__notice"
|
|
164
|
+
data-testid="cn-chart-widget-notice">
|
|
165
|
+
<p class="cn-chart-widget__notice-text">
|
|
166
|
+
<strong v-if="noticeParts.lead">{{ noticeParts.lead }}</strong>{{ noticeParts.lead && noticeParts.text ? ' ' : '' }}{{ noticeParts.text }}
|
|
167
|
+
</p>
|
|
168
|
+
</NcNoteCard>
|
|
151
169
|
</div>
|
|
152
170
|
</template>
|
|
153
171
|
|
|
154
172
|
<script>
|
|
155
173
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
156
174
|
import { getLanguage, translate as t } from '@nextcloud/l10n'
|
|
175
|
+
import { NcNoteCard } from '@nextcloud/vue'
|
|
157
176
|
import { inject, markRaw, ref } from 'vue'
|
|
158
177
|
import VueApexCharts from 'vue3-apexcharts'
|
|
159
178
|
import { useDataSource } from '../../composables/useDataSource.js'
|
|
@@ -405,7 +424,7 @@ function formatterFor(vf) {
|
|
|
405
424
|
export default {
|
|
406
425
|
name: 'CnChartWidget',
|
|
407
426
|
|
|
408
|
-
components: { CnEndpointNotice },
|
|
427
|
+
components: { CnEndpointNotice, NcNoteCard },
|
|
409
428
|
|
|
410
429
|
inject: {
|
|
411
430
|
/**
|
|
@@ -455,6 +474,18 @@ export default {
|
|
|
455
474
|
default: null,
|
|
456
475
|
},
|
|
457
476
|
|
|
477
|
+
/**
|
|
478
|
+
* A `bar-list` without a track: each bar as long as its share and its
|
|
479
|
+
* value right after the bar's end (DqProcesanalyse "Doorlooptijd per
|
|
480
|
+
* status"). False, the default, keeps the track and the value column.
|
|
481
|
+
*
|
|
482
|
+
* @spec openspec/changes/chart-bar-list-value-at-end/specs/chart-bar-list-value-at-end/spec.md#requirement-a-bar-list-can-put-each-value-at-its-bars-end
|
|
483
|
+
*/
|
|
484
|
+
barValueAtEnd: {
|
|
485
|
+
type: Boolean,
|
|
486
|
+
default: false,
|
|
487
|
+
},
|
|
488
|
+
|
|
458
489
|
/**
|
|
459
490
|
* A `matrix`'s row-label column width in px. Null, the default, sizes
|
|
460
491
|
* the column to its longest label (at least 120px); a width wraps a
|
|
@@ -467,6 +498,24 @@ export default {
|
|
|
467
498
|
default: null,
|
|
468
499
|
},
|
|
469
500
|
|
|
501
|
+
/**
|
|
502
|
+
* A toned note card under the chart whose words carry values from the
|
|
503
|
+
* page record: `{ lead?, text?, values?, labels?, tone? }`. `lead`
|
|
504
|
+
* (bold) and `text` go through the host's translate, then each
|
|
505
|
+
* `{name}` is filled from `values[name]`, a literal or a token such as
|
|
506
|
+
* `@object.recommendedIntervention`. `labels[name]` turns a key into
|
|
507
|
+
* words: `"rows"`, `"columns"` or `"values"` read the matrix's
|
|
508
|
+
* cellSource label maps, an object is its own map. `tone` is info
|
|
509
|
+
* (default), success, warning or error. Hidden while a value is
|
|
510
|
+
* missing, so a half-filled sentence never shows.
|
|
511
|
+
*
|
|
512
|
+
* @spec openspec/changes/chart-notice/specs/chart-notice/spec.md#requirement-a-chart-can-carry-a-notice-with-record-values
|
|
513
|
+
*/
|
|
514
|
+
notice: {
|
|
515
|
+
type: Object,
|
|
516
|
+
default: null,
|
|
517
|
+
},
|
|
518
|
+
|
|
470
519
|
/**
|
|
471
520
|
* A line under the chart ("Klik een balk om de zaken te zien"),
|
|
472
521
|
* through the host's translate: under the rows of a `bar-list` or
|
|
@@ -1601,6 +1650,38 @@ export default {
|
|
|
1601
1650
|
}
|
|
1602
1651
|
},
|
|
1603
1652
|
|
|
1653
|
+
/**
|
|
1654
|
+
* The notice's bold lead, text and note-card type, values filled in;
|
|
1655
|
+
* null when there is none or a value is missing.
|
|
1656
|
+
*
|
|
1657
|
+
* @return {{lead: string, text: string, type: string}|null} The notice.
|
|
1658
|
+
* @spec openspec/changes/chart-notice/specs/chart-notice/spec.md#requirement-a-chart-can-carry-a-notice-with-record-values
|
|
1659
|
+
*/
|
|
1660
|
+
noticeParts() {
|
|
1661
|
+
const def = this.notice
|
|
1662
|
+
if (!def || typeof def !== 'object' || (!def.lead && !def.text)) {
|
|
1663
|
+
return null
|
|
1664
|
+
}
|
|
1665
|
+
const declared = def.values && typeof def.values === 'object' ? def.values : {}
|
|
1666
|
+
const resolved = resolveFilterTokens(declared, this.chartTokenCtx())
|
|
1667
|
+
const source = this.cellSource && typeof this.cellSource === 'object' ? this.cellSource : {}
|
|
1668
|
+
const maps = { rows: source.rowLabels, columns: source.columnLabels, values: source.valueLabels }
|
|
1669
|
+
const words = {}
|
|
1670
|
+
for (const name of Object.keys(declared)) {
|
|
1671
|
+
const raw = resolved[name]
|
|
1672
|
+
if (raw === null || raw === undefined || raw === '' || (typeof raw === 'string' && raw.charAt(0) === '@')) {
|
|
1673
|
+
return null
|
|
1674
|
+
}
|
|
1675
|
+
const ref = def.labels && def.labels[name]
|
|
1676
|
+
const map = typeof ref === 'string' ? maps[ref] : ref
|
|
1677
|
+
const label = map && typeof map === 'object' && typeof map[String(raw)] === 'string' ? map[String(raw)] : null
|
|
1678
|
+
words[name] = label ? this.hostTranslate(label) : String(raw)
|
|
1679
|
+
}
|
|
1680
|
+
const fill = (key) => (key ? this.hostTranslate(key).replace(/\{(\w+)\}/g, (match, name) => (name in words ? words[name] : match)) : '')
|
|
1681
|
+
const type = ['info', 'success', 'warning', 'error'].includes(def.tone) ? def.tone : 'info'
|
|
1682
|
+
return { lead: fill(def.lead), text: fill(def.text), type }
|
|
1683
|
+
},
|
|
1684
|
+
|
|
1604
1685
|
/**
|
|
1605
1686
|
* The matrix cell of the record (`highlight: { row, column }`, tokens
|
|
1606
1687
|
* such as `@object.behaviour` resolved), as row and column indexes. A
|
|
@@ -2943,11 +3024,46 @@ export default {
|
|
|
2943
3024
|
white-space: nowrap;
|
|
2944
3025
|
}
|
|
2945
3026
|
|
|
3027
|
+
/* `barValueAtEnd`: no track; the bar takes its share of the row and gives way
|
|
3028
|
+
to the value right after its end, so the longest bar still fits. */
|
|
3029
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-line {
|
|
3030
|
+
grid-template-columns: minmax(80px, 130px) minmax(0, 1fr);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-track {
|
|
3034
|
+
align-items: center;
|
|
3035
|
+
background: none;
|
|
3036
|
+
display: flex;
|
|
3037
|
+
gap: 6px;
|
|
3038
|
+
height: auto;
|
|
3039
|
+
overflow: visible;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-fill {
|
|
3043
|
+
border-radius: 4px;
|
|
3044
|
+
flex: 0 1 auto;
|
|
3045
|
+
height: 18px;
|
|
3046
|
+
min-width: 2px;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-value {
|
|
3050
|
+
flex: none;
|
|
3051
|
+
text-align: start;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
2946
3054
|
.cn-chart-widget__footnote {
|
|
2947
3055
|
color: var(--color-text-maxcontrast);
|
|
2948
3056
|
margin: 0;
|
|
2949
3057
|
}
|
|
2950
3058
|
|
|
3059
|
+
.cn-chart-widget__notice {
|
|
3060
|
+
margin: 12px 0 0;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
.cn-chart-widget__notice-text {
|
|
3064
|
+
margin: 0;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
2951
3067
|
/* Under an ApexCharts plot the footnote is a sibling of the canvas, so it keeps
|
|
2952
3068
|
its own line height while the canvas takes the rest of a fitted column. */
|
|
2953
3069
|
.cn-chart-widget > .cn-chart-widget__footnote {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="cn-case-widget-card">
|
|
8
8
|
<CnConversationThread
|
|
9
|
+
ref="thread"
|
|
9
10
|
:messages="messages"
|
|
10
11
|
:title="tr(content.title || '')"
|
|
11
12
|
:subtitle="tr(content.subtitle || '')"
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
data-testid="cn-conversation-templates"
|
|
35
36
|
@apply="applyTemplate($event, setDraft)" />
|
|
36
37
|
</template>
|
|
37
|
-
<template v-if="replyOptions.length > 0" #reply-actions>
|
|
38
|
+
<template v-if="replyOptions.length > 0 || draftUrl" #reply-actions="{ draft }">
|
|
38
39
|
<NcCheckboxRadioSwitch
|
|
39
40
|
v-for="option in replyOptions"
|
|
40
41
|
:key="option.key"
|
|
@@ -42,6 +43,19 @@
|
|
|
42
43
|
:data-testid="`cn-conversation-option-${option.key}`">
|
|
43
44
|
{{ tr(option.label) }}
|
|
44
45
|
</NcCheckboxRadioSwitch>
|
|
46
|
+
<span v-if="draftUrl && draftStatus"
|
|
47
|
+
class="cn-conversation-widget__draft-status"
|
|
48
|
+
role="status"
|
|
49
|
+
data-testid="cn-conversation-draft-status">
|
|
50
|
+
{{ draftStatus }}
|
|
51
|
+
</span>
|
|
52
|
+
<NcButton
|
|
53
|
+
v-if="draftUrl"
|
|
54
|
+
data-testid="cn-conversation-save-draft"
|
|
55
|
+
:disabled="savingDraft"
|
|
56
|
+
@click="saveDraft(draft)">
|
|
57
|
+
{{ t('nextcloud-vue', 'Save as draft') }}
|
|
58
|
+
</NcButton>
|
|
45
59
|
</template>
|
|
46
60
|
<template v-if="headerAction || statusBadgeText" #header-actions>
|
|
47
61
|
<span v-if="statusBadgeText"
|
|
@@ -71,11 +85,13 @@
|
|
|
71
85
|
|
|
72
86
|
<script>
|
|
73
87
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
88
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
74
89
|
import { NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue'
|
|
75
90
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
76
91
|
import CnTemplateSelect from '../CnTemplateSelect/CnTemplateSelect.vue'
|
|
77
92
|
import CnConversationThread from './CnConversationThread.vue'
|
|
78
93
|
import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
94
|
+
import { formatTimeOfDay } from '../../utils/boardDate.js'
|
|
79
95
|
import { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'
|
|
80
96
|
import { readPath } from '../../utils/readPath.js'
|
|
81
97
|
import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveFilterTokens.js'
|
|
@@ -178,6 +194,10 @@ export default {
|
|
|
178
194
|
acting: '',
|
|
179
195
|
/** The threads `statusBadge` reads. */
|
|
180
196
|
badgeRows: [],
|
|
197
|
+
/** What the last draft save said: when it was saved, or that it failed. */
|
|
198
|
+
draftStatus: '',
|
|
199
|
+
/** Whether a draft is being saved. */
|
|
200
|
+
savingDraft: false,
|
|
181
201
|
}
|
|
182
202
|
},
|
|
183
203
|
|
|
@@ -315,6 +335,21 @@ export default {
|
|
|
315
335
|
* @return {string} The URL, or '' when the content names none.
|
|
316
336
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
|
|
317
337
|
*/
|
|
338
|
+
/**
|
|
339
|
+
* The reply draft's url (`content.draft.url`, `@objectId` filled in),
|
|
340
|
+
* or '' without one.
|
|
341
|
+
*
|
|
342
|
+
* @return {string}
|
|
343
|
+
* @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
|
|
344
|
+
*/
|
|
345
|
+
draftUrl() {
|
|
346
|
+
const url = this.content.draft && this.content.draft.url
|
|
347
|
+
if (typeof url !== 'string' || url === '') {
|
|
348
|
+
return ''
|
|
349
|
+
}
|
|
350
|
+
return url.replace(/@objectId/g, encodeURIComponent(String(this.objectId ?? '')))
|
|
351
|
+
},
|
|
352
|
+
|
|
318
353
|
endpoint() {
|
|
319
354
|
const url = this.content.endpoint
|
|
320
355
|
if (typeof url !== 'string' || url === '') {
|
|
@@ -340,19 +375,101 @@ export default {
|
|
|
340
375
|
handler() {
|
|
341
376
|
this.loadSource()
|
|
342
377
|
this.loadBadge()
|
|
378
|
+
this.loadDraft()
|
|
343
379
|
},
|
|
344
380
|
},
|
|
345
381
|
},
|
|
346
382
|
|
|
347
383
|
mounted() {
|
|
348
384
|
subscribe('cn:page:refresh', this.onPageRefresh)
|
|
385
|
+
subscribe('cn:conversation:reply-sent', this.onReplySentElsewhere)
|
|
349
386
|
},
|
|
350
387
|
|
|
351
388
|
beforeUnmount() {
|
|
352
389
|
unsubscribe('cn:page:refresh', this.onPageRefresh)
|
|
390
|
+
unsubscribe('cn:conversation:reply-sent', this.onReplySentElsewhere)
|
|
353
391
|
},
|
|
354
392
|
|
|
355
393
|
methods: {
|
|
394
|
+
t,
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Read the saved reply draft into the reply box.
|
|
398
|
+
*
|
|
399
|
+
* @return {Promise<void>}
|
|
400
|
+
* @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
|
|
401
|
+
*/
|
|
402
|
+
async loadDraft() {
|
|
403
|
+
if (!this.draftUrl) {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
408
|
+
const { generateUrl } = await import('@nextcloud/router')
|
|
409
|
+
const { data } = await axios.get(generateUrl(this.draftUrl))
|
|
410
|
+
const text = data && typeof data.text === 'string' ? data.text : ''
|
|
411
|
+
if (text !== '' && this.$refs.thread) {
|
|
412
|
+
this.$refs.thread.setDraft(text)
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
// No saved draft to read leaves the box empty.
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Store the reply box as the draft and say when it was saved.
|
|
421
|
+
*
|
|
422
|
+
* @param {string} text The reply box's text.
|
|
423
|
+
* @return {Promise<void>}
|
|
424
|
+
*/
|
|
425
|
+
async saveDraft(text) {
|
|
426
|
+
this.savingDraft = true
|
|
427
|
+
try {
|
|
428
|
+
await this.storeDraft(String(text ?? ''))
|
|
429
|
+
this.draftStatus = t('nextcloud-vue', 'Draft saved at {time}', { time: formatTimeOfDay(new Date()) })
|
|
430
|
+
} catch {
|
|
431
|
+
this.draftStatus = t('nextcloud-vue', 'The draft could not be saved')
|
|
432
|
+
} finally {
|
|
433
|
+
this.savingDraft = false
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* A reply on this record went out through a dialog or another surface
|
|
439
|
+
* (`cn:conversation:reply-sent` with `{ objectId }`): the saved draft
|
|
440
|
+
* and the reply box are cleared.
|
|
441
|
+
*
|
|
442
|
+
* @param {{objectId?: string}} payload The record the reply was on.
|
|
443
|
+
* @return {Promise<void>}
|
|
444
|
+
* @spec openspec/changes/conversation-reply-draft/specs/conversation-reply-draft/spec.md#requirement-a-reply-can-be-saved-as-a-draft-and-comes-back
|
|
445
|
+
*/
|
|
446
|
+
async onReplySentElsewhere(payload) {
|
|
447
|
+
if (!this.draftUrl || String(payload?.objectId ?? '') !== String(this.objectId ?? '')) {
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
if (this.$refs.thread) {
|
|
451
|
+
this.$refs.thread.setDraft('')
|
|
452
|
+
}
|
|
453
|
+
this.draftStatus = ''
|
|
454
|
+
try {
|
|
455
|
+
await this.storeDraft('')
|
|
456
|
+
} catch {
|
|
457
|
+
// The next save overwrites a draft that could not be cleared.
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* PUT the draft text.
|
|
463
|
+
*
|
|
464
|
+
* @param {string} text The text; '' clears the draft.
|
|
465
|
+
* @return {Promise<void>}
|
|
466
|
+
*/
|
|
467
|
+
async storeDraft(text) {
|
|
468
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
469
|
+
const { generateUrl } = await import('@nextcloud/router')
|
|
470
|
+
await axios.put(generateUrl(this.draftUrl), { text })
|
|
471
|
+
},
|
|
472
|
+
|
|
356
473
|
/**
|
|
357
474
|
* Read the messages again when the page refreshes (a dialog sent one).
|
|
358
475
|
*
|
|
@@ -691,6 +808,9 @@ export default {
|
|
|
691
808
|
if (typeof text !== 'string' || text === '') {
|
|
692
809
|
return
|
|
693
810
|
}
|
|
811
|
+
if (this.draftUrl) {
|
|
812
|
+
this.storeDraft('')
|
|
813
|
+
}
|
|
694
814
|
this.sentHere.push({
|
|
695
815
|
id: `sent-${this.sentHere.length}`,
|
|
696
816
|
time: new Date().toISOString(),
|
|
@@ -722,4 +842,9 @@ export default {
|
|
|
722
842
|
border-radius: var(--border-radius-large);
|
|
723
843
|
padding: calc(5 * var(--default-grid-baseline));
|
|
724
844
|
}
|
|
845
|
+
|
|
846
|
+
.cn-conversation-widget__draft-status {
|
|
847
|
+
color: var(--color-text-maxcontrast);
|
|
848
|
+
font-size: 13px;
|
|
849
|
+
}
|
|
725
850
|
</style>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
'cn-table-container--board': isBoardLook,
|
|
7
7
|
'cn-table-container--title-plain': isBoardLook && rowTitle === 'plain',
|
|
8
8
|
'cn-table-container--title-text': isBoardLook && rowTitle === 'text',
|
|
9
|
+
'cn-table-container--title-strong': isBoardLook && rowTitle === 'strong',
|
|
9
10
|
'cn-table-container--scrollable': scrollable,
|
|
10
11
|
'cn-table-container--borderless': borderless,
|
|
11
12
|
'cn-table-container--fill': fillHeight,
|
|
@@ -856,8 +857,10 @@ export default {
|
|
|
856
857
|
* the 15px bold title without an underline and a 13px muted secondary
|
|
857
858
|
* line, as the PqTickets and OcPublicaties boards draw it; `text` is an
|
|
858
859
|
* ordinary cell, for a log table whose first column is a date
|
|
859
|
-
* (DqZaakContact)
|
|
860
|
-
*
|
|
860
|
+
* (DqZaakContact); `strong` is the cell's own size at weight 600 in the
|
|
861
|
+
* main ink without an underline, the name column of a card's list
|
|
862
|
+
* (DqZaaktypeStatussen). The row stays clickable either way. The
|
|
863
|
+
* Nextcloud look ignores it.
|
|
861
864
|
*
|
|
862
865
|
* @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
|
|
863
866
|
* @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
|
|
@@ -865,7 +868,7 @@ export default {
|
|
|
865
868
|
rowTitle: {
|
|
866
869
|
type: String,
|
|
867
870
|
default: 'link',
|
|
868
|
-
validator: (value) => ['link', 'plain', 'text'].includes(value),
|
|
871
|
+
validator: (value) => ['link', 'plain', 'text', 'strong'].includes(value),
|
|
869
872
|
},
|
|
870
873
|
|
|
871
874
|
/**
|