ghost 4.21.0 → 4.22.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 (73) hide show
  1. package/Gruntfile.js +1 -0
  2. package/content/themes/casper/assets/built/screen.css +1 -1
  3. package/content/themes/casper/assets/built/screen.css.map +1 -1
  4. package/content/themes/casper/assets/css/screen.css +263 -50
  5. package/content/themes/casper/default.hbs +12 -3
  6. package/content/themes/casper/index.hbs +25 -23
  7. package/content/themes/casper/package.json +91 -2
  8. package/content/themes/casper/partials/post-card.hbs +1 -1
  9. package/content/themes/casper/post.hbs +18 -14
  10. package/content/themes/casper/yarn.lock +245 -192
  11. package/core/boot.js +5 -0
  12. package/core/bridge.js +14 -0
  13. package/core/built/assets/{chunk.3.065ee3c3bdf674bd81a4.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
  14. package/core/built/assets/{ghost-dark-1328db4a7dd128305646305a8731bcfe.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
  15. package/core/built/assets/{ghost.min-5abc69c04ad1d5301a857e01009b9c05.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
  16. package/core/built/assets/{ghost.min-6c546c322127ae6d1d1b0ddbf34be75b.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1742 -1891
  17. package/core/built/assets/{vendor.min-c6ef90bfd7eff256e10b85583bfe9a74.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +601 -571
  18. package/core/frontend/helpers/asset.js +9 -1
  19. package/core/frontend/helpers/ghost_head.js +13 -1
  20. package/core/frontend/services/card-assets/index.js +16 -0
  21. package/core/frontend/services/card-assets/service.js +101 -0
  22. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  23. package/core/frontend/services/theme-engine/config/index.js +1 -1
  24. package/core/frontend/src/cards/css/bookmark.css +83 -0
  25. package/core/frontend/src/cards/css/gallery.css +36 -0
  26. package/core/frontend/src/cards/js/gallery.js +8 -0
  27. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  28. package/core/frontend/web/site.js +10 -9
  29. package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
  30. package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
  31. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
  32. package/core/server/adapters/storage/index.js +1 -1
  33. package/core/server/adapters/storage/utils.js +2 -2
  34. package/core/server/api/canary/index.js +4 -0
  35. package/core/server/api/canary/media.js +22 -0
  36. package/core/server/api/canary/redirects.js +1 -6
  37. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  38. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  39. package/core/server/api/canary/utils/serializers/output/media.js +28 -0
  40. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  41. package/core/server/api/canary/utils/validators/input/media.js +7 -0
  42. package/core/server/api/v2/redirects.js +1 -6
  43. package/core/server/api/v3/members.js +5 -1
  44. package/core/server/api/v3/redirects.js +1 -6
  45. package/core/server/data/migrations/utils.js +55 -16
  46. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  47. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  48. package/core/server/data/schema/default-settings.json +8 -0
  49. package/core/server/frontend/ghost.min.css +1 -1
  50. package/core/server/lib/image/blog-icon.js +2 -4
  51. package/core/server/lib/image/image-size.js +1 -1
  52. package/core/server/services/limits.js +3 -6
  53. package/core/server/services/mega/template.js +4 -0
  54. package/core/server/services/offers/service.js +1 -31
  55. package/core/server/services/redirects/api.js +270 -0
  56. package/core/server/services/redirects/index.js +27 -12
  57. package/core/server/services/themes/ThemeStorage.js +5 -5
  58. package/core/server/web/admin/views/default-prod.html +4 -4
  59. package/core/server/web/admin/views/default.html +4 -4
  60. package/core/server/web/api/canary/admin/routes.js +13 -4
  61. package/core/server/web/api/middleware/upload.js +117 -10
  62. package/core/server/web/members/app.js +1 -1
  63. package/core/server/web/shared/middlewares/index.js +0 -4
  64. package/core/shared/config/defaults.json +3 -1
  65. package/core/shared/config/helpers.js +2 -0
  66. package/core/shared/config/overrides.json +8 -0
  67. package/core/shared/labs.js +5 -3
  68. package/package.json +14 -13
  69. package/yarn.lock +875 -851
  70. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  71. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  72. package/core/server/services/redirects/settings.js +0 -234
  73. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -105,6 +105,10 @@ module.exports = {
105
105
  return shared.pipeline(require('./images'), localUtils);
106
106
  },
107
107
 
108
+ get media() {
109
+ return shared.pipeline(require('./media'), localUtils);
110
+ },
111
+
108
112
  get tags() {
109
113
  return shared.pipeline(require('./tags'), localUtils);
110
114
  },
@@ -0,0 +1,22 @@
1
+ const storage = require('../../adapters/storage');
2
+
3
+ module.exports = {
4
+ docName: 'media',
5
+ upload: {
6
+ statusCode: 201,
7
+ permissions: false,
8
+ async query(frame) {
9
+ let thumbnail = null;
10
+ if (frame.files.thumbnail && frame.files.thumbnail[0]) {
11
+ thumbnail = await storage.getStorage('media').save(frame.files.thumbnail[0]);
12
+ }
13
+
14
+ const file = await storage.getStorage('media').save(frame.files.file[0]);
15
+
16
+ return {
17
+ filePath: file,
18
+ thumbnailPath: thumbnail
19
+ };
20
+ }
21
+ }
22
+ };
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };
@@ -102,6 +102,13 @@ const forceStatusFilter = (frame) => {
102
102
  }
103
103
  };
104
104
 
105
+ const transformPageVisibilityFilters = (frame) => {
106
+ if (frame.data.pages[0].visibility === 'filter' && frame.data.pages[0].visibility_filter) {
107
+ frame.data.pages[0].visibility = frame.data.pages[0].visibility_filter;
108
+ }
109
+ delete frame.data.pages[0].visibility_filter;
110
+ };
111
+
105
112
  module.exports = {
106
113
  browse(apiConfig, frame) {
107
114
  debug('browse');
@@ -180,6 +187,7 @@ module.exports = {
180
187
  });
181
188
  }
182
189
 
190
+ transformPageVisibilityFilters(frame);
183
191
  handlePostsMeta(frame);
184
192
  defaultFormat(frame);
185
193
  defaultRelations(frame);
@@ -85,6 +85,10 @@ module.exports = {
85
85
  return require('./images');
86
86
  },
87
87
 
88
+ get media() {
89
+ return require('./media');
90
+ },
91
+
88
92
  get tags() {
89
93
  return require('./tags');
90
94
  },
@@ -0,0 +1,28 @@
1
+ const config = require('../../../../../../shared/config');
2
+ const {STATIC_MEDIA_URL_PREFIX} = require('@tryghost/constants');
3
+
4
+ function getURL(urlPath) {
5
+ const media = new RegExp('^' + config.getSubdir() + '/' + STATIC_MEDIA_URL_PREFIX);
6
+ const absolute = media.test(urlPath) ? true : false;
7
+
8
+ if (absolute) {
9
+ // Remove the sub-directory from the URL because ghostConfig will add it back.
10
+ urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
11
+ const baseUrl = config.getSiteUrl().replace(/\/$/, '');
12
+ urlPath = baseUrl + urlPath;
13
+ }
14
+
15
+ return urlPath;
16
+ }
17
+
18
+ module.exports = {
19
+ upload({filePath, thumbnailPath}, apiConfig, frame) {
20
+ return frame.response = {
21
+ media: [{
22
+ url: getURL(filePath),
23
+ thumbnail_url: getURL(thumbnailPath),
24
+ ref: frame.data.ref || null
25
+ }]
26
+ };
27
+ }
28
+ };
@@ -27,6 +27,10 @@ module.exports = {
27
27
  return require('./members');
28
28
  },
29
29
 
30
+ get media() {
31
+ return require('./media');
32
+ },
33
+
30
34
  get settings() {
31
35
  return require('./settings');
32
36
  },
@@ -0,0 +1,7 @@
1
+ const limitService = require('../../../../../services/limits');
2
+
3
+ module.exports = {
4
+ async upload(apiConfig, frame) {
5
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
6
+ }
7
+ };
@@ -1,4 +1,3 @@
1
- const web = require('../../web');
2
1
  const redirects = require('../../services/redirects');
3
2
 
4
3
  module.exports = {
@@ -23,11 +22,7 @@ module.exports = {
23
22
  cacheInvalidate: true
24
23
  },
25
24
  query(frame) {
26
- return redirects.api.setFromFilePath(frame.file.path)
27
- .then(() => {
28
- // CASE: trigger that redirects are getting re-registered
29
- web.shared.middlewares.customRedirects.reload();
30
- });
25
+ return redirects.api.setFromFilePath(frame.file.path);
31
26
  }
32
27
  }
33
28
  };
@@ -154,7 +154,11 @@ module.exports = {
154
154
  }
155
155
 
156
156
  if (frame.options.send_email) {
157
- await membersService.api.sendEmailWithMagicLink({email: member.get('email'), requestedType: frame.options.email_type});
157
+ await membersService.api.sendEmailWithMagicLink({
158
+ email: member.get('email'), requestedType: frame.options.email_type, options: {
159
+ forceEmailType: true
160
+ }
161
+ });
158
162
  }
159
163
 
160
164
  return member;
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };
@@ -191,14 +191,8 @@ function addPermissionToRole(config) {
191
191
  }).first();
192
192
 
193
193
  if (!permission) {
194
- throw new errors.GhostError({
195
- message: tpl(messages.permissionRoleActionError, {
196
- action: 'remove',
197
- permission: config.permission,
198
- role: config.role,
199
- resource: 'permission'
200
- })
201
- });
194
+ logging.warn(`Removing permission(${config.permission}) from role(${config.role}) - Permission not found.`);
195
+ return;
202
196
  }
203
197
 
204
198
  const role = await connection('roles').where({
@@ -206,14 +200,8 @@ function addPermissionToRole(config) {
206
200
  }).first();
207
201
 
208
202
  if (!role) {
209
- throw new errors.GhostError({
210
- message: tpl(messages.permissionRoleActionError, {
211
- action: 'remove',
212
- permission: config.permission,
213
- role: config.role,
214
- resource: 'role'
215
- })
216
- });
203
+ logging.warn(`Removing permission(${config.permission}) from role(${config.role}) - Role not found.`);
204
+ return;
217
205
  }
218
206
 
219
207
  const existingRelation = await connection('permissions_roles').where({
@@ -421,12 +409,63 @@ function createDropColumnMigration(table, column, columnDefinition) {
421
409
  );
422
410
  }
423
411
 
412
+ /**
413
+ * Creates a migration which will insert a new setting in settings table
414
+ * @param {object} settingSpec - setting key, value, group and type
415
+ *
416
+ * @returns {Object} migration object returning config/up/down properties
417
+ */
418
+ function addSetting({key, value, type, group}) {
419
+ return createTransactionalMigration(
420
+ async function up(connection) {
421
+ const settingExists = await connection('settings')
422
+ .where('key', '=', key)
423
+ .first();
424
+ if (settingExists) {
425
+ logging.warn(`Skipping adding setting: ${key} - setting already exists`);
426
+ return;
427
+ }
428
+
429
+ logging.info(`Adding setting: ${key}`);
430
+ const now = connection.raw('CURRENT_TIMESTAMP');
431
+
432
+ return connection('settings')
433
+ .insert({
434
+ id: ObjectId().toHexString(),
435
+ key,
436
+ value,
437
+ group,
438
+ type,
439
+ created_at: now,
440
+ created_by: MIGRATION_USER,
441
+ updated_at: now,
442
+ updated_by: MIGRATION_USER
443
+ });
444
+ },
445
+ async function down(connection) {
446
+ const settingExists = await connection('settings')
447
+ .where('key', '=', key)
448
+ .first();
449
+ if (!settingExists) {
450
+ logging.warn(`Skipping dropping setting: ${key} - setting does not exist`);
451
+ return;
452
+ }
453
+
454
+ logging.info(`Dropping setting: ${key}`);
455
+ return connection('settings')
456
+ .where('key', '=', key)
457
+ .del();
458
+ }
459
+ );
460
+ }
461
+
424
462
  module.exports = {
425
463
  addTable,
426
464
  dropTables,
427
465
  addPermission,
428
466
  addPermissionToRole,
429
467
  addPermissionWithRoles,
468
+ addSetting,
430
469
  createTransactionalMigration,
431
470
  createNonTransactionalMigration,
432
471
  createIrreversibleMigration,
@@ -0,0 +1,8 @@
1
+ const {addSetting} = require('../../utils.js');
2
+
3
+ module.exports = addSetting({
4
+ key: 'editor_is_launch_complete',
5
+ value: 'false',
6
+ type: 'boolean',
7
+ group: 'editor'
8
+ });
@@ -0,0 +1,39 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils.js');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ const userRows = await knex('users').select('accessibility').whereNotNull('accessibility');
7
+ const hasLaunchComplete = userRows.find((user) => {
8
+ try {
9
+ const userAccessibility = JSON.parse(user.accessibility);
10
+ return userAccessibility.launchComplete;
11
+ } catch (e) {
12
+ return false;
13
+ }
14
+ });
15
+ if (hasLaunchComplete) {
16
+ logging.info('Updating setting: "editor_is_launch_complete" to "true"');
17
+ await knex('settings')
18
+ .where({
19
+ key: 'editor_is_launch_complete'
20
+ })
21
+ .update({
22
+ value: 'true'
23
+ });
24
+ } else {
25
+ logging.warn('Skipped setting update: "editor_is_launch_complete" setting - is already correct value!');
26
+ }
27
+ },
28
+
29
+ async function down(knex) {
30
+ logging.info('Reverting setting: "editor_is_launch_complete" - to "false"');
31
+ await knex('settings')
32
+ .where({
33
+ key: 'editor_is_launch_complete'
34
+ })
35
+ .update({
36
+ value: 'false'
37
+ });
38
+ }
39
+ );
@@ -590,6 +590,14 @@
590
590
  "editor_default_email_recipients_filter": {
591
591
  "defaultValue": "all",
592
592
  "type": "string"
593
+ },
594
+ "editor_is_launch_complete": {
595
+ "defaultValue": "false",
596
+ "validations": {
597
+ "isEmpty": false,
598
+ "isIn": [["true", "false"]]
599
+ },
600
+ "type": "boolean"
593
601
  }
594
602
  }
595
603
  }
@@ -1 +1 @@
1
- /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.darkgrey{color:#343f44}.midgrey{color:#738a94}.lightgrey{color:#e5eff5}.blue{color:#3eb0ef}.red{color:#f05230}.orange{color:#fecd35}.green{color:#a4d037}.darkgrey-hover:hover{color:#343f44}.midgrey-hover:hover{color:#738a94}.lightgrey-hover:hover{color:#e5eff5}.blue-hover:hover{color:#3eb0ef}.red-hover:hover{color:#f05230}.orange-hover:hover{color:#fecd35}.green-hover:hover{color:#a4d037}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;text-shadow:0 1px 0 #fff;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{-ms-flex-positive:1;-ms-flex-direction:column;flex-direction:column;flex-grow:1;min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-head{display:-ms-flexbox;display:flex}.gh-flow-head{-ms-flex-negative:0;-ms-flex-pack:justify;flex-shrink:0;justify-content:space-between;padding-bottom:20px;padding-top:4vh}.gh-flow-content-wrap{-ms-flex-positive:1;-ms-flex-negative:0;-ms-flex-pack:center;flex-grow:1;flex-shrink:0;justify-content:center;margin:0 5%;padding-bottom:8vh}.gh-flow-back,.gh-flow-content-wrap{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.gh-flow-back{border:1px solid transparent;border-radius:4px;color:#7d878a;font-weight:100;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;top:0;transition:all .3s ease}.gh-flow-back svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back:hover{border:1px solid #dae1e3}.gh-flow-back-plain{-ms-flex-align:center;align-items:center;color:#7d878a;display:-ms-flexbox;display:flex;font-weight:300;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;text-decoration:none;top:0;transition:all .3s ease}.gh-flow-back-plain svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back-plain svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back-plain:hover{color:#15212a}.gh-flow-nav{-ms-flex:1;flex:1;position:relative}.gh-flow-content{color:#738a94;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;font-size:1.9rem;font-weight:100;line-height:1.5em;max-width:700px;text-align:center;width:100%}.gh-flow-content .gh-input-icon input{padding-left:35px}.gh-flow-content-unsubscribe{font-weight:300}@media (max-width:500px){.gh-flow-head-unsubscribe{padding-top:2.8vh}.gh-flow-content{font-size:4vw}.gh-flow-content-unsubscribe{font-size:1.8rem;line-height:1.6em}}.gh-flow-content header{margin:0 auto;max-width:520px}.gh-flow-content h1{font-size:4.2rem;font-weight:100}@media (max-width:600px){.gh-flow-content h1{font-size:7vw}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem}.gh-flow-content input{border:1px solid #dae1e3;font-size:1.6rem;font-weight:100;line-height:1.4em;padding:10px}.gh-flow-em{font-weight:500}.gh-signin{background:#f8fbfd;border:1px solid #dae1e3;border-radius:5px;margin:30px auto;max-width:400px;padding:40px;position:relative;text-align:left;width:100%}.gh-signin .form-group{margin-bottom:1.5rem}.gh-signin .gh-btn{margin:0}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
1
+ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.darkgrey{color:#343f44}.midgrey{color:#738a94}.lightgrey{color:#e5eff5}.blue{color:#3eb0ef}.red{color:#f05230}.orange{color:#fecd35}.green{color:#a4d037}.darkgrey-hover:hover{color:#343f44}.midgrey-hover:hover{color:#738a94}.lightgrey-hover:hover{color:#e5eff5}.blue-hover:hover{color:#3eb0ef}.red-hover:hover{color:#f05230}.orange-hover:hover{color:#fecd35}.green-hover:hover{color:#a4d037}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;text-shadow:0 1px 0 #fff;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{-ms-flex-positive:1;-ms-flex-direction:column;flex-direction:column;flex-grow:1;min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-head{display:-ms-flexbox;display:flex}.gh-flow-head{-ms-flex-negative:0;-ms-flex-pack:justify;flex-shrink:0;justify-content:space-between;padding-bottom:20px;padding-top:4vh}.gh-flow-content-wrap{-ms-flex-positive:1;-ms-flex-negative:0;-ms-flex-pack:center;flex-grow:1;flex-shrink:0;justify-content:center;margin:0 5%;padding-bottom:8vh}.gh-flow-back,.gh-flow-content-wrap{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.gh-flow-back{border:1px solid transparent;border-radius:4px;color:#7d878a;font-weight:100;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;top:0;transition:all .3s ease}.gh-flow-back svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back:hover{border:1px solid #dae1e3}.gh-flow-back-plain{-ms-flex-align:center;align-items:center;color:#7d878a;display:-ms-flexbox;display:flex;font-weight:300;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;text-decoration:none;top:0;transition:all .3s ease}.gh-flow-back-plain svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back-plain svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back-plain:hover{color:#15212a}.gh-flow-nav{-ms-flex:1;flex:1;position:relative}.gh-flow-content{color:#738a94;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;font-size:1.9rem;font-weight:100;line-height:1.5em;max-width:700px;text-align:center;width:100%}.gh-flow-content .gh-input-icon input{padding-left:35px}.gh-flow-content-unsubscribe{font-weight:300}@media (max-width:500px){.gh-flow-head-unsubscribe{padding-top:2.8vh}.gh-flow-content{font-size:4vw}.gh-flow-content-unsubscribe{font-size:1.8rem;line-height:1.6em}}.gh-flow-content header{margin:0 auto;max-width:520px}.gh-flow-content h1{font-size:4.2rem;font-weight:100}@media (max-width:600px){.gh-flow-content h1{font-size:7vw}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem}.gh-flow-content input{border:1px solid #dae1e3;font-size:1.6rem;font-weight:100;line-height:1.4em;padding:10px}.gh-flow-em{font-weight:500}.gh-signin{background:#f8fbfd;border:1px solid #dae1e3;border-radius:5px;margin:30px auto;max-width:400px;padding:40px;position:relative;text-align:left;width:100%}.gh-signin .form-group{margin-bottom:1.5rem}.gh-signin .gh-btn{margin:0}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
@@ -107,17 +107,15 @@ class BlogIcon {
107
107
  }
108
108
 
109
109
  /**
110
- * Return path for Blog icon without [subdirectory]/content/image prefix
111
- * Always returns {string} getIconPath
112
- * @returns {string} physical storage path of icon
113
110
  * @description Checks if we have a custom uploaded icon. If no custom uploaded icon
114
111
  * exists, we're returning the default `favicon.ico`
112
+ * @returns {string} physical storage path of site icon without [subdirectory]/content/image prefix
115
113
  */
116
114
  getIconPath() {
117
115
  const blogIcon = this.settingsCache.get('icon');
118
116
 
119
117
  if (blogIcon) {
120
- return this.storageUtils.getLocalFileStoragePath(blogIcon);
118
+ return this.storageUtils.getLocalImagesStoragePath(blogIcon);
121
119
  } else {
122
120
  return path.join(this.config.get('paths:publicFilePath'), 'favicon.ico');
123
121
  }
@@ -214,7 +214,7 @@ class ImageSize {
214
214
  imagePath = this.urlUtils.urlFor('image', {image: imagePath}, true);
215
215
 
216
216
  // get the storage readable filePath
217
- filePath = this.storageUtils.getLocalFileStoragePath(imagePath);
217
+ filePath = this.storageUtils.getLocalImagesStoragePath(imagePath);
218
218
 
219
219
  return this.storage.getStorage('images')
220
220
  .read({path: filePath})
@@ -4,10 +4,7 @@ const db = require('../data/db');
4
4
  const LimitService = require('@tryghost/limit-service');
5
5
  let limitService = new LimitService();
6
6
 
7
- /**
8
- * @param {Object} [limits] - An object containing limit configuration
9
- **/
10
- const initFn = (limits = {}) => {
7
+ const init = () => {
11
8
  let helpLink;
12
9
 
13
10
  if (config.get('hostSettings:billing:enabled') && config.get('hostSettings:billing:enabled') === true && config.get('hostSettings:billing:url')) {
@@ -28,7 +25,7 @@ const initFn = (limits = {}) => {
28
25
  const hostLimits = config.get('hostSettings:limits') || {};
29
26
 
30
27
  limitService.loadLimits({
31
- limits: Object.assign(hostLimits, limits),
28
+ limits: hostLimits,
32
29
  subscription,
33
30
  db,
34
31
  helpLink,
@@ -38,4 +35,4 @@ const initFn = (limits = {}) => {
38
35
 
39
36
  module.exports = limitService;
40
37
 
41
- module.exports.init = initFn;
38
+ module.exports.init = init;
@@ -74,6 +74,10 @@ table td {
74
74
  max-width: 600px;
75
75
  }
76
76
 
77
+ .content a {
78
+ overflow-wrap: anywhere;
79
+ }
80
+
77
81
  /* -------------------------------------
78
82
  POST CONTENT
79
83
  ------------------------------------- */
@@ -1,6 +1,3 @@
1
- const labs = require('../../../shared/labs');
2
- const events = require('../../lib/common/events');
3
-
4
1
  const DynamicRedirectManager = require('@tryghost/express-dynamic-redirects');
5
2
  const OffersModule = require('@tryghost/members-offers');
6
3
 
@@ -28,34 +25,7 @@ module.exports = {
28
25
 
29
26
  this.api = offersModule.api;
30
27
 
31
- let initCalled = false;
32
- if (labs.isSet('offers')) {
33
- // handles setting up redirects
34
- const promise = offersModule.init();
35
- initCalled = true;
36
- await promise;
37
- }
38
-
39
- // TODO: Delete after GA
40
- let offersEnabled = labs.isSet('offers');
41
- events.on('settings.labs.edited', async () => {
42
- if (labs.isSet('offers') && !initCalled) {
43
- const promise = offersModule.init();
44
- initCalled = true;
45
- await promise;
46
- } else if (labs.isSet('offers') !== offersEnabled) {
47
- offersEnabled = labs.isSet('offers');
48
-
49
- if (offersEnabled) {
50
- const offers = await this.api.listOffers({});
51
- for (const offer of offers) {
52
- redirectManager.addRedirect(`/${offer.code}`, `/#/portal/offers/${offer.id}`, {permanent: false});
53
- }
54
- } else {
55
- redirectManager.removeAllRedirects();
56
- }
57
- }
58
- });
28
+ await offersModule.init();
59
29
  },
60
30
 
61
31
  api: null,