API 获取并处理块的一些疑问

因为突然想要写一个插件来帮我格式化一下普通的内容块,所以今天研究了一个晚上的插件开发和 api,现在发现内容块 API 有点问题不好理解,希望各位能帮我解答一二。

  1. 在更新块 API 里支持两种模式:markdownDOM。不过这两种元素我感觉都不是很好获取。
    1. 使用 api 能获取的是 kramdown,这个会带有 {: updated id} 属性,要是用 markdown 再发回去就会当成普通的文字放进去。
    2. DOM 也不算好获取,从块标里面的插件按钮获取到的结果是不带 HTML 的,没法获取当前元素的 html。这样难道只能用 ts 去页面里获取吗?
  2. 获取了 kramdown 之后,如果要更新块一定要去掉 {: updated id} 这个属性吗,有没有可能不处理这个属性也不影响文本显示的办法?
  3. 有可能通过 api 获取到当前块的 dom?或者获取到当前块的类型?
  4. 还在继续思考中...感谢各位的帮助。如果想要先行了解代码可以先在这里了解最新的进度。目前通过 ai 暂时解决了 {: updated id} 属性,不过这个方法不算好,也希望各位能帮我提出一些建议。
  • 思源笔记

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

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

    23064 引用 • 92807 回帖 • 1 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    8471 引用 • 38574 回帖 • 153 关注
被采纳的回答
  • "/api/block/insertBlock" "/api/block/updateBlock" 可以传入 kramdown。 内容与属性需要用 '\n' 来隔开。

    id updated 可以不用去掉。 如果你要插入新的块,需要生成新的 id 避免重复。

    "/api/block/getBlockDOM" 可以按块 id 读取 dom.

    "/api/transactions" 可以按 dom 来更新或者插入等。

    另外, dom 与 markdown 的转换可以用:
    export const NewLute: () => Lute = (globalThis as any).Lute.New;

    lute = NewLute();

    const md = lute.BlockDOM2Md(div.outerHTML);

    export class Lute {
        public static WalkStop: number;
        public static WalkSkipChildren: number;
        public static WalkContinue: number;
        public static Version: string;
        public static Caret: string;
    
        public static New(): Lute;
    
        public static EChartsMindmapStr(text: string): string;
    
        public static NewNodeID(): string;
    
        public static Sanitize(html: string): string;
    
        public static EscapeHTMLStr(str: string): string;
    
        public static UnEscapeHTMLStr(str: string): string;
    
        public static GetHeadingID(node: ILuteNode): string;
    
        public static BlockDOM2Content(html: string): string;
    
        private constructor();
    
        public BlockDOM2Content(text: string): string;
    
        public BlockDOM2EscapeMarkerContent(text: string): string;
    
        public SetSpin(enable: boolean): void;
    
        public SetTextMark(enable: boolean): void;
    
        public SetHTMLTag2TextMark(enable: boolean): void;
    
        public SetHeadingID(enable: boolean): void;
    
        public SetProtyleMarkNetImg(enable: boolean): void;
    
        public SetSpellcheck(enable: boolean): void;
    
        public SetFileAnnotationRef(enable: boolean): void;
    
        public SetSetext(enable: boolean): void;
    
        public SetYamlFrontMatter(enable: boolean): void;
    
        public SetChineseParagraphBeginningSpace(enable: boolean): void;
    
        public SetRenderListStyle(enable: boolean): void;
    
        public SetImgPathAllowSpace(enable: boolean): void;
    
        public SetKramdownIAL(enable: boolean): void;
    
        public BlockDOM2Md(html: string): string;
    
        public BlockDOM2StdMd(html: string): string;
    
        public SetSuperBlock(enable: boolean): void;
    
        public SetTag(enable: boolean): void;
    
        public SetInlineMath(enable: boolean): void;
    
        public SetGFMStrikethrough(enable: boolean): void;
    
        public SetGFMStrikethrough1(enable: boolean): void;
    
        public SetMark(enable: boolean): void;
    
        public SetSub(enable: boolean): void;
    
        public SetSup(enable: boolean): void;
    
        public SetInlineAsterisk(enable: boolean): void;
    
        public SetInlineUnderscore(enable: boolean): void;
    
        public SetBlockRef(enable: boolean): void;
    
        public SetSanitize(enable: boolean): void;
    
        public SetHeadingAnchor(enable: boolean): void;
    
        public SetImageLazyLoading(imagePath: string): void;
    
        public SetInlineMathAllowDigitAfterOpenMarker(enable: boolean): void;
    
        public SetToC(enable: boolean): void;
    
        public SetIndentCodeBlock(enable: boolean): void;
    
        public SetParagraphBeginningSpace(enable: boolean): void;
    
        public SetFootnotes(enable: boolean): void;
    
        public SetLinkRef(enable: boolean): void;
    
        public SetEmojiSite(emojiSite: string): void;
    
        public PutEmojis(emojis: IObject): void;
    
        public SpinBlockDOM(html: string): string;
    
        public Md2BlockDOM(html: string): string;
    
        public SetProtyleWYSIWYG(wysiwyg: boolean): void;
    
        public MarkdownStr(name: string, md: string): string;
    
        public GetLinkDest(text: string): string;
    
        public BlockDOM2InlineBlockDOM(html: string): string;
    
        public BlockDOM2HTML(html: string): string;
    
        public HTML2Md(html: string): string;
    
        public HTML2BlockDOM(html: string): string;
    }
    

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • Achuan-2 2 赞同

    修改 dom 的时需要用到,直接调用 api 更新 block,速度有点慢,而且调用 api 是不能撤回的

    我之前写插件已经很多次都需要了,比如删除修改行内元素,修改脚注编号,删除链接删除块引,删除空格等场景

  • 其他回帖
  • player 2 2 评论

    补充几个我常用的工具,用于创建一系列 IOperation, 传给 "/api/transactions" 即可。 批量的 IOperation 形成一个事务。

      transBatchUpdateAttrs(blockAttrs: { id: string, old: AttrType, new: AttrType }[]) {
            return blockAttrs.map(b => {
                const op = {} as IOperation;
                op.action = "updateAttrs";
                op.id = b.id;
                op.data = JSON.stringify({ old: b.old, new: b.new });
                return op;
            });
        },
    
      transUpdateBlocks(ops: { id: string, domStr: string }[]) {
            ops = ops.filter(op => !!op.id);
            if (!(ops.length > 0)) return [];
            return ops.map(({ id, domStr }) => {
                const op = {} as IOperation;
                op.action = "update"; // dom
                op.id = id;
                op.data = domStr;
                return op;
            });
        },
    
      transDeleteBlocks(ids: string[]) {
            return ids?.map(id => {
                const op = {} as IOperation;
                op.action = "delete";
                op.id = id;
                return op;
            }) ?? [];
        },
    
      transMoveBlocksAfter(ids: string[], previousID: string) {
            return ids.slice().reverse().map(id => {
                const op = {} as IOperation;
                op.action = "move";
                op.id = id;
                op.previousID = previousID;
                return op;
            });
        },
    
        transMoveBlocksAsChild(ids: string[], parentID: string) {
            return ids.slice().reverse().map(id => {
                const op = {} as IOperation;
                op.action = "move";
                op.id = id;
                op.parentID = parentID;
                return op;
            });
        },
    
    1 回复
    感谢大佬
    Achuan-2
    感谢大佬
    EmptyLight
  • 数据库的,有些可能不通用。

     transSetAttrViewColWrap(avID: string, blockID: string, colID: string, wrap = true) {
            const op = {} as IOperation;
            op.action = "setAttrViewColWrap";
            op.avID = avID;
            op.blockID = blockID;
            op.id = colID;
            op.data = wrap;
            return op;
        },
        transSetAttrViewColHidden(avID: string, blockID: string, colID: string, hide = true) {
            const op = {} as IOperation;
            op.action = "setAttrViewColHidden";
            op.avID = avID;
            op.blockID = blockID;
            op.id = colID;
            op.data = hide
            return op;
        },
        transSetAttrViewSorts(avID: string, blockID: string, colID: string, order: "ASC" | "DESC") {
            const op = {} as IOperation;
            op.action = "setAttrViewSorts";
            op.avID = avID;
            op.blockID = blockID;
            op.data = [{ column: colID, order }]
            return op;
        },
        transSetAttrViewColCalc(avID: string, blockID: string, colID: string, operator: CalcOperator) {
            const op = {} as IOperation;
            op.action = "setAttrViewColCalc";
            op.avID = avID;
            op.data = { operator };
            op.blockID = blockID;
            op.id = colID;
            return op;
        },
        transSetAttrViewFilters(avID: string, blockID: string, filters: IAVFilter[]) {
            const op = {} as IOperation;
            op.action = "setAttrViewFilters";
            op.avID = avID;
            op.data = filters;
            op.blockID = blockID;
            return op;
        },
        transSetAttrViewViewName(avID: string, viewID: string, name: string) {
            const op = {} as IOperation;
            op.action = "setAttrViewViewName";
            op.avID = avID;
            op.id = viewID;
            op.data = name;
            return op;
        },
        transUpdateAttrViewCol(avID: string, colId: string, name: string, type?: TAVCol) {
            const op = {} as IOperation;
            op.action = "updateAttrViewCol";
            op.id = colId;
            op.avID = avID;
            op.name = name;
            op.type = type;
            return op;
        },
        transSetAttrViewName(avID: string, name: string) {
            const op = {} as IOperation;
            op.action = "setAttrViewName";
            op.id = avID;
            op.data = name;
            return op;
        },
        transRemoveAttrViewCol(avID: string, colID: string) {
            const op = {} as IOperation;
            op.action = "removeAttrViewCol";
            op.avID = avID;
            op.id = colID;
            return op;
        },
        transAddAttrViewCol(avID: string, name: string, id = NewNodeID(), type: TAVCol = "text", previousID = "") {
            const op = {} as IOperation;
            op.action = "addAttrViewCol";
            op.avID = avID;
            op.name = name;
            op.previousID = previousID;
            op.type = type;
            op.id = id;
            return op;
        },
        transUpdateAttrViewCellBatch(args: { avID: string, cellID?: string, colID: string, rowID_BlockID: string, value: IAVCellValue }[]) {
            return args.map(arg => {
                const op = {} as IOperation;
                op.action = "updateAttrViewCell";
                op.avID = arg.avID;
                op.id = arg.cellID;
                op.keyID = arg.colID;
                op.rowID = arg.rowID_BlockID;
                op.data = arg.value;
                return op;
            })
        },
        transInsertAttrViewBlock(avID: string, blockID: string, srcs: IOperationSrcs[], previousID = "", ignoreFillFilter = true) {
            const op = {} as IOperation;
            op.action = "insertAttrViewBlock";
            op.avID = avID;
            op.blockID = blockID;
            op.previousID = previousID;
            op.ignoreFillFilter = ignoreFillFilter;
            op.srcs = srcs;
            return op;
        },
        transDoUpdateUpdated(blockID: string, data = timeUtil.getYYYYMMDDHHmmss()) {
            const op = {} as IOperation;
            op.action = "doUpdateUpdated";
            op.id = blockID
            op.data = data
            return op;
        },
    
  • Achuan-2 1

    想问问大佬,/api/transactions 的传入值有没有示例呢,data 值是传入 id 和操作 update 就可以了吗


    摸索了下,这样就能把 html 修改的内容保存了

    // 1. 获取目标元素
    const targetElement = document.querySelector('div[data-node-id="20241205223647-qaxnubd"]');
    
    // 2. 获取 HTML 内容 (并进行一些基本的错误处理)
    let originalHtmlContent = ""; // 用于保存原始 HTML 内容
    let originalNowTime; // 用于保存原始时间
    
    if (targetElement) {
        originalHtmlContent = targetElement.outerHTML;
    } else {
        console.error("未找到指定 data-node-id 的元素!");
        // 可以选择在这里返回或者执行其他错误处理逻辑
        // return; 
    }
    
    // 修改HTML
    newHtmlContent = targetElement.outerHTML;
    
    // 3. 更新
    let nowTime = new Date().getTime(); // 更新后的时间
    const res = await fetchSyncPost("/api/transactions", {
        session: siyuan.ws.app.appId,
        app: siyuan.ws.app.appId,
        reqId: nowTime,
        transactions: [{
            "doOperations": [{
                action: "update",
                id: '20241205223647-qaxnubd',
                data: newHtmlContent, // 更新后的 HTML 内容 (如果需要更新)
            }],
            "undoOperations": [{
                action: "update",
                id: '20241205223647-qaxnubd',
                data: originalHtmlContent, // 使用原始 HTML 内容
            }]
        }]
    });
    console.log(res);
    
    
    3 回复
    1 操作
    Achuan-2 在 2024-12-18 07:22:50 更新了该回帖
  • 查看全部回帖

推荐标签 标签

  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 7 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 440 关注
  • 微信

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

    132 引用 • 795 回帖
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 101 关注
  • 爬虫

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

    106 引用 • 275 回帖
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 518 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 613 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 2 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 655 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    545 引用 • 672 回帖
  • B3log

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

    1063 引用 • 3454 回帖 • 191 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    156 引用 • 3793 回帖
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    171 引用 • 512 回帖
  • OnlyOffice
    4 引用 • 2 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 173 关注
  • 域名

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

    43 引用 • 208 回帖
  • Windows

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

    223 引用 • 474 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 548 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 252 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • Sandbox

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

    410 引用 • 1246 回帖 • 587 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 14 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    186 引用 • 318 回帖 • 282 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖 • 1 关注