Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
- 首页两列对齐约束:`getIndexRecentArticles` 的第一页会插入全部置顶且不截断;第二页起需按第一页“置顶占位数”补偿 `fetchSize` 与分页偏移,并基于已排除置顶 ID 的普通文章序列取数,保证两列等高、不重复、不丢中间文章。
- 首页右侧排行补偿(无前端延迟):由 `IndexProcessor#loadIndexData` 按两列最新文章的最大行数计算 `rankCompensateRows`,先换算“右栏总补偿行数”再分摊到 `checkinVisibleCount/onlineVisibleCount`,Freemarker 直接按该数量渲染,不再依赖 JS 运行时增删行。
- 专栏封面链路:封面字段是 `long_article_column.columnCoverURL`;默认封面在 `LongArticleColumnQueryService` 填充;作者管理页为 `/column/manage`,编辑长篇页的弹窗脚本是 `long-article-cover-dialog.js`,新建长篇封面随 `/article` 请求的 `columnCoverURL` 写入;封面保存接口为 `POST /api/columns/{columnId}/cover`,处理器是 `LongArticleColumnProcessor`。
- 长文阅读页以 `articleType=6` 区分,PC/移动模板均为 `skins/classic/*/article.ftl`,样式在 `index.scss`/`mobile-base.scss`,交互在 `long-article.js`/`m-long-article.js`;模板通过 `hidePageChrome` 不渲染顶部与底部视觉内容,但 footer 的公共脚本仍需加载;桌面宽度和两端字号统一保存在 `localStorage.longArticleSettings`,宽度仅在 PC 生效;PC 评论按钮控制居中的“正文 + 评论”阅读舞台,移动端使用右侧抽屉,现有评论列表、筛选、分页和编辑器均复用。
- 长文阅读页以 `articleType=6` 区分,PC/移动模板均为 `skins/classic/*/article.ftl`,样式在 `index.scss`/`mobile-base.scss`,交互在 `long-article.js`/`m-long-article.js`;模板通过 `hidePageChrome` 不渲染顶部与底部视觉内容,但 footer 的公共脚本仍需加载;桌面宽度和两端字号统一保存在 `localStorage.longArticleSettings`,宽度仅在 PC 生效;PC 评论按钮控制居中的“正文 + 评论”阅读舞台,移动端使用右侧抽屉,现有评论列表、筛选、分页和编辑器均复用;专栏目录直接复用阅读页数据模型中的 `longArticleChapters`,PC/移动均需同步目录模板、样式与工具栏脚本
- 长文段评链路:`LongArticleParagraphService` 根据服务端清洗后的正文生成段落锚点并给 HTML 添加 `data-long-paragraph-*`;段评字段存于 `comment`(`commentType=1`),新增接口位于 `CommentProcessor` 的 `/comment/paragraph*`;评论编辑事务调用段落迁移,无法匹配的段评标记为失效但保留快照。PC/移动均加载 `long-article-paragraph.js`,段评列表复用现有评论线程与编辑器能力。
- 管理员文章页编辑长文专栏时,`admin/article.ftl` 会随文章表单提交 `columnCoverURL`,后端在 `ArticleMgmtService` 的文章事务内复用 `ColumnCoverMgmtService` 校验并更新或清空专栏封面。
- 路由总入口:`Router#requestMapping` + 各 Processor `register()`;新增路由先决定使用 `loginCheck` / `apiCheck` / `permission` / `anonymousViewCheck` 哪条链路。
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/css/index.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main/resources/css/mobile-base.css

Large diffs are not rendered by default.

30 changes: 20 additions & 10 deletions src/main/resources/js/color-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const mode = localStorage.getItem('color-scheme') || 'auto';
DarkReader.setFetchMethod(window.fetch)
setColorMode(mode);
function toggleColorMode() {
const body = document.body;
let mode = localStorage.getItem('color-scheme'); // light / dark / auto
if (mode === 'dark') {
mode = isDarkModeInSystem ? 'light' : 'auto';
Expand All @@ -34,25 +33,36 @@ function toggleColorMode() {
}

function setColorMode(mode) {
if (mode == 'dark' || (mode == 'auto' && isDarkModeInSystem)) {
const isDark = mode == 'dark' || (mode == 'auto' && isDarkModeInSystem);
if (isDark) {
DarkReader.enable({
brightness: 100,
contrast: 90,
sepia: 10
});
document.querySelector('#color-mode svg use')?.setAttribute('xlink:href', '#color-moon')
} else {
DarkReader.auto(false);
document.querySelector('#color-mode svg use')?.setAttribute('xlink:href', '#color-sun')
}
localStorage.setItem('color-scheme', mode);
updateColorModeButtons(isDark);
}

function updateColorModeButtons(isDark) {
const headerButton = document.querySelector('#color-mode svg use');
headerButton?.setAttribute('xlink:href', isDark ? '#color-moon' : '#color-sun');
document.querySelectorAll('[data-color-mode-toggle]').forEach((button) => {
const icon = button.querySelector('svg use');
icon?.setAttribute('xlink:href', isDark ? '#color-sun' : '#color-moon');
const label = isDark ? '白天模式' : '黑夜模式';
button.setAttribute('aria-label', label);
button.setAttribute('title', label);
});
}

document.addEventListener("DOMContentLoaded", () => {
document.querySelector('#color-mode').addEventListener('click', toggleColorMode);
if (mode == 'dark' || (mode == 'auto' && isDarkModeInSystem)) {
document.querySelector('#color-mode svg use').setAttribute('xlink:href', '#color-moon');
} else {
document.querySelector('#color-mode svg use').setAttribute('xlink:href', '#color-sun');
const colorModeButton = document.querySelector('#color-mode');
if (colorModeButton) {
colorModeButton.addEventListener('click', toggleColorMode);
}
});
updateColorModeButtons(localStorage.getItem('color-scheme') == 'dark' || (localStorage.getItem('color-scheme') == 'auto' && isDarkModeInSystem));
});
2 changes: 1 addition & 1 deletion src/main/resources/js/color-mode.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 65 additions & 1 deletion src/main/resources/js/long-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,14 @@ window.LongArticle = {
}
var action = actionElement.getAttribute('data-long-article-action');
if (action === 'top') {
self.closeCatalog();
self.scrollToTop();
} else if (action === 'catalog') {
self.toggleCatalog();
} else if (action === 'color-mode') {
if (typeof window.toggleColorMode === 'function') {
window.toggleColorMode();
}
} else if (action === 'comments') {
if (window.LongArticleParagraphComments && window.LongArticleParagraphComments.getActiveParagraphId()) {
window.LongArticleParagraphComments.showChapterComments();
Expand Down Expand Up @@ -179,10 +186,15 @@ window.LongArticle = {
if (event.target.closest && event.target.closest('[data-long-article-comments-close]')) {
self.closeComments();
}
if (event.target.closest && event.target.closest('[data-long-article-catalog-close]')) {
self.closeCatalog();
}
});

document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && self.isCommentsOpen()) {
if (event.key === 'Escape' && self.isCatalogOpen()) {
self.closeCatalog();
} else if (event.key === 'Escape' && self.isCommentsOpen()) {
self.closeComments();
}
});
Expand Down Expand Up @@ -302,6 +314,57 @@ window.LongArticle = {
window.scrollTo({top: 0, behavior: 'smooth'});
},

getCatalog: function () {
return document.querySelector('[data-long-article-catalog]');
},

isCatalogOpen: function () {
return document.body.classList.contains('long-article-catalog-open');
},

toggleCatalog: function () {
if (this.isCatalogOpen()) {
this.closeCatalog();
} else {
this.openCatalog();
}
},

openCatalog: function () {
var catalog = this.getCatalog();
var button = document.querySelector('[data-long-article-action="catalog"]');
if (!catalog) {
return;
}
this.closeComments();
this.closeLayoutPanel();
document.body.classList.add('long-article-catalog-open');
catalog.setAttribute('aria-hidden', 'false');
if (button) {
button.classList.add('is-active');
button.setAttribute('aria-expanded', 'true');
}
window.setTimeout(function () {
var activeChapter = catalog.querySelector('.long-article-catalog__chapter--active');
if (activeChapter) {
activeChapter.scrollIntoView({block: 'center'});
}
}, 0);
},

closeCatalog: function () {
var catalog = this.getCatalog();
var button = document.querySelector('[data-long-article-action="catalog"]');
document.body.classList.remove('long-article-catalog-open');
if (catalog) {
catalog.setAttribute('aria-hidden', 'true');
}
if (button) {
button.classList.remove('is-active');
button.setAttribute('aria-expanded', 'false');
}
},

getCommentsPanel: function () {
return document.getElementById('articleCommentsPanel');
},
Expand All @@ -324,6 +387,7 @@ window.LongArticle = {
if (!panel) {
return;
}
this.closeCatalog();
this.closeLayoutPanel();
this.updateLayoutMetrics();
document.body.classList.add('long-article-comments-open');
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/js/long-article.min.js

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions src/main/resources/js/m-long-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,14 @@ window.MLongArticle = {
target.classList.toggle('active', open);
target.setAttribute('aria-expanded', open ? 'true' : 'false');
} else if (action === 'top') {
self.closeCatalog();
window.scrollTo({top: 0, behavior: 'smooth'});
} else if (action === 'catalog') {
self.toggleCatalog();
} else if (action === 'color-mode') {
if (typeof window.toggleColorMode === 'function') {
window.toggleColorMode();
}
} else if (action === 'comments') {
if (window.LongArticleParagraphComments && window.LongArticleParagraphComments.getActiveParagraphId()) {
window.LongArticleParagraphComments.showChapterComments();
Expand All @@ -130,6 +137,17 @@ window.MLongArticle = {
if (event.target.closest && event.target.closest('[data-long-article-comments-close]')) {
self.closeComments();
}
if (event.target.closest && event.target.closest('[data-long-article-catalog-close]')) {
self.closeCatalog();
}
});

document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && self.isCatalogOpen()) {
self.closeCatalog();
} else if (event.key === 'Escape' && self.isCommentsOpen()) {
self.closeComments();
}
});
},

Expand All @@ -139,6 +157,56 @@ window.MLongArticle = {
this.applySettings();
},

getCatalog: function () {
return document.querySelector('[data-long-article-catalog]');
},

isCatalogOpen: function () {
return document.body.classList.contains('long-article-catalog-open');
},

toggleCatalog: function () {
if (this.isCatalogOpen()) {
this.closeCatalog();
} else {
this.openCatalog();
}
},

openCatalog: function () {
var catalog = this.getCatalog();
var button = document.querySelector('[data-long-article-action="catalog"]');
if (!catalog) {
return;
}
this.closeComments();
document.body.classList.add('long-article-catalog-open');
catalog.setAttribute('aria-hidden', 'false');
if (button) {
button.classList.add('is-active');
button.setAttribute('aria-expanded', 'true');
}
window.setTimeout(function () {
var activeChapter = catalog.querySelector('.long-article-catalog__chapter--active');
if (activeChapter) {
activeChapter.scrollIntoView({block: 'center'});
}
}, 0);
},

closeCatalog: function () {
var catalog = this.getCatalog();
var button = document.querySelector('[data-long-article-action="catalog"]');
document.body.classList.remove('long-article-catalog-open');
if (catalog) {
catalog.setAttribute('aria-hidden', 'true');
}
if (button) {
button.classList.remove('is-active');
button.setAttribute('aria-expanded', 'false');
}
},

getCommentsPanel: function () {
return document.getElementById('comments');
},
Expand All @@ -161,6 +229,7 @@ window.MLongArticle = {
if (!panel) {
return;
}
this.closeCatalog();
document.body.classList.add('long-article-comments-open');
panel.setAttribute('aria-hidden', 'false');
if (button) {
Expand Down
Loading
Loading