【QuickAdd脚本】带图复制-自动上传图片到图床

需求分析

有时候我们想要分享自己的笔记到网络上。但 Obsidian 笔记本身存在 WIKI 链接语法以及图片保存在本地,不太方便转换。希望有一种方法可以将包含本地图片的 Markdown 文章一键转换为在知乎、CSDN 等网站上能够正确显示图片的形式,以便分享更加便捷,内容展示更完整。

实现方法

最好的办法就是将图片上传到图床,但又不想改变笔记原来的结构,只想把图片保留到本地,因此该脚本的作用就是自动检测复制文本中存在的本地图片链接,将图片上传到图床替换为在线 Markdown 图片格式。

  1. 检测本地图片链接
  2. 图片上传至图床
    1. PS: 上传的是PicGo的接口,图床设置请自行了解
  3. 替换 Markdown 文章中图片链接
  4. 发送到剪切板并提示

PS: 本方法灵感来源于 Ob 群友新奥尔良烤乳猪简哲之间的讨论。

实现效果

PixPin_2024-05-07_13-13-27

QuickAdd Macro脚本源码
const path = require('path');
const quickAddApi = app.plugins.plugins.quickadd.api;
const { editor, file, containerEl } = app.workspace.activeEditor;
const url = "http://127.0.0.1:36677/upload";

module.exports = async () => {
  const files = app.vault.getFiles();
  let selection = "";
  let content = "";
  selection = editor.getSelection();
  console.log(selection);
  for (let line of selection.split("\n")) {
    let embed = "";
    if (line) {
      embed = matchSelectionEmbed(line);
    }
    console.log(embed);
    if (embed && /\.(png|jpg|jpeg|gif|bmp)$/.test(embed)) {
      let wikiPath = getFilePath(files, embed); // 匹配Wiki链接
      // 获取绝对路径
      const imgPath = app.vault.adapter.getFullPath(wikiPath);
      console.log(imgPath);
      const data = await uploadFiles([imgPath], url);
      if (data.success) {
        const imgWiki = `[[${embed}]]`;
        const imgLink = `[${embed}](${data.result})`;
        line = line.replace(imgWiki, imgLink);
      } else {
        new Notice(`❌上传${imgPath.name}图片失败`);
      }
    }
    content += line + "\n";
  }
  console.log(content);
  copyToClipboard(content)
  new Notice(`✅复制成功`);
};


// 获取文件路径函数
function getFilePath(files, baseName) {
  let files2 = files.filter(f => path.basename(f.path).replace(".md", "") === baseName.replace(".md", ""));
  let filePath = files2.map((f) => f.path);
  return filePath[0];
}

function matchSelectionEmbed(text) {
  const regex = /\[\[?([^\]]*?)(\|.*)?\]\]?\(?([^)\n]*)\)?/;
  const matches = text.match(regex);
  if (!matches) return;
  if (matches[3]) return decodeURIComponent(matches[3]);
  if (matches[1]) return decodeURIComponent(matches[1]);
}

async function uploadFiles(imagePathList, url) {
  const response = await requestUrl({
    url: url,
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ list: imagePathList }),
  });
  data = await response.json;
  return data;
};

function copyToClipboard(extrTexts) {
  const txtArea = document.createElement('textarea');
  txtArea.value = extrTexts;
  document.body.appendChild(txtArea);
  txtArea.select();
  if (document.execCommand('copy')) {
      console.log('copy to clipboard.');
  }else{
      console.log('fail to copy.');
  }
  document.body.removeChild(txtArea);
}

不足之处:

  • 相同文档多次复制会重复上传已有图片
  • 如果一行多个图片正则匹配不到,还请手动上传
1 个赞

直接的Markdown可能不能直接转到知乎,微信等平台,目前还不支持自动识别Markdown语法,可以借助墨滴来辅助转换一下哈,这样各个平台都比较好转发,以及知乎、微信之类的会自动识别图片链接上传到它们自己的图床上。

我觉得只是偶尔笔记需要发布的话,可以用 copy document as html
复制到微信公众号后台,和csdn的富文本编辑器都行
知乎不行(知乎的话其实用vscode的zhihu插件最方便)
应该是支持富文本编辑器的都还好,直接粘贴html就会转换

很多笔记要发布的话,还是找找更方便的办法

1 个赞

哦哦 学到了 感谢感谢

这个需求是不是用Image Upload Toolkit插进就可以实现了?

当然,有两点值得注意:

  1. Toolkit中需要关闭下面这个选项
    image
  2. Toolkit支持的图床服务商太少了,国内能用的只有阿里云

哦哦 我之前没接触过这个插件,看了下功能,它是上传当前文档,而不是选中文本,也是可以实现的。脚本主要依赖PicGo、PicList等图床工具,这个插件是自带的,反正都可以的,感谢分享。

可以求一下更详细的设置吗?跟着尝试了,但是没有奏效

如果还是看不懂,论坛里也有相关介绍的: QuickAdd JS & Templater JS 简介及相互修改 - 经验分享 - Obsidian 中文论坛

太感谢了。已经搞定!

试用的时候发现一个问题:

如果内部链接类型设置为基于当前笔记的相对路径,脚本会报错
QuickAdd: (ERROR) failed to run user script 带图 复制.Error: The “path" argument must be of type string. Received undefined

采用绝对路径也会有同样问题。改为尽量短路径就正常了。然后我参考你的脚本,做了修改,新版脚本测试了三种保存方法都是可以正常工作的。

修复后的代码
const path = require('path');
const quickAddApi = app.plugins.plugins.quickadd.api;
const { editor, file, containerEl } = app.workspace.activeEditor;
const url = "http://127.0.0.1:36677/upload";

module.exports = async () => {
  const files = app.vault.getFiles();
  let selection = "";
  let content = "";
  selection = editor.getSelection();
  console.log("Selected text:", selection);

  for (let line of selection.split("\n")) {
    let embed = "";
    if (line) {
      embed = matchSelectionEmbed(line);
    }
    console.log("Matched embed:", embed);

    if (embed && /\.(png|jpg|jpeg|gif|bmp)$/.test(embed)) {
      let wikiPath = getFilePath(files, embed); // 匹配Wiki链接
      if (!wikiPath) {
        new Notice(`❌无法找到文件: ${embed}`);
        console.log(`❌无法找到文件: ${embed}`);
        continue;
      }

      // 获取绝对路径
      const imgPath = app.vault.adapter.getFullPath(wikiPath);
      console.log("Image path:", imgPath);

      const data = await uploadFiles([imgPath], url);
      if (data.success) {
        const imgWiki = `![[${embed}]]`;
        const imgLink = `![${embed}](${data.result})`;
        line = line.replace(imgWiki, imgLink);
      } else {
        new Notice(`❌上传 ${path.basename(imgPath)} 图片失败`);
        console.log(`❌上传 ${path.basename(imgPath)} 图片失败`);
      }
    }
    content += line + "\n";
  }
  
  console.log("Final content:", content);
  copyToClipboard(content)
  new Notice(`✅复制成功`);
};

// 获取文件路径函数
function getFilePath(files, baseName) {
  let matchingFiles = files.filter(f => {
    const fullPath = f.path;
    console.log(`Comparing ${fullPath} with ${baseName}`);
    return fullPath.endsWith(baseName);
  });
  
  if (matchingFiles.length === 0) {
    console.log(`No files matched for: ${baseName}`);
    return undefined;
  }

  return matchingFiles[0].path;
}

function matchSelectionEmbed(text) {
  const regex = /!\[\[?([^\]]*?)(\|.*)?\]\]?\(?([^)\n]*)\)?/;
  const matches = text.match(regex);
  if (!matches) return;
  if (matches[3]) return decodeURIComponent(matches[3]);
  if (matches[1]) return decodeURIComponent(matches[1]);
}

async function uploadFiles(imagePathList, url) {
  const response = await requestUrl({
    url: url,
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ list: imagePathList }),
  });
  const data = response.json; // 直接访问 `json` 属性,而不是调用 `json()` 方法
  return data;
};

function copyToClipboard(extrTexts) {
  const txtArea = document.createElement('textarea');
  txtArea.value = extrTexts;
  document.body.appendChild(txtArea);
  txtArea.select();
  if (document.execCommand('copy')) {
      console.log('copy to clipboard.');
  } else {
      console.log('fail to copy.');
  }
  document.body.removeChild(txtArea);
}

1 个赞