@terrymooreii/sia 1.0.2 → 2.0.1

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 (61) hide show
  1. package/_config.yml +32 -0
  2. package/bin/cli.js +51 -0
  3. package/defaults/includes/footer.njk +14 -0
  4. package/defaults/includes/header.njk +71 -0
  5. package/defaults/includes/pagination.njk +26 -0
  6. package/defaults/includes/tag-list.njk +11 -0
  7. package/defaults/layouts/base.njk +41 -0
  8. package/defaults/layouts/note.njk +25 -0
  9. package/defaults/layouts/page.njk +14 -0
  10. package/defaults/layouts/post.njk +43 -0
  11. package/defaults/pages/blog.njk +36 -0
  12. package/defaults/pages/feed.njk +28 -0
  13. package/defaults/pages/index.njk +60 -0
  14. package/defaults/pages/notes.njk +34 -0
  15. package/defaults/pages/tag.njk +41 -0
  16. package/defaults/pages/tags.njk +39 -0
  17. package/defaults/styles/main.css +1074 -0
  18. package/lib/assets.js +234 -0
  19. package/lib/build.js +263 -19
  20. package/lib/collections.js +195 -0
  21. package/lib/config.js +122 -0
  22. package/lib/content.js +325 -0
  23. package/lib/index.js +53 -18
  24. package/lib/init.js +555 -6
  25. package/lib/new.js +379 -41
  26. package/lib/server.js +257 -0
  27. package/lib/templates.js +268 -0
  28. package/package.json +30 -15
  29. package/readme.md +212 -63
  30. package/src/images/.gitkeep +3 -0
  31. package/src/notes/2024-12-17-first-note.md +6 -0
  32. package/src/pages/about.md +29 -0
  33. package/src/posts/2024-12-16-markdown-features.md +76 -0
  34. package/src/posts/2024-12-17-welcome-to-sia.md +78 -0
  35. package/src/posts/2024-12-17-welcome-to-static-forge.md +78 -0
  36. package/.prettierignore +0 -3
  37. package/.prettierrc +0 -8
  38. package/lib/helpers.js +0 -37
  39. package/lib/markdown.js +0 -33
  40. package/lib/parse.js +0 -100
  41. package/lib/readconfig.js +0 -18
  42. package/lib/rss.js +0 -63
  43. package/templates/siarc-template.js +0 -53
  44. package/templates/src/_partials/_footer.njk +0 -1
  45. package/templates/src/_partials/_head.njk +0 -35
  46. package/templates/src/_partials/_header.njk +0 -1
  47. package/templates/src/_partials/_layout.njk +0 -12
  48. package/templates/src/_partials/_nav.njk +0 -12
  49. package/templates/src/_partials/page.njk +0 -5
  50. package/templates/src/_partials/post.njk +0 -13
  51. package/templates/src/_partials/posts.njk +0 -19
  52. package/templates/src/assets/android-chrome-192x192.png +0 -0
  53. package/templates/src/assets/android-chrome-512x512.png +0 -0
  54. package/templates/src/assets/apple-touch-icon.png +0 -0
  55. package/templates/src/assets/favicon-16x16.png +0 -0
  56. package/templates/src/assets/favicon-32x32.png +0 -0
  57. package/templates/src/assets/favicon.ico +0 -0
  58. package/templates/src/assets/site.webmanifest +0 -19
  59. package/templates/src/content/index.md +0 -7
  60. package/templates/src/css/markdown.css +0 -1210
  61. package/templates/src/css/theme.css +0 -120
package/_config.yml ADDED
@@ -0,0 +1,32 @@
1
+ site:
2
+ title: "Terry Moore II"
3
+ description: "A personal blog built with Sia"
4
+ url: "http://localhost:3000"
5
+ author: "Terry Moore II"
6
+
7
+ input: src
8
+ output: dist
9
+
10
+ collections:
11
+ posts:
12
+ path: posts
13
+ layout: post
14
+ permalink: /blog/:slug/
15
+ sortBy: date
16
+ sortOrder: desc
17
+ pages:
18
+ path: pages
19
+ layout: page
20
+ permalink: /:slug/
21
+ notes:
22
+ path: notes
23
+ layout: note
24
+ permalink: /notes/:slug/
25
+ sortBy: date
26
+ sortOrder: desc
27
+
28
+ pagination:
29
+ size: 10
30
+
31
+ server:
32
+ port: 3000
package/bin/cli.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { readFileSync } from 'fs';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
11
+
12
+ // Import commands
13
+ import { devCommand } from '../lib/server.js';
14
+ import { buildCommand } from '../lib/build.js';
15
+ import { newCommand } from '../lib/new.js';
16
+ import { initCommand } from '../lib/init.js';
17
+
18
+ program
19
+ .name('sia')
20
+ .description('A simple, powerful static site generator')
21
+ .version(pkg.version);
22
+
23
+ program
24
+ .command('init [directory]')
25
+ .description('Create a new Sia site')
26
+ .option('-y, --yes', 'Skip prompts and use defaults')
27
+ .option('-g, --github-actions', 'Add GitHub Actions workflow for GitHub Pages')
28
+ .action(initCommand);
29
+
30
+ program
31
+ .command('dev')
32
+ .description('Start development server with live reload')
33
+ .option('-p, --port <port>', 'Port to run the server on', '3000')
34
+ .action(devCommand);
35
+
36
+ program
37
+ .command('build')
38
+ .description('Build the site for production')
39
+ .option('-c, --clean', 'Clean output directory before building', true)
40
+ .action(buildCommand);
41
+
42
+ program
43
+ .command('new [type] [title]')
44
+ .description('Create new content (post, page, note)')
45
+ .option('-q, --quick', 'Skip prompts and use defaults')
46
+ .option('-t, --tags <tags>', 'Comma-separated tags')
47
+ .option('-d, --draft', 'Save as draft (posts only)')
48
+ .action(newCommand);
49
+
50
+ program.parse();
51
+
@@ -0,0 +1,14 @@
1
+ <footer class="site-footer">
2
+ <div class="container">
3
+ <p class="footer-text">
4
+ © {{ "now" | date('year') }} {{ site.author or site.title }}.
5
+ Built with <a href="https://github.com/sia/sia">Sia</a>.
6
+ </p>
7
+
8
+ <nav class="footer-nav">
9
+ <a href="{{ '/feed.xml' | url }}" class="footer-link">RSS</a>
10
+ <a href="{{ '/tags/' | url }}" class="footer-link">Tags</a>
11
+ </nav>
12
+ </div>
13
+ </footer>
14
+
@@ -0,0 +1,71 @@
1
+ <header class="site-header">
2
+ <div class="container">
3
+ <a href="{{ '/' | url }}" class="site-logo">{{ site.title }}</a>
4
+
5
+ <nav class="site-nav">
6
+ <a href="{{ '/' | url }}" class="nav-link{% if page.url == ('/' | url) %} active{% endif %}">Home</a>
7
+ <a href="{{ '/blog/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/blog') %} active{% endif %}">Blog</a>
8
+ <a href="{{ '/notes/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/notes') %} active{% endif %}">Notes</a>
9
+ <a href="{{ '/tags/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/tags') %} active{% endif %}">Tags</a>
10
+ {% if collections.pages %}
11
+ {% for p in collections.pages | limit(3) %}
12
+ <a href="{{ p.url }}" class="nav-link{% if page.url == p.url %} active{% endif %}">{{ p.title }}</a>
13
+ {% endfor %}
14
+ {% endif %}
15
+
16
+ <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
17
+ <svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
18
+ <circle cx="12" cy="12" r="5"></circle>
19
+ <line x1="12" y1="1" x2="12" y2="3"></line>
20
+ <line x1="12" y1="21" x2="12" y2="23"></line>
21
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
22
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
23
+ <line x1="1" y1="12" x2="3" y2="12"></line>
24
+ <line x1="21" y1="12" x2="23" y2="12"></line>
25
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
26
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
27
+ </svg>
28
+ <svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
29
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
30
+ </svg>
31
+ </button>
32
+ </nav>
33
+ </div>
34
+ </header>
35
+
36
+ <script>
37
+ (function() {
38
+ const toggle = document.getElementById('theme-toggle');
39
+ const html = document.documentElement;
40
+
41
+ // Check for saved preference or system preference
42
+ function getPreferredTheme() {
43
+ const saved = localStorage.getItem('theme');
44
+ if (saved) return saved;
45
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
46
+ }
47
+
48
+ // Apply theme
49
+ function setTheme(theme) {
50
+ html.setAttribute('data-theme', theme);
51
+ localStorage.setItem('theme', theme);
52
+ }
53
+
54
+ // Initialize
55
+ setTheme(getPreferredTheme());
56
+
57
+ // Toggle handler
58
+ toggle.addEventListener('click', function() {
59
+ const current = html.getAttribute('data-theme');
60
+ setTheme(current === 'dark' ? 'light' : 'dark');
61
+ });
62
+
63
+ // Listen for system preference changes
64
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
65
+ if (!localStorage.getItem('theme')) {
66
+ setTheme(e.matches ? 'dark' : 'light');
67
+ }
68
+ });
69
+ })();
70
+ </script>
71
+
@@ -0,0 +1,26 @@
1
+ {% if pagination and pagination.totalPages > 1 %}
2
+ <nav class="pagination" aria-label="Pagination">
3
+ <div class="pagination-info">
4
+ Page {{ pagination.pageNumber }} of {{ pagination.totalPages }}
5
+ </div>
6
+
7
+ <div class="pagination-links">
8
+ {% if pagination.previousUrl %}
9
+ <a href="{{ pagination.previousUrl }}" class="pagination-link pagination-prev" aria-label="Previous page">
10
+ ← Newer
11
+ </a>
12
+ {% else %}
13
+ <span class="pagination-link pagination-prev disabled">← Newer</span>
14
+ {% endif %}
15
+
16
+ {% if pagination.nextUrl %}
17
+ <a href="{{ pagination.nextUrl }}" class="pagination-link pagination-next" aria-label="Next page">
18
+ Older →
19
+ </a>
20
+ {% else %}
21
+ <span class="pagination-link pagination-next disabled">Older →</span>
22
+ {% endif %}
23
+ </div>
24
+ </nav>
25
+ {% endif %}
26
+
@@ -0,0 +1,11 @@
1
+ {% if allTags and allTags.length %}
2
+ <div class="tag-cloud">
3
+ {% for tag in allTags %}
4
+ <a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="tag tag-{{ 'lg' if tag.count > 10 else ('md' if tag.count > 5 else 'sm') }}">
5
+ {{ tag.name }}
6
+ <span class="tag-count">({{ tag.count }})</span>
7
+ </a>
8
+ {% endfor %}
9
+ </div>
10
+ {% endif %}
11
+
@@ -0,0 +1,41 @@
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.0">
6
+ <meta name="description" content="{{ page.excerpt or site.description }}">
7
+ <title>{% if title %}{{ title }} | {% endif %}{{ site.title }}</title>
8
+
9
+ <!-- Prevent flash of incorrect theme -->
10
+ <script>
11
+ (function() {
12
+ var saved = localStorage.getItem('theme');
13
+ var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
14
+ document.documentElement.setAttribute('data-theme', theme);
15
+ })();
16
+ </script>
17
+
18
+ <link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
19
+
20
+ <!-- Open Graph -->
21
+ <meta property="og:title" content="{{ title or site.title }}">
22
+ <meta property="og:description" content="{{ page.excerpt or site.description }}">
23
+ <meta property="og:type" content="{% if page.collection == 'posts' %}article{% else %}website{% endif %}">
24
+ <meta property="og:url" content="{{ site.url }}{{ page.url or ('/' | url) }}">
25
+
26
+ <!-- RSS Feed -->
27
+ <link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ '/feed.xml' | url }}">
28
+
29
+ {% block head %}{% endblock %}
30
+ </head>
31
+ <body>
32
+ {% include "header.njk" %}
33
+
34
+ <main class="main">
35
+ {% block content %}{% endblock %}
36
+ </main>
37
+
38
+ {% include "footer.njk" %}
39
+ </body>
40
+ </html>
41
+
@@ -0,0 +1,25 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <article class="note">
5
+ <div class="note-content prose">
6
+ {{ content | safe }}
7
+ </div>
8
+
9
+ <footer class="note-footer">
10
+ <time datetime="{{ page.date | date('iso') }}">{{ page.date | date('full') }}</time>
11
+ {% if page.tags and page.tags.length %}
12
+ <span class="note-tags">
13
+ {% for tag in page.tags %}
14
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
15
+ {% endfor %}
16
+ </span>
17
+ {% endif %}
18
+ </footer>
19
+ </article>
20
+
21
+ <nav class="note-nav">
22
+ <a href="{{ '/notes/' | url }}" class="btn btn-secondary">← All Notes</a>
23
+ </nav>
24
+ {% endblock %}
25
+
@@ -0,0 +1,14 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <article class="page">
5
+ <header class="page-header">
6
+ <h1 class="page-title">{{ page.title }}</h1>
7
+ </header>
8
+
9
+ <div class="page-content prose">
10
+ {{ content | safe }}
11
+ </div>
12
+ </article>
13
+ {% endblock %}
14
+
@@ -0,0 +1,43 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <article class="post">
5
+ <header class="post-header">
6
+ <h1 class="post-title">{{ page.title }}</h1>
7
+
8
+ <div class="post-meta">
9
+ <time datetime="{{ page.date | date('iso') }}">{{ page.date | date('long') }}</time>
10
+ {% if page.tags and page.tags.length %}
11
+ <span class="post-meta-divider">·</span>
12
+ <span class="post-tags">
13
+ {% for tag in page.tags %}
14
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>{% if not loop.last %}, {% endif %}
15
+ {% endfor %}
16
+ </span>
17
+ {% endif %}
18
+ <span class="post-meta-divider">·</span>
19
+ <span class="reading-time">{{ page.content | readingTime }}</span>
20
+ </div>
21
+ </header>
22
+
23
+ <div class="post-content prose">
24
+ {{ content | safe }}
25
+ </div>
26
+
27
+ <footer class="post-footer">
28
+ {% if page.tags and page.tags.length %}
29
+ <div class="post-tags-footer">
30
+ <span class="tags-label">Tagged:</span>
31
+ {% for tag in page.tags %}
32
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
33
+ {% endfor %}
34
+ </div>
35
+ {% endif %}
36
+
37
+ <nav class="post-nav">
38
+ <a href="{{ '/blog/' | url }}" class="btn btn-secondary">← Back to Blog</a>
39
+ </nav>
40
+ </footer>
41
+ </article>
42
+ {% endblock %}
43
+
@@ -0,0 +1,36 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <header class="page-header">
5
+ <h1 class="page-title">Blog</h1>
6
+ <p class="page-description">All posts, newest first</p>
7
+ </header>
8
+
9
+ <div class="post-list">
10
+ {% for post in posts %}
11
+ <article class="post-card">
12
+ <h2 class="post-card-title">
13
+ <a href="{{ post.url }}">{{ post.title }}</a>
14
+ </h2>
15
+ <div class="post-card-meta">
16
+ <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
17
+ <span class="post-card-reading-time">{{ post.content | readingTime }}</span>
18
+ </div>
19
+ {% if post.tags and post.tags.length %}
20
+ <div class="post-card-tags">
21
+ {% for tag in post.tags %}
22
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
23
+ {% endfor %}
24
+ </div>
25
+ {% endif %}
26
+ <p class="post-card-excerpt">{{ post.excerpt }}</p>
27
+ <a href="{{ post.url }}" class="post-card-link">Read more →</a>
28
+ </article>
29
+ {% else %}
30
+ <p class="empty-message">No posts yet. Create your first post with <code>npx sia new post "My First Post"</code></p>
31
+ {% endfor %}
32
+ </div>
33
+
34
+ {% include "pagination.njk" %}
35
+ {% endblock %}
36
+
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
3
+ <channel>
4
+ <title>{{ site.title | safe }}</title>
5
+ <description>{{ site.description | safe }}</description>
6
+ <link>{{ site.url | safe }}</link>
7
+ <atom:link href="{{ site.url | safe }}/feed.xml" rel="self" type="application/rss+xml"/>
8
+ <language>en-us</language>
9
+ <lastBuildDate>{{ buildDate | safe }}</lastBuildDate>
10
+ <generator>Sia Static Site Generator</generator>
11
+ {% for post in posts | limit(20) %}
12
+ <item>
13
+ <title><![CDATA[{{ post.title | safe }}]]></title>
14
+ <link>{{ site.url | safe }}{{ post.url | safe }}</link>
15
+ <guid isPermaLink="true">{{ site.url | safe }}{{ post.url | safe }}</guid>
16
+ <pubDate>{{ post.date | date('rss') }}</pubDate>
17
+ {% if post.tags and post.tags.length %}
18
+ {% for tag in post.tags %}
19
+ <category>{{ tag | safe }}</category>
20
+ {% endfor %}
21
+ {% endif %}
22
+ <description><![CDATA[{{ post.excerpt | safe }}]]></description>
23
+ <content:encoded><![CDATA[{{ post.content | safe }}]]></content:encoded>
24
+ </item>
25
+ {% endfor %}
26
+ </channel>
27
+ </rss>
28
+
@@ -0,0 +1,60 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <section class="hero">
5
+ <h1 class="hero-title">{{ site.title }}</h1>
6
+ <p class="hero-description">{{ site.description }}</p>
7
+ </section>
8
+
9
+ <section class="section">
10
+ <div class="section-header">
11
+ <h2 class="section-title">Latest Posts</h2>
12
+ <a href="{{ '/blog/' | url }}" class="section-link">View all →</a>
13
+ </div>
14
+
15
+ <div class="post-list">
16
+ {% for post in collections.posts | limit(5) %}
17
+ <article class="post-card">
18
+ <h3 class="post-card-title">
19
+ <a href="{{ post.url }}">{{ post.title }}</a>
20
+ </h3>
21
+ <div class="post-card-meta">
22
+ <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('short') }}</time>
23
+ {% if post.tags and post.tags.length %}
24
+ <span class="post-card-tags">
25
+ {% for tag in post.tags | limit(3) %}
26
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
27
+ {% endfor %}
28
+ </span>
29
+ {% endif %}
30
+ </div>
31
+ <p class="post-card-excerpt">{{ post.excerpt }}</p>
32
+ </article>
33
+ {% else %}
34
+ <p class="empty-message">No posts yet. Create your first post with <code>npx sia new post "My First Post"</code></p>
35
+ {% endfor %}
36
+ </div>
37
+ </section>
38
+
39
+ {% if collections.notes and collections.notes.length %}
40
+ <section class="section">
41
+ <div class="section-header">
42
+ <h2 class="section-title">Recent Notes</h2>
43
+ <a href="{{ '/notes/' | url }}" class="section-link">View all →</a>
44
+ </div>
45
+
46
+ <div class="notes-grid">
47
+ {% for note in collections.notes | limit(3) %}
48
+ <article class="note-card">
49
+ <div class="note-card-content">{{ note.excerpt }}</div>
50
+ <footer class="note-card-footer">
51
+ <time datetime="{{ note.date | date('iso') }}">{{ note.date | date('short') }}</time>
52
+ <a href="{{ note.url }}" class="note-card-link">View →</a>
53
+ </footer>
54
+ </article>
55
+ {% endfor %}
56
+ </div>
57
+ </section>
58
+ {% endif %}
59
+ {% endblock %}
60
+
@@ -0,0 +1,34 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <header class="page-header">
5
+ <h1 class="page-title">Notes</h1>
6
+ <p class="page-description">Short thoughts and quick updates</p>
7
+ </header>
8
+
9
+ <div class="notes-stream">
10
+ {% for note in notes %}
11
+ <article class="note-item">
12
+ <div class="note-item-content prose">
13
+ {{ note.content | safe }}
14
+ </div>
15
+ <footer class="note-item-footer">
16
+ <time datetime="{{ note.date | date('iso') }}">{{ note.date | date('full') }}</time>
17
+ {% if note.tags and note.tags.length %}
18
+ <div class="note-item-tags">
19
+ {% for tag in note.tags %}
20
+ <a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
21
+ {% endfor %}
22
+ </div>
23
+ {% endif %}
24
+ <a href="{{ note.url }}" class="note-item-permalink">Permalink</a>
25
+ </footer>
26
+ </article>
27
+ {% else %}
28
+ <p class="empty-message">No notes yet. Create your first note with <code>npx sia new note "Quick thought"</code></p>
29
+ {% endfor %}
30
+ </div>
31
+
32
+ {% include "pagination.njk" %}
33
+ {% endblock %}
34
+
@@ -0,0 +1,41 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <header class="page-header">
5
+ <h1 class="page-title">Tagged: {{ tag.name }}</h1>
6
+ <p class="page-description">{{ tag.count }} item{% if tag.count != 1 %}s{% endif %} tagged with "{{ tag.name }}"</p>
7
+ </header>
8
+
9
+ <div class="post-list">
10
+ {% for post in posts %}
11
+ <article class="post-card">
12
+ <h2 class="post-card-title">
13
+ <a href="{{ post.url }}">{{ post.title or post.excerpt }}</a>
14
+ </h2>
15
+ <div class="post-card-meta">
16
+ <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
17
+ {% if post.collection %}
18
+ <span class="post-card-collection">{{ post.collection }}</span>
19
+ {% endif %}
20
+ </div>
21
+ {% if post.tags and post.tags.length %}
22
+ <div class="post-card-tags">
23
+ {% for t in post.tags %}
24
+ <a href="{{ '/tags/' | url }}{{ t | slug }}/" class="tag tag-sm{% if t | slug == tag.slug %} active{% endif %}">{{ t }}</a>
25
+ {% endfor %}
26
+ </div>
27
+ {% endif %}
28
+ {% if post.excerpt %}
29
+ <p class="post-card-excerpt">{{ post.excerpt }}</p>
30
+ {% endif %}
31
+ </article>
32
+ {% endfor %}
33
+ </div>
34
+
35
+ {% include "pagination.njk" %}
36
+
37
+ <nav class="page-nav">
38
+ <a href="{{ '/tags/' | url }}" class="btn btn-secondary">← All Tags</a>
39
+ </nav>
40
+ {% endblock %}
41
+
@@ -0,0 +1,39 @@
1
+ {% extends "base.njk" %}
2
+
3
+ {% block content %}
4
+ <header class="page-header">
5
+ <h1 class="page-title">Tags</h1>
6
+ <p class="page-description">Browse all {{ allTags.length }} tags</p>
7
+ </header>
8
+
9
+ {% if allTags and allTags.length %}
10
+ <div class="tags-page">
11
+ {% include "tag-list.njk" %}
12
+
13
+ <div class="tags-detail">
14
+ {% for tag in allTags %}
15
+ <section class="tag-section" id="{{ tag.slug }}">
16
+ <h2 class="tag-section-title">
17
+ <a href="{{ '/tags/' | url }}{{ tag.slug }}/">{{ tag.name }}</a>
18
+ <span class="tag-section-count">({{ tag.count }})</span>
19
+ </h2>
20
+ <ul class="tag-posts">
21
+ {% for item in tag.items | limit(5) %}
22
+ <li>
23
+ <a href="{{ item.url }}">{{ item.title or item.excerpt }}</a>
24
+ <time datetime="{{ item.date | date('iso') }}">{{ item.date | date('short') }}</time>
25
+ </li>
26
+ {% endfor %}
27
+ {% if tag.count > 5 %}
28
+ <li><a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="more-link">View all {{ tag.count }} →</a></li>
29
+ {% endif %}
30
+ </ul>
31
+ </section>
32
+ {% endfor %}
33
+ </div>
34
+ </div>
35
+ {% else %}
36
+ <p class="empty-message">No tags yet. Add tags to your posts in the front matter.</p>
37
+ {% endif %}
38
+ {% endblock %}
39
+