@conduction/nextcloud-vue 2.56.0-beta.31 → 2.56.0-beta.32
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/CnBannerWidget/CnBannerWidget.vue2.js +55 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +51 -15
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +20 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +7 -1
- 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/CnFieldListWidget/CnFieldListWidget.vue2.js +77 -3
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +0 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
- package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +4 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +1 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +1 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +843 -4
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/fetchFilterCounts.js +40 -5
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +7 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectWrite.js +59 -0
- package/dist/esm/utils/objectWrite.js.map +1 -0
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +10510 -6830
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +843 -4
- package/l10n/en.json +1 -0
- package/l10n/nl.json +1 -0
- package/package.json +1 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +93 -13
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +43 -3
- package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
- package/src/components/CnChipsFieldWidget/index.js +25 -0
- package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
- package/src/components/CnChoicePickerWidget/index.js +26 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +38 -2
- package/src/components/CnDataTable/CnDataTable.vue +2 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +45 -2
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -3
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +265 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +0 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +43 -1
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
- package/src/components/CnSettingsFormWidget/index.js +26 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
- package/src/components/CnSwitchRowsWidget/index.js +25 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
- package/src/components/CnToggleTableWidget/index.js +26 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +28 -1
- package/src/components/index.js +7 -0
- package/src/css/look-board-detail.css +36 -2
- package/src/css/look-board-index.css +23 -0
- package/src/css/look-board.css +72 -0
- package/src/index.js +7 -0
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +7 -0
- package/src/utils/objectWrite.js +55 -0
- package/src/utils/widgetDispatch.js +1 -1
|
@@ -90,7 +90,9 @@
|
|
|
90
90
|
:modelValue="selectedOption"
|
|
91
91
|
:options="displayOptions"
|
|
92
92
|
:clearable="false"
|
|
93
|
-
:inputLabel="label || writeKey"
|
|
93
|
+
:inputLabel="content.inline ? '' : (label || writeKey)"
|
|
94
|
+
:labelOutside="content.inline === true"
|
|
95
|
+
:ariaLabelCombobox="label || writeKey"
|
|
94
96
|
label="label"
|
|
95
97
|
data-testid="cn-workspace-filter-select"
|
|
96
98
|
@update:modelValue="onSelect" />
|
|
@@ -928,4 +930,29 @@ export default {
|
|
|
928
930
|
color: var(--color-text-maxcontrast);
|
|
929
931
|
font-size: 0.85em;
|
|
930
932
|
}
|
|
933
|
+
|
|
934
|
+
/* On a phone the bar's controls take a row each (the chips already wrap), and
|
|
935
|
+
a switch and a select are 44px touch targets. */
|
|
936
|
+
@media (max-width: 640px) {
|
|
937
|
+
.cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--inline {
|
|
938
|
+
flex: 1 1 100%;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--end {
|
|
942
|
+
margin-inline-start: 0;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.cn-workspace-filter-widget--inline .cn-workspace-filter-widget__toggle {
|
|
946
|
+
min-height: 44px;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.cn-workspace-filter-widget--inline :deep(.v-select) {
|
|
950
|
+
flex: 1 1 auto;
|
|
951
|
+
min-width: 0;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.cn-workspace-filter-widget--inline :deep(.vs__dropdown-toggle) {
|
|
955
|
+
min-height: 44px;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
931
958
|
</style>
|
package/src/components/index.js
CHANGED
|
@@ -219,6 +219,12 @@ export { CnTasksWidgetForm } from './CnTasksWidgetForm/index.js'
|
|
|
219
219
|
export { CnSectionsWidget } from './CnSectionsWidget/index.js'
|
|
220
220
|
export { CnFactsWidget } from './CnFactsWidget/index.js'
|
|
221
221
|
export { CnFieldListWidget } from './CnFieldListWidget/index.js'
|
|
222
|
+
export { CnSettingsFormWidget } from './CnSettingsFormWidget/index.js'
|
|
223
|
+
export { CnSwitchRowsWidget } from './CnSwitchRowsWidget/index.js'
|
|
224
|
+
export { CnRadioCardsWidget } from './CnRadioCardsWidget/index.js'
|
|
225
|
+
export { CnChipsFieldWidget } from './CnChipsFieldWidget/index.js'
|
|
226
|
+
export { CnToggleTableWidget } from './CnToggleTableWidget/index.js'
|
|
227
|
+
export { CnChoicePickerWidget } from './CnChoicePickerWidget/index.js'
|
|
222
228
|
export { CnRecordListWidget } from './CnRecordListWidget/index.js'
|
|
223
229
|
export { CnBannerWidget } from './CnBannerWidget/index.js'
|
|
224
230
|
export { default as CnConversationWidget } from './CnConversationThread/CnConversationWidget.vue'
|
|
@@ -236,6 +242,7 @@ export { CnMarkdownWidget } from './CnMarkdownWidget/index.js'
|
|
|
236
242
|
export { CnMarkdownWidgetForm } from './CnMarkdownWidgetForm/index.js'
|
|
237
243
|
export { CnObjectListWidget } from './CnObjectListWidget/index.js'
|
|
238
244
|
export { CnObjectListWidgetForm } from './CnObjectListWidgetForm/index.js'
|
|
245
|
+
export { CnSavedViewsWidget } from './CnSavedViewsWidget/index.js'
|
|
239
246
|
export { CnSavedViewWidget } from './CnSavedViewWidget/index.js'
|
|
240
247
|
export { CnSavedViewWidgetForm } from './CnSavedViewWidgetForm/index.js'
|
|
241
248
|
export { CnChartWidgetForm } from './CnChartWidgetForm/index.js'
|
|
@@ -202,6 +202,15 @@
|
|
|
202
202
|
gap: 4px;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/* No inner scroller: tabs that do not fit the width wrap onto a second row
|
|
206
|
+
instead of scrolling sideways (the library's default strip), so the strip
|
|
207
|
+
has no scrollbar, no clipped tab and no fade or chevron to find the rest. */
|
|
208
|
+
.cn-look-board .cn-tabs--board .cn-tabs__nav {
|
|
209
|
+
flex-wrap: wrap;
|
|
210
|
+
overflow: visible;
|
|
211
|
+
row-gap: 4px;
|
|
212
|
+
}
|
|
213
|
+
|
|
205
214
|
.cn-look-board .cn-tabs--board .cn-tabs__nav-item {
|
|
206
215
|
background-color: var(--color-background-dark);
|
|
207
216
|
border: 1px solid var(--color-border-dark);
|
|
@@ -347,12 +356,28 @@
|
|
|
347
356
|
padding: 0 0 10px;
|
|
348
357
|
}
|
|
349
358
|
|
|
350
|
-
|
|
351
|
-
|
|
359
|
+
/* Outranks the component's own `.cn-object-data-widget__label[data-v-...]`
|
|
360
|
+
(weight 500, 0.85em, which has the same specificity as the look's rule and
|
|
361
|
+
wins on source order wherever a bundle, such as a companion app's, loads
|
|
362
|
+
after this stylesheet), through the grid the labels sit in. Measured live:
|
|
363
|
+
the labels read bold and dark where the boards (DqContact, PqKlant, ShKlant)
|
|
364
|
+
draw them 13px, regular weight and light grey.
|
|
365
|
+
|
|
366
|
+
The colour carries `!important`: a theme (nldesign's element overrides) sets
|
|
367
|
+
the ink of every `div` outside the header with a selector that has an id in
|
|
368
|
+
its `:not()`, which no class rule outranks. */
|
|
369
|
+
.cn-look-board .cn-object-data-widget__grid .cn-object-data-widget__label {
|
|
370
|
+
color: var(--color-text-maxcontrast) !important;
|
|
352
371
|
font-size: 13px;
|
|
353
372
|
font-weight: 400;
|
|
354
373
|
}
|
|
355
374
|
|
|
375
|
+
/* The facts card's labels: the same light grey, regular weight. */
|
|
376
|
+
.cn-look-board .cn-facts-widget .cn-facts-widget__label {
|
|
377
|
+
color: var(--color-text-maxcontrast);
|
|
378
|
+
font-weight: 400;
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
.cn-look-board .cn-object-data-widget__value {
|
|
357
382
|
font-weight: 500;
|
|
358
383
|
}
|
|
@@ -610,3 +635,12 @@
|
|
|
610
635
|
font-size: 13px;
|
|
611
636
|
margin: 2px 0 0;
|
|
612
637
|
}
|
|
638
|
+
|
|
639
|
+
/* The sidebar's own open toggle (NcAppSidebar's `.app-sidebar__toggle`, drawn
|
|
640
|
+
when the sidebar is closed) is positioned over the content's top-right
|
|
641
|
+
corner, which in this look is the header's last button: a stray square icon
|
|
642
|
+
on "More". The board draws none, so it is hidden on a detail page and the
|
|
643
|
+
header menu offers "Open sidebar" instead. */
|
|
644
|
+
.cn-look-board:has(.cn-detail-page) > .app-sidebar__toggle {
|
|
645
|
+
display: none;
|
|
646
|
+
}
|
|
@@ -557,6 +557,29 @@
|
|
|
557
557
|
font-size: 13px;
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
/* An avatar in the title column (PtAccounts): a 32px chip circle, 10px before
|
|
561
|
+
the name, the e-mail under the name beside it. The name keeps the title's
|
|
562
|
+
weight and size, and its underline: an inline-flex box is atomic, so the
|
|
563
|
+
cell's own underline does not reach it. */
|
|
564
|
+
.cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar {
|
|
565
|
+
gap: 10px;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar-initials {
|
|
569
|
+
background: var(--color-background-dark);
|
|
570
|
+
color: var(--color-main-text);
|
|
571
|
+
font-size: 12px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-cell-renderer__avatar-name {
|
|
575
|
+
text-decoration: underline;
|
|
576
|
+
text-underline-offset: 2px;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title .cn-cell-renderer__avatar-name {
|
|
580
|
+
text-decoration: none;
|
|
581
|
+
}
|
|
582
|
+
|
|
560
583
|
/* A status pill: 3px 10px, radius 12, 13px weight 600. The colours stay the
|
|
561
584
|
status colour map's. */
|
|
562
585
|
.cn-look-board .cn-table-container--board .cn-data-table .cn-status-badge {
|
package/src/css/look-board.css
CHANGED
|
@@ -555,3 +555,75 @@
|
|
|
555
555
|
.cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
|
|
556
556
|
font-weight: 600;
|
|
557
557
|
}
|
|
558
|
+
|
|
559
|
+
/* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
|
|
560
|
+
/* The dashboard's card body is flush, so the rows carry their own inset: the
|
|
561
|
+
board's card pads 24px at the sides and each row 14px, a soft hairline
|
|
562
|
+
between rows, the title 15px weight 600 and the meta 13px muted. A row is a
|
|
563
|
+
link, but only the hover says so: no underline on any text run, however the
|
|
564
|
+
host styles its links (`!important`, because a host rule such as
|
|
565
|
+
`#content a` outranks any class). The pill and the date sit at the right. */
|
|
566
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list {
|
|
567
|
+
padding: 0 24px 10px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--rows .cn-record-list__item + .cn-record-list__item {
|
|
571
|
+
border-top: 1px solid var(--color-border-dark, var(--color-border));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row {
|
|
575
|
+
padding: 14px 0;
|
|
576
|
+
flex-wrap: wrap;
|
|
577
|
+
gap: 8px 16px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row,
|
|
581
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:hover,
|
|
582
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:focus-visible,
|
|
583
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row * {
|
|
584
|
+
color: inherit;
|
|
585
|
+
text-decoration: none !important;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__title {
|
|
589
|
+
color: var(--color-main-text);
|
|
590
|
+
font-size: 15px;
|
|
591
|
+
font-weight: 600;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
|
|
595
|
+
color: var(--color-text-maxcontrast);
|
|
596
|
+
font-size: 13px;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__trailing {
|
|
600
|
+
color: var(--color-text-maxcontrast);
|
|
601
|
+
font-size: 13px;
|
|
602
|
+
font-weight: 600;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/* Hover: the whole row takes the hover ground, 8px beyond the text so the
|
|
606
|
+
text does not move. */
|
|
607
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {
|
|
608
|
+
margin-inline: -8px;
|
|
609
|
+
padding-inline: 8px;
|
|
610
|
+
border-radius: 8px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover,
|
|
614
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:focus-visible {
|
|
615
|
+
background: var(--color-background-hover);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover .cn-record-list__title {
|
|
619
|
+
text-decoration: none !important;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__aside {
|
|
623
|
+
margin-inline-start: auto;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* The board draws no chevron after the pill and date. */
|
|
627
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
|
|
628
|
+
display: none;
|
|
629
|
+
}
|
package/src/index.js
CHANGED
|
@@ -60,7 +60,9 @@ export {
|
|
|
60
60
|
CnChartWidgetForm,
|
|
61
61
|
CnChatPage,
|
|
62
62
|
CnChildRecordsField,
|
|
63
|
+
CnChipsFieldWidget,
|
|
63
64
|
CnChoiceCards,
|
|
65
|
+
CnChoicePickerWidget,
|
|
64
66
|
CnColorPicker,
|
|
65
67
|
CnCommandPalette,
|
|
66
68
|
CnConfigurationCard,
|
|
@@ -245,6 +247,7 @@ export {
|
|
|
245
247
|
CnQuickFilterBar,
|
|
246
248
|
CnQuicklinksWidget,
|
|
247
249
|
CnQuicklinksWidgetForm,
|
|
250
|
+
CnRadioCardsWidget,
|
|
248
251
|
CnRecordListWidget,
|
|
249
252
|
CnReferencePreview,
|
|
250
253
|
CnRegisterMapping,
|
|
@@ -263,6 +266,7 @@ export {
|
|
|
263
266
|
CnSavedViewsControl,
|
|
264
267
|
CnSavedViewShareDialog,
|
|
265
268
|
CnSavedViewShareFields,
|
|
269
|
+
CnSavedViewsWidget,
|
|
266
270
|
CnSavedViewWidget,
|
|
267
271
|
CnSavedViewWidgetForm,
|
|
268
272
|
CnSaveViewDialog,
|
|
@@ -270,6 +274,7 @@ export {
|
|
|
270
274
|
CnSearchPage,
|
|
271
275
|
CnSectionsWidget,
|
|
272
276
|
CnSettingsCard,
|
|
277
|
+
CnSettingsFormWidget,
|
|
273
278
|
CnSettingsPage,
|
|
274
279
|
CnSettingsSection,
|
|
275
280
|
CnSetupWizard,
|
|
@@ -289,6 +294,7 @@ export {
|
|
|
289
294
|
CnSubObjectsField,
|
|
290
295
|
CnSummaryAggregates,
|
|
291
296
|
CnSupportDialog,
|
|
297
|
+
CnSwitchRowsWidget,
|
|
292
298
|
CnTab,
|
|
293
299
|
CnTabbedFormDialog,
|
|
294
300
|
CnTableWidget,
|
|
@@ -309,6 +315,7 @@ export {
|
|
|
309
315
|
CnTimelineStages,
|
|
310
316
|
CnTimelineView,
|
|
311
317
|
CnTimelineWidget,
|
|
318
|
+
CnToggleTableWidget,
|
|
312
319
|
CnTranslatedBadge,
|
|
313
320
|
CnTreeView,
|
|
314
321
|
CnUnreadMarker,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* contentHeaderLink — the text link a widget's content asks its card header
|
|
6
|
+
* to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an
|
|
7
|
+
* optional `viewAllLabel`) that an inbox-style widget uses for its "View all".
|
|
8
|
+
*
|
|
9
|
+
* @module utils/contentHeaderLink
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The header link a widget's `content` declares, in the shape
|
|
14
|
+
* CnWidgetWrapper's `headerLink` takes, or null when it declares none.
|
|
15
|
+
* The label is left as written: the wrapper translates it.
|
|
16
|
+
*
|
|
17
|
+
* `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.
|
|
18
|
+
*
|
|
19
|
+
* @param {object|null|undefined} content The widget's content.
|
|
20
|
+
* @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
|
|
21
|
+
*/
|
|
22
|
+
export function contentHeaderLink(content) {
|
|
23
|
+
if (!content || typeof content !== 'object') {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const declared = content.headerLink
|
|
27
|
+
if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
|
|
28
|
+
return declared
|
|
29
|
+
}
|
|
30
|
+
const view = content.viewAllRoute
|
|
31
|
+
const name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '')
|
|
32
|
+
if (typeof name !== 'string' || name === '') {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const link = {
|
|
36
|
+
label: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',
|
|
37
|
+
route: name,
|
|
38
|
+
}
|
|
39
|
+
if (view && typeof view === 'object') {
|
|
40
|
+
if (view.params && typeof view.params === 'object') {
|
|
41
|
+
link.params = view.params
|
|
42
|
+
}
|
|
43
|
+
if (view.query && typeof view.query === 'object') {
|
|
44
|
+
link.query = view.query
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return link
|
|
48
|
+
}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `status: closed`, ...). Those are answered by one `/grouped` request on that
|
|
10
10
|
* field. A filter that narrows on something else (two fields, an operator, a
|
|
11
11
|
* list of values) cannot be read off a grouped result, so each of those costs
|
|
12
|
-
* one `/value` count. A filter that holds a LIST of values
|
|
13
|
-
* the list endpoint, because the
|
|
12
|
+
* one `/value` count. A filter that holds a LIST of values, or tests a field
|
|
13
|
+
* for no value (`isnull`), is counted through the list endpoint, because the
|
|
14
|
+
* aggregation endpoints cannot express it.
|
|
14
15
|
* Nothing is requested for an entry that did not ask.
|
|
15
16
|
*
|
|
16
17
|
* @module utils/fetchFilterCounts
|
|
@@ -38,6 +39,37 @@ export function hasListValue(filter) {
|
|
|
38
39
|
return Object.values(filter).some((value) => Array.isArray(value))
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Whether a filter tests a field for "no value" (`{ assignee: null }` or
|
|
44
|
+
* `{ supersededBy: { isnull: true } }`).
|
|
45
|
+
*
|
|
46
|
+
* OpenRegister's aggregation endpoints have no null operator: `isnull` is
|
|
47
|
+
* refused there and an empty value is dropped or matches nothing, so such a
|
|
48
|
+
* filter counted 0 while the list showed rows. The list endpoint does read
|
|
49
|
+
* `field[isnull]=true`, so a filter like this is counted through it.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} filter A filter map.
|
|
52
|
+
* @return {boolean} True when any value is null or carries an `isnull` operator.
|
|
53
|
+
*/
|
|
54
|
+
export function hasNullTest(filter) {
|
|
55
|
+
if (!filter || typeof filter !== 'object') {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return Object.values(filter).some((value) => value === null
|
|
59
|
+
|| (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the aggregation endpoints cannot express a filter, so it is counted
|
|
64
|
+
* through the list endpoint: a list of values, or a test for no value.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} filter A filter map.
|
|
67
|
+
* @return {boolean} True when the filter needs the list endpoint.
|
|
68
|
+
*/
|
|
69
|
+
export function needsListEndpoint(filter) {
|
|
70
|
+
return hasListValue(filter) || hasNullTest(filter)
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/**
|
|
42
74
|
* Count the records a filter matches by asking the LIST endpoint for one
|
|
43
75
|
* record and reading its `total`. The filter is sent the way the list request
|
|
@@ -59,6 +91,9 @@ export async function fetchListTotal(register, schema, filter, ctx) {
|
|
|
59
91
|
for (const [op, operand] of Object.entries(value)) {
|
|
60
92
|
params[`${key}[${op}]`] = operand
|
|
61
93
|
}
|
|
94
|
+
} else if (value === null) {
|
|
95
|
+
// "No value": the list endpoint reads it as the isnull operator.
|
|
96
|
+
params[`${key}[isnull]`] = 'true'
|
|
62
97
|
} else {
|
|
63
98
|
params[key] = value
|
|
64
99
|
}
|
|
@@ -125,12 +160,12 @@ export async function fetchFilterCounts({ register, schema, entries, baseFilter,
|
|
|
125
160
|
// and `/value` returns exactly its number.
|
|
126
161
|
// A list of values anywhere in a request rules the aggregation endpoints
|
|
127
162
|
// out for it. A base filter with one rules them out for every entry.
|
|
128
|
-
const baseHasList =
|
|
163
|
+
const baseHasList = needsListEndpoint(base)
|
|
129
164
|
const byField = {}
|
|
130
165
|
const single = []
|
|
131
166
|
const viaList = []
|
|
132
167
|
for (const entry of entries) {
|
|
133
|
-
if (baseHasList ||
|
|
168
|
+
if (baseHasList || needsListEndpoint(entry.filter)) {
|
|
134
169
|
viaList.push(entry)
|
|
135
170
|
continue
|
|
136
171
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* initials: the letters an avatar shows for a free name.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/initials
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The initials of a name: the first letters of its first and its last word,
|
|
12
|
+
* upper-cased ("Sanne de Vries" reads "SV", "Jan" reads "J").
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} name The name.
|
|
15
|
+
* @return {string} One or two letters, or '' when there is no name.
|
|
16
|
+
* @spec openspec/changes/screens-record-list-avatar/specs/record-list-avatar/spec.md#requirement-a-record-list-row-can-carry-an-avatar
|
|
17
|
+
*/
|
|
18
|
+
export function initialsOf(name) {
|
|
19
|
+
if (typeof name !== 'string') {
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
const words = name.trim().split(/\s+/).filter((word) => word !== '')
|
|
23
|
+
if (words.length === 0) {
|
|
24
|
+
return ''
|
|
25
|
+
}
|
|
26
|
+
const first = Array.from(words[0])[0] || ''
|
|
27
|
+
const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''
|
|
28
|
+
return (first + last).toLocaleUpperCase()
|
|
29
|
+
}
|
|
@@ -93,6 +93,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
93
93
|
'banner',
|
|
94
94
|
'calendar',
|
|
95
95
|
'chart',
|
|
96
|
+
'chips-field',
|
|
97
|
+
'choice-picker',
|
|
96
98
|
'container',
|
|
97
99
|
'conversation',
|
|
98
100
|
'countdown',
|
|
@@ -125,21 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
|
|
|
125
127
|
'object-table',
|
|
126
128
|
'people',
|
|
127
129
|
'quicklinks',
|
|
130
|
+
'radio-cards',
|
|
128
131
|
'record-list',
|
|
129
132
|
'related',
|
|
130
133
|
'saved-view',
|
|
134
|
+
'saved-views',
|
|
131
135
|
'sections',
|
|
136
|
+
'settings-form',
|
|
132
137
|
'spend-analytics',
|
|
133
138
|
'stacked-bar',
|
|
134
139
|
'stages',
|
|
135
140
|
'stat',
|
|
136
141
|
'stats-block',
|
|
142
|
+
'switch-rows',
|
|
137
143
|
'table',
|
|
138
144
|
'tabs',
|
|
139
145
|
'tasks',
|
|
140
146
|
'text',
|
|
141
147
|
'tile',
|
|
142
148
|
'timeline',
|
|
149
|
+
'toggle-table',
|
|
143
150
|
'video',
|
|
144
151
|
'week-strip',
|
|
145
152
|
'workspace-filter',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* objectWrite: the one-object PATCH the settings widgets share.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/objectWrite
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { prefixUrl } from './headers.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The OpenRegister URL of one object, or '' when register, schema or id is missing.
|
|
14
|
+
*
|
|
15
|
+
* @param {string|object} register Register slug.
|
|
16
|
+
* @param {string|object} schema Schema slug.
|
|
17
|
+
* @param {string|number} id Object id.
|
|
18
|
+
* @return {string} The object URL.
|
|
19
|
+
*/
|
|
20
|
+
export function objectUrlOf(register, schema, id) {
|
|
21
|
+
if (typeof register !== 'string' || typeof schema !== 'string' || !register || !schema || id === undefined || id === null || id === '') {
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
|
+
return `/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}/${encodeURIComponent(String(id))}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* PATCH only the given fields of one object.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} url The object URL (see objectUrlOf).
|
|
31
|
+
* @param {object} patch The fields to write.
|
|
32
|
+
* @return {Promise<object>} The object the server answered with.
|
|
33
|
+
*/
|
|
34
|
+
export async function patchObject(url, patch) {
|
|
35
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
36
|
+
const { data } = await axios.patch(prefixUrl(url), patch)
|
|
37
|
+
return data && typeof data === 'object' ? data : {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The fields of `next` that differ from `base`, compared by value.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} base What the server last held.
|
|
44
|
+
* @param {object} next What the form holds.
|
|
45
|
+
* @return {object} The changed fields only.
|
|
46
|
+
*/
|
|
47
|
+
export function changedFields(base, next) {
|
|
48
|
+
const patch = {}
|
|
49
|
+
for (const [key, value] of Object.entries(next)) {
|
|
50
|
+
if (JSON.stringify(value === undefined ? null : value) !== JSON.stringify(base[key] === undefined ? null : base[key])) {
|
|
51
|
+
patch[key] = value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return patch
|
|
55
|
+
}
|
|
@@ -48,7 +48,7 @@ import { canonicalWidgetType } from './widgetTypeAliases.js'
|
|
|
48
48
|
*
|
|
49
49
|
* @type {string[]}
|
|
50
50
|
*/
|
|
51
|
-
export const CONTENT_ONLY_TYPES = Object.freeze(['object-list', 'table', 'files', 'flow-runs', 'tasks', 'facts', 'field-list', 'record-list'])
|
|
51
|
+
export const CONTENT_ONLY_TYPES = Object.freeze(['object-list', 'table', 'files', 'flow-runs', 'tasks', 'facts', 'field-list', 'record-list', 'settings-form', 'switch-rows', 'radio-cards', 'chips-field', 'toggle-table', 'choice-picker'])
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* The stored content/config blob for a widget definition.
|