1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Improve search keyword highlighting

This commit is contained in:
Laurent Cozic 2019-01-17 19:01:35 +00:00
parent 8dc0b34fdc
commit 96cd56548e
9 changed files with 90 additions and 64 deletions

1
.gitignore vendored
View File

@ -40,4 +40,5 @@ Tools/github_oauth_token.txt
_releases _releases
ReactNativeClient/lib/csstojs/ ReactNativeClient/lib/csstojs/
ElectronClient/app/gui/note-viewer/fonts/ ElectronClient/app/gui/note-viewer/fonts/
ElectronClient/app/gui/note-viewer/lib.js
Tools/commit_hook.txt Tools/commit_hook.txt

View File

@ -2,6 +2,7 @@ const fs = require('fs-extra');
const spawnSync = require('child_process').spawnSync; const spawnSync = require('child_process').spawnSync;
const babelPath = __dirname + '/node_modules/.bin/babel' + (process.platform === 'win32' ? '.cmd' : ''); const babelPath = __dirname + '/node_modules/.bin/babel' + (process.platform === 'win32' ? '.cmd' : '');
const basePath = __dirname + '/../..';
const guiPath = __dirname + '/gui'; const guiPath = __dirname + '/gui';
function fileIsNewerThan(path1, path2) { function fileIsNewerThan(path1, path2) {
@ -27,7 +28,7 @@ fs.readdirSync(guiPath).forEach((filename) => {
if (fileIsNewerThan(jsxPath, jsPath)) { if (fileIsNewerThan(jsxPath, jsPath)) {
console.info('Compiling ' + jsxPath + '...'); console.info('Compiling ' + jsxPath + '...');
const result = spawnSync(babelPath, ['--presets', 'react', '--out-file',jsPath, jsxPath]); const result = spawnSync(babelPath, ['--presets', 'react', '--out-file', jsPath, jsxPath]);
if (result.status !== 0) { if (result.status !== 0) {
const msg = []; const msg = [];
if (result.stdout) msg.push(result.stdout.toString()); if (result.stdout) msg.push(result.stdout.toString());
@ -38,3 +39,5 @@ fs.readdirSync(guiPath).forEach((filename) => {
} }
} }
}); });
fs.copySync(basePath + '/ReactNativeClient/lib/string-utils-common.js', __dirname + '/gui/note-viewer/lib.js');

View File

@ -37,6 +37,7 @@
<div id="hlScriptContainer"></div> <div id="hlScriptContainer"></div>
<div id="markScriptContainer"></div> <div id="markScriptContainer"></div>
<div id="content" ondragstart="return false;" ondrop="return false;"></div> <div id="content" ondragstart="return false;" ondrop="return false;"></div>
<script src="./lib.js"></script>
<script> <script>
const contentElement = document.getElementById('content'); const contentElement = document.getElementById('content');
@ -226,10 +227,6 @@
function setMarkers(keywords, options = null) { function setMarkers(keywords, options = null) {
if (!options) options = {}; if (!options) options = {};
// TODO: It should highlight queries without accents - eg "penche*" should highlight "penchés"
// TODO: It should highlight Chinese, Japanese characters, etc.
// TODO: It should highlight Russian
// TODO: not working - "oue*" doesn't highlight "ouéé"
// TODO: Search engine support to mobile app (sync tables) // TODO: Search engine support to mobile app (sync tables)
// TODO: Update everywhere that uses allParsedQueryTerms to support new format // TODO: Update everywhere that uses allParsedQueryTerms to support new format
// TODO: Add support for scriptType on mobile and CLI // TODO: Add support for scriptType on mobile and CLI
@ -275,12 +272,20 @@
const isBasicSearch = ['ja', 'zh', 'ko'].indexOf(keyword.scriptType) >= 0; const isBasicSearch = ['ja', 'zh', 'ko'].indexOf(keyword.scriptType) >= 0;
if (keyword.type === 'regex') { if (keyword.type === 'regex') {
let regexString = '\\b' + keyword.value + '\\b'; const b = '[' + pregQuote(' \t\n\r,.,+-*?!={}<>|:"\'()[]') + ']+';
// The capturing groups are a hack to go around the strange behaviour of the ignoreGroups property. What we want is to
// exclude the first and last matches (the boundaries). What ignoreGroups does is ignore the first X groups. So
// we put the first boundary and the keyword inside a group, that way the first groups is ignored (ignoreGroups = 1)
// the second is included. And the last boundary is dropped because it's not in any group (it's important NOT to
// put this one in a group because otherwise it cannot be excluded).
let regexString = '(' + b + ')' + '(' + replaceRegexDiacritics(keyword.value) + ')' + b;
if (isBasicSearch) regexString = keyword.value; if (isBasicSearch) regexString = keyword.value;
mark_.markRegExp(new RegExp(regexString, 'gmi'), { mark_.markRegExp(new RegExp(regexString, 'gmi'), {
each: onEachElement, each: onEachElement,
acrossElements: true, acrossElements: true,
ignoreGroups: 1,
}); });
} else { } else {
let accuracy = keyword.accuracy ? keyword.accuracy : 'exactly'; let accuracy = keyword.accuracy ? keyword.accuracy : 'exactly';

View File

@ -4594,7 +4594,7 @@
}, },
"nan": { "nan": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
}, },
"nanomatch": { "nanomatch": {

View File

@ -8,8 +8,8 @@
"pack": "node_modules/.bin/electron-builder --dir", "pack": "node_modules/.bin/electron-builder --dir",
"dist": "node_modules/.bin/electron-builder", "dist": "node_modules/.bin/electron-builder",
"publish": "build -p always", "publish": "build -p always",
"postinstall": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts && install-app-deps", "postinstall": "node compile.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts && install-app-deps",
"compile": "node compile-jsx.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts" "compile": "node compile.js && node compile-package-info.js && node ../../Tools/copycss.js --copy-fonts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

11
ElectronClient/package-lock.json generated Normal file
View File

@ -0,0 +1,11 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"diacritics": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
"integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E="
}
}
}

View File

@ -226,8 +226,8 @@ class SearchEngine {
let regexString = pregQuote(term); let regexString = pregQuote(term);
if (regexString[regexString.length - 1] === '*') { if (regexString[regexString.length - 1] === '*') {
// regexString = regexString.substr(0, regexString.length - 2) + '[^' + pregQuote(' \t\n\r,.,+-*?!={}<>|:"\'()[]') + ']' + '*'; regexString = regexString.substr(0, regexString.length - 2) + '[^' + pregQuote(' \t\n\r,.,+-*?!={}<>|:"\'()[]') + ']' + '*?';
regexString = regexString.substr(0, regexString.length - 2) + '.*?'; // regexString = regexString.substr(0, regexString.length - 2) + '.*?';
} }
return regexString; return regexString;

View File

@ -0,0 +1,55 @@
function pregQuote(str, delimiter = '') {
return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
}
function replaceRegexDiacritics(regexString) {
if (!regexString) return '';
const diacriticReplacements = {
'a': '[aàáâãäåāą]',
'A': '[AÀÁÂÃÄÅĀĄ]',
'c': '[cçćč]',
'C': '[CÇĆČ]',
'd': '[dđď]',
'D': '[DĐĎ]',
'e': '[eèéêëěēę]',
'E': '[EÈÉÊËĚĒĘ]',
'i': '[iìíîïī]',
'I': '[IÌÍÎÏĪ]',
'l': '[lł]',
'L': '[LŁ]',
'n': '[nñňń]',
'N': '[NÑŇŃ]',
'o': '[oòóôõöøō]',
'O': '[OÒÓÔÕÖØŌ]',
'r': '[rř]',
'R': '[RŘ]',
's': '[sšś]',
'S': '[SŠŚ]',
't': '[tť]',
'T': '[TŤ]',
'u': '[uùúûüůū]',
'U': '[UÙÚÛÜŮŪ]',
'y': '[yÿý]',
'Y': '[YŸÝ]',
'z': '[zžżź]',
'Z': '[ZŽŻŹ]',
};
let output = '';
for (let i = 0; i < regexString.length; i++) {
let c = regexString[i];
const r = diacriticReplacements[c];
if (r) {
output += r;
} else {
output += c;
}
}
return output;
}
if (typeof module !== 'undefined') {
module.exports = { pregQuote, replaceRegexDiacritics };
}

View File

@ -223,10 +223,6 @@ function escapeHtml(s) {
.replace(/'/g, "&#039;"); .replace(/'/g, "&#039;");
} }
function pregQuote(str, delimiter = '') {
return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
}
function surroundKeywords(keywords, text, prefix, suffix) { function surroundKeywords(keywords, text, prefix, suffix) {
if (!keywords.length) return text; if (!keywords.length) return text;
@ -242,54 +238,6 @@ function surroundKeywords(keywords, text, prefix, suffix) {
return text.replace(re, prefix + '$1' + suffix); return text.replace(re, prefix + '$1' + suffix);
} }
function replaceRegexDiacritics(regexString) {
if (!regexString) return '';
const diacriticReplacements = {
'a': '[aàáâãäåāą]',
'A': '[AÀÁÂÃÄÅĀĄ]',
'c': '[cçćč]',
'C': '[CÇĆČ]',
'd': '[dđď]',
'D': '[DĐĎ]',
'e': '[eèéêëěēę]',
'E': '[EÈÉÊËĚĒĘ]',
'i': '[iìíîïī]',
'I': '[IÌÍÎÏĪ]',
'l': '[lł]',
'L': '[LŁ]',
'n': '[nñňń]',
'N': '[NÑŇŃ]',
'o': '[oòóôõöøō]',
'O': '[OÒÓÔÕÖØŌ]',
'r': '[rř]',
'R': '[RŘ]',
's': '[sšś]',
'S': '[SŠŚ]',
't': '[tť]',
'T': '[TŤ]',
'u': '[uùúûüůū]',
'U': '[UÙÚÛÜŮŪ]',
'y': '[yÿý]',
'Y': '[YŸÝ]',
'z': '[zžżź]',
'Z': '[ZŽŻŹ]',
};
let output = '';
for (let i = 0; i < regexString.length; i++) {
let c = regexString[i];
const r = diacriticReplacements[c];
if (r) {
output += r;
} else {
output += c;
}
}
return output;
}
const REGEX_JAPANESE = /[\u3000-\u303f]|[\u3040-\u309f]|[\u30a0-\u30ff]|[\uff00-\uff9f]|[\u4e00-\u9faf]|[\u3400-\u4dbf]/; const REGEX_JAPANESE = /[\u3000-\u303f]|[\u3040-\u309f]|[\u30a0-\u30ff]|[\uff00-\uff9f]|[\u4e00-\u9faf]|[\u3400-\u4dbf]/;
const REGEX_CHINESE = /[\u4e00-\u9fff]|[\u3400-\u4dbf]|[\u{20000}-\u{2a6df}]|[\u{2a700}-\u{2b73f}]|[\u{2b740}-\u{2b81f}]|[\u{2b820}-\u{2ceaf}]|[\uf900-\ufaff]|[\u3300-\u33ff]|[\ufe30-\ufe4f]|[\uf900-\ufaff]|[\u{2f800}-\u{2fa1f}]/u; const REGEX_CHINESE = /[\u4e00-\u9fff]|[\u3400-\u4dbf]|[\u{20000}-\u{2a6df}]|[\u{2a700}-\u{2b73f}]|[\u{2b740}-\u{2b81f}]|[\u{2b820}-\u{2ceaf}]|[\uf900-\ufaff]|[\u3300-\u33ff]|[\ufe30-\ufe4f]|[\uf900-\ufaff]|[\u{2f800}-\u{2fa1f}]/u;
const REGEX_KOREAN = /[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/; const REGEX_KOREAN = /[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/;
@ -301,4 +249,7 @@ function scriptType(s) {
return 'en'; return 'en';
} }
module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase, urlDecode, escapeHtml, pregQuote, surroundKeywords, scriptType, replaceRegexDiacritics }; module.exports = Object.assign(
{ removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase, urlDecode, escapeHtml, surroundKeywords, scriptType },
require('./string-utils-common.js'),
);