Skip to content

Commit e8ef914

Browse files
committed
Link nodes using a flat list of all nodes
Instead of traversing the contentModel's collections and categories, flatten all nodes (including collections, categories, subpages and posts) and link them. Now, the linking logic is not coupled with contentModel structure. Also linking works in a more flexible way. Linking to a node can be as simple as: myNode: +my-node given my-node is the slug of the node to link to, and there are no other nodes with the same slug. If there are multiple nodes with the same slug, then a path to node should be specified, like this: myNode: +this/way/my-node and 'this' can be a category of any depth, as well as a collection. Otherwise link is not added. New e2e tests needed to verify all this. Currently, if multiple nodes in a category share the same slug, and if the path specified is just the category slug, then the first match is linked. Maybe this is inconsistent with the result of other same-slug scenario (when no path is specified).
1 parent a333c1c commit e8ef914

1 file changed

Lines changed: 80 additions & 75 deletions

File tree

src/compiler/contentModel/index.js

Lines changed: 80 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { resolve } = require('path')
2+
const _ = require('lodash')
23
const ImmutableStack = require('../../lib/ImmutableStack')
34
const { removeExtension } = require('../../lib/contentModelHelpers')
45
const ContentModelEntryNode = require('../../lib/ContentModelEntryNode')
@@ -16,47 +17,74 @@ const models = {
1617
const LINKED_FIELD_SYNTAX = /^\+[^ ]+$/
1718

1819
const parseLink = (value) => {
19-
const [collectionSlug, ...restPath] = value.replace(/^\+/g, '').split('/')
20-
const entrySlug = restPath.pop()
21-
const categorySlugs = restPath
22-
return {
23-
collectionSlug,
24-
categorySlugs,
25-
entrySlug
26-
}
20+
return value.replace(/^\+/g, '').split('/').filter(Boolean)
2721
}
2822

29-
const findLinkedEntry = (contentModel, link) => {
30-
const collectionSlugRe = new RegExp(link.collectionSlug, 'i')
31-
const collection = contentModel.subtree.collections.find(c => c.slug.match(collectionSlugRe))
32-
let container = collection
23+
const findLinkedNode = (allNodes, linkPath) => {
24+
const leafSlug = linkPath.pop()
25+
const leafRe = new RegExp(`^${leafSlug}$`, 'i')
26+
const leafMatches = allNodes.filter(p => p.slug.match(leafRe))
3327

34-
for (const categorySlug of link.categorySlugs) {
35-
const categorySlugRe = new RegExp(categorySlug, 'i')
36-
const category = container.subtree.categories?.find(c => c.slug.match(categorySlugRe))
37-
if (!category) {
38-
break
39-
}
40-
container = category
28+
if (!leafMatches.length) {
29+
return undefined
4130
}
4231

43-
const entrySlugRe = new RegExp(link.entrySlug, 'i')
44-
const queue = [container]
45-
while (queue.length > 0) {
46-
const node = queue.shift()
47-
if (node.slug?.match(entrySlugRe)) {
48-
return node
49-
}
50-
const match = node.subtree.levelPosts?.find(p => p.slug.match(entrySlugRe))
51-
if (match) {
52-
return match
53-
}
54-
if (node.subtree.categories) {
55-
queue.push(...node.subtree.categories)
56-
}
32+
if (leafMatches.length === 1) {
33+
return leafMatches[0]
5734
}
5835

59-
return undefined
36+
if (!linkPath.length) {
37+
return undefined
38+
}
39+
40+
const paths = linkPath.reverse()
41+
return leafMatches.find(node => {
42+
let ctx = node.context
43+
for (const path of paths) {
44+
ctx = ctx.throwUntil(item => {
45+
return item.slug?.match(new RegExp(`^${path}$`, 'i'))
46+
})
47+
}
48+
return !!ctx.items.length
49+
})
50+
}
51+
52+
const linkNodes = (nodes) => {
53+
nodes.forEach(node => {
54+
const fields = Object.keys(node)
55+
Object.keys(node).forEach(key => {
56+
const value = node[key]
57+
if (Array.isArray(value)) {
58+
for (let i = 0; i < value.length; i++) {
59+
let valueItem = value[i]
60+
if (!LINKED_FIELD_SYNTAX.test(valueItem)) {
61+
break
62+
}
63+
const link = parseLink(valueItem)
64+
const linkedNode = findLinkedNode(nodes, link)
65+
if (linkedNode) {
66+
node[key][i] = Object.assign({}, linkedNode)
67+
linkBack(node, linkedNode, key)
68+
} else {
69+
node[key].splice(i, 1)
70+
i--
71+
}
72+
}
73+
} else {
74+
if (!LINKED_FIELD_SYNTAX.test(value)) {
75+
return
76+
}
77+
const link = parseLink(value)
78+
const linkedNode = findLinkedNode(nodes, link)
79+
if (linkedNode) {
80+
node[key] = Object.assign({}, linkedNode)
81+
linkBack(node, linkedNode, key)
82+
} else {
83+
node[key] = undefined
84+
}
85+
}
86+
})
87+
})
6088
}
6189

6290
const linkBack = (post, entry, key) => {
@@ -93,46 +121,6 @@ const linkBack = (post, entry, key) => {
93121
}
94122
}
95123

96-
const linkEntries = (contentModel) => {
97-
contentModel.subtree.collections.forEach(collection => {
98-
collection.subtree.posts.forEach(post => {
99-
const fields = Object.keys(post)
100-
Object.keys(post).forEach(key => {
101-
const value = post[key]
102-
if (Array.isArray(value)) {
103-
for (let i = 0; i < value.length; i++) {
104-
let valueItem = value[i]
105-
if (!LINKED_FIELD_SYNTAX.test(valueItem)) {
106-
break
107-
}
108-
const link = parseLink(valueItem)
109-
const entry = findLinkedEntry(contentModel, link)
110-
if (entry) {
111-
post[key][i] = Object.assign({}, entry)
112-
linkBack(post, entry, key)
113-
} else {
114-
post[key].splice(i, 1)
115-
i--
116-
}
117-
}
118-
} else {
119-
if (!LINKED_FIELD_SYNTAX.test(value)) {
120-
return
121-
}
122-
const link = parseLink(value)
123-
const entry = findLinkedEntry(contentModel, link)
124-
if (entry) {
125-
post[key] = Object.assign({}, entry)
126-
linkBack(post, entry, key)
127-
} else {
128-
post[key] = undefined
129-
}
130-
}
131-
})
132-
})
133-
})
134-
}
135-
136124
const defaultSettings = {
137125
permalinkPrefix: '/',
138126
out: resolve('.'),
@@ -308,7 +296,24 @@ class ContentModel extends ContentModelEntryNode {
308296
}
309297

310298
afterEffects() {
311-
linkEntries(this)
299+
const flatMapDeepCategories = (container) => {
300+
return _.flatMapDeep(container, ({ subtree }) => {
301+
if (subtree.categories.length) {
302+
return [
303+
subtree.categories,
304+
flatMapDeepCategories(subtree.categories)
305+
]
306+
}
307+
return []
308+
})
309+
}
310+
311+
linkNodes([
312+
...this.subtree.subpages,
313+
...this.subtree.collections,
314+
...flatMapDeepCategories(this.subtree.collections),
315+
..._.flatMap(this.subtree.collections, ({ subtree }) => subtree.posts)
316+
])
312317

313318
this.subtree.collections.forEach(collection => {
314319
collection.afterEffects(this.subtree)

0 commit comments

Comments
 (0)