From 1c35bf32bb04f6eca43f807717bf4a7053496e39 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:05:17 +0900 Subject: [PATCH] =?UTF-8?q?media=E3=81=BE=E3=82=8F=E3=82=8A=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 4 ++ locales/ja-JP.yml | 1 + .../frontend/src/components/MkMediaImage.vue | 2 + .../frontend/src/components/MkMediaList.vue | 42 ++++++++++++++++--- packages/frontend/src/components/MkNote.vue | 3 +- .../src/components/MkNoteDetailed.vue | 4 +- .../components/MkReactionsViewer.reaction.vue | 3 +- .../src/components/MkSubNoteContent.vue | 3 +- packages/frontend/src/pages/embed/note.vue | 5 ++- .../src/pages/embed/user-timeline.vue | 6 ++- packages/frontend/src/ui/embed.vue | 12 +----- 11 files changed, 60 insertions(+), 25 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 0b1b86d373..1ba4ad79c3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4984,6 +4984,10 @@ export interface Locale extends ILocale { * お問い合わせ */ "inquiry": string; + /** + * {x}から + */ + "fromX": ParameterizedString<"x">; "_delivery": { /** * 配信状態 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a89cfbd843..da45b0159b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1242,6 +1242,7 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ noDescription: "説明文はありません" alwaysConfirmFollow: "フォローの際常に確認する" inquiry: "お問い合わせ" +fromX: "{x}から" _delivery: status: "配信状態" diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 82f36fe5c4..30e6cf11db 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -14,6 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only title: image.name, class: $style.imageContainer, href: image.url, + target: '_blank', + rel: 'noopener', style: 'cursor: zoom-in;' }" > diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index b1321a8ef9..4909842db6 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> + <div v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :class="$style.banner"> + <XBanner :media="media"/> + <a v-if="inEmbedPage && originalEntityUrl" :href="originalEntityUrl" target="_blank" rel="noopener" :class="$style.mediaLinkForEmbed"></a> + </div> <div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container"> <div ref="gallery" @@ -18,17 +21,18 @@ SPDX-License-Identifier: AGPL-3.0-only }] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany, ]" > - <template v-for="media in mediaList.filter(media => previewable(media))"> - <XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/> - <XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/> - </template> + <div v-for="media in mediaList.filter(media => previewable(media))" :class="$style.media"> + <XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :video="media" :class="$style.mediaInner"/> + <XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.mediaInner" class="image" :data-id="media.id" :image="media" :raw="raw"/> + <a v-if="inEmbedPage && originalEntityUrl" :href="originalEntityUrl" target="_blank" rel="noopener" :class="$style.mediaLinkForEmbed"></a> + </div> </div> </div> </div> </template> <script lang="ts" setup> -import { computed, onMounted, onUnmounted, shallowRef } from 'vue'; +import { computed, onMounted, onUnmounted, shallowRef, inject } from 'vue'; import * as Misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; @@ -43,8 +47,13 @@ import { defaultStore } from '@/store.js'; const props = defineProps<{ mediaList: Misskey.entities.DriveFile[]; raw?: boolean; + + /** 埋め込みページ用 親要素の正規URL */ + originalEntityUrl?: string; }>(); +const inEmbedPage = inject<boolean>('EMBED_PAGE', false); + const gallery = shallowRef<HTMLDivElement>(); const pswpZIndex = os.claimZIndex('middle'); document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); @@ -90,6 +99,7 @@ async function calcAspectRatio() { onMounted(() => { calcAspectRatio(); + if (defaultStore.state.imageNewTab || inEmbedPage) return; lightbox = new PhotoSwipeLightbox({ dataSource: props.mediaList @@ -284,6 +294,26 @@ const previewable = (file: Misskey.entities.DriveFile): boolean => { .media { overflow: hidden; // clipにするとバグる border-radius: 8px; + position: relative; + + >.mediaInner { + width: 100%; + height: 100%; + } +} + +.banner { + position: relative; +} + +.mediaLinkForEmbed::after { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + content: ''; } :global(.pswp) { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 5bdf7c90cf..70a42f34c3 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> - <MkMediaList :mediaList="appearNote.files"/> + <MkMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/> </div> <MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/> <div v-if="isEnabledUrlPreview"> @@ -216,6 +216,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { shouldCollapsed } from '@/scripts/collapsed.js'; import { isEnabledUrlPreview } from '@/instance.js'; +import { url } from '@/config.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 31bda0302c..a69a625f62 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> - <MkMediaList :mediaList="appearNote.files"/> + <MkMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/> </div> <MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/> <div v-if="isEnabledUrlPreview"> @@ -645,7 +645,7 @@ function loadConversation() { font-size: 1.2em; &.embeddedNote { - padding: 16px 32px; + padding: 24px 32px 16px; } &:hover > .main > .footer > .button { diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index c41811febe..c6ddb8cd7f 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -44,6 +44,7 @@ const props = defineProps<{ }>(); const mock = inject<boolean>('mock', false); +const inEmbedPage = inject<boolean>('EMBED_PAGE', false); const emit = defineEmits<{ (ev: 'reactionToggled', emoji: string, newCount: number): void; @@ -140,7 +141,7 @@ onMounted(() => { if (!props.isInitial) anime(); }); -if (!mock) { +if (!mock && !inEmbedPage) { useTooltip(buttonEl, async (showing) => { const reactions = await misskeyApiGet('notes/reactions', { noteId: props.note.id, diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 9a07826f1a..2b26e449ca 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <details v-if="note.files && note.files.length > 0"> <summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary> - <MkMediaList :mediaList="note.files"/> + <MkMediaList :mediaList="note.files" :originalEntityUrl="`${url}/notes/${note.id}`"/> </details> <details v-if="note.poll"> <summary>{{ i18n.ts.poll }}</summary> @@ -36,6 +36,7 @@ import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; import { i18n } from '@/i18n.js'; import { shouldCollapsed } from '@/scripts/collapsed.js'; +import { url } from '@/config.js'; const props = defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/pages/embed/note.vue b/packages/frontend/src/pages/embed/note.vue index 3689a0d244..adf5c1f4b1 100644 --- a/packages/frontend/src/pages/embed/note.vue +++ b/packages/frontend/src/pages/embed/note.vue @@ -12,16 +12,19 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script setup lang="ts"> -import { ref } from 'vue'; +import { ref, provide } from 'vue'; import * as Misskey from 'misskey-js'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import XNotFound from '@/pages/not-found.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; +import { url } from '@/config.js'; const props = defineProps<{ noteId: string; }>(); +provide('EMBED_ORIGINAL_ENTITY_URL', `${url}/notes/${props.noteId}`); + const note = ref<Misskey.entities.Note | null>(null); const loading = ref(true); diff --git a/packages/frontend/src/pages/embed/user-timeline.vue b/packages/frontend/src/pages/embed/user-timeline.vue index 6cb35e3e5f..360c6a8d89 100644 --- a/packages/frontend/src/pages/embed/user-timeline.vue +++ b/packages/frontend/src/pages/embed/user-timeline.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </a> </template> </I18n> - <div :class="$style.sub"></div> + <div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div> </div> <a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer"> <img @@ -48,7 +48,7 @@ import type { Paging } from '@/components/MkPagination.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { url } from '@/config.js'; +import { url, instanceName } from '@/config.js'; const props = defineProps<{ username: string; @@ -101,6 +101,7 @@ misskeyApi('users/show', { .headerTitle { font-weight: 700; + line-height: 1.1; .sub { font-size: 0.8em; @@ -112,6 +113,7 @@ misskeyApi('users/show', { .instanceIconLink { display: block; margin-left: auto; + height: 24px; } .instanceIcon { diff --git a/packages/frontend/src/ui/embed.vue b/packages/frontend/src/ui/embed.vue index c7d44a67bb..42ce7ff7af 100644 --- a/packages/frontend/src/ui/embed.vue +++ b/packages/frontend/src/ui/embed.vue @@ -71,24 +71,14 @@ const maxHeight = ref(params.get('maxHeight') ? parseInt(params.get('maxHeight') //#region Embed Resizer const rootEl = shallowRef<HTMLElement | null>(null); -let resizeMessageThrottleTimer: number | null = null; -let resizeMessageThrottleFlag = false; let previousHeight = 0; const resizeObserver = new ResizeObserver(async () => { const height = rootEl.value!.scrollHeight + 2; // border 上下1px - if (resizeMessageThrottleFlag && Math.abs(previousHeight - height) < 30) return; // プラマイ30px未満の変化は無視 - if (resizeMessageThrottleTimer) window.clearTimeout(resizeMessageThrottleTimer); - + if (Math.abs(previousHeight - height) < 1) return; // 1px未満の変化は無視 postMessageToParentWindow('misskey:embed:changeHeight', { height: (maxHeight.value > 0 && height > maxHeight.value) ? maxHeight.value : height, }); previousHeight = height; - - resizeMessageThrottleFlag = true; - - resizeMessageThrottleTimer = window.setTimeout(() => { - resizeMessageThrottleFlag = false; // 収縮をやりすぎるとチカチカする - }, 500); }); onMounted(() => { resizeObserver.observe(rootEl.value!);