Skip to content

Commit cd365cd

Browse files
feat: replace anchorme with linkify-it (#25)
1 parent 5bbba37 commit cd365cd

File tree

3 files changed

+63
-44
lines changed

3 files changed

+63
-44
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@storybook/addons": "^6.1.21",
7373
"@storybook/react": "^6.1.21",
7474
"@types/d3": "^6.3.0",
75+
"@types/linkify-it": "^3.0.2",
7576
"@types/react": "^17.0.3",
7677
"@types/react-dom": "^17.0.2",
7778
"@types/react-virtualized": "^9.21.11",
@@ -101,12 +102,12 @@
101102
"@types/lodash": "^4.0.6",
102103
"@types/react-virtualized-auto-sizer": "^1.0.0",
103104
"@types/react-window": "^1.8.2",
104-
"anchorme": "^2.1.2",
105105
"d3": "^6.6.0",
106106
"date-fns": "^2.19.0",
107107
"dompurify": "^2.2.9",
108108
"downshift": "^6.1.1",
109109
"immer": "^9.0.12",
110+
"linkify-it": "^3.0.3",
110111
"lodash": "^4.17.21",
111112
"lodash-es": "^4.17.21",
112113
"match-sorter": "^6.3.0",

src/components/cell.tsx

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React, { useEffect } from 'react';
22
import { areEqual } from 'react-window';
33
import tw, { TwStyle } from 'twin.macro';
4-
import anchorme from 'anchorme';
4+
import Linkify from 'linkify-it';
55
import { cellTypeMap } from '../store';
66
import { DashIcon, DiffModifiedIcon, PlusIcon } from '@primer/octicons-react';
77
import DOMPurify from 'dompurify';
88
import { EditableCell } from './editable-cell';
99

10+
const linkify = Linkify().add('ftp:', null).add('mailto:', null);
11+
1012
interface CellProps {
1113
type: string;
1214
value: any;
@@ -47,37 +49,39 @@ export const Cell = React.memo(function (props: CellProps) {
4749
onFocusChange,
4850
background,
4951
style = {},
50-
onMouseEnter = () => { },
52+
onMouseEnter = () => {},
5153
} = props;
5254

5355
// @ts-ignore
5456
const cellInfo = cellTypeMap[type];
5557

56-
const { cell: CellComponent } = cellInfo || {}
58+
const { cell: CellComponent } = cellInfo || {};
5759

58-
const displayValue = (formattedValue || value || "").toString();
60+
const displayValue = (formattedValue || value || '').toString();
5961
const isLongValue = (displayValue || '').length > 23;
60-
const stringWithLinks = React.useMemo(
61-
() => displayValue ? (
62-
DOMPurify.sanitize(
63-
anchorme({
64-
input: displayValue + '',
65-
options: {
66-
attributes: {
67-
target: '_blank',
68-
rel: 'noopener',
69-
},
70-
},
71-
})
72-
)
73-
) : "",
74-
[value]
75-
)
62+
const stringWithLinks = React.useMemo(() => {
63+
if (!displayValue) return '';
64+
65+
const sanitized = DOMPurify.sanitize(displayValue);
66+
// Does the sanitized string contain any links?
67+
if (!linkify.test(sanitized)) return sanitized;
68+
69+
// If so, we need to linkify it.
70+
const matches = linkify.match(sanitized);
71+
72+
// If there are no matches, we can just return the sanitized string.
73+
if (!matches || matches.length === 0) return sanitized;
74+
75+
// Otherwise, let's naively use the first match.
76+
return `<a href="${matches[0].url}" target="_blank" rel="noopener">
77+
${matches[0].url}
78+
</a>`;
79+
}, [value]);
7680

7781
useEffect(() => {
78-
if (!isFocused) return
79-
onMouseEnter()
80-
}, [isFocused])
82+
if (!isFocused) return;
83+
onMouseEnter();
84+
}, [isFocused]);
8185

8286
if (!cellInfo) return null;
8387

@@ -91,14 +95,15 @@ export const Cell = React.memo(function (props: CellProps) {
9195
'modified-row': DiffModifiedIcon,
9296
}[status || ''];
9397
const statusColor =
94-
isFirstColumn &&
95-
// @ts-ignore
96-
{
97-
new: 'text-green-400',
98-
old: 'text-pink-400',
99-
modified: 'text-yellow-500',
100-
'modified-row': 'text-yellow-500',
101-
}[status || ''] || ""
98+
(isFirstColumn &&
99+
// @ts-ignore
100+
{
101+
new: 'text-green-400',
102+
old: 'text-pink-400',
103+
modified: 'text-yellow-500',
104+
'modified-row': 'text-yellow-500',
105+
}[status || '']) ||
106+
'';
102107

103108
return (
104109
<div
@@ -109,12 +114,13 @@ export const Cell = React.memo(function (props: CellProps) {
109114
status === 'old' && tw`border-pink-200`,
110115
status === 'modified' && tw`border-yellow-200`,
111116
status === 'modified-row' && tw`border-gray-200`,
112-
!status && tw`border-gray-200`
117+
!status && tw`border-gray-200`,
113118
]}
114119
style={{
115120
...style,
116121
background: background || '#fff',
117-
}}>
122+
}}
123+
>
118124
<EditableCell
119125
value={rawValue}
120126
isEditable={isEditable}
@@ -123,7 +129,8 @@ export const Cell = React.memo(function (props: CellProps) {
123129
isFocused={isFocused}
124130
isExtraBlankRow={isExtraBlankRow}
125131
onFocusChange={onFocusChange}
126-
onRowDelete={onRowDelete}>
132+
onRowDelete={onRowDelete}
133+
>
127134
<CellInner
128135
value={value}
129136
isFirstColumn={isFirstColumn}
@@ -143,7 +150,6 @@ export const Cell = React.memo(function (props: CellProps) {
143150
);
144151
}, areEqual);
145152

146-
147153
const CellInner = React.memo(function CellInner({
148154
value,
149155
isFirstColumn,
@@ -176,7 +182,7 @@ const CellInner = React.memo(function CellInner({
176182
css={[
177183
tw`w-full h-full flex flex-none items-center px-4`,
178184
typeof value === 'undefined' ||
179-
(Number.isNaN(value) && tw`text-gray-300`),
185+
(Number.isNaN(value) && tw`text-gray-300`),
180186
]}
181187
onMouseEnter={() => onMouseEnter?.()}
182188
>
@@ -214,5 +220,5 @@ const CellInner = React.memo(function CellInner({
214220
</div>
215221
)}
216222
</div>
217-
)
218-
})
223+
);
224+
});

yarn.lock

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,6 +3251,11 @@
32513251
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
32523252
integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==
32533253

3254+
"@types/linkify-it@^3.0.2":
3255+
version "3.0.2"
3256+
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
3257+
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
3258+
32543259
"@types/lodash@^4.0.6":
32553260
version "4.14.171"
32563261
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b"
@@ -3837,11 +3842,6 @@ alphanum-sort@^1.0.0, alphanum-sort@^1.0.2:
38373842
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
38383843
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
38393844

3840-
anchorme@^2.1.2:
3841-
version "2.1.2"
3842-
resolved "https://registry.yarnpkg.com/anchorme/-/anchorme-2.1.2.tgz#4abc7e128a8a42d0036a61ebb9b18bbc032fa52a"
3843-
integrity sha512-2iPY3kxDDZvtRzauqKDb4v7a5sTF4GZ+esQTY8nGYvmhAtGTeFPMn4cRnvyWS1qmtPTP0Mv8hyLOp9l3ZzWMKg==
3844-
38453845
ansi-align@^3.0.0:
38463846
version "3.0.0"
38473847
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
@@ -9793,6 +9793,13 @@ lines-and-columns@^1.1.6:
97939793
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
97949794
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
97959795

9796+
linkify-it@^3.0.3:
9797+
version "3.0.3"
9798+
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
9799+
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
9800+
dependencies:
9801+
uc.micro "^1.0.1"
9802+
97969803
load-json-file@^4.0.0:
97979804
version "4.0.0"
97989805
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
@@ -14795,6 +14802,11 @@ ua-parser-js@^0.7.18:
1479514802
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
1479614803
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
1479714804

14805+
uc.micro@^1.0.1:
14806+
version "1.0.6"
14807+
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
14808+
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
14809+
1479814810
unbox-primitive@^1.0.1:
1479914811
version "1.0.1"
1480014812
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"

0 commit comments

Comments
 (0)