Skip to content

Commit 5b49197

Browse files
committed
Resolve links into node.__links
So CMS can avoid circular references by omitting __links from each node in contentModel. Find a better way for this omission though. Extract linking related logic into its own lib/linking.js. And ContentModelEntryNode implements some linking related methods. Resolve all links as soon as contentModel subtree is built. The links are kept all together in nodes's __link attribute. Serialize links into nodes only during render. Posts need to update their linked-field facets, so now they do it also just for rendering. Tests needed for the new linking.js
1 parent b08ef66 commit 5b49197

File tree

9 files changed

+182
-108
lines changed

9 files changed

+182
-108
lines changed

src/cms/api/models/contentModel.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
const createContentModelModel = ({ getContentModel }) => {
22
return {
3-
get: getContentModel
3+
get() {
4+
// TODO: find a nicer way for contentModel to serialize for cms
5+
return JSON.parse(JSON.stringify(getContentModel(), (key, value) => {
6+
if (key === '__links') {
7+
return undefined
8+
}
9+
return value
10+
}))
11+
}
412
}
513
}
614

src/compiler/contentModel/collection/category.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Category extends ContentModelEntryNode {
4343
static serialize(category) {
4444
const data = {
4545
...category,
46+
...category.serializeLinks(),
4647
facets: category.facets.map(models.facet().serialize),
4748
posts: category.subtree.posts.map(models.Post.serialize),
4849
levelPosts: category.subtree.levelPosts.map(models.Post.serialize),
@@ -188,7 +189,10 @@ class Category extends ContentModelEntryNode {
188189
})
189190

190191
this.facets = models.facet().collectFacets(
191-
this.subtree.posts,
192+
this.subtree.posts.map(post => ({
193+
...post,
194+
...post.serializeLinks()
195+
})),
192196
this.settings.facetKeys,
193197
childContext
194198
)

src/compiler/contentModel/collection/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Collection extends ContentModelEntryNode {
5050
static serialize(collection) {
5151
const data = {
5252
...collection,
53+
...collection.serializeLinks(),
5354
facets: collection.facets.map(models.facet().serialize),
5455
categories: collection.subtree.categories.map(models.Category.serialize),
5556
posts: collection.subtree.posts.map(models.Post.serialize),
@@ -258,7 +259,10 @@ class Collection extends ContentModelEntryNode {
258259
})
259260

260261
this.facets = models.facet().collectFacets(
261-
this.subtree.posts,
262+
this.subtree.posts.map(post => ({
263+
...post,
264+
...post.serializeLinks()
265+
})),
262266
facetKeys,
263267
childContext
264268
)

src/compiler/contentModel/collection/post.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,19 @@ const defaultSettings = {
1616
}
1717
class Post extends ContentModelEntryNode {
1818
static serialize(post) {
19-
return {
19+
const postWithSerializedLinks = {
2020
...post,
21+
...post.serializeLinks()
22+
}
23+
// TODO: feels like collection should handle this
24+
models.facet().linkWithEntryFields(
25+
postWithSerializedLinks,
26+
post.collectionFacets,
27+
post.settings.facetKeys
28+
)
29+
30+
return {
31+
...postWithSerializedLinks,
2132
attachments: post.subtree.attachments.map(models.Attachment.serialize)
2233
}
2334
}
@@ -52,9 +63,7 @@ class Post extends ContentModelEntryNode {
5263
}
5364

5465
afterEffects(contentModel, collectionFacets) {
55-
// TODO: feels like collection should handle this
56-
models.facet().linkWithEntryFields(this, collectionFacets, this.settings.facetKeys)
57-
66+
this.collectionFacets = collectionFacets
5867
this.subtree.attachments.forEach(attachment => {
5968
attachment.afterEffects(contentModel)
6069
})

src/compiler/contentModel/homepage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Homepage extends ContentModelEntryNode {
1414
static serialize(homepage) {
1515
return {
1616
...homepage,
17+
...homepage.serializeLinks(),
1718
attachments: homepage.subtree.attachments.map(models.Attachment.serialize)
1819
}
1920
}

src/compiler/contentModel/index.js

Lines changed: 11 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -14,104 +14,6 @@ const models = {
1414
Asset: require('./asset')
1515
}
1616

17-
const findLinkedNode = (allNodes, linkPath) => {
18-
const leafSlug = linkPath.pop()
19-
const leafRe = new RegExp(`^${leafSlug}$`, 'i')
20-
const leafMatches = allNodes.filter(p => p.slug.match(leafRe))
21-
22-
if (!leafMatches.length) {
23-
return undefined
24-
}
25-
26-
if (leafMatches.length === 1) {
27-
return leafMatches[0]
28-
}
29-
30-
if (!linkPath.length) {
31-
return undefined
32-
}
33-
34-
const paths = linkPath.reverse()
35-
return leafMatches.find(node => {
36-
let ctx = node.context
37-
for (const path of paths) {
38-
ctx = ctx.throwUntil(item => {
39-
return item.slug?.match(new RegExp(`^${path}$`, 'i'))
40-
})
41-
}
42-
return !!ctx.items.length
43-
})
44-
}
45-
46-
const linkNodes = (nodes) => {
47-
nodes.forEach(node => {
48-
Object.keys(node).forEach(key => {
49-
const value = node[key]
50-
if (Array.isArray(value)) {
51-
for (let i = 0; i < value.length; i++) {
52-
let valueItem = value[i]
53-
if (!valueItem.linkPath) {
54-
break
55-
}
56-
const linkedNode = findLinkedNode(nodes, valueItem.linkPath)
57-
if (linkedNode) {
58-
node[key][i] = Object.assign({}, linkedNode)
59-
linkBack(node, linkedNode, key)
60-
} else {
61-
node[key].splice(i, 1)
62-
i--
63-
}
64-
}
65-
} else {
66-
if (!value?.linkPath) {
67-
return
68-
}
69-
const linkedNode = findLinkedNode(nodes, value.linkPath)
70-
if (linkedNode) {
71-
node[key] = Object.assign({}, linkedNode)
72-
linkBack(node, linkedNode, key)
73-
} else {
74-
node[key] = undefined
75-
}
76-
}
77-
})
78-
})
79-
}
80-
81-
const linkBack = (post, entry, key) => {
82-
if (entry.schema) {
83-
Object.keys(entry.schema).forEach(schemaKey => {
84-
const schemaValue = entry.schema[schemaKey]
85-
const isSchemaValueArray = Array.isArray(schemaValue)
86-
const re = new RegExp(`^\\+(${post.contentType}|):${key}$`)
87-
const match = isSchemaValueArray ?
88-
schemaValue.find(v => re.test(v)) :
89-
re.test(schemaValue)
90-
if (match) {
91-
if (isSchemaValueArray) {
92-
// console.log('linking', post.title, 'to', schemaKey, 'field of', entry.title)
93-
entry[schemaKey] = entry[schemaKey] || []
94-
entry[schemaKey].push(post)
95-
} else {
96-
entry[schemaKey] = post
97-
}
98-
}
99-
})
100-
return
101-
}
102-
entry.links = entry.links || {}
103-
entry.links.relations = entry.links.relations || []
104-
const relation = entry.links.relations.find(r => r.key === key)
105-
if (relation) {
106-
relation.entries.push(post)
107-
} else {
108-
entry.links.relations.push({
109-
key,
110-
entries: [post]
111-
})
112-
}
113-
}
114-
11517
const defaultSettings = {
11618
permalinkPrefix: '/',
11719
out: resolve('.'),
@@ -129,6 +31,8 @@ const defaultSettings = {
12931
class ContentModel extends ContentModelEntryNode {
13032
static serialize(contentModel) {
13133
return {
34+
...contentModel,
35+
...contentModel.serializeLinks(),
13236
homepage: models.Homepage.serialize(contentModel.subtree.homepage),
13337
subpages: contentModel.subtree.subpages.map(models.Subpage.serialize),
13438
collections: contentModel.subtree.collections.map(models.Collection.serialize),
@@ -286,7 +190,7 @@ class ContentModel extends ContentModelEntryNode {
286190
}]
287191
}
288192

289-
afterEffects() {
193+
linkNodes() {
290194
const flatMapDeepCategories = (container) => {
291195
return _.flatMapDeep(container, ({ subtree }) => {
292196
if (subtree.categories.length) {
@@ -299,12 +203,18 @@ class ContentModel extends ContentModelEntryNode {
299203
})
300204
}
301205

302-
linkNodes([
206+
const nodes = [
303207
...this.subtree.subpages,
304208
...this.subtree.collections,
305209
...flatMapDeepCategories(this.subtree.collections),
306210
..._.flatMap(this.subtree.collections, ({ subtree }) => subtree.posts)
307-
])
211+
]
212+
213+
nodes.forEach(node => node.resolveLinks(nodes))
214+
}
215+
216+
afterEffects() {
217+
this.linkNodes()
308218

309219
this.subtree.collections.forEach(collection => {
310220
collection.afterEffects(this.subtree)

src/compiler/contentModel/subpage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Subpage extends ContentModelEntryNode {
1414
static serialize(subpage) {
1515
return {
1616
...subpage,
17+
...subpage.serializeLinks(),
1718
attachments: subpage.subtree.attachments.map(models.Attachment.serialize)
1819
}
1920
}

src/lib/ContentModelEntryNode.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
const { join } = require('path')
22
const makeSlug = require('slug')
3+
const _ = require('lodash')
34
const { makePermalink } = require('./contentModelHelpers')
45
const { parseTextEntry } = require('./parseTextEntry')
6+
const {
7+
findLinkedNode,
8+
addLinkBack,
9+
serializeLinks,
10+
resolveLinks
11+
} = require('./linking')
512
const ContentModelNode = require('./ContentModelNode')
613

714
class ContentModelEntryNode extends ContentModelNode {
815
constructor(fsNode, context, settings = {}) {
916
super(fsNode, context, settings)
1017

18+
this.__links = []
1119
this.indexFile = this.getIndexFile()
1220

1321
const isFlatData = !fsNode.stats?.birthtime
@@ -107,6 +115,26 @@ class ContentModelEntryNode extends ContentModelNode {
107115
return this.settings.mode === 'start' || !entry.draft
108116
}
109117

118+
getLinks() {
119+
return this.__links
120+
}
121+
122+
addLink(keyPath, node) {
123+
this.__links.push({ keyPath, node })
124+
}
125+
126+
addLinkBack(post, key) {
127+
addLinkBack(this, post, key)
128+
}
129+
130+
resolveLinks(nodes) {
131+
resolveLinks(this, nodes)
132+
}
133+
134+
serializeLinks() {
135+
return serializeLinks(this)
136+
}
137+
110138
afterEffects(contentModel) {}
111139

112140
render(renderer) {}

0 commit comments

Comments
 (0)