ghost 4.21.0 → 4.22.3

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 (97) hide show
  1. package/.eslintrc.js +6 -0
  2. package/Gruntfile.js +2 -0
  3. package/content/themes/casper/assets/built/screen.css +1 -1
  4. package/content/themes/casper/assets/built/screen.css.map +1 -1
  5. package/content/themes/casper/assets/css/screen.css +263 -50
  6. package/content/themes/casper/default.hbs +12 -3
  7. package/content/themes/casper/index.hbs +25 -23
  8. package/content/themes/casper/package.json +91 -2
  9. package/content/themes/casper/partials/post-card.hbs +1 -1
  10. package/content/themes/casper/post.hbs +18 -14
  11. package/content/themes/casper/yarn.lock +245 -192
  12. package/core/boot.js +8 -0
  13. package/core/bridge.js +14 -0
  14. package/core/built/assets/{chunk.3.065ee3c3bdf674bd81a4.js → chunk.3.324fd0cc598c73650219.js} +59 -59
  15. package/core/built/assets/{ghost-dark-1328db4a7dd128305646305a8731bcfe.css → ghost-dark-39fb496d051565531062d7e047d1c0b1.css} +1 -1
  16. package/core/built/assets/{ghost.min-5abc69c04ad1d5301a857e01009b9c05.css → ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css} +1 -1
  17. package/core/built/assets/{ghost.min-6c546c322127ae6d1d1b0ddbf34be75b.js → ghost.min-7da921f6c6cac3fe10da1ba104575440.js} +1775 -1897
  18. package/core/built/assets/{vendor.min-c6ef90bfd7eff256e10b85583bfe9a74.js → vendor.min-413f887176a041e6dbf88214ca9a7481.js} +6849 -6688
  19. package/core/frontend/helpers/asset.js +9 -1
  20. package/core/frontend/helpers/ghost_head.js +13 -1
  21. package/core/frontend/services/card-assets/index.js +16 -0
  22. package/core/frontend/services/card-assets/service.js +109 -0
  23. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  24. package/core/frontend/services/theme-engine/config/index.js +1 -1
  25. package/core/frontend/src/cards/css/bookmark.css +83 -0
  26. package/core/frontend/src/cards/css/button.css +30 -0
  27. package/core/frontend/src/cards/css/callout.css +12 -0
  28. package/core/frontend/src/cards/css/gallery.css +36 -0
  29. package/core/frontend/src/cards/css/nft.css +85 -0
  30. package/core/frontend/src/cards/js/gallery.js +8 -0
  31. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  32. package/core/frontend/web/routes.js +0 -1
  33. package/core/frontend/web/site.js +13 -9
  34. package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
  35. package/core/server/adapters/storage/LocalImagesStorage.js +51 -0
  36. package/core/server/adapters/storage/LocalMediaStorage.js +24 -0
  37. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +64 -51
  38. package/core/server/adapters/storage/index.js +1 -1
  39. package/core/server/adapters/storage/utils.js +2 -2
  40. package/core/server/api/canary/files.js +19 -0
  41. package/core/server/api/canary/index.js +8 -0
  42. package/core/server/api/canary/media.js +42 -0
  43. package/core/server/api/canary/oembed.js +3 -0
  44. package/core/server/api/canary/redirects.js +1 -6
  45. package/core/server/api/canary/utils/serializers/input/index.js +4 -0
  46. package/core/server/api/canary/utils/serializers/input/media.js +8 -0
  47. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  48. package/core/server/api/canary/utils/serializers/output/config.js +21 -14
  49. package/core/server/api/canary/utils/serializers/output/files.js +27 -0
  50. package/core/server/api/canary/utils/serializers/output/index.js +8 -0
  51. package/core/server/api/canary/utils/serializers/output/media.js +37 -0
  52. package/core/server/api/canary/utils/validators/input/files.js +7 -0
  53. package/core/server/api/canary/utils/validators/input/index.js +8 -0
  54. package/core/server/api/canary/utils/validators/input/media.js +11 -0
  55. package/core/server/api/v2/redirects.js +1 -6
  56. package/core/server/api/v3/members.js +5 -1
  57. package/core/server/api/v3/redirects.js +1 -6
  58. package/core/server/data/migrations/utils.js +55 -16
  59. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  60. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  61. package/core/server/data/schema/default-settings.json +8 -0
  62. package/core/server/frontend/ghost.min.css +1 -1
  63. package/core/server/lib/image/blog-icon.js +2 -4
  64. package/core/server/lib/image/image-size.js +1 -1
  65. package/core/server/services/limits.js +3 -6
  66. package/core/server/services/mega/template.js +62 -1
  67. package/core/server/services/nft-oembed.js +71 -0
  68. package/core/server/services/oembed.js +145 -110
  69. package/core/server/services/offers/service.js +1 -31
  70. package/core/server/services/public-config/config.js +2 -1
  71. package/core/server/services/redirects/api.js +270 -0
  72. package/core/server/services/redirects/index.js +27 -12
  73. package/core/server/services/stripe/index.js +4 -2
  74. package/core/server/services/themes/ThemeStorage.js +5 -5
  75. package/core/server/services/url/Resource.js +1 -1
  76. package/core/server/services/url/Resources.js +28 -21
  77. package/core/server/services/url/UrlService.js +66 -8
  78. package/core/server/services/url/Urls.js +7 -2
  79. package/core/server/services/url/index.js +8 -1
  80. package/core/server/web/admin/views/default-prod.html +4 -4
  81. package/core/server/web/admin/views/default.html +4 -4
  82. package/core/server/web/api/canary/admin/routes.js +28 -4
  83. package/core/server/web/api/middleware/cors.js +7 -7
  84. package/core/server/web/api/middleware/upload.js +117 -10
  85. package/core/server/web/members/app.js +1 -1
  86. package/core/server/web/shared/middlewares/index.js +0 -4
  87. package/core/shared/config/defaults.json +5 -1
  88. package/core/shared/config/helpers.js +4 -0
  89. package/core/shared/config/overrides.json +8 -0
  90. package/core/shared/labs.js +12 -3
  91. package/package.json +28 -27
  92. package/urls.json +597 -0
  93. package/yarn.lock +972 -941
  94. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  95. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  96. package/core/server/services/redirects/settings.js +0 -234
  97. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -2,7 +2,7 @@
2
2
  // Usage: `{{asset "css/screen.css"}}`
3
3
  //
4
4
  // Returns the path to the specified asset.
5
- const {metaData} = require('../services/proxy');
5
+ const {metaData, urlUtils} = require('../services/proxy');
6
6
  const {SafeString} = require('../services/rendering');
7
7
 
8
8
  const errors = require('@tryghost/errors');
@@ -22,6 +22,14 @@ module.exports = function asset(path, options) {
22
22
  message: tpl(messages.pathIsRequired)
23
23
  });
24
24
  }
25
+ if (typeof urlUtils.getSiteUrl() !== 'undefined'
26
+ && typeof urlUtils.getAdminUrl() !== 'undefined'
27
+ && urlUtils.getSiteUrl() !== urlUtils.getAdminUrl()) {
28
+ const target = new URL(getAssetUrl(path, hasMinFile), urlUtils.getSiteUrl());
29
+ return new SafeString(
30
+ target.href
31
+ );
32
+ }
25
33
 
26
34
  return new SafeString(
27
35
  getAssetUrl(path, hasMinFile)
@@ -5,12 +5,16 @@
5
5
  const {metaData, settingsCache, config, blogIcon, urlUtils, labs} = require('../services/proxy');
6
6
  const {escapeExpression, SafeString} = require('../services/rendering');
7
7
 
8
+ // BAD REQUIRE
9
+ // @TODO fix this require
10
+ const cardAssetService = require('../services/card-assets');
11
+
8
12
  const logging = require('@tryghost/logging');
9
13
  const _ = require('lodash');
10
14
  const debug = require('@tryghost/debug')('ghost_head');
11
15
  const templateStyles = require('./tpl/styles');
12
16
 
13
- const getMetaData = metaData.get;
17
+ const {get: getMetaData, getAssetUrl} = metaData;
14
18
 
15
19
  function writeMetaTag(property, content, type) {
16
20
  type = type || property.substring(0, 7) === 'twitter' ? 'name' : 'property';
@@ -193,6 +197,14 @@ module.exports = function ghost_head(options) { // eslint-disable-line camelcase
193
197
  if (!_.includes(context, 'amp')) {
194
198
  head.push(getMembersHelper(options.data));
195
199
 
200
+ // @TODO do this in a more "frameworky" way
201
+ if (cardAssetService.hasFile('js')) {
202
+ head.push(`<script async src="${getAssetUrl('public/cards.min.js')}"></script>`);
203
+ }
204
+ if (cardAssetService.hasFile('css')) {
205
+ head.push(`<link rel="stylesheet" type="text/css" href="${getAssetUrl('public/cards.min.css')}">`);
206
+ }
207
+
196
208
  if (!_.isEmpty(globalCodeinjection)) {
197
209
  head.push(globalCodeinjection);
198
210
  }
@@ -0,0 +1,16 @@
1
+ const debug = require('@tryghost/debug')('card-assets');
2
+ const themeEngine = require('../theme-engine');
3
+
4
+ const CardAssetService = require('./service');
5
+ let cardAssetService = new CardAssetService();
6
+
7
+ const initFn = async () => {
8
+ const cardAssetConfig = themeEngine.getActive().config('card_assets');
9
+ debug('initialising with config', cardAssetConfig);
10
+
11
+ await cardAssetService.load(cardAssetConfig);
12
+ };
13
+
14
+ module.exports = cardAssetService;
15
+
16
+ module.exports.init = initFn;
@@ -0,0 +1,109 @@
1
+ const Minifier = require('@tryghost/minifier');
2
+ const _ = require('lodash');
3
+ const path = require('path');
4
+ const fs = require('fs').promises;
5
+ const logging = require('@tryghost/logging');
6
+
7
+ class CardAssetService {
8
+ constructor(options = {}) {
9
+ // @TODO: use our config paths concept
10
+ this.src = options.src || path.join(__dirname, '../../src/cards');
11
+ this.dest = options.dest || path.join(__dirname, '../../public');
12
+ this.minifier = new Minifier({src: this.src, dest: this.dest});
13
+
14
+ if ('config' in options) {
15
+ this.config = options.config;
16
+ }
17
+
18
+ this.files = [];
19
+ }
20
+
21
+ generateGlobs() {
22
+ // CASE: The theme has asked for all card assets to be included by default
23
+ if (this.config === true) {
24
+ return {
25
+ 'cards.min.css': 'css/*.css',
26
+ 'cards.min.js': 'js/*.js'
27
+ };
28
+ }
29
+
30
+ // CASE: the theme has declared an include directive, we should include exactly these assets
31
+ // Include rules take precedence over exclude rules.
32
+ if (_.has(this.config, 'include')) {
33
+ return {
34
+ 'cards.min.css': `css/(${this.config.include.join('|')}).css`,
35
+ 'cards.min.js': `js/(${this.config.include.join('|')}).js`
36
+ };
37
+ }
38
+
39
+ // CASE: the theme has declared an exclude directive, we should include exactly these assets
40
+ if (_.has(this.config, 'exclude')) {
41
+ return {
42
+ 'cards.min.css': `css/!(${this.config.exclude.join('|')}).css`,
43
+ 'cards.min.js': `js/!(${this.config.exclude.join('|')}).js`
44
+ };
45
+ }
46
+
47
+ // CASE: theme has asked that no assets be included
48
+ // CASE: we didn't understand config, don't do anything
49
+ return {};
50
+ }
51
+
52
+ async minify(globs) {
53
+ try {
54
+ return await this.minifier.minify(globs);
55
+ } catch (err) {
56
+ // @TODO: Convert this back to a proper error once the underlying bug is fixed
57
+ if (err.code === 'EACCES') {
58
+ logging.warn('Ghost was not able to write card asset files due to permissions.');
59
+ }
60
+ }
61
+ }
62
+
63
+ async clearFiles() {
64
+ this.files = [];
65
+
66
+ // @deprecated switch this to use fs.rm when we drop support for Node v12
67
+ try {
68
+ await fs.unlink(path.join(this.dest, 'cards.min.css'));
69
+ } catch (error) {
70
+ // Don't worry if the file didn't exist or we don't have perms here
71
+ if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ try {
77
+ await fs.unlink(path.join(this.dest, 'cards.min.js'));
78
+ } catch (error) {
79
+ // Don't worry if the file didn't exist or we don't have perms here
80
+ if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
81
+ throw error;
82
+ }
83
+ }
84
+ }
85
+
86
+ hasFile(type) {
87
+ return this.files.indexOf(`cards.min.${type}`) > -1;
88
+ }
89
+
90
+ /**
91
+ * A theme can declare which cards it supports, and we'll do the rest
92
+ *
93
+ * @param {Array|boolean} config
94
+ * @returns
95
+ */
96
+ async load(config) {
97
+ if (config) {
98
+ this.config = config;
99
+ }
100
+
101
+ await this.clearFiles();
102
+
103
+ const globs = this.generateGlobs();
104
+
105
+ this.files = await this.minify(globs) || [];
106
+ }
107
+ }
108
+
109
+ module.exports = CardAssetService;
@@ -1,3 +1,6 @@
1
1
  {
2
- "posts_per_page": 5
2
+ "posts_per_page": 5,
3
+ "card_assets": {
4
+ "exclude": ["bookmark", "gallery"]
5
+ }
3
6
  }
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const defaultConfig = require('./defaults');
3
- const allowedKeys = ['posts_per_page', 'image_sizes'];
3
+ const allowedKeys = ['posts_per_page', 'image_sizes', 'card_assets'];
4
4
 
5
5
  module.exports.create = function configLoader(packageJson) {
6
6
  let config = _.cloneDeep(defaultConfig);
@@ -0,0 +1,83 @@
1
+ /* style.css */
2
+
3
+ .kg-bookmark-card {
4
+ width: 100%;
5
+ position: relative;
6
+ }
7
+
8
+ .kg-bookmark-container {
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ flex-direction: row-reverse;
12
+ color: currentColor;
13
+ font-family: inherit;
14
+ text-decoration: none;
15
+ border: 1px solid rgba(0, 0, 0, 0.1);
16
+ }
17
+
18
+ .kg-bookmark-container:hover {
19
+ text-decoration: none;
20
+ }
21
+
22
+ .kg-bookmark-content {
23
+ flex-basis: 0;
24
+ flex-grow: 999;
25
+ padding: 20px;
26
+ order: 1;
27
+ }
28
+
29
+ .kg-bookmark-title {
30
+ font-weight: 600;
31
+ }
32
+
33
+ .kg-bookmark-metadata,
34
+ .kg-bookmark-description {
35
+ margin-top: .5em;
36
+ }
37
+
38
+ .kg-bookmark-metadata {
39
+ align-items: center;
40
+ white-space: nowrap;
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ }
44
+
45
+ .kg-bookmark-description {
46
+ display: -webkit-box;
47
+ -webkit-box-orient: vertical;
48
+ -webkit-line-clamp: 2;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .kg-bookmark-icon {
53
+ display: inline-block;
54
+ width: 1em;
55
+ height: 1em;
56
+ vertical-align: text-bottom;
57
+ margin-right: .5em;
58
+ margin-bottom: .05em;
59
+ }
60
+
61
+ .kg-bookmark-thumbnail {
62
+ display: flex;
63
+ flex-basis: 24rem;
64
+ flex-grow: 1;
65
+ }
66
+
67
+ .kg-bookmark-thumbnail img {
68
+ max-width: 100%;
69
+ height: auto;
70
+ vertical-align: bottom;
71
+ object-fit: cover;
72
+ }
73
+
74
+ .kg-bookmark-author {
75
+ white-space: nowrap;
76
+ text-overflow: ellipsis;
77
+ overflow: hidden;
78
+ }
79
+
80
+ .kg-bookmark-publisher::before {
81
+ content: "•";
82
+ margin: 0 .5em;
83
+ }
@@ -0,0 +1,30 @@
1
+ .kg-button-card {
2
+ display: flex;
3
+ position: static;
4
+ align-items: center;
5
+ width: 100%;
6
+ justify-content: center;
7
+ }
8
+
9
+ a.kg-btn {
10
+ display: flex;
11
+ position: static;
12
+ align-items: center;
13
+ padding: 0 2.0rem;
14
+ height: 4.0rem;
15
+ line-height: 4.0rem;
16
+ font-size: 1.65rem;
17
+ font-weight: 600;
18
+ text-decoration: none;
19
+ border-radius: 5px;
20
+ transition: opacity 0.2s ease-in-out;
21
+ }
22
+
23
+ a.kg-btn:hover {
24
+ opacity: 0.85;
25
+ }
26
+
27
+ a.kg-btn-accent {
28
+ background-color: var(--ghost-accent-color);
29
+ color: #fff;
30
+ }
@@ -0,0 +1,12 @@
1
+ .kg-callout-card {
2
+ display: flex;
3
+ padding: 16px 24px;
4
+ background: #f1f3f4;
5
+ border-radius: 3px;
6
+ }
7
+
8
+ .kg-callout-emoji {
9
+ padding-right: 12px;
10
+ line-height: 1.6;
11
+ font-size: 2rem;
12
+ }
@@ -0,0 +1,36 @@
1
+ .kg-gallery-card {
2
+ margin: 0 0 1.5em;
3
+ }
4
+
5
+ .kg-gallery-card figcaption {
6
+ margin: -1.0em 0 1.5em;
7
+ }
8
+
9
+ .kg-gallery-container {
10
+ display: flex;
11
+ flex-direction: column;
12
+ margin: 1.5em auto;
13
+ max-width: 1040px;
14
+ width: 100vw;
15
+ }
16
+
17
+ .kg-gallery-row {
18
+ display: flex;
19
+ flex-direction: row;
20
+ justify-content: center;
21
+ }
22
+
23
+ .kg-gallery-image img {
24
+ display: block;
25
+ margin: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ }
29
+
30
+ .kg-gallery-row:not(:first-of-type) {
31
+ margin: 0.75em 0 0 0;
32
+ }
33
+
34
+ .kg-gallery-image:not(:first-of-type) {
35
+ margin: 0 0 0 0.75em;
36
+ }
@@ -0,0 +1,85 @@
1
+ a.kg-nft-card {
2
+ position: static;
3
+ display: flex;
4
+ flex: auto;
5
+ flex-direction: column;
6
+ text-decoration: none;
7
+ font-family: -apple-system, BlinkMacSystemFont,
8
+ 'avenir next', avenir,
9
+ 'helvetica neue', helvetica,
10
+ ubuntu,
11
+ roboto, noto,
12
+ 'segoe ui', arial,
13
+ sans-serif;
14
+ font-size: 1.4rem;
15
+ font-weight: 400;
16
+ box-shadow: 0 2px 6px -2px rgb(0 0 0 / 10%), 0 0 1px rgb(0 0 0 / 40%);
17
+ width: 100%;
18
+ max-width: 512px;
19
+ color: #222;
20
+ background: #fff;
21
+ border-radius: 5px;
22
+ transition: none;
23
+ }
24
+
25
+ a.kg-nft-card:hover {
26
+ color: #333;
27
+ opacity: 1.0;
28
+ transition: none;
29
+ }
30
+
31
+ a.kg-nft-card * {
32
+ position: static;
33
+ line-height: 1;
34
+ }
35
+
36
+ .kg-nft-metadata {
37
+ padding: 2.0rem;
38
+ }
39
+
40
+ .kg-nft-card img {
41
+ min-width: unset;
42
+ max-width: unset;
43
+ margin: -1px;
44
+ border-radius: 5px 5px 0 0;
45
+ }
46
+
47
+ .kg-nft-header {
48
+ display: flex;
49
+ justify-content: space-between;
50
+ gap: 20px;
51
+ }
52
+
53
+ .kg-nft-header h4.kg-nft-title {
54
+ font-family: inherit;
55
+ font-size: 1.9rem;
56
+ font-weight: 700;
57
+ min-width: unset;
58
+ max-width: unset;
59
+ margin: 0;
60
+ color: #222;
61
+ }
62
+
63
+ .kg-nft-header svg {
64
+ width: 100px;
65
+ height: auto;
66
+ }
67
+
68
+ .kg-nft-creator {
69
+ font-family: inherit;
70
+ margin: 0.8rem 0 0;
71
+ color: #ababab;
72
+ }
73
+
74
+ .kg-nft-creator span {
75
+ font-weight: 500;
76
+ color: #222;
77
+ }
78
+
79
+ .kg-nft-card p.kg-nft-description {
80
+ font-family: inherit;
81
+ font-size: 1.4rem;
82
+ line-height: 1.4em;
83
+ margin: 2.0rem 0 0;
84
+ color: #222;
85
+ }
@@ -0,0 +1,8 @@
1
+ var images = document.querySelectorAll('.kg-gallery-image img');
2
+ images.forEach(function (image) {
3
+ var container = image.closest('.kg-gallery-image');
4
+ var width = image.attributes.width.value;
5
+ var height = image.attributes.height.value;
6
+ var ratio = width / height;
7
+ container.style.flex = ratio + ' 1 0%';
8
+ })
@@ -7,7 +7,8 @@ const urlUtils = require('../../../shared/url-utils');
7
7
  const tpl = require('@tryghost/tpl');
8
8
 
9
9
  const messages = {
10
- imageNotFound: 'Image not found'
10
+ imageNotFound: 'Image not found',
11
+ fileNotFound: 'File not found'
11
12
  };
12
13
 
13
14
  function createPublicFileMiddleware(file, type, maxAge) {
@@ -43,6 +44,14 @@ function createPublicFileMiddleware(file, type, maxAge) {
43
44
  // modify text files before caching+serving to ensure URL placeholders are transformed
44
45
  fs.readFile(filePath, (err, buf) => {
45
46
  if (err) {
47
+ // Downgrade to a simple 404 if the file didn't exist
48
+ if (err.code === 'ENOENT') {
49
+ err = new errors.NotFoundError({
50
+ message: tpl(messages.fileNotFound),
51
+ code: 'PUBLIC_FILE_NOT_FOUND',
52
+ property: err.path
53
+ });
54
+ }
46
55
  return next(err);
47
56
  }
48
57
 
@@ -1,7 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('routing');
2
2
 
3
3
  const routing = require('../services/routing');
4
- // NOTE: temporary import from the frontend, will become a backend service soon
5
4
  const urlService = require('../../server/services/url');
6
5
  const routeSettings = require('../../server/services/route-settings');
7
6
 
@@ -17,11 +17,15 @@ const themeEngine = require('../services/theme-engine');
17
17
  const themeMiddleware = themeEngine.middleware;
18
18
  const membersService = require('../../server/services/members');
19
19
  const offersService = require('../../server/services/offers');
20
+ const customRedirects = require('../../server/services/redirects');
20
21
  const siteRoutes = require('./routes');
21
22
  const shared = require('../../server/web/shared');
22
23
  const mw = require('./middleware');
24
+ const labs = require('../../shared/labs');
23
25
 
24
26
  const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
27
+ const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
28
+ const STATIC_FILES_URL_PREFIX = `/${constants.STATIC_FILES_URL_PREFIX}`;
25
29
 
26
30
  let router;
27
31
 
@@ -89,7 +93,7 @@ module.exports = function setupSiteApp(options = {}) {
89
93
 
90
94
  // you can extend Ghost with a custom redirects file
91
95
  // see https://github.com/TryGhost/Ghost/issues/7707
92
- shared.middlewares.customRedirects.use(siteApp);
96
+ siteApp.use(customRedirects.middleware);
93
97
 
94
98
  // (Optionally) redirect any requests to /ghost to the admin panel
95
99
  siteApp.use(mw.redirectGhostToAdmin());
@@ -106,16 +110,16 @@ module.exports = function setupSiteApp(options = {}) {
106
110
  siteApp.use(mw.servePublicFile('public/ghost.css', 'text/css', constants.ONE_HOUR_S));
107
111
  siteApp.use(mw.servePublicFile('public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));
108
112
 
113
+ // Card assets
114
+ siteApp.use(mw.servePublicFile('public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
115
+ siteApp.use(mw.servePublicFile('public/cards.min.js', 'text/js', constants.ONE_YEAR_S));
116
+
109
117
  // Serve blog images using the storage adapter
110
118
  siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
111
-
112
- // @TODO find this a better home
113
- // We do this here, at the top level, because helpers require so much stuff.
114
- // Moving this to being inside themes, where it probably should be requires the proxy to be refactored
115
- // Else we end up with circular dependencies
116
- // themeEngine.loadCoreHelpers();
117
- // themeEngine.registerHandlebarsHelpers();
118
- // debug('Helpers done');
119
+ // Serve blog media using the storage adapter
120
+ siteApp.use(STATIC_MEDIA_URL_PREFIX, labs.enabledMiddleware('mediaAPI'), storage.getStorage('media').serve());
121
+ // Serve blog files using the storage adapter
122
+ siteApp.use(STATIC_FILES_URL_PREFIX, labs.enabledMiddleware('filesAPI'), storage.getStorage('files').serve());
119
123
 
120
124
  // Global handling for member session, ensures a member is logged in to the frontend
121
125
  siteApp.use(membersService.middleware.loadMemberSession);
@@ -0,0 +1,17 @@
1
+ // # Local File System Storage module
2
+ // The (default) module for storing media, using the local file system
3
+ const config = require('../../../shared/config');
4
+ const constants = require('@tryghost/constants');
5
+ const LocalStorageBase = require('./LocalStorageBase');
6
+
7
+ class LocalFilesStorage extends LocalStorageBase {
8
+ constructor() {
9
+ super({
10
+ storagePath: config.getContentPath('files'),
11
+ siteUrl: config.getSiteUrl(),
12
+ staticFileURLPrefix: constants.STATIC_FILES_URL_PREFIX
13
+ });
14
+ }
15
+ }
16
+
17
+ module.exports = LocalFilesStorage;
@@ -0,0 +1,51 @@
1
+ // # Local File System Image Storage module
2
+ // The (default) module for storing images, using the local file system
3
+
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const config = require('../../../shared/config');
7
+
8
+ const urlUtils = require('../../../shared/url-utils');
9
+ const LocalStorageBase = require('./LocalStorageBase');
10
+
11
+ let messages = {
12
+ notFound: 'Image not found',
13
+ notFoundWithRef: 'Image not found: {file}',
14
+ cannotRead: 'Could not read image: {file}'
15
+ };
16
+
17
+ class LocalImagesStorage extends LocalStorageBase {
18
+ constructor() {
19
+ super({
20
+ storagePath: config.getContentPath('images'),
21
+ staticFileURLPrefix: urlUtils.STATIC_IMAGE_URL_PREFIX,
22
+ siteUrl: config.getSiteUrl(),
23
+ errorMessages: messages
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Saves a buffer in the targetPath
29
+ * @param {Buffer} buffer is an instance of Buffer
30
+ * @param {String} targetPath path to which the buffer should be written
31
+ * @returns {Promise<String>} a URL to retrieve the data
32
+ */
33
+ async saveRaw(buffer, targetPath) {
34
+ const storagePath = path.join(this.storagePath, targetPath);
35
+ const targetDir = path.dirname(storagePath);
36
+
37
+ await fs.mkdirs(targetDir);
38
+ await fs.writeFile(storagePath, buffer);
39
+
40
+ // For local file system storage can use relative path so add a slash
41
+ const fullUrl = (
42
+ urlUtils.urlJoin('/', urlUtils.getSubdir(),
43
+ this.staticFileURLPrefix,
44
+ targetPath)
45
+ ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
46
+
47
+ return fullUrl;
48
+ }
49
+ }
50
+
51
+ module.exports = LocalImagesStorage;
@@ -0,0 +1,24 @@
1
+ // # Local File System Media Storage module
2
+ // The (default) module for storing media, using the local file system
3
+ const config = require('../../../shared/config');
4
+ const constants = require('@tryghost/constants');
5
+ const LocalStorageBase = require('./LocalStorageBase');
6
+
7
+ const messages = {
8
+ notFound: 'Media file not found',
9
+ notFoundWithRef: 'Media file not found: {file}',
10
+ cannotRead: 'Could not read media file: {file}'
11
+ };
12
+
13
+ class LocalMediaStore extends LocalStorageBase {
14
+ constructor() {
15
+ super({
16
+ storagePath: config.getContentPath('media'),
17
+ staticFileURLPrefix: constants.STATIC_MEDIA_URL_PREFIX,
18
+ siteUrl: config.getSiteUrl(),
19
+ errorMessages: messages
20
+ });
21
+ }
22
+ }
23
+
24
+ module.exports = LocalMediaStore;