diff options
| author | おさむのひと <46447427+samunohito@users.noreply.github.com> | 2024-03-21 18:46:42 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-21 18:46:42 +0900 |
| commit | 831c74a25b2db0ba3f6d43a9a1a9072d342b2822 (patch) | |
| tree | 5ad9371369ef6a346e0ca27bc6dea1682044df52 /packages/frontend | |
| parent | enhance(antenna): Botの投稿を除外できるように (#13603) (diff) | |
| download | misskey-831c74a25b2db0ba3f6d43a9a1a9072d342b2822.tar.gz misskey-831c74a25b2db0ba3f6d43a9a1a9072d342b2822.tar.bz2 misskey-831c74a25b2db0ba3f6d43a9a1a9072d342b2822.zip | |
fix: URLプレビューの動作改善+動作設定を可能にする (#13579)
* wip
* support new version
* URLプレビュー無効化時、フロント側も非表示にしてリクエストをしないようにする
* fix lint
* fix lint
* tweak preview request error handles
* fix: CHANGELOG.md
* fix
* fix
---------
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Diffstat (limited to 'packages/frontend')
| -rw-r--r-- | packages/frontend/src/components/MkLink.vue | 17 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNote.vue | 7 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteDetailed.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkUrlPreview.vue | 12 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkUrl.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/components/page/page.text.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/instance.ts | 2 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/security.vue | 16 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/settings.vue | 74 |
9 files changed, 106 insertions, 35 deletions
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 3f7aba2fe4..ca875242b4 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -18,6 +18,7 @@ import { defineAsyncComponent, ref } from 'vue'; import { url as local } from '@/config.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import * as os from '@/os.js'; +import { isEnabledUrlPreview } from '@/instance.js'; const props = withDefaults(defineProps<{ url: string; @@ -31,13 +32,15 @@ const target = self ? null : '_blank'; const el = ref<HTMLElement | { $el: HTMLElement }>(); -useTooltip(el, (showing) => { - os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { - showing, - url: props.url, - source: el.value instanceof HTMLElement ? el.value : el.value?.$el, - }, {}, 'closed'); -}); +if (isEnabledUrlPreview.value) { + useTooltip(el, (showing) => { + os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { + showing, + url: props.url, + source: el.value instanceof HTMLElement ? el.value : el.value?.$el, + }, {}, 'closed'); + }); +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 5ca0eae012..50741f2cb7 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -82,7 +82,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkMediaList :mediaList="appearNote.files"/> </div> <MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> + <div v-if="isEnabledUrlPreview"> + <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> + </div> <div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> <button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> <span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> @@ -194,6 +196,7 @@ import { MenuItem } from '@/types/menu.js'; 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'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -268,7 +271,7 @@ const renoteCollapsed = ref( defaultStore.state.collapseRenotes && isRenote && ( ($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131 (appearNote.value.myReaction != null) - ) + ), ); /* Overload FunctionにLintが対応していないのでコメントアウト diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index e271215516..1b7dcda409 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -95,7 +95,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkMediaList :mediaList="appearNote.files"/> </div> <MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/> + <div v-if="isEnabledUrlPreview"> + <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/> + </div> <div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> </div> <MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> @@ -229,6 +231,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; +import { isEnabledUrlPreview } from '@/instance.js'; const props = defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index efc58b7e29..b3dc492616 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -110,7 +110,6 @@ const MOBILE_THRESHOLD = 500; const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); const self = props.url.startsWith(local); -const attr = self ? 'to' : 'href'; const target = self ? null : '_blank'; const fetching = ref(true); const title = ref<string | null>(null); @@ -152,15 +151,16 @@ requestUrl.hash = ''; window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`) .then(res => { if (!res.ok) { - fetching.value = false; - unknownUrl.value = true; - return; + if (_DEV_) { + console.warn(`[HTTP${res.status}] Failed to fetch url preview`); + } + return null; } return res.json(); }) - .then((info: SummalyResult) => { - if (info.url == null) { + .then((info: SummalyResult | null) => { + if (!info || info.url == null) { fetching.value = false; unknownUrl.value = true; return; diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index 8d29a4da8c..d2945a78b9 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -30,6 +30,7 @@ import { url as local } from '@/config.js'; import * as os from '@/os.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import { safeURIDecode } from '@/scripts/safe-uri-decode.js'; +import { isEnabledUrlPreview } from '@/instance.js'; const props = withDefaults(defineProps<{ url: string; @@ -44,7 +45,7 @@ const url = new URL(props.url); if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url'); const el = ref(); -if (props.showUrlPreview) { +if (props.showUrlPreview && isEnabledUrlPreview.value) { useTooltip(el, (showing) => { os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { showing, diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue index 61247b381f..4e501bd699 100644 --- a/packages/frontend/src/components/page/page.text.vue +++ b/packages/frontend/src/components/page/page.text.vue @@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps" :class="$style.textRoot"> <Mfm :text="block.text ?? ''" :isNote="false"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url"/> + <div v-if="isEnabledUrlPreview"> + <MkUrlPreview v-for="url in urls" :key="url" :url="url"/> + </div> </div> </template> @@ -15,6 +17,7 @@ import { defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; +import { isEnabledUrlPreview } from '@/instance.js'; const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 4232cbcd78..22337e7eb9 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -36,6 +36,8 @@ export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); +export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true); + export async function fetchInstance(force = false): Promise<void> { if (!force) { const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index c4745978df..9bccee89a5 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -118,19 +118,6 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </div> </MkFolder> - - <MkFolder> - <template #label>Summaly Proxy</template> - - <div class="_gaps_m"> - <MkInput v-model="summalyProxy"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>Summaly Proxy URL</template> - </MkInput> - - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </div> - </MkFolder> </div> </FormSuspense> </MkSpacer> @@ -155,7 +142,6 @@ import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -const summalyProxy = ref<string>(''); const enableHcaptcha = ref<boolean>(false); const enableMcaptcha = ref<boolean>(false); const enableRecaptcha = ref<boolean>(false); @@ -175,7 +161,6 @@ const bannedEmailDomains = ref<string>(''); async function init() { const meta = await misskeyApi('admin/meta'); - summalyProxy.value = meta.summalyProxy; enableHcaptcha.value = meta.enableHcaptcha; enableMcaptcha.value = meta.enableMcaptcha; enableRecaptcha.value = meta.enableRecaptcha; @@ -201,7 +186,6 @@ async function init() { function save() { os.apiWithDialog('admin/update-meta', { - summalyProxy: summalyProxy.value, sensitiveMediaDetection: sensitiveMediaDetection.value, sensitiveMediaDetectionSensitivity: sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' : diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 9a198ee8a3..6f45c212ec 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -143,6 +143,53 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> </FormSection> + + <FormSection> + <template #label>{{ i18n.ts._urlPreviewSetting.title }}</template> + + <div class="_gaps_m"> + <MkSwitch v-model="urlPreviewEnabled"> + <template #label>{{ i18n.ts._urlPreviewSetting.enable }}</template> + </MkSwitch> + + <MkSwitch v-model="urlPreviewRequireContentLength"> + <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</template> + <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template> + </MkSwitch> + + <MkInput v-model="urlPreviewMaximumContentLength" type="number"> + <template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</template> + <template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template> + </MkInput> + + <MkInput v-model="urlPreviewTimeout" type="number"> + <template #label>{{ i18n.ts._urlPreviewSetting.timeout }}</template> + <template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template> + </MkInput> + + <MkInput v-model="urlPreviewUserAgent" type="text"> + <template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}</template> + <template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template> + </MkInput> + + <div> + <MkInput v-model="urlPreviewSummaryProxyUrl" type="text"> + <template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</template> + <template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template> + </MkInput> + + <div :class="$style.subCaption"> + {{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }} + <ul style="padding-left: 20px; margin: 4px 0"> + <li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li> + <li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li> + <li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li> + <li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li> + </ul> + </div> + </div> + </div> + </FormSection> </div> </FormSuspense> </MkSpacer> @@ -173,6 +220,8 @@ import { fetchInstance, instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkSelect from '@/components/MkSelect.vue'; const name = ref<string | null>(null); const shortName = ref<string | null>(null); @@ -194,6 +243,12 @@ const perRemoteUserUserTimelineCacheMax = ref<number>(0); const perUserHomeTimelineCacheMax = ref<number>(0); const perUserListTimelineCacheMax = ref<number>(0); const notesPerOneAd = ref<number>(0); +const urlPreviewEnabled = ref<boolean>(true); +const urlPreviewTimeout = ref<number>(10000); +const urlPreviewMaximumContentLength = ref<number>(1024 * 1024 * 10); +const urlPreviewRequireContentLength = ref<boolean>(true); +const urlPreviewUserAgent = ref<string | null>(null); +const urlPreviewSummaryProxyUrl = ref<string | null>(null); async function init(): Promise<void> { const meta = await misskeyApi('admin/meta'); @@ -217,9 +272,15 @@ async function init(): Promise<void> { perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax; perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax; notesPerOneAd.value = meta.notesPerOneAd; + urlPreviewEnabled.value = meta.urlPreviewEnabled; + urlPreviewTimeout.value = meta.urlPreviewTimeout; + urlPreviewMaximumContentLength.value = meta.urlPreviewMaximumContentLength; + urlPreviewRequireContentLength.value = meta.urlPreviewRequireContentLength; + urlPreviewUserAgent.value = meta.urlPreviewUserAgent; + urlPreviewSummaryProxyUrl.value = meta.urlPreviewSummaryProxyUrl; } -async function save(): void { +async function save() { await os.apiWithDialog('admin/update-meta', { name: name.value, shortName: shortName.value === '' ? null : shortName.value, @@ -241,6 +302,12 @@ async function save(): void { perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value, perUserListTimelineCacheMax: perUserListTimelineCacheMax.value, notesPerOneAd: notesPerOneAd.value, + urlPreviewEnabled: urlPreviewEnabled.value, + urlPreviewTimeout: urlPreviewTimeout.value, + urlPreviewMaximumContentLength: urlPreviewMaximumContentLength.value, + urlPreviewRequireContentLength: urlPreviewRequireContentLength.value, + urlPreviewUserAgent: urlPreviewUserAgent.value, + urlPreviewSummaryProxyUrl: urlPreviewSummaryProxyUrl.value, }); fetchInstance(true); @@ -259,4 +326,9 @@ definePageMetadata(() => ({ -webkit-backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px)); } + +.subCaption { + font-size: 0.85em; + color: var(--fgTransparentWeak); +} </style> |