@antelopejs/dms 0.6.1 → 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 (472) hide show
  1. package/CHANGELOG.md +39 -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/layout-banners.d.ts +4 -1
  27. package/dist/implementations/dms/layout-banners.js +33 -18
  28. package/dist/implementations/dms/layout-banners.js.map +1 -1
  29. package/dist/implementations/dms/nav-badges.d.ts +6 -4
  30. package/dist/implementations/dms/nav-badges.js +6 -5
  31. package/dist/implementations/dms/nav-badges.js.map +1 -1
  32. package/dist/implementations/dms/page.d.ts +1 -0
  33. package/dist/implementations/dms/page.js +46 -8
  34. package/dist/implementations/dms/page.js.map +1 -1
  35. package/dist/implementations/dms/permission-preview.d.ts +1 -1
  36. package/dist/implementations/dms/permission-preview.js +9 -4
  37. package/dist/implementations/dms/permission-preview.js.map +1 -1
  38. package/dist/implementations/dms-auth/index.d.ts +3 -2
  39. package/dist/implementations/dms-auth/index.js +39 -35
  40. package/dist/implementations/dms-auth/index.js.map +1 -1
  41. package/dist/implementations/dms-auth/token-session.d.ts +3 -0
  42. package/dist/implementations/dms-auth/token-session.js +22 -0
  43. package/dist/implementations/dms-auth/token-session.js.map +1 -0
  44. package/dist/implementations/dms-notifications/index.d.ts +0 -1
  45. package/dist/implementations/dms-notifications/index.js +0 -7
  46. package/dist/implementations/dms-notifications/index.js.map +1 -1
  47. package/dist/pages/settings/settings-index.js +3 -2
  48. package/dist/pages/settings/settings-index.js.map +1 -1
  49. package/dist/pages/settings/users/account-activity-feed.d.ts +1 -1
  50. package/dist/pages/settings/users/account-activity-feed.js +1 -1
  51. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  52. package/dist/pages/settings/users/account-activity.d.ts +1 -1
  53. package/dist/pages/settings/users/account-activity.js +1 -1
  54. package/dist/pages/settings/users/account-activity.js.map +1 -1
  55. package/dist/pages/settings/users/account-credentials.d.ts +3 -0
  56. package/dist/pages/settings/users/account-credentials.js +2 -0
  57. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  58. package/dist/pages/settings/users/current-password.d.ts +1 -1
  59. package/dist/pages/settings/users/current-password.js +23 -2
  60. package/dist/pages/settings/users/current-password.js.map +1 -1
  61. package/dist/pages/settings/users/email-change.d.ts +1 -1
  62. package/dist/pages/settings/users/email-change.js +33 -26
  63. package/dist/pages/settings/users/email-change.js.map +1 -1
  64. package/dist/pages/settings/users/invite-resend.js +0 -1
  65. package/dist/pages/settings/users/invite-resend.js.map +1 -1
  66. package/dist/pages/settings/users/invites.d.ts +1 -2
  67. package/dist/pages/settings/users/invites.js +21 -19
  68. package/dist/pages/settings/users/invites.js.map +1 -1
  69. package/dist/pages/settings/users/member-invite-batch.js +0 -1
  70. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  71. package/dist/pages/settings/users/member-invite-form.js +10 -22
  72. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  73. package/dist/pages/settings/users/member-role-options.d.ts +6 -4
  74. package/dist/pages/settings/users/member-role-options.js +15 -4
  75. package/dist/pages/settings/users/member-role-options.js.map +1 -1
  76. package/dist/pages/settings/users/members.d.ts +2 -3
  77. package/dist/pages/settings/users/members.js +7 -12
  78. package/dist/pages/settings/users/members.js.map +1 -1
  79. package/dist/pages/settings/users/notifications.d.ts +1 -8
  80. package/dist/pages/settings/users/notifications.js +6 -38
  81. package/dist/pages/settings/users/notifications.js.map +1 -1
  82. package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
  83. package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
  84. package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
  85. package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
  86. package/dist/pages/settings/users/permission-warnings.js +42 -0
  87. package/dist/pages/settings/users/permission-warnings.js.map +1 -0
  88. package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
  89. package/dist/pages/settings/users/profile-helpers.js +0 -16
  90. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  91. package/dist/pages/settings/users/role-editor-store.js +6 -3
  92. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  93. package/dist/pages/settings/users/role-editor.d.ts +2 -1
  94. package/dist/pages/settings/users/role-editor.js +8 -4
  95. package/dist/pages/settings/users/role-editor.js.map +1 -1
  96. package/dist/pages/settings/users/role-preview.js +2 -1
  97. package/dist/pages/settings/users/role-preview.js.map +1 -1
  98. package/dist/pages/settings/users/roles.js +5 -1
  99. package/dist/pages/settings/users/roles.js.map +1 -1
  100. package/dist/pages/settings/users/security-attention.d.ts +4 -0
  101. package/dist/pages/settings/users/security-attention.js +23 -7
  102. package/dist/pages/settings/users/security-attention.js.map +1 -1
  103. package/dist/pages/settings/users/security.d.ts +2 -1
  104. package/dist/pages/settings/users/security.js +13 -2
  105. package/dist/pages/settings/users/security.js.map +1 -1
  106. package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
  107. package/dist/pages/settings/users/two-factor-operations.js +28 -29
  108. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  109. package/dist/routes/auth/forgot-password.js +3 -0
  110. package/dist/routes/auth/forgot-password.js.map +1 -1
  111. package/dist/routes/auth/index.d.ts +1 -0
  112. package/dist/routes/auth/index.js +1 -0
  113. package/dist/routes/auth/index.js.map +1 -1
  114. package/dist/routes/auth/invite.js +28 -4
  115. package/dist/routes/auth/invite.js.map +1 -1
  116. package/dist/routes/auth/login-throttle.d.ts +2 -0
  117. package/dist/routes/auth/login-throttle.js +5 -4
  118. package/dist/routes/auth/login-throttle.js.map +1 -1
  119. package/dist/routes/auth/request-2fa-email.js +6 -18
  120. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  121. package/dist/routes/auth/reset-code.d.ts +3 -0
  122. package/dist/routes/auth/reset-code.js +37 -0
  123. package/dist/routes/auth/reset-code.js.map +1 -0
  124. package/dist/routes/auth/reset-password.d.ts +2 -2
  125. package/dist/routes/auth/reset-password.js +5 -10
  126. package/dist/routes/auth/reset-password.js.map +1 -1
  127. package/dist/routes/auth/signup.js +10 -32
  128. package/dist/routes/auth/signup.js.map +1 -1
  129. package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
  130. package/dist/routes/auth/two-factor-throttle.js +81 -0
  131. package/dist/routes/auth/two-factor-throttle.js.map +1 -0
  132. package/dist/routes/auth/validate-forgot-password-token.js +2 -8
  133. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  134. package/dist/routes/auth/validate-invite-token.d.ts +1 -0
  135. package/dist/routes/auth/validate-invite-token.js +11 -0
  136. package/dist/routes/auth/validate-invite-token.js.map +1 -0
  137. package/dist/routes/auth/verify-2fa.js +10 -13
  138. package/dist/routes/auth/verify-2fa.js.map +1 -1
  139. package/dist/routes/auth.d.ts +1 -0
  140. package/dist/routes/auth.js +11 -1
  141. package/dist/routes/auth.js.map +1 -1
  142. package/dist/test/helpers/auth.d.ts +0 -1
  143. package/dist/test/helpers/auth.js +0 -1
  144. package/dist/test/helpers/auth.js.map +1 -1
  145. package/dist/test/helpers/emails.d.ts +10 -0
  146. package/dist/test/helpers/emails.js +36 -0
  147. package/dist/test/helpers/emails.js.map +1 -0
  148. package/dist/test/helpers/fixtures.d.ts +0 -1
  149. package/dist/test/helpers/fixtures.js +1 -2
  150. package/dist/test/helpers/fixtures.js.map +1 -1
  151. package/dist/test/helpers/page-access.d.ts +1 -0
  152. package/dist/test/helpers/page-access.js +6 -1
  153. package/dist/test/helpers/page-access.js.map +1 -1
  154. package/dist/test/integration/auth/current-password.test.d.ts +1 -0
  155. package/dist/test/integration/auth/current-password.test.js +65 -0
  156. package/dist/test/integration/auth/current-password.test.js.map +1 -0
  157. package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
  158. package/dist/test/integration/auth/password-reset.test.js +126 -0
  159. package/dist/test/integration/auth/password-reset.test.js.map +1 -0
  160. package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
  161. package/dist/test/integration/auth/session-revocation.test.js +45 -0
  162. package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
  163. package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
  164. package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
  165. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
  166. package/dist/test/integration/auth/two-factor-password.test.js +5 -1
  167. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  168. package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
  169. package/dist/test/integration/auth/two-factor-token.test.js +92 -0
  170. package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
  171. package/dist/test/integration/email-change.test.js +36 -0
  172. package/dist/test/integration/email-change.test.js.map +1 -1
  173. package/dist/test/integration/invite-extension-edit.test.js +0 -1
  174. package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
  175. package/dist/test/integration/invite-link-status.test.d.ts +1 -0
  176. package/dist/test/integration/invite-link-status.test.js +110 -0
  177. package/dist/test/integration/invite-link-status.test.js.map +1 -0
  178. package/dist/test/integration/invite-resolution.test.js +0 -3
  179. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  180. package/dist/test/integration/member-settings-access.test.js +17 -0
  181. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  182. package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
  183. package/dist/test/integration/notification-unread-badge.test.js +92 -0
  184. package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
  185. package/dist/test/integration/permission-warnings.test.d.ts +1 -0
  186. package/dist/test/integration/permission-warnings.test.js +71 -0
  187. package/dist/test/integration/permission-warnings.test.js.map +1 -0
  188. package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
  189. package/dist/test/integration/removed-member-notifications.test.js +80 -0
  190. package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
  191. package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
  192. package/dist/test/integration/role-permission-ancestors.test.js +146 -0
  193. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
  194. package/dist/test/integration/tenant-lifecycle.test.js +0 -4
  195. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  196. package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
  197. package/dist/test/unit/auth-session-revocation.test.js +105 -0
  198. package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
  199. package/dist/test/unit/db/email-normalization.test.js +0 -1
  200. package/dist/test/unit/db/email-normalization.test.js.map +1 -1
  201. package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
  202. package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
  203. package/dist/test/unit/db/notification-inbox.test.js +63 -13
  204. package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
  205. package/dist/test/unit/db/notification-preferences.test.js +0 -17
  206. package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
  207. package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
  208. package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
  209. package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
  210. package/dist/test/unit/implementations/dms/layout-banners.test.js +78 -1
  211. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
  212. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
  213. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
  214. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
  215. package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
  216. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  217. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
  218. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
  219. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
  220. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
  221. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
  222. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
  223. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
  224. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
  225. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
  226. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
  227. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
  228. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
  229. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
  230. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
  231. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
  232. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
  233. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
  234. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
  235. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
  236. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
  237. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
  238. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
  239. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
  240. package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
  241. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  242. package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
  243. package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
  244. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
  245. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
  246. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
  247. package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
  248. package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
  249. package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
  250. package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
  251. package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
  252. package/dist/test/unit/interfaces/dms-base/column-display.test.js +70 -0
  253. package/dist/test/unit/interfaces/dms-base/column-display.test.js.map +1 -1
  254. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +38 -0
  255. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
  256. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
  257. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  258. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
  259. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
  260. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
  261. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
  262. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
  263. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  264. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
  265. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
  266. package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
  267. package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
  268. package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
  269. package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
  270. package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
  271. package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
  272. package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
  273. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
  274. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
  275. package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
  276. package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
  277. package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
  278. package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
  279. package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
  280. package/dist/test/unit/pages/settings/members-page.test.js +7 -10
  281. package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
  282. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
  283. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
  284. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
  285. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
  286. package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
  287. package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
  288. package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
  289. package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
  290. package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
  291. package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
  292. package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
  293. package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
  294. package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
  295. package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
  296. package/dist/test/unit/sanitize-user.test.d.ts +1 -0
  297. package/dist/test/unit/sanitize-user.test.js +21 -0
  298. package/dist/test/unit/sanitize-user.test.js.map +1 -0
  299. package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
  300. package/dist/test/unit/signup-email-validation.test.js +79 -0
  301. package/dist/test/unit/signup-email-validation.test.js.map +1 -0
  302. package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
  303. package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
  304. package/dist/utils/account-notifications.d.ts +5 -0
  305. package/dist/utils/account-notifications.js +8 -0
  306. package/dist/utils/account-notifications.js.map +1 -1
  307. package/dist/utils/notification-emitter.d.ts +1 -1
  308. package/dist/utils/notification-emitter.js +1 -1
  309. package/dist/utils/notification-emitter.js.map +1 -1
  310. package/dist/utils/notification-tones.d.ts +2 -1
  311. package/dist/utils/notification-tones.js +5 -1
  312. package/dist/utils/notification-tones.js.map +1 -1
  313. package/dist/utils/one-time-code.d.ts +3 -0
  314. package/dist/utils/one-time-code.js +21 -0
  315. package/dist/utils/one-time-code.js.map +1 -0
  316. package/dist/utils/sign-in-monitor.d.ts +1 -0
  317. package/dist/utils/sign-in-monitor.js +3 -2
  318. package/dist/utils/sign-in-monitor.js.map +1 -1
  319. package/dist/utils/two-factor-codes.d.ts +10 -0
  320. package/dist/utils/two-factor-codes.js +76 -0
  321. package/dist/utils/two-factor-codes.js.map +1 -0
  322. package/dist/validation/auth.schema.d.ts +17 -0
  323. package/dist/validation/auth.schema.js +17 -6
  324. package/dist/validation/auth.schema.js.map +1 -1
  325. package/dist/validation/issue-message.d.ts +1 -0
  326. package/dist/validation/issue-message.js +9 -0
  327. package/dist/validation/issue-message.js.map +1 -0
  328. package/dist/validation/member-invite.schema.d.ts +1 -25
  329. package/dist/validation/member-invite.schema.js +2 -23
  330. package/dist/validation/member-invite.schema.js.map +1 -1
  331. package/frontend-vue/dms.frontend.ts +2 -0
  332. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
  333. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
  334. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
  335. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
  336. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +25 -0
  337. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
  338. package/frontend-vue/layers/dms-core/app/types/composed-text.ts +64 -0
  339. package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
  340. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +227 -0
  341. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +49 -0
  342. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
  343. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
  344. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
  345. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
  346. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +17 -3
  347. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
  348. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
  349. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
  350. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
  351. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
  352. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
  353. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
  354. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
  355. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
  356. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
  357. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
  358. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
  359. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
  360. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
  361. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
  362. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
  363. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
  364. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
  365. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
  366. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
  367. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
  368. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
  369. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
  370. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
  371. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
  372. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
  373. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
  374. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
  375. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
  376. package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
  377. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
  378. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
  379. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
  380. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
  381. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
  382. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
  383. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
  384. package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
  385. package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
  386. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
  387. package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
  388. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
  389. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
  390. package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
  391. package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
  392. package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
  393. package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
  394. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
  395. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
  396. package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
  397. package/frontend-vue/layers/dms-layout/app/types/page.ts +11 -1
  398. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
  399. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
  400. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
  401. package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
  402. package/frontend-vue/layers/dms-ui/app/build/components/table-view/IdentityCell.vue +36 -7
  403. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/cellHelpers.ts +57 -0
  404. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +19 -5
  405. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +22 -2
  406. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +118 -0
  407. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +5 -3
  408. package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
  409. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
  410. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
  411. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
  412. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
  413. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
  414. package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
  415. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +4 -35
  416. package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
  417. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
  418. package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +8 -5
  419. package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +38 -18
  420. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
  421. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +26 -23
  422. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
  423. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
  424. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
  425. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  426. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
  427. package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
  428. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
  429. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
  430. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
  432. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
  433. package/frontend-vue/package.json +2 -2
  434. package/frontend-vue/pnpm-lock.yaml +5 -5
  435. package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
  436. package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
  437. package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
  438. package/frontend-vue/tests/composed-text-blocks.test.ts +205 -0
  439. package/frontend-vue/tests/composed-text.test.ts +265 -0
  440. package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
  441. package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
  442. package/frontend-vue/tests/form-display-options.test.ts +64 -0
  443. package/frontend-vue/tests/form-load-error.test.ts +160 -0
  444. package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
  445. package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
  446. package/frontend-vue/tests/header-actions.test.ts +191 -0
  447. package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
  448. package/frontend-vue/tests/layout-banners.test.ts +56 -0
  449. package/frontend-vue/tests/member-role-picker.test.ts +26 -0
  450. package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
  451. package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
  452. package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
  453. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
  454. package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
  455. package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
  456. package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
  457. package/frontend-vue/tests/page-fill-height.test.ts +9 -1
  458. package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
  459. package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
  460. package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
  461. package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
  462. package/frontend-vue/tests/role-permissions.test.ts +178 -56
  463. package/frontend-vue/tests/side-panels.test.ts +470 -0
  464. package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
  465. package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
  466. package/frontend-vue/tests/table-cell-sublines.test.ts +466 -0
  467. package/frontend-vue/tests/table-metric-cells.test.ts +4 -0
  468. package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
  469. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  470. package/package.json +2 -2
  471. package/skills/dms/REFERENCE.md +1 -1
  472. package/skills/dms/SKILL.md +7 -0
@@ -50,6 +50,8 @@ export interface PermissionRowContext {
50
50
  changeOf: (id: string) => PermissionChange;
51
51
  requiresOf: (node: RolePermissionNode) => string[];
52
52
  autoAddedHintOf: (id: string) => string | undefined;
53
+ /** Warnings a closed row hides (see `selectedWarningsBelow`). */
54
+ warningsBelowOf: (node: RolePermissionNode) => string[];
53
55
  isDisabled: (node: RolePermissionNode) => boolean;
54
56
  disabledHint: string;
55
57
  toggle: (id: string, checked: boolean) => void;
@@ -289,25 +291,36 @@ export function selectionState(count: SelectionCount): SelectionState {
289
291
  return count.selected === count.total ? true : "indeterminate";
290
292
  }
291
293
 
294
+ /** The permissions a node declares it needs, as far as the tree knows them. */
295
+ function dependenciesOf(index: PermissionIndex, id: string): string[] {
296
+ return (index.nodes.get(id)?.dependencies ?? []).filter((dependency) =>
297
+ index.nodes.has(dependency),
298
+ );
299
+ }
300
+
292
301
  /**
293
- * The ids a dotted permission id sits under, itself first: `a.b.c` gives
294
- * `a.b.c`, `a.b` and `a`. Granting follows the ids, not the tree, exactly as
295
- * the original roles form did: a registered `media.upload` lifted to the top
296
- * of the tree still grants `media` with it.
302
+ * Ids a set of permissions needs, the set included: the nodes above each one
303
+ * and the permissions each one depends on, with what those need in turn.
297
304
  */
298
- export function idPrefixes(id: string): string[] {
299
- const parts = id.split(".");
300
- return parts.map((_, position) =>
301
- parts.slice(0, parts.length - position).join("."),
302
- );
305
+ function requiredIds(index: PermissionIndex, ids: string[]): Set<string> {
306
+ const required = new Set<string>();
307
+ const pending = [...ids];
308
+ for (let id = pending.shift(); id !== undefined; id = pending.shift()) {
309
+ if (required.has(id)) continue;
310
+ required.add(id);
311
+ pending.push(...ancestorIds(index, id), ...dependenciesOf(index, id));
312
+ }
313
+ return required;
303
314
  }
304
315
 
305
316
  /**
306
- * Grant a node's whole subtree with every id it sits under (a component
307
- * needs its page, a page its category), as the original roles form stored
308
- * it. Ids the user may not grant are skipped. Declared dependencies are shown
309
- * on the rows but not added: nothing enforces them, and the original form
310
- * never stored them either.
317
+ * Grant a node's whole subtree with everything it needs: every node above it
318
+ * (a component needs its page, a page its category) and the permissions it
319
+ * depends on (a table's edit needs its view, which loads the row). The tree
320
+ * nests by id, so the nodes above are the registered ids it sits under; the
321
+ * server completes a saved role the same way, with the unregistered rest (a
322
+ * lifted `media.upload` gets `media` there). Ids the user may not grant are
323
+ * skipped.
311
324
  */
312
325
  export function grantPermission(
313
326
  index: PermissionIndex,
@@ -316,25 +329,48 @@ export function grantPermission(
316
329
  canGrant: (permissionId: string) => boolean,
317
330
  ): PermissionGrant {
318
331
  const subtree = new Set(index.subtrees.get(id) ?? [id]);
319
- const wanted = new Set([...subtree, ...idPrefixes(id)]);
332
+ const wanted = requiredIds(index, [...subtree]);
320
333
  const added = [...wanted].filter(
321
334
  (wantedId) => !selection.has(wantedId) && canGrant(wantedId),
322
335
  );
323
336
  return {
324
337
  selection: new Set([...selection, ...added]),
325
- autoAdded: added.filter(
326
- (addedId) => !subtree.has(addedId) && index.nodes.has(addedId),
327
- ),
338
+ autoAdded: added.filter((addedId) => !subtree.has(addedId)),
328
339
  };
329
340
  }
330
341
 
331
342
  /**
332
- * Revoke a node's subtree. An id it sits under that is a node with children
333
- * goes too when none of its descendants is left once the subtree is gone: a
334
- * page is granted for its components. Same rule as the original roles form,
335
- * which judges every ancestor against that one state, before any ancestor
336
- * is dropped: an ancestor above one dropped this way still saw it selected,
337
- * and stays.
343
+ * The selected nodes that depend on a removed one, with their subtrees added
344
+ * to `removed`, until none is left: a table's edit goes with its view, which
345
+ * the server would otherwise put back when the role is saved. Returns the
346
+ * nodes taken this way.
347
+ */
348
+ function removeDependents(
349
+ index: PermissionIndex,
350
+ selection: Set<string>,
351
+ removed: Set<string>,
352
+ ): string[] {
353
+ const dependents: string[] = [];
354
+ const dependsOnRemoved = (id: string) =>
355
+ !removed.has(id) &&
356
+ dependenciesOf(index, id).some((dependency) => removed.has(dependency));
357
+ for (let next = [...selection].find(dependsOnRemoved); next; ) {
358
+ dependents.push(next);
359
+ for (const descendant of index.subtrees.get(next) ?? [next]) {
360
+ removed.add(descendant);
361
+ }
362
+ next = [...selection].find(dependsOnRemoved);
363
+ }
364
+ return dependents;
365
+ }
366
+
367
+ /**
368
+ * Revoke a node's subtree, and what depends on it. A node above them goes
369
+ * too when none of its descendants is left once they are gone: a page is
370
+ * granted for its components. Same rule as the original roles form, which
371
+ * judges every ancestor against that one state, before any ancestor is
372
+ * dropped: an ancestor above one dropped this way still saw it selected, and
373
+ * stays.
338
374
  */
339
375
  export function revokePermission(
340
376
  index: PermissionIndex,
@@ -342,15 +378,16 @@ export function revokePermission(
342
378
  id: string,
343
379
  ): Set<string> {
344
380
  const removed = new Set(index.subtrees.get(id) ?? [id]);
381
+ const dependents = removeDependents(index, selection, removed);
345
382
  const remaining = new Set(
346
383
  [...selection].filter((kept) => !removed.has(kept)),
347
384
  );
348
- const emptied = idPrefixes(id).filter((ancestor) => {
385
+ const judged = new Set(
386
+ [id, ...dependents].flatMap((revoked) => ancestorIds(index, revoked)),
387
+ );
388
+ const emptied = [...judged].filter((ancestor) => {
349
389
  const descendants = (index.subtrees.get(ancestor) ?? []).slice(1);
350
- return (
351
- descendants.length > 0 &&
352
- !descendants.some((descendant) => remaining.has(descendant))
353
- );
390
+ return !descendants.some((descendant) => remaining.has(descendant));
354
391
  });
355
392
  for (const ancestor of emptied) remaining.delete(ancestor);
356
393
  return remaining;
@@ -408,6 +445,48 @@ export function searchPermissions(
408
445
  }));
409
446
  }
410
447
 
448
+ function warningOf(index: PermissionIndex, id: string): string | undefined {
449
+ return index.nodes.get(id)?.warning;
450
+ }
451
+
452
+ /**
453
+ * Distinct warnings of the selected permissions below a node, itself
454
+ * excluded: what a closed row hides once a parent ticked them.
455
+ */
456
+ export function selectedWarningsBelow(
457
+ index: PermissionIndex,
458
+ id: string,
459
+ selection: Set<string>,
460
+ ): string[] {
461
+ const descendants = (index.subtrees.get(id) ?? []).slice(1);
462
+ const warnings = descendants
463
+ .filter((descendant) => selection.has(descendant))
464
+ .map((descendant) => warningOf(index, descendant))
465
+ .filter((warning): warning is string => Boolean(warning));
466
+ return [...new Set(warnings)];
467
+ }
468
+
469
+ /**
470
+ * The selected permissions that carry a warning, in tree order, each named
471
+ * under its area ("Members › Edit") so a summary can list them.
472
+ */
473
+ export function selectedWarningLabels(
474
+ index: PermissionIndex,
475
+ areas: PermissionArea[],
476
+ selection: Set<string>,
477
+ translate: (label: string) => string,
478
+ ): string[] {
479
+ const areaIds = new Set(areas.map((area) => area.node.id));
480
+ return index.allIds
481
+ .filter((id) => selection.has(id) && warningOf(index, id))
482
+ .map((id) => {
483
+ const areaId = areaIdOf(index, areaIds, id);
484
+ const labels = [index.nodes.get(id)?.label ?? id];
485
+ if (areaId !== id) labels.unshift(index.nodes.get(areaId)?.label ?? "");
486
+ return joinPath(labels.map((label) => translate(label)));
487
+ });
488
+ }
489
+
411
490
  /** Joins path labels the way the editor shows them. */
412
491
  export function joinPath(labels: string[]): string {
413
492
  return labels.join(PATH_SEPARATOR);
@@ -6,6 +6,8 @@ export interface RolePermissionNode {
6
6
  description?: string;
7
7
  /** Permissions this one needs; granting it grants them too. */
8
8
  dependencies?: string[];
9
+ /** What granting it really allows, beyond its label (an i18n key). */
10
+ warning?: string;
9
11
  children?: RolePermissionNode[];
10
12
  }
11
13
 
@@ -3,6 +3,8 @@ export type SecurityDialogTone = "primary" | "error" | "warning";
3
3
  </script>
4
4
 
5
5
  <script setup lang="ts">
6
+ import DmsModalEscHint from "#dms-ui/app/build/components/modal/ModalEscHint.vue";
7
+
6
8
  interface SecurityDialogFrameProps {
7
9
  title: string;
8
10
  icon: string;
@@ -51,12 +53,10 @@ const { t } = useI18n();
51
53
  <template #footer>
52
54
  <!-- Phones: no Esc hint (no keyboard) and the buttons may wrap. -->
53
55
  <div class="flex w-full flex-wrap items-center justify-end gap-2">
54
- <span
55
- class="text-dimmed me-auto flex items-center gap-1.5 text-xs max-sm:hidden"
56
- >
57
- <UKbd value="Esc" size="sm" />
58
- {{ t("page.settings.security.esc_to_cancel") }}
59
- </span>
56
+ <DmsModalEscHint
57
+ class="max-sm:hidden"
58
+ :label="t('page.settings.security.esc_to_cancel')"
59
+ />
60
60
  <UButton
61
61
  color="neutral"
62
62
  variant="outline"
@@ -21,6 +21,8 @@ const CODE_ERRORS = {
21
21
  "error.invalid_token": "code",
22
22
  "error.token_expired": "code",
23
23
  } as const;
24
+ // Too many wrong codes: the change is dropped and a new code must be asked.
25
+ const CODE_BURNT = "error.email_change_too_many_attempts";
24
26
  const FORGOT_PASSWORD_PATH = "/auth/forgot";
25
27
  const INVALID_CURRENT_PASSWORD = "error.invalid_current_password";
26
28
  const EMAIL_ALREADY_USED = "error.email_already_used";
@@ -39,7 +41,7 @@ const toast = useToast();
39
41
  const { $authFetch } = useAuthFetch();
40
42
  const { refresh: refreshSession } = useCurrentUser();
41
43
  const { overview, refresh } = useSecurityOverview();
42
- const { errorMessage } = useSecurityFormat();
44
+ const { errorMessage, errorCode } = useSecurityFormat();
43
45
  const { processApiMessage } = useTranslation();
44
46
 
45
47
  const isEditing = ref(false);
@@ -218,6 +220,10 @@ async function confirmCode(code: string): Promise<void> {
218
220
  title: errorMessage(error, "page.settings.security.email.error"),
219
221
  color: "error",
220
222
  });
223
+ if (errorCode(error) === CODE_BURNT) {
224
+ isCodeOpen.value = false;
225
+ await refresh();
226
+ }
221
227
  } finally {
222
228
  isConfirming.value = false;
223
229
  }
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { MONO_CHIP_CLASS } from "#dms-ui/app/build/utils/monoChip";
3
+ import DmsModalEscHint from "#dms-ui/app/build/components/modal/ModalEscHint.vue";
3
4
  import { useSecurityFormat } from "../../../../composables/settings/security/useSecurityFormat";
4
5
  import {
5
6
  SECURITY_ENDPOINT,
@@ -340,10 +341,10 @@ onMounted(loadSessions);
340
341
  </template>
341
342
  <template #footer>
342
343
  <div class="flex w-full items-center justify-end gap-2">
343
- <span class="text-dimmed me-auto flex items-center gap-1.5 text-xs">
344
- <UKbd value="Esc" size="sm" />
345
- {{ t("page.settings.security.esc_to_cancel") }}
346
- </span>
344
+ <DmsModalEscHint
345
+ class="max-sm:hidden"
346
+ :label="t('page.settings.security.esc_to_cancel')"
347
+ />
347
348
  <UButton
348
349
  color="neutral"
349
350
  variant="outline"
@@ -14,7 +14,9 @@ const METHODS_LABEL_KEYS: Record<string, string> = {
14
14
  const SKELETON_COUNT = 4;
15
15
 
16
16
  const { t } = useI18n();
17
- const { overview, attention, isUnavailable, refresh } = useSecurityOverview();
17
+ const { overview, attention, attentionToneOf, isUnavailable, refresh } =
18
+ useSecurityOverview();
19
+ const BACKUP_ATTENTION = ["backup_codes_low", "backup_codes_unsaved"] as const;
18
20
  const { formatDate, daysSince } = useSecurityFormat();
19
21
 
20
22
  function twoFactorItem(data: SecurityOverview): StatGroupItem {
@@ -53,12 +55,11 @@ function backupDetail(
53
55
  function backupItem({ twoFactor }: SecurityOverview): StatGroupItem {
54
56
  const isOn = twoFactor.methods.length > 0;
55
57
  const isUnsaved = attention.value.includes("backup_codes_unsaved");
56
- const isLow = attention.value.includes("backup_codes_low");
57
58
  return {
58
59
  id: "backup-codes",
59
60
  to: "#backup-codes",
60
61
  icon: "i-ph-key",
61
- tone: isOn && (isUnsaved || isLow) ? "warning" : "muted",
62
+ tone: (isOn && attentionToneOf([...BACKUP_ATTENTION])) || "muted",
62
63
  eyebrow: t("page.settings.security.status.backup_codes"),
63
64
  value: isOn
64
65
  ? t("page.settings.security.codes_left", {
@@ -67,7 +67,7 @@ const METHODS: MethodConfig[] = [
67
67
  const { t } = useI18n();
68
68
  const toast = useToast();
69
69
  const { $authFetch } = useAuthFetch();
70
- const { overview, attention, refresh } = useSecurityOverview();
70
+ const { overview, attention, attentionToneOf, refresh } = useSecurityOverview();
71
71
  const { formatDate, errorMessage } = useSecurityFormat();
72
72
 
73
73
  const isProcessing = ref(false);
@@ -81,6 +81,8 @@ const {
81
81
  reveal: revealCodes,
82
82
  } = useRevealedBackupCodes(computed(() => user.value?._id));
83
83
  const isRemoveOpen = ref(false);
84
+ const isEmailSetupOpen = ref(false);
85
+ const emailSetupCodeError = ref<string>();
84
86
  const totpCodeError = ref<string>();
85
87
  const removeCodeError = ref<string>();
86
88
  const regenerateCodeError = ref<string>();
@@ -112,6 +114,10 @@ const isUnsaved = computed(() =>
112
114
  attention.value.includes("backup_codes_unsaved"),
113
115
  );
114
116
  const isLow = computed(() => attention.value.includes("backup_codes_low"));
117
+ // The backup codes row and its pill, in the tone the server gives them.
118
+ const backupTone = computed(() =>
119
+ attentionToneOf(["backup_codes_low", "backup_codes_unsaved"]),
120
+ );
115
121
 
116
122
  const addItems = computed<DropdownMenuItem[]>(() =>
117
123
  addableMethods.value.map((method) => ({
@@ -256,22 +262,38 @@ async function confirmTotp(code: string): Promise<void> {
256
262
  );
257
263
  }
258
264
 
259
- async function enableEmail(currentPassword?: string): Promise<void> {
265
+ // Like the app, email codes turn on only once a code proves they arrive: a
266
+ // mistyped address would otherwise lock the user out at the next sign-in.
267
+ async function startEmail(currentPassword?: string): Promise<void> {
268
+ await run(
269
+ async () => {
270
+ await $authFetch(`${TWO_FACTOR_URL}/enable-email`, {
271
+ method: "POST",
272
+ body: { currentPassword },
273
+ });
274
+ leavePasswordDialog(() => (isEmailSetupOpen.value = true));
275
+ },
276
+ "page.settings.two_factor.setup_error",
277
+ passwordRefusal,
278
+ );
279
+ }
280
+
281
+ async function confirmEmail(code: string): Promise<void> {
260
282
  await run(
261
283
  async () => {
262
284
  const response = await $authFetch<MethodEnabledResponse>(
263
- `${TWO_FACTOR_URL}/enable-email`,
264
- { method: "POST", body: { currentPassword } },
285
+ `${TWO_FACTOR_URL}/confirm-email`,
286
+ { method: "POST", body: { code } },
265
287
  );
266
- leavePasswordDialog();
288
+ isEmailSetupOpen.value = false;
267
289
  toast.add({
268
290
  title: t("page.settings.two_factor.enable_success"),
269
291
  color: "success",
270
292
  });
271
293
  showCodes(response.backupCodes);
272
294
  },
273
- "page.settings.two_factor.setup_error",
274
- passwordRefusal,
295
+ INVALID_CODE_KEY,
296
+ codeRefusal(emailSetupCodeError),
275
297
  );
276
298
  }
277
299
 
@@ -280,7 +302,7 @@ const ADD_HANDLERS: Record<
280
302
  (currentPassword?: string) => Promise<void>
281
303
  > = {
282
304
  totp: startTotp,
283
- email: enableEmail,
305
+ email: startEmail,
284
306
  };
285
307
 
286
308
  // Adding a method asks for the account password first, so a borrowed
@@ -440,22 +462,18 @@ async function markSaved(): Promise<void> {
440
462
 
441
463
  <DmsFieldRow v-if="isOn" id="backup-codes" class="scroll-mt-6">
442
464
  <template #label>
443
- <DmsListRow
444
- bare
445
- icon="i-ph-key"
446
- :tone="isUnsaved || isLow ? 'warning' : 'muted'"
447
- >
465
+ <DmsListRow bare icon="i-ph-key" :tone="backupTone ?? 'muted'">
448
466
  {{ t("page.settings.security.backup.title") }}
449
467
  <UBadge
450
- v-if="isUnsaved || isLow"
451
- color="warning"
468
+ v-if="backupTone"
469
+ :color="backupTone"
452
470
  variant="subtle"
453
471
  size="sm"
454
472
  icon="i-ph-warning"
455
473
  :label="
456
- isUnsaved
457
- ? t('page.settings.security.backup.not_saved')
458
- : t('page.settings.security.backup.running_low')
474
+ isLow
475
+ ? t('page.settings.security.backup.running_low')
476
+ : t('page.settings.security.backup.not_saved')
459
477
  "
460
478
  />
461
479
  <template #meta>
@@ -554,6 +572,24 @@ async function markSaved(): Promise<void> {
554
572
  :loading="isProcessing"
555
573
  @confirm="confirmTotp"
556
574
  />
575
+ <SecurityCodeModal
576
+ v-model:open="isEmailSetupOpen"
577
+ v-model:error="emailSetupCodeError"
578
+ :title="t('page.settings.two_factor.email_enable')"
579
+ :description="
580
+ t('page.settings.security.two_factor.email_setup_description', {
581
+ email,
582
+ })
583
+ "
584
+ icon="i-ph-envelope-simple"
585
+ tone="primary"
586
+ :code-label="t('page.settings.security.two_factor.code_label_email')"
587
+ :confirm-label="t('page.settings.security.totp.submit')"
588
+ :loading="isProcessing"
589
+ can-send-email-code
590
+ @confirm="confirmEmail"
591
+ @send-code="sendEmailCode"
592
+ />
557
593
  <SecurityBackupCodesModal
558
594
  v-model:open="isCodesOpen"
559
595
  :codes="codes"
@@ -2,6 +2,7 @@
2
2
  import { tv } from "tailwind-variants";
3
3
  import { usePermissionPreview } from "#dms-core/app/build/composables/auth/usePermissionPreview";
4
4
  import { useNavBadges } from "#dms-ui/app/build/composables/navigation/useNavBadges";
5
+ import { navBadgeClass } from "#dms-ui/app/build/utils/navBadge";
5
6
  import DmsSearchInput from "#dms-ui/app/build/components/form/SearchInput.vue";
6
7
  import {
7
8
  findActiveSettingsPath,
@@ -27,8 +28,7 @@ const theme = tv({
27
28
  item: "flex h-8 shrink-0 items-center gap-2.5 rounded-md px-2.5 text-[13.5px] font-medium whitespace-nowrap text-muted transition-colors hover:bg-elevated hover:text-highlighted max-lg:border max-lg:border-default",
28
29
  itemIcon: "size-[17px] shrink-0",
29
30
  itemLabel: "min-w-0 truncate",
30
- trailBadge:
31
- "ms-auto hidden rounded-full bg-elevated px-1.5 font-mono text-[10.5px] font-semibold tabular-nums text-muted lg:inline",
31
+ trailBadge: "ms-auto hidden lg:inline",
32
32
  trailDot: "ms-auto hidden size-[7px] shrink-0 rounded-full lg:block",
33
33
  empty: "hidden px-2.5 text-[12.5px] text-muted lg:block",
34
34
  previewLock: "ms-auto size-3.5 shrink-0",
@@ -116,10 +116,12 @@ onMounted(revealActiveItem);
116
116
  watch(() => route.path, revealActiveItem, { flush: "post" });
117
117
 
118
118
  // The page's badge, counted by the server when the menu loads (`navBadge`)
119
- // or declared, then kept fresh by the page that shows the count.
120
- const { badges: navBadges } = useNavBadges();
121
- const badgeOf = (page: SettingsNavPage): string | undefined =>
122
- navBadges.value[page.fullId] ?? page.badge;
119
+ // or declared, then kept fresh by the page that shows the count; drawn in
120
+ // its tone, as a status pill is.
121
+ const { navBadgeOf } = useNavBadges();
122
+ const badgeOf = (page: SettingsNavPage) => navBadgeOf(page);
123
+ const badgeClass = (page: SettingsNavPage): string =>
124
+ navBadgeClass(badgeOf(page)?.tone);
123
125
 
124
126
  // "Preview as role": a settings page the role could not open stays listed,
125
127
  // locked, so the preview shows what the role loses; one it opens without all
@@ -207,8 +209,11 @@ const previewStateLabel = (
207
209
  role="img"
208
210
  :aria-label="previewStateLabel(preview.entryState(page.fullId))"
209
211
  />
210
- <span v-else-if="badgeOf(page)" :class="ui.trailBadge()">
211
- {{ badgeOf(page) }}
212
+ <span
213
+ v-else-if="badgeOf(page)"
214
+ :class="ui.trailBadge({ class: badgeClass(page) })"
215
+ >
216
+ {{ badgeOf(page)?.label }}
212
217
  </span>
213
218
  <span
214
219
  v-else-if="page.status && page.status !== 'neutral'"
@@ -0,0 +1,143 @@
1
+ import type { CommandPaletteGroup, CommandPaletteItem } from "@nuxt/ui";
2
+
3
+ /** @internal */
4
+ export const ASSISTANT_ASK_GROUP_ID = "dms-assistant-ask";
5
+ /** @internal */
6
+ export const ASSISTANT_PROMPT_GROUP_ID = "dms-assistant-prompt";
7
+ /** @internal */
8
+ export const ASSISTANT_SUGGESTIONS_GROUP_ID = "dms-assistant-suggestions";
9
+ /** @internal The hint shown on the item that Tab also reaches. */
10
+ export const ASSISTANT_MODE_KEY = "Tab";
11
+
12
+ // The assistant's items take the violet accent instead of the primary one, on
13
+ // the row and on its icon well.
14
+ const ASSISTANT_ITEM_CLASS = [
15
+ "data-highlighted:before:bg-(--dms-assistant-tint)",
16
+ "[&_[data-slot=itemLeadingIcon]]:border-(--dms-assistant-line)",
17
+ "[&_[data-slot=itemLeadingIcon]]:bg-(--dms-assistant-tint)",
18
+ "[&_[data-slot=itemLeadingIcon]]:text-secondary",
19
+ ].join(" ");
20
+
21
+ /** @internal A suggestion with its label and prompt resolved for display. */
22
+ export interface ResolvedAssistantSuggestion {
23
+ label: string;
24
+ prompt: string;
25
+ icon: string;
26
+ }
27
+
28
+ /** @internal What the search-mode "Ask the assistant" group needs. */
29
+ export interface AssistantAskGroupOptions {
30
+ /** Group header: the assistant's resolved label. */
31
+ label: string;
32
+ icon: string;
33
+ askLabel: (query: string) => string;
34
+ onAsk: (query: string) => void;
35
+ }
36
+
37
+ /** @internal What the assistant-mode groups need. */
38
+ export interface AssistantModeGroupsOptions {
39
+ icon: string;
40
+ promptLabel: string;
41
+ suggestionsLabel: string;
42
+ sendLabel: (prompt: string) => string;
43
+ suggestions: ResolvedAssistantSuggestion[];
44
+ onSubmit: (prompt: string) => void;
45
+ }
46
+
47
+ function assistantItem(
48
+ label: string,
49
+ icon: string,
50
+ onChoose: () => void,
51
+ ): CommandPaletteItem {
52
+ return {
53
+ label,
54
+ icon,
55
+ class: ASSISTANT_ITEM_CLASS,
56
+ // Cancelling the selection keeps the palette open: `UDashboardSearch`
57
+ // closes on every selection that reaches its model.
58
+ onSelect: (event: Event) => {
59
+ event.preventDefault();
60
+ onChoose();
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Appends the "Ask the assistant “query”" group after the search groups once
67
+ * the query is non-empty. The group ignores the palette's filter, so the item
68
+ * stays listed whatever matched, and is the only, highlighted item when
69
+ * nothing did: Enter then asks.
70
+ *
71
+ * @internal
72
+ */
73
+ export function appendAssistantAskGroup(
74
+ groups: CommandPaletteGroup[],
75
+ query: string,
76
+ options: AssistantAskGroupOptions,
77
+ ): CommandPaletteGroup[] {
78
+ if (!query) return groups;
79
+
80
+ const item = assistantItem(options.askLabel(query), options.icon, () =>
81
+ options.onAsk(query),
82
+ );
83
+ const askGroup: CommandPaletteGroup = {
84
+ id: ASSISTANT_ASK_GROUP_ID,
85
+ label: options.label,
86
+ ignoreFilter: true,
87
+ items: [{ ...item, kbds: [ASSISTANT_MODE_KEY] }],
88
+ };
89
+
90
+ return [...groups, askGroup];
91
+ }
92
+
93
+ function promptGroup(
94
+ prompt: string,
95
+ options: AssistantModeGroupsOptions,
96
+ ): CommandPaletteGroup[] {
97
+ if (!prompt) return [];
98
+
99
+ const item = assistantItem(options.sendLabel(prompt), options.icon, () =>
100
+ options.onSubmit(prompt),
101
+ );
102
+ return [
103
+ {
104
+ id: ASSISTANT_PROMPT_GROUP_ID,
105
+ label: options.promptLabel,
106
+ ignoreFilter: true,
107
+ items: [item],
108
+ },
109
+ ];
110
+ }
111
+
112
+ function suggestionsGroup(
113
+ options: AssistantModeGroupsOptions,
114
+ ): CommandPaletteGroup[] {
115
+ if (!options.suggestions.length) return [];
116
+
117
+ return [
118
+ {
119
+ id: ASSISTANT_SUGGESTIONS_GROUP_ID,
120
+ label: options.suggestionsLabel,
121
+ ignoreFilter: true,
122
+ items: options.suggestions.map((suggestion) =>
123
+ assistantItem(suggestion.label, suggestion.icon, () =>
124
+ options.onSubmit(suggestion.prompt),
125
+ ),
126
+ ),
127
+ },
128
+ ];
129
+ }
130
+
131
+ /**
132
+ * The groups of assistant mode: the typed prompt first, so Enter on the
133
+ * highlighted first item sends it, then the suggestions, which stay listed
134
+ * while the user types and are reached with the arrow keys.
135
+ *
136
+ * @internal
137
+ */
138
+ export function buildAssistantModeGroups(
139
+ prompt: string,
140
+ options: AssistantModeGroupsOptions,
141
+ ): CommandPaletteGroup[] {
142
+ return [...promptGroup(prompt, options), ...suggestionsGroup(options)];
143
+ }