Merge branch 'develop' into feature/2024.9.0

This commit is contained in:
dakkar 2024-10-11 10:10:16 +01:00
commit 02d36c4518
10 changed files with 102 additions and 62 deletions

View File

@ -121,6 +121,7 @@ inChannelQuote: "Channel-only Quote"
renoteToChannel: "Renote to channel" renoteToChannel: "Renote to channel"
renoteToOtherChannel: "Renote to other channel" renoteToOtherChannel: "Renote to other channel"
pinnedNote: "Pinned note" pinnedNote: "Pinned note"
pinnedOnly: "Pinned"
pinned: "Pin to profile" pinned: "Pin to profile"
you: "You" you: "You"
clickToShow: "Click to show" clickToShow: "Click to show"
@ -1268,7 +1269,8 @@ alwaysConfirmFollow: "Always confirm when following"
inquiry: "Contact" inquiry: "Contact"
tryAgain: "Please try again later" tryAgain: "Please try again later"
confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media"
sensitiveMediaRevealConfirm: "This might be a sensitive media. Are you sure to reveal?" sensitiveMediaRevealConfirm: "This media might be sensitive. Are you sure you want to reveal it?"
warnExternalUrl: "Show warning when opening external URLs"
createdLists: "Created lists" createdLists: "Created lists"
createdAntennas: "Created antennas" createdAntennas: "Created antennas"
fromX: "From {x}" fromX: "From {x}"

8
locales/index.d.ts vendored
View File

@ -502,6 +502,10 @@ export interface Locale extends ILocale {
* *
*/ */
"pinnedNote": string; "pinnedNote": string;
/**
* Pinned
*/
"pinnedOnly": string;
/** /**
* *
*/ */
@ -5103,6 +5107,10 @@ export interface Locale extends ILocale {
* This media might be sensitive. Are you sure you want to reveal it? * This media might be sensitive. Are you sure you want to reveal it?
*/ */
"sensitiveMediaRevealConfirm": string; "sensitiveMediaRevealConfirm": string;
/**
* URLを開く際に警告を表示する
*/
"warnExternalUrl": string;
/** /**
* *
*/ */

View File

@ -121,6 +121,7 @@ inChannelQuote: "チャンネル内引用"
renoteToChannel: "チャンネルにリノート" renoteToChannel: "チャンネルにリノート"
renoteToOtherChannel: "他のチャンネルにリノート" renoteToOtherChannel: "他のチャンネルにリノート"
pinnedNote: "ピン留めされたノート" pinnedNote: "ピン留めされたノート"
pinnedOnly: "Pinned"
pinned: "ピン留め" pinned: "ピン留め"
you: "あなた" you: "あなた"
clickToShow: "クリックして表示" clickToShow: "クリックして表示"
@ -1271,6 +1272,7 @@ inquiry: "お問い合わせ"
tryAgain: "もう一度お試しください。" tryAgain: "もう一度お試しください。"
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する" confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?" sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
warnExternalUrl: "外部URLを開く際に警告を表示する"
createdLists: "作成したリスト" createdLists: "作成したリスト"
createdAntennas: "作成したアンテナ" createdAntennas: "作成したアンテナ"
fromX: "{x}から" fromX: "{x}から"

View File

@ -128,20 +128,30 @@ export class QueueProcessorService implements OnApplicationShutdown {
) { ) {
this.logger = this.queueLoggerService.logger; this.logger = this.queueLoggerService.logger;
function renderError(e: Error): any { function renderError(e?: Error) {
if (e) { // 何故かeがundefinedで来ることがある // 何故かeがundefinedで来ることがある
if (!e) return '?';
if (e instanceof Bull.UnrecoverableError || e.name === 'AbortError') {
return `${e.name}: ${e.message}`;
}
return { return {
stack: e.stack, stack: e.stack,
message: e.message, message: e.message,
name: e.name, name: e.name,
}; };
} else {
return {
stack: '?',
message: '?',
name: '?',
};
} }
function renderJob(job?: Bull.Job) {
if (!job) return '?';
return {
name: job.name || undefined,
info: getJobInfo(job),
failedReason: job.failedReason || undefined,
data: job.data,
};
} }
//#region system //#region system
@ -176,15 +186,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active id=${job.id}`)) .on('active', (job) => logger.debug(`active id=${job.id}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err: Error) => { .on('failed', (job, err: Error) => {
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.message}`, { Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -241,15 +251,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active id=${job.id}`)) .on('active', (job) => logger.debug(`active id=${job.id}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.message}`, { Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -281,15 +291,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: Deliver: ${err.message}`, { Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -321,15 +331,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`)) .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: Inbox: ${err.message}`, { Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -361,15 +371,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.message}`, { Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -401,15 +411,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.message}`, { Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -448,15 +458,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active id=${job.id}`)) .on('active', (job) => logger.debug(`active id=${job.id}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.message}`, { Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion
@ -489,15 +499,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('active', (job) => logger.debug(`active id=${job.id}`)) .on('active', (job) => logger.debug(`active id=${job.id}`))
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (config.sentryForBackend) {
Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.message}`, { Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
}); });
} }
}) })
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
} }
//#endregion //#endregion

View File

@ -233,7 +233,7 @@ watch(q, () => {
// //
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) { if (keywords.every(keyword => emoji.name.toLowerCase().includes(keyword))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -242,7 +242,7 @@ watch(q, () => {
// //
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { if (keywords.every(keyword => emoji.name.toLowerCase().includes(keyword) || emoji.aliases.some(alias => alias.toLowerCase().includes(keyword)))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -254,7 +254,7 @@ watch(q, () => {
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias === newQ)) { if (emoji.aliases.some(alias => alias.toLowerCase() === newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -262,7 +262,7 @@ watch(q, () => {
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) { if (emoji.name.toLowerCase().startsWith(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -270,7 +270,7 @@ watch(q, () => {
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.startsWith(newQ))) { if (emoji.aliases.some(alias => alias.toLowerCase().startsWith(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -278,7 +278,7 @@ watch(q, () => {
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.name.includes(newQ)) { if (emoji.name.toLowerCase().includes(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
@ -286,7 +286,7 @@ watch(q, () => {
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.includes(newQ))) { if (emoji.aliases.some(alias => alias.toLowerCase().includes(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }

View File

@ -238,11 +238,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch> <MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch> <MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
<MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch> <MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
<MkSwitch v-model="showBots">{{ i18n.ts.showBots }}</MkSwitch>
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch> <MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch> <MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch> <MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch> <MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
<MkSwitch v-model="warnExternalUrl">{{ i18n.ts.warnExternalUrl }}</MkSwitch>
</div> </div>
<MkSelect v-model="serverDisconnectedBehavior"> <MkSelect v-model="serverDisconnectedBehavior">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template> <template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@ -380,14 +380,6 @@ const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthO
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes')); const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo')); const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen')); const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
// copied from src/pages/timeline.vue
const showBots = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.withBots,
set: (newValue) => {
const out = deepMerge({ filter: { withBots: newValue } }, defaultStore.state.tl);
defaultStore.set('tl', out);
},
});
const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles')); const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles'));
const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation')); const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
@ -440,6 +432,7 @@ const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('u
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow')); const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia')); const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu')); const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
const warnExternalUrl = computed(defaultStore.makeGetterSetter('warnExternalUrl'));
watch(lang, () => { watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string); miLocalStorage.setItem('lang', lang.value as string);
@ -501,6 +494,7 @@ watch([
alwaysConfirmFollow, alwaysConfirmFollow,
confirmWhenRevealingSensitiveMedia, confirmWhenRevealingSensitiveMedia,
contextMenu, contextMenu,
warnExternalUrl,
], async () => { ], async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
}); });

View File

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tl"> <div :class="$style.tl">
<MkTimeline <MkTimeline
ref="tlComponent" ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles" :key="src + withRenotes + withBots + withReplies + onlyFiles"
:src="src.split(':')[0]" :src="src.split(':')[0]"
:list="src.split(':')[1]" :list="src.split(':')[1]"
:withRenotes="withRenotes" :withRenotes="withRenotes"
@ -273,6 +273,10 @@ const headerActions = computed(() => {
type: 'switch', type: 'switch',
text: i18n.ts.showRenotes, text: i18n.ts.showRenotes,
ref: withRenotes, ref: withRenotes,
}, {
type: 'switch',
text: i18n.ts.showBots,
ref: withBots,
}); });
if (isBasicTimeline(src.value) && hasWithReplies(src.value)) { if (isBasicTimeline(src.value) && hasWithReplies(src.value)) {

View File

@ -130,10 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div class="contents _gaps"> <div class="contents _gaps">
<div v-if="user.pinnedNotes.length > 0" class="_gaps"> <MkInfo v-if="user.pinnedNotes.length === 0 && $i?.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
</div>
<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<template v-if="narrow"> <template v-if="narrow">
<MkLazy> <MkLazy>
<XFiles :key="user.id" :user="user"/> <XFiles :key="user.id" :user="user"/>
@ -152,14 +149,27 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> --> </div> -->
<MkStickyContainer> <MkStickyContainer>
<template #header> <template #header>
<!-- You can't use v-if on these, as MkTab first *deletes* and replaces all children with native HTML elements. -->
<!-- Instead, we add a "no notes" placeholder and default to null (all notes) if there's nothing pinned. -->
<!-- It also converts all comments into text! -->
<MkTab v-model="noteview" :class="$style.tab"> <MkTab v-model="noteview" :class="$style.tab">
<option value="pinned">{{ i18n.ts.pinnedOnly }}</option>
<option :value="null">{{ i18n.ts.notes }}</option> <option :value="null">{{ i18n.ts.notes }}</option>
<option value="all">{{ i18n.ts.all }}</option> <option value="all">{{ i18n.ts.all }}</option>
<option value="files">{{ i18n.ts.withFiles }}</option> <option value="files">{{ i18n.ts.withFiles }}</option>
</MkTab> </MkTab>
</template> </template>
<MkLazy> <MkLazy>
<MkNotes :class="$style.tl" :noGap="true" :pagination="AllPagination"/> <div v-if="noteview === 'pinned'" class="_gaps">
<div v-if="user.pinnedNotes.length < 1" class="_fullinfo">
<img :src="infoImageUrl" class="_ghost" aria-hidden="true" :alt="i18n.ts.noNotes"/>
<div>{{ i18n.ts.noNotes }}</div>
</div>
<div v-else class="_panel">
<MkNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/>
</div>
</div>
<MkNotes v-else :class="$style.tl" :noGap="true" :pagination="AllPagination"/>
</MkLazy> </MkLazy>
</MkStickyContainer> </MkStickyContainer>
</div> </div>
@ -199,6 +209,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js'; import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { infoImageUrl } from '@/instance.js';
const MkNote = defineAsyncComponent(() => const MkNote = defineAsyncComponent(() =>
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') : (defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
@ -809,7 +820,7 @@ onUnmounted(() => {
} }
.tab { .tab {
margin: calc(var(--margin) / 2) 0; margin-bottom: calc(var(--margin) / 2);
padding: calc(var(--margin) / 2) 0; padding: calc(var(--margin) / 2) 0;
background: color-mix(in srgb, var(--bg) 65%, transparent); background: color-mix(in srgb, var(--bg) 65%, transparent);
backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px));
@ -826,4 +837,8 @@ onUnmounted(() => {
margin-left: 4px; margin-left: 4px;
color: var(--success); color: var(--success);
} }
.pinnedNote:not(:last-child) {
border-bottom: solid 0.5px var(--divider);
}
</style> </style>

View File

@ -29,8 +29,9 @@ export async function warningExternalWebsite(url: string) {
}); });
const isTrustedByUser = defaultStore.reactiveState.trustedDomains.value.includes(domain); const isTrustedByUser = defaultStore.reactiveState.trustedDomains.value.includes(domain);
const isDisabledByUser = !defaultStore.reactiveState.warnExternalUrl.value;
if (!isTrustedByInstance && !isTrustedByUser) { if (!isTrustedByInstance && !isTrustedByUser && !isDisabledByUser) {
const confirm = await new Promise<{ canceled: boolean }>(resolve => { const confirm = await new Promise<{ canceled: boolean }>(resolve => {
const { dispose } = os.popup(MkUrlWarningDialog, { const { dispose } = os.popup(MkUrlWarningDialog, {
url, url,

View File

@ -171,6 +171,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account', where: 'account',
default: [] as string[], default: [] as string[],
}, },
warnExternalUrl: {
where: 'account',
default: true,
},
menu: { menu: {
where: 'deviceAccount', where: 'deviceAccount',