@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.
Files changed (79) hide show
  1. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  2. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
  3. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  6. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  7. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
  8. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  9. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
  10. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  11. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  12. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
  13. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
  15. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  17. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
  18. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
  20. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  27. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  28. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +301 -18
  29. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
  34. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  35. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
  36. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
  38. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  39. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  40. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
  41. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  44. package/dist/esm/composables/indexSources.js +182 -3
  45. package/dist/esm/composables/indexSources.js.map +1 -1
  46. package/dist/esm/l10n/en.json.js +7 -1
  47. package/dist/esm/l10n/en.json.js.map +1 -1
  48. package/dist/esm/l10n/nl.json.js +7 -1
  49. package/dist/esm/l10n/nl.json.js.map +1 -1
  50. package/dist/esm/library-style.js +1 -1
  51. package/dist/esm/nextcloud-vue.css +301 -177
  52. package/dist/esm/utils/queryPresets.js +13 -2
  53. package/dist/esm/utils/queryPresets.js.map +1 -1
  54. package/dist/esm/utils/scopeListLayout.js +37 -1
  55. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  56. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  57. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  58. package/dist/nextcloud-vue.cjs.js +3009 -1907
  59. package/dist/nextcloud-vue.cjs.js.map +2 -2
  60. package/dist/nextcloud-vue.css +301 -177
  61. package/l10n/en.json +7 -1
  62. package/l10n/nl.json +7 -1
  63. package/package.json +1 -1
  64. package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
  65. package/src/components/CnConversationThread/CnConversationThread.md +12 -0
  66. package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
  67. package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
  68. package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
  69. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
  70. package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  71. package/src/components/CnIndexPage/CnIndexPage.vue +324 -24
  72. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
  73. package/src/components/CnIndexPage/useNamedSource.js +1 -1
  74. package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
  75. package/src/composables/indexSources.js +182 -3
  76. package/src/schemas/app-manifest-v2.schema.json +11 -4
  77. package/src/utils/queryPresets.js +13 -2
  78. package/src/utils/scopeListLayout.js +37 -1
  79. 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 `pill`.
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);