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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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"
|
|
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>` : ""}
|