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 00000000..5d525b82 Binary files /dev/null and b/write/.recycle/posts/202605232251/mpih806u5rw5.png differ 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 ( - - + + +