@conduction/nextcloud-vue 2.91.0 → 2.92.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/CnDataTable/CnDataTable.vue2.js +10 -5
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +18 -10
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +16 -8
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +156 -3
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +168 -52
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js +9 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js +83 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js.map +1 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js +119 -0
- package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue.js +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js +63 -4
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js +19 -2
- package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +22 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +21 -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 +317 -103
- package/dist/esm/utils/boardDate.js +35 -1
- package/dist/esm/utils/boardDate.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +12 -3
- package/dist/esm/utils/fillTemplate.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 +11451 -10784
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +317 -103
- package/l10n/en.json +22 -1
- package/l10n/nl.json +21 -1
- package/package.json +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +10 -5
- package/src/components/CnFormDialog/CnFormDialog.md +1 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +13 -0
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
- package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -2
- package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
- package/src/schemas/app-manifest-v2.schema.json +59 -2
- package/src/utils/boardDate.js +34 -0
- package/src/utils/fillTemplate.js +12 -3
- package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json
CHANGED
|
@@ -499,6 +499,7 @@
|
|
|
499
499
|
"NC Activity is currently unavailable.": "NC Activity is currently unavailable.",
|
|
500
500
|
"Could not load activity.": "Could not load activity.",
|
|
501
501
|
"Today": "Today",
|
|
502
|
+
"Today, {date}": "Today, {date}",
|
|
502
503
|
"Last 8 hours": "Last 8 hours",
|
|
503
504
|
"Last 24 hours": "Last 24 hours",
|
|
504
505
|
"Last 7 days": "Last 7 days",
|
|
@@ -1473,7 +1474,27 @@
|
|
|
1473
1474
|
"Link related object": "Link related object",
|
|
1474
1475
|
"{type} parts": "{type} parts",
|
|
1475
1476
|
"Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.": "Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.",
|
|
1476
|
-
"Save failed": "Save failed"
|
|
1477
|
+
"Save failed": "Save failed",
|
|
1478
|
+
"Modules": "Modules",
|
|
1479
|
+
"In the menu": "In the menu",
|
|
1480
|
+
"Off": "Off",
|
|
1481
|
+
"Pages": "Pages",
|
|
1482
|
+
"Open {module}": "Open {module}",
|
|
1483
|
+
"{list} and {last}": "{list} and {last}",
|
|
1484
|
+
"%n page: {pages}": [
|
|
1485
|
+
"%n page: {pages}",
|
|
1486
|
+
"%n pages: {pages}"
|
|
1487
|
+
],
|
|
1488
|
+
"%n page, including {pages}": [
|
|
1489
|
+
"%n page, including {pages}",
|
|
1490
|
+
"%n pages, including {pages}"
|
|
1491
|
+
],
|
|
1492
|
+
"{on} of %n is in the menu": [
|
|
1493
|
+
"{on} of %n is in the menu",
|
|
1494
|
+
"{on} of %n are in the menu"
|
|
1495
|
+
],
|
|
1496
|
+
"Put it in the menu:": "Put it in the menu:",
|
|
1497
|
+
"Admin settings": "Admin settings"
|
|
1477
1498
|
},
|
|
1478
1499
|
"plurals": {
|
|
1479
1500
|
"{count} selected": [
|
package/l10n/nl.json
CHANGED
|
@@ -521,6 +521,7 @@
|
|
|
521
521
|
"NC Activity is currently unavailable.": "NC Activiteit is momenteel niet beschikbaar.",
|
|
522
522
|
"Could not load activity.": "Kon activiteit niet laden.",
|
|
523
523
|
"Today": "Vandaag",
|
|
524
|
+
"Today, {date}": "Vandaag, {date}",
|
|
524
525
|
"Last 8 hours": "Laatste 8 uur",
|
|
525
526
|
"Last 24 hours": "Laatste 24 uur",
|
|
526
527
|
"Last 7 days": "Laatste 7 dagen",
|
|
@@ -1660,7 +1661,26 @@
|
|
|
1660
1661
|
"Link related object": "Gerelateerd object koppelen",
|
|
1661
1662
|
"{type} parts": "Onderdelen van {type}",
|
|
1662
1663
|
"Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.": "Opslaan kan niet: dit record heeft geen id, dus de wijziging zou een kopie maken in plaats van het record bij te werken.",
|
|
1663
|
-
"Save failed": "Opslaan is mislukt"
|
|
1664
|
+
"Save failed": "Opslaan is mislukt",
|
|
1665
|
+
"Modules": "Modules",
|
|
1666
|
+
"In the menu": "In het menu",
|
|
1667
|
+
"Off": "Uit",
|
|
1668
|
+
"Pages": "Pagina's",
|
|
1669
|
+
"Open {module}": "{module} openen",
|
|
1670
|
+
"{list} and {last}": "{list} en {last}",
|
|
1671
|
+
"%n page: {pages}": [
|
|
1672
|
+
"%n pagina: {pages}",
|
|
1673
|
+
"%n pagina's: {pages}"
|
|
1674
|
+
],
|
|
1675
|
+
"%n page, including {pages}": [
|
|
1676
|
+
"%n pagina, waaronder {pages}",
|
|
1677
|
+
"%n pagina's, waaronder {pages}"
|
|
1678
|
+
],
|
|
1679
|
+
"{on} of %n is in the menu": [
|
|
1680
|
+
"{on} van %n staat in het menu",
|
|
1681
|
+
"{on} van %n staan in het menu"
|
|
1682
|
+
],
|
|
1683
|
+
"Put it in the menu:": "In het menu zetten:"
|
|
1664
1684
|
},
|
|
1665
1685
|
"plurals": {
|
|
1666
1686
|
"{count} selected": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.92.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": {
|
|
@@ -404,6 +404,7 @@ import CnColumnFilterPopover from './CnColumnFilterPopover.vue'
|
|
|
404
404
|
import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
|
|
405
405
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
406
406
|
import { clearedColumnFilterParams, columnFilterDef, columnFilterParams, columnFilterState, isColumnFilterActive, isColumnSortable } from '../../utils/columnFilters.js'
|
|
407
|
+
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
407
408
|
import { lensUnavailableText, readLensReports } from '../../utils/lensAvailability.js'
|
|
408
409
|
import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
|
|
409
410
|
import { nextSortState } from '../../utils/multiColumnSort.js'
|
|
@@ -561,8 +562,8 @@ export default {
|
|
|
561
562
|
*
|
|
562
563
|
* `secondary` draws a second, muted line under the cell's value: a field
|
|
563
564
|
* key (`"number"`), a template with `{field}` placeholders
|
|
564
|
-
* (`"{number} · {requester}"`, dotted paths allowed
|
|
565
|
-
* the row. Empty fields leave their placeholder blank; a line that
|
|
565
|
+
* (`"{number} · {requester}"`, dotted paths allowed; `{startAt|weekday}`
|
|
566
|
+
* formats through `fillTemplate`), or a function of the row. Empty fields leave their placeholder blank; a line that
|
|
566
567
|
* resolves to nothing is not drawn.
|
|
567
568
|
*
|
|
568
569
|
* @type {Array<{key: string, label: string, description: string, sortable: boolean, width: string, class: string, cellClass: string, secondary: (string|((row: object) => string))}|string>}
|
|
@@ -1769,8 +1770,10 @@ export default {
|
|
|
1769
1770
|
/**
|
|
1770
1771
|
* The secondary line of a cell, from `col.secondary`: a function is
|
|
1771
1772
|
* called with the row; a string with `{field}` placeholders is a
|
|
1772
|
-
* template filled from the row (dotted paths allowed)
|
|
1773
|
-
*
|
|
1773
|
+
* template filled from the row (dotted paths allowed), where a
|
|
1774
|
+
* `{field|format}` token is formatted by `fillTemplate` ("{startAt|weekday}"
|
|
1775
|
+
* reads "wo 7 okt"); any other string is a field key. Returns '' when
|
|
1776
|
+
* there is nothing to draw.
|
|
1774
1777
|
*
|
|
1775
1778
|
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-table-column-carries-a-secondary-line
|
|
1776
1779
|
* @param {object} row The row.
|
|
@@ -1787,7 +1790,9 @@ export default {
|
|
|
1787
1790
|
return ''
|
|
1788
1791
|
}
|
|
1789
1792
|
if (spec.includes('{')) {
|
|
1790
|
-
const filled = fillSecondaryTemplate(spec, (key) =>
|
|
1793
|
+
const filled = fillSecondaryTemplate(spec, (key) => (key.includes('|')
|
|
1794
|
+
? fillTemplate(`{${key}}`, row)
|
|
1795
|
+
: this.secondaryFieldText(row, key)))
|
|
1791
1796
|
// A template whose every field is empty leaves only separators:
|
|
1792
1797
|
// nothing worth a line.
|
|
1793
1798
|
return /[\p{L}\p{N}]/u.test(filled) ? filled.trim() : ''
|
|
@@ -169,6 +169,7 @@ All user-visible strings have props so they can be pre-translated by the consume
|
|
|
169
169
|
| `cancelLabel` | `'Cancel'` | Label for the dismiss button before the action is confirmed. |
|
|
170
170
|
| `closeLabel` | `'Close'` | Label for the dismiss button after the result is shown. |
|
|
171
171
|
| `confirmLabel` | `''` | Confirm button label. Defaults to `'Create'` or `'Save'` depending on mode. |
|
|
172
|
+
| `confirmIcon` | `''` | MDI icon name on the Confirm button (`SwapHorizontal`, `Archive`, `Send`, `Gavel`). Defaults to Plus when creating, else a save icon. |
|
|
172
173
|
| `feedback` | `true` | Toast the write: "Saved {title}" on success, the server's message on failure. `false` suppresses the toasts only. |
|
|
173
174
|
| `confirmDisabled` | `false` | Keep the Confirm button disabled regardless of the form state. |
|
|
174
175
|
|
|
@@ -758,6 +758,11 @@
|
|
|
758
758
|
@click="executeConfirm">
|
|
759
759
|
<template #icon>
|
|
760
760
|
<NcLoadingIcon v-if="loading" :size="20" />
|
|
761
|
+
<CnIcon
|
|
762
|
+
v-else-if="confirmIcon"
|
|
763
|
+
:name="confirmIcon"
|
|
764
|
+
:size="20"
|
|
765
|
+
data-testid="cn-form-dialog-confirm-icon" />
|
|
761
766
|
<Plus v-else-if="isCreateMode" :size="20" />
|
|
762
767
|
<ContentSaveOutline v-else :size="20" />
|
|
763
768
|
</template>
|
|
@@ -781,6 +786,7 @@ import CnDurationField from '../CnDurationField/CnDurationField.vue'
|
|
|
781
786
|
import CnFieldHelper from '../CnFieldHelper/CnFieldHelper.vue'
|
|
782
787
|
import CnFileField from '../CnFileField/CnFileField.vue'
|
|
783
788
|
import CnFormField from '../CnFormField/CnFormField.vue'
|
|
789
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
784
790
|
import CnIconBrowser from '../CnIconBrowser/CnIconBrowser.vue'
|
|
785
791
|
import CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'
|
|
786
792
|
import CnPropertySourceField from '../CnPropertySourceField/CnPropertySourceField.vue'
|
|
@@ -1023,6 +1029,7 @@ export default {
|
|
|
1023
1029
|
CnSubObjectsField,
|
|
1024
1030
|
CnChildRecordsField,
|
|
1025
1031
|
CnDialog,
|
|
1032
|
+
CnIcon,
|
|
1026
1033
|
CnFormField,
|
|
1027
1034
|
NcButton,
|
|
1028
1035
|
NcNoteCard,
|
|
@@ -1303,6 +1310,12 @@ export default {
|
|
|
1303
1310
|
default: '',
|
|
1304
1311
|
},
|
|
1305
1312
|
|
|
1313
|
+
/** MDI icon name on the Confirm button (`SwapHorizontal`, `Archive`, `Send`, `Gavel`). Defaults to Plus when creating, else a save icon. */
|
|
1314
|
+
confirmIcon: {
|
|
1315
|
+
type: String,
|
|
1316
|
+
default: '',
|
|
1317
|
+
},
|
|
1318
|
+
|
|
1306
1319
|
/** Keep the Confirm button disabled regardless of the form state, for a host that knows the form cannot be completed. */
|
|
1307
1320
|
confirmDisabled: {
|
|
1308
1321
|
type: Boolean,
|
|
@@ -14,8 +14,34 @@
|
|
|
14
14
|
</p>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
+
<section
|
|
18
|
+
v-if="visibleModules.length > 0"
|
|
19
|
+
class="cn-link-cards-page__modules"
|
|
20
|
+
:aria-labelledby="`cn-link-cards-modules-${uid}`"
|
|
21
|
+
data-testid="cn-link-cards-modules">
|
|
22
|
+
<div class="cn-link-cards-page__modules-head">
|
|
23
|
+
<h3 :id="`cn-link-cards-modules-${uid}`" class="cn-link-cards-page__modules-title">
|
|
24
|
+
{{ resolvedModulesTitle }}
|
|
25
|
+
</h3>
|
|
26
|
+
<span class="cn-link-cards-page__modules-count" data-testid="cn-link-cards-modules-count">
|
|
27
|
+
{{ modulesCount }}
|
|
28
|
+
</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="cn-link-cards-page__modules-grid">
|
|
31
|
+
<CnModuleCard
|
|
32
|
+
v-for="module in visibleModules"
|
|
33
|
+
:key="module.id"
|
|
34
|
+
:module="module"
|
|
35
|
+
:headingId="`cn-link-cards-module-${uid}-${module.key}`" />
|
|
36
|
+
</div>
|
|
37
|
+
<p v-if="resolvedFootnote" class="cn-link-cards-page__modules-note" data-testid="cn-link-cards-modules-note">
|
|
38
|
+
<InformationOutline :size="18" class="cn-link-cards-page__modules-note-icon" />
|
|
39
|
+
<span>{{ resolvedFootnote }}</span>
|
|
40
|
+
</p>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
17
43
|
<p
|
|
18
|
-
v-if="groups.length === 0"
|
|
44
|
+
v-if="groups.length === 0 && visibleModules.length === 0"
|
|
19
45
|
class="cn-link-cards-page__empty"
|
|
20
46
|
data-testid="cn-link-cards-empty">
|
|
21
47
|
{{ resolvedEmptyLabel }}
|
|
@@ -25,6 +51,7 @@
|
|
|
25
51
|
v-for="group in groups"
|
|
26
52
|
:key="group.key"
|
|
27
53
|
class="cn-link-cards-page__group"
|
|
54
|
+
:class="{ 'cn-link-cards-page__group--list': isListStyle }"
|
|
28
55
|
:aria-labelledby="group.label ? group.headingId : null"
|
|
29
56
|
:aria-label="group.label ? null : resolvedTitle || null"
|
|
30
57
|
data-testid="cn-link-cards-group"
|
|
@@ -35,7 +62,38 @@
|
|
|
35
62
|
class="cn-link-cards-page__caption">
|
|
36
63
|
{{ group.label }}
|
|
37
64
|
</h3>
|
|
38
|
-
|
|
65
|
+
<!-- groupStyle "list": the group is one card of rows, each an icon
|
|
66
|
+
chip, the label over its description, and a trailing chevron
|
|
67
|
+
(an external-link icon for a URL). -->
|
|
68
|
+
<ul v-if="isListStyle" class="cn-link-cards-page__list">
|
|
69
|
+
<li v-for="card in group.cards" :key="card.id">
|
|
70
|
+
<a
|
|
71
|
+
class="cn-link-cards-page__row"
|
|
72
|
+
data-testid="cn-link-card"
|
|
73
|
+
:data-card-id="card.id"
|
|
74
|
+
:href="card.url"
|
|
75
|
+
:target="card.external ? '_blank' : null"
|
|
76
|
+
:rel="card.external ? 'noopener noreferrer' : null"
|
|
77
|
+
:aria-describedby="card.description ? card.descriptionId : null"
|
|
78
|
+
@click="open(card, $event)">
|
|
79
|
+
<span v-if="card.icon" class="cn-link-cards-page__row-icon" aria-hidden="true">
|
|
80
|
+
<CnIcon :name="card.icon" :size="20" />
|
|
81
|
+
</span>
|
|
82
|
+
<span class="cn-link-cards-page__row-body">
|
|
83
|
+
<span class="cn-link-cards-page__row-label">{{ card.label }}</span>
|
|
84
|
+
<span
|
|
85
|
+
v-if="card.description"
|
|
86
|
+
:id="card.descriptionId"
|
|
87
|
+
class="cn-link-cards-page__row-description">
|
|
88
|
+
{{ card.description }}
|
|
89
|
+
</span>
|
|
90
|
+
</span>
|
|
91
|
+
<OpenInNew v-if="trailingOf(card) === 'external'" :size="18" class="cn-link-cards-page__row-trailing" />
|
|
92
|
+
<ChevronRight v-else :size="20" class="cn-link-cards-page__row-trailing" />
|
|
93
|
+
</a>
|
|
94
|
+
</li>
|
|
95
|
+
</ul>
|
|
96
|
+
<ul v-else class="cn-link-cards-page__grid">
|
|
39
97
|
<li v-for="card in group.cards" :key="card.id" class="cn-link-cards-page__cell">
|
|
40
98
|
<!-- A real <a href>, so the card can be middle-clicked, copied
|
|
41
99
|
and announced as a link. A plain click on an in-app card
|
|
@@ -72,7 +130,11 @@
|
|
|
72
130
|
</template>
|
|
73
131
|
|
|
74
132
|
<script>
|
|
75
|
-
import { translate as t } from '@nextcloud/l10n'
|
|
133
|
+
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
134
|
+
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
135
|
+
import InformationOutline from 'vue-material-design-icons/InformationOutline.vue'
|
|
136
|
+
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
|
|
137
|
+
import CnModuleCard from '../CnModuleCard/CnModuleCard.vue'
|
|
76
138
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
77
139
|
import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
|
|
78
140
|
import { passesContextPredicates } from '../../utils/visibleIfContext.js'
|
|
@@ -107,7 +169,7 @@ let linkCardsPageUid = 0
|
|
|
107
169
|
export default {
|
|
108
170
|
name: 'CnLinkCardsPage',
|
|
109
171
|
|
|
110
|
-
components: { CnIcon },
|
|
172
|
+
components: { ChevronRight, CnIcon, CnModuleCard, InformationOutline, OpenInNew },
|
|
111
173
|
|
|
112
174
|
inject: {
|
|
113
175
|
/** The app's translate function, provided by CnAppRoot. Identity outside one. */
|
|
@@ -158,6 +220,37 @@ export default {
|
|
|
158
220
|
default: null,
|
|
159
221
|
},
|
|
160
222
|
|
|
223
|
+
/**
|
|
224
|
+
* The modules: `{ id, label, description?, icon?, enabled?, enabledIf?,
|
|
225
|
+
* route?, pages: [{ label, route, highlight? }], settings?: { label?, prefix?,
|
|
226
|
+
* route?, href?, permission?, visibleIf? }, permission?, visibleIf? }`.
|
|
227
|
+
* Drawn as module cards above the link cards.
|
|
228
|
+
*
|
|
229
|
+
* @type {Array<object>|null}
|
|
230
|
+
*/
|
|
231
|
+
modules: {
|
|
232
|
+
type: Array,
|
|
233
|
+
default: null,
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/** `cards` (default): a grid of link cards per group. `list`: one card per group with a row per link. */
|
|
237
|
+
groupStyle: {
|
|
238
|
+
type: String,
|
|
239
|
+
default: null,
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/** Heading of the modules section. Defaults to "Modules". */
|
|
243
|
+
modulesTitle: {
|
|
244
|
+
type: String,
|
|
245
|
+
default: null,
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/** A note under the module cards. */
|
|
249
|
+
footnote: {
|
|
250
|
+
type: String,
|
|
251
|
+
default: null,
|
|
252
|
+
},
|
|
253
|
+
|
|
161
254
|
/** Text shown when no card is visible. */
|
|
162
255
|
emptyLabel: {
|
|
163
256
|
type: String,
|
|
@@ -192,7 +285,7 @@ export default {
|
|
|
192
285
|
config() {
|
|
193
286
|
const fromPage = (this.page && this.page.config) || {}
|
|
194
287
|
const fromProps = {}
|
|
195
|
-
for (const key of ['cards', 'categories', 'description', 'title', 'emptyLabel']) {
|
|
288
|
+
for (const key of ['cards', 'categories', 'description', 'title', 'emptyLabel', 'modules', 'modulesTitle', 'footnote', 'groupStyle']) {
|
|
196
289
|
if (this[key] !== null && this[key] !== undefined) {
|
|
197
290
|
fromProps[key] = this[key]
|
|
198
291
|
}
|
|
@@ -261,6 +354,90 @@ export default {
|
|
|
261
354
|
.filter((card) => card.url !== '')
|
|
262
355
|
},
|
|
263
356
|
|
|
357
|
+
/**
|
|
358
|
+
* The modules a reader may see, with text translated and links
|
|
359
|
+
* resolved. `enabled` is the declared boolean, or the result of
|
|
360
|
+
* `enabledIf` (a visibleIf condition against `runtime`). A page that
|
|
361
|
+
* resolves to nothing is dropped; the admin-settings link only shows when
|
|
362
|
+
* its own `permission` and `visibleIf` pass.
|
|
363
|
+
*
|
|
364
|
+
* @return {Array<object>} The modules.
|
|
365
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-links-page-can-show-modules
|
|
366
|
+
*/
|
|
367
|
+
visibleModules() {
|
|
368
|
+
const declared = Array.isArray(this.config.modules) ? this.config.modules : []
|
|
369
|
+
const runtime = this.cnManifest?.runtime ?? null
|
|
370
|
+
return declared
|
|
371
|
+
.filter((module) => module && module.id && module.label)
|
|
372
|
+
.filter((module) => this.passesPermission(module) && this.passesVisibleIf(module, runtime))
|
|
373
|
+
.map((module) => {
|
|
374
|
+
const pages = (Array.isArray(module.pages) ? module.pages : [])
|
|
375
|
+
.filter((page) => page && page.label)
|
|
376
|
+
.map((page, index) => ({ key: String(index), label: this.tr(page.label), highlight: page.highlight === true, ...this.targetOf(page) }))
|
|
377
|
+
.filter((page) => page.url !== '')
|
|
378
|
+
const own = this.targetOf(module)
|
|
379
|
+
const open = own.url !== '' ? own : (pages[0] || null)
|
|
380
|
+
const settingsDeclared = module.settings && typeof module.settings === 'object' ? module.settings : null
|
|
381
|
+
const settingsTarget = settingsDeclared && this.passesPermission(settingsDeclared) && this.passesVisibleIf(settingsDeclared, runtime)
|
|
382
|
+
? this.targetOf(settingsDeclared)
|
|
383
|
+
: null
|
|
384
|
+
return {
|
|
385
|
+
id: String(module.id),
|
|
386
|
+
key: String(module.id).replace(/[^A-Za-z0-9_-]/g, '-'),
|
|
387
|
+
label: this.tr(module.label),
|
|
388
|
+
description: this.tr(module.description || ''),
|
|
389
|
+
icon: module.icon || '',
|
|
390
|
+
enabled: this.moduleEnabled(module, runtime),
|
|
391
|
+
pages,
|
|
392
|
+
open,
|
|
393
|
+
settings: settingsTarget && settingsTarget.url !== ''
|
|
394
|
+
? { ...settingsTarget, label: this.tr(settingsDeclared.label || ''), prefix: this.tr(settingsDeclared.prefix || '') }
|
|
395
|
+
: null,
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Whether the groups are drawn as list cards (`config.groupStyle: "list"`).
|
|
402
|
+
*
|
|
403
|
+
* @return {boolean} True for list cards.
|
|
404
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-group-can-be-a-list-card
|
|
405
|
+
*/
|
|
406
|
+
isListStyle() {
|
|
407
|
+
return this.config.groupStyle === 'list'
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The modules heading, translated.
|
|
412
|
+
*
|
|
413
|
+
* @return {string} The heading.
|
|
414
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-links-page-can-show-modules
|
|
415
|
+
*/
|
|
416
|
+
resolvedModulesTitle() {
|
|
417
|
+
return this.config.modulesTitle ? this.tr(this.config.modulesTitle) : t('nextcloud-vue', 'Modules')
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* "3 of 6 are in the menu".
|
|
422
|
+
*
|
|
423
|
+
* @return {string} The count.
|
|
424
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-links-page-can-show-modules
|
|
425
|
+
*/
|
|
426
|
+
modulesCount() {
|
|
427
|
+
const on = this.visibleModules.filter((module) => module.enabled).length
|
|
428
|
+
return n('nextcloud-vue', '{on} of %n is in the menu', '{on} of %n are in the menu', this.visibleModules.length, { on })
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The note under the module cards, translated.
|
|
433
|
+
*
|
|
434
|
+
* @return {string} The note, or ''.
|
|
435
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-links-page-can-show-modules
|
|
436
|
+
*/
|
|
437
|
+
resolvedFootnote() {
|
|
438
|
+
return this.tr(this.config.footnote || '')
|
|
439
|
+
},
|
|
440
|
+
|
|
264
441
|
/**
|
|
265
442
|
* The groups to draw: ungrouped cards first under no caption, then
|
|
266
443
|
* one group per category in declaration order. A group without a
|
|
@@ -290,6 +467,40 @@ export default {
|
|
|
290
467
|
},
|
|
291
468
|
|
|
292
469
|
methods: {
|
|
470
|
+
/**
|
|
471
|
+
* Whether a module is in the menu: its `enabled` boolean, else its
|
|
472
|
+
* `enabledIf` condition evaluated as a `visibleIf`, else false.
|
|
473
|
+
*
|
|
474
|
+
* @param {object} module The declared module.
|
|
475
|
+
* @param {object|null} runtime `manifest.runtime`, or null.
|
|
476
|
+
* @return {boolean} True when the module is in the menu.
|
|
477
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-links-page-can-show-modules
|
|
478
|
+
*/
|
|
479
|
+
/**
|
|
480
|
+
* The icon at the end of a list row: an external-link icon for a URL,
|
|
481
|
+
* a chevron for a page in the app. A resolved `trailing` wins.
|
|
482
|
+
*
|
|
483
|
+
* @param {object} card The resolved card.
|
|
484
|
+
* @return {string} `external` or `chevron`.
|
|
485
|
+
* @spec openspec/changes/screens-links-module-cards/specs/link-cards-page/spec.md#requirement-a-group-can-be-a-list-card
|
|
486
|
+
*/
|
|
487
|
+
trailingOf(card) {
|
|
488
|
+
if (card.trailing === 'external' || card.trailing === 'chevron') {
|
|
489
|
+
return card.trailing
|
|
490
|
+
}
|
|
491
|
+
return card.external ? 'external' : 'chevron'
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
moduleEnabled(module, runtime) {
|
|
495
|
+
if (typeof module.enabled === 'boolean') {
|
|
496
|
+
return module.enabled
|
|
497
|
+
}
|
|
498
|
+
if (module.enabledIf && typeof module.enabledIf === 'object') {
|
|
499
|
+
return this.passesVisibleIf({ visibleIf: module.enabledIf }, runtime)
|
|
500
|
+
}
|
|
501
|
+
return false
|
|
502
|
+
},
|
|
503
|
+
|
|
293
504
|
/**
|
|
294
505
|
* Translate a manifest string through the app's translate function.
|
|
295
506
|
*
|
|
@@ -514,4 +725,126 @@ export default {
|
|
|
514
725
|
font-size: 0.9em;
|
|
515
726
|
line-height: 1.4;
|
|
516
727
|
}
|
|
728
|
+
|
|
729
|
+
.cn-link-cards-page__group--list {
|
|
730
|
+
padding: 20px 22px;
|
|
731
|
+
border: 1px solid var(--color-border);
|
|
732
|
+
border-radius: var(--border-radius-container-large, 12px);
|
|
733
|
+
background: var(--color-main-background);
|
|
734
|
+
gap: 6px;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.cn-link-cards-page__group--list .cn-link-cards-page__caption {
|
|
738
|
+
margin: 0 0 8px;
|
|
739
|
+
color: var(--color-main-text);
|
|
740
|
+
font-size: 17px;
|
|
741
|
+
font-weight: 700;
|
|
742
|
+
letter-spacing: normal;
|
|
743
|
+
text-transform: none;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.cn-link-cards-page__list {
|
|
747
|
+
margin: 0;
|
|
748
|
+
padding: 0;
|
|
749
|
+
list-style: none;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.cn-link-cards-page__row {
|
|
753
|
+
display: flex;
|
|
754
|
+
align-items: center;
|
|
755
|
+
gap: 12px;
|
|
756
|
+
padding: 10px 0;
|
|
757
|
+
border-top: 1px solid var(--color-background-dark);
|
|
758
|
+
color: var(--color-main-text);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.cn-link-cards-page__row,
|
|
762
|
+
.cn-link-cards-page__row:hover,
|
|
763
|
+
.cn-link-cards-page__row:focus,
|
|
764
|
+
.cn-link-cards-page__row:active {
|
|
765
|
+
text-decoration: none !important;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.cn-link-cards-page__row:focus-visible {
|
|
769
|
+
outline: 2px solid var(--color-primary-element);
|
|
770
|
+
outline-offset: 2px;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.cn-link-cards-page__row-icon {
|
|
774
|
+
display: flex;
|
|
775
|
+
flex: none;
|
|
776
|
+
align-items: center;
|
|
777
|
+
justify-content: center;
|
|
778
|
+
width: 34px;
|
|
779
|
+
height: 34px;
|
|
780
|
+
border-radius: var(--border-radius-element, 8px);
|
|
781
|
+
background: var(--color-background-dark);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.cn-link-cards-page__row-body {
|
|
785
|
+
display: flex;
|
|
786
|
+
flex: 1 1 auto;
|
|
787
|
+
flex-direction: column;
|
|
788
|
+
gap: 2px;
|
|
789
|
+
min-width: 0;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.cn-link-cards-page__row-label {
|
|
793
|
+
font-size: 15px;
|
|
794
|
+
font-weight: 600;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.cn-link-cards-page__row-description {
|
|
798
|
+
font-size: 13px;
|
|
799
|
+
color: var(--color-text-maxcontrast);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.cn-link-cards-page__row-trailing {
|
|
803
|
+
flex: none;
|
|
804
|
+
color: var(--color-text-maxcontrast);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.cn-link-cards-page__modules {
|
|
808
|
+
display: flex;
|
|
809
|
+
flex-direction: column;
|
|
810
|
+
gap: 14px;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.cn-link-cards-page__modules-head {
|
|
814
|
+
display: flex;
|
|
815
|
+
flex-wrap: wrap;
|
|
816
|
+
justify-content: space-between;
|
|
817
|
+
align-items: baseline;
|
|
818
|
+
gap: 8px;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.cn-link-cards-page__modules-title {
|
|
822
|
+
margin: 0;
|
|
823
|
+
font-size: 20px;
|
|
824
|
+
font-weight: 700;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.cn-link-cards-page__modules-count {
|
|
828
|
+
font-size: 14px;
|
|
829
|
+
color: var(--color-text-maxcontrast);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.cn-link-cards-page__modules-grid {
|
|
833
|
+
display: grid;
|
|
834
|
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
835
|
+
gap: 16px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.cn-link-cards-page__modules-note {
|
|
839
|
+
display: flex;
|
|
840
|
+
align-items: flex-start;
|
|
841
|
+
gap: 8px;
|
|
842
|
+
margin: 0;
|
|
843
|
+
font-size: 14px;
|
|
844
|
+
color: var(--color-text-maxcontrast);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.cn-link-cards-page__modules-note-icon {
|
|
848
|
+
flex: none;
|
|
849
|
+
}
|
|
517
850
|
</style>
|