@conduction/nextcloud-vue 2.137.0 → 2.139.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  2. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +6 -4
  3. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDetailCard/CnDetailCard.vue.js +1 -1
  9. package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js +6 -0
  10. package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js +24 -7
  12. package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +2 -0
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  17. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  18. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +133 -6
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -17
  27. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  28. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +2 -0
  29. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +13 -2
  31. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  32. package/dist/esm/components/CnMailTab/dashboardRegistration.js +4 -0
  33. package/dist/esm/components/CnMailTab/dashboardRegistration.js.map +1 -1
  34. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
  35. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
  36. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
  37. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  38. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
  39. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  44. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  45. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  46. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  47. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +6 -0
  48. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  49. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +3 -3
  50. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  51. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  52. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +16 -0
  53. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +11 -5
  55. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  56. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
  57. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
  58. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js +1 -0
  59. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
  60. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  61. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  62. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  63. package/dist/esm/composables/indexSources.js +78 -12
  64. package/dist/esm/composables/indexSources.js.map +1 -1
  65. package/dist/esm/l10n/en.json.js +12 -1
  66. package/dist/esm/l10n/en.json.js.map +1 -1
  67. package/dist/esm/l10n/nl.json.js +12 -1
  68. package/dist/esm/l10n/nl.json.js.map +1 -1
  69. package/dist/esm/library-style.js +1 -1
  70. package/dist/esm/nextcloud-vue.css +685 -512
  71. package/dist/esm/utils/resolveFilterTokens.js +9 -0
  72. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  73. package/dist/esm/utils/sentinelTokens.js +1 -1
  74. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  75. package/dist/nextcloud-vue.cjs.js +580 -236
  76. package/dist/nextcloud-vue.cjs.js.map +2 -2
  77. package/dist/nextcloud-vue.css +685 -512
  78. package/l10n/en.json +12 -1
  79. package/l10n/nl.json +12 -1
  80. package/package.json +1 -1
  81. package/src/components/CnCellRenderer/CnCellRenderer.vue +6 -4
  82. package/src/components/CnChartWidget/CnChartWidget.vue +4 -0
  83. package/src/components/CnDetailCard/CnDetailCard.md +1 -0
  84. package/src/components/CnDetailCard/CnDetailCard.vue +25 -1
  85. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -0
  86. package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
  87. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -1
  88. package/src/components/CnIndexPage/CnIndexPage.vue +135 -7
  89. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +10 -2
  90. package/src/components/CnMailTab/dashboardRegistration.js +4 -0
  91. package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -0
  92. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
  93. package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
  94. package/src/components/CnOrderedListField/CnOrderedListField.vue +11 -1
  95. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +22 -3
  96. package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +1 -0
  97. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +3 -0
  98. package/src/composables/indexSources.js +78 -12
  99. package/src/css/dialog.css +26 -0
  100. package/src/css/index-page.css +5 -0
  101. package/src/css/look-board-detail.css +36 -0
  102. package/src/css/look-board-index.css +21 -0
  103. package/src/css/look-board.css +47 -0
  104. package/src/utils/resolveFilterTokens.js +9 -0
  105. package/src/utils/sentinelTokens.js +1 -1
@@ -1,9 +1,11 @@
1
1
  import { translate, getCanonicalLocale } from '@nextcloud/l10n';
2
2
  import { generateUrl, generateOcsUrl } from '@nextcloud/router';
3
3
  import { reactive } from 'vue';
4
+ import AccountOutline from 'vue-material-design-icons/AccountOutline.vue';
4
5
  import CalendarClock from 'vue-material-design-icons/CalendarClock.vue';
5
6
  import Cancel from 'vue-material-design-icons/Cancel.vue';
6
7
  import Check from 'vue-material-design-icons/Check.vue';
8
+ import EmailOutline from 'vue-material-design-icons/EmailOutline.vue';
7
9
  import HandBackRight from 'vue-material-design-icons/HandBackRight.vue';
8
10
  import LightningBolt from 'vue-material-design-icons/LightningBolt.vue';
9
11
  import Pencil from 'vue-material-design-icons/Pencil.vue';
@@ -14,6 +16,7 @@ import { fillTemplate } from '../utils/fillTemplate.js';
14
16
  import { flowRunStatuses, FLOW_RUN_ERROR_STATUSES } from '../utils/flowRunStatus.js';
15
17
  import { fetchUsers, fetchGroupMembers, fetchStatuses, fetchPresence } from '../utils/nextcloudDirectory.js';
16
18
  import { readPath } from '../utils/readPath.js';
19
+ import { resolveNextcloudUser } from '../utils/userAutocomplete.js';
17
20
  import { useFlowStore } from './useFlowStore.js';
18
21
  import { useOrganisationStore, organisationRecord } from './useOrganisationStore.js';
19
22
  import { useTaskInboxStore } from './useTaskInboxStore.js';
@@ -1089,7 +1092,7 @@ function fillRowTemplate(template, row) {
1089
1092
  * and its count (DqZoekresultaten).
1090
1093
  *
1091
1094
  * `sourceConfig.kinds` lists what is searched:
1092
- * `{ id, label, register, schema, titleField?, subtitleField?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
1095
+ * `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, linkedUser?, href?, limit? }`
1093
1096
  * for objects (OpenRegister's `_search`, so the reader sees what they may
1094
1097
  * read), or `{ id, label, files: true, limit? }` for the reader's files
1095
1098
  * (Nextcloud's unified search). The term is `sourceConfig.term`, else the
@@ -1103,6 +1106,10 @@ function fillRowTemplate(template, row) {
1103
1106
  * coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
1104
1107
  * template, "{case.title}") or `linkedField` (a path) fills the "Linked"
1105
1108
  * column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
1109
+ * `subtitleTemplate` ("Case {identifier}", translated) fills the line under a
1110
+ * result, else `subtitleField`.
1111
+ * `linkedUser: true` reads that value as a user id and shows the user's
1112
+ * display name. `sourceConfig.columnWidths` sets a width per column key.
1106
1113
  * The page's title and count line can name the term as `{term}`, and the
1107
1114
  * count is the active kind's total. Kind, link and status are filterable.
1108
1115
  *
@@ -1112,6 +1119,23 @@ function fillRowTemplate(template, row) {
1112
1119
  * @return {object} The source adapter.
1113
1120
  * @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
1114
1121
  */
1122
+ /**
1123
+ * Columns with the widths a page names per column key
1124
+ * (`sourceConfig.columnWidths`, `{ title: '250px' }`); a column it does not
1125
+ * name keeps none.
1126
+ *
1127
+ * @param {Array<object>} columns The columns.
1128
+ * @param {object|undefined} widths The widths by key.
1129
+ * @return {Array<object>} The columns.
1130
+ * @spec openspec/changes/search-linked-user-and-widths/specs/search-linked-user-and-widths/spec.md
1131
+ */
1132
+ function withColumnWidths(columns, widths) {
1133
+ if (!widths || typeof widths !== 'object') {
1134
+ return columns
1135
+ }
1136
+ return columns.map((col) => (typeof widths[col.key] === 'string' && widths[col.key] !== '' ? { ...col, width: widths[col.key] } : col))
1137
+ }
1138
+
1115
1139
  function searchSource(config = {}, context = {}) {
1116
1140
  const translate$1 = typeof context.translate === 'function' ? context.translate : (text) => text;
1117
1141
  const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
@@ -1200,12 +1224,27 @@ function searchSource(config = {}, context = {}) {
1200
1224
  return ''
1201
1225
  };
1202
1226
 
1227
+ const subtitleOf = (kind, object) => {
1228
+ if (kind.subtitleTemplate) {
1229
+ return fillTemplate(translate$1(kind.subtitleTemplate), object)
1230
+ }
1231
+ return kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : ''
1232
+ };
1233
+
1234
+ // `linkedUser`: the Linked cell holds a user id; name the user instead.
1235
+ const linkedUserNames = async (kind, objects) => {
1236
+ const ids = [...new Set(objects.map((object) => linkedOf(kind, object)).filter((id) => id !== ''))];
1237
+ const options = await Promise.all(ids.map((id) => resolveNextcloudUser(id).catch(() => null)));
1238
+ return new Map(ids.map((id, index) => [id, (options[index] && options[index].label) || id]))
1239
+ };
1240
+
1203
1241
  const searchObjects = async (axios, kind, term) => {
1204
1242
  const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema });
1205
1243
  const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } });
1206
1244
  const body = (res && res.data) || {};
1207
1245
  const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : []);
1208
1246
  const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null;
1247
+ const userNames = kind.linkedUser === true ? await linkedUserNames(kind, results) : null;
1209
1248
  return {
1210
1249
  total: typeof body.total === 'number' ? body.total : results.length,
1211
1250
  rows: results.map((object) => {
@@ -1220,9 +1259,9 @@ function searchSource(config = {}, context = {}) {
1220
1259
  kindLabel: kind.label,
1221
1260
  kindText: kindTextOf(kind, object),
1222
1261
  title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
1223
- subtitle: kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : '',
1262
+ subtitle: subtitleOf(kind, object),
1224
1263
  status,
1225
- linked: linkedOf(kind, object),
1264
+ linked: userNames ? (userNames.get(linkedOf(kind, object)) || linkedOf(kind, object)) : linkedOf(kind, object),
1226
1265
  href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
1227
1266
  object,
1228
1267
  }
@@ -1330,7 +1369,7 @@ function searchSource(config = {}, context = {}) {
1330
1369
  */
1331
1370
  loading: () => state.loading,
1332
1371
 
1333
- columns: [
1372
+ columns: withColumnWidths([
1334
1373
  { key: 'title', label: translate('nextcloud-vue', 'Result'), secondary: 'subtitle', sortable: true },
1335
1374
  // `kindAsText`: what one row is, as plain text ("Zaak", "Lantaarnpaal",
1336
1375
  // DqZoekresultaten), instead of the plural kind as a pill.
@@ -1345,7 +1384,7 @@ function searchSource(config = {}, context = {}) {
1345
1384
  widgetProps: { colorMap: (config && config.statusColorMap) || {} },
1346
1385
  sortable: true,
1347
1386
  },
1348
- ],
1387
+ ], config && config.columnWidths),
1349
1388
 
1350
1389
  /**
1351
1390
  * Every column header carries a filter button (DqZoekresultaten); the
@@ -1407,14 +1446,24 @@ function searchSource(config = {}, context = {}) {
1407
1446
  * the pager asks for.
1408
1447
  *
1409
1448
  * @param {Array<object>} members The members, by name.
1410
- * @param {object} config `search`, `sortKey`, `sortOrder`, `limit`, `offset`.
1449
+ * @param {object} config `search`, `nameLike`, `rolesLike`, `emailLike`, `today`, `sortKey`, `sortOrder`, `limit`, `offset`.
1411
1450
  * @return {{rows: Array<object>, total: number}} The page and how many match.
1412
1451
  */
1413
1452
  function membersPage(members, config = {}) {
1453
+ const has = (member, fields, text) => fields.some((field) => String(member[field] || '').toLowerCase().includes(text));
1414
1454
  const term = String(config.search || '').trim().toLowerCase();
1415
- let rows = term === ''
1416
- ? members.slice()
1417
- : members.filter((member) => ['displayname', 'function', 'roles', 'email'].some((field) => String(member[field] || '').toLowerCase().includes(term)));
1455
+ let rows = term === '' ? members.slice() : members.filter((member) => has(member, ['displayname', 'function', 'roles', 'email'], term));
1456
+ // The table heads' filters: text contains, and the Today values picked.
1457
+ for (const [param, fields] of [['nameLike', ['displayname', 'function']], ['rolesLike', ['roles']], ['emailLike', ['email']]]) {
1458
+ const text = String(config[param] || '').trim().toLowerCase();
1459
+ if (text !== '') {
1460
+ rows = rows.filter((member) => has(member, fields, text));
1461
+ }
1462
+ }
1463
+ const today = (Array.isArray(config.today) ? config.today : (config.today ? [config.today] : [])).map(String);
1464
+ if (today.length > 0) {
1465
+ rows = rows.filter((member) => today.includes(member.today));
1466
+ }
1418
1467
  if (config.sortKey) {
1419
1468
  const sign = config.sortOrder === 'desc' ? -1 : 1;
1420
1469
  rows = rows.sort((a, b) => sign * String(a[config.sortKey] ?? '').localeCompare(String(b[config.sortKey] ?? '')));
@@ -1476,10 +1525,11 @@ function usersSource() {
1476
1525
  */
1477
1526
  load: async (config) => {
1478
1527
  const group = String((config && (config.group || config.defaultGroup)) || '');
1479
- const { search, sortKey, sortOrder, limit, offset, ...request } = config || {};
1528
+ const { search, sortKey, sortOrder, limit, offset, nameLike, rolesLike, emailLike, today, ...request } = config || {};
1480
1529
  const key = JSON.stringify({ ...request, group });
1481
- // A new page, term or order of the members already read: no request.
1482
- if (key === membersKey && (offset !== undefined || search !== undefined || sortKey !== undefined)) {
1530
+ const narrows = [offset, search, sortKey, nameLike, rolesLike, emailLike, today].some((value) => value !== undefined);
1531
+ // A new page, term, order or head filter of the members already read: no request.
1532
+ if (key === membersKey && narrows) {
1483
1533
  const page = membersPage(readMembers, config);
1484
1534
  state.rows = page.rows;
1485
1535
  state.total = page.total;
@@ -1575,8 +1625,24 @@ function usersSource() {
1575
1625
 
1576
1626
  searchFields: {
1577
1627
  group: { param: 'group', single: true },
1628
+ 'displayname[like]': { param: 'nameLike', single: true },
1629
+ 'roles[like]': { param: 'rolesLike', single: true },
1630
+ 'email[like]': { param: 'emailLike', single: true },
1631
+ today: { param: 'today' },
1578
1632
  },
1579
1633
 
1634
+ /** A filter button on every head (DqAfdelingen); the members read are narrowed here. */
1635
+ headerFilters: true,
1636
+
1637
+ /**
1638
+ * A member's row menu (DqAfdelingen): their Nextcloud profile, and an
1639
+ * e-mail when they have an address. Both are links.
1640
+ */
1641
+ rowActions: [
1642
+ { id: 'open-profile', label: translate('nextcloud-vue', 'Open profile'), icon: AccountOutline, href: (row) => generateUrl(`/u/${encodeURIComponent(row.id)}`) },
1643
+ { id: 'send-email', label: translate('nextcloud-vue', 'Send email'), icon: EmailOutline, href: (row) => `mailto:${row.email}`, visible: (row) => Boolean(row && row.email) },
1644
+ ],
1645
+
1580
1646
  showAdd: false,
1581
1647
  }
1582
1648
  }