1
0
Fork 0
mirror of https://github.com/paricafe/misskey.git synced 2025-04-21 03:56:12 -05:00

Merge branch 'develop' into pari

This commit is contained in:
FLY_MC 2025-02-12 16:25:58 +08:00
commit 999ee8753b
20 changed files with 119 additions and 16 deletions

View file

@ -1,15 +1,20 @@
## Unreleased
### General
-
- Feat: アクセストークン発行時に通知するように
### Client
- Feat: 投稿フォームで画像をプレビュー可能に
- Enhance: 投稿フォームの「迷惑になる可能性があります」のダイアログを表示する条件においてCWを考慮するように
- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
- Enhance: クライアントエラー画面の多言語対応
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
### Server
- Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
## 2025.2.0
@ -47,7 +52,6 @@
* β版として公開のため、旧画面も引き続き利用可能です
### Client
- Feat: 投稿フォームで画像をプレビュー可能に
- Enhance: PC画面でチャンネルが複数列で表示されるように
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
- Enhance: 照会に失敗した場合、その理由を表示するように

8
locales/index.d.ts vendored
View file

@ -9489,6 +9489,14 @@ export interface Locale extends ILocale {
*
*/
"login": string;
/**
*
*/
"createToken": string;
/**
* {text}
*/
"createTokenDescription": ParameterizedString<"text">;
"_types": {
/**
*

View file

@ -2504,6 +2504,8 @@ _notification:
flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが完了しました"
login: "ログインがありました"
createToken: "アクセストークンが作成されました"
createTokenDescription: "心当たりがない場合は「{text}」を通じてアクセストークンを削除してください。"
_types:
all: "すべて"

View file

@ -173,7 +173,8 @@ export class DriveService {
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
const prefix = this.meta.objectStoragePrefix ? `${this.meta.objectStoragePrefix}/` : '';
const key = `${prefix}${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`;
// for alts
@ -190,7 +191,7 @@ export class DriveService {
];
if (alts.webpublic) {
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicKey = `${prefix}webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@ -198,7 +199,7 @@ export class DriveService {
}
if (alts.thumbnail) {
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailKey = `${prefix}thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);

View file

@ -90,6 +90,10 @@ export type MiNotification = {
type: 'login';
id: string;
createdAt: string;
} | {
type: 'createToken';
id: string;
createdAt: string;
} | {
type: 'app';
id: string;

View file

@ -332,6 +332,16 @@ export const packedNotificationSchema = {
enum: ['login'],
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['createToken'],
},
},
}, {
type: 'object',
properties: {

View file

@ -516,6 +516,7 @@ export const meta = {
},
federation: {
type: 'string',
enum: ['all', 'specified', 'none'],
optional: false, nullable: false,
},
federationHosts: {

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DI } from '@/di-symbols.js';
@ -50,6 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository,
private idService: IdService,
private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
// Generate access token
@ -71,6 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission: ps.permission,
});
// アクセストークンが生成されたことを通知
this.notificationService.createNotification(me.id, 'createToken', {});
return {
token: accessToken,
};

View file

@ -18,6 +18,7 @@
* achievementEarned -
* exportCompleted -
* login -
* createToken -
* app -
* test -
*/
@ -36,6 +37,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
'createToken',
'app',
'test',
] as const;

View file

@ -70,6 +70,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
'createToken',
'test',
'app',
] as const;

View file

@ -91,10 +91,11 @@ SPDX-License-Identifier: AGPL-3.0-only
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/scripts/hotkey.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import type { Keymap } from '@/scripts/hotkey.js';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
@ -227,6 +228,16 @@ function showMenu(ev: MouseEvent) {
});
}
if (defaultStore.state.devMode) {
menu.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.audio.id);
},
});
}
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',

View file

@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { watch, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import bytes from '@/filters/bytes.js';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
@ -143,6 +144,16 @@ function showMenu(ev: MouseEvent) {
});
}
if (defaultStore.state.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.image.id);
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}

View file

@ -113,6 +113,7 @@ import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/scripts/hotkey.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
import { defaultStore } from '@/store.js';
@ -252,6 +253,16 @@ function showMenu(ev: MouseEvent) {
});
}
if (defaultStore.state.devMode) {
menu.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.video.id);
},
});
}
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<div :class="$style.head">
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
[$style.t_createToken]: notification.type === 'createToken',
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
}]"
>
@ -41,6 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
<i v-else-if="notification.type === 'createToken'" class="ti ti-key"></i>
<template v-else-if="notification.type === 'roleAssigned'">
<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
<i v-else class="ti ti-badges"></i>
@ -61,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
<span v-else-if="notification.type === 'createToken'">{{ i18n.ts._notification.createToken }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
@ -107,6 +110,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
{{ i18n.ts.showFile }}
</MkA>
<MkA v-else-if="notification.type === 'createToken'" :class="$style.text" to="/settings/apps">
<Mfm :text="i18n.tsx._notification.createTokenDescription({ text: i18n.ts.manageAccessTokens })"/>
</MkA>
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template>
@ -357,6 +363,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
.t_createToken {
padding: 3px;
background: var(--eventOther);
pointer-events: none;
}
.tail {
flex: 1;
min-width: 0;

View file

@ -36,6 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent, inject } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu';
import { defaultStore } from '@/store';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
@ -196,6 +198,16 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
action: () => { detachAndDeleteMedia(file); },
});
if (defaultStore.state.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
menuShowing = true;
}

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<iframe
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin"
scrolling="no"
:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
:class="$style.playerIframe"

View file

@ -422,7 +422,7 @@ async function deleteAccount() {
}
async function assignRole() {
const roles = await misskeyApi('admin/roles/list');
const roles = await misskeyApi('admin/roles/list').then(it => it.filter(r => r.target === 'manual'));
const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign,

View file

@ -81,7 +81,7 @@ const $i = signinRequired();
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login'] satisfies (typeof notificationTypes[number])[] as string[];
const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken'] satisfies (typeof notificationTypes[number])[] as string[];
const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);

View file

@ -98,7 +98,7 @@ describe('MkUrlPreview', () => {
assert.strictEqual(iframe?.src, 'https://example.local/player?autoplay=1&auto_play=1');
assert.strictEqual(
iframe?.sandbox.toString(),
'allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin',
'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin',
);
});

View file

@ -4410,6 +4410,13 @@ export type components = {
createdAt: string;
/** @enum {string} */
type: 'login';
} | {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
type: 'createToken';
} | ({
/** Format: id */
id: string;
@ -8406,7 +8413,8 @@ export type operations = {
urlPreviewRequireContentLength: boolean;
urlPreviewUserAgent: string | null;
urlPreviewSummaryProxyUrl: string | null;
federation: string;
/** @enum {string} */
federation: 'all' | 'specified' | 'none';
federationHosts: string[];
};
};
@ -20195,8 +20203,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@ -20263,8 +20271,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
};
};
};