@conduction/nextcloud-vue 2.75.0 → 2.77.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/CnAppNav/CnAppNav.vue2.js +18 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +6 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +42 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +228 -203
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
- package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
- package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +41 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +24 -33
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +242 -19
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -20
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -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/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +4 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useObjectLock.js +41 -10
- package/dist/esm/composables/useObjectLock.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +31 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +32 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +376 -75
- package/dist/esm/store/plugins/interactions.js +20 -10
- package/dist/esm/store/plugins/interactions.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +54 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/builtInFormatters.js +15 -7
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/groupAutocomplete.js +63 -1
- package/dist/esm/utils/groupAutocomplete.js.map +1 -1
- package/dist/esm/utils/lensAvailability.js +90 -0
- package/dist/esm/utils/lensAvailability.js.map +1 -0
- package/dist/esm/utils/objectSignals.js +44 -0
- package/dist/esm/utils/objectSignals.js.map +1 -0
- package/dist/esm/utils/personalLenses.js +13 -6
- package/dist/esm/utils/personalLenses.js.map +1 -1
- package/dist/esm/utils/recordInteractions.js +31 -13
- package/dist/esm/utils/recordInteractions.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +46 -3
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +1 -1
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +4 -4
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetVisibility.js +46 -1
- package/dist/esm/utils/widgetVisibility.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4732 -2990
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +376 -75
- package/l10n/en.json +31 -1
- package/l10n/nl.json +32 -2
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +44 -5
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +56 -1
- package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
- package/src/components/CnCredentials/CnCredentials.vue +17 -16
- package/src/components/CnDataTable/CnDataTable.md +9 -0
- package/src/components/CnDataTable/CnDataTable.vue +80 -20
- package/src/components/CnDetailPage/CnDetailPage.vue +18 -32
- package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
- package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
- package/src/components/CnIndexPage/CnIndexPage.md +51 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +261 -35
- package/src/components/CnIndexPage/selfModeActions.js +89 -13
- package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
- package/src/composables/useListView.js +4 -0
- package/src/composables/useObjectLock.js +41 -10
- package/src/css/look-board-detail.css +40 -2
- package/src/css/look-board-index.css +35 -1
- package/src/css/table.css +82 -17
- package/src/index.js +3 -1
- package/src/schemas/app-manifest-v2.schema.json +11 -7
- package/src/store/plugins/interactions.js +20 -10
- package/src/store/useObjectStore.js +54 -9
- package/src/utils/builtInFormatters.js +15 -7
- package/src/utils/groupAutocomplete.js +75 -0
- package/src/utils/index.js +2 -1
- package/src/utils/lensAvailability.js +86 -0
- package/src/utils/objectSignals.js +41 -0
- package/src/utils/personalLenses.js +13 -6
- package/src/utils/recordInteractions.js +31 -13
- package/src/utils/resolveFilterTokens.js +45 -2
- package/src/utils/schema.js +1 -1
- package/src/utils/sentinelTokens.js +4 -4
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/widgetVisibility.js +45 -0
|
@@ -164,13 +164,15 @@ function formatDaysUntil(value) {
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Elapsed-time formatter (`daysSince` registry key), i18n'd through the
|
|
167
|
-
* library's own translation slug (`nextcloud-vue`)
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
167
|
+
* library's own translation slug (`nextcloud-vue`) in the Nextcloud user
|
|
168
|
+
* language. Whole calendar days, read as a running phrase so it sits after a
|
|
169
|
+
* title or in a meta line: today renders "today", one day back renders
|
|
170
|
+
* "yesterday", further back renders "N days ago". A (nonsensical but
|
|
171
|
+
* harmless) future date renders "tomorrow" or "in N days". Day counts are
|
|
172
|
+
* plural-aware via `translatePlural`.
|
|
171
173
|
*
|
|
172
174
|
* Null-safe per the built-in-formatter contract: `''` for null/empty,
|
|
173
|
-
* `String(value)` for unparseable input
|
|
175
|
+
* `String(value)` for unparseable input, never throws.
|
|
174
176
|
*
|
|
175
177
|
* @param {unknown} value A `Date`, parseable date string, or timestamp.
|
|
176
178
|
* @return {string} The relative-day phrasing (or ''/original on bad input).
|
|
@@ -185,13 +187,19 @@ function formatDaysSince(value) {
|
|
|
185
187
|
}
|
|
186
188
|
const days = dayDiffFromToday(d);
|
|
187
189
|
if (days === 0) {
|
|
188
|
-
return translate('nextcloud-vue', '
|
|
190
|
+
return translate('nextcloud-vue', 'today')
|
|
191
|
+
}
|
|
192
|
+
if (days === -1) {
|
|
193
|
+
return translate('nextcloud-vue', 'yesterday')
|
|
194
|
+
}
|
|
195
|
+
if (days === 1) {
|
|
196
|
+
return translate('nextcloud-vue', 'tomorrow')
|
|
189
197
|
}
|
|
190
198
|
if (days < 0) {
|
|
191
199
|
const ago = Math.abs(days);
|
|
192
200
|
return translatePlural('nextcloud-vue', '{count} day ago', '{count} days ago', ago, { count: ago })
|
|
193
201
|
}
|
|
194
|
-
return translatePlural('nextcloud-vue', '
|
|
202
|
+
return translatePlural('nextcloud-vue', 'in {count} day', 'in {count} days', days, { count: days })
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
/**
|
|
@@ -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 { safeCurrencyCode } from './formatMetric.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`): a past date renders\n * \"N days ago\", today renders \"Today\", and a (nonsensical but harmless)\n * future date renders \"In N days\". Day counts are plural-aware via\n * `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 < 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 * 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 (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{currency?: 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\tconst opts = options || {}\n\tconst decimals = Number.isFinite(opts.decimals) ? opts.decimals : 2\n\treturn new Intl.NumberFormat(undefined, {\n\t\tstyle: 'currency',\n\t\tcurrency: safeCurrencyCode(opts.currency),\n\t\tminimumFractionDigits: decimals,\n\t\tmaximumFractionDigits: decimals,\n\t}).format(num)\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 * 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 *\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 * @return {string} The translated label, or the value itself when it is not one of the seven.\n */\nexport function formatConnectionStatus(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tconst label = Object.hasOwn(CONNECTION_STATUS_LABELS, value)\n\t\t? CONNECTION_STATUS_LABELS[value]\n\t\t: 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 * Built-in formatter registry merged under any consumer-registered\n * `formatters` in `CnAppRoot`'s `cnFormatters` provide.\n */\nexport const BUILT_IN_FORMATTERS = {\n\tdate: formatDate,\n\tdatetime: formatDateTime,\n\t'relative-time': formatRelativeTime,\n\tdaysSince: formatDaysSince,\n\tdaysUntil: formatDaysUntil,\n\tcurrency: formatCurrency,\n\tconditionalPhrase: formatConditionalPhrase,\n\tcount: formatCount,\n\tconnectionStatus: formatConnectionStatus,\n\tconnectionSettingsLabel: formatConnectionSettingsLabel,\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 { safeCurrencyCode } from './formatMetric.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 * 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 (unused).\n * @param {object} [_property] The schema property (unused).\n * @param {{currency?: 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\tconst opts = options || {}\n\tconst decimals = Number.isFinite(opts.decimals) ? opts.decimals : 2\n\treturn new Intl.NumberFormat(undefined, {\n\t\tstyle: 'currency',\n\t\tcurrency: safeCurrencyCode(opts.currency),\n\t\tminimumFractionDigits: decimals,\n\t\tmaximumFractionDigits: decimals,\n\t}).format(num)\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 * 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 *\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 * @return {string} The translated label, or the value itself when it is not one of the seven.\n */\nexport function formatConnectionStatus(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tconst label = Object.hasOwn(CONNECTION_STATUS_LABELS, value)\n\t\t? CONNECTION_STATUS_LABELS[value]\n\t\t: 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 * Built-in formatter registry merged under any consumer-registered\n * `formatters` in `CnAppRoot`'s `cnFormatters` provide.\n */\nexport const BUILT_IN_FORMATTERS = {\n\tdate: formatDate,\n\tdatetime: formatDateTime,\n\t'relative-time': formatRelativeTime,\n\tdaysSince: formatDaysSince,\n\tdaysUntil: formatDaysUntil,\n\tcurrency: formatCurrency,\n\tconditionalPhrase: formatConditionalPhrase,\n\tcount: formatCount,\n\tconnectionStatus: formatConnectionStatus,\n\tconnectionSettingsLabel: formatConnectionSettingsLabel,\n}\n"],"names":["t","n"],"mappings":";;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,MAAM,CAAC,KAAK,EAAE;AACvB,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,IAAI,KAAK,YAAY,IAAI,EAAE;AAC5B,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,KAAK;AACrD,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,EAAC;AAC1B,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC;AAC5C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,KAAK,EAAE;AAClC,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAC;AACxB,CAAC,IAAI,CAAC,CAAC,EAAE;AACT,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAC7E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,KAAK,EAAE;AACtC,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAC;AACxB,CAAC,IAAI,CAAC,CAAC,EAAE;AACT,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACjG,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,KAAK,EAAE;AAC1C,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAC;AACxB,CAAC,IAAI,CAAC,CAAC,EAAE;AACT,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,GAAE;AACxC,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAC;AACxE,CAAC,MAAM,KAAK,GAAG;AACf,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC;AACvB,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC;AACvB,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;AACrB,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC;AACnB,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;AACnB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;AACnB,GAAE;AACF,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE;AACjC,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,KAAK,QAAQ,EAAE;AACnD,GAAG,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC;AACnD,GAAG;AACH,EAAE;AACF,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC;AAC/B,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,gBAAgB,CAAC,CAAC,EAAE;AAC7B,CAAC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,EAAC;AAC3B,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC;AAC5B,CAAC,MAAM,KAAK,GAAG,IAAI,IAAI,GAAE;AACzB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC;AAC3B,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,QAAQ,CAAC;AACnE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAC;AACxB,CAAC,IAAI,CAAC,CAAC,EAAE;AACT,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAAC;AACjC,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;AACjB,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,WAAW,CAAC;AACxC,EAAE;AACF,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE;AACf,EAAE,OAAOC,eAAC,CAAC,eAAe,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACrG,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAC;AAC/B,CAAC,OAAOA,eAAC,CAAC,eAAe,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtG,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAC;AACxB,CAAC,IAAI,CAAC,CAAC,EAAE;AACT,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAAC;AACjC,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;AACjB,EAAE,OAAOD,SAAC,CAAC,eAAe,EAAE,OAAO,CAAC;AACpC,EAAE;AACF,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,EAAE;AAClB,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,WAAW,CAAC;AACxC,EAAE;AACF,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;AACjB,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAC;AACvC,EAAE;AACF,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE;AACf,EAAE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAC;AAC5B,EAAE,OAAOC,eAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACvF,EAAE;AACF,CAAC,OAAOA,eAAC,CAAC,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACtF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;AAChE,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAC;AAC1B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC5B,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,OAAO,IAAI,GAAE;AAC3B,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,EAAC;AACpE,CAAC,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE;AACzC,EAAE,KAAK,EAAE,UAAU;AACnB,EAAE,QAAQ,EAAE,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC;AAC3C,EAAE,qBAAqB,EAAE,QAAQ;AACjC,EAAE,qBAAqB,EAAE,QAAQ;AACjC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;AACf,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;AACzE,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5D,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAC;AAC1B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC5B,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,OAAO,IAAI,GAAE;AAC3B,CAAC,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAC;AAC/E,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,EAAE;AAClD,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,eAAe,CAAC,KAAK,EAAE;AAChC,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,GAAE;AAC7B,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;AAC/C,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,IAAI;AACL,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAC;AACpC,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,GAAG,KAAK;AACzE,EAAE,CAAC,MAAM;AACT,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;AAC7D,CAAC,MAAM,IAAI,GAAG,OAAO,IAAI,GAAE;AAC3B,CAAC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,GAAG,IAAI,CAAC,IAAI,GAAG,GAAE;AAC5D;AACA;AACA;AACA;AACA,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,KAAK,EAAE;AAC9F,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,EAAC;AAC1C,CAAC,IAAI,EAAC;AACN,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;AAChC,EAAE,CAAC,GAAG,UAAU,CAAC,OAAM;AACvB,EAAE,MAAM,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;AAC5C,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAM;AACpC,EAAE,MAAM;AACR,EAAE,CAAC,GAAG,EAAC;AACP,EAAE;AACF,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;AACd,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAC;AACzF,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,EAAE;AAClD,EAAE,OAAO,MAAM,CAAC,CAAC,CAAC;AAClB,EAAE;AACF,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,wBAAwB,GAAG;AACjC,CAAC,UAAU,EAAE,YAAY;AACzB,CAAC,OAAO,EAAE,SAAS;AACnB,CAAC,YAAY,EAAE,gBAAgB;AAC/B,CAAC,SAAS,EAAE,WAAW;AACvB,CAAC,QAAQ,EAAE,cAAc;AACzB,CAAC,WAAW,EAAE,eAAe;AAC7B,CAAC,KAAK,EAAE,OAAO;AACf,EAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,KAAK,EAAE;AAC9C,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE;AAC5C,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAwB,EAAE,KAAK,CAAC;AAC7D,IAAI,wBAAwB,CAAC,KAAK,CAAC;AACnC,IAAI,KAAI;AACR,CAAC,OAAO,KAAK,GAAGD,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;AACzD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,6BAA6B,CAAC,KAAK,EAAE;AACrD,CAAC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;AACjD,IAAIA,SAAC,CAAC,eAAe,EAAE,eAAe,CAAC;AACvC,IAAI,EAAE;AACN,CAAC;AACD;AACA;AACA;AACA;AACA;AACY,MAAC,mBAAmB,GAAG;AACnC,CAAC,IAAI,EAAE,UAAU;AACjB,CAAC,QAAQ,EAAE,cAAc;AACzB,CAAC,eAAe,EAAE,kBAAkB;AACpC,CAAC,SAAS,EAAE,eAAe;AAC3B,CAAC,SAAS,EAAE,eAAe;AAC3B,CAAC,QAAQ,EAAE,cAAc;AACzB,CAAC,iBAAiB,EAAE,uBAAuB;AAC3C,CAAC,KAAK,EAAE,WAAW;AACnB,CAAC,gBAAgB,EAAE,sBAAsB;AACzC,CAAC,uBAAuB,EAAE,6BAA6B;AACvD;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios__default from '@nextcloud/axios';
|
|
2
2
|
import { generateOcsUrl } from '@nextcloud/router';
|
|
3
|
+
import { reactive } from 'vue';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Nextcloud group search for the schema-driven form's group picker
|
|
@@ -119,5 +120,66 @@ async function resolveNextcloudGroup(gid) {
|
|
|
119
120
|
return results.find((opt) => opt.id === String(gid)) || fallback
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Display names already looked up, by group id. Reactive, so every cell that
|
|
125
|
+
* shows the same group updates when its name arrives.
|
|
126
|
+
*
|
|
127
|
+
* @type {Record<string, string>}
|
|
128
|
+
*/
|
|
129
|
+
const groupNames = reactive({});
|
|
130
|
+
|
|
131
|
+
/** In-flight lookups by group id, so one id is asked for once. */
|
|
132
|
+
const pendingNames = new Map();
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The cached display name of a group, or `null` when it has not been looked
|
|
136
|
+
* up yet. Reactive: read in a computed or a render, it updates when
|
|
137
|
+
* {@link loadGroupDisplayName} finishes.
|
|
138
|
+
*
|
|
139
|
+
* @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
|
|
140
|
+
* @param {string} gid The group id.
|
|
141
|
+
* @return {string|null} The display name, or null when unknown.
|
|
142
|
+
*/
|
|
143
|
+
function groupDisplayName(gid) {
|
|
144
|
+
if (gid === undefined || gid === null || gid === '') {
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
const name = groupNames[String(gid)];
|
|
148
|
+
return typeof name === 'string' ? name : null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Look a group's display name up once per page and cache it. Concurrent
|
|
153
|
+
* callers for the same id share one request. A failed or empty lookup caches
|
|
154
|
+
* the id itself, so the cell shows the id and does not ask again.
|
|
155
|
+
*
|
|
156
|
+
* @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
|
|
157
|
+
* @param {string} gid The group id.
|
|
158
|
+
* @return {Promise<string>} The display name, or the id.
|
|
159
|
+
*/
|
|
160
|
+
function loadGroupDisplayName(gid) {
|
|
161
|
+
if (gid === undefined || gid === null || gid === '') {
|
|
162
|
+
return Promise.resolve('')
|
|
163
|
+
}
|
|
164
|
+
const key = String(gid);
|
|
165
|
+
const known = groupDisplayName(key);
|
|
166
|
+
if (known !== null) {
|
|
167
|
+
return Promise.resolve(known)
|
|
168
|
+
}
|
|
169
|
+
if (pendingNames.has(key)) {
|
|
170
|
+
return pendingNames.get(key)
|
|
171
|
+
}
|
|
172
|
+
const request = resolveNextcloudGroup(key)
|
|
173
|
+
.then((option) => (option && option.label) || key)
|
|
174
|
+
.catch(() => key)
|
|
175
|
+
.then((label) => {
|
|
176
|
+
groupNames[key] = label;
|
|
177
|
+
pendingNames.delete(key);
|
|
178
|
+
return label
|
|
179
|
+
});
|
|
180
|
+
pendingNames.set(key, request);
|
|
181
|
+
return request
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { groupDisplayName, loadGroupDisplayName, resolveNextcloudGroup, searchNextcloudGroups };
|
|
123
185
|
//# sourceMappingURL=groupAutocomplete.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupAutocomplete.js","sources":["../../../src/utils/groupAutocomplete.js"],"sourcesContent":["/**\n * Nextcloud group search for the schema-driven form's group picker\n * (a property marked `format: 'nc-group'` or `referenceType: 'nextcloud-group'`).\n *\n * Asks the core autocomplete OCS endpoint for groups first: every signed-in\n * user may call it, and it returns the group's display name. The provisioning\n * endpoint `cloud/groups` is the fallback; it also answers non-admins, but it\n * returns only group ids, so the id doubles as the label there.\n *\n * Options are `{ id: <gid>, label: <display name> }`: the form stores the gid.\n *\n * @module utils/groupAutocomplete\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\n\nconst OCS_HEADERS = {\n\t'OCS-APIRequest': 'true',\n\tAccept: 'application/json',\n}\n\n/**\n * Read the `ocs.data` payload, tolerating a bare body.\n *\n * @param {object} response An axios response.\n * @return {unknown} The data.\n */\nfunction ocsData(response) {\n\tconst body = response && response.data\n\treturn body && body.ocs ? body.ocs.data : body\n}\n\n/**\n * Map one autocomplete suggestion to an option, keeping only groups.\n *\n * @param {object} suggestion An autocomplete suggestion.\n * @return {{id: string, label: string}|null} The option, or null when it is not a group.\n */\nfunction toGroupOption(suggestion) {\n\tif (!suggestion || typeof suggestion !== 'object') {\n\t\treturn null\n\t}\n\tconst isGroup = suggestion.source === 'groups'\n\t\t|| suggestion.shareType === 1\n\t\t|| (suggestion.value && suggestion.value.shareType === 1)\n\tif (!isGroup) {\n\t\treturn null\n\t}\n\tconst gid = suggestion.id || (suggestion.value && suggestion.value.shareWith)\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn null\n\t}\n\treturn { id: String(gid), label: suggestion.label || String(gid) }\n}\n\n/**\n * Search Nextcloud groups by name or id. Fails soft: an error yields `[]`.\n *\n * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md\n * @param {string} [query] The search term (empty loads a first page).\n * @param {object} [options] Tuning options.\n * @param {number} [options.limit] Max results (default 25).\n * @return {Promise<Array<{id: string, label: string}>>} Picker options.\n */\nexport async function searchNextcloudGroups(query = '', options = {}) {\n\tconst { limit = 25 } = options\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('core/autocomplete/get'), {\n\t\t\theaders: OCS_HEADERS,\n\t\t\tparams: {\n\t\t\t\tsearch: query || '',\n\t\t\t\titemType: ' ',\n\t\t\t\titemId: ' ',\n\t\t\t\t'shareTypes[]': 1,\n\t\t\t\tlimit,\n\t\t\t},\n\t\t})\n\t\tconst list = ocsData(response)\n\t\tconst groups = (Array.isArray(list) ? list : [])\n\t\t\t.map(toGroupOption)\n\t\t\t.filter((opt) => opt !== null)\n\t\tif (groups.length > 0) {\n\t\t\treturn groups\n\t\t}\n\t} catch {\n\t\t// Fall through to the provisioning endpoint.\n\t}\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('cloud/groups'), {\n\t\t\theaders: OCS_HEADERS,\n\t\t\tparams: { search: query || '', limit },\n\t\t})\n\t\tconst data = ocsData(response)\n\t\tconst gids = data && Array.isArray(data.groups) ? data.groups : []\n\t\treturn gids\n\t\t\t.filter((gid) => typeof gid === 'string' && gid !== '')\n\t\t\t.map((gid) => ({ id: gid, label: gid }))\n\t} catch {\n\t\treturn []\n\t}\n}\n\n/**\n * Resolve a stored gid to `{ id, label }` for edit-mode display. Falls back\n * to the gid itself when the name cannot be looked up.\n *\n * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md\n * @param {string} gid The stored group id.\n * @return {Promise<{id: string, label: string}>} The resolved option.\n */\nexport async function resolveNextcloudGroup(gid) {\n\tconst fallback = { id: String(gid), label: String(gid) }\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn fallback\n\t}\n\tconst results = await searchNextcloudGroups(String(gid))\n\treturn results.find((opt) => opt.id === String(gid)) || fallback\n}\n"],"names":["axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"groupAutocomplete.js","sources":["../../../src/utils/groupAutocomplete.js"],"sourcesContent":["/**\n * Nextcloud group search for the schema-driven form's group picker\n * (a property marked `format: 'nc-group'` or `referenceType: 'nextcloud-group'`).\n *\n * Asks the core autocomplete OCS endpoint for groups first: every signed-in\n * user may call it, and it returns the group's display name. The provisioning\n * endpoint `cloud/groups` is the fallback; it also answers non-admins, but it\n * returns only group ids, so the id doubles as the label there.\n *\n * Options are `{ id: <gid>, label: <display name> }`: the form stores the gid.\n *\n * @module utils/groupAutocomplete\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\nimport { reactive } from 'vue'\n\nconst OCS_HEADERS = {\n\t'OCS-APIRequest': 'true',\n\tAccept: 'application/json',\n}\n\n/**\n * Read the `ocs.data` payload, tolerating a bare body.\n *\n * @param {object} response An axios response.\n * @return {unknown} The data.\n */\nfunction ocsData(response) {\n\tconst body = response && response.data\n\treturn body && body.ocs ? body.ocs.data : body\n}\n\n/**\n * Map one autocomplete suggestion to an option, keeping only groups.\n *\n * @param {object} suggestion An autocomplete suggestion.\n * @return {{id: string, label: string}|null} The option, or null when it is not a group.\n */\nfunction toGroupOption(suggestion) {\n\tif (!suggestion || typeof suggestion !== 'object') {\n\t\treturn null\n\t}\n\tconst isGroup = suggestion.source === 'groups'\n\t\t|| suggestion.shareType === 1\n\t\t|| (suggestion.value && suggestion.value.shareType === 1)\n\tif (!isGroup) {\n\t\treturn null\n\t}\n\tconst gid = suggestion.id || (suggestion.value && suggestion.value.shareWith)\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn null\n\t}\n\treturn { id: String(gid), label: suggestion.label || String(gid) }\n}\n\n/**\n * Search Nextcloud groups by name or id. Fails soft: an error yields `[]`.\n *\n * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md\n * @param {string} [query] The search term (empty loads a first page).\n * @param {object} [options] Tuning options.\n * @param {number} [options.limit] Max results (default 25).\n * @return {Promise<Array<{id: string, label: string}>>} Picker options.\n */\nexport async function searchNextcloudGroups(query = '', options = {}) {\n\tconst { limit = 25 } = options\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('core/autocomplete/get'), {\n\t\t\theaders: OCS_HEADERS,\n\t\t\tparams: {\n\t\t\t\tsearch: query || '',\n\t\t\t\titemType: ' ',\n\t\t\t\titemId: ' ',\n\t\t\t\t'shareTypes[]': 1,\n\t\t\t\tlimit,\n\t\t\t},\n\t\t})\n\t\tconst list = ocsData(response)\n\t\tconst groups = (Array.isArray(list) ? list : [])\n\t\t\t.map(toGroupOption)\n\t\t\t.filter((opt) => opt !== null)\n\t\tif (groups.length > 0) {\n\t\t\treturn groups\n\t\t}\n\t} catch {\n\t\t// Fall through to the provisioning endpoint.\n\t}\n\ttry {\n\t\tconst response = await axios.get(generateOcsUrl('cloud/groups'), {\n\t\t\theaders: OCS_HEADERS,\n\t\t\tparams: { search: query || '', limit },\n\t\t})\n\t\tconst data = ocsData(response)\n\t\tconst gids = data && Array.isArray(data.groups) ? data.groups : []\n\t\treturn gids\n\t\t\t.filter((gid) => typeof gid === 'string' && gid !== '')\n\t\t\t.map((gid) => ({ id: gid, label: gid }))\n\t} catch {\n\t\treturn []\n\t}\n}\n\n/**\n * Resolve a stored gid to `{ id, label }` for edit-mode display. Falls back\n * to the gid itself when the name cannot be looked up.\n *\n * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md\n * @param {string} gid The stored group id.\n * @return {Promise<{id: string, label: string}>} The resolved option.\n */\nexport async function resolveNextcloudGroup(gid) {\n\tconst fallback = { id: String(gid), label: String(gid) }\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn fallback\n\t}\n\tconst results = await searchNextcloudGroups(String(gid))\n\treturn results.find((opt) => opt.id === String(gid)) || fallback\n}\n\n/**\n * Display names already looked up, by group id. Reactive, so every cell that\n * shows the same group updates when its name arrives.\n *\n * @type {Record<string, string>}\n */\nconst groupNames = reactive({})\n\n/** In-flight lookups by group id, so one id is asked for once. */\nconst pendingNames = new Map()\n\n/**\n * The cached display name of a group, or `null` when it has not been looked\n * up yet. Reactive: read in a computed or a render, it updates when\n * {@link loadGroupDisplayName} finishes.\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n * @param {string} gid The group id.\n * @return {string|null} The display name, or null when unknown.\n */\nexport function groupDisplayName(gid) {\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn null\n\t}\n\tconst name = groupNames[String(gid)]\n\treturn typeof name === 'string' ? name : null\n}\n\n/**\n * Look a group's display name up once per page and cache it. Concurrent\n * callers for the same id share one request. A failed or empty lookup caches\n * the id itself, so the cell shows the id and does not ask again.\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n * @param {string} gid The group id.\n * @return {Promise<string>} The display name, or the id.\n */\nexport function loadGroupDisplayName(gid) {\n\tif (gid === undefined || gid === null || gid === '') {\n\t\treturn Promise.resolve('')\n\t}\n\tconst key = String(gid)\n\tconst known = groupDisplayName(key)\n\tif (known !== null) {\n\t\treturn Promise.resolve(known)\n\t}\n\tif (pendingNames.has(key)) {\n\t\treturn pendingNames.get(key)\n\t}\n\tconst request = resolveNextcloudGroup(key)\n\t\t.then((option) => (option && option.label) || key)\n\t\t.catch(() => key)\n\t\t.then((label) => {\n\t\t\tgroupNames[key] = label\n\t\t\tpendingNames.delete(key)\n\t\t\treturn label\n\t\t})\n\tpendingNames.set(key, request)\n\treturn request\n}\n\n/**\n * Forget every cached group name (tests, or after a group was renamed).\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name\n * @return {void}\n */\nexport function clearGroupNameCache() {\n\tpendingNames.clear()\n\tfor (const key of Object.keys(groupNames)) {\n\t\tdelete groupNames[key]\n\t}\n}\n"],"names":["axios"],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
5
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6
|
+
*
|
|
7
|
+
* Why a personal lens (Recent, later Following) came back empty.
|
|
8
|
+
*
|
|
9
|
+
* OpenRegister reports per asked lens, at response level, whether it could
|
|
10
|
+
* answer: `@self.lenses.<lens> = { available, reason }` (openregister#4514,
|
|
11
|
+
* `read-history-on-audit-trail`). `available: false` always comes with an
|
|
12
|
+
* empty page. These helpers read that report and turn it into the short
|
|
13
|
+
* explanation a list shows in place of its generic empty text.
|
|
14
|
+
*
|
|
15
|
+
* The lens name is a key, so a later lens (`watching`) only needs copy.
|
|
16
|
+
*
|
|
17
|
+
* @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Built-in explanations per lens and reason. Object-neutral: the library
|
|
22
|
+
* does not know whether the app lists cases, tickets or documents. An app
|
|
23
|
+
* says it in its own words through `lensReasonTexts`.
|
|
24
|
+
*
|
|
25
|
+
* @return {Object<string, Object<string, string>>} Lens name to reason to text.
|
|
26
|
+
*/
|
|
27
|
+
function builtInTexts() {
|
|
28
|
+
return {
|
|
29
|
+
recent: {
|
|
30
|
+
'audit-trail-disabled': translate('nextcloud-vue', 'This server does not keep track of what you open.'),
|
|
31
|
+
anonymous: translate('nextcloud-vue', 'Log in to see what you opened recently.'),
|
|
32
|
+
'read-history-unavailable': translate('nextcloud-vue', 'Your recent items are not available right now.'),
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The lens reports of a list response body: `@self.lenses`, or `{}` when the
|
|
39
|
+
* body carries none (every response before openregister#4514, and every
|
|
40
|
+
* response that did not ask for a lens).
|
|
41
|
+
*
|
|
42
|
+
* @param {object|null|undefined} body A parsed list response.
|
|
43
|
+
* @return {Object<string, {available: boolean, reason: (string|null)}>} The reports, keyed by lens name.
|
|
44
|
+
* @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-a-list-response-carries-the-report-of-the-lenses-it-was-asked-for
|
|
45
|
+
*/
|
|
46
|
+
function readLensReports(body) {
|
|
47
|
+
const lenses = body && typeof body === 'object' && body['@self'] && typeof body['@self'] === 'object'
|
|
48
|
+
? body['@self'].lenses
|
|
49
|
+
: null;
|
|
50
|
+
return lenses && typeof lenses === 'object' && !Array.isArray(lenses) ? lenses : {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The explanation for the first lens that reports `available: false` with a
|
|
55
|
+
* reason there is text for, or `''` when there is none (no report, every lens
|
|
56
|
+
* available, or a reason nobody wrote text for). The caller then shows its
|
|
57
|
+
* own empty text, as before.
|
|
58
|
+
*
|
|
59
|
+
* App text wins over built-in text; within app text, `<lens>.<reason>` wins
|
|
60
|
+
* over `<reason>`.
|
|
61
|
+
*
|
|
62
|
+
* @param {object|null|undefined} reports The lens reports (see `readLensReports`).
|
|
63
|
+
* @param {Object<string, string>|null} [overrides] App text keyed `<lens>.<reason>` or `<reason>`.
|
|
64
|
+
* @return {string} The explanation, or an empty string.
|
|
65
|
+
* @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page
|
|
66
|
+
*/
|
|
67
|
+
function lensUnavailableText(reports, overrides = null) {
|
|
68
|
+
if (!reports || typeof reports !== 'object') {
|
|
69
|
+
return ''
|
|
70
|
+
}
|
|
71
|
+
const own = overrides && typeof overrides === 'object' ? overrides : {};
|
|
72
|
+
const builtIn = builtInTexts();
|
|
73
|
+
for (const [lens, report] of Object.entries(reports)) {
|
|
74
|
+
if (!report || typeof report !== 'object' || report.available !== false) {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
const reason = typeof report.reason === 'string' ? report.reason : '';
|
|
78
|
+
if (reason === '') {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
const text = own[`${lens}.${reason}`] || own[reason] || (builtIn[lens] && builtIn[lens][reason]) || '';
|
|
82
|
+
if (text) {
|
|
83
|
+
return text
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return ''
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { lensUnavailableText, readLensReports };
|
|
90
|
+
//# sourceMappingURL=lensAvailability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lensAvailability.js","sources":["../../../src/utils/lensAvailability.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Why a personal lens (Recent, later Following) came back empty.\n *\n * OpenRegister reports per asked lens, at response level, whether it could\n * answer: `@self.lenses.<lens> = { available, reason }` (openregister#4514,\n * `read-history-on-audit-trail`). `available: false` always comes with an\n * empty page. These helpers read that report and turn it into the short\n * explanation a list shows in place of its generic empty text.\n *\n * The lens name is a key, so a later lens (`watching`) only needs copy.\n *\n * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n */\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * Built-in explanations per lens and reason. Object-neutral: the library\n * does not know whether the app lists cases, tickets or documents. An app\n * says it in its own words through `lensReasonTexts`.\n *\n * @return {Object<string, Object<string, string>>} Lens name to reason to text.\n */\nfunction builtInTexts() {\n\treturn {\n\t\trecent: {\n\t\t\t'audit-trail-disabled': t('nextcloud-vue', 'This server does not keep track of what you open.'),\n\t\t\tanonymous: t('nextcloud-vue', 'Log in to see what you opened recently.'),\n\t\t\t'read-history-unavailable': t('nextcloud-vue', 'Your recent items are not available right now.'),\n\t\t},\n\t}\n}\n\n/**\n * The lens reports of a list response body: `@self.lenses`, or `{}` when the\n * body carries none (every response before openregister#4514, and every\n * response that did not ask for a lens).\n *\n * @param {object|null|undefined} body A parsed list response.\n * @return {Object<string, {available: boolean, reason: (string|null)}>} The reports, keyed by lens name.\n * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-a-list-response-carries-the-report-of-the-lenses-it-was-asked-for\n */\nexport function readLensReports(body) {\n\tconst lenses = body && typeof body === 'object' && body['@self'] && typeof body['@self'] === 'object'\n\t\t? body['@self'].lenses\n\t\t: null\n\treturn lenses && typeof lenses === 'object' && !Array.isArray(lenses) ? lenses : {}\n}\n\n/**\n * The explanation for the first lens that reports `available: false` with a\n * reason there is text for, or `''` when there is none (no report, every lens\n * available, or a reason nobody wrote text for). The caller then shows its\n * own empty text, as before.\n *\n * App text wins over built-in text; within app text, `<lens>.<reason>` wins\n * over `<reason>`.\n *\n * @param {object|null|undefined} reports The lens reports (see `readLensReports`).\n * @param {Object<string, string>|null} [overrides] App text keyed `<lens>.<reason>` or `<reason>`.\n * @return {string} The explanation, or an empty string.\n * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n */\nexport function lensUnavailableText(reports, overrides = null) {\n\tif (!reports || typeof reports !== 'object') {\n\t\treturn ''\n\t}\n\tconst own = overrides && typeof overrides === 'object' ? overrides : {}\n\tconst builtIn = builtInTexts()\n\tfor (const [lens, report] of Object.entries(reports)) {\n\t\tif (!report || typeof report !== 'object' || report.available !== false) {\n\t\t\tcontinue\n\t\t}\n\t\tconst reason = typeof report.reason === 'string' ? report.reason : ''\n\t\tif (reason === '') {\n\t\t\tcontinue\n\t\t}\n\t\tconst text = own[`${lens}.${reason}`] || own[reason] || (builtIn[lens] && builtIn[lens][reason]) || ''\n\t\tif (text) {\n\t\t\treturn text\n\t\t}\n\t}\n\treturn ''\n}\n"],"names":["t"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,YAAY,GAAG;AACxB,CAAC,OAAO;AACR,EAAE,MAAM,EAAE;AACV,GAAG,sBAAsB,EAAEA,SAAC,CAAC,eAAe,EAAE,mDAAmD,CAAC;AAClG,GAAG,SAAS,EAAEA,SAAC,CAAC,eAAe,EAAE,yCAAyC,CAAC;AAC3E,GAAG,0BAA0B,EAAEA,SAAC,CAAC,eAAe,EAAE,gDAAgD,CAAC;AACnG,GAAG;AACH,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,IAAI,EAAE;AACtC,CAAC,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ;AACtG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM;AACxB,IAAI,KAAI;AACR,CAAC,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,EAAE;AACpF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,EAAE;AAC/D,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;AAC9C,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,GAAG,SAAS,GAAG,GAAE;AACxE,CAAC,MAAM,OAAO,GAAG,YAAY,GAAE;AAC/B,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;AACvD,EAAE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,EAAE;AAC3E,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,GAAE;AACvE,EAAE,IAAI,MAAM,KAAK,EAAE,EAAE;AACrB,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,GAAE;AACxG,EAAE,IAAI,IAAI,EAAE;AACZ,GAAG,OAAO,IAAI;AACd,GAAG;AACH,EAAE;AACF,CAAC,OAAO,EAAE;AACV;;;;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Signal that objects changed outside the surface showing them, such as files
|
|
6
|
+
* uploaded or an object saved from an app's own modal. CnIndexPage listens and
|
|
7
|
+
* refreshes its list when the register and schema match.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/objectSignals
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const OBJECTS_CHANGED_EVENT = 'cn:objects-changed';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Announce that objects of a register and schema changed.
|
|
16
|
+
*
|
|
17
|
+
* Pass the same register and schema identifiers (slug or id) the listing page
|
|
18
|
+
* uses. Leaving one out matches every page on that part.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} [payload] What changed.
|
|
21
|
+
* @param {string|number} [payload.register] The register slug or id.
|
|
22
|
+
* @param {string|number} [payload.schema] The schema slug or id.
|
|
23
|
+
* @param {string} [payload.id] The changed object's id, when there is one.
|
|
24
|
+
* @return {boolean} True when the event was dispatched.
|
|
25
|
+
*/
|
|
26
|
+
function dispatchObjectsChanged({ register, schema, id } = {}) {
|
|
27
|
+
if (typeof window === 'undefined') {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
const detail = {
|
|
31
|
+
register: register === undefined || register === null || register === '' ? null : String(register),
|
|
32
|
+
schema: schema === undefined || schema === null || schema === '' ? null : String(schema),
|
|
33
|
+
id: id ? String(id) : null,
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
window.dispatchEvent(new CustomEvent(OBJECTS_CHANGED_EVENT, { detail }));
|
|
37
|
+
return true
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { OBJECTS_CHANGED_EVENT, dispatchObjectsChanged };
|
|
44
|
+
//# sourceMappingURL=objectSignals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectSignals.js","sources":["../../../src/utils/objectSignals.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Signal that objects changed outside the surface showing them, such as files\n * uploaded or an object saved from an app's own modal. CnIndexPage listens and\n * refreshes its list when the register and schema match.\n *\n * @module utils/objectSignals\n */\n\nexport const OBJECTS_CHANGED_EVENT = 'cn:objects-changed'\n\n/**\n * Announce that objects of a register and schema changed.\n *\n * Pass the same register and schema identifiers (slug or id) the listing page\n * uses. Leaving one out matches every page on that part.\n *\n * @param {object} [payload] What changed.\n * @param {string|number} [payload.register] The register slug or id.\n * @param {string|number} [payload.schema] The schema slug or id.\n * @param {string} [payload.id] The changed object's id, when there is one.\n * @return {boolean} True when the event was dispatched.\n */\nexport function dispatchObjectsChanged({ register, schema, id } = {}) {\n\tif (typeof window === 'undefined') {\n\t\treturn false\n\t}\n\tconst detail = {\n\t\tregister: register === undefined || register === null || register === '' ? null : String(register),\n\t\tschema: schema === undefined || schema === null || schema === '' ? null : String(schema),\n\t\tid: id ? String(id) : null,\n\t}\n\ttry {\n\t\twindow.dispatchEvent(new CustomEvent(OBJECTS_CHANGED_EVENT, { detail }))\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,qBAAqB,GAAG,qBAAoB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;AACtE,CAAC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACpC,EAAE,OAAO,KAAK;AACd,EAAE;AACF,CAAC,MAAM,MAAM,GAAG;AAChB,EAAE,QAAQ,EAAE,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,EAAE,GAAG,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;AACpG,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,EAAE,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;AAC1F,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI;AAC5B,GAAE;AACF,CAAC,IAAI;AACL,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,CAAC,EAAC;AAC1E,EAAE,OAAO,IAAI;AACb,EAAE,CAAC,MAAM;AACT,EAAE,OAAO,KAAK;AACd,EAAE;AACF;;;;"}
|
|
@@ -4,29 +4,36 @@ import { translate } from '@nextcloud/l10n';
|
|
|
4
4
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
5
5
|
* SPDX-License-Identifier: EUPL-1.2
|
|
6
6
|
*
|
|
7
|
-
* The personal lenses of an index page (
|
|
8
|
-
*
|
|
7
|
+
* The personal lenses of an index page (Following, Recent, Unread) as quick
|
|
8
|
+
* filters, appended after the page's own.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* There is no Favourites lens any more: a favourite is a follow with
|
|
11
|
+
* notifications off (OpenRegister `merge-follow-and-favourites`), so
|
|
12
|
+
* `favourite` is an alias of `watching` and asking for both gives one tab.
|
|
13
|
+
*
|
|
14
|
+
* @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-index-pages-offer-one-following-lens-and-a-follow-column
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
/** Lens id to the query key OpenRegister reads. */
|
|
14
|
-
const LENS_FILTER_KEYS = {
|
|
18
|
+
const LENS_FILTER_KEYS = { recent: '_recent', watching: '_watching', unread: '_unread' };
|
|
19
|
+
|
|
20
|
+
/** Deprecated lens ids and the lens they now mean. */
|
|
21
|
+
const LENS_ALIASES = { favourite: 'watching' };
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Quick filters for the requested lenses. Unknown ids are ignored.
|
|
18
25
|
*
|
|
19
|
-
* @param {string[]} lenses Any of `
|
|
26
|
+
* @param {string[]} lenses Any of `watching`, `recent`, `unread`, and the deprecated `favourite` (read as `watching`).
|
|
20
27
|
* @return {Array<{label: string, filter: object, lens: string}>} One tab per lens, in the order asked.
|
|
21
28
|
*/
|
|
22
29
|
function personalLensTabs(lenses) {
|
|
23
30
|
const labels = {
|
|
24
|
-
favourite: translate('nextcloud-vue', 'Favourites'),
|
|
25
31
|
recent: translate('nextcloud-vue', 'Recent'),
|
|
26
32
|
watching: translate('nextcloud-vue', 'Following'),
|
|
27
33
|
unread: translate('nextcloud-vue', 'Unread'),
|
|
28
34
|
};
|
|
29
35
|
return (Array.isArray(lenses) ? lenses : [])
|
|
36
|
+
.map((id) => (Object.hasOwn(LENS_ALIASES, id) ? LENS_ALIASES[id] : id))
|
|
30
37
|
.filter((id, i, all) => Object.hasOwn(LENS_FILTER_KEYS, id) && all.indexOf(id) === i)
|
|
31
38
|
.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))
|
|
32
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalLenses.js","sources":["../../../src/utils/personalLenses.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The personal lenses of an index page (
|
|
1
|
+
{"version":3,"file":"personalLenses.js","sources":["../../../src/utils/personalLenses.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * The personal lenses of an index page (Following, Recent, Unread) as quick\n * filters, appended after the page's own.\n *\n * There is no Favourites lens any more: a favourite is a follow with\n * notifications off (OpenRegister `merge-follow-and-favourites`), so\n * `favourite` is an alias of `watching` and asking for both gives one tab.\n *\n * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-index-pages-offer-one-following-lens-and-a-follow-column\n */\nimport { translate as t } from '@nextcloud/l10n'\n\n/** Lens id to the query key OpenRegister reads. */\nexport const LENS_FILTER_KEYS = { recent: '_recent', watching: '_watching', unread: '_unread' }\n\n/** Deprecated lens ids and the lens they now mean. */\nconst LENS_ALIASES = { favourite: 'watching' }\n\n/**\n * Quick filters for the requested lenses. Unknown ids are ignored.\n *\n * @param {string[]} lenses Any of `watching`, `recent`, `unread`, and the deprecated `favourite` (read as `watching`).\n * @return {Array<{label: string, filter: object, lens: string}>} One tab per lens, in the order asked.\n */\nexport function personalLensTabs(lenses) {\n\tconst labels = {\n\t\trecent: t('nextcloud-vue', 'Recent'),\n\t\twatching: t('nextcloud-vue', 'Following'),\n\t\tunread: t('nextcloud-vue', 'Unread'),\n\t}\n\treturn (Array.isArray(lenses) ? lenses : [])\n\t\t.map((id) => (Object.hasOwn(LENS_ALIASES, id) ? LENS_ALIASES[id] : id))\n\t\t.filter((id, i, all) => Object.hasOwn(LENS_FILTER_KEYS, id) && all.indexOf(id) === i)\n\t\t.map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))\n}\n\n/**\n * The page's quick filters with its personal lenses appended. A page with no\n * quick filters of its own gets an \"All\" tab first, so a lens is not active\n * before the person asks for it.\n *\n * @param {Array<object>|null} quickFilters The page's own quick filters.\n * @param {string[]} lenses The requested lenses.\n * @return {Array<object>|null} The combined list, or the page's own list unchanged when no lens is asked for.\n */\nexport function withPersonalLenses(quickFilters, lenses) {\n\tconst tabs = personalLensTabs(lenses)\n\tif (tabs.length === 0) {\n\t\treturn quickFilters\n\t}\n\tconst own = Array.isArray(quickFilters) ? quickFilters : []\n\treturn own.length > 0\n\t\t? [...own, ...tabs]\n\t\t: [{ label: t('nextcloud-vue', 'All'), filter: {}, default: true }, ...tabs]\n}\n"],"names":["t"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACY,MAAC,gBAAgB,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAE;AAC/F;AACA;AACA,MAAM,YAAY,GAAG,EAAE,SAAS,EAAE,UAAU,GAAE;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,MAAM,EAAE;AACzC,CAAC,MAAM,MAAM,GAAG;AAChB,EAAE,MAAM,EAAEA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAC;AACtC,EAAE,QAAQ,EAAEA,SAAC,CAAC,eAAe,EAAE,WAAW,CAAC;AAC3C,EAAE,MAAM,EAAEA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAC;AACtC,GAAE;AACF,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,EAAE;AAC5C,GAAG,GAAG,CAAC,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AACzE,GAAG,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AACvF,GAAG,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE;AACzD,CAAC,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,EAAC;AACtC,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AACxB,EAAE,OAAO,YAAY;AACrB,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,GAAG,GAAE;AAC5D,CAAC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC;AACtB,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC;AACrB,IAAI,CAAC,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC;AAC9E;;;;"}
|
|
@@ -5,8 +5,10 @@ import { prefixUrl, buildHeaders } from './headers.js';
|
|
|
5
5
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
6
6
|
* SPDX-License-Identifier: EUPL-1.2
|
|
7
7
|
*
|
|
8
|
-
* The per-user interaction calls OpenRegister serves on a record:
|
|
9
|
-
* watch
|
|
8
|
+
* The per-user interaction calls OpenRegister serves on a record: follow
|
|
9
|
+
* (watch, with its notifications switch), the watchers list, the read state,
|
|
10
|
+
* and the deprecated favourite (a quiet follow since OpenRegister's
|
|
11
|
+
* `merge-follow-and-favourites`). Plain functions over `fetch`, shared
|
|
10
12
|
* by the interactions store plugin and by the toggles, so a toggle works with
|
|
11
13
|
* or without the plugin installed.
|
|
12
14
|
*
|
|
@@ -32,13 +34,18 @@ function interactionUrl(register, schema, id, tail) {
|
|
|
32
34
|
/**
|
|
33
35
|
* Send one interaction call and normalise the answer. Never throws.
|
|
34
36
|
*
|
|
35
|
-
* @param {string}
|
|
36
|
-
* @param {string}
|
|
37
|
+
* @param {string} method HTTP method.
|
|
38
|
+
* @param {string} url Full URL.
|
|
39
|
+
* @param {object|null} [body] JSON body, or null for none.
|
|
37
40
|
* @return {Promise<{ok: boolean, status: number|null, data: (object|null), message: string}>} The outcome; `message` is the server's, or a network message.
|
|
38
41
|
*/
|
|
39
|
-
async function call(method, url) {
|
|
42
|
+
async function call(method, url, body = null) {
|
|
40
43
|
try {
|
|
41
|
-
const
|
|
44
|
+
const init = { method, headers: buildHeaders() };
|
|
45
|
+
if (body !== null) {
|
|
46
|
+
init.body = JSON.stringify(body);
|
|
47
|
+
}
|
|
48
|
+
const response = await fetch(prefixUrl(url), init);
|
|
42
49
|
if (!response.ok) {
|
|
43
50
|
const error = await parseResponseError(response, 'record');
|
|
44
51
|
// The server's own words win over the generic status text.
|
|
@@ -62,6 +69,9 @@ async function call(method, url) {
|
|
|
62
69
|
/**
|
|
63
70
|
* Star or unstar a record for the current user.
|
|
64
71
|
*
|
|
72
|
+
* @deprecated A star is a quiet follow since OpenRegister's
|
|
73
|
+
* `merge-follow-and-favourites`; use `setWatching(..., { notify: false })`.
|
|
74
|
+
*
|
|
65
75
|
* @param {string} register Register slug.
|
|
66
76
|
* @param {string} schema Schema slug.
|
|
67
77
|
* @param {string} id Object id.
|
|
@@ -73,16 +83,24 @@ function setFavourite(register, schema, id, on) {
|
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/**
|
|
76
|
-
* Follow or unfollow a record for the current user
|
|
86
|
+
* Follow or unfollow a record for the current user, optionally setting the
|
|
87
|
+
* follow's notifications switch.
|
|
77
88
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* @param {
|
|
89
|
+
* `options.notify` is sent only when following and only when it is a boolean:
|
|
90
|
+
* left out, a new follow notifies and an existing follow keeps its setting.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} register Register slug.
|
|
93
|
+
* @param {string} schema Schema slug.
|
|
94
|
+
* @param {string} id Object id.
|
|
95
|
+
* @param {boolean} on True to follow, false to unfollow.
|
|
96
|
+
* @param {object} [options] Options.
|
|
97
|
+
* @param {boolean} [options.notify] The notifications switch.
|
|
82
98
|
* @return {Promise<object>} The outcome (see `call`).
|
|
99
|
+
* @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-one-follow-control-with-a-notifications-switch
|
|
83
100
|
*/
|
|
84
|
-
function setWatching(register, schema, id, on) {
|
|
85
|
-
|
|
101
|
+
function setWatching(register, schema, id, on, options = {}) {
|
|
102
|
+
const body = on && options && typeof options.notify === 'boolean' ? { notify: options.notify } : null;
|
|
103
|
+
return call(on ? 'PUT' : 'DELETE', interactionUrl(register, schema, id, 'watch'), body)
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
/**
|