domma-cms 0.92.1 → 0.94.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 (177) hide show
  1. package/CLAUDE.md +5 -3
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/app.js +2 -2
  4. package/admin/js/lib/action-editor-arrange.js +1 -1
  5. package/admin/js/lib/api-tokens-arrange.js +2 -2
  6. package/admin/js/lib/block-editor-arrange.js +1 -1
  7. package/admin/js/lib/blocks-arrange.js +1 -1
  8. package/admin/js/lib/collection-entries-arrange.js +1 -1
  9. package/admin/js/lib/components-arrange.js +1 -1
  10. package/admin/js/lib/dashboard-arrange.js +1 -1
  11. package/admin/js/lib/dates.js +1 -0
  12. package/admin/js/lib/forms-arrange.js +1 -1
  13. package/admin/js/lib/media-arrange.js +1 -1
  14. package/admin/js/lib/notifications-arrange.js +1 -1
  15. package/admin/js/lib/pages-arrange.js +1 -1
  16. package/admin/js/lib/related.js +1 -1
  17. package/admin/js/lib/timeline-builder.js +2 -2
  18. package/admin/js/templates/action-editor.html +6 -5
  19. package/admin/js/templates/actions-list.html +1 -1
  20. package/admin/js/templates/contacts.html +1 -1
  21. package/admin/js/templates/docs/api-actions.html +86 -60
  22. package/admin/js/templates/docs/api-authentication.html +159 -123
  23. package/admin/js/templates/docs/api-builder.html +197 -0
  24. package/admin/js/templates/docs/api-collections.html +199 -259
  25. package/admin/js/templates/docs/api-external.html +225 -0
  26. package/admin/js/templates/docs/api-forms.html +268 -0
  27. package/admin/js/templates/docs/api-layouts.html +70 -45
  28. package/admin/js/templates/docs/api-media.html +57 -80
  29. package/admin/js/templates/docs/api-navigation.html +66 -22
  30. package/admin/js/templates/docs/api-pages.html +109 -129
  31. package/admin/js/templates/docs/api-plugins.html +123 -61
  32. package/admin/js/templates/docs/api-scaffold.html +185 -0
  33. package/admin/js/templates/docs/api-settings.html +72 -64
  34. package/admin/js/templates/docs/api-users.html +74 -107
  35. package/admin/js/templates/docs/api-views.html +68 -54
  36. package/admin/js/templates/docs/components-howto.html +20 -17
  37. package/admin/js/templates/docs/components-reference.html +13 -16
  38. package/admin/js/templates/docs/components-rules.html +7 -6
  39. package/admin/js/templates/docs/components-walkthrough.html +19 -19
  40. package/admin/js/templates/docs/tutorial-crud.html +71 -40
  41. package/admin/js/templates/docs/tutorial-forms.html +51 -35
  42. package/admin/js/templates/docs/tutorial-plugin.html +132 -56
  43. package/admin/js/templates/docs/usage-actions.html +61 -15
  44. package/admin/js/templates/docs/usage-collections.html +108 -0
  45. package/admin/js/templates/docs/usage-cta-shortcode.html +14 -3
  46. package/admin/js/templates/docs/usage-dconfig.html +0 -3
  47. package/admin/js/templates/docs/usage-editions.html +213 -0
  48. package/admin/js/templates/docs/usage-media.html +22 -6
  49. package/admin/js/templates/docs/usage-navigation.html +74 -18
  50. package/admin/js/templates/docs/usage-pages.html +60 -20
  51. package/admin/js/templates/docs/usage-plugins.html +89 -17
  52. package/admin/js/templates/docs/usage-shortcodes.html +123 -70
  53. package/admin/js/templates/docs/usage-site-settings.html +50 -18
  54. package/admin/js/templates/docs/usage-tools.html +73 -0
  55. package/admin/js/templates/docs/usage-users-roles.html +99 -20
  56. package/admin/js/templates/docs/usage-views.html +36 -19
  57. package/admin/js/templates/documentation.html +153 -32
  58. package/admin/js/templates/page-editor.html +0 -5
  59. package/admin/js/templates/plugin-guide.html +15 -0
  60. package/admin/js/templates/plugin-guides.html +21 -0
  61. package/admin/js/templates/pro-docs.html +53 -234
  62. package/admin/js/templates/tutorials.html +5 -4
  63. package/admin/js/views/actions-list.js +3 -3
  64. package/admin/js/views/analytics.js +5 -5
  65. package/admin/js/views/api-endpoint-editor.js +2 -2
  66. package/admin/js/views/block-editor.js +4 -4
  67. package/admin/js/views/blocks.js +4 -4
  68. package/admin/js/views/collection-editor.js +4 -4
  69. package/admin/js/views/collection-entries.js +7 -7
  70. package/admin/js/views/component-editor.js +2 -2
  71. package/admin/js/views/contacts.js +22 -20
  72. package/admin/js/views/context-menu-editor.js +5 -5
  73. package/admin/js/views/doc-pages.js +1 -1
  74. package/admin/js/views/form-editor.js +4 -4
  75. package/admin/js/views/form-submissions.js +2 -2
  76. package/admin/js/views/index.js +1 -1
  77. package/admin/js/views/media.js +3 -3
  78. package/admin/js/views/menu-editor.js +13 -13
  79. package/admin/js/views/menu-locations.js +2 -2
  80. package/admin/js/views/my-profile.js +1 -1
  81. package/admin/js/views/page-editor.js +8 -8
  82. package/admin/js/views/plugin-guides.js +5 -0
  83. package/admin/js/views/project-detail.js +2 -2
  84. package/admin/js/views/project-settings.js +1 -1
  85. package/admin/js/views/role-editor.js +4 -4
  86. package/admin/js/views/search.js +2 -2
  87. package/admin/js/views/seo.js +17 -17
  88. package/admin/js/views/settings.js +3 -3
  89. package/admin/js/views/theme.js +3 -3
  90. package/admin/js/views/user-editor.js +1 -1
  91. package/admin/js/views/users.js +2 -2
  92. package/admin/js/views/view-editor.js +1 -1
  93. package/bin/cli.js +13 -13
  94. package/bin/lib/node-version.js +29 -0
  95. package/package.json +1 -1
  96. package/plugins/_lib/admin/mail/compose-window.js +3 -2
  97. package/plugins/_lib/admin/mail/reader-view.js +7 -6
  98. package/plugins/_lib/admin/mail/scheduling.js +4 -2
  99. package/plugins/_lib/admin/mail/templates.js +4 -4
  100. package/plugins/_lib/admin/ui/dates.js +85 -0
  101. package/plugins/blog/CLAUDE.md +31 -22
  102. package/plugins/blog/admin/views/blog.js +3 -2
  103. package/plugins/blog/admin/views/comments.js +2 -1
  104. package/plugins/blog/admin/views/post-editor.js +4 -4
  105. package/plugins/blog/blocks/blog-card-row.html +1 -1
  106. package/plugins/blog/blocks/blog-card.html +2 -2
  107. package/plugins/blog/blocks/blog-post-classic.html +2 -2
  108. package/plugins/blog/blocks/blog-post-essay.html +2 -2
  109. package/plugins/blog/blocks/blog-post-feature.html +2 -2
  110. package/plugins/blog/blocks/blog-post-minimal.html +2 -2
  111. package/plugins/blog/blocks/blog-post-sidebar.html +2 -2
  112. package/plugins/blog/blocks/blog-post-split.html +2 -2
  113. package/plugins/blog/docs/guide.md +205 -0
  114. package/plugins/blog/lib/layouts.js +3 -3
  115. package/plugins/blog/lib/page.js +2 -1
  116. package/plugins/blog/plugin.js +3 -3
  117. package/plugins/blog/plugin.json +4 -4
  118. package/plugins/blog/tests/layouts.test.js +6 -0
  119. package/plugins/feedback/CLAUDE.md +22 -3
  120. package/plugins/feedback/admin/lib/kit.js +6 -7
  121. package/plugins/feedback/admin/views/feedback.js +79 -10
  122. package/plugins/feedback/admin/views/send.js +28 -6
  123. package/plugins/feedback/docs/guide.md +95 -0
  124. package/plugins/feedback/lib/receiver.js +9 -2
  125. package/plugins/feedback/lib/sender.js +3 -2
  126. package/plugins/feedback/plugin.js +54 -6
  127. package/plugins/feedback/plugin.json +4 -4
  128. package/plugins/feedback/tests/api.test.js +74 -2
  129. package/plugins/free-tier.lock.json +49 -44
  130. package/plugins/mail-reader/CLAUDE.md +33 -18
  131. package/plugins/mail-reader/docs/guide.md +147 -0
  132. package/plugins/mail-reader/plugin.json +1 -1
  133. package/plugins/security/CLAUDE.md +4 -1
  134. package/plugins/security/admin/views/security.js +5 -5
  135. package/plugins/security/docs/guide.md +170 -0
  136. package/plugins/security/plugin.js +2 -1
  137. package/plugins/security/plugin.json +2 -1
  138. package/plugins/shopping-cart/CLAUDE.md +7 -1
  139. package/plugins/shopping-cart/admin/lib/kit.js +5 -2
  140. package/plugins/shopping-cart/admin/views/orders.js +4 -4
  141. package/plugins/shopping-cart/admin/views/overview.js +2 -2
  142. package/plugins/shopping-cart/docs/guide.md +191 -0
  143. package/plugins/shopping-cart/lib/render.js +2 -1
  144. package/plugins/shopping-cart/plugin.json +3 -3
  145. package/public/js/collection-browser.js +2 -2
  146. package/public/js/site.js +1 -1
  147. package/scripts/gen-instance-secret.js +3 -1
  148. package/scripts/setup.js +3 -1
  149. package/server/middleware/auth.js +2 -1
  150. package/server/routes/api/actions.js +47 -27
  151. package/server/routes/api/blocks.js +2 -1
  152. package/server/routes/api/collections.js +16 -52
  153. package/server/routes/api/contacts.js +66 -3
  154. package/server/routes/api/documentation.js +42 -0
  155. package/server/routes/api/notifications.js +3 -2
  156. package/server/routes/api/users.js +10 -6
  157. package/server/server.js +16 -1
  158. package/server/services/actions.js +110 -34
  159. package/server/services/adapterRegistry.js +169 -16
  160. package/server/services/adapters/FileAdapter.js +25 -0
  161. package/server/services/adapters/MongoAdapter.js +23 -0
  162. package/server/services/collections.js +104 -1
  163. package/server/services/connectionManager.js +12 -0
  164. package/server/services/dates.js +81 -0
  165. package/server/services/docs.js +13 -2
  166. package/server/services/markdown.js +75 -26
  167. package/server/services/notification-sources.js +6 -5
  168. package/server/services/passwordReset.js +2 -1
  169. package/server/services/permissionRegistry.js +3 -2
  170. package/server/services/pluginGuides.js +255 -0
  171. package/server/services/pluginInstaller.js +54 -13
  172. package/server/services/plugins.js +29 -1
  173. package/server/services/presetCollections.js +31 -5
  174. package/server/services/renderer.js +2 -2
  175. package/server/services/sidebarBadges.js +3 -1
  176. package/server/services/tools.js +4 -2
  177. package/server/templates/page.html +2 -2
@@ -1,55 +1,56 @@
1
1
  {
2
2
  "note": "Written by `make sync-free-tier` in dcms-marketplace. Do not edit these plugins here - edit them there and sync.",
3
- "source": "dcms-marketplace@9434f03",
3
+ "source": "dcms-marketplace@5dcb9db",
4
4
  "plugins": {
5
5
  "blog": {
6
- "version": "1.9.0",
6
+ "version": "1.9.3",
7
7
  "files": {
8
- "CLAUDE.md": "5f3260acc22f202ce3dd1a262a41d4a98016a71e05da8f95732dcd73b6e08e33",
8
+ "CLAUDE.md": "926730d510cc38f6764ffcb9afbed1c3ae3a547117c6c0243b14f899271350fb",
9
9
  "admin/css/index.css": "6142a6b756e1218157842528830b1e84f592f0b84b7d5ce6449db421b6f4b039",
10
10
  "admin/templates/blog.html": "a6054c1febe764bb1edf5c9e466d44a9080d9c83ba5486437b7a8244acf5ba54",
11
11
  "admin/templates/categories.html": "ab7d6fc2fedd48876c585bc216a1532b3bdd78c946dee976c1171bfe4664feb6",
12
12
  "admin/templates/comments.html": "095ff03bd5e1ed9ac75edf1ceb9fbe7b2907a1e35b261672da51e00742c2814a",
13
13
  "admin/templates/post-editor.html": "f826089f040a13188087721eaa5324707b2f7d491bad41610682242d5e86cec6",
14
14
  "admin/templates/settings.html": "e0730cd57435e592034328a699198d5e1326084767156bfc2289852f5f1a45af",
15
- "admin/views/blog.js": "3fc84f7ea9fce352f150771b4d1f775965592a8690ad7fd1110a3a4e72c49c74",
15
+ "admin/views/blog.js": "e260e1b7f9c4b22350d9c4f2170693fabb3d14e0ac9cab1d0e075630b5706fc4",
16
16
  "admin/views/categories.js": "75140feb2b382ba0ab4a52c30007845bf576883c99577fc0965783adb8808f6b",
17
- "admin/views/comments.js": "7c93f964560b5b02df270bd17526b8f749cc7c8871a9e863bb84e9cbfbc4da0e",
17
+ "admin/views/comments.js": "064dad6316dd4aea6bcd414418405894f0e5d282cd5a333c878a6cdbf27b0f2e",
18
18
  "admin/views/kit.js": "91292d63a96c1d1e9fdc0c8f931d0f31e07fa37d06f06e0fc45b308863e126aa",
19
- "admin/views/post-editor.js": "6bb0867d3b57d817497115e5c56479332f2a5324ab99aab2fdabc714c888c543",
19
+ "admin/views/post-editor.js": "60da0f8b2d508afd8dbd7671e3b408cf72d81c46b94100cf495439e18d0d63ee",
20
20
  "admin/views/settings-cog.js": "307efc93835863dd8511f8fb17a224cf45a77532c90b854a86d2cd1d4e5f9f18",
21
21
  "admin/views/settings.js": "2b7f775e37e034dee4ae3dfadea5e446c2e526bed8a066ef3ca2e777146ada23",
22
22
  "blocks/blog-card-compact.css": "eb8e8ae9a6461c362bc021ea0396c6b4481047b6b36a50eeb59e47375e49be75",
23
23
  "blocks/blog-card-compact.html": "7e1e31923808e6108d57d34309d3442b5d93b4aa4396f0e471e870249934ab8f",
24
24
  "blocks/blog-card-row.css": "f5f0249c51a40cc71ff8d054bfa887353badc79730b0f323c76ac32f7d893b44",
25
- "blocks/blog-card-row.html": "eb28d26c86c4cefcf553273e6547885b0dc03fbb6e2543e9c1aa4b132db6216a",
25
+ "blocks/blog-card-row.html": "da6c25793afc14381dd8aa70cd01b859a1dcce20e859770f6b3b24cea4b0f868",
26
26
  "blocks/blog-card.css": "d480a80a7378bd55659e9549bb30d8c106bdae1a9cd450a8c0114bfaefa94cd2",
27
- "blocks/blog-card.html": "cf676e8e9f676ea8b49637cb54a7d6fcd6f3395af8f4a8833a65ba78b35efc76",
27
+ "blocks/blog-card.html": "60a45772f7642856fe462dc1ef7e15f6b85a451d9b1a307af7ba814f0a6b1e73",
28
28
  "blocks/blog-post-classic.css": "9acac617847b5a10dabd5efccb601572e2751f792c4038475325f11e24142584",
29
- "blocks/blog-post-classic.html": "1fab77c7aeb67fe72b73c4db42ff4c3a66466c041104639f4898c390c665fc38",
29
+ "blocks/blog-post-classic.html": "380c55a638645ca5bf5810836bcf9327d050123b513007027012f451f2d16558",
30
30
  "blocks/blog-post-essay.css": "d1842443927ea7c93772a06ade69fc834099e6820d99767e0aab8e80981a3784",
31
- "blocks/blog-post-essay.html": "f699fb6399d8845d762dfde4dd6f318351427d412363b786105ddc9f3a3af2c1",
31
+ "blocks/blog-post-essay.html": "85bb2eafd07192f97fdf7a9690e80aab266edd43277d64fa0e91ebbc37c2d243",
32
32
  "blocks/blog-post-feature.css": "7d8a242b627f3826df21d5470e643b386967122ea3585e69f96cce9ffc2452e9",
33
- "blocks/blog-post-feature.html": "c0408538cc269e4f2873f9c9c44a000347e4d5cafe3ecb024f62112ea711a9ab",
33
+ "blocks/blog-post-feature.html": "d34cb682f2285f6688a86b539e76f9d2229db4cce61ef0200cf5940148983a80",
34
34
  "blocks/blog-post-minimal.css": "2323b84a7d0a99e1c85c30faa18bb2ee16763ff08cfe87f181ffadf8cff84dd0",
35
- "blocks/blog-post-minimal.html": "3a04925980018a13038ebf4bcfecc4f69284c1bac087631ddc0f66e0e95e07fe",
35
+ "blocks/blog-post-minimal.html": "b0fa2e33389b186fc6834e839a424ef0b3122d13e31219cb91dd58ccd82021b9",
36
36
  "blocks/blog-post-sidebar.css": "3c66109fa48ca448605eb5acd0c998f43649fb3747813b4a811992ec89886007",
37
- "blocks/blog-post-sidebar.html": "d510a9e272e9415941064b4567def47a179b88825e29c6dd340540870ab04a61",
37
+ "blocks/blog-post-sidebar.html": "2fb6e1348c4800ae5cea08db02fc842916e16f64ab91ef6a20a47f7db310bba1",
38
38
  "blocks/blog-post-split.css": "61ae29850d6c324ffbfc2110a917d5980ad17b0635870656ccd885d20ac9ec86",
39
- "blocks/blog-post-split.html": "c09a15c537c0502d5200dc8f3137c4f56394b0656557a88d23d3326c7238c249",
39
+ "blocks/blog-post-split.html": "1b5fa63eee45b06d62757c96bd069645ace69e3b5a6e145ba1612626b2e4c7a2",
40
40
  "collections/categories/schema.json": "0728e522ed8e3c623fd45e137cd03e0960aa5158e70613ec8628c8b3928e0929",
41
41
  "collections/comments/schema.json": "29cbd289cbfcfc4158c9357a218db4ce06eddc3370bfbf9afb7d7f52065a849e",
42
42
  "collections/posts/schema.json": "19a5a770d3eefea73f4117454bd1312a7a7e3d727cfcdfde141139fbc81cabd7",
43
43
  "config.js": "ee67396262762f5c31269d258affd75f7392a3e364f72f7792c2f1cdafd94bba",
44
+ "docs/guide.md": "6a7466881851797789a18e0d32c4fcb515f2259b110948c55be03923c38e0de5",
44
45
  "lib/access.js": "af47440c7233a56000641a3231d7e9ff81b12f40dfb9528aa6dd4e2dd9bca8df",
45
- "lib/layouts.js": "bbb61ac7ec2e0cecd3d59700e9f3bd1064cd53b2e9f639d28b45ffc2ad80ab27",
46
+ "lib/layouts.js": "0a921fb427c5ffe0a31eb687a6f8f10cb5573a1ad638a88ce386b687b49a2692",
46
47
  "lib/nav-link.js": "6a8f9368bb8aee1c21e90fb335a691859cb9a9bc250c2b9167a33ca9189fa2ff",
47
48
  "lib/notices.js": "a9a2918ee6f5f7e0bbaeced0d3539584279bdbe68d15472a8615bdddb4ef905b",
48
- "lib/page.js": "464face32871185368de096ed9a6aa635523bf319af6459308cfceb77d66f82c",
49
+ "lib/page.js": "bfb6a355a027feaf775d6d8ac871fc6d999354774e018f8e16caec551e99f40a",
49
50
  "lib/render.js": "0e97c6a950bc962c8d8ea4b2f21d06cb73fbcdc738d8942d6935d01309a29b1d",
50
51
  "lib/samples.js": "e8cdb77ce22e43937707f6854d3a5cb722437dc584e22ac7b24480bef686cf85",
51
- "plugin.js": "a6e4442400594116c43aab1db706f0b9c2953e85699aa458709752c6c27fd781",
52
- "plugin.json": "234c8067a4bf9f214667b5c99e05933c3321f0b8b35815a76e8ac8eacbbda857",
52
+ "plugin.js": "0aa46a01b5298065497f39729b88e34a9f56ac684b0a14f8d94ab6007f0221d0",
53
+ "plugin.json": "f74ba12c5dfef7d0b9accd267cc0c4e333ea61a936cc8afa565db82da5b02389",
53
54
  "plugin.public.js": "ab23fadd5655fab8eea0e636a3e1f8ced78432d6da929059a4a147f0805ff3a4",
54
55
  "public/blog.css": "fd733e6b624190da1e70dc1b6613e2376efd7f7570a28fab311d81006d03d960",
55
56
  "public/blog.js": "d01c72a9834dcc7c5241923371f2b164ac30d20ac9f1a5c5dc5b71fa3cb09f08",
@@ -65,47 +66,49 @@
65
66
  "templates/post.html": "28e9f06071115ecc4fbba007b35782732bf12ef72528525893d424e3c4f0313f",
66
67
  "tests/access.test.js": "65b760910117c3a44195543acdcff91074f39b4e5567911a2bd8676204152cb2",
67
68
  "tests/comment-notices.test.js": "34026cc9a2a4746cf87bc41df8be849fadaa43091a72ac2aa1d06c1f437bf618",
68
- "tests/layouts.test.js": "5325aad79a153f1d17f49b27223f50be182a2acce8ee8be7da658ae92ae36fe8",
69
+ "tests/layouts.test.js": "4219d0e1dab7ed11d176e8eb55086531fc4eaf375cdf3d7aea5b85073d2ace7e",
69
70
  "tests/nav-link.test.js": "d36e33ad7483abca27ae6e2098ad2f75bafa18d91f3beba70372ebe6b9b88994",
70
71
  "tests/notices.test.js": "2b02aa84e780dc4c82e6c503fc8fe17c64a63bee0709627866ef97c99cb4e131",
71
72
  "tests/public.test.js": "a6fcdc4ede242aa050ffe5a52f970d6bec1f4a70ae78967eb8a7bdeec73df19d"
72
73
  }
73
74
  },
74
75
  "feedback": {
75
- "version": "1.0.0",
76
+ "version": "1.1.2",
76
77
  "files": {
77
- "CLAUDE.md": "8cdfa2ccf028cb6b9e4a185dc81e5547927132bda9b6326725b99fc221bc6758",
78
+ "CLAUDE.md": "38122c71cacfda28fa87afd3321ae77b8a8b02facfa2f0d1bc637e6cfa59e448",
78
79
  "admin/css/index.css": "f2e934c8b94f883340148aba17945dfa725eeb2b0ddb6cabb8a8c17e2487c1de",
79
- "admin/lib/kit.js": "3e153c8e1f14ab88c2ed847e65d3b3f597ef833603f2e4a50aa878f10f8c490e",
80
+ "admin/lib/kit.js": "913761a2cc19b69bed3f05267d45fdb1c08338b1edb081b9272231733d9b5790",
80
81
  "admin/templates/feedback.html": "a366dce78f6c3e84a7cb0ac63eba53e1980a4eb44154496a0536f5214c5bb36f",
81
- "admin/views/feedback.js": "11da66fd3aa18dabdc6a334ebd0eb55040ece95128a8f1cfbf08d89b1765cac1",
82
+ "admin/views/feedback.js": "ac33bf7de7445722dd055dcd9b6737a545b70379863328bc0d112abe52ce29c3",
82
83
  "admin/views/inbox.js": "649b86844230be73939d82aa7c97432252c0b119baa102ebf53808e1d5f23737",
83
- "admin/views/send.js": "1a876a20cf34e1348a6ee7239432801dc9f26852a2e65d386c34b0f9674856b2",
84
+ "admin/views/send.js": "5868038a4020158746c5705d2a0ab5f552ac5021328a1dcf64a2def890495427",
84
85
  "collections/feedback-reports.json": "a072291b52463f7785a77f5f77d36b8238ba8f89cfc2672c6642eccdb3144486",
85
86
  "config.js": "29d9999d1028c2c11931f67fb9abdf35132430b7cf58c161681e734c0b45143a",
86
- "lib/receiver.js": "39b2ebac0a705b4cf182913ec8d006dff4b522fb5aaf6023747b48137559ea6d",
87
- "lib/sender.js": "efb00d06ec149843ffd746f68f3c1ba94333f2205942a786bb37fa91eb3e9806",
87
+ "docs/guide.md": "e006cc3f3c114583285964bc1b5d22b2a01285c2d2015d132434bfed363917d4",
88
+ "lib/receiver.js": "c6ab2858dc7bfff6de19b41c84295c0c918ba50ddc57f6d3d83f1352662ebd91",
89
+ "lib/sender.js": "037f9b4469a34ffe1d302a633e664860d625f7fb3be4dfadd01b8a3d094849b7",
88
90
  "lib/shape.js": "67acf62f812c2c9a01b52776641ed7ae1601c59ebab8ea278a07d1087a9f299b",
89
- "plugin.js": "a2f4cccc7ec38cf3877db8390a880e45d1b0e46a61df8dc60ac1300285fe66ae",
90
- "plugin.json": "4c92ad716deb845058db90cbc67c2e386e6d9f85d1c1d60481eb6b130dbc427e",
91
- "tests/api.test.js": "6984a2a8c31b06b0ccb1092853567856fc79027345b26e8114dcce631a0949e9",
91
+ "plugin.js": "96c95eb4c4c0b84c23822eb6e69aa8ae050d1ba14b283be848bcaa5b236a1ceb",
92
+ "plugin.json": "1cc8f582be6b73e39fa1600ea738a5173d6c7378b01b092d35999d8f5de80741",
93
+ "tests/api.test.js": "e822b0b9cd2dc5d32eb1f77ae76e34adfe13fb7d0fbe77fe3a7c3cca2a9c6b11",
92
94
  "tests/shape.test.js": "5133578142a79cc1a100b3215436372129f01fb0de8c757fd743ff99e1d7e37f"
93
95
  }
94
96
  },
95
97
  "mail-reader": {
96
- "version": "1.2.1",
98
+ "version": "1.2.3",
97
99
  "files": {
98
- "CLAUDE.md": "6418f9a4c56ed617962afa7c82c88ea40af060e796ceb86310bafafa79434d55",
100
+ "CLAUDE.md": "e7722bf8464087e6568bac2f3cf2cf1eb4a84702e3f5d591737976db4fe5313e",
99
101
  "admin/views/mail.js": "c5c6483f9618c75cac8f4be32d3aea99511db76d4b9d38b8ef43bdc1d35db3c5",
100
102
  "config.js": "6e65803ad822a9acda40c203631b8eb32a87cf9d9e5f1c7cb59d01aa406075c6",
103
+ "docs/guide.md": "518441f4dcbd95c1318e1db9ad002b024dbe7b2aa3dee479ebb6976f00355d18",
101
104
  "plugin.js": "7f602a60c4eca7d3637659d03d5de9e242584666846b16c9e5a1b758e4856c9f",
102
- "plugin.json": "3a22a46f3605589104d054b8f4bf5503052fa203c1d4271a837332f2fab7c56b"
105
+ "plugin.json": "2cae5b11f9975bcf76aa6369001e42ba4e5cf5bc3cee169f125720383acd7fdb"
103
106
  }
104
107
  },
105
108
  "security": {
106
- "version": "1.1.0",
109
+ "version": "1.1.3",
107
110
  "files": {
108
- "CLAUDE.md": "dc6f8755b8e6cdcac69cd928654395cde794e5b698032c132ce83dede24acead",
111
+ "CLAUDE.md": "29507f4460053b1a16a1902d47c007213d83ad3b497e86398ccaab92d07d3d51",
109
112
  "admin/lib/blocks.js": "5ef346b7de751518f8beb9212e3b89b270ae236560ae3d8a1f40d129c50918ff",
110
113
  "admin/lib/health.js": "3b233a3d29b2d69c333321602c13233b8221cfe1114aa03903739d9ae8ad65d5",
111
114
  "admin/lib/ip.js": "cf33be4fa2e2cad37bafd12ebec17f293891a88de402caad1bf9c9a57ce87680",
@@ -115,11 +118,12 @@
115
118
  "admin/lib/resets.js": "3724a2aec9f3c18ada96948aa6ce375ae3505bb7623a2c556e2e2670c5ca343c",
116
119
  "admin/lib/settings.js": "b319953914f3a6f029ba991b36fe6fcd521956ea099578be42505f30e6c32faf",
117
120
  "admin/templates/security.html": "df5798497ca1fd5ed200ce0ff921a055168ac7720f38b2db1bebf2c0efe71f04",
118
- "admin/views/security.js": "280c1681f1aae6cf82a141e670d046c160c5f5fc3cb9de43f9d134a08b15a199",
121
+ "admin/views/security.js": "89c8dcaea89a0b8999798ecb84e9b41e5a08ceefa404aff529b02a6f28874361",
119
122
  "bin/unlock.js": "5fcc3c0ef3ba3c424a366aa62896f51abad578183dfde283aa838a86549ca785",
120
123
  "config.js": "e6a00f7816637687d6a68ebbac65a71489246a926c28523c2a78d859e75b7ae0",
121
- "plugin.js": "db5dc669a6a26bbe8756da59bdd04217e403be48012921b6d2b868cec57af489",
122
- "plugin.json": "319d4333d03ed7d42ef9c2d0999befff7594343be963b6fee9c90bf7916ad142",
124
+ "docs/guide.md": "a27d5303f9146057fd79c2093d7476ada1a5ee4fd14d34df7f94ad8555d88ca0",
125
+ "plugin.js": "9d3a2e8b56dd637407c8abd93252dda9f67042231bc79d35771f766a62022c14",
126
+ "plugin.json": "c3457c117c66a8434318e51e13f0569d5b0f3f5e8500c9864a95c9c91049ce15",
123
127
  "server/common-passwords.NOTICE": "3b148a49a569bfa24feaa7879db602da8f0607fa61ab6c52b7a62391fc7c009a",
124
128
  "server/common-passwords.txt": "68782d6a4a19a4768d5f15dd66bd534e7a33055cc755411e33f16d18c50fdcce",
125
129
  "server/facts.js": "25538a8d1cfe012688fceb8ec0d0dd7b43ac9e168430723d6fb731183701fb46",
@@ -128,30 +132,31 @@
128
132
  }
129
133
  },
130
134
  "shopping-cart": {
131
- "version": "1.0.0",
135
+ "version": "1.0.3",
132
136
  "files": {
133
- "CLAUDE.md": "0f89bab23796eba425e26792c952eb00eddb09fb2c74a4a7a1fb3722437dae0b",
137
+ "CLAUDE.md": "fb9167502ceac9b1cc264f6c287c2e778e628137c87b6f87f84959618df4f1cb",
134
138
  "admin/css/index.css": "a6bab1d0072e364ee1d94c2d7d7b99c3660ba91c5eba537e269a579f5b17f924",
135
- "admin/lib/kit.js": "3e721a53a97f136833975f99d9f985fcec4018b328b4d355267fe11702234fa0",
139
+ "admin/lib/kit.js": "c2111d1b10c6c8b18bc061f9f55c83ee8baecc2bcff66f74eb38298f39886d86",
136
140
  "admin/templates/shop.html": "c5bb03f1f7cbabfbd8202f3533783eb1848103755b1ba25fdcf1fd8e9478cb8e",
137
- "admin/views/orders.js": "8255b75a4a0eeccec7cf76982a5f64469ab7a2ad5f742a54afe55bca61ace713",
138
- "admin/views/overview.js": "3901dfb9527fed0689bb31098488136471ff081b9fe28979339f39b2d53e3bd3",
141
+ "admin/views/orders.js": "e69694e5fe0b56662a2df3f7c7b8a5cc5f9a602debda469c2da464cb78fdd458",
142
+ "admin/views/overview.js": "6abd9779cf5fc359c4708f96cf0e8ecad155fb52583b2ec854400b288fc9ba98",
139
143
  "admin/views/products.js": "4947de61aa57da59fd0e819ac0f5380bbdee6b73855bd91d3872445fdd6c3e2d",
140
144
  "admin/views/settings.js": "28cb0b5e8d45e3723ffe77e46299bfc13fbad02a2ae8103843c6894dc232387a",
141
145
  "admin/views/shop.js": "a53a1793251b962a87c5d01685f934b6476bcfcdd51e3dea3a319eb8c03a4137",
142
146
  "collections/shop-orders.json": "0bca8eef2d5f9108651f73d273480265c167dc983aa115ce59613d13015ca255",
143
147
  "collections/shop-products.json": "4d4613ecec7b8a5a0744c4f0a57b17a4429b461e060206a4bbdea5ad7c8c75dc",
144
148
  "config.js": "dcb91db2839c1ab9e40bb1811397024fad9e37e3753d17e2baa2b9b31d4a7d7c",
149
+ "docs/guide.md": "cc9935594a14ea8cda1eb0d9c507bbc580ba30c8d6beb9265f55f9b03a4c1ba1",
145
150
  "inject/body.html": "de20f92c83229545d20e5186fd37ef0046a8820881bff34e7df5391cd7a2c75d",
146
151
  "lib/extensions.js": "2cba90efef5b437ace557c7265fe7363ac7923d155681087a63e31e1168f6601",
147
152
  "lib/payments.js": "84ad14813688de1f38448b4935180a786cda8af2f7f4b8890a8f1d426982d1aa",
148
153
  "lib/pricing.js": "94953075062e9b36cdbd6ea149662d013d97ee12359a9ff7459669beff99ab99",
149
- "lib/render.js": "3eacb4f7bf62376873e6fe59e2fe55e72a97fda8ed6bff73b93791f568cceffd",
154
+ "lib/render.js": "310d528f69cd0160db3bd3d3c88f16221e43004c93856587175c0bb149fe3a3d",
150
155
  "lib/samples.js": "912b3d5bd543dffa5f870caf3c2b873f642fd55e3ebf3f97e1b31eeaaac77d3b",
151
156
  "lib/shop.js": "36e1cea342e23f1a53b9fac63b993f860afc1bcb74b25bef9f7bf8eaf07d2d85",
152
157
  "lib/summary.js": "a510386e28eb19d9617f488551d1057819ae9f76fc85c744f2e5dbce492607fe",
153
158
  "plugin.js": "594c2174e36566f90ac1e9ff11cb76656ad111dc392dc8e9556d731b7dd0a41a",
154
- "plugin.json": "d5a3aeebb422cde97ac30605e8706acfb39e69601e7e6ae5d626c3744d49093c",
159
+ "plugin.json": "1d76fea187dea958b82e752e5d6e7a729217b08b061490c8590e2c6e6537496e",
155
160
  "plugin.public.js": "176383dfa7af913c433e0397584c46a67a21ab3307b80a08f0aaf50da2634f9e",
156
161
  "public/cart.js": "fcc706773a36e4bfd02ea235bcdad629137e996218e0a0e23892e33c689f2a6e",
157
162
  "public/lib/money.js": "d22b2b6f80dde6f7887b69e24188c05ea93c02217aba639b5331aa3bd616f4fc",
@@ -1,26 +1,30 @@
1
- # mail-reader — AI Assistant Guide
1
+ # mail-reader - AI Assistant Guide
2
2
 
3
3
  ## Purpose
4
4
 
5
5
  Read-only IMAP mailbox reader. Connects to a **standard IMAP server** and displays mail. It
6
6
  deliberately cannot write: no mark-as-read, no flagging, no move, no delete, no send.
7
7
 
8
- Full mail (sending, flags, drafts, search, sync) is **`email-pro`** — a separate Pro-tier plugin
9
- that requires MongoDB. Nothing here should grow toward it by accident — phase 1 keeps **no message store at
8
+ Full mail (sending, flags, drafts, search, sync) is **`email-pro`** - a separate Pro-tier plugin
9
+ that requires MongoDB. Nothing here should grow toward it by accident - phase 1 keeps **no message store at
10
10
  all**, which is what makes the Pro sync layer a clean addition rather than a migration.
11
11
 
12
12
  ## Files
13
13
 
14
- - `plugin.json`, `config.js`, `plugin.js` — manifest, defaults, routes
15
- - `admin/views/mail.js`, `admin/templates/mail.html`, `admin/css/mail.css`
14
+ - `plugin.json`, `config.js`, `plugin.js` - manifest, defaults, and the wiring: it registers
15
+ `registerReadRoutes()` from `_lib/mail/readRoutes.js` behind the `email` permission, and nothing else
16
+ - `admin/views/mail.js` - the only admin file in this plugin. Its template and stylesheet are shared with
17
+ Email Pro and live in `plugins/_lib/admin/mail/` (`mail.html`, `mail.css`, and the section modules such
18
+ as `image-senders-section.js` and `diagnostics-section.js`)
16
19
 
17
- The IMAP core — crypto, account store, connection pool, renderer — is **not here**. It lives in
20
+ The IMAP core - crypto, account store, connection pool, renderer - is **not here**. It lives in
18
21
  `plugins/_lib/mail/`, shared with `email-pro`, with its state at `plugins/_lib/data/`. See
19
22
  `plugins/_lib/mail/CLAUDE.md` before changing anything about credentials or connections.
20
23
 
21
24
  ## Routes
22
25
 
23
- All under `/api/plugins/mail-reader`, all requiring authentication.
26
+ All under `/api/plugins/mail-reader`. Every route but `/body` needs a signed-in user holding the `email`
27
+ permission (`read`); `/body` takes a short-lived signed token instead (below).
24
28
 
25
29
  | Method | Path | Purpose |
26
30
  |---|---|---|
@@ -34,6 +38,12 @@ All under `/api/plugins/mail-reader`, all requiring authentication.
34
38
  | GET | `/messages` | One page of envelopes, newest first. `?account=&folder=&page=&limit=` |
35
39
  | GET | `/messages/:uid` | One message, rendered. `?account=&folder=&images=load` |
36
40
  | GET | `/messages/:uid/attachments/:index` | Streams one part as a download. `?account=&folder=` |
41
+ | GET | `/body` | A message body as its own document for the sandboxed iframe, with its own CSP. Authorised by a short-lived token naming one message for one user (an `<iframe src>` cannot send a Bearer header). |
42
+ | GET | `/diagnostics` | This build and its configuration for a bug report: hosts, usernames, the key SOURCE - never a key or password. |
43
+ | GET | `/image-senders` | The senders whose remote images always load, and whether remembering is on. |
44
+ | POST | `/image-senders` | Trust a sender's images from now on. |
45
+ | DELETE | `/image-senders` | Stop trusting one. |
46
+ | PUT | `/image-senders/remember` | Turn trusted senders on or off; off ignores the list without discarding it. |
37
47
 
38
48
  A user may hold several mailboxes. Omitting `?account=` selects their first one, so the reader can
39
49
  open on something rather than asking which before it has shown anything.
@@ -42,13 +52,16 @@ open on something rather than asking which before it has shown anything.
42
52
 
43
53
  - **Read-only is enforced, not merely omitted.** Mailboxes open `{readOnly: true}` and ImapFlow
44
54
  fetches with `BODY.PEEK[]`. Drop either and the server sets `\Seen` as a side effect of *reading*
45
- a message — the user's phone then shows mail they never opened. There is no route that writes;
55
+ a message - the user's phone then shows mail they never opened. There is no route that writes;
46
56
  keep it that way until mark-as-read is a deliberate, visible action.
47
- - **No permission resource, on purpose.** Access control is ownership: every route resolves the
48
- account from `request.user`, and no route takes someone else's id. Registering a `mail-reader` resource
49
- would 403 every user *including super-admin* (no bypass since 0.22) from the moment the plugin was
50
- enabled, because a new resource belongs to no role and nothing back-fills it.
51
- - **Credentials are not a collection.** `schema.plugin` is a badge, not a visibility gate — a
57
+ - **The `email` permission (1.2), shared with Email Pro.** plugin.json declares it with
58
+ `grant: ["admin"]`, so admins hold it from the moment the plugin is enabled; give it to other roles under
59
+ Roles. Every route checks it (`mayUse` wrapped into `authenticate` in plugin.js; an older core without
60
+ `requirePermission` keeps the old signed-in rule). On top of that, access is ownership: every route
61
+ resolves the account from `request.user`, and no route takes someone else's id. (Before 1.2 there was
62
+ deliberately no resource, because one declared WITHOUT a grant would have 403ed everyone - the `grant`
63
+ is what makes it safe.)
64
+ - **Credentials are not a collection.** `schema.plugin` is a badge, not a visibility gate - a
52
65
  plugin-owned collection still appears in admin Collections with a working Entries link, is
53
66
  reachable via `/api/v1`, and can be pointed at a shared Mongo. Hence `data/accounts.json`, 0600,
54
67
  gitignored, and excluded from the npm `files` list.
@@ -61,12 +74,14 @@ open on something rather than asking which before it has shown anything.
61
74
  - **`transformTags` runs BEFORE attribute filtering** in sanitize-html. `target` and `rel` must be
62
75
  on `allowedAttributes.a` or the values the transform just set are stripped straight back off, and
63
76
  links inside the sandboxed iframe silently do nothing. This has already been fixed once.
64
- - **Remote images are blocked by default** and the per-message override is never persisted — it
65
- lives in a `Set` in the view. Loading a remote image reports the open back to the sender; that
66
- decision should be per message, not a preference someone forgets they set.
77
+ - **Remote images are blocked by default.** "Load images" on one message is never persisted - it
78
+ lives in a `Set` in the view - because loading a remote image reports the open back to the sender.
79
+ What IS persisted is the per-user list of **trusted image senders** (`/image-senders`): a sender added
80
+ there always has its images loaded, and the whole list can be switched off without losing it. That is a
81
+ deliberate, visible choice per sender, not a forgotten global preference.
67
82
  - **The iframe sandbox has neither `allow-scripts` nor `allow-same-origin`.** That pair is the whole
68
83
  defence. `allow-popups` is there only so links can open in a new tab.
69
- - **Attachment downloads go through `fetch` + blob**, not a plain `<a href>` — the route needs the
84
+ - **Attachment downloads go through `fetch` + blob**, not a plain `<a href>` - the route needs the
70
85
  bearer token, which a link cannot carry.
71
86
  - **Single-process only.** The connection pool is module state, like the messages plugin's SSE
72
87
  registry. Multiple workers would each open their own connections and hit per-user server limits.
@@ -74,7 +89,7 @@ open on something rather than asking which before it has shown anything.
74
89
  ## Testing
75
90
 
76
91
  `services/secretbox.js` and `services/render.js` are pure and unit-tested. Connection behaviour
77
- should be checked against a **real IMAP server** — `mail.dommajs.org` answers on 993 (TLS) and 143
92
+ should be checked against a **real IMAP server** - `mail.dommajs.org` answers on 993 (TLS) and 143
78
93
  (STARTTLS). A wrong-credentials attempt is enough to verify TLS, auth-failure mapping and that the
79
94
  pool leaves nothing open; it does not verify folder listing or rendering, which need a real mailbox.
80
95
 
@@ -0,0 +1,147 @@
1
+ ---
2
+ title: Guide
3
+ order: 1
4
+ ---
5
+
6
+ Mail Reader lets you read your own email inside the admin. It connects to any standard IMAP mail server and never changes anything there: nothing is marked as read, moved, deleted or sent.
7
+
8
+ ## What it does
9
+
10
+ - Three panes: **Folders**, the message list and the message itself. Drag the dividers to resize them.
11
+ - Several mailboxes per person. Each person sees only their own mailboxes; nobody else in the admin can see them.
12
+ - Read-only by design. Opening a message does not mark it as read on the server, so your phone or desktop mail app still shows exactly what you have and have not read.
13
+ - Remote images are blocked until you ask for them.
14
+ - Attachments can be downloaded.
15
+ - An unread count on the **Mail Reader** sidebar item, taken from each mailbox's Inbox.
16
+
17
+ Mail Reader is free. For sending, replying, flags, moving, deleting and search, see "Mail Reader or Email Pro" below.
18
+
19
+ ## Getting started
20
+
21
+ You need your mailbox's IMAP details from your email provider: the server name, port, username and password.
22
+
23
+ 1. Open [Mail Reader](#/plugins/mail-reader) from the admin sidebar.
24
+ 2. If no mailbox is set up yet, click **Add a mailbox**. Otherwise click the cog at the top of the **Folders** pane (**Mail account settings**).
25
+ 3. On the **Mailboxes** tab, click **Add mailbox**.
26
+ 4. Fill in:
27
+ - **Name** - what to call this mailbox, for example "Office".
28
+ - **IMAP server** - for example `mail.example.com`.
29
+ - **Port** - 993 for TLS, 143 without.
30
+ - **Username** and **Password**.
31
+ - **Use TLS (port 993)** - leave ticked unless your provider says otherwise. Ticking or unticking it switches the port between 993 and 143 until you type a port yourself.
32
+ 5. Click **Test connection**. Fix anything it reports.
33
+ 6. Click **Save**. Your folders appear on the left.
34
+
35
+ ## Screens
36
+
37
+ ### Folders
38
+
39
+ - Click a folder to list its messages. Folders that cannot hold mail are hidden.
40
+ - The cog opens **Mail Settings**.
41
+
42
+ ### Message list
43
+
44
+ - Newest first, 50 messages a page. Use **Newer** and **Older** to page.
45
+ - Icons on a row show high priority, messages you have replied to, and attachments.
46
+ - **Refresh** (the circular arrow) fetches the list again.
47
+ - Right-click a message for **Open**, **Copy sender's address**, **Copy subject** and **Add sender to contacts**. The last one is shown only when the Contacts Tool is switched on, and is greyed out when the sender is already in your contacts.
48
+
49
+ ### Message
50
+
51
+ - The header shows who sent it and when. Click **Show headers** for Reply-To, all recipients and the message id; **Hide headers** folds them again. Your choice is remembered.
52
+ - Messages are shown in a locked-down frame: scripts in email never run.
53
+ - If a message has remote images, a notice says how many were blocked. Click **Load images** to load them for this message only. Loading them tells the sender you opened it.
54
+ - **Always from** (followed by the sender's address) trusts that sender, so their images load straight away in future. It appears only when **Offer to remember senders** is on (see Settings).
55
+ - Click an attachment to download it.
56
+
57
+ ### Keyboard
58
+
59
+ Press `?` to see the list.
60
+
61
+ | Key | Does |
62
+ |---|---|
63
+ | Up and Down, or j and k | Move through the list |
64
+ | Left and Right | Move between folders and messages |
65
+ | Enter | Open |
66
+ | Esc | Clear the selection |
67
+ | ? | Show the shortcut list |
68
+
69
+ The list also names shortcuts for replying, starring and deleting. In Mail Reader they do nothing; they work only in Email Pro.
70
+
71
+ ## Settings
72
+
73
+ Click the cog in the **Folders** pane to open **Mail Settings**. It has three tabs.
74
+
75
+ ### Mailboxes
76
+
77
+ Lists your mailboxes with **Edit** and **Remove**, plus **Add mailbox**.
78
+
79
+ - When you edit a mailbox, leave **Password** blank to keep the saved one.
80
+ - **Remove** forgets the mailbox and deletes its stored password. Nothing on the mail server changes.
81
+ - Passwords are stored encrypted on the server and are never sent back to the browser.
82
+
83
+ ### Images
84
+
85
+ - Lists the senders you trust. Their remote images load without asking. Click **Stop trusting** to remove one.
86
+ - **Offer to remember senders** switches trusted senders on or off for the current mailbox. Switching it off ignores the list but keeps it.
87
+
88
+ ### Diagnostics
89
+
90
+ Shows what the screen knows about itself: the build it is running, the mailbox and folder, and recent errors. Click **Copy diagnostics** to copy it for a support request. It never shows passwords.
91
+
92
+ ### Site-wide options
93
+
94
+ Whoever runs the site can change these in the plugin's configuration file (`config/plugins.json`). There is no screen for them.
95
+
96
+ | Option | Default | Meaning |
97
+ |---|---|---|
98
+ | `listLimit` | 50 | Messages per page |
99
+ | `allowRemoteImages` | false | Load remote images without asking |
100
+ | `allowInsecureTLS` | false | Accept mail server certificates that do not verify (see Tips) |
101
+ | `connectionTimeoutMs` | 20000 | How long to wait for a mail server, in milliseconds |
102
+ | `maxConnections` | 20 | Most mail server connections kept open at once |
103
+ | `maxMessageBytes` | 25000000 | Largest message that will be opened, in bytes |
104
+
105
+ ## Permissions and roles
106
+
107
+ In System > Roles, Mail Reader uses the **Email** permission in the **Plugins** group. It has one action:
108
+
109
+ | Action | Label | Allows |
110
+ |---|---|---|
111
+ | `email.read` | Use | Add your own mailboxes and read them |
112
+
113
+ Only the **admin** role has it by default. To let other staff read their own mail here, give their role **Email > Use**. Even with this permission, people only ever see their own mailboxes.
114
+
115
+ Email Pro uses the same **Email** permission.
116
+
117
+ ## Tips
118
+
119
+ - **Use the mail server's real name.** If **Test connection** says "The mail server's TLS certificate could not be verified", the server name you typed probably does not match the name on the server's certificate. This often happens with a name like `mail.yourdomain.com` when the provider's certificate is issued for their own host name. Ask your provider for the exact IMAP host name (the one their certificate covers) and use that. Your mailbox is the same either way.
120
+ - Only set `allowInsecureTLS` to true for a mail server you control that has a self-signed certificate. It turns off the check that the server is who it says it is, for every mailbox on the site.
121
+ - Other error messages mean:
122
+ - "That mail server hostname could not be resolved" - check the spelling of **IMAP server**.
123
+ - "The mail server refused the connection on that port" - check **Port** and **Use TLS**.
124
+ - "The mail server rejected that username and password" - check the login details. Some providers need an app password.
125
+ - Your pane widths and folded folders are remembered in your browser, and carry over if you move to Email Pro.
126
+
127
+ ## Mail Reader or Email Pro
128
+
129
+ | | Mail Reader (free) | Email Pro (paid) |
130
+ |---|---|---|
131
+ | Read mail, folders, attachments | Yes | Yes |
132
+ | Marks mail as read on the server | Never | Yes, like any mail app |
133
+ | Send, reply, forward, drafts | No | Yes |
134
+ | Flag, move, delete, spam | No | Yes |
135
+ | Search and conversations | No | Yes |
136
+ | Rules, away message, send later, snooze | No | Yes |
137
+ | Needs MongoDB | No | For search, conversations, rules, send later, snooze and the away message. Without it, reading, filing and sending still work. |
138
+
139
+ When Email Pro is installed it takes over from Mail Reader. Your saved mailboxes, passwords and trusted senders carry across.
140
+
141
+ ## Limitations
142
+
143
+ - Read only. It cannot send, reply, mark as read, flag, move or delete.
144
+ - No search, and no conversation (thread) view.
145
+ - IMAP only. There is no sign-in with Google or Microsoft accounts; use the provider's IMAP settings and, where needed, an app password.
146
+ - The unread badge counts each mailbox's Inbox only. It is refreshed at most once a minute.
147
+ - Mail connections are kept by the running site. The site must run as a single server process; several worker processes for one site are not supported.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mail-reader",
3
3
  "displayName": "Mail Reader",
4
- "version": "1.2.1",
4
+ "version": "1.2.3",
5
5
  "tier": "free",
6
6
  "description": "Read-only IMAP mailbox reader. Connects to a standard IMAP server and reads mail without ever writing to it.",
7
7
  "author": "Darryl Waterhouse",
@@ -27,6 +27,8 @@ Ships with the engine (free tier, `make sync-free-tier`); switched ON when it fi
27
27
  Unknown = 'info', never a pass. Cached 10 min; the sidebar badge (`/count`) = checks needing attention, red only
28
28
  when a failing check is critical.
29
29
  - Engines before 0.83 have no sign-in checks: the plugin logs it, the screen says so, lockout/rules are off.
30
+ - plugin.json `minCmsVersion: "0.83.0"` (1.1.1) states that floor; core enforces it at install/update from the release after 0.92.1,
31
+ so the degraded path above is only reachable on an engine too old to enforce it. Resets (0.88) stay feature-detected.
30
32
 
31
33
  ## On dcm
32
34
  The manager proxies every site, and Security's guard runs there first: probes to ANY site's domain are answered by
@@ -47,7 +49,8 @@ within ~3 s. It must always work - keep it dependency-free.
47
49
 
48
50
  ## Admin
49
51
  `admin/views/security.js` + `admin/templates/security.html`: Overview / Sign-ins / Blocked, settings slideover
50
- (cog), Refresh. Public data (emails, addresses, user-agents) only through data-bind-text; rows by index.
52
+ (cog), Refresh. Sign-ins has a CSV export (browser-side, `toCsv`, formula-leading cells defused - the log holds
53
+ what strangers typed). Public data (emails, addresses, user-agents) only through data-bind-text; rows by index.
51
54
 
52
55
  ## Tests
53
56
  `tests/security.test.js` (pure). Headless: throwaway CMS with TRUST_PROXY=loopback and X-Forwarded-For.
@@ -10,6 +10,7 @@
10
10
  * only, and rows are addressed by index.
11
11
  * `#/plugins/security/<tab>` opens a tab. Pure logic: ../lib/*.js.
12
12
  */
13
+ import {fmtDate, fmtDateTime, fmtTime} from '/plugins/_lib/admin/ui/dates.js';
13
14
  import {resetRowText} from '../lib/resets.js';
14
15
  import {checkSettings, DEFAULT_SETTINGS, mergeDefaults} from '../lib/settings.js';
15
16
  import {ipRuleValid} from '../lib/ip.js';
@@ -55,10 +56,9 @@ function refreshBadge() {
55
56
  const when = (iso) => {
56
57
  const d = new Date(iso);
57
58
  const today = new Date().toDateString() === d.toDateString();
58
- const t = d.toLocaleTimeString('en-GB', {hour: '2-digit', minute: '2-digit'});
59
- return today ? `Today ${t}` : `${d.toLocaleDateString('en-GB', {day: 'numeric', month: 'short'})} ${t}`;
59
+ return today ? `Today ${fmtTime(d)}` : fmtDateTime(d);
60
60
  };
61
- const until = (ms) => `until ${new Date(ms).toLocaleString('en-GB', {day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit'})}`;
61
+ const until = (ms) => `until ${fmtDateTime(ms)}`;
62
62
  /** "Chrome on Windows" from a user-agent - enough to recognise one. */
63
63
  function browserOf(ua) {
64
64
  const s = String(ua || '');
@@ -150,7 +150,7 @@ export const securityView = {
150
150
  const scoreWidth = M.computed(() => `${health.value?.score ?? 0}%`);
151
151
  const scoreTone = M.computed(() => { const s = health.value?.score; return s === undefined ? '' : s >= 85 ? 'good' : s >= 60 ? 'poor' : 'bad'; });
152
152
  const attentionText = M.computed(() => (health.value ? String(health.value.failing) : '—'));
153
- const asOf = M.computed(() => (health.value?.at ? `Checked ${new Date(health.value.at).toLocaleTimeString('en-GB', {hour: '2-digit', minute: '2-digit'})}` : ''));
153
+ const asOf = M.computed(() => (health.value?.at ? `Checked ${fmtTime(health.value.at)}` : ''));
154
154
  const ov = M.computed(() => {
155
155
  const o = overview.value;
156
156
  return o ? {failed: String(o.failed24h), blocked: String(o.blocked + o.locks), probes: String(o.today?.probes || 0), resets: String(o.resets24h ?? 0)}
@@ -217,7 +217,7 @@ export const securityView = {
217
217
  const b = blocks.value;
218
218
  lockRows.value = b.locks.map((l, i) => ({id: `l${i}`, who: l.who, why: l.kind === 'account' ? `Account - ${l.locks > 1 ? `locked ${l.locks} times` : 'wrong passwords'}` : 'Address - wrong passwords', when: until(l.until)}));
219
219
  tempRows.value = b.temp.map((t, i) => ({id: `t${i}`, who: t.ip, why: t.reason || 'Probing', when: until(t.until)}));
220
- ruleRows.value = b.rules.map((r, i) => ({id: `r${i}`, who: r.rule, why: r.note || (r.by ? `Added by ${r.by}` : 'Blocked by hand'), when: r.at ? `since ${new Date(r.at).toLocaleDateString('en-GB', {day: 'numeric', month: 'short', year: 'numeric'})}` : ''}));
220
+ ruleRows.value = b.rules.map((r, i) => ({id: `r${i}`, who: r.rule, why: r.note || (r.by ? `Added by ${r.by}` : 'Blocked by hand'), when: r.at ? `since ${fmtDate(r.at)}` : ''}));
221
221
  }));
222
222
  const youText = M.computed(() => (blocks.value.you ? `You are ${blocks.value.you}` : ''));
223
223