fossbook 0.2.0 → 0.2.1

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
@@ -1,6 +1,71 @@
1
1
  const marked = require("marked");
2
2
  const hljs = require("highlight.js");
3
3
 
4
+ function escapeAttribute(value) {
5
+ return value
6
+ .replace(/&/g, "&")
7
+ .replace(/"/g, """)
8
+ .replace(/</g, "&lt;")
9
+ .replace(/>/g, "&gt;");
10
+ }
11
+
12
+ function parsePanelAttributes(source) {
13
+ const attributes = {};
14
+ const attributePattern = /([a-z][a-z-]*)="([^"]*)"/gy;
15
+ let offset = 0;
16
+
17
+ while (offset < source.length) {
18
+ while (source[offset] === " " || source[offset] === "\t") offset += 1;
19
+ if (offset === source.length) break;
20
+
21
+ attributePattern.lastIndex = offset;
22
+ const match = attributePattern.exec(source);
23
+ if (!match) throw new Error(`Invalid panels attribute near: ${source.slice(offset)}`);
24
+ if (match[1] !== "columns" && match[1] !== "label") {
25
+ throw new Error(`Unsupported panels attribute: ${match[1]}`);
26
+ }
27
+ if (Object.hasOwn(attributes, match[1])) {
28
+ throw new Error(`Duplicate panels attribute: ${match[1]}`);
29
+ }
30
+ attributes[match[1]] = match[2];
31
+ offset = attributePattern.lastIndex;
32
+ }
33
+
34
+ const columns = attributes.columns === undefined ? 2 : Number(attributes.columns);
35
+ if (!Number.isInteger(columns) || columns < 1 || columns > 6) {
36
+ throw new Error("Panel columns must be an integer from 1 to 6");
37
+ }
38
+
39
+ return { columns, label: attributes.label };
40
+ }
41
+
42
+ const panelsExtension = {
43
+ name: "panels",
44
+ level: "block",
45
+ start(source) {
46
+ return source.match(/^:::panels(?:[ \t]|$)/m)?.index;
47
+ },
48
+ tokenizer(source) {
49
+ const match = /^:::panels([^\n]*)\n([\s\S]*?)\n:::(?:\n|$)/.exec(source);
50
+ if (!match) return undefined;
51
+
52
+ const attributes = parsePanelAttributes(match[1].trim());
53
+ return {
54
+ type: "panels",
55
+ raw: match[0],
56
+ columns: attributes.columns,
57
+ label: attributes.label,
58
+ tokens: this.lexer.blockTokens(match[2]),
59
+ };
60
+ },
61
+ renderer(token) {
62
+ const label = token.label
63
+ ? ` role="group" aria-label="${escapeAttribute(token.label)}"`
64
+ : "";
65
+ return `<div class="panel-group" style="--panel-columns: ${token.columns};"${label}>\n${this.parser.parse(token.tokens)}</div>\n`;
66
+ },
67
+ };
68
+
4
69
  marked.setOptions({
5
70
  renderer: new marked.Renderer(),
6
71
  pedantic: false,
@@ -66,7 +131,7 @@ const renderer = {
66
131
  // Construct the image tag with optional size and title
67
132
  let imageTag = `<img src="${href}" alt="${text}"`;
68
133
  if (size) {
69
- imageTag += ` style="width: ${size};"`;
134
+ imageTag += ` class="sized-image" style="width: ${size};"`;
70
135
  }
71
136
  imageTag += ">";
72
137
 
@@ -114,6 +179,7 @@ const renderer = {
114
179
  };
115
180
 
116
181
  marked.use({
182
+ extensions: [panelsExtension],
117
183
  renderer,
118
184
  hooks: {
119
185
  postprocess(html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fossbook",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A lightweight static blog site generator for GitHub Pages",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -223,6 +223,46 @@
223
223
  figure img {
224
224
  max-height: 500px;
225
225
  }
226
+
227
+ .panel-group {
228
+ display: grid;
229
+ grid-template-columns: repeat(var(--panel-columns), minmax(0, 1fr));
230
+ gap: 1.5rem;
231
+ align-items: start;
232
+ margin: 1.5rem 0;
233
+ }
234
+
235
+ .panel-group .image-container,
236
+ .panel-group figure {
237
+ min-width: 0;
238
+ width: 100%;
239
+ }
240
+
241
+ .panel-group figure {
242
+ display: block;
243
+ padding: 0;
244
+ }
245
+
246
+ .panel-group img {
247
+ display: block;
248
+ height: auto;
249
+ width: 100%;
250
+ }
251
+
252
+ .panel-group figcaption {
253
+ margin-top: 0.75rem;
254
+ text-align: center;
255
+ }
256
+
257
+ @media screen and (max-width: 599px) {
258
+ .panel-group {
259
+ grid-template-columns: 1fr;
260
+ }
261
+
262
+ .sized-image {
263
+ width: 100% !important;
264
+ }
265
+ }
226
266
 
227
267
  @media screen and (min-width: 600px) {
228
268
  figure {