mirror of
https://github.com/sixtarocyan/smart-folder-view.git
synced 2026-07-22 06:50:05 +00:00
Fix bot scan Required issues: async/await, unhandled promises, sentence case
This commit is contained in:
parent
fed2d564bb
commit
62332e5008
3 changed files with 164 additions and 73 deletions
83
NEXT_STEPS.md
Normal file
83
NEXT_STEPS.md
Normal 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 Release(tag 必须与版本号完全一致,不加 `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
76
main.js
|
|
@ -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() {
|
||||
|
|
|
|||
78
src/main.ts
78
src/main.ts
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue