🐛(frontend) sanitize pasted toolbar links

Strip < and > from pasted BlockNote links to keep valid external URLs.
This commit is contained in:
Cyril
2026-04-15 09:54:19 +02:00
parent 9a5d81f983
commit 598a6adc02
2 changed files with 37 additions and 3 deletions

View File

@@ -11,17 +11,18 @@ and this project adheres to
- 🚸(frontend) redirect on current url tab after 401 #2197
- 🐛(frontend) abort check media status unmount #2194
- ✨(backend) order pinned documents by last updated at #2028
- 🐛(frontend) sanitize pasted toolbar links #2214
### Changed
- ♿️(frontend) structure correctly 5xx error alerts #2128
- ♿️(frontend) structure correctly 5xx error alerts #2128
## [v4.8.6] - 2026-04-08
### Added
- 🚸(frontend) allow opening "@page" links with
ctrl/command/middle-mouse click #2170
- 🚸(frontend) allow opening "@page" links with
ctrl/command/middle-mouse click #2170
- ✅ E2E - Any instance friendly #2142
### Changed

View File

@@ -88,6 +88,38 @@ interface BlockNoteEditorProps {
provider: HocuspocusProvider;
}
/**
* Strips angle brackets wrapping URLs (e.g. `<https://example.com>` → `https://example.com`).
* BlockNote copies links in Markdown autolink format; pasting into the link
* toolbar input keeps the brackets, producing broken hrefs.
*/
const stripAngleBrackets = (text: string): string =>
text.replace(/^<(.+)>$/, '$1');
const handlePasteUrlBrackets = (e: React.ClipboardEvent<HTMLDivElement>) => {
const target = e.target;
if (
!(target instanceof HTMLInputElement) &&
!(target instanceof HTMLTextAreaElement)
) {
return;
}
const text = e.clipboardData?.getData('text/plain') ?? '';
const cleaned = stripAngleBrackets(text.trim());
if (cleaned === text) {
return;
}
e.preventDefault();
// Use the native value setter (input/textarea) so React-controlled fields pick up the pasted value change.
const proto =
target instanceof HTMLInputElement
? HTMLInputElement.prototype
: HTMLTextAreaElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
setter?.call(target, cleaned);
target.dispatchEvent(new Event('input', { bubbles: true }));
};
export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const { user } = useAuth();
const { setEditor } = useEditorStore();
@@ -267,6 +299,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
return (
<Box
ref={refEditorContainer}
onPasteCapture={handlePasteUrlBrackets}
$css={css`
${cssEditor};
${cssComments(showComments, currentUserAvatarUrl)}