feat: 优化代码块样式并实现点击复制功能
This commit is contained in:
1 parent
e73af54b2f
commit
ade9ea9598
2 files changed
+66
-15
No files matched your search
+12
-6
@@ -316,25 +316,31 @@ body.single-col .post-list {
|
|||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--code-background-color, #f5f5f5);
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-header {
|
.code-block-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--code-header-bg, #e6e6e6);
|
background-color: #ededed;
|
||||||
padding: 0.4rem 0.8rem;
|
padding: 0.4rem 0.8rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-title {
|
.code-title {
|
||||||
font-family: var(--font-sans);
|
font-family: "Source Code Pro", Menlo, Consolas, Monaco, monospace;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.copy-code-button {
|
.copy-code-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -355,7 +361,7 @@ body.single-col .post-list {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-family: "Source Code Pro", Menlo, Consolas, Monaco, monospace;
|
font-family: "Source Code Pro", Menlo, Consolas, Monaco, monospace;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
|
|||||||
@@ -1,20 +1,46 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { marked } from 'marked';
|
import { Marked } from 'marked';
|
||||||
import { markedHighlight } from "marked-highlight";
|
|
||||||
import hljs from 'highlight.js';
|
import hljs from 'highlight.js';
|
||||||
import 'highlight.js/styles/github.css';
|
import 'highlight.js/styles/github.css';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
// Configure marked for code highlighting
|
// Create a new Marked instance to avoid HMR leaks and double-escaping issues
|
||||||
marked.use(markedHighlight({
|
const markedInstance = new Marked();
|
||||||
langPrefix: 'hljs language-',
|
markedInstance.use({
|
||||||
highlight(code, lang) {
|
renderer: {
|
||||||
|
code(token) {
|
||||||
|
const code = token.text;
|
||||||
|
const lang = (token.lang || '').split(' ')[0];
|
||||||
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||||
return hljs.highlight(code, { language }).value;
|
const highlighted = hljs.highlight(code, { language }).value;
|
||||||
|
const encodedCode = encodeURIComponent(code);
|
||||||
|
const copyIcon = '<svg viewBox="0 0 448 512" fill="currentColor" height="1em" width="1em" style="font-size: 1.1em"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v200a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>';
|
||||||
|
const chevronIcon = '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em" style="margin-right: 0.5rem;"><path d="M256 294.1L383 167c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-144 144c-9.4 9.4-24.6 9.4-33.9 0l-144-144c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0L256 294.1z"></path></svg>';
|
||||||
|
const listOlIcon = '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h34.71L15.32 424.64A29.85 29.85 0 0 0 8 444v16c0 26.51 21.49 48 48 48h24a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H56c-8.84 0-16-7.16-16-16v-4.52a6.01 6.01 0 0 1 1.76-4.24l19.24-19.24a48.28 48.28 0 0 0 7.58 3.06L64 448c0 35.35 28.65 64 64 64s64-28.65 64-64-28.65-64-64-64c-9.67 0-18.77 2.21-27.05 6.04L75.64 361.7A29.8 29.8 0 0 0 56 352h-7.66c10.37-4.14 17.66-14.24 17.66-26.34v-3.31c0-15.46-12.54-28-28-28H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22c2.21 0 4 1.79 4 4v3.31c0 2.21-1.79 4-4 4H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h15.4c17.21 0 33.15 6.94 44.97 19.46l5.4-6.23a43.83 43.83 0 0 1 7.23-7.19h-15zM400 352H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h224a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176 112h224a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zM400 208H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h224a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM88 32H16C7.16 32 0 39.16 0 48v20c0 8.84 7.16 16 16 16h16v64c0 8.84 7.16 16 16 16h16c8.84 0 16-7.16 16-16V48c0-8.84-7.16-16-16-16h-48c0-8.84 7.16-16 16-16zm64 160c0-35.35-28.65-64-64-64s-64 28.65-64 64 28.65 64 64 64 64-28.65 64-64zm-32 0c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32z"></path></svg>';
|
||||||
|
const barsIcon = '<svg viewBox="0 0 448 512" fill="currentColor" height="1em" width="1em"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></svg>';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="code-block highlight">
|
||||||
|
<div class="code-block-header">
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
${chevronIcon}
|
||||||
|
<span class="code-title">${lang || language}</span>
|
||||||
|
</div>
|
||||||
|
<div class="code-header-actions">
|
||||||
|
<span style="opacity: 0.5;">${listOlIcon}</span>
|
||||||
|
<span style="opacity: 0.5;">${barsIcon}</span>
|
||||||
|
<button class="copy-code-button" title="Copy code" data-code="${encodedCode}">
|
||||||
|
${copyIcon}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre><code class="hljs language-${language}">${highlighted}</code></pre>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
@@ -68,9 +94,28 @@
|
|||||||
|
|
||||||
// Parse markdown synchronously so it renders on the server (SSR)
|
// Parse markdown synchronously so it renders on the server (SSR)
|
||||||
// and doesn't rely on client-side effects which were causing the empty content.
|
// and doesn't rely on client-side effects which were causing the empty content.
|
||||||
const htmlContent = marked.parse(preprocessedContent) as string;
|
const htmlContent = markedInstance.parse(preprocessedContent) as string;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Initialize copy code buttons
|
||||||
|
document.querySelectorAll('.copy-code-button').forEach((btn) => {
|
||||||
|
const buttonEl = btn as HTMLElement;
|
||||||
|
buttonEl.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const code = buttonEl.getAttribute('data-code');
|
||||||
|
if (code) {
|
||||||
|
navigator.clipboard.writeText(decodeURIComponent(code)).then(() => {
|
||||||
|
// Temporarily change icon to a checkmark or just "Copied!" text if we had a check icon
|
||||||
|
// For now, let's just make it green temporarily
|
||||||
|
buttonEl.style.color = '#4CAF50';
|
||||||
|
setTimeout(() => {
|
||||||
|
buttonEl.style.color = '';
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize details expand/collapse animation logic (DoIt style)
|
// Initialize details expand/collapse animation logic (DoIt style)
|
||||||
document.querySelectorAll('.details').forEach((detailsEl) => {
|
document.querySelectorAll('.details').forEach((detailsEl) => {
|
||||||
const summaryEl = detailsEl.querySelector('.details-summary') as HTMLElement;
|
const summaryEl = detailsEl.querySelector('.details-summary') as HTMLElement;
|
||||||
|
|||||||
Reference in new issue
Block a user