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"
renoteToOtherChannel: "Renote to other channel"
pinnedNote: "Pinned note"
pinnedOnly: "Pinned"
pinned: "Pin to profile"
you: "You"
clickToShow: "Click to show"
@ -1268,7 +1269,8 @@ alwaysConfirmFollow: "Always confirm when following"
inquiry: "Contact"
tryAgain: "Please try again later"
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"
createdAntennas: "Created antennas"
fromX: "From {x}"

8
locales/index.d.ts vendored
View file

@ -502,6 +502,10 @@ export interface Locale extends ILocale {
*
*/
"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?
*/
"sensitiveMediaRevealConfirm": string;
/**
* URLを開く際に警告を表示する
*/
"warnExternalUrl": string;
/**
*
*/

View file

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

View file

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

View file

@ -233,7 +233,7 @@ watch(q, () => {
//
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);
if (matches.size >= max) break;
}
@ -242,7 +242,7 @@ watch(q, () => {
//
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);
if (matches.size >= max) break;
}
@ -254,7 +254,7 @@ watch(q, () => {
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias === newQ)) {
if (emoji.aliases.some(alias => alias.toLowerCase() === newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
@ -262,7 +262,7 @@ watch(q, () => {
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) {
if (emoji.name.toLowerCase().startsWith(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
@ -270,7 +270,7 @@ watch(q, () => {
if (matches.size >= max) return matches;
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);
if (matches.size >= max) break;
}
@ -278,7 +278,7 @@ watch(q, () => {
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(newQ)) {
if (emoji.name.toLowerCase().includes(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
@ -286,7 +286,7 @@ watch(q, () => {
if (matches.size >= max) return matches;
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);
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="keepScreenOn">{{ i18n.ts.keepScreenOn }}</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="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
<MkSwitch v-model="warnExternalUrl">{{ i18n.ts.warnExternalUrl }}</MkSwitch>
</div>
<MkSelect v-model="serverDisconnectedBehavior">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@ -380,14 +380,6 @@ const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthO
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
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 autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
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 confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
const warnExternalUrl = computed(defaultStore.makeGetterSetter('warnExternalUrl'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
@ -501,6 +494,7 @@ watch([
alwaysConfirmFollow,
confirmWhenRevealingSensitiveMedia,
contextMenu,
warnExternalUrl,
], async () => {
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">
<MkTimeline
ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles"
:key="src + withRenotes + withBots + withReplies + onlyFiles"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"
@ -273,6 +273,10 @@ const headerActions = computed(() => {
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
}, {
type: 'switch',
text: i18n.ts.showBots,
ref: withBots,
});
if (isBasicTimeline(src.value) && hasWithReplies(src.value)) {

View file

@ -130,10 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div class="contents _gaps">
<div v-if="user.pinnedNotes.length > 0" class="_gaps">
<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>
<MkInfo v-if="user.pinnedNotes.length === 0 && $i?.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<template v-if="narrow">
<MkLazy>
<XFiles :key="user.id" :user="user"/>
@ -152,14 +149,27 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> -->
<MkStickyContainer>
<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">
<option value="pinned">{{ i18n.ts.pinnedOnly }}</option>
<option :value="null">{{ i18n.ts.notes }}</option>
<option value="all">{{ i18n.ts.all }}</option>
<option value="files">{{ i18n.ts.withFiles }}</option>
</MkTab>
</template>
<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>
</MkStickyContainer>
</div>
@ -199,6 +209,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
import { useRouter } from '@/router/supplier.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { infoImageUrl } from '@/instance.js';
const MkNote = defineAsyncComponent(() =>
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
@ -809,7 +820,7 @@ onUnmounted(() => {
}
.tab {
margin: calc(var(--margin) / 2) 0;
margin-bottom: calc(var(--margin) / 2);
padding: calc(var(--margin) / 2) 0;
background: color-mix(in srgb, var(--bg) 65%, transparent);
backdrop-filter: var(--blur, blur(15px));
@ -826,4 +837,8 @@ onUnmounted(() => {
margin-left: 4px;
color: var(--success);
}
.pinnedNote:not(:last-child) {
border-bottom: solid 0.5px var(--divider);
}
</style>

View file

@ -29,8 +29,9 @@ export async function warningExternalWebsite(url: string) {
});
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 { dispose } = os.popup(MkUrlWarningDialog, {
url,

View file

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