From 728b988144eea76ca83d15cf94a0c2d662fe6d94 Mon Sep 17 00:00:00 2001 From: zqlit Date: Sun, 24 May 2026 12:17:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=BB=E9=A2=98=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/Ying/assets/css/main.css | 181 +++++++++++++++++- themes/Ying/assets/js/main.js | 42 ++++ themes/Ying/assets/js/modules/mypjax.js | 7 + .../_default/_markup/render-image.html | 4 +- write/.recycle/meta/202605232244.json | 7 + write/.recycle/meta/202605232251.json | 7 + .../.recycle/posts/202605232244}/index.md | 0 write/.recycle/posts/202605232251/index.md | 14 ++ .../posts/202605232251/mpih806u5rw5.png | Bin 0 -> 13684 bytes write/src/app/api/deploy/route.ts | 90 ++++++--- write/src/app/comments/page.tsx | 18 +- write/src/app/feeds/page.tsx | 4 +- write/src/app/globals.css | 46 +++++ write/src/app/images/page.tsx | 2 +- write/src/app/layout.tsx | 15 +- write/src/app/links/page.tsx | 4 +- write/src/app/page.tsx | 16 +- write/src/app/recycle/page.tsx | 2 +- write/src/app/wechat/page.tsx | 108 ++++++++++- write/src/components/AIPanel.tsx | 4 +- write/src/components/PostEditor.tsx | 89 ++++++++- write/src/components/ShortcutPanel.tsx | 2 +- write/src/components/Sidebar.tsx | 2 + write/src/components/ThemeToggle.tsx | 48 +++++ 24 files changed, 645 insertions(+), 67 deletions(-) create mode 100644 write/.recycle/meta/202605232244.json create mode 100644 write/.recycle/meta/202605232251.json rename {content/post/2026/2026-05-23-rss的优化点 => write/.recycle/posts/202605232244}/index.md (100%) create mode 100644 write/.recycle/posts/202605232251/index.md create mode 100644 write/.recycle/posts/202605232251/mpih806u5rw5.png create mode 100644 write/src/components/ThemeToggle.tsx diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index ee344510..62b99259 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -2994,6 +2994,87 @@ input[type=submit] { contain-intrinsic-size: 700px auto; } +.post-content .sen-image-frame, +.entry-content .sen-image-frame { + position: relative; + display: block; + width: fit-content; + max-width: 100%; + margin: 1.2rem auto; + line-height: 0; + opacity: 0; + transform: translateY(14px); + transition: opacity 0.6s cubic-bezier(0.22, 0.61, 0.36, 1), + transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1); +} + +.post-content .sen-image-frame.is-visible, +.entry-content .sen-image-frame.is-visible { + opacity: 1; + transform: translateY(0); +} + +.post-content .sen-image-frame>img, +.entry-content .sen-image-frame>img { + display: block; + width: auto; + max-width: 100%; + margin: 0; + box-shadow: none; +} + +.post-content .sen-image-frame[data-title]::after, +.entry-content .sen-image-frame[data-title]::after { + content: attr(data-title); + position: absolute; + left: 10px; + bottom: 10px; + max-width: calc(100% - 20px); + padding: 5px 10px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.08); + background: rgba(255, 255, 255, 0.72); + color: rgba(0, 0, 0, 0.82); + -webkit-backdrop-filter: blur(16px) saturate(180%); + backdrop-filter: blur(16px) saturate(180%); + font-size: 13px; + font-weight: 500; + letter-spacing: 0.03em; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; + opacity: 0; + transform: translateY(4px); + transition: opacity 0.4s 0.2s cubic-bezier(0.22, 0.61, 0.36, 1), + transform 0.4s 0.2s cubic-bezier(0.22, 0.61, 0.36, 1); +} + +.post-content .sen-image-frame.is-visible[data-title]::after, +.entry-content .sen-image-frame.is-visible[data-title]::after { + opacity: 1; + transform: translateY(0); +} + +[data-theme="dark"] .post-content .sen-image-frame[data-title]::after, +[data-theme="dark"] .entry-content .sen-image-frame[data-title]::after { + border-color: rgba(255, 255, 255, 0.12); + background: rgba(0, 0, 0, 0.62); + color: rgba(255, 255, 255, 0.92); +} + +@media screen and (max-width: 768px) { + .post-content .sen-image-frame[data-title]::after, + .entry-content .sen-image-frame[data-title]::after { + left: 8px; + bottom: 8px; + max-width: calc(100% - 16px); + padding: 4px 8px; + font-size: 12px; + } +} + /* 设置进度条的背景色 */ .bar { background: #2aae67 !important; @@ -3504,9 +3585,107 @@ input[type=submit] { pointer-events: none !important; } -/* Override ViewImage z-index to ensure it is above floating tools */ +/* ── ViewImage: Apple frosted glass lightbox ── */ .view-image { z-index: 2000 !important; + background: rgba(0, 0, 0, 0.45) !important; +} + +@supports (backdrop-filter: blur(20px)) { + .view-image { + background: rgba(0, 0, 0, 0.32) !important; + } +} + +/* Bottom toolbar — frosted glass bar */ +.view-image-tools { + background: rgba(28, 28, 30, 0.78) !important; + backdrop-filter: blur(44px) saturate(200%) !important; + -webkit-backdrop-filter: blur(44px) saturate(200%) !important; + border: 1px solid rgba(255, 255, 255, 0.16) !important; + border-radius: 8px !important; + padding: 6px 12px !important; + max-width: 360px !important; + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.38), + 0 0 0 0.5px rgba(255, 255, 255, 0.1) inset !important; +} + +/* Counter text */ +.view-image-tools__count { + font-size: 13px; + font-weight: 500; + letter-spacing: 0.03em; + opacity: 0.9; +} + +/* Glass buttons */ +.view-image-btn { + width: 34px !important; + height: 34px !important; + border-radius: 6px !important; + background: rgba(255, 255, 255, 0.14) !important; + border: 1px solid rgba(255, 255, 255, 0.12); + transition: background 0.2s ease, transform 0.15s ease; +} + +.view-image-btn:hover { + background: rgba(255, 255, 255, 0.28) !important; + transform: scale(1.06); +} + +.view-image-btn:active { + transform: scale(0.96); +} + +/* Close button in toolbar */ +.view-image-tools .view-image-close { + margin: 0 0 0 10px !important; +} + +/* Close area overlay */ +.view-image-close__full { + cursor: zoom-out; +} + +/* Image */ +.view-image-lead img { + border-radius: 2px !important; + box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(255, 255, 255, 0.06) !important; +} + +/* Dark mode tweaks */ +[data-theme="dark"] .view-image { + background: rgba(0, 0, 0, 0.55) !important; +} + +@supports (backdrop-filter: blur(20px)) { + [data-theme="dark"] .view-image { + background: rgba(0, 0, 0, 0.4) !important; + } +} + +[data-theme="dark"] .view-image-tools { + background: rgba(18, 18, 22, 0.82) !important; + border-color: rgba(255, 255, 255, 0.1) !important; +} + +/* Mobile */ +@media screen and (max-width: 768px) { + .view-image { + padding: 4% !important; + } + + .view-image-tools { + max-width: 100% !important; + border-radius: 6px !important; + padding: 5px 10px !important; + } + + .view-image-btn { + width: 30px !important; + height: 30px !important; + } } .floating-tools a { diff --git a/themes/Ying/assets/js/main.js b/themes/Ying/assets/js/main.js index 5c69ee16..c1adb3ff 100644 --- a/themes/Ying/assets/js/main.js +++ b/themes/Ying/assets/js/main.js @@ -32,6 +32,9 @@ document.addEventListener('DOMContentLoaded', function() { // Initialize Typewriter Effect for Mobile Header initTypewriter(); + + // Image frame scroll reveal + initImageFrameReveal(); }); /** @@ -71,3 +74,42 @@ function initTypewriter() { // Start typing with a small delay setTimeout(typeWriter, 500); } + +/** + * Scroll-triggered reveal for sen-image-frame + */ +var senFrameObserver = null; + +function initImageFrameReveal() { + var frames = document.querySelectorAll('.sen-image-frame'); + if (!frames.length) return; + + if (!window.IntersectionObserver) { + frames.forEach(function(frame) { + frame.classList.add('is-visible'); + }); + return; + } + + if (senFrameObserver) { + senFrameObserver.disconnect(); + } + + senFrameObserver = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + entry.target.classList.add('is-visible'); + senFrameObserver.unobserve(entry.target); + } + }); + }, { + rootMargin: "0px 0px 50px 0px", + threshold: 0.08 + }); + + frames.forEach(function(frame) { + senFrameObserver.observe(frame); + }); +} + +window.initImageFrameReveal = initImageFrameReveal; diff --git a/themes/Ying/assets/js/modules/mypjax.js b/themes/Ying/assets/js/modules/mypjax.js index 2d5d5530..8c3b63b7 100644 --- a/themes/Ying/assets/js/modules/mypjax.js +++ b/themes/Ying/assets/js/modules/mypjax.js @@ -152,5 +152,12 @@ document.addEventListener("pjax:complete", function () { } catch(e) { console.error('initSearch error:', e); } } + // 重载图片框架滚动显示 + if (typeof window.initImageFrameReveal === 'function') { + try { + window.initImageFrameReveal(); + } catch(e) { console.error('initImageFrameReveal error:', e); } + } + refreshHeatmap(); }); diff --git a/themes/Ying/layouts/_default/_markup/render-image.html b/themes/Ying/layouts/_default/_markup/render-image.html index abd39b8e..2f1d43f1 100644 --- a/themes/Ying/layouts/_default/_markup/render-image.html +++ b/themes/Ying/layouts/_default/_markup/render-image.html @@ -14,4 +14,6 @@ {{- $src = $src | relURL -}} {{- end -}} {{- end -}} -{{ $alt }} \ No newline at end of file + + {{ $alt }} + \ No newline at end of file diff --git a/write/.recycle/meta/202605232244.json b/write/.recycle/meta/202605232244.json new file mode 100644 index 00000000..e19df88c --- /dev/null +++ b/write/.recycle/meta/202605232244.json @@ -0,0 +1,7 @@ +{ + "slug": "202605232244", + "title": "rss的优化点", + "deletedAt": "2026-05-23T14:45:08.470Z", + "originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-05-23-rss的优化点", + "expiresAt": "2026-06-22T14:45:08.470Z" +} \ No newline at end of file diff --git a/write/.recycle/meta/202605232251.json b/write/.recycle/meta/202605232251.json new file mode 100644 index 00000000..3bcec594 --- /dev/null +++ b/write/.recycle/meta/202605232251.json @@ -0,0 +1,7 @@ +{ + "slug": "202605232251", + "title": "测试", + "deletedAt": "2026-05-23T15:59:30.678Z", + "originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-05-23-测试", + "expiresAt": "2026-06-22T15:59:30.678Z" +} \ No newline at end of file diff --git a/content/post/2026/2026-05-23-rss的优化点/index.md b/write/.recycle/posts/202605232244/index.md similarity index 100% rename from content/post/2026/2026-05-23-rss的优化点/index.md rename to write/.recycle/posts/202605232244/index.md diff --git a/write/.recycle/posts/202605232251/index.md b/write/.recycle/posts/202605232251/index.md new file mode 100644 index 00000000..2cea35a5 --- /dev/null +++ b/write/.recycle/posts/202605232251/index.md @@ -0,0 +1,14 @@ +--- +title: 测试 +date: '2026-05-23T00:00:00.000Z' +slug: '202605232251' +draft: false +categories: + - 测试 +tags: + - 测试 +author: 小赵 +layout: post +--- + +![](mpih806u5rw5.png) diff --git a/write/.recycle/posts/202605232251/mpih806u5rw5.png b/write/.recycle/posts/202605232251/mpih806u5rw5.png new file mode 100644 index 0000000000000000000000000000000000000000..5d525b82b991e73e23988601c3ea5cbaaf36fa78 GIT binary patch literal 13684 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 J<#Mk-{x9khl?MO- literal 0 HcmV?d00001 diff --git a/write/src/app/api/deploy/route.ts b/write/src/app/api/deploy/route.ts index 3dac3d95..25bc7ed4 100644 --- a/write/src/app/api/deploy/route.ts +++ b/write/src/app/api/deploy/route.ts @@ -1,45 +1,83 @@ import { NextRequest, NextResponse } from "next/server"; -import { exec } from "child_process"; +import { execFile } from "child_process"; import { promisify } from "util"; import { BLOG_ROOT } from "@/lib/config"; -const execAsync = promisify(exec); +const execFileAsync = promisify(execFile); + +type GitExecError = Error & { + stdout?: string; + stderr?: string; + code?: number; +}; + +async function runGit(args: string[], allowFailure = false) { + try { + const { stdout, stderr } = await execFileAsync("git", args, { + cwd: BLOG_ROOT, + windowsHide: true, + maxBuffer: 1024 * 1024, + }); + return { stdout, stderr, code: 0 }; + } catch (error) { + const err = error as GitExecError; + const result = { + stdout: err.stdout || "", + stderr: err.stderr || err.message, + code: typeof err.code === "number" ? err.code : 1, + }; + if (allowFailure) return result; + throw new Error((result.stderr || result.stdout || err.message).trim()); + } +} + +function isRebaseConflict(message: string) { + return /conflict|CONFLICT|Resolve all conflicts|Automatic merge failed|could not apply|rebase/i.test(message); +} export async function POST(request: NextRequest) { try { const body = await request.json(); - const { title, slug } = body; + const { title } = body; if (!title) { return NextResponse.json({ error: "Title is required" }, { status: 400 }); } - const commitMsg = `发布: ${title}`; - - // git add, commit, push from the blog root - const commands = [ - `cd "${BLOG_ROOT}" && git add -A`, - `cd "${BLOG_ROOT}" && git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, - `cd "${BLOG_ROOT}" && git push`, - ]; - let output = ""; - for (const cmd of commands) { - try { - const { stdout, stderr } = await execAsync(cmd); - output += stdout + (stderr || ""); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - // git commit may fail if nothing to commit — that's ok - if (msg.includes("nothing to commit") || msg.includes("nothing added")) { - continue; - } - // git push failure is real - throw new Error(`Git command failed: ${msg}`); - } + + const addResult = await runGit(["add", "content/post", "static"]); + output += addResult.stdout + addResult.stderr; + + const diffResult = await runGit(["diff", "--cached", "--quiet"], true); + if (diffResult.code === 1) { + const commitResult = await runGit(["commit", "-m", `发布: ${title}`]); + output += commitResult.stdout + commitResult.stderr; + } else if (diffResult.code !== 0) { + throw new Error((diffResult.stderr || diffResult.stdout || "检查暂存区失败").trim()); } - return NextResponse.json({ success: true, output: output.slice(0, 500) }); + try { + const pullResult = await runGit(["pull", "--rebase", "--autostash", "origin", "main"]); + output += pullResult.stdout + pullResult.stderr; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (isRebaseConflict(message)) { + return NextResponse.json( + { + error: "远程仓库有新内容,自动合并失败。请在终端解决冲突后执行 git rebase --continue,再重新发布。", + detail: message.slice(0, 1000), + }, + { status: 409 } + ); + } + throw error; + } + + const pushResult = await runGit(["push"]); + output += pushResult.stdout + pushResult.stderr; + + return NextResponse.json({ success: true, output: output.slice(0, 1000) }); } catch (error: unknown) { return NextResponse.json( { error: error instanceof Error ? error.message : "Deploy failed" }, diff --git a/write/src/app/comments/page.tsx b/write/src/app/comments/page.tsx index 6f3d15c4..6e1eee44 100644 --- a/write/src/app/comments/page.tsx +++ b/write/src/app/comments/page.tsx @@ -390,7 +390,7 @@ export default function CommentsPage() { if (!loggedIn) { return (
-
+
@@ -480,7 +480,7 @@ export default function CommentsPage() { { label: "评论总数", value: stats.totalComments }, { label: "评论用户", value: stats.uniqueUsers }, ].map((s) => ( -
+
{s.label}
{s.value}
@@ -490,7 +490,7 @@ export default function CommentsPage() { {/* Top articles & top users side by side */}
{/* Top articles */} -
+

评论最多的文章 Top 20

{stats.topArticles.map((a, i) => ( @@ -506,7 +506,7 @@ export default function CommentsPage() {
{/* Top commenters */} -
+

最活跃的用户 Top 30

{stats.topUsers.map((u, i) => ( @@ -540,7 +540,7 @@ export default function CommentsPage() { 加载中
) : ( -
+
{pages.map((p) => (
setEditTitleText(e.target.value)} - className="flex-1 bg-white border border-line rounded-md px-2 py-1 text-[13px] text-text focus:outline-none focus:border-accent/30" + className="flex-1 bg-card border border-line rounded-md px-2 py-1 text-[13px] text-text focus:outline-none focus:border-accent/30" onKeyDown={(e) => { if (e.key === "Enter") handleSaveTitle(); if (e.key === "Escape") setEditingTitle(false); }} autoFocus /> @@ -610,13 +610,13 @@ export default function CommentsPage() { ) : (
{comments.length === 0 && ( -
+
暂无评论
)} {comments.map((c) => (
@@ -695,7 +695,7 @@ export default function CommentsPage() { )} ) : ( -
+
选择左侧文章查看评论
diff --git a/write/src/app/feeds/page.tsx b/write/src/app/feeds/page.tsx index 6f39d457..a9960b62 100644 --- a/write/src/app/feeds/page.tsx +++ b/write/src/app/feeds/page.tsx @@ -152,7 +152,7 @@ export default function FeedsPage() { )} {showAdd && ( -
+
@@ -184,7 +184,7 @@ export default function FeedsPage() { const h = health.get(f.url); const ck = checkingMap.get(f.url); return ( -
{ck ? diff --git a/write/src/app/globals.css b/write/src/app/globals.css index cbc3ffaf..82303c43 100644 --- a/write/src/app/globals.css +++ b/write/src/app/globals.css @@ -1,8 +1,10 @@ @import "tailwindcss"; :root { + color-scheme: light; --bg: #fcfaf5; --surface: #f6f1e7; + --card: #fffdf8; --text: #223d66; --muted: #6f7f96; --line: #e4dfd2; @@ -14,9 +16,26 @@ --warning: #c09a46; } +:root[data-theme="dark"] { + color-scheme: dark; + --bg: #111827; + --surface: #1b2638; + --card: #243247; + --text: #f3f7fd; + --muted: #c1ccdc; + --line: #51627a; + --accent: #9fc4f4; + --accent-hover: #bfdbfe; + --danger: #fca5a5; + --danger-hover: #fecaca; + --success: #86efac; + --warning: #f3d58a; +} + @theme inline { --color-bg: var(--bg); --color-surface: var(--surface); + --color-card: var(--card); --color-text: var(--text); --color-muted: var(--muted); --color-line: var(--line); @@ -36,6 +55,33 @@ body { -webkit-font-smoothing: antialiased; } +:root[data-theme="dark"] input, +:root[data-theme="dark"] textarea, +:root[data-theme="dark"] select { + color: var(--text); +} + +:root[data-theme="dark"] input::placeholder, +:root[data-theme="dark"] textarea::placeholder { + color: #aebbd0; +} + +:root[data-theme="dark"] .text-muted\/30, +:root[data-theme="dark"] .text-muted\/35, +:root[data-theme="dark"] .text-muted\/40, +:root[data-theme="dark"] .text-muted\/50 { + color: #aebbd0; +} + +:root[data-theme="dark"] .text-text\/70, +:root[data-theme="dark"] .text-text\/80 { + color: #d8e2f0; +} + +:root[data-theme="dark"] .border-line\/50 { + border-color: #43546d; +} + /* Scrollbar */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } diff --git a/write/src/app/images/page.tsx b/write/src/app/images/page.tsx index f7942611..b92fccd4 100644 --- a/write/src/app/images/page.tsx +++ b/write/src/app/images/page.tsx @@ -86,7 +86,7 @@ export default function ImagesPage() { ) : (
{images.map((img) => ( -
+
) { + const themeScript = ` + try { + if (localStorage.getItem("write-theme") === "dark") { + document.documentElement.dataset.theme = "dark"; + } + } catch {} + `; + return ( - - + + +