Skip to content

Commit 9e308c6

Browse files
Update tag hash algorithm to make it consistent with other platforms (#3017)
Co-authored-by: Kat Hagan <[email protected]>
1 parent 0572e35 commit 9e308c6

File tree

5 files changed

+41
-5
lines changed

5 files changed

+41
-5
lines changed

RELEASE-NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [v2.23.1]
44

5+
### Fixes
6+
7+
- Fixed inconsistent tag encoding of special characters that led to duplicate tags across platforms [#3017](https://github.com/Automattic/simplenote-electron/pull/3017)
8+
59
### Other Changes
610

711
- Updated dependencies [#3286](https://github.com/Automattic/simplenote-electron/pull/3286)

lib/tag-field/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import EmailToolTip from '../tag-email-tooltip';
88
import TagChip from '../components/tag-chip';
99
import TagInput from '../tag-input';
1010
import classNames from 'classnames';
11-
import { tagHashOf } from '../utils/tag-hash';
11+
import { tagHashOf, MAX_TAG_HASH_LENGTH } from '../utils/tag-hash';
1212
import { noteCanonicalTags } from '../state/selectors';
1313

1414
import type * as S from '../state';
@@ -203,6 +203,14 @@ export class TagField extends Component<Props, OwnState> {
203203
this.hideEmailTooltip();
204204
}
205205

206+
if (
207+
tagHashOf(this.state.tagInput as T.TagName).length >
208+
MAX_TAG_HASH_LENGTH &&
209+
String.fromCharCode(e.which).match(/([^,\s])/g)
210+
) {
211+
e.preventDefault();
212+
}
213+
206214
return this.interceptKeys(e);
207215
};
208216

lib/tag-input/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import React, {
88
import { connect } from 'react-redux';
99
import { get, identity, invoke, noop } from 'lodash';
1010

11+
import { tagHashOf, MAX_TAG_HASH_LENGTH } from '../utils/tag-hash';
12+
1113
import type * as S from '../state';
1214
import type * as T from '../types';
1315

@@ -221,10 +223,14 @@ export class TagInput extends Component<Props> {
221223
clipboardText = window.clipboardData.getData('Text'); // IE11
222224
}
223225

224-
// Convert spaces/commans to submitted tags
226+
// Convert spaces/commas to submitted tags
225227
const tags = clipboardText.split(/\s|,|\n/);
226-
const submittedTags = tags.slice(0, tags.length - 1);
227-
const [pendingTag] = tags.slice(tags.length - 1);
228+
const filteredTags = tags.filter(
229+
(tag) => tagHashOf(tag).length <= MAX_TAG_HASH_LENGTH
230+
);
231+
const submittedTags = filteredTags.slice(0, filteredTags.length - 1);
232+
const [pendingTag] = filteredTags.slice(filteredTags.length - 1);
233+
228234
submittedTags.filter(Boolean).forEach(this.props.onSelect);
229235
this.onInput(pendingTag, { moveCaretToEndOfValue: true });
230236

lib/tag-list/input.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import actions from '../state/actions';
66

77
import type * as S from '../state';
88
import type * as T from '../types';
9+
import { tagHashOf, MAX_TAG_HASH_LENGTH } from '../utils/tag-hash';
910

1011
type OwnProps = {
1112
editable: boolean;
@@ -35,9 +36,23 @@ export const TagListInput: FunctionComponent<Props> = ({
3536
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
3637
setTagName(event.target.value.replace(/[\s,]/g, ''));
3738
};
39+
40+
const onKeyDown = (event: React.KeyboardEvent) => {
41+
if (
42+
tagHashOf(enteredTagName).length >= MAX_TAG_HASH_LENGTH &&
43+
String.fromCharCode(event.which).match(/([^,\s])/g)
44+
) {
45+
event.preventDefault();
46+
}
47+
};
48+
3849
const onDone = (event: React.FocusEvent<HTMLInputElement>) => {
3950
const newTagName = event.target?.value.trim() as T.TagName;
4051

52+
if (tagHashOf(newTagName).length > MAX_TAG_HASH_LENGTH) {
53+
setTagName(tagName);
54+
return;
55+
}
4156
if (newTagName && newTagName !== tagName) {
4257
renameTag(tagName, newTagName);
4358
} else {
@@ -57,6 +72,7 @@ export const TagListInput: FunctionComponent<Props> = ({
5772
onChange={onChange}
5873
onBlur={onDone}
5974
spellCheck={false}
75+
onKeyDown={onKeyDown}
6076
/>
6177
) : (
6278
<button className={classes} onClick={onClick}>

lib/utils/tag-hash.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type * as T from '../types';
22

3+
export const MAX_TAG_HASH_LENGTH = 256;
4+
35
export const tagHashOf = (tagName: T.TagName): T.TagHash => {
46
const normalized = tagName.normalize('NFC');
57
const lowercased = normalized.toLocaleLowerCase('en-US');
68
const encoded = encodeURIComponent(lowercased);
79

810
return encoded.replace(
9-
/[!'()*]/g,
11+
/[!'()*\-_~.]/g,
1012
(c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()
1113
) as T.TagHash;
1214
};

0 commit comments

Comments
 (0)