@conduction/nextcloud-vue 2.91.0 → 2.93.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/CnAppRoot/CnAppRoot.vue2.js +57 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +10 -39
- 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/CnDetailPage/CnDetailPage.vue2.js +5 -3
- 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/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +39 -10
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +418 -399
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.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/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
- 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/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +12 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +45 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +44 -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 +1182 -328
- package/dist/esm/utils/boardDate.js +35 -1
- package/dist/esm/utils/boardDate.js.map +1 -1
- package/dist/esm/utils/featureCatalog.js +135 -0
- package/dist/esm/utils/featureCatalog.js.map +1 -0
- package/dist/esm/utils/fillTemplate.js +84 -4
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerMeta.js +62 -12
- package/dist/esm/utils/headerMeta.js.map +1 -1
- package/dist/esm/utils/roadmapIssues.js +86 -0
- package/dist/esm/utils/roadmapIssues.js.map +1 -0
- package/dist/esm/utils/userDisplayName.js +47 -0
- package/dist/esm/utils/userDisplayName.js.map +1 -0
- 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 +15618 -13097
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1182 -328
- package/l10n/en.json +45 -1
- package/l10n/nl.json +44 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.md +2 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
- package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
- package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -6
- package/src/components/CnDataTable/CnDataTable.vue +10 -39
- package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
- package/src/components/CnFormDialog/CnFormDialog.md +1 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +619 -562
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
- package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
- package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
- package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -2
- package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +12 -0
- package/src/css/dialog.css +47 -0
- package/src/css/look-board-detail.css +8 -0
- package/src/css/look-board.css +33 -0
- package/src/schemas/app-manifest-v2.schema.json +67 -4
- package/src/utils/boardDate.js +34 -0
- package/src/utils/featureCatalog.js +132 -0
- package/src/utils/fillTemplate.js +83 -3
- package/src/utils/headerMeta.js +62 -12
- package/src/utils/roadmapIssues.js +82 -0
- package/src/utils/userDisplayName.js +54 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="cn-nc-widget-widget" :class="{ 'cn-nc-widget-widget--horizontal': displayMode === 'horizontal' }">
|
|
8
|
-
<header class="cn-nc-widget-widget__header">
|
|
8
|
+
<header v-if="showsHeader" class="cn-nc-widget-widget__header" data-testid="cn-nc-widget-header">
|
|
9
9
|
<img
|
|
10
10
|
v-if="widgetIconUrl"
|
|
11
11
|
class="cn-nc-widget-widget__header-icon"
|
|
@@ -130,9 +130,16 @@ function getDashboardGlobal() {
|
|
|
130
130
|
export default {
|
|
131
131
|
name: 'CnNcWidgetWidget',
|
|
132
132
|
|
|
133
|
+
inject: {
|
|
134
|
+
/** Whether the hosting card draws a title row (CnWidgetWrapper). */
|
|
135
|
+
cnWidgetTitled: { default: null },
|
|
136
|
+
},
|
|
137
|
+
|
|
133
138
|
props: {
|
|
134
139
|
/**
|
|
135
|
-
* Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?}`.
|
|
140
|
+
* Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?, showHeader?}`.
|
|
141
|
+
* `showHeader: false` drops the widget's own header (icon and title);
|
|
142
|
+
* it is dropped too when the hosting card draws a title row.
|
|
136
143
|
* `limit` is the most items shown (the list then comes from the items
|
|
137
144
|
* API, so it fits its cell); `showAllUrl` / `showAllLabel` the link to
|
|
138
145
|
* the rest (default the widget's own page and "Show all").
|
|
@@ -207,6 +214,20 @@ export default {
|
|
|
207
214
|
return null
|
|
208
215
|
},
|
|
209
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Whether the widget draws its own header: not with
|
|
219
|
+
* `content.showHeader: false`, nor inside a card that draws a title.
|
|
220
|
+
*
|
|
221
|
+
* @spec openspec/changes/nc-widget-header/specs/nc-widget-header/spec.md#requirement-a-nextcloud-widget-draws-no-second-heading
|
|
222
|
+
* @return {boolean}
|
|
223
|
+
*/
|
|
224
|
+
showsHeader() {
|
|
225
|
+
if (this.content && this.content.showHeader === false) {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
return !(typeof this.cnWidgetTitled === 'function' && this.cnWidgetTitled())
|
|
229
|
+
},
|
|
230
|
+
|
|
210
231
|
/**
|
|
211
232
|
* The header title.
|
|
212
233
|
*
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<NcAppSettingsSection id="notifications" :name="t('nextcloud-vue', 'Notifications')">
|
|
3
|
+
<template v-if="cnLook === 'board'" #icon>
|
|
4
|
+
<BellOutline :size="18" />
|
|
5
|
+
</template>
|
|
3
6
|
<NcLoadingIcon v-if="loading" :size="24" />
|
|
4
7
|
|
|
5
8
|
<NcEmptyContent v-else-if="error"
|
|
@@ -107,6 +110,8 @@ export default {
|
|
|
107
110
|
* than an always-empty pane.
|
|
108
111
|
*/
|
|
109
112
|
cnAppId: { default: () => '' },
|
|
113
|
+
/** The app's look; `board` gives the section its navigation icon. */
|
|
114
|
+
cnLook: { default: 'nextcloud' },
|
|
110
115
|
/**
|
|
111
116
|
* App-supplied labels for its notification rules, provided by the
|
|
112
117
|
* CnAppRoot ancestor from its `notificationLabels` prop. Keyed
|
|
@@ -69,9 +69,7 @@
|
|
|
69
69
|
*
|
|
70
70
|
* Spec: features-roadmap-component — Requirement "RoadmapTab".
|
|
71
71
|
*/
|
|
72
|
-
import axios from '@nextcloud/axios'
|
|
73
72
|
import { translate as t } from '@nextcloud/l10n'
|
|
74
|
-
import { generateUrl } from '@nextcloud/router'
|
|
75
73
|
import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
76
74
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
77
75
|
import ClockOutline from 'vue-material-design-icons/ClockOutline.vue'
|
|
@@ -79,6 +77,7 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
|
|
|
79
77
|
import KeyOutline from 'vue-material-design-icons/KeyOutline.vue'
|
|
80
78
|
import RoadVariant from 'vue-material-design-icons/RoadVariant.vue'
|
|
81
79
|
import CnRoadmapItem from '../CnRoadmapItem/CnRoadmapItem.vue'
|
|
80
|
+
import { fetchRoadmapIssues, sortByReactions } from '../../utils/roadmapIssues.js'
|
|
82
81
|
|
|
83
82
|
export default {
|
|
84
83
|
name: 'CnRoadmapTab',
|
|
@@ -124,7 +123,7 @@ export default {
|
|
|
124
123
|
|
|
125
124
|
computed: {
|
|
126
125
|
sortedItems() {
|
|
127
|
-
return
|
|
126
|
+
return sortByReactions(this.items)
|
|
128
127
|
},
|
|
129
128
|
|
|
130
129
|
disabledLabel() {
|
|
@@ -175,31 +174,10 @@ export default {
|
|
|
175
174
|
this.loading = true
|
|
176
175
|
this.degraded = null
|
|
177
176
|
this.items = []
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
repo: this.repo,
|
|
183
|
-
labels: 'enhancement,feature',
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
|
-
const data = response.data || {}
|
|
187
|
-
if (data.hint === 'github_pat_not_configured') {
|
|
188
|
-
this.degraded = 'pat_not_configured'
|
|
189
|
-
} else if (Array.isArray(data.items)) {
|
|
190
|
-
this.items = data.items
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {
|
|
193
|
-
const status = e?.response?.status
|
|
194
|
-
const code = e?.response?.data?.error
|
|
195
|
-
if (status === 429 || code === 'github_rate_limited' || code === 'rate_limited') {
|
|
196
|
-
this.degraded = 'rate_limited'
|
|
197
|
-
} else {
|
|
198
|
-
this.degraded = 'error'
|
|
199
|
-
}
|
|
200
|
-
} finally {
|
|
201
|
-
this.loading = false
|
|
202
|
-
}
|
|
177
|
+
const { items, degraded } = await fetchRoadmapIssues(this.repo)
|
|
178
|
+
this.items = items
|
|
179
|
+
this.degraded = degraded
|
|
180
|
+
this.loading = false
|
|
203
181
|
},
|
|
204
182
|
},
|
|
205
183
|
}
|
|
@@ -187,7 +187,7 @@ import { resolveTabCount } from '../../utils/detailActionModel.js'
|
|
|
187
187
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
188
188
|
import { isActivityWidget } from '../../utils/headerMeta.js'
|
|
189
189
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
190
|
-
import { evaluateVisibleWhen, evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
|
|
190
|
+
import { evaluateVisibleWhen, evaluateVisibleWhenLocal, evaluateWorkspaceCondition, isLocallyDecidableVisibleWhen, isWorkspaceCondition } from '../../utils/visibleWhen.js'
|
|
191
191
|
import { widgetTitleOf } from '../../utils/widgetDispatch.js'
|
|
192
192
|
import { CnActionsMenu } from '../CnActionsMenu/index.js'
|
|
193
193
|
|
|
@@ -331,6 +331,13 @@ export default {
|
|
|
331
331
|
* `visibleWhen` reads them as `@context.<name>`. Null on any other page.
|
|
332
332
|
*/
|
|
333
333
|
cnDetailVisibilityRows: { default: null },
|
|
334
|
+
/**
|
|
335
|
+
* The page's workspace bag (CnDashboardPage provides it), which a
|
|
336
|
+
* page control such as a chips filter writes. A widget in a stack is
|
|
337
|
+
* left out while its workspace `visibleWhen` does not hold. Null off a
|
|
338
|
+
* dashboard.
|
|
339
|
+
*/
|
|
340
|
+
cnWorkspaceContext: { default: null },
|
|
334
341
|
/**
|
|
335
342
|
* The host's translate (CnAppRoot provides `cnTranslate`), so a tab
|
|
336
343
|
* label written in the manifest reads in the user's language.
|
|
@@ -588,7 +595,8 @@ export default {
|
|
|
588
595
|
// widget is skipped. The first that resolves is the panel's.
|
|
589
596
|
const ids = this.tabWidgetIds(tab)
|
|
590
597
|
const widgets = ids.length > 1
|
|
591
|
-
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
598
|
+
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
599
|
+
.filter((w) => w && !this.hiddenByWorkspace(w))
|
|
592
600
|
: []
|
|
593
601
|
const widget = ids.length > 1
|
|
594
602
|
? (widgets[0] || null)
|
|
@@ -837,6 +845,29 @@ export default {
|
|
|
837
845
|
},
|
|
838
846
|
|
|
839
847
|
methods: {
|
|
848
|
+
/**
|
|
849
|
+
* Whether a page control hides a widget of a stack right now: its
|
|
850
|
+
* `visibleWhen` (on the widget, its content or its props) is a
|
|
851
|
+
* workspace condition that does not hold. Off a dashboard there is no
|
|
852
|
+
* workspace, and nothing is hidden. Other conditions are not looked at
|
|
853
|
+
* here.
|
|
854
|
+
*
|
|
855
|
+
* @param {object} widget The widget definition.
|
|
856
|
+
* @return {boolean} True when the widget must not draw.
|
|
857
|
+
* @spec openspec/changes/tabs-stack-follows-page-controls/specs/tabs-widget/spec.md#requirement-a-widget-in-a-tab-stack-follows-the-page-controls
|
|
858
|
+
*/
|
|
859
|
+
hiddenByWorkspace(widget) {
|
|
860
|
+
const cond = widget.visibleWhen || widget.content?.visibleWhen || widget.props?.visibleWhen || null
|
|
861
|
+
if (!isWorkspaceCondition(cond)) {
|
|
862
|
+
return false
|
|
863
|
+
}
|
|
864
|
+
const bag = this.cnWorkspaceContext
|
|
865
|
+
if (!bag || typeof bag !== 'object') {
|
|
866
|
+
return false
|
|
867
|
+
}
|
|
868
|
+
return !evaluateWorkspaceCondition(cond, 'value' in bag ? bag.value : bag)
|
|
869
|
+
},
|
|
870
|
+
|
|
840
871
|
/**
|
|
841
872
|
* The grid a stack lays its widgets out in, as one column span (of 12)
|
|
842
873
|
* per widget in order, or null for the plain vertical stack. A tab says
|
|
@@ -60,6 +60,17 @@
|
|
|
60
60
|
:style="contentStyle">
|
|
61
61
|
{{ placeholderText }}
|
|
62
62
|
</span>
|
|
63
|
+
<a
|
|
64
|
+
v-if="link"
|
|
65
|
+
class="cn-text-widget__link"
|
|
66
|
+
data-testid="cn-text-widget-link"
|
|
67
|
+
:href="link.url"
|
|
68
|
+
:target="link.external ? '_blank' : null"
|
|
69
|
+
:rel="link.external ? 'noopener noreferrer' : null"
|
|
70
|
+
@click="openLink">
|
|
71
|
+
{{ link.label }}
|
|
72
|
+
<ChevronRight :size="16" />
|
|
73
|
+
</a>
|
|
63
74
|
</div>
|
|
64
75
|
</template>
|
|
65
76
|
|
|
@@ -67,6 +78,8 @@
|
|
|
67
78
|
import { translate as t } from '@nextcloud/l10n'
|
|
68
79
|
import DOMPurify from 'dompurify'
|
|
69
80
|
import { Marked } from 'marked'
|
|
81
|
+
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
82
|
+
import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
|
|
70
83
|
import { isPlaceholderCell } from '../../utils/textTable.js'
|
|
71
84
|
|
|
72
85
|
// Per-module `Marked` instance so configuration is scoped here and does NOT
|
|
@@ -120,13 +133,23 @@ function ensureDomPurifyHook() {
|
|
|
120
133
|
export default {
|
|
121
134
|
name: 'CnTextWidget',
|
|
122
135
|
|
|
136
|
+
components: { ChevronRight },
|
|
137
|
+
|
|
138
|
+
inject: {
|
|
139
|
+
/** The app's translate function, provided by CnAppRoot. Identity outside one. */
|
|
140
|
+
cnTranslate: { default: () => (key) => key },
|
|
141
|
+
},
|
|
142
|
+
|
|
123
143
|
props: {
|
|
124
144
|
/**
|
|
125
145
|
* Persisted widget content blob: `{text, fontSize, color,
|
|
126
|
-
* backgroundColor, textAlign, contentMode, tableMode, tableData}`.
|
|
146
|
+
* backgroundColor, textAlign, contentMode, tableMode, tableData, link}`.
|
|
127
147
|
* All keys are optional; missing keys collapse to documented defaults.
|
|
148
|
+
* `text` and `link.label` go through the app's translate function.
|
|
149
|
+
* `link` is `{label, route}` (a page id, or a path starting with `/`) or
|
|
150
|
+
* `{label, href}` (an external URL), drawn under the text.
|
|
128
151
|
*
|
|
129
|
-
* @type {{text: string, fontSize: string, color: string, backgroundColor: string, textAlign: string, contentMode: string, tableMode: boolean, tableData: object}}
|
|
152
|
+
* @type {{text: string, fontSize: string, color: string, backgroundColor: string, textAlign: string, contentMode: string, tableMode: boolean, tableData: object, link: {label: string, route: string, href: string}}}
|
|
130
153
|
*/
|
|
131
154
|
content: {
|
|
132
155
|
type: Object,
|
|
@@ -135,9 +158,34 @@ export default {
|
|
|
135
158
|
},
|
|
136
159
|
|
|
137
160
|
computed: {
|
|
138
|
-
/** The author text, or '' when absent / non-string. */
|
|
161
|
+
/** The author text through the app's translate function, or '' when absent / non-string. */
|
|
139
162
|
text() {
|
|
140
|
-
return typeof this.content?.text === 'string' ? this.content.text : ''
|
|
163
|
+
return typeof this.content?.text === 'string' ? this.tr(this.content.text) : ''
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The link under the text, from `content.link`: `{ label, route }`
|
|
168
|
+
* (a page id, or a router path when it starts with `/`) or
|
|
169
|
+
* `{ label, href }` (an external URL, opened in a new tab). The label
|
|
170
|
+
* goes through the app's translate function. Null without a label or
|
|
171
|
+
* a target.
|
|
172
|
+
*
|
|
173
|
+
* @return {{label: string, url: string, location: (object|null), external: boolean}|null} The link.
|
|
174
|
+
*/
|
|
175
|
+
link() {
|
|
176
|
+
const link = this.content?.link
|
|
177
|
+
if (!link || typeof link !== 'object' || typeof link.label !== 'string' || link.label === '') {
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
if (typeof link.route === 'string' && link.route !== '') {
|
|
181
|
+
const location = link.route.startsWith('/') ? { path: link.route } : { name: link.route }
|
|
182
|
+
const url = resolveHref(location, this.$router)
|
|
183
|
+
return url ? { label: this.tr(link.label), url, location, external: false } : null
|
|
184
|
+
}
|
|
185
|
+
if (typeof link.href === 'string' && link.href !== '') {
|
|
186
|
+
return { label: this.tr(link.label), url: link.href, location: null, external: true }
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
141
189
|
},
|
|
142
190
|
|
|
143
191
|
/** Whether the text has any non-whitespace content. */
|
|
@@ -253,6 +301,7 @@ export default {
|
|
|
253
301
|
'justify-content': 'center',
|
|
254
302
|
overflow: 'auto',
|
|
255
303
|
'background-color': this.backgroundColor,
|
|
304
|
+
...(this.link ? { 'flex-direction': 'column', gap: '8px', 'align-items': 'flex-start' } : {}),
|
|
256
305
|
}
|
|
257
306
|
},
|
|
258
307
|
|
|
@@ -287,6 +336,27 @@ export default {
|
|
|
287
336
|
},
|
|
288
337
|
|
|
289
338
|
methods: {
|
|
339
|
+
/**
|
|
340
|
+
* A manifest string through the app's translate function.
|
|
341
|
+
*
|
|
342
|
+
* @param {string} text The string.
|
|
343
|
+
* @return {string} The translated string.
|
|
344
|
+
*/
|
|
345
|
+
tr(text) {
|
|
346
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Route a plain click on an in-app link, so the page does not reload.
|
|
351
|
+
*
|
|
352
|
+
* @param {MouseEvent} event The click.
|
|
353
|
+
*/
|
|
354
|
+
openLink(event) {
|
|
355
|
+
if (this.link && !this.link.external) {
|
|
356
|
+
followLinkClick(event, this.link.location, this.$router)
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
|
|
290
360
|
/**
|
|
291
361
|
* Run cell text through DOMPurify so the table-mode render path has
|
|
292
362
|
* the same XSS-stripping guarantees as the text-mode path.
|
|
@@ -518,4 +588,19 @@ export default {
|
|
|
518
588
|
color: var(--color-text-maxcontrast);
|
|
519
589
|
font-style: italic;
|
|
520
590
|
}
|
|
591
|
+
|
|
592
|
+
.cn-text-widget__link {
|
|
593
|
+
display: inline-flex;
|
|
594
|
+
align-items: center;
|
|
595
|
+
gap: 2px;
|
|
596
|
+
font-size: 14px;
|
|
597
|
+
font-weight: 600;
|
|
598
|
+
color: var(--color-primary-element);
|
|
599
|
+
text-decoration: none;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.cn-text-widget__link:hover,
|
|
603
|
+
.cn-text-widget__link:focus-visible {
|
|
604
|
+
text-decoration: underline;
|
|
605
|
+
}
|
|
521
606
|
</style>
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<CnDialog
|
|
7
|
+
class="cn-user-settings-dialog"
|
|
8
|
+
:name="title"
|
|
9
|
+
look="board"
|
|
10
|
+
width="wizard"
|
|
11
|
+
:eyebrow="eyebrow"
|
|
12
|
+
:subtitle="subtitle"
|
|
13
|
+
:open="open"
|
|
14
|
+
:style="eyebrowStyle"
|
|
15
|
+
data-testid="cn-user-settings-dialog"
|
|
16
|
+
@update:open="onUpdateOpen"
|
|
17
|
+
@closing="close">
|
|
18
|
+
<div class="cn-user-settings-dialog__body">
|
|
19
|
+
<nav
|
|
20
|
+
v-if="sections.length > 0"
|
|
21
|
+
class="cn-user-settings-dialog__nav"
|
|
22
|
+
:aria-label="navLabel"
|
|
23
|
+
data-testid="cn-user-settings-nav">
|
|
24
|
+
<ul>
|
|
25
|
+
<li v-for="section in sections" :key="section.id">
|
|
26
|
+
<a
|
|
27
|
+
:href="`#settings-section_${section.id}`"
|
|
28
|
+
class="cn-user-settings-dialog__nav-link"
|
|
29
|
+
:class="{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }"
|
|
30
|
+
:aria-current="section.id === selected ? 'true' : undefined"
|
|
31
|
+
:data-testid="`cn-user-settings-nav-${section.id}`"
|
|
32
|
+
@click.prevent="selectSection(section.id)">
|
|
33
|
+
<span v-if="section.icon" class="cn-user-settings-dialog__nav-icon" aria-hidden="true">
|
|
34
|
+
<CnVNodes :vnodes="section.icon" />
|
|
35
|
+
</span>
|
|
36
|
+
<span>{{ section.name }}</span>
|
|
37
|
+
</a>
|
|
38
|
+
</li>
|
|
39
|
+
</ul>
|
|
40
|
+
</nav>
|
|
41
|
+
<div class="cn-user-settings-dialog__sections">
|
|
42
|
+
<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<template #actions>
|
|
47
|
+
<p class="cn-user-settings-dialog__note" data-testid="cn-user-settings-note">
|
|
48
|
+
{{ footerNote }}
|
|
49
|
+
</p>
|
|
50
|
+
<NcButton variant="secondary" data-testid="cn-user-settings-close" @click="close">
|
|
51
|
+
{{ closeLabel }}
|
|
52
|
+
</NcButton>
|
|
53
|
+
</template>
|
|
54
|
+
</CnDialog>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
59
|
+
import { NcButton } from '@nextcloud/vue'
|
|
60
|
+
import { markRaw } from 'vue'
|
|
61
|
+
import CnDialog from '../CnDialog/CnDialog.vue'
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The key NcAppSettingsSection injects to register itself with its dialog
|
|
65
|
+
* (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).
|
|
66
|
+
*/
|
|
67
|
+
const REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')
|
|
68
|
+
|
|
69
|
+
/** Renders a section's icon vnodes as they were registered. */
|
|
70
|
+
const CnVNodes = {
|
|
71
|
+
name: 'CnVNodes',
|
|
72
|
+
props: { vnodes: { type: [Array, Object], default: null } },
|
|
73
|
+
render() {
|
|
74
|
+
return this.vnodes
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* CnUserSettingsDialog - the user settings dialog in the board look (board
|
|
80
|
+
* PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the
|
|
81
|
+
* sections with their icons, the sections themselves, and a footer with a
|
|
82
|
+
* note that changes save straight away and a Close button.
|
|
83
|
+
*
|
|
84
|
+
* The sections stay the app's: any `NcAppSettingsSection` in the default slot
|
|
85
|
+
* registers itself with this dialog exactly as it does with
|
|
86
|
+
* `NcAppSettingsDialog`, with its name, order and `#icon` slot. CnAppRoot
|
|
87
|
+
* renders this dialog instead of `NcAppSettingsDialog` under the board look.
|
|
88
|
+
*
|
|
89
|
+
* @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look
|
|
90
|
+
*
|
|
91
|
+
* @event update:open Emitted with `false` when the dialog closes.
|
|
92
|
+
*/
|
|
93
|
+
export default {
|
|
94
|
+
name: 'CnUserSettingsDialog',
|
|
95
|
+
|
|
96
|
+
components: { CnDialog, CnVNodes, NcButton },
|
|
97
|
+
|
|
98
|
+
provide() {
|
|
99
|
+
return {
|
|
100
|
+
[REGISTRATION_KEY]: {
|
|
101
|
+
registerSection: this.registerSection,
|
|
102
|
+
unregisterSection: this.unregisterSection,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
props: {
|
|
108
|
+
/** Whether the dialog is open. */
|
|
109
|
+
open: { type: Boolean, default: false },
|
|
110
|
+
/** Dialog title; also its accessible name. */
|
|
111
|
+
title: { type: String, required: true },
|
|
112
|
+
/** Context line above the title. */
|
|
113
|
+
eyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },
|
|
114
|
+
/** Sentence under the title. */
|
|
115
|
+
subtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },
|
|
116
|
+
/** The app's name, after the footer note. */
|
|
117
|
+
appName: { type: String, default: '' },
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
emits: ['update:open'],
|
|
121
|
+
|
|
122
|
+
data() {
|
|
123
|
+
return {
|
|
124
|
+
sections: [],
|
|
125
|
+
selected: '',
|
|
126
|
+
picked: false,
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
computed: {
|
|
131
|
+
/** @return {object} The board's eyebrow: muted, in sentence case. */
|
|
132
|
+
eyebrowStyle() {
|
|
133
|
+
return { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/** @return {string} The navigation's accessible name. */
|
|
137
|
+
navLabel() {
|
|
138
|
+
return t('nextcloud-vue', 'Sections of the settings')
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/** @return {string} The footer note, with the app's name when known. */
|
|
142
|
+
footerNote() {
|
|
143
|
+
const note = t('nextcloud-vue', 'Changes are saved straight away')
|
|
144
|
+
return this.appName ? `${note} · ${this.appName}` : note
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/** @return {string} The Close button label. */
|
|
148
|
+
closeLabel() {
|
|
149
|
+
return t('nextcloud-vue', 'Close')
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
methods: {
|
|
154
|
+
/**
|
|
155
|
+
* Add a section to the navigation, kept in `order`; a section
|
|
156
|
+
* without one goes last. Until the reader picks one, the first
|
|
157
|
+
* section is current.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} id The section id.
|
|
160
|
+
* @param {string} name The section name.
|
|
161
|
+
* @param {number} [order] The section's place.
|
|
162
|
+
* @param {Array} [icon] The section's icon vnodes.
|
|
163
|
+
*/
|
|
164
|
+
registerSection(id, name, order, icon) {
|
|
165
|
+
const rest = this.sections.filter((section) => section.id !== id)
|
|
166
|
+
const place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1
|
|
167
|
+
this.sections = [...rest, { id, name, order: place, icon: icon ? markRaw(icon) : null }].sort((a, b) => a.order - b.order)
|
|
168
|
+
if (!this.picked) {
|
|
169
|
+
this.selected = this.sections[0].id
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Remove a section from the navigation.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} id The section id.
|
|
177
|
+
*/
|
|
178
|
+
unregisterSection(id) {
|
|
179
|
+
this.sections = this.sections.filter((section) => section.id !== id)
|
|
180
|
+
if (this.selected === id) {
|
|
181
|
+
this.selected = this.sections.length > 0 ? this.sections[0].id : ''
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mark a section current and scroll it into view.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} id The section id.
|
|
189
|
+
*/
|
|
190
|
+
selectSection(id) {
|
|
191
|
+
this.picked = true
|
|
192
|
+
this.selected = id
|
|
193
|
+
const target = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null
|
|
194
|
+
if (target && typeof target.scrollIntoView === 'function') {
|
|
195
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Follow NcDialog's own open state.
|
|
201
|
+
*
|
|
202
|
+
* @param {boolean} value Whether NcDialog is open.
|
|
203
|
+
*/
|
|
204
|
+
onUpdateOpen(value) {
|
|
205
|
+
if (!value) {
|
|
206
|
+
this.close()
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/** Close the dialog. */
|
|
211
|
+
close() {
|
|
212
|
+
/**
|
|
213
|
+
* @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).
|
|
214
|
+
*/
|
|
215
|
+
this.$emit('update:open', false)
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
<style scoped>
|
|
222
|
+
.cn-user-settings-dialog__body {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-wrap: wrap;
|
|
225
|
+
gap: 0 24px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.cn-user-settings-dialog__nav {
|
|
229
|
+
flex: 0 0 200px;
|
|
230
|
+
width: 200px;
|
|
231
|
+
padding-top: 18px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.cn-user-settings-dialog__nav ul {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 2px;
|
|
238
|
+
margin: 0;
|
|
239
|
+
padding: 0;
|
|
240
|
+
list-style: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.cn-user-settings-dialog__nav-link {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: 10px;
|
|
247
|
+
min-height: 40px;
|
|
248
|
+
padding: 0 12px;
|
|
249
|
+
border-radius: var(--border-radius-element, 8px);
|
|
250
|
+
font-size: 15px;
|
|
251
|
+
color: var(--color-main-text);
|
|
252
|
+
text-decoration: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.cn-user-settings-dialog__nav-link:hover {
|
|
256
|
+
background: var(--color-background-hover);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.cn-user-settings-dialog__nav-link:focus-visible {
|
|
260
|
+
outline: 2px solid var(--color-primary-element);
|
|
261
|
+
outline-offset: 2px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.cn-user-settings-dialog__nav-link--active,
|
|
265
|
+
.cn-user-settings-dialog__nav-link--active:hover {
|
|
266
|
+
background: var(--color-primary-element-light);
|
|
267
|
+
color: var(--color-primary-element-light-text);
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.cn-user-settings-dialog__nav-icon {
|
|
272
|
+
display: inline-flex;
|
|
273
|
+
flex: none;
|
|
274
|
+
width: 18px;
|
|
275
|
+
height: 18px;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.cn-user-settings-dialog__sections {
|
|
281
|
+
display: flex;
|
|
282
|
+
flex: 1 1 380px;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
min-width: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.cn-user-settings-dialog__sections :deep(.app-settings-section) {
|
|
288
|
+
margin: 0;
|
|
289
|
+
padding: 18px 0;
|
|
290
|
+
border-top: 1px solid var(--color-border);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {
|
|
294
|
+
margin: 0 0 12px;
|
|
295
|
+
font-size: 17px;
|
|
296
|
+
font-weight: 700;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.cn-user-settings-dialog__note {
|
|
300
|
+
flex: 1 1 auto;
|
|
301
|
+
margin: 0;
|
|
302
|
+
font-size: 14px;
|
|
303
|
+
color: var(--color-text-maxcontrast);
|
|
304
|
+
}
|
|
305
|
+
</style>
|
|
@@ -225,6 +225,18 @@ export default {
|
|
|
225
225
|
CnActionsMenu,
|
|
226
226
|
},
|
|
227
227
|
|
|
228
|
+
provide() {
|
|
229
|
+
return {
|
|
230
|
+
/**
|
|
231
|
+
* Whether this card draws a title row, so a widget inside it can
|
|
232
|
+
* leave out a heading of its own (nc-widget-header).
|
|
233
|
+
*
|
|
234
|
+
* @return {boolean}
|
|
235
|
+
*/
|
|
236
|
+
cnWidgetTitled: () => this.titleVisible && Boolean(this.displayTitle),
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
228
240
|
inject: {
|
|
229
241
|
/**
|
|
230
242
|
* Host translate function provided by CnAppRoot as
|