ghost 4.19.1 → 4.20.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.
- package/.eslintrc.js +9 -8
- package/Gruntfile.js +1 -1
- package/PRIVACY.md +3 -0
- package/content/adapters/README.md +2 -2
- package/core/boot.js +4 -4
- package/core/bridge.js +9 -1
- package/core/built/assets/{chunk.3.0778d8e4d707d2a625f1.js → chunk.3.777d43e2ce954ba8b2f5.js} +1 -1
- package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
- package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
- package/core/built/assets/{ghost.min-102753ec485602c8fe80d60a1750bf84.js → ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js} +525 -340
- package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
- package/core/built/assets/icons/arrow-left-small.svg +0 -4
- package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
- package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
- package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
- package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
- package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
- package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
- package/core/built/assets/{vendor.min-0916203b598271a795909e8e0b1c16c2.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +1046 -1043
- package/core/frontend/apps/amp/lib/router.js +1 -1
- package/core/frontend/meta/author-url.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/{server → frontend}/public/favicon.ico +0 -0
- package/core/{server → frontend}/public/ghost.css +0 -0
- package/core/{server → frontend}/public/ghost.min.css +0 -0
- package/core/{server → frontend}/public/robots.txt +0 -0
- package/core/{server → frontend}/public/sitemap.xsl +0 -0
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/CollectionRouter.js +3 -49
- package/core/frontend/services/routing/ParentRouter.js +1 -4
- package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
- package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
- package/core/frontend/services/routing/controllers/collection.js +2 -2
- package/core/frontend/services/routing/controllers/email-post.js +2 -2
- package/core/frontend/services/routing/controllers/entry.js +2 -2
- package/core/frontend/services/routing/controllers/preview.js +2 -2
- package/core/frontend/services/routing/index.js +6 -12
- package/core/frontend/services/routing/registry.js +13 -0
- package/core/frontend/services/routing/router-manager.js +185 -0
- package/core/frontend/services/rss/generate-feed.js +2 -2
- package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
- package/core/frontend/services/theme-engine/i18n/index.js +1 -1
- package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
- package/core/frontend/web/index.js +1 -0
- package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
- package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
- package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
- package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
- package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
- package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
- package/core/{server/web/site → frontend/web}/routes.js +5 -4
- package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
- package/core/server/adapters/storage/LocalFileStorage.js +35 -39
- package/core/server/adapters/storage/index.js +12 -2
- package/core/server/api/canary/images.js +1 -1
- package/core/server/api/canary/offers.js +19 -0
- package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
- package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v2/images.js +1 -1
- package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v3/images.js +1 -1
- package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
- package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
- package/core/server/data/importer/handlers/image.js +1 -1
- package/core/server/data/importer/importers/image.js +1 -1
- package/core/server/data/migrations/init/1-create-tables.js +7 -8
- package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
- package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
- package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
- package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
- package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
- package/core/server/data/schema/fixtures/utils.js +150 -143
- package/core/server/data/schema/schema.js +4 -3
- package/core/server/frontend/ghost.min.css +1 -0
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/lib/mobiledoc.js +3 -2
- package/core/server/models/action.js +7 -4
- package/core/server/models/base/plugins/overrides.js +19 -6
- package/core/server/models/index.js +4 -46
- package/core/server/models/member.js +5 -0
- package/core/server/models/user.js +2 -1
- package/core/server/overrides.js +6 -2
- package/core/server/services/adapter-manager/config.js +1 -0
- package/core/server/services/adapter-manager/index.js +9 -5
- package/core/server/services/adapter-manager/options-resolver.js +18 -0
- package/core/server/services/bulk-email/mailgun.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/members/api.js +1 -3
- package/core/server/services/members/emails/signin.js +1 -1
- package/core/server/services/members/emails/signup.js +1 -1
- package/core/server/services/members/emails/subscribe.js +1 -1
- package/core/server/services/members/service.js +2 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/route-settings/route-settings.js +1 -1
- package/core/server/services/settings/index.js +3 -1
- package/core/server/services/settings/settings-bread-service.js +42 -20
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activate.js +2 -2
- package/core/server/services/themes/activation-bridge.js +6 -6
- package/core/server/services/themes/storage.js +1 -1
- package/core/{frontend → server}/services/url/Queue.js +0 -0
- package/core/{frontend → server}/services/url/Resource.js +0 -0
- package/core/{frontend → server}/services/url/Resources.js +2 -2
- package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
- package/core/{frontend → server}/services/url/UrlService.js +12 -15
- package/core/{frontend → server}/services/url/Urls.js +1 -1
- package/core/{frontend → server}/services/url/configs/canary.js +0 -0
- package/core/{frontend → server}/services/url/configs/v2.js +0 -0
- package/core/{frontend → server}/services/url/configs/v3.js +0 -0
- package/core/{frontend → server}/services/url/configs/v4.js +0 -0
- package/core/{frontend → server}/services/url/index.js +0 -0
- package/core/server/services/xmlrpc.js +1 -1
- package/core/server/update-check.js +3 -3
- package/core/server/web/admin/controller.js +11 -0
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +8 -9
- package/core/server/web/oauth/app.js +4 -2
- package/core/server/web/parent/backend.js +3 -3
- package/core/server/web/parent/frontend.js +2 -2
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
- package/core/server/web/shared/middlewares/maintenance.js +1 -1
- package/core/server/web/well-known.js +10 -10
- package/core/shared/config/overrides.json +1 -1
- package/core/shared/express.js +10 -0
- package/core/shared/html-to-plaintext.js +2 -2
- package/core/shared/labs.js +14 -5
- package/package.json +45 -43
- package/yarn.lock +649 -284
- package/core/built/assets/ghost-dark-da8e8eba130fb52f97494e51850d1045.css +0 -1
- package/core/built/assets/ghost.min-0d8f19623e9f077351bce453034daf4d.css +0 -1
- package/core/frontend/services/routing/bootstrap.js +0 -134
- package/core/server/public/404-ghost.png +0 -0
- package/core/server/public/404-ghost@2x.png +0 -0
- package/core/server/web/site/index.js +0 -1
- package/core/shared/i18n/i18n.js +0 -312
- package/core/shared/i18n/index.js +0 -6
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module.exports = function resolveAdapterOptions(name, adapterServiceConfig) {
|
|
2
|
+
const [adapterType, feature] = name.split(':');
|
|
3
|
+
const adapterSettings = adapterServiceConfig[adapterType];
|
|
4
|
+
|
|
5
|
+
let adapterName;
|
|
6
|
+
let adapterConfig;
|
|
7
|
+
|
|
8
|
+
// CASE: load resource-specific adapter when there is an adapter feature name specified as well as custom feature config
|
|
9
|
+
if (feature && adapterSettings[feature] && adapterSettings[adapterSettings[feature]]) {
|
|
10
|
+
adapterName = adapterSettings[feature];
|
|
11
|
+
adapterConfig = adapterSettings[adapterName];
|
|
12
|
+
} else {
|
|
13
|
+
adapterName = adapterSettings.active;
|
|
14
|
+
adapterConfig = adapterSettings[adapterName];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {adapterType, adapterName, adapterConfig};
|
|
18
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const {URL} = require('url');
|
|
3
|
-
const mailgun = require('mailgun-js');
|
|
4
3
|
const logging = require('@tryghost/logging');
|
|
5
4
|
const configService = require('../../../shared/config');
|
|
6
5
|
const settingsCache = require('../../../shared/settings-cache');
|
|
@@ -8,6 +7,7 @@ const settingsCache = require('../../../shared/settings-cache');
|
|
|
8
7
|
const BATCH_SIZE = 1000;
|
|
9
8
|
|
|
10
9
|
function createMailgun(config) {
|
|
10
|
+
const mailgun = require('mailgun-js');
|
|
11
11
|
const baseUrl = new URL(config.baseUrl);
|
|
12
12
|
|
|
13
13
|
return mailgun({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const juice = require('juice');
|
|
3
2
|
const template = require('./template');
|
|
4
3
|
const settingsCache = require('../../../shared/settings-cache');
|
|
5
4
|
const urlUtils = require('../../../shared/url-utils');
|
|
@@ -20,6 +19,7 @@ const ALLOWED_REPLACEMENTS = ['first_name'];
|
|
|
20
19
|
const formatHtmlForEmail = function formatHtmlForEmail(html) {
|
|
21
20
|
const juiceOptions = {inlinePseudoElements: true};
|
|
22
21
|
|
|
22
|
+
const juice = require('juice');
|
|
23
23
|
let juicedHtml = juice(html, juiceOptions);
|
|
24
24
|
|
|
25
25
|
// convert juiced HTML to a DOM-like interface for further manipulation
|
|
@@ -227,7 +227,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
|
|
|
227
227
|
const momentDate = post.published_at ? moment(post.published_at) : moment();
|
|
228
228
|
post.published_at = momentDate.tz(timezone).format('DD MMM YYYY');
|
|
229
229
|
|
|
230
|
-
post.authors = post.authors && post.authors.map(author => author.name).join(',');
|
|
230
|
+
post.authors = post.authors && post.authors.map(author => author.name).join(', ');
|
|
231
231
|
if (post.posts_meta) {
|
|
232
232
|
post.email_subject = post.posts_meta.email_subject;
|
|
233
233
|
}
|
|
@@ -70,7 +70,6 @@ function createApiInstance(config) {
|
|
|
70
70
|
For your security, the link will expire in 24 hours time.
|
|
71
71
|
|
|
72
72
|
All the best!
|
|
73
|
-
The team at ${siteTitle}
|
|
74
73
|
|
|
75
74
|
---
|
|
76
75
|
|
|
@@ -88,7 +87,6 @@ function createApiInstance(config) {
|
|
|
88
87
|
For your security, the link will expire in 24 hours time.
|
|
89
88
|
|
|
90
89
|
See you soon!
|
|
91
|
-
The team at ${siteTitle}
|
|
92
90
|
|
|
93
91
|
---
|
|
94
92
|
|
|
@@ -122,7 +120,6 @@ function createApiInstance(config) {
|
|
|
122
120
|
For your security, the link will expire in 24 hours time.
|
|
123
121
|
|
|
124
122
|
See you soon!
|
|
125
|
-
The team at ${siteTitle}
|
|
126
123
|
|
|
127
124
|
---
|
|
128
125
|
|
|
@@ -181,6 +178,7 @@ function createApiInstance(config) {
|
|
|
181
178
|
MemberProductEvent: models.MemberProductEvent,
|
|
182
179
|
MemberAnalyticEvent: models.MemberAnalyticEvent,
|
|
183
180
|
OfferRedemption: models.OfferRedemption,
|
|
181
|
+
Offer: models.Offer,
|
|
184
182
|
StripeProduct: models.StripeProduct,
|
|
185
183
|
StripePrice: models.StripePrice,
|
|
186
184
|
Product: models.Product,
|
|
@@ -134,7 +134,7 @@ module.exports = ({siteTitle, email, url, accentColor = '#15212A', siteDomain, s
|
|
|
134
134
|
</tbody>
|
|
135
135
|
</table>
|
|
136
136
|
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
|
|
137
|
-
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon
|
|
137
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon!</p>
|
|
138
138
|
<hr/>
|
|
139
139
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
|
|
140
140
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top:0; color: #3A464C;">${url}</p>
|
|
@@ -134,7 +134,7 @@ module.exports = ({siteTitle, email, url, accentColor = '#15212A', siteDomain, s
|
|
|
134
134
|
</tbody>
|
|
135
135
|
</table>
|
|
136
136
|
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
|
|
137
|
-
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon
|
|
137
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon!</p>
|
|
138
138
|
<hr/>
|
|
139
139
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
|
|
140
140
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top: 0; color: #3A464C;">${url}</p>
|
|
@@ -134,7 +134,7 @@ module.exports = ({siteTitle, email, url, accentColor = '#15212A', siteDomain, s
|
|
|
134
134
|
</tbody>
|
|
135
135
|
</table>
|
|
136
136
|
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
|
|
137
|
-
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 30px;">All the best
|
|
137
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 30px;">All the best!</p>
|
|
138
138
|
<hr/>
|
|
139
139
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
|
|
140
140
|
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top: 0; color: #3A464C;">${url}</p>
|
|
@@ -61,7 +61,8 @@ function reconfigureMembersAPI() {
|
|
|
61
61
|
*/
|
|
62
62
|
const fetchImportThreshold = async () => {
|
|
63
63
|
const membersTotal = await membersService.stats.getTotalMembers();
|
|
64
|
-
const
|
|
64
|
+
const configThreshold = _.get(config.get('hostSettings'), 'emailVerification.importThreshold');
|
|
65
|
+
const volumeThreshold = (configThreshold === undefined) ? Infinity : configThreshold;
|
|
65
66
|
const threshold = Math.max(membersTotal, volumeThreshold);
|
|
66
67
|
|
|
67
68
|
return threshold;
|
|
@@ -47,7 +47,7 @@ module.exports = {
|
|
|
47
47
|
offersEnabled = labs.isSet('offers');
|
|
48
48
|
|
|
49
49
|
if (offersEnabled) {
|
|
50
|
-
const offers = await this.api.listOffers();
|
|
50
|
+
const offers = await this.api.listOffers({});
|
|
51
51
|
for (const offer of offers) {
|
|
52
52
|
redirectManager.addRedirect(`/${offer.code}`, `/#/portal/offers/${offer.id}`, {permanent: false});
|
|
53
53
|
}
|
|
@@ -3,7 +3,7 @@ const moment = require('moment-timezone');
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
-
const urlService = require('
|
|
6
|
+
const urlService = require('../url');
|
|
7
7
|
|
|
8
8
|
const debug = require('@tryghost/debug')('services:route-settings');
|
|
9
9
|
const errors = require('@tryghost/errors');
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const events = require('../../lib/common/events');
|
|
6
6
|
const models = require('../../models');
|
|
7
|
+
const labs = require('../../../shared/labs');
|
|
7
8
|
const SettingsCache = require('../../../shared/settings-cache');
|
|
8
9
|
const SettingsBREADService = require('./settings-bread-service');
|
|
9
10
|
const {obfuscatedSetting, isSecretSetting, hideValueIfSecret} = require('./settings-utils');
|
|
@@ -14,7 +15,8 @@ const {obfuscatedSetting, isSecretSetting, hideValueIfSecret} = require('./setti
|
|
|
14
15
|
const getSettingsBREADServiceInstance = () => {
|
|
15
16
|
return new SettingsBREADService({
|
|
16
17
|
SettingsModel: models.Settings,
|
|
17
|
-
settingsCache: SettingsCache
|
|
18
|
+
settingsCache: SettingsCache,
|
|
19
|
+
labsService: labs
|
|
18
20
|
});
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -14,10 +14,12 @@ class SettingsBREADService {
|
|
|
14
14
|
* @param {Object} options
|
|
15
15
|
* @param {Object} options.SettingsModel
|
|
16
16
|
* @param {Object} options.settingsCache - SettingsCache instance
|
|
17
|
+
* @param {Object} options.labsService - labs service instance
|
|
17
18
|
*/
|
|
18
|
-
constructor({SettingsModel, settingsCache}) {
|
|
19
|
+
constructor({SettingsModel, settingsCache, labsService}) {
|
|
19
20
|
this.SettingsModel = SettingsModel;
|
|
20
21
|
this.settingsCache = settingsCache;
|
|
22
|
+
this.labs = labsService;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -28,24 +30,7 @@ class SettingsBREADService {
|
|
|
28
30
|
browse(context) {
|
|
29
31
|
let settings = this.settingsCache.getAll();
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
if (!context) {
|
|
33
|
-
return Promise.resolve(settings.filter((setting) => {
|
|
34
|
-
return setting.group === 'site';
|
|
35
|
-
}));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!context.internal) {
|
|
39
|
-
// CASE: omit core settings unless internal request
|
|
40
|
-
settings = _.filter(settings, (setting) => {
|
|
41
|
-
const isCore = setting.group === 'core';
|
|
42
|
-
return !isCore;
|
|
43
|
-
});
|
|
44
|
-
// CASE: omit secret settings unless internal request
|
|
45
|
-
settings = settings.map(hideValueIfSecret);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return settings;
|
|
33
|
+
return this._formatBrowse(settings, context);
|
|
49
34
|
}
|
|
50
35
|
|
|
51
36
|
/**
|
|
@@ -86,6 +71,12 @@ class SettingsBREADService {
|
|
|
86
71
|
}));
|
|
87
72
|
}
|
|
88
73
|
|
|
74
|
+
// NOTE: Labs flags can exist outside of the DB when they are forced on/off
|
|
75
|
+
// so we grab them from the labs service instead as that's source-of-truth
|
|
76
|
+
if (setting.key === 'labs') {
|
|
77
|
+
setting.value = JSON.stringify(this.labs.getAll());
|
|
78
|
+
}
|
|
79
|
+
|
|
89
80
|
setting = hideValueIfSecret(setting);
|
|
90
81
|
|
|
91
82
|
return {
|
|
@@ -161,7 +152,9 @@ class SettingsBREADService {
|
|
|
161
152
|
});
|
|
162
153
|
}
|
|
163
154
|
|
|
164
|
-
return this.SettingsModel.edit(filteredSettings, options)
|
|
155
|
+
return this.SettingsModel.edit(filteredSettings, options).then((result) => {
|
|
156
|
+
return this._formatBrowse(_.keyBy(_.invokeMap(result, 'toJSON'), 'key'), options.context);
|
|
157
|
+
});
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
/**
|
|
@@ -183,6 +176,35 @@ class SettingsBREADService {
|
|
|
183
176
|
}
|
|
184
177
|
}
|
|
185
178
|
}
|
|
179
|
+
|
|
180
|
+
_formatBrowse(inputSettings, context) {
|
|
181
|
+
let settings = _.values(inputSettings);
|
|
182
|
+
// CASE: no context passed (functional call)
|
|
183
|
+
if (!context) {
|
|
184
|
+
return Promise.resolve(settings.filter((setting) => {
|
|
185
|
+
return setting.group === 'site';
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!context.internal) {
|
|
190
|
+
// CASE: omit core settings unless internal request
|
|
191
|
+
settings = _.filter(settings, (setting) => {
|
|
192
|
+
const isCore = setting.group === 'core';
|
|
193
|
+
return !isCore;
|
|
194
|
+
});
|
|
195
|
+
// CASE: omit secret settings unless internal request
|
|
196
|
+
settings = settings.map(hideValueIfSecret);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// NOTE: Labs flags can exist outside of the DB when they are forced on/off
|
|
200
|
+
// so we grab them from the labs service instead as that's source-of-truth
|
|
201
|
+
const labsSetting = settings.find(setting => setting.key === 'labs');
|
|
202
|
+
if (labsSetting) {
|
|
203
|
+
labsSetting.value = JSON.stringify(this.labs.getAll());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return settings;
|
|
207
|
+
}
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
module.exports = SettingsBREADService;
|
|
@@ -4,7 +4,7 @@ const logging = require('@tryghost/logging');
|
|
|
4
4
|
const request = require('@tryghost/request');
|
|
5
5
|
const {blogIcon} = require('../lib/image');
|
|
6
6
|
const urlUtils = require('../../shared/url-utils');
|
|
7
|
-
const urlService = require('
|
|
7
|
+
const urlService = require('./url');
|
|
8
8
|
const settingsCache = require('../../shared/settings-cache');
|
|
9
9
|
const schema = require('../data/schema').checks;
|
|
10
10
|
const moment = require('moment');
|
|
@@ -30,7 +30,7 @@ module.exports.loadAndActivate = async (themeName) => {
|
|
|
30
30
|
logging.warn(validate.getThemeValidationError('activeThemeHasErrors', themeName, checkedTheme));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
activator.activateFromBoot(themeName, loadedTheme, checkedTheme);
|
|
33
|
+
await activator.activateFromBoot(themeName, loadedTheme, checkedTheme);
|
|
34
34
|
} catch (err) {
|
|
35
35
|
if (err instanceof errors.NotFoundError) {
|
|
36
36
|
// CASE: active theme is missing, we don't want to exit because the admin panel will still work
|
|
@@ -56,7 +56,7 @@ module.exports.activate = async (themeName) => {
|
|
|
56
56
|
// Validate
|
|
57
57
|
const checkedTheme = await validate.checkSafe(themeName, loadedTheme);
|
|
58
58
|
// Activate
|
|
59
|
-
activator.activateFromAPI(themeName, loadedTheme, checkedTheme);
|
|
59
|
+
await activator.activateFromAPI(themeName, loadedTheme, checkedTheme);
|
|
60
60
|
// Return the checked theme
|
|
61
61
|
return checkedTheme;
|
|
62
62
|
};
|
|
@@ -8,27 +8,27 @@ const customThemeSettings = require('../custom-theme-settings');
|
|
|
8
8
|
* And also adds a little debug statement, which is very handy when debugging theme logic
|
|
9
9
|
*/
|
|
10
10
|
module.exports = {
|
|
11
|
-
activateFromBoot: (themeName, theme, checkedTheme) => {
|
|
11
|
+
activateFromBoot: async (themeName, theme, checkedTheme) => {
|
|
12
12
|
debug('Activating theme (method A on boot)', themeName);
|
|
13
13
|
// TODO: probably a better place for this to happen - after successful activation / when reloading site?
|
|
14
14
|
if (labs.isSet('customThemeSettings')) {
|
|
15
|
-
customThemeSettings.api.activateTheme(checkedTheme);
|
|
15
|
+
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
16
16
|
}
|
|
17
17
|
bridge.activateTheme(theme, checkedTheme);
|
|
18
18
|
},
|
|
19
|
-
activateFromAPI: (themeName, theme, checkedTheme) => {
|
|
19
|
+
activateFromAPI: async (themeName, theme, checkedTheme) => {
|
|
20
20
|
debug('Activating theme (method B on API "activate")', themeName);
|
|
21
21
|
// TODO: probably a better place for this to happen - after successful activation / when reloading site?
|
|
22
22
|
if (labs.isSet('customThemeSettings')) {
|
|
23
|
-
customThemeSettings.api.activateTheme(checkedTheme);
|
|
23
|
+
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
24
24
|
}
|
|
25
25
|
bridge.activateTheme(theme, checkedTheme);
|
|
26
26
|
},
|
|
27
|
-
activateFromAPIOverride: (themeName, theme, checkedTheme) => {
|
|
27
|
+
activateFromAPIOverride: async (themeName, theme, checkedTheme) => {
|
|
28
28
|
debug('Activating theme (method C on API "override")', themeName);
|
|
29
29
|
// TODO: probably a better place for this to happen - after successful activation / when reloading site?
|
|
30
30
|
if (labs.isSet('customThemeSettings')) {
|
|
31
|
-
customThemeSettings.api.activateTheme(checkedTheme);
|
|
31
|
+
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
32
32
|
}
|
|
33
33
|
bridge.activateTheme(theme, checkedTheme);
|
|
34
34
|
}
|
|
@@ -83,7 +83,7 @@ module.exports = {
|
|
|
83
83
|
// CASE: if this is the active theme, we are overriding
|
|
84
84
|
if (overrideTheme) {
|
|
85
85
|
debug('setFromZip Theme is active already');
|
|
86
|
-
activator.activateFromAPIOverride(themeName, loadedTheme, checkedTheme);
|
|
86
|
+
await activator.activateFromAPIOverride(themeName, loadedTheme, checkedTheme);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// @TODO: unify the name across gscan and Ghost!
|
|
File without changes
|
|
File without changes
|
|
@@ -3,10 +3,10 @@ const Promise = require('bluebird');
|
|
|
3
3
|
const debug = require('@tryghost/debug')('services:url:resources');
|
|
4
4
|
const Resource = require('./Resource');
|
|
5
5
|
const config = require('../../../shared/config');
|
|
6
|
-
const models = require('
|
|
6
|
+
const models = require('../../models');
|
|
7
7
|
|
|
8
8
|
// This listens to all manner of model events to find new content that needs a URL...
|
|
9
|
-
const events = require('
|
|
9
|
+
const events = require('../../lib/common/events');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @description At the moment the resources class is directly responsible for data population
|
|
@@ -68,24 +68,24 @@ class UrlGenerator {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* @
|
|
72
|
-
* @
|
|
71
|
+
* @NOTE: currently only used if the permalink setting changes and it's used for this url generator.
|
|
72
|
+
* @TODO: https://github.com/TryGhost/Ghost/issues/10699
|
|
73
73
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* @NOTE: currently only used if the permalink setting changes and it's used for this url generator.
|
|
77
|
-
* @TODO: https://github.com/TryGhost/Ghost/issues/10699
|
|
78
|
-
*/
|
|
79
|
-
this.router.addListener('updated', () => {
|
|
80
|
-
const myResources = this.urls.getByGeneratorId(this.uid);
|
|
74
|
+
regenerateResources() {
|
|
75
|
+
const myResources = this.urls.getByGeneratorId(this.uid);
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
77
|
+
myResources.forEach((object) => {
|
|
78
|
+
this.urls.removeResourceId(object.resource.data.id);
|
|
79
|
+
object.resource.release();
|
|
80
|
+
this._try(object.resource);
|
|
87
81
|
});
|
|
82
|
+
}
|
|
88
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @description Helper function to register listeners for each url generator instance.
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
_listeners() {
|
|
89
89
|
/**
|
|
90
90
|
* Listen on two events:
|
|
91
91
|
*
|
|
@@ -9,7 +9,7 @@ const Resources = require('./Resources');
|
|
|
9
9
|
const urlUtils = require('../../../shared/url-utils');
|
|
10
10
|
|
|
11
11
|
// This listens to services.themes.api.changed, routing events, and it's own queue events
|
|
12
|
-
const events = require('
|
|
12
|
+
const events = require('../../lib/common/events');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The url service class holds all instances in a centralized place.
|
|
@@ -35,9 +35,6 @@ class UrlService {
|
|
|
35
35
|
* @private
|
|
36
36
|
*/
|
|
37
37
|
_listeners() {
|
|
38
|
-
this._onRouterAddedListener = this._onRouterAddedType.bind(this);
|
|
39
|
-
events.on('router.created', this._onRouterAddedListener);
|
|
40
|
-
|
|
41
38
|
this._onThemeChangedListener = this._onThemeChangedListener.bind(this);
|
|
42
39
|
events.on('services.themes.api.changed', this._onThemeChangedListener);
|
|
43
40
|
|
|
@@ -77,22 +74,23 @@ class UrlService {
|
|
|
77
74
|
/**
|
|
78
75
|
* @description Router was created, connect it with a url generator.
|
|
79
76
|
* @param {ExpressRouter} router
|
|
80
|
-
* @private
|
|
81
77
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// e.g. static route router
|
|
85
|
-
// we are listening on the general `router.created` event - every router throws this event
|
|
86
|
-
if (!router || !router.getPermalinks()) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
debug('router.created');
|
|
78
|
+
onRouterAddedType(router) {
|
|
79
|
+
debug('Registering route: ', router.name);
|
|
91
80
|
|
|
92
81
|
let urlGenerator = new UrlGenerator(router, this.queue, this.resources, this.urls, this.urlGenerators.length);
|
|
93
82
|
this.urlGenerators.push(urlGenerator);
|
|
94
83
|
}
|
|
95
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @description Router update handler - regenerates it's resources
|
|
87
|
+
* @param {ExpressRouter} router
|
|
88
|
+
*/
|
|
89
|
+
onRouterUpdated(router) {
|
|
90
|
+
const generator = this.urlGenerators.find(g => g.router.id === router.id);
|
|
91
|
+
generator.regenerateResources();
|
|
92
|
+
}
|
|
93
|
+
|
|
96
94
|
/**
|
|
97
95
|
* @description If the API version in the theme config changes, we have to reset urls and resources.
|
|
98
96
|
* @private
|
|
@@ -307,7 +305,6 @@ class UrlService {
|
|
|
307
305
|
if (!options.keepListeners) {
|
|
308
306
|
this._onQueueStartedListener && this.queue.removeListener('started', this._onQueueStartedListener);
|
|
309
307
|
this._onQueueEndedListener && this.queue.removeListener('ended', this._onQueueEndedListener);
|
|
310
|
-
this._onRouterAddedListener && events.removeListener('router.created', this._onRouterAddedListener);
|
|
311
308
|
this._onThemeChangedListener && events.removeListener('services.themes.api.changed', this._onThemeChangedListener);
|
|
312
309
|
}
|
|
313
310
|
}
|
|
@@ -5,7 +5,7 @@ const logging = require('@tryghost/logging');
|
|
|
5
5
|
const errors = require('@tryghost/errors');
|
|
6
6
|
|
|
7
7
|
// This emits its own url added/removed events
|
|
8
|
-
const events = require('
|
|
8
|
+
const events = require('../../lib/common/events');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* This class keeps track of all urls in the system.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const xml = require('xml');
|
|
3
3
|
const config = require('../../shared/config');
|
|
4
|
-
const urlService = require('
|
|
4
|
+
const urlService = require('./url');
|
|
5
5
|
const errors = require('@tryghost/errors');
|
|
6
6
|
const tpl = require('@tryghost/tpl');
|
|
7
7
|
const logging = require('@tryghost/logging');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
|
|
3
3
|
const api = require('./api');
|
|
4
|
-
const GhostMailer = require('./services/mail').GhostMailer;
|
|
5
4
|
const config = require('../shared/config');
|
|
6
5
|
const urlUtils = require('./../shared/url-utils');
|
|
7
6
|
const jobsService = require('./services/jobs');
|
|
@@ -10,8 +9,6 @@ const request = require('@tryghost/request');
|
|
|
10
9
|
const ghostVersion = require('@tryghost/version');
|
|
11
10
|
const UpdateCheckService = require('@tryghost/update-check-service');
|
|
12
11
|
|
|
13
|
-
const ghostMailer = new GhostMailer();
|
|
14
|
-
|
|
15
12
|
/**
|
|
16
13
|
* Initializes and triggers update check
|
|
17
14
|
*
|
|
@@ -25,6 +22,9 @@ module.exports = async () => {
|
|
|
25
22
|
return;
|
|
26
23
|
}
|
|
27
24
|
|
|
25
|
+
const {GhostMailer} = require('./services/mail');
|
|
26
|
+
const ghostMailer = new GhostMailer();
|
|
27
|
+
|
|
28
28
|
const updateChecker = new UpdateCheckService({
|
|
29
29
|
api: {
|
|
30
30
|
settings: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('web:admin:controller');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const crypto = require('crypto');
|
|
3
5
|
const config = require('../../../shared/config');
|
|
4
6
|
const updateCheck = require('../../update-check');
|
|
5
7
|
|
|
@@ -21,6 +23,15 @@ module.exports = function adminController(req, res) {
|
|
|
21
23
|
const templatePath = path.resolve(config.get('paths').adminViews, defaultTemplate);
|
|
22
24
|
const headers = {};
|
|
23
25
|
|
|
26
|
+
// Generate our own ETag header
|
|
27
|
+
// `sendFile` by default uses filesize+lastmod date to generate an etag.
|
|
28
|
+
// That doesn't work for admin templates because the filesize doesn't change between versions
|
|
29
|
+
// and `npm pack` sets a fixed lastmod date for every file meaning the default etag never changes
|
|
30
|
+
const fileBuffer = fs.readFileSync(templatePath);
|
|
31
|
+
const hashSum = crypto.createHash('md5');
|
|
32
|
+
hashSum.update(fileBuffer);
|
|
33
|
+
headers.ETag = hashSum.digest('hex');
|
|
34
|
+
|
|
24
35
|
if (config.get('adminFrameProtection')) {
|
|
25
36
|
headers['X-Frame-Options'] = 'sameorigin';
|
|
26
37
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.20%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
<link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
|
|
44
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
44
|
+
<link rel="stylesheet" href="assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css" title="light">
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
<script src="assets/vendor.min-
|
|
63
|
-
<script src="assets/ghost.min-
|
|
62
|
+
<script src="assets/vendor.min-af502ac4142871500fc424f6a5a254ec.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.20%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
<link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
|
|
44
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
44
|
+
<link rel="stylesheet" href="assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css" title="light">
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
<script src="assets/vendor.min-
|
|
63
|
-
<script src="assets/ghost.min-
|
|
62
|
+
<script src="assets/vendor.min-af502ac4142871500fc424f6a5a254ec.js"></script>
|
|
63
|
+
<script src="assets/ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js"></script>
|
|
64
64
|
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|