From d7f177af11fd412441a43c4d525b84db470cc60e Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 2 Mar 2023 18:29:17 +0100 Subject: [PATCH] Use the href of links for link previews, not `node.textContent` Until now we used node.textContent to determine whether a paragraph is a link that warrants a link preview. Instead, we now check whether the paragraph has a single text node wich is a link and use its href. Text nodes with empty textContent are ignored in order to allow whitespaces before and after the link. This way we ensure to always show the preview of the link target, not of the description text. Both may differ, which has security implications. Also, links with a custom description get a link preview as well. And last but not least, it fixes link previes for URLs with spaces. (Background: for some reason, url-encoded spaces in textContent of links get decoded when they're transformed to markdown and written to a file. Therefore URLs with spaces lost their link preview once the Text session was closed prior to this commit) Fixes: #3871 Signed-off-by: Jonas --- src/nodes/ParagraphView.vue | 47 ++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/nodes/ParagraphView.vue b/src/nodes/ParagraphView.vue index 23b0ba6b55f..27bc468035e 100644 --- a/src/nodes/ParagraphView.vue +++ b/src/nodes/ParagraphView.vue @@ -48,6 +48,10 @@ export default { data() { return { text: null, +<<<<<<< HEAD +======= + isLoggedIn: getCurrentUser(), +>>>>>>> 35db3d4d9 (Use the href of links for link previews, not `node.textContent`) } }, watch: { @@ -63,20 +67,51 @@ export default { }, beforeCreate() { this.debouncedUpdateText = debounce((newNode) => { - this.text = this.getTextReference(this.node?.textContent) + this.text = this.getTextReference(this.node) }, 500) }, created() { - this.text = this.getTextReference(this.node?.textContent) + this.text = this.getTextReference(this.node) }, beforeUnmount() { this.debouncedUpdateText?.cancel() }, methods: { - getTextReference(text) { - const PATTERN = /(^)(https?:\/\/)?((?:[-A-Z0-9+_]+\.)+[-A-Z]+(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)($)/ig - if ((new RegExp(PATTERN)).test(text)) { - return text + getTextReference(node) { + if (!node?.childCount) { + return null + } + + // Only regard paragraphs with exactly one text node (ignoring whitespace-only nodes) + let textNode + for (let i = 0; i < node.childCount; i++) { + const childNode = node.child(i) + + // Disregard paragraphs with non-text nodes + if (childNode.type.name !== 'text') { + return null + } + + // Ignore children with empty text + if (!childNode.textContent.trim()) { + continue + } + + // Disregard paragraphs with more than one text nodes + if (textNode) { + return null + } + + textNode = childNode + } + + // Check if the text node is a link + const linkMark = textNode?.marks.find((m) => m.type.name === 'link') + const href = linkMark?.attrs?.href + + const PATTERN = /(^)(https?:\/\/)((?:[-A-Z0-9+_]+\.)+[-A-Z]+(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)($)/ig + if ((new RegExp(PATTERN)).test(href)) { + return href } return null