[js] 选中部分文本复制出 Markdown 格式

如果官方考虑能给个选项就更好了 😄 ,我这个实现还是有点拉的

效果图

screenshot20250203131812.gif

修改前是这个样子的:

选择部分文字时无法正确复制公式.gif

同时不影响原有块复制实现(块内直接按 Ctrl+C)(注意到控制台中输出了 Not Edit Block):

不影响原有块复制实现.gif

实现基本思路

前端不太懂,俺结合论坛内其他大佬的实现、还有 Deepseek 的帮助下,搓出来的。一并表示感谢。

观察到复制出的文本包含两种类型,一种是 text/plain,即纯文本结果;另一种是 text/html,即包含格式的富文本结果。所以总思路是将 text/html 转换为 Markdown。

screenshot20250203133053.png

具体实现方式如下:

  1. 判断是否是选中部分文本。考虑到对整个块进行复制时是可以正常复制出 Markdown 文本的,为了不干扰选中整个块的情况,需判断是否选中部分文本。实现相对鸡贼,是通过判断 Toolbar 是否冒出来判断的;
  2. 从原始复制结果中读出来 text/html 内容,并借助 Protyle 提供的助手函数将 html 变回 Markdown
  3. 将 Markdown 结果写入剪切板。为保证在编辑器内复制效果不受影响,先读取了原本的 text/html,然后将读上来的 text/html 和 Markdown 结果一起写入剪切板。

已知问题

受限于“判断是否选中部分文本”的实现方式,在选中文本后,需直接按下Ctrl+C进行复制,不可进行其他点击操作;这意味着通过右键菜单进行复制时,本脚本无法生效。

脚本内容

!(function () { // 获取Protyle对象,以使用其中的BlockDOM2StdMd助手函数 function getProtyle() { try { if (document.getElementById("sidebar")) return siyuan.mobile.editor.protyle; const currDoc = siyuan?.layout?.centerLayout?.children.map(item => item.children.find(item => item.headElement?.classList.contains('item--focus') && (item.panelElement.closest('.layout__wnd--active') || item.panelElement.closest('[data-type="wnd"]')))).find(item => item); return currDoc?.model.editor.protyle; } catch (e) { console.error(e); return null; } }; // 判断是否选中部分文本 function isNotEditBlock() { const p = getProtyle(); // 甜菜,看toolbar是否出来,判断是否选中部分文本 if(p.toolbar.element.className.includes('fn__none')) return true; return false; }; let toolbar_show = false; document.addEventListener( "mouseup", async (event) =>{ setTimeout(async () => { if(isNotEditBlock()){ toolbar_show = false; }else{ toolbar_show = true; } }, 20) }); document.addEventListener( "copy", async (event) => { setTimeout(async () => { try { // 判断是不是选中部分文本,如果是的话才处理 if(toolbar_show == false){ console.info("Not Edit ablock"); }else{ let mddata = ''; let clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types) { if(type == "text/html"){ // 读取剪切板中的text/html数据,然后借助助手函数把html转换为md const blob = await clipboardItem.getType(type); // 创建FileReader对象,以做到用文本方式读取blob对象 const reader = new FileReader(); reader.readAsText(blob, 'utf-8'); reader.onload = function(e) { const htmlString = reader.result; // 读取后的结果 // console.log('htmlString: ' + htmlString); // 因为读出来的 text/html 可能包含多个并列的 p 标签,需要遍历处理 const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); const pTags = doc.querySelectorAll('p'); // 获取所有 p 标签 const p = getProtyle(); pTags.forEach((pTag, index) => { // console.log(`pTag[${index}].innerHTML: ` + pTag.innerHTML); // 如果需要在每个处理结果之间加上换行符,可以在后面拼接 "\n" mddata += p.lute.HTML2Md(pTag.innerHTML).trimEnd() + "\n"; }); }; } } } // 仅修改text/plain,text/html不动 clipboardItems = await navigator.clipboard.read(); const newItems = []; for (const item of clipboardItems) { const types = item.types; const newData = {}; for (const type of types) { // 其他类型直接保留原数据 newData[type] = await item.getType(type); } newData['text/plain'] = new Blob([mddata], { type: 'text/plain' }); newItems.push(new ClipboardItem(newData)); } // 将修改后的内容写回剪贴板 await navigator.clipboard.write(newItems); } } catch (err) { console.error("读取剪贴板内容失败:", err); } }, 100); }, { passive: false, capture: true } ); })();

参考了如下工作,感谢各位大佬的分享:

  1. [js] 复制行级代码或图片时, 替换原始复制 - 链滴 (ld246.com)
  2. [js] 思源复制内容添加块链接 - 链滴 (ld246.com)
  3. [js] 快速改样式 ----// 利用 protyle.toolbar.setInlineMark 进行块更新后保存 - 链滴 (ld246.com),其中选出来 Protyle 的函数很有用。
  • 思源笔记

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

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

    25454 引用 • 105290 回帖
2 操作
jielahou 在 2025-02-03 13:41:29 更新了该帖
jielahou 在 2025-02-03 13:38:41 更新了该帖

相关帖子

欢迎来到这里!

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

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