@conduction/nextcloud-vue 2.96.0 → 2.97.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/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
- 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 +144 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +301 -18
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +182 -3
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +7 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +7 -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 +301 -177
- package/dist/esm/utils/queryPresets.js +13 -2
- package/dist/esm/utils/queryPresets.js.map +1 -1
- package/dist/esm/utils/scopeListLayout.js +37 -1
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +3009 -1907
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +301 -177
- package/l10n/en.json +7 -1
- package/l10n/nl.json +7 -1
- package/package.json +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
- package/src/components/CnConversationThread/CnConversationThread.md +12 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
- package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
- package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +324 -24
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
- package/src/components/CnIndexPage/useNamedSource.js +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
- package/src/composables/indexSources.js +182 -3
- package/src/schemas/app-manifest-v2.schema.json +11 -4
- package/src/utils/queryPresets.js +13 -2
- package/src/utils/scopeListLayout.js +37 -1
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -101,6 +101,28 @@
|
|
|
101
101
|
<span v-if="block.subtitle" class="cn-facts-widget__lead-subtitle">{{ block.subtitle }}</span>
|
|
102
102
|
</span>
|
|
103
103
|
</div>
|
|
104
|
+
<div
|
|
105
|
+
v-else-if="block.kind === 'meter'"
|
|
106
|
+
class="cn-facts-widget__meter"
|
|
107
|
+
:class="{ 'cn-facts-widget__meter--over': block.over }"
|
|
108
|
+
:data-testid="`cn-fact-${block.key}`">
|
|
109
|
+
<div class="cn-facts-widget__meter-head">
|
|
110
|
+
<span class="cn-facts-widget__meter-label">{{ block.label }}</span>
|
|
111
|
+
<span class="cn-facts-widget__meter-value">{{ block.value }}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div
|
|
114
|
+
v-if="block.limit !== null"
|
|
115
|
+
class="cn-facts-widget__meter-track"
|
|
116
|
+
role="meter"
|
|
117
|
+
:aria-label="block.label"
|
|
118
|
+
:aria-valuenow="block.amount"
|
|
119
|
+
aria-valuemin="0"
|
|
120
|
+
:aria-valuemax="block.limit"
|
|
121
|
+
:aria-valuetext="block.value"
|
|
122
|
+
:data-testid="`cn-fact-${block.key}-bar`">
|
|
123
|
+
<span class="cn-facts-widget__meter-bar" :style="{ width: `${block.percent}%` }" />
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
104
126
|
<div v-else-if="block.kind === 'pill'">
|
|
105
127
|
<CnStatusBadge
|
|
106
128
|
:label="block.value"
|
|
@@ -133,6 +155,7 @@
|
|
|
133
155
|
|
|
134
156
|
<script>
|
|
135
157
|
import { emit } from '@nextcloud/event-bus'
|
|
158
|
+
import { formatFileSize } from '@nextcloud/files'
|
|
136
159
|
import { translate as t } from '@nextcloud/l10n'
|
|
137
160
|
import { NcLoadingIcon } from '@nextcloud/vue'
|
|
138
161
|
import Check from 'vue-material-design-icons/Check.vue'
|
|
@@ -152,7 +175,7 @@ import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveF
|
|
|
152
175
|
import { userDisplayName } from '../../utils/userDisplayName.js'
|
|
153
176
|
|
|
154
177
|
/** Kinds of entry a `fields[]` item can be. A labelled item without `as` is a row. */
|
|
155
|
-
const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text'])
|
|
178
|
+
const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text', 'meter'])
|
|
156
179
|
|
|
157
180
|
/**
|
|
158
181
|
* The label resolver every facts widget on the page shares, made on first use
|
|
@@ -260,13 +283,14 @@ export default {
|
|
|
260
283
|
* The widget configuration.
|
|
261
284
|
*
|
|
262
285
|
* - `fields` — the entries, in order. Each is `{ path, label?, as?, from?, format?, map?, itemPath?, parts?, separator?, route?, variant?, variantMap?, colorMap?, icon?, copy?, reveal?, text?, title?, subtitle?, initialsPath?, tone? }`.
|
|
263
|
-
* `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `
|
|
286
|
+
* `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`), `pill` or `meter`.
|
|
287
|
+
* A `meter` shows the amount at `path` against the limit at `limitPath` (or a constant `limit`) as "12 of 50" with a bar; past the limit the bar turns red and the text says "over the limit"; without a positive limit it shows the amount and no bar.
|
|
264
288
|
* A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
|
|
265
289
|
* A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
|
|
266
290
|
* `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read. A resolved value works as a pill too: `map` is applied to the resolved label, and `variantMap` / `colorMap` look the tone up by the value of `resolve.toneField` on the referenced object when that is set (a status named by its `name`, coloured by its `state`), then by the resolved label, then by the stored value.
|
|
267
291
|
* `format: 'user'` shows a Nextcloud user id as the user's display name, read once per id; the entry stays empty until the name is known.
|
|
268
292
|
* - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
|
|
269
|
-
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
|
|
293
|
+
* `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`), `bytes` (a size, "1.5 MB") or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
|
|
270
294
|
* `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
|
|
271
295
|
* - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object. An entry with `from: "object"` reads the current object even then (a client card that also shows the ticket's channel).
|
|
272
296
|
* - `link` — `{ label, route? | href? }` at the foot.
|
|
@@ -420,6 +444,9 @@ export default {
|
|
|
420
444
|
block.initials = block.icon ? '' : this.initialsOf(readPath(record, field.initialsPath))
|
|
421
445
|
block.tone = ['success', 'info', 'warning', 'error'].includes(field.tone) ? field.tone : 'neutral'
|
|
422
446
|
}
|
|
447
|
+
if (kind === 'meter') {
|
|
448
|
+
Object.assign(block, this.meterOf(record, field))
|
|
449
|
+
}
|
|
423
450
|
if (kind === 'title' && field.route) {
|
|
424
451
|
block.to = this.resolveRoute(field.route, record)
|
|
425
452
|
}
|
|
@@ -546,6 +573,42 @@ export default {
|
|
|
546
573
|
return field.label || field.icon ? 'row' : 'line'
|
|
547
574
|
},
|
|
548
575
|
|
|
576
|
+
/**
|
|
577
|
+
* A meter entry's parts: the amount at `path` against the limit at
|
|
578
|
+
* `limitPath` (or a constant `limit`), both formatted with the entry's
|
|
579
|
+
* `format`. Without a positive numeric limit there is no bar, only the
|
|
580
|
+
* amount. Past the limit the text says so, so the state never rests on
|
|
581
|
+
* the bar's colour alone.
|
|
582
|
+
*
|
|
583
|
+
* @param {object} record The object.
|
|
584
|
+
* @param {object} field The entry.
|
|
585
|
+
* @return {{label: string, value: string, amount: number|null, limit: number|null, percent: number, over: boolean}}
|
|
586
|
+
* @spec openspec/changes/facts-meter-row/specs/facts-meter-row/spec.md#requirement-a-facts-entry-can-be-a-meter
|
|
587
|
+
*/
|
|
588
|
+
meterOf(record, field) {
|
|
589
|
+
const amount = Number(readPath(record, field.path))
|
|
590
|
+
const rawLimit = typeof field.limitPath === 'string' && field.limitPath !== '' ? readPath(record, field.limitPath) : field.limit
|
|
591
|
+
const limit = rawLimit === null || rawLimit === undefined || rawLimit === '' ? NaN : Number(rawLimit)
|
|
592
|
+
const label = this.tr(field.label || '')
|
|
593
|
+
const value = this.valueOf(record, field)
|
|
594
|
+
if (!Number.isFinite(amount) || !Number.isFinite(limit) || limit <= 0) {
|
|
595
|
+
return { label, value, amount: Number.isFinite(amount) ? amount : null, limit: null, percent: 0, over: false }
|
|
596
|
+
}
|
|
597
|
+
const over = amount > limit
|
|
598
|
+
const limitText = this.format(limit, field, record)
|
|
599
|
+
return {
|
|
600
|
+
label,
|
|
601
|
+
value: over
|
|
602
|
+
? t('nextcloud-vue', '{value} of {limit}, over the limit', { value, limit: limitText })
|
|
603
|
+
: t('nextcloud-vue', '{value} of {limit}', { value, limit: limitText }),
|
|
604
|
+
|
|
605
|
+
amount,
|
|
606
|
+
limit,
|
|
607
|
+
percent: Math.max(0, Math.min(100, (amount / limit) * 100)),
|
|
608
|
+
over,
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
|
|
549
612
|
/**
|
|
550
613
|
* The display text of an entry of any kind: a lead's title, the parts of
|
|
551
614
|
* a line or text, a constant `text`, else the value at `path`.
|
|
@@ -988,6 +1051,10 @@ export default {
|
|
|
988
1051
|
? formatBoardDate(date, new Date(date.getFullYear() + 1, 0, 1))
|
|
989
1052
|
: formatBoardDate(date)
|
|
990
1053
|
}
|
|
1054
|
+
if (format === 'bytes') {
|
|
1055
|
+
const bytes = Number(raw)
|
|
1056
|
+
return Number.isFinite(bytes) && bytes >= 0 ? formatFileSize(bytes) : ''
|
|
1057
|
+
}
|
|
991
1058
|
if (format === 'user') {
|
|
992
1059
|
return userDisplayName(raw)
|
|
993
1060
|
}
|
|
@@ -1125,6 +1192,50 @@ export default {
|
|
|
1125
1192
|
color: var(--color-text-maxcontrast);
|
|
1126
1193
|
}
|
|
1127
1194
|
|
|
1195
|
+
.cn-facts-widget__meter {
|
|
1196
|
+
display: flex;
|
|
1197
|
+
flex-direction: column;
|
|
1198
|
+
gap: 4px;
|
|
1199
|
+
font-size: 14px;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.cn-facts-widget__meter-head {
|
|
1203
|
+
display: flex;
|
|
1204
|
+
justify-content: space-between;
|
|
1205
|
+
gap: 16px;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.cn-facts-widget__meter-label {
|
|
1209
|
+
color: var(--color-text-maxcontrast);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.cn-facts-widget__meter-value {
|
|
1213
|
+
font-weight: 500;
|
|
1214
|
+
text-align: end;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.cn-facts-widget__meter-track {
|
|
1218
|
+
height: 6px;
|
|
1219
|
+
border-radius: 3px;
|
|
1220
|
+
background: var(--color-background-darker);
|
|
1221
|
+
overflow: hidden;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.cn-facts-widget__meter-bar {
|
|
1225
|
+
display: block;
|
|
1226
|
+
height: 100%;
|
|
1227
|
+
border-radius: inherit;
|
|
1228
|
+
background: var(--color-primary-element);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.cn-facts-widget__meter--over .cn-facts-widget__meter-value {
|
|
1232
|
+
color: var(--color-error-text);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.cn-facts-widget__meter--over .cn-facts-widget__meter-bar {
|
|
1236
|
+
background: var(--color-error);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1128
1239
|
.cn-facts-widget__muted {
|
|
1129
1240
|
margin: 0;
|
|
1130
1241
|
color: var(--color-text-maxcontrast);
|