fossbook 0.2.5 → 0.2.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/lib/mod/marked.js CHANGED
@@ -9,6 +9,36 @@ function escapeAttribute(value) {
9
9
  .replace(/>/g, ">");
10
10
  }
11
11
 
12
+ function getWikipediaCitation(href) {
13
+ let url;
14
+ try {
15
+ url = new URL(href);
16
+ } catch {
17
+ return null;
18
+ }
19
+
20
+ const hostname = url.hostname.toLowerCase();
21
+ const hostnameMatch = /^([a-z-]+)\.(?:m\.)?wikipedia\.org$/.exec(hostname);
22
+ if (!hostnameMatch || !url.pathname.startsWith("/wiki/")) return null;
23
+
24
+ let articleTitle;
25
+ try {
26
+ articleTitle = decodeURIComponent(url.pathname.slice(6)).replace(/_/g, " ");
27
+ } catch {
28
+ return null;
29
+ }
30
+ if (!articleTitle) return null;
31
+
32
+ const source = hostnameMatch[1] === "ko" ? "위키백과" : "Wikipedia";
33
+ return `${source}, ${articleTitle}`;
34
+ }
35
+
36
+ function renderWikipediaCitation(href) {
37
+ const citation = getWikipediaCitation(href);
38
+ if (!citation) return null;
39
+ return `<a href="${escapeAttribute(href)}">${escapeAttribute(citation)}</a>`;
40
+ }
41
+
12
42
  function matchFencedContainer(source, name) {
13
43
  const opening = new RegExp(`^(:{3,})${name}(?=[ \\t]|\\n)([^\\n]*)\\n`).exec(source);
14
44
  if (!opening) return undefined;
@@ -27,15 +57,7 @@ function matchFencedContainer(source, name) {
27
57
  };
28
58
  }
29
59
 
30
- const panelStyleProperties = new Map([
31
- ["background", "--panel-background"],
32
- ["background-color", "--panel-background-color"],
33
- ["border", "--panel-border"],
34
- ["border-color", "--panel-border-color"],
35
- ["border-radius", "--panel-border-radius"],
36
- ["border-style", "--panel-border-style"],
37
- ["border-width", "--panel-border-width"],
38
- ["box-shadow", "--panel-box-shadow"],
60
+ const panelGroupStyleProperties = new Map([
39
61
  ["gap", "--panel-gap"],
40
62
  ]);
41
63
 
@@ -96,7 +118,7 @@ function parsePanelAttributes(source) {
96
118
  attributePattern.lastIndex = offset;
97
119
  const match = attributePattern.exec(source);
98
120
  if (!match) throw new Error(`Invalid panels attribute near: ${source.slice(offset)}`);
99
- if (!["columns", "label", "boxed", "style"].includes(match[1])) {
121
+ if (!["columns", "label", "style"].includes(match[1])) {
100
122
  throw new Error(`Unsupported panels attribute: ${match[1]}`);
101
123
  }
102
124
  if (Object.hasOwn(attributes, match[1])) {
@@ -111,20 +133,10 @@ function parsePanelAttributes(source) {
111
133
  throw new Error("Panel columns must be an integer from 1 to 6");
112
134
  }
113
135
 
114
- if (attributes.boxed !== undefined && attributes.boxed !== "true") {
115
- throw new Error('Panel boxed must be "true"');
116
- }
117
-
118
- const style = parseStyle(attributes.style || "", panelStyleProperties, "panel");
119
- if (attributes.boxed !== "true" && style.some((declaration) => !declaration.startsWith("--panel-gap:"))) {
120
- throw new Error('Panel card styles require boxed="true"');
121
- }
122
-
123
136
  return {
124
137
  columns,
125
138
  label: attributes.label,
126
- boxed: attributes.boxed === "true",
127
- style,
139
+ style: parseStyle(attributes.style || "", panelGroupStyleProperties, "panel group"),
128
140
  };
129
141
  }
130
142
 
@@ -140,7 +152,7 @@ function parseComicPanelAttributes(source) {
140
152
  attributePattern.lastIndex = offset;
141
153
  const match = attributePattern.exec(source);
142
154
  if (!match) throw new Error(`Invalid panel attribute near: ${source.slice(offset)}`);
143
- if (!["label", "divider", "style"].includes(match[1])) {
155
+ if (!["label", "divider", "rounded", "style"].includes(match[1])) {
144
156
  throw new Error(`Unsupported panel attribute: ${match[1]}`);
145
157
  }
146
158
  if (Object.hasOwn(attributes, match[1])) {
@@ -153,10 +165,14 @@ function parseComicPanelAttributes(source) {
153
165
  if (attributes.divider !== undefined && attributes.divider !== "true") {
154
166
  throw new Error('Panel divider must be "true"');
155
167
  }
168
+ if (attributes.rounded !== undefined && attributes.rounded !== "true") {
169
+ throw new Error('Panel rounded must be "true"');
170
+ }
156
171
 
157
172
  return {
158
173
  label: attributes.label,
159
174
  divider: attributes.divider === "true",
175
+ rounded: attributes.rounded === "true",
160
176
  style: parseStyle(attributes.style || "", comicPanelStyleProperties, "panel"),
161
177
  };
162
178
  }
@@ -178,7 +194,6 @@ const panelsExtension = {
178
194
  raw: container.raw,
179
195
  columns: attributes.columns,
180
196
  label: attributes.label,
181
- boxed: attributes.boxed,
182
197
  style: attributes.style,
183
198
  nested: tokens.some((token) => token.type === "panel"),
184
199
  tokens,
@@ -190,7 +205,6 @@ const panelsExtension = {
190
205
  : "";
191
206
  const className = [
192
207
  "panel-group",
193
- token.boxed ? "panel-group-boxed" : "",
194
208
  token.nested ? "panel-group-nested" : "",
195
209
  ].filter(Boolean).join(" ");
196
210
  const style = [`--panel-columns: ${token.columns};`, ...token.style].join(" ");
@@ -198,6 +212,26 @@ const panelsExtension = {
198
212
  },
199
213
  };
200
214
 
215
+ const wikipediaUrlExtension = {
216
+ name: "wikipediaUrl",
217
+ level: "inline",
218
+ start(source) {
219
+ return source.search(/https?:\/\/[a-z-]+\.(?:m\.)?wikipedia\.org\/wiki\//i);
220
+ },
221
+ tokenizer(source) {
222
+ const match = /^https?:\/\/[a-z-]+\.(?:m\.)?wikipedia\.org\/wiki\/[^\s<>"']+/i.exec(source);
223
+ if (!match) return undefined;
224
+
225
+ const raw = match[0].replace(/[.,;:!?]+$/, "");
226
+ const citation = getWikipediaCitation(raw);
227
+ if (!citation) return undefined;
228
+ return { type: "wikipediaUrl", raw, href: raw, citation };
229
+ },
230
+ renderer(token) {
231
+ return renderWikipediaCitation(token.href);
232
+ },
233
+ };
234
+
201
235
  function createComicPanelExtension(name, type) {
202
236
  return {
203
237
  name: type,
@@ -216,6 +250,7 @@ function createComicPanelExtension(name, type) {
216
250
  raw: container.raw,
217
251
  label: attributes.label,
218
252
  divider: attributes.divider,
253
+ rounded: attributes.rounded,
219
254
  style: attributes.style,
220
255
  tokens: this.lexer.blockTokens(container.content),
221
256
  };
@@ -227,9 +262,11 @@ function createComicPanelExtension(name, type) {
227
262
  const style = token.style.length
228
263
  ? ` style="${escapeAttribute(token.style.join(" "))}"`
229
264
  : "";
230
- const className = token.divider
231
- ? "comic-panel comic-panel-divider"
232
- : "comic-panel";
265
+ const className = [
266
+ "comic-panel",
267
+ token.divider ? "comic-panel-divider" : "",
268
+ token.rounded ? "comic-panel-rounded" : "",
269
+ ].filter(Boolean).join(" ");
233
270
  return `<section class="${className}"${style}${label}>\n${this.parser.parse(token.tokens)}</section>\n`;
234
271
  },
235
272
  };
@@ -316,6 +353,11 @@ const renderer = {
316
353
  `;
317
354
  },
318
355
  link(href, title, text) {
356
+ if (!title && text === href) {
357
+ const citation = renderWikipediaCitation(href);
358
+ if (citation) return citation;
359
+ }
360
+
319
361
  let align = null;
320
362
  if (title && title.includes("align:")) {
321
363
  // align: left, right, center
@@ -350,7 +392,7 @@ const renderer = {
350
392
  };
351
393
 
352
394
  marked.use({
353
- extensions: [panelsExtension, panelExtension],
395
+ extensions: [panelsExtension, panelExtension, wikipediaUrlExtension],
354
396
  renderer,
355
397
  hooks: {
356
398
  postprocess(html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fossbook",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "A lightweight static blog site generator for GitHub Pages",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -179,6 +179,10 @@
179
179
  font-weight: bold;
180
180
  }
181
181
 
182
+ ol > li::marker {
183
+ font-family: 'Roboto Mono', monospace;
184
+ }
185
+
182
186
  /*
183
187
  ul.pagination {
184
188
  display: flex;
@@ -266,60 +270,17 @@
266
270
  text-align: center;
267
271
  }
268
272
 
269
- .panel-group-boxed {
270
- align-items: stretch;
271
- grid-auto-rows: 1fr;
272
- gap: var(--panel-gap, 0.75rem);
273
- }
274
-
275
- .panel-group-boxed > .image-container {
276
- height: 100%;
277
- }
278
-
279
- .panel-group-boxed > .image-container > figure {
280
- height: 100%;
281
- display: flex;
282
- flex-direction: column;
283
- border: 1px solid #575752;
284
- border-radius: var(--panel-border-radius, 0);
285
- background: var(--panel-background, #fff);
286
- box-shadow: var(--panel-box-shadow, none);
287
- }
288
-
289
- .panel-group-boxed[style*="--panel-border:"] > .image-container > figure {
290
- border: var(--panel-border);
291
- }
292
-
293
- .panel-group-boxed[style*="--panel-border-color:"] > .image-container > figure {
294
- border-color: var(--panel-border-color);
295
- }
296
-
297
- .panel-group-boxed[style*="--panel-border-style:"] > .image-container > figure {
298
- border-style: var(--panel-border-style);
299
- }
300
-
301
- .panel-group-boxed[style*="--panel-border-width:"] > .image-container > figure {
302
- border-width: var(--panel-border-width);
303
- }
304
-
305
- .panel-group-boxed[style*="--panel-background-color:"] > .image-container > figure {
306
- background-color: var(--panel-background-color);
307
- }
308
-
309
- .panel-group-boxed > .image-container > figure > figcaption {
310
- flex: 1;
311
- margin-top: 0;
312
- padding: 0.75rem;
313
- font-size: 0.9rem;
314
- }
315
-
316
273
  .comic-panel {
317
274
  margin: 2rem 0;
318
275
  padding: 1rem;
319
- border: 2px solid #575752;
276
+ border: 1.5px solid #575752;
320
277
  border-radius: var(--comic-panel-border-radius, 0);
321
278
  background: var(--comic-panel-background, #fff);
322
- box-shadow: var(--comic-panel-shadow, 4px 4px 0 rgba(70, 70, 66, 0.14));
279
+ box-shadow: var(--comic-panel-shadow, 2px 2px 0 rgba(70, 70, 66, 0.14));
280
+ }
281
+
282
+ .comic-panel-rounded {
283
+ --comic-panel-border-radius: 4px 7px 5px 8px / 7px 4px 8px 5px;
323
284
  }
324
285
 
325
286
  .comic-panel[style*="--comic-panel-border-color:"] {
@@ -378,7 +339,7 @@
378
339
  margin: 1.5rem 0;
379
340
  padding: 0.75rem;
380
341
  border-width: 1px;
381
- box-shadow: 2px 2px 0 rgba(70, 70, 66, 0.14);
342
+ box-shadow: 1px 1px 0 rgba(70, 70, 66, 0.14);
382
343
  }
383
344
  }
384
345
 
@@ -581,6 +542,16 @@
581
542
  display: flex;
582
543
  justify-content: space-between;
583
544
  }
545
+ .post-tags ul.tags {
546
+ display: flex;
547
+ flex-wrap: wrap;
548
+ gap: 0.35rem 0.8rem;
549
+ margin: 0;
550
+ padding: 0;
551
+ }
552
+ .post-tags ul.tags li {
553
+ text-indent: 0;
554
+ }
584
555
  .tags li::before{
585
556
  content: "🏷 ";
586
557
  }