czon 0.9.7 → 0.9.8
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/dist/ssg/ContentPage.js +85 -153
- package/dist/ssg/style.js +86 -1
- package/package.json +1 -1
package/dist/ssg/ContentPage.js
CHANGED
|
@@ -90,20 +90,36 @@ const ContentPage = props => {
|
|
|
90
90
|
react_1.default.createElement("button", { className: "share-float-btn", id: "share-float-btn" }, "Share"),
|
|
91
91
|
react_1.default.createElement("div", { className: "share-modal-overlay", id: "share-modal-overlay" },
|
|
92
92
|
react_1.default.createElement("div", { className: "share-modal" },
|
|
93
|
-
react_1.default.createElement("
|
|
93
|
+
react_1.default.createElement("img", { className: "share-preview", id: "share-preview", alt: "Share preview", src: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" }),
|
|
94
94
|
react_1.default.createElement("div", { className: "share-modal-actions" },
|
|
95
95
|
react_1.default.createElement("button", { className: "share-download-btn", id: "share-download-btn" }, "Save Image"),
|
|
96
96
|
react_1.default.createElement("button", { className: "share-close-btn", id: "share-close-btn" }, "Close")))),
|
|
97
|
+
react_1.default.createElement("div", { className: "share-card", id: "share-card" },
|
|
98
|
+
react_1.default.createElement("div", { className: "share-card-header" },
|
|
99
|
+
react_1.default.createElement("div", { className: "share-card-header-left" },
|
|
100
|
+
react_1.default.createElement("div", { className: "share-card-site", id: "share-card-site" }),
|
|
101
|
+
react_1.default.createElement("div", { className: "share-card-title", id: "share-card-title" })),
|
|
102
|
+
react_1.default.createElement("div", { className: "share-card-qr" },
|
|
103
|
+
react_1.default.createElement("canvas", { id: "share-qr-canvas", width: "64", height: "64" }),
|
|
104
|
+
react_1.default.createElement("span", { className: "share-card-qr-hint" }, "Scan to read"))),
|
|
105
|
+
react_1.default.createElement("div", { className: "share-card-divider" }),
|
|
106
|
+
react_1.default.createElement("div", { className: "share-card-body", id: "share-card-body" })),
|
|
97
107
|
react_1.default.createElement("script", { id: "qrcode-lib", src: "https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js", defer: true }),
|
|
108
|
+
react_1.default.createElement("script", { id: "html2canvas-lib", src: "https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js", defer: true }),
|
|
98
109
|
react_1.default.createElement("script", { dangerouslySetInnerHTML: {
|
|
99
110
|
__html: `
|
|
100
111
|
(function() {
|
|
101
112
|
var floatBtn = document.getElementById('share-float-btn');
|
|
102
113
|
var overlay = document.getElementById('share-modal-overlay');
|
|
103
|
-
var
|
|
114
|
+
var preview = document.getElementById('share-preview');
|
|
104
115
|
var downloadBtn = document.getElementById('share-download-btn');
|
|
105
116
|
var closeBtn = document.getElementById('share-close-btn');
|
|
106
|
-
var
|
|
117
|
+
var shareCard = document.getElementById('share-card');
|
|
118
|
+
var cardSite = document.getElementById('share-card-site');
|
|
119
|
+
var cardTitle = document.getElementById('share-card-title');
|
|
120
|
+
var cardBody = document.getElementById('share-card-body');
|
|
121
|
+
var qrCanvas = document.getElementById('share-qr-canvas');
|
|
122
|
+
var savedRange = null;
|
|
107
123
|
var articleTitle = ${JSON.stringify(title)};
|
|
108
124
|
var siteName = ${JSON.stringify(props.ctx.site.options?.site?.title || 'CZON')};
|
|
109
125
|
|
|
@@ -125,25 +141,18 @@ const ContentPage = props => {
|
|
|
125
141
|
floatBtn.style.display = 'none';
|
|
126
142
|
return;
|
|
127
143
|
}
|
|
128
|
-
|
|
144
|
+
savedRange = range.cloneRange();
|
|
129
145
|
var rect = range.getBoundingClientRect();
|
|
130
146
|
floatBtn.style.display = 'block';
|
|
131
|
-
floatBtn.style.top = (window.scrollY + rect.
|
|
147
|
+
floatBtn.style.top = (window.scrollY + rect.bottom + 6) + 'px';
|
|
132
148
|
floatBtn.style.left = (window.scrollX + rect.left + rect.width / 2 - 30) + 'px';
|
|
133
149
|
});
|
|
134
150
|
|
|
135
|
-
// Hide float button on click elsewhere
|
|
136
|
-
document.addEventListener('mousedown', function(e) {
|
|
137
|
-
if (e.target === floatBtn) return;
|
|
138
|
-
// Let selectionchange handle hiding
|
|
139
|
-
});
|
|
140
|
-
|
|
141
151
|
floatBtn.addEventListener('click', function(e) {
|
|
142
152
|
e.preventDefault();
|
|
143
153
|
e.stopPropagation();
|
|
144
|
-
if (!
|
|
145
|
-
renderShareCard(
|
|
146
|
-
overlay.classList.add('active');
|
|
154
|
+
if (!savedRange) return;
|
|
155
|
+
renderShareCard(savedRange);
|
|
147
156
|
floatBtn.style.display = 'none';
|
|
148
157
|
});
|
|
149
158
|
|
|
@@ -155,159 +164,82 @@ const ContentPage = props => {
|
|
|
155
164
|
});
|
|
156
165
|
|
|
157
166
|
downloadBtn.addEventListener('click', function() {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
a.download = 'share.png';
|
|
163
|
-
a.click();
|
|
164
|
-
URL.revokeObjectURL(url);
|
|
165
|
-
}, 'image/png');
|
|
167
|
+
var a = document.createElement('a');
|
|
168
|
+
a.href = preview.src;
|
|
169
|
+
a.download = 'share.png';
|
|
170
|
+
a.click();
|
|
166
171
|
});
|
|
167
172
|
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
var
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
function renderQR() {
|
|
174
|
+
if (typeof qrcode === 'undefined') return;
|
|
175
|
+
var size = 64;
|
|
176
|
+
var qr = qrcode(0, 'M');
|
|
177
|
+
qr.addData(window.location.href);
|
|
178
|
+
qr.make();
|
|
179
|
+
var moduleCount = qr.getModuleCount();
|
|
180
|
+
var cellSize = size / moduleCount;
|
|
181
|
+
var ctx = qrCanvas.getContext('2d');
|
|
182
|
+
ctx.clearRect(0, 0, size, size);
|
|
183
|
+
ctx.fillStyle = '#1a1a1a';
|
|
184
|
+
for (var r = 0; r < moduleCount; r++) {
|
|
185
|
+
for (var c = 0; c < moduleCount; c++) {
|
|
186
|
+
if (qr.isDark(r, c)) {
|
|
187
|
+
ctx.fillRect(c * cellSize, r * cellSize, cellSize + 0.5, cellSize + 0.5);
|
|
182
188
|
}
|
|
183
189
|
}
|
|
184
|
-
if (line) lines.push(line);
|
|
185
|
-
if (p < paragraphs.length - 1) lines.push('');
|
|
186
190
|
}
|
|
187
|
-
return lines;
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
function renderShareCard(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
var contentW = W - pad * 2;
|
|
195
|
-
var ctx = canvas.getContext('2d');
|
|
196
|
-
|
|
197
|
-
// Pre-calculate heights
|
|
198
|
-
ctx.font = 'bold 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
199
|
-
var titleLines = wrapText(ctx, articleTitle, contentW, 36);
|
|
200
|
-
var titleH = titleLines.length * 36;
|
|
201
|
-
|
|
202
|
-
ctx.font = '20px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
203
|
-
var maxTextLen = 300;
|
|
204
|
-
var displayText = text.length > maxTextLen ? text.slice(0, maxTextLen) + '...' : text;
|
|
205
|
-
var textLines = wrapText(ctx, displayText, contentW - 32, 30);
|
|
206
|
-
var textH = textLines.length * 30;
|
|
193
|
+
function renderShareCard(range) {
|
|
194
|
+
// Populate card content
|
|
195
|
+
cardSite.textContent = siteName;
|
|
196
|
+
cardTitle.textContent = articleTitle;
|
|
207
197
|
|
|
208
|
-
|
|
209
|
-
var
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
var quoteBottomPad = 24;
|
|
213
|
-
var qrTopPad = 32;
|
|
214
|
-
var qrBottomPad = 16;
|
|
215
|
-
var bottomPad = 32;
|
|
198
|
+
// Clone selected DOM fragment with rich formatting
|
|
199
|
+
var fragment = range.cloneContents();
|
|
200
|
+
cardBody.innerHTML = '';
|
|
201
|
+
cardBody.appendChild(fragment);
|
|
216
202
|
|
|
217
|
-
|
|
203
|
+
// Copy computed styles for elements that need them (e.g. KaTeX)
|
|
204
|
+
var allStyles = document.querySelectorAll('style, link[rel="stylesheet"]');
|
|
205
|
+
var styleClones = [];
|
|
206
|
+
allStyles.forEach(function(s) {
|
|
207
|
+
styleClones.push(s.cloneNode(true));
|
|
208
|
+
});
|
|
218
209
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
canvas.style.width = W + 'px';
|
|
222
|
-
canvas.style.height = H + 'px';
|
|
223
|
-
ctx.scale(dpr, dpr);
|
|
210
|
+
// Render QR code
|
|
211
|
+
renderQR();
|
|
224
212
|
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
ctx.beginPath();
|
|
228
|
-
ctx.roundRect(0, 0, W, H, 12);
|
|
229
|
-
ctx.fill();
|
|
230
|
-
|
|
231
|
-
var y = pad;
|
|
232
|
-
|
|
233
|
-
// Site name
|
|
234
|
-
ctx.fillStyle = '#999999';
|
|
235
|
-
ctx.font = '16px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
236
|
-
ctx.fillText(siteName, pad, y + 16);
|
|
237
|
-
y += siteNameH;
|
|
238
|
-
|
|
239
|
-
// Title
|
|
240
|
-
ctx.fillStyle = '#1a1a1a';
|
|
241
|
-
ctx.font = 'bold 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
242
|
-
for (var i = 0; i < titleLines.length; i++) {
|
|
243
|
-
ctx.fillText(titleLines[i], pad, y + 28);
|
|
244
|
-
y += 36;
|
|
245
|
-
}
|
|
246
|
-
y += separatorGap;
|
|
213
|
+
// Show card for html2canvas to capture
|
|
214
|
+
shareCard.classList.add('rendering');
|
|
247
215
|
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// Quote block background
|
|
258
|
-
var quoteBlockY = y;
|
|
259
|
-
var quoteBlockH = quoteTopPad + textH + quoteBottomPad;
|
|
260
|
-
ctx.fillStyle = '#f8f9fa';
|
|
261
|
-
ctx.beginPath();
|
|
262
|
-
ctx.roundRect(pad, quoteBlockY, contentW, quoteBlockH, 8);
|
|
263
|
-
ctx.fill();
|
|
264
|
-
|
|
265
|
-
// Quote accent bar
|
|
266
|
-
ctx.fillStyle = '#007bff';
|
|
267
|
-
ctx.beginPath();
|
|
268
|
-
ctx.roundRect(pad, quoteBlockY, 4, quoteBlockH, 2);
|
|
269
|
-
ctx.fill();
|
|
270
|
-
|
|
271
|
-
// Quote text
|
|
272
|
-
y += quoteTopPad;
|
|
273
|
-
ctx.fillStyle = '#333333';
|
|
274
|
-
ctx.font = '20px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
275
|
-
for (var i = 0; i < textLines.length; i++) {
|
|
276
|
-
if (textLines[i] !== '') {
|
|
277
|
-
ctx.fillText(textLines[i], pad + 16, y + 20);
|
|
216
|
+
// Wait a frame for layout
|
|
217
|
+
requestAnimationFrame(function() {
|
|
218
|
+
// Enforce minimum 3:4 aspect ratio
|
|
219
|
+
var cardW = shareCard.offsetWidth;
|
|
220
|
+
var cardH = shareCard.offsetHeight;
|
|
221
|
+
var minH = Math.round(cardW * 4 / 3);
|
|
222
|
+
if (cardH < minH) {
|
|
223
|
+
shareCard.style.minHeight = minH + 'px';
|
|
278
224
|
}
|
|
279
|
-
y += 30;
|
|
280
|
-
}
|
|
281
|
-
y += quoteBottomPad + qrTopPad;
|
|
282
|
-
|
|
283
|
-
// QR code
|
|
284
|
-
if (typeof qrcode !== 'undefined') {
|
|
285
|
-
var qr = qrcode(0, 'M');
|
|
286
|
-
qr.addData(window.location.href);
|
|
287
|
-
qr.make();
|
|
288
|
-
var moduleCount = qr.getModuleCount();
|
|
289
|
-
var cellSize = qrSize / moduleCount;
|
|
290
|
-
var qrX = W - pad - qrSize;
|
|
291
|
-
var qrY = y;
|
|
292
225
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
226
|
+
html2canvas(shareCard, {
|
|
227
|
+
scale: 2,
|
|
228
|
+
useCORS: true,
|
|
229
|
+
backgroundColor: '#ffffff',
|
|
230
|
+
logging: false,
|
|
231
|
+
}).then(function(canvas) {
|
|
232
|
+
shareCard.classList.remove('rendering');
|
|
233
|
+
shareCard.style.minHeight = '';
|
|
296
234
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// Hint text next to QR
|
|
307
|
-
ctx.fillStyle = '#999999';
|
|
308
|
-
ctx.font = '14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
309
|
-
ctx.fillText('Scan to read the original', pad, y + qrSize / 2 + 5);
|
|
310
|
-
}
|
|
235
|
+
preview.src = canvas.toDataURL('image/png');
|
|
236
|
+
overlay.classList.add('active');
|
|
237
|
+
}).catch(function(err) {
|
|
238
|
+
console.error('Share card render error:', err);
|
|
239
|
+
shareCard.classList.remove('rendering');
|
|
240
|
+
shareCard.style.minHeight = '';
|
|
241
|
+
});
|
|
242
|
+
});
|
|
311
243
|
}
|
|
312
244
|
})();
|
|
313
245
|
`,
|
|
@@ -410,7 +342,7 @@ const ContentPage = props => {
|
|
|
410
342
|
|
|
411
343
|
function renderEmblaCarousels() {
|
|
412
344
|
// Detect image groups, make them carousels automatically
|
|
413
|
-
Map.groupBy(document.querySelectorAll('img'), x => x.parentNode).entries().forEach(([container, images]) => {
|
|
345
|
+
Map.groupBy(document.querySelectorAll('.content-body img'), x => x.parentNode).entries().forEach(([container, images]) => {
|
|
414
346
|
const outer = document.createElement('div');
|
|
415
347
|
outer.classList.add('embla');
|
|
416
348
|
container.appendChild(outer);
|
package/dist/ssg/style.js
CHANGED
|
@@ -424,7 +424,7 @@ html:not(.dark) body {
|
|
|
424
424
|
align-items: center;
|
|
425
425
|
gap: 16px;
|
|
426
426
|
}
|
|
427
|
-
.share-modal
|
|
427
|
+
.share-modal img.share-preview {
|
|
428
428
|
max-width: 100%;
|
|
429
429
|
height: auto;
|
|
430
430
|
border-radius: 8px;
|
|
@@ -449,5 +449,90 @@ html:not(.dark) body {
|
|
|
449
449
|
background: var(--tag-bg);
|
|
450
450
|
color: var(--tag-text);
|
|
451
451
|
}
|
|
452
|
+
|
|
453
|
+
/* Share card (off-screen DOM for html2canvas capture) */
|
|
454
|
+
.share-card {
|
|
455
|
+
display: none;
|
|
456
|
+
position: fixed;
|
|
457
|
+
left: -9999px;
|
|
458
|
+
top: 0;
|
|
459
|
+
width: 540px;
|
|
460
|
+
min-height: 720px;
|
|
461
|
+
background: #ffffff;
|
|
462
|
+
border-radius: 12px;
|
|
463
|
+
padding: 36px;
|
|
464
|
+
box-sizing: border-box;
|
|
465
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
466
|
+
color: #1a1a1a;
|
|
467
|
+
flex-direction: column;
|
|
468
|
+
pointer-events: none;
|
|
469
|
+
}
|
|
470
|
+
.share-card.rendering {
|
|
471
|
+
display: flex;
|
|
472
|
+
}
|
|
473
|
+
.share-card-header {
|
|
474
|
+
display: flex;
|
|
475
|
+
justify-content: space-between;
|
|
476
|
+
align-items: flex-start;
|
|
477
|
+
gap: 12px;
|
|
478
|
+
margin-bottom: 20px;
|
|
479
|
+
}
|
|
480
|
+
.share-card-header-left {
|
|
481
|
+
flex: 1;
|
|
482
|
+
min-width: 0;
|
|
483
|
+
}
|
|
484
|
+
.share-card-site {
|
|
485
|
+
font-size: 14px;
|
|
486
|
+
color: #999999;
|
|
487
|
+
margin-bottom: 8px;
|
|
488
|
+
}
|
|
489
|
+
.share-card-title {
|
|
490
|
+
font-size: 24px;
|
|
491
|
+
font-weight: 700;
|
|
492
|
+
color: #1a1a1a;
|
|
493
|
+
line-height: 1.3;
|
|
494
|
+
word-wrap: break-word;
|
|
495
|
+
}
|
|
496
|
+
.share-card-qr {
|
|
497
|
+
flex-shrink: 0;
|
|
498
|
+
display: flex;
|
|
499
|
+
flex-direction: column;
|
|
500
|
+
align-items: center;
|
|
501
|
+
}
|
|
502
|
+
.share-card-qr canvas {
|
|
503
|
+
display: block;
|
|
504
|
+
}
|
|
505
|
+
.share-card-qr-hint {
|
|
506
|
+
font-size: 10px;
|
|
507
|
+
color: #bbbbbb;
|
|
508
|
+
margin-top: 4px;
|
|
509
|
+
white-space: nowrap;
|
|
510
|
+
}
|
|
511
|
+
.share-card-divider {
|
|
512
|
+
height: 1px;
|
|
513
|
+
background: #e5e5e5;
|
|
514
|
+
margin-bottom: 20px;
|
|
515
|
+
}
|
|
516
|
+
.share-card-body {
|
|
517
|
+
flex: 1;
|
|
518
|
+
background: #f8f9fa;
|
|
519
|
+
border-radius: 8px;
|
|
520
|
+
padding: 20px;
|
|
521
|
+
font-size: 18px;
|
|
522
|
+
line-height: 1.8;
|
|
523
|
+
color: #333333;
|
|
524
|
+
overflow: hidden;
|
|
525
|
+
}
|
|
526
|
+
.share-card-body img {
|
|
527
|
+
max-width: 100%;
|
|
528
|
+
height: auto;
|
|
529
|
+
}
|
|
530
|
+
.share-card-body pre {
|
|
531
|
+
white-space: pre-wrap;
|
|
532
|
+
word-wrap: break-word;
|
|
533
|
+
}
|
|
534
|
+
.share-card-body strong {
|
|
535
|
+
color: #ff5722;
|
|
536
|
+
}
|
|
452
537
|
`;
|
|
453
538
|
//# sourceMappingURL=style.js.map
|