diff --git a/manifest.json b/manifest.json
index 5bdde59..18699d6 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "querydash",
"name": "QueryDash",
- "version": "2.0.3",
+ "version": "2.1.0",
"minAppVersion": "1.8.0",
"description": "The new view for bases refers to an updated or additional interface that allows users to interact with base data in a different way, such as through enhanced search, sorting, and pagination features, similar to Notion. This improves usability and data management within your application. It was originally a new view for dataview, but now its functionality has been extended to bases.",
"author": "lwx",
diff --git a/package.json b/package.json
index 6358747..1044fb0 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
"obsidian": "latest",
"obsidian-dataview": "^0.5.68",
"tslib": "2.8.1",
- "vite": "7.1.9",
+ "vite": "7.1.11",
"vite-plugin-css-injected-by-js": "^3.5.2"
}
}
diff --git a/src/pages/ReactComponent.tsx b/src/pages/ReactComponent.tsx
index 871e832..741d801 100644
--- a/src/pages/ReactComponent.tsx
+++ b/src/pages/ReactComponent.tsx
@@ -15,12 +15,20 @@ export default class ReactComponent extends MarkdownRenderChild {
}
onload() {
+ super.onload();
this.renderReactComponent();
}
renderReactComponent() {
-
const root = createRoot(this.container);
- root.render();
+ this.root = root;
+ root.render();
+ }
+
+ onunload() {
+ if (this.root) {
+ this.root.unmount();
+ }
+ super.onunload();
}
}
diff --git a/src/pages/bases/DashMemoryCardView.tsx b/src/pages/bases/DashMemoryCardView.tsx
index 2ac7bff..101d8d0 100644
--- a/src/pages/bases/DashMemoryCardView.tsx
+++ b/src/pages/bases/DashMemoryCardView.tsx
@@ -23,6 +23,18 @@ export class DashMemoryCardView extends BasesView {
public async onDataUpdated(): Promise {
this.containerEl.empty();
const allCards = this.data.data;
+
+ // 重新排序卡片
+ this.sortAndFilterCards(allCards);
+
+ // 找到第一个需要复习的卡片
+ this.findFirstCardToReview();
+
+ // 渲染卡片
+ this.renderMemoryCard();
+ }
+
+ private sortAndFilterCards(allCards: any[]) {
// 按 sr-due 升序排序
this.sortedCards = [...allCards]
.map(function (card) {
@@ -38,28 +50,66 @@ export class DashMemoryCardView extends BasesView {
return dayjs(a.due).isSameOrBefore(b.due) ? -1 : 1;
})
.map(item => item.card);
- // 找到 sr-due 最近的卡片
- let idx = 0;
+ }
+
+ private findFirstCardToReview() {
const now = dayjs();
+ let idx = 0;
+
+ // 找到第一个到期日期在今天或之前的卡片
for (let i = 0; i < this.sortedCards.length; i++) {
const due = this.sortedCards[i].getValue('note.sr-due');
- if (due && dayjs(due).isSameOrBefore(now, 'day')) {
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
idx = i;
break;
}
}
+
this.currentIndex = idx;
- this.renderMemoryCard();
+
+ // 如果没有找到需要复习的卡片,显示完成消息
+ if (this.sortedCards.length > 0) {
+ const due = this.sortedCards[idx].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (!dueString || !dayjs(dueString).isSameOrBefore(now, 'day')) {
+ // 所有卡片的到期日期都在未来
+ this.currentIndex = -1;
+ }
+ }
}
private async renderMemoryCard() {
+ // 检查是否有需要复习的卡片
+ if (this.currentIndex === -1 || !this.sortedCards[this.currentIndex]) {
+ this.containerEl.empty();
+ const completeDiv = this.containerEl.createDiv({
+ text: '🎉 恭喜!所有卡片都已复习完成!',
+ cls: 'memory-card-complete'
+ });
+ completeDiv.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ font-size: 24px;
+ font-weight: bold;
+ color: #52c41a;
+ text-align: center;
+ padding: 40px;
+ `;
+ return;
+ }
+
const cardData = this.sortedCards[this.currentIndex];
if (!cardData) return;
+
const filePath = cardData?.getValue('file.path');
const filePathStr = typeof filePath === 'string' ? filePath : filePath?.toString() || '';
let markdownStr = '';
let title = '';
let frontmatter: Record | undefined = undefined;
+
if (filePathStr) {
const file = this.app.vault.getAbstractFileByPath(filePathStr);
if (file instanceof TFile) {
@@ -71,8 +121,35 @@ export class DashMemoryCardView extends BasesView {
}
}
}
+
const container = this.containerEl.createDiv();
const root = createRoot(container);
+
+ // 计算是否有前一个/后一个需要复习的卡片
+ const now = dayjs();
+ let hasPrevCard = false;
+ let hasNextCard = false;
+
+ // 检查前一个需要复习的卡片
+ for (let i = this.currentIndex - 1; i >= 0; i--) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ hasPrevCard = true;
+ break;
+ }
+ }
+
+ // 检查后一个需要复习的卡片
+ for (let i = this.currentIndex + 1; i < this.sortedCards.length; i++) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ hasNextCard = true;
+ break;
+ }
+ }
+
root.render(
);
}
private handlePrev = () => {
- if (this.currentIndex > 0) {
- this.currentIndex--;
+ // 查找前一个需要复习的卡片
+ this.findPrevCardToReview();
+ };
+
+ private findPrevCardToReview() {
+ const now = dayjs();
+ let prevIndex = -1;
+
+ // 从当前索引-1开始向前查找
+ for (let i = this.currentIndex - 1; i >= 0; i--) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ prevIndex = i;
+ break;
+ }
+ }
+
+ // 如果前面没有找到,从末尾开始查找
+ if (prevIndex === -1) {
+ for (let i = this.sortedCards.length - 1; i >= 0; i--) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ prevIndex = i;
+ break;
+ }
+ }
+ }
+
+ // 更新当前索引并重新渲染
+ if (prevIndex !== -1 && prevIndex !== this.currentIndex) {
+ this.currentIndex = prevIndex;
this.containerEl.empty();
this.renderMemoryCard();
}
- };
+ }
private handleNext = () => {
- if (this.currentIndex < this.sortedCards.length - 1) {
- this.currentIndex++;
+ // 重新计算需要复习的卡片
+ this.findNextCardToReview();
+ };
+
+ private findNextCardToReview() {
+ const now = dayjs();
+ let nextIndex = -1;
+
+ // 从当前索引+1开始查找
+ for (let i = this.currentIndex + 1; i < this.sortedCards.length; i++) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ nextIndex = i;
+ break;
+ }
+ }
+
+ // 如果后面没有找到,从头开始查找
+ if (nextIndex === -1) {
+ for (let i = 0; i < this.sortedCards.length; i++) {
+ const due = this.sortedCards[i].getValue('note.sr-due');
+ const dueString = due?.toString() || '';
+ if (dueString && dayjs(dueString).isSameOrBefore(now, 'day')) {
+ nextIndex = i;
+ break;
+ }
+ }
+ }
+
+ // 更新当前索引并重新渲染
+ if (nextIndex !== -1 && nextIndex !== this.currentIndex) {
+ this.currentIndex = nextIndex;
this.containerEl.empty();
this.renderMemoryCard();
+ } else {
+ // 没有更多需要复习的卡片
+ this.containerEl.empty();
+ const completeDiv = this.containerEl.createDiv({
+ text: '🎉 恭喜!所有卡片都已复习完成!',
+ cls: 'memory-card-complete'
+ });
+ completeDiv.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ font-size: 24px;
+ font-weight: bold;
+ color: #52c41a;
+ text-align: center;
+ padding: 40px;
+ `;
}
- };
+ }
}
interface MemoryCardProps {
@@ -127,33 +284,52 @@ const updateSRFrontmatter = async (
if (!filePath) return false;
const file = app.vault.getAbstractFileByPath(filePath);
if (!(file instanceof TFile)) return false;
+
+ // 获取当前值
let repetitions = Number(frontmatter?.['sr-repetitions']) || 0;
- let interval = Number(frontmatter?.['sr-interval']) || 1;
+ let interval = Number(frontmatter?.['sr-interval']) || 0;
let ease = Number(frontmatter?.['sr-ease']) || 2.5;
let lapses = Number(frontmatter?.['sr-lapses']) || 0;
+ // 基于SM-2算法的改进版本
if (rating < 3) {
- repetitions = 0;
+ // 回答错误或困难:重置间隔,增加遗忘次数
lapses += 1;
+ repetitions = 0;
interval = 1;
- ease = Math.max(1.3, Number((ease - 0.2).toFixed(2)));
+ // 降低易度因子,但不要低于1.3
+ ease = Math.max(1.3, ease - 0.2);
} else {
- repetitions += 1;
- if (repetitions === 1) {
+ // 回答正确
+ if (repetitions === 0) {
+ // 第一次复习
interval = 1;
- } else if (repetitions === 2) {
+ } else if (repetitions === 1) {
+ // 第二次复习
interval = 6;
} else {
+ // 后续复习:使用当前间隔乘以易度因子
interval = Math.round(interval * ease);
}
+
+ // 更新易度因子
+ // 公式:新易度 = 旧易度 + (0.1 - (5 - 评分) * (0.08 + (5 - 评分) * 0.02))
ease = ease + 0.1 - (5 - rating) * (0.08 + (5 - rating) * 0.02);
ease = Math.max(1.3, Number(ease.toFixed(2)));
+
+ // 增加复习次数
+ repetitions += 1;
}
+
+ // 计算下次复习日期
const due = dayjs().add(interval, 'day');
- const now = dayjs();
- if (due.diff(now, 'day') >= 365) {
+
+ // 如果间隔超过1年,标记为已掌握
+ if (interval >= 365) {
return true;
}
+
+ // 更新frontmatter
await app.fileManager.processFrontMatter(file, (fm: Record) => {
fm['sr-repetitions'] = repetitions;
fm['sr-interval'] = interval;
@@ -161,6 +337,8 @@ const updateSRFrontmatter = async (
fm['sr-due'] = due.format('YYYY-MM-DD');
fm['sr-lapses'] = lapses;
});
+
+ // 触发刷新
refresh();
return false;
};
@@ -183,25 +361,31 @@ const MemoryCard: React.FC = ({
useEffect(() => {
if (mdRef.current && !rendered) {
- // 创建一个简单的组件实例
- const component = {
- load: () => {},
- unload: () => {}
- };
-
- MarkdownRenderer.render(
- app,
- markdown,
- mdRef.current,
- filePath,
- component as any
- );
- setRendered(true);
+ try {
+ // 使用renderMarkdown方法渲染markdown
+ // 这个方法更适合在React组件中使用
+ MarkdownRenderer.renderMarkdown(
+ markdown,
+ mdRef.current,
+ filePath,
+ null as any
+ );
+ setRendered(true);
+ } catch (error) {
+ console.error('Error rendering markdown:', error);
+ // 如果渲染失败,直接显示原始markdown
+ if (mdRef.current) {
+ mdRef.current.innerHTML = markdown;
+ }
+ }
}
}, [markdown, app, filePath]);
const refresh = async () => {
-
+ // 刷新数据:重新获取卡片数据并重新渲染
+ // 这个函数会被updateSRFrontmatter调用
+ // 在实际实现中,这里应该触发父组件重新加载数据
+ // 目前我们先留空,因为数据更新已经在updateSRFrontmatter中处理了
};
const handleDifficulty = async (action: 'hard' | 'medium' | 'easy') => {
@@ -210,7 +394,11 @@ const MemoryCard: React.FC = ({
else if (action === 'medium') rating = 3;
else if (action === 'easy') rating = 5;
const isDueOverYear = await updateSRFrontmatter(app, filePath, fm, rating, refresh);
- if (isDueOverYear) setShowModal(true);
+ if (isDueOverYear) {
+ setShowModal(true);
+ }
+ // 评分后自动跳转到下一个卡片
+ onNext();
};
return (