mirror of
https://github.com/liufree/obsidian-querydash.git
synced 2026-07-22 05:41:49 +00:00
feat: fix issues
This commit is contained in:
parent
b35646aa72
commit
13bf7d5ec3
4 changed files with 236 additions and 40 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,20 @@ export default class ReactComponent extends MarkdownRenderChild {
|
|||
}
|
||||
|
||||
onload() {
|
||||
super.onload();
|
||||
this.renderReactComponent();
|
||||
}
|
||||
|
||||
renderReactComponent() {
|
||||
|
||||
const root = createRoot(this.container);
|
||||
root.render(<QueryDashView app={this.app} source={this.source} />);
|
||||
this.root = root;
|
||||
root.render(<QueryDashView app={this.app} source={this.source} />);
|
||||
}
|
||||
|
||||
onunload() {
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
}
|
||||
super.onunload();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,18 @@ export class DashMemoryCardView extends BasesView {
|
|||
public async onDataUpdated(): Promise<void> {
|
||||
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<string, any> | 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(
|
||||
<MemoryCard
|
||||
markdown={markdownStr}
|
||||
|
|
@ -82,27 +159,107 @@ export class DashMemoryCardView extends BasesView {
|
|||
frontmatter={frontmatter}
|
||||
onPrev={this.handlePrev}
|
||||
onNext={this.handleNext}
|
||||
isPrevDisabled={this.currentIndex === 0}
|
||||
isNextDisabled={this.currentIndex === this.sortedCards.length - 1}
|
||||
isPrevDisabled={!hasPrevCard}
|
||||
isNextDisabled={!hasNextCard}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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<string, any>) => {
|
||||
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<MemoryCardProps> = ({
|
|||
|
||||
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<MemoryCardProps> = ({
|
|||
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 (
|
||||
|
|
|
|||
Loading…
Reference in a new issue