Greasy Fork is available in English.
Add similarity search based on Levenshtein distance to the highlight feature of SearchJumper.
// ==UserScript== // @name SearchJumper levenshtein addon // @name:zh-CN 搜索酱单词模式扩展 // @name:zh-TW 搜尋醬單詞模式擴展 // @namespace hoothin // @version 0.2 // @description Add similarity search based on Levenshtein distance to the highlight feature of SearchJumper. // @description:zh-CN 为搜索酱的页内高亮添加基于莱文斯#距离的相似度查找 // @description:zh-TW 為搜尋醬的頁内高亮添加基於萊文斯#距離的相似度查找 // @author hoothin // @match *://*/* // @grant unsafeWindow // @run-at document-start // ==/UserScript== (function() { 'use strict'; var _unsafeWindow = (typeof unsafeWindow == 'undefined') ? window : unsafeWindow; if (!_unsafeWindow.searchJumperAddons) _unsafeWindow.searchJumperAddons = []; function levenshteinDistance(a, b) { //構造矩陣 const distanceMatrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null)); //第一行 for (let i = 0; i <= a.length; i += 1) { distanceMatrix[0][i] = i; } //第一列 for (let j = 0; j <= b.length; j += 1) { distanceMatrix[j][0] = j; } for (let j = 1; j <= b.length; j += 1) { for (let i = 1; i <= a.length; i += 1) { const indicator = a[i - 1] === b[j - 1] ? 0 : 1; distanceMatrix[j][i] = Math.min( distanceMatrix[j][i - 1] + 1, // 前一個,增加位數,必須加一 distanceMatrix[j - 1][i] + 1, // 上一個,增加位數,必須加一 distanceMatrix[j - 1][i - 1] + indicator, // 斜方向一個,位數不變 ); } } return distanceMatrix[b.length][a.length]; } const gapStr = "[\n\/\\'\"‘’“”,.!\?,。!?…\(\) ]"; const gapStrs = new RegExp(gapStr + "+", "g"); _unsafeWindow.searchJumperAddons.push({ name: "Levenshtein", type: "findInPage", sort: 0, run: (text, keywords) => { if (!text || !keywords) return {matched: false}; if (keywords.charCodeAt(0) > 255) { let len = keywords.length; let pos = text.toUpperCase().indexOf(keywords.toUpperCase()); return {matched: pos != -1, pos: pos, len: len}; } text = text.toLowerCase(); keywords = keywords.toLowerCase(); let wordArr = text.replace(gapStrs, " ").split(" "); let kwArr = keywords.replace(gapStrs, " ").split(" "); let matched = false, pos = -1, len = 0, matchedStr = []; for (let i = 0; i < wordArr.length; i++) { matched = true; matchedStr = []; for (let j = 0; j < kwArr.length; j++) { let kwLen = kwArr[j].length; let maxTolerance = kwLen>>2; if (kwLen > 3) maxTolerance++; if (!wordArr[i + j] || levenshteinDistance(kwArr[j], wordArr[i + j]) > maxTolerance) { matched = false; break; } else { matchedStr.push(wordArr[i + j].replace(/([\[\]\(\)\^\$\.\+\*\?\|\{\}\-])/g, "\\$1")); } } if (matched) { break; } } if (matched) { let wordMatch = text.match(new RegExp(`(\\b|\\s)(` + matchedStr.join(gapStr + "+") + `)(\\b|\\s)`, "i")); if (wordMatch) { let content = wordMatch[2]; len = content.length; pos = wordMatch.index + wordMatch[1].length; } } return {matched: matched, pos: pos, len: len}; } }); })();