fossbook 0.2.2 → 0.2.4
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 +1 -1
- package/lib/mod/marked.js +181 -10
- package/package.json +1 -1
- package/themes/archie/assets/styles/main.css +123 -1
- package/themes/archie/layouts/post.html +1 -1
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,81 @@ function escapeAttribute(value) {
|
|
|
9
9
|
.replace(/>/g, ">");
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function matchFencedContainer(source, name) {
|
|
13
|
+
const opening = new RegExp(`^(:{3,})${name}(?=[ \\t]|\\n)([^\\n]*)\\n`).exec(source);
|
|
14
|
+
if (!opening) return undefined;
|
|
15
|
+
|
|
16
|
+
const contentStart = opening[0].length;
|
|
17
|
+
const closing = new RegExp(`^${opening[1]}[ \\t]*(?:\\n|$)`, "m")
|
|
18
|
+
.exec(source.slice(contentStart));
|
|
19
|
+
if (!closing) return undefined;
|
|
20
|
+
|
|
21
|
+
const contentEnd = contentStart + closing.index;
|
|
22
|
+
const rawEnd = contentEnd + closing[0].length;
|
|
23
|
+
return {
|
|
24
|
+
attributes: opening[2].trim(),
|
|
25
|
+
content: source.slice(contentStart, contentEnd),
|
|
26
|
+
raw: source.slice(0, rawEnd),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
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"],
|
|
39
|
+
["gap", "--panel-gap"],
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const comicPanelStyleProperties = new Map([
|
|
43
|
+
["background", "--comic-panel-background"],
|
|
44
|
+
["background-color", "--comic-panel-background-color"],
|
|
45
|
+
["border", "--comic-panel-border"],
|
|
46
|
+
["border-color", "--comic-panel-border-color"],
|
|
47
|
+
["border-radius", "--comic-panel-border-radius"],
|
|
48
|
+
["border-style", "--comic-panel-border-style"],
|
|
49
|
+
["border-width", "--comic-panel-border-width"],
|
|
50
|
+
["box-shadow", "--comic-panel-shadow"],
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
function parseStyle(source, styleProperties, context) {
|
|
54
|
+
const declarations = [];
|
|
55
|
+
const properties = new Set();
|
|
56
|
+
const declarationPattern = /([a-z][a-z-]*)\s*:\s*([^;]+)(?:;|$)/gy;
|
|
57
|
+
let offset = 0;
|
|
58
|
+
|
|
59
|
+
while (offset < source.length) {
|
|
60
|
+
while (/\s/.test(source[offset])) offset += 1;
|
|
61
|
+
if (offset === source.length) break;
|
|
62
|
+
|
|
63
|
+
declarationPattern.lastIndex = offset;
|
|
64
|
+
const match = declarationPattern.exec(source);
|
|
65
|
+
if (!match) throw new Error(`Invalid ${context} style near: ${source.slice(offset)}`);
|
|
66
|
+
|
|
67
|
+
const property = match[1];
|
|
68
|
+
const value = match[2].trim();
|
|
69
|
+
if (!styleProperties.has(property)) {
|
|
70
|
+
throw new Error(`Unsupported ${context} style property: ${property}`);
|
|
71
|
+
}
|
|
72
|
+
if (properties.has(property)) {
|
|
73
|
+
throw new Error(`Duplicate ${context} style property: ${property}`);
|
|
74
|
+
}
|
|
75
|
+
if (!value || /[{}]|url\s*\(|expression\s*\(|@import|javascript\s*:/i.test(value)) {
|
|
76
|
+
throw new Error(`Invalid value for ${context} style property: ${property}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
properties.add(property);
|
|
80
|
+
declarations.push(`${styleProperties.get(property)}: ${value};`);
|
|
81
|
+
offset = declarationPattern.lastIndex;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return declarations;
|
|
85
|
+
}
|
|
86
|
+
|
|
12
87
|
function parsePanelAttributes(source) {
|
|
13
88
|
const attributes = {};
|
|
14
89
|
const attributePattern = /([a-z][a-z-]*)="([^"]*)"/gy;
|
|
@@ -21,7 +96,7 @@ function parsePanelAttributes(source) {
|
|
|
21
96
|
attributePattern.lastIndex = offset;
|
|
22
97
|
const match = attributePattern.exec(source);
|
|
23
98
|
if (!match) throw new Error(`Invalid panels attribute near: ${source.slice(offset)}`);
|
|
24
|
-
if (
|
|
99
|
+
if (!["columns", "label", "boxed", "style"].includes(match[1])) {
|
|
25
100
|
throw new Error(`Unsupported panels attribute: ${match[1]}`);
|
|
26
101
|
}
|
|
27
102
|
if (Object.hasOwn(attributes, match[1])) {
|
|
@@ -36,36 +111,132 @@ function parsePanelAttributes(source) {
|
|
|
36
111
|
throw new Error("Panel columns must be an integer from 1 to 6");
|
|
37
112
|
}
|
|
38
113
|
|
|
39
|
-
|
|
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
|
+
return {
|
|
124
|
+
columns,
|
|
125
|
+
label: attributes.label,
|
|
126
|
+
boxed: attributes.boxed === "true",
|
|
127
|
+
style,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseComicPanelAttributes(source) {
|
|
132
|
+
const attributes = {};
|
|
133
|
+
const attributePattern = /([a-z][a-z-]*)="([^"]*)"/gy;
|
|
134
|
+
let offset = 0;
|
|
135
|
+
|
|
136
|
+
while (offset < source.length) {
|
|
137
|
+
while (source[offset] === " " || source[offset] === "\t") offset += 1;
|
|
138
|
+
if (offset === source.length) break;
|
|
139
|
+
|
|
140
|
+
attributePattern.lastIndex = offset;
|
|
141
|
+
const match = attributePattern.exec(source);
|
|
142
|
+
if (!match) throw new Error(`Invalid panel attribute near: ${source.slice(offset)}`);
|
|
143
|
+
if (!["label", "divider", "style"].includes(match[1])) {
|
|
144
|
+
throw new Error(`Unsupported panel attribute: ${match[1]}`);
|
|
145
|
+
}
|
|
146
|
+
if (Object.hasOwn(attributes, match[1])) {
|
|
147
|
+
throw new Error(`Duplicate panel attribute: ${match[1]}`);
|
|
148
|
+
}
|
|
149
|
+
attributes[match[1]] = match[2];
|
|
150
|
+
offset = attributePattern.lastIndex;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (attributes.divider !== undefined && attributes.divider !== "true") {
|
|
154
|
+
throw new Error('Panel divider must be "true"');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
label: attributes.label,
|
|
159
|
+
divider: attributes.divider === "true",
|
|
160
|
+
style: parseStyle(attributes.style || "", comicPanelStyleProperties, "panel"),
|
|
161
|
+
};
|
|
40
162
|
}
|
|
41
163
|
|
|
42
164
|
const panelsExtension = {
|
|
43
165
|
name: "panels",
|
|
44
166
|
level: "block",
|
|
45
167
|
start(source) {
|
|
46
|
-
return source.match(
|
|
168
|
+
return source.match(/^:{3,}panels(?:[ \t]|$)/m)?.index;
|
|
47
169
|
},
|
|
48
170
|
tokenizer(source) {
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
171
|
+
const container = matchFencedContainer(source, "panels");
|
|
172
|
+
if (!container) return undefined;
|
|
51
173
|
|
|
52
|
-
const attributes = parsePanelAttributes(
|
|
174
|
+
const attributes = parsePanelAttributes(container.attributes);
|
|
175
|
+
const tokens = this.lexer.blockTokens(container.content);
|
|
53
176
|
return {
|
|
54
177
|
type: "panels",
|
|
55
|
-
raw:
|
|
178
|
+
raw: container.raw,
|
|
56
179
|
columns: attributes.columns,
|
|
57
180
|
label: attributes.label,
|
|
58
|
-
|
|
181
|
+
boxed: attributes.boxed,
|
|
182
|
+
style: attributes.style,
|
|
183
|
+
nested: tokens.some((token) => token.type === "panel"),
|
|
184
|
+
tokens,
|
|
59
185
|
};
|
|
60
186
|
},
|
|
61
187
|
renderer(token) {
|
|
62
188
|
const label = token.label
|
|
63
189
|
? ` role="group" aria-label="${escapeAttribute(token.label)}"`
|
|
64
190
|
: "";
|
|
65
|
-
|
|
191
|
+
const className = [
|
|
192
|
+
"panel-group",
|
|
193
|
+
token.boxed ? "panel-group-boxed" : "",
|
|
194
|
+
token.nested ? "panel-group-nested" : "",
|
|
195
|
+
].filter(Boolean).join(" ");
|
|
196
|
+
const style = [`--panel-columns: ${token.columns};`, ...token.style].join(" ");
|
|
197
|
+
return `<div class="${className}" style="${escapeAttribute(style)}"${label}>\n${this.parser.parse(token.tokens)}</div>\n`;
|
|
66
198
|
},
|
|
67
199
|
};
|
|
68
200
|
|
|
201
|
+
function createComicPanelExtension(name, type) {
|
|
202
|
+
return {
|
|
203
|
+
name: type,
|
|
204
|
+
level: "block",
|
|
205
|
+
start(source) {
|
|
206
|
+
return source.match(new RegExp(`^:{3,}${name}(?:[ \\t]|$)`, "m"))?.index;
|
|
207
|
+
},
|
|
208
|
+
tokenizer(source) {
|
|
209
|
+
const container = matchFencedContainer(source, name);
|
|
210
|
+
if (!container) return undefined;
|
|
211
|
+
|
|
212
|
+
const attributes = parseComicPanelAttributes(container.attributes);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
type,
|
|
216
|
+
raw: container.raw,
|
|
217
|
+
label: attributes.label,
|
|
218
|
+
divider: attributes.divider,
|
|
219
|
+
style: attributes.style,
|
|
220
|
+
tokens: this.lexer.blockTokens(container.content),
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
renderer(token) {
|
|
224
|
+
const label = token.label
|
|
225
|
+
? ` aria-label="${escapeAttribute(token.label)}"`
|
|
226
|
+
: "";
|
|
227
|
+
const style = token.style.length
|
|
228
|
+
? ` style="${escapeAttribute(token.style.join(" "))}"`
|
|
229
|
+
: "";
|
|
230
|
+
const className = token.divider
|
|
231
|
+
? "comic-panel comic-panel-divider"
|
|
232
|
+
: "comic-panel";
|
|
233
|
+
return `<section class="${className}"${style}${label}>\n${this.parser.parse(token.tokens)}</section>\n`;
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const panelExtension = createComicPanelExtension("panel", "panel");
|
|
239
|
+
|
|
69
240
|
marked.setOptions({
|
|
70
241
|
renderer: new marked.Renderer(),
|
|
71
242
|
pedantic: false,
|
|
@@ -179,7 +350,7 @@ const renderer = {
|
|
|
179
350
|
};
|
|
180
351
|
|
|
181
352
|
marked.use({
|
|
182
|
-
extensions: [panelsExtension],
|
|
353
|
+
extensions: [panelsExtension, panelExtension],
|
|
183
354
|
renderer,
|
|
184
355
|
hooks: {
|
|
185
356
|
postprocess(html) {
|
package/package.json
CHANGED
|
@@ -227,11 +227,23 @@
|
|
|
227
227
|
.panel-group {
|
|
228
228
|
display: grid;
|
|
229
229
|
grid-template-columns: repeat(var(--panel-columns), minmax(0, 1fr));
|
|
230
|
-
gap: 1.5rem;
|
|
230
|
+
gap: var(--panel-gap, 1.5rem);
|
|
231
231
|
align-items: start;
|
|
232
232
|
margin: 1.5rem 0;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
.panel-group-nested {
|
|
236
|
+
align-items: stretch;
|
|
237
|
+
grid-auto-rows: 1fr;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.panel-group-nested > .comic-panel {
|
|
241
|
+
box-sizing: border-box;
|
|
242
|
+
min-width: 0;
|
|
243
|
+
height: 100%;
|
|
244
|
+
margin: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
235
247
|
.panel-group .image-container,
|
|
236
248
|
.panel-group figure {
|
|
237
249
|
min-width: 0;
|
|
@@ -254,6 +266,101 @@
|
|
|
254
266
|
text-align: center;
|
|
255
267
|
}
|
|
256
268
|
|
|
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: var(--panel-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-color:"] > .image-container > figure {
|
|
290
|
+
border-color: var(--panel-border-color);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.panel-group-boxed[style*="--panel-border-style:"] > .image-container > figure {
|
|
294
|
+
border-style: var(--panel-border-style);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.panel-group-boxed[style*="--panel-border-width:"] > .image-container > figure {
|
|
298
|
+
border-width: var(--panel-border-width);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.panel-group-boxed[style*="--panel-background-color:"] > .image-container > figure {
|
|
302
|
+
background-color: var(--panel-background-color);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.panel-group-boxed > .image-container > figure > figcaption {
|
|
306
|
+
flex: 1;
|
|
307
|
+
margin-top: 0;
|
|
308
|
+
padding: 0.75rem;
|
|
309
|
+
font-size: 0.9rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.comic-panel {
|
|
313
|
+
margin: 2rem 0;
|
|
314
|
+
padding: 1rem;
|
|
315
|
+
border: var(--comic-panel-border, 2px solid #575752);
|
|
316
|
+
border-radius: var(--comic-panel-border-radius, 0);
|
|
317
|
+
background: var(--comic-panel-background, #fff);
|
|
318
|
+
box-shadow: var(--comic-panel-shadow, 4px 4px 0 rgba(70, 70, 66, 0.14));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.comic-panel[style*="--comic-panel-border-color:"] {
|
|
322
|
+
border-color: var(--comic-panel-border-color);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.comic-panel[style*="--comic-panel-border-style:"] {
|
|
326
|
+
border-style: var(--comic-panel-border-style);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.comic-panel[style*="--comic-panel-border-width:"] {
|
|
330
|
+
border-width: var(--comic-panel-border-width);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.comic-panel[style*="--comic-panel-background-color:"] {
|
|
334
|
+
background-color: var(--comic-panel-background-color);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.comic-panel > :first-child {
|
|
338
|
+
margin-top: 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.comic-panel > :last-child {
|
|
342
|
+
margin-bottom: 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.comic-panel .image-container,
|
|
346
|
+
.comic-panel figure {
|
|
347
|
+
display: block;
|
|
348
|
+
width: 100%;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.comic-panel figure {
|
|
352
|
+
padding: 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.comic-panel .image-dialogue {
|
|
356
|
+
margin-bottom: 1rem;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.comic-panel-divider .image-dialogue {
|
|
360
|
+
padding-bottom: 1rem;
|
|
361
|
+
border-bottom: 1px solid #575752;
|
|
362
|
+
}
|
|
363
|
+
|
|
257
364
|
@media screen and (max-width: 599px) {
|
|
258
365
|
.panel-group {
|
|
259
366
|
grid-template-columns: 1fr;
|
|
@@ -262,6 +369,21 @@
|
|
|
262
369
|
.sized-image {
|
|
263
370
|
width: 100% !important;
|
|
264
371
|
}
|
|
372
|
+
|
|
373
|
+
.comic-panel {
|
|
374
|
+
margin: 1.5rem 0;
|
|
375
|
+
padding: 0.75rem;
|
|
376
|
+
border-width: 1px;
|
|
377
|
+
box-shadow: 2px 2px 0 rgba(70, 70, 66, 0.14);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.comic-panel[style*="--comic-panel-border:"] {
|
|
382
|
+
border: var(--comic-panel-border);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.comic-panel[style*="--comic-panel-shadow:"] {
|
|
386
|
+
box-shadow: var(--comic-panel-shadow);
|
|
265
387
|
}
|
|
266
388
|
|
|
267
389
|
@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>` : ""}
|