ghost 5.22.9 → 5.22.10

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 (129) hide show
  1. package/components/tryghost-adapter-manager-5.22.10.tgz +0 -0
  2. package/components/tryghost-api-framework-5.22.10.tgz +0 -0
  3. package/components/tryghost-api-version-compatibility-service-5.22.10.tgz +0 -0
  4. package/components/tryghost-audience-feedback-5.22.10.tgz +0 -0
  5. package/components/tryghost-bootstrap-socket-5.22.10.tgz +0 -0
  6. package/components/tryghost-constants-5.22.10.tgz +0 -0
  7. package/components/tryghost-custom-theme-settings-service-5.22.10.tgz +0 -0
  8. package/components/tryghost-data-generator-5.22.10.tgz +0 -0
  9. package/components/tryghost-domain-events-5.22.10.tgz +0 -0
  10. package/components/tryghost-email-analytics-provider-mailgun-5.22.10.tgz +0 -0
  11. package/components/tryghost-email-analytics-service-5.22.10.tgz +0 -0
  12. package/components/tryghost-email-content-generator-5.22.10.tgz +0 -0
  13. package/components/tryghost-express-dynamic-redirects-5.22.10.tgz +0 -0
  14. package/components/tryghost-extract-api-key-5.22.10.tgz +0 -0
  15. package/components/tryghost-html-to-plaintext-5.22.10.tgz +0 -0
  16. package/components/{tryghost-job-manager-5.22.9.tgz → tryghost-job-manager-5.22.10.tgz} +0 -0
  17. package/components/tryghost-link-redirects-5.22.10.tgz +0 -0
  18. package/components/tryghost-link-replacer-5.22.10.tgz +0 -0
  19. package/components/tryghost-link-tracking-5.22.10.tgz +0 -0
  20. package/components/{tryghost-magic-link-5.22.9.tgz → tryghost-magic-link-5.22.10.tgz} +0 -0
  21. package/components/tryghost-mailgun-client-5.22.10.tgz +0 -0
  22. package/components/tryghost-member-analytics-service-5.22.10.tgz +0 -0
  23. package/components/tryghost-member-attribution-5.22.10.tgz +0 -0
  24. package/components/tryghost-member-events-5.22.10.tgz +0 -0
  25. package/components/tryghost-members-analytics-ingress-5.22.10.tgz +0 -0
  26. package/components/tryghost-members-api-5.22.10.tgz +0 -0
  27. package/components/tryghost-members-csv-5.22.10.tgz +0 -0
  28. package/components/tryghost-members-events-service-5.22.10.tgz +0 -0
  29. package/components/tryghost-members-importer-5.22.10.tgz +0 -0
  30. package/components/tryghost-members-offers-5.22.10.tgz +0 -0
  31. package/components/tryghost-members-payments-5.22.10.tgz +0 -0
  32. package/components/tryghost-members-ssr-5.22.10.tgz +0 -0
  33. package/components/tryghost-members-stripe-service-5.22.10.tgz +0 -0
  34. package/components/tryghost-minifier-5.22.10.tgz +0 -0
  35. package/components/tryghost-mw-api-version-mismatch-5.22.10.tgz +0 -0
  36. package/components/tryghost-mw-cache-control-5.22.10.tgz +0 -0
  37. package/components/tryghost-mw-error-handler-5.22.10.tgz +0 -0
  38. package/components/tryghost-mw-session-from-token-5.22.10.tgz +0 -0
  39. package/components/tryghost-mw-update-user-last-seen-5.22.10.tgz +0 -0
  40. package/components/tryghost-mw-vhost-5.22.10.tgz +0 -0
  41. package/components/tryghost-oembed-service-5.22.10.tgz +0 -0
  42. package/components/tryghost-package-json-5.22.10.tgz +0 -0
  43. package/components/{tryghost-referrers-5.22.9.tgz → tryghost-referrers-5.22.10.tgz} +0 -0
  44. package/components/tryghost-security-5.22.10.tgz +0 -0
  45. package/components/tryghost-session-service-5.22.10.tgz +0 -0
  46. package/components/{tryghost-settings-path-manager-5.22.9.tgz → tryghost-settings-path-manager-5.22.10.tgz} +0 -0
  47. package/components/tryghost-staff-service-5.22.10.tgz +0 -0
  48. package/components/tryghost-stats-service-5.22.10.tgz +0 -0
  49. package/components/{tryghost-tiers-5.22.9.tgz → tryghost-tiers-5.22.10.tgz} +0 -0
  50. package/components/tryghost-update-check-service-5.22.10.tgz +0 -0
  51. package/components/tryghost-verification-trigger-5.22.10.tgz +0 -0
  52. package/components/tryghost-version-notifications-data-service-5.22.10.tgz +0 -0
  53. package/content/themes/casper/assets/built/casper.js +1 -1
  54. package/content/themes/casper/assets/built/casper.js.map +1 -1
  55. package/content/themes/casper/assets/built/screen.css +1 -1
  56. package/content/themes/casper/assets/built/screen.css.map +1 -1
  57. package/content/themes/casper/assets/css/screen.css +332 -194
  58. package/content/themes/casper/assets/js/dropdown.js +81 -0
  59. package/content/themes/casper/assets/js/lib/imagesloaded.pkgd.min.js +7 -0
  60. package/content/themes/casper/default.hbs +28 -28
  61. package/content/themes/casper/gulpfile.js +2 -1
  62. package/content/themes/casper/index.hbs +2 -2
  63. package/content/themes/casper/package.json +10 -6
  64. package/core/built/admin/assets/{chunk.143.f9b9a3ccbc33fc98b4dd.js → chunk.143.efc32dbcc893663c95cb.js} +5 -5
  65. package/core/built/admin/assets/{chunk.178.f7ccd862df19d9dda5bb.js → chunk.178.95a503a229a8fd49e91e.js} +4 -4
  66. package/core/built/admin/assets/{ghost-be360e360f53a63fafd228ae89bf96c8.css → ghost-03c7a25d23ad4d0725da171f8d7c7b2a.css} +1 -1
  67. package/core/built/admin/assets/{ghost-dab6700715992e873d0672192fc8e2be.js → ghost-b204dcc6ad523053868da9b2d8d65f80.js} +430 -370
  68. package/core/built/admin/assets/{ghost-dark-08002fad1d09cb7a88d09abf2ce4da29.css → ghost-dark-8896a076fc06ec2b09343b1c9df7feca.css} +1 -1
  69. package/core/built/admin/index.html +3 -3
  70. package/core/server/data/importer/handlers/markdown.js +0 -1
  71. package/core/server/data/importer/importers/data/base.js +26 -39
  72. package/core/server/data/importer/importers/data/custom-theme-settings.js +33 -49
  73. package/core/server/data/importer/importers/data/data-importer.js +38 -68
  74. package/core/server/data/importer/importers/data/settings.js +10 -10
  75. package/core/server/data/importer/importers/data/tags.js +27 -28
  76. package/core/server/data/schema/fixtures/fixture-manager.js +1 -1
  77. package/core/server/services/mega/feedback-buttons.js +19 -5
  78. package/core/server/services/mega/post-email-serializer.js +10 -2
  79. package/core/server/services/member-attribution/index.js +2 -1
  80. package/core/server/web/members/app.js +1 -1
  81. package/package.json +103 -103
  82. package/yarn.lock +68 -49
  83. package/components/tryghost-adapter-manager-5.22.9.tgz +0 -0
  84. package/components/tryghost-api-framework-5.22.9.tgz +0 -0
  85. package/components/tryghost-api-version-compatibility-service-5.22.9.tgz +0 -0
  86. package/components/tryghost-audience-feedback-5.22.9.tgz +0 -0
  87. package/components/tryghost-bootstrap-socket-5.22.9.tgz +0 -0
  88. package/components/tryghost-constants-5.22.9.tgz +0 -0
  89. package/components/tryghost-custom-theme-settings-service-5.22.9.tgz +0 -0
  90. package/components/tryghost-data-generator-5.22.9.tgz +0 -0
  91. package/components/tryghost-domain-events-5.22.9.tgz +0 -0
  92. package/components/tryghost-email-analytics-provider-mailgun-5.22.9.tgz +0 -0
  93. package/components/tryghost-email-analytics-service-5.22.9.tgz +0 -0
  94. package/components/tryghost-email-content-generator-5.22.9.tgz +0 -0
  95. package/components/tryghost-express-dynamic-redirects-5.22.9.tgz +0 -0
  96. package/components/tryghost-extract-api-key-5.22.9.tgz +0 -0
  97. package/components/tryghost-html-to-plaintext-5.22.9.tgz +0 -0
  98. package/components/tryghost-link-redirects-5.22.9.tgz +0 -0
  99. package/components/tryghost-link-replacer-5.22.9.tgz +0 -0
  100. package/components/tryghost-link-tracking-5.22.9.tgz +0 -0
  101. package/components/tryghost-mailgun-client-5.22.9.tgz +0 -0
  102. package/components/tryghost-member-analytics-service-5.22.9.tgz +0 -0
  103. package/components/tryghost-member-attribution-5.22.9.tgz +0 -0
  104. package/components/tryghost-member-events-5.22.9.tgz +0 -0
  105. package/components/tryghost-members-analytics-ingress-5.22.9.tgz +0 -0
  106. package/components/tryghost-members-api-5.22.9.tgz +0 -0
  107. package/components/tryghost-members-csv-5.22.9.tgz +0 -0
  108. package/components/tryghost-members-events-service-5.22.9.tgz +0 -0
  109. package/components/tryghost-members-importer-5.22.9.tgz +0 -0
  110. package/components/tryghost-members-offers-5.22.9.tgz +0 -0
  111. package/components/tryghost-members-payments-5.22.9.tgz +0 -0
  112. package/components/tryghost-members-ssr-5.22.9.tgz +0 -0
  113. package/components/tryghost-members-stripe-service-5.22.9.tgz +0 -0
  114. package/components/tryghost-minifier-5.22.9.tgz +0 -0
  115. package/components/tryghost-mw-api-version-mismatch-5.22.9.tgz +0 -0
  116. package/components/tryghost-mw-cache-control-5.22.9.tgz +0 -0
  117. package/components/tryghost-mw-error-handler-5.22.9.tgz +0 -0
  118. package/components/tryghost-mw-session-from-token-5.22.9.tgz +0 -0
  119. package/components/tryghost-mw-update-user-last-seen-5.22.9.tgz +0 -0
  120. package/components/tryghost-mw-vhost-5.22.9.tgz +0 -0
  121. package/components/tryghost-oembed-service-5.22.9.tgz +0 -0
  122. package/components/tryghost-package-json-5.22.9.tgz +0 -0
  123. package/components/tryghost-security-5.22.9.tgz +0 -0
  124. package/components/tryghost-session-service-5.22.9.tgz +0 -0
  125. package/components/tryghost-staff-service-5.22.9.tgz +0 -0
  126. package/components/tryghost-stats-service-5.22.9.tgz +0 -0
  127. package/components/tryghost-update-check-service-5.22.9.tgz +0 -0
  128. package/components/tryghost-verification-trigger-5.22.9.tgz +0 -0
  129. package/components/tryghost-version-notifications-data-service-5.22.9.tgz +0 -0
@@ -37,7 +37,7 @@
37
37
  </style>
38
38
 
39
39
  <link integrity="" rel="stylesheet" href="assets/vendor-3e6947aa681f0fb82b193090e520dc73.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-be360e360f53a63fafd228ae89bf96c8.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-03c7a25d23ad4d0725da171f8d7c7b2a.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -58,7 +58,7 @@
58
58
 
59
59
  <script src="assets/vendor-dc9f883b3468ff84794cf13741e6c4b4.js"></script>
60
60
  <script src="assets/chunk.613.695f31829550fb00d43c.js"></script>
61
- <script src="assets/chunk.143.f9b9a3ccbc33fc98b4dd.js"></script>
62
- <script src="assets/ghost-dab6700715992e873d0672192fc8e2be.js"></script>
61
+ <script src="assets/chunk.143.efc32dbcc893663c95cb.js"></script>
62
+ <script src="assets/ghost-b204dcc6ad523053868da9b2d8d65f80.js"></script>
63
63
  </body>
64
64
  </html>
@@ -1,5 +1,4 @@
1
1
  const _ = require('lodash');
2
- const Promise = require('bluebird');
3
2
  const fs = require('fs-extra');
4
3
  const moment = require('moment');
5
4
  const featuredImageRegex = /^(!\[]\(([^)]*?)\)\s+)(?=#)/;
@@ -1,6 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('importer:base');
2
2
  const _ = require('lodash');
3
- const Promise = require('bluebird');
4
3
  const ObjectId = require('bson-objectid').default;
5
4
  const errors = require('@tryghost/errors');
6
5
  const {sequence} = require('@tryghost/promise');
@@ -12,6 +11,7 @@ class Base {
12
11
  this.modelName = options.modelName;
13
12
 
14
13
  this.problems = [];
14
+ this.errors = [];
15
15
 
16
16
  this.errorConfig = {
17
17
  allowDuplicates: true,
@@ -165,11 +165,11 @@ class Base {
165
165
  this.problems = this.problems.concat(problems);
166
166
  debug('detected problem/warning', problems);
167
167
 
168
- return Promise.resolve();
168
+ return;
169
169
  }
170
170
 
171
171
  debug('err', errorsToReject, obj);
172
- return Promise.reject(errorsToReject);
172
+ this.errors = this.errors.concat(errorsToReject);
173
173
  }
174
174
 
175
175
  /**
@@ -317,50 +317,37 @@ class Base {
317
317
  };
318
318
  }
319
319
 
320
- doImport(options, importOptions) {
320
+ async doImport(options, importOptions) {
321
321
  debug('doImport', this.modelName, this.dataToImport.length);
322
322
 
323
323
  let ops = [];
324
324
 
325
325
  _.each(this.dataToImport, (obj, index) => {
326
- ops.push(() => {
327
- return models[this.modelName].add(obj, options)
328
- .then((importedModel) => {
329
- obj.model = {
330
- id: importedModel.id
331
- };
332
-
333
- if (importOptions.returnImportedData) {
334
- this.importedDataToReturn.push(importedModel.toJSON());
335
- }
336
-
337
- // for identifier lookup
338
- this.importedData.push(
339
- this.mapImportedData(obj, importedModel)
340
- );
341
-
342
- importedModel = null;
343
- this.dataToImport.splice(index, 1);
344
- })
345
- .catch((err) => {
346
- return this.handleError(err, obj);
347
- })
348
- .reflect();
326
+ ops.push(async () => {
327
+ try {
328
+ const importedModel = await models[this.modelName].add(obj, options);
329
+ obj.model = {
330
+ id: importedModel.id
331
+ };
332
+
333
+ if (importOptions.returnImportedData) {
334
+ this.importedDataToReturn.push(importedModel.toJSON());
335
+ }
336
+
337
+ // for identifier lookup
338
+ this.importedData.push(
339
+ this.mapImportedData(obj, importedModel)
340
+ );
341
+
342
+ // To free memory early
343
+ this.dataToImport.splice(index, 1);
344
+ } catch (err) {
345
+ this.handleError(err, obj);
346
+ }
349
347
  });
350
348
  });
351
349
 
352
- /**
353
- * NOTE: Do not run with Promise.all in this case. With a large import file, we run an enormous
354
- * amount of queries in parallel. Node will very fast eat lot's of memory, because all queries start
355
- * at the same time, but memory can only be released if the query finished.
356
- *
357
- * Promise.map(.., {concurrency: Int}) was not really improving the end performance for me.
358
- */
359
- return sequence(ops).then((response) => {
360
- this.dataToImport = null;
361
- ops = null;
362
- return response;
363
- });
350
+ await sequence(ops);
364
351
  }
365
352
  }
366
353
 
@@ -1,9 +1,9 @@
1
1
  const _ = require('lodash');
2
- const Promise = require('bluebird');
3
2
  const debug = require('@tryghost/debug')('importer:roles');
4
3
  const BaseImporter = require('./base');
5
4
  const models = require('../../../../models');
6
5
  const {activate} = require('../../../../services/themes/activate');
6
+ const {sequence} = require('@tryghost/promise');
7
7
 
8
8
  class CustomThemeSettingsImporter extends BaseImporter {
9
9
  constructor(allDataFromFile) {
@@ -18,64 +18,48 @@ class CustomThemeSettingsImporter extends BaseImporter {
18
18
  return super.beforeImport();
19
19
  }
20
20
 
21
- doImport(options, importOptions) {
21
+ async doImport(options, importOptions) {
22
22
  debug('doImport', this.modelName, this.dataToImport.length);
23
23
 
24
24
  let ops = [];
25
25
 
26
26
  _.each(this.dataToImport, (item) => {
27
- ops.push(models.CustomThemeSetting.findOne({theme: item.theme, key: item.key}, options)
28
- .then((setting) => {
29
- if (_.isObject(item.value)) {
30
- item.value = JSON.stringify(item.value);
31
- }
32
-
33
- if (setting) {
34
- setting.set('value', item.value);
35
- if (setting.hasChanged()) {
36
- return setting.save(null, options)
37
- .then((importedModel) => {
38
- if (importOptions.returnImportedData) {
39
- this.importedDataToReturn.push(importedModel.toJSON());
40
- }
41
- return importedModel;
42
- })
43
- .catch((err) => {
44
- return this.handleError(err, item);
45
- });
46
- }
27
+ ops.push(async () => {
28
+ const setting = await models.CustomThemeSetting.findOne({theme: item.theme, key: item.key}, options);
29
+ if (_.isObject(item.value)) {
30
+ item.value = JSON.stringify(item.value);
31
+ }
47
32
 
48
- return Promise.resolve();
49
- }
33
+ if (setting) {
34
+ setting.set('value', item.value);
35
+ }
50
36
 
51
- return models.CustomThemeSetting.add(item, options)
52
- .then((importedModel) => {
53
- if (importOptions.returnImportedData) {
54
- this.importedDataToReturn.push(importedModel.toJSON());
55
- }
56
- return importedModel;
57
- })
58
- .catch((err) => {
59
- return this.handleError(err, item);
60
- });
61
- })
62
- .reflect());
63
- });
64
-
65
- const opsPromise = Promise.all(ops);
37
+ if (setting && !setting.hasChanged()) {
38
+ return;
39
+ }
66
40
 
67
- // activate function is called to refresh cache when importing custom theme settings for active theme
68
- opsPromise.then(() => {
69
- models.Settings.findOne({key: 'active_theme'})
70
- .then((theme) => {
71
- const currentTheme = theme.get('value');
72
- if (this.dataToImport.some(themeSetting => themeSetting.theme === currentTheme)) {
73
- activate(currentTheme);
41
+ try {
42
+ const importedModel = setting
43
+ ? await setting.save(null, options)
44
+ : await models.CustomThemeSetting.add(item, options);
45
+
46
+ if (importOptions.returnImportedData) {
47
+ this.importedDataToReturn.push(importedModel.toJSON());
74
48
  }
75
- });
49
+ } catch (err) {
50
+ this.handleError(err, item);
51
+ }
52
+ });
76
53
  });
77
54
 
78
- return opsPromise;
55
+ await sequence(ops);
56
+
57
+ models.Settings.findOne({key: 'active_theme'}).then((theme) => {
58
+ const currentTheme = theme.get('value');
59
+ if (this.dataToImport.some(themeSetting => themeSetting.theme === currentTheme)) {
60
+ activate(currentTheme);
61
+ }
62
+ });
79
63
  }
80
64
  }
81
- module.exports = CustomThemeSettingsImporter;
65
+ module.exports = CustomThemeSettingsImporter;
@@ -15,6 +15,7 @@ const StripeProductsImporter = require('./stripe-products');
15
15
  const StripePricesImporter = require('./stripe-prices');
16
16
  const CustomThemeSettingsImporter = require('./custom-theme-settings');
17
17
  const RolesImporter = require('./roles');
18
+
18
19
  let importers = {};
19
20
  let DataImporter;
20
21
 
@@ -42,12 +43,13 @@ DataImporter = {
42
43
  },
43
44
 
44
45
  // Allow importing with an options object that is passed through the importer
45
- doImport: function doImport(importData, importOptions) {
46
+ doImport: async function doImport(importData, importOptions) {
46
47
  importOptions = importOptions || {};
47
48
 
48
49
  const ops = [];
50
+ let problems = [];
49
51
  let errors = [];
50
- let results = [];
52
+ let importedData = {};
51
53
 
52
54
  const modelOptions = {
53
55
  importing: true,
@@ -89,36 +91,34 @@ DataImporter = {
89
91
 
90
92
  this.init(importData);
91
93
 
92
- return models.Base.transaction(function (transacting) {
94
+ return models.Base.transaction(async function (transacting) {
93
95
  modelOptions.transacting = transacting;
94
96
 
95
97
  _.each(importers, function (importer) {
96
- ops.push(function doModelImport() {
97
- return importer.fetchExisting(modelOptions, importOptions)
98
- .then(function () {
99
- return importer.beforeImport(modelOptions, importOptions);
100
- })
101
- .then(function () {
102
- if (importer.options.requiredImportedData.length) {
103
- _.each(importer.options.requiredImportedData, (key) => {
104
- importer.requiredImportedData[key] = importers[key].importedData;
105
- });
106
- }
107
-
108
- if (importer.options.requiredExistingData.length) {
109
- _.each(importer.options.requiredExistingData, (key) => {
110
- importer.requiredExistingData[key] = importers[key].existingData;
111
- });
112
- }
113
-
114
- return importer.replaceIdentifiers(modelOptions, importOptions);
115
- })
116
- .then(function () {
117
- return importer.doImport(modelOptions, importOptions)
118
- .then(function (_results) {
119
- results = results.concat(_results);
120
- });
98
+ ops.push(async function doModelImport() {
99
+ await importer.fetchExisting(modelOptions, importOptions);
100
+ await importer.beforeImport(modelOptions, importOptions);
101
+
102
+ if (importer.options.requiredImportedData.length) {
103
+ _.each(importer.options.requiredImportedData, (key) => {
104
+ importer.requiredImportedData[key] = importers[key].importedData;
105
+ });
106
+ }
107
+
108
+ if (importer.options.requiredExistingData.length) {
109
+ _.each(importer.options.requiredExistingData, (key) => {
110
+ importer.requiredExistingData[key] = importers[key].existingData;
121
111
  });
112
+ }
113
+
114
+ await importer.replaceIdentifiers(modelOptions, importOptions);
115
+ await importer.doImport(modelOptions, importOptions);
116
+
117
+ errors = errors.concat(importer.errors);
118
+ problems = problems.concat(importer.problems);
119
+ if (importOptions.returnImportedData) {
120
+ importedData[importer.dataKeyToImport] = importer.importedDataToReturn;
121
+ }
122
122
  });
123
123
  });
124
124
 
@@ -151,49 +151,19 @@ DataImporter = {
151
151
  return sequence(productOps);
152
152
  });
153
153
 
154
- return sequence(ops)
155
- .then(function () {
156
- results.forEach(function (promise) {
157
- if (!promise.isFulfilled()) {
158
- errors = errors.concat(promise.reason());
159
- }
160
- });
154
+ await sequence(ops);
161
155
 
162
- if (errors.length === 0) {
163
- return;
164
- } else {
165
- throw errors;
166
- }
167
- });
168
- }).then(function () {
169
- /**
170
- * data: imported data
171
- * originalData: data from the json file
172
- * problems: warnings
173
- */
174
- const toReturn = {
175
- data: {},
156
+ // Errors preventing import:
157
+ if (errors.length > 0) {
158
+ debug(errors);
159
+ throw errors;
160
+ }
161
+
162
+ return {
163
+ data: importedData,
176
164
  originalData: importData.data,
177
- problems: []
165
+ problems: problems
178
166
  };
179
-
180
- _.each(importers, function (importer) {
181
- toReturn.problems = toReturn.problems.concat(importer.problems);
182
-
183
- if (importOptions.returnImportedData) {
184
- toReturn.data[importer.dataKeyToImport] = importer.importedDataToReturn;
185
- }
186
- });
187
-
188
- return toReturn;
189
- }).catch(function (err) {
190
- debug(err);
191
- return Promise.reject(err);
192
- }).finally(() => {
193
- // release memory
194
- importers = {};
195
- results = null;
196
- importData = null;
197
167
  });
198
168
  }
199
169
  };
@@ -1,5 +1,4 @@
1
1
  const debug = require('@tryghost/debug')('importer:settings');
2
- const Promise = require('bluebird');
3
2
  const ObjectId = require('bson-objectid').default;
4
3
  const _ = require('lodash');
5
4
  const BaseImporter = require('./base');
@@ -8,6 +7,7 @@ const defaultSettings = require('../../../schema').defaultSettings;
8
7
  const keyGroupMapper = require('../../../../api/endpoints/utils/serializers/input/utils/settings-key-group-mapper');
9
8
  const keyTypeMapper = require('../../../../api/endpoints/utils/serializers/input/utils/settings-key-type-mapper');
10
9
  const {WRITABLE_KEYS_ALLOWLIST} = require('../../../../../shared/labs');
10
+ const {sequence} = require('@tryghost/promise');
11
11
 
12
12
  const labsDefaults = JSON.parse(defaultSettings.labs.labs.defaultValue);
13
13
  const ignoredSettings = ['slack_url', 'members_from_address', 'members_support_address', 'portal_products'];
@@ -252,22 +252,22 @@ class SettingsImporter extends BaseImporter {
252
252
  return Promise.resolve();
253
253
  }
254
254
 
255
- doImport(options) {
255
+ async doImport(options) {
256
256
  debug('doImport', this.dataToImport.length);
257
257
 
258
258
  let ops = [];
259
259
 
260
260
  _.each(this.dataToImport, (model) => {
261
- ops.push(
262
- models.Settings.edit(model, options)
263
- .catch((err) => {
264
- return this.handleError(err, model);
265
- })
266
- .reflect()
267
- );
261
+ ops.push(async () => {
262
+ try {
263
+ await models.Settings.edit(model, options);
264
+ } catch (err) {
265
+ this.handleError(err, model);
266
+ }
267
+ });
268
268
  });
269
269
 
270
- return Promise.all(ops);
270
+ await sequence(ops);
271
271
  }
272
272
  }
273
273
 
@@ -1,8 +1,8 @@
1
1
  const debug = require('@tryghost/debug')('importer:tags');
2
- const Promise = require('bluebird');
3
2
  const _ = require('lodash');
4
3
  const BaseImporter = require('./base');
5
4
  const models = require('../../../../models');
5
+ const {sequence} = require('@tryghost/promise');
6
6
 
7
7
  class TagsImporter extends BaseImporter {
8
8
  constructor(allDataFromFile) {
@@ -33,45 +33,44 @@ class TagsImporter extends BaseImporter {
33
33
  *
34
34
  * @TODO: Add a flag to the base implementation e.g. `fetchBeforeAdd`
35
35
  */
36
- doImport(options, importOptions) {
36
+ async doImport(options, importOptions) {
37
37
  debug('doImport', this.modelName, this.dataToImport.length);
38
38
 
39
39
  let ops = [];
40
40
 
41
41
  _.each(this.dataToImport, (obj) => {
42
- ops.push(models[this.modelName].findOne({name: obj.name}, options)
43
- .then((tag) => {
42
+ ops.push(async () => {
43
+ if (obj.slug) {
44
+ const tag = await models[this.modelName].findOne({slug: obj.slug}, options);
44
45
  if (tag) {
45
- return Promise.resolve();
46
+ return;
46
47
  }
48
+ }
47
49
 
48
- return models[this.modelName].add(obj, options)
49
- .then((importedModel) => {
50
- obj.model = {
51
- id: importedModel.id
52
- };
50
+ try {
51
+ const importedModel = await models[this.modelName].add(obj, options);
52
+ obj.model = {
53
+ id: importedModel.id
54
+ };
53
55
 
54
- if (importOptions.returnImportedData) {
55
- this.importedDataToReturn.push(importedModel.toJSON());
56
- }
57
-
58
- // for identifier lookup
59
- this.importedData.push({
60
- id: importedModel.id,
61
- originalId: this.originalIdMap[importedModel.id],
62
- slug: importedModel.get('slug'),
63
- originalSlug: obj.slug
64
- });
56
+ if (importOptions.returnImportedData) {
57
+ this.importedDataToReturn.push(importedModel.toJSON());
58
+ }
65
59
 
66
- return importedModel;
67
- })
68
- .catch((err) => {
69
- return this.handleError(err, obj);
70
- });
71
- }).reflect());
60
+ // for identifier lookup
61
+ this.importedData.push({
62
+ id: importedModel.id,
63
+ originalId: this.originalIdMap[importedModel.id],
64
+ slug: importedModel.get('slug'),
65
+ originalSlug: obj.slug
66
+ });
67
+ } catch (err) {
68
+ this.handleError(err, obj);
69
+ }
70
+ });
72
71
  });
73
72
 
74
- return Promise.all(ops);
73
+ await sequence(ops);
75
74
  }
76
75
  }
77
76
 
@@ -215,7 +215,7 @@ class FixtureManager {
215
215
  // would change the fixturesHash.
216
216
  modelFixture = _.cloneDeep(modelFixture);
217
217
  // The Post model fixtures need a `published_at` date, where at least the seconds
218
- // are different, otherwise `prev_post` and `next_post` helpers won't workd with
218
+ // are different, otherwise `prev_post` and `next_post` helpers won't work with
219
219
  // them.
220
220
  if (modelFixture.name === 'Post') {
221
221
  _.forEach(modelFixture.entries, (post, index) => {
@@ -58,20 +58,34 @@ const getTemplate = (accentColor) => {
58
58
  function getButtonHtml(href, buttonText, accentColor, className, iconUrl) {
59
59
  const bgColor = getButtonLightTheme(accentColor).backgroundColor;
60
60
  const textColor = getButtonLightTheme(accentColor).color;
61
+ const buttonAttr = {
62
+ width: 100,
63
+ height: 38,
64
+ iconWidth: 24
65
+ };
66
+
67
+ // Sizes defined in pixels won’t be adjusted when Outlook is rendering at 120 dpi.
68
+ // To solve the problem we use values in points (1 pixel = 0.75 point).
69
+ // resource: https://www.hteumeuleu.com/2021/background-properties-in-vml/
70
+ const buttonAttrOutlook = {
71
+ width: (buttonAttr.width + buttonAttr.iconWidth) * 0.75,
72
+ height: buttonAttr.height * 0.75 + 1,
73
+ iconWidth: buttonAttr.iconWidth * 0.75
74
+ };
61
75
 
62
76
  return (`
63
77
  <td dir="ltr" valign="top" align="center" style="vertical-align: top; color: ${textColor}; font-family: inherit; font-size: 14px; text-align: center; padding: 0 8px;" nowrap>
64
78
  <table class="feedback-buttons" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="background-color: ${bgColor}; overflow: hidden; border-radius: 22px;border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
65
79
  <tr>
66
- <td width="16" height="38" style="paddig-left:10px;"></td>
67
- <td class=${className} background=${iconUrl} bgcolor="${textColor}" width="24" height="38" valign="top" style="background-image: url(${iconUrl});vertical-align: middle; text-align: center;background-size: cover; background-position: 0 50%; background-repeat:no-repeat;">
80
+ <td width="16" height="${buttonAttr.height}"></td>
81
+ <td class=${className} background=${iconUrl} bgcolor="${textColor}" width="${buttonAttr.iconWidth}" height="${buttonAttr.height}" valign="top" style="background-image: url(${iconUrl});vertical-align: middle; text-align: center;background-size: cover; background-position: 0 50%; background-repeat:no-repeat;">
68
82
  <!--[if gte mso 9]>
69
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:24px;height:38px;">
83
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:${buttonAttrOutlook.iconWidth}pt;height:${buttonAttrOutlook.height}pt;">
70
84
  <v:fill origin="0.5, 0.5" position="0.5, 0.5" type="tile" src=${iconUrl} color="${textColor}" size="1,1" aspect="atleast" />
71
85
  <v:textbox inset="0,0,0,0">
72
86
  <![endif]-->
73
87
  <div>
74
- <a style="background-color: ${bgColor};border: none; width: 24px; height: 38px; display: block" href=${href} target="_blank"></a>
88
+ <a style="background-color: ${bgColor};border: none; width: ${buttonAttr.iconWidth}px; height: ${buttonAttr.height}px; display: block" href=${href} target="_blank"></a>
75
89
  </div>
76
90
  <!--[if gte mso 9]>
77
91
  </v:textbox>
@@ -80,7 +94,7 @@ function getButtonHtml(href, buttonText, accentColor, className, iconUrl) {
80
94
  </td>
81
95
  <td style="text-align: right;font-size: 18px; vertical-align: middle; color: ${textColor}!important; background-position: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';">
82
96
  <div style="color: ${textColor}"><!--[if mso]>
83
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href=${href} style="height:38px;v-text-anchor:middle;width:120px;" stroke="f">
97
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href=${href} style="height:${buttonAttrOutlook.height}pt;v-text-anchor:middle;width:${buttonAttrOutlook.width}pt;" stroke="f">
84
98
  <w:anchorlock/>
85
99
  <center>
86
100
  <![endif]-->
@@ -47,7 +47,10 @@ const PostEmailSerializer = {
47
47
 
48
48
  // Fix any unsupported chars in Outlook
49
49
  juicedHtml = juicedHtml.replace(/&apos;/g, '&#39;');
50
-
50
+ juicedHtml = juicedHtml.replace(/→/g, '&rarr;');
51
+ juicedHtml = juicedHtml.replace(/–/g, '&ndash;');
52
+ juicedHtml = juicedHtml.replace(/“/g, '&ldquo;');
53
+ juicedHtml = juicedHtml.replace(/”/g, '&rdquo;');
51
54
  return juicedHtml;
52
55
  },
53
56
 
@@ -392,12 +395,17 @@ const PostEmailSerializer = {
392
395
  if (!options.isBrowserPreview && !options.isTestEmail && settingsCache.get('email_track_clicks')) {
393
396
  result.html = await linkReplacer.replace(result.html, async (url) => {
394
397
  // Add newsletter source attribution
395
- url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter);
396
398
  const isSite = urlUtils.isSiteUrl(url);
397
399
 
398
400
  if (isSite) {
401
+ // Add newsletter name as ref to the URL
402
+ url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter);
403
+
399
404
  // Only add post attribution to our own site (because external sites could/should not process this information)
400
405
  url = memberAttribution.service.addPostAttributionTracking(url, post);
406
+ } else {
407
+ // Add email source attribution without the newsletter name
408
+ url = memberAttribution.service.addEmailSourceAttributionTracking(url);
401
409
  }
402
410
 
403
411
  // Add link click tracking
@@ -40,7 +40,8 @@ class MemberAttributionServiceWrapper {
40
40
  Integration: models.Integration
41
41
  },
42
42
  attributionBuilder: this.attributionBuilder,
43
- getTrackingEnabled: () => !!settingsCache.get('members_track_sources')
43
+ getTrackingEnabled: () => !!settingsCache.get('members_track_sources'),
44
+ getSiteTitle: () => settingsCache.get('title')
44
45
  });
45
46
  }
46
47
  }
@@ -34,7 +34,7 @@ module.exports = function setupMembersApp() {
34
34
  membersApp.post('/webhooks/stripe', bodyParser.raw({type: 'application/json'}), stripeService.webhookController.handle.bind(stripeService.webhookController));
35
35
 
36
36
  // Initializes members specific routes as well as assigns members specific data to the req/res objects
37
- // We don't want to add global bodyParser middleware as that interfers with stripe webhook requests on - `/webhooks`.
37
+ // We don't want to add global bodyParser middleware as that interferes with stripe webhook requests on - `/webhooks`.
38
38
 
39
39
  // Manage newsletter subscription via unsubscribe link
40
40
  membersApp.get('/api/member/newsletters', middleware.getMemberNewsletters);