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 -}}
-
\ No newline at end of file
+
+
+
\ 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
+---
+
+
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 (