yuxinzhao
关注
189874 号成员,2025-03-25 11:05:48 加入
281
个人主页 浏览
278
帖子 + 回帖 + 评论
40h22m
在线时长
  • 嵌入式系列插件第一弹:TikZ 挂件升级为 TikZ 插件

    2025-12-04 14:05
    \usetikzlibrary{calc}
    \tikzcdset{
      curve/.style args={height=#1}{
        to path={
          (\tikztostart) .. controls
            ($(\tikztostart)!0.5!(\tikztotarget) - (0,#1)$) and
            ($(\tikztostart)!0.5!(\tikztotarget) - (0,#1)$)
          .. (\tikztotarget)
        }
      }
    }
    

    @ringx 加上上面这个自定义样式可以接近原本的效果,不过也不完全一致,你可以试试再调整调整,如果效果可以的话我就把它加插件里

  • 嵌入式系列更新:Excalidraw 支持嵌入思源内容

    2025-12-03 20:33

    导出图片也能显示思源块我在 0.6.0 解决了trollface

  • 建议修改那些影响体验的设计(个人许愿向)

    2025-12-01 11:50

    这些都是可以通过代码片段自定义的

  • 嵌入式系列插件重大更新:Excalidraw 代码片段功能上线(脚本 / 样式)

    2025-11-27 09:34

    选中元素 Tab 切换形状:

    // 图形切换顺序
    const SHAPE_CYCLE = ['rectangle', 'ellipse', 'diamond'];
    const SHAPE_NAMES = {
      rectangle: '矩形',
      ellipse: '椭圆',
      diamond: '菱形'
    };
    
    // 切换选中元素的图形类型
    const cycleSelectedElementShape = () => {
      const appState = window.excalidrawAPI.getAppState();
      const selectedIds = Object.keys(appState.selectedElementIds || {});
    
      // 必须只选中一个元素
      if (selectedIds.length !== 1) {
        return false;
      }
    
      const allElements = window.excalidrawAPI.getSceneElements();
      const targetId = selectedIds[0];
      const targetElement = allElements.find(el => el.id === targetId);
    
      if (!targetElement) return false;
    
      const currentType = targetElement.type;
      if (!SHAPE_CYCLE.includes(currentType)) {
        return false; // 不是可切换的图形
      }
    
      // 找到下一个类型(循环)
      const currentIndex = SHAPE_CYCLE.indexOf(currentType);
      const nextType = SHAPE_CYCLE[(currentIndex + 1) % SHAPE_CYCLE.length];
    
      // 创建新元素(保持位置、尺寸、样式)
      const newElement = {
        ...targetElement,
        type: nextType,
        version: (targetElement.version || 0) + 1, // 触发更新
        versionNonce: Math.floor(Math.random() * 1000) // 避免缓存
      };
    
      // 更新场景
      const updatedElements = allElements.map(el =>
        el.id === targetId ? newElement : el
      );
    
      window.excalidrawAPI.updateScene({ elements: updatedElements });
      console.log(`🔄 图形已切换为:${SHAPE_NAMES[nextType]}`);
      return true;
    };
    
    // 全局 Tab 键监听(仅在 Excalidraw 画布有焦点时响应)
    const handleKeyDown = (e) => {
      // 只处理 Tab 键,且未组合其他修饰键
      if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
        e.preventDefault(); // 阻止默认 Tab 行为(如焦点跳转)
        const handled = cycleSelectedElementShape();
        if (handled) {
          // 可选:阻止冒泡
          e.stopImmediatePropagation();
        }
      }
    };
    
    // 将监听器绑定到 Excalidraw 容器(更精准,避免全局污染)
    const attachTabListener = () => {
      const excalidrawContainer = document.querySelector('.excalidraw');
      if (excalidrawContainer && !excalidrawContainer.hasTabListener) {
        excalidrawContainer.addEventListener('keydown', handleKeyDown, true); // useCapture=true 更早拦截
        excalidrawContainer.hasTabListener = true;
        console.log('✅ Tab 图形切换监听器已启用');
      }
    };
    
    // 同时尝试立即绑定(如果已存在)
    attachTabListener();
    
  • 嵌入式系列插件重大更新:Excalidraw 代码片段功能上线(脚本 / 样式)

    2025-11-27 09:23

    选中元素网格布局:

    const arrangeSelectedElementsInGrid = (spacing = 10) => {
      const appState = window.excalidrawAPI.getAppState();
      const selectedIds = Object.keys(appState.selectedElementIds || {});
    
      if (!selectedIds || selectedIds.length === 0) {
        console.log("⚠️ 请先选择至少一个元素");
        return;
      }
    
      const allElements = window.excalidrawAPI.getSceneElements();
      const selectedElements = allElements.filter(el => selectedIds.includes(el.id));
    
      if (selectedElements.length === 0) {
        console.log("⚠️ 未找到选中的元素");
        return;
      }
    
      // === 1. 按 y 聚类成行 ===
      const sortedByY = [...selectedElements].sort((a, b) => a.y - b.y);
      const avgHeight = selectedElements.reduce((sum, el) => sum + el.height, 0) / selectedElements.length;
      const rowThreshold = Math.max(avgHeight * 0.6, 20); // 行容差
    
      const rows = [];
      let currentRow = [sortedByY[0]];
    
      for (let i = 1; i < sortedByY.length; i++) {
        const prev = currentRow[currentRow.length - 1];
        const curr = sortedByY[i];
        if (Math.abs(curr.y - prev.y) <= rowThreshold) {
          currentRow.push(curr);
        } else {
          rows.push(currentRow);
          currentRow = [curr];
        }
      }
      rows.push(currentRow);
    
      // 每行内部按 x 排序(从左到右)
      rows.forEach(row => row.sort((a, b) => a.x - b.x));
    
      // === 2. 计算每列最大宽度、每行最大高度 ===
      const maxColumns = Math.max(...rows.map(row => row.length));
      const columnWidths = new Array(maxColumns).fill(0);
      const rowHeights = rows.map(row =>
        Math.max(...row.map(el => el.height))
      );
    
      // 填充 columnWidths
      for (let colIndex = 0; colIndex < maxColumns; colIndex++) {
        for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
          const row = rows[rowIndex];
          if (colIndex < row.length) {
            columnWidths[colIndex] = Math.max(columnWidths[colIndex], row[colIndex].width);
          }
        }
      }
    
      // === 3. 重新定位:每个元素左上角 = 网格单元格左上角 ===
      const updates = new Map();
    
      // 起点:以整个选区最左上角元素的原始位置为基准(保持整体位置不变)
      const globalMinX = Math.min(...selectedElements.map(el => el.x));
      const globalMinY = Math.min(...selectedElements.map(el => el.y));
    
      let currentY = globalMinY;
    
      for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
        let currentX = globalMinX; // 每行都从全局最左开始
    
        for (let colIndex = 0; colIndex < rows[rowIndex].length; colIndex++) {
          const el = rows[rowIndex][colIndex];
          // ✅ 关键:元素左上角直接设为 (currentX, currentY)
          updates.set(el.id, { x: currentX, y: currentY });
    
          // 移动到下一列:加上该列统一宽度 + 间距
          currentX += columnWidths[colIndex] + spacing;
        }
    
        // 移动到下一行:加上该行统一高度 + 间距
        currentY += rowHeights[rowIndex] + spacing;
      }
    
      // === 4. 应用更新 ===
      const updatedElements = allElements.map(el => {
        if (updates.has(el.id)) {
          const { x, y } = updates.get(el.id);
          return { ...el, x, y };
        }
        return el;
      });
    
      window.excalidrawAPI.updateScene({ elements: updatedElements });
      console.log(`✅ 已将 ${selectedElements.length} 个元素按网格左上对齐`);
    };
    
    const mutationObserver = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach(node => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              const menuElement = node.querySelector(".popover .context-menu");
              if (menuElement) {
                const itemElement = document.createElement("li");
                itemElement.setAttribute("data-testid", "arrangeSelectedElementsInRow");
                itemElement.innerHTML = `
    <button type="button" class="context-menu-item">
      <div class="context-menu-item__label">将选中的元素网格布局</div>
      <kbd class="context-menu-item__shortcut"></kbd>
    </button>`;
                itemElement.addEventListener("click", () => {
                  arrangeSelectedElementsInGrid(10);
                })
                menuElement.querySelector(`[data-testid="wrapSelectionInFrame"]`)?.insertAdjacentElement('afterend', itemElement);
              }
            }
          });
        }
      }
    });
    
    mutationObserver.observe(document.body, {
      childList: true,
      subtree: true
    });
    ```
    
  • 开发思源笔记思维导图插件之 simple mindmap 坑

    2025-11-26 23:15

    我的想法是树状结构的内容可以切换回思源的 markdown 格式,其他不在树状结构里的还是存图片里

  • 开发思源笔记思维导图插件之 simple mindmap 坑

    2025-11-26 22:23

    我的想法是把它做成类似我的 嵌入式系列 伪代码 插件的形式,和思源块内容是绑定的,可以随意开关 思维导图 视图。这样子需要的时候可以切换回列表形式

  • 开发思源笔记思维导图插件之 simple mindmap 坑

    2025-11-26 20:05

    看起来 simple mind map 确实挺好用的,等我最近论文写完就回来做 嵌入式系列 的思维导图插件。其实 simple mind map 原作者更新频率不高在我看来也有好处,方便 fork 过来自己魔改,也不用太考虑兼容问题。tikz 那个用的 tikzjax 库就是这个情况,这个库已经经过三个人转手了,到我这里才通过魔改解决中文显示问题。

  • 开发思源笔记思维导图插件之 simple mindmap 坑

    2025-11-26 19:39

    如果 Excalidraw 能做到这样是不是也可以平替思维导图的功能了:https://www.bilibili.com/video/BV1Je411e7Qx

  • 开发思源笔记思维导图插件之 simple mindmap 坑

    2025-11-26 12:27

    大佬辛苦了,好多坑啊

  • STtools 插件:白板进一步改进(v0.23.x)

    2025-11-25 13:24

    原来更新也会增加下载量呀

  • 有没有插件开发大佬能集成一下 quiver ?

    2025-11-25 12:58

    可能得晚一阵子了,导师催着回去写论文,最近开发节奏会变缓。顺便问一下,交换图一般是数学里面用的多吗

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-24 20:07

    如果你只是想要图片背景不透明的话可以使用以下 css 代码片段:

    .viewer-canvas img[src^="assets/drawio-"] {
        background-color: white;
    }
    

    如果你想要图片查看器整个背景都不是透明的可以用:

    .viewer-canvas {
        background-color: white;
    }
    

    灯箱主要是服务于 draw.io 具有多页的情况的,并不打算让它覆盖思源本身的图像查看器

  • 这大概就是思源图片编辑该有的样子吧

    2025-11-24 18:22

    👍 又是一个 嵌入式系列 的好题材trollface

  • 嵌入式系列插件第四弹:Excalidraw 插件

    2025-11-22 15:25

    draw.io 和 excalidraw 新版本都可设置默认明暗主题

  • 嵌入式系列插件第四弹:Excalidraw 插件

    2025-11-22 15:24

    已支持以 Tab 形式打开编辑窗口

  • 嵌入式系列插件第四弹:Excalidraw 插件

    2025-11-21 18:11

    两个插件最近会把明暗模式和 Tab 编辑都加上。另外,draw.io 除了明暗模式,其他功能应该是全量的了,如果发现缺什么了可以详细说说,可能我没有发现

  • 思源笔记必装插挂件

    2025-11-21 01:21

    👍 赞。顺便一提,两个嵌入式系列插件现在都支持 PNG 和全屏编辑了,excalidraw 也简化了菜单中的一些选项

  • 嵌入式系列插件第四弹:Excalidraw 插件

    2025-11-21 00:04

    PNG 和全屏编辑模式在 0.2.0 版本都已支持

  • 思源笔记如何类似 obsidian 发布为网页笔记

    2025-11-16 12:51

    可以用嵌入式系列插件,所有内容都是嵌入到原生 markdown 格式中的,不会有自定义块导致的分享时显示错误问题

  • 有没有插件开发大佬能集成一下 quiver ?

    2025-11-14 19:44

    嵌入到图片包没问题的,我看看后续做一下,加入到 嵌入式系列 里,目前的安排是先做完 Excalidraw,然后再做这个。中间视情况可能会插入个思维导图,不过应该一个月以内会有吧(没意外的话)

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-14 16:19

    可以呀,刚好没啥插件点子了,那下一个就做 Excalidraw,嵌入式系列插件主要看社区反馈需要什么以及我自己用不用得上

  • STtools 插件:白板改进(v0.22.x)

    2025-11-14 14:38

    这白板无敌了 👍,请问考虑拆分成单独的插件吗,我个人觉得整个插件多了太多用不到的功能会有点重/冗余,如果想保留个人品牌也可以考虑像我这样统一为一个 嵌入式系列

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-14 11:24

    image.png

    我把你的 svg 拖进思源里是正常的,会不会是你的主题或者 css 代码片段设置了图像圆角,我看那个位置刚好在角落。你可以创建只有以一个矩形的图像测试一下是不是这个原因。还可以创建一个新的工作空间把图像放进去看看是不是思源/系统本身问题。

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-11 15:20

    0.2.1 版本加入了灯箱功能,可以在右键菜单里打开,如果你要浏览每一页的话能用上

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-11 13:50

    0.2.0 版本会在图像块的右上角显示 draw.io 的标签,如果包含多页的话会显示有几页,你可以不用手动加页数文本:

    image.png

  • mermaid 块不支持鼠标缩放其内容吗?

    2025-11-10 14:59

    我写了个 draw.io 插件,里面可以写 mermaid,然后图像会以 SVG 图像形式存储于思源,因此可以借助思源本身的图像查看器缩放查看 mermaid 图片,算是个官方修改前的解决方案。详情看 嵌入式系列插件第三弹:draw.io 插件

  • 希望增加一个 mermaid 的缩放拖拽功能

    2025-11-10 14:59

    我写了个 draw.io 插件,里面可以写 mermaid,然后图像会以 SVG 图像形式存储于思源,因此可以借助思源本身的图像查看器缩放查看 mermaid 图片,算是个官方修改前的解决方案。详情看 嵌入式系列插件第三弹:draw.io 插件

  • 嵌入式系列插件第三弹:draw.io 插件

    2025-11-10 14:54

    意外发现有个额外用途:思源目前的 mermaid 渲染出的图存在无法放大查看细节的问题,而刚好 draw.io 里面可以写 mermaid,于是借助这个插件编辑后得到的图像是可以用思源本身的图片查看器查看细节的