Skip to content

Commit e564868

Browse files
committed
refactor: tweak prop and class names for custom matchers
1 parent 3f91a09 commit e564868

File tree

5 files changed

+92
-128
lines changed

5 files changed

+92
-128
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"rules": {
2121
"class-methods-use-this": "off",
22+
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
2223
"import/extensions": ["error", "ignorePackages", {
2324
"js": "never",
2425
"mjs": "never",

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@
8181
},
8282
"release-it": {
8383
"git": {
84-
"commitMessage": "chore: release %s",
85-
"tagName": "v%s"
84+
"commitMessage": "chore: release ${version}",
85+
"tagName": "v${version}"
8686
},
8787
"npm": {
8888
"publish": true

src/CustomMatch.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Match, MatchConfig } from 'autolinker/dist/es2015';
2+
import { StyleProp, TextStyle } from 'react-native';
3+
4+
// The variadic arguments of a regex replacer function, wrapped in an array.
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
export type ReplacerArgs = [string, ...any[]];
7+
8+
export interface CustomMatcher {
9+
/* Regular expression pattern to match/link user-specified patterns */
10+
pattern: RegExp;
11+
/* Custom press handler for links of this type */
12+
onPress?: (match: CustomMatch) => void;
13+
/* Custom long-press handler for links of this type */
14+
onLongPress?: (match: CustomMatch) => void;
15+
/* Custom styling for links of this type */
16+
style?: StyleProp<TextStyle>;
17+
/* Custom type/identifier for use with match.getType() */
18+
type?: string;
19+
/* Custom function for extracting link text using regex replacer args */
20+
getLinkText?: (replacerArgs: ReplacerArgs) => string;
21+
/* Custom function for extracting link URL using regex replacer args */
22+
getLinkUrl?: (replacerArgs: ReplacerArgs) => string;
23+
}
24+
25+
export interface CustomMatchConfig extends MatchConfig {
26+
matcher: CustomMatcher;
27+
replacerArgs: ReplacerArgs;
28+
}
29+
30+
export class CustomMatch extends Match {
31+
private matcher: CustomMatcher;
32+
private replacerArgs: ReplacerArgs;
33+
34+
constructor({ matcher, replacerArgs, ...config }: CustomMatchConfig) {
35+
super(config);
36+
37+
this.matcher = matcher;
38+
this.replacerArgs = replacerArgs;
39+
}
40+
41+
getType(): string {
42+
return this.matcher.type || 'custom';
43+
}
44+
45+
getAnchorHref(): string {
46+
return this.matcher.getLinkUrl?.(this.replacerArgs) ?? this.matchedText;
47+
}
48+
49+
getAnchorText(): string {
50+
return this.matcher.getLinkText?.(this.replacerArgs) ?? this.matchedText;
51+
}
52+
53+
getMatcher(): CustomMatcher {
54+
return this.matcher;
55+
}
56+
}

src/index.tsx

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import {
2828
} from 'react-native';
2929
import * as Truncate from './truncate';
3030
import { Matchers, MatcherId, LatLngMatch } from './matchers';
31-
import { UserCustomMatch, UserCustomMatchSpec } from './user-custom-match';
31+
import { CustomMatch, CustomMatcher } from './CustomMatch';
3232
import { PropsOf } from './types';
3333

34-
export * from './user-custom-match';
34+
export * from './CustomMatch';
3535

3636
const tagBuilder = new AnchorTagBuilder();
3737

@@ -43,12 +43,12 @@ const styles = StyleSheet.create({
4343

4444
interface AutolinkProps<C extends React.ComponentType = React.ComponentType> {
4545
component?: C;
46-
customLinks?: UserCustomMatchSpec[];
4746
email?: boolean;
4847
hashtag?: false | 'facebook' | 'instagram' | 'twitter';
4948
latlng?: boolean;
5049
linkProps?: TextProps;
5150
linkStyle?: StyleProp<TextStyle>;
51+
matchers?: CustomMatcher[];
5252
mention?: false | 'instagram' | 'soundcloud' | 'twitter';
5353
onPress?: (url: string, match: Match) => void;
5454
onLongPress?: (url: string, match: Match) => void;
@@ -117,7 +117,10 @@ export default class Autolink<
117117
webFallback: Platform.OS !== 'ios', // iOS requires LSApplicationQueriesSchemes for Linking.canOpenURL
118118
};
119119

120-
onPress(match: Match, alertShown?: boolean): void {
120+
onPress(match: Match | CustomMatch, alertShown?: boolean): void {
121+
// Bypass default press handling if matcher has custom onPress
122+
if (match instanceof CustomMatch && match.getMatcher().onPress?.(match)) return;
123+
121124
const {
122125
onPress,
123126
showAlert,
@@ -157,6 +160,9 @@ export default class Autolink<
157160
}
158161

159162
onLongPress(match: Match): void {
163+
// Bypass default press handling if matcher has custom onLongPress
164+
if (match instanceof CustomMatch && match.getMatcher().onLongPress?.(match)) return;
165+
160166
const { onLongPress } = this.props;
161167

162168
if (onLongPress) {
@@ -232,31 +238,18 @@ export default class Autolink<
232238

233239
renderLink(
234240
text: string,
235-
match: Match,
241+
match: Match | CustomMatch,
236242
index: number,
237243
textProps: Partial<TextProps> = {},
238244
): ReactNode {
239245
const { truncate, linkStyle } = this.props;
240246
const truncated = truncate ? Autolink.truncate(text, this.props) : text;
241247

242-
let style: StyleProp<TextStyle> | undefined;
243-
let onPress: (() => void) | undefined;
244-
let onLongPress: (() => void) | undefined;
245-
if (match.getType() === 'userCustom') {
246-
style = (match as UserCustomMatch).getStyle();
247-
onPress = (match as UserCustomMatch).getOnPress();
248-
onLongPress = (match as UserCustomMatch).getOnLongPress();
249-
}
250-
251-
style = style ?? linkStyle ?? styles.link;
252-
onPress = onPress ?? (() => this.onPress(match));
253-
onLongPress = onLongPress ?? (() => this.onLongPress(match));
254-
255248
return (
256249
<Text
257-
style={style}
258-
onPress={onPress}
259-
onLongPress={onLongPress}
250+
style={(match as CustomMatch).getMatcher?.().style ?? linkStyle ?? styles.link}
251+
onPress={() => this.onPress(match)}
252+
onLongPress={() => this.onLongPress(match)}
260253
// eslint-disable-next-line react/jsx-props-no-spreading
261254
{...textProps}
262255
key={index}
@@ -270,12 +263,12 @@ export default class Autolink<
270263
const {
271264
children,
272265
component = Text,
273-
customLinks = [],
274266
email,
275267
hashtag,
276268
latlng,
277269
linkProps,
278270
linkStyle,
271+
matchers = [],
279272
mention,
280273
onPress,
281274
onLongPress,
@@ -347,17 +340,17 @@ export default class Autolink<
347340
});
348341

349342
// User-specified custom matchers
350-
customLinks.forEach((spec) => {
351-
linkedText = linkedText.replace(spec.pattern, (...args) => {
343+
matchers.forEach((matcher) => {
344+
linkedText = linkedText.replace(matcher.pattern, (...replacerArgs) => {
352345
const token = generateToken();
353-
const matchedText = args[0];
346+
const matchedText = replacerArgs[0];
354347

355-
matches[token] = new UserCustomMatch({
356-
...spec,
357-
tagBuilder,
348+
matches[token] = new CustomMatch({
349+
matcher,
358350
matchedText,
359-
offset: args[args.length - 2],
360-
replacerArgs: args,
351+
offset: replacerArgs[replacerArgs.length - 2],
352+
replacerArgs,
353+
tagBuilder,
361354
});
362355

363356
return token;
@@ -375,23 +368,17 @@ export default class Autolink<
375368
.map((part, index) => {
376369
const match = matches[part];
377370

378-
switch (match?.getType()) {
379-
case 'email':
380-
case 'hashtag':
381-
case 'latlng':
382-
case 'mention':
383-
case 'phone':
384-
case 'url':
385-
case 'userCustom':
386-
return renderLink
387-
? renderLink(match.getAnchorText(), match, index)
388-
: this.renderLink(match.getAnchorText(), match, index, linkProps);
389-
default:
390-
return renderText
391-
? renderText(part, index)
392-
// eslint-disable-next-line react/jsx-props-no-spreading, react/no-array-index-key
393-
: <Text {...textProps} key={index}>{part}</Text>;
371+
// Check if rendering link or text node
372+
if (match?.getType()) {
373+
return renderLink
374+
? renderLink(match.getAnchorText(), match, index)
375+
: this.renderLink(match.getAnchorText(), match, index, linkProps);
394376
}
377+
378+
return renderText
379+
? renderText(part, index)
380+
// eslint-disable-next-line react/jsx-props-no-spreading, react/no-array-index-key
381+
: <Text {...textProps} key={index}>{part}</Text>;
395382
});
396383

397384
return createElement(component, other, ...nodes);

src/user-custom-match.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)