@conduction/nextcloud-vue 2.139.0 → 2.141.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 +9 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +28 -4
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +64 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +76 -37
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +5 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +28 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -2
- 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/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +20 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +5 -2
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +4 -3
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +6 -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 +961 -605
- package/dist/esm/utils/builtInFormatters.js +23 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/esm/utils/myTeam.js +24 -4
- package/dist/esm/utils/myTeam.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +5 -4
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/transitionInputs.js +43 -2
- package/dist/esm/utils/transitionInputs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1731 -846
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +961 -605
- package/l10n/en.json +3 -1
- package/l10n/nl.json +6 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +30 -6
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +121 -5
- package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +6 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
- package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +43 -9
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
- package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
- package/src/composables/indexSources.js +5 -2
- package/src/composables/useTaskInboxStore.js +4 -3
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +12 -9
- package/src/css/look-board-index.css +90 -7
- package/src/css/look-board.css +102 -2
- package/src/css/table.css +25 -0
- package/src/utils/builtInFormatters.js +22 -0
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/groupSublines.js +125 -0
- package/src/utils/myTeam.js +24 -4
- package/src/utils/resolveFilterTokens.js +5 -4
- package/src/utils/transitionInputs.js +42 -1
|
@@ -470,6 +470,27 @@ function formatMasked(value, _row, _property, options) {
|
|
|
470
470
|
return '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
/**
|
|
474
|
+
* A stored value through a display map (`valueMap` registry key): the label
|
|
475
|
+
* `options.map` gives the value, else the value as stored. "Besloten
|
|
476
|
+
* Vennootschap" reads "BV" on DqOrganisaties.
|
|
477
|
+
*
|
|
478
|
+
* @param {unknown} value The stored value.
|
|
479
|
+
* @param {object} [_row] The full row (unused).
|
|
480
|
+
* @param {object} [_property] The schema property (unused).
|
|
481
|
+
* @param {{map?: Object<string, string>}} [options] The column's `formatterOptions`.
|
|
482
|
+
* @return {string} The label, or the value.
|
|
483
|
+
* @spec openspec/changes/value-map-formatter/specs/index-page/spec.md#requirement-a-column-can-show-stored-values-through-a-display-map
|
|
484
|
+
*/
|
|
485
|
+
function formatValueMap(value, _row, _property, options) {
|
|
486
|
+
if (value === null || value === undefined || value === '') {
|
|
487
|
+
return ''
|
|
488
|
+
}
|
|
489
|
+
const map = options && options.map && typeof options.map === 'object' ? options.map : {};
|
|
490
|
+
const label = Object.hasOwn(map, String(value)) ? map[String(value)] : undefined;
|
|
491
|
+
return typeof label === 'string' && label !== '' ? label : String(value)
|
|
492
|
+
}
|
|
493
|
+
|
|
473
494
|
/**
|
|
474
495
|
* Built-in formatter registry merged under any consumer-registered
|
|
475
496
|
* `formatters` in `CnAppRoot`'s `cnFormatters` provide.
|
|
@@ -516,7 +537,8 @@ const BUILT_IN_FORMATTERS = {
|
|
|
516
537
|
connectionSettingsLabel: formatConnectionSettingsLabel,
|
|
517
538
|
masked: formatMasked,
|
|
518
539
|
isoDuration: formatIsoDuration,
|
|
540
|
+
valueMap: formatValueMap,
|
|
519
541
|
};
|
|
520
542
|
|
|
521
|
-
export { BUILT_IN_FORMATTERS, formatConditionalPhrase, formatConnectionSettingsLabel, formatConnectionStatus, formatCount, formatCurrency, formatDate, formatDateTime, formatDaysSince, formatDaysUntil, formatIsoDuration, formatMasked, formatRelativeDay, formatRelativeTime };
|
|
543
|
+
export { BUILT_IN_FORMATTERS, formatConditionalPhrase, formatConnectionSettingsLabel, formatConnectionStatus, formatCount, formatCurrency, formatDate, formatDateTime, formatDaysSince, formatDaysUntil, formatIsoDuration, formatMasked, formatRelativeDay, formatRelativeTime, formatValueMap };
|
|
522
544
|
//# sourceMappingURL=builtInFormatters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtInFormatters.js","sources":["../../../src/utils/builtInFormatters.js"],"sourcesContent":["import { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { formatRelativeDate } from './boardDate.js'\nimport { formatMoney } from './formatMetric.js'\nimport { parseDuration } from './isoDuration.js'\n\n/**\n * @file Built-in formatters merged into the `cnFormatters` registry\n * `CnAppRoot` provides to descendants.\n *\n * Resolution order (in `CnCellRenderer`):\n * 1. Consumer-registered `formatters[id]` (passed to CnAppRoot).\n * 2. Built-in `BUILT_IN_FORMATTERS[id]` from this file.\n * 3. Schema-type-aware rendering.\n * 4. Plain `formatValue()` fallback.\n *\n * Consumer formatters win (CnAppRoot spreads\n * `{ ...BUILT_IN_FORMATTERS, ...props.formatters }`).\n *\n * A formatter is invoked as `fn(value, row, property, options)` — `options`\n * is the column's declarative `formatterOptions` map (undefined when the\n * column declares none), so config-driven formatters (`currency`,\n * `conditionalPhrase`) work without one registry function per configuration.\n * The fourth argument is additive: pre-existing three-argument formatters\n * are unaffected.\n *\n * Every built-in MUST be safe against null / empty / non-parseable\n * inputs — the contract is \"return the original value (or empty\n * string) rather than throw\".\n */\n\n/**\n * Parse a value that's meant to be a date — accepts a `Date` instance,\n * an ISO/parseable string, or a numeric timestamp.\n *\n * @param {Date|string|number|null|undefined} value The candidate date value.\n * @return {Date|null} A `Date` instance, or `null` if the input is null/empty/unparseable.\n */\nfunction toDate(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn null\n\t}\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? null : value\n\t}\n\tconst d = new Date(value)\n\treturn Number.isNaN(d.getTime()) ? null : d\n}\n\n/**\n * Locale-formatted date (no time). Backed by `Intl.DateTimeFormat`\n * (`dateStyle: 'medium'`) using the user-agent locale.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Formatted date, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatDate(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\treturn new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(d)\n}\n\n/**\n * Locale-formatted date + time. Backed by `Intl.DateTimeFormat`\n * (`dateStyle: 'medium'`, `timeStyle: 'short'`).\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Formatted date + time, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatDateTime(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\treturn new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(d)\n}\n\n/**\n * Relative time (\"3 days ago\") via `Intl.RelativeTimeFormat`.\n * Picks the coarsest unit whose absolute delta exceeds one unit,\n * down to minutes (anything sub-minute clamps to \"now\"/seconds).\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Relative phrasing, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatRelativeTime(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst diffMs = d.getTime() - Date.now()\n\tconst rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })\n\tconst units = [\n\t\t['year', 31536000000],\n\t\t['month', 2592000000],\n\t\t['week', 604800000],\n\t\t['day', 86400000],\n\t\t['hour', 3600000],\n\t\t['minute', 60000],\n\t]\n\tfor (const [unit, ms] of units) {\n\t\tif (Math.abs(diffMs) >= ms || unit === 'minute') {\n\t\t\treturn rtf.format(Math.round(diffMs / ms), unit)\n\t\t}\n\t}\n\treturn rtf.format(0, 'second')\n}\n\n/**\n * Whole-day difference between a date value and today, comparing\n * date-only (both clamped to local midnight) so a due date later today\n * still counts as \"today\", not \"in 0.4 days\".\n *\n * @param {Date} d The parsed date.\n * @return {number} Signed day count: positive = future, negative = past, 0 = today.\n */\nfunction dayDiffFromToday(d) {\n\tconst target = new Date(d)\n\ttarget.setHours(0, 0, 0, 0)\n\tconst today = new Date()\n\ttoday.setHours(0, 0, 0, 0)\n\treturn Math.round((target.getTime() - today.getTime()) / 86400000)\n}\n\n/**\n * Future-oriented deadline formatter (`daysUntil` registry key), i18n'd\n * through the library's own translation slug (`nextcloud-vue`):\n * a future date renders \"N days remaining\", today renders \"Due today\",\n * and a past date renders \"N days overdue\". Day counts are plural-aware\n * via `translatePlural`.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input — never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n */\nexport function formatDaysUntil(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst days = dayDiffFromToday(d)\n\tif (days === 0) {\n\t\treturn t('nextcloud-vue', 'Due today')\n\t}\n\tif (days > 0) {\n\t\treturn n('nextcloud-vue', '{count} day remaining', '{count} days remaining', days, { count: days })\n\t}\n\tconst overdue = Math.abs(days)\n\treturn n('nextcloud-vue', '{count} day overdue', '{count} days overdue', overdue, { count: overdue })\n}\n\n/**\n * Elapsed-time formatter (`daysSince` registry key), i18n'd through the\n * library's own translation slug (`nextcloud-vue`) in the Nextcloud user\n * language. Whole calendar days, read as a running phrase so it sits after a\n * title or in a meta line: today renders \"today\", one day back renders\n * \"yesterday\", further back renders \"N days ago\". A (nonsensical but\n * harmless) future date renders \"tomorrow\" or \"in N days\". Day counts are\n * plural-aware via `translatePlural`.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input, never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n */\nexport function formatDaysSince(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst days = dayDiffFromToday(d)\n\tif (days === 0) {\n\t\treturn t('nextcloud-vue', 'today')\n\t}\n\tif (days === -1) {\n\t\treturn t('nextcloud-vue', 'yesterday')\n\t}\n\tif (days === 1) {\n\t\treturn t('nextcloud-vue', 'tomorrow')\n\t}\n\tif (days < 0) {\n\t\tconst ago = Math.abs(days)\n\t\treturn n('nextcloud-vue', '{count} day ago', '{count} days ago', ago, { count: ago })\n\t}\n\treturn n('nextcloud-vue', 'in {count} day', 'in {count} days', days, { count: days })\n}\n\n/**\n * Relative-date formatter (`relativeDate` registry key): \"today\", \"yesterday\",\n * then the board's short date (\"5 okt\"). The column's `formatterOptions` may set\n * `time: true` to add the time of day after the day (\"vandaag, 09.42\").\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input, never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{time?: boolean}} [options] The column's `formatterOptions`.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n * @spec openspec/changes/screens-relative-dates/specs/relative-dates/spec.md#requirement-a-relative-date-reads-today-yesterday-then-a-short-date\n */\nexport function formatRelativeDay(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst text = formatRelativeDate(value, { withTime: options?.time === true })\n\treturn text === '' ? String(value) : text\n}\n\n/**\n * Currency formatter (`currency` registry key) via `Intl.NumberFormat`.\n * Defaults to EUR; the column's `formatterOptions` may set `currency`\n * (ISO-4217 code, guarded by `safeCurrencyCode` so an invalid code can\n * never throw a `RangeError`) and `decimals` (default 2).\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for non-numeric input — never throws.\n *\n * @param {unknown} value A numeric value (or numeric string).\n * @param {object} [row] The full row, for `currencyField`.\n * @param {object} [_property] The schema property (unused).\n * @param {{currency?: string, currencyField?: string, decimals?: number}} [options] The column's `formatterOptions`.\n * @return {string} The locale currency string (or ''/original on bad input).\n */\nexport function formatCurrency(value, row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst num = Number(value)\n\tif (!Number.isFinite(num)) {\n\t\treturn String(value)\n\t}\n\treturn formatMoney(num, options || {}, row)\n}\n\n/**\n * Sign/zero-based phrase selector (`conditionalPhrase` registry key) —\n * generalizes `daysUntil` to ANY numeric field: the column's\n * `formatterOptions` supply pre-translated `{ negative, zero, positive }`\n * phrases and the matching one renders with `{n}` replaced by the ABSOLUTE\n * value (so \"{n} days overdue\" reads naturally for -3). Phrases are\n * consumer-provided strings — apps translate them before declaring the\n * column, keeping i18n in the app layer.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for non-numeric input or when the selected phrase is\n * missing — never throws.\n *\n * @param {unknown} value A numeric value (or numeric string).\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{negative?: string, zero?: string, positive?: string}} [options] The column's `formatterOptions`.\n * @return {string} The selected phrase with `{n}` substituted (or ''/original on bad input).\n */\nexport function formatConditionalPhrase(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst num = Number(value)\n\tif (!Number.isFinite(num)) {\n\t\treturn String(value)\n\t}\n\tconst opts = options || {}\n\tconst phrase = num < 0 ? opts.negative : (num > 0 ? opts.positive : opts.zero)\n\tif (typeof phrase !== 'string' || phrase === '') {\n\t\treturn String(value)\n\t}\n\treturn phrase.replace(/\\{n\\}/g, String(Math.abs(num)))\n}\n\n/**\n * Read a value that may be a collection SERIALISED as JSON back into the\n * collection itself. OpenRegister hands a stringified array back for some\n * property types (and an app's own import path may persist one), which\n * `formatCount` would otherwise report as a single entry.\n *\n * Only `[`/`{`-leading strings are tried, so an ordinary text value never pays\n * for a parse attempt, and a string that merely looks like JSON but isn't falls\n * back to being one entry.\n *\n * @param {unknown} value The raw cell value.\n * @return {unknown} The parsed collection, or `value` unchanged.\n */\nfunction parseCollection(value) {\n\tif (typeof value !== 'string') {\n\t\treturn value\n\t}\n\tconst trimmed = value.trim()\n\tif (trimmed[0] !== '[' && trimmed[0] !== '{') {\n\t\treturn value\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(trimmed)\n\t\treturn (parsed !== null && typeof parsed === 'object') ? parsed : value\n\t} catch {\n\t\treturn value\n\t}\n}\n\n/**\n * Entry-count summariser (`count` registry key) — renders a collection-valued\n * cell as \"5 frames\" instead of the truncated JSON blob `formatValue` would\n * produce. Counts array entries or object keys; a PRESENT scalar counts as 1.\n * The column's `formatterOptions` supply pre-translated\n * `{ singular, plural, zero }` phrases with `{n}` substituted, following the\n * `conditionalPhrase` convention of keeping i18n in the app layer. Without\n * phrases it renders the bare count.\n *\n * Null-safe per the built-in-formatter contract: `zero` (or `''`) for\n * null/empty — never throws.\n *\n * @param {unknown} value An array, object, JSON-encoded collection, or scalar.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{singular?: string, plural?: string, zero?: string}} [options] The column's `formatterOptions`.\n * @return {string} The selected phrase with `{n}` substituted, or the bare count.\n */\nexport function formatCount(value, _row, _property, options) {\n\tconst opts = options || {}\n\tconst zero = typeof opts.zero === 'string' ? opts.zero : ''\n\t// Every EMPTY value takes the zero phrase, not just null/''. `0` and `false`\n\t// used to fall through to the scalar branch and count as one entry, so a\n\t// column over a `0` rendered \"1 retry\" — the singular of a thing that isn't\n\t// there.\n\tif (value === null || value === undefined || value === '' || value === 0 || value === false) {\n\t\treturn zero\n\t}\n\tconst collection = parseCollection(value)\n\tlet n\n\tif (Array.isArray(collection)) {\n\t\tn = collection.length\n\t} else if (typeof collection === 'object') {\n\t\tn = Object.keys(collection).length\n\t} else {\n\t\tn = 1\n\t}\n\tif (n === 0) {\n\t\treturn zero\n\t}\n\tconst phrase = n === 1 ? (opts.singular ?? opts.plural) : (opts.plural ?? opts.singular)\n\tif (typeof phrase !== 'string' || phrase === '') {\n\t\treturn String(n)\n\t}\n\treturn phrase.replace(/\\{n\\}/g, String(n))\n}\n\n/**\n * The seven stored connection status values and their English source labels.\n *\n * The label is translated on each call, not here. A module-level `t()` runs\n * before the catalogue is registered and would freeze every label in English.\n */\nconst CONNECTION_STATUS_LABELS = {\n\tconfigured: 'Configured',\n\tlimited: 'Limited',\n\tunconfigured: 'Not configured',\n\tsimulated: 'Simulated',\n\tdisabled: 'Switched off',\n\tunavailable: 'Not available',\n\terror: 'Error',\n}\n\n/**\n * The short labels `formatterOptions.short` draws (board DqInstellingen\n * \"Aan / Uit / Fout\"): a connection that works in some form is on, one that\n * does not run is off, and a failing one is an error.\n */\nconst CONNECTION_STATUS_SHORT_LABELS = {\n\tconfigured: 'On',\n\tlimited: 'On',\n\tsimulated: 'On',\n\tunconfigured: 'Off',\n\tdisabled: 'Off',\n\tunavailable: 'Off',\n\terror: 'Error',\n}\n\n/**\n * Connection status formatter (`connectionStatus` registry key) for the\n * fleet connection registry. Renders one of the seven stored status values\n * as its translated label through the library's own slug (`nextcloud-vue`).\n * With `formatterOptions.short` it renders the board's three short labels\n * instead: On (configured, limited, simulated), Off (not configured,\n * switched off, not available) and Error.\n *\n * An unknown value passes through unchanged, so a status added later still\n * shows as its raw value instead of an empty cell. Null and undefined render\n * as `''`. Never throws.\n *\n * @param {unknown} value The stored status value.\n * @param {object} [row] The row (unused).\n * @param {object} [property] The column's property (unused).\n * @param {{short?: boolean}} [options] The column's `formatterOptions`.\n * @return {string} The translated label, or the value itself when it is not one of the seven.\n */\nexport function formatConnectionStatus(value, row, property, options) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tconst labels = options && options.short === true ? CONNECTION_STATUS_SHORT_LABELS : CONNECTION_STATUS_LABELS\n\tconst label = Object.hasOwn(labels, value) ? labels[value] : null\n\treturn label ? t('nextcloud-vue', label) : String(value)\n}\n\n/**\n * Connection settings label formatter (`connectionSettingsLabel` registry\n * key). Renders \"Open settings\" when the row's `settingsUrl` is a non-empty\n * string, and `''` otherwise. Paired with the built-in `link` cell widget, an\n * empty label and href fall through to plain empty text, so a connection with\n * no settings section offers nothing to click.\n *\n * @param {unknown} value The row's `settingsUrl`.\n * @return {string} The translated link text, or `''`.\n */\nexport function formatConnectionSettingsLabel(value) {\n\treturn typeof value === 'string' && value !== ''\n\t\t? t('nextcloud-vue', 'Open settings')\n\t\t: ''\n}\n\n/**\n * Show a value with all but its last characters replaced by `*`, as the facts\n * widget's `masked` format does: an IBAN or a BSN reads `*****4567`.\n *\n * DISPLAY ONLY, NOT ACCESS CONTROL. The full value is still in the row the\n * page received; a reader with the browser's developer tools can read it.\n * A value the reader must not have belongs hidden in OpenRegister (property\n * authorization), not masked here.\n *\n * The column's `formatterOptions.visible` sets how many characters stay\n * (default 4); at most five `*` are drawn, so the length does not leak.\n * Null-safe: `''` for null or empty.\n *\n * @param {unknown} value The value.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{visible?: number}} [options] The column's `formatterOptions`.\n * @return {string} The masked value.\n * @spec openspec/changes/masked-formatter-max-aggregate/specs/index-page/spec.md#requirement-a-column-can-mask-its-value-for-display\n */\nexport function formatMasked(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst text = String(value)\n\tconst visible = Number.isInteger(options && options.visible) && options.visible >= 0 ? options.visible : 4\n\tif (text.length <= visible) {\n\t\treturn text\n\t}\n\treturn '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')\n}\n\n/**\n * Built-in formatter registry merged under any consumer-registered\n * `formatters` in `CnAppRoot`'s `cnFormatters` provide.\n */\n/**\n * An ISO-8601 period of one unit in words (`isoDuration` registry key):\n * `P10Y` reads \"10 years\", `P6W` \"6 weeks\". A retention period or a term is\n * stored as such a period and was shown raw. Anything that is not a one-unit\n * period is returned as written, `''` for null or empty.\n *\n * @param {unknown} value The stored period.\n * @return {string} The period in words.\n */\nexport function formatIsoDuration(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst period = parseDuration(String(value))\n\tif (period === null) {\n\t\treturn String(value)\n\t}\n\tconst words = {\n\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', period.amount),\n\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', period.amount),\n\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', period.amount),\n\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', period.amount),\n\t\thours: () => n('nextcloud-vue', '%n hour', '%n hours', period.amount),\n\t\tminutes: () => n('nextcloud-vue', '%n minute', '%n minutes', period.amount),\n\t}\n\treturn words[period.unit] ? words[period.unit]() : String(value)\n}\n\nexport const BUILT_IN_FORMATTERS = {\n\tdate: formatDate,\n\tdatetime: formatDateTime,\n\t'relative-time': formatRelativeTime,\n\tdaysSince: formatDaysSince,\n\tdaysUntil: formatDaysUntil,\n\trelativeDate: formatRelativeDay,\n\tcurrency: formatCurrency,\n\tconditionalPhrase: formatConditionalPhrase,\n\tcount: formatCount,\n\tconnectionStatus: formatConnectionStatus,\n\tconnectionSettingsLabel: formatConnectionSettingsLabel,\n\tmasked: formatMasked,\n\tisoDuration: formatIsoDuration,\n}\n"],"names":["t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"builtInFormatters.js","sources":["../../../src/utils/builtInFormatters.js"],"sourcesContent":["import { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { formatRelativeDate } from './boardDate.js'\nimport { formatMoney } from './formatMetric.js'\nimport { parseDuration } from './isoDuration.js'\n\n/**\n * @file Built-in formatters merged into the `cnFormatters` registry\n * `CnAppRoot` provides to descendants.\n *\n * Resolution order (in `CnCellRenderer`):\n * 1. Consumer-registered `formatters[id]` (passed to CnAppRoot).\n * 2. Built-in `BUILT_IN_FORMATTERS[id]` from this file.\n * 3. Schema-type-aware rendering.\n * 4. Plain `formatValue()` fallback.\n *\n * Consumer formatters win (CnAppRoot spreads\n * `{ ...BUILT_IN_FORMATTERS, ...props.formatters }`).\n *\n * A formatter is invoked as `fn(value, row, property, options)` — `options`\n * is the column's declarative `formatterOptions` map (undefined when the\n * column declares none), so config-driven formatters (`currency`,\n * `conditionalPhrase`) work without one registry function per configuration.\n * The fourth argument is additive: pre-existing three-argument formatters\n * are unaffected.\n *\n * Every built-in MUST be safe against null / empty / non-parseable\n * inputs — the contract is \"return the original value (or empty\n * string) rather than throw\".\n */\n\n/**\n * Parse a value that's meant to be a date — accepts a `Date` instance,\n * an ISO/parseable string, or a numeric timestamp.\n *\n * @param {Date|string|number|null|undefined} value The candidate date value.\n * @return {Date|null} A `Date` instance, or `null` if the input is null/empty/unparseable.\n */\nfunction toDate(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn null\n\t}\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? null : value\n\t}\n\tconst d = new Date(value)\n\treturn Number.isNaN(d.getTime()) ? null : d\n}\n\n/**\n * Locale-formatted date (no time). Backed by `Intl.DateTimeFormat`\n * (`dateStyle: 'medium'`) using the user-agent locale.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Formatted date, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatDate(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\treturn new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(d)\n}\n\n/**\n * Locale-formatted date + time. Backed by `Intl.DateTimeFormat`\n * (`dateStyle: 'medium'`, `timeStyle: 'short'`).\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Formatted date + time, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatDateTime(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\treturn new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(d)\n}\n\n/**\n * Relative time (\"3 days ago\") via `Intl.RelativeTimeFormat`.\n * Picks the coarsest unit whose absolute delta exceeds one unit,\n * down to minutes (anything sub-minute clamps to \"now\"/seconds).\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} Relative phrasing, or `''` for null/empty, or `String(value)` for unparseable.\n */\nexport function formatRelativeTime(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst diffMs = d.getTime() - Date.now()\n\tconst rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })\n\tconst units = [\n\t\t['year', 31536000000],\n\t\t['month', 2592000000],\n\t\t['week', 604800000],\n\t\t['day', 86400000],\n\t\t['hour', 3600000],\n\t\t['minute', 60000],\n\t]\n\tfor (const [unit, ms] of units) {\n\t\tif (Math.abs(diffMs) >= ms || unit === 'minute') {\n\t\t\treturn rtf.format(Math.round(diffMs / ms), unit)\n\t\t}\n\t}\n\treturn rtf.format(0, 'second')\n}\n\n/**\n * Whole-day difference between a date value and today, comparing\n * date-only (both clamped to local midnight) so a due date later today\n * still counts as \"today\", not \"in 0.4 days\".\n *\n * @param {Date} d The parsed date.\n * @return {number} Signed day count: positive = future, negative = past, 0 = today.\n */\nfunction dayDiffFromToday(d) {\n\tconst target = new Date(d)\n\ttarget.setHours(0, 0, 0, 0)\n\tconst today = new Date()\n\ttoday.setHours(0, 0, 0, 0)\n\treturn Math.round((target.getTime() - today.getTime()) / 86400000)\n}\n\n/**\n * Future-oriented deadline formatter (`daysUntil` registry key), i18n'd\n * through the library's own translation slug (`nextcloud-vue`):\n * a future date renders \"N days remaining\", today renders \"Due today\",\n * and a past date renders \"N days overdue\". Day counts are plural-aware\n * via `translatePlural`.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input — never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n */\nexport function formatDaysUntil(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst days = dayDiffFromToday(d)\n\tif (days === 0) {\n\t\treturn t('nextcloud-vue', 'Due today')\n\t}\n\tif (days > 0) {\n\t\treturn n('nextcloud-vue', '{count} day remaining', '{count} days remaining', days, { count: days })\n\t}\n\tconst overdue = Math.abs(days)\n\treturn n('nextcloud-vue', '{count} day overdue', '{count} days overdue', overdue, { count: overdue })\n}\n\n/**\n * Elapsed-time formatter (`daysSince` registry key), i18n'd through the\n * library's own translation slug (`nextcloud-vue`) in the Nextcloud user\n * language. Whole calendar days, read as a running phrase so it sits after a\n * title or in a meta line: today renders \"today\", one day back renders\n * \"yesterday\", further back renders \"N days ago\". A (nonsensical but\n * harmless) future date renders \"tomorrow\" or \"in N days\". Day counts are\n * plural-aware via `translatePlural`.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input, never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n */\nexport function formatDaysSince(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst d = toDate(value)\n\tif (!d) {\n\t\treturn String(value)\n\t}\n\tconst days = dayDiffFromToday(d)\n\tif (days === 0) {\n\t\treturn t('nextcloud-vue', 'today')\n\t}\n\tif (days === -1) {\n\t\treturn t('nextcloud-vue', 'yesterday')\n\t}\n\tif (days === 1) {\n\t\treturn t('nextcloud-vue', 'tomorrow')\n\t}\n\tif (days < 0) {\n\t\tconst ago = Math.abs(days)\n\t\treturn n('nextcloud-vue', '{count} day ago', '{count} days ago', ago, { count: ago })\n\t}\n\treturn n('nextcloud-vue', 'in {count} day', 'in {count} days', days, { count: days })\n}\n\n/**\n * Relative-date formatter (`relativeDate` registry key): \"today\", \"yesterday\",\n * then the board's short date (\"5 okt\"). The column's `formatterOptions` may set\n * `time: true` to add the time of day after the day (\"vandaag, 09.42\").\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for unparseable input, never throws.\n *\n * @param {unknown} value A `Date`, parseable date string, or timestamp.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{time?: boolean}} [options] The column's `formatterOptions`.\n * @return {string} The relative-day phrasing (or ''/original on bad input).\n * @spec openspec/changes/screens-relative-dates/specs/relative-dates/spec.md#requirement-a-relative-date-reads-today-yesterday-then-a-short-date\n */\nexport function formatRelativeDay(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst text = formatRelativeDate(value, { withTime: options?.time === true })\n\treturn text === '' ? String(value) : text\n}\n\n/**\n * Currency formatter (`currency` registry key) via `Intl.NumberFormat`.\n * Defaults to EUR; the column's `formatterOptions` may set `currency`\n * (ISO-4217 code, guarded by `safeCurrencyCode` so an invalid code can\n * never throw a `RangeError`) and `decimals` (default 2).\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for non-numeric input — never throws.\n *\n * @param {unknown} value A numeric value (or numeric string).\n * @param {object} [row] The full row, for `currencyField`.\n * @param {object} [_property] The schema property (unused).\n * @param {{currency?: string, currencyField?: string, decimals?: number}} [options] The column's `formatterOptions`.\n * @return {string} The locale currency string (or ''/original on bad input).\n */\nexport function formatCurrency(value, row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst num = Number(value)\n\tif (!Number.isFinite(num)) {\n\t\treturn String(value)\n\t}\n\treturn formatMoney(num, options || {}, row)\n}\n\n/**\n * Sign/zero-based phrase selector (`conditionalPhrase` registry key) —\n * generalizes `daysUntil` to ANY numeric field: the column's\n * `formatterOptions` supply pre-translated `{ negative, zero, positive }`\n * phrases and the matching one renders with `{n}` replaced by the ABSOLUTE\n * value (so \"{n} days overdue\" reads naturally for -3). Phrases are\n * consumer-provided strings — apps translate them before declaring the\n * column, keeping i18n in the app layer.\n *\n * Null-safe per the built-in-formatter contract: `''` for null/empty,\n * `String(value)` for non-numeric input or when the selected phrase is\n * missing — never throws.\n *\n * @param {unknown} value A numeric value (or numeric string).\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{negative?: string, zero?: string, positive?: string}} [options] The column's `formatterOptions`.\n * @return {string} The selected phrase with `{n}` substituted (or ''/original on bad input).\n */\nexport function formatConditionalPhrase(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst num = Number(value)\n\tif (!Number.isFinite(num)) {\n\t\treturn String(value)\n\t}\n\tconst opts = options || {}\n\tconst phrase = num < 0 ? opts.negative : (num > 0 ? opts.positive : opts.zero)\n\tif (typeof phrase !== 'string' || phrase === '') {\n\t\treturn String(value)\n\t}\n\treturn phrase.replace(/\\{n\\}/g, String(Math.abs(num)))\n}\n\n/**\n * Read a value that may be a collection SERIALISED as JSON back into the\n * collection itself. OpenRegister hands a stringified array back for some\n * property types (and an app's own import path may persist one), which\n * `formatCount` would otherwise report as a single entry.\n *\n * Only `[`/`{`-leading strings are tried, so an ordinary text value never pays\n * for a parse attempt, and a string that merely looks like JSON but isn't falls\n * back to being one entry.\n *\n * @param {unknown} value The raw cell value.\n * @return {unknown} The parsed collection, or `value` unchanged.\n */\nfunction parseCollection(value) {\n\tif (typeof value !== 'string') {\n\t\treturn value\n\t}\n\tconst trimmed = value.trim()\n\tif (trimmed[0] !== '[' && trimmed[0] !== '{') {\n\t\treturn value\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(trimmed)\n\t\treturn (parsed !== null && typeof parsed === 'object') ? parsed : value\n\t} catch {\n\t\treturn value\n\t}\n}\n\n/**\n * Entry-count summariser (`count` registry key) — renders a collection-valued\n * cell as \"5 frames\" instead of the truncated JSON blob `formatValue` would\n * produce. Counts array entries or object keys; a PRESENT scalar counts as 1.\n * The column's `formatterOptions` supply pre-translated\n * `{ singular, plural, zero }` phrases with `{n}` substituted, following the\n * `conditionalPhrase` convention of keeping i18n in the app layer. Without\n * phrases it renders the bare count.\n *\n * Null-safe per the built-in-formatter contract: `zero` (or `''`) for\n * null/empty — never throws.\n *\n * @param {unknown} value An array, object, JSON-encoded collection, or scalar.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{singular?: string, plural?: string, zero?: string}} [options] The column's `formatterOptions`.\n * @return {string} The selected phrase with `{n}` substituted, or the bare count.\n */\nexport function formatCount(value, _row, _property, options) {\n\tconst opts = options || {}\n\tconst zero = typeof opts.zero === 'string' ? opts.zero : ''\n\t// Every EMPTY value takes the zero phrase, not just null/''. `0` and `false`\n\t// used to fall through to the scalar branch and count as one entry, so a\n\t// column over a `0` rendered \"1 retry\" — the singular of a thing that isn't\n\t// there.\n\tif (value === null || value === undefined || value === '' || value === 0 || value === false) {\n\t\treturn zero\n\t}\n\tconst collection = parseCollection(value)\n\tlet n\n\tif (Array.isArray(collection)) {\n\t\tn = collection.length\n\t} else if (typeof collection === 'object') {\n\t\tn = Object.keys(collection).length\n\t} else {\n\t\tn = 1\n\t}\n\tif (n === 0) {\n\t\treturn zero\n\t}\n\tconst phrase = n === 1 ? (opts.singular ?? opts.plural) : (opts.plural ?? opts.singular)\n\tif (typeof phrase !== 'string' || phrase === '') {\n\t\treturn String(n)\n\t}\n\treturn phrase.replace(/\\{n\\}/g, String(n))\n}\n\n/**\n * The seven stored connection status values and their English source labels.\n *\n * The label is translated on each call, not here. A module-level `t()` runs\n * before the catalogue is registered and would freeze every label in English.\n */\nconst CONNECTION_STATUS_LABELS = {\n\tconfigured: 'Configured',\n\tlimited: 'Limited',\n\tunconfigured: 'Not configured',\n\tsimulated: 'Simulated',\n\tdisabled: 'Switched off',\n\tunavailable: 'Not available',\n\terror: 'Error',\n}\n\n/**\n * The short labels `formatterOptions.short` draws (board DqInstellingen\n * \"Aan / Uit / Fout\"): a connection that works in some form is on, one that\n * does not run is off, and a failing one is an error.\n */\nconst CONNECTION_STATUS_SHORT_LABELS = {\n\tconfigured: 'On',\n\tlimited: 'On',\n\tsimulated: 'On',\n\tunconfigured: 'Off',\n\tdisabled: 'Off',\n\tunavailable: 'Off',\n\terror: 'Error',\n}\n\n/**\n * Connection status formatter (`connectionStatus` registry key) for the\n * fleet connection registry. Renders one of the seven stored status values\n * as its translated label through the library's own slug (`nextcloud-vue`).\n * With `formatterOptions.short` it renders the board's three short labels\n * instead: On (configured, limited, simulated), Off (not configured,\n * switched off, not available) and Error.\n *\n * An unknown value passes through unchanged, so a status added later still\n * shows as its raw value instead of an empty cell. Null and undefined render\n * as `''`. Never throws.\n *\n * @param {unknown} value The stored status value.\n * @param {object} [row] The row (unused).\n * @param {object} [property] The column's property (unused).\n * @param {{short?: boolean}} [options] The column's `formatterOptions`.\n * @return {string} The translated label, or the value itself when it is not one of the seven.\n */\nexport function formatConnectionStatus(value, row, property, options) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tconst labels = options && options.short === true ? CONNECTION_STATUS_SHORT_LABELS : CONNECTION_STATUS_LABELS\n\tconst label = Object.hasOwn(labels, value) ? labels[value] : null\n\treturn label ? t('nextcloud-vue', label) : String(value)\n}\n\n/**\n * Connection settings label formatter (`connectionSettingsLabel` registry\n * key). Renders \"Open settings\" when the row's `settingsUrl` is a non-empty\n * string, and `''` otherwise. Paired with the built-in `link` cell widget, an\n * empty label and href fall through to plain empty text, so a connection with\n * no settings section offers nothing to click.\n *\n * @param {unknown} value The row's `settingsUrl`.\n * @return {string} The translated link text, or `''`.\n */\nexport function formatConnectionSettingsLabel(value) {\n\treturn typeof value === 'string' && value !== ''\n\t\t? t('nextcloud-vue', 'Open settings')\n\t\t: ''\n}\n\n/**\n * Show a value with all but its last characters replaced by `*`, as the facts\n * widget's `masked` format does: an IBAN or a BSN reads `*****4567`.\n *\n * DISPLAY ONLY, NOT ACCESS CONTROL. The full value is still in the row the\n * page received; a reader with the browser's developer tools can read it.\n * A value the reader must not have belongs hidden in OpenRegister (property\n * authorization), not masked here.\n *\n * The column's `formatterOptions.visible` sets how many characters stay\n * (default 4); at most five `*` are drawn, so the length does not leak.\n * Null-safe: `''` for null or empty.\n *\n * @param {unknown} value The value.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{visible?: number}} [options] The column's `formatterOptions`.\n * @return {string} The masked value.\n * @spec openspec/changes/masked-formatter-max-aggregate/specs/index-page/spec.md#requirement-a-column-can-mask-its-value-for-display\n */\nexport function formatMasked(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst text = String(value)\n\tconst visible = Number.isInteger(options && options.visible) && options.visible >= 0 ? options.visible : 4\n\tif (text.length <= visible) {\n\t\treturn text\n\t}\n\treturn '*'.repeat(Math.min(text.length - visible, 5)) + (visible > 0 ? text.slice(-visible) : '')\n}\n\n/**\n * A stored value through a display map (`valueMap` registry key): the label\n * `options.map` gives the value, else the value as stored. \"Besloten\n * Vennootschap\" reads \"BV\" on DqOrganisaties.\n *\n * @param {unknown} value The stored value.\n * @param {object} [_row] The full row (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{map?: Object<string, string>}} [options] The column's `formatterOptions`.\n * @return {string} The label, or the value.\n * @spec openspec/changes/value-map-formatter/specs/index-page/spec.md#requirement-a-column-can-show-stored-values-through-a-display-map\n */\nexport function formatValueMap(value, _row, _property, options) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst map = options && options.map && typeof options.map === 'object' ? options.map : {}\n\tconst label = Object.hasOwn(map, String(value)) ? map[String(value)] : undefined\n\treturn typeof label === 'string' && label !== '' ? label : String(value)\n}\n\n/**\n * Built-in formatter registry merged under any consumer-registered\n * `formatters` in `CnAppRoot`'s `cnFormatters` provide.\n */\n/**\n * An ISO-8601 period of one unit in words (`isoDuration` registry key):\n * `P10Y` reads \"10 years\", `P6W` \"6 weeks\". A retention period or a term is\n * stored as such a period and was shown raw. Anything that is not a one-unit\n * period is returned as written, `''` for null or empty.\n *\n * @param {unknown} value The stored period.\n * @return {string} The period in words.\n */\nexport function formatIsoDuration(value) {\n\tif (value === null || value === undefined || value === '') {\n\t\treturn ''\n\t}\n\tconst period = parseDuration(String(value))\n\tif (period === null) {\n\t\treturn String(value)\n\t}\n\tconst words = {\n\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', period.amount),\n\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', period.amount),\n\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', period.amount),\n\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', period.amount),\n\t\thours: () => n('nextcloud-vue', '%n hour', '%n hours', period.amount),\n\t\tminutes: () => n('nextcloud-vue', '%n minute', '%n minutes', period.amount),\n\t}\n\treturn words[period.unit] ? words[period.unit]() : String(value)\n}\n\nexport const BUILT_IN_FORMATTERS = {\n\tdate: formatDate,\n\tdatetime: formatDateTime,\n\t'relative-time': formatRelativeTime,\n\tdaysSince: formatDaysSince,\n\tdaysUntil: formatDaysUntil,\n\trelativeDate: formatRelativeDay,\n\tcurrency: formatCurrency,\n\tconditionalPhrase: formatConditionalPhrase,\n\tcount: formatCount,\n\tconnectionStatus: formatConnectionStatus,\n\tconnectionSettingsLabel: formatConnectionSettingsLabel,\n\tmasked: formatMasked,\n\tisoDuration: formatIsoDuration,\n\tvalueMap: formatValueMap,\n}\n"],"names":["t","n"],"mappings":"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"}
|
|
@@ -65,7 +65,7 @@ function formatLongDate(value, now = new Date(), withYear = false) {
|
|
|
65
65
|
* Format one token value.
|
|
66
66
|
*
|
|
67
67
|
* @param {unknown} value The raw value.
|
|
68
|
-
* @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower` or '' (as is).
|
|
68
|
+
* @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower`, `mailbox` or '' (as is).
|
|
69
69
|
* @param {Date} now The reference moment.
|
|
70
70
|
* @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).
|
|
71
71
|
* @param {object|null} [object] The row, for a currency read from a field.
|
|
@@ -126,6 +126,12 @@ function formatValue(value, format, now, arg = '', object = null, config = null,
|
|
|
126
126
|
if (format === 'ago') {
|
|
127
127
|
return formatAgo(value, now)
|
|
128
128
|
}
|
|
129
|
+
if (format === 'mailbox') {
|
|
130
|
+
// An address up to and with its @ ("gemeente@" from gemeente@zuiddrecht.nl).
|
|
131
|
+
const text = String(value);
|
|
132
|
+
const at = text.indexOf('@');
|
|
133
|
+
return at > 0 ? text.slice(0, at + 1) : text
|
|
134
|
+
}
|
|
129
135
|
if (format === 'lower') {
|
|
130
136
|
return String(value).toLocaleLowerCase()
|
|
131
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fillTemplate.js","sources":["../../../src/utils/fillTemplate.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { translate as t } from '@nextcloud/l10n'\nimport { boardDateLocale, formatAgo, formatBoardDate, formatBoardWeekday, formatRelativeDate, formatTimeOfDay, isoWeek } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { formatMoney } from './formatMetric.js'\nimport { readPath } from './readPath.js'\nimport { userDisplayName } from './userDisplayName.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([A-Za-z]+)(?::([^{}\\s]+))?)?\\}/g\n\n/**\n * A moment in the board's short form with the time: \"4 okt, 10:52\", or\n * \"14 feb 2024, 09:00\" outside the current year.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} [now] The reference moment.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function formatBoardDateTime(value, now = new Date()) {\n\tconst day = formatBoardDate(value, now)\n\tconst date = parseDateValue(value)\n\tif (day === '' || date === null) {\n\t\treturn ''\n\t}\n\tlet time\n\ttry {\n\t\ttime = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)\n\t} catch {\n\t\ttime = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)\n\t}\n\treturn `${day}, ${time}`\n}\n\n/**\n * A date with the month written out: \"7 oktober\", or \"6 oktober 2026\"\n * outside the current year or when `withYear` asks for it.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} now The reference moment.\n * @param {boolean} [withYear] Always write the year.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-header-meta-tokens/specs/detail-page-board-look/spec.md#requirement-the-header-meta-line-formats-users-long-dates-and-enum-labels\n */\nexport function formatLongDate(value, now = new Date(), withYear = false) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst options = { day: 'numeric', month: 'long' }\n\tif (withYear || date.getFullYear() !== now.getFullYear()) {\n\t\toptions.year = 'numeric'\n\t}\n\ttry {\n\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(date)\n\t} catch {\n\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t}\n}\n\n/**\n * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower` or '' (as is).\n * @param {Date} now The reference moment.\n * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).\n * @param {object|null} [object] The row, for a currency read from a field.\n * @param {object|null} [config] The app config map, for the reporting currency.\n * @param {object|null} [formatters] The app's named formatters (`cnFormatters`), for any other format name.\n * @return {string} The text, '' when nothing can be shown.\n * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters\n */\nfunction formatValue(value, format, now, arg = '', object = null, config = null, formatters = null) {\n\t// `{list|count}`: how many entries a list holds, nothing for an empty one.\n\tif (format === 'count') {\n\t\tconst count = Array.isArray(value) ? value.length : Number(value)\n\t\treturn Number.isFinite(count) && count > 0 ? String(count) : ''\n\t}\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn ''\n\t}\n\tif (format !== '' && formatters && typeof formatters[format] === 'function') {\n\t\tconst shown = formatters[format](value, object)\n\t\treturn shown === null || shown === undefined ? '' : String(shown)\n\t}\n\tif (format === 'date') {\n\t\treturn formatBoardDate(value, now)\n\t}\n\tif (format === 'datetime') {\n\t\treturn formatBoardDateTime(value, now)\n\t}\n\tif (format === 'weekday') {\n\t\treturn formatBoardWeekday(value, now, { today: arg === 'today' })\n\t}\n\tif (format === 'week') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : t('nextcloud-vue', 'Week {week}', { week: isoWeek(date) })\n\t}\n\tif (format === 'longdate') {\n\t\treturn formatLongDate(value, now, arg === 'year')\n\t}\n\tif (format === 'user') {\n\t\treturn userDisplayName(value)\n\t}\n\tif (format === 'time') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : formatTimeOfDay(date)\n\t}\n\tif (format === 'currency') {\n\t\t// `{amount|currency:EUR}` names the code, `{amount|currency:price.code}`\n\t\t// reads it from the row, `{amount|currency}` is the app's reporting\n\t\t// currency (EUR without one).\n\t\tconst isCode = /^[A-Za-z]{3}$/.test(arg)\n\t\treturn formatMoney(value, { currency: isCode ? arg : undefined, currencyField: !isCode && arg ? arg : undefined }, object, config)\n\t}\n\tif (format === 'relative') {\n\t\treturn formatRelativeDate(value, { now })\n\t}\n\tif (format === 'relativetime') {\n\t\treturn formatRelativeDate(value, { now, withTime: true })\n\t}\n\tif (format === 'ago') {\n\t\treturn formatAgo(value, now)\n\t}\n\tif (format === 'lower') {\n\t\treturn String(value).toLocaleLowerCase()\n\t}\n\tif (format === 'days') {\n\t\t// Whole days from the date to now (0 for today; negative for a future date).\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))\n\t}\n\treturn String(value)\n}\n\n/**\n * Fill a row template such as `\"{identifier} · {caseTypeName}\"` or\n * `\"Termijn {deadline|date}\"` from an object.\n *\n * Every `{path}` token is a dot path into the object; `{path|date}` and\n * `{path|datetime}` format it as the boards write dates, `{path|weekday}` adds\n * the short weekday (\"wo 7 okt\"; `{path|weekday:today}` reads \"Vandaag, wo 7 okt\"\n * for today), `{path|longdate}` writes the month out (\"7 oktober\";\n * `{path|longdate:year}` always adds the year), `{path|time}` is the time\n * of day (\"08:45\"), `{path|user}` is a user id's display name (looked up\n * once, '' until it arrives), `{path|currency}`,\n * `{path|currency:EUR}` and `{path|currency:field}` write an amount in a\n * currency (a code, or the row field that holds it; without either, or when\n * the row field is empty, the app's reporting currency), `{path|relative}` and\n * `{path|relativetime}` say \"today\", \"yesterday\" and then the short date\n * (\"vandaag, 09.42\"), `{path|ago}` says how long ago it was (\"10 min\n * geleden\", \"vanochtend\", \"vrijdag\"), and `{path|days}` is the\n * whole days since that date (\"has waited {createdAt|days} days\"),\n * `{path|count}` is how many entries a list holds (nothing for an empty one),\n * `{path|lower}` writes the value in lower case (\"Step 2: {statusLabel|lower}\"\n * reads \"Step 2: in progress\" mid-sentence), and any other name is one of\n * the app's formatters, passed as `context.formatters` (`{id|caseAddress}`).\n * A token without a\n * value empties the WHOLE template, because \"Termijn \" is worse than nothing.\n * A template without tokens is returned as written (a constant label).\n *\n * @param {string} template The template, already translated.\n * @param {object|null} object The object to read.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object, formatters?: object}|null} [context] The app config map, for the reporting currency of a money token, and the app's named formatters.\n * @return {string} The filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function fillTemplate(template, object, now = new Date(), context = null) {\n\tif (typeof template !== 'string' || template.trim() === '') {\n\t\treturn ''\n\t}\n\tlet missing = false\n\tconst text = template.replace(TOKEN, (_match, path, format, arg) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null, context && context.formatters ? context.formatters : null)\n\t\tif (shown === '') {\n\t\t\tmissing = true\n\t\t}\n\t\treturn shown\n\t})\n\treturn missing ? '' : text.trim()\n}\n\n/**\n * The first of several templates that fills: `[\"Klaar {closedAt|date}\",\n * \"Termijn {deadline|date}\"]` reads \"Klaar ...\" once the case is closed and\n * \"Termijn ...\" while it is open. A single string is one candidate.\n *\n * @param {string|string[]} templates The candidates, in order.\n * @param {object|null} object The object to read.\n * @param {(key: string) => string} [translate] Applied to each template before filling.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object}|null} [context] Passed on to `fillTemplate`.\n * @return {string} The first filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function firstFilled(templates, object, translate = (key) => key, now = new Date(), context = null) {\n\tconst list = Array.isArray(templates) ? templates : [templates]\n\tfor (const template of list) {\n\t\tif (typeof template !== 'string') {\n\t\t\tcontinue\n\t\t}\n\t\tconst text = fillTemplate(translate(template), object, now, context)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n\n/**\n * Fill a template such as `\"{identifier} · {requester}\"` field by field\n * (a table column's `secondary` line, a detail page's meta line).\n *\n * A field without a value takes its separator with it: the text between two\n * fields belongs to the field after it, so an empty requester leaves\n * \"2026-0002\", not \"2026-0002 ·\", and an empty first field does not leave a\n * leading separator either. Text before the first field and after the last\n * one is kept as written. A template whose fields all have values fills\n * exactly as before.\n *\n * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it\n * @param {string} template The template, with `{field}` placeholders.\n * @param {(key: string) => string} valueOf The text of a field ('' when empty).\n * @return {string} The filled template.\n */\nexport function joinTemplateParts(template, valueOf) {\n\t// split() with a capture group alternates literal, field, literal, …\n\tconst parts = template.split(/\\{([^}]+)\\}/)\n\tconst lead = parts[0]\n\tconst tail = parts[parts.length - 1]\n\tlet body = ''\n\tfor (let i = 1; i < parts.length; i += 2) {\n\t\tconst value = valueOf(parts[i].trim())\n\t\tif (value === '') {\n\t\t\tcontinue\n\t\t}\n\t\t// The literal before this field: the separator, unless nothing has\n\t\t// been written yet (then there is nothing to separate from).\n\t\tconst separator = i === 1 ? '' : parts[i - 1]\n\t\tbody += (body === '' ? '' : separator) + value\n\t}\n\treturn lead + body + tail\n}\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fillTemplate.js","sources":["../../../src/utils/fillTemplate.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\nimport { translate as t } from '@nextcloud/l10n'\nimport { boardDateLocale, formatAgo, formatBoardDate, formatBoardWeekday, formatRelativeDate, formatTimeOfDay, isoWeek } from './boardDate.js'\nimport { parseDateValue } from './dateVariant.js'\nimport { formatMoney } from './formatMetric.js'\nimport { readPath } from './readPath.js'\nimport { userDisplayName } from './userDisplayName.js'\n\nconst TOKEN = /\\{([^{}\\s|]+)(?:\\|([A-Za-z]+)(?::([^{}\\s]+))?)?\\}/g\n\n/**\n * A moment in the board's short form with the time: \"4 okt, 10:52\", or\n * \"14 feb 2024, 09:00\" outside the current year.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} [now] The reference moment.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function formatBoardDateTime(value, now = new Date()) {\n\tconst day = formatBoardDate(value, now)\n\tconst date = parseDateValue(value)\n\tif (day === '' || date === null) {\n\t\treturn ''\n\t}\n\tlet time\n\ttry {\n\t\ttime = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)\n\t} catch {\n\t\ttime = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)\n\t}\n\treturn `${day}, ${time}`\n}\n\n/**\n * A date with the month written out: \"7 oktober\", or \"6 oktober 2026\"\n * outside the current year or when `withYear` asks for it.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} now The reference moment.\n * @param {boolean} [withYear] Always write the year.\n * @return {string} The text, or '' when the value is not a date.\n * @spec openspec/changes/screens-header-meta-tokens/specs/detail-page-board-look/spec.md#requirement-the-header-meta-line-formats-users-long-dates-and-enum-labels\n */\nexport function formatLongDate(value, now = new Date(), withYear = false) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst options = { day: 'numeric', month: 'long' }\n\tif (withYear || date.getFullYear() !== now.getFullYear()) {\n\t\toptions.year = 'numeric'\n\t}\n\ttry {\n\t\treturn new Intl.DateTimeFormat(boardDateLocale(), options).format(date)\n\t} catch {\n\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t}\n}\n\n/**\n * Format one token value.\n *\n * @param {unknown} value The raw value.\n * @param {string} format `date`, `datetime`, `weekday`, `longdate`, `time`, `user`, `currency`, `relative`, `relativetime`, `ago`, `week`, `days`, `lower`, `mailbox` or '' (as is).\n * @param {Date} now The reference moment.\n * @param {string} [arg] The format's argument (`currency:EUR`, or a row path holding the code).\n * @param {object|null} [object] The row, for a currency read from a field.\n * @param {object|null} [config] The app config map, for the reporting currency.\n * @param {object|null} [formatters] The app's named formatters (`cnFormatters`), for any other format name.\n * @return {string} The text, '' when nothing can be shown.\n * @spec openspec/changes/map-list-formatters/specs/map-list-formatters/spec.md#requirement-a-map-list-reads-display-names-through-the-apps-formatters\n */\nfunction formatValue(value, format, now, arg = '', object = null, config = null, formatters = null) {\n\t// `{list|count}`: how many entries a list holds, nothing for an empty one.\n\tif (format === 'count') {\n\t\tconst count = Array.isArray(value) ? value.length : Number(value)\n\t\treturn Number.isFinite(count) && count > 0 ? String(count) : ''\n\t}\n\tif (value === null || value === undefined || value === '' || typeof value === 'object') {\n\t\treturn ''\n\t}\n\tif (format !== '' && formatters && typeof formatters[format] === 'function') {\n\t\tconst shown = formatters[format](value, object)\n\t\treturn shown === null || shown === undefined ? '' : String(shown)\n\t}\n\tif (format === 'date') {\n\t\treturn formatBoardDate(value, now)\n\t}\n\tif (format === 'datetime') {\n\t\treturn formatBoardDateTime(value, now)\n\t}\n\tif (format === 'weekday') {\n\t\treturn formatBoardWeekday(value, now, { today: arg === 'today' })\n\t}\n\tif (format === 'week') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : t('nextcloud-vue', 'Week {week}', { week: isoWeek(date) })\n\t}\n\tif (format === 'longdate') {\n\t\treturn formatLongDate(value, now, arg === 'year')\n\t}\n\tif (format === 'user') {\n\t\treturn userDisplayName(value)\n\t}\n\tif (format === 'time') {\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : formatTimeOfDay(date)\n\t}\n\tif (format === 'currency') {\n\t\t// `{amount|currency:EUR}` names the code, `{amount|currency:price.code}`\n\t\t// reads it from the row, `{amount|currency}` is the app's reporting\n\t\t// currency (EUR without one).\n\t\tconst isCode = /^[A-Za-z]{3}$/.test(arg)\n\t\treturn formatMoney(value, { currency: isCode ? arg : undefined, currencyField: !isCode && arg ? arg : undefined }, object, config)\n\t}\n\tif (format === 'relative') {\n\t\treturn formatRelativeDate(value, { now })\n\t}\n\tif (format === 'relativetime') {\n\t\treturn formatRelativeDate(value, { now, withTime: true })\n\t}\n\tif (format === 'ago') {\n\t\treturn formatAgo(value, now)\n\t}\n\tif (format === 'mailbox') {\n\t\t// An address up to and with its @ (\"gemeente@\" from gemeente@zuiddrecht.nl).\n\t\tconst text = String(value)\n\t\tconst at = text.indexOf('@')\n\t\treturn at > 0 ? text.slice(0, at + 1) : text\n\t}\n\tif (format === 'lower') {\n\t\treturn String(value).toLocaleLowerCase()\n\t}\n\tif (format === 'days') {\n\t\t// Whole days from the date to now (0 for today; negative for a future date).\n\t\tconst date = parseDateValue(value)\n\t\treturn date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))\n\t}\n\treturn String(value)\n}\n\n/**\n * Fill a row template such as `\"{identifier} · {caseTypeName}\"` or\n * `\"Termijn {deadline|date}\"` from an object.\n *\n * Every `{path}` token is a dot path into the object; `{path|date}` and\n * `{path|datetime}` format it as the boards write dates, `{path|weekday}` adds\n * the short weekday (\"wo 7 okt\"; `{path|weekday:today}` reads \"Vandaag, wo 7 okt\"\n * for today), `{path|longdate}` writes the month out (\"7 oktober\";\n * `{path|longdate:year}` always adds the year), `{path|time}` is the time\n * of day (\"08:45\"), `{path|user}` is a user id's display name (looked up\n * once, '' until it arrives), `{path|currency}`,\n * `{path|currency:EUR}` and `{path|currency:field}` write an amount in a\n * currency (a code, or the row field that holds it; without either, or when\n * the row field is empty, the app's reporting currency), `{path|relative}` and\n * `{path|relativetime}` say \"today\", \"yesterday\" and then the short date\n * (\"vandaag, 09.42\"), `{path|ago}` says how long ago it was (\"10 min\n * geleden\", \"vanochtend\", \"vrijdag\"), and `{path|days}` is the\n * whole days since that date (\"has waited {createdAt|days} days\"),\n * `{path|count}` is how many entries a list holds (nothing for an empty one),\n * `{path|lower}` writes the value in lower case (\"Step 2: {statusLabel|lower}\"\n * reads \"Step 2: in progress\" mid-sentence), and any other name is one of\n * the app's formatters, passed as `context.formatters` (`{id|caseAddress}`).\n * A token without a\n * value empties the WHOLE template, because \"Termijn \" is worse than nothing.\n * A template without tokens is returned as written (a constant label).\n *\n * @param {string} template The template, already translated.\n * @param {object|null} object The object to read.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object, formatters?: object}|null} [context] The app config map, for the reporting currency of a money token, and the app's named formatters.\n * @return {string} The filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function fillTemplate(template, object, now = new Date(), context = null) {\n\tif (typeof template !== 'string' || template.trim() === '') {\n\t\treturn ''\n\t}\n\tlet missing = false\n\tconst text = template.replace(TOKEN, (_match, path, format, arg) => {\n\t\tconst value = object && typeof object === 'object' ? readPath(object, path) : undefined\n\t\tconst shown = formatValue(value, format || '', now, arg || '', object, context && context.config ? context.config : null, context && context.formatters ? context.formatters : null)\n\t\tif (shown === '') {\n\t\t\tmissing = true\n\t\t}\n\t\treturn shown\n\t})\n\treturn missing ? '' : text.trim()\n}\n\n/**\n * The first of several templates that fills: `[\"Klaar {closedAt|date}\",\n * \"Termijn {deadline|date}\"]` reads \"Klaar ...\" once the case is closed and\n * \"Termijn ...\" while it is open. A single string is one candidate.\n *\n * @param {string|string[]} templates The candidates, in order.\n * @param {object|null} object The object to read.\n * @param {(key: string) => string} [translate] Applied to each template before filling.\n * @param {Date} [now] The reference moment for dates.\n * @param {{config?: object}|null} [context] Passed on to `fillTemplate`.\n * @return {string} The first filled text, or ''.\n * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record\n */\nexport function firstFilled(templates, object, translate = (key) => key, now = new Date(), context = null) {\n\tconst list = Array.isArray(templates) ? templates : [templates]\n\tfor (const template of list) {\n\t\tif (typeof template !== 'string') {\n\t\t\tcontinue\n\t\t}\n\t\tconst text = fillTemplate(translate(template), object, now, context)\n\t\tif (text !== '') {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n\n/**\n * Fill a template such as `\"{identifier} · {requester}\"` field by field\n * (a table column's `secondary` line, a detail page's meta line).\n *\n * A field without a value takes its separator with it: the text between two\n * fields belongs to the field after it, so an empty requester leaves\n * \"2026-0002\", not \"2026-0002 ·\", and an empty first field does not leave a\n * leading separator either. Text before the first field and after the last\n * one is kept as written. A template whose fields all have values fills\n * exactly as before.\n *\n * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it\n * @param {string} template The template, with `{field}` placeholders.\n * @param {(key: string) => string} valueOf The text of a field ('' when empty).\n * @return {string} The filled template.\n */\nexport function joinTemplateParts(template, valueOf) {\n\t// split() with a capture group alternates literal, field, literal, …\n\tconst parts = template.split(/\\{([^}]+)\\}/)\n\tconst lead = parts[0]\n\tconst tail = parts[parts.length - 1]\n\tlet body = ''\n\tfor (let i = 1; i < parts.length; i += 2) {\n\t\tconst value = valueOf(parts[i].trim())\n\t\tif (value === '') {\n\t\t\tcontinue\n\t\t}\n\t\t// The literal before this field: the separator, unless nothing has\n\t\t// been written yet (then there is nothing to separate from).\n\t\tconst separator = i === 1 ? '' : parts[i - 1]\n\t\tbody += (body === '' ? '' : separator) + value\n\t}\n\treturn lead + body + tail\n}\n"],"names":["t"],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import axios__default from '@nextcloud/axios';
|
|
2
|
+
import { translatePlural } from '@nextcloud/l10n';
|
|
3
|
+
import { generateOcsUrl } from '@nextcloud/router';
|
|
4
|
+
import { fetchReferenceObjects } from './boardReferenceColumns.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The line under a team in a picker: how many members it has and what it is
|
|
8
|
+
* for ("6 leden · bezwaar, beroep en Woo-advies", board DqOverdragen).
|
|
9
|
+
*
|
|
10
|
+
* The count comes from Nextcloud's group details; that endpoint answers
|
|
11
|
+
* administrators and group admins only, so for anyone else the count is left
|
|
12
|
+
* out rather than guessed. Nextcloud keeps no description for a group, so the
|
|
13
|
+
* text comes from an app's own records when the app names them (`describe`:
|
|
14
|
+
* `{ register, schema, groupField?, textField? }`, by default the record's
|
|
15
|
+
* `ncGroupId` and `description`).
|
|
16
|
+
*
|
|
17
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
18
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
19
|
+
*
|
|
20
|
+
* @module utils/groupSublines
|
|
21
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const descriptionCache = new Map();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Member counts by group id for the groups a search finds. Fails soft to `{}`.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} query The search term.
|
|
31
|
+
* @return {Promise<Record<string, number>>} Counts by group id.
|
|
32
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
33
|
+
*/
|
|
34
|
+
async function memberCounts(query) {
|
|
35
|
+
try {
|
|
36
|
+
const response = await axios__default.get(generateOcsUrl('cloud/groups/details'), {
|
|
37
|
+
headers: { 'OCS-APIRequest': 'true', Accept: 'application/json' },
|
|
38
|
+
params: { search: query || '', limit: 50 },
|
|
39
|
+
});
|
|
40
|
+
const data = response && response.data && response.data.ocs ? response.data.ocs.data : (response && response.data);
|
|
41
|
+
const groups = data && Array.isArray(data.groups) ? data.groups : [];
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const group of groups) {
|
|
44
|
+
const count = Number(group && group.usercount);
|
|
45
|
+
if (group && group.id && Number.isFinite(count)) {
|
|
46
|
+
out[String(group.id)] = count;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
} catch {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Descriptions by group id from the app's records. Read once per source.
|
|
57
|
+
*
|
|
58
|
+
* @param {{register?: string, schema?: string, groupField?: string, textField?: string}|null} describe The source.
|
|
59
|
+
* @return {Promise<Record<string, string>>} Descriptions by group id.
|
|
60
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
61
|
+
*/
|
|
62
|
+
async function descriptions(describe) {
|
|
63
|
+
if (!describe || !describe.register || !describe.schema) {
|
|
64
|
+
return {}
|
|
65
|
+
}
|
|
66
|
+
const groupField = describe.groupField || 'ncGroupId';
|
|
67
|
+
const textField = describe.textField || 'description';
|
|
68
|
+
const key = [describe.register, describe.schema, groupField, textField].join('/');
|
|
69
|
+
if (!descriptionCache.has(key)) {
|
|
70
|
+
descriptionCache.set(key, fetchReferenceObjects({ register: describe.register, schema: describe.schema, limit: 500 })
|
|
71
|
+
.then((rows) => {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const gid = row && row[groupField];
|
|
75
|
+
const text = row && typeof row[textField] === 'string' ? row[textField].trim() : '';
|
|
76
|
+
if (gid && text && out[String(gid)] === undefined) {
|
|
77
|
+
out[String(gid)] = text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {
|
|
83
|
+
descriptionCache.delete(key);
|
|
84
|
+
return {}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
return descriptionCache.get(key)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The line under each group: its member count and its description, joined by
|
|
92
|
+
* " · ", either left out when unknown.
|
|
93
|
+
*
|
|
94
|
+
* @param {string[]} ids The group ids to describe.
|
|
95
|
+
* @param {string} query The search the groups came from.
|
|
96
|
+
* @param {object|null} [describe] Where the app keeps a group's description.
|
|
97
|
+
* @return {Promise<Record<string, string>>} The line by group id ('' when nothing is known).
|
|
98
|
+
* @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md
|
|
99
|
+
*/
|
|
100
|
+
async function groupSublines(ids, query, describe = null) {
|
|
101
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
102
|
+
return {}
|
|
103
|
+
}
|
|
104
|
+
const [counts, texts] = await Promise.all([memberCounts(query), descriptions(describe)]);
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const id of ids) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (counts[id] !== undefined) {
|
|
109
|
+
parts.push(translatePlural('nextcloud-vue', '{count} member', '{count} members', counts[id], { count: counts[id] }));
|
|
110
|
+
}
|
|
111
|
+
if (texts[id]) {
|
|
112
|
+
parts.push(texts[id]);
|
|
113
|
+
}
|
|
114
|
+
out[id] = parts.join(' · ');
|
|
115
|
+
}
|
|
116
|
+
return out
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { groupSublines };
|
|
120
|
+
//# sourceMappingURL=groupSublines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groupSublines.js","sources":["../../../src/utils/groupSublines.js"],"sourcesContent":["/**\n * The line under a team in a picker: how many members it has and what it is\n * for (\"6 leden · bezwaar, beroep en Woo-advies\", board DqOverdragen).\n *\n * The count comes from Nextcloud's group details; that endpoint answers\n * administrators and group admins only, so for anyone else the count is left\n * out rather than guessed. Nextcloud keeps no description for a group, so the\n * text comes from an app's own records when the app names them (`describe`:\n * `{ register, schema, groupField?, textField? }`, by default the record's\n * `ncGroupId` and `description`).\n *\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * @module utils/groupSublines\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\n\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n } from '@nextcloud/l10n'\nimport { generateOcsUrl } from '@nextcloud/router'\nimport { fetchReferenceObjects } from './boardReferenceColumns.js'\n\nconst descriptionCache = new Map()\n\n/**\n * Member counts by group id for the groups a search finds. Fails soft to `{}`.\n *\n * @param {string} query The search term.\n * @return {Promise<Record<string, number>>} Counts by group id.\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nasync function memberCounts(query) {\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('cloud/groups/details'), {\n\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\tparams: { search: query || '', limit: 50 },\n\t\t})\n\t\tconst data = response && response.data && response.data.ocs ? response.data.ocs.data : (response && response.data)\n\t\tconst groups = data && Array.isArray(data.groups) ? data.groups : []\n\t\tconst out = {}\n\t\tfor (const group of groups) {\n\t\t\tconst count = Number(group && group.usercount)\n\t\t\tif (group && group.id && Number.isFinite(count)) {\n\t\t\t\tout[String(group.id)] = count\n\t\t\t}\n\t\t}\n\t\treturn out\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * Descriptions by group id from the app's records. Read once per source.\n *\n * @param {{register?: string, schema?: string, groupField?: string, textField?: string}|null} describe The source.\n * @return {Promise<Record<string, string>>} Descriptions by group id.\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nasync function descriptions(describe) {\n\tif (!describe || !describe.register || !describe.schema) {\n\t\treturn {}\n\t}\n\tconst groupField = describe.groupField || 'ncGroupId'\n\tconst textField = describe.textField || 'description'\n\tconst key = [describe.register, describe.schema, groupField, textField].join('/')\n\tif (!descriptionCache.has(key)) {\n\t\tdescriptionCache.set(key, fetchReferenceObjects({ register: describe.register, schema: describe.schema, limit: 500 })\n\t\t\t.then((rows) => {\n\t\t\t\tconst out = {}\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tconst gid = row && row[groupField]\n\t\t\t\t\tconst text = row && typeof row[textField] === 'string' ? row[textField].trim() : ''\n\t\t\t\t\tif (gid && text && out[String(gid)] === undefined) {\n\t\t\t\t\t\tout[String(gid)] = text\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn out\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tdescriptionCache.delete(key)\n\t\t\t\treturn {}\n\t\t\t}))\n\t}\n\treturn descriptionCache.get(key)\n}\n\n/**\n * The line under each group: its member count and its description, joined by\n * \" · \", either left out when unknown.\n *\n * @param {string[]} ids The group ids to describe.\n * @param {string} query The search the groups came from.\n * @param {object|null} [describe] Where the app keeps a group's description.\n * @return {Promise<Record<string, string>>} The line by group id ('' when nothing is known).\n * @spec openspec/changes/assign-team-option-subline/specs/assign-dialog/spec.md\n */\nexport async function groupSublines(ids, query, describe = null) {\n\tif (!Array.isArray(ids) || ids.length === 0) {\n\t\treturn {}\n\t}\n\tconst [counts, texts] = await Promise.all([memberCounts(query), descriptions(describe)])\n\tconst out = {}\n\tfor (const id of ids) {\n\t\tconst parts = []\n\t\tif (counts[id] !== undefined) {\n\t\t\tparts.push(n('nextcloud-vue', '{count} member', '{count} members', counts[id], { count: counts[id] }))\n\t\t}\n\t\tif (texts[id]) {\n\t\t\tparts.push(texts[id])\n\t\t}\n\t\tout[id] = parts.join(' · ')\n\t}\n\treturn out\n}\n\n/**\n * Forget the cached descriptions (tests, or after the source changed).\n *\n * @return {void}\n */\nexport function clearGroupSublineCache() {\n\tdescriptionCache.clear()\n}\n"],"names":["axios","n"],"mappings":"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"}
|
package/dist/esm/utils/myTeam.js
CHANGED
|
@@ -31,12 +31,32 @@ let _source = null;
|
|
|
31
31
|
/** The read in flight, shared by every caller. */
|
|
32
32
|
let _pending = null;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The team the app answered, as kept: one group id, a list of two or more,
|
|
36
|
+
* or `''` for none. Blank entries are dropped and a list of one reads as that
|
|
37
|
+
* one id.
|
|
38
|
+
*
|
|
39
|
+
* @spec openspec/changes/my-team-source-may-answer-several-teams/specs/schema-utilities/spec.md#requirement-myteam-may-resolve-to-several-teams
|
|
40
|
+
* @param {unknown} team What the app's loader answered.
|
|
41
|
+
* @return {string|string[]} The kept team.
|
|
42
|
+
*/
|
|
43
|
+
function normalizeTeam(team) {
|
|
44
|
+
if (Array.isArray(team)) {
|
|
45
|
+
const ids = team.filter((id) => typeof id === 'string').map((id) => id.trim()).filter((id) => id !== '');
|
|
46
|
+
if (ids.length === 0) {
|
|
47
|
+
return ''
|
|
48
|
+
}
|
|
49
|
+
return ids.length === 1 ? ids[0] : ids
|
|
50
|
+
}
|
|
51
|
+
return typeof team === 'string' ? team.trim() : ''
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
/**
|
|
35
55
|
* Name where the current user's primary team is read: a function answering
|
|
36
56
|
* the team's Nextcloud group id, or null/'' for a user in no team.
|
|
37
57
|
*
|
|
38
58
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
39
|
-
* @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.
|
|
59
|
+
* @param {(() => Promise<string|string[]|null>)|null} loader The app's loader (a group id, or the ids of several teams), or null to unregister.
|
|
40
60
|
* @return {void}
|
|
41
61
|
*/
|
|
42
62
|
function registerMyTeamSource(loader) {
|
|
@@ -50,7 +70,7 @@ function registerMyTeamSource(loader) {
|
|
|
50
70
|
* when no source is registered, or when the read fails.
|
|
51
71
|
*
|
|
52
72
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
53
|
-
* @return {Promise<string>} The team's group id, or ''.
|
|
73
|
+
* @return {Promise<string|string[]>} The team's group id (or ids), or ''.
|
|
54
74
|
*/
|
|
55
75
|
function loadMyTeam() {
|
|
56
76
|
if (_state.team !== null) {
|
|
@@ -65,7 +85,7 @@ function loadMyTeam() {
|
|
|
65
85
|
}
|
|
66
86
|
_pending = Promise.resolve()
|
|
67
87
|
.then(() => _source())
|
|
68
|
-
.then((team) => (
|
|
88
|
+
.then((team) => normalizeTeam(team))
|
|
69
89
|
.catch(() => '')
|
|
70
90
|
.then((team) => {
|
|
71
91
|
_state.team = team;
|
|
@@ -80,7 +100,7 @@ function loadMyTeam() {
|
|
|
80
100
|
* it is being read (and starts the read on first use).
|
|
81
101
|
*
|
|
82
102
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
83
|
-
* @return {string|null} The team, '' or null.
|
|
103
|
+
* @return {string|string[]|null} The team (or teams), '' or null.
|
|
84
104
|
*/
|
|
85
105
|
function peekMyTeam() {
|
|
86
106
|
if (_state.team === null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"myTeam.js","sources":["../../../src/utils/myTeam.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The current user's primary team, for the `@myTeam` filter token.\n *\n * A team is the app's notion, not Nextcloud's: Nextcloud knows the groups a\n * user is in, and only the app knows which one of them is the user's team.\n * So the app registers where to read it (`registerMyTeamSource`), and the\n * library reads it once per page. This module imports no `@nextcloud/*`\n * package, for the same reason `userGroupsPeek.js` does not: the token\n * resolver runs on public pages and in node.\n *\n * @module utils/myTeam\n */\n\nimport { reactive } from 'vue'\n\n/**\n * `team` is the group id, `''` when the user has no team (or the app named no\n * source), and `null` while it has not been read.\n *\n * @type {{ team: (string|null) }}\n */\nconst _state = reactive({ team: null })\n\n/** The app's loader, `() => Promise<string|null>`. */\nlet _source = null\n\n/** The read in flight, shared by every caller. */\nlet _pending = null\n\n/**\n * Name where the current user's primary team is read: a function answering\n * the team's Nextcloud group id, or null/'' for a user in no team.\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.\n * @return {void}\n */\nexport function registerMyTeamSource(loader) {\n\t_source = typeof loader === 'function' ? loader : null\n\t_state.team = null\n\t_pending = null\n}\n\n/**\n * Read the team, once. Resolves to the group id, or `''` when there is none,\n * when no source is registered, or when the read fails.\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @return {Promise<string>} The team's group id, or ''.\n */\nexport function loadMyTeam() {\n\tif (_state.team !== null) {\n\t\treturn Promise.resolve(_state.team)\n\t}\n\tif (_pending) {\n\t\treturn _pending\n\t}\n\tif (!_source) {\n\t\t_state.team = ''\n\t\treturn Promise.resolve('')\n\t}\n\t_pending = Promise.resolve()\n\t\t.then(() => _source())\n\t\t.then((team) => (typeof team === 'string' ? team.trim() : ''))\n\t\t.catch(() => '')\n\t\t.then((team) => {\n\t\t\t_state.team = team\n\t\t\t_pending = null\n\t\t\treturn team\n\t\t})\n\treturn _pending\n}\n\n/**\n * Synchronous, reactive read: the group id, `''` for no team, or `null` while\n * it is being read (and starts the read on first use).\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @return {string|null} The team, '' or null.\n */\nexport function peekMyTeam() {\n\tif (_state.team === null) {\n\t\tloadMyTeam()\n\t}\n\treturn _state.team\n}\n\n/**\n * The reader's team by name, for `{myTeam}` in a count line. `name` is `''`\n * for no team (or no source) and `null` while it has not been read.\n *\n * @type {{ name: (string|null) }}\n */\nconst _nameState = reactive({ name: null })\n\n/** The app's name loader, `() => Promise<string|null>`. */\nlet _nameSource = null\n\n/** The name read in flight. */\nlet _namePending = null\n\n/**\n * Name where the reader's team name is read: a function answering the\n * team's display name, or null/'' for a reader in no team.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.\n * @return {void}\n */\nexport function registerMyTeamNameSource(loader) {\n\t_nameSource = typeof loader === 'function' ? loader : null\n\t_nameState.name = null\n\t_namePending = null\n}\n\n/**\n * Read the team name, once. Resolves to the name, or `''` without a team,\n * without a source, or when the read fails.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @return {Promise<string>} The name, or ''.\n */\nexport function loadMyTeamName() {\n\tif (_nameState.name !== null) {\n\t\treturn Promise.resolve(_nameState.name)\n\t}\n\tif (_namePending) {\n\t\treturn _namePending\n\t}\n\tif (!_nameSource) {\n\t\t_nameState.name = ''\n\t\treturn Promise.resolve('')\n\t}\n\t_namePending = Promise.resolve()\n\t\t.then(() => _nameSource())\n\t\t.then((name) => (typeof name === 'string' ? name.trim() : ''))\n\t\t.catch(() => '')\n\t\t.then((name) => {\n\t\t\t_nameState.name = name\n\t\t\t_namePending = null\n\t\t\treturn name\n\t\t})\n\treturn _namePending\n}\n\n/**\n * Synchronous, reactive read of the team name: the name, `''` for no team,\n * or `null` while it is read (and starts the read on first use).\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @return {string|null} The name, '' or null.\n */\nexport function peekMyTeamName() {\n\tif (_nameState.name === null) {\n\t\tloadMyTeamName()\n\t}\n\treturn _nameState.name\n}\n\n/**\n * Fill `{myTeam}` in a line with the team name. A part in square brackets\n * that holds `{myTeam}` is left out while there is no name; other brackets\n * are kept as written.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @param {string} text The line.\n * @param {string} name The team name, '' for none.\n * @return {string} The filled line.\n */\nexport function fillMyTeamToken(text, name) {\n\tif (typeof text !== 'string' || !text.includes('{myTeam}')) {\n\t\treturn text\n\t}\n\treturn text\n\t\t.replace(/\\[([^\\]]*\\{myTeam\\}[^\\]]*)\\]/g, (match, part) => (name ? part.replaceAll('{myTeam}', name) : ''))\n\t\t.replaceAll('{myTeam}', name)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"myTeam.js","sources":["../../../src/utils/myTeam.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The current user's primary team, for the `@myTeam` filter token.\n *\n * A team is the app's notion, not Nextcloud's: Nextcloud knows the groups a\n * user is in, and only the app knows which one of them is the user's team.\n * So the app registers where to read it (`registerMyTeamSource`), and the\n * library reads it once per page. This module imports no `@nextcloud/*`\n * package, for the same reason `userGroupsPeek.js` does not: the token\n * resolver runs on public pages and in node.\n *\n * @module utils/myTeam\n */\n\nimport { reactive } from 'vue'\n\n/**\n * `team` is the group id, `''` when the user has no team (or the app named no\n * source), and `null` while it has not been read.\n *\n * @type {{ team: (string|null) }}\n */\nconst _state = reactive({ team: null })\n\n/** The app's loader, `() => Promise<string|null>`. */\nlet _source = null\n\n/** The read in flight, shared by every caller. */\nlet _pending = null\n\n/**\n * The team the app answered, as kept: one group id, a list of two or more,\n * or `''` for none. Blank entries are dropped and a list of one reads as that\n * one id.\n *\n * @spec openspec/changes/my-team-source-may-answer-several-teams/specs/schema-utilities/spec.md#requirement-myteam-may-resolve-to-several-teams\n * @param {unknown} team What the app's loader answered.\n * @return {string|string[]} The kept team.\n */\nfunction normalizeTeam(team) {\n\tif (Array.isArray(team)) {\n\t\tconst ids = team.filter((id) => typeof id === 'string').map((id) => id.trim()).filter((id) => id !== '')\n\t\tif (ids.length === 0) {\n\t\t\treturn ''\n\t\t}\n\t\treturn ids.length === 1 ? ids[0] : ids\n\t}\n\treturn typeof team === 'string' ? team.trim() : ''\n}\n\n/**\n * Name where the current user's primary team is read: a function answering\n * the team's Nextcloud group id, or null/'' for a user in no team.\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @param {(() => Promise<string|string[]|null>)|null} loader The app's loader (a group id, or the ids of several teams), or null to unregister.\n * @return {void}\n */\nexport function registerMyTeamSource(loader) {\n\t_source = typeof loader === 'function' ? loader : null\n\t_state.team = null\n\t_pending = null\n}\n\n/**\n * Read the team, once. Resolves to the group id, or `''` when there is none,\n * when no source is registered, or when the read fails.\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @return {Promise<string|string[]>} The team's group id (or ids), or ''.\n */\nexport function loadMyTeam() {\n\tif (_state.team !== null) {\n\t\treturn Promise.resolve(_state.team)\n\t}\n\tif (_pending) {\n\t\treturn _pending\n\t}\n\tif (!_source) {\n\t\t_state.team = ''\n\t\treturn Promise.resolve('')\n\t}\n\t_pending = Promise.resolve()\n\t\t.then(() => _source())\n\t\t.then((team) => normalizeTeam(team))\n\t\t.catch(() => '')\n\t\t.then((team) => {\n\t\t\t_state.team = team\n\t\t\t_pending = null\n\t\t\treturn team\n\t\t})\n\treturn _pending\n}\n\n/**\n * Synchronous, reactive read: the group id, `''` for no team, or `null` while\n * it is being read (and starts the read on first use).\n *\n * @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team\n * @return {string|string[]|null} The team (or teams), '' or null.\n */\nexport function peekMyTeam() {\n\tif (_state.team === null) {\n\t\tloadMyTeam()\n\t}\n\treturn _state.team\n}\n\n/**\n * The reader's team by name, for `{myTeam}` in a count line. `name` is `''`\n * for no team (or no source) and `null` while it has not been read.\n *\n * @type {{ name: (string|null) }}\n */\nconst _nameState = reactive({ name: null })\n\n/** The app's name loader, `() => Promise<string|null>`. */\nlet _nameSource = null\n\n/** The name read in flight. */\nlet _namePending = null\n\n/**\n * Name where the reader's team name is read: a function answering the\n * team's display name, or null/'' for a reader in no team.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @param {(() => Promise<string|null>)|null} loader The app's loader, or null to unregister.\n * @return {void}\n */\nexport function registerMyTeamNameSource(loader) {\n\t_nameSource = typeof loader === 'function' ? loader : null\n\t_nameState.name = null\n\t_namePending = null\n}\n\n/**\n * Read the team name, once. Resolves to the name, or `''` without a team,\n * without a source, or when the read fails.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @return {Promise<string>} The name, or ''.\n */\nexport function loadMyTeamName() {\n\tif (_nameState.name !== null) {\n\t\treturn Promise.resolve(_nameState.name)\n\t}\n\tif (_namePending) {\n\t\treturn _namePending\n\t}\n\tif (!_nameSource) {\n\t\t_nameState.name = ''\n\t\treturn Promise.resolve('')\n\t}\n\t_namePending = Promise.resolve()\n\t\t.then(() => _nameSource())\n\t\t.then((name) => (typeof name === 'string' ? name.trim() : ''))\n\t\t.catch(() => '')\n\t\t.then((name) => {\n\t\t\t_nameState.name = name\n\t\t\t_namePending = null\n\t\t\treturn name\n\t\t})\n\treturn _namePending\n}\n\n/**\n * Synchronous, reactive read of the team name: the name, `''` for no team,\n * or `null` while it is read (and starts the read on first use).\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @return {string|null} The name, '' or null.\n */\nexport function peekMyTeamName() {\n\tif (_nameState.name === null) {\n\t\tloadMyTeamName()\n\t}\n\treturn _nameState.name\n}\n\n/**\n * Fill `{myTeam}` in a line with the team name. A part in square brackets\n * that holds `{myTeam}` is left out while there is no name; other brackets\n * are kept as written.\n *\n * @spec openspec/changes/count-line-my-team-name/specs/index-page/spec.md#requirement-the-count-line-can-name-the-readers-team\n * @param {string} text The line.\n * @param {string} name The team name, '' for none.\n * @return {string} The filled line.\n */\nexport function fillMyTeamToken(text, name) {\n\tif (typeof text !== 'string' || !text.includes('{myTeam}')) {\n\t\treturn text\n\t}\n\treturn text\n\t\t.replace(/\\[([^\\]]*\\{myTeam\\}[^\\]]*)\\]/g, (match, part) => (name ? part.replaceAll('{myTeam}', name) : ''))\n\t\t.replaceAll('{myTeam}', name)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -88,15 +88,16 @@ const MY_GROUPS_TOKEN = '@myGroups';
|
|
|
88
88
|
const MY_TEAM_TOKEN = '@myTeam';
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* Resolve `@myTeam`: the team's group id
|
|
91
|
+
* Resolve `@myTeam`: the team's group id, or the ids of several teams when the
|
|
92
|
+
* app answers a list (an IN filter); `@myTeam` while it is being read (the
|
|
92
93
|
* caller waits); `@myTeam?` for a user in no team (the caller drops the key).
|
|
93
94
|
*
|
|
94
95
|
* @spec openspec/changes/saved-view-tokens-my-team/specs/schema-utilities/spec.md#requirement-myteam-resolves-to-the-current-users-primary-team
|
|
95
|
-
* @param {{myTeam?: string}} [ctx] Optional context; `ctx.myTeam` wins.
|
|
96
|
-
* @return {string} The group id or the token.
|
|
96
|
+
* @param {{myTeam?: (string|string[])}} [ctx] Optional context; `ctx.myTeam` wins.
|
|
97
|
+
* @return {string|string[]} The group id(s) or the token.
|
|
97
98
|
*/
|
|
98
99
|
function resolveMyTeam(ctx) {
|
|
99
|
-
const team = ctx && typeof ctx.myTeam === 'string' ? ctx.myTeam : peekMyTeam();
|
|
100
|
+
const team = ctx && (typeof ctx.myTeam === 'string' || Array.isArray(ctx.myTeam)) ? ctx.myTeam : peekMyTeam();
|
|
100
101
|
if (team === null) {
|
|
101
102
|
return MY_TEAM_TOKEN
|
|
102
103
|
}
|