@conduction/nextcloud-vue 2.78.0 → 2.79.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 (115) hide show
  1. package/dist/bundled-packages.json +0 -2
  2. package/dist/esm/bundled-packages.json +0 -2
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +80 -0
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +115 -91
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
  20. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
  29. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  30. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
  31. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
  32. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +1 -1
  33. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +25 -2
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
  38. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  39. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
  40. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  41. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  43. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
  44. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  45. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  46. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  47. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
  48. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  49. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
  50. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  52. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  53. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  54. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  55. package/dist/esm/composables/useAppManifest.js +8 -3
  56. package/dist/esm/composables/useAppManifest.js.map +1 -1
  57. package/dist/esm/index.js +1 -0
  58. package/dist/esm/index.js.map +1 -1
  59. package/dist/esm/l10n/en.json.js +61 -1
  60. package/dist/esm/l10n/en.json.js.map +1 -1
  61. package/dist/esm/l10n/nl.json.js +106 -2
  62. package/dist/esm/l10n/nl.json.js.map +1 -1
  63. package/dist/esm/nextcloud-vue.css +314 -15
  64. package/dist/esm/utils/boardDate.js +193 -0
  65. package/dist/esm/utils/boardDate.js.map +1 -0
  66. package/dist/esm/utils/dynamicProperties.js +5 -1
  67. package/dist/esm/utils/dynamicProperties.js.map +1 -1
  68. package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
  69. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  70. package/dist/nextcloud-vue.cjs.js +2039 -581
  71. package/dist/nextcloud-vue.cjs.js.map +2 -2
  72. package/dist/nextcloud-vue.css +314 -15
  73. package/l10n/en.json +61 -1
  74. package/l10n/nl.json +106 -2
  75. package/package.json +1 -1
  76. package/scripts/build-validators.js +26 -0
  77. package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
  78. package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
  79. package/src/components/CnCellRenderer/CnCellRenderer.vue +119 -2
  80. package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
  81. package/src/components/CnDataTable/CnDataTable.md +2 -0
  82. package/src/components/CnDataTable/CnDataTable.vue +81 -2
  83. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
  84. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
  85. package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
  86. package/src/components/CnFilesBrowser/index.js +1 -0
  87. package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
  88. package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
  89. package/src/components/CnIndexPage/CnIndexPage.vue +27 -3
  90. package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
  91. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
  92. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
  93. package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
  94. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  95. package/src/components/index.js +1 -1
  96. package/src/composables/useAppManifest.js +8 -3
  97. package/src/css/look-board-detail.css +10 -0
  98. package/src/css/look-board-index.css +92 -14
  99. package/src/css/look-board.css +77 -0
  100. package/src/css/table.css +37 -0
  101. package/src/index.js +4 -0
  102. package/src/schemas/app-manifest-v2.schema.json +46 -1
  103. package/src/utils/boardDate.js +189 -0
  104. package/src/utils/dynamicProperties.js +5 -1
  105. package/src/utils/validateManifestV2.compiled.js +57 -2
  106. package/dist/esm/_virtual/equal.js +0 -4
  107. package/dist/esm/_virtual/equal.js.map +0 -1
  108. package/dist/esm/_virtual/ucs2length.js +0 -4
  109. package/dist/esm/_virtual/ucs2length.js.map +0 -1
  110. package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
  111. package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
  112. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
  113. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
  114. package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
  115. package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
package/l10n/nl.json CHANGED
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "translations": {
3
+ "You cannot see these files": "Je kunt deze bestanden niet zien",
4
+ "Delete {name}? This cannot be undone here.": "{name} verwijderen? Dit kun je hier niet ongedaan maken.",
3
5
  "Linked items": "Gekoppelde items",
4
6
  "Still loading: {sections}": "Nog aan het laden: {sections}",
5
7
  "Could not load {section}.": "{section} kon niet worden geladen.",
@@ -571,6 +573,8 @@
571
573
  "Order": "Volgorde",
572
574
  "Owner:": "Eigenaar:",
573
575
  "Page {current} of {total}": "Pagina {current} van {total}",
576
+ "{shown} of {total}": "{shown} van {total}",
577
+ "{from}–{to} of {total}": "{from}–{to} van {total}",
574
578
  "Parent schemas:": "Bovenliggende schema's:",
575
579
  "Password": "Wachtwoord",
576
580
  "Pattern (regex)": "Patroon (regex)",
@@ -769,7 +773,28 @@
769
773
  "AI assistant": "AI-assistent",
770
774
  "Message input": "Berichtinvoer",
771
775
  "Start new chat": "Nieuwe chat starten",
772
- "History": "Geschiedenis",
776
+ "History": "Historie",
777
+ "No audit entries yet": "Nog geen wijzigingen vastgelegd",
778
+ "Meetings": "Afspraken",
779
+ "No meetings": "Geen afspraken",
780
+ "Open in Calendar": "Openen in Agenda",
781
+ "(no title)": "(geen titel)",
782
+ "Could not load meetings.": "De afspraken konden niet worden geladen.",
783
+ "Could not load meeting.": "De afspraak kon niet worden geladen.",
784
+ "Calendar is currently unavailable.": "De agenda is nu niet beschikbaar.",
785
+ "Show all {count} fields": "Alle {count} velden tonen",
786
+ "Show less": "Minder tonen",
787
+ "{count} more": "Nog {count}",
788
+ "Identifier: {identifier}": "Kenmerk: {identifier}",
789
+ "What happened, who did it and when, newest first": "Wat er gebeurde, door wie en wanneer, nieuwste eerst",
790
+ "{app} is not installed": "{app} is niet geïnstalleerd",
791
+ "Install and enable the {app} app to see this.": "Installeer en activeer de app {app} om dit te zien.",
792
+ "{title} needs the {app} app. Install and enable it to see this.": "{title} heeft de app {app} nodig. Installeer en activeer die om dit te zien.",
793
+ "View all ({total})": "Alles bekijken ({total})",
794
+ "No items": "Geen items",
795
+ "Nothing here yet": "Hier staat nog niets",
796
+ "No items match this filter": "Geen items voldoen aan dit filter",
797
+ "Could not load these records": "Deze gegevens konden niet worden geladen",
773
798
  "No conversations yet": "Nog geen gesprekken",
774
799
  "Connecting...": "Verbinden...",
775
800
  "Loading conversations...": "Gesprekken laden...",
@@ -1360,7 +1385,78 @@
1360
1385
  "Filter by {column}, active": "Filteren op {column}, actief",
1361
1386
  "Turn notifications off": "Meldingen uitzetten",
1362
1387
  "Turn notifications on": "Meldingen aanzetten",
1363
- "Could not change notifications.": "Meldingen konden niet worden gewijzigd."
1388
+ "Could not change notifications.": "Meldingen konden niet worden gewijzigd.",
1389
+ "Actions for the selection": "Acties voor de selectie",
1390
+ "Active:": "Actief:",
1391
+ "Filter": "Filter",
1392
+ "List": "Lijst",
1393
+ "Map": "Kaart",
1394
+ "Remove filter: {label}": "Filter verwijderen: {label}",
1395
+ "Search and columns": "Zoeken en kolommen",
1396
+ "Sort by": "Sorteren op",
1397
+ "View mode": "Weergave",
1398
+ "With the selected {plural}": "Met de geselecteerde {plural}",
1399
+ "items": "items",
1400
+ "{count} active": "{count} actief",
1401
+ "Delete \"{name}\"": "\"{name}\" verwijderen",
1402
+ "No saved views yet": "Nog geen opgeslagen weergaven",
1403
+ "No views with this label": "Geen weergaven met dit label",
1404
+ "Save current view…": "Huidige weergave opslaan…",
1405
+ "Save view": "Weergave opslaan",
1406
+ "Saved views": "Opgeslagen weergaven",
1407
+ "Show only views labelled \"{label}\"": "Alleen weergaven met label \"{label}\" tonen",
1408
+ "Stop filtering on \"{label}\"": "Niet meer filteren op \"{label}\"",
1409
+ "{name}, level {level}": "{name}, niveau {level}",
1410
+ "{name}. Parts of this view come from one you cannot see.": "{name}. Delen van deze weergave komen uit een weergave die u niet kunt zien.",
1411
+ "More filters": "Meer filters",
1412
+ "Quick filters": "Snelfilters",
1413
+ "{count} filters": "{count} filters",
1414
+ "{count} more filters": "{count} filters meer",
1415
+ "{label} — show the other filters": "{label}, toon de andere filters",
1416
+ "A file or folder with that name is already here": "Hier staat al een bestand of map met die naam",
1417
+ "A file with that name is already here": "Hier staat al een bestand met die naam",
1418
+ "A name cannot contain a slash": "Een naam kan geen schuine streep bevatten",
1419
+ "Actions for {name}": "Acties voor {name}",
1420
+ "Download": "Downloaden",
1421
+ "Drop files here, or use New": "Sleep bestanden hierheen, of gebruik Nieuw",
1422
+ "Folder name": "Mapnaam",
1423
+ "Modified": "Gewijzigd",
1424
+ "New": "Nieuw",
1425
+ "New folder": "Nieuwe map",
1426
+ "New name": "Nieuwe naam",
1427
+ "Rename": "Naam wijzigen",
1428
+ "Show in Files": "Tonen in Bestanden",
1429
+ "Size": "Grootte",
1430
+ "The folder could not be created": "De map kon niet worden gemaakt",
1431
+ "The folder could not be read": "De map kon niet worden gelezen",
1432
+ "The name could not be changed": "De naam kon niet worden gewijzigd",
1433
+ "The upload failed": "Het uploaden is mislukt",
1434
+ "This folder is empty": "Deze map is leeg",
1435
+ "This folder no longer exists": "Deze map bestaat niet meer",
1436
+ "Try again": "Opnieuw proberen",
1437
+ "Upload files": "Bestanden uploaden",
1438
+ "You cannot see this folder": "Je kunt deze map niet zien",
1439
+ "{action} did not succeed": "{action} is niet gelukt",
1440
+ "Error:": "Fout:",
1441
+ "optional": "optioneel",
1442
+ "Copy link": "Link kopiëren",
1443
+ "Details and sharing": "Details en delen",
1444
+ "Link copied": "Link gekopieerd",
1445
+ "Share uploaded files": "Geüploade bestanden delen",
1446
+ "The link could not be copied": "De link kon niet worden gekopieerd",
1447
+ "Invalid JSON: {msg}": "Ongeldige JSON: {msg}",
1448
+ "Loading the fields this choice adds.": "De velden van deze keuze worden geladen.",
1449
+ "Restore": "Herstellen",
1450
+ "Save draft": "Concept opslaan",
1451
+ "Saved {title}": "{title} opgeslagen",
1452
+ "Saved, but some rows were not saved: {rows}": "Opgeslagen, maar sommige rijen zijn niet opgeslagen: {rows}",
1453
+ "The list of choices could not be loaded. Enter the value by hand.": "De keuzelijst kon niet worden geladen. Vul de waarde zelf in.",
1454
+ "You have unsaved changes from an earlier visit.": "Je hebt niet-opgeslagen wijzigingen van een eerder bezoek.",
1455
+ "no longer offered": "niet meer beschikbaar",
1456
+ "1 day left": "Nog 1 dag",
1457
+ "{count} days left": "Nog {count} dagen",
1458
+ "1 day overdue": "1 dag te laat",
1459
+ "{count} days overdue": "{count} dagen te laat"
1364
1460
  },
1365
1461
  "plurals": {
1366
1462
  "%n year": [
@@ -1371,6 +1467,10 @@
1371
1467
  "%n maand",
1372
1468
  "%n maanden"
1373
1469
  ],
1470
+ "%n hour": [
1471
+ "%n uur",
1472
+ "%n uur"
1473
+ ],
1374
1474
  "%n day": [
1375
1475
  "%n dag",
1376
1476
  "%n dagen"
@@ -1426,6 +1526,10 @@
1426
1526
  "in {count} day": [
1427
1527
  "over {count} dag",
1428
1528
  "over {count} dagen"
1529
+ ],
1530
+ "{count} more filter": [
1531
+ "Nog {count} filter",
1532
+ "Nog {count} filters"
1429
1533
  ]
1430
1534
  }
1431
1535
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.78.0",
3
+ "version": "2.79.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": {
@@ -148,6 +148,32 @@ if (/require\("ajv-formats/.test(moduleCode)) {
148
148
  throw new Error('build-validators: a require("ajv-formats…") reference survived inlining — the standalone validator is not self-contained')
149
149
  }
150
150
 
151
+ // Inline Ajv's runtime helpers for the same reason. The standalone output
152
+ // emits `require("ajv/dist/runtime/ucs2length").default` (every `minLength` /
153
+ // `maxLength`) and `require("ajv/dist/runtime/equal").default` (`enum` / `const`
154
+ // on objects). Rollup turns each into a bare side-effect import that fills an
155
+ // `__exports` object, and a consumer's webpack drops that import because the
156
+ // library's `sideEffects` allowlist does not name it. `.default` is then
157
+ // undefined, the validator throws "f is not a function" on the first string it
158
+ // length-checks, and useAppManifest's catch-all kept the bundled manifest: the
159
+ // `/api/manifest` delta never reached the navigation (observed 2026-10-10 on
160
+ // dossiq). Both helpers are self-contained functions (fast-deep-equal recurses
161
+ // through its own name), so their source inlines as is.
162
+ moduleCode = moduleCode.replace(
163
+ /require\("ajv\/dist\/runtime\/([A-Za-z0-9_]+)"\)\.default/g,
164
+ (_match, name) => {
165
+ const fn = require(`ajv/dist/runtime/${name}`).default
166
+ if (typeof fn !== 'function') {
167
+ throw new Error(`build-validators: ajv runtime helper "${name}" is not a function and cannot be inlined`)
168
+ }
169
+ return `(${fn.toString()})`
170
+ },
171
+ )
172
+
173
+ if (/require\(/.test(moduleCode)) {
174
+ throw new Error('build-validators: a require(…) survived inlining — the standalone validator is not self-contained')
175
+ }
176
+
151
177
  const banner = `// SPDX-License-Identifier: EUPL-1.2
152
178
  // Copyright (C) 2026 Conduction B.V.
153
179
  //
@@ -18,45 +18,54 @@
18
18
  @description Under the board look: saved views as chips and the Save view button, in row 1 after the quick-filter chips.
19
19
  -->
20
20
  <slot name="actions-end" />
21
- <NcButton v-if="showSidebarToggle"
22
- variant="secondary"
23
- class="cn-actions-bar__filter-button"
24
- data-testid="cn-actions-bar-filter-button"
25
- :pressed="sidebarOpen"
26
- @click="$emit('toggle-sidebar')">
27
- <template #icon>
28
- <Tune :size="18" />
29
- </template>
30
- {{ t('nextcloud-vue', 'Filter') }}
31
- <span
32
- v-if="filterCount > 0"
33
- class="cn-actions-bar__filter-badge"
34
- data-testid="cn-actions-bar-filter-badge"
35
- :aria-label="filterCountLabel">{{ filterCount }}</span>
36
- </NcButton>
37
- <div v-if="showViewToggle && viewSegments.length > 1"
38
- class="cn-actions-bar__view-toggle"
39
- role="group"
40
- :aria-label="t('nextcloud-vue', 'View')">
41
- <button
42
- v-for="seg in viewSegments"
43
- :key="seg.mode"
44
- type="button"
45
- class="cn-actions-bar__view-toggle-btn"
46
- :class="{ 'cn-actions-bar__view-toggle-btn--active': viewMode === seg.mode }"
47
- :aria-pressed="viewMode === seg.mode"
48
- :aria-label="seg.label"
49
- :title="seg.label"
50
- @click="$emit('view-mode-change', seg.mode)">
51
- <CnIcon v-if="seg.icon"
52
- :name="seg.icon"
53
- :size="24"
54
- class="cn-actions-bar__view-toggle-icon" />
55
- <component :is="seg.fallback"
56
- v-else
57
- :size="24"
58
- class="cn-actions-bar__view-toggle-icon" />
59
- </button>
21
+ <!--
22
+ Filter and the view switch are one unit: when row 1 is full they
23
+ wrap together to the start of the next line (the DqZaken board),
24
+ never the switch alone.
25
+ -->
26
+ <div v-if="showSidebarToggle || (showViewToggle && viewSegments.length > 1)"
27
+ class="cn-actions-bar__view-controls"
28
+ data-testid="cn-actions-bar-view-controls">
29
+ <NcButton v-if="showSidebarToggle"
30
+ variant="secondary"
31
+ class="cn-actions-bar__filter-button"
32
+ data-testid="cn-actions-bar-filter-button"
33
+ :pressed="sidebarOpen"
34
+ @click="$emit('toggle-sidebar')">
35
+ <template #icon>
36
+ <Tune :size="16" />
37
+ </template>
38
+ {{ t('nextcloud-vue', 'Filter') }}
39
+ <span
40
+ v-if="filterCount > 0"
41
+ class="cn-actions-bar__filter-badge"
42
+ data-testid="cn-actions-bar-filter-badge"
43
+ :aria-label="filterCountLabel">{{ filterCount }}</span>
44
+ </NcButton>
45
+ <div v-if="showViewToggle && viewSegments.length > 1"
46
+ class="cn-actions-bar__view-toggle"
47
+ role="group"
48
+ :aria-label="t('nextcloud-vue', 'View')">
49
+ <button
50
+ v-for="seg in viewSegments"
51
+ :key="seg.mode"
52
+ type="button"
53
+ class="cn-actions-bar__view-toggle-btn"
54
+ :class="{ 'cn-actions-bar__view-toggle-btn--active': viewMode === seg.mode }"
55
+ :aria-pressed="viewMode === seg.mode"
56
+ :aria-label="seg.label"
57
+ :title="seg.label"
58
+ @click="$emit('view-mode-change', seg.mode)">
59
+ <CnIcon v-if="seg.icon"
60
+ :name="seg.icon"
61
+ :size="24"
62
+ class="cn-actions-bar__view-toggle-icon" />
63
+ <component :is="seg.fallback"
64
+ v-else
65
+ :size="24"
66
+ class="cn-actions-bar__view-toggle-icon" />
67
+ </button>
68
+ </div>
60
69
  </div>
61
70
  </div>
62
71
  <div v-if="!isBoardLayout" class="cn-actions-bar__info">
@@ -3724,6 +3724,7 @@ export default {
3724
3724
  * entry continues there (local mirror + per-user preference).
3725
3725
  *
3726
3726
  * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
3727
+ * @spec openspec/changes/walkthrough-autostart-and-countdown-dutch/specs/walkthrough-autostart-and-countdown-dutch/spec.md#requirement-a-tour-that-opens-on-its-own-opens-once
3727
3728
  * @param {object} progress `{ tourId, stepId, index }` from CnWalkthrough.
3728
3729
  * @return {void}
3729
3730
  */
@@ -3731,9 +3732,18 @@ export default {
3731
3732
  if (!progress || !progress.tourId) {
3732
3733
  return
3733
3734
  }
3734
- // The first step of a fresh tour is where a restart lands anyway,
3735
- // so it costs no request.
3735
+ // A fresh tour that opens on its own is recorded as paused at its
3736
+ // first step, so a user who never touches it does not get it again
3737
+ // on every page load; "Continue" in the settings brings it back.
3738
+ // Kept out of memory so the open tour runs on as it is.
3736
3739
  if (progress.index === 0 && !this.walkthroughProgressValue) {
3740
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, {
3741
+ tourId: progress.tourId,
3742
+ stepId: progress.stepId || '',
3743
+ index: 0,
3744
+ version: String((this.manifest && this.manifest.version) || ''),
3745
+ paused: true,
3746
+ })
3737
3747
  return
3738
3748
  }
3739
3749
  const value = {
@@ -118,6 +118,19 @@
118
118
  <span v-else class="cn-cell-renderer__dash">—</span>
119
119
  </template>
120
120
 
121
+ <!-- Built-in "age" widget: how long something has waited ("4 uur",
122
+ "3 dagen"), coloured by widgetProps.variantWhen rules on the days
123
+ the cell shows. The full date is the tooltip. -->
124
+ <template v-else-if="widget === 'age'">
125
+ <time
126
+ v-if="dateTimestamp"
127
+ class="cn-cell-renderer__age"
128
+ :class="ageVariant ? 'cn-cell-renderer__age--' + ageVariant : null"
129
+ :datetime="dateTimestamp.toISOString()"
130
+ :title="absoluteDateLabel">{{ ageLabel }}</time>
131
+ <span v-else class="cn-cell-renderer__dash">—</span>
132
+ </template>
133
+
121
134
  <!-- Built-in "date" widget: a date whose colour follows
122
135
  widgetProps.variantWhen, rules on the number of days until it
123
136
  (0 = today, negative = overdue). -->
@@ -156,7 +169,12 @@
156
169
 
157
170
  <!-- Date / date-time: dynamic NcDateTime (relative time, absolute on hover) -->
158
171
  <template v-else-if="isDate">
159
- <NcDateTime v-if="dateTimestamp" :timestamp="dateTimestamp" />
172
+ <time
173
+ v-if="dateTimestamp && isBoardLook"
174
+ class="cn-cell-renderer__date"
175
+ :datetime="dateTimestamp.toISOString()"
176
+ :title="absoluteDateLabel">{{ boardDateLabel }}</time>
177
+ <NcDateTime v-else-if="dateTimestamp" :timestamp="dateTimestamp" />
160
178
  <span v-else class="cn-cell-renderer__dash">—</span>
161
179
  </template>
162
180
 
@@ -210,6 +228,8 @@ import { NcAvatar, NcDateTime } from '@nextcloud/vue'
210
228
  import CheckBold from 'vue-material-design-icons/CheckBold.vue'
211
229
  import CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'
212
230
  import CnGroupNameCell from './CnGroupNameCell.vue'
231
+ import { normalizeLook } from '../../composables/useLook.js'
232
+ import { formatAge, formatBoardDate, resolveAgeVariant } from '../../utils/boardDate.js'
213
233
  import { parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
214
234
  import { safeCurrencyCode } from '../../utils/formatMetric.js'
215
235
  import { objectFieldValue } from '../../utils/objectName.js'
@@ -272,6 +292,12 @@ export default {
272
292
  * Defaults to identity so standalone use is unaffected.
273
293
  */
274
294
  cnTranslate: { default: () => (key) => key },
295
+ /**
296
+ * The look the app is drawn in, provided by CnAppRoot (or a page with
297
+ * `config.look`). Under `board` a date cell reads the board's short
298
+ * form ("5 okt"). Defaults to `nextcloud`, which renders as before.
299
+ */
300
+ cnLook: { default: 'nextcloud' },
275
301
  },
276
302
 
277
303
  props: {
@@ -326,6 +352,10 @@ export default {
326
352
  * a date whose colour follows `widgetProps.variantWhen`, rules on the
327
353
  * number of days until the date (`[{ op: "lt", value: 0, variant:
328
354
  * "error" }, { op: "lte", value: 5, variant: "warning" }]`).
355
+ * The built-in id `"age"` renders how long the row has waited since
356
+ * the date ("4 hours", "3 days"), coloured by `widgetProps.variantWhen`
357
+ * rules on the days it shows (`[{ op: "gte", value: 3, variant:
358
+ * "error" }]`).
329
359
  * The built-in id `"group"` shows a Nextcloud group id (or a list of
330
360
  * them) as the group's display name; a column whose property is
331
361
  * marked `referenceType: "nextcloud-group"` gets it without asking.
@@ -528,6 +558,69 @@ export default {
528
558
  return variant === 'default' ? '' : variant
529
559
  },
530
560
 
561
+ /**
562
+ * Whether the app takes the board look (`cnLook` is `board`).
563
+ *
564
+ * @return {boolean}
565
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
566
+ */
567
+ isBoardLook() {
568
+ return normalizeLook(this.cnLook) === 'board'
569
+ },
570
+
571
+ /**
572
+ * The board's short date ("5 okt", "14 feb 2024" outside this year).
573
+ *
574
+ * @return {string}
575
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
576
+ */
577
+ boardDateLabel() {
578
+ return formatBoardDate(this.value)
579
+ },
580
+
581
+ /**
582
+ * The full date for a tooltip: day, month and year in the user's
583
+ * locale, with the time for a date-time value.
584
+ *
585
+ * @return {string|undefined}
586
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
587
+ */
588
+ absoluteDateLabel() {
589
+ const date = parseDateValue(this.value)
590
+ if (date === null) {
591
+ return undefined
592
+ }
593
+ const withTime = this.property?.format === 'date-time' || this.widget === 'age'
594
+ const options = { day: 'numeric', month: 'long', year: 'numeric', ...(withTime ? { hour: '2-digit', minute: '2-digit' } : {}) }
595
+ try {
596
+ return new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(date)
597
+ } catch {
598
+ return new Intl.DateTimeFormat(undefined, options).format(date)
599
+ }
600
+ },
601
+
602
+ /**
603
+ * `age` widget: how long the row has waited ("4 uur", "3 dagen").
604
+ *
605
+ * @return {string}
606
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
607
+ */
608
+ ageLabel() {
609
+ return formatAge(this.value)
610
+ },
611
+
612
+ /**
613
+ * `age` widget: the variant of the first `widgetProps.variantWhen`
614
+ * rule the shown days match, or '' for none.
615
+ *
616
+ * @return {string}
617
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
618
+ */
619
+ ageVariant() {
620
+ const variant = resolveAgeVariant(this.value, this.widgetProps && this.widgetProps.variantWhen)
621
+ return variant === 'default' ? '' : variant
622
+ },
623
+
531
624
  /**
532
625
  * `date` widget: the date written out in the user's locale.
533
626
  *
@@ -544,6 +637,11 @@ export default {
544
637
  // 08:30"); `widgetProps.timeOnly: true` shows just the time
545
638
  // ("08:30"), for a timetable row. Default: the date alone.
546
639
  const props = this.widgetProps || {}
640
+ // The board look writes a plain date in its short form ("5 okt");
641
+ // a time of day keeps the full form below.
642
+ if (this.isBoardLook && props.timeOnly !== true && props.showTime !== true) {
643
+ return formatBoardDate(date)
644
+ }
547
645
  const options = props.timeOnly === true
548
646
  ? { hour: '2-digit', minute: '2-digit' }
549
647
  : { day: 'numeric', month: 'short', year: 'numeric', ...(props.showTime === true ? { hour: '2-digit', minute: '2-digit' } : {}) }
@@ -1127,10 +1225,29 @@ export default {
1127
1225
  does not rest on colour alone. */
1128
1226
  .cn-cell-renderer__date--success,
1129
1227
  .cn-cell-renderer__date--warning,
1130
- .cn-cell-renderer__date--error {
1228
+ .cn-cell-renderer__date--error,
1229
+ .cn-cell-renderer__age--success,
1230
+ .cn-cell-renderer__age--warning,
1231
+ .cn-cell-renderer__age--error {
1131
1232
  font-weight: 600;
1132
1233
  }
1133
1234
 
1235
+ .cn-cell-renderer__age {
1236
+ white-space: nowrap;
1237
+ }
1238
+
1239
+ .cn-cell-renderer__age--success {
1240
+ color: var(--color-text-success, var(--color-success-text));
1241
+ }
1242
+
1243
+ .cn-cell-renderer__age--warning {
1244
+ color: var(--color-text-warning, var(--color-warning-text));
1245
+ }
1246
+
1247
+ .cn-cell-renderer__age--error {
1248
+ color: var(--color-text-error, var(--color-error-text));
1249
+ }
1250
+
1134
1251
  .cn-cell-renderer__date--success {
1135
1252
  color: var(--color-text-success, var(--color-success-text));
1136
1253
  }