You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

求Summer-Note编辑器自定义查找下一个与替换按钮的JS代码

解决Summer-Note查找下一个与替换功能的完整方案

我之前帮不少开发者搞定过Summer-Note的自定义工具栏功能,你的需求核心是要维护查找状态,逐个遍历匹配项并支持替换当前选中的匹配内容,下面是可直接运行的实现代码和关键逻辑说明:


1. 基础配置与工具栏自定义

首先确保你已经引入了Summer-Note的CSS和JS依赖,然后在编辑器初始化时,把自定义的「查找下一个」和「替换」按钮加入工具栏:

<!-- 引入Summer-Note依赖 -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>

<!-- 编辑器容器 -->
<div id="summernote"></div>
// 初始化Summer-Note
$(document).ready(function() {
  // 维护查找状态的变量(封装在闭包内避免全局污染)
  let currentSearchTerm = '';
  let matches = [];
  let currentMatchIndex = -1;
  const $editor = $('#summernote');

  $editor.summernote({
    height: 300,
    toolbar: [
      ['style', ['style']],
      ['font', ['bold', 'underline', 'clear']],
      ['color', ['color']],
      ['para', ['ul', 'ol', 'paragraph']],
      ['custom', ['findNext', 'replace']], // 新增自定义工具栏组
      ['view', ['fullscreen', 'codeview', 'help']]
    ],
    // 注册自定义按钮
    buttons: {
      findNext: function(context) {
        const button = context.ui.button({
          contents: '<i class="fa fa-search"></i> 查找下一个',
          tooltip: '查找下一个匹配项',
          click: function() {
            handleFindNext(context);
          }
        });
        return button.render();
      },
      replace: function(context) {
        const button = context.ui.button({
          contents: '<i class="fa fa-exchange"></i> 替换',
          tooltip: '替换当前匹配项',
          click: function() {
            const replaceText = prompt('请输入替换内容:');
            if (replaceText !== null) {
              handleReplaceCurrent(context, replaceText);
            }
          }
        });
        return button.render();
      }
    }
  });

  // --- 核心功能函数 ---
  // 获取所有匹配项的位置信息
  function findMatches(context, searchTerm) {
    const $editable = context.layoutInfo.editable;
    const content = $editable.text();
    // 转义正则特殊字符,启用不区分大小写匹配
    const regex = new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
    const matchList = [];
    let match;
    while ((match = regex.exec(content)) !== null) {
      matchList.push({
        start: match.index,
        end: match.index + match[0].length
      });
      // 处理空匹配的无限循环问题
      if (match[0].length === 0) regex.lastIndex++;
    }
    return matchList;
  }

  // 选中指定位置的匹配项
  function selectMatch(context, match) {
    const $editable = context.layoutInfo.editable[0];
    const range = document.createRange();
    const selection = window.getSelection();
    
    // 定位到目标文本节点(适配简单HTML结构)
    let textNode = $editable.firstChild;
    let currentOffset = 0;
    while (textNode && currentOffset + textNode.length < match.start) {
      currentOffset += textNode.length;
      textNode = textNode.nextSibling;
    }
    if (textNode) {
      range.setStart(textNode, match.start - currentOffset);
      range.setEnd(textNode, match.end - currentOffset);
      selection.removeAllRanges();
      selection.addRange(range);
      // 平滑滚动到选中区域
      $editable.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  }

  // 查找下一个匹配项
  function handleFindNext(context) {
    const searchTerm = prompt('请输入要查找的内容:');
    if (searchTerm === null) return;

    // 切换新关键词时重置查找状态
    if (searchTerm !== currentSearchTerm) {
      currentSearchTerm = searchTerm;
      matches = findMatches(context, searchTerm);
      currentMatchIndex = -1;
    }

    if (matches.length === 0) {
      alert('未找到匹配内容');
      return;
    }

    // 循环遍历匹配项(到末尾后回到第一个)
    currentMatchIndex = (currentMatchIndex + 1) % matches.length;
    selectMatch(context, matches[currentMatchIndex]);
  }

  // 替换当前匹配项
  function handleReplaceCurrent(context, replaceText) {
    if (currentMatchIndex === -1 || matches.length === 0) {
      alert('请先执行查找操作');
      return;
    }

    const $editable = context.layoutInfo.editable;
    const content = $editable.text();
    // 替换当前匹配的内容
    const newContent = content.substring(0, matches[currentMatchIndex].start) + 
                      replaceText + 
                      content.substring(matches[currentMatchIndex].end);
    
    // 更新编辑器内容
    $editable.text(newContent);
    // 重新扫描匹配项(内容已修改)
    matches = findMatches(context, currentSearchTerm);
    // 定位到下一个有效匹配项
    if (matches.length > 0) {
      currentMatchIndex = Math.min(currentMatchIndex, matches.length - 1);
      selectMatch(context, matches[currentMatchIndex]);
    } else {
      currentMatchIndex = -1;
      alert('已无匹配内容');
    }
  }
});

关键逻辑说明

  1. 状态管理:通过currentSearchTermmatchescurrentMatchIndex三个变量,记录当前查找关键词、所有匹配项位置、当前选中匹配项的索引,这是实现「查找下一个」「替换当前」的核心。
  2. 匹配定位:使用document.createRange()window.getSelection()API,精准定位编辑器内的匹配文本并高亮选中。
  3. 替换后更新:替换完成后重新扫描内容生成新的匹配列表,确保后续查找的准确性。
  4. 正则安全处理:对用户输入的关键词进行正则特殊字符转义,避免因输入特殊字符导致正则解析错误。

你可以直接复制这段代码到项目中测试,也可以根据需求调整按钮图标、提示文本或适配复杂HTML结构。

内容的提问来源于stack exchange,提问作者keerti

火山引擎 最新活动