open-spaced-repetition / obsidian-spaced-repetition-recall

Fight the forgetting curve by reviewing flashcards & entire notes on Obsidian.md
https://www.stephenmwangi.com/obsidian-spaced-repetition/
MIT License
109 stars 7 forks source link

[Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 #62

Closed dryezl closed 1 month ago

dryezl commented 5 months ago

Is your feature request related to a problem? Please describe.

因为个人笔记较多,因此每天进行复习的时候,其实不可能全部复习完的,所以想利用层级tags来对笔记的重要性进行分类的。这样,最起码能把重要性较高先复习完的。

Describe the solution you'd like

目前的笔记复习只能考虑一级标签的,没有二级标签的 image

Additional context

目前的插件转折实现的,新定义一级标签作为重要性的。好像这个想法也可以的。因为加上二级标签之后,如何展示也是一个问题的。卡片复习那边可以展示二级标签,因为它不用展示卡片列表内容的。 暂时提个想法的

Newdea commented 3 months ago

在侧边栏上这样显示确实不方便,可以借用dataview直接在markdown视图中进行显示,你觉得如何?

不过,目前保存的数据中,只有一级标签,具体如何做,还要再考虑下。 我下版先把数据接口开放出来,也许通过dv-js的方式就能完全达成效果了。

dryezl commented 3 months ago

在侧边栏上这样显示确实不方便,可以借用dataview直接在markdown视图中进行显示,你觉得如何?

我有点没有跟上,没有想好效果会是怎么样的?可能有示意图,会更清晰一些的。

Newdea commented 3 months ago

示意图没做出来,就是dataview查询的那种。 这里有dataview的一些教程,可以先了解下 https://obsidian.vip/zh/dataview/

Newdea commented 3 months ago

做出来了一版,不用更新插件,目前的就可以使用。

这是效果: image 这是md文件及dataviewjs代码 240815-22.48-srs-dvjs.md

并不是最佳实践,只能算是抛砖引玉吧,欢迎讨论分享

```dataviewjs
// 配置项
const showList = false;   // true: 显示为列表, false: 显示为表格,表格会更丰富复杂些
const tableTitle = ["tags", "fileLink", "复习次数", "难度", "上次", "间隔", "复习效果"]; // 表头, 与下边tableDataPro返回值保持一致
const tableDataPro = (tags, p, item)=> {
    // tags 即该笔记含有的全部标签
    // p 即dataview中的文件,使用格式p.file.xx, 其中xx为文件属性,具体可参考: 
    // 中文翻译版(较旧,有些属性没有更新) https://obsidian.vip/zh/dataview/dataview-advanced-a.html#_3-2-file-%E6%96%87%E4%BB%B6%E9%9A%90%E5%BC%8F%E5%AD%97%E6%AE%B5
    // 官方版: https://blacksmithgu.github.io/obsidian-dataview/annotation/metadata-pages/
    // item 即trackefiles.json中的数据结构
    // 如 {"nextReview":1694649162045,"ID":6,"fileIndex":4,"itemType":"note","deckName":"#incrmwrt","timesReviewed":3,"timesCorrect":3,"errorStreak":0,"data":{"due":"2023-09-13T23:52:42.039Z","stability":2.6,"difficulty":5.86999,"elapsed_days":0,"scheduled_days":0,"reps":3,"lapses":0,"state":1,"last_review":"2023-09-13T23:47:42.039Z"}}
    return [tags, p.file.link, item.timesReviewed, item.data.difficulty, item.data.last_review?.toDateString(), item.data.scheduled_days, item.data.state]; // 可根据需要修改为想显示的数据, 注意同步更新上面表头tableTitle 变量
}

// 插件
const srplugin = app.plugins.plugins["obsidian-spaced-repetition-recall"];
const store =   srplugin.store;
console.debug("store:", store);

const decks = srplugin.reviewDecks;  // 侧边栏显示的数据组
const tags = Object.keys(decks);  //要显示的一级标签
let deckfilepathes = [];  // 要显示的笔记路径

// 读取笔记路径
Object.values(decks).forEach(deck => {
    deck.newNotes.forEach(file=> deckfilepathes.push(file.note.path));
    deck.scheduledNotes.forEach(file => deckfilepathes.push(file.note.path));
})
//console.debug(decks);

// 主逻辑处理
Object.values(decks).forEach(deck => {
    const gpnew = {};
    const folderEl = summaryEl(deck.deckName);      // 创建一级标签css 

    // 新笔记进行子标签分组
    const newNotes = dv.array(deck.newNotes).groupBy(file =>{
       const pf = dv.page(file.note.path);
       return getMultiTag(pf,deck.deckName);
    })
    console.debug("gpnew: ", newNotes);
    newNotes.forEach((gp) => {
        gpnew[gp.key]=gp.rows;
        //_render_file(gp,gp.rows);
        //const links = dv.fileLink(gp.rows.note.path);
        //console.debug("links: ",links);
        //dv.list("[["+links.path+"]]");
    });

    // 复习过的笔记进行子标签分组
    const schedGroups = dv.array(deck.scheduledNotes);
    const dvGroups = schedGroups
    .groupBy((file) =>{
        return getMultiTag(dv.page(file.note.path),deck.deckName);
    });
    // console.debug("schedGroups: ", schedGroups, dvGroups);

    // 复习过的笔记根据复习日期分组
    dvGroups.forEach(gp =>{
        const schednotes = gp.rows.groupBy((file) =>{
            return new Date(file.item.nextReview).toDateString();
        })
        .sort(gp => new Date(gp.key).valueOf());
        // console.debug("schednotes: ", schednotes);

        // 创建子标签css 
        let subTagEl = folderEl;
        if(newNotes.length <= 1 && dvGroups.length <=1) {
            subTagEl = folderEl;
            }else {
                subTagEl = summaryEl(gp.key,true, folderEl);
            }

        // 显示新笔记
        if(gpnew[gp.key]){
            _render_file(subTagEl, gp.key,gpnew[gp.key], " - new");
            gpnew[gp.key]=undefined;
        }

        // 显示复习过的笔记
        schednotes.forEach(sns =>{
            // console.debug("sns: ", sns);
            _render_file(subTagEl, sns.key,sns.rows);
        });
    });

    // 显示只有新笔记的标签及笔记
    for(const key in gpnew) {
        if(gpnew[key]) {
            const subTagEl = summaryEl(key,true, folderEl);
            _render_file(subTagEl, key,gpnew[key], " - new");
            gpnew[key]=undefined;
        }
    }
    console.debug("schedGroups: ", schedGroups, dvGroups);

});

// 获取多级标签
function getMultiTag(p,key=undefined) {
    let gt = "default";
    tags.some(t => {
        for(const ft of p.file.tags) {
            if(ft.includes(t)) {
                gt=ft ;
                return true;
            }
        }
    });
    if(gt==="default" && key) {
        gt = key+"/"+gt;
    }
    return gt;
    };

// 创建大纲风格的css 元素
function summaryEl(key,child=false, folderEl=undefined) {
    key=key.startsWith("#")?key.substring(1):key;
    if(child) {
        return folderEl = dv.el("details", dv.el("summary", `${key}`), {container:folderEl,cls:"tree-item-children"});
    }
    return folderEl = dv.el("details", dv.el("summary", `** ${key}**`));
}

// 渲染笔记
function _render_file(folderEl, key, notes, title="") {
    if(notes.values.length<1) return;
    let tv = notes.map(file => {
        // console.debug("file", p)
        const p=dv.page(file.note.path);
        let tag="default";
        if(p.file.tags) {
            tag = p.file.tags;
        }
        if(showList) {
            return p.file.link;     // 返回值,以方便使用列表markdownList 进行渲染
            }else {
                return tableDataPro(tag,p, file.item);  // 返回数组,以方便使用表格markdownTable 进行渲染 
            }
    });
    // console.debug("tv:", tv);

    let dtlEl = summaryEl(key+title, true, folderEl);
    let data_render;
    if(showList) {
        data_render= dv.markdownList(tv);
    }else {
        data_render= dv.markdownTable(tableTitle,tv);
    }
    dv.el("div", data_render, {container:dtlEl});
}
dryezl commented 3 months ago

谢谢,可以实现的。

请问笔记复习(note review)的悬浮栏是否可以更新为当前笔记的状态呢?

我激活笔记复习的悬浮栏之后,通过上述dataviewjs跳转到对应笔记之后,发现悬浮栏上的状态没有对应更新的。

Newdea commented 3 months ago

这个真没办法🤣,dataview渲染的,已经不在插件控制范围内了

---原始邮件--- 发件人: "James @.> 发送时间: 2024年8月21日(周三) 中午1:02 收件人: @.>; 抄送: @.**@.>; 主题: Re: [open-spaced-repetition/obsidian-spaced-repetition-recall] [Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 (Issue #62)

谢谢,可以实现的。

请问笔记复习(note review)的悬浮栏是否可以更新为当前笔记的状态呢?

我激活笔记复习的悬浮栏之后,通过上述dataviewjs跳转到对应笔记之后,发现悬浮栏上的状态没有对应更新的。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

Newdea commented 3 months ago

不过,如果悬浮栏有显示,你点击记忆程度后的评分还是会对应到当前显示的笔记上的。

---原始邮件--- 发件人: @.> 发送时间: 2024年8月21日(周三) 晚上7:25 收件人: @*.**@*.>; 抄送: @*.**@*.***>; 主题: 回复:[open-spaced-repetition/obsidian-spaced-repetition-recall] [Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 (Issue #62)

这个真没办法🤣,dataview渲染的,已经不在插件控制范围内了

---原始邮件--- 发件人: "James @.> 发送时间: 2024年8月21日(周三) 中午1:02 收件人: @.>; 抄送: @.**@.>; 主题: Re: [open-spaced-repetition/obsidian-spaced-repetition-recall] [Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 (Issue #62)

谢谢,可以实现的。

请问笔记复习(note review)的悬浮栏是否可以更新为当前笔记的状态呢?

我激活笔记复习的悬浮栏之后,通过上述dataviewjs跳转到对应笔记之后,发现悬浮栏上的状态没有对应更新的。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

dryezl commented 3 months ago

不好意思,我没有表达清楚。 我目前流程是,点击obsidian右下角 (图片中标号1),激活悬浮栏(图片中标号2)。然后通过dataviewjs对笔记情况汇总,通过其链接跳转到目标笔记。 我的困惑是,我通过链接跳转到笔记之后,悬浮栏的内容没有更新的。 这个悬浮栏的内容,应该是当前插件控制的吧 1724240310030 另外有一个问题,如果一个笔记标记了多个标签(tag),请问它是有一个记忆状态还是每个标签有一个记忆状态呢?

Newdea commented 3 months ago

悬浮栏显示的内容是插件控的,但在你点击侧边栏或底栏或插件时,它才会更新显示的内容。而点击双链打开的笔记,不在插件的逻辑内,自然就没法更新内容了。

---原始邮件--- 发件人: "James @.> 发送时间: 2024年8月21日(周三) 晚上7:44 收件人: @.>; 抄送: @.**@.>; 主题: Re: [open-spaced-repetition/obsidian-spaced-repetition-recall] [Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 (Issue #62)

不好意思,我没有表达清楚。 我目前流程尝试流程是,点击obsidian右下角 (图片中标号1),激活悬浮栏(图片中标号2)。然后通过dataviewjs对笔记情况汇总,通过其链接跳转到目标笔记。 我的困惑是,我通过链接跳转到笔记之后,悬浮栏的内容没有更新的。 这个悬浮栏的内容,应该是当前插件控制的吧 1724240310030.jpg (view on web) 另外有一个问题,如果一个笔记标记了多个标签(tag),请问它是有一个记忆状态还是每个标签有一个记忆状态呢?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

dryezl commented 3 months ago

请问有没有可能让悬浮栏和当前打开笔记挂钩呢?即悬浮栏的信息体现为当前打开笔记(active-session)的信息的

Newdea commented 3 months ago

应该是可以的,但会有一点点降低Ob性能(这样实现后,会在每次打开笔记时都会进行判断操作)。 我会考虑下这个需求,看还有没别的更好的方法。

tangwells commented 2 months ago

在侧边栏上这样显示确实不方便,可以借用dataview直接在markdown视图中进行显示,你觉得如何?

不过,目前保存的数据中,只有一级标签,具体如何做,还要再考虑下。 我下版先把数据接口开放出来,也许通过dv-js的方式就能完全达成效果了。

大佬,对于他所提到的优先级问题,我在想插件是否增加一个基于标签顺序的推送机制。例如依据这个设置里填入标签的先后顺序:

image

在开启「直接复习笔记?」功能后,插件优先推送带有review标签的待复习笔记,然后再推送带有undone标签的笔记。

Newdea commented 2 months ago

直接复习是随机选一标签,然后直到该标签复习完。 你说的这个标签顺序,没有必要,可以关闭“直接复习”设置,在复习开始时根据需要选对应标签即可。

dryezl commented 2 months ago

另外有一个问题,如果一个笔记标记了多个标签(tag),请问它是有一个记忆状态还是每个标签有一个记忆状态呢?

请问目前插件是怎么设定这个呢?

Newdea commented 2 months ago

就是两个循环(笔记中出现的标签、设置中的标签)查找匹配,具体顺序记不清楚了,你试下就知道了。

---原始邮件--- 发件人: "James @.> 发送时间: 2024年9月10日(周二) 晚上9:12 收件人: @.>; 抄送: @.**@.>; 主题: Re: [open-spaced-repetition/obsidian-spaced-repetition-recall] [Share]: 与dataview插件联动-自定义数据显示-笔记(note)复习支持层级tags分类 (Issue #62)

另外有一个问题,如果一个笔记标记了多个标签(tag),请问它是有一个记忆状态还是每个标签有一个记忆状态呢?

请问目前插件是怎么设定这个呢?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

dryezl commented 1 month ago

const srplugin = app.plugins.plugins["obsidian-spaced-repetition-recall"]; 请问我在哪里可以看到 srplugin 这个对象的介绍呢?比如卡片数据存储在哪里的。我想查看单独笔记的卡片数量,但现在搞不懂怎么使用这个的。

Newdea commented 1 month ago

srplugin就是本插件的实例

可以加入调试代码, ctrl+shift+i在调试窗口中看到,然后展开摸索吧,如果不熟悉,建议只修改上面示例代码配置项部分

console.debug("sr插件实例:", srplugin);
dryezl commented 1 month ago

谢谢。我先试一下的。