diff --git a/package-lock.json b/package-lock.json index 4ba89c5d..6f630d0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "imwald", - "version": "22.5.3", + "version": "22.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "imwald", - "version": "22.5.3", + "version": "22.5.4", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index 724121e6..68ce4250 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "imwald", - "version": "22.5.3", + "version": "22.5.4", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery", "private": true, "type": "module", diff --git a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx index e0d8edac..58f58854 100644 --- a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx +++ b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx @@ -3305,8 +3305,8 @@ function parseMarkdownContentMarked( } const renderParagraph = (token: any, key: string): React.ReactNode => { - const paragraphText = String(token.text ?? '').trim() - const rawParagraphText = String(token.text ?? '') + const rawParagraphText = String(token.text ?? token.raw ?? '') + const paragraphText = rawParagraphText.trim() const standaloneMath = parseDelimitedMath(rawParagraphText.trim()) if (standaloneMath) { return ( @@ -3645,7 +3645,7 @@ function parseMarkdownContentMarked( } } - const paragraphTokens = lexInlineProtected(String(token.text ?? token.raw ?? '')) + const paragraphTokens = lexInlineProtected(rawParagraphText) const parseNostrHref = (href: string): string | null => { if (!href.toLowerCase().startsWith('nostr:')) return null const raw = href.slice(6).trim() @@ -3760,6 +3760,56 @@ function parseMarkdownContentMarked( return
{nodes}
} } + + // GFM autolinks become `link` tokens; without this, "…text\nhttps://youtube.com/…" can become + // [text, br, link] and fall through to a single

with a plain anchor instead of an embed. + const hasInlineYouTubeLink = paragraphTokens.some((t: any) => { + if (t?.type !== 'link') return false + const cleaned = cleanUrl(String(t.href ?? '')) + return !!cleaned && isYouTubeUrl(cleaned) + }) + if (hasInlineYouTubeLink) { + const nodes: React.ReactNode[] = [] + let inlineSegment: any[] = [] + const flushInlineSegment = (segmentIdx: number) => { + if (inlineSegment.length === 0) return + nodes.push( +

+ {renderInlineTokens(inlineSegment, `${key}-yt-inline-segment-${segmentIdx}`)} +

+ ) + inlineSegment = [] + } + + let segmentIdx = 0 + paragraphTokens.forEach((t: any, idx: number) => { + if (t?.type !== 'link') { + inlineSegment.push(t) + return + } + const cleaned = cleanUrl(String(t.href ?? '')) + if (!cleaned || !isYouTubeUrl(cleaned)) { + inlineSegment.push(t) + return + } + + flushInlineSegment(segmentIdx++) + nodes.push( +
+ +
+ ) + }) + + flushInlineSegment(segmentIdx++) + if (nodes.length > 0) { + return
{nodes}
+ } + } } // If the paragraph is a single markdown image token, render it as block media/image diff --git a/src/components/YoutubeEmbeddedPlayer/index.tsx b/src/components/YoutubeEmbeddedPlayer/index.tsx index 7cf39673..b8bebe18 100644 --- a/src/components/YoutubeEmbeddedPlayer/index.tsx +++ b/src/components/YoutubeEmbeddedPlayer/index.tsx @@ -33,15 +33,29 @@ export default function YoutubeEmbeddedPlayer({ }, [autoLoadMedia]) const showEmbed = mustLoad || autoLoadMedia || userClickedLoad + /** - * Electron + dev server (http/https): plain `/embed/` iframes often show error 150; use the Iframe API like the browser. - * Packaged app loads `file:`: use a plain embed URL only. Do not pass a fake `origin` query param — YouTube matches it - * to the real embedder and `file:` will not match `https://…`, which triggers error 150. + * YouTube in Electron: + * - **Iframe API** (`YT.Player`) against `http(s)://localhost` often ends in error **153** (player configuration) + * in recent Chromium/Electron builds; it worked more reliably in plain browsers only. + * - **Native `/embed/` iframe** works if the `origin` query param matches the real page origin. Use + * `window.location.origin` for dev (`http://127.0.0.1:5173`, etc.). On **`file:`** there is no valid https + * origin — omit `origin` (a fake `https://…` origin caused **150**). + * Non-Electron: keep the Iframe API (unchanged from pre–Electron-split behavior). */ - const useNativeEmbed = - isImwaldElectron() && - typeof window !== 'undefined' && - window.location.protocol === 'file:' + const useNativeEmbed = isImwaldElectron() + + const nativeEmbedSrc = useMemo(() => { + if (!videoId || !isImwaldElectron()) return null + const params = new URLSearchParams({ playsinline: '1', rel: '0' }) + if (typeof window !== 'undefined') { + const { protocol, origin } = window.location + if (protocol === 'http:' || protocol === 'https:') { + params.set('origin', origin) + } + } + return `https://www.youtube.com/embed/${encodeURIComponent(videoId)}?${params}` + }, [videoId]) const posterUrl = useMemo( () => (videoId ? `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg` : undefined), @@ -125,8 +139,7 @@ export default function YoutubeEmbeddedPlayer({ return } - if (useNativeEmbed && videoId) { - const embedSrc = `https://www.youtube.com/embed/${encodeURIComponent(videoId)}?playsinline=1&rel=0` + if (useNativeEmbed && nativeEmbedSrc) { return (