自定义 Excalidraw 脚本 - 建立库外 Eagle 素材库的连接
温馨提醒
这个脚本是作用在 Obsidian 笔记库外的 Eagle 库,Eagle 在导入文件时会创建很多其他数据文件,并不适应于在 Obsidian 中建立,可以尝试用 Billfish 管理 Obsidian 内部的图片。
这脚本联动 Obsidian 笔记外的好处就是当你删除 Eagle 库中素材时 (素材库定期整理),Obsidian 的内部笔记并不受影响,而且 Eagle 作为素材管理器非常优秀,可以快速预览和编辑图片,管理很多类型的文档,包括用浏览器拓展插件剪藏网页,批量收集网页画板或者图片,以图搜图,用标签和标星管理文件等等,具体功能可以看看相关介绍。
这个脚本在 硬核工作流:实现以图搜图 中也提到过,这里就单独提出来,脚本的初始目的是为了建立对 PDF 的图片做笔记时,同时保留图片的回链来定位到具体的 PDF 所在的页面,其中涉及到了一些 PDF 提取图片的操作。
功能演示
该脚本的主要功能是通过拖拽将 Eagle 中素材拖入 Excalidraw 画板中时,会自动复制素材到 Obsidian 笔记库中,同时读取 Eagle 素材文件下的 metadata.json 文件中的信息,将存储的 url 连接随着素材嵌入到 Excalidraw 画板中,这样就可以让存放的图片、HTML、PDF、PPT 等素材嵌入到 Excalidraw 画板同时可以打开对应的外部链接。
针对 Office 和 PDF 的嵌入
- 针对 Office 三件套和 PDF 的优化
如果嵌入的是 Office 三件套或者 PDF 的话,会出现弹窗选择插入缩略图还是链接:确定则插入缩略图附加文件回链,取消则只插入连接:
通过图片跳转 Eagle 原文件
Eagle 的外部回链
Eagle 也存在外部链接,默认为
eagle://item/{文件名}
,文件会在 Eagle 中打开。
故当你选择一个图片时,再次单击这个脚本,就会在 Eagle 中打开这个图片。
OpenSelectImage 脚本
OpenSelectImage
默认软件打开画板中选中的图片,适用于当您想用默认软件编辑笔记画板中的图片时,采用的一个快捷方式。
await ea.addElementsToView(); //to ensure all images are saved into the file
const { exec } = require('child_process');
const img = ea.getViewSelectedElements().filter(el => el.type === "image");
if (img.length === 0) {
new Notice("No image is selected");
return;
}
for (i of img) {
const currentPath = ea.plugin.filesMaster.get(i.fileId).path;
const file = app.vault.getAbstractFileByPath(currentPath);
if (!file) {
new Notice("Can't find file: " + currentPath);
continue;
}
const pathNoExtension = file.path.substring(0, file.path.length - file.extension.length - 1);
let fileName = file.path;
let fileBasePath = file.vault.adapter.basePath;
let filePath = `${fileBasePath}/${fileName}`
// 使用默认应用打开文件
exec(`start "" "${filePath}"`, (error, stdout, stderr) => {
if (error) {
console.error(`打开文件时出错: ${error.message}`);
return;
}
if (stderr) {
console.error(`打开文件时出错: ${stderr}`);
return;
}
console.log(`文件已成功打开`);
});
}
- 如果你是 Mac 系统,请把 start 换为 open
保存为 md 文件保存到指定文件夹
修改完路径后将下面代码保存为 md 文件,修改放入 Excalidraw 指定的 Scripts 的文件夹中,在 Excalidraw 的画板中的工具面板中就会出现一个齿轮按钮,需要时点击运行就可以了。
EagleToExcalidraw 脚本
同样需要保存为 md 文件保存到指定文件夹
- 需要修改的路径
- 第一次安装改脚本时,单击运行脚本一下,然后在 Excalidraw 插件设置中会出现这个选项
- 修改为 Obsidian 中存放素材的路径 (已创建的路径)
- 注意使用
/
来转义路径,采用相对路径。
该脚本可能并不适应每个人的习惯,这里只供参考借鉴,请大家根据自己需求来适当修改代码。
let settings = ea.getScriptSettings();
//set default values on first run
if (!settings["Eagle Images Path"]) {
settings = {
"Eagle Images Path": {
value: "Y-图形文件存储/EagleImages",
description: "Obsidian库内存放Eagle的图片的相对路径,比如:Y-图形文件存储/EagleImages"
}
};
ea.setScriptSettings(settings);
}
const path = require('path');
const fs = require("fs");
let api = ea.getExcalidrawAPI();
let el = ea.targetView.containerEl.querySelectorAll(".excalidraw-wrapper")[0];
// 获取库的基本路径
const basePath = (app.vault.adapter).getBasePath();
// 设置相对路径
const relativePath = settings["Eagle Images Path"].value;
// 对于选中的项目,则通过文件名来创建Eagle的回链并打开
let selectedEls = ea.getViewSelectedElements()
for (selectedEl of selectedEls) {
let embeddedFile = ea.targetView.excalidrawData.getFile(selectedEl.fileId);
if (!embeddedFile) {
new Notice("Can't find file: " + selectedEl.fileId);
continue;
}
let abstractPath = path.join(embeddedFile.file.vault.adapter.basePath, embeddedFile.file.path);
const eagle_id = path.basename(abstractPath, path.extname(abstractPath));
let EagleLink = `eagle://item/${eagle_id}`;
// 打开链接
window.open(EagleLink);
}
new Notice("EagleToExcalidraw脚本已启动!");
// 对于从Eagle拖拽过来的文件,以Eagle文件夹名命名,根据后缀名来创建不同的拖拽形式
el.ondrop = async function (event) {
console.log("ondrop");
event.preventDefault();
if (event.dataTransfer.types.includes("Files")) {
console.log("文件类型判断");
for (let file of event.dataTransfer.files) {
let directoryPath = file.path;
console.log(directoryPath);
if (directoryPath) {
console.log("获取路径");
// 清空插入的环境变量
event.stopPropagation();
ea.clear();
ea.style.strokeStyle = "solid";
ea.style.backgroundColor = "#ffec99";
ea.style.fillStyle = 'solid';
ea.style.roughness = 0;
// ea.style.roundness = { type: 3 };
ea.style.strokeWidth = 1;
ea.style.fontFamily = 4;
ea.style.fontSize = 20;
// 判断是否为Eagle文件,不是这不执行
let folderPathName = path.basename(path.dirname(directoryPath));
console.log(folderPathName);
console.log(folderPathName)
if (!folderPathName.match(".info")) {
console.log("不为Eagle文件夹文件");
continue;
}
console.log("为Eagle文件夹文件");
let fileName = path.basename(directoryPath);
if (folderPathName && fileName) {
let eagleId = folderPathName.replace('.info', '');
console.log(eagleId)
console.log(`folder: ${folderPathName};file_name:${fileName};eagle_id:${eagleId}`);
// 获取原文件名,不带后缀
let insertFilename = fileName.split(".").slice(0, -1).join(".");
// 获取文件名后缀
const fileExtension = fileName.split('.').pop();
// 将图片文件移动到指定文件夹
let sourcePath = directoryPath;
// 📌定义附件保存的地址
var destinationName = `${eagleId}.${fileExtension}`;
let destinationPath = `${basePath}/${relativePath}/${destinationName}`;
console.log(destinationPath)
// 读取metadata.json文件
let Eaglefolder = path.dirname(directoryPath);
const metadataPath = `${Eaglefolder}/metadata.json`; // 替换为实际的文件路径
// 缩略图的路径
let ThumbnailImage = `${Eaglefolder}/${insertFilename}_thumbnail.png`;
fs.copyFileSync(sourcePath, destinationPath);
await new Promise((resolve) => setTimeout(resolve, 300)); // 暂停一会儿
// 让默认插入文本为文件名
let insert_txt = fileName;
// new Notice("插入Eagle素材:" + file_name);
const metadataContent = fs.readFileSync(metadataPath, 'utf8');
// 解析为JSON对象
const metadata = JSON.parse(metadataContent);
// 设置不同文件类型的导入方式ea.addText为文本、ea.addImage为图片
if (
// 对网页统一用内部链接的形式
fileName.toLowerCase().endsWith(".html") ||
fileName.toLowerCase().endsWith(".mhtml") ||
fileName.toLowerCase().endsWith(".htm")
) {
let id = await ea.addText(0, 0, `[[${destinationName}|${insert_txt}]]`, { width: 300, box: true, wrapAt: 100, textAlign: "center", textVerticalAlign: "middle", box: "box" });
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else if (
// 对图片统一用导入图片后附加链接的形式
fileName.toLowerCase().endsWith(".png") ||
fileName.toLowerCase().endsWith(".jpg") ||
fileName.toLowerCase().endsWith(".jpeg") ||
fileName.toLowerCase().endsWith(".icon") ||
fileName.toLowerCase().endsWith(".svg")
) {
let id = await ea.addImage(0, 0, destinationName);
let el = ea.getElement(id);
if (metadata.url) {
// 将el.link的值设置为metadata.json中的url
el.link = metadata.url;
} else {
// 将el.link的值设置为Eagle的回链
el.link = ``;
}
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else if (fileName.toLowerCase().endsWith(".url")) {
// 对url文件采用文本加入json的连接形式
link = metadata.url;
let id = await ea.addText(0, 0, `🌐[${insert_txt.replace(".url", "")}](${link})`, { width: 400, box: true, wrapAt: 100, textAlign: "center", textVerticalAlign: "middle", box: "box" });
let el = ea.getElement(id);
// 将el.link的值设置为Eagle的回链
el.link = `eagle://item/${eagleId}`;
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else if (
// 针对Office三件套
fileName.toLowerCase().endsWith(".pptx") ||
fileName.toLowerCase().endsWith(".ppt") ||
fileName.toLowerCase().endsWith(".xlsx") ||
fileName.toLowerCase().endsWith(".xls") ||
fileName.toLowerCase().endsWith(".docx") ||
fileName.toLowerCase().endsWith(".doc") ||
fileName.toLowerCase().endsWith(".xmind") ||
fileName.toLowerCase().endsWith(".pdf")
) {
let InsertPDFImage = confirm("是否插入附件缩略图?");
if (InsertPDFImage) {
let destinationPath = `${basePath}/${relativePath}/${eagleId}.png`;
fs.copyFileSync(ThumbnailImage, destinationPath)
await new Promise((resolve) => setTimeout(resolve, 200)); // 暂停一会儿
var id = await ea.addImage(0, 0, `${eagleId}.png`);
} else {
var id = await ea.addText(0, 0, `[[${insert_txt}]]`, { width: 400, box: true, wrapAt: 100, textAlign: "center", textVerticalAlign: "middle", box: "box" });
}
let el = ea.getElement(id);
el.link = `[[${destinationName}]]`;
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else if (
// 对gif、mp4等动态进行设置(可根据需要的格式自行添加)
fileName.toLowerCase().endsWith(".gif") ||
fileName.toLowerCase().endsWith(".mp4")
) {
// 清空插入的环境变量
event.stopPropagation();
ea.clear();
ea.style.strokeStyle = "solid";
ea.style.strokeColor = "transparent";
ea.style.backgroundColor = "transparent";
ea.style.fillStyle = 'solid';
ea.style.roughness = 0;
ea.style.strokeWidth = 1;
ea.style.fontFamily = 4;
let eagleGifFile = app.vault.getAbstractFileByPath(`${relativePath}/${destinationName}`);
let id = await await ea.addIFrame(0, 0, 500, 280, 0, eagleGifFile);
let el = ea.getElement(id);
// ea.style.fillStyle = "solid";
el.link = `[[${destinationName}]]`;
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else if (
// 对mp3等音频进行设置(可根据需要的格式自行添加)
fileName.toLowerCase().endsWith(".mp3") ||
fileName.toLowerCase().endsWith(".WAV")
) {
// 清空插入的环境变量
event.stopPropagation();
ea.clear();
ea.style.strokeStyle = "solid";
ea.style.strokeColor = "transparent";
ea.style.backgroundColor = "transparent";
ea.style.fillStyle = 'solid';
ea.style.roughness = 0;
ea.style.strokeWidth = 1;
ea.style.fontFamily = 4;
let eagleGifFile = app.vault.getAbstractFileByPath(`${relativePath}/${destinationName}`);
let id = await await ea.addIFrame(0, 0, 400, 80, 0, eagleGifFile);
let el = ea.getElement(id);
// ea.style.fillStyle = "solid";
el.link = `[[${destinationName}]]`;
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
} else {
// 其余统一插入eagle连接
let id = await ea.addText(0, 0, `[[${insert_txt}]]`, { width: 400, box: true, wrapAt: 100, textAlign: "center", textVerticalAlign: "middle", box: "box" });
let el = ea.getElement(id);
// 将el.link的值设置为Eagle的回链
el.link = `eagle://item/${eagleId}`;
await ea.addElementsToView(true, false, false);
if (ea.targetView.draginfoDiv) {
document.body.removeChild(ea.targetView.draginfoDiv);
delete ea.targetView.draginfoDiv;
}
}
}
}
}
}
};
讨论
若阁下有独到的见解或新颖的想法,诚邀您在文章下方留言,与大家共同探讨。
反馈交流
其他渠道
版权声明
版权声明:所有 PKMer 文章如果需要转载,请附上原文出处链接。