@jsnchn/buntastic 0.1.0 → 0.2.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.
package/AGENTS.md CHANGED
@@ -85,22 +85,25 @@ The `{{ content | safe }}` placeholder is where child content gets injected.
85
85
 
86
86
  ## Head Block
87
87
 
88
- Layouts can inject content into the `<head>` section using `{{ head | safe }}`. Content BEFORE the marker goes into `<head>`, content AFTER the marker is treated as the layout body:
88
+ Layouts can inject content into the `<head>` section using `[head]...[/head]` blocks:
89
89
 
90
90
  ```html
91
91
  <!-- layouts/post.html -->
92
92
  ---
93
93
  extends: base.html
94
94
  ---
95
+ [head]
95
96
  <link rel="stylesheet" href="/post.css">
96
- {{ head | safe }}
97
+ [/head]
98
+ [content]
97
99
  <article class="post">
98
100
  <h1>{{ title }}</h1>
99
101
  {{ content | safe }}
100
102
  </article>
103
+ [/content]
101
104
  ```
102
105
 
103
- The `{{ head | safe }}` marker is where additional head content can be injected (from child layouts or page frontmatter). Content before the marker (e.g., the post.css link) is automatically placed in the parent's `<head>` section. Child layout head content is appended to parent head content (parent's head first, then child's).
106
+ Content inside `[head]` blocks is automatically placed in the parent's `<head>` section. Child layout head content is appended to parent head content.
104
107
 
105
108
  ## Development
106
109
 
package/README.md CHANGED
@@ -164,11 +164,12 @@ extends: base.html
164
164
  | Variable | Description |
165
165
  |----------|-------------|
166
166
  | `{{ title }}` | Page title |
167
- | `{{ content \| safe }}` | Rendered markdown HTML |
167
+ | `{{ content | safe }}` | Rendered markdown HTML |
168
168
  | `{{ date }}` | Page date |
169
169
  | `{{ description }}` | Meta description |
170
170
  | `{{ url }}` | Current page URL |
171
171
  | `{{ collection }}` | List of posts in current folder (for index pages) |
172
+ | `{{ head | safe }}` | Head content from child layouts (appended to parent) |
172
173
 
173
174
  ### Layout Inheritance
174
175
 
@@ -187,6 +188,28 @@ Use `extends:` to build on top of other layouts:
187
188
  </html>
188
189
  ```
189
190
 
191
+ ### Head Block
192
+
193
+ Layouts can inject content into the `<head>` section using `[head]...[/head]` blocks:
194
+
195
+ ```html
196
+ <!-- layouts/post.html -->
197
+ ---
198
+ extends: base.html
199
+ ---
200
+ [head]
201
+ <link rel="stylesheet" href="/post.css">
202
+ [/head]
203
+ [content]
204
+ <article class="post">
205
+ <h1>{{ title }}</h1>
206
+ {{ content | safe }}
207
+ </article>
208
+ [/content]
209
+ ```
210
+
211
+ Content inside `[head]` blocks is automatically placed in the parent's `<head>` section. Child layout head content is appended to parent head content.
212
+
190
213
  ## Collections
191
214
 
192
215
  For folder index pages (e.g., `content/posts/index.md`), use `{{ collection }}` to list all pages in that folder:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsnchn/buntastic",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A simple static site generator built with Bun",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -101,35 +101,58 @@ async function resolveLayout(frontmatter: Frontmatter): Promise<{ template: stri
101
101
  const extendsMatch = template.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
102
102
 
103
103
  let childHead = "";
104
- let childBody = template;
104
+ let childBody = "";
105
105
 
106
106
  if (extendsMatch) {
107
107
  const parentLayout = extendsMatch[1].match(/extends:\s*(\w+)/);
108
108
  if (parentLayout) {
109
- childBody = extendsMatch[2];
110
- const headPlaceholderMatch = childBody.match(/(\{\{\s*head\s*\|\s*safe\s*\}\})/);
109
+ const childContent = extendsMatch[2];
111
110
 
112
- if (headPlaceholderMatch) {
113
- const placeholderIdx = headPlaceholderMatch.index || 0;
114
- const beforePlaceholder = childBody.substring(0, placeholderIdx).trim();
115
- const afterPlaceholder = childBody.substring(placeholderIdx + headPlaceholderMatch[0].length).trim();
116
- childHead = beforePlaceholder;
117
- childBody = afterPlaceholder;
111
+ const headMatch = childContent.match(/\[head\]([\s\S]*?)\[\/head\]/);
112
+ if (headMatch) {
113
+ childHead = headMatch[1].trim();
114
+ }
115
+
116
+ const bodyMatch = childContent.match(/\[content\]([\s\S]*?)\[\/content\]/);
117
+ if (bodyMatch) {
118
+ childBody = bodyMatch[1];
119
+ } else {
120
+ childBody = childContent;
118
121
  }
119
122
 
120
123
  const parentResult = await resolveLayout({ extends: parentLayout[1] } as Frontmatter);
121
124
 
122
- let contentReplaced = parentResult.template.replace(
123
- /\{\{\s*content\s*\|\s*safe\s*\}\}/g,
124
- childBody
125
- );
125
+ let contentReplaced = parentResult.template;
126
+
127
+ if (bodyMatch) {
128
+ contentReplaced = contentReplaced.replace(
129
+ /\[content\]([\s\S]*?)\[\/content\]/g,
130
+ childBody
131
+ );
132
+ } else {
133
+ contentReplaced = contentReplaced.replace(
134
+ /\{\{\s*content\s*\|\s*safe\s*\}\}/g,
135
+ childBody
136
+ );
137
+ }
126
138
 
127
139
  const mergedHead = parentResult.head + (childHead ? "\n" + childHead : "");
128
140
 
129
- contentReplaced = contentReplaced.replace(
130
- /\{\{\s*head\s*\|\s*safe\s*\}\}/g,
131
- mergedHead
132
- );
141
+ if (headMatch) {
142
+ contentReplaced = contentReplaced.replace(
143
+ /\[head\]([\s\S]*?)\[\/head\]/g,
144
+ mergedHead
145
+ );
146
+ } else {
147
+ contentReplaced = contentReplaced.replace(
148
+ /\[head\]([\s\S]*?)\[\/head\]/g,
149
+ mergedHead
150
+ );
151
+ contentReplaced = contentReplaced.replace(
152
+ /\{\{\s*head\s*\|\s*safe\s*\}\}/g,
153
+ mergedHead
154
+ );
155
+ }
133
156
 
134
157
  return { template: contentReplaced, head: mergedHead };
135
158
  }
@@ -138,8 +161,8 @@ async function resolveLayout(frontmatter: Frontmatter): Promise<{ template: stri
138
161
  let ownHead = "";
139
162
  if (extendsMatch) {
140
163
  const bodyMatch = extendsMatch[2];
141
- const headMatch = bodyMatch.match(/\{\{\s*head\s*\|\s*safe\s*\}\}/);
142
- ownHead = headMatch ? headMatch[0] : "";
164
+ const headMatch = bodyMatch.match(/\[head\]([\s\S]*?)\[\/head\]/);
165
+ ownHead = headMatch ? headMatch[1].trim() : "";
143
166
  }
144
167
 
145
168
  return { template, head: ownHead };
@@ -6,7 +6,8 @@
6
6
  <title>{{ title }}</title>
7
7
  <meta name="description" content="{{ description }}">
8
8
  <link rel="stylesheet" href="/style.css">
9
- {{ head | safe }}
9
+ [head]
10
+ [/head]
10
11
  </head>
11
12
  <body>
12
13
  <header>
@@ -17,7 +18,8 @@
17
18
  </nav>
18
19
  </header>
19
20
  <main>
20
- {{ content | safe }}
21
+ [content]
22
+ [/content]
21
23
  </main>
22
24
  <footer>
23
25
  <p>Built with BunPress</p>
@@ -1,7 +1,9 @@
1
1
  ---
2
2
  extends: base.html
3
3
  ---
4
+ [content]
4
5
  <article class="page">
5
6
  <h1>{{ title }}</h1>
6
7
  {{ content | safe }}
7
8
  </article>
9
+ [/content]
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  extends: base.html
3
3
  ---
4
+ [head]
4
5
  <link rel="stylesheet" href="/post.css">
5
- {{ head | safe }}
6
+ [/head]
7
+ [content]
6
8
  <article class="post">
7
9
  <header>
8
10
  <h1>{{ title }}</h1>
@@ -12,3 +14,4 @@ extends: base.html
12
14
  {{ content | safe }}
13
15
  </div>
14
16
  </article>
17
+ [/content]