Merge pull request #552 from immortal521/feat/code-block-line-number

feat: add code block line number display
This commit is contained in:
Tim
2025-08-14 16:47:46 +08:00
committed by GitHub
20 changed files with 401 additions and 232 deletions

View File

@@ -0,0 +1,143 @@
/* Maple Mono - Thin 100 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-100-normal.woff2") format("woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
}
/* Maple Mono - Thin Italic 100 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-100-italic.woff2") format("woff2");
font-weight: 100;
font-style: italic;
font-display: swap;
}
/* Maple Mono - ExtraLight 200 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-200-normal.woff2") format("woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
/* Maple Mono - ExtraLight Italic 200 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-200-italic.woff2") format("woff2");
font-weight: 200;
font-style: italic;
font-display: swap;
}
/* Maple Mono - Light 300 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-300-normal.woff2") format("woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
/* Maple Mono - Light Italic 300 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-300-italic.woff2") format("woff2");
font-weight: 300;
font-style: italic;
font-display: swap;
}
/* Maple Mono - Regular 400 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-400-normal.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* Maple Mono - Italic 400 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-400-italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* Maple Mono - Medium 500 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-500-normal.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
/* Maple Mono - Medium Italic 500 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-500-italic.woff2") format("woff2");
font-weight: 500;
font-style: italic;
font-display: swap;
}
/* Maple Mono - SemiBold 600 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-600-normal.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
/* Maple Mono - SemiBold Italic 600 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-600-italic.woff2") format("woff2");
font-weight: 600;
font-style: italic;
font-display: swap;
}
/* Maple Mono - Bold 700 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-700-normal.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* Maple Mono - Bold Italic 700 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-700-italic.woff2") format("woff2");
font-weight: 700;
font-style: italic;
font-display: swap;
}
/* Maple Mono - ExtraBold 800 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-800-normal.woff2") format("woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
/* Maple Mono - ExtraBold Italic 800 */
@font-face {
font-family: "Maple Mono";
src: url("/fonts/maple-mono-800-italic.woff2") format("woff2");
font-weight: 800;
font-style: italic;
font-display: swap;
}

View File

@@ -1,86 +1,86 @@
:root { :root {
--primary-color-hover: rgb(9, 95, 105); --primary-color-hover: rgb(9, 95, 105);
--primary-color: rgb(10, 110, 120); --primary-color: rgb(10, 110, 120);
--primary-color-disabled: rgba(93, 152, 156, 0.5); --primary-color-disabled: rgba(93, 152, 156, 0.5);
--header-height: 60px; --header-height: 60px;
--header-background-color: white; --header-background-color: white;
--header-border-color: lightgray; --header-border-color: lightgray;
--header-text-color: black; --header-text-color: black;
--menu-background-color: white; --menu-background-color: white;
--background-color: white; --background-color: white;
/* --background-color-blur: rgba(255, 255, 255, 0.57); */ /* --background-color-blur: rgba(255, 255, 255, 0.57); */
--background-color-blur: var(--background-color); --background-color-blur: var(--background-color);
--menu-border-color: lightgray; --menu-border-color: lightgray;
--normal-border-color: lightgray; --normal-border-color: lightgray;
--menu-selected-background-color: rgba(208, 250, 255, 0.659); --menu-selected-background-color: rgba(208, 250, 255, 0.659);
--menu-text-color: black; --menu-text-color: black;
--scroller-background-color: rgba(130, 175, 180, 0.5); --scroller-background-color: rgba(130, 175, 180, 0.5);
--normal-background-color: rgb(241, 241, 241); --normal-background-color: rgb(241, 241, 241);
--lottery-background-color: rgb(241, 241, 241); --lottery-background-color: rgb(241, 241, 241);
--login-background-color: rgb(248, 248, 248); --login-background-color: rgb(248, 248, 248);
--login-background-color-hover: #e0e0e0; --login-background-color-hover: #e0e0e0;
--text-color: black; --text-color: black;
--blockquote-text-color: #6a737d; --blockquote-text-color: #6a737d;
--menu-width: 200px; --menu-width: 200px;
--page-max-width: 1200px; --page-max-width: 1200px;
--page-max-width-mobile: 900px; --page-max-width-mobile: 900px;
--article-info-background-color: #f0f0f0; --article-info-background-color: #f0f0f0;
--activity-card-background-color: #fafafa; --activity-card-background-color: #fafafa;
} }
[data-theme='dark'] { [data-theme="dark"] {
--header-background-color: #2b2b2b; --header-background-color: #2b2b2b;
--header-border-color: #555; --header-border-color: #555;
--primary-color: rgb(17, 182, 197); --primary-color: rgb(17, 182, 197);
--primary-color-hover: rgb(13, 137, 151); --primary-color-hover: rgb(13, 137, 151);
--header-text-color: white; --header-text-color: white;
--menu-background-color: #333; --menu-background-color: #333;
--background-color: #333; --background-color: #333;
/* --background-color-blur: #333333a4; */ /* --background-color-blur: #333333a4; */
--background-color-blur: var(--background-color); --background-color-blur: var(--background-color);
--menu-border-color: #555; --menu-border-color: #555;
--normal-border-color: #555; --normal-border-color: #555;
--menu-selected-background-color: rgba(255, 255, 255, 0.1); --menu-selected-background-color: rgba(255, 255, 255, 0.1);
--menu-text-color: white; --menu-text-color: white;
--normal-background-color: #000000; --normal-background-color: #000000;
--lottery-background-color: #4e4e4e; --lottery-background-color: #4e4e4e;
--login-background-color: #575757; --login-background-color: #575757;
--login-background-color-hover: #717171; --login-background-color-hover: #717171;
--text-color: #eee; --text-color: #eee;
--blockquote-text-color: #999; --blockquote-text-color: #999;
--article-info-background-color: #747373; --article-info-background-color: #747373;
--activity-card-background-color: #585858; --activity-card-background-color: #585858;
} }
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Roboto', sans-serif; font-family: "Roboto", sans-serif;
background-color: var(--normal-background-color); background-color: var(--normal-background-color);
color: var(--text-color); color: var(--text-color);
/* 禁止滚动 */ /* 禁止滚动 */
/* overflow: hidden; */ /* overflow: hidden; */
} }
/************************* /*************************
* Vditor 自定义皮肤覆写 * Vditor 自定义皮肤覆写
*************************/ *************************/
.vditor { .vditor {
min-height: 200px; min-height: 200px;
} }
.vditor-toolbar--pin { .vditor-toolbar--pin {
top: var(--header-height) !important; top: var(--header-height) !important;
} }
.vditor-panel { .vditor-panel {
min-width: 400px; min-width: 400px;
} }
.emoji { .emoji {
width: 20px; width: 20px;
height: 20px; height: 20px;
vertical-align: middle; vertical-align: middle;
} }
/* .vditor { /* .vditor {
@@ -108,202 +108,224 @@ body {
*************************/ *************************/
.info-content-text ul, .info-content-text ul,
.info-content-text ol { .info-content-text ol {
padding-left: 1.5em; padding-left: 1.5em;
} }
.info-content-text h1, .info-content-text h1,
.info-content-text h2 { .info-content-text h2 {
border-bottom: 1px solid var(--normal-border-color); border-bottom: 1px solid var(--normal-border-color);
padding-bottom: 0.3em; padding-bottom: 0.3em;
margin-bottom: 0.8em; margin-bottom: 0.8em;
} }
.info-content-text { .info-content-text {
word-break: break-word; word-break: break-word;
max-width: 100%; max-width: 100%;
} }
.info-content-text blockquote { .info-content-text blockquote {
margin: 1em 0; margin: 1em 0;
padding-left: 1em; padding-left: 1em;
border-left: 4px solid #d0d7de; border-left: 4px solid #d0d7de;
color: var(--blockquote-text-color); color: var(--blockquote-text-color);
} }
.info-content-text pre { .info-content-text pre {
background-color: var(--normal-background-color); display: flex;
padding: 8px 12px; background-color: var(--normal-background-color);
border-radius: 4px; padding: 8px 12px;
line-height: 1.5; border-radius: 4px;
position: relative; line-height: 1.5;
position: relative;
} }
.copy-code-btn { .info-content-text pre .line-numbers {
position: absolute; counter-reset: line-number 0;
top: 4px; width: 2em;
right: 4px; font-size: 13px;
font-size: 12px; position: sticky;
padding: 2px 6px; flex-shrink: 0;
border: none; font-family: "Maple Mono", monospace;
border-radius: 4px; margin: 1em 0;
background-color: white; color: #888;
opacity: 0.8; background-color: var(--normal-background-color);
color: black; border-right: 1px solid #888;
cursor: pointer; box-sizing: border-box;
padding-right: 0.5em;
text-align: end;
} }
.copy-code-btn:hover { .info-content-text pre .line-numbers .line-number::before {
opacity: 1; content: counter(line-number);
counter-increment: line-number;
} }
.info-content-text code { .info-content-text code {
font-family: 'Roboto Mono', monospace; font-family: "Maple Mono", monospace;
font-size: 13px; font-size: 13px;
border-radius: 4px; border-radius: 4px;
white-space: pre-wrap; white-space: no-wrap;
background-color: var(--normal-background-color); background-color: var(--normal-background-color);
color: var(--text-color); color: var(--text-color);
}
.copy-code-btn {
position: absolute;
top: 4px;
right: 4px;
font-size: 12px;
padding: 2px 6px;
border: none;
border-radius: 4px;
background-color: white;
opacity: 0.8;
color: black;
cursor: pointer;
}
.copy-code-btn:hover {
opacity: 1;
} }
.info-content-text a { .info-content-text a {
color: var(--primary-color); color: var(--primary-color);
text-decoration: none; text-decoration: none;
} }
.info-content-text a:hover { .info-content-text a:hover {
text-decoration: underline; text-decoration: underline;
} }
.info-content-text img { .info-content-text img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.info-content-text table { .info-content-text table {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin: 1.2em 0; margin: 1.2em 0;
font-size: 14px; font-size: 14px;
line-height: 1.45; line-height: 1.45;
overflow-x: auto; /* 小屏可横向滚动 */ overflow-x: auto; /* 小屏可横向滚动 */
} }
.info-content-text thead th { .info-content-text thead th {
background-color: var(--primary-color); background-color: var(--primary-color);
color: #fff; color: #fff;
padding: 10px 14px; padding: 10px 14px;
text-align: left; text-align: left;
font-weight: 600; font-weight: 600;
} }
[data-theme='dark'] .info-content-text thead th { [data-theme="dark"] .info-content-text thead th {
background-color: var(--primary-color-hover); /* 暗色稍暗一点 */ background-color: var(--primary-color-hover); /* 暗色稍暗一点 */
} }
.info-content-text tbody tr:nth-child(even) { .info-content-text tbody tr:nth-child(even) {
background-color: rgba(208, 250, 255, 0.25); /* 斑马纹 */ background-color: rgba(208, 250, 255, 0.25); /* 斑马纹 */
} }
[data-theme='dark'] .info-content-text tbody tr:nth-child(even) { [data-theme="dark"] .info-content-text tbody tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 255, 255, 0.05);
} }
.info-content-text th, .info-content-text th,
.info-content-text td { .info-content-text td {
border: 1px solid var(--menu-border-color); border: 1px solid var(--menu-border-color);
padding: 8px 14px; padding: 8px 14px;
vertical-align: top; vertical-align: top;
} }
.info-content-text tbody td { .info-content-text tbody td {
color: var(--text-color); color: var(--text-color);
} }
/* 首列加粗,便于阅读 */ /* 首列加粗,便于阅读 */
.info-content-text tbody td:first-child { .info-content-text tbody td:first-child {
font-weight: 500; font-weight: 500;
} }
/* 鼠标悬停行高亮 */ /* 鼠标悬停行高亮 */
.info-content-text tbody tr:hover { .info-content-text tbody tr:hover {
background-color: var(--menu-selected-background-color); background-color: var(--menu-selected-background-color);
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.vditor { .vditor {
min-height: 100px; min-height: 100px;
} }
.vditor-toolbar { .vditor-toolbar {
overflow-x: auto; overflow-x: auto;
} }
.about-content h1, .about-content h1,
.info-content-text h1 { .info-content-text h1 {
font-size: 20px; font-size: 20px;
} }
.about-content h2, .about-content h2,
.info-content-text h2 { .info-content-text h2 {
font-size: 18px; font-size: 18px;
} }
.about-content p, .about-content p,
.info-content-text p { .info-content-text p {
font-size: 14px; font-size: 14px;
margin-top: 3px; margin-top: 3px;
margin-bottom: 3px; margin-bottom: 3px;
} }
.vditor-toolbar--pin { .vditor-toolbar--pin {
top: 0 !important; top: 0 !important;
} }
.about-content li, .about-content li,
.info-content-text li { .info-content-text li {
font-size: 14px; font-size: 14px;
} }
.info-content-text pre { .info-content-text pre {
line-height: 1.1; line-height: 1.5;
} }
.vditor-panel { .vditor-panel {
position: relative; position: relative;
min-width: 0; min-width: 0;
} }
} }
/* NProgress styles */ /* NProgress styles */
#nprogress { #nprogress {
pointer-events: none; pointer-events: none;
} }
#nprogress .bar { #nprogress .bar {
background: var(--primary-color); background: var(--primary-color);
position: fixed; position: fixed;
z-index: 1031; z-index: 1031;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 3px; height: 3px;
} }
#nprogress .peg { #nprogress .peg {
display: block; display: block;
position: absolute; position: absolute;
right: 0; right: 0;
width: 100px; width: 100px;
height: 100%; height: 100%;
box-shadow: box-shadow:
0 0 10px var(--primary-color), 0 0 10px var(--primary-color),
0 0 5px var(--primary-color); 0 0 5px var(--primary-color);
opacity: 1; opacity: 1;
transform: rotate(3deg) translate(0px, -4px); transform: rotate(3deg) translate(0px, -4px);
} }
#nprogress .spinner { #nprogress .spinner {
display: none; display: none;
} }

View File

@@ -1,25 +1,25 @@
import { defineNuxtConfig } from 'nuxt/config' import { defineNuxtConfig } from "nuxt/config";
export default defineNuxtConfig({ export default defineNuxtConfig({
ssr: true, ssr: true,
runtimeConfig: { runtimeConfig: {
public: { public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || '', apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || "",
websiteBaseUrl: process.env.NUXT_PUBLIC_WEBSITE_BASE_URL || '', websiteBaseUrl: process.env.NUXT_PUBLIC_WEBSITE_BASE_URL || "",
googleClientId: process.env.NUXT_PUBLIC_GOOGLE_CLIENT_ID || '', googleClientId: process.env.NUXT_PUBLIC_GOOGLE_CLIENT_ID || "",
githubClientId: process.env.NUXT_PUBLIC_GITHUB_CLIENT_ID || '', githubClientId: process.env.NUXT_PUBLIC_GITHUB_CLIENT_ID || "",
discordClientId: process.env.NUXT_PUBLIC_DISCORD_CLIENT_ID || '', discordClientId: process.env.NUXT_PUBLIC_DISCORD_CLIENT_ID || "",
twitterClientId: process.env.NUXT_PUBLIC_TWITTER_CLIENT_ID || '', twitterClientId: process.env.NUXT_PUBLIC_TWITTER_CLIENT_ID || "",
}, },
}, },
// Ensure Vditor styles load before our overrides in global.css // Ensure Vditor styles load before our overrides in global.css
css: ['vditor/dist/index.css', '~/assets/global.css'], css: ["vditor/dist/index.css", "~/assets/global.css", "~/assets/fonts.css"],
app: { app: {
head: { head: {
script: [ script: [
{ {
tagPriority: 'high', tagPriority: "high",
innerHTML: ` innerHTML: `
(function () { (function () {
try { try {
const mode = localStorage.getItem('theme-mode'); const mode = localStorage.getItem('theme-mode');
@@ -29,35 +29,35 @@ export default defineNuxtConfig({
} catch (e) {} } catch (e) {}
})(); })();
`, `,
}, },
], ],
link: [ link: [
{ {
rel: 'icon', rel: "icon",
type: 'image/x-icon', type: "image/x-icon",
href: '/favicon.ico', href: "/favicon.ico",
}, },
{ {
rel: 'apple-touch-icon', rel: "apple-touch-icon",
href: '/apple-touch-icon.png', href: "/apple-touch-icon.png",
}, },
{ {
rel: 'manifest', rel: "manifest",
href: '/manifest.webmanifest', href: "/manifest.webmanifest",
}, },
{ {
rel: 'stylesheet', rel: "stylesheet",
href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css', href: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
referrerpolicy: 'no-referrer', referrerpolicy: "no-referrer",
}, },
], ],
}, },
baseURL: '/', baseURL: "/",
buildAssetsDir: '/_nuxt/', buildAssetsDir: "/_nuxt/",
}, },
vue: { vue: {
compilerOptions: { compilerOptions: {
isCustomElement: (tag) => ['l-hatch', 'l-hatch-spinner'].includes(tag), isCustomElement: (tag) => ["l-hatch", "l-hatch-spinner"].includes(tag),
}, },
}, },
}) });

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -86,7 +86,11 @@ const md = new MarkdownIt({
} else { } else {
code = hljs.highlightAuto(str).value code = hljs.highlightAuto(str).value
} }
return `<pre class="code-block"><button class="copy-code-btn">Copy</button><code class="hljs language-${lang || ''}">${code}</code></pre>` const lineNumbers = code
.trim()
.split('\n')
.map(() => `<div class="line-number"></div>`)
return `<pre class="code-block"><button class="copy-code-btn">Copy</button><div class="line-numbers">${lineNumbers.join('')}</div><code class="hljs language-${lang || ''}">${code.trim()}</code></pre>`
}, },
}) })