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>
This commit is contained in:
@@ -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>
|
||||
|
@@ -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が対応していないのでコメントアウト
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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'));
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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' :
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user