[js] 快捷键移动列表节点

image.png

各位以后要注意先看编辑选项里有没有想要的功能 😂

没仔细看直接想当然的让 AI 搓了 10 分钟搓了个弱化版,当反向示例了这下doge


类似代码编辑器中移动代码行

移动块演示.gif

可能有神奇的特性,个人暂时只打算用于列表节点的临时交换。

(function() { const listSwapper = { // 获取当前文档编辑区 getActiveEditor: function() { return document.querySelector('.protyle:not(.fn__none) .protyle-wysiwyg'); }, // 判断节点是否为列表项 isListItem: function(node) { return node && node.classList && node.classList.contains('li'); }, // 判断节点是否为列表容器 isList: function(node) { return node && node.classList && node.classList.contains('list'); }, // 从当前选择的节点查找最近的列表项节点 findListItemNode: function(node) { // 如果是文本节点,先获取其父元素 if (node.nodeType === 3) { node = node.parentElement; } // 向上查找,直到找到列表项节点 while (node) { if (this.isListItem(node)) { return node; } node = node.parentElement; // 如果超出编辑区范围则停止 if (!node || node.classList.contains('protyle-wysiwyg')) { return null; } } return null; }, // 获取同级的前一个列表项 getPreviousListItem: function(listItem) { if (!this.isListItem(listItem)) return null; // 直接获取前一个兄弟元素 let prev = listItem.previousElementSibling; // 确保是列表项 if (prev && this.isListItem(prev)) { return prev; } return null; }, // 获取同级的后一个列表项 getNextListItem: function(listItem) { if (!this.isListItem(listItem)) return null; // 直接获取后一个兄弟元素 let next = listItem.nextElementSibling; // 确保是列表项 if (next && this.isListItem(next)) { return next; } return null; }, // 交换两个列表项节点的位置 swapListItems: function(item1, item2) { if (!item1 || !item2 || !this.isListItem(item1) || !this.isListItem(item2)) { return false; } try { const parent = item1.parentNode; // 确保两个节点在同一个父列表中 if (parent !== item2.parentNode) { console.log('列表项不在同一个父容器中,无法交换'); return false; } // 保存当前项的ID用于后续恢复焦点 const itemId = item1.getAttribute('data-node-id'); // 判断相对位置并交换 const next = item1.nextElementSibling; if (item2 === next) { // item2紧跟在item1后面,直接调整顺序 parent.insertBefore(item2, item1); } else { // 创建节点引用的副本,用于判断顺序 const item1Clone = item1; const item2Clone = item2; // 标记item2的下一个节点 const item2Next = item2.nextElementSibling; // 如果item1在item2之前 if (Array.from(parent.children).indexOf(item1Clone) < Array.from(parent.children).indexOf(item2Clone)) { // 将item2移到item1之前 parent.insertBefore(item2, item1); // 将item1移到item2原来的位置 if (item2Next) { parent.insertBefore(item1, item2Next); } else { parent.appendChild(item1); } } else { // item2在item1之前 // 先将item1移到item2的位置 parent.insertBefore(item1, item2); // 再将item2移到item1原来的位置 if (next) { parent.insertBefore(item2, next); } else { parent.appendChild(item2); } } } console.log('列表项交换成功'); return itemId; } catch (e) { console.error('交换列表项失败:', e); return false; } }, // 向上移动列表项 moveListItemUp: function() { // 获取当前选中内容所在的列表项 const selection = window.getSelection(); if (!selection.rangeCount) return; const listItem = this.findListItemNode(selection.getRangeAt(0).startContainer); if (!listItem) { console.log('当前光标不在列表项内'); return; } console.log('当前列表项:', listItem); // 获取同级的前一个列表项 const prevItem = this.getPreviousListItem(listItem); if (!prevItem) { console.log('已经是第一个列表项,无法上移'); return; } console.log('前一个列表项:', prevItem); // 交换列表项 const itemId = this.swapListItems(listItem, prevItem); // 恢复焦点 if (itemId) { setTimeout(() => { this.focusListItemById(itemId); }, 10); } }, // 向下移动列表项 moveListItemDown: function() { // 获取当前选中内容所在的列表项 const selection = window.getSelection(); if (!selection.rangeCount) return; const listItem = this.findListItemNode(selection.getRangeAt(0).startContainer); if (!listItem) { console.log('当前光标不在列表项内'); return; } console.log('当前列表项:', listItem); // 获取同级的后一个列表项 const nextItem = this.getNextListItem(listItem); if (!nextItem) { console.log('已经是最后一个列表项,无法下移'); return; } console.log('后一个列表项:', nextItem); // 交换列表项 const itemId = this.swapListItems(listItem, nextItem); // 恢复焦点 if (itemId) { setTimeout(() => { this.focusListItemById(itemId); }, 10); } }, // 通过ID聚焦到列表项 focusListItemById: function(itemId) { if (!itemId) return; // 查找具有特定ID的列表项 const item = document.querySelector(`[data-node-id="${itemId}"]`); if (!item) return; // 查找列表项中的可编辑段落 const paragraph = item.querySelector('[contenteditable="true"]'); if (!paragraph) return; // 聚焦并将光标设置到段落末尾 paragraph.focus(); const range = document.createRange(); const selection = window.getSelection(); // 将光标设置到段落内容末尾 if (paragraph.childNodes.length > 0) { const lastChild = paragraph.childNodes[paragraph.childNodes.length - 1]; if (lastChild.nodeType === Node.TEXT_NODE) { range.setStart(lastChild, lastChild.length); range.setEnd(lastChild, lastChild.length); } else { range.selectNodeContents(lastChild); range.collapse(false); } } else { range.selectNodeContents(paragraph); range.collapse(false); } selection.removeAllRanges(); selection.addRange(range); }, // 初始化键盘事件监听 init: function() { document.addEventListener('keydown', (event) => { // 检查是否是Alt+箭头组合键 if (event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) { // Alt + 上箭头:向上移动列表项 if (event.key === 'ArrowUp') { event.preventDefault(); this.moveListItemUp(); return false; } // Alt + 下箭头:向下移动列表项 else if (event.key === 'ArrowDown') { event.preventDefault(); this.moveListItemDown(); return false; } } }); console.log('思源笔记列表项交换脚本已加载,使用 Alt+↑/↓ 来交换列表项位置'); } }; // 初始化脚本 listSwapper.init(); })();
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24727 引用 • 101556 回帖
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    132 引用 • 875 回帖 • 3 关注
1 操作
xqh042 在 2025-03-23 16:30:31 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 话说代码片段除了不会保存,跟思源内置的功能有什么区别吗?

    image.png

    1 回复
  • xqh042

    甚至还更弱了 😂 主打一个没仔细看设置乱折腾


    好吧,其实我还是搜了一下设置的,但是关键词(移动、块)都没命中,就以为没这功能了doge

推荐标签 标签

  • danl
    161 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    21 引用 • 204 回帖
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 734 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 442 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 105 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 661 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 1 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 86 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 788 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    586 引用 • 3538 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    57 引用 • 25 回帖 • 7 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 33 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    179 引用 • 408 回帖 • 486 关注
  • 叶归
    5 引用 • 16 回帖 • 8 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 606 关注
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    6 引用 • 140 回帖
  • CodeMirror
    1 引用 • 2 回帖 • 154 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 117 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 23 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 32 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    31 引用 • 108 回帖
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 297 关注
  • Excel
    31 引用 • 28 回帖