diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9ae480af..4c36dd13f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - ### Client +- Enhance: Blueskyの投稿埋め込みプレビューに対応 - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 - Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 0ae188f1f7..34c3f2c819 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2585,7 +2585,7 @@ export interface Locale extends ILocale { /** * ポストを展開する */ - "expandTweet": string; + "expandPost": string; /** * テーマエディター */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1b59708d85..f4672a5ea6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -641,7 +641,7 @@ poll: "アンケート" useCw: "内容を隠す" enablePlayer: "プレイヤーを開く" disablePlayer: "プレイヤーを閉じる" -expandTweet: "ポストを展開する" +expandPost: "ポストを展開する" themeEditor: "テーマエディター" description: "説明" describeFile: "キャプションを付ける" diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index f0da8fd3f2..1dcfb7bbdf 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -26,19 +26,34 @@ SPDX-License-Identifier: AGPL-3.0-only </MkButton> </div> </template> -<template v-else-if="tweetId && tweetExpanded"> - <div ref="twitter"> +<template v-else-if="tweetId && postExpanded"> + <div> <iframe - ref="tweet" allow="fullscreen;web-share" sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin" scrolling="no" - :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }" + :style="{ position: 'relative', width: '100%', height: `${postHeight}px`, border: 0 }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`" ></iframe> </div> <div :class="$style.action"> - <MkButton :small="true" inline @click="tweetExpanded = false"> + <MkButton :small="true" inline @click="postExpanded = false"> + <i class="ti ti-x"></i> {{ i18n.ts.close }} + </MkButton> + </div> +</template> +<template v-else-if="bskyDid && bskyPostRecordKey && postExpanded"> + <div> + <iframe + allow="fullscreen;web-share" + sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin" + scrolling="no" + :style="{ position: 'relative', width: '100%', height: `${postHeight}px`, border: 0 }" + :src="`https://embed.bsky.app/embed/${bskyDid}/app.bsky.feed.post/${bskyPostRecordKey}?id=${embedId}`" + ></iframe> + </div> + <div :class="$style.action"> + <MkButton :small="true" inline @click="postExpanded = false"> <i class="ti ti-x"></i> {{ i18n.ts.close }} </MkButton> </div> @@ -66,8 +81,13 @@ SPDX-License-Identifier: AGPL-3.0-only </component> <template v-if="showActions"> <div v-if="tweetId" :class="$style.action"> - <MkButton :small="true" inline @click="tweetExpanded = true"> - <i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }} + <MkButton :small="true" inline @click="postExpanded = true"> + <i class="ti ti-brand-x"></i> {{ i18n.ts.expandPost }} + </MkButton> + </div> + <div v-if="bskyPostRecordKey" :class="$style.action"> + <MkButton :small="true" inline @click="openBskyEmbed"> + <i class="ti ti-brand-bluesky"></i> {{ i18n.ts.expandPost }} </MkButton> </div> <div v-if="!playerEnabled && player.url" :class="$style.action"> @@ -126,10 +146,17 @@ const player = ref({ height: null, } as SummalyResult['player']); const playerEnabled = ref(false); -const tweetId = ref<string | null>(null); -const tweetExpanded = ref(props.detail); + const embedId = `embed${Math.random().toString().replace(/\D/, '')}`; -const tweetHeight = ref(150); +const postExpanded = ref(props.detail); +const postHeight = ref(150); + +const tweetId = ref<string | null>(null); + +const bskyHandleOrDid = ref<string | null>(null); +const bskyDid = ref<string | null>(null); +const bskyPostRecordKey = ref<string | null>(null); + const unknownUrl = ref(false); onDeactivated(() => { @@ -144,6 +171,19 @@ if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twi if (m) tweetId.value = m[1]; } +if (requestUrl.hostname === 'bsky.app') { + const bskyPostPageUrl = requestUrl.pathname.slice(1).split('/'); + + if (bskyPostPageUrl[0] === 'profile' && bskyPostPageUrl[1] && bskyPostPageUrl[2] === 'post' && bskyPostPageUrl[3]) { + bskyHandleOrDid.value = bskyPostPageUrl[1]; + bskyPostRecordKey.value = bskyPostPageUrl[3]; + + if (postExpanded.value) { + openBskyEmbed(); + } + } +} + if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { requestUrl.hostname = 'www.youtube.com'; } @@ -180,13 +220,23 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa sensitive.value = info.sensitive ?? false; }); -function adjustTweetHeight(message: MessageEvent) { - if (message.origin !== 'https://platform.twitter.com') return; - const embed = message.data?.['twttr.embed']; - if (embed?.method !== 'twttr.private.resize') return; - if (embed?.id !== embedId) return; - const height = embed?.params[0]?.height; - if (height) tweetHeight.value = height; +async function openBskyEmbed() { + if (bskyHandleOrDid.value == null || bskyPostRecordKey.value == null) return; + + if (bskyDid.value == null) { + if (bskyHandleOrDid.value.startsWith('did:')) { + bskyDid.value = bskyHandleOrDid.value; + } else { + // handleで来ている場合はdidに変換 + const bskyApiRes = await window.fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${bskyHandleOrDid.value}`); + if (bskyApiRes.ok) { + const bskyApiData = await bskyApiRes.json() as { did: string }; + bskyDid.value = bskyApiData.did; + } + } + } + + postExpanded.value = true; } function openPlayer(): void { @@ -199,10 +249,25 @@ function openPlayer(): void { }); } -window.addEventListener('message', adjustTweetHeight); +function adjustSocialsEmbedHeight(message: MessageEvent) { + if (message.origin === 'https://platform.twitter.com') { + const embed = message.data?.['twttr.embed']; + if (embed?.method === 'twttr.private.resize' && embed?.id === embedId) { + const height = embed?.params[0]?.height; + if (height) postHeight.value = height; + } + } else if (message.origin === 'https://embed.bsky.app') { + if (message.data?.id === embedId) { + const height = message.data?.height; + if (height) postHeight.value = height; + } + } +} + +window.addEventListener('message', adjustSocialsEmbedHeight); onUnmounted(() => { - window.removeEventListener('message', adjustTweetHeight); + window.removeEventListener('message', adjustSocialsEmbedHeight); }); </script> diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html index 08ff0c58dd..14dfe62dba 100644 --- a/packages/frontend/src/index.html +++ b/packages/frontend/src/index.html @@ -22,7 +22,7 @@ style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; - connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com; + connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://public.api.bsky.app; frame-src *;" /> <meta property="og:site_name" content="[DEV BUILD] Misskey" />