Fix bot scan Required issues: async/await, unhandled promises, sentence case

This commit is contained in:
Chenyan Wang 2026-05-03 20:52:40 +08:00
parent fed2d564bb
commit 62332e5008
3 changed files with 164 additions and 73 deletions

83
NEXT_STEPS.md Normal file
View file

@ -0,0 +1,83 @@
# Smart Folder View - 下一步执行清单
更新时间2026-05-03
## 当前状态
- 社区插件提交流程已完成PR 已创建并通过自动校验Ready for review
- 插件仓库已发布 `0.1.0` 正式 Release非 pre-release
## 下次开工先做这 3 件事
1. 打开社区 PR查看是否有人工审核评论。
2. 如果有修改意见:在仓库修复 -> 发新 Release 资产 -> 在同一个 PR 回复“已修复”。
3. 如果没有评论:转去完善 README截图/GIF/使用示例),提升安装转化。
## 审核阶段操作规范
- 不要新开 PR所有修改都在现有 PR 上继续。
- 不要关闭 PR。
- 每次修改后都在 PR 下留一句简短回复,说明改了什么。
- 如果改了影响发布的文件(如 `main.js`、`manifest.json`、`styles.css`),要同步更新 GitHub Release 资产。
## README 强化清单(优先级高)
- [ ] 增加 1 张总览截图(时间轴 + 看板)
- [ ] 增加 3-4 段短 GIF每段 10-20 秒)
- [ ] 增加“30 秒上手”步骤
- [ ] 增加“典型使用场景”
- [ ] 增加“常见问题 / 排查”
- [ ] 增加中英文标题锚点(便于分享)
## 演示素材脚本(可直接照着录)
### GIF 1创建页面
1. 打开命令Create Smart Folder Page
2. 选择来源文件夹、模板、字段
3. 点击 Preview
4. 点击 Save and Open
### GIF 2时间轴操作
1. 切换筛选条件
2. 拖动卡片调整顺序
3. 点击 Undo last action
### GIF 3看板操作
1. 切换到 Board
2. 跨列拖拽卡片
3. 展示自动更新分组字段
### GIF 4预设与复用
1. 保存 preset
2. 切换 preset
3. 展示状态持久化
## 版本发布 SOP后续每个版本都按这个走
1. 修改代码。
2. 更新 `manifest.json``version` 与必要字段。
3. 更新 `versions.json`:新增 `"新版本": "最低兼容版本"`
4. 打包并创建 GitHub Releasetag 必须与版本号完全一致,不加 `v`)。
5. 上传 Release 资产:
- `main.js`
- `manifest.json`
- `styles.css`(如有)
6. 在社区 PR审核期或新版本发布说明已上架后同步说明变更。
## 你可以晚点再做的优化
- [ ] 配置 Git 全局身份,消除 commit 提示:
- `git config --global user.name "your-name"`
- `git config --global user.email "your-email"`
- [ ] 补一个 `CHANGELOG.md`(可选)
- [ ] 增加 issue 模板bug / feature request
## 快速入口链接
- 插件仓库:<https://github.com/sixtarocyan/smart-folder-view>
- 社区提交 PR<https://github.com/obsidianmd/obsidian-releases/pull/12499>
- Obsidian 插件指南:<https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines>

76
main.js
View file

@ -156,11 +156,11 @@ var I18N = {
},
en: {
title: "Smart Folder View",
openBuilder: "Create Smart Folder Page",
openManager: "Manage Saved Pages",
openLast: "Open Last Page",
openBuilder: "Create Smart Folder page",
openManager: "Manage saved pages",
openLast: "Open last page",
noProfile: "No saved pages yet. Please create one first.",
setupTitle: "Configure Page",
setupTitle: "Configure page",
setupDesc: "Page settings anchor data source: folder, template file, and filterable fields. Preview first, then save.",
profileName: "Page name",
sourceFolder: "Source folder",
@ -181,19 +181,19 @@ var I18N = {
board: "Board",
asc: "Ascending (old -> new)",
desc: "Descending (new -> old)",
savePage: "Save Page",
updatePage: "Update Page",
openBuilderBtn: "Create Page",
openManagerBtn: "Page Manager",
savePage: "Save page",
updatePage: "Update page",
openBuilderBtn: "Create page",
openManagerBtn: "Page manager",
startActionTitle: "Smart Folder",
startActionDesc: "Choose an action:",
startOpenPage: "Open Page",
startNewPage: "Create Page",
startOpenPage: "Open page",
startNewPage: "Create page",
pageSaved: "Page saved",
pageUpdated: "Page updated",
saveFailed: "Failed to save page",
pageNamePrompt: "Enter page name",
managerTitle: "Saved Pages",
managerTitle: "Saved pages",
open: "Open",
edit: "Edit",
delete: "Delete",
@ -281,7 +281,7 @@ function detectLanguage() {
try {
const locale = window.moment?.locale?.() || "";
if (locale.toLowerCase().startsWith("zh")) return "zh";
} catch (_e) {
} catch {
}
return "en";
}
@ -720,7 +720,7 @@ var ProfileManagerModal = class extends obsidian.Modal {
contentEl.createEl("h2", { text: this.plugin.t("managerTitle") });
for (const profile of this.plugin.data.profiles) {
const s = new obsidian.Setting(contentEl).setName(profile.name || profile.sourceFolder).setDesc(profile.sourceFolder);
s.addButton((b) => b.setButtonText(this.plugin.t("open")).onClick(async () => {
s.addButton((b) => b.setButtonText(this.plugin.t("open")).onClick(() => {
void (async () => {
this.plugin.clearDraftProfile();
this.plugin.data.lastActiveProfileId = profile.id;
@ -891,7 +891,7 @@ var SmartFolderView = class extends obsidian.ItemView {
async rerenderPreserveScroll() {
const container = this.containerEl.children[1];
const scrollTop = container?.scrollTop || 0;
await this.renderView();
this.renderView();
const nextContainer = this.containerEl.children[1];
await new Promise((resolve) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
if (nextContainer) nextContainer.scrollTop = scrollTop;
@ -1014,7 +1014,7 @@ var SmartFolderView = class extends obsidian.ItemView {
e.preventDefault();
this.app.workspace.openLinkText(href, file.path, e.ctrlKey || e.metaKey);
}, true);
this.app.vault.read(file).then((raw) => {
void this.app.vault.read(file).then((raw) => {
const body = stripFrontmatter(raw) || "\uFF08\u65E0\u6B63\u6587\uFF09";
obsidian.MarkdownRenderer.render(this.app, body, bodyEl, file.path, this);
}).catch(() => bodyEl.setText("\uFF08\u6B63\u6587\u8BFB\u53D6\u5931\u8D25\uFF09"));
@ -1124,13 +1124,15 @@ var SmartFolderView = class extends obsidian.ItemView {
topBar.addClass("sfv-top-bar");
const saveBtn = topBar.createEl("button", { text: this.plugin.isDraft(profile.id) ? this.plugin.t("savePage") : this.plugin.t("updatePage") });
saveBtn.addClass("mod-cta");
saveBtn.addEventListener("click", async () => {
try {
await this.plugin.saveProfile(profile, true, false);
await this.rerenderPreserveScroll();
} catch {
new obsidian.Notice(this.plugin.t("saveFailed"));
}
saveBtn.addEventListener("click", () => {
void (async () => {
try {
await this.plugin.saveProfile(profile, true, false);
await this.rerenderPreserveScroll();
} catch {
new obsidian.Notice(this.plugin.t("saveFailed"));
}
})();
});
topBar.createEl("button", { text: this.plugin.t("openBuilderBtn") }).addEventListener("click", () => new SetupModal(this.app, this.plugin).open());
topBar.createEl("button", { text: this.plugin.t("openManagerBtn") }).addEventListener("click", () => new ProfileManagerModal(this.app, this.plugin).open());
@ -1331,14 +1333,16 @@ var SmartFolderView = class extends obsidian.ItemView {
this.saveOrder(profile.id, this.orderKey(profile), ordered.map((e) => e.file));
new obsidian.Notice(this.plugin.t("saveOrderDone"));
});
orderActions2.createEl("button", { text: this.plugin.t("resetOrder") }).addEventListener("click", async () => {
this.recordUndoSnapshot(profile);
this.clearOrder(profile.id, this.orderKey(profile));
new obsidian.Notice(this.plugin.t("resetOrderDone"));
await this.rerenderPreserveScroll();
orderActions2.createEl("button", { text: this.plugin.t("resetOrder") }).addEventListener("click", () => {
void (async () => {
this.recordUndoSnapshot(profile);
this.clearOrder(profile.id, this.orderKey(profile));
new obsidian.Notice(this.plugin.t("resetOrderDone"));
await this.rerenderPreserveScroll();
})();
});
orderActions2.createEl("button", { text: this.plugin.t("undoLastAction") }).addEventListener("click", async () => {
await this.undoLastAction(profile);
orderActions2.createEl("button", { text: this.plugin.t("undoLastAction") }).addEventListener("click", () => {
void this.undoLastAction(profile);
});
const timelineEl = container.createEl("div");
timelineEl.addClass("sfv-timeline");
@ -1495,14 +1499,14 @@ var SmartFolderView = class extends obsidian.ItemView {
}
new obsidian.Notice(this.plugin.t("saveOrderDone"));
});
orderActions.createEl("button", { text: this.plugin.t("resetOrder") }).addEventListener("click", async () => {
orderActions.createEl("button", { text: this.plugin.t("resetOrder") }).addEventListener("click", () => {
this.recordUndoSnapshot(profile);
for (const col of columns) this.clearOrder(profile.id, this.orderKey(profile, col));
new obsidian.Notice(this.plugin.t("resetOrderDone"));
await this.renderView();
this.renderView();
});
orderActions.createEl("button", { text: this.plugin.t("undoLastAction") }).addEventListener("click", async () => {
await this.undoLastAction(profile);
orderActions.createEl("button", { text: this.plugin.t("undoLastAction") }).addEventListener("click", () => {
void this.undoLastAction(profile);
});
const boardEl = container.createEl("div");
boardEl.addClass("sfv-board");
@ -1845,7 +1849,7 @@ var SmartFolderPlugin = class extends obsidian.Plugin {
async onload() {
await this.loadPluginData();
this.registerView(VIEW_TYPE, (leaf) => new SmartFolderView(leaf, this));
this.registerMarkdownCodeBlockProcessor("smart-folder-page", async (source, el) => {
this.registerMarkdownCodeBlockProcessor("smart-folder-page", (source, el) => {
el.empty();
let raw;
try {
@ -1939,7 +1943,7 @@ var SmartFolderPlugin = class extends obsidian.Plugin {
leaf = workspace.getLeaf("tab");
await leaf.setViewState({ type: VIEW_TYPE, active: true });
}
workspace.revealLeaf(leaf);
void workspace.revealLeaf(leaf);
const view = leaf.view;
if (view instanceof SmartFolderView) {
view.setPinnedProfile(profile || null);
@ -1947,7 +1951,7 @@ var SmartFolderPlugin = class extends obsidian.Plugin {
this.data.lastActiveProfileId = profile.id;
await this.persist();
}
await view.onOpen();
view.onOpen();
}
}
onunload() {

View file

@ -135,11 +135,11 @@ const I18N = {
},
en: {
title: 'Smart Folder View',
openBuilder: 'Create Smart Folder Page',
openManager: 'Manage Saved Pages',
openLast: 'Open Last Page',
openBuilder: 'Create Smart Folder page',
openManager: 'Manage saved pages',
openLast: 'Open last page',
noProfile: 'No saved pages yet. Please create one first.',
setupTitle: 'Configure Page',
setupTitle: 'Configure page',
setupDesc: 'Page settings anchor data source: folder, template file, and filterable fields. Preview first, then save.',
profileName: 'Page name',
sourceFolder: 'Source folder',
@ -160,19 +160,19 @@ const I18N = {
board: 'Board',
asc: 'Ascending (old -> new)',
desc: 'Descending (new -> old)',
savePage: 'Save Page',
updatePage: 'Update Page',
openBuilderBtn: 'Create Page',
openManagerBtn: 'Page Manager',
savePage: 'Save page',
updatePage: 'Update page',
openBuilderBtn: 'Create page',
openManagerBtn: 'Page manager',
startActionTitle: 'Smart Folder',
startActionDesc: 'Choose an action:',
startOpenPage: 'Open Page',
startNewPage: 'Create Page',
startOpenPage: 'Open page',
startNewPage: 'Create page',
pageSaved: 'Page saved',
pageUpdated: 'Page updated',
saveFailed: 'Failed to save page',
pageNamePrompt: 'Enter page name',
managerTitle: 'Saved Pages',
managerTitle: 'Saved pages',
open: 'Open',
edit: 'Edit',
delete: 'Delete',
@ -262,7 +262,7 @@ function detectLanguage() {
try {
const locale = window.moment?.locale?.() || '';
if (locale.toLowerCase().startsWith('zh')) return 'zh';
} catch (_e) {
} catch {
// ignore
}
return 'en';
@ -768,7 +768,7 @@ class ProfileManagerModal extends obsidian.Modal {
for (const profile of this.plugin.data.profiles) {
const s = new obsidian.Setting(contentEl).setName(profile.name || profile.sourceFolder).setDesc(profile.sourceFolder);
s.addButton(b => b.setButtonText(this.plugin.t('open')).onClick(async () => {
s.addButton(b => b.setButtonText(this.plugin.t('open')).onClick(() => {
void (async () => {
this.plugin.clearDraftProfile();
this.plugin.data.lastActiveProfileId = profile.id;
@ -950,7 +950,7 @@ class SmartFolderView extends obsidian.ItemView {
async rerenderPreserveScroll() {
const container = this.containerEl.children[1];
const scrollTop = container?.scrollTop || 0;
await this.renderView();
this.renderView();
const nextContainer = this.containerEl.children[1];
// Wait two animation frames to ensure browser has completed layout before restoring scroll
await new Promise(resolve => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
@ -1087,7 +1087,7 @@ class SmartFolderView extends obsidian.ItemView {
e.preventDefault();
this.app.workspace.openLinkText(href, file.path, e.ctrlKey || e.metaKey);
}, true);
this.app.vault.read(file).then(raw => {
void this.app.vault.read(file).then(raw => {
const body = stripFrontmatter(raw) || '(无正文)';
obsidian.MarkdownRenderer.render(this.app, body, bodyEl, file.path, this);
}).catch(() => bodyEl.setText('(正文读取失败)'));
@ -1215,14 +1215,16 @@ class SmartFolderView extends obsidian.ItemView {
topBar.addClass('sfv-top-bar');
const saveBtn = topBar.createEl('button', { text: this.plugin.isDraft(profile.id) ? this.plugin.t('savePage') : this.plugin.t('updatePage') });
saveBtn.addClass('mod-cta');
saveBtn.addEventListener('click', async () => {
try {
// Save current page config directly into saved pages/history list.
await this.plugin.saveProfile(profile, true, false);
await this.rerenderPreserveScroll();
} catch {
new obsidian.Notice(this.plugin.t('saveFailed'));
}
saveBtn.addEventListener('click', () => {
void (async () => {
try {
// Save current page config directly into saved pages/history list.
await this.plugin.saveProfile(profile, true, false);
await this.rerenderPreserveScroll();
} catch {
new obsidian.Notice(this.plugin.t('saveFailed'));
}
})();
});
topBar.createEl('button', { text: this.plugin.t('openBuilderBtn') }).addEventListener('click', () => new SetupModal(this.app, this.plugin).open());
topBar.createEl('button', { text: this.plugin.t('openManagerBtn') }).addEventListener('click', () => new ProfileManagerModal(this.app, this.plugin).open());
@ -1446,14 +1448,16 @@ class SmartFolderView extends obsidian.ItemView {
this.saveOrder(profile.id, this.orderKey(profile), ordered.map(e => e.file));
new obsidian.Notice(this.plugin.t('saveOrderDone'));
});
orderActions.createEl('button', { text: this.plugin.t('resetOrder') }).addEventListener('click', async () => {
this.recordUndoSnapshot(profile);
this.clearOrder(profile.id, this.orderKey(profile));
new obsidian.Notice(this.plugin.t('resetOrderDone'));
await this.rerenderPreserveScroll();
orderActions.createEl('button', { text: this.plugin.t('resetOrder') }).addEventListener('click', () => {
void (async () => {
this.recordUndoSnapshot(profile);
this.clearOrder(profile.id, this.orderKey(profile));
new obsidian.Notice(this.plugin.t('resetOrderDone'));
await this.rerenderPreserveScroll();
})();
});
orderActions.createEl('button', { text: this.plugin.t('undoLastAction') }).addEventListener('click', async () => {
await this.undoLastAction(profile);
orderActions.createEl('button', { text: this.plugin.t('undoLastAction') }).addEventListener('click', () => {
void this.undoLastAction(profile);
});
const timelineEl = container.createEl('div');
@ -1624,14 +1628,14 @@ class SmartFolderView extends obsidian.ItemView {
}
new obsidian.Notice(this.plugin.t('saveOrderDone'));
});
orderActions.createEl('button', { text: this.plugin.t('resetOrder') }).addEventListener('click', async () => {
orderActions.createEl('button', { text: this.plugin.t('resetOrder') }).addEventListener('click', () => {
this.recordUndoSnapshot(profile);
for (const col of columns) this.clearOrder(profile.id, this.orderKey(profile, col));
new obsidian.Notice(this.plugin.t('resetOrderDone'));
await this.renderView();
this.renderView();
});
orderActions.createEl('button', { text: this.plugin.t('undoLastAction') }).addEventListener('click', async () => {
await this.undoLastAction(profile);
orderActions.createEl('button', { text: this.plugin.t('undoLastAction') }).addEventListener('click', () => {
void this.undoLastAction(profile);
});
const boardEl = container.createEl('div');
@ -2067,7 +2071,7 @@ class SmartFolderPlugin extends obsidian.Plugin {
this.registerView(VIEW_TYPE, leaf => new SmartFolderView(leaf, this));
this.registerMarkdownCodeBlockProcessor('smart-folder-page', async (source, el) => {
this.registerMarkdownCodeBlockProcessor('smart-folder-page', (source, el) => {
el.empty();
let raw;
try {
@ -2165,7 +2169,7 @@ class SmartFolderPlugin extends obsidian.Plugin {
leaf = workspace.getLeaf('tab');
await leaf.setViewState({ type: VIEW_TYPE, active: true });
}
workspace.revealLeaf(leaf);
void workspace.revealLeaf(leaf);
const view = leaf.view;
if (view instanceof SmartFolderView) {
view.setPinnedProfile(profile || null);
@ -2173,7 +2177,7 @@ class SmartFolderPlugin extends obsidian.Plugin {
this.data.lastActiveProfileId = profile.id;
await this.persist();
}
await view.onOpen();
view.onOpen();
}
}