@conduction/nextcloud-vue 2.56.0-beta.47 → 2.56.0-beta.49
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/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +46 -28
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +17 -10
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +91 -36
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +115 -72
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +56 -16
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js +9 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js +240 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js +65 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js +30 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +17 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +30 -31
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js +6 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js.map +1 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js +90 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +51 -4
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +20 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +230 -25
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +447 -221
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +20 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +18 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +18 -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 +712 -330
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.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 +8275 -6302
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +712 -330
- package/l10n/en.json +18 -1
- package/l10n/nl.json +18 -1
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +20 -0
- package/src/components/CnBrandBar/CnBrandBar.vue +5 -4
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +96 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +39 -10
- package/src/components/CnDetailPage/CnDetailPage.vue +55 -15
- package/src/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue +360 -0
- package/src/components/CnDeviceFramesWidget/index.js +29 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +46 -2
- package/src/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue +107 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +84 -4
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +10 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +511 -28
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +31 -2
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +14 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +27 -0
- package/src/css/page-header.css +16 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +27 -15
- package/src/utils/dueText.js +49 -8
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json
CHANGED
|
@@ -1517,7 +1517,12 @@
|
|
|
1517
1517
|
"Your account": "Your account",
|
|
1518
1518
|
"Only for you. Colleagues keep their own settings.": "Only for you. Colleagues keep their own settings.",
|
|
1519
1519
|
"Sections of the settings": "Sections of the settings",
|
|
1520
|
-
"Changes are saved straight away": "Changes are saved straight away"
|
|
1520
|
+
"Changes are saved straight away": "Changes are saved straight away",
|
|
1521
|
+
"Change the schema for {type}": "Change the schema for {type}",
|
|
1522
|
+
"Show all types": "Show all types",
|
|
1523
|
+
"All types set up": "All types set up",
|
|
1524
|
+
"Set up": "Set up",
|
|
1525
|
+
"{configured} of {total} set up": "{configured} of {total} set up"
|
|
1521
1526
|
},
|
|
1522
1527
|
"plurals": {
|
|
1523
1528
|
"{count} selected": [
|
|
@@ -1599,6 +1604,18 @@
|
|
|
1599
1604
|
"{count} more filter": [
|
|
1600
1605
|
"{count} more filter",
|
|
1601
1606
|
"{count} more filters"
|
|
1607
|
+
],
|
|
1608
|
+
"%n type": [
|
|
1609
|
+
"%n type",
|
|
1610
|
+
"%n types"
|
|
1611
|
+
],
|
|
1612
|
+
"And %n more type in this group, set up.": [
|
|
1613
|
+
"And %n more type in this group, set up.",
|
|
1614
|
+
"And %n more types in this group, all set up."
|
|
1615
|
+
],
|
|
1616
|
+
"And %n more type in this group.": [
|
|
1617
|
+
"And %n more type in this group.",
|
|
1618
|
+
"And %n more types in this group."
|
|
1602
1619
|
]
|
|
1603
1620
|
}
|
|
1604
1621
|
}
|
package/l10n/nl.json
CHANGED
|
@@ -1703,7 +1703,12 @@
|
|
|
1703
1703
|
"Your account": "Uw account",
|
|
1704
1704
|
"Only for you. Colleagues keep their own settings.": "Alleen voor jou. Collega's houden hun eigen instellingen.",
|
|
1705
1705
|
"Sections of the settings": "Onderdelen van de instellingen",
|
|
1706
|
-
"Changes are saved straight away": "Wijzigingen worden meteen bewaard"
|
|
1706
|
+
"Changes are saved straight away": "Wijzigingen worden meteen bewaard",
|
|
1707
|
+
"Change the schema for {type}": "Schema wijzigen voor {type}",
|
|
1708
|
+
"Show all types": "Alle soorten tonen",
|
|
1709
|
+
"All types set up": "Alle soorten ingesteld",
|
|
1710
|
+
"Set up": "Ingesteld",
|
|
1711
|
+
"{configured} of {total} set up": "{configured} van {total} ingesteld"
|
|
1707
1712
|
},
|
|
1708
1713
|
"plurals": {
|
|
1709
1714
|
"{count} selected": [
|
|
@@ -1785,6 +1790,18 @@
|
|
|
1785
1790
|
"{count} more filter": [
|
|
1786
1791
|
"Nog {count} filter",
|
|
1787
1792
|
"Nog {count} filters"
|
|
1793
|
+
],
|
|
1794
|
+
"%n type": [
|
|
1795
|
+
"%n soort",
|
|
1796
|
+
"%n soorten"
|
|
1797
|
+
],
|
|
1798
|
+
"And %n more type in this group, set up.": [
|
|
1799
|
+
"En nog %n soort in deze groep, ingesteld.",
|
|
1800
|
+
"En nog %n soorten in deze groep, allemaal ingesteld."
|
|
1801
|
+
],
|
|
1802
|
+
"And %n more type in this group.": [
|
|
1803
|
+
"En nog %n soort in deze groep.",
|
|
1804
|
+
"En nog %n soorten in deze groep."
|
|
1788
1805
|
]
|
|
1789
1806
|
}
|
|
1790
1807
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.56.0-beta.
|
|
3
|
+
"version": "2.56.0-beta.49",
|
|
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": {
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
<template #icon>
|
|
33
33
|
<DotsHorizontal :size="20" />
|
|
34
34
|
</template>
|
|
35
|
+
<!-- @slot lead-items Items rendered at the very top of the menu, above Refresh: the tabs a board strip lists here instead of drawing them (CnTabsWidget). The caller owns any separator after them. -->
|
|
36
|
+
<slot name="lead-items" />
|
|
35
37
|
<!-- Stays open while refreshing; onRefreshClick closes the menu once the refresh settles. -->
|
|
36
38
|
<NcActionButton
|
|
37
39
|
v-if="showRefresh"
|
|
@@ -569,7 +571,7 @@ export default {
|
|
|
569
571
|
if (this.showRequestFeature) {
|
|
570
572
|
return true
|
|
571
573
|
}
|
|
572
|
-
if (this.hasPrimaryItems || this.$slots['primary-items']) {
|
|
574
|
+
if (this.hasPrimaryItems || this.$slots['primary-items'] || this.$slots['lead-items']) {
|
|
573
575
|
return true
|
|
574
576
|
}
|
|
575
577
|
return Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
<template #footer>
|
|
140
140
|
<!-- @slot footer Replaces the default support/SLA footer inside the version card. -->
|
|
141
141
|
<slot name="footer">
|
|
142
|
-
<div v-if="showSupport" class="cn-support-info">
|
|
142
|
+
<div v-if="showSupport && !supportAtEnd" class="cn-support-info">
|
|
143
143
|
<h4>{{ supportTitle }}</h4>
|
|
144
144
|
<p>
|
|
145
145
|
{{ supportLine }}
|
|
@@ -185,6 +185,24 @@
|
|
|
185
185
|
<!-- @slot default The app's own settings sections, rendered below the version card, or above it with `versionCardPosition: "end"`. With `tabs`, only while the first tab is open. -->
|
|
186
186
|
<slot v-if="!versionCardAtEnd && onFirstTab" />
|
|
187
187
|
|
|
188
|
+
<!-- `supportPosition: "end"`: the support lines as their own wide card,
|
|
189
|
+
last on the page (board PqBeheer), not in the version card. -->
|
|
190
|
+
<CnSettingsSection
|
|
191
|
+
v-if="showSupport && supportAtEnd && onFirstTab"
|
|
192
|
+
class="cn-admin-settings-shell__support"
|
|
193
|
+
:name="supportTitle"
|
|
194
|
+
:wide="true"
|
|
195
|
+
data-testid="cn-admin-settings-support">
|
|
196
|
+
<p class="cn-admin-settings-shell__support-line">
|
|
197
|
+
{{ supportLine }}
|
|
198
|
+
<a :href="`mailto:${supportEmail}`">{{ supportEmail }}</a>
|
|
199
|
+
</p>
|
|
200
|
+
<p v-if="slaEmail" class="cn-admin-settings-shell__support-line">
|
|
201
|
+
{{ slaLine }}
|
|
202
|
+
<a :href="`mailto:${slaEmail}`">{{ slaEmail }}</a>
|
|
203
|
+
</p>
|
|
204
|
+
</CnSettingsSection>
|
|
205
|
+
|
|
188
206
|
<!-- First-time setup wizard, opened from the admin page (ADR-042). -->
|
|
189
207
|
<CnSetupWizard
|
|
190
208
|
v-if="showSetup && setupWizardOpen"
|
|
@@ -216,6 +234,7 @@ import { normalizeLook, useLook } from '../../composables/useLook.js'
|
|
|
216
234
|
import { ensureTranslations } from '../../l10n/index.js'
|
|
217
235
|
import { buildFeatureRequestUrl } from '../../utils/forge.js'
|
|
218
236
|
import { prefixUrl } from '../../utils/headers.js'
|
|
237
|
+
import { CnSettingsSection } from '../CnSettingsSection/index.js'
|
|
219
238
|
import { CnTab, CnTabs } from '../CnTabs/index.js'
|
|
220
239
|
import { CnVersionInfoCard } from '../CnVersionInfoCard/index.js'
|
|
221
240
|
|
|
@@ -327,6 +346,7 @@ export default {
|
|
|
327
346
|
BookOpenVariant,
|
|
328
347
|
NcSettingsSection,
|
|
329
348
|
CnCredentials,
|
|
349
|
+
CnSettingsSection,
|
|
330
350
|
CnVersionInfoCard,
|
|
331
351
|
CnSetupWizard,
|
|
332
352
|
CnTab,
|
|
@@ -616,6 +636,19 @@ export default {
|
|
|
616
636
|
default: true,
|
|
617
637
|
},
|
|
618
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Where the support lines go: `card`, in the version card's footer (the
|
|
641
|
+
* default), or `end`, as their own wide card after everything else on
|
|
642
|
+
* the first tab (board PqBeheer).
|
|
643
|
+
*
|
|
644
|
+
* @spec openspec/changes/admin-shell-support-end/specs/settings-board-look/spec.md#requirement-support-can-be-its-own-card-at-the-end
|
|
645
|
+
*/
|
|
646
|
+
supportPosition: {
|
|
647
|
+
type: String,
|
|
648
|
+
default: 'card',
|
|
649
|
+
validator: (value) => ['card', 'end'].includes(value),
|
|
650
|
+
},
|
|
651
|
+
|
|
619
652
|
/** Support contact email. */
|
|
620
653
|
supportEmail: {
|
|
621
654
|
type: String,
|
|
@@ -723,6 +756,11 @@ export default {
|
|
|
723
756
|
return this.versionCardPosition === 'end'
|
|
724
757
|
},
|
|
725
758
|
|
|
759
|
+
/** @return {boolean} Whether the support lines are their own card at the end. */
|
|
760
|
+
supportAtEnd() {
|
|
761
|
+
return this.supportPosition === 'end'
|
|
762
|
+
},
|
|
763
|
+
|
|
726
764
|
/**
|
|
727
765
|
* The crumbs that have a label.
|
|
728
766
|
*
|
|
@@ -193,6 +193,8 @@
|
|
|
193
193
|
<template v-else>
|
|
194
194
|
<!-- The brand block in Nextcloud's own header (board look); teleported, renders nothing here. -->
|
|
195
195
|
<CnBrandBar v-if="brandInHeader" :brand="resolvedBrandBlock" @unavailable="headerMissing = true" />
|
|
196
|
+
<!-- The app's own line on the header's search field (board look); renders nothing here. -->
|
|
197
|
+
<CnHeaderSearchLabel v-if="headerSearchLabel" :label="headerSearchLabel" />
|
|
196
198
|
<!--
|
|
197
199
|
@slot menu
|
|
198
200
|
@description Left-rail navigation surface. Default:
|
|
@@ -713,6 +715,7 @@ import CnCommandPalette from '../CnCommandPalette/CnCommandPalette.vue'
|
|
|
713
715
|
import CnCredentials from '../CnCredentials/CnCredentials.vue'
|
|
714
716
|
import CnDependencyMissing from '../CnDependencyMissing/CnDependencyMissing.vue'
|
|
715
717
|
import CnEnvironmentBanner from '../CnEnvironmentBanner/CnEnvironmentBanner.vue'
|
|
718
|
+
import CnHeaderSearchLabel from '../CnHeaderSearchLabel/CnHeaderSearchLabel.vue'
|
|
716
719
|
import CnNotificationPreferences from '../CnNotificationPreferences/CnNotificationPreferences.vue'
|
|
717
720
|
import CnObjectSidebar from '../CnObjectSidebar/CnObjectSidebar.vue'
|
|
718
721
|
import CnSetupWizard from '../CnSetupWizard/CnSetupWizard.vue'
|
|
@@ -819,6 +822,7 @@ export default {
|
|
|
819
822
|
|
|
820
823
|
components: {
|
|
821
824
|
CnBrandBar,
|
|
825
|
+
CnHeaderSearchLabel,
|
|
822
826
|
NcAppContent,
|
|
823
827
|
NcAppSettingsDialog,
|
|
824
828
|
NcAppSettingsSection,
|
|
@@ -2195,6 +2199,22 @@ export default {
|
|
|
2195
2199
|
&& this.resolvedBrandBlock.placement !== 'nav'
|
|
2196
2200
|
},
|
|
2197
2201
|
|
|
2202
|
+
/**
|
|
2203
|
+
* The line on the search field in Nextcloud's header: the manifest's
|
|
2204
|
+
* `nav.searchPlaceholder`, translated, under the board look on a page
|
|
2205
|
+
* with a header. Empty otherwise, and then the field keeps Nextcloud's line.
|
|
2206
|
+
*
|
|
2207
|
+
* @spec openspec/changes/screens-board-top-bar-search/specs/layout-components/spec.md#requirement-the-board-look-names-what-the-header-search-finds
|
|
2208
|
+
* @return {string}
|
|
2209
|
+
*/
|
|
2210
|
+
headerSearchLabel() {
|
|
2211
|
+
const key = this.manifest?.nav?.searchPlaceholder
|
|
2212
|
+
if (this.resolvedLook !== 'board' || !this.headerPresent || typeof key !== 'string' || key === '') {
|
|
2213
|
+
return ''
|
|
2214
|
+
}
|
|
2215
|
+
return this.manifestTranslate(key)
|
|
2216
|
+
},
|
|
2217
|
+
|
|
2198
2218
|
/**
|
|
2199
2219
|
* The label lookup provided as `cnTranslate`: the manifest's own
|
|
2200
2220
|
* translations for the language first, then the host `translate`, then the
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
CnBrandBar — the brand block in Nextcloud's own header (screens-brand-block-top-bar).
|
|
3
3
|
|
|
4
|
-
Draws the 237px brand
|
|
5
|
-
divider
|
|
4
|
+
Draws the brand block (a 237px brand link with the emblem, organisation and
|
|
5
|
+
app name, then a 1px divider 12px on) at the START of `#header`, before the app menu, under the
|
|
6
6
|
board look. CnAppRoot mounts it when a brand is declared and `#header` exists.
|
|
7
7
|
|
|
8
8
|
`#header` is Nextcloud's and offers no extension point for apps, so this is
|
|
@@ -170,11 +170,12 @@ export default {
|
|
|
170
170
|
display: flex;
|
|
171
171
|
align-items: center;
|
|
172
172
|
gap: 12px;
|
|
173
|
-
width: var(--cn-board-brand-width, 237px);
|
|
174
173
|
}
|
|
175
174
|
|
|
175
|
+
/* The brand link is 237px; the divider follows it, 12px on. */
|
|
176
176
|
.cn-brand-bar__brand {
|
|
177
|
-
flex:
|
|
177
|
+
flex: none;
|
|
178
|
+
width: var(--cn-board-brand-width, 237px);
|
|
178
179
|
box-sizing: border-box;
|
|
179
180
|
display: flex;
|
|
180
181
|
align-items: center;
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
|
-
<div
|
|
8
|
-
|
|
7
|
+
<div
|
|
8
|
+
class="cn-kpi-card cn-countdown-widget"
|
|
9
|
+
:class="[`cn-countdown-widget--${variant}`, { 'cn-kpi-card--board cn-countdown-widget--board': isBoardLook }]">
|
|
10
|
+
<div v-if="!isBoardLook" class="cn-kpi-card__icon cn-countdown-widget__icon" :style="iconCircleStyle">
|
|
9
11
|
<CnWidgetIcon :name="resolvedIcon" :size="24" />
|
|
10
12
|
</div>
|
|
11
13
|
|
|
@@ -16,9 +18,35 @@
|
|
|
16
18
|
<div class="cn-kpi-card__value cn-countdown-widget__value" :style="{ color: valueColor }">
|
|
17
19
|
{{ headline }}
|
|
18
20
|
</div>
|
|
19
|
-
<div v-if="subLabel" class="cn-countdown-widget__sub">
|
|
21
|
+
<div v-if="subLabel" class="cn-countdown-widget__sub" data-testid="cn-countdown-sub">
|
|
20
22
|
{{ subLabel }}
|
|
21
23
|
</div>
|
|
24
|
+
<dl v-if="facts.length" class="cn-countdown-widget__facts" data-testid="cn-countdown-facts">
|
|
25
|
+
<template v-for="fact in facts" :key="fact.label">
|
|
26
|
+
<dt>{{ fact.label }}</dt>
|
|
27
|
+
<dd>{{ fact.value }}</dd>
|
|
28
|
+
</template>
|
|
29
|
+
</dl>
|
|
30
|
+
<span
|
|
31
|
+
v-if="progress !== null"
|
|
32
|
+
class="cn-countdown-widget__progress"
|
|
33
|
+
role="meter"
|
|
34
|
+
:aria-label="progressLabel"
|
|
35
|
+
:aria-valuenow="Math.round(progress)"
|
|
36
|
+
aria-valuemin="0"
|
|
37
|
+
aria-valuemax="100"
|
|
38
|
+
data-testid="cn-countdown-progress">
|
|
39
|
+
<span class="cn-countdown-widget__progress-fill" :style="{ width: `${progress}%` }" />
|
|
40
|
+
</span>
|
|
41
|
+
<button
|
|
42
|
+
v-if="actionLabel"
|
|
43
|
+
type="button"
|
|
44
|
+
class="cn-countdown-widget__action"
|
|
45
|
+
:class="{ 'cn-countdown-widget__action--primary': content.action && content.action.variant === 'primary' }"
|
|
46
|
+
data-testid="cn-countdown-action"
|
|
47
|
+
@click="runAction">
|
|
48
|
+
{{ actionLabel }}
|
|
49
|
+
</button>
|
|
22
50
|
</div>
|
|
23
51
|
</div>
|
|
24
52
|
</template>
|
|
@@ -28,7 +56,10 @@ import { translate as t } from '@nextcloud/l10n'
|
|
|
28
56
|
import { inject } from 'vue'
|
|
29
57
|
import CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'
|
|
30
58
|
import { getByPath } from '../../composables/useEndpointSource.js'
|
|
59
|
+
import { normalizeLook } from '../../composables/useLook.js'
|
|
60
|
+
import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
31
61
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
62
|
+
import { resolveDeepTokens } from '../../utils/resolveFilterTokens.js'
|
|
32
63
|
import { userLocale } from '../../utils/userLocale.js'
|
|
33
64
|
|
|
34
65
|
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
|
@@ -106,8 +137,15 @@ export default {
|
|
|
106
137
|
* - `showDate` — render the absolute date underneath (default true).
|
|
107
138
|
* - `settledField` — property on the record that is truthy once the work is done; the tile then reads `settledLabel` and never counts overdue.
|
|
108
139
|
* - `settledLabel` — the headline of a settled record, run through the host's translate function (default "Closed").
|
|
140
|
+
* - `daysLabel` — `{ one, other }` with `{count}`, the headline while days remain ("27 dagen").
|
|
141
|
+
* - `caption` — the line under the headline instead of the date, with `{date}` (the long date) and `{count}` (days left).
|
|
142
|
+
* - `startField` — property holding the start of the term; a bar then shows how much of it has passed.
|
|
143
|
+
* - `action` — one manifest action under the card (`{ label, type, variant?, ... }`, the headerActions shape; `variant: "primary"` draws it in the primary tint).
|
|
144
|
+
* - `emptyLabel` — the headline while the date is unset (default a dash).
|
|
145
|
+
* - `emptyCaption` — the line under it while the date is unset, with `{date}` read from `emptyDateField`.
|
|
146
|
+
* - `facts` — `[{ label, field }]` rows under the caption.
|
|
109
147
|
*
|
|
110
|
-
* @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string }}
|
|
148
|
+
* @type {{ field?: string, label?: string, icon?: string, thresholds?: {warn?: number, danger?: number}, showDate?: boolean, settledField?: string, settledLabel?: string, daysLabel?: {one?: string, other?: string}, caption?: string, startField?: string, action?: object, emptyLabel?: string, emptyCaption?: string, emptyDateField?: string, facts?: Array<{label: string, field: string}> }}
|
|
111
149
|
*/
|
|
112
150
|
content: {
|
|
113
151
|
type: Object,
|
|
@@ -131,6 +169,8 @@ export default {
|
|
|
131
169
|
cnObjectContextInjected: inject('cnObjectContext', null),
|
|
132
170
|
cnDetailObjectContextInjected: inject('cnDetailObjectContext', null),
|
|
133
171
|
cnTranslate: inject('cnTranslate', null),
|
|
172
|
+
cnLook: inject('cnLook', 'nextcloud'),
|
|
173
|
+
cnDispatchAction: inject('cnDispatchAction', null),
|
|
134
174
|
}
|
|
135
175
|
},
|
|
136
176
|
|
|
@@ -222,7 +262,7 @@ export default {
|
|
|
222
262
|
}
|
|
223
263
|
const days = this.daysRemaining
|
|
224
264
|
if (days === null) {
|
|
225
|
-
return '—'
|
|
265
|
+
return this.content?.emptyLabel ? this.translate(this.content.emptyLabel) : '—'
|
|
226
266
|
}
|
|
227
267
|
if (days < 0) {
|
|
228
268
|
const overdue = Math.abs(days)
|
|
@@ -233,12 +273,89 @@ export default {
|
|
|
233
273
|
if (days === 0) {
|
|
234
274
|
return t('nextcloud-vue', 'Due today')
|
|
235
275
|
}
|
|
276
|
+
const labels = this.content?.daysLabel
|
|
277
|
+
if (labels && typeof labels === 'object' && (labels.one || labels.other)) {
|
|
278
|
+
const template = this.translate((days === 1 ? labels.one : labels.other) || labels.other || labels.one)
|
|
279
|
+
return template.replace(/\{count\}/g, String(days))
|
|
280
|
+
}
|
|
236
281
|
if (days === 1) {
|
|
237
282
|
return t('nextcloud-vue', '1 day left')
|
|
238
283
|
}
|
|
239
284
|
return t('nextcloud-vue', '{count} days left', { count: days })
|
|
240
285
|
},
|
|
241
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Whether the app takes the board look: no icon circle.
|
|
289
|
+
*
|
|
290
|
+
* @return {boolean} True in the board look.
|
|
291
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
292
|
+
*/
|
|
293
|
+
isBoardLook() {
|
|
294
|
+
return normalizeLook(this.cnLook) === 'board'
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* How much of the term has passed, from `startField` to the date, in
|
|
299
|
+
* whole days and as a percentage (0 to 100), or null without a usable
|
|
300
|
+
* start.
|
|
301
|
+
*
|
|
302
|
+
* @return {number|null} The share passed.
|
|
303
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
304
|
+
*/
|
|
305
|
+
progress() {
|
|
306
|
+
const field = this.content?.startField
|
|
307
|
+
if (!field || !this.record || !this.targetDate || this.isSettled) {
|
|
308
|
+
return null
|
|
309
|
+
}
|
|
310
|
+
const raw = getByPath(this.record, field)
|
|
311
|
+
const start = raw === null || raw === undefined || raw === '' ? null : new Date(raw)
|
|
312
|
+
if (!start || Number.isNaN(start.getTime())) {
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
const startOfDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())
|
|
316
|
+
const span = startOfDay(this.targetDate) - startOfDay(start)
|
|
317
|
+
if (span <= 0) {
|
|
318
|
+
return null
|
|
319
|
+
}
|
|
320
|
+
return Math.min(100, Math.max(0, ((startOfDay(new Date()) - startOfDay(start)) / span) * 100))
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The `facts` rows under the caption (`[{ label, field }]`), with
|
|
325
|
+
* their values read off the record; a row without a value is left out.
|
|
326
|
+
*
|
|
327
|
+
* @return {Array<{label: string, value: string}>} The rows.
|
|
328
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
329
|
+
*/
|
|
330
|
+
facts() {
|
|
331
|
+
const rows = Array.isArray(this.content?.facts) ? this.content.facts : []
|
|
332
|
+
return rows
|
|
333
|
+
.filter((row) => row && typeof row.label === 'string' && typeof row.field === 'string')
|
|
334
|
+
.map((row) => {
|
|
335
|
+
const value = this.record ? getByPath(this.record, row.field) : null
|
|
336
|
+
return { label: this.translate(row.label), value: value === null || value === undefined ? '' : this.translate(String(value)) }
|
|
337
|
+
})
|
|
338
|
+
.filter((row) => row.value !== '')
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
/** @return {string} The accessible name of the progress bar. */
|
|
342
|
+
progressLabel() {
|
|
343
|
+
return t('nextcloud-vue', 'Time passed')
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The label of `content.action`, translated, or ''.
|
|
348
|
+
*
|
|
349
|
+
* @return {string} The label.
|
|
350
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
351
|
+
*/
|
|
352
|
+
actionLabel() {
|
|
353
|
+
const action = this.content?.action
|
|
354
|
+
return action && typeof action === 'object' && typeof action.label === 'string' && action.label !== ''
|
|
355
|
+
? this.translate(action.label)
|
|
356
|
+
: ''
|
|
357
|
+
},
|
|
358
|
+
|
|
242
359
|
/**
|
|
243
360
|
* The absolute date under the headline, so the tile answers "how long"
|
|
244
361
|
* and "when" at once.
|
|
@@ -246,10 +363,21 @@ export default {
|
|
|
246
363
|
* @return {string} The formatted date, or ''.
|
|
247
364
|
*/
|
|
248
365
|
subLabel() {
|
|
366
|
+
if (!this.targetDate && typeof this.content?.emptyCaption === 'string' && this.content.emptyCaption !== '') {
|
|
367
|
+
const raw = this.content.emptyDateField && this.record ? getByPath(this.record, this.content.emptyDateField) : null
|
|
368
|
+
const when = raw ? new Date(raw) : null
|
|
369
|
+
const date = when && !Number.isNaN(when.getTime()) ? when.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' }) : ''
|
|
370
|
+
return this.translate(this.content.emptyCaption).replace(/\{date\}/g, date)
|
|
371
|
+
}
|
|
249
372
|
if (!this.targetDate || this.content?.showDate === false) {
|
|
250
373
|
return ''
|
|
251
374
|
}
|
|
252
|
-
|
|
375
|
+
const date = this.targetDate.toLocaleDateString(userLocale(), { day: 'numeric', month: 'long', year: 'numeric' })
|
|
376
|
+
const caption = this.content?.caption
|
|
377
|
+
if (typeof caption === 'string' && caption !== '') {
|
|
378
|
+
return this.translate(caption).replace(/\{date\}/g, date).replace(/\{count\}/g, String(this.daysRemaining ?? ''))
|
|
379
|
+
}
|
|
380
|
+
return date
|
|
253
381
|
},
|
|
254
382
|
|
|
255
383
|
/**
|
|
@@ -278,6 +406,9 @@ export default {
|
|
|
278
406
|
|
|
279
407
|
/** @return {string} The value colour for the current band. */
|
|
280
408
|
valueColor() {
|
|
409
|
+
if (this.isBoardLook && this.variant === 'default') {
|
|
410
|
+
return 'var(--color-main-text)'
|
|
411
|
+
}
|
|
281
412
|
return VARIANT_COLORS[this.variant] || VARIANT_COLORS.default
|
|
282
413
|
},
|
|
283
414
|
|
|
@@ -296,6 +427,39 @@ export default {
|
|
|
296
427
|
return { color: this.valueColor }
|
|
297
428
|
},
|
|
298
429
|
},
|
|
430
|
+
|
|
431
|
+
methods: {
|
|
432
|
+
/**
|
|
433
|
+
* The host's translate, or the text unchanged.
|
|
434
|
+
*
|
|
435
|
+
* @param {string} text The text.
|
|
436
|
+
* @return {string} The translation.
|
|
437
|
+
*/
|
|
438
|
+
translate(text) {
|
|
439
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Dispatch `content.action` with the record's tokens resolved, through
|
|
444
|
+
* the page's dispatcher when there is one.
|
|
445
|
+
*
|
|
446
|
+
* @return {unknown}
|
|
447
|
+
* @spec openspec/changes/countdown-board-look/specs/countdown-board-look/spec.md#requirement-the-countdown-draws-the-boards-term-card
|
|
448
|
+
*/
|
|
449
|
+
runAction() {
|
|
450
|
+
const action = this.content?.action
|
|
451
|
+
if (!action || typeof action !== 'object') {
|
|
452
|
+
return undefined
|
|
453
|
+
}
|
|
454
|
+
const ctx = resolveObjectTokenContext(this.cnObjectContextInjected, this.cnDetailObjectContextInjected) || {}
|
|
455
|
+
const tokenCtx = { ...ctx, object: this.record || ctx.object || null, objectId: this.record?.id ?? ctx.objectId ?? null }
|
|
456
|
+
const resolved = resolveDeepTokens(action, tokenCtx)
|
|
457
|
+
if (typeof this.cnDispatchAction === 'function') {
|
|
458
|
+
return this.cnDispatchAction(resolved)
|
|
459
|
+
}
|
|
460
|
+
return dispatchAction(resolved, { router: this.$router || null, tokenCtx })
|
|
461
|
+
},
|
|
462
|
+
},
|
|
299
463
|
}
|
|
300
464
|
</script>
|
|
301
465
|
|
|
@@ -304,6 +468,58 @@ export default {
|
|
|
304
468
|
white-space: nowrap;
|
|
305
469
|
}
|
|
306
470
|
|
|
471
|
+
.cn-countdown-widget__progress {
|
|
472
|
+
background: var(--color-border);
|
|
473
|
+
border-radius: 3px;
|
|
474
|
+
display: block;
|
|
475
|
+
height: 6px;
|
|
476
|
+
margin-block-start: 10px;
|
|
477
|
+
overflow: hidden;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.cn-countdown-widget__progress-fill {
|
|
481
|
+
background: var(--color-primary-element);
|
|
482
|
+
display: block;
|
|
483
|
+
height: 100%;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.cn-countdown-widget__action {
|
|
487
|
+
align-self: flex-start;
|
|
488
|
+
background: var(--color-background-dark);
|
|
489
|
+
border: 0;
|
|
490
|
+
border-radius: var(--border-radius-pill, 17px);
|
|
491
|
+
color: var(--color-main-text);
|
|
492
|
+
font-weight: 600;
|
|
493
|
+
margin-block-start: 10px;
|
|
494
|
+
min-height: 34px;
|
|
495
|
+
padding: 0 12px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.cn-countdown-widget__action--primary {
|
|
499
|
+
background: var(--color-primary-element-light);
|
|
500
|
+
color: var(--color-primary-element-light-text);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.cn-countdown-widget__facts {
|
|
504
|
+
display: grid;
|
|
505
|
+
gap: 8px 16px;
|
|
506
|
+
grid-template-columns: max-content 1fr;
|
|
507
|
+
margin: 10px 0 0;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.cn-countdown-widget__facts dt {
|
|
511
|
+
color: var(--color-text-maxcontrast);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.cn-countdown-widget__facts dd {
|
|
515
|
+
margin: 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.cn-countdown-widget__action:hover,
|
|
519
|
+
.cn-countdown-widget__action:focus-visible {
|
|
520
|
+
background: var(--color-background-hover);
|
|
521
|
+
}
|
|
522
|
+
|
|
307
523
|
.cn-countdown-widget__sub {
|
|
308
524
|
color: var(--color-text-maxcontrast);
|
|
309
525
|
font-size: 0.85em;
|