@lullabot/eleventy-decision-records 1.0.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/package.json +94 -0
  4. package/src/YYYYMMDD-decision-template.md +41 -0
  5. package/src/about.md +36 -0
  6. package/src/assets/fonts/OFL.txt +93 -0
  7. package/src/assets/fonts/gabarito-v9-latin-500.woff2 +0 -0
  8. package/src/assets/fonts/gabarito-v9-latin-700.woff2 +0 -0
  9. package/src/assets/fonts/gabarito-v9-latin-regular.woff2 +0 -0
  10. package/src/assets/icons/facebook.svg +1 -0
  11. package/src/assets/icons/github.svg +1 -0
  12. package/src/assets/icons/linkedin.svg +1 -0
  13. package/src/assets/icons/mastodon.svg +1 -0
  14. package/src/assets/icons/x.svg +1 -0
  15. package/src/assets/icons/youtube.svg +1 -0
  16. package/src/assets/images/horizontal.jpg +0 -0
  17. package/src/assets/images/vertical.jpg +0 -0
  18. package/src/assets/js/scrollable.js +49 -0
  19. package/src/assets/js/search.js +189 -0
  20. package/src/assets/js/time-since.js +31 -0
  21. package/src/assets/styles/content.css +149 -0
  22. package/src/assets/styles/fonts.css +33 -0
  23. package/src/assets/styles/footer.css +65 -0
  24. package/src/assets/styles/layout.css +483 -0
  25. package/src/assets/styles/pages.css +348 -0
  26. package/src/assets/styles/recent-decisions.css +104 -0
  27. package/src/assets/styles/reset.css +107 -0
  28. package/src/assets/styles/search-dialog.css +196 -0
  29. package/src/assets/styles/tags.css +16 -0
  30. package/src/assets/styles/tokens.css +181 -0
  31. package/src/assets/styles/typography.css +182 -0
  32. package/src/assets/styles/view-transitions.css +67 -0
  33. package/src/config.js +38 -0
  34. package/src/defaultConfig.json +99 -0
  35. package/src/index.js +227 -0
  36. package/src/index.md +28 -0
  37. package/src/lib/collections/adrs.js +10 -0
  38. package/src/lib/collections/contributors.js +15 -0
  39. package/src/lib/collections/index.js +14 -0
  40. package/src/lib/collections/topics.js +18 -0
  41. package/src/lib/data/index.js +19 -0
  42. package/src/lib/filters/collections.js +63 -0
  43. package/src/lib/filters/dates.js +68 -0
  44. package/src/lib/filters/index.js +26 -0
  45. package/src/lib/filters/strings.js +23 -0
  46. package/src/lib/plugins/bundle.js +23 -0
  47. package/src/lib/plugins/index.js +16 -0
  48. package/src/lib/plugins/markdown.js +10 -0
  49. package/src/lib/plugins/rss.js +24 -0
  50. package/src/lib/plugins/syntax-highlight.js +8 -0
  51. package/src/lib/plugins/toc.js +11 -0
  52. package/src/lib/shortcodes/icons.js +91 -0
  53. package/src/lib/shortcodes/index.js +11 -0
  54. package/src/lib/shortcodes/search.js +32 -0
  55. package/src/templates/assets/favicon.njk +7 -0
  56. package/src/templates/assets/search_index.njk +13 -0
  57. package/src/templates/layouts/adr.njk +59 -0
  58. package/src/templates/layouts/page.njk +136 -0
  59. package/src/templates/pages/contributor.njk +51 -0
  60. package/src/templates/pages/contributors.njk +46 -0
  61. package/src/templates/pages/decisions.njk +41 -0
  62. package/src/templates/pages/practice-areas.njk +48 -0
  63. package/src/templates/pages/topics.njk +50 -0
  64. package/src/templates/partials/footer.njk +12 -0
  65. package/src/templates/partials/recent-decisions.njk +33 -0
  66. package/src/templates/partials/site-nav.njk +61 -0
@@ -0,0 +1,24 @@
1
+ import { feedPlugin } from '@11ty/eleventy-plugin-rss';
2
+
3
+ /**
4
+ * Register the Atom feed plugin, served at /feed.xml.
5
+ */
6
+ export function rss(eleventyConfig, site) {
7
+ eleventyConfig.addPlugin(feedPlugin, {
8
+ type: 'atom',
9
+ outputPath: '/feed.xml',
10
+ collection: {
11
+ name: 'adrs',
12
+ limit: 0,
13
+ },
14
+ metadata: {
15
+ language: 'en',
16
+ title: `${site.title} — ${site.organization}`,
17
+ subtitle: site.description,
18
+ base: site.url,
19
+ author: {
20
+ name: site.organization,
21
+ },
22
+ },
23
+ });
24
+ }
@@ -0,0 +1,8 @@
1
+ import pluginSyntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight';
2
+
3
+ /**
4
+ * Register the syntax highlighting plugin.
5
+ */
6
+ export function syntaxHighlight(eleventyConfig) {
7
+ eleventyConfig.addPlugin(pluginSyntaxHighlight);
8
+ }
@@ -0,0 +1,11 @@
1
+ import pluginTOC from 'eleventy-plugin-toc';
2
+
3
+ /**
4
+ * Register the table of contents plugin.
5
+ */
6
+ export function toc(eleventyConfig) {
7
+ eleventyConfig.addPlugin(pluginTOC, {
8
+ tags: ['h2', 'h3'],
9
+ wrapper: '',
10
+ });
11
+ }
@@ -0,0 +1,91 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { parse } from 'node-html-parser';
4
+
5
+ /**
6
+ * Resolves an icon name to a file path by checking each directory in
7
+ * order. The plugin assembles the list as: project icon overrides,
8
+ * then the theme's custom icons, then Material Symbols.
9
+ */
10
+ function resolveIcon(iconDirs, name) {
11
+ for (const dir of iconDirs) {
12
+ const path = join(dir, `${name}.svg`);
13
+ if (existsSync(path)) {
14
+ return path;
15
+ }
16
+ }
17
+ throw new Error(`Icon "${name}" not found in: ${iconDirs.join(', ')}`);
18
+ }
19
+
20
+ /**
21
+ * Returns a standalone SVG suitable for use as a favicon.
22
+ * Embeds a <style> block so the icon adapts to light/dark mode.
23
+ *
24
+ * Usage: {% favicon "brand_family-fill" %}
25
+ */
26
+ export function makeFavicon(iconDirs) {
27
+ return function favicon(name) {
28
+ const file = resolveIcon(iconDirs, name);
29
+ const root = parse(readFileSync(file, 'utf8'));
30
+ const svg = root.querySelector('svg');
31
+
32
+ svg.removeAttribute('width');
33
+ svg.removeAttribute('height');
34
+
35
+ const style = parse(`
36
+ <style>
37
+ svg {
38
+ background-color: #fff;
39
+ }
40
+
41
+ path {
42
+ fill: #01070b;
43
+ }
44
+
45
+ @media(prefers-color-scheme:dark) {
46
+ svg {
47
+ background-color: #01070b;
48
+ }
49
+ path {
50
+ fill: #fff;
51
+ }
52
+ }
53
+ </style>
54
+ `);
55
+ svg.insertAdjacentHTML('afterbegin', style.toString());
56
+
57
+ return root.toString();
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Inlines an SVG icon. Checks the project's icon directory first, then
63
+ * the theme's custom SVGs, then falls back to Material Symbols.
64
+ *
65
+ * Usage: {% icon "home-fill" %}
66
+ * {% icon "github", "GitHub" %}
67
+ */
68
+ export function makeIcon(iconDirs) {
69
+ return function icon(name, label) {
70
+ const file = resolveIcon(iconDirs, name);
71
+ const root = parse(readFileSync(file, 'utf8'));
72
+ const svg = root.querySelector('svg');
73
+
74
+ svg.removeAttribute('width');
75
+ svg.removeAttribute('height');
76
+ svg.setAttribute('fill', 'currentColor');
77
+
78
+ for (const el of svg.querySelectorAll('[fill]')) {
79
+ el.setAttribute('fill', 'currentColor');
80
+ }
81
+
82
+ if (label) {
83
+ svg.setAttribute('role', 'img');
84
+ svg.setAttribute('aria-label', label);
85
+ } else {
86
+ svg.setAttribute('aria-hidden', 'true');
87
+ }
88
+
89
+ return root.toString();
90
+ };
91
+ }
@@ -0,0 +1,11 @@
1
+ import { makeFavicon, makeIcon } from './icons.js';
2
+ import { oramaIndex } from './search.js';
3
+
4
+ /**
5
+ * Register all custom shortcodes with Eleventy.
6
+ */
7
+ export default function (eleventyConfig, opts) {
8
+ eleventyConfig.addShortcode('favicon', makeFavicon(opts.iconDirs));
9
+ eleventyConfig.addShortcode('icon', makeIcon(opts.iconDirs));
10
+ eleventyConfig.addShortcode('oramaIndex', oramaIndex);
11
+ }
@@ -0,0 +1,32 @@
1
+ import { create, insert, save } from '@orama/orama';
2
+ import striptags from 'striptags';
3
+ import he from 'he';
4
+ import { isoDate } from '../filters/dates.js';
5
+
6
+ const SCHEMA = {
7
+ url: 'string',
8
+ title: 'string',
9
+ topics: 'string[]',
10
+ context: 'string',
11
+ content: 'string',
12
+ status: 'string',
13
+ date: 'string',
14
+ };
15
+
16
+ export async function oramaIndex(adrs) {
17
+ const db = create({ schema: SCHEMA });
18
+
19
+ for (const adr of adrs) {
20
+ insert(db, {
21
+ url: adr.page.url,
22
+ title: adr.data.title,
23
+ topics: adr.data.topics || [],
24
+ context: adr.data.context || '',
25
+ content: he.decode(striptags(adr.content)),
26
+ status: adr.data.status,
27
+ date: isoDate(adr.data.date),
28
+ });
29
+ }
30
+
31
+ return JSON.stringify(save(db));
32
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ permalink: /favicon.svg
3
+ eleventyExcludeFromCollections: true
4
+ layout: false
5
+ ---
6
+
7
+ {% favicon site.icon %}
@@ -0,0 +1,13 @@
1
+ ---
2
+ layout: false
3
+ permalink: /searchindex.json
4
+ eleventyExcludeFromCollections: true
5
+ ---
6
+
7
+ {% filter spaceless %}
8
+ {% set rendercache %}
9
+ {% for adr in collections.adrs %}
10
+ {{ adr.content }}
11
+ {% endfor %}
12
+ {% endset %}
13
+ {% endfilter %}{% oramaIndex collections.adrs %}
@@ -0,0 +1,59 @@
1
+ ---
2
+ layout: page.njk
3
+ ---
4
+
5
+ <h1>{{ title }}</h1>
6
+
7
+ <dl class="adr-detail-meta">
8
+ <dt>Date</dt>
9
+ <dd>
10
+ <time datetime="{{ date | isoDate }}">{{ date | datetimeFormat }}</time>
11
+ </dd>
12
+
13
+ {%- if practiceArea %}
14
+ <dt>Practice Area</dt>
15
+ <dd>
16
+ <a href="/practice-areas/{{ practiceArea | slugify }}/"
17
+ >{{ practiceArea }}</a
18
+ >
19
+ </dd>
20
+ {%- endif %}
21
+
22
+ {%- if contributors | length %}
23
+ <dt>Contributors</dt>
24
+ <dd>
25
+ {%- for contributor in contributors %}
26
+ <a href="/contributors/{{ contributor | slugify }}/"
27
+ >{{ contributor }}</a
28
+ >{% if not loop.last %},{% endif %}
29
+ {%- endfor %}
30
+ </dd>
31
+ {%- endif %}
32
+
33
+ {%- if topics | length %}
34
+ <dt>Topics</dt>
35
+ <dd class="adr-detail-topics">
36
+ {%- for topic in topics %}
37
+ <a class="tag" href="/topics/{{ topic | lower | slugify }}/"
38
+ >{{ topic }}</a
39
+ >
40
+ {%- endfor %}
41
+ </dd>
42
+ {%- endif %}
43
+ </dl>
44
+
45
+ {%- set tocList = content | toc %}
46
+ <div class="adr-body{{ ' has-toc' if tocList }}">
47
+ {%- if tocList %}
48
+ <div class="toc-wrapper">
49
+ <nav class="toc" aria-labelledby="toc-heading">
50
+ <h2 id="toc-heading">On this page:</h2>
51
+ {{ tocList | safe }}
52
+ </nav>
53
+ </div>
54
+ {%- endif %}
55
+
56
+ <div class="adr-content">{{ content | scrollable | safe }}</div>
57
+ </div>
58
+
59
+ <script type="module" src="/js/scrollable.js"></script>
@@ -0,0 +1,136 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{ title or site.title }} — {{ site.organization }}</title>
7
+ <meta name="description" content="{{ description or site.description }}" />
8
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
9
+ <style>
10
+ @view-transition {
11
+ navigation: auto;
12
+ }
13
+ </style>
14
+ <link
15
+ rel="preload"
16
+ href="/fonts/gabarito-v9-latin-regular.woff2"
17
+ as="font"
18
+ type="font/woff2"
19
+ crossorigin
20
+ />
21
+ <link
22
+ rel="preload"
23
+ href="/fonts/gabarito-v9-latin-500.woff2"
24
+ as="font"
25
+ type="font/woff2"
26
+ crossorigin
27
+ />
28
+ <link
29
+ rel="preload"
30
+ href="/fonts/gabarito-v9-latin-700.woff2"
31
+ as="font"
32
+ type="font/woff2"
33
+ crossorigin
34
+ />
35
+ {% css %}{% include "styles/reset.css" %}{% endcss %}
36
+ {% css %}{% include "styles/fonts.css" %}{% endcss %}
37
+ {% css %}{% include "styles/tokens.css" %}{% endcss %}
38
+ {% css %}{% include "styles/typography.css" %}{% endcss %}
39
+ {% css %}{% include "styles/layout.css" %}{% endcss %}
40
+ {% css %}{% include "styles/recent-decisions.css" %}{% endcss %}
41
+ {% css %}{% include "styles/search-dialog.css" %}{% endcss %}
42
+ {% css %}{% include "styles/footer.css" %}{% endcss %}
43
+ {% css %}{% include "styles/pages.css" %}{% endcss %}
44
+ {% css %}{% include "styles/content.css" %}{% endcss %}
45
+ {% css %}{% include "styles/tags.css" %}{% endcss %}
46
+ {% css %}{% include "styles/view-transitions.css" %}{% endcss %}
47
+ <link rel="stylesheet" href="{% getBundleFileUrl 'css' %}" />
48
+ </head>
49
+ <body>
50
+ <a href="#site-main" class="skip-link">Skip to main content</a>
51
+
52
+ {% include "site-nav.njk" %}
53
+
54
+ <main class="site-main" id="site-main">{{ content | safe }}</main>
55
+
56
+ {% include "recent-decisions.njk" %}
57
+ {% include "footer.njk" %}
58
+
59
+ <dialog id="search-dialog" aria-labelledby="search-dialog-title">
60
+ <header>
61
+ <h2 id="search-dialog-title">Search Decisions</h2>
62
+ <form method="dialog">
63
+ <button aria-label="Close search">{% icon "close" %}</button>
64
+ </form>
65
+ </header>
66
+ <input
67
+ type="search"
68
+ placeholder="Search decisions…"
69
+ aria-label="Search decisions"
70
+ autofocus
71
+ />
72
+ <p class="results-summary" aria-hidden="true"></p>
73
+ <p class="visually-hidden" aria-live="polite" aria-atomic="true"></p>
74
+ <ol>
75
+ <template>
76
+ <li>
77
+ <article>
78
+ <h3><a></a></h3>
79
+ <p class="snippet"></p>
80
+ <footer>
81
+ <span class="status"></span>
82
+ <span class="tags"></span>
83
+ <span class="age"></span>
84
+ </footer>
85
+ </article>
86
+ </li>
87
+ </template>
88
+ </ol>
89
+ </dialog>
90
+
91
+ <script>
92
+ document
93
+ .querySelector('.site-nav footer button')
94
+ .addEventListener('click', () => {
95
+ const nav = document.querySelector('.site-nav');
96
+ const btn = document.querySelector('.site-nav footer button');
97
+ const isExpanded = nav.classList.contains('expanded');
98
+ const isMobile = window.matchMedia('(max-width: 48rem)').matches;
99
+
100
+ if (isExpanded && isMobile) {
101
+ nav.classList.add('closing');
102
+ nav.classList.remove('expanded');
103
+ btn.setAttribute('aria-expanded', 'false');
104
+ nav.querySelector('.menu').addEventListener(
105
+ 'animationend',
106
+ () => {
107
+ nav.classList.remove('closing');
108
+ },
109
+ { once: true },
110
+ );
111
+ } else {
112
+ const expanded = nav.classList.toggle('expanded');
113
+ btn.setAttribute('aria-expanded', String(expanded));
114
+ }
115
+ });
116
+
117
+ const searchDialog = document.getElementById('search-dialog');
118
+ document
119
+ .querySelector('.site-nav button[aria-label="Search"]')
120
+ .addEventListener('click', () => {
121
+ searchDialog.showModal();
122
+ });
123
+ </script>
124
+ <script type="module" src="/js/search.js"></script>
125
+ <script type="speculationrules">
126
+ {
127
+ "prefetch": [
128
+ {
129
+ "where": { "href_matches": "/*" },
130
+ "eagerness": "moderate"
131
+ }
132
+ ]
133
+ }
134
+ </script>
135
+ </body>
136
+ </html>
@@ -0,0 +1,51 @@
1
+ ---
2
+ pagination:
3
+ data: collections.contributors
4
+ size: 1
5
+ alias: contributor
6
+ permalink: 'contributors/{{ contributor | slugify }}/'
7
+ eleventyExcludeFromCollections: true
8
+ eleventyComputed:
9
+ title: 'Decisions by {{ contributor }}'
10
+ description: 'Architectural decision records contributed to by {{ contributor }}'
11
+ ---
12
+
13
+ {%- set contributorAdrs = collections.adrs | byContributor(contributor) %}
14
+
15
+ <h1>Decisions by {{ contributor }}</h1>
16
+ <p class="page-description">{{ description }}</p>
17
+
18
+ <div class="adr-filters">
19
+ <span class="adr-count"
20
+ >{{ contributorAdrs | length }}
21
+ decision{{ "s" if contributorAdrs | length != 1 }}</span
22
+ >
23
+ </div>
24
+
25
+ <ol class="adr-list" reversed>
26
+ {%- for adr in contributorAdrs %}
27
+ <li class="adr-item">
28
+ <div class="adr-header">
29
+ <span class="adr-status adr-status--{{ adr.data.status }}">
30
+ {% if adr.data.status == "accepted" %}{% icon "verified" %}{% else %}{% icon "cancel" %}{% endif %}
31
+ {{ adr.data.status }}
32
+ </span>
33
+ <time datetime="{{ adr.date | isoDate }}"
34
+ >{{ adr.date | timeSince }}</time
35
+ >
36
+ </div>
37
+ <a class="adr-title" href="{{ adr.url }}">{{ adr.data.title }}</a>
38
+ {%- if adr.data.context %}
39
+ <p class="adr-context">{{ adr.data.context }}</p>
40
+ {%- endif %}
41
+ <div class="adr-meta">
42
+ {%- if adr.data.practiceArea %}
43
+ <span class="tag">{{ adr.data.practiceArea }}</span>
44
+ {%- endif %}
45
+ {%- for topic in adr.data.topics %}
46
+ <span class="tag">{{ topic }}</span>
47
+ {%- endfor %}
48
+ </div>
49
+ </li>
50
+ {%- endfor %}
51
+ </ol>
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: Contributors
3
+ description: Team members who have contributed to our decision records
4
+ permalink: /contributors/
5
+ eleventyExcludeFromCollections: true
6
+ ---
7
+
8
+ <h1>{{ title }}</h1>
9
+ <p class="page-description">{{ description }}</p>
10
+
11
+ <table class="contributor-table">
12
+ <thead>
13
+ <tr>
14
+ <th>Name</th>
15
+ <th>Decisions</th>
16
+ <th>Top Topics</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ {%- for contributor in collections.contributors %}
21
+ {%- set adrs = collections.adrs | byContributor(contributor) %}
22
+ {%- set topicCounts = {} %}
23
+ {%- for adr in adrs %}
24
+ {%- for topic in adr.data.topics %}
25
+ {%- set key = topic | lower %}
26
+ {%- set topicCounts = topicCounts | setKey(key, (topicCounts[key] or 0) + 1) %}
27
+ {%- endfor %}
28
+ {%- endfor %}
29
+ <tr>
30
+ <td>
31
+ <a href="/contributors/{{ contributor | slugify }}/"
32
+ >{{ contributor }}</a
33
+ >
34
+ </td>
35
+ <td>{{ adrs | length }}</td>
36
+ <td>
37
+ <div class="tags">
38
+ {%- for topic in topicCounts | topTopics(3) %}
39
+ <span class="tag">{{ topic }}</span>
40
+ {%- endfor %}
41
+ </div>
42
+ </td>
43
+ </tr>
44
+ {%- endfor %}
45
+ </tbody>
46
+ </table>
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: All Decisions
3
+ description: A complete list of architectural decision records
4
+ permalink: /{{ dirs.decisions }}/
5
+ eleventyExcludeFromCollections: true
6
+ ---
7
+
8
+ <h1>{{ title }}</h1>
9
+ <p class="page-description">{{ description }}</p>
10
+
11
+ <div class="adr-filters">
12
+ <span class="adr-count">{{ collections.adrs | length }} decisions</span>
13
+ </div>
14
+
15
+ <ol class="adr-list" reversed>
16
+ {%- for adr in collections.adrs %}
17
+ <li class="adr-item">
18
+ <div class="adr-header">
19
+ <span class="adr-status adr-status--{{ adr.data.status }}">
20
+ {% if adr.data.status == "accepted" %}{% icon "verified" %}{% else %}{% icon "cancel" %}{% endif %}
21
+ {{ adr.data.status }}
22
+ </span>
23
+ <time datetime="{{ adr.date | isoDate }}"
24
+ >{{ adr.date | timeSince }}</time
25
+ >
26
+ </div>
27
+ <a class="adr-title" href="{{ adr.url }}">{{ adr.data.title }}</a>
28
+ {%- if adr.data.context %}
29
+ <p class="adr-context">{{ adr.data.context }}</p>
30
+ {%- endif %}
31
+ <div class="adr-meta">
32
+ {%- if adr.data.practiceArea %}
33
+ <span class="tag">{{ adr.data.practiceArea }}</span>
34
+ {%- endif %}
35
+ {%- for topic in adr.data.topics %}
36
+ <span class="tag">{{ topic }}</span>
37
+ {%- endfor %}
38
+ </div>
39
+ </li>
40
+ {%- endfor %}
41
+ </ol>
@@ -0,0 +1,48 @@
1
+ ---
2
+ pagination:
3
+ data: practiceAreas
4
+ size: 1
5
+ alias: area
6
+ permalink: 'practice-areas/{{ area.name | slugify }}/'
7
+ eleventyExcludeFromCollections: true
8
+ eleventyComputed:
9
+ title: '{{ area.name }} Decisions'
10
+ description: 'Architectural decision records for {{ area.name }}'
11
+ ---
12
+
13
+ {%- set areaAdrs = collections.adrs | withPracticeArea(area.name) %}
14
+
15
+ <h1>{{ area.name }} Decisions</h1>
16
+
17
+ <p class="page-description">{{ description }}</p>
18
+
19
+ <div class="adr-filters">
20
+ <span class="adr-count"
21
+ >{{ areaAdrs | length }} decision{{ "s" if areaAdrs | length != 1 }}</span
22
+ >
23
+ </div>
24
+
25
+ <ol class="adr-list" reversed>
26
+ {%- for adr in areaAdrs %}
27
+ <li class="adr-item">
28
+ <div class="adr-header">
29
+ <span class="adr-status adr-status--{{ adr.data.status }}">
30
+ {% if adr.data.status == "accepted" %}{% icon "verified" %}{% else %}{% icon "cancel" %}{% endif %}
31
+ {{ adr.data.status }}
32
+ </span>
33
+ <time datetime="{{ adr.date | isoDate }}"
34
+ >{{ adr.date | timeSince }}</time
35
+ >
36
+ </div>
37
+ <a class="adr-title" href="{{ adr.url }}">{{ adr.data.title }}</a>
38
+ {%- if adr.data.context %}
39
+ <p class="adr-context">{{ adr.data.context }}</p>
40
+ {%- endif %}
41
+ <div class="adr-meta">
42
+ {%- for topic in adr.data.topics %}
43
+ <span class="tag">{{ topic }}</span>
44
+ {%- endfor %}
45
+ </div>
46
+ </li>
47
+ {%- endfor %}
48
+ </ol>
@@ -0,0 +1,50 @@
1
+ ---
2
+ pagination:
3
+ data: collections.topics
4
+ size: 1
5
+ alias: topic
6
+ permalink: 'topics/{{ topic.label | slugify }}/'
7
+ eleventyExcludeFromCollections: true
8
+ eleventyComputed:
9
+ title: 'Decisions about {{ topic.label }}'
10
+ description: 'Architectural decision records tagged with {{ topic.label }}'
11
+ ---
12
+
13
+ {%- set topicAdrs = collections.adrs | withTopic(topic.label) %}
14
+
15
+ <h1>Decisions about {{ topic.label }}</h1>
16
+ <p class="page-description">{{ description }}</p>
17
+
18
+ <div class="adr-filters">
19
+ <span class="adr-count"
20
+ >{{ topicAdrs | length }} decision{{ "s" if topicAdrs | length != 1 }}</span
21
+ >
22
+ </div>
23
+
24
+ <ol class="adr-list" reversed>
25
+ {%- for adr in topicAdrs %}
26
+ <li class="adr-item">
27
+ <div class="adr-header">
28
+ <span class="adr-status adr-status--{{ adr.data.status }}">
29
+ {% if adr.data.status == "accepted" %}{% icon "verified" %}{% else %}{% icon "cancel" %}{% endif %}
30
+ {{ adr.data.status }}
31
+ </span>
32
+ <time datetime="{{ adr.date | isoDate }}"
33
+ >{{ adr.date | timeSince }}</time
34
+ >
35
+ </div>
36
+ <a class="adr-title" href="{{ adr.url }}">{{ adr.data.title }}</a>
37
+ {%- if adr.data.context %}
38
+ <p class="adr-context">{{ adr.data.context }}</p>
39
+ {%- endif %}
40
+ <div class="adr-meta">
41
+ {%- if adr.data.practiceArea %}
42
+ <span class="tag">{{ adr.data.practiceArea }}</span>
43
+ {%- endif %}
44
+ {%- for t in adr.data.topics %}
45
+ <span class="tag">{{ t }}</span>
46
+ {%- endfor %}
47
+ </div>
48
+ </li>
49
+ {%- endfor %}
50
+ </ol>
@@ -0,0 +1,12 @@
1
+ <footer class="site-footer">
2
+ <ul class="social-links">
3
+ {%- for item in site.social %}
4
+ <li>
5
+ <a href="{{ item.url }}" aria-label="{{ item.label }}">
6
+ {% icon item.icon %}
7
+ </a>
8
+ </li>
9
+ {%- endfor %}
10
+ </ul>
11
+ <p>&copy; {{ null | year }} {{ site.legalName }} CC-BY 4.0.</p>
12
+ </footer>