v1.4.1: 관리자 미디어 업로드 한도·라이브 에디터 UX 개선
종류별 업로드 크기 한도와 413 안내를 추가하고, 임베드·미디어 라이브 프리뷰·제목 Enter 포커스·스크롤 동작을 보정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,9 +59,48 @@ const getTweetId = (value) => {
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon 공개 게시물 URL인지 확인하고 embed URL을 반환한다.
|
||||
* @param {string} value - Mastodon 게시물 URL
|
||||
* @returns {string} Mastodon embed URL
|
||||
*/
|
||||
const getMastodonEmbedUrl = (value) => {
|
||||
try {
|
||||
const parsedUrl = new URL(value.trim())
|
||||
const path = parsedUrl.pathname.replace(/\/$/, '')
|
||||
const isKnownNonMastodonHost = [
|
||||
'twitter.com',
|
||||
'x.com',
|
||||
'mobile.twitter.com',
|
||||
'youtube.com',
|
||||
'www.youtube.com',
|
||||
'youtu.be'
|
||||
].includes(parsedUrl.hostname.replace(/^www\./, ''))
|
||||
|
||||
if (
|
||||
isKnownNonMastodonHost ||
|
||||
!['http:', 'https:'].includes(parsedUrl.protocol)
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (/^\/@[^/]+\/\d+$/.test(path) || /^\/users\/[^/]+\/statuses\/\d+$/.test(path)) {
|
||||
return `${parsedUrl.origin}${path}/embed`
|
||||
}
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const youtubeId = computed(() => getYouTubeId(props.url))
|
||||
const youtubeEmbedUrl = computed(() => youtubeId.value ? `https://www.youtube.com/embed/${youtubeId.value}` : '')
|
||||
const tweetId = computed(() => getTweetId(props.url))
|
||||
const mastodonEmbedUrl = computed(() => getMastodonEmbedUrl(props.url))
|
||||
const mastodonIframeRef = ref(null)
|
||||
const mastodonEmbedHeight = ref(640)
|
||||
const mastodonEmbedId = ref(0)
|
||||
|
||||
/**
|
||||
* 외부 링크로 열어도 되는 URL인지 확인한다.
|
||||
@@ -92,10 +131,74 @@ const tweetEmbedUrl = computed(() => {
|
||||
|
||||
return `https://platform.twitter.com/embed/Tweet.html?id=${encodeURIComponent(tweetId.value)}&theme=${twitterTheme}&dnt=true`
|
||||
})
|
||||
|
||||
/**
|
||||
* Mastodon embed iframe에 실제 콘텐츠 높이 계산을 요청한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const requestMastodonEmbedHeight = () => {
|
||||
if (!mastodonIframeRef.value?.contentWindow || !mastodonEmbedId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
mastodonIframeRef.value.contentWindow.postMessage({
|
||||
type: 'setHeight',
|
||||
id: mastodonEmbedId.value
|
||||
}, '*')
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon embed 높이 응답을 반영한다.
|
||||
* @param {MessageEvent} event - iframe 메시지 이벤트
|
||||
* @returns {void}
|
||||
*/
|
||||
const handleMastodonEmbedMessage = (event) => {
|
||||
const data = event.data || {}
|
||||
|
||||
if (
|
||||
!mastodonIframeRef.value ||
|
||||
event.source !== mastodonIframeRef.value.contentWindow ||
|
||||
typeof data !== 'object' ||
|
||||
data.type !== 'setHeight' ||
|
||||
data.id !== mastodonEmbedId.value ||
|
||||
typeof data.height !== 'number'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const expectedOrigin = new URL(mastodonEmbedUrl.value).origin
|
||||
|
||||
if (event.origin !== expectedOrigin) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
mastodonEmbedHeight.value = Math.max(320, Math.ceil(data.height))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mastodonEmbedId.value = Math.floor(Math.random() * 1000000000) + 1
|
||||
window.addEventListener('message', handleMastodonEmbedMessage)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('message', handleMastodonEmbedMessage)
|
||||
})
|
||||
|
||||
watch(mastodonEmbedUrl, () => {
|
||||
mastodonEmbedHeight.value = 640
|
||||
requestMastodonEmbedHeight()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="prose-embed prose-embed-card my-8 overflow-hidden rounded-[10px] border border-[var(--site-line)] bg-[var(--site-panel)]">
|
||||
<div
|
||||
class="prose-embed prose-embed-card my-8 overflow-hidden rounded-[10px]"
|
||||
:class="tweetEmbedUrl ? 'mx-auto max-w-[550px]' : mastodonEmbedUrl ? 'mx-auto max-w-[560px] border border-[var(--site-line)] bg-[var(--site-panel)]' : 'border border-[var(--site-line)] bg-[var(--site-panel)]'"
|
||||
>
|
||||
<iframe
|
||||
v-if="youtubeEmbedUrl"
|
||||
class="prose-embed__frame aspect-video w-full"
|
||||
@@ -108,11 +211,25 @@ const tweetEmbedUrl = computed(() => {
|
||||
<iframe
|
||||
v-else-if="tweetEmbedUrl"
|
||||
:key="tweetEmbedUrl"
|
||||
class="prose-embed__tweet min-h-[420px] w-full border-0 sm:min-h-[458px]"
|
||||
class="prose-embed__tweet block min-h-[560px] w-full border-0 sm:min-h-[620px]"
|
||||
:src="tweetEmbedUrl"
|
||||
title="Embedded post"
|
||||
loading="lazy"
|
||||
/>
|
||||
<iframe
|
||||
v-else-if="mastodonEmbedUrl"
|
||||
:key="mastodonEmbedUrl"
|
||||
ref="mastodonIframeRef"
|
||||
class="prose-embed__mastodon block w-full border-0"
|
||||
:src="mastodonEmbedUrl"
|
||||
:height="mastodonEmbedHeight"
|
||||
title="Embedded Mastodon post"
|
||||
allow="fullscreen"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups"
|
||||
scrolling="no"
|
||||
@load="requestMastodonEmbedHeight"
|
||||
/>
|
||||
<a
|
||||
v-else-if="safeExternalUrl"
|
||||
class="prose-embed__link block p-5 text-sm font-semibold text-[var(--site-text)] hover:opacity-70"
|
||||
|
||||
Reference in New Issue
Block a user