8.1 层功力 remnote 制卡,支持内置卡包制卡和移除

本贴最后更新于 595 天前,其中的信息可能已经时过境迁

2023.4.5 这些代码片段还能用,但假如不是你不愿意使用插件系统、或插件系统不能正常运行的话,我还是建议是使用插件版本的闪卡增强插件 v0.0.1 发布 基于社区插件系统 - 链滴


前情提要:8 层功力的 remnote 制卡 并多多一键制卡更新,支持列表项制卡,增加一键移除

在尝试了 v2.7.7 最新版本的文档树制卡后,我不得不真香了,我必须承认:

从文档树打开闪卡界面真是太酷了,很符合我对思源未来的想象,科技并带着趣味。

故紧急更新了一版,支持直接制卡到文档树的内置卡包。

不过,制卡到文档树的内置卡包和制卡到之前的自定义卡包是一个二选一的选项,因为这会造成重复制卡,也就是说一张卡片你会在复习的时候重复见两遍 :(

所以暂时只能做成开关了。默认为之前的自定义卡包制卡方式,想要使用新版本的文档树内置卡包,那就划到代码的最下方,把 false 改成 true 即可。

// 一键制卡 v0.7
// 使用匿名函数,防止污染空间
// 支持使用内建卡片
((useBulitIn = false) => {
    // 请求函数
    function request(url, data = null, method = "POST") {
        return new Promise((resolve, reject) => {
            if (method.toUpperCase() == "POST") {
                fetch(url, {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(data),
                })
                    .then(
                        (data) => resolve(data.json()),
                        (error) => {
                            reject(error);
                        }
                    )
                    .catch((err) => {
                        console.error("请求失败:", err);
                    });
            }
        });
    }

    // 弹出提示信息
    async function showMessage(msg) {
        await request("/api/notification/pushMsg", { msg, timeout: 3000 });
    }

    // 获取当前文档id
    function getFileID() {
        //获取当前页面
        const currentPage = getCurrentPage();
        //获取当前页面id
        const currentPageID = currentPage.querySelector(
            "span.protyle-breadcrumb__item--active"
        ).getAttribute("data-node-id");

        return currentPageID;
    }

    function getCurrentPage() {
        var currentScreen;
        var currentPage;
        try {
            //获取当前屏幕
            currentScreen = document.querySelector(".layout__wnd--active");
            //获取当前页面
            currentPage = currentScreen.querySelector(
                ".fn__flex-1.protyle:not(.fn__none)"
            );
            return currentPage;
        }
        catch (e) {
            showMessage(`未能获取到页面焦点!`)
        }
        throw new Error("未能获取到页面焦点!");
    }

    //传入 node节点列表 arr和数据抽取函数 fliter。
    // fliter 传入一个节点,返回一个字典
    // 字典格式:
    // {status,data}
    // status 为 ok,则代表为抽取成功,将data压入返回列表anwer,并最后返回。
    function iterArr(arr, fliter) {

        if (!arr && arr.length <= 0) {
            return [];
        }

        length = arr.length;
        anwer = [];
        for (let node of arr) {
            data = fliter(node);
            if (data["status"] === "ok" && data["data"]) {
                anwer.push(data["data"]);
            }
        }
        return anwer;
    }

    // 传入列表下的段落,返回被标记段落的父节点:列表项的id
    function fliterListCard(node) {
        var re = /(>>|(》》))\s?\u200b?$/;
        if (re.test(node.innerText)) {
            return {
                status: "ok",
                data: node.parentElement.getAttribute("data-node-id")
            }
        }
        return { status: "no", data: null };
    }

    function fliterMarkCard(node) {
        return {
            status: "ok",
            data: node.parentElement.parentElement.getAttribute("data-node-id")
        }
    }

    function fliterSuperBlockCard(node) {
        return {
            status: "ok",
            data: node.getAttribute("data-node-id")
        }
    }

    // 获取当前文档路径
    async function getHpath() {
        currentPageID = getFileID()
        body = { "id": currentPageID }
        let res = await request("/api/filetree/getHPathByID", body);

        //检测返回值
        if (res.code === 0) {
            let currentPagePath = res.data;
            return currentPagePath;
        }
        return null;
    }

    // 添加多个卡片
    async function addCards() {
        //获取全部deck
        let res = await request("/api/riff/getRiffDecks", {});

        //获取当前打开文档的路径
        focusItemPath = await getHpath()
        if (focusItemPath === null) {
            showMessage(`未能当前文档读取路径`);
            return;
        }


        let customDeckIdArr = [];
        // 导入默认卡包
        if (useBulitIn === true) {
            customDeckIdArr.push('20230218211946-2kw8jgx');
        }
        else {

            //获取deck.id, 其deck.name 包含在 focusItemPath 中
            Array.from(res.data).forEach((item) => {
                let name = item.name;
                if (focusItemPath.search(name) != -1) {
                    customDeckIdArr.push(item.id);
                }
            })
        }



        if (customDeckIdArr.length === 0) {
            showMessage(`未能获取到:${focusItemPath} 的卡包`);
            return;
        }

        // 获取需要被制成卡片的块的ID
        const currentPage = getCurrentPage()
        //标记块
        const markList = currentPage.querySelectorAll("span[data-type='mark']")
        //超级块
        const superBlockList = currentPage.querySelectorAll("div[class='sb']")
        //列表块
        const listList = currentPage.querySelectorAll("div[class='li']:has(div[class='li'])  > div[class = 'p']")

        let arr = [];
        var markCardList = iterArr(markList, fliterMarkCard)
        var superBlockCardList = iterArr(superBlockList, fliterSuperBlockCard)
        var listCardList = iterArr(listList, fliterListCard)
        arr = arr.concat(markCardList, superBlockCardList, listCardList)


        if (arr.length === 0) {
            showMessage(`未能获取到:${focusItemPath} 页面的卡片`);
            return;
        }

        // 去重
        arr = [... new Set(arr)];

        for (deckIndex in customDeckIdArr) {

            // 把标记所在块全部制成卡片
            let body = {
                deckID: customDeckIdArr[deckIndex],
                blockIDs: arr,
            };
            let res = await request("/api/riff/addRiffCards", body);
            if (res.code === 0) {
                showMessage(`${res.data.name}卡包的总卡片数:${res.data.size}`);
            }
        }

    }

    // 添加一个按钮
    const barMode = document.getElementById("barMode");
    barMode.insertAdjacentHTML(
        "beforebegin",
        '<div id="addCards" class="toolbar__item b3-tooltips b3-tooltips__se" aria-label="制作卡片" ></div>'
    );
    const addCardsBtn = document.getElementById("addCards");
    addCardsBtn.style.width = "auto";
    const addCardsBtnIcon = `<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4526" width="200" height="200"><path d="M928 160H96a32 32 0 0 0-32 32v672a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32V192a32 32 0 0 0-32-32z m-32 672H128V224h768v608z" p-id="4527" fill="#9aa0a6"></path><path d="M230.592 448.096H544a32 32 0 1 0 0-64H230.592a32 32 0 0 0 0 64zM230.592 640.096H544a32 32 0 1 0 0-64H230.592a32 32 0 1 0 0 64zM768 704a32 32 0 0 0 32-32V350.016a32 32 0 1 0-64 0V672a32 32 0 0 0 32 32z" p-id="4528" fill="#9aa0a6"></path></svg>`;
    addCardsBtn.innerHTML = addCardsBtnIcon;
    addCardsBtn.addEventListener(
        "click",
        (e) => {
            addCards();
            e.stopPropagation();
            e.preventDefault();
        },
        true
    );

})(false);
// 一键移除卡片 v0.2
// 使用匿名函数,防止污染空间
(() => {
    // 请求函数
    function request(url, data = null, method = "POST") {
        return new Promise((resolve, reject) => {
            if (method.toUpperCase() == "POST") {
                fetch(url, {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(data),
                })
                    .then(
                        (data) => resolve(data.json()),
                        (error) => {
                            reject(error);
                        }
                    )
                    .catch((err) => {
                        console.error("请求失败:", err);
                    });
            }
        });
    }

    // 弹出提示信息
    async function showMessage(msg) {
        await request("/api/notification/pushMsg", { msg, timeout: 3000 });
    }


    function getCurrentPage() {
        var currentScreen;
        var currentPage;
        try {
            //获取当前屏幕
            currentScreen = document.querySelector(".layout__wnd--active");
            //获取当前页面
            currentPage = currentScreen.querySelector(
                ".fn__flex-1.protyle:not(.fn__none)"
            );
            return currentPage;
        }
        catch (e) {
            showMessage(`未能获取到页面焦点!`)
        }
        throw new Error("未能获取到页面焦点!");
    }

    //传入 node节点列表 arr和数据抽取函数 fliter。
    // fliter 传入一个节点,返回一个字典
    // 字典格式:
    // {status,data}
    // status 为 ok,则代表为抽取成功,将data压入返回列表anwer,并最后返回。
    function iterArr(arr, fliter) {

        if (!arr && arr.length <= 0) {
            return [];
        }

        length = arr.length;
        anwer = [];
        for (let node of arr) {
            data = fliter(node);
            if (data["status"] === "ok" && data["data"]) {
                anwer.push(data["data"]);
            }
        }
        return anwer;
    }

    function fliterBlockCard(node) {
        return {
            status: "ok",
            data: node.getAttribute("data-node-id")
        }
    }

    async function removeCards() {


        // 获取需要被制成卡片的块的ID
        const currentPage = getCurrentPage()
        //已制卡块
        const cardedBlockList = currentPage.querySelectorAll(".protyle-wysiwyg [data-node-id][custom-riff-decks]")


        let arr = [];
        var cardedBlockIdList = iterArr(cardedBlockList, fliterBlockCard)

        arr = arr.concat(cardedBlockIdList)


        if (arr.length === 0) {
            showMessage(`未能获取到页面的卡片`);
            return;
        }

        // 去重
        arr = [... new Set(arr)];


        let body = {
            deckID: '',
            blockIDs: arr,
        };
        let res = await request("/api/riff/removeRiffCards", body);
        if (res.code === 0) {
            showMessage(`All卡包的移除卡片数:${arr.length}`);
        }

    }

    const barMode = document.getElementById("barMode");
    barMode.insertAdjacentHTML(
        "beforebegin",
        '<div id="removeCards" class="toolbar__item b3-tooltips b3-tooltips__se" aria-label="移除卡片" ></div>'
    );
    const removeCardsBtn = document.getElementById("removeCards");
    removeCardsBtn.style.width = "auto";
    const removeCardsBtnIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M600 826v-60h145v60H600Zm0-368v-60h280v60H600Zm0 184v-60h235v60H600ZM125 381H80v-60h170v-45h135v45h170v60h-45v415q0 24-18 42t-42 18H185q-24 0-42-18t-18-42V381Zm60 0v415h265V381H185Zm0 0v415-415Z"/></svg>`;
    removeCardsBtn.innerHTML = removeCardsBtnIcon;
    removeCardsBtn.addEventListener(
        "click",
        (e) => {
            removeCards();
            e.stopPropagation();
            e.preventDefault();
        },
        true
    );

})();

如果喜欢,请给我留言吧。

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22357 引用 • 89473 回帖 • 1 关注
1 操作
zxhd86 在 2023-04-07 22:25:46 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • wz169645

    支持,很棒

  • 其他回帖
  • renky

    所以现在的代码是还暂时不可以列表项制卡吗?

    2 回复
  • 想问请教一下大佬,现在一键移除这么方便,也紧紧挨着一键制卡的。

    如果去点一键制卡的时候不小心点到了一键移除,怎么撤销呢?

    是只能用历史数据回滚么,还是有撤销的快捷键呢?

    如果有撤销的快捷键麻烦大佬告知一下呀,谢谢。

    如果没有,希望下一版的一键移除可以改进。(方式有:增加撤销键 或 增加一键移除的难度,比如多一步确认才能一键移除)

  • zxhd86

    @gladi 我不太理解这个意思,不过现在确实是支持列表项制卡的。

    有什么改进的想法也可以提一下,虽然我暂时想不到什么能改进而且我能实现的。

    比如我有一个想法是动态跟踪用户的标记,不用手动点击,但是想不到有什么比较好的办法追踪,如果定期执行感觉又太浪费性能,而且可能不符合用户的想法。

    1 回复
  • 查看全部回帖

推荐标签 标签

  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    209 引用 • 2031 回帖
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 764 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 733 关注
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 699 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 204 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    77 引用 • 390 回帖
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    130 引用 • 793 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    286 引用 • 729 回帖
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 391 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 633 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 327 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    107 引用 • 295 回帖
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    407 引用 • 1246 回帖 • 582 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖
  • 反馈

    Communication channel for makers and users.

    123 引用 • 911 回帖 • 245 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖 • 1 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    15 引用 • 122 回帖 • 1 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 334 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖 • 1 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 99 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    222 引用 • 473 回帖
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 40 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    93 引用 • 113 回帖