diff --git a/app/assets/javascripts/components/AutocompleteTagResult.tsx b/app/assets/javascripts/components/AutocompleteTagResult.tsx
index d732541af..d2c43fbea 100644
--- a/app/assets/javascripts/components/AutocompleteTagResult.tsx
+++ b/app/assets/javascripts/components/AutocompleteTagResult.tsx
@@ -1,4 +1,5 @@
import { AppState } from '@/ui_models/app_state';
+import { splitQueryInString } from '@/utils/stringUtils';
import { SNTag } from '@standardnotes/snjs';
import { observer } from 'mobx-react-lite';
import { useEffect, useRef } from 'preact/hooks';
@@ -71,7 +72,7 @@ export const AutocompleteTagResult = observer(
useEffect(() => {
if (focusedTagResultUuid === tagResult.uuid) {
- tagResultRef.current!.focus();
+ tagResultRef.current?.focus();
appState.noteTags.setFocusedTagResultUuid(undefined);
}
}, [appState.noteTags, focusedTagResultUuid, tagResult]);
@@ -92,9 +93,8 @@ export const AutocompleteTagResult = observer(
{prefixTitle && {prefixTitle}}
{autocompleteSearchQuery === ''
? title
- : title
- .split(new RegExp(`(${autocompleteSearchQuery})`, 'gi'))
- .map((substring, index) => (
+ : splitQueryInString(title, autocompleteSearchQuery).map(
+ (substring, index) => (
{substring}
- ))}
+ )
+ )}
);
diff --git a/app/assets/javascripts/utils/stringUtils.spec.ts b/app/assets/javascripts/utils/stringUtils.spec.ts
new file mode 100644
index 000000000..90bea9504
--- /dev/null
+++ b/app/assets/javascripts/utils/stringUtils.spec.ts
@@ -0,0 +1,65 @@
+import {
+ getIndexOfQueryInString,
+ splitQueryInString,
+ splitRangeWithinString,
+} from './stringUtils';
+
+describe('string utils', () => {
+ describe('splitRangeWithinString', () => {
+ it('should return whole string if range is invalid or out of bounds', () => {
+ const string = 'test-string';
+
+ const outOfBoundsStartResult = splitRangeWithinString(string, 15, 0);
+ expect(outOfBoundsStartResult).toStrictEqual(['test-string']);
+
+ const outOfBoundsEndResult = splitRangeWithinString(string, 0, -15);
+ expect(outOfBoundsEndResult).toStrictEqual(['test-string']);
+
+ const invalidRangeResult = splitRangeWithinString(string, 15, 0);
+ expect(invalidRangeResult).toStrictEqual(['test-string']);
+ });
+
+ it('should return split string if range is valid', () => {
+ const string = 'test-string';
+
+ const case1 = splitRangeWithinString(string, 0, 3);
+ expect(case1).toStrictEqual(['tes', 't-string']);
+
+ const case2 = splitRangeWithinString(string, 2, 6);
+ expect(case2).toStrictEqual(['te', 'st-s', 'tring']);
+
+ const case3 = splitRangeWithinString(string, 4, 9);
+ expect(case3).toStrictEqual(['test', '-stri', 'ng']);
+ });
+ });
+
+ describe('getIndexOfQueryInString', () => {
+ it('should get correct index of query in string', () => {
+ const string = 'tEsT-sTrInG';
+
+ const indexOfQuery1 = getIndexOfQueryInString(string, 'tRi');
+ expect(indexOfQuery1).toBe(6);
+
+ const indexOfQuery2 = getIndexOfQueryInString(string, 'StR');
+ expect(indexOfQuery2).toBe(5);
+
+ const indexOfQuery3 = getIndexOfQueryInString(string, 'stringUtils');
+ expect(indexOfQuery3).toBe(-1);
+ });
+ });
+
+ describe('splitQueryInString', () => {
+ it('should split string if it includes the query', () => {
+ const string = 'TeSt-StRiNg';
+
+ const query1Result = splitQueryInString(string, 'T-sTr');
+ expect(query1Result).toStrictEqual(['TeS', 't-StR', 'iNg']);
+
+ const query2Result = splitQueryInString(string, 'InG');
+ expect(query2Result).toStrictEqual(['TeSt-StR', 'iNg']);
+
+ const query3Result = splitQueryInString(string, 'invalid query');
+ expect(query3Result).toStrictEqual(['TeSt-StRiNg']);
+ });
+ });
+});
diff --git a/app/assets/javascripts/utils/stringUtils.ts b/app/assets/javascripts/utils/stringUtils.ts
new file mode 100644
index 000000000..f7d29eaa8
--- /dev/null
+++ b/app/assets/javascripts/utils/stringUtils.ts
@@ -0,0 +1,39 @@
+export const splitRangeWithinString = (
+ string: string,
+ start: number,
+ end: number
+) => {
+ const isStartOutOfBounds = start > string.length || start < 0;
+ const isEndOutOfBounds = end > string.length || end < 0;
+ const isInvalidRange = start > end;
+
+ if (isStartOutOfBounds || isEndOutOfBounds || isInvalidRange) {
+ return [string];
+ } else {
+ return [
+ string.slice(0, start),
+ string.slice(start, end),
+ string.slice(end),
+ ].filter((slice) => slice.length > 0);
+ }
+};
+
+export const getIndexOfQueryInString = (string: string, query: string) => {
+ const lowercasedTitle = string.toLowerCase();
+ const lowercasedQuery = query.toLowerCase();
+ return lowercasedTitle.indexOf(lowercasedQuery);
+};
+
+export const splitQueryInString = (string: string, query: string) => {
+ const indexOfQueryInTitle = getIndexOfQueryInString(string, query);
+
+ if (indexOfQueryInTitle < 0) {
+ return [string];
+ }
+
+ return splitRangeWithinString(
+ string,
+ indexOfQueryInTitle,
+ indexOfQueryInTitle + query.length
+ );
+};
diff --git a/package.json b/package.json
index c7e811a57..d0cff8b22 100644
--- a/package.json
+++ b/package.json
@@ -70,11 +70,11 @@
"@reach/listbox": "^0.16.2",
"@reach/tooltip": "^0.16.2",
"@standardnotes/components": "1.7.12",
- "@standardnotes/features": "1.35.0",
+ "@standardnotes/features": "1.35.1",
"@standardnotes/filepicker": "1.10.0",
"@standardnotes/settings": "1.13.1",
"@standardnotes/sncrypto-web": "1.8.0",
- "@standardnotes/snjs": "2.86.2",
+ "@standardnotes/snjs": "2.86.4",
"@zip.js/zip.js": "^2.4.7",
"mobx": "^6.5.0",
"mobx-react-lite": "^3.3.0",
diff --git a/yarn.lock b/yarn.lock
index d31fb90de..fd42fa63d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2345,6 +2345,13 @@
dependencies:
"@standardnotes/common" "^1.16.0"
+"@standardnotes/applications@^1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@standardnotes/applications/-/applications-1.2.3.tgz#03f9067c20fb2960560bc084e9e37981044ee373"
+ integrity sha512-YiQZzjiApTGSOUosidks99O3I1AQv5gzJ12+yc99GKYno564nn0QHOUMKqIgMJsEG+Yp6gJ2QpgzC/0hmUUlVQ==
+ dependencies:
+ "@standardnotes/common" "^1.16.1"
+
"@standardnotes/auth@^3.17.7":
version "3.17.7"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.17.7.tgz#cb53b415c9a5a61a8721d7099d0d13d8ab79b597"
@@ -2353,25 +2360,46 @@
"@standardnotes/common" "^1.16.0"
jsonwebtoken "^8.5.1"
+"@standardnotes/auth@^3.17.8":
+ version "3.17.8"
+ resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.17.8.tgz#b74fb5a8b6b7176d2ea8d175dcd76532caa5d367"
+ integrity sha512-/VkfzhYJkYW7cct0O3How5UVs4o3CG331uOrQawabeAUVXNNqnKkofFzsDX+LEhLGxwR2iwxRqszzEwSsOj0OA==
+ dependencies:
+ "@standardnotes/common" "^1.16.1"
+ jsonwebtoken "^8.5.1"
+
"@standardnotes/common@^1.16.0":
version "1.16.0"
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.16.0.tgz#d80c00a4e7fefa6ac03946abaaffccb2fe29e882"
integrity sha512-C4Hy+G4GsKrTF4SqHrZmPu/S6eQbQC+VqlAp6BcZcEofwP7ZHVAi1todxAEED+5wPXz5VT+ctCUeoJpHtikHfA==
+"@standardnotes/common@^1.16.1":
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.16.1.tgz#428bbd7c0ca2293e97dd0bcb033c20b3da093c16"
+ integrity sha512-4Uo1f0dpbWWiOVku/zxOQ7vhE5VnJbmPpWADA92Q0yTL94KRi2R39cjb/sazQqX7RVX03LpYaswyXUmH8zas1w==
+
"@standardnotes/components@1.7.12":
version "1.7.12"
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.12.tgz#c87c3c8d90c0030b711d4f59aae47e14c745ea2a"
integrity sha512-geE3xpBagZFJCucvFymUK4qIWT45nb8OXGW8Ck0EJothVSbz4rF3MJJ/W1pvI6+kYKbT12AaUoGecL6uKxi+1Q==
-"@standardnotes/domain-events@^2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.25.0.tgz#73d003967cb2b8af8afc6d26360d9af99db928fe"
- integrity sha512-o9k07Urfs8u04P9TGS7vA/ol5PQ6pi7BeejHWbWInCQ/kXgHcVHJkWBPsEPRxDboLzhju7ivbzUMYlEBmkj1vw==
+"@standardnotes/domain-events@^2.25.1":
+ version "2.25.1"
+ resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.25.1.tgz#3a9fa51243a036cae30beba8b83b369d598ecc80"
+ integrity sha512-4rOojrgVYT5QZM8kSSumP5eqGZdUjH8rs9Y7IiW5zSVi2Idq78WhLu33WTDbRn47WTsDZGkeQSRxaPPkBLabmg==
dependencies:
- "@standardnotes/auth" "^3.17.7"
- "@standardnotes/features" "^1.35.0"
+ "@standardnotes/auth" "^3.17.8"
+ "@standardnotes/features" "^1.35.1"
-"@standardnotes/features@1.35.0", "@standardnotes/features@^1.35.0":
+"@standardnotes/features@1.35.1", "@standardnotes/features@^1.35.1":
+ version "1.35.1"
+ resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.35.1.tgz#1c641dc0c55cc0f90321101bb05066d008b89c92"
+ integrity sha512-nNyo83k+9UPOKALqJSVtqTaDq0hWHSQYlRBBQ1Vx7snC9Rxrlsgq2EO2k16ZIsRZiJbUlI0eK8uWwMey4p+L4Q==
+ dependencies:
+ "@standardnotes/auth" "^3.17.8"
+ "@standardnotes/common" "^1.16.1"
+
+"@standardnotes/features@^1.35.0":
version "1.35.0"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.35.0.tgz#2f3d1f1e7831ec86cf265227f4b61fbf5e245aa2"
integrity sha512-ysibgtv9KoxYsD7f77Vn4DmViDnWeLANGtGwn6uUBnQbKHzuClFmSqBfAB7rmpu+1IRc+dfL8TlkPIwY2xvWqw==
@@ -2394,6 +2422,16 @@
"@standardnotes/features" "^1.35.0"
"@standardnotes/utils" "^1.4.0"
+"@standardnotes/payloads@^1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@standardnotes/payloads/-/payloads-1.4.14.tgz#f89eebc1d3920d4759b38bfad892f821d48cf1bd"
+ integrity sha512-Ym9zH6IykNLNk+QrmRAaPCq5n5trLNRbWppdQmMWnQyeOOiNStd+pvVsFRCVumxDImova8KrFEdGmnTnzns+TQ==
+ dependencies:
+ "@standardnotes/applications" "^1.2.3"
+ "@standardnotes/common" "^1.16.1"
+ "@standardnotes/features" "^1.35.1"
+ "@standardnotes/utils" "^1.4.1"
+
"@standardnotes/responses@1.3.14", "@standardnotes/responses@^1.3.14":
version "1.3.14"
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.3.14.tgz#6a5d34490cb7a26dce0af23da8aa832aac7ca3f7"
@@ -2404,7 +2442,17 @@
"@standardnotes/features" "^1.35.0"
"@standardnotes/payloads" "^1.4.13"
-"@standardnotes/services@1.6.3", "@standardnotes/services@^1.6.3":
+"@standardnotes/responses@^1.3.15":
+ version "1.3.15"
+ resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.3.15.tgz#dd56c96fce38213603b8eaf61383e7c3ea0773d0"
+ integrity sha512-zJMeRxmq19RID8fLwgndVYdwMNO+y6EDTjHfKzO/ndmHY6CefNgsrVQLgcG26CIfv3Ivg4LpJLwclAmOB70Sjw==
+ dependencies:
+ "@standardnotes/auth" "^3.17.8"
+ "@standardnotes/common" "^1.16.1"
+ "@standardnotes/features" "^1.35.1"
+ "@standardnotes/payloads" "^1.4.14"
+
+"@standardnotes/services@1.6.3":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.6.3.tgz#27223ea7bb28334e3118ba9351c752b3584f3c63"
integrity sha512-p5wjHF0xaInMGQE2wVelboGp+YAYZtIQMOkA++hrIWuwziH+G+Hq2NthHtopx72kGzK8VZ+1Ay+9CRwXx+SMrg==
@@ -2414,6 +2462,16 @@
"@standardnotes/responses" "^1.3.14"
"@standardnotes/utils" "^1.4.0"
+"@standardnotes/services@^1.6.4":
+ version "1.6.4"
+ resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.6.4.tgz#132836c70ed68302759e9607595c5390e8a847d5"
+ integrity sha512-g7NGoN1IuXYIbnIkqaQo+J2vjHtk6Yy+suI/o8as0oDAvl4ygVykEZTx6tnl4GjQblLhJK4j9za5+V9uCn1sQg==
+ dependencies:
+ "@standardnotes/applications" "^1.2.3"
+ "@standardnotes/common" "^1.16.1"
+ "@standardnotes/responses" "^1.3.15"
+ "@standardnotes/utils" "^1.4.1"
+
"@standardnotes/settings@1.13.1", "@standardnotes/settings@^1.13.1":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.13.1.tgz#c0f8558079da178667e7addd569b6953cd5e6bc2"
@@ -2433,22 +2491,22 @@
buffer "^6.0.3"
libsodium-wrappers "^0.7.9"
-"@standardnotes/snjs@2.86.2":
- version "2.86.2"
- resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.86.2.tgz#8fd238cd49a5a2a47a20a9615d204da609000595"
- integrity sha512-tM+QHmnKHf3bNYW5FHZNmW16dpaU43tjzjdD9fGLbBvWXVF+6LrZXkYopNPD3TihoIzmuR3AZddfp99UGDZyNQ==
+"@standardnotes/snjs@2.86.4":
+ version "2.86.4"
+ resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.86.4.tgz#d0cc9d1e1e890d192bf252b19f06775902bfc6f0"
+ integrity sha512-Cp9nQ7a+Tjr/PVp72+qAgOaDetjyItuc9SwvnsPp4SlKku4KtCsIRQODPZfpF9ifxV/QIii+ZQbqk2ankHUQ0g==
dependencies:
- "@standardnotes/applications" "^1.2.2"
- "@standardnotes/auth" "^3.17.7"
- "@standardnotes/common" "^1.16.0"
- "@standardnotes/domain-events" "^2.25.0"
- "@standardnotes/features" "^1.35.0"
- "@standardnotes/payloads" "^1.4.13"
- "@standardnotes/responses" "^1.3.14"
- "@standardnotes/services" "^1.6.3"
+ "@standardnotes/applications" "^1.2.3"
+ "@standardnotes/auth" "^3.17.8"
+ "@standardnotes/common" "^1.16.1"
+ "@standardnotes/domain-events" "^2.25.1"
+ "@standardnotes/features" "^1.35.1"
+ "@standardnotes/payloads" "^1.4.14"
+ "@standardnotes/responses" "^1.3.15"
+ "@standardnotes/services" "^1.6.4"
"@standardnotes/settings" "^1.13.1"
"@standardnotes/sncrypto-common" "^1.7.3"
- "@standardnotes/utils" "^1.4.0"
+ "@standardnotes/utils" "^1.4.1"
"@standardnotes/stylekit@5.17.0":
version "5.17.0"
@@ -2472,6 +2530,15 @@
dompurify "^2.3.6"
lodash "^4.17.21"
+"@standardnotes/utils@^1.4.1":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@standardnotes/utils/-/utils-1.4.1.tgz#ce185e1ace6b1d4db43e7b7025747c0b7427438f"
+ integrity sha512-ouLfnVdwXQOBjxuxSJ9kPZmC9K+Qqq66V/Jlp0W1n6l6ywhBeS6Yv+oGTG4yvDNZeyP1myUF6VY4F9hJ8Y9wRQ==
+ dependencies:
+ "@standardnotes/common" "^1.16.1"
+ dompurify "^2.3.6"
+ lodash "^4.17.21"
+
"@svgr/babel-plugin-add-jsx-attribute@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18"