Profile editing: Add warning for links (#38148)

This commit is contained in:
Echo
2026-03-11 14:19:39 +01:00
committed by GitHub
parent f971670c62
commit 12c6c6dcf9
4 changed files with 68 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ import {
useAppDispatch,
useAppSelector,
} from '@/mastodon/store';
import { isUrlWithoutProtocol } from '@/mastodon/utils/checks';
import { ConfirmationModal } from '../../ui/components/confirmation_modals';
import type { DialogModalProps } from '../../ui/components/dialog_modal';
@@ -48,7 +49,7 @@ const messages = defineMessages({
},
editValueHint: {
id: 'account_edit.field_edit_modal.value_hint',
defaultMessage: 'E.g. “example.me”',
defaultMessage: 'E.g. “https://example.me”',
},
limitHeader: {
id: 'account_edit.field_edit_modal.limit_header',
@@ -109,6 +110,10 @@ export const EditFieldModal: FC<DialogModalProps & { fieldKey?: string }> = ({
);
return hasLink && hasEmoji;
}, [customEmojiCodes, newLabel, newValue]);
const hasLinkWithoutProtocol = useMemo(
() => isUrlWithoutProtocol(newValue),
[newValue],
);
const dispatch = useAppDispatch();
const handleSave = useCallback(() => {
@@ -175,6 +180,19 @@ export const EditFieldModal: FC<DialogModalProps & { fieldKey?: string }> = ({
/>
</Callout>
)}
{hasLinkWithoutProtocol && (
<Callout variant='warning'>
<FormattedMessage
id='account_edit.field_edit_modal.url_warning'
defaultMessage='To add a link, please include {protocol} at the beginning.'
description='{protocol} is https://'
values={{
protocol: <code>https://</code>,
}}
/>
</Callout>
)}
</ConfirmationModal>
);
};

View File

@@ -173,7 +173,8 @@
"account_edit.field_edit_modal.link_emoji_warning": "We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.",
"account_edit.field_edit_modal.name_hint": "E.g. “Personal website”",
"account_edit.field_edit_modal.name_label": "Label",
"account_edit.field_edit_modal.value_hint": "E.g. “example.me”",
"account_edit.field_edit_modal.url_warning": "To add a link, please include {protocol} at the beginning.",
"account_edit.field_edit_modal.value_hint": "E.g. “https://example.me”",
"account_edit.field_edit_modal.value_label": "Value",
"account_edit.field_reorder_modal.drag_cancel": "Dragging was cancelled. Field \"{item}\" was dropped.",
"account_edit.field_reorder_modal.drag_end": "Field \"{item}\" was dropped.",

View File

@@ -0,0 +1,21 @@
import { isUrlWithoutProtocol } from './checks';
describe('isUrlWithoutProtocol', () => {
test.concurrent.each([
['example.com', true],
['sub.domain.co.uk', true],
['example', false], // No dot
['example..com', false], // Consecutive dots
['example.com.', false], // Trailing dot
['example.c', false], // TLD too short
['example.123', false], // Numeric TLDs are not valid
['example.com/path', true], // Paths are allowed
['example.com?query=string', true], // Query strings are allowed
['example.com#fragment', true], // Fragments are allowed
['example .com', false], // Spaces are not allowed
['example://com', false], // Protocol inside the string is not allowed
['example.com^', false], // Invalid characters not allowed
])('should return %s for input "%s"', (input, expected) => {
expect(isUrlWithoutProtocol(input)).toBe(expected);
});
});

View File

@@ -9,3 +9,29 @@ export function isValidUrl(
return false;
}
}
/**
* Checks if the input string is probably a URL without a protocol. Note this is not full URL validation,
* and is mostly used to detect link-like inputs.
* @see https://www.xjavascript.com/blog/check-if-a-javascript-string-is-a-url/
* @param input The input string to check
*/
export function isUrlWithoutProtocol(input: string): boolean {
if (!input.length || input.includes(' ') || input.includes('://')) {
return false;
}
try {
const url = new URL(`http://${input}`);
const { host } = url;
return (
host !== '' && // Host is not empty
host.includes('.') && // Host contains at least one dot
!host.endsWith('.') && // No trailing dot
!host.includes('..') && // No consecutive dots
/\.[\w]{2,}$/.test(host) // TLD is at least 2 characters
);
} catch {}
return false;
}