@conduction/nextcloud-vue 2.76.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.
Files changed (119) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +42 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +228 -203
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
  10. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
  11. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
  12. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
  13. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
  14. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +41 -4
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +16 -28
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  23. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
  24. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
  25. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  30. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
  31. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
  33. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +98 -13
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +13 -9
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
  39. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  44. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
  45. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  46. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  47. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  49. package/dist/esm/composables/useListView.js +4 -0
  50. package/dist/esm/composables/useListView.js.map +1 -1
  51. package/dist/esm/index.js +2 -1
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/l10n/en.json.js +25 -1
  54. package/dist/esm/l10n/en.json.js.map +1 -1
  55. package/dist/esm/l10n/nl.json.js +25 -1
  56. package/dist/esm/l10n/nl.json.js.map +1 -1
  57. package/dist/esm/nextcloud-vue.css +288 -74
  58. package/dist/esm/store/plugins/interactions.js +20 -10
  59. package/dist/esm/store/plugins/interactions.js.map +1 -1
  60. package/dist/esm/store/useObjectStore.js +17 -0
  61. package/dist/esm/store/useObjectStore.js.map +1 -1
  62. package/dist/esm/utils/builtInFormatters.js +15 -7
  63. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  64. package/dist/esm/utils/groupAutocomplete.js +63 -1
  65. package/dist/esm/utils/groupAutocomplete.js.map +1 -1
  66. package/dist/esm/utils/lensAvailability.js +90 -0
  67. package/dist/esm/utils/lensAvailability.js.map +1 -0
  68. package/dist/esm/utils/personalLenses.js +13 -6
  69. package/dist/esm/utils/personalLenses.js.map +1 -1
  70. package/dist/esm/utils/recordInteractions.js +31 -13
  71. package/dist/esm/utils/recordInteractions.js.map +1 -1
  72. package/dist/esm/utils/resolveFilterTokens.js +46 -3
  73. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  74. package/dist/esm/utils/schema.js +1 -1
  75. package/dist/esm/utils/schema.js.map +1 -1
  76. package/dist/esm/utils/sentinelTokens.js +4 -4
  77. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  78. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  79. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  80. package/dist/esm/utils/widgetVisibility.js +46 -1
  81. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  82. package/dist/nextcloud-vue.cjs.js +3873 -2674
  83. package/dist/nextcloud-vue.cjs.js.map +2 -2
  84. package/dist/nextcloud-vue.css +288 -74
  85. package/l10n/en.json +25 -1
  86. package/l10n/nl.json +25 -1
  87. package/package.json +1 -1
  88. package/src/components/CnAppNav/CnAppNav.vue +44 -5
  89. package/src/components/CnCellRenderer/CnCellRenderer.vue +56 -1
  90. package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
  91. package/src/components/CnDataTable/CnDataTable.md +9 -0
  92. package/src/components/CnDataTable/CnDataTable.vue +80 -20
  93. package/src/components/CnDetailPage/CnDetailPage.vue +10 -27
  94. package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
  95. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
  96. package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
  97. package/src/components/CnIndexPage/CnIndexPage.md +36 -0
  98. package/src/components/CnIndexPage/CnIndexPage.vue +106 -19
  99. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
  100. package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
  101. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  102. package/src/composables/useListView.js +4 -0
  103. package/src/css/look-board-detail.css +40 -2
  104. package/src/css/table.css +82 -17
  105. package/src/index.js +2 -1
  106. package/src/schemas/app-manifest-v2.schema.json +11 -7
  107. package/src/store/plugins/interactions.js +20 -10
  108. package/src/store/useObjectStore.js +17 -0
  109. package/src/utils/builtInFormatters.js +15 -7
  110. package/src/utils/groupAutocomplete.js +75 -0
  111. package/src/utils/index.js +1 -1
  112. package/src/utils/lensAvailability.js +86 -0
  113. package/src/utils/personalLenses.js +13 -6
  114. package/src/utils/recordInteractions.js +31 -13
  115. package/src/utils/resolveFilterTokens.js +45 -2
  116. package/src/utils/schema.js +1 -1
  117. package/src/utils/sentinelTokens.js +4 -4
  118. package/src/utils/validateManifestV2.compiled.js +2 -2
  119. package/src/utils/widgetVisibility.js +45 -0
@@ -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
  * Cached user groups — fetched once per session and reused.
@@ -12,6 +13,18 @@ const _cache = {
12
13
  promise: null,
13
14
  };
14
15
 
16
+ /**
17
+ * Reactive mirror of the group cache, keyed by user id, for synchronous
18
+ * readers ({@link peekCurrentUserGroups}). A computed that reads a missing key
19
+ * tracks it, so it re-runs when the groups arrive.
20
+ *
21
+ * @type {{ byUser: Record<string, string[]> }}
22
+ */
23
+ const _peek = reactive({ byUser: {} });
24
+
25
+ /** User ids whose groups a peek already asked for (not reactive on purpose). */
26
+ const _peekRequested = new Set();
27
+
15
28
  /**
16
29
  * Get the current Nextcloud user ID from OC.currentUser.
17
30
  *
@@ -59,6 +72,34 @@ async function getCurrentUserGroups() {
59
72
  return _cache.groups
60
73
  }
61
74
 
75
+ /**
76
+ * Synchronous, reactive read of the current user's group ids, for code that
77
+ * cannot await (the `@myGroups` filter token). Returns the ids once they are
78
+ * loaded, `null` while the first request is in flight (and starts that
79
+ * request on first use), and `[]` when nobody is signed in. Read inside a
80
+ * computed, the computed re-runs when the groups arrive.
81
+ *
82
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/schema-utilities/spec.md#requirement-mygroups-resolves-to-the-current-users-group-ids
83
+ * @return {string[]|null} The group ids, or null while loading.
84
+ */
85
+ function peekCurrentUserGroups() {
86
+ const userId = getCurrentUserId();
87
+ if (!userId) {
88
+ return []
89
+ }
90
+ const known = _peek.byUser[userId];
91
+ if (Array.isArray(known)) {
92
+ return known
93
+ }
94
+ if (!_peekRequested.has(userId)) {
95
+ _peekRequested.add(userId);
96
+ getCurrentUserGroups().then((groups) => {
97
+ _peek.byUser[userId] = Array.isArray(groups) ? [...groups] : [];
98
+ });
99
+ }
100
+ return null
101
+ }
102
+
62
103
  /**
63
104
  * Internal: fetch groups from OCS API.
64
105
  *
@@ -161,7 +202,11 @@ function resetVisibilityCache() {
161
202
  _cache.userId = null;
162
203
  _cache.groups = null;
163
204
  _cache.promise = null;
205
+ _peekRequested.clear();
206
+ for (const key of Object.keys(_peek.byUser)) {
207
+ delete _peek.byUser[key];
208
+ }
164
209
  }
165
210
 
166
- export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, resetVisibilityCache };
211
+ export { filterWidgetsByVisibility, getCurrentUserGroups, getCurrentUserId, isWidgetVisible, peekCurrentUserGroups, resetVisibilityCache };
167
212
  //# sourceMappingURL=widgetVisibility.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgetVisibility.js","sources":["../../../src/utils/widgetVisibility.js"],"sourcesContent":["import axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * Cached user groups — fetched once per session and reused.\n *\n * @type {{ userId: string|null, groups: string[]|null, promise: Promise|null }}\n */\nconst _cache = {\n\tuserId: null,\n\tgroups: null,\n\tpromise: null,\n}\n\n/**\n * Get the current Nextcloud user ID from OC.currentUser.\n *\n * @return {string|null} The current user ID, or null if not logged in\n */\nexport function getCurrentUserId() {\n\treturn window.OC?.currentUser?.uid\n\t\t|| window.OC?.currentUser\n\t\t|| null\n}\n\n/**\n * Fetch the current user's Nextcloud groups. Results are cached so the\n * OCS API is only called once per page load.\n *\n * @return {Promise<string[]>} Array of group IDs the current user belongs to\n */\nexport async function getCurrentUserGroups() {\n\tconst userId = getCurrentUserId()\n\tif (!userId) {\n\t\treturn []\n\t}\n\n\t// Return cached result if we already fetched for this user\n\tif (_cache.userId === userId && _cache.groups !== null) {\n\t\treturn _cache.groups\n\t}\n\n\t// If a fetch is already in progress for this user, await it\n\tif (_cache.userId === userId && _cache.promise) {\n\t\treturn _cache.promise\n\t}\n\n\t_cache.userId = userId\n\t_cache.promise = _fetchGroups(userId)\n\n\ttry {\n\t\t_cache.groups = await _cache.promise\n\t} catch {\n\t\t_cache.groups = []\n\t} finally {\n\t\t_cache.promise = null\n\t}\n\n\treturn _cache.groups\n}\n\n/**\n * Internal: fetch groups from OCS API.\n *\n * @param {string} userId The user ID to look up\n * @return {Promise<string[]>} Array of group IDs\n */\nasync function _fetchGroups(userId) {\n\ttry {\n\t\tconst url = generateOcsUrl('/cloud/users/{userId}/groups', { userId })\n\t\tconst response = await axios.get(url)\n\t\tconst groups = response.data?.ocs?.data?.groups || []\n\t\treturn groups\n\t} catch (error) {\n\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\tconsole.error('[widgetVisibility] Failed to fetch user groups:', error)\n\t\treturn []\n\t}\n}\n\n/**\n * Check whether a single widget is visible to the current user based on\n * its `visibility` configuration.\n *\n * Visibility rules:\n * - No `visibility` property -> visible to everyone\n * - `visibility.users` contains the current user ID -> visible\n * - `visibility.groups` overlaps with the user's groups -> visible\n * - Both `users` and `groups` are specified -> either match grants access (OR logic)\n * - Both are empty arrays -> visible to everyone\n *\n * @param {object} widget Widget definition object\n * @param {object} [widget.visibility] Optional visibility configuration\n * @param {string[]} [widget.visibility.users] User IDs who can see this widget\n * @param {string[]} [widget.visibility.groups] Group names who can see this widget\n * @param {string|null} userId Current user ID\n * @param {string[]} userGroups Current user's group memberships\n * @return {boolean} Whether the widget should be visible\n */\nexport function isWidgetVisible(widget, userId, userGroups) {\n\tconst visibility = widget?.visibility\n\tif (!visibility) {\n\t\treturn true\n\t}\n\n\tconst allowedUsers = visibility.users || []\n\tconst allowedGroups = visibility.groups || []\n\n\t// If both are empty, visible to everyone\n\tif (allowedUsers.length === 0 && allowedGroups.length === 0) {\n\t\treturn true\n\t}\n\n\t// Check user match\n\tif (allowedUsers.length > 0 && userId && allowedUsers.includes(userId)) {\n\t\treturn true\n\t}\n\n\t// Check group match\n\tif (allowedGroups.length > 0 && userGroups.length > 0) {\n\t\tconst hasGroupMatch = allowedGroups.some((group) => userGroups.includes(group))\n\t\tif (hasGroupMatch) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Filter an array of widget definitions by visibility for the current user.\n *\n * This is an async function because it may need to fetch the user's groups\n * from the OCS API (cached after first call).\n *\n * @param {Array} widgets Array of widget definition objects\n * @return {Promise<Array>} Filtered array of visible widgets\n */\nexport async function filterWidgetsByVisibility(widgets) {\n\tif (!widgets || widgets.length === 0) {\n\t\treturn []\n\t}\n\n\t// Quick path: if no widgets have visibility config, return all\n\tconst hasVisibilityConfig = widgets.some((w) => w.visibility)\n\tif (!hasVisibilityConfig) {\n\t\treturn widgets\n\t}\n\n\tconst userId = getCurrentUserId()\n\tconst userGroups = await getCurrentUserGroups()\n\n\treturn widgets.filter((widget) => isWidgetVisible(widget, userId, userGroups))\n}\n\n/**\n * Reset the internal groups cache. Useful for testing or when user\n * context changes.\n */\nexport function resetVisibilityCache() {\n\t_cache.userId = null\n\t_cache.groups = null\n\t_cache.promise = null\n}\n"],"names":["axios"],"mappings":"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"}
1
+ {"version":3,"file":"widgetVisibility.js","sources":["../../../src/utils/widgetVisibility.js"],"sourcesContent":["import axios from '@nextcloud/axios'\nimport { generateOcsUrl } from '@nextcloud/router'\nimport { reactive } from 'vue'\n\n/**\n * Cached user groups — fetched once per session and reused.\n *\n * @type {{ userId: string|null, groups: string[]|null, promise: Promise|null }}\n */\nconst _cache = {\n\tuserId: null,\n\tgroups: null,\n\tpromise: null,\n}\n\n/**\n * Reactive mirror of the group cache, keyed by user id, for synchronous\n * readers ({@link peekCurrentUserGroups}). A computed that reads a missing key\n * tracks it, so it re-runs when the groups arrive.\n *\n * @type {{ byUser: Record<string, string[]> }}\n */\nconst _peek = reactive({ byUser: {} })\n\n/** User ids whose groups a peek already asked for (not reactive on purpose). */\nconst _peekRequested = new Set()\n\n/**\n * Get the current Nextcloud user ID from OC.currentUser.\n *\n * @return {string|null} The current user ID, or null if not logged in\n */\nexport function getCurrentUserId() {\n\treturn window.OC?.currentUser?.uid\n\t\t|| window.OC?.currentUser\n\t\t|| null\n}\n\n/**\n * Fetch the current user's Nextcloud groups. Results are cached so the\n * OCS API is only called once per page load.\n *\n * @return {Promise<string[]>} Array of group IDs the current user belongs to\n */\nexport async function getCurrentUserGroups() {\n\tconst userId = getCurrentUserId()\n\tif (!userId) {\n\t\treturn []\n\t}\n\n\t// Return cached result if we already fetched for this user\n\tif (_cache.userId === userId && _cache.groups !== null) {\n\t\treturn _cache.groups\n\t}\n\n\t// If a fetch is already in progress for this user, await it\n\tif (_cache.userId === userId && _cache.promise) {\n\t\treturn _cache.promise\n\t}\n\n\t_cache.userId = userId\n\t_cache.promise = _fetchGroups(userId)\n\n\ttry {\n\t\t_cache.groups = await _cache.promise\n\t} catch {\n\t\t_cache.groups = []\n\t} finally {\n\t\t_cache.promise = null\n\t}\n\n\treturn _cache.groups\n}\n\n/**\n * Synchronous, reactive read of the current user's group ids, for code that\n * cannot await (the `@myGroups` filter token). Returns the ids once they are\n * loaded, `null` while the first request is in flight (and starts that\n * request on first use), and `[]` when nobody is signed in. Read inside a\n * computed, the computed re-runs when the groups arrive.\n *\n * @spec openspec/changes/nextcloud-group-surfaces/specs/schema-utilities/spec.md#requirement-mygroups-resolves-to-the-current-users-group-ids\n * @return {string[]|null} The group ids, or null while loading.\n */\nexport function peekCurrentUserGroups() {\n\tconst userId = getCurrentUserId()\n\tif (!userId) {\n\t\treturn []\n\t}\n\tconst known = _peek.byUser[userId]\n\tif (Array.isArray(known)) {\n\t\treturn known\n\t}\n\tif (!_peekRequested.has(userId)) {\n\t\t_peekRequested.add(userId)\n\t\tgetCurrentUserGroups().then((groups) => {\n\t\t\t_peek.byUser[userId] = Array.isArray(groups) ? [...groups] : []\n\t\t})\n\t}\n\treturn null\n}\n\n/**\n * Internal: fetch groups from OCS API.\n *\n * @param {string} userId The user ID to look up\n * @return {Promise<string[]>} Array of group IDs\n */\nasync function _fetchGroups(userId) {\n\ttry {\n\t\tconst url = generateOcsUrl('/cloud/users/{userId}/groups', { userId })\n\t\tconst response = await axios.get(url)\n\t\tconst groups = response.data?.ocs?.data?.groups || []\n\t\treturn groups\n\t} catch (error) {\n\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\tconsole.error('[widgetVisibility] Failed to fetch user groups:', error)\n\t\treturn []\n\t}\n}\n\n/**\n * Check whether a single widget is visible to the current user based on\n * its `visibility` configuration.\n *\n * Visibility rules:\n * - No `visibility` property -> visible to everyone\n * - `visibility.users` contains the current user ID -> visible\n * - `visibility.groups` overlaps with the user's groups -> visible\n * - Both `users` and `groups` are specified -> either match grants access (OR logic)\n * - Both are empty arrays -> visible to everyone\n *\n * @param {object} widget Widget definition object\n * @param {object} [widget.visibility] Optional visibility configuration\n * @param {string[]} [widget.visibility.users] User IDs who can see this widget\n * @param {string[]} [widget.visibility.groups] Group names who can see this widget\n * @param {string|null} userId Current user ID\n * @param {string[]} userGroups Current user's group memberships\n * @return {boolean} Whether the widget should be visible\n */\nexport function isWidgetVisible(widget, userId, userGroups) {\n\tconst visibility = widget?.visibility\n\tif (!visibility) {\n\t\treturn true\n\t}\n\n\tconst allowedUsers = visibility.users || []\n\tconst allowedGroups = visibility.groups || []\n\n\t// If both are empty, visible to everyone\n\tif (allowedUsers.length === 0 && allowedGroups.length === 0) {\n\t\treturn true\n\t}\n\n\t// Check user match\n\tif (allowedUsers.length > 0 && userId && allowedUsers.includes(userId)) {\n\t\treturn true\n\t}\n\n\t// Check group match\n\tif (allowedGroups.length > 0 && userGroups.length > 0) {\n\t\tconst hasGroupMatch = allowedGroups.some((group) => userGroups.includes(group))\n\t\tif (hasGroupMatch) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Filter an array of widget definitions by visibility for the current user.\n *\n * This is an async function because it may need to fetch the user's groups\n * from the OCS API (cached after first call).\n *\n * @param {Array} widgets Array of widget definition objects\n * @return {Promise<Array>} Filtered array of visible widgets\n */\nexport async function filterWidgetsByVisibility(widgets) {\n\tif (!widgets || widgets.length === 0) {\n\t\treturn []\n\t}\n\n\t// Quick path: if no widgets have visibility config, return all\n\tconst hasVisibilityConfig = widgets.some((w) => w.visibility)\n\tif (!hasVisibilityConfig) {\n\t\treturn widgets\n\t}\n\n\tconst userId = getCurrentUserId()\n\tconst userGroups = await getCurrentUserGroups()\n\n\treturn widgets.filter((widget) => isWidgetVisible(widget, userId, userGroups))\n}\n\n/**\n * Reset the internal groups cache. Useful for testing or when user\n * context changes.\n */\nexport function resetVisibilityCache() {\n\t_cache.userId = null\n\t_cache.groups = null\n\t_cache.promise = null\n\t_peekRequested.clear()\n\tfor (const key of Object.keys(_peek.byUser)) {\n\t\tdelete _peek.byUser[key]\n\t}\n}\n"],"names":["axios"],"mappings":"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"}