pandorga 1.7.5 → 1.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/_includes/content-runtime/20-markdown.html +35 -0
- data/assets/css/article.css +102 -0
- data/assets/css/ledger/detail.css +4 -3
- data/lib/pandorga/commands/new.rb +1 -1
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/render-parity/proof.json +5 -0
- data/scripts/test/render-parity/run-fixture.mjs +19 -0
- data/scripts/test/render-parity/theorem.json +5 -0
- data/scripts/test/test-css-structure.rb +22 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03c8db4a237e9092a2f0d9256c118a162476cb1d056fc88518cfeccd60379bed
|
|
4
|
+
data.tar.gz: e8a2725fbc96d9c02ad55e39dce2c8e98d1eec14f20996ae5636d60f2d54181e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4e9108eac5982492245285b4954dc1e43a0ca10534411a8a221af35c5520a5a65c3a0fc9e8700bfc9c27588e41ee332a0204ebb697b9339514128e70b5237d1
|
|
7
|
+
data.tar.gz: 8ae715831b65d21cf206f071841f6c444f0b99598d93a2917ccaa09f4eecce658e2e93c0b61a75e375afa9b9a9baf0a6f17a47c70584f4ac6c5f656f8a728e75
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.1] — 2026-10-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Theorems are bare (no gray plate); only proofs carry the surface band
|
|
8
|
+
- Proof gray bleeds to the edges of `.text-page-main` while body text stays
|
|
9
|
+
on the reading measure
|
|
10
|
+
- Proofs prefix **Proof.** from the runtime, like theorem heads — authors
|
|
11
|
+
no longer write `*Proof.*` in the body
|
|
12
|
+
|
|
13
|
+
No `pandorga:` config keys were added or renamed. Font roles and the
|
|
14
|
+
ledger fallbacks are unchanged.
|
|
15
|
+
|
|
16
|
+
## [1.8.0] — 2026-10-12
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Theorem and proof Markdown wrappers in the content runtime, same
|
|
21
|
+
`markdown="1"` contract as `article-note` and `poem`:
|
|
22
|
+
- `<div class="theorem" name="Corollary 1" description="nadir camera" markdown="1">…</div>`
|
|
23
|
+
builds the head from `name` / `description` and italicizes the body
|
|
24
|
+
- `<div class="proof" markdown="1">…</div>` wraps a proof and places a
|
|
25
|
+
QED square on the last line
|
|
26
|
+
- Full-column styles for `.theorem` and `.proof` in `article.css`
|
|
27
|
+
- Render-parity fixtures `theorem.json` and `proof.json`
|
|
28
|
+
|
|
29
|
+
No `pandorga:` config keys were added or renamed. Font roles and the
|
|
30
|
+
ledger fallbacks are unchanged.
|
|
31
|
+
|
|
32
|
+
## [1.7.6] — 2026-10-12
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A long inline formula scrolls inside the article column below 768px.
|
|
37
|
+
Display math was already contained, and a 390px phone still measured
|
|
38
|
+
scrollWidth 667 because `mjx-container` without `display="true"` is
|
|
39
|
+
`display: inline` and cannot scroll. Below 768px that box is
|
|
40
|
+
`inline-block`, `max-width: 100%`, `overflow-x: auto`, and
|
|
41
|
+
`overflow-y: hidden`. Wider viewports keep it inline. `html` and
|
|
42
|
+
`body` do not set overflow.
|
|
43
|
+
|
|
44
|
+
No `pandorga:` config keys were added or renamed. Font roles and the
|
|
45
|
+
ledger fallbacks are unchanged.
|
|
46
|
+
|
|
3
47
|
## [1.7.5] — 2026-10-12
|
|
4
48
|
|
|
5
49
|
### Fixed
|
|
@@ -109,6 +109,31 @@
|
|
|
109
109
|
return parts.join('');
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function attrValue(openTag, name) {
|
|
113
|
+
var re = new RegExp('\\b' + name + '\\s*=\\s*(?:"([^"]*)"|\'([^\']*)\')', 'i');
|
|
114
|
+
var m = String(openTag || '').match(re);
|
|
115
|
+
return m ? String(m[1] != null ? m[1] : m[2] || '') : '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function renderTheoremHtml(openTag, inner) {
|
|
119
|
+
var name = attrValue(openTag, 'name').trim();
|
|
120
|
+
var description = attrValue(openTag, 'description').trim();
|
|
121
|
+
var head = '';
|
|
122
|
+
if (name) {
|
|
123
|
+
head = '<p class="theorem-head"><span class="theorem-name">' + escapeHtml(name) + '</span>';
|
|
124
|
+
if (description) {
|
|
125
|
+
head += ' <span class="theorem-description">(' + escapeHtml(description) + ')</span>';
|
|
126
|
+
}
|
|
127
|
+
head += '.</p>';
|
|
128
|
+
}
|
|
129
|
+
return '<div class="theorem">' + head + '<div class="theorem-body">' + parseInlineMarkdown(inner) + '</div></div>';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderProofHtml(inner) {
|
|
133
|
+
var head = '<p class="proof-head"><span class="proof-name">Proof</span>.</p>';
|
|
134
|
+
return '<div class="proof">' + head + '<div class="proof-body">' + parseInlineMarkdown(inner) + '</div></div>';
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
function preprocessMarkdownDivs(markdown) {
|
|
113
138
|
var text = String(markdown || '');
|
|
114
139
|
text = text.replace(/<div\s+([^>]*class="[^"]*\barticle-note\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
|
|
@@ -119,6 +144,16 @@
|
|
|
119
144
|
if (!hasMarkdownAttr(openTag)) return match;
|
|
120
145
|
return renderPoemHtml(inner);
|
|
121
146
|
});
|
|
147
|
+
// Theorem / proof: same markdown="1" contract as article-note. Without
|
|
148
|
+
// this pass, CommonMark ends the HTML block at the first blank line.
|
|
149
|
+
text = text.replace(/<div\s+([^>]*class="[^"]*\btheorem\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
|
|
150
|
+
if (!hasMarkdownAttr(openTag)) return match;
|
|
151
|
+
return renderTheoremHtml(openTag, inner);
|
|
152
|
+
});
|
|
153
|
+
text = text.replace(/<div\s+([^>]*class="[^"]*\bproof\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
|
|
154
|
+
if (!hasMarkdownAttr(openTag)) return match;
|
|
155
|
+
return renderProofHtml(inner);
|
|
156
|
+
});
|
|
122
157
|
return text;
|
|
123
158
|
}
|
|
124
159
|
|
data/assets/css/article.css
CHANGED
|
@@ -99,6 +99,23 @@
|
|
|
99
99
|
vertical-align: baseline;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/* Below 768px a long inline formula is still `display: inline`, so it has
|
|
103
|
+
no scrollport and widens the document. Display math was already contained,
|
|
104
|
+
and a 390px phone still measured scrollWidth 667 from the inline boxes.
|
|
105
|
+
`inline-block` keeps the formula in the line and lets max-width and
|
|
106
|
+
overflow apply. `[jax]` stays ahead of MathJax's own container rule.
|
|
107
|
+
Desktop stays inline. Do not put this overflow on html or body. */
|
|
108
|
+
@media (max-width: 767px) {
|
|
109
|
+
.article-content mjx-container:not([display="true"]),
|
|
110
|
+
.article-content mjx-container[jax]:not([display="true"]) {
|
|
111
|
+
display: inline-block;
|
|
112
|
+
max-width: 100%;
|
|
113
|
+
overflow-x: auto;
|
|
114
|
+
overflow-y: hidden;
|
|
115
|
+
vertical-align: baseline;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
/* A display equation is a block in the prose flow, so it takes the paragraph
|
|
103
120
|
gap and nothing more. Two things used to inflate it, to a measured 52px
|
|
104
121
|
above the equation and 39px below against a 22px paragraph gap.
|
|
@@ -990,6 +1007,91 @@ a.xcite-ref:hover {
|
|
|
990
1007
|
margin-bottom: 0;
|
|
991
1008
|
}
|
|
992
1009
|
|
|
1010
|
+
/* ── Theorem and proof blocks ──
|
|
1011
|
+
Authored as `<div class="theorem" name="…" description="…" markdown="1">`
|
|
1012
|
+
and `<div class="proof" markdown="1">`. Theorems are bare (no plate): the
|
|
1013
|
+
runtime builds the head from name / description and italicizes the body.
|
|
1014
|
+
Proofs sit on a gray band that bleeds to the edges of `.text-page-main`
|
|
1015
|
+
while the text keeps the body measure; the runtime prefixes "Proof." and
|
|
1016
|
+
a QED square lands on the last line of the body. */
|
|
1017
|
+
.article-content .theorem {
|
|
1018
|
+
display: block;
|
|
1019
|
+
width: 100%;
|
|
1020
|
+
box-sizing: border-box;
|
|
1021
|
+
margin: 1.5em 0;
|
|
1022
|
+
font-size: inherit;
|
|
1023
|
+
line-height: inherit;
|
|
1024
|
+
color: var(--color-on-surface);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.article-content .theorem-head,
|
|
1028
|
+
.article-content .proof-head {
|
|
1029
|
+
margin: 0 0 0.5em;
|
|
1030
|
+
font-style: normal;
|
|
1031
|
+
font-weight: var(--weight-body);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.article-content .theorem-name,
|
|
1035
|
+
.article-content .proof-name {
|
|
1036
|
+
font-weight: var(--weight-emphasis);
|
|
1037
|
+
font-style: normal;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.article-content .theorem-description {
|
|
1041
|
+
font-weight: var(--weight-body);
|
|
1042
|
+
font-style: normal;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.article-content .theorem-body {
|
|
1046
|
+
font-style: italic;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.article-content .theorem-body > :first-child,
|
|
1050
|
+
.article-content .proof-body > :first-child {
|
|
1051
|
+
margin-top: 0;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.article-content .theorem-body > :last-child,
|
|
1055
|
+
.article-content .proof-body > :last-child {
|
|
1056
|
+
margin-bottom: 0;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.article-content .proof {
|
|
1060
|
+
display: flow-root;
|
|
1061
|
+
box-sizing: border-box;
|
|
1062
|
+
position: relative;
|
|
1063
|
+
width: auto;
|
|
1064
|
+
max-width: none;
|
|
1065
|
+
margin: 1.5em calc(-1 * var(--space-md));
|
|
1066
|
+
padding: 1rem var(--space-md);
|
|
1067
|
+
background: var(--color-surface-container-low);
|
|
1068
|
+
font-size: inherit;
|
|
1069
|
+
line-height: inherit;
|
|
1070
|
+
color: var(--color-on-surface);
|
|
1071
|
+
-webkit-print-color-adjust: exact;
|
|
1072
|
+
print-color-adjust: exact;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
@media (min-width: 768px) {
|
|
1076
|
+
.article-content .proof {
|
|
1077
|
+
margin-left: calc(-1 * var(--space-2xl));
|
|
1078
|
+
margin-right: calc(-1 * var(--space-2xl));
|
|
1079
|
+
padding-left: var(--space-2xl);
|
|
1080
|
+
padding-right: var(--space-2xl);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.article-content .proof-body > :last-child::after {
|
|
1085
|
+
content: "□";
|
|
1086
|
+
float: right;
|
|
1087
|
+
margin-left: 0.35em;
|
|
1088
|
+
font-size: inherit;
|
|
1089
|
+
line-height: inherit;
|
|
1090
|
+
color: var(--color-on-surface);
|
|
1091
|
+
pointer-events: none;
|
|
1092
|
+
user-select: none;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
993
1095
|
/* ══ Code blocks ══
|
|
994
1096
|
A fence used to render as a bare grey box: no indication of what language
|
|
995
1097
|
was in it, and nothing to do with it but select the text by hand. It now
|
|
@@ -818,9 +818,10 @@ aside:has(> .text-detail-sidebar) {
|
|
|
818
818
|
font-weight: inherit;
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
-
/* ──
|
|
822
|
-
Authored
|
|
823
|
-
|
|
821
|
+
/* ── Field-note callout ──
|
|
822
|
+
Authored as `<div class="article-note" markdown="1">`; styled here, never
|
|
823
|
+
injected. Ochre wash + tertiary spine = scannable field note. Formal
|
|
824
|
+
theorem / proof blocks use `.theorem` / `.proof` in article.css instead. */
|
|
824
825
|
.article-content .article-note {
|
|
825
826
|
position: relative;
|
|
826
827
|
background: color-mix(in srgb, var(--color-tertiary) 10%, var(--color-panel-contrast));
|
|
@@ -24,7 +24,7 @@ module Pandorga
|
|
|
24
24
|
|
|
25
25
|
gem "pandorga", "~> 1.3"
|
|
26
26
|
# Local checkout: gem "pandorga", path: "../pandorga"
|
|
27
|
-
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.
|
|
27
|
+
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.8.1"
|
|
28
28
|
gem "csv"
|
|
29
29
|
gem "base64"
|
|
30
30
|
RUBY
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -128,6 +128,25 @@ function preprocessMarkdownDivs(markdown) {
|
|
|
128
128
|
if (!hasMarkdownAttr(openTag)) return match;
|
|
129
129
|
return `<div class="poem"><p>${inner.trim()}</p></div>`;
|
|
130
130
|
});
|
|
131
|
+
text = text.replace(/<div\s+([^>]*class="[^"]*\btheorem\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, (match, openTag, inner) => {
|
|
132
|
+
if (!hasMarkdownAttr(openTag)) return match;
|
|
133
|
+
const nameMatch = openTag.match(/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
|
|
134
|
+
const descMatch = openTag.match(/\bdescription\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
|
|
135
|
+
const n = nameMatch ? (nameMatch[1] ?? nameMatch[2] ?? '') : '';
|
|
136
|
+
const d = descMatch ? (descMatch[1] ?? descMatch[2] ?? '') : '';
|
|
137
|
+
let head = '';
|
|
138
|
+
if (n) {
|
|
139
|
+
head = `<p class="theorem-head"><span class="theorem-name">${escapeAttr(n)}</span>`;
|
|
140
|
+
if (d) head += ` <span class="theorem-description">(${escapeAttr(d)})</span>`;
|
|
141
|
+
head += '.</p>';
|
|
142
|
+
}
|
|
143
|
+
return `<div class="theorem">${head}<div class="theorem-body">${marked.parse(inner.trim())}</div></div>`;
|
|
144
|
+
});
|
|
145
|
+
text = text.replace(/<div\s+([^>]*class="[^"]*\bproof\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, (match, openTag, inner) => {
|
|
146
|
+
if (!hasMarkdownAttr(openTag)) return match;
|
|
147
|
+
const head = '<p class="proof-head"><span class="proof-name">Proof</span>.</p>';
|
|
148
|
+
return `<div class="proof">${head}<div class="proof-body">${marked.parse(inner.trim())}</div></div>`;
|
|
149
|
+
});
|
|
131
150
|
return text;
|
|
132
151
|
}
|
|
133
152
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "theorem",
|
|
3
|
+
"markdown": "<div class=\"theorem\" name=\"Corollary 1\" description=\"nadir camera\" markdown=\"1\">\n\nLet the camera look straight down.\n\n</div>",
|
|
4
|
+
"selectors": ["theorem", "theorem-head", "theorem-name", "theorem-description", "theorem-body"]
|
|
5
|
+
}
|
|
@@ -160,6 +160,28 @@ if article_sheet.match?(/mjx-container[^{]*\{[^}]*overflow\s*:\s*visible/m)
|
|
|
160
160
|
errors << "assets/css/article.css: display math sets overflow: visible"
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
+
# PLT-AC-38. Inline formulas scroll inside the column below 768px. The
|
|
164
|
+
# unscoped rule stays display:inline so a short formula on a wide column
|
|
165
|
+
# remains in the line.
|
|
166
|
+
inline_selector = '.article-content mjx-container:not([display="true"])'
|
|
167
|
+
inline_jax = '.article-content mjx-container[jax]:not([display="true"])'
|
|
168
|
+
inline_bodies = declaration_bodies(article_sheet, inline_selector)
|
|
169
|
+
inline_base = inline_bodies.find { |body| body.match?(/display:\s*inline\s*;/) && !body.match?(/inline-block/) }
|
|
170
|
+
unless inline_base
|
|
171
|
+
errors << "assets/css/article.css: #{inline_selector} must stay display:inline outside the phone query"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
mobile_inline = nil
|
|
175
|
+
article_sheet.scan(/@media\s*\(\s*max-width:\s*767px\s*\)\s*\{((?:[^{}]|\{[^{}]*\})*)\}/m) do |inner,|
|
|
176
|
+
mobile_inline = inner if inner.include?(inline_selector)
|
|
177
|
+
end
|
|
178
|
+
if mobile_inline.nil?
|
|
179
|
+
errors << "assets/css/article.css: inline math scroll is not limited to max-width 767px"
|
|
180
|
+
else
|
|
181
|
+
require_declarations(errors, "assets/css/article.css", mobile_inline, inline_selector, math_box + ["display: inline-block"])
|
|
182
|
+
require_declarations(errors, "assets/css/article.css", mobile_inline, inline_jax, math_box + ["display: inline-block"])
|
|
183
|
+
end
|
|
184
|
+
|
|
163
185
|
require_declarations(
|
|
164
186
|
errors, "assets/css/article.css", article_sheet,
|
|
165
187
|
".article-content figure", ["max-width: 100%"]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pandorga
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Pandorga contributors
|
|
@@ -393,12 +393,14 @@ files:
|
|
|
393
393
|
- scripts/test/render-parity/ongoing-product.json
|
|
394
394
|
- scripts/test/render-parity/plotly.json
|
|
395
395
|
- scripts/test/render-parity/poem.json
|
|
396
|
+
- scripts/test/render-parity/proof.json
|
|
396
397
|
- scripts/test/render-parity/run-absent-field-checks.mjs
|
|
397
398
|
- scripts/test/render-parity/run-block-fixture.mjs
|
|
398
399
|
- scripts/test/render-parity/run-detail-parity-checks.mjs
|
|
399
400
|
- scripts/test/render-parity/run-field-flow-checks.mjs
|
|
400
401
|
- scripts/test/render-parity/run-fixture.mjs
|
|
401
402
|
- scripts/test/render-parity/selectors.mjs
|
|
403
|
+
- scripts/test/render-parity/theorem.json
|
|
402
404
|
- scripts/test/render-parity/video.json
|
|
403
405
|
- scripts/test/render-parity/youtube-short.json
|
|
404
406
|
- scripts/test/render-parity/youtube.json
|