@geekity/cms 0.9.0 → 0.10.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 (145) hide show
  1. package/README.md +71 -21
  2. package/admin/components/admin-bar.njk +26 -0
  3. package/admin/components/public-admin-bar.njk +41 -0
  4. package/admin/layouts/base.njk +1 -0
  5. package/admin/layouts/shell.njk +8 -19
  6. package/admin/static/admin-bar.css +137 -0
  7. package/admin/static/admin.css +1 -102
  8. package/dist/admin/comments.js +2 -1
  9. package/dist/admin/comments.js.map +1 -1
  10. package/dist/admin/index.d.ts +1 -1
  11. package/dist/admin/index.d.ts.map +1 -1
  12. package/dist/admin/index.js.map +1 -1
  13. package/dist/admin/personal-data.d.ts.map +1 -1
  14. package/dist/admin/personal-data.js +5 -1
  15. package/dist/admin/personal-data.js.map +1 -1
  16. package/dist/admin/preview.d.ts.map +1 -1
  17. package/dist/admin/preview.js +1 -1
  18. package/dist/admin/preview.js.map +1 -1
  19. package/dist/admin/routes.d.ts.map +1 -1
  20. package/dist/admin/routes.js +2 -1
  21. package/dist/admin/routes.js.map +1 -1
  22. package/dist/admin/store.d.ts +35 -0
  23. package/dist/admin/store.d.ts.map +1 -1
  24. package/dist/admin/store.js +62 -0
  25. package/dist/admin/store.js.map +1 -1
  26. package/dist/admin/templates.d.ts +1 -0
  27. package/dist/admin/templates.d.ts.map +1 -1
  28. package/dist/admin/templates.js +1 -0
  29. package/dist/admin/templates.js.map +1 -1
  30. package/dist/admin/tools.d.ts +2 -0
  31. package/dist/admin/tools.d.ts.map +1 -1
  32. package/dist/admin/tools.js +3 -2
  33. package/dist/admin/tools.js.map +1 -1
  34. package/dist/comments/index.d.ts +2 -2
  35. package/dist/comments/index.d.ts.map +1 -1
  36. package/dist/comments/index.js +1 -1
  37. package/dist/comments/index.js.map +1 -1
  38. package/dist/comments/records.d.ts +43 -2
  39. package/dist/comments/records.d.ts.map +1 -1
  40. package/dist/comments/records.js +201 -54
  41. package/dist/comments/records.js.map +1 -1
  42. package/dist/comments/routes.js +1 -1
  43. package/dist/comments/routes.js.map +1 -1
  44. package/dist/comments/viewer.d.ts +13 -0
  45. package/dist/comments/viewer.d.ts.map +1 -1
  46. package/dist/comments/viewer.js +13 -1
  47. package/dist/comments/viewer.js.map +1 -1
  48. package/dist/config.d.ts +11 -0
  49. package/dist/config.d.ts.map +1 -1
  50. package/dist/config.js +1 -0
  51. package/dist/config.js.map +1 -1
  52. package/dist/env.d.ts +19 -0
  53. package/dist/env.d.ts.map +1 -1
  54. package/dist/federation/delivery.d.ts +3 -1
  55. package/dist/federation/delivery.d.ts.map +1 -1
  56. package/dist/federation/delivery.js +6 -1
  57. package/dist/federation/delivery.js.map +1 -1
  58. package/dist/federation/federation.d.ts +3 -0
  59. package/dist/federation/federation.d.ts.map +1 -1
  60. package/dist/federation/federation.js.map +1 -1
  61. package/dist/federation/inbox.d.ts +7 -5
  62. package/dist/federation/inbox.d.ts.map +1 -1
  63. package/dist/federation/inbox.js +15 -32
  64. package/dist/federation/inbox.js.map +1 -1
  65. package/dist/federation/index.d.ts +2 -0
  66. package/dist/federation/index.d.ts.map +1 -1
  67. package/dist/federation/index.js +1 -0
  68. package/dist/federation/index.js.map +1 -1
  69. package/dist/federation/mount.js +6 -1
  70. package/dist/federation/mount.js.map +1 -1
  71. package/dist/federation/profiles.d.ts +95 -0
  72. package/dist/federation/profiles.d.ts.map +1 -0
  73. package/dist/federation/profiles.js +171 -0
  74. package/dist/federation/profiles.js.map +1 -0
  75. package/dist/federation/relays.d.ts +3 -1
  76. package/dist/federation/relays.d.ts.map +1 -1
  77. package/dist/federation/relays.js +6 -1
  78. package/dist/federation/relays.js.map +1 -1
  79. package/dist/federation/replies.d.ts +13 -4
  80. package/dist/federation/replies.d.ts.map +1 -1
  81. package/dist/federation/replies.js +25 -6
  82. package/dist/federation/replies.js.map +1 -1
  83. package/dist/images/icons.d.ts.map +1 -1
  84. package/dist/images/icons.js +3 -2
  85. package/dist/images/icons.js.map +1 -1
  86. package/dist/images/index.d.ts +2 -2
  87. package/dist/images/index.d.ts.map +1 -1
  88. package/dist/images/markup.d.ts +15 -4
  89. package/dist/images/markup.d.ts.map +1 -1
  90. package/dist/images/markup.js +24 -15
  91. package/dist/images/markup.js.map +1 -1
  92. package/dist/images/paths.d.ts +11 -0
  93. package/dist/images/paths.d.ts.map +1 -1
  94. package/dist/images/paths.js +17 -1
  95. package/dist/images/paths.js.map +1 -1
  96. package/dist/images/variants.d.ts +13 -3
  97. package/dist/images/variants.d.ts.map +1 -1
  98. package/dist/images/variants.js +13 -11
  99. package/dist/images/variants.js.map +1 -1
  100. package/dist/index.d.ts +11 -5
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +50 -11
  103. package/dist/index.js.map +1 -1
  104. package/dist/notifications/routes.js +1 -1
  105. package/dist/notifications/routes.js.map +1 -1
  106. package/dist/privacy/erase.d.ts.map +1 -1
  107. package/dist/privacy/erase.js +2 -3
  108. package/dist/privacy/erase.js.map +1 -1
  109. package/dist/privacy/retention.js +1 -1
  110. package/dist/privacy/retention.js.map +1 -1
  111. package/dist/web/admin-bar.d.ts +21 -0
  112. package/dist/web/admin-bar.d.ts.map +1 -0
  113. package/dist/web/admin-bar.js +145 -0
  114. package/dist/web/admin-bar.js.map +1 -0
  115. package/dist/web/assets.d.ts +29 -1
  116. package/dist/web/assets.d.ts.map +1 -1
  117. package/dist/web/assets.js +48 -3
  118. package/dist/web/assets.js.map +1 -1
  119. package/dist/web/compression.d.ts +19 -0
  120. package/dist/web/compression.d.ts.map +1 -0
  121. package/dist/web/compression.js +160 -0
  122. package/dist/web/compression.js.map +1 -0
  123. package/dist/web/context.d.ts +5 -1
  124. package/dist/web/context.d.ts.map +1 -1
  125. package/dist/web/context.js +5 -2
  126. package/dist/web/context.js.map +1 -1
  127. package/dist/web/conversation.d.ts.map +1 -1
  128. package/dist/web/conversation.js +13 -12
  129. package/dist/web/conversation.js.map +1 -1
  130. package/dist/web/render.d.ts +27 -0
  131. package/dist/web/render.d.ts.map +1 -1
  132. package/dist/web/render.js +77 -38
  133. package/dist/web/render.js.map +1 -1
  134. package/dist/web/routes.d.ts.map +1 -1
  135. package/dist/web/routes.js +63 -28
  136. package/dist/web/routes.js.map +1 -1
  137. package/dist/web/templates.d.ts.map +1 -1
  138. package/dist/web/templates.js +9 -2
  139. package/dist/web/templates.js.map +1 -1
  140. package/dist/webmention/service.d.ts.map +1 -1
  141. package/dist/webmention/service.js +5 -1
  142. package/dist/webmention/service.js.map +1 -1
  143. package/package.json +1 -1
  144. package/themes/default/README.md +143 -19
  145. package/themes/default/layouts/base.njk +51 -10
@@ -65,10 +65,10 @@ export const TOOLS_FIELDS = {
65
65
  * reason a boot scan is silent.
66
66
  */
67
67
  export async function rebuildContentIndex(options) {
68
- const { store, admin, contentDir, rescan } = options;
68
+ const { store, admin, contentDir, dataDir, rescan } = options;
69
69
  store.clear();
70
70
  const federation = rebuildFederationIndexes({ admin, contentDir });
71
- const comments = rebuildCommentIndexes({ admin, contentDir });
71
+ const comments = rebuildCommentIndexes({ admin, contentDir, dataDir });
72
72
  const scan = await rescan();
73
73
  return {
74
74
  scan,
@@ -118,6 +118,7 @@ export function mountToolsScreen(app, options) {
118
118
  store: c.var.store,
119
119
  admin: c.var.admin,
120
120
  contentDir: c.var.config.contentDir,
121
+ dataDir: c.var.config.dataDir,
121
122
  rescan: c.var.rescan,
122
123
  });
123
124
  let report;
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/admin/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/admin/tools.ts"],"names":[],"mappings":"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"}
@@ -7,8 +7,8 @@ export { isModerationAction, moderateComment, MODERATION_ACTIONS } from './moder
7
7
  export type { ModerateCommentOptions, ModerationAction, ModerationOutcome } from './moderate.ts';
8
8
  export { COMMENTS_FRONT_MATTER_KEY, commentPolicyOf, commentsOpen, DEFAULT_COMMENTS_CLOSE_AFTER_DAYS, } from './policy.ts';
9
9
  export type { CommentPolicy } from './policy.ts';
10
- export { addComment, COMMENTS_DATA_DIRECTORY, commentsDirectory, commentsFile, deleteComment, heldWebmention, intakeComment, readComments, rebuildCommentIndexes, updateComment, } from './records.ts';
11
- export type { CommentIndexReport, CommentIntakeOutcome, CommentNotices, CommentOrigin, CommentRecords, IntakeCommentOptions, NewComment, ProposedComment, } from './records.ts';
10
+ export { addComment, COMMENT_EMAILS_DIRECTORY, COMMENT_EMAILS_FILE_MODE, commentEmailsFile, COMMENTS_DATA_DIRECTORY, commentsDirectory, commentsFile, deleteComment, heldWebmention, intakeComment, migrateCommentEmails, readComments, rebuildCommentIndexes, updateComment, } from './records.ts';
11
+ export type { CommentEmailMigrationReport, CommentIndexReport, CommentIntakeOutcome, CommentNotices, CommentOrigin, CommentRecords, IntakeCommentOptions, NewComment, ProposedComment, } from './records.ts';
12
12
  export { commentFormFor, commentNoticeFor, mountComments } from './routes.ts';
13
13
  export { COMMENT_FIELDS, COMMENT_RATE_LIMIT, COMMENT_RATE_WINDOW_SECONDS, COMMENT_SALT_FILE, commentKeys, commentProblems, hashClientAddress, MAXIMUM_BODY_LENGTH, MAXIMUM_NAME_LENGTH, MAXIMUM_URL_LENGTH, MAXIMUM_FORM_AGE_SECONDS, MINIMUM_SUBMIT_SECONDS, normalizeWebsite, submitComment, } from './submission.ts';
14
14
  export type { CommentChecker, CommentForm, CommentOutcome, CommentProblems, CommentRefusal, CommentReport, CommentSubmission, SubmissionType, CommentThrottle, CommentVerdict, SignedInAuthor, SubmitCommentOptions, } from './submission.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxF,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,YAAY,EACZ,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,UAAU,EACV,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,UAAU,EACV,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxF,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,YAAY,EACZ,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,2BAA2B,EAC3B,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,UAAU,EACV,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -3,7 +3,7 @@ export { blankValues, COMMENT_NOTICE_PARAM, COMMENT_NOTICES, COMMENT_POST_PATH,
3
3
  export { renderCommentMarkdown } from './markdown.js';
4
4
  export { isModerationAction, moderateComment, MODERATION_ACTIONS } from './moderate.js';
5
5
  export { COMMENTS_FRONT_MATTER_KEY, commentPolicyOf, commentsOpen, DEFAULT_COMMENTS_CLOSE_AFTER_DAYS, } from './policy.js';
6
- export { addComment, COMMENTS_DATA_DIRECTORY, commentsDirectory, commentsFile, deleteComment, heldWebmention, intakeComment, readComments, rebuildCommentIndexes, updateComment, } from './records.js';
6
+ export { addComment, COMMENT_EMAILS_DIRECTORY, COMMENT_EMAILS_FILE_MODE, commentEmailsFile, COMMENTS_DATA_DIRECTORY, commentsDirectory, commentsFile, deleteComment, heldWebmention, intakeComment, migrateCommentEmails, readComments, rebuildCommentIndexes, updateComment, } from './records.js';
7
7
  export { commentFormFor, commentNoticeFor, mountComments } from './routes.js';
8
8
  export { COMMENT_FIELDS, COMMENT_RATE_LIMIT, COMMENT_RATE_WINDOW_SECONDS, COMMENT_SALT_FILE, commentKeys, commentProblems, hashClientAddress, MAXIMUM_BODY_LENGTH, MAXIMUM_NAME_LENGTH, MAXIMUM_URL_LENGTH, MAXIMUM_FORM_AGE_SECONDS, MINIMUM_SUBMIT_SECONDS, normalizeWebsite, submitComment, } from './submission.js';
9
9
  export { signedInCommenter } from './viewer.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AAOtB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,YAAY,EACZ,iCAAiC,GAClC,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,aAAa,GACd,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAezB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AAOtB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,YAAY,EACZ,iCAAiC,GAClC,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,aAAa,GACd,MAAM,cAAc,CAAC;AAYtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAezB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -21,6 +21,14 @@ import type { CommentChecker } from './submission.ts';
21
21
  * {@link commentFrom} whether it is being written for the first time or
22
22
  * rebuilt at boot.
23
23
  *
24
+ * **The email is not in the file.** Everything under `content/` is published
25
+ * and goes into git, where a removed line stays in history until somebody
26
+ * rewrites it, so a commenter's `author.email` and the `notify` that only
27
+ * means anything beside it live in `data/comments/{slug}.json` instead (mode
28
+ * `0600`), keyed by comment id. A record read here has both merged back in, and
29
+ * a record written here is split again, so nothing outside this module knows
30
+ * there are two files (TASK-182).
31
+ *
24
32
  * The entry shape is deliberately wider than a form submission. A webmention
25
33
  * (TASK-51) is somebody else's post pointing at this one: it has a `url` and no
26
34
  * email, it may be a like rather than a reply, and it belongs in the same file
@@ -50,6 +58,16 @@ export declare function commentsDirectory(contentDir: string): string;
50
58
  * sanitised to one name would silently share a thread.
51
59
  */
52
60
  export declare function commentsFile(contentDir: string, slug: string): string;
61
+ /** Where commenters' emails live, relative to the data directory. */
62
+ export declare const COMMENT_EMAILS_DIRECTORY = "comments";
63
+ /** Nobody but the site's own user reads a commenter's email. */
64
+ export declare const COMMENT_EMAILS_FILE_MODE = 384;
65
+ /**
66
+ * The absolute path of the private file holding one post's commenters' emails:
67
+ * `data/comments/{slug}.json`, beside the post's own file in `content/` but
68
+ * never published with it.
69
+ */
70
+ export declare function commentEmailsFile(dataDir: string, slug: string): string;
53
71
  /** A {@link CommentRecord} on its way in, before the store has named it. */
54
72
  export type NewComment = Omit<CommentRecord, 'id'> & {
55
73
  /** The post it is on. */
@@ -65,9 +83,11 @@ export interface CommentRecords {
65
83
  readonly admin: AdminStore;
66
84
  /** The content directory the files live under. */
67
85
  readonly contentDir: string;
86
+ /** The data directory the commenters' emails live under. */
87
+ readonly dataDir: string;
68
88
  }
69
89
  /**
70
- * One post's comments, as its file says them, oldest first.
90
+ * One post's comments, as its files say them, oldest first.
71
91
  *
72
92
  * A missing file is no comments, which is what almost every post has. Unlike
73
93
  * `followers.json`, an entry that will not parse is dropped rather than
@@ -76,7 +96,7 @@ export interface CommentRecords {
76
96
  * down on the next boot. Nothing irreplaceable is at stake — a comment that
77
97
  * cannot be read is one nobody could have shown either way.
78
98
  */
79
- export declare function readComments(contentDir: string, slug: string): CommentRecord[];
99
+ export declare function readComments(records: Pick<CommentRecords, 'contentDir' | 'dataDir'>, slug: string): CommentRecord[];
80
100
  /**
81
101
  * Record a comment: append it to its post's file and index it.
82
102
  *
@@ -278,4 +298,25 @@ export interface CommentIndexReport {
278
298
  * synchronous because both callers are.
279
299
  */
280
300
  export declare function rebuildCommentIndexes(records: CommentRecords): CommentIndexReport;
301
+ /** What moving the emails out of `content/` changed. */
302
+ export interface CommentEmailMigrationReport {
303
+ /** How many comment files were rewritten. */
304
+ files: number;
305
+ }
306
+ /**
307
+ * Move every email still in a comment file into `data/` (TASK-182).
308
+ *
309
+ * A site that kept comments before the emails moved has them in
310
+ * `content/_data/comments/`, and a comment file somebody edits by hand may
311
+ * gain one again. Boot calls this before {@link rebuildCommentIndexes}, so
312
+ * both end up in the private file and out of the published one. It edits each
313
+ * file's JSON as it finds it, taking off `author.email` and `notify` and
314
+ * nothing else, so a key this version does not know and an entry it cannot
315
+ * read survive it. An email `data/` already holds for that comment wins. A file
316
+ * with nothing to take off is not written, so a second boot writes nothing.
317
+ *
318
+ * Synchronous, like the other boot migrations, and run before anything else
319
+ * can be writing a comment.
320
+ */
321
+ export declare function migrateCommentEmails(records: Pick<CommentRecords, 'contentDir' | 'dataDir'>): CommentEmailMigrationReport;
281
322
  //# sourceMappingURL=records.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/comments/records.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/comments/records.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { readdirSync } from 'node:fs';
2
+ import { readdirSync, rmSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { COMMENT_KINDS, COMMENT_SOURCES, COMMENT_STATUSES, REDACTED_FIELDS, } from '../admin/store.js';
5
5
  import { readFileIfPresentSync, withFileLock, writeFileAtomicallySync } from '../files/atomic.js';
@@ -25,6 +25,14 @@ import { hashClientAddress } from '../forms/protection.js';
25
25
  * {@link commentFrom} whether it is being written for the first time or
26
26
  * rebuilt at boot.
27
27
  *
28
+ * **The email is not in the file.** Everything under `content/` is published
29
+ * and goes into git, where a removed line stays in history until somebody
30
+ * rewrites it, so a commenter's `author.email` and the `notify` that only
31
+ * means anything beside it live in `data/comments/{slug}.json` instead (mode
32
+ * `0600`), keyed by comment id. A record read here has both merged back in, and
33
+ * a record written here is split again, so nothing outside this module knows
34
+ * there are two files (TASK-182).
35
+ *
28
36
  * The entry shape is deliberately wider than a form submission. A webmention
29
37
  * (TASK-51) is somebody else's post pointing at this one: it has a `url` and no
30
38
  * email, it may be a like rather than a reply, and it belongs in the same file
@@ -63,8 +71,23 @@ export function commentsFile(contentDir, slug) {
63
71
  }
64
72
  /** Slugs a file may be named after: what {@link slugify} produces, and no more. */
65
73
  const SAFE_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
74
+ /** Where commenters' emails live, relative to the data directory. */
75
+ export const COMMENT_EMAILS_DIRECTORY = 'comments';
76
+ /** Nobody but the site's own user reads a commenter's email. */
77
+ export const COMMENT_EMAILS_FILE_MODE = 0o600;
78
+ /**
79
+ * The absolute path of the private file holding one post's commenters' emails:
80
+ * `data/comments/{slug}.json`, beside the post's own file in `content/` but
81
+ * never published with it.
82
+ */
83
+ export function commentEmailsFile(dataDir, slug) {
84
+ if (!SAFE_SLUG.test(slug)) {
85
+ throw new Error(`"${slug}" is not a slug a comment file can be named after.`);
86
+ }
87
+ return path.join(dataDir, COMMENT_EMAILS_DIRECTORY, `${slug}.json`);
88
+ }
66
89
  /**
67
- * One post's comments, as its file says them, oldest first.
90
+ * One post's comments, as its files say them, oldest first.
68
91
  *
69
92
  * A missing file is no comments, which is what almost every post has. Unlike
70
93
  * `followers.json`, an entry that will not parse is dropped rather than
@@ -73,14 +96,90 @@ const SAFE_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
73
96
  * down on the next boot. Nothing irreplaceable is at stake — a comment that
74
97
  * cannot be read is one nobody could have shown either way.
75
98
  */
76
- export function readComments(contentDir, slug) {
77
- const source = readFileIfPresentSync(commentsFile(contentDir, slug));
99
+ export function readComments(records, slug) {
100
+ return readPost(records, slug).comments;
101
+ }
102
+ /**
103
+ * One post's comments with their emails merged back in.
104
+ *
105
+ * `data/` wins over `content/`. An email still in a comment file is read too,
106
+ * so a site that has not booted since upgrading loses nothing before
107
+ * {@link migrateCommentEmails} moves it.
108
+ */
109
+ function readPost(records, slug) {
110
+ const held = fileContents(readFileIfPresentSync(commentsFile(records.contentDir, slug)));
111
+ const contacts = contactsIn(readFileIfPresentSync(commentEmailsFile(records.dataDir, slug)));
112
+ return {
113
+ post: held.post,
114
+ comments: held.comments.map((comment) => {
115
+ const contact = contacts.get(comment.id);
116
+ if (contact === undefined)
117
+ return comment;
118
+ return {
119
+ ...comment,
120
+ author: { ...comment.author, email: contact.email },
121
+ notify: contact.notify,
122
+ };
123
+ }),
124
+ };
125
+ }
126
+ /**
127
+ * Write one post's comments: the emails to `data/`, everything else to
128
+ * `content/`. Called inside the lock on the post's comment file, which is the
129
+ * lock on both, because nothing writes the private file but this.
130
+ *
131
+ * `data/` goes first, so a crash between the two writes leaves an email in
132
+ * both places or in `data/` alone, and never in neither.
133
+ */
134
+ function writePost(records, slug, permalink, comments) {
135
+ const contacts = new Map();
136
+ for (const comment of comments) {
137
+ if (comment.author.email !== null) {
138
+ contacts.set(comment.id, { email: comment.author.email, notify: comment.notify });
139
+ }
140
+ }
141
+ const privateFile = commentEmailsFile(records.dataDir, slug);
142
+ if (contacts.size > 0) {
143
+ writeFileAtomicallySync(privateFile, contactsJson(contacts), {
144
+ mode: COMMENT_EMAILS_FILE_MODE,
145
+ });
146
+ }
147
+ else {
148
+ rmSync(privateFile, { force: true });
149
+ }
150
+ writeFileAtomicallySync(commentsFile(records.contentDir, slug), commentsJson(permalink, comments.map(publishedEntryOf)));
151
+ }
152
+ /** The emails a private file holds, by comment id. A damaged file holds none. */
153
+ function contactsIn(source) {
154
+ const contacts = new Map();
78
155
  if (source === undefined)
79
- return [];
80
- return commentsIn(source);
156
+ return contacts;
157
+ let parsed;
158
+ try {
159
+ parsed = JSON.parse(source);
160
+ }
161
+ catch {
162
+ return contacts;
163
+ }
164
+ if (!isRecord(parsed) || !isRecord(parsed['comments']))
165
+ return contacts;
166
+ for (const [id, value] of Object.entries(parsed['comments'])) {
167
+ if (!isRecord(value))
168
+ continue;
169
+ const email = optionalText(value['email']);
170
+ if (email !== null)
171
+ contacts.set(id, { email, notify: value['notify'] === true });
172
+ }
173
+ return contacts;
174
+ }
175
+ /** A private file's contents: indented, newline ended. */
176
+ function contactsJson(contacts) {
177
+ return `${JSON.stringify({ comments: Object.fromEntries(contacts) }, null, 2)}\n`;
81
178
  }
82
179
  /** One post's comment file as the post it names and the comments it holds. */
83
180
  function fileContents(source) {
181
+ if (source === undefined)
182
+ return { post: '', comments: [] };
84
183
  let parsed;
85
184
  try {
86
185
  parsed = JSON.parse(source);
@@ -98,10 +197,6 @@ function fileContents(source) {
98
197
  : [],
99
198
  };
100
199
  }
101
- /** Just the comments, for the readers that have the post already. */
102
- function commentsIn(source) {
103
- return fileContents(source).comments;
104
- }
105
200
  /**
106
201
  * One file entry as the record it is, or `undefined` when it is not one.
107
202
  *
@@ -164,17 +259,16 @@ function redactedIn(value) {
164
259
  * same content directory can never be given the same name.
165
260
  */
166
261
  export async function addComment(records, comment) {
167
- const { admin, contentDir } = records;
168
- const file = commentsFile(contentDir, comment.slug);
262
+ const file = commentsFile(records.contentDir, comment.slug);
169
263
  const stored = {
170
264
  ...commentRecordOf(comment),
171
265
  slug: comment.slug,
172
266
  permalink: comment.permalink,
173
267
  };
174
268
  return await withFileLock(file, () => {
175
- const held = readCommentFile(file);
176
- writeFileAtomicallySync(file, commentsJson(comment.permalink, [...held, entryOf(stored)]));
177
- admin.putComment(stored);
269
+ const held = readPost(records, comment.slug).comments;
270
+ writePost(records, comment.slug, comment.permalink, [...held, stored]);
271
+ records.admin.putComment(stored);
178
272
  return stored;
179
273
  });
180
274
  }
@@ -357,19 +451,18 @@ function messageOf(error) {
357
451
  * one thing an id alone does not carry.
358
452
  */
359
453
  export async function updateComment(records, id, change) {
360
- const { admin, contentDir } = records;
361
- const known = admin.getComment(id);
454
+ const known = records.admin.getComment(id);
362
455
  if (known === undefined)
363
456
  return undefined;
364
- const file = commentsFile(contentDir, known.slug);
457
+ const file = commentsFile(records.contentDir, known.slug);
365
458
  return await withFileLock(file, () => {
366
- const held = readCommentFile(file);
459
+ const held = readPost(records, known.slug).comments;
367
460
  const at = held.findIndex((entry) => entry.id === id);
368
461
  if (at === -1)
369
462
  return undefined;
370
463
  const moved = { ...known, ...held[at], ...change };
371
- writeFileAtomicallySync(file, commentsJson(known.permalink, held.with(at, entryOf(moved))));
372
- admin.putComment(moved);
464
+ writePost(records, known.slug, known.permalink, held.with(at, moved));
465
+ records.admin.putComment(moved);
373
466
  return moved;
374
467
  });
375
468
  }
@@ -381,18 +474,16 @@ export async function updateComment(records, id, change) {
381
474
  * edited by hand cannot go on being shown.
382
475
  */
383
476
  export async function deleteComment(records, id) {
384
- const { admin, contentDir } = records;
385
- const known = admin.getComment(id);
477
+ const known = records.admin.getComment(id);
386
478
  if (known === undefined)
387
479
  return false;
388
- const file = commentsFile(contentDir, known.slug);
480
+ const file = commentsFile(records.contentDir, known.slug);
389
481
  return await withFileLock(file, () => {
390
- const held = readCommentFile(file);
482
+ const held = readPost(records, known.slug).comments;
391
483
  const next = held.filter((entry) => entry.id !== id);
392
- if (next.length !== held.length) {
393
- writeFileAtomicallySync(file, commentsJson(known.permalink, next));
394
- }
395
- const indexed = admin.deleteComment(id);
484
+ if (next.length !== held.length)
485
+ writePost(records, known.slug, known.permalink, next);
486
+ const indexed = records.admin.deleteComment(id);
396
487
  return next.length !== held.length || indexed;
397
488
  });
398
489
  }
@@ -409,13 +500,9 @@ export async function deleteComment(records, id) {
409
500
  * that changed come back, each already put into the index.
410
501
  */
411
502
  export async function rewriteComments(records, slug, change) {
412
- const { admin, contentDir } = records;
413
- const file = commentsFile(contentDir, slug);
503
+ const file = commentsFile(records.contentDir, slug);
414
504
  return await withFileLock(file, () => {
415
- const source = readFileIfPresentSync(file);
416
- if (source === undefined)
417
- return [];
418
- const held = fileContents(source);
505
+ const held = readPost(records, slug);
419
506
  const changed = [];
420
507
  const next = held.comments.map((entry) => {
421
508
  const moved = change(entry);
@@ -426,9 +513,9 @@ export async function rewriteComments(records, slug, change) {
426
513
  });
427
514
  if (changed.length === 0)
428
515
  return [];
429
- writeFileAtomicallySync(file, commentsJson(held.post, next.map(entryOf)));
516
+ writePost(records, slug, held.post, next);
430
517
  for (const comment of changed)
431
- admin.putComment(comment);
518
+ records.admin.putComment(comment);
432
519
  return changed.map((comment) => comment.id);
433
520
  });
434
521
  }
@@ -449,21 +536,84 @@ export function commentSlugs(contentDir) {
449
536
  * synchronous because both callers are.
450
537
  */
451
538
  export function rebuildCommentIndexes(records) {
452
- const { admin, contentDir } = records;
453
539
  const comments = [];
454
- for (const file of commentFiles(contentDir)) {
455
- const source = readFileIfPresentSync(file);
456
- if (source === undefined)
457
- continue;
458
- const slug = path.basename(file, '.json');
459
- const held = fileContents(source);
540
+ for (const slug of commentSlugs(records.contentDir)) {
541
+ const held = readPost(records, slug);
460
542
  for (const entry of held.comments) {
461
543
  comments.push({ ...entry, slug, permalink: held.post });
462
544
  }
463
545
  }
464
- admin.replaceComments(comments);
546
+ records.admin.replaceComments(comments);
465
547
  return { comments: comments.length };
466
548
  }
549
+ /**
550
+ * Move every email still in a comment file into `data/` (TASK-182).
551
+ *
552
+ * A site that kept comments before the emails moved has them in
553
+ * `content/_data/comments/`, and a comment file somebody edits by hand may
554
+ * gain one again. Boot calls this before {@link rebuildCommentIndexes}, so
555
+ * both end up in the private file and out of the published one. It edits each
556
+ * file's JSON as it finds it, taking off `author.email` and `notify` and
557
+ * nothing else, so a key this version does not know and an entry it cannot
558
+ * read survive it. An email `data/` already holds for that comment wins. A file
559
+ * with nothing to take off is not written, so a second boot writes nothing.
560
+ *
561
+ * Synchronous, like the other boot migrations, and run before anything else
562
+ * can be writing a comment.
563
+ */
564
+ export function migrateCommentEmails(records) {
565
+ let files = 0;
566
+ for (const slug of commentSlugs(records.contentDir)) {
567
+ const file = commentsFile(records.contentDir, slug);
568
+ let parsed;
569
+ try {
570
+ parsed = JSON.parse(readFileIfPresentSync(file) ?? '');
571
+ }
572
+ catch {
573
+ continue;
574
+ }
575
+ if (!isRecord(parsed) || !Array.isArray(parsed['comments']))
576
+ continue;
577
+ const found = new Map();
578
+ let stripped = false;
579
+ const entries = parsed['comments'].map((entry) => {
580
+ if (!isRecord(entry))
581
+ return entry;
582
+ const author = isRecord(entry['author']) ? entry['author'] : undefined;
583
+ if (!('notify' in entry) && !(author !== undefined && 'email' in author))
584
+ return entry;
585
+ stripped = true;
586
+ const email = optionalText(author?.['email']);
587
+ const id = entry['id'];
588
+ if (typeof id === 'string' && id !== '' && email !== null) {
589
+ found.set(id, { email, notify: entry['notify'] === true });
590
+ }
591
+ const kept = without(entry, 'notify');
592
+ if (author !== undefined)
593
+ kept['author'] = without(author, 'email');
594
+ return kept;
595
+ });
596
+ if (!stripped)
597
+ continue;
598
+ const privateFile = commentEmailsFile(records.dataDir, slug);
599
+ const contacts = contactsIn(readFileIfPresentSync(privateFile));
600
+ for (const [id, contact] of found)
601
+ if (!contacts.has(id))
602
+ contacts.set(id, contact);
603
+ if (contacts.size > 0) {
604
+ writeFileAtomicallySync(privateFile, contactsJson(contacts), {
605
+ mode: COMMENT_EMAILS_FILE_MODE,
606
+ });
607
+ }
608
+ writeFileAtomicallySync(file, `${JSON.stringify({ ...parsed, comments: entries }, null, 2)}\n`);
609
+ files += 1;
610
+ }
611
+ return { files };
612
+ }
613
+ /** A copy of a record without one key, every other key where it was. */
614
+ function without(record, key) {
615
+ return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key));
616
+ }
467
617
  /** Every comment file a site has, in a stable order. */
468
618
  function commentFiles(contentDir) {
469
619
  let entries;
@@ -480,11 +630,6 @@ function commentFiles(contentDir) {
480
630
  .sort()
481
631
  .map((name) => path.join(commentsDirectory(contentDir), name));
482
632
  }
483
- /** One file's comments, read inside a lock the caller already holds. */
484
- function readCommentFile(file) {
485
- const source = readFileIfPresentSync(file);
486
- return source === undefined ? [] : commentsIn(source);
487
- }
488
633
  /** A new comment as the record it becomes, id and all. */
489
634
  function commentRecordOf(comment) {
490
635
  return {
@@ -502,20 +647,22 @@ function commentRecordOf(comment) {
502
647
  ...(comment.redacted === undefined ? {} : { redacted: [...comment.redacted] }),
503
648
  };
504
649
  }
505
- /** A row as the file spells it: the post it is on is the file's, not the entry's. */
506
- function entryOf(comment) {
650
+ /**
651
+ * A row as `content/` spells it: the post it is on is the file's, not the
652
+ * entry's, and the email and `notify` are `data/`'s.
653
+ */
654
+ function publishedEntryOf(comment) {
507
655
  return {
508
656
  id: comment.id,
509
657
  source: comment.source,
510
658
  kind: comment.kind,
511
659
  status: comment.status,
512
- author: { ...comment.author },
660
+ author: { name: comment.author.name, url: comment.author.url, avatar: comment.author.avatar },
513
661
  content: { ...comment.content },
514
662
  submitted: comment.submitted,
515
663
  addressHash: comment.addressHash,
516
664
  inReplyTo: comment.inReplyTo,
517
665
  url: comment.url,
518
- notify: comment.notify,
519
666
  ...(comment.redacted === undefined ? {} : { redacted: [...comment.redacted] }),
520
667
  };
521
668
  }