ghost 5.4.0 → 5.6.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 (116) hide show
  1. package/PRIVACY.md +3 -2
  2. package/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
  3. package/components/tryghost-api-version-compatibility-service-0.0.0.tgz +0 -0
  4. package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
  5. package/components/tryghost-constants-0.0.0.tgz +0 -0
  6. package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
  7. package/components/tryghost-domain-events-0.0.0.tgz +0 -0
  8. package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
  9. package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
  10. package/components/tryghost-email-content-generator-0.0.0.tgz +0 -0
  11. package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
  12. package/components/tryghost-extract-api-key-0.0.0.tgz +0 -0
  13. package/components/tryghost-job-manager-0.0.0.tgz +0 -0
  14. package/components/tryghost-magic-link-0.0.0.tgz +0 -0
  15. package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
  16. package/components/tryghost-member-events-0.0.0.tgz +0 -0
  17. package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
  18. package/components/tryghost-members-api-0.0.0.tgz +0 -0
  19. package/components/tryghost-members-csv-0.0.0.tgz +0 -0
  20. package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
  21. package/components/tryghost-members-importer-0.0.0.tgz +0 -0
  22. package/components/tryghost-members-offers-0.0.0.tgz +0 -0
  23. package/components/tryghost-members-payments-0.0.0.tgz +0 -0
  24. package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
  25. package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
  26. package/components/tryghost-minifier-0.0.0.tgz +0 -0
  27. package/components/tryghost-mw-api-version-mismatch-0.0.0.tgz +0 -0
  28. package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
  29. package/components/tryghost-mw-session-from-token-0.0.0.tgz +0 -0
  30. package/components/tryghost-mw-update-user-last-seen-0.0.0.tgz +0 -0
  31. package/components/tryghost-mw-vhost-0.0.0.tgz +0 -0
  32. package/components/tryghost-package-json-0.0.0.tgz +0 -0
  33. package/components/tryghost-security-0.0.0.tgz +0 -0
  34. package/components/tryghost-session-service-0.0.0.tgz +0 -0
  35. package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
  36. package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
  37. package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
  38. package/components/tryghost-version-notifications-data-service-0.0.0.tgz +0 -0
  39. package/content/themes/casper/assets/built/global.css +1 -1
  40. package/content/themes/casper/assets/built/global.css.map +1 -1
  41. package/content/themes/casper/assets/built/screen.css +1 -1
  42. package/content/themes/casper/assets/built/screen.css.map +1 -1
  43. package/content/themes/casper/assets/css/screen.css +9 -1
  44. package/content/themes/casper/gulpfile.js +1 -1
  45. package/content/themes/casper/package.json +9 -9
  46. package/content/themes/casper/yarn.lock +1154 -1249
  47. package/core/boot.js +6 -1
  48. package/core/built/assets/{chunk.3.dc389a0f93cb5fabd695.js → chunk.3.33097bb5eb150719bdd2.js} +19 -19
  49. package/core/built/assets/fonts/Inter.ttf +0 -0
  50. package/core/built/assets/ghost-dark-1bdd57aba1fa4a23388121740454dab2.css +1 -0
  51. package/core/built/assets/{ghost.min-36b64813b14c45075770658269d4b478.js → ghost.min-31bd482d1bcfe706448bc6f401481a28.js} +3001 -2874
  52. package/core/built/assets/ghost.min-b0576e0e36343533e70992f3e5bb02bb.css +1 -0
  53. package/core/built/assets/icons/event-comment.svg +3 -0
  54. package/core/built/assets/{vendor.min-be0129c9c6897c9f10425e2402881d77.js → vendor.min-3dd40d3052381526f38fd290d13baa47.js} +2394 -924
  55. package/core/frontend/helpers/comments.js +39 -14
  56. package/core/frontend/helpers/ghost_head.js +22 -4
  57. package/core/frontend/utils/frontend-apps.js +33 -0
  58. package/core/server/api/endpoints/{comments-comments.js → comments-members.js} +24 -43
  59. package/core/server/api/endpoints/index.js +2 -6
  60. package/core/server/api/endpoints/utils/serializers/output/config.js +2 -1
  61. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +17 -0
  62. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +18 -0
  63. package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
  64. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +11 -0
  65. package/core/server/api/endpoints/utils/serializers/output/members.js +12 -1
  66. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +4 -0
  67. package/core/server/data/exporter/table-lists.js +2 -1
  68. package/core/server/data/migrations/versions/5.3/2022-07-06-09-17-add-ghost-explore-integration.js +0 -1
  69. package/core/server/data/migrations/versions/5.3/2022-07-06-09-26-add-ghost-explore-integration-api-key.js +0 -1
  70. package/core/server/data/migrations/versions/5.5/2022-07-18-14-29-add-comment-reporting-permissions.js +10 -0
  71. package/core/server/data/migrations/versions/5.5/2022-07-18-14-31-drop-reports-reason.js +3 -0
  72. package/core/server/data/migrations/versions/5.5/2022-07-18-14-32-drop-nullable-member-id-from-likes.js +4 -0
  73. package/core/server/data/migrations/versions/5.5/2022-07-18-14-33-fix-comments-on-delete-foreign-keys.js +119 -0
  74. package/core/server/data/migrations/versions/5.5/2022-07-21-08-56-add-jobs-table.js +11 -0
  75. package/core/server/data/migrations/versions/5.6/2022-07-27-13-40-change-explore-type.js +24 -0
  76. package/core/server/data/schema/commands.js +7 -2
  77. package/core/server/data/schema/fixtures/fixtures.json +6 -1
  78. package/core/server/data/schema/schema.js +12 -4
  79. package/core/server/ghost-server.js +0 -22
  80. package/core/server/models/comment-report.js +34 -0
  81. package/core/server/models/comment.js +8 -7
  82. package/core/server/models/job.js +9 -0
  83. package/core/server/services/bulk-email/bulk-email-processor.js +6 -0
  84. package/core/server/services/comments/controller.js +82 -0
  85. package/core/server/services/comments/email-templates/new-comment-reply.hbs +2 -2
  86. package/core/server/services/comments/email-templates/new-comment-reply.txt.js +7 -8
  87. package/core/server/services/comments/email-templates/new-comment.hbs +2 -2
  88. package/core/server/services/comments/email-templates/new-comment.txt.js +7 -6
  89. package/core/server/services/comments/email-templates/report.hbs +199 -0
  90. package/core/server/services/comments/email-templates/report.txt.js +16 -0
  91. package/core/server/services/comments/emails.js +57 -1
  92. package/core/server/services/comments/index.js +6 -1
  93. package/core/server/services/comments/service.js +291 -9
  94. package/core/server/services/jobs/job-service.js +24 -1
  95. package/core/server/services/mail/GhostMailer.js +1 -0
  96. package/core/server/services/mega/email-preview.js +5 -1
  97. package/core/server/services/mega/mega.js +2 -4
  98. package/core/server/services/mega/post-email-serializer.js +97 -2
  99. package/core/server/services/mega/segment-parser.js +10 -1
  100. package/core/server/services/members/api.js +2 -1
  101. package/core/server/services/members/service.js +9 -4
  102. package/core/server/services/public-config/config.js +2 -1
  103. package/core/server/services/settings/settings-bread-service.js +1 -1
  104. package/core/server/services/stripe/service.js +9 -1
  105. package/core/server/web/admin/views/default-prod.html +4 -4
  106. package/core/server/web/admin/views/default.html +4 -4
  107. package/core/server/web/api/testmode/jobs/graceful-job.js +2 -2
  108. package/core/server/web/api/testmode/routes.js +14 -0
  109. package/core/server/web/comments/routes.js +10 -8
  110. package/core/shared/config/defaults.json +12 -7
  111. package/core/shared/config/env/config.testing.json +3 -2
  112. package/core/shared/labs.js +3 -1
  113. package/package.json +92 -59
  114. package/yarn.lock +1821 -2011
  115. package/core/built/assets/ghost-dark-739c1f5546bd048eeeb253965ef36712.css +0 -1
  116. package/core/built/assets/ghost.min-5211776b9497f36fac8c9e5f2584cbcc.css +0 -1
@@ -50,6 +50,8 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
50
50
  }
51
51
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'cascadeDelete') && columnSpec.cascadeDelete === true) {
52
52
  column.onDelete('CASCADE');
53
+ } else if (Object.prototype.hasOwnProperty.call(columnSpec, 'setNullDelete') && columnSpec.setNullDelete === true) {
54
+ column.onDelete('SET NULL');
53
55
  }
54
56
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'defaultTo')) {
55
57
  column.defaultTo(columnSpec.defaultTo);
@@ -206,10 +208,11 @@ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, trans
206
208
  * @param {string} configuration.fromColumn - column of the table to add the foreign key to
207
209
  * @param {string} configuration.toTable - name of the table to point the foreign key to
208
210
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
209
- * @param {Boolean} configuration.cascadeDelete - adds the "on delete cascade" option if true
211
+ * @param {Boolean} [configuration.cascadeDelete] - adds the "on delete cascade" option if true
212
+ * @param {Boolean} [configuration.setNullDelete] - adds the "on delete SET NULL" option if true
210
213
  * @param {import('knex')} configuration.transaction - connection object containing knex reference
211
214
  */
212
- async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, transaction = db.knex}) {
215
+ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, setNullDelete = false, transaction = db.knex}) {
213
216
  if (DatabaseInfo.isSQLite(transaction)) {
214
217
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
215
218
  if (foreignKeyExists) {
@@ -232,6 +235,8 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
232
235
  await transaction.schema.table(fromTable, function (table) {
233
236
  if (cascadeDelete) {
234
237
  table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('CASCADE');
238
+ } else if (setNullDelete) {
239
+ table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('SET NULL');
235
240
  } else {
236
241
  table.foreign(fromColumn).references(`${toTable}.${toColumn}`);
237
242
  }
@@ -610,6 +610,11 @@
610
610
  "name": "Unlike comments",
611
611
  "action_type": "unlike",
612
612
  "object_type": "comment"
613
+ },
614
+ {
615
+ "name": "Report comments",
616
+ "action_type": "report",
617
+ "object_type": "comment"
613
618
  }
614
619
  ]
615
620
  },
@@ -667,7 +672,7 @@
667
672
  "slug": "ghost-explore",
668
673
  "name": "Ghost Explore",
669
674
  "description": "Built-in Ghost Explore integration",
670
- "type": "internal",
675
+ "type": "builtin",
671
676
  "api_keys": [{"type": "admin", "role": "Ghost Explore Integration"}]
672
677
  },
673
678
  {
@@ -750,7 +750,7 @@ module.exports = {
750
750
  comments: {
751
751
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
752
752
  post_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'posts.id', cascadeDelete: true},
753
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
753
+ member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
754
754
  parent_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id'},
755
755
  status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'published', validations: {isIn: [['published', 'hidden', 'deleted']]}},
756
756
  html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
@@ -761,16 +761,24 @@ module.exports = {
761
761
  comment_likes: {
762
762
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
763
763
  comment_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'comments.id', cascadeDelete: true},
764
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
764
+ member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
765
765
  created_at: {type: 'dateTime', nullable: false},
766
766
  updated_at: {type: 'dateTime', nullable: false}
767
767
  },
768
768
  comment_reports: {
769
769
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
770
770
  comment_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'comments.id', cascadeDelete: true},
771
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
772
- reason: {type: 'text', maxlength: 65535, nullable: false},
771
+ member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
773
772
  created_at: {type: 'dateTime', nullable: false},
774
773
  updated_at: {type: 'dateTime', nullable: false}
774
+ },
775
+ jobs: {
776
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
777
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
778
+ status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'queued', validations: {isIn: [['started', 'finished', 'failed', 'queued']]}},
779
+ started_at: {type: 'dateTime', nullable: true},
780
+ finished_at: {type: 'dateTime', nullable: true},
781
+ created_at: {type: 'dateTime', nullable: false},
782
+ updated_at: {type: 'dateTime', nullable: true}
775
783
  }
776
784
  };
@@ -213,9 +213,6 @@ class GhostServer {
213
213
  * Internal Method for TestMode.
214
214
  */
215
215
  _startTestMode() {
216
- // This is horrible and very temporary
217
- const jobService = require('./services/jobs');
218
-
219
216
  // Output how many connections are open every 5 seconds
220
217
  const connectionInterval = setInterval(() => this.httpServer.getConnections(
221
218
  (err, connections) => logging.warn(`${connections} connections currently open`)
@@ -226,25 +223,6 @@ class GhostServer {
226
223
  clearInterval(connectionInterval);
227
224
  logging.warn('Server has fully closed');
228
225
  });
229
-
230
- // Output job queue length every 5 seconds
231
- setInterval(() => {
232
- logging.warn(`${jobService.queue.length()} jobs in the queue. Idle: ${jobService.queue.idle()}`);
233
-
234
- const runningScheduledjobs = Object.keys(jobService.bree.workers);
235
- if (Object.keys(jobService.bree.workers).length) {
236
- logging.warn(`${Object.keys(jobService.bree.workers).length} jobs running: ${runningScheduledjobs}`);
237
- }
238
-
239
- const scheduledJobs = Object.keys(jobService.bree.intervals);
240
- if (Object.keys(jobService.bree.intervals).length) {
241
- logging.warn(`${Object.keys(jobService.bree.intervals).length} scheduled jobs: ${scheduledJobs}`);
242
- }
243
-
244
- if (runningScheduledjobs.length === 0 && scheduledJobs.length === 0) {
245
- logging.warn('No scheduled or running jobs');
246
- }
247
- }, 5000);
248
226
  }
249
227
 
250
228
  /**
@@ -0,0 +1,34 @@
1
+ const ghostBookshelf = require('./base');
2
+
3
+ const CommentReport = ghostBookshelf.Model.extend({
4
+ tableName: 'comment_reports',
5
+
6
+ defaults: function defaults() {
7
+ return {};
8
+ },
9
+
10
+ comment() {
11
+ return this.belongsTo('Comment', 'comment_id');
12
+ },
13
+
14
+ member() {
15
+ return this.belongsTo('Member', 'member_id');
16
+ },
17
+
18
+ emitChange: function emitChange(event, options) {
19
+ const eventToTrigger = 'comment_report' + '.' + event;
20
+ ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
21
+ },
22
+
23
+ onCreated: function onCreated(model, options) {
24
+ ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
25
+
26
+ model.emitChange('added', options);
27
+ }
28
+ }, {
29
+
30
+ });
31
+
32
+ module.exports = {
33
+ CommentReport: ghostBookshelf.model('CommentReport', CommentReport)
34
+ };
@@ -2,7 +2,6 @@ const ghostBookshelf = require('./base');
2
2
  const _ = require('lodash');
3
3
  const errors = require('@tryghost/errors');
4
4
  const tpl = require('@tryghost/tpl');
5
- const commentsService = require('../services/comments');
6
5
 
7
6
  const messages = {
8
7
  commentNotFound: 'Comment could not be found',
@@ -70,14 +69,14 @@ const Comment = ghostBookshelf.Model.extend({
70
69
  onCreated: function onCreated(model, options) {
71
70
  ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
72
71
 
73
- if (!options.context.internal) {
74
- commentsService.api.sendNewCommentNotifications(model);
75
- }
76
-
77
72
  model.emitChange('added', options);
78
73
  },
79
74
 
80
- enforcedFilters: function enforcedFilters() {
75
+ enforcedFilters: function enforcedFilters(options) {
76
+ if (options.context && options.context.user) {
77
+ return null;
78
+ }
79
+
81
80
  return 'parent_id:null';
82
81
  }
83
82
 
@@ -151,7 +150,9 @@ const Comment = ghostBookshelf.Model.extend({
151
150
  defaultRelations: function defaultRelations(methodName, options) {
152
151
  // @todo: the default relations are not working for 'add' when we add it below
153
152
  if (['findAll', 'findPage', 'edit', 'findOne'].indexOf(methodName) !== -1) {
154
- options.withRelated = _.union(['member', 'likes', 'replies', 'replies.member', 'replies.likes'], options.withRelated || []);
153
+ if (!options.withRelated || options.withRelated.length === 0) {
154
+ options.withRelated = ['member', 'likes', 'replies', 'replies.member', 'replies.likes'];
155
+ }
155
156
  }
156
157
 
157
158
  return options;
@@ -0,0 +1,9 @@
1
+ const ghostBookshelf = require('./base');
2
+
3
+ const Job = ghostBookshelf.Model.extend({
4
+ tableName: 'jobs'
5
+ });
6
+
7
+ module.exports = {
8
+ Job: ghostBookshelf.model('Job', Job)
9
+ };
@@ -7,6 +7,7 @@ const logging = require('@tryghost/logging');
7
7
  const models = require('../../models');
8
8
  const mailgunProvider = require('./mailgun');
9
9
  const sentry = require('../../../shared/sentry');
10
+ const labs = require('../../../shared/labs');
10
11
  const debug = require('@tryghost/debug')('mega');
11
12
  const postEmailSerializer = require('../mega/post-email-serializer');
12
13
 
@@ -170,6 +171,11 @@ module.exports = {
170
171
  // Load newsletter data on email
171
172
  await emailBatchModel.relations.email.getLazyRelation('newsletter', {require: false, ...knexOptions});
172
173
 
174
+ if (labs.isSet('newsletterPaywall')) {
175
+ // Load post data on email - for content gating on paywall
176
+ await emailBatchModel.relations.email.getLazyRelation('post', {require: false, ...knexOptions});
177
+ }
178
+
173
179
  // send the email
174
180
  const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment);
175
181
 
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @typedef {import('../../api/shared/frame')} Frame
3
+ */
4
+
5
+ const {MethodNotAllowedError} = require('@tryghost/errors');
6
+ const tpl = require('@tryghost/tpl');
7
+
8
+ const messages = {
9
+ cannotDestroyComments: 'You cannot destroy comments.'
10
+ };
11
+
12
+ module.exports = class CommentsController {
13
+ /**
14
+ * @param {import('./service')} service
15
+ */
16
+ constructor(service) {
17
+ this.service = service;
18
+ }
19
+
20
+ /**
21
+ * @param {Frame} frame
22
+ */
23
+ async browse(frame) {
24
+ return this.service.getComments(frame.options);
25
+ }
26
+
27
+ /**
28
+ * @param {Frame} frame
29
+ */
30
+ async read(frame) {
31
+ return await this.service.getCommentByID(frame.data.id, frame.options);
32
+ }
33
+
34
+ /**
35
+ * @param {Frame} frame
36
+ */
37
+ async edit(frame) {
38
+ if (frame.data.comments[0].status === 'deleted') {
39
+ return await this.service.deleteComment(
40
+ frame.options.id,
41
+ frame?.options?.context?.member?.id,
42
+ frame.options
43
+ );
44
+ }
45
+
46
+ return await this.service.editCommentContent(
47
+ frame.options.id,
48
+ frame?.options?.context?.member?.id,
49
+ frame.data.comments[0].html,
50
+ frame.options
51
+ );
52
+ }
53
+
54
+ /**
55
+ * @param {Frame} frame
56
+ */
57
+ async add(frame) {
58
+ const data = frame.data.comments[0];
59
+
60
+ if (data.parent_id) {
61
+ return await this.service.replyToComment(
62
+ data.parent_id,
63
+ frame.options.context.member.id,
64
+ data.html,
65
+ frame.options
66
+ );
67
+ }
68
+
69
+ return await this.service.commentOnPost(
70
+ data.post_id,
71
+ frame.options.context.member.id,
72
+ data.html,
73
+ frame.options
74
+ );
75
+ }
76
+
77
+ async destroy() {
78
+ throw new MethodNotAllowedError({
79
+ message: tpl(messages.cannotDestroyComments)
80
+ });
81
+ }
82
+ };
@@ -116,7 +116,7 @@
116
116
  <tr>
117
117
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
118
118
  <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
119
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just replied to your comment on <a href="https://ghost.org">{{postTitle}}</a>.</p>
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just replied to your comment on <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>.</p>
120
120
 
121
121
  <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
122
122
  <tbody>
@@ -196,4 +196,4 @@
196
196
  </tr>
197
197
  </table>
198
198
  </body>
199
- </html>
199
+ </html>
@@ -1,14 +1,13 @@
1
1
  module.exports = function (data) {
2
- return `
3
- Hey there,
2
+ // Be careful when you indent the email, because whitespaces are visible in emails!
3
+ return `Hey there,
4
4
 
5
- Someone just replied to your comment on "${data.postTitle}"
5
+ Someone just replied to your comment on "${data.postTitle}"
6
6
 
7
- ${data.postUrl}#comments-area
7
+ ${data.postUrl}#ghost-comments-root
8
8
 
9
- ---
9
+ ---
10
10
 
11
- Sent to ${data.toEmail} from ${data.siteDomain}.
12
- You can manage your notification preferences at ${data.profileUrl}.
13
- `;
11
+ Sent to ${data.toEmail} from ${data.siteDomain}.
12
+ You can manage your notification preferences at ${data.profileUrl}.`;
14
13
  };
@@ -116,7 +116,7 @@
116
116
  <tr>
117
117
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
118
118
  <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
119
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just left a comment on your post <a href="https://ghost.org">{{postTitle}}</a>.</p>
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just left a comment on your post <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>.</p>
120
120
 
121
121
  <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
122
122
  <tbody>
@@ -196,4 +196,4 @@
196
196
  </tr>
197
197
  </table>
198
198
  </body>
199
- </html>
199
+ </html>
@@ -1,14 +1,15 @@
1
1
  module.exports = function (data) {
2
+ // Be careful when you indent the email, because whitespaces are visible in emails!
2
3
  return `
3
- Hey there,
4
+ Hey there,
4
5
 
5
- Someone just posted a comment on your post "${data.postTitle}"
6
+ Someone just posted a comment on your post "${data.postTitle}"
6
7
 
7
- ${data.postUrl}#comments-area
8
+ ${data.postUrl}#ghost-comments-root
8
9
 
9
- ---
10
+ ---
10
11
 
11
- Sent to ${data.toEmail} from ${data.siteDomain}.
12
- You can manage your notification preferences at ${data.staffUrl}.
12
+ Sent to ${data.toEmail} from ${data.siteDomain}.
13
+ You can manage your notification preferences at ${data.staffUrl}.
13
14
  `;
14
15
  };
@@ -0,0 +1,199 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width">
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6
+ <title>🚩 A comment has been reported on {{postTitle}}</title>
7
+ <style>
8
+ /* -------------------------------------
9
+ RESPONSIVE AND MOBILE FRIENDLY STYLES
10
+ ------------------------------------- */
11
+ @media only screen and (max-width: 620px) {
12
+ table[class=body] h1 {
13
+ font-size: 28px !important;
14
+ margin-bottom: 10px !important;
15
+ }
16
+ table[class=body] p,
17
+ table[class=body] ul,
18
+ table[class=body] ol,
19
+ table[class=body] td,
20
+ table[class=body] span,
21
+ table[class=body] a {
22
+ font-size: 16px !important;
23
+ }
24
+ table[class=body] .wrapper,
25
+ table[class=body] .article {
26
+ padding: 10px !important;
27
+ }
28
+ table[class=body] .content {
29
+ padding: 0 !important;
30
+ }
31
+ table[class=body] .container {
32
+ padding: 0 !important;
33
+ width: 100% !important;
34
+ }
35
+ table[class=body] .main {
36
+ border-left-width: 0 !important;
37
+ border-radius: 0 !important;
38
+ border-right-width: 0 !important;
39
+ }
40
+ table[class=body] .btn table {
41
+ width: 100% !important;
42
+ }
43
+ table[class=body] .btn a {
44
+ width: 100% !important;
45
+ }
46
+ table[class=body] .img-responsive {
47
+ height: auto !important;
48
+ max-width: 100% !important;
49
+ width: auto !important;
50
+ }
51
+ table[class=body] p[class=small],
52
+ table[class=body] a[class=small] {
53
+ font-size: 11px !important;
54
+ }
55
+ }
56
+ /* -------------------------------------
57
+ PRESERVE THESE STYLES IN THE HEAD
58
+ ------------------------------------- */
59
+ @media all {
60
+ .ExternalClass {
61
+ width: 100%;
62
+ }
63
+ .ExternalClass,
64
+ .ExternalClass p,
65
+ .ExternalClass span,
66
+ .ExternalClass font,
67
+ .ExternalClass td,
68
+ .ExternalClass div {
69
+ line-height: 100%;
70
+ }
71
+ .recipient-link a {
72
+ color: inherit !important;
73
+ font-family: inherit !important;
74
+ font-size: inherit !important;
75
+ font-weight: inherit !important;
76
+ line-height: inherit !important;
77
+ text-decoration: none !important;
78
+ }
79
+ #MessageViewBody a {
80
+ color: inherit;
81
+ text-decoration: none;
82
+ font-size: inherit;
83
+ font-family: inherit;
84
+ font-weight: inherit;
85
+ line-height: inherit;
86
+ }
87
+ }
88
+ hr {
89
+ border-width: 0;
90
+ height: 0;
91
+ margin-top: 34px;
92
+ margin-bottom: 34px;
93
+ border-bottom-width: 1px;
94
+ border-bottom-color: #EEF5F8;
95
+ }
96
+ a {
97
+ color: #3A464C;
98
+ }
99
+ </style>
100
+ </head>
101
+ <body style="background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.5em; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
102
+ <table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
103
+ <tr>
104
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">&nbsp;</td>
105
+ <td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 540px; padding: 10px; width: 540px;">
106
+ <div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 600px; padding: 30px 20px;">
107
+
108
+ <!-- START CENTERED CONTAINER -->
109
+ <span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">{{reporterName}} ({{reporterEmail}}) reported a comment on your post</span>
110
+ <table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
111
+
112
+ <!-- START MAIN CONTENT AREA -->
113
+ <tr>
114
+ <td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
115
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
116
+ <tr>
117
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
118
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">{{reporter}} has reported the comment below on <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>. This comment will remain visible until you choose to remove it, which can be done directly on the post.</p>
120
+
121
+ <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
122
+ <tbody>
123
+ <tr>
124
+ <td align="left" style="padding: 16px;">
125
+ <table border="0" cellpadding="0" cellspacing="0">
126
+ <tr>
127
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px;">
128
+ <table border="0" cellpadding="0" cellspacing="0" width="44" height="44" style="width: 44px; height: 44px; background-color: #15171A; border-radius: 999px;">
129
+ <tr>
130
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; color: #FFFFFF; text-align: center; font-weight: 500; height: 44px; width: 44px;">{{memberInitials}}</td>
131
+ </tr>
132
+ </table>
133
+ </td>
134
+ <td style="padding-right: 8px;">
135
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600;">{{memberName}}</p>
136
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">{{#if memberBio}}{{memberBio}} &#8226; {{/if}}{{commentDate}}</p>
137
+ </td>
138
+ </tr>
139
+ </table>
140
+ </td>
141
+ </tr>
142
+ <tr>
143
+ <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 0; padding-right: 16px; padding-bottom: 16px; padding-left: 16px; color: #15171A;">
144
+ {{{commentHtml}}}
145
+ </td>
146
+ </tr>
147
+ </tbody>
148
+ </table>
149
+
150
+ <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
151
+ <tbody>
152
+ <tr>
153
+ <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 32px; padding-bottom: 12px;">
154
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
155
+ <tbody>
156
+ <tr>
157
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: {{accentColor}}; border-radius: 5px; text-align: center;"> <a href="{{postUrl}}#ghost-comments-root" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: {{accentColor}};">View comment</a> </td>
158
+ </tr>
159
+ </tbody>
160
+ </table>
161
+ </td>
162
+ </tr>
163
+ </tbody>
164
+ </table>
165
+ <hr/>
166
+ <p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
167
+ <p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top:0; color: #3A464C;">{{postUrl}}#ghost-comments-root</p>
168
+ </td>
169
+ </tr>
170
+
171
+ <!-- START FOOTER -->
172
+ <tr>
173
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 80px;">
174
+ <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{toEmail}}</a></p>
175
+ </td>
176
+ </tr>
177
+ <tr>
178
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px">
179
+ <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;"><a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">Manage your email preferences</a></p>
180
+ </td>
181
+ </tr>
182
+
183
+ <!-- END FOOTER -->
184
+ </table>
185
+ </td>
186
+ </tr>
187
+
188
+ <!-- END MAIN CONTENT AREA -->
189
+ </table>
190
+
191
+
192
+ <!-- END CENTERED CONTAINER -->
193
+ </div>
194
+ </td>
195
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">&nbsp;</td>
196
+ </tr>
197
+ </table>
198
+ </body>
199
+ </html>
@@ -0,0 +1,16 @@
1
+ module.exports = function (data) {
2
+ // Be careful when you indent the email, because whitespaces are visible in emails!
3
+ return `Hey there,
4
+
5
+ ${data.reporter} has reported the comment below on ${data.postTitle}. This comment will remain visible until you choose to remove it, which can be done directly on the post.
6
+
7
+ ${data.memberName} (${data.memberEmail}):
8
+ ${data.commentText}
9
+
10
+ ${data.postUrl}#ghost-comments-root
11
+
12
+ ---
13
+
14
+ Sent to ${data.toEmail} from ${data.siteDomain}.
15
+ You can manage your notification preferences at ${data.staffUrl}.`;
16
+ };