Merge branch 'misskey-dev:develop' into dev

This commit is contained in:
MomentQYC 2024-10-05 21:56:59 +08:00 committed by GitHub
commit f3ef935cd3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 1151 additions and 344 deletions

View file

@ -6,26 +6,33 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkFolder>
<template #icon>
<i v-if="report.resolved" class="ti ti-check" style="color: var(--success)"></i>
<i v-if="report.resolved && report.resolvedAs === 'accept'" class="ti ti-check" style="color: var(--success)"></i>
<i v-else-if="report.resolved && report.resolvedAs === 'reject'" class="ti ti-x" style="color: var(--error)"></i>
<i v-else-if="report.resolved" class="ti ti-slash"></i>
<i v-else class="ti ti-exclamation-circle" style="color: var(--warn)"></i>
</template>
<template #label><MkAcct :user="report.targetUser"/> (by <MkAcct :user="report.reporter"/>)</template>
<template #caption>{{ report.comment }}</template>
<template #suffix><MkTime :time="report.createdAt"/></template>
<template v-if="!report.resolved" #footer>
<template #footer>
<div class="_buttons">
<MkButton primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
<template v-if="report.targetUser.host == null || report.resolved">
<MkButton primary @click="resolveAndForward">{{ i18n.ts.forwardReport }}</MkButton>
<div v-tooltip:dialog="i18n.ts.forwardReportIsAnonymous" class="_button _help"><i class="ti ti-help-circle"></i></div>
<template v-if="!report.resolved">
<MkButton @click="resolve('accept')"><i class="ti ti-check" style="color: var(--success)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.accept }})</MkButton>
<MkButton @click="resolve('reject')"><i class="ti ti-x" style="color: var(--error)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.reject }})</MkButton>
<MkButton @click="resolve(null)"><i class="ti ti-slash"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts.other }})</MkButton>
</template>
<template v-if="report.targetUser.host == null">
<MkButton :disabled="report.forwarded" primary @click="forward"><i class="ti ti-corner-up-right"></i> {{ i18n.ts._abuseUserReport.forward }}</MkButton>
<div v-tooltip:dialog="i18n.ts._abuseUserReport.forwardDescription" class="_button _help"><i class="ti ti-help-circle"></i></div>
</template>
<button class="_button" style="margin-left: auto; width: 34px;" @click="showMenu"><i class="ti ti-dots"></i></button>
</div>
</template>
<div :class="$style.root" class="_gaps_s">
<MkFolder :withSpacer="false">
<template #icon><MkAvatar :user="report.targetUser" style="width: 18px; height: 18px;"/></template>
<template #label>Target: <MkAcct :user="report.targetUser"/></template>
<template #label>{{ i18n.ts.target }}: <MkAcct :user="report.targetUser"/></template>
<template #suffix>#{{ report.targetUserId.toUpperCase() }}</template>
<div style="container-type: inline-size;">
@ -36,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-message-2"></i></template>
<template #label>{{ i18n.ts.details }}</template>
<div>
<div class="_gaps_s">
<Mfm :text="report.comment" :linkNavigationBehavior="'window'"/>
</div>
</MkFolder>
@ -51,6 +58,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>
<MkFolder :defaultOpen="false">
<template #icon><i class="ti ti-message-2"></i></template>
<template #label>{{ i18n.ts.moderationNote }}</template>
<template #suffix>{{ moderationNote.length > 0 ? '...' : i18n.ts.none }}</template>
<div class="_gaps_s">
<MkTextarea v-model="moderationNote" manualSave>
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
</MkTextarea>
</div>
</MkFolder>
<div v-if="report.assignee">
{{ i18n.ts.moderator }}:
<MkAcct :user="report.assignee"/>
@ -60,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { provide, ref } from 'vue';
import { provide, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
@ -71,6 +89,8 @@ import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue';
import RouterView from '@/components/global/RouterView.vue';
import { useRouterFactory } from '@/router/supplier';
import MkTextarea from '@/components/MkTextarea.vue';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@ -86,22 +106,48 @@ targetRouter.init();
const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
reporterRouter.init();
function resolve() {
const moderationNote = ref(props.report.moderationNote ?? '');
watch(moderationNote, async () => {
os.apiWithDialog('admin/update-abuse-user-report', {
reportId: props.report.id,
moderationNote: moderationNote.value,
}).then(() => {
});
});
function resolve(resolvedAs) {
os.apiWithDialog('admin/resolve-abuse-user-report', {
reportId: props.report.id,
resolvedAs,
}).then(() => {
emit('resolved', props.report.id);
});
}
function resolveAndForward() {
os.apiWithDialog('admin/resolve-abuse-user-report', {
forward: true,
function forward() {
os.apiWithDialog('admin/forward-abuse-user-report', {
reportId: props.report.id,
}).then(() => {
emit('resolved', props.report.id);
});
}
function showMenu(ev: MouseEvent) {
os.popupMenu([{
icon: 'ti ti-id',
text: 'Copy ID',
action: () => {
copyToClipboard(props.report.id);
},
}, {
icon: 'ti ti-json',
text: 'Copy JSON',
action: () => {
copyToClipboard(JSON.stringify(props.report, null, '\t'));
},
}], ev.currentTarget ?? ev.target);
}
</script>
<style lang="scss" module>

View file

@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[
$style.root,
tail === 'left' ? $style.left : $style.right,
negativeMargin === true && $style.negativeMergin,
negativeMargin === true && $style.negativeMargin,
shadow === true && $style.shadow,
]"
>
@ -54,7 +54,7 @@ withDefaults(defineProps<{
&.left {
padding-left: calc(var(--fukidashi-radius) * .13);
&.negativeMergin {
&.negativeMargin {
margin-left: calc(calc(var(--fukidashi-radius) * .13) * -1);
}
}
@ -62,7 +62,7 @@ withDefaults(defineProps<{
&.right {
padding-right: calc(var(--fukidashi-radius) * .13);
&.negativeMergin {
&.negativeMargin {
margin-right: calc(calc(var(--fukidashi-radius) * .13) * -1);
}
}

View file

@ -437,9 +437,11 @@ onBeforeUnmount(() => {
&.big:not(.asDrawer) {
> .menu {
min-width: 230px;
> .item {
padding: 6px 20px;
font-size: 1em;
font-size: 0.95em;
line-height: 24px;
}
}

View file

@ -83,7 +83,7 @@ import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/br
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
const emit = defineEmits<{
(ev: 'login', v: Misskey.entities.SigninResponse): void;
(ev: 'login', v: Misskey.entities.SigninFlowResponse): void;
}>();
const props = withDefaults(defineProps<{
@ -212,23 +212,63 @@ async function onTotpSubmitted(token: string) {
}
}
async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<Misskey.entities.SigninResponse> {
async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promise<Misskey.entities.SigninFlowResponse> {
const _req = {
username: req.username ?? userInfo.value?.username,
...req,
};
function assertIsSigninRequest(x: Partial<Misskey.entities.SigninRequest>): x is Misskey.entities.SigninRequest {
function assertIsSigninFlowRequest(x: Partial<Misskey.entities.SigninFlowRequest>): x is Misskey.entities.SigninFlowRequest {
return x.username != null;
}
if (!assertIsSigninRequest(_req)) {
if (!assertIsSigninFlowRequest(_req)) {
throw new Error('Invalid request');
}
return await misskeyApi('signin', _req).then(async (res) => {
emit('login', res);
await onLoginSucceeded(res);
return await misskeyApi('signin-flow', _req).then(async (res) => {
if (res.finished) {
emit('login', res);
await onLoginSucceeded(res);
} else {
switch (res.next) {
case 'captcha': {
needCaptcha.value = true;
page.value = 'password';
break;
}
case 'password': {
needCaptcha.value = false;
page.value = 'password';
break;
}
case 'totp': {
page.value = 'totp';
break;
}
case 'passkey': {
if (webAuthnSupported()) {
credentialRequest.value = parseRequestOptionsFromJSON({
publicKey: res.authRequest,
});
page.value = 'passkey';
} else {
page.value = 'totp';
}
break;
}
}
if (doingPasskeyFromInputPage.value === true) {
doingPasskeyFromInputPage.value = false;
page.value = 'input';
password.value = '';
}
passwordPageEl.value?.resetCaptcha();
nextTick(() => {
waiting.value = false;
});
}
return res;
}).catch((err) => {
onSigninApiError(err);
@ -236,7 +276,7 @@ async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<M
});
}
async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
async function onLoginSucceeded(res: Misskey.entities.SigninFlowResponse & { finished: true; }) {
if (props.autoSet) {
await login(res.i);
}
@ -245,112 +285,82 @@ async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
function onSigninApiError(err?: any): void {
const id = err?.id ?? null;
if (typeof err === 'object' && 'next' in err) {
switch (err.next) {
case 'captcha': {
needCaptcha.value = true;
page.value = 'password';
break;
}
case 'password': {
needCaptcha.value = false;
page.value = 'password';
break;
}
case 'totp': {
page.value = 'totp';
break;
}
case 'passkey': {
if (webAuthnSupported() && 'authRequest' in err) {
credentialRequest.value = parseRequestOptionsFromJSON({
publicKey: err.authRequest,
});
page.value = 'passkey';
} else {
page.value = 'totp';
}
break;
}
switch (id) {
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.noSuchUser,
});
break;
}
} else {
switch (id) {
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.noSuchUser,
});
break;
}
case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectPassword,
});
break;
}
case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
showSuspendedDialog();
break;
}
case '22d05606-fbcf-421a-a2db-b32610dcfd1b': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.rateLimitExceeded,
});
break;
}
case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectTotp,
});
break;
}
case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.unknownWebAuthnKey,
});
break;
}
case '93b86c4b-72f9-40eb-9815-798928603d1e': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationFailed,
});
break;
}
case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationFailed,
});
break;
}
case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
});
break;
}
default: {
console.error(err);
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: JSON.stringify(err),
});
}
case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectPassword,
});
break;
}
case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
showSuspendedDialog();
break;
}
case '22d05606-fbcf-421a-a2db-b32610dcfd1b': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.rateLimitExceeded,
});
break;
}
case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectTotp,
});
break;
}
case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.unknownWebAuthnKey,
});
break;
}
case '93b86c4b-72f9-40eb-9815-798928603d1e': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationFailed,
});
break;
}
case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationFailed,
});
break;
}
case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
});
break;
}
default: {
console.error(err);
os.alert({
type: 'error',
title: i18n.ts.loginFailed,
text: JSON.stringify(err),
});
}
}

View file

@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
(ev: 'signup', user: Misskey.entities.SigninResponse): void;
(ev: 'signup', user: Misskey.entities.SigninFlowResponse): void;
(ev: 'signupEmailPending'): void;
}>();
@ -269,14 +269,19 @@ async function onSubmit(): Promise<void> {
});
emit('signupEmailPending');
} else {
const res = await misskeyApi('signin', {
const res = await misskeyApi('signin-flow', {
username: username.value,
password: password.value,
});
emit('signup', res);
if (props.autoSet) {
if (props.autoSet && res.finished) {
return login(res.i);
} else {
os.alert({
type: 'error',
text: i18n.ts.somethingHappened,
});
}
}
} catch {

View file

@ -47,7 +47,7 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
(ev: 'done', res: Misskey.entities.SigninResponse): void;
(ev: 'done', res: Misskey.entities.SigninFlowResponse): void;
(ev: 'closed'): void;
}>();
@ -55,7 +55,7 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const isAcceptedServerRule = ref(false);
function onSignup(res: Misskey.entities.SigninResponse) {
function onSignup(res: Misskey.entities.SigninFlowResponse) {
emit('done', res);
dialog.value?.close();
}

View file

@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTextarea v-model="moderationNote" manualSave>
<template #label>{{ i18n.ts.moderationNote }}</template>
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
</MkTextarea>
<!--
@ -205,6 +206,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, defineAsyncComponent, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { url } from '@@/js/config.js';
import MkChart from '@/components/MkChart.vue';
import MkObjectView from '@/components/MkObjectView.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@ -220,7 +222,6 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { url } from '@@/js/config.js';
import { acct } from '@/filters/user.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';

View file

@ -12,6 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton>
</div>
<MkInfo v-if="!defaultStore.reactiveState.abusesTutorial.value" closable @close="closeTutorial()">
{{ i18n.ts._abuseUserReport.resolveTutorial }}
</MkInfo>
<div :class="$style.inputs" class="_gaps">
<MkSelect v-model="state" style="margin: 0; flex: 1;">
<template #label>{{ i18n.ts.state }}</template>
@ -56,7 +60,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, shallowRef, ref } from 'vue';
import XHeader from './_header_.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
@ -64,6 +67,8 @@ import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import { defaultStore } from '@/store.js';
const reports = shallowRef<InstanceType<typeof MkPagination>>();
@ -87,6 +92,10 @@ function resolved(reportId) {
reports.value?.removeItem(reportId);
}
function closeTutorial() {
defaultStore.set('abusesTutorial', false);
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);

View file

@ -165,6 +165,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateAbuseReportNote'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>
<details>
<summary>raw</summary>

View file

@ -55,7 +55,8 @@ const tab = ref('featured');
const featuredFlashsPagination = {
endpoint: 'flash/featured' as const,
noPaging: true,
limit: 5,
offsetMode: true,
};
const myFlashsPagination = {
endpoint: 'flash/my' as const,

View file

@ -51,6 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
<MkTextarea v-model="moderationNote" manualSave>
<template #label>{{ i18n.ts.moderationNote }}</template>
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
</MkTextarea>
</div>
</FormSection>

View file

@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="user.followedMessage != null" class="followedMessage">
<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow>
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
<div><Mfm :text="user.followedMessage" :author="user"/></div>
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
</MkFukidashi>
</div>
<div v-if="user.roles.length > 0" class="roles">
@ -64,6 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="iAmModerator" class="moderationNote">
<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
<template #label>{{ i18n.ts.moderationNote }}</template>
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
</MkTextarea>
<div v-else>
<MkButton small @click="editModerationNote = true">{{ i18n.ts.addModerationNote }}</MkButton>
@ -159,6 +160,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { getScrollPosition } from '@@/js/scroll.js';
import MkNote from '@/components/MkNote.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
import MkAccountMoved from '@/components/MkAccountMoved.vue';
@ -168,7 +170,6 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkOmit from '@/components/MkOmit.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkButton from '@/components/MkButton.vue';
import { getScrollPosition } from '@@/js/scroll.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
import number from '@/filters/number.js';
import { userPage } from '@/filters/user.js';
@ -182,6 +183,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 MkSparkle from '@/components/MkSparkle.vue';
function calcAge(birthdate: string): number {
const date = new Date(birthdate);
@ -472,7 +474,7 @@ onUnmounted(() => {
> .fukidashi {
display: block;
--fukidashi-bg: color-mix(in srgb, var(--love), var(--panel) 85%);
--fukidashi-bg: color-mix(in srgb, var(--accent), var(--panel) 85%);
--fukidashi-radius: 16px;
font-size: 0.9em;

View file

@ -78,6 +78,10 @@ export const defaultStore = markRaw(new Storage('base', {
global: false,
},
},
abusesTutorial: {
where: 'account',
default: false,
},
keepCw: {
where: 'account',
default: true,
@ -222,7 +226,7 @@ export const defaultStore = markRaw(new Storage('base', {
},
animatedMfm: {
where: 'device',
default: false,
default: true,
},
advancedMfm: {
where: 'device',