• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

scriptype / writ-cms / 18160146944

01 Oct 2025 10:55AM UTC coverage: 72.83% (-2.7%) from 75.488%
18160146944

push

github

scriptype
[CM2] Introduce facets

An interactive rebase of months worth of thinking, doing and not doing.

Faceting is similar to the earlier tag indexing. With facets, posts can be indexed by any fields, not just tags.

With this, tag indexing is no longer a special case. There's no need for modelling or rendering tags.

Facets are created based on 'facetKeys' of a contentType. A simple array of keys works.

Now, each collection has a new route: '/by' which is listing all available facet views in the collection.

And each facet view has a route: /by/facet-name. E.g. /by/author, /by/date, /by/tags. In that page, possible values for the facet can be listed.

And each possible value for the facet has the route: /by/facet-name/value. E.g. /by/author/enes, /by/date/1970-11-23, /by/tags/css. There, posts matching the criteria can be shown.

These new render targets should be implemented inside the theme, and that's what should come soon. So far, working with a local laboratory that has a custom theme.

A notable side-effect of turning a field into a facet is that the field becomes an object in the shape of: { value, facetPermalink }. So, if 'date' field is a facet, templates should do: {{date.value}} to display its value and {{date.facetPermalink}} to render a link to the facet value page in the collection. The name 'facetPermalink' is so ugly tho.

If a facet field links to another entry, then the field's value, which is populated with the linked entry, just gets a 'facetPermalink', to make it possible to render links both to the linked entry and to the facet value page.

Facets work across deep category tree. So, all of the mentioned routes exist at each stop.

E.g.
site.com/photos/by/subject,
site.com/photos/astro/by/subject,
site.com/photos/astro/deep-space/by-subject

An 'afterEffects' mechanism is invented for models. Facets needed to be collected after the tree-building and linking are finished. So, all the way from the root, each model does whatever side-eff... (continued)

495 of 810 branches covered (61.11%)

Branch coverage included in aggregate %.

45 of 144 new or added lines in 8 files covered. (31.25%)

8 existing lines in 3 files now uncovered.

2282 of 3003 relevant lines covered (75.99%)

242.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.1
/src/compiler/contentModel2/helpers.js
1
const marked = require('marked')
7✔
2

3
const templateExtensions = [
7✔
4
  '.hbs',
5
  '.handlebars',
6
  '.md',
7
  '.markdown',
8
  '.txt',
9
  '.text',
10
  '.html'
11
]
12

13
const isTemplateFile = (node) => {
7✔
14
  return new RegExp(templateExtensions.join('|'), 'i').test(node.extension)
364✔
15
}
16

17
const removeExtension = (fileName) => {
7✔
18
  if (fileName.lastIndexOf('.') > 0) {
56✔
19
    return fileName.replace(/(\.[^.]+)?$/, '')
49✔
20
  }
21
  return fileName
7✔
22
}
23

24
const parseArray = (array = []) => {
7!
25
  return typeof array === 'string' ?
56✔
26
    array.split(',').map(t => t.trim()) :
126✔
27
    array
28
}
29

30
const makePermalink = (...parts) => {
7✔
31
  if (parts[0] === '/') {
105!
32
    return parts[0] + parts.slice(1).join('/')
×
33
  }
34
  return parts.join('/')
105✔
35
}
36

37
const makeDateSlug = (date) => {
7✔
NEW
38
  return date.toISOString().split('T')[0]
×
39
}
40

41
const Markdown = {
7✔
42
  parse(text) {
43
    return Markdown.unescapeHandlebarsExpressions(
42✔
44
      marked.parse(
45
        text.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, '')
46
      )
47
    )
48
  },
49

50
  unescapeHandlebarsExpressions(html) {
51
    // partial greater sign
52
    html = html.replace(/{{>/g, '{{>')
42✔
53
    // helpers
54
    html = html.replace(/{{(.+)(?:"|')(.+)(?:"|')(.*)}}/g, '{{\$1"\$2"\$3}}')
42✔
55
    // partials
56
    html = html.replace(/{{>(.+)(?:"|')(.+)(?:"|').*}}/g, '{{>\$1"\$2"\$3}}')
42✔
57
    return html
42✔
58
  },
59
}
60

61
module.exports = {
7✔
62
  templateExtensions,
63
  isTemplateFile,
64
  removeExtension,
65
  parseArray,
66
  makePermalink,
67
  makeDateSlug,
68
  Markdown
69
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc