pandorga 1.7.6 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5073aa0e7217490eff99e42008ea7eb10fdd6d5f7218d924d7ede563a77c2f4
4
- data.tar.gz: 38b8947ef41f97e1e1c6c6c2d2048fd5a16ebd1a441d4dc6ae62a9b5237582c1
3
+ metadata.gz: 03c8db4a237e9092a2f0d9256c118a162476cb1d056fc88518cfeccd60379bed
4
+ data.tar.gz: e8a2725fbc96d9c02ad55e39dce2c8e98d1eec14f20996ae5636d60f2d54181e
5
5
  SHA512:
6
- metadata.gz: 73d9948f8662e076b4f652697969ed7da53c108b336efea4b91c497310c7332f3c82f3d4ee854f6630a248c27356d219b6d50ffec70e437b098340a1153b1ee0
7
- data.tar.gz: b2dde560408d681b3cfcf3a4f5bc377542d1179b28279a19bbd065b8f7f9d04a994ebe7800a20645a18eb6562c9335d6e785f22f952d6c4b605c4f61f1ad912e
6
+ metadata.gz: a4e9108eac5982492245285b4954dc1e43a0ca10534411a8a221af35c5520a5a65c3a0fc9e8700bfc9c27588e41ee332a0204ebb697b9339514128e70b5237d1
7
+ data.tar.gz: 8ae715831b65d21cf206f071841f6c444f0b99598d93a2917ccaa09f4eecce658e2e93c0b61a75e375afa9b9a9baf0a6f17a47c70584f4ac6c5f656f8a728e75
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
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
+
3
32
  ## [1.7.6] — 2026-10-12
4
33
 
5
34
  ### 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
 
@@ -1007,6 +1007,91 @@ a.xcite-ref:hover {
1007
1007
  margin-bottom: 0;
1008
1008
  }
1009
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
+
1010
1095
  /* ══ Code blocks ══
1011
1096
  A fence used to render as a bare grey box: no indication of what language
1012
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
- /* ── Theorem / proof / key-takeaways callout ──
822
- Authored per article as `<div class="article-note" markdown="1">`; styled
823
- here, never injected. Ochre wash + tertiary spine = scannable field note. */
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.7.6"
27
+ # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.8.1"
28
28
  gem "csv"
29
29
  gem "base64"
30
30
  RUBY
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.7.6"
4
+ VERSION = "1.8.1"
5
5
  end
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "proof",
3
+ "markdown": "<div class=\"proof\" markdown=\"1\">\n\nA short claim with **bold**.\n\n</div>",
4
+ "selectors": ["proof", "proof-head", "proof-name", "proof-body"]
5
+ }
@@ -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
+ }
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.7.6
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