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.
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "CZON - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",