自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--

前言简介

借用 Obsidian 的 Kanban 插件 的 UI 功能,配合 Excalidraw 的 Frame 框架 以及 局部引用功能 生成一个 FrameKanban.md 文件,该 FrameKanban.md 文件其实是一个动态文件,会根据当前的 Excalidraw 而生成对应的缩略图或者标题大纲,实现一个 Excalidraw 的简单的大纲。

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--前言简介

借用 Kanban 的拖拽功能可以按照 Kanban 的自上而下的顺序对 Frame 进行排序,然后你可以用该脚本对 Excalidraw 的 Frame 进行排序。同时用大叔的 SildeShow 脚本演示画布,实现像 PPT 一样的演示。

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--前言简介

Frame 的命名

正常的 Fame 命名稍微有点不方便 (仅个人感觉,可能大叔之后会优化),我之前写过 TextExtractor 的一个脚本,是用于对图片进行 OCR 的,其实还可以对 Frame 或者 Text 的元素进行编辑,也就是不仅可以编辑图形文本还是对 Frame 和包含文本元素进行文本编辑。
自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--前言简介

使用教程

视频教程

使用用法

首先感谢群友 @颜凯 提供了以下的使用教程,比我写的好多了,我估计都不会写这些详细,以后注意并学习一下 (by 熊猫 24.01.15)

第一步:Frame 选定演示范围

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--第一步:Frame 选定演示范围

第二步:打开 Kanban 文件

一定要先打开 kanban 文件,不然运行脚本找不到对应的显示列表

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--第二步:打开 Kanban 文件

第三步:运行脚本功能

  • 脚本功能介绍
    • true:有缩略图
    • false:无缩略图
    • sort:排序(调整顺序后,再按 sort,顺序完成更新)
    • open:以 4 种模式 (新标签页 tab、垂直标签页 vertical、水平标签页 horizontal、悬浮标签页 hover,需要安装 Hover 插件) 打开 kanban
      • PS:一般固定到侧边后就不需要额外打开了。

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--第三步:运行脚本功能

不足之处

  1. 依赖于 kanban,不过拖拽到文本,只能在看板之间移动;
  2. 不支持嵌套,没有层级管理,感觉功能太复杂了,反而不好用,也不想折腾了。

常见 BUG

缩略图没有生成或者刷新

Kanban 文件的刷新稍微有点延迟,而且 Excalidraw 的局部引用视图生成在第一次打开时需要一点时间的,你可以稍微等一会儿。

Excalidraw 的嵌入图片设置

自定义 Excalidraw 脚本 - 画板与 Kanban 得梦幻结合 - 像 PPT 一样演示--缩略图没有生成或者刷新
推荐模式: 如果电脑性能一般的情况下,建议在编辑或者快速查看的时候采用 PNG 模式,当你需要真正的复制文本的话,再去切换 Native SVG 格式,不过 Kanban 的图片是不能预览的。

EA 脚本

Excalidraw如何安装脚本_脚本设置介绍

const fs = require('fs');
let settings = ea.getScriptSettings();
// 加载默认值
if (!settings["ExcalidrawFameKanbanPath"]) {

    settings = {
        "ExcalidrawFameKanbanPath": {
            value: "Y-图形文件存储/Excalidraw/ExcalidrawFrameKanban.md",
            description: "用于存放Frame的Kanban文件的存储路径<br>ob的路径,如:Y-图形文件存储/Excalidraw/ExcalidrawFrameKanban.md"
        },
        "FameKanbanLaneWidth": {
            value: 340,
            description: "Kanban的宽度,默认值为330"
        },
    };
    ea.setScriptSettings(settings);
}
const kanbanFilePath = settings["ExcalidrawFameKanbanPath"].value;
const KanbanLaneWidth = settings["FameKanbanLaneWidth"].value;

await ea.addElementsToView(); 
const frameElements = ea.getViewElements().filter(el => el.type === "frame");
const fileName = app.workspace.getActiveFile().name;
const choices = [true, false, "sort", "open"];

const choice = await utils.suggester(choices, choices, "是否生成缩略图或者排序");
if (typeof choice === "undefined") {
    return; // 退出函数或程序
}

// ! open打开形式
if (choice === "open") {
    let KanbanFullPath = app.vault.getAbstractFileByPath(kanbanFilePath);
    const choices = ["tab", "vertical", "horizontal", "hover"];
    const choice = await utils.suggester(choices, choices, "是否生成缩略图或者排序");
    if (choice === "tab") {
        // app.workspace.activeLeaf.openFile(KanbanFullPath);
        app.workspace.getLeaf("tab").openFile(KanbanFullPath);
    } else if (choice === "vertical") {
        app.workspace.getLeaf('split', 'vertical').openFile(KanbanFullPath);

    } else if (choice === "horizontal") {
        app.workspace.getLeaf('split', 'horizontal').openFile(KanbanFullPath);

    } else if (choice === "hover") {
        let newLeaf = app.plugins.plugins["obsidian-hover-editor"].spawnPopover(undefined, () => this.app.workspace.setActiveLeaf(newLeaf, false, true));
        newLeaf.openFile(KanbanFullPath);
    }

    return;
}


// ! 依据看板(kanban)顺序来排序
if (choice === "sort") {
    // 获取库的基本路径
    const basePath = (app.vault.adapter).getBasePath();
    const frameKanbanFullPath = `${basePath}/${kanbanFilePath}`;
    // 处理
    const updatedElements = await processFile(frameElements, frameKanbanFullPath, fileName);
    let markdownFile = app.vault.getAbstractFileByPath(kanbanFilePath);
    if (markdownFile) app.vault.modify(markdownFile, updatedElements.join("\n"));
    new Notice(`♻FrameKanban已排序`, 3000);
    return;
}

// ! 由Excalidraw的Frame生成Kanban
let frameLinks = [];
if (frameElements.length >= 1) {
    frameElements.sort((a, b) => {
        // 根据 fileName 进行排序
        if (a.name < b.name) {
            return -1;
        }
        if (a.name > b.name) {
            return 1;
        }
        return 0;
    });

    for (let el of frameElements) {
        let frameLink;
        // !
        if (choice === true) {
            frameLink = `- ⏩[[${fileName}#^frame=${el.id}|${el.name}]]<br>![[${fileName}#^frame=${el.id}]]`;
        } else {
            frameLink = `- ⏩[[${fileName}#^frame=${el.id}|${el.name}]]`;

        }
        frameLinks.push(frameLink);
    }
}
const kanbanYaml = "---\n\nkanban-plugin: basic\n\n---\n\n";

const kanbanSetting = {
    "kanban-plugin": "basic",
    "lane-width": KanbanLaneWidth,
};

const kanbanEndText = `\n\n%% kanban:settings\n\`\`\`\n${JSON.stringify(kanbanSetting)}\n\`\`\`\n%%`;
const extrTexts = kanbanYaml + `## [[${fileName.length > 16 ? `${fileName}|${fileName.slice(0, 14)}......` : fileName}]]\n\n` + frameLinks.join("\n") + kanbanEndText;

let markdownFile = app.vault.getAbstractFileByPath(kanbanFilePath);

if (markdownFile) {
    app.vault.modify(markdownFile, extrTexts);
} else {
    file = await app.vault.create(kanbanFilePath, extrTexts);
}

if (choice === true) {
    new Notice(`🖼FrameKanban已刷新`, 3000);
} else {
    new Notice(`⏩FrameKanban已刷新`, 3000);
}

return;

// 排序
async function processFile(allFrameEls, frameKanbanFullPath, fileName) {
    try {
        const data = await fs.promises.readFile(frameKanbanFullPath, 'utf8');
        const lines = data.split('\n');
        const updatedElements = [];

        const regex = new RegExp(`${fileName}#`);
        let j = 0;
        for (let i = 0; i < lines.length; i++) {

            if (regex.test(lines[i])) {
                // 匹配对应的Excalidraw链接
                let regex = /^-\s.*?\[\[(.*?\.md)#\^(\w+)=([a-zA-Z0-9-_]+)\|?(.*?)\]\].*/;
                let elLinkStyle = lines[i].match(regex)[2];
                let elID = lines[i].match(regex)[3];
                let elText = lines[i].match(regex)[4] ? lines[i].match(regex)[4] : `未定义名称`;
                console.log(`第${i}行:${elID} ${elLinkStyle} ${elText}`);

                // 满足条件的修改
                if (elLinkStyle !== 'frame') return;
                for (let selectedEl of allFrameEls) {
                    console.log(selectedEl.id);
                    if (selectedEl.id === elID) {
                        j = j + 1;
                        console.log(selectedEl.name);
                        elText = `Frame${j < 10 ? 0 : ""}${j}_${elText.replace(/Frame\d+_/, "")}`;
                        selectedEl.name = elText;
                        ea.addElementsToView();
                        lines[i] = lines[i].replace(/(^-\s.*?\[\[.*?\.md#\^\w+=[a-zA-Z0-9-_]+\|?)(.*?)(\]\].*)/, `$1${elText}$3`);
                    }
                }
            }
            updatedElements.push(lines[i]);
        }
        // console.log(updatedElements);
        return updatedElements;
    } catch (error) {
        new Notice("🔴读取文件出现错误!");
        console.error(error);
    }
}


反馈交流

其他渠道

版权声明