@antelopejs/dms 0.6.2 → 0.7.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 (444) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/db/models/notification-feed-filter.d.ts +0 -1
  3. package/dist/db/models/notification-feed-filter.js +0 -4
  4. package/dist/db/models/notification-feed-filter.js.map +1 -1
  5. package/dist/db/models/signInAttempts.model.d.ts +18 -0
  6. package/dist/db/models/signInAttempts.model.js +83 -12
  7. package/dist/db/models/signInAttempts.model.js.map +1 -1
  8. package/dist/db/models/userEmailChanges.model.d.ts +8 -1
  9. package/dist/db/models/userEmailChanges.model.js +27 -2
  10. package/dist/db/models/userEmailChanges.model.js.map +1 -1
  11. package/dist/db/models/userNotificationPreferences.model.d.ts +0 -2
  12. package/dist/db/models/userNotificationPreferences.model.js +0 -13
  13. package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
  14. package/dist/db/models/userNotifications.model.d.ts +5 -2
  15. package/dist/db/models/userNotifications.model.js +19 -2
  16. package/dist/db/models/userNotifications.model.js.map +1 -1
  17. package/dist/db/tables/signInAttempts.table.d.ts +2 -1
  18. package/dist/db/tables/signInAttempts.table.js +4 -0
  19. package/dist/db/tables/signInAttempts.table.js.map +1 -1
  20. package/dist/db/tables/userEmailChanges.table.d.ts +2 -1
  21. package/dist/db/tables/userEmailChanges.table.js +6 -1
  22. package/dist/db/tables/userEmailChanges.table.js.map +1 -1
  23. package/dist/db/tables/userNotificationPreferences.table.d.ts +0 -1
  24. package/dist/db/tables/userNotificationPreferences.table.js +0 -4
  25. package/dist/db/tables/userNotificationPreferences.table.js.map +1 -1
  26. package/dist/implementations/dms/nav-badges.d.ts +6 -4
  27. package/dist/implementations/dms/nav-badges.js +6 -5
  28. package/dist/implementations/dms/nav-badges.js.map +1 -1
  29. package/dist/implementations/dms/page.d.ts +1 -0
  30. package/dist/implementations/dms/page.js +46 -8
  31. package/dist/implementations/dms/page.js.map +1 -1
  32. package/dist/implementations/dms/permission-preview.d.ts +1 -1
  33. package/dist/implementations/dms/permission-preview.js +9 -4
  34. package/dist/implementations/dms/permission-preview.js.map +1 -1
  35. package/dist/implementations/dms-auth/index.d.ts +3 -2
  36. package/dist/implementations/dms-auth/index.js +39 -35
  37. package/dist/implementations/dms-auth/index.js.map +1 -1
  38. package/dist/implementations/dms-auth/token-session.d.ts +3 -0
  39. package/dist/implementations/dms-auth/token-session.js +22 -0
  40. package/dist/implementations/dms-auth/token-session.js.map +1 -0
  41. package/dist/implementations/dms-notifications/index.d.ts +0 -1
  42. package/dist/implementations/dms-notifications/index.js +0 -7
  43. package/dist/implementations/dms-notifications/index.js.map +1 -1
  44. package/dist/pages/settings/settings-index.js +3 -2
  45. package/dist/pages/settings/settings-index.js.map +1 -1
  46. package/dist/pages/settings/users/account-activity-feed.d.ts +1 -1
  47. package/dist/pages/settings/users/account-activity-feed.js +1 -1
  48. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  49. package/dist/pages/settings/users/account-activity.d.ts +1 -1
  50. package/dist/pages/settings/users/account-activity.js +1 -1
  51. package/dist/pages/settings/users/account-activity.js.map +1 -1
  52. package/dist/pages/settings/users/account-credentials.d.ts +3 -0
  53. package/dist/pages/settings/users/account-credentials.js +2 -0
  54. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  55. package/dist/pages/settings/users/current-password.d.ts +1 -1
  56. package/dist/pages/settings/users/current-password.js +23 -2
  57. package/dist/pages/settings/users/current-password.js.map +1 -1
  58. package/dist/pages/settings/users/email-change.d.ts +1 -1
  59. package/dist/pages/settings/users/email-change.js +33 -26
  60. package/dist/pages/settings/users/email-change.js.map +1 -1
  61. package/dist/pages/settings/users/invite-resend.js +0 -1
  62. package/dist/pages/settings/users/invite-resend.js.map +1 -1
  63. package/dist/pages/settings/users/invites.d.ts +1 -2
  64. package/dist/pages/settings/users/invites.js +21 -19
  65. package/dist/pages/settings/users/invites.js.map +1 -1
  66. package/dist/pages/settings/users/member-invite-batch.js +0 -1
  67. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  68. package/dist/pages/settings/users/member-invite-form.js +10 -22
  69. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  70. package/dist/pages/settings/users/member-role-options.d.ts +6 -4
  71. package/dist/pages/settings/users/member-role-options.js +15 -4
  72. package/dist/pages/settings/users/member-role-options.js.map +1 -1
  73. package/dist/pages/settings/users/members.d.ts +2 -3
  74. package/dist/pages/settings/users/members.js +7 -12
  75. package/dist/pages/settings/users/members.js.map +1 -1
  76. package/dist/pages/settings/users/notifications.d.ts +1 -8
  77. package/dist/pages/settings/users/notifications.js +6 -38
  78. package/dist/pages/settings/users/notifications.js.map +1 -1
  79. package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
  80. package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
  81. package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
  82. package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
  83. package/dist/pages/settings/users/permission-warnings.js +42 -0
  84. package/dist/pages/settings/users/permission-warnings.js.map +1 -0
  85. package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
  86. package/dist/pages/settings/users/profile-helpers.js +0 -16
  87. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  88. package/dist/pages/settings/users/role-editor-store.js +6 -3
  89. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  90. package/dist/pages/settings/users/role-editor.d.ts +2 -1
  91. package/dist/pages/settings/users/role-editor.js +8 -4
  92. package/dist/pages/settings/users/role-editor.js.map +1 -1
  93. package/dist/pages/settings/users/role-preview.js +2 -1
  94. package/dist/pages/settings/users/role-preview.js.map +1 -1
  95. package/dist/pages/settings/users/roles.js +5 -1
  96. package/dist/pages/settings/users/roles.js.map +1 -1
  97. package/dist/pages/settings/users/security-attention.d.ts +4 -0
  98. package/dist/pages/settings/users/security-attention.js +23 -7
  99. package/dist/pages/settings/users/security-attention.js.map +1 -1
  100. package/dist/pages/settings/users/security.d.ts +2 -1
  101. package/dist/pages/settings/users/security.js +13 -2
  102. package/dist/pages/settings/users/security.js.map +1 -1
  103. package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
  104. package/dist/pages/settings/users/two-factor-operations.js +28 -29
  105. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  106. package/dist/routes/auth/forgot-password.js +3 -0
  107. package/dist/routes/auth/forgot-password.js.map +1 -1
  108. package/dist/routes/auth/index.d.ts +1 -0
  109. package/dist/routes/auth/index.js +1 -0
  110. package/dist/routes/auth/index.js.map +1 -1
  111. package/dist/routes/auth/invite.js +28 -4
  112. package/dist/routes/auth/invite.js.map +1 -1
  113. package/dist/routes/auth/login-throttle.d.ts +2 -0
  114. package/dist/routes/auth/login-throttle.js +5 -4
  115. package/dist/routes/auth/login-throttle.js.map +1 -1
  116. package/dist/routes/auth/request-2fa-email.js +6 -18
  117. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  118. package/dist/routes/auth/reset-code.d.ts +3 -0
  119. package/dist/routes/auth/reset-code.js +37 -0
  120. package/dist/routes/auth/reset-code.js.map +1 -0
  121. package/dist/routes/auth/reset-password.d.ts +2 -2
  122. package/dist/routes/auth/reset-password.js +5 -10
  123. package/dist/routes/auth/reset-password.js.map +1 -1
  124. package/dist/routes/auth/signup.js +10 -32
  125. package/dist/routes/auth/signup.js.map +1 -1
  126. package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
  127. package/dist/routes/auth/two-factor-throttle.js +81 -0
  128. package/dist/routes/auth/two-factor-throttle.js.map +1 -0
  129. package/dist/routes/auth/validate-forgot-password-token.js +2 -8
  130. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  131. package/dist/routes/auth/validate-invite-token.d.ts +1 -0
  132. package/dist/routes/auth/validate-invite-token.js +11 -0
  133. package/dist/routes/auth/validate-invite-token.js.map +1 -0
  134. package/dist/routes/auth/verify-2fa.js +10 -13
  135. package/dist/routes/auth/verify-2fa.js.map +1 -1
  136. package/dist/routes/auth.d.ts +1 -0
  137. package/dist/routes/auth.js +11 -1
  138. package/dist/routes/auth.js.map +1 -1
  139. package/dist/test/helpers/auth.d.ts +0 -1
  140. package/dist/test/helpers/auth.js +0 -1
  141. package/dist/test/helpers/auth.js.map +1 -1
  142. package/dist/test/helpers/emails.d.ts +10 -0
  143. package/dist/test/helpers/emails.js +36 -0
  144. package/dist/test/helpers/emails.js.map +1 -0
  145. package/dist/test/helpers/fixtures.d.ts +0 -1
  146. package/dist/test/helpers/fixtures.js +1 -2
  147. package/dist/test/helpers/fixtures.js.map +1 -1
  148. package/dist/test/helpers/page-access.d.ts +1 -0
  149. package/dist/test/helpers/page-access.js +6 -1
  150. package/dist/test/helpers/page-access.js.map +1 -1
  151. package/dist/test/integration/auth/current-password.test.d.ts +1 -0
  152. package/dist/test/integration/auth/current-password.test.js +65 -0
  153. package/dist/test/integration/auth/current-password.test.js.map +1 -0
  154. package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
  155. package/dist/test/integration/auth/password-reset.test.js +126 -0
  156. package/dist/test/integration/auth/password-reset.test.js.map +1 -0
  157. package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
  158. package/dist/test/integration/auth/session-revocation.test.js +45 -0
  159. package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
  160. package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
  161. package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
  162. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
  163. package/dist/test/integration/auth/two-factor-password.test.js +5 -1
  164. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  165. package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
  166. package/dist/test/integration/auth/two-factor-token.test.js +92 -0
  167. package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
  168. package/dist/test/integration/email-change.test.js +36 -0
  169. package/dist/test/integration/email-change.test.js.map +1 -1
  170. package/dist/test/integration/invite-extension-edit.test.js +0 -1
  171. package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
  172. package/dist/test/integration/invite-link-status.test.d.ts +1 -0
  173. package/dist/test/integration/invite-link-status.test.js +110 -0
  174. package/dist/test/integration/invite-link-status.test.js.map +1 -0
  175. package/dist/test/integration/invite-resolution.test.js +0 -3
  176. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  177. package/dist/test/integration/member-settings-access.test.js +17 -0
  178. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  179. package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
  180. package/dist/test/integration/notification-unread-badge.test.js +92 -0
  181. package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
  182. package/dist/test/integration/permission-warnings.test.d.ts +1 -0
  183. package/dist/test/integration/permission-warnings.test.js +71 -0
  184. package/dist/test/integration/permission-warnings.test.js.map +1 -0
  185. package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
  186. package/dist/test/integration/removed-member-notifications.test.js +80 -0
  187. package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
  188. package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
  189. package/dist/test/integration/role-permission-ancestors.test.js +146 -0
  190. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
  191. package/dist/test/integration/tenant-lifecycle.test.js +0 -4
  192. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  193. package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
  194. package/dist/test/unit/auth-session-revocation.test.js +105 -0
  195. package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
  196. package/dist/test/unit/db/email-normalization.test.js +0 -1
  197. package/dist/test/unit/db/email-normalization.test.js.map +1 -1
  198. package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
  199. package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
  200. package/dist/test/unit/db/notification-inbox.test.js +63 -13
  201. package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
  202. package/dist/test/unit/db/notification-preferences.test.js +0 -17
  203. package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
  204. package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
  205. package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
  206. package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
  207. package/dist/test/unit/implementations/dms/layout-banners.test.js +2 -1
  208. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
  209. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
  210. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
  211. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
  212. package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
  213. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  214. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
  215. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
  216. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
  217. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
  218. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
  219. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
  220. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
  221. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
  222. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
  223. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
  224. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
  225. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
  226. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
  227. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
  228. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
  229. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
  230. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
  231. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
  232. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
  233. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
  234. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
  235. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
  236. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
  237. package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
  238. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  239. package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
  240. package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
  241. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
  242. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
  243. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
  244. package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
  245. package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
  246. package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
  247. package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
  248. package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
  249. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
  250. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  251. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
  252. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
  253. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
  254. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
  255. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
  256. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  257. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
  258. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
  259. package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
  260. package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
  261. package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
  262. package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
  263. package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
  264. package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
  265. package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
  266. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
  267. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
  268. package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
  269. package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
  270. package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
  271. package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
  272. package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
  273. package/dist/test/unit/pages/settings/members-page.test.js +7 -10
  274. package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
  275. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
  276. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
  277. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
  278. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
  279. package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
  280. package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
  281. package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
  282. package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
  283. package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
  284. package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
  285. package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
  286. package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
  287. package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
  288. package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
  289. package/dist/test/unit/sanitize-user.test.d.ts +1 -0
  290. package/dist/test/unit/sanitize-user.test.js +21 -0
  291. package/dist/test/unit/sanitize-user.test.js.map +1 -0
  292. package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
  293. package/dist/test/unit/signup-email-validation.test.js +79 -0
  294. package/dist/test/unit/signup-email-validation.test.js.map +1 -0
  295. package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
  296. package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
  297. package/dist/utils/account-notifications.d.ts +5 -0
  298. package/dist/utils/account-notifications.js +8 -0
  299. package/dist/utils/account-notifications.js.map +1 -1
  300. package/dist/utils/notification-emitter.d.ts +1 -1
  301. package/dist/utils/notification-emitter.js +1 -1
  302. package/dist/utils/notification-emitter.js.map +1 -1
  303. package/dist/utils/notification-tones.d.ts +2 -1
  304. package/dist/utils/notification-tones.js +5 -1
  305. package/dist/utils/notification-tones.js.map +1 -1
  306. package/dist/utils/one-time-code.d.ts +3 -0
  307. package/dist/utils/one-time-code.js +21 -0
  308. package/dist/utils/one-time-code.js.map +1 -0
  309. package/dist/utils/sign-in-monitor.d.ts +1 -0
  310. package/dist/utils/sign-in-monitor.js +3 -2
  311. package/dist/utils/sign-in-monitor.js.map +1 -1
  312. package/dist/utils/two-factor-codes.d.ts +10 -0
  313. package/dist/utils/two-factor-codes.js +76 -0
  314. package/dist/utils/two-factor-codes.js.map +1 -0
  315. package/dist/validation/auth.schema.d.ts +17 -0
  316. package/dist/validation/auth.schema.js +17 -6
  317. package/dist/validation/auth.schema.js.map +1 -1
  318. package/dist/validation/issue-message.d.ts +1 -0
  319. package/dist/validation/issue-message.js +9 -0
  320. package/dist/validation/issue-message.js.map +1 -0
  321. package/dist/validation/member-invite.schema.d.ts +1 -25
  322. package/dist/validation/member-invite.schema.js +2 -23
  323. package/dist/validation/member-invite.schema.js.map +1 -1
  324. package/frontend-vue/dms.frontend.ts +2 -0
  325. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
  326. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
  327. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
  328. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
  329. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
  330. package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
  331. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
  332. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
  333. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
  334. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
  335. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
  336. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
  337. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
  338. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
  339. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
  340. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
  341. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
  342. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
  343. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
  344. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
  345. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
  346. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
  347. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
  348. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
  349. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
  350. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
  351. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
  352. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
  353. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
  354. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
  355. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
  356. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
  357. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
  358. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
  359. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
  360. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
  361. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
  362. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
  363. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
  364. package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
  365. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
  366. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
  367. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
  368. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
  369. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
  370. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
  371. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
  372. package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
  373. package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
  374. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
  375. package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
  376. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
  377. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
  378. package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
  379. package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
  380. package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
  381. package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
  382. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
  383. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
  384. package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
  385. package/frontend-vue/layers/dms-layout/app/types/page.ts +4 -0
  386. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
  387. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
  388. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
  389. package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
  390. package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
  391. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
  392. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
  393. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
  394. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
  395. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
  396. package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
  397. package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
  398. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
  399. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
  400. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
  401. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
  402. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
  403. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  404. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
  405. package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
  406. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
  407. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
  408. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
  409. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
  410. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
  411. package/frontend-vue/package.json +2 -2
  412. package/frontend-vue/pnpm-lock.yaml +5 -5
  413. package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
  414. package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
  415. package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
  416. package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
  417. package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
  418. package/frontend-vue/tests/form-display-options.test.ts +64 -0
  419. package/frontend-vue/tests/form-load-error.test.ts +160 -0
  420. package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
  421. package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
  422. package/frontend-vue/tests/header-actions.test.ts +191 -0
  423. package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
  424. package/frontend-vue/tests/member-role-picker.test.ts +26 -0
  425. package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
  426. package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
  427. package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
  428. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
  429. package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
  430. package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
  431. package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
  432. package/frontend-vue/tests/page-fill-height.test.ts +9 -1
  433. package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
  434. package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
  435. package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
  436. package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
  437. package/frontend-vue/tests/role-permissions.test.ts +178 -56
  438. package/frontend-vue/tests/side-panels.test.ts +470 -0
  439. package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
  440. package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
  441. package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
  442. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  443. package/package.json +1 -1
  444. package/skills/dms/REFERENCE.md +1 -1
@@ -0,0 +1,249 @@
1
+ import type { CommandPaletteGroup } from "@nuxt/ui";
2
+ import { computed, ref, watch, type ComputedRef, type Ref } from "vue";
3
+ import type { I18nTranslate } from "#dms-core/app/composables/translation/useTranslation";
4
+ import {
5
+ appendAssistantAskGroup,
6
+ buildAssistantModeGroups,
7
+ type ResolvedAssistantSuggestion,
8
+ } from "./assistantGroups";
9
+
10
+ /** @internal */
11
+ export type CommandPaletteMode = "search" | "assistant";
12
+
13
+ /** @internal One submitted prompt; `id` changes on every submission. */
14
+ export interface CommandPaletteSubmission {
15
+ id: number;
16
+ prompt: string;
17
+ }
18
+
19
+ /** @internal The registered assistant's fields, resolved for display. */
20
+ export interface ResolvedCommandPaletteAssistant {
21
+ label: string;
22
+ icon: string;
23
+ placeholder: string;
24
+ answerComponent: string;
25
+ suggestions: ResolvedAssistantSuggestion[];
26
+ }
27
+
28
+ interface PaletteGroupsContext {
29
+ assistant: ResolvedCommandPaletteAssistant | undefined;
30
+ isAssistantMode: boolean;
31
+ hasAnswer: boolean;
32
+ prompt: string;
33
+ translate: I18nTranslate;
34
+ submit: (prompt: string) => void;
35
+ }
36
+
37
+ const MODE_ANNOUNCEMENT_KEYS: Record<CommandPaletteMode, string> = {
38
+ search: "commandPalette.assistant.modeSearch",
39
+ assistant: "commandPalette.assistant.modeAssistant",
40
+ };
41
+
42
+ const OTHER_MODE: Record<CommandPaletteMode, CommandPaletteMode> = {
43
+ search: "assistant",
44
+ assistant: "search",
45
+ };
46
+
47
+ /**
48
+ * Tab without a modifier: Shift+Tab and the browser's own combinations keep
49
+ * moving focus, so the dialog's keyboard navigation stays reachable.
50
+ *
51
+ * @internal
52
+ */
53
+ export function isModeToggleKey(event: KeyboardEvent): boolean {
54
+ return (
55
+ event.key === "Tab" &&
56
+ !event.shiftKey &&
57
+ !event.altKey &&
58
+ !event.ctrlKey &&
59
+ !event.metaKey &&
60
+ !event.isComposing
61
+ );
62
+ }
63
+
64
+ function createModeToggleHandler(
65
+ isEnabled: () => boolean,
66
+ toggle: () => void,
67
+ ): (event: KeyboardEvent) => void {
68
+ return (event) => {
69
+ if (!isEnabled() || !isModeToggleKey(event)) return;
70
+ // The dialog's focus trap listens further up and would move focus.
71
+ event.preventDefault();
72
+ event.stopPropagation();
73
+ toggle();
74
+ };
75
+ }
76
+
77
+ function useResolvedAssistant(): ComputedRef<
78
+ ResolvedCommandPaletteAssistant | undefined
79
+ > {
80
+ const { assistant } = useCommandPaletteAssistant();
81
+ const { t } = useI18n();
82
+ const { processI18n } = useTranslation();
83
+
84
+ return computed(() => {
85
+ const entry = assistant.value;
86
+ if (!entry) return undefined;
87
+
88
+ return {
89
+ label: processI18n(entry.label),
90
+ icon: entry.icon,
91
+ placeholder: entry.placeholder
92
+ ? processI18n(entry.placeholder)
93
+ : t("commandPalette.assistant.placeholder"),
94
+ answerComponent: entry.answerComponent,
95
+ suggestions: entry.suggestions().map((suggestion) => {
96
+ const label = processI18n(suggestion.label);
97
+ return {
98
+ label,
99
+ prompt: suggestion.prompt ?? label,
100
+ icon: suggestion.icon ?? entry.icon,
101
+ };
102
+ }),
103
+ };
104
+ });
105
+ }
106
+
107
+ function useAssistantSession(
108
+ isOpen: Ref<boolean>,
109
+ searchTerm: Ref<string>,
110
+ hasAssistant: ComputedRef<boolean>,
111
+ ) {
112
+ const { t } = useI18n();
113
+ const mode = ref<CommandPaletteMode>("search");
114
+ const submission = ref<CommandPaletteSubmission | null>(null);
115
+ const announcement = ref("");
116
+ let submissionCount = 0;
117
+
118
+ function setMode(next: CommandPaletteMode): void {
119
+ if (!hasAssistant.value) return;
120
+ mode.value = next;
121
+ submission.value = null;
122
+ announcement.value = t(MODE_ANNOUNCEMENT_KEYS[next]);
123
+ }
124
+
125
+ function submit(prompt: string): void {
126
+ const trimmed = prompt.trim();
127
+ if (!trimmed || !hasAssistant.value) return;
128
+ mode.value = "assistant";
129
+ searchTerm.value = trimmed;
130
+ submissionCount += 1;
131
+ submission.value = { id: submissionCount, prompt: trimmed };
132
+ }
133
+
134
+ watch(isOpen, (open) => {
135
+ if (open) return;
136
+ mode.value = "search";
137
+ submission.value = null;
138
+ announcement.value = "";
139
+ });
140
+ const onInputKeydown = createModeToggleHandler(
141
+ () => hasAssistant.value,
142
+ () => setMode(OTHER_MODE[mode.value]),
143
+ );
144
+
145
+ return { mode, submission, announcement, setMode, submit, onInputKeydown };
146
+ }
147
+
148
+ /** The submission whose answer shows: gone as soon as the prompt is edited. */
149
+ function useShownAnswer(
150
+ isAssistantMode: ComputedRef<boolean>,
151
+ submission: Ref<CommandPaletteSubmission | null>,
152
+ searchTerm: Ref<string>,
153
+ ): ComputedRef<CommandPaletteSubmission | null> {
154
+ return computed(() =>
155
+ isAssistantMode.value &&
156
+ submission.value?.prompt === searchTerm.value.trim()
157
+ ? submission.value
158
+ : null,
159
+ );
160
+ }
161
+
162
+ function assistantModeGroups(
163
+ assistant: ResolvedCommandPaletteAssistant,
164
+ context: PaletteGroupsContext,
165
+ ): CommandPaletteGroup[] {
166
+ if (context.hasAnswer) return [];
167
+
168
+ return buildAssistantModeGroups(context.prompt, {
169
+ icon: assistant.icon,
170
+ promptLabel: assistant.label,
171
+ suggestionsLabel: context.translate(
172
+ "commandPalette.assistant.suggestions",
173
+ {},
174
+ ),
175
+ sendLabel: (prompt) =>
176
+ context.translate("commandPalette.assistant.send", { prompt }),
177
+ suggestions: assistant.suggestions,
178
+ onSubmit: context.submit,
179
+ });
180
+ }
181
+
182
+ /**
183
+ * The groups the palette renders: the sources' groups untouched while no
184
+ * assistant is registered, the same groups followed by the "Ask the
185
+ * assistant" item once a search is typed, and the prompt and suggestions in
186
+ * assistant mode — none once an answer is shown, which the empty slot renders.
187
+ *
188
+ * @internal
189
+ */
190
+ export function buildPaletteGroups(
191
+ sourceGroups: CommandPaletteGroup[],
192
+ context: PaletteGroupsContext,
193
+ ): CommandPaletteGroup[] {
194
+ const { assistant } = context;
195
+ if (!assistant) return sourceGroups;
196
+ if (context.isAssistantMode) return assistantModeGroups(assistant, context);
197
+
198
+ return appendAssistantAskGroup(sourceGroups, context.prompt, {
199
+ label: assistant.label,
200
+ icon: assistant.icon,
201
+ askLabel: (query) =>
202
+ context.translate("commandPalette.assistant.ask", { query }),
203
+ onAsk: context.submit,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * State of the command palette's assistant mode: the mode, the groups shown
209
+ * for it, the submitted prompt, and the Tab toggle bound to the input. Every
210
+ * piece degrades to the plain search palette while no assistant is registered.
211
+ *
212
+ * @internal
213
+ */
214
+ export function useCommandPaletteAssistantMode(
215
+ searchGroups: Ref<CommandPaletteGroup[]>,
216
+ ) {
217
+ const { t } = useI18n();
218
+ const isOpen = ref(false);
219
+ const searchTerm = ref("");
220
+ const assistant = useResolvedAssistant();
221
+ const hasAssistant = computed(() => !!assistant.value);
222
+ const session = useAssistantSession(isOpen, searchTerm, hasAssistant);
223
+ const { mode, submission } = session;
224
+
225
+ const isAssistantMode = computed(
226
+ () => hasAssistant.value && mode.value === "assistant",
227
+ );
228
+ const answer = useShownAnswer(isAssistantMode, submission, searchTerm);
229
+ const groups = computed(() =>
230
+ buildPaletteGroups(searchGroups.value, {
231
+ assistant: assistant.value,
232
+ isAssistantMode: isAssistantMode.value,
233
+ hasAnswer: !!answer.value,
234
+ prompt: searchTerm.value.trim(),
235
+ translate: (key, params) => t(key, params),
236
+ submit: session.submit,
237
+ }),
238
+ );
239
+
240
+ return {
241
+ ...session,
242
+ isOpen,
243
+ searchTerm,
244
+ assistant,
245
+ isAssistantMode,
246
+ answer,
247
+ groups,
248
+ };
249
+ }
@@ -0,0 +1,178 @@
1
+ import {
2
+ computed,
3
+ onBeforeUnmount,
4
+ onMounted,
5
+ ref,
6
+ type ComputedRef,
7
+ } from "vue";
8
+ import { GITHUB_REPOSITORY_URL } from "../../utils/github-repository";
9
+
10
+ const STORAGE_PREFIX = "dms-github-star-prompt:";
11
+
12
+ /** `localStorage` keys of the star prompt, per browser. */
13
+ export const GITHUB_STAR_PROMPT_KEYS = {
14
+ /** Visible time spent on signed-in dashboard pages, in milliseconds. */
15
+ activeMs: `${STORAGE_PREFIX}active-ms`,
16
+ /** Epoch milliseconds until which "Later" keeps the prompt hidden. */
17
+ snoozedUntil: `${STORAGE_PREFIX}snoozed-until`,
18
+ /** Set once the prompt is starred or closed: it never shows again. */
19
+ dismissed: `${STORAGE_PREFIX}dismissed`,
20
+ } as const;
21
+
22
+ /** Active dashboard time before the prompt first shows. */
23
+ export const GITHUB_STAR_PROMPT_DELAY_MS = 15 * 60 * 1000;
24
+ /** How long "Later" hides the prompt. */
25
+ export const GITHUB_STAR_PROMPT_SNOOZE_MS = 48 * 60 * 60 * 1000;
26
+ /** How often the visible time is saved and the snooze checked. */
27
+ export const GITHUB_STAR_PROMPT_TICK_MS = 30 * 1000;
28
+
29
+ const DISMISSED = "1";
30
+ const VISIBLE = "visible";
31
+
32
+ function readStorage(key: string): string | null {
33
+ try {
34
+ return localStorage.getItem(key);
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ function writeStorage(key: string, value: string): void {
41
+ try {
42
+ localStorage.setItem(key, value);
43
+ } catch {
44
+ /* storage unavailable (private mode, quota): the prompt state lasts the page */
45
+ }
46
+ }
47
+
48
+ function readCount(key: string): number {
49
+ const value = Number(readStorage(key));
50
+ return Number.isFinite(value) && value > 0 ? value : 0;
51
+ }
52
+
53
+ function isPageVisible(): boolean {
54
+ return document.visibilityState === VISIBLE;
55
+ }
56
+
57
+ export interface GithubStarPrompt {
58
+ /** Whether the prompt is due: enough active time, not snoozed, not dismissed. */
59
+ isDue: ComputedRef<boolean>;
60
+ /** "Later": hides the prompt for {@link GITHUB_STAR_PROMPT_SNOOZE_MS}. */
61
+ snooze: () => void;
62
+ /** Opens the repository in a new tab, then never shows the prompt again. */
63
+ star: () => void;
64
+ /** Never shows the prompt again. */
65
+ dismiss: () => void;
66
+ }
67
+
68
+ /**
69
+ * The GitHub star invitation's timing, kept per browser in `localStorage`.
70
+ * Only the time the tab is visible counts, added up across sessions, so a
71
+ * dashboard left open in a background tab does not bring the prompt sooner.
72
+ * Without `isEnabled` nothing is counted or shown.
73
+ */
74
+ export const useGithubStarPrompt = (isEnabled: boolean): GithubStarPrompt => {
75
+ const isMounted = ref(false);
76
+ const activeMs = ref(0);
77
+ const snoozedUntil = ref(0);
78
+ const isDismissed = ref(false);
79
+ const now = ref(0);
80
+ let countedSince: number | null = null;
81
+ let ticker: ReturnType<typeof setInterval> | undefined;
82
+
83
+ const isDue = computed(
84
+ () =>
85
+ isMounted.value &&
86
+ !isDismissed.value &&
87
+ activeMs.value >= GITHUB_STAR_PROMPT_DELAY_MS &&
88
+ now.value >= snoozedUntil.value,
89
+ );
90
+
91
+ // Merged with what this page holds: a choice made in another tab is
92
+ // followed here, and one made here outlives a storage that cannot be read.
93
+ function load(): void {
94
+ activeMs.value = storedActiveMs();
95
+ snoozedUntil.value = Math.max(
96
+ snoozedUntil.value,
97
+ readCount(GITHUB_STAR_PROMPT_KEYS.snoozedUntil),
98
+ );
99
+ isDismissed.value ||=
100
+ readStorage(GITHUB_STAR_PROMPT_KEYS.dismissed) === DISMISSED;
101
+ now.value = Date.now();
102
+ }
103
+
104
+ function storedActiveMs(): number {
105
+ return Math.max(
106
+ activeMs.value,
107
+ readCount(GITHUB_STAR_PROMPT_KEYS.activeMs),
108
+ );
109
+ }
110
+
111
+ // Added to the stored total rather than written over it, so two tabs add
112
+ // up. One tick at most is counted per call: a longer gap means the device
113
+ // slept, which is not time spent on the dashboard.
114
+ function count(): void {
115
+ if (countedSince === null) return;
116
+ const at = Date.now();
117
+ const elapsed = Math.min(at - countedSince, GITHUB_STAR_PROMPT_TICK_MS);
118
+ countedSince = at;
119
+ if (activeMs.value >= GITHUB_STAR_PROMPT_DELAY_MS) return;
120
+ activeMs.value = storedActiveMs() + elapsed;
121
+ writeStorage(GITHUB_STAR_PROMPT_KEYS.activeMs, String(activeMs.value));
122
+ }
123
+
124
+ function onVisibilityChange(): void {
125
+ count();
126
+ countedSince = isPageVisible() ? Date.now() : null;
127
+ }
128
+
129
+ function tick(): void {
130
+ count();
131
+ load();
132
+ if (isDismissed.value) stop();
133
+ }
134
+
135
+ function stop(): void {
136
+ clearInterval(ticker);
137
+ ticker = undefined;
138
+ document.removeEventListener("visibilitychange", onVisibilityChange);
139
+ }
140
+
141
+ function dismiss(): void {
142
+ isDismissed.value = true;
143
+ writeStorage(GITHUB_STAR_PROMPT_KEYS.dismissed, DISMISSED);
144
+ stop();
145
+ }
146
+
147
+ function snooze(): void {
148
+ now.value = Date.now();
149
+ snoozedUntil.value = now.value + GITHUB_STAR_PROMPT_SNOOZE_MS;
150
+ writeStorage(
151
+ GITHUB_STAR_PROMPT_KEYS.snoozedUntil,
152
+ String(snoozedUntil.value),
153
+ );
154
+ }
155
+
156
+ function star(): void {
157
+ window.open(GITHUB_REPOSITORY_URL, "_blank", "noopener,noreferrer");
158
+ dismiss();
159
+ }
160
+
161
+ onMounted(() => {
162
+ if (!isEnabled) return;
163
+ load();
164
+ isMounted.value = true;
165
+ if (isDismissed.value) return;
166
+ countedSince = isPageVisible() ? Date.now() : null;
167
+ document.addEventListener("visibilitychange", onVisibilityChange);
168
+ ticker = setInterval(tick, GITHUB_STAR_PROMPT_TICK_MS);
169
+ });
170
+
171
+ onBeforeUnmount(() => {
172
+ if (ticker === undefined) return;
173
+ count();
174
+ stop();
175
+ });
176
+
177
+ return { isDue, snooze, star, dismiss };
178
+ };
@@ -0,0 +1,191 @@
1
+ import type { ComputedRef, Ref } from "vue";
2
+ import {
3
+ resolveSidePanelWidthBounds,
4
+ type SidePanel,
5
+ type SidePanelWidthBounds,
6
+ useAppSidePanels,
7
+ } from "../../../composables/useAppSidePanels";
8
+
9
+ /**
10
+ * Set to the panel's width in px by the panel itself and by the dashboard
11
+ * frame, which keeps that much room free on its right from `lg`. Both cap it
12
+ * at 60vw.
13
+ *
14
+ * @internal
15
+ */
16
+ export const SIDE_PANEL_WIDTH_VARIABLE = "--dms-side-panel-width";
17
+ /** @internal */
18
+ export const SIDE_PANEL_COOKIE = "dms-side-panel";
19
+ const SIDE_PANEL_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
20
+ const SIDE_PANEL_DRAG_WIDTH_STATE_KEY = "dms:side-panel-drag-width";
21
+
22
+ /**
23
+ * What the DMS remembers of the side panels on this device: which one is open
24
+ * and the width each was last given. A cookie, so the server renders the open
25
+ * panel at its width and a reload does not move the page.
26
+ *
27
+ * @internal
28
+ */
29
+ export interface SidePanelPreferences {
30
+ openId: string | null;
31
+ widths: Record<string, number>;
32
+ }
33
+
34
+ /** @internal */
35
+ export interface ActiveSidePanel {
36
+ panel: ComputedRef<SidePanel | null>;
37
+ bounds: ComputedRef<SidePanelWidthBounds | null>;
38
+ width: ComputedRef<number>;
39
+ }
40
+
41
+ function emptyPreferences(): SidePanelPreferences {
42
+ return { openId: null, widths: {} };
43
+ }
44
+
45
+ function isRecord(value: unknown): value is Record<string, unknown> {
46
+ return typeof value === "object" && value !== null;
47
+ }
48
+
49
+ function readWidths(value: unknown): Record<string, number> {
50
+ if (!isRecord(value)) return {};
51
+ return Object.fromEntries(
52
+ Object.entries(value).filter(([, width]) => Number.isFinite(width)),
53
+ ) as Record<string, number>;
54
+ }
55
+
56
+ /**
57
+ * Reads the cookie value back defensively: it comes from the browser, and an
58
+ * older or hand-edited value must not break the dashboard.
59
+ *
60
+ * @internal
61
+ */
62
+ export function readSidePanelPreferences(value: unknown): SidePanelPreferences {
63
+ if (!isRecord(value)) return emptyPreferences();
64
+ const openId = typeof value.openId === "string" ? value.openId : null;
65
+ return { openId, widths: readWidths(value.widths) };
66
+ }
67
+
68
+ /** @internal */
69
+ export function useSidePanelPreferences(): Ref<SidePanelPreferences> {
70
+ return useDmsCookie<SidePanelPreferences>(SIDE_PANEL_COOKIE, {
71
+ default: emptyPreferences,
72
+ maxAge: SIDE_PANEL_COOKIE_MAX_AGE,
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Opens the panel `openId`, or closes every panel with `null`. One panel is
78
+ * open at a time, so opening one closes the other.
79
+ *
80
+ * @internal
81
+ */
82
+ export function setOpenSidePanel(
83
+ preferences: Ref<SidePanelPreferences>,
84
+ openId: string | null,
85
+ ): void {
86
+ const current = readSidePanelPreferences(preferences.value);
87
+ if (current.openId === openId) return;
88
+ preferences.value = { ...current, openId };
89
+ }
90
+
91
+ /** @internal */
92
+ export function isSidePanelOpen(
93
+ preferences: Ref<SidePanelPreferences>,
94
+ id: string,
95
+ ): boolean {
96
+ return readSidePanelPreferences(preferences.value).openId === id;
97
+ }
98
+
99
+ /** @internal */
100
+ export function toggleSidePanel(
101
+ preferences: Ref<SidePanelPreferences>,
102
+ id: string,
103
+ ): void {
104
+ setOpenSidePanel(preferences, isSidePanelOpen(preferences, id) ? null : id);
105
+ }
106
+
107
+ /** @internal */
108
+ export function storeSidePanelWidth(
109
+ preferences: Ref<SidePanelPreferences>,
110
+ id: string,
111
+ width: number,
112
+ ): void {
113
+ const current = readSidePanelPreferences(preferences.value);
114
+ preferences.value = {
115
+ ...current,
116
+ widths: { ...current.widths, [id]: width },
117
+ };
118
+ }
119
+
120
+ /**
121
+ * The width of the panel being dragged, shared with the dashboard frame so the
122
+ * page follows the handle. The cookie is written once the drag ends.
123
+ *
124
+ * @internal
125
+ */
126
+ export function useSidePanelDragWidth(): Ref<number | null> {
127
+ return useDmsState<number | null>(
128
+ SIDE_PANEL_DRAG_WIDTH_STATE_KEY,
129
+ () => null,
130
+ );
131
+ }
132
+
133
+ /** @internal */
134
+ export function sidePanelWidthStyle(width: number): Record<string, string> {
135
+ return { [SIDE_PANEL_WIDTH_VARIABLE]: `${width}px` };
136
+ }
137
+
138
+ /** @internal */
139
+ export function clampSidePanelWidth(
140
+ width: number,
141
+ bounds: SidePanelWidthBounds,
142
+ ): number {
143
+ return Math.round(
144
+ Math.min(bounds.maxWidth, Math.max(bounds.minWidth, width)),
145
+ );
146
+ }
147
+
148
+ /**
149
+ * The width a panel was last given, or its default, within its bounds.
150
+ *
151
+ * @internal
152
+ */
153
+ export function storedSidePanelWidth(
154
+ preferences: SidePanelPreferences,
155
+ id: string,
156
+ bounds: SidePanelWidthBounds,
157
+ ): number {
158
+ return clampSidePanelWidth(
159
+ preferences.widths[id] ?? bounds.defaultWidth,
160
+ bounds,
161
+ );
162
+ }
163
+
164
+ /**
165
+ * The side panel the dashboard shows, and its width: the open panel, if it is
166
+ * registered and a user is signed in. Read by the panel host and by every
167
+ * dashboard frame, which reserves that width next to the page.
168
+ *
169
+ * @internal
170
+ */
171
+ export function useActiveSidePanel(): ActiveSidePanel {
172
+ const { panels } = useAppSidePanels();
173
+ const preferences = useSidePanelPreferences();
174
+ const dragWidth = useSidePanelDragWidth();
175
+ const { loggedIn } = useUserSession();
176
+ const panel = computed(() => {
177
+ if (!loggedIn.value) return null;
178
+ const { openId } = readSidePanelPreferences(preferences.value);
179
+ return panels.value.find((entry) => entry.id === openId) ?? null;
180
+ });
181
+ const bounds = computed(() =>
182
+ panel.value ? resolveSidePanelWidthBounds(panel.value) : null,
183
+ );
184
+ const width = computed(() => {
185
+ if (!panel.value || !bounds.value) return 0;
186
+ if (dragWidth.value !== null) return dragWidth.value;
187
+ const current = readSidePanelPreferences(preferences.value);
188
+ return storedSidePanelWidth(current, panel.value.id, bounds.value);
189
+ });
190
+ return { panel, bounds, width };
191
+ }
@@ -0,0 +1,51 @@
1
+ import type { HeaderAction } from "../../../composables/useHeaderActions";
2
+ import {
3
+ isSidePanelOpen,
4
+ toggleSidePanel,
5
+ useSidePanelPreferences,
6
+ } from "./sidePanelState";
7
+
8
+ /** @internal */
9
+ export interface HeaderActionControls {
10
+ /** Whether the action is a toggle, whose button reports a pressed state. */
11
+ isToggle: (action: HeaderAction) => boolean;
12
+ /** Whether the toggle is on: its side panel is open, or `isActive()` says so. */
13
+ isActive: (action: HeaderAction) => boolean;
14
+ /** Toggles the action's side panel, or runs its `onSelect`. */
15
+ select: (action: HeaderAction) => void;
16
+ }
17
+
18
+ /**
19
+ * How the header reads and runs any {@link HeaderAction}: an action linked to
20
+ * a side panel gets its state and its click from the DMS, any other one from
21
+ * its own callables.
22
+ *
23
+ * @internal
24
+ */
25
+ export function useHeaderActionControls(): HeaderActionControls {
26
+ const preferences = useSidePanelPreferences();
27
+
28
+ function isToggle(action: HeaderAction): boolean {
29
+ return (
30
+ typeof action.sidePanelId === "string" ||
31
+ typeof action.isActive === "function"
32
+ );
33
+ }
34
+
35
+ function isActive(action: HeaderAction): boolean {
36
+ if (typeof action.sidePanelId === "string") {
37
+ return isSidePanelOpen(preferences, action.sidePanelId);
38
+ }
39
+ return typeof action.isActive === "function" && action.isActive();
40
+ }
41
+
42
+ function select(action: HeaderAction): void {
43
+ if (typeof action.sidePanelId === "string") {
44
+ toggleSidePanel(preferences, action.sidePanelId);
45
+ return;
46
+ }
47
+ action.onSelect?.();
48
+ }
49
+
50
+ return { isToggle, isActive, select };
51
+ }