czon 0.9.6 → 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 +158 -1
- package/dist/ssg/style.js +156 -0
- package/package.json +1 -1
package/dist/ssg/ContentPage.js
CHANGED
|
@@ -87,6 +87,163 @@ const ContentPage = props => {
|
|
|
87
87
|
react_1.default.createElement("footer", { className: "footer" },
|
|
88
88
|
react_1.default.createElement(LanguageSwitcher_1.LanguageSwitcher, { ctx: props.ctx, lang: props.lang, file: props.file }),
|
|
89
89
|
react_1.default.createElement(CZONFooter_1.CZONFooter, null))) }),
|
|
90
|
+
react_1.default.createElement("button", { className: "share-float-btn", id: "share-float-btn" }, "Share"),
|
|
91
|
+
react_1.default.createElement("div", { className: "share-modal-overlay", id: "share-modal-overlay" },
|
|
92
|
+
react_1.default.createElement("div", { className: "share-modal" },
|
|
93
|
+
react_1.default.createElement("img", { className: "share-preview", id: "share-preview", alt: "Share preview", src: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" }),
|
|
94
|
+
react_1.default.createElement("div", { className: "share-modal-actions" },
|
|
95
|
+
react_1.default.createElement("button", { className: "share-download-btn", id: "share-download-btn" }, "Save Image"),
|
|
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" })),
|
|
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 }),
|
|
109
|
+
react_1.default.createElement("script", { dangerouslySetInnerHTML: {
|
|
110
|
+
__html: `
|
|
111
|
+
(function() {
|
|
112
|
+
var floatBtn = document.getElementById('share-float-btn');
|
|
113
|
+
var overlay = document.getElementById('share-modal-overlay');
|
|
114
|
+
var preview = document.getElementById('share-preview');
|
|
115
|
+
var downloadBtn = document.getElementById('share-download-btn');
|
|
116
|
+
var closeBtn = document.getElementById('share-close-btn');
|
|
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;
|
|
123
|
+
var articleTitle = ${JSON.stringify(title)};
|
|
124
|
+
var siteName = ${JSON.stringify(props.ctx.site.options?.site?.title || 'CZON')};
|
|
125
|
+
|
|
126
|
+
// Detect text selection within .content-body
|
|
127
|
+
document.addEventListener('selectionchange', function() {
|
|
128
|
+
var sel = window.getSelection();
|
|
129
|
+
if (!sel || sel.isCollapsed || !sel.rangeCount) {
|
|
130
|
+
floatBtn.style.display = 'none';
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
var range = sel.getRangeAt(0);
|
|
134
|
+
var container = document.querySelector('.content-body');
|
|
135
|
+
if (!container || !container.contains(range.commonAncestorContainer)) {
|
|
136
|
+
floatBtn.style.display = 'none';
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
var text = sel.toString().trim();
|
|
140
|
+
if (!text) {
|
|
141
|
+
floatBtn.style.display = 'none';
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
savedRange = range.cloneRange();
|
|
145
|
+
var rect = range.getBoundingClientRect();
|
|
146
|
+
floatBtn.style.display = 'block';
|
|
147
|
+
floatBtn.style.top = (window.scrollY + rect.bottom + 6) + 'px';
|
|
148
|
+
floatBtn.style.left = (window.scrollX + rect.left + rect.width / 2 - 30) + 'px';
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
floatBtn.addEventListener('click', function(e) {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
if (!savedRange) return;
|
|
155
|
+
renderShareCard(savedRange);
|
|
156
|
+
floatBtn.style.display = 'none';
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
closeBtn.addEventListener('click', function() {
|
|
160
|
+
overlay.classList.remove('active');
|
|
161
|
+
});
|
|
162
|
+
overlay.addEventListener('click', function(e) {
|
|
163
|
+
if (e.target === overlay) overlay.classList.remove('active');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
downloadBtn.addEventListener('click', function() {
|
|
167
|
+
var a = document.createElement('a');
|
|
168
|
+
a.href = preview.src;
|
|
169
|
+
a.download = 'share.png';
|
|
170
|
+
a.click();
|
|
171
|
+
});
|
|
172
|
+
|
|
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);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function renderShareCard(range) {
|
|
194
|
+
// Populate card content
|
|
195
|
+
cardSite.textContent = siteName;
|
|
196
|
+
cardTitle.textContent = articleTitle;
|
|
197
|
+
|
|
198
|
+
// Clone selected DOM fragment with rich formatting
|
|
199
|
+
var fragment = range.cloneContents();
|
|
200
|
+
cardBody.innerHTML = '';
|
|
201
|
+
cardBody.appendChild(fragment);
|
|
202
|
+
|
|
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
|
+
});
|
|
209
|
+
|
|
210
|
+
// Render QR code
|
|
211
|
+
renderQR();
|
|
212
|
+
|
|
213
|
+
// Show card for html2canvas to capture
|
|
214
|
+
shareCard.classList.add('rendering');
|
|
215
|
+
|
|
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';
|
|
224
|
+
}
|
|
225
|
+
|
|
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 = '';
|
|
234
|
+
|
|
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
|
+
});
|
|
243
|
+
}
|
|
244
|
+
})();
|
|
245
|
+
`,
|
|
246
|
+
} }),
|
|
90
247
|
react_1.default.createElement("script", { id: "hljs-lib", src: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js", defer: true }),
|
|
91
248
|
react_1.default.createElement("script", null, `
|
|
92
249
|
document.getElementById('hljs-lib').addEventListener('load', () => {
|
|
@@ -185,7 +342,7 @@ const ContentPage = props => {
|
|
|
185
342
|
|
|
186
343
|
function renderEmblaCarousels() {
|
|
187
344
|
// Detect image groups, make them carousels automatically
|
|
188
|
-
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]) => {
|
|
189
346
|
const outer = document.createElement('div');
|
|
190
347
|
outer.classList.add('embla');
|
|
191
348
|
container.appendChild(outer);
|
package/dist/ssg/style.js
CHANGED
|
@@ -378,5 +378,161 @@ html:not(.dark) body {
|
|
|
378
378
|
flex: 0 0 100%;
|
|
379
379
|
min-width: 0;
|
|
380
380
|
}
|
|
381
|
+
|
|
382
|
+
/* Share feature */
|
|
383
|
+
.share-float-btn {
|
|
384
|
+
display: none;
|
|
385
|
+
position: absolute;
|
|
386
|
+
z-index: 1000;
|
|
387
|
+
background: var(--primary-color);
|
|
388
|
+
color: #fff;
|
|
389
|
+
border: none;
|
|
390
|
+
border-radius: 6px;
|
|
391
|
+
padding: 4px 12px;
|
|
392
|
+
font-size: 14px;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
395
|
+
white-space: nowrap;
|
|
396
|
+
line-height: 1.6;
|
|
397
|
+
}
|
|
398
|
+
.share-float-btn:hover {
|
|
399
|
+
opacity: 0.9;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.share-modal-overlay {
|
|
403
|
+
display: none;
|
|
404
|
+
position: fixed;
|
|
405
|
+
inset: 0;
|
|
406
|
+
z-index: 2000;
|
|
407
|
+
background: rgba(0,0,0,0.5);
|
|
408
|
+
justify-content: center;
|
|
409
|
+
align-items: center;
|
|
410
|
+
}
|
|
411
|
+
.share-modal-overlay.active {
|
|
412
|
+
display: flex;
|
|
413
|
+
}
|
|
414
|
+
.share-modal {
|
|
415
|
+
background: var(--bg-primary);
|
|
416
|
+
border-radius: 12px;
|
|
417
|
+
padding: 20px;
|
|
418
|
+
max-width: 90vw;
|
|
419
|
+
max-height: 90vh;
|
|
420
|
+
overflow: auto;
|
|
421
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
422
|
+
display: flex;
|
|
423
|
+
flex-direction: column;
|
|
424
|
+
align-items: center;
|
|
425
|
+
gap: 16px;
|
|
426
|
+
}
|
|
427
|
+
.share-modal img.share-preview {
|
|
428
|
+
max-width: 100%;
|
|
429
|
+
height: auto;
|
|
430
|
+
border-radius: 8px;
|
|
431
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
432
|
+
}
|
|
433
|
+
.share-modal-actions {
|
|
434
|
+
display: flex;
|
|
435
|
+
gap: 12px;
|
|
436
|
+
}
|
|
437
|
+
.share-modal-actions button {
|
|
438
|
+
padding: 8px 24px;
|
|
439
|
+
border-radius: 6px;
|
|
440
|
+
border: none;
|
|
441
|
+
font-size: 14px;
|
|
442
|
+
cursor: pointer;
|
|
443
|
+
}
|
|
444
|
+
.share-modal-actions .share-download-btn {
|
|
445
|
+
background: var(--primary-color);
|
|
446
|
+
color: #fff;
|
|
447
|
+
}
|
|
448
|
+
.share-modal-actions .share-close-btn {
|
|
449
|
+
background: var(--tag-bg);
|
|
450
|
+
color: var(--tag-text);
|
|
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
|
+
}
|
|
381
537
|
`;
|
|
382
538
|
//# sourceMappingURL=style.js.map
|