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

本贴最后更新于 216 天前,其中的信息可能已经物是人非

因为突然想要写一个插件来帮我格式化一下普通的内容块,所以今天研究了一个晚上的插件开发和 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} 属性,不过这个方法不算好,也希望各位能帮我提出一些建议。
  • 思源笔记

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

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

    26455 引用 • 110036 回帖 • 1 关注
  • Q&A

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

    10185 引用 • 46282 回帖 • 65 关注

相关帖子

被采纳的回答
  • player 3

    "/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; }

欢迎来到这里!

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

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

    "/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; }
    3 回复
  • EmptyLight

    感谢!我去研究一下!

  • 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 更新了该回帖
  • 一般没必要使用/api/transactions 这个 api 吧,这个 api 的使用场景应该是那些需要事务的操作,比如多个操作保证原子性。

    image.png

    1 回复
  • Achuan-2 2 赞同 via Android

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

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

  • EmptyLight 1 评论

    /api/transactions 的参数是只有 HTML 还是同时兼容 markdown 呢

    只有 html
    player
  • 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; },
  • EmptyLight 1 赞同

    大佬,我在用 transactions 更新之后块内容显示[object Object],需要刷新才能显示内容是不是哪里没用对,还是事务执行本身需要刷新


    不需要了,我已经试出来了,是我传入的时候把获取到的 dom 整个传进入了,把里面的 html 取出来就可以了

    1 操作
    EmptyLight 在 2024-12-20 20:54:35 更新了该回帖
  • EmptyLight 1 评论

    大佬,抱歉打扰一下,我发现 /api/transactions 进行的 update 操作(见 Achuan-2 的回帖)突然就没有写入块 protyle 的 undo 属性,导致撤回操作不能生效。请问这个应该怎么排查问题,代码和实现思路基本上和代码块一致。还是说应该检查一下思源的 respond 来判断有没有成功更新?


    在 protyle 里面的 transactionsTimeundoupdated 这些变量都没有更新,还保持着更新前的状态。

    1 操作
    EmptyLight 在 2024-12-23 20:12:52 更新了该回帖
    undo 要自己加。不然就没有
    player
  • EmptyLight 1

    经过研究和 V 大在 issue 的回复,现在整理出要更新块使用的插件 API。

    // 代码在插件环境中使用,这里略过前面的处理,直接到提交事务部分 // 这部分代码本来不是一个方法,这里封装过了 // 仅需要更新一个块 function updateOneBlock(blockId: string, updatedDom: string, originalDom: string, protyle: Protyle["protyle"]) { // protyle是{detail}获取的detail.protyle,这里获取当前编辑器的实例 // blockId是{detail}获取的当前块的块id,仅适用于一个块 // updatedDom和originalDom是修改后的Dom和修改前的Dom,用于撤回操作 protyle.getInstance().updateTransaction(blockId, updatedDom, originalDom); // 操作后需要等待操作完成刷新界面,但是这部分获取有点问题,不知道什么时候事务完成 // 如果使用getIns().isuploading()获取,好像无法判断当前是否完成事务 // 这里刷新界面,建议自行设置延时,示例中不进行处理了 protyle.getInstance().reload(true); } // 需要更新多个块 function updateMultiBlock(doOperations: IOperation[], undoOperations: IOperation[], protyle: Protyle["protyle"]) { // 这里的protyle不变 protyle.getInstance().transaction(doOperations, undoOperations); // 这里用到的do和undo分别是自行封装的IOperation数组,其中的action是必须项,可用id标识修改块,data标识更新前后的dom // 可参考前面Achuan-2给出的/api/transactions代码,不过需要单独处理do和undo // 之后要不要刷新还没有进行测试,这里给出刷新代码 protyle.getInstance().reload(true); } // 封装doOperation和undoOperation的方法 // 这里只收一组数据,多组数据请自行处理 function setTrans(blockId: String, updatedDom: string, originalDom: string) { let doOperations: IOperations[] = []; let undoOperations: IOperations[] = []; doOperations.push({ action: "update", id: blockId, data: updatedDom }); undoOperations.push({ action: "update", id: blockId, data: originalDom }); let transaction = [doOperations, undoOperations]; return transaction; }

    这里给出的基本上都是思路,具体实现需要根据使用环境进行修改。

    撤回需要从 eventBus 获取当前的 protyle 实例,即 detail.protyle.getInstance(),用这个实例提交事务 updateTransaction 或者 updateBatchTransaction,或是直接使用 transaction 提交自己封装好的 do 和 undo 操作。

    在 do 和 undo 操作或者事务 updateTransaction 中需要提交修改前后的 dom,以此写入撤回栈,完成撤回操作。如果直接通过 /api/transactions 调用事务无法写入前端的撤回。

    如果不获取当前的 protyle 实例而直接使用 Protyle.prototype.updateTransaction 来提交事务,可以更新块但是无法写入撤回栈。

    调用后端 api 和使用插件 Protyle api 只能二选一,并且任何时候如果将要修改的块 id 传入到 blockId 属性会立即触发数据错误弹窗(退出 or 重建索引)。而传入 id 属性不会。

    对于非更新操作也可类推。

请输入回帖内容 ...
EmptyLight
就地开摆,有事邮件,爱发电:https://afdian.com/a/tingyuting

推荐标签 标签

  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 233 回帖 • 1 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    93 引用 • 122 回帖 • 614 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    151 引用 • 257 回帖 • 2 关注
  • AWS
    11 引用 • 28 回帖 • 7 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 223 关注
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    20 引用 • 245 回帖 • 233 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖 • 2 关注
  • Sandbox

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

    441 引用 • 1238 回帖 • 600 关注
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 271 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    36 引用 • 200 回帖 • 40 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 41 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 649 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 77 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    7 引用 • 28 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • 反馈

    Communication channel for makers and users.

    120 引用 • 906 回帖 • 281 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 354 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 2 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    497 引用 • 934 回帖
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 1 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖 • 1 关注
  • CloudFoundry

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

    4 引用 • 16 回帖 • 198 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    173 引用 • 414 回帖 • 362 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 518 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    284 引用 • 248 回帖
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖