czon 0.9.5 → 0.9.7
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 +241 -4
- package/dist/ssg/style.js +71 -0
- package/package.json +1 -1
package/dist/ssg/ContentPage.js
CHANGED
|
@@ -59,15 +59,15 @@ const ContentPage = props => {
|
|
|
59
59
|
} })),
|
|
60
60
|
react_1.default.createElement("body", null,
|
|
61
61
|
react_1.default.createElement(PageLayout_1.PageLayout, { header: react_1.default.createElement(CZONHeader_1.CZONHeader, { ctx: props.ctx, lang: props.lang, file: props.file }), navigator: react_1.default.createElement("nav", { className: "sidebar border-r" },
|
|
62
|
-
react_1.default.createElement(Navigator_1.Navigator, { ctx: props.ctx, file: props.file, lang: props.lang })), rightSidebar: react_1.default.createElement("aside", { className: "sidebar border-l" },
|
|
62
|
+
react_1.default.createElement(Navigator_1.Navigator, { ctx: props.ctx, file: props.file, lang: props.lang })), rightSidebar: props.content.headings.length > 0 ? (react_1.default.createElement("aside", { className: "sidebar border-l" },
|
|
63
63
|
react_1.default.createElement("h2", { className: "text-2xl font-semibold mb-2" }, "Table of Contents"),
|
|
64
|
-
props.content.headings.map(heading => (react_1.default.createElement("a", { key: heading.id, href: `#${heading.id}`, className: `block ms-${(heading.depth - 1) * 4} mb-2 border-b`, dangerouslySetInnerHTML: { __html: heading.html } })))), main: react_1.default.createElement("main", { className: "content max-w-4xl mx-auto my-8 px-4" },
|
|
64
|
+
props.content.headings.map(heading => (react_1.default.createElement("a", { key: heading.id, href: `#${heading.id}`, className: `block ms-${(heading.depth - 1) * 4} mb-2 border-b`, dangerouslySetInnerHTML: { __html: heading.html } }))))) : undefined, main: react_1.default.createElement("main", { className: "content max-w-4xl mx-auto my-8 px-4" },
|
|
65
65
|
react_1.default.createElement(ContentMeta_1.ContentMeta, { ctx: props.ctx, file: props.file, lang: props.lang }),
|
|
66
|
-
react_1.default.createElement("div", { className: "border-b mb-4 pb-2 xl:hidden" },
|
|
66
|
+
props.content.headings.length > 0 && (react_1.default.createElement("div", { className: "border-b mb-4 pb-2 xl:hidden" },
|
|
67
67
|
react_1.default.createElement("h2", { className: "text-2xl font-semibold mb-2" }, "Table of Contents"),
|
|
68
68
|
props.content.headings.map(heading => (react_1.default.createElement("a", { key: heading.id, href: `#${heading.id}`,
|
|
69
69
|
// 按照 heading.depth 设置缩进
|
|
70
|
-
className: `block ps-${heading.depth * 4} mb-2`, dangerouslySetInnerHTML: { __html: heading.html } })))),
|
|
70
|
+
className: `block ps-${heading.depth * 4} mb-2`, dangerouslySetInnerHTML: { __html: heading.html } }))))),
|
|
71
71
|
react_1.default.createElement("div", { className: "content-body" },
|
|
72
72
|
react_1.default.createElement("article", { dangerouslySetInnerHTML: { __html: props.content.body } }),
|
|
73
73
|
relatedContents.length > 0 && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
@@ -87,6 +87,231 @@ 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("canvas", { id: "share-canvas" }),
|
|
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("script", { id: "qrcode-lib", src: "https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js", defer: true }),
|
|
98
|
+
react_1.default.createElement("script", { dangerouslySetInnerHTML: {
|
|
99
|
+
__html: `
|
|
100
|
+
(function() {
|
|
101
|
+
var floatBtn = document.getElementById('share-float-btn');
|
|
102
|
+
var overlay = document.getElementById('share-modal-overlay');
|
|
103
|
+
var canvas = document.getElementById('share-canvas');
|
|
104
|
+
var downloadBtn = document.getElementById('share-download-btn');
|
|
105
|
+
var closeBtn = document.getElementById('share-close-btn');
|
|
106
|
+
var selectedText = '';
|
|
107
|
+
var articleTitle = ${JSON.stringify(title)};
|
|
108
|
+
var siteName = ${JSON.stringify(props.ctx.site.options?.site?.title || 'CZON')};
|
|
109
|
+
|
|
110
|
+
// Detect text selection within .content-body
|
|
111
|
+
document.addEventListener('selectionchange', function() {
|
|
112
|
+
var sel = window.getSelection();
|
|
113
|
+
if (!sel || sel.isCollapsed || !sel.rangeCount) {
|
|
114
|
+
floatBtn.style.display = 'none';
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
var range = sel.getRangeAt(0);
|
|
118
|
+
var container = document.querySelector('.content-body');
|
|
119
|
+
if (!container || !container.contains(range.commonAncestorContainer)) {
|
|
120
|
+
floatBtn.style.display = 'none';
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
var text = sel.toString().trim();
|
|
124
|
+
if (!text) {
|
|
125
|
+
floatBtn.style.display = 'none';
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
selectedText = text;
|
|
129
|
+
var rect = range.getBoundingClientRect();
|
|
130
|
+
floatBtn.style.display = 'block';
|
|
131
|
+
floatBtn.style.top = (window.scrollY + rect.top - 36) + 'px';
|
|
132
|
+
floatBtn.style.left = (window.scrollX + rect.left + rect.width / 2 - 30) + 'px';
|
|
133
|
+
});
|
|
134
|
+
|
|
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
|
+
floatBtn.addEventListener('click', function(e) {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
if (!selectedText) return;
|
|
145
|
+
renderShareCard(selectedText);
|
|
146
|
+
overlay.classList.add('active');
|
|
147
|
+
floatBtn.style.display = 'none';
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
closeBtn.addEventListener('click', function() {
|
|
151
|
+
overlay.classList.remove('active');
|
|
152
|
+
});
|
|
153
|
+
overlay.addEventListener('click', function(e) {
|
|
154
|
+
if (e.target === overlay) overlay.classList.remove('active');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
downloadBtn.addEventListener('click', function() {
|
|
158
|
+
canvas.toBlob(function(blob) {
|
|
159
|
+
var url = URL.createObjectURL(blob);
|
|
160
|
+
var a = document.createElement('a');
|
|
161
|
+
a.href = url;
|
|
162
|
+
a.download = 'share.png';
|
|
163
|
+
a.click();
|
|
164
|
+
URL.revokeObjectURL(url);
|
|
165
|
+
}, 'image/png');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
function wrapText(ctx, text, maxWidth, lineHeight) {
|
|
169
|
+
var lines = [];
|
|
170
|
+
var paragraphs = text.split('\\n');
|
|
171
|
+
for (var p = 0; p < paragraphs.length; p++) {
|
|
172
|
+
var words = paragraphs[p];
|
|
173
|
+
var line = '';
|
|
174
|
+
for (var i = 0; i < words.length; i++) {
|
|
175
|
+
var testLine = line + words[i];
|
|
176
|
+
var metrics = ctx.measureText(testLine);
|
|
177
|
+
if (metrics.width > maxWidth && line.length > 0) {
|
|
178
|
+
lines.push(line);
|
|
179
|
+
line = words[i];
|
|
180
|
+
} else {
|
|
181
|
+
line = testLine;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (line) lines.push(line);
|
|
185
|
+
if (p < paragraphs.length - 1) lines.push('');
|
|
186
|
+
}
|
|
187
|
+
return lines;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function renderShareCard(text) {
|
|
191
|
+
var dpr = window.devicePixelRatio || 1;
|
|
192
|
+
var W = 800;
|
|
193
|
+
var pad = 48;
|
|
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;
|
|
207
|
+
|
|
208
|
+
var qrSize = 120;
|
|
209
|
+
var siteNameH = 40;
|
|
210
|
+
var separatorGap = 24;
|
|
211
|
+
var quoteTopPad = 24;
|
|
212
|
+
var quoteBottomPad = 24;
|
|
213
|
+
var qrTopPad = 32;
|
|
214
|
+
var qrBottomPad = 16;
|
|
215
|
+
var bottomPad = 32;
|
|
216
|
+
|
|
217
|
+
var H = pad + siteNameH + titleH + separatorGap * 2 + quoteTopPad + textH + quoteBottomPad + qrTopPad + qrSize + qrBottomPad + 20 + bottomPad;
|
|
218
|
+
|
|
219
|
+
canvas.width = W * dpr;
|
|
220
|
+
canvas.height = H * dpr;
|
|
221
|
+
canvas.style.width = W + 'px';
|
|
222
|
+
canvas.style.height = H + 'px';
|
|
223
|
+
ctx.scale(dpr, dpr);
|
|
224
|
+
|
|
225
|
+
// Background
|
|
226
|
+
ctx.fillStyle = '#ffffff';
|
|
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;
|
|
247
|
+
|
|
248
|
+
// Separator
|
|
249
|
+
ctx.strokeStyle = '#e5e5e5';
|
|
250
|
+
ctx.lineWidth = 1;
|
|
251
|
+
ctx.beginPath();
|
|
252
|
+
ctx.moveTo(pad, y);
|
|
253
|
+
ctx.lineTo(W - pad, y);
|
|
254
|
+
ctx.stroke();
|
|
255
|
+
y += separatorGap;
|
|
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);
|
|
278
|
+
}
|
|
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
|
+
|
|
293
|
+
// White background for QR
|
|
294
|
+
ctx.fillStyle = '#ffffff';
|
|
295
|
+
ctx.fillRect(qrX - 4, qrY - 4, qrSize + 8, qrSize + 8);
|
|
296
|
+
|
|
297
|
+
ctx.fillStyle = '#1a1a1a';
|
|
298
|
+
for (var r = 0; r < moduleCount; r++) {
|
|
299
|
+
for (var c = 0; c < moduleCount; c++) {
|
|
300
|
+
if (qr.isDark(r, c)) {
|
|
301
|
+
ctx.fillRect(qrX + c * cellSize, qrY + r * cellSize, cellSize + 0.5, cellSize + 0.5);
|
|
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
|
+
}
|
|
311
|
+
}
|
|
312
|
+
})();
|
|
313
|
+
`,
|
|
314
|
+
} }),
|
|
90
315
|
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
316
|
react_1.default.createElement("script", null, `
|
|
92
317
|
document.getElementById('hljs-lib').addEventListener('load', () => {
|
|
@@ -159,6 +384,18 @@ const ContentPage = props => {
|
|
|
159
384
|
}, 300);
|
|
160
385
|
}
|
|
161
386
|
|
|
387
|
+
`),
|
|
388
|
+
react_1.default.createElement("script", null, `
|
|
389
|
+
// 页面加载后将当前激活的导航链接滚动到可视区域
|
|
390
|
+
if (document.readyState === 'loading') {
|
|
391
|
+
document.addEventListener('DOMContentLoaded', scrollActiveNavLink);
|
|
392
|
+
} else {
|
|
393
|
+
scrollActiveNavLink();
|
|
394
|
+
}
|
|
395
|
+
function scrollActiveNavLink() {
|
|
396
|
+
var el = document.querySelector('.nav-link.active');
|
|
397
|
+
if (el) el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
398
|
+
}
|
|
162
399
|
`),
|
|
163
400
|
react_1.default.createElement("script", { id: "embla-lib", src: "https://unpkg.com/embla-carousel/embla-carousel.umd.js", defer: true }),
|
|
164
401
|
react_1.default.createElement("script", { id: "embla-autoplay-lib", src: "https://unpkg.com/embla-carousel-autoplay/embla-carousel-autoplay.umd.js", defer: true }),
|
package/dist/ssg/style.js
CHANGED
|
@@ -378,5 +378,76 @@ 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 canvas {
|
|
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
|
+
}
|
|
381
452
|
`;
|
|
382
453
|
//# sourceMappingURL=style.js.map
|