
Joplin TinyMCE 自定义图标包:从 SVG 素材到编辑器工具栏的构建全流程【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplinJoplin 桌面版富文本编辑器基于 TinyMCE,而编辑器工具栏上的回形针等图标来自一个独立的图标包工程。本文基于 图标包说明文档 展开,完整还原从 Fork Awesome 获取图标素材、规范化 SVG 尺寸、用 gulp 构建、再到产物被桌面端 TinyMCE 加载的全链路,并逐行拆解 gulpfile.js 中的三个构建任务,帮助你在需要为编辑器补充新图标时能够直接照着流程操作。为什么 Joplin 要维护一个独立的 TinyMCE 图标包TinyMCE 的图标系统支持通过IconManager注册自定义图标包,并用icons_url指定图标脚本地址。在 Joplin 桌面端,富文本编辑器的初始化配置中就显式挂载了这条路径:// packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx icons_url: gui/NoteEditor/NoteBody/TinyMCE/icons.js,也就是说,桌面端 TinyMCE 不会依赖内置图标集,而是运行时加载 icons.js 中注册的图标。而这个icons.js并不是手工编写的——它是 IconPack 子工程构建后的产物。该子工程脱胎于 TinyMCE 官方的 oxide icon pack 模板,因此 README 开头注明了其模板来源;图标素材则建议从 Fork Awesome 图标库获取。IconPack 工程结构:每个文件各司其职Assets/TinyMCE/IconPack/目录下是一个可以独立运行的 npm 工程,核心文件如下:文件作用README.md说明图标新增流程与构建步骤package.json声明构建依赖,并用自定义字段iconPackName指定图标包名为Joplingulpfile.js定义clean、icon-packager、deploy三个任务及默认的串行编排postinstall.js在npm install后交互式询问图标包名并回写到package.jsonsrc/svg/paperclip.svg图标 SVG 素材源目录,当前仓库中收录的唯一一枚图标IconDimensions.png图标尺寸规范示意图(24x24 画布、图标主体 15~20 像素)从 package.json 可以看到构建工具链:{ name: oxide-icon-pack-template, scripts: { postinstall: node postinstall.js }, devDependencies: { ephox/oxide-icons-tools: ^2.1.1, gulp: 4.0.2, gulp-clean: ^0.4.0, prompts: ^2.2.1 }, iconPackName: Joplin }几个值得注意的细节:name仍然是模板名oxide-icon-pack-template,真正标识图标包的字段是自定义的iconPackName: Joplin,构建时 gulp 任务会读取它。构建核心是ephox/oxide-icons-tools提供的iconPackager管道,这是 TinyMCE oxide 生态的官方图标打包器。postinstall脚本会在安装依赖时执行 postinstall.js:它通过prompts交互询问 Enter the name of the icon pack.,做非空校验后把结果写回package.json的iconPackName字段。这是模板自带的一次性配置流程,Joplin 仓库中该字段已固定为Joplin。添加一个新图标:素材获取与 SVG 规范README 给出的添加流程可以归纳为三步:获取图标:从 Fork Awesome 图标库中挑选所需的图标(它是 Font Awesome 的社区延续,图标风格统一,适合工具栏场景)。规范尺寸:使用矢量编辑工具(README 推荐 Vectr 的 Resize 功能)把矢量图形缩放到15x15 到 20x20 像素之间(最大 20x20),并把页面(画布)尺寸设置为24x24 像素。入库:将 SVG 文件保存到src/svg目录。为什么图标本身要比画布小一圈?从尺寸示意图和现有素材 paperclip.svg 可以印证这一点:该文件的根元素为svg ... viewBox0 0 24 24 width24 height24即 24x24 的视口中只容纳一枚留白过的纸夹图形,保证图标在工具栏中不会贴边、不会与其他控件拥挤。新增图标时保持同样的viewBox0 0 24 24与内部缩放比例,视觉密度才能与现有图标一致。一个容易踩的坑是命名:构建器按src/svg下的文件名生成图标键名。当前产物 icons.js 中注册的键正是paperclip,与素材文件名一一对应。因此新增src/svg/xxx.svg后,编辑器侧引用图标时使用的就是xxx这个名字。构建流水线:gulfile.js 三个任务逐段解析构建入口是 README 中的gulp命令,对应 gulpfile.js 里定义的default串行任务,完整流水线为clean → icon-packager → deploy:// Assets/TinyMCE/IconPack/gulpfile.js const iconPackager require(ephox/oxide-icons-tools).iconPackager; const clean require(gulp-clean); const gulp require(gulp); const fs require(fs); gulp.task(icon-packager, function() { const contents fs.readFileSync(package.json); const name JSON.parse(contents).iconPackName; return gulp.src(src/svg/**/*.svg) .pipe(iconPackager({ name })) .pipe(gulp.dest(dist)); }); gulp.task(deploy, function() { fs.copyFileSync(${__dirname}/dist/icons/Joplin/icons.js, ${__dirname}/../../../packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/icons.js); return Promise.resolve(); }); gulp.task(clean, function() { return gulp.src(./dist, { read: false, allowEmpty: true, }).pipe(clean()); }); gulp.task(default, gulp.series(clean, icon-packager, deploy));各任务的职责:clean:清空./dist目录,保证每次构建产物纯净,不会被上一次构建的残留文件干扰。allowEmpty: true让目录不存在时任务也能正常通过。icon-packager:先读取package.json取出iconPackName(即Joplin),再收集src/svg/**/*.svg的全部素材,交给iconPackager({ name })管道打包,输出到dist目录。打包器的输出结构是dist/icons/包名/icons.js,即dist/icons/Joplin/icons.js。deploy:把构建产物dist/icons/Joplin/icons.js原样复制到桌面端工程内的 packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/icons.js,完成图标包工程 → 应用工程的交付。这里有一个值得留意的实现细节:deploy任务中的目标路径dist/icons/Joplin/icons.js里的Joplin是硬编码的,而icon-packager中的包名却来自package.json。从源码结构看,如果日后把iconPackName改成别的值,构建仍会成功,但deploy会因为找不到文件而失败——对当前 Joplin 场景而言这不是问题,但它说明这个子工程实际上是按包名固定为 Joplin的假设被定制的。构建产物如何被编辑器加载icon-packager生成的icons.js是一段面向 TinyMCE 运行时的注册脚本。当前仓库中的产物(即deploy复制后的最终文件)内容为:// packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/icons.js tinymce.IconManager.add(Joplin, { icons: { paperclip: svg xmlns:xlinkhttp://www.w3.org/1999/xlink width24 height24defspath dM17.5 21.8c-1 0-2.1-.4-2.9-1.2L5 10.9a4.9 4.9 0 01-1.4-3.3c0-2.7 2.1-4.8 4.8-4.8 1.2 0 2.4.5 3.3 1.4l7.6 7.6.1.2c0 .3-.7 1-1 1l-.2-.1-7.6-7.6c-.6-.6-1.4-1-2.2-1A3.2 3.2 0 006 9.8l9.6 9.8c.5.4 1.2.7 1.8.7 1 0 1.9-.7 1.9-1.8 0-.7-.3-1.3-.8-1.8l-7.2-7.2c-.2-.2-.5-.3-.8-.3-.4 0-.8.3-.8.8 0 .3.1.5.3.7l5.1 5.1.1.3c0 .3-.7 1-1 1l-.2-.1L9 11.8c-.5-.5-.8-1.2-.8-1.9 0-1.4 1-2.4 2.4-2.4.7 0 1.4.3 1.9.8l7.2 7.2c.8.8 1.3 1.8 1.3 2.9 0 2-1.5 3.4-3.5 3.4z ida//defsuse xlink:href#a/use xlink:href#a fill-opacity0 stroke#000 stroke-opacity0//svg, }, });可以把这条加载链路串起来理解:iconPackager把每个 SVG 文件内联压缩成一段字符串(可以看到 paperclip 的路径数据被简化、defsuse结构被保留,同时附加了一个fill-opacity0的描边副本用于可访问性),按文件名作为键组织进icons对象;tinymce.IconManager.add(Joplin, ...)把整包注册到 TinyMCE 的全局图标管理器;桌面端 TinyMCE.tsx 初始化编辑器时通过icons_url: gui/NoteEditor/NoteBody/TinyMCE/icons.js加载该脚本,工具栏插件即可按Joplin包中的键名引用图标。从产物中目前只有paperclip一枚图标可以推断,这个图标包是按需最小化维护的——只有当桌面端富文本编辑器引入了 TinyMCE 内置图标集之外的新按钮时,才会往src/svg增加素材并重新构建。完整操作手册与注意事项把 README 的流程和源码细节合并后,在本地完整走一遍图标包构建的步骤是:进入Assets/TinyMCE/IconPack目录;执行npm install安装依赖(ephox/oxide-icons-tools、gulp、gulp-clean、prompts),此时postinstall会提示输入图标包名,输入Joplin即可;将符合尺寸规范(24x24 画布、主体 15~20 像素)的 SVG 放入src/svg;执行gulp,依次完成clean→icon-packager→deploy,产物会落到dist/icons/Joplin/icons.js,并被复制到packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/icons.js;在编辑器侧按文件名 图标键名的约定引用新图标。几点实操提醒:不要在未规范尺寸的情况下直接放入 Fork Awesome 的原始 SVG。FA 图标默认多为 16x16 或 512x512 视口,直接投放会导致工具栏中图标忽大忽小,破坏 IconDimensions.png 所示的视觉密度;文件名即键名,素材命名要与编辑器代码中引用图标的标识符保持一致;deploy任务依赖固定的输出路径dist/icons/Joplin/icons.js,构建环境(如 Node 版本)需满足 gulp 4.x 的要求;构建完成后请核对桌面端目录下的 icons.js 是否已更新,它是真正被icons_url加载的文件,而非dist中的中间产物。小结Joplin 的 TinyMCE 图标包是一个麻雀虽小、五脏俱全的独立构建工程:postinstall.js负责一次性配置包名,gulpfile.js用clean/icon-packager/deploy三个任务把src/svg下的 SVG 素材经ephox/oxide-icons-tools打包成 TinyMCE 可加载的icons.js,并自动交付到桌面端工程。理解这条SVG 素材 → 规范化 → 打包 → 注册加载的流水线后,为 Joplin 桌面版富文本编辑器扩充自定义图标就是一个有明确验收标准的工程操作,而不是碰运气式的资源替换。【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考