fossbook 0.2.2 → 0.2.3

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/init.js CHANGED
@@ -128,7 +128,7 @@ function initProject(options = {}) {
128
128
  // },
129
129
  // Add localized blog information to fossbook.config.en.js,
130
130
  // fossbook.config.ko.js, and so on. Menu labels can also be localized with
131
- // homeLabel, allPostsLabel, aboutLabel, tagsLabel, and allTagsLabel.
131
+ // homeLabel, allPostsLabel, aboutLabel, tagsLabel, allTagsLabel, and transcriptLabel.
132
132
 
133
133
  // Comment system (optional)
134
134
  // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
package/lib/mod/marked.js CHANGED
@@ -9,6 +9,52 @@ function escapeAttribute(value) {
9
9
  .replace(/>/g, ">");
10
10
  }
11
11
 
12
+ const panelStyleProperties = new Map([
13
+ ["background", "--panel-background"],
14
+ ["background-color", "--panel-background-color"],
15
+ ["border", "--panel-border"],
16
+ ["border-color", "--panel-border-color"],
17
+ ["border-radius", "--panel-border-radius"],
18
+ ["border-style", "--panel-border-style"],
19
+ ["border-width", "--panel-border-width"],
20
+ ["box-shadow", "--panel-box-shadow"],
21
+ ["gap", "--panel-gap"],
22
+ ]);
23
+
24
+ function parsePanelStyle(source) {
25
+ const declarations = [];
26
+ const properties = new Set();
27
+ const declarationPattern = /([a-z][a-z-]*)\s*:\s*([^;]+)(?:;|$)/gy;
28
+ let offset = 0;
29
+
30
+ while (offset < source.length) {
31
+ while (/\s/.test(source[offset])) offset += 1;
32
+ if (offset === source.length) break;
33
+
34
+ declarationPattern.lastIndex = offset;
35
+ const match = declarationPattern.exec(source);
36
+ if (!match) throw new Error(`Invalid panel style near: ${source.slice(offset)}`);
37
+
38
+ const property = match[1];
39
+ const value = match[2].trim();
40
+ if (!panelStyleProperties.has(property)) {
41
+ throw new Error(`Unsupported panel style property: ${property}`);
42
+ }
43
+ if (properties.has(property)) {
44
+ throw new Error(`Duplicate panel style property: ${property}`);
45
+ }
46
+ if (!value || /[{}]|url\s*\(|expression\s*\(|@import|javascript\s*:/i.test(value)) {
47
+ throw new Error(`Invalid value for panel style property: ${property}`);
48
+ }
49
+
50
+ properties.add(property);
51
+ declarations.push(`${panelStyleProperties.get(property)}: ${value};`);
52
+ offset = declarationPattern.lastIndex;
53
+ }
54
+
55
+ return declarations;
56
+ }
57
+
12
58
  function parsePanelAttributes(source) {
13
59
  const attributes = {};
14
60
  const attributePattern = /([a-z][a-z-]*)="([^"]*)"/gy;
@@ -21,7 +67,7 @@ function parsePanelAttributes(source) {
21
67
  attributePattern.lastIndex = offset;
22
68
  const match = attributePattern.exec(source);
23
69
  if (!match) throw new Error(`Invalid panels attribute near: ${source.slice(offset)}`);
24
- if (match[1] !== "columns" && match[1] !== "label") {
70
+ if (!["columns", "label", "boxed", "style"].includes(match[1])) {
25
71
  throw new Error(`Unsupported panels attribute: ${match[1]}`);
26
72
  }
27
73
  if (Object.hasOwn(attributes, match[1])) {
@@ -36,7 +82,20 @@ function parsePanelAttributes(source) {
36
82
  throw new Error("Panel columns must be an integer from 1 to 6");
37
83
  }
38
84
 
39
- return { columns, label: attributes.label };
85
+ if (attributes.boxed !== undefined && attributes.boxed !== "true") {
86
+ throw new Error('Panel boxed must be "true"');
87
+ }
88
+
89
+ if (attributes.style !== undefined && attributes.boxed !== "true") {
90
+ throw new Error('Panel style requires boxed="true"');
91
+ }
92
+
93
+ return {
94
+ columns,
95
+ label: attributes.label,
96
+ boxed: attributes.boxed === "true",
97
+ style: parsePanelStyle(attributes.style || ""),
98
+ };
40
99
  }
41
100
 
42
101
  const panelsExtension = {
@@ -55,6 +114,8 @@ const panelsExtension = {
55
114
  raw: match[0],
56
115
  columns: attributes.columns,
57
116
  label: attributes.label,
117
+ boxed: attributes.boxed,
118
+ style: attributes.style,
58
119
  tokens: this.lexer.blockTokens(match[2]),
59
120
  };
60
121
  },
@@ -62,7 +123,34 @@ const panelsExtension = {
62
123
  const label = token.label
63
124
  ? ` role="group" aria-label="${escapeAttribute(token.label)}"`
64
125
  : "";
65
- return `<div class="panel-group" style="--panel-columns: ${token.columns};"${label}>\n${this.parser.parse(token.tokens)}</div>\n`;
126
+ const className = token.boxed ? "panel-group panel-group-boxed" : "panel-group";
127
+ const style = [`--panel-columns: ${token.columns};`, ...token.style].join(" ");
128
+ return `<div class="${className}" style="${escapeAttribute(style)}"${label}>\n${this.parser.parse(token.tokens)}</div>\n`;
129
+ },
130
+ };
131
+
132
+ const comicBoxExtension = {
133
+ name: "comicBox",
134
+ level: "block",
135
+ start(source) {
136
+ return source.match(/^:::comic-box(?:[ \t]|$)/m)?.index;
137
+ },
138
+ tokenizer(source) {
139
+ const match = /^:::comic-box(?:[ \t]+label="([^"]*)")?[ \t]*\n([\s\S]*?)\n:::(?:\n|$)/.exec(source);
140
+ if (!match) return undefined;
141
+
142
+ return {
143
+ type: "comicBox",
144
+ raw: match[0],
145
+ label: match[1],
146
+ tokens: this.lexer.blockTokens(match[2]),
147
+ };
148
+ },
149
+ renderer(token) {
150
+ const label = token.label
151
+ ? ` aria-label="${escapeAttribute(token.label)}"`
152
+ : "";
153
+ return `<section class="comic-box"${label}>\n${this.parser.parse(token.tokens)}</section>\n`;
66
154
  },
67
155
  };
68
156
 
@@ -179,7 +267,7 @@ const renderer = {
179
267
  };
180
268
 
181
269
  marked.use({
182
- extensions: [panelsExtension],
270
+ extensions: [panelsExtension, comicBoxExtension],
183
271
  renderer,
184
272
  hooks: {
185
273
  postprocess(html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fossbook",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A lightweight static blog site generator for GitHub Pages",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -254,6 +254,81 @@
254
254
  text-align: center;
255
255
  }
256
256
 
257
+ .panel-group-boxed {
258
+ align-items: stretch;
259
+ grid-auto-rows: 1fr;
260
+ gap: var(--panel-gap, 0.75rem);
261
+ }
262
+
263
+ .panel-group-boxed .image-container {
264
+ height: 100%;
265
+ }
266
+
267
+ .panel-group-boxed figure {
268
+ height: 100%;
269
+ display: flex;
270
+ flex-direction: column;
271
+ border: var(--panel-border, 1px solid #575752);
272
+ border-radius: var(--panel-border-radius, 0);
273
+ background: var(--panel-background, #fff);
274
+ box-shadow: var(--panel-box-shadow, none);
275
+ }
276
+
277
+ .panel-group-boxed[style*="--panel-border-color:"] figure {
278
+ border-color: var(--panel-border-color);
279
+ }
280
+
281
+ .panel-group-boxed[style*="--panel-border-style:"] figure {
282
+ border-style: var(--panel-border-style);
283
+ }
284
+
285
+ .panel-group-boxed[style*="--panel-border-width:"] figure {
286
+ border-width: var(--panel-border-width);
287
+ }
288
+
289
+ .panel-group-boxed[style*="--panel-background-color:"] figure {
290
+ background-color: var(--panel-background-color);
291
+ }
292
+
293
+ .panel-group-boxed figcaption {
294
+ flex: 1;
295
+ margin-top: 0;
296
+ padding: 0.75rem;
297
+ font-size: 0.9rem;
298
+ }
299
+
300
+ .comic-box {
301
+ margin: 2rem 0;
302
+ padding: 1rem;
303
+ border: 2px solid #575752;
304
+ background: #fff;
305
+ box-shadow: 4px 4px 0 rgba(70, 70, 66, 0.14);
306
+ }
307
+
308
+ .comic-box > :first-child {
309
+ margin-top: 0;
310
+ }
311
+
312
+ .comic-box > :last-child {
313
+ margin-bottom: 0;
314
+ }
315
+
316
+ .comic-box .image-container,
317
+ .comic-box figure {
318
+ display: block;
319
+ width: 100%;
320
+ }
321
+
322
+ .comic-box figure {
323
+ padding: 0;
324
+ }
325
+
326
+ .comic-box .image-dialogue {
327
+ margin-bottom: 1rem;
328
+ padding-bottom: 1rem;
329
+ border-bottom: 1px solid #575752;
330
+ }
331
+
257
332
  @media screen and (max-width: 599px) {
258
333
  .panel-group {
259
334
  grid-template-columns: 1fr;
@@ -262,6 +337,13 @@
262
337
  .sized-image {
263
338
  width: 100% !important;
264
339
  }
340
+
341
+ .comic-box {
342
+ margin: 1.5rem 0;
343
+ padding: 0.75rem;
344
+ border-width: 1px;
345
+ box-shadow: 2px 2px 0 rgba(70, 70, 66, 0.14);
346
+ }
265
347
  }
266
348
 
267
349
  @media screen and (min-width: 600px) {
@@ -58,7 +58,7 @@
58
58
  </div>
59
59
  ${page.body.includes('class="blockquote-container image-dialogue"') ? `<div class="transcript-control">
60
60
  <div class="transcript-toggle">
61
- <label for="comic-transcript-toggle">Comic transcript</label>
61
+ <label for="comic-transcript-toggle">${page.config.transcriptLabel || "Display dialogue text"}</label>
62
62
  <input id="comic-transcript-toggle" type="checkbox" role="switch" aria-controls="comic-transcript" checked>
63
63
  </div>
64
64
  </div>` : ""}