ghost 4.22.1 → 4.23.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.
- package/.c8rc.json +24 -0
- package/.eslintrc.js +6 -0
- package/Gruntfile.js +1 -1
- package/content/public/README.md +3 -0
- package/core/boot.js +20 -12
- package/core/built/assets/{chunk.3.1148677ff3b78e5aeaee.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
- package/core/built/assets/{ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css → ghost-dark-42cf6e0c730578940ec069bda45aea41.css} +1 -1
- package/core/built/assets/{ghost.min-f7037eca328f4d4eb99f0309c19c9bae.js → ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js} +189 -143
- package/core/built/assets/{ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css → ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css} +1 -1
- package/core/built/assets/icons/powered-by-tenor.svg +35 -0
- package/core/built/assets/icons/tenor.svg +7 -0
- package/core/built/assets/{vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js → vendor.min-c9002845b6c30ac978abdadde9f33d7c.js} +8189 -7601
- package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/services/card-assets/service.js +21 -13
- package/core/frontend/services/routing/CollectionRouter.js +4 -5
- package/core/frontend/services/routing/EmailRouter.js +1 -1
- package/core/frontend/services/routing/ParentRouter.js +0 -8
- package/core/frontend/services/routing/PreviewRouter.js +1 -1
- package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
- package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
- package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
- package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
- package/core/frontend/services/routing/router-manager.js +7 -2
- package/core/frontend/services/rss/generate-feed.js +2 -1
- package/core/frontend/src/cards/css/bookmark.css +66 -48
- package/core/frontend/src/cards/css/button.css +30 -0
- package/core/frontend/src/cards/css/callout.css +50 -0
- package/core/frontend/src/cards/css/gallery.css +8 -13
- package/core/frontend/src/cards/css/nft.css +94 -0
- package/core/frontend/src/cards/css/toggle.css +47 -0
- package/core/frontend/src/cards/js/toggle.js +16 -0
- package/core/frontend/web/middleware/serve-public-file.js +14 -8
- package/core/frontend/web/routes.js +0 -1
- package/core/frontend/web/site.js +15 -12
- package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
- package/core/server/adapters/storage/LocalImagesStorage.js +1 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +2 -1
- package/core/server/adapters/storage/LocalStorageBase.js +30 -5
- package/core/server/api/canary/authentication.js +1 -1
- package/core/server/api/canary/files.js +19 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/media.js +25 -5
- package/core/server/api/canary/oembed.js +3 -0
- package/core/server/api/canary/utils/serializers/input/index.js +4 -0
- package/core/server/api/canary/utils/serializers/input/media.js +8 -0
- package/core/server/api/canary/utils/serializers/output/config.js +21 -14
- package/core/server/api/canary/utils/serializers/output/files.js +27 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/serializers/output/media.js +9 -0
- package/core/server/api/canary/utils/validators/input/files.js +7 -0
- package/core/server/api/canary/utils/validators/input/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/media.js +4 -0
- package/core/server/api/v2/authentication.js +1 -1
- package/core/server/api/v3/authentication.js +1 -1
- package/core/server/data/db/connection.js +7 -0
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
- package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
- package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
- package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
- package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
- package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
- package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
- package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
- package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
- package/core/server/data/schema/fixtures/index.js +8 -2
- package/core/server/services/mega/post-email-serializer.js +5 -1
- package/core/server/services/mega/segment-parser.js +1 -2
- package/core/server/services/mega/template.js +69 -1
- package/core/server/services/nft-oembed.js +57 -0
- package/core/server/services/oembed.js +161 -126
- package/core/server/services/public-config/config.js +2 -1
- package/core/server/services/stripe/index.js +4 -2
- package/core/server/services/url/Resource.js +1 -1
- package/core/server/services/url/Resources.js +36 -23
- package/core/server/services/url/UrlGenerator.js +23 -20
- package/core/server/services/url/UrlService.js +123 -21
- package/core/server/services/url/Urls.js +7 -2
- package/core/server/services/url/index.js +9 -1
- package/core/server/web/admin/app.js +6 -6
- 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 +1 -1
- package/core/server/web/api/canary/admin/app.js +4 -4
- package/core/server/web/api/canary/admin/middleware.js +6 -6
- package/core/server/web/api/canary/admin/routes.js +20 -5
- package/core/server/web/api/canary/content/app.js +4 -4
- package/core/server/web/api/canary/content/middleware.js +3 -3
- package/core/server/web/api/middleware/cors.js +7 -7
- package/core/server/web/api/v2/admin/app.js +4 -4
- package/core/server/web/api/v2/admin/middleware.js +6 -6
- package/core/server/web/api/v2/admin/routes.js +5 -5
- package/core/server/web/api/v2/content/app.js +4 -4
- package/core/server/web/api/v2/content/middleware.js +3 -3
- package/core/server/web/api/v3/admin/app.js +4 -4
- package/core/server/web/api/v3/admin/middleware.js +6 -6
- package/core/server/web/api/v3/admin/routes.js +5 -5
- package/core/server/web/api/v3/content/app.js +4 -4
- package/core/server/web/api/v3/content/middleware.js +3 -3
- package/core/server/web/members/app.js +7 -7
- package/core/server/web/oauth/app.js +1 -1
- package/core/server/web/parent/app.js +2 -3
- package/core/server/web/parent/frontend.js +1 -1
- package/core/server/web/shared/index.js +2 -2
- package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/error-handler.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/maintenance.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
- package/core/shared/config/defaults.json +10 -2
- package/core/shared/config/helpers.js +44 -0
- package/core/shared/config/loader.js +1 -1
- package/core/shared/config/overrides.json +2 -2
- package/core/shared/labs.js +8 -1
- package/loggingrc.js +19 -20
- package/package.json +35 -35
- package/urls.json +597 -0
- package/yarn.lock +655 -339
- package/core/server/data/schema/fixtures/utils.js +0 -321
- package/core/server/web/parent/vhost-utils.js +0 -39
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const Promise = require('bluebird');
|
|
3
|
+
const logging = require('@tryghost/logging');
|
|
4
|
+
const {sequence} = require('@tryghost/promise');
|
|
5
|
+
|
|
6
|
+
const models = require('../../../models');
|
|
7
|
+
const baseUtils = require('../../../models/base/utils');
|
|
8
|
+
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
|
|
11
|
+
class FixtureManager {
|
|
12
|
+
constructor(fixtures) {
|
|
13
|
+
this.fixtures = fixtures;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ### Match Func
|
|
18
|
+
* Figures out how to match across various combinations of keys and values.
|
|
19
|
+
* Match can be a string or an array containing 2 strings
|
|
20
|
+
* Key and Value are the values to be found
|
|
21
|
+
* Value can also be an array, in which case we look for a match in the array.
|
|
22
|
+
* @api private
|
|
23
|
+
* @param {String|Array} match
|
|
24
|
+
* @param {String|Integer} key
|
|
25
|
+
* @param {String|Array} [value]
|
|
26
|
+
* @returns {Function} matching function
|
|
27
|
+
*/
|
|
28
|
+
static matchFunc(match, key, value) {
|
|
29
|
+
if (_.isArray(match)) {
|
|
30
|
+
return function (item) {
|
|
31
|
+
let valueTest = true;
|
|
32
|
+
|
|
33
|
+
if (_.isArray(value)) {
|
|
34
|
+
valueTest = value.indexOf(item.get(match[1])) > -1;
|
|
35
|
+
} else if (value !== 'all') {
|
|
36
|
+
valueTest = item.get(match[1]) === value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return item.get(match[0]) === key && valueTest;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return function (item) {
|
|
44
|
+
key = key === 0 && value ? value : key;
|
|
45
|
+
return item.get(match) === key;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static matchObj(match, item) {
|
|
50
|
+
const matchedObj = {};
|
|
51
|
+
|
|
52
|
+
if (_.isArray(match)) {
|
|
53
|
+
_.each(match, (matchProp) => {
|
|
54
|
+
matchedObj[matchProp] = item.get(matchProp);
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
matchedObj[match] = item.get(match);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return matchedObj;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Add All Fixtures
|
|
65
|
+
*
|
|
66
|
+
* Helper method to handle adding all fixtures
|
|
67
|
+
*
|
|
68
|
+
* @param {object} options
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
async addAllFixtures(options) {
|
|
72
|
+
const localOptions = _.merge({
|
|
73
|
+
context: {internal: true},
|
|
74
|
+
migrating: true
|
|
75
|
+
}, options);
|
|
76
|
+
|
|
77
|
+
await Promise.mapSeries(this.fixtures.models, (model) => {
|
|
78
|
+
logging.info('Model: ' + model.name);
|
|
79
|
+
|
|
80
|
+
return this.addFixturesForModel(model, localOptions);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Promise.mapSeries(this.fixtures.relations, (relation) => {
|
|
84
|
+
logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
|
|
85
|
+
return this.addFixturesForRelation(relation, localOptions);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Find methods - use the local fixtures
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* ### Find Model Fixture
|
|
95
|
+
* Finds a model fixture based on model name
|
|
96
|
+
* @api private
|
|
97
|
+
* @param {String} modelName
|
|
98
|
+
* @returns {Object} model fixture
|
|
99
|
+
*/
|
|
100
|
+
findModelFixture(modelName) {
|
|
101
|
+
return _.find(this.fixtures.models, (modelFixture) => {
|
|
102
|
+
return modelFixture.name === modelName;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* ### Find Model Fixture Entry
|
|
108
|
+
* Find a single model fixture entry by model name & a matching expression for the FIND function
|
|
109
|
+
* @param {String} modelName
|
|
110
|
+
* @param {String|Object|Function} matchExpr
|
|
111
|
+
* @returns {Object} model fixture entry
|
|
112
|
+
*/
|
|
113
|
+
findModelFixtureEntry(modelName, matchExpr) {
|
|
114
|
+
return _.find(this.findModelFixture(modelName).entries, matchExpr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ### Find Model Fixtures
|
|
119
|
+
* Find a model fixture name & a matching expression for the FILTER function
|
|
120
|
+
* @param {String} modelName
|
|
121
|
+
* @param {String|Object|Function} matchExpr
|
|
122
|
+
* @returns {Object} model fixture
|
|
123
|
+
*/
|
|
124
|
+
findModelFixtures(modelName, matchExpr) {
|
|
125
|
+
const foundModel = _.cloneDeep(this.findModelFixture(modelName));
|
|
126
|
+
foundModel.entries = _.filter(foundModel.entries, matchExpr);
|
|
127
|
+
return foundModel;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* ### Find Relation Fixture
|
|
132
|
+
* Find a relation fixture by from & to models
|
|
133
|
+
* @api private
|
|
134
|
+
* @param {String} from
|
|
135
|
+
* @param {String} to
|
|
136
|
+
* @returns {Object} relation fixture
|
|
137
|
+
*/
|
|
138
|
+
findRelationFixture(from, to) {
|
|
139
|
+
return _.find(this.fixtures.relations, (relation) => {
|
|
140
|
+
return relation.from.model === from && relation.to.model === to;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* ### Find Permission Relations For Object
|
|
146
|
+
* Specialist function can return the permission relation fixture with only entries for a particular object.model
|
|
147
|
+
* @param {String} objName
|
|
148
|
+
* @returns {Object} fixture relation
|
|
149
|
+
*/
|
|
150
|
+
findPermissionRelationsForObject(objName, role) {
|
|
151
|
+
// Make a copy and delete any entries we don't want
|
|
152
|
+
const foundRelation = _.cloneDeep(this.findRelationFixture('Role', 'Permission'));
|
|
153
|
+
|
|
154
|
+
_.each(foundRelation.entries, (entry, key) => {
|
|
155
|
+
_.each(entry, (perm, obj) => {
|
|
156
|
+
if (obj !== objName) {
|
|
157
|
+
delete entry[obj];
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (_.isEmpty(entry) || (role && role !== key)) {
|
|
162
|
+
delete foundRelation.entries[key];
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return foundRelation;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/******************************************************
|
|
170
|
+
* From here down, the methods require access to models
|
|
171
|
+
* But aren't dependent on this.fixtures
|
|
172
|
+
******************************************************/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* ### Fetch Relation Data
|
|
176
|
+
* Before we build relations we need to fetch all of the models from both sides so that we can
|
|
177
|
+
* use filter and find to quickly locate the correct models.
|
|
178
|
+
* @api private
|
|
179
|
+
* @param {{from, to, entries}} relation
|
|
180
|
+
* @returns {Promise<*>}
|
|
181
|
+
*/
|
|
182
|
+
fetchRelationData(relation, options) {
|
|
183
|
+
const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
|
|
184
|
+
|
|
185
|
+
const props = {
|
|
186
|
+
from: models[relation.from.model].findAll(fromOptions),
|
|
187
|
+
to: models[relation.to.model].findAll(options)
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return Promise.props(props);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* ### Add Fixtures for Model
|
|
195
|
+
* Takes a model fixture, with a name and some entries and processes these
|
|
196
|
+
* into a sequence of promises to get each fixture added.
|
|
197
|
+
*
|
|
198
|
+
* @param {{name, entries}} modelFixture
|
|
199
|
+
* @returns {Promise<any>}
|
|
200
|
+
*/
|
|
201
|
+
async addFixturesForModel(modelFixture, options = {}) {
|
|
202
|
+
// Clone the fixtures as they get changed in this function.
|
|
203
|
+
// The initial blog posts will be added a `published_at` property, which
|
|
204
|
+
// would change the fixturesHash.
|
|
205
|
+
modelFixture = _.cloneDeep(modelFixture);
|
|
206
|
+
// The Post model fixtures need a `published_at` date, where at least the seconds
|
|
207
|
+
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
|
208
|
+
// them.
|
|
209
|
+
if (modelFixture.name === 'Post') {
|
|
210
|
+
_.forEach(modelFixture.entries, (post, index) => {
|
|
211
|
+
if (!post.published_at) {
|
|
212
|
+
post.published_at = moment().add(index, 'seconds');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
218
|
+
let data = {};
|
|
219
|
+
|
|
220
|
+
// CASE: if id is specified, only query by id
|
|
221
|
+
if (entry.id) {
|
|
222
|
+
data.id = entry.id;
|
|
223
|
+
} else if (entry.slug) {
|
|
224
|
+
data.slug = entry.slug;
|
|
225
|
+
} else {
|
|
226
|
+
data = _.cloneDeep(entry);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (modelFixture.name === 'Post') {
|
|
230
|
+
data.status = 'all';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const found = await models[modelFixture.name].findOne(data, options);
|
|
234
|
+
if (!found) {
|
|
235
|
+
return models[modelFixture.name].add(entry, options);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return {expected: modelFixture.entries.length, done: _.compact(results).length};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* ## Add Fixtures for Relation
|
|
244
|
+
* Takes a relation fixtures object, with a from, to and some entries and processes these
|
|
245
|
+
* into a sequence of promises, to get each fixture added.
|
|
246
|
+
*
|
|
247
|
+
* @param {{from, to, entries}} relationFixture
|
|
248
|
+
* @returns {Promise<any>}
|
|
249
|
+
*/
|
|
250
|
+
async addFixturesForRelation(relationFixture, options) {
|
|
251
|
+
const ops = [];
|
|
252
|
+
let max = 0;
|
|
253
|
+
|
|
254
|
+
const data = await this.fetchRelationData(relationFixture, options);
|
|
255
|
+
|
|
256
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
257
|
+
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
|
258
|
+
|
|
259
|
+
// CASE: You add new fixtures e.g. a new role in a new release.
|
|
260
|
+
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
|
261
|
+
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
|
262
|
+
if (!fromItem) {
|
|
263
|
+
logging.warn('Skip: Target database entry not found for key: ' + key);
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_.each(entry, (value, entryKey) => {
|
|
268
|
+
let toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
|
269
|
+
max += toItems.length;
|
|
270
|
+
|
|
271
|
+
// Remove any duplicates that already exist in the collection
|
|
272
|
+
toItems = _.reject(toItems, (item) => {
|
|
273
|
+
return fromItem
|
|
274
|
+
.related(relationFixture.from.relation)
|
|
275
|
+
.find((model) => {
|
|
276
|
+
const objectToMatch = FixtureManager.matchObj(relationFixture.to.match, item);
|
|
277
|
+
return Object.keys(objectToMatch).every((keyToCheck) => {
|
|
278
|
+
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (toItems && toItems.length > 0) {
|
|
284
|
+
ops.push(function addRelationItems() {
|
|
285
|
+
return baseUtils.attach(
|
|
286
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
287
|
+
fromItem.id,
|
|
288
|
+
relationFixture.from.relation,
|
|
289
|
+
toItems,
|
|
290
|
+
options
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const result = await sequence(ops);
|
|
298
|
+
return {expected: max, done: _(result).map('length').sum()};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async removeFixturesForModel(modelFixture, options) {
|
|
302
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
303
|
+
const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
|
|
304
|
+
if (found) {
|
|
305
|
+
return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return {expected: modelFixture.entries.length, done: results.length};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async removeFixturesForRelation(relationFixture, options) {
|
|
313
|
+
const data = await this.fetchRelationData(relationFixture, options);
|
|
314
|
+
const ops = [];
|
|
315
|
+
|
|
316
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
317
|
+
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
|
318
|
+
|
|
319
|
+
_.each(entry, (value, entryKey) => {
|
|
320
|
+
const toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
|
321
|
+
|
|
322
|
+
if (toItems && toItems.length > 0) {
|
|
323
|
+
ops.push(function detachRelation() {
|
|
324
|
+
return baseUtils.detach(
|
|
325
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
326
|
+
fromItem.id,
|
|
327
|
+
relationFixture.from.relation,
|
|
328
|
+
toItems,
|
|
329
|
+
options
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return await sequence(ops);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = FixtureManager;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const FixtureManager = require('./fixture-manager');
|
|
2
|
+
const config = require('../../../../shared/config');
|
|
3
|
+
|
|
4
|
+
const fixturePath = config.get('paths').fixtures;
|
|
5
|
+
const fixtures = require(fixturePath);
|
|
6
|
+
|
|
7
|
+
module.exports.FixtureManager = FixtureManager;
|
|
8
|
+
module.exports.fixtureManager = new FixtureManager(fixtures);
|
|
@@ -3,7 +3,6 @@ const template = require('./template');
|
|
|
3
3
|
const settingsCache = require('../../../shared/settings-cache');
|
|
4
4
|
const urlUtils = require('../../../shared/url-utils');
|
|
5
5
|
const moment = require('moment-timezone');
|
|
6
|
-
const cheerio = require('cheerio');
|
|
7
6
|
const api = require('../../api');
|
|
8
7
|
const {URL} = require('url');
|
|
9
8
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
@@ -24,6 +23,8 @@ const formatHtmlForEmail = function formatHtmlForEmail(html) {
|
|
|
24
23
|
|
|
25
24
|
// convert juiced HTML to a DOM-like interface for further manipulation
|
|
26
25
|
// happens after inlining of CSS so we can change element types without worrying about styling
|
|
26
|
+
|
|
27
|
+
const cheerio = require('cheerio');
|
|
27
28
|
const _cheerio = cheerio.load(juicedHtml);
|
|
28
29
|
|
|
29
30
|
// force all links to open in new tab
|
|
@@ -243,6 +244,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
|
|
|
243
244
|
|
|
244
245
|
// perform any email specific adjustments to the mobiledoc->HTML render output
|
|
245
246
|
// body wrapper is required so we can get proper top-level selections
|
|
247
|
+
const cheerio = require('cheerio');
|
|
246
248
|
let _cheerio = cheerio.load(`<body>${post.html}</body>`);
|
|
247
249
|
// remove leading/trailing HRs
|
|
248
250
|
_cheerio(`
|
|
@@ -311,6 +313,8 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
|
|
|
311
313
|
};
|
|
312
314
|
|
|
313
315
|
function renderEmailForSegment(email, memberSegment) {
|
|
316
|
+
const cheerio = require('cheerio');
|
|
317
|
+
|
|
314
318
|
const result = {...email};
|
|
315
319
|
const $ = cheerio.load(result.html);
|
|
316
320
|
|
|
@@ -517,10 +517,12 @@ figure blockquote p {
|
|
|
517
517
|
display: block;
|
|
518
518
|
text-decoration: none !important;
|
|
519
519
|
}
|
|
520
|
+
|
|
520
521
|
.kg-video-preview table {
|
|
521
522
|
background-size: cover;
|
|
522
523
|
min-height: 200px; /* for when images aren't loaded */
|
|
523
524
|
}
|
|
525
|
+
|
|
524
526
|
.kg-video-play-button {
|
|
525
527
|
height: 2em;
|
|
526
528
|
width: 3em;
|
|
@@ -530,6 +532,7 @@ figure blockquote p {
|
|
|
530
532
|
font-size: 1em; /* change this to resize */
|
|
531
533
|
background-color: rgba(0,0,0,0.85);
|
|
532
534
|
}
|
|
535
|
+
|
|
533
536
|
.kg-video-play-button div {
|
|
534
537
|
display: block;
|
|
535
538
|
width: 0;
|
|
@@ -541,6 +544,72 @@ figure blockquote p {
|
|
|
541
544
|
border-width: 0.8em 0 0.8em 1.5em;
|
|
542
545
|
}
|
|
543
546
|
|
|
547
|
+
.kg-nft-link {
|
|
548
|
+
display: block;
|
|
549
|
+
text-decoration: none !important;
|
|
550
|
+
color: #15212A !important;
|
|
551
|
+
font-family: inherit !important;
|
|
552
|
+
font-size: 14px;
|
|
553
|
+
line-height: 1.3em;
|
|
554
|
+
padding-top: 4px;
|
|
555
|
+
padding-right: 20px;
|
|
556
|
+
padding-left: 20px;
|
|
557
|
+
padding-bottom: 4px;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.kg-card-callout {
|
|
561
|
+
display: flex;
|
|
562
|
+
margin: 0 0 1.5em 0;
|
|
563
|
+
padding: 20px 28px;
|
|
564
|
+
border-radius: 3px;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.kg-card-callout p {
|
|
568
|
+
margin: 0
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.kg-card-callout-grey {
|
|
572
|
+
background: #eef0f2;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.kg-card-callout-white {
|
|
576
|
+
background: #fff;
|
|
577
|
+
box-shadow: inset 0 0 0 1px #dddedf;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.kg-card-callout-blue {
|
|
581
|
+
background: #E9F6FB;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.kg-card-callout-green {
|
|
585
|
+
background: #E8F8EA;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.kg-card-callout-yellow {
|
|
589
|
+
background: #FCF4E3;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.kg-card-callout-red {
|
|
593
|
+
background: #FBE9E9;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.kg-card-callout-pink {
|
|
597
|
+
background: #FCEEF8;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.kg-card-callout-purple {
|
|
601
|
+
background: #F2EDFC;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.kg-card-callout-accent {
|
|
605
|
+
background: var(--ghost-accent-color);
|
|
606
|
+
color: #fff;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.kg-callout-emoji {
|
|
610
|
+
padding-right: 12px;
|
|
611
|
+
font-size: 20px;
|
|
612
|
+
}
|
|
544
613
|
|
|
545
614
|
/* -------------------------------------
|
|
546
615
|
HEADER, FOOTER, MAIN
|
|
@@ -614,7 +683,6 @@ figure blockquote p {
|
|
|
614
683
|
margin: 0;
|
|
615
684
|
padding: 12px 25px;
|
|
616
685
|
text-decoration: none;
|
|
617
|
-
text-transform: capitalize;
|
|
618
686
|
}
|
|
619
687
|
|
|
620
688
|
.btn-primary table td {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./oembed').ICustomProvider} ICustomProvider
|
|
3
|
+
* @typedef {import('./oembed').IExternalRequest} IExternalRequest
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const OPENSEA_PATH_REGEX = /^\/assets\/(0x[a-f0-9]+)\/(\d+)/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @implements ICustomProvider
|
|
10
|
+
*/
|
|
11
|
+
class NFTOEmbedProvider {
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} dependencies
|
|
14
|
+
*/
|
|
15
|
+
constructor(dependencies) {
|
|
16
|
+
this.dependencies = dependencies;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {URL} url
|
|
21
|
+
* @returns {Promise<boolean>}
|
|
22
|
+
*/
|
|
23
|
+
async canSupportRequest(url) {
|
|
24
|
+
return url.host === 'opensea.io' && OPENSEA_PATH_REGEX.test(url.pathname);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {URL} url
|
|
29
|
+
* @param {IExternalRequest} externalRequest
|
|
30
|
+
*
|
|
31
|
+
* @returns {Promise<object>}
|
|
32
|
+
*/
|
|
33
|
+
async getOEmbedData(url, externalRequest) {
|
|
34
|
+
const [match, transaction, asset] = url.pathname.match(OPENSEA_PATH_REGEX);
|
|
35
|
+
if (!match) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
|
|
39
|
+
json: true
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
version: '1.0',
|
|
43
|
+
type: 'nft',
|
|
44
|
+
title: result.body.name,
|
|
45
|
+
author_name: result.body.creator.user.username,
|
|
46
|
+
author_url: `https://opensea.io/${result.body.creator.user.username}`,
|
|
47
|
+
provider_name: 'OpenSea',
|
|
48
|
+
provider_url: 'https://opensea.io',
|
|
49
|
+
image_url: result.body.image_url,
|
|
50
|
+
creator_name: result.body.creator.user.username,
|
|
51
|
+
description: result.body.description,
|
|
52
|
+
collection_name: result.body.collection.name
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = NFTOEmbedProvider;
|