Skip to content

Commit c8c320c

Browse files
ahtesham-quraishAhtesham Quraish
andauthored
fix: change media url input behaviour (#2835)
* fix: change media url input behaviour --------- Co-authored-by: Ahtesham Quraish <ahtesham.quraish@192.168.1.14>
1 parent 669c918 commit c8c320c

4 files changed

Lines changed: 107 additions & 1 deletion

File tree

frontends/main/src/page-components/TiptapEditor/ArticleEditor.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ArticleByLineInfoBarNode } from "./extensions/node/ArticleByLineInfoBar
3434

3535
import { LearningResourceNode } from "./extensions/node/LearningResource/LearningResourceNode"
3636
import { LearningResourceURLHandler } from "./extensions/node/LearningResource/LearningResourcePaste"
37+
import { MediaEmbedURLHandler } from "./extensions/node/MediaEmbed/MediaEmbedURLHandler"
3738
import { MediaEmbedNode } from "./extensions/node/MediaEmbed/MediaEmbedNode"
3839
import { HorizontalRule } from "./vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension"
3940
import { ImageNode } from "./extensions/node/Image/ImageNode"
@@ -333,6 +334,7 @@ const ArticleEditor = ({ onSave, readOnly, article }: ArticleEditorProps) => {
333334
DividerNode,
334335
ArticleByLineInfoBarNode,
335336
ImageWithCaptionNode,
337+
MediaEmbedURLHandler,
336338
ImageNode.configure({
337339
accept: "image/*",
338340
maxSize: MAX_FILE_SIZE,

frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"
33
import { FullWidth, WideWidth, DefaultWidth } from "./Icons"
44
import styled from "@emotion/styled"
55

6-
const StyledNodeViewWrapper = styled(NodeViewWrapper)<{
6+
const StyledNodeViewWrapper = styled(NodeViewWrapper, {
7+
shouldForwardProp: (prop) => prop !== "hovering" && prop !== "layout",
8+
})<{
79
layout: string
810
hovering: boolean
911
}>`
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Extension } from "@tiptap/core"
2+
import { Plugin } from "@tiptap/pm/state"
3+
4+
import { convertToEmbedUrl } from "./lib"
5+
6+
function extractMediaEmbedUrl(text: string): string | null {
7+
try {
8+
const embedUrl = convertToEmbedUrl(text.trim())
9+
return embedUrl || null
10+
} catch {
11+
return null
12+
}
13+
}
14+
15+
export const MediaEmbedURLHandler = Extension.create({
16+
name: "mediaEmbedURLHandler",
17+
18+
addProseMirrorPlugins() {
19+
return [
20+
new Plugin({
21+
props: {
22+
handleKeyDown(view, event) {
23+
if (event.key !== "Enter") return false
24+
25+
const { state } = view
26+
const { $from } = state.selection
27+
const parent = $from.parent
28+
29+
// Only transform plain paragraphs
30+
if (parent.type.name !== "paragraph") return false
31+
32+
const text = parent.textContent.trim()
33+
const embedSrc = extractMediaEmbedUrl(text)
34+
if (!embedSrc) return false
35+
36+
event.preventDefault()
37+
38+
const startPos = $from.before()
39+
const endPos = startPos + parent.nodeSize
40+
41+
const mediaNode = state.schema.nodes.mediaEmbed.create({
42+
src: embedSrc,
43+
})
44+
45+
const tr = state.tr.replaceWith(startPos, endPos, mediaNode)
46+
view.dispatch(tr)
47+
48+
return true
49+
},
50+
},
51+
}),
52+
]
53+
},
54+
})
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export function convertToEmbedUrl(url: string): string {
2+
let parsed: URL
3+
4+
try {
5+
parsed = new URL(url)
6+
} catch {
7+
return url // not a valid URL
8+
}
9+
10+
const hostname = parsed.hostname.replace("www.", "")
11+
12+
// --- YOUTUBE WATCH ---
13+
if (hostname === "youtube.com" && parsed.pathname === "/watch") {
14+
const videoId = parsed.searchParams.get("v")
15+
return videoId ? `https://www.youtube.com/embed/${videoId}` : url
16+
}
17+
18+
// --- YOUTUBE SHORTS ---
19+
if (hostname === "youtube.com" && parsed.pathname.startsWith("/shorts/")) {
20+
const id = parsed.pathname.split("/shorts/")[1]
21+
return id ? `https://www.youtube.com/embed/${id}` : url
22+
}
23+
24+
// --- YOUTUBE SHORT youtu.be/shorts/??? (rare but possible) ---
25+
if (hostname === "youtu.be" && parsed.pathname.startsWith("/shorts/")) {
26+
const id = parsed.pathname.split("/shorts/")[1]
27+
return id ? `https://www.youtube.com/embed/${id}` : url
28+
}
29+
30+
// --- YOUTUBE SHORT youtu.be/VIDEO_ID ---
31+
if (hostname === "youtu.be") {
32+
const id = parsed.pathname.slice(1)
33+
return id ? `https://www.youtube.com/embed/${id}` : url
34+
}
35+
36+
// --- YOUTUBE EMBED (leave as-is) ---
37+
if (hostname === "youtube.com" && parsed.pathname.startsWith("/embed/")) {
38+
return url
39+
}
40+
41+
// --- VIMEO ---
42+
if (hostname === "vimeo.com") {
43+
const id = parsed.pathname.slice(1)
44+
return id ? `https://player.vimeo.com/video/${id}` : url
45+
}
46+
47+
return url // fallback
48+
}

0 commit comments

Comments
 (0)