@terrymooreii/sia 2.1.4 → 2.1.6
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.
- package/_config.yml +3 -1
- package/docs/README.md +132 -0
- package/docs/creating-themes.md +772 -0
- package/docs/front-matter.md +557 -0
- package/docs/markdown-guide.md +536 -0
- package/docs/template-reference.md +581 -0
- package/lib/assets.js +1 -1
- package/lib/build.js +4 -4
- package/lib/config.js +3 -1
- package/lib/content.js +74 -2
- package/lib/templates.js +3 -2
- package/package.json +1 -1
- package/readme.md +2 -1
- package/themes/_shared/includes/meta.njk +5 -4
- package/themes/developer/includes/hero.njk +6 -0
- package/themes/developer/pages/blog.njk +1 -1
- package/themes/developer/pages/index.njk +3 -6
- package/themes/developer/pages/notes.njk +1 -1
- package/themes/developer/pages/tag.njk +1 -1
- package/themes/magazine/includes/hero.njk +8 -0
- package/themes/magazine/layouts/post.njk +1 -1
- package/themes/magazine/pages/blog.njk +1 -1
- package/themes/magazine/pages/index.njk +1 -6
- package/themes/magazine/pages/notes.njk +1 -1
- package/themes/magazine/pages/tag.njk +1 -1
- package/themes/main/includes/hero.njk +6 -0
- package/themes/main/pages/index.njk +1 -4
- package/themes/minimal/includes/hero.njk +6 -0
- package/themes/minimal/pages/index.njk +2 -5
package/lib/content.js
CHANGED
|
@@ -247,6 +247,72 @@ marked.use({
|
|
|
247
247
|
}
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Safely truncate markdown text without breaking inline formatting
|
|
252
|
+
* Avoids cutting in the middle of links, bold, italic, code, images, etc.
|
|
253
|
+
*/
|
|
254
|
+
function truncateMarkdownSafely(text, maxLength) {
|
|
255
|
+
if (text.length <= maxLength) {
|
|
256
|
+
return text;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Find all inline markdown element ranges to avoid cutting inside them
|
|
260
|
+
const inlinePatterns = [
|
|
261
|
+
/!\[([^\]]*)\]\([^)]*\)/g, // Images  - must come before links
|
|
262
|
+
/\[([^\]]*)\]\([^)]*\)/g, // Links [text](url)
|
|
263
|
+
/\[([^\]]*)\]\[[^\]]*\]/g, // Reference links [text][ref]
|
|
264
|
+
/\*\*([^*]+)\*\*/g, // Bold **text**
|
|
265
|
+
/__([^_]+)__/g, // Bold __text__
|
|
266
|
+
/\*([^*\n]+)\*/g, // Italic *text*
|
|
267
|
+
/_([^_\n]+)_/g, // Italic _text_
|
|
268
|
+
/`([^`]+)`/g, // Inline code `code`
|
|
269
|
+
/~~([^~]+)~~/g, // Strikethrough ~~text~~
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
// Collect all ranges where inline elements exist
|
|
273
|
+
const ranges = [];
|
|
274
|
+
for (const pattern of inlinePatterns) {
|
|
275
|
+
let match;
|
|
276
|
+
// Reset lastIndex for each pattern
|
|
277
|
+
pattern.lastIndex = 0;
|
|
278
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
279
|
+
ranges.push({ start: match.index, end: match.index + match[0].length });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Sort ranges by start position
|
|
284
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
285
|
+
|
|
286
|
+
// Find the best truncation point
|
|
287
|
+
let truncateAt = maxLength;
|
|
288
|
+
|
|
289
|
+
// Check if our target position is inside any markdown element
|
|
290
|
+
for (const range of ranges) {
|
|
291
|
+
if (truncateAt > range.start && truncateAt < range.end) {
|
|
292
|
+
// We're inside this element - decide whether to include it or exclude it
|
|
293
|
+
if (range.end <= maxLength + 50) {
|
|
294
|
+
// Include the whole element if it doesn't extend too far past our limit
|
|
295
|
+
truncateAt = range.end;
|
|
296
|
+
} else {
|
|
297
|
+
// Otherwise, truncate before this element starts
|
|
298
|
+
truncateAt = range.start;
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try to break at a word boundary for cleaner excerpts
|
|
305
|
+
if (truncateAt > 0) {
|
|
306
|
+
const lastSpace = text.lastIndexOf(' ', truncateAt);
|
|
307
|
+
// Only use word boundary if it's reasonably close to our target
|
|
308
|
+
if (lastSpace > truncateAt - 30 && lastSpace > maxLength * 0.5) {
|
|
309
|
+
truncateAt = lastSpace;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return text.substring(0, truncateAt).trim() + '...';
|
|
314
|
+
}
|
|
315
|
+
|
|
250
316
|
/**
|
|
251
317
|
* Generate a URL-friendly slug from a string
|
|
252
318
|
*/
|
|
@@ -321,12 +387,17 @@ export function parseContent(filePath) {
|
|
|
321
387
|
if (!excerpt) {
|
|
322
388
|
const firstParagraph = markdown.split('\n\n')[0];
|
|
323
389
|
excerpt = firstParagraph.replace(/^#+\s+.+\n?/, '').trim();
|
|
324
|
-
// Limit excerpt length
|
|
390
|
+
// Limit excerpt length using safe truncation that preserves markdown syntax
|
|
325
391
|
if (excerpt.length > 200) {
|
|
326
|
-
excerpt = excerpt
|
|
392
|
+
excerpt = truncateMarkdownSafely(excerpt, 200);
|
|
327
393
|
}
|
|
328
394
|
}
|
|
329
395
|
|
|
396
|
+
// Create HTML version of excerpt for templates that need rendered output
|
|
397
|
+
let excerptHtml = marked.parse(excerpt);
|
|
398
|
+
// Clean up the HTML (remove wrapping <p> tags for inline use)
|
|
399
|
+
excerptHtml = excerptHtml.replace(/^<p>/, '').replace(/<\/p>\n?$/, '');
|
|
400
|
+
|
|
330
401
|
// Normalize tags to array
|
|
331
402
|
let tags = frontMatter.tags || [];
|
|
332
403
|
if (typeof tags === 'string') {
|
|
@@ -338,6 +409,7 @@ export function parseContent(filePath) {
|
|
|
338
409
|
slug,
|
|
339
410
|
date,
|
|
340
411
|
excerpt,
|
|
412
|
+
excerptHtml,
|
|
341
413
|
tags,
|
|
342
414
|
content: html,
|
|
343
415
|
rawContent: markdown,
|
package/lib/templates.js
CHANGED
|
@@ -12,7 +12,8 @@ const __dirname = dirname(__filename);
|
|
|
12
12
|
function dateFilter(date, format = 'long') {
|
|
13
13
|
if (!date) return '';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Handle special "now" keyword for current date/time
|
|
16
|
+
const d = (date === 'now') ? new Date() : new Date(date);
|
|
16
17
|
|
|
17
18
|
if (isNaN(d.getTime())) return '';
|
|
18
19
|
|
|
@@ -205,7 +206,7 @@ export function createTemplateEngine(config) {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
// Default templates from the selected theme
|
|
208
|
-
const themeName = config.theme || 'main';
|
|
209
|
+
const themeName = config.theme?.name || 'main';
|
|
209
210
|
const themeDir = join(__dirname, '..', 'themes', themeName);
|
|
210
211
|
templatePaths.push(join(themeDir, 'layouts'));
|
|
211
212
|
templatePaths.push(join(themeDir, 'includes'));
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
<title>{% if title %}{{ title }} | {% endif %}{{ site.title }}</title>
|
|
7
7
|
|
|
8
8
|
{# Open Graph #}
|
|
9
|
+
{% set siteOrigin = site.url | replace(site.basePath, '') if site.basePath else site.url %}
|
|
9
10
|
<meta property="og:title" content="{{ title or site.title }}">
|
|
10
11
|
<meta property="og:description" content="{{ page.excerpt or page.description or site.description }}">
|
|
11
12
|
<meta property="og:type" content="{% if page.collection == 'posts' %}article{% else %}website{% endif %}">
|
|
12
|
-
<meta property="og:url" content="{{
|
|
13
|
+
<meta property="og:url" content="{{ siteOrigin }}{{ page.url if page.url else ('/' | url) }}">
|
|
13
14
|
<meta property="og:site_name" content="{{ site.title }}">
|
|
14
15
|
{% if page.image %}
|
|
15
|
-
<meta property="og:image" content="{{
|
|
16
|
+
<meta property="og:image" content="{{ siteOrigin }}{{ page.image | url }}">
|
|
16
17
|
{% endif %}
|
|
17
18
|
|
|
18
19
|
{# Twitter Card #}
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
<meta name="twitter:title" content="{{ title or site.title }}">
|
|
21
22
|
<meta name="twitter:description" content="{{ page.excerpt or page.description or site.description }}">
|
|
22
23
|
{% if page.image %}
|
|
23
|
-
<meta name="twitter:image" content="{{
|
|
24
|
+
<meta name="twitter:image" content="{{ siteOrigin }}{{ page.image | url }}">
|
|
24
25
|
{% endif %}
|
|
25
26
|
|
|
26
27
|
{# Article specific meta (for blog posts) #}
|
|
@@ -38,4 +39,4 @@
|
|
|
38
39
|
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ '/feed.xml' | url }}">
|
|
39
40
|
|
|
40
41
|
{# Canonical URL #}
|
|
41
|
-
<link rel="canonical" href="{{
|
|
42
|
+
<link rel="canonical" href="{{ siteOrigin }}{{ page.url if page.url else ('/' | url) }}">
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
4
|
<div class="home-page">
|
|
5
|
-
|
|
6
|
-
<h1 class="hero-title">{{ site.title }}</h1>
|
|
7
|
-
<p class="hero-description">{{ site.description }}</p>
|
|
8
|
-
</section>
|
|
5
|
+
{% include "hero.njk" %}
|
|
9
6
|
|
|
10
7
|
<section class="posts-section">
|
|
11
8
|
<div class="section-header">
|
|
@@ -26,7 +23,7 @@
|
|
|
26
23
|
{% endif %}
|
|
27
24
|
|
|
28
25
|
<h3 class="card-title">
|
|
29
|
-
<a href="{{ post.url
|
|
26
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
30
27
|
</h3>
|
|
31
28
|
|
|
32
29
|
<p class="card-excerpt">{{ post.excerpt | excerpt(120) }}</p>
|
|
@@ -55,7 +52,7 @@
|
|
|
55
52
|
<article class="note-card">
|
|
56
53
|
<time class="note-time">{{ note.date | date('short') }}</time>
|
|
57
54
|
<div class="note-preview">{{ note.excerpt | excerpt(150) }}</div>
|
|
58
|
-
<a href="{{ note.url
|
|
55
|
+
<a href="{{ note.url }}" class="note-link">Read more →</a>
|
|
59
56
|
</article>
|
|
60
57
|
{% endfor %}
|
|
61
58
|
</div>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</time>
|
|
18
18
|
<div class="timeline-body">
|
|
19
19
|
<div class="note-text">{{ note.excerpt | excerpt(200) }}</div>
|
|
20
|
-
<a href="{{ note.url
|
|
20
|
+
<a href="{{ note.url }}" class="read-more">Continue reading →</a>
|
|
21
21
|
</div>
|
|
22
22
|
{% if note.tags %}
|
|
23
23
|
<div class="timeline-tags">
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
{% if post.url != page.url %}
|
|
69
69
|
<article class="read-more-card">
|
|
70
70
|
<h3 class="read-more-card-title">
|
|
71
|
-
<a href="{{ post.url
|
|
71
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
72
72
|
</h3>
|
|
73
73
|
<time class="read-more-date">{{ post.date | date('short') }}</time>
|
|
74
74
|
</article>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<a href="{{ ('/tags/' + (post.tags[0] | slug) + '/') | url }}" class="row-category">{{ post.tags[0] }}</a>
|
|
15
15
|
{% endif %}
|
|
16
16
|
<h2 class="row-title">
|
|
17
|
-
<a href="{{ post.url
|
|
17
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
18
18
|
</h2>
|
|
19
19
|
<p class="row-excerpt">{{ post.excerpt | excerpt(180) }}</p>
|
|
20
20
|
<div class="row-meta">
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
{% extends "base.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
-
|
|
5
|
-
<div class="hero-content">
|
|
6
|
-
<h1 class="hero-title">{{ site.title }}</h1>
|
|
7
|
-
<p class="hero-subtitle">{{ site.description }}</p>
|
|
8
|
-
</div>
|
|
9
|
-
</section>
|
|
4
|
+
{% include "hero.njk" %}
|
|
10
5
|
|
|
11
6
|
{% set featuredPost = collections.posts[0] %}
|
|
12
7
|
{% if featuredPost %}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
</time>
|
|
15
15
|
<div class="note-content">
|
|
16
16
|
<p>{{ note.excerpt | excerpt(250) }}</p>
|
|
17
|
-
<a href="{{ note.url
|
|
17
|
+
<a href="{{ note.url }}" class="note-read-more">Continue reading →</a>
|
|
18
18
|
</div>
|
|
19
19
|
{% if note.tags %}
|
|
20
20
|
<div class="note-item-tags">
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<article class="article-row">
|
|
13
13
|
<div class="article-row-content">
|
|
14
14
|
<h2 class="row-title">
|
|
15
|
-
<a href="{{ post.url
|
|
15
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
16
16
|
</h2>
|
|
17
17
|
<p class="row-excerpt">{{ post.excerpt | excerpt(180) }}</p>
|
|
18
18
|
<div class="row-meta">
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{% extends "base.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
-
|
|
5
|
-
<h1 class="hero-title">{{ site.title }}</h1>
|
|
6
|
-
<p class="hero-description">{{ site.description }}</p>
|
|
7
|
-
</section>
|
|
4
|
+
{% include "hero.njk" %}
|
|
8
5
|
|
|
9
6
|
<section class="section">
|
|
10
7
|
<div class="section-header">
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{% extends "base.njk" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
-
|
|
5
|
-
<h1 class="hero-title">{{ site.title }}</h1>
|
|
6
|
-
<p class="hero-description">{{ site.description }}</p>
|
|
7
|
-
</section>
|
|
4
|
+
{% include "hero.njk" %}
|
|
8
5
|
|
|
9
6
|
<section class="section">
|
|
10
7
|
<div class="section-header">
|
|
@@ -47,7 +44,7 @@
|
|
|
47
44
|
<div class="notes-grid">
|
|
48
45
|
{% for note in collections.notes | limit(3) %}
|
|
49
46
|
<article class="note-card">
|
|
50
|
-
<div class="note-card-content">{{ note.
|
|
47
|
+
<div class="note-card-content">{{ note.excerptHtml | safe }}</div>
|
|
51
48
|
<footer class="note-card-footer">
|
|
52
49
|
<time datetime="{{ note.date | date('iso') }}">{{ note.date | date('full_time') }}</time>
|
|
53
50
|
<a href="{{ note.url }}" class="note-card-link">View →</a>
|