[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(); })();
  • 思源笔记

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

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

    26193 引用 • 108815 回帖 • 1 关注
  • 代码片段

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

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

    194 引用 • 1389 回帖
1 操作
xqh042 在 2025-03-23 16:30:31 更新了该帖

相关帖子

欢迎来到这里!

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

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

    image.png

    1 回复
  • xqh042

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


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

推荐标签 标签

  • 小说

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

    32 引用 • 108 回帖 • 5 关注
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖 • 4 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    20 引用 • 37 回帖 • 578 关注
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 558 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖 • 1 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 70 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 661 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 2 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 2 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 2 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 87 关注
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    92 引用 • 752 回帖 • 3 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 192 关注
  • WiFiDog

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

    1 引用 • 7 回帖 • 615 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 119 关注
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    167 引用 • 597 回帖 • 1 关注
  • 安全

    安全永远都不是一个小问题。

    199 引用 • 818 回帖
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 606 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 351 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    10 引用 • 77 回帖 • 1 关注
  • Sillot

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

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

    主仓库地址:Hi-Windom/Sillot

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

    注意事项:

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

    69 引用 • 93 回帖 • 1 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    19 引用 • 23 回帖 • 738 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 545 关注