Merge aee1c34cf3 into 6718a54f6f
This commit is contained in:
commit
55a9d215ee
10 changed files with 148 additions and 25 deletions
|
|
@ -3,6 +3,7 @@
|
|||
### General
|
||||
- Feat: コンテンツの表示にログインを必須にできるように
|
||||
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
|
||||
- Fix: センシティブな画像をアイコン・バナーに指定できないように
|
||||
|
||||
### Client
|
||||
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
|
||||
|
|
|
|||
9
locales/index.d.ts
vendored
9
locales/index.d.ts
vendored
|
|
@ -5218,6 +5218,15 @@ export interface Locale extends ILocale {
|
|||
* 利用可能なロール
|
||||
*/
|
||||
"availableRoles": string;
|
||||
/**
|
||||
* センシティブなメディアは選択できません
|
||||
*/
|
||||
"cannotSelectSensitiveMedia": string;
|
||||
/**
|
||||
* 自分でセンシティブ設定を行っていないのにこのエラーが出ている場合、自動判定によりセンシティブなメディアとされている可能性があります。
|
||||
* サーバーの規則に照らして不要な場合は、ファイルのセンシティブ設定を解除してもう一度お試しください。
|
||||
*/
|
||||
"cannotSelectSensitiveMediaDescription": string;
|
||||
"_accountSettings": {
|
||||
/**
|
||||
* コンテンツの表示にログインを必須にする
|
||||
|
|
|
|||
|
|
@ -1300,6 +1300,8 @@ thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示に
|
|||
lockdown: "ロックダウン"
|
||||
pleaseSelectAccount: "アカウントを選択してください"
|
||||
availableRoles: "利用可能なロール"
|
||||
cannotSelectSensitiveMedia: "センシティブなメディアは選択できません"
|
||||
cannotSelectSensitiveMediaDescription: "自分でセンシティブ設定を行っていないのにこのエラーが出ている場合、自動判定によりセンシティブなメディアとされている可能性があります。\nサーバーの規則に照らして不要な場合は、ファイルのセンシティブ設定を解除してもう一度お試しください。"
|
||||
|
||||
_accountSettings:
|
||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||
|
|
|
|||
|
|
@ -74,6 +74,18 @@ export const meta = {
|
|||
id: '75aedb19-2afd-4e6d-87fc-67941256fa60',
|
||||
},
|
||||
|
||||
avatarIsSensitive: {
|
||||
message: 'The file specified as an avatar is marked as sensitive.',
|
||||
code: 'AVATAR_IS_SENSITIVE',
|
||||
id: '71bb5e53-4742-4609-b465-36081e131208',
|
||||
},
|
||||
|
||||
bannerIsSensitive: {
|
||||
message: 'The file specified as a banner is marked as sensitive.',
|
||||
code: 'BANNER_IS_SENSITIVE',
|
||||
id: 'e148b34c-9f33-4300-93e0-7817008fb366',
|
||||
},
|
||||
|
||||
noSuchPage: {
|
||||
message: 'No such page.',
|
||||
code: 'NO_SUCH_PAGE',
|
||||
|
|
@ -359,6 +371,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
||||
if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage);
|
||||
if (avatar.isSensitive) throw new ApiError(meta.errors.avatarIsSensitive);
|
||||
|
||||
updates.avatarId = avatar.id;
|
||||
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
|
||||
|
|
@ -377,6 +390,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
||||
if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
|
||||
if (banner.isSensitive) throw new ApiError(meta.errors.bannerIsSensitive);
|
||||
|
||||
updates.bannerId = banner.id;
|
||||
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div
|
||||
:class="[$style.root, { [$style.isSelected]: isSelected }]"
|
||||
:class="[$style.root, { [$style.isSelected]: isSelected, [$style.isDisabled]: isDisabled }]"
|
||||
draggable="true"
|
||||
:title="title"
|
||||
@click="onClick"
|
||||
|
|
@ -55,9 +55,11 @@ const props = withDefaults(defineProps<{
|
|||
file: Misskey.entities.DriveFile;
|
||||
folder: Misskey.entities.DriveFolder | null;
|
||||
isSelected?: boolean;
|
||||
isDisabled?: boolean;
|
||||
selectMode?: boolean;
|
||||
}>(), {
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
selectMode: false,
|
||||
});
|
||||
|
||||
|
|
@ -72,6 +74,8 @@ const isDragging = ref(false);
|
|||
const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`);
|
||||
|
||||
function onClick(ev: MouseEvent) {
|
||||
if (props.isDisabled) return;
|
||||
|
||||
if (props.selectMode) {
|
||||
emit('chosen', props.file);
|
||||
} else {
|
||||
|
|
@ -88,6 +92,8 @@ function onContextmenu(ev: MouseEvent) {
|
|||
}
|
||||
|
||||
function onDragstart(ev: DragEvent) {
|
||||
if (props.isDisabled) return;
|
||||
|
||||
if (ev.dataTransfer) {
|
||||
ev.dataTransfer.effectAllowed = 'move';
|
||||
ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
|
||||
|
|
@ -173,6 +179,23 @@ function onDragend() {
|
|||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.isDisabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
.thumbnail {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.name {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:folder="folder"
|
||||
:selectMode="select === 'file'"
|
||||
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
||||
:isDisabled="excludeSensitive && file.isSensitive"
|
||||
@chosen="chooseFile"
|
||||
@dragstart="isDragSource = true"
|
||||
@dragend="isDragSource = false"
|
||||
|
|
@ -115,9 +116,11 @@ const props = withDefaults(defineProps<{
|
|||
initialFolder?: Misskey.entities.DriveFolder;
|
||||
type?: string;
|
||||
multiple?: boolean;
|
||||
excludeSensitive?: boolean;
|
||||
select?: 'file' | 'folder' | null;
|
||||
}>(), {
|
||||
multiple: false,
|
||||
excludeSensitive: false,
|
||||
select: null,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
{{ multiple ? ((type === 'file') ? i18n.ts.selectFiles : i18n.ts.selectFolders) : ((type === 'file') ? i18n.ts.selectFile : i18n.ts.selectFolder) }}
|
||||
<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span>
|
||||
</template>
|
||||
<XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/>
|
||||
<XDrive :multiple="multiple" :excludeSensitive="excludeSensitive" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ import { i18n } from '@/i18n.js';
|
|||
withDefaults(defineProps<{
|
||||
type?: 'file' | 'folder';
|
||||
multiple: boolean;
|
||||
excludeSensitive: boolean;
|
||||
}>(), {
|
||||
type: 'file',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -569,11 +569,12 @@ export async function selectUser(opts: { includeSelf?: boolean; localOnly?: bool
|
|||
});
|
||||
}
|
||||
|
||||
export async function selectDriveFile(multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
export async function selectDriveFile(multiple: boolean, excludeSensitive: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
return new Promise(resolve => {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), {
|
||||
type: 'file',
|
||||
multiple,
|
||||
excludeSensitive,
|
||||
}, {
|
||||
done: files => {
|
||||
if (files) {
|
||||
|
|
@ -585,11 +586,12 @@ export async function selectDriveFile(multiple: boolean): Promise<Misskey.entiti
|
|||
});
|
||||
}
|
||||
|
||||
export async function selectDriveFolder(multiple: boolean): Promise<Misskey.entities.DriveFolder[]> {
|
||||
export async function selectDriveFolder(multiple: boolean, excludeSensitive: boolean): Promise<Misskey.entities.DriveFolder[]> {
|
||||
return new Promise(resolve => {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), {
|
||||
type: 'folder',
|
||||
multiple,
|
||||
excludeSensitive,
|
||||
}, {
|
||||
done: folders => {
|
||||
if (folders) {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ import { defaultStore } from '@/store.js';
|
|||
import { globalEvents } from '@/events.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
|
|
@ -223,7 +224,26 @@ function save() {
|
|||
}
|
||||
|
||||
function changeAvatar(ev) {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar, {
|
||||
excludeSensitive: true,
|
||||
additionalMenu: $i.avatarId ? [
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'button',
|
||||
text: i18n.ts.detach,
|
||||
icon: 'ti ti-circle-x',
|
||||
action: () => {
|
||||
os.apiWithDialog('i/update', {
|
||||
avatarId: null,
|
||||
}).then(() => {
|
||||
$i.avatarId = null;
|
||||
$i.avatarUrl = null;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
});
|
||||
},
|
||||
},
|
||||
] : undefined,
|
||||
}).then(async (file) => {
|
||||
let originalOrCropped = file;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
|
|
@ -239,18 +259,43 @@ function changeAvatar(ev) {
|
|||
});
|
||||
}
|
||||
|
||||
const i = await os.apiWithDialog('i/update', {
|
||||
await os.apiWithDialog('i/update', {
|
||||
avatarId: originalOrCropped.id,
|
||||
}, undefined, {
|
||||
'71bb5e53-4742-4609-b465-36081e131208': {
|
||||
title: i18n.ts.cannotSelectSensitiveMedia,
|
||||
text: i18n.ts.cannotSelectSensitiveMediaDescription,
|
||||
},
|
||||
}).then(() => {
|
||||
$i.avatarId = originalOrCropped.id;
|
||||
$i.avatarUrl = originalOrCropped.url;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
claimAchievement('profileFilled');
|
||||
});
|
||||
$i.avatarId = i.avatarId;
|
||||
$i.avatarUrl = i.avatarUrl;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
claimAchievement('profileFilled');
|
||||
});
|
||||
}
|
||||
|
||||
function changeBanner(ev) {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner, {
|
||||
excludeSensitive: true,
|
||||
additionalMenu: $i.bannerId ? [
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'button',
|
||||
text: i18n.ts.detach,
|
||||
icon: 'ti ti-circle-x',
|
||||
action: () => {
|
||||
os.apiWithDialog('i/update', {
|
||||
bannerId: null,
|
||||
}).then(() => {
|
||||
$i.bannerId = null;
|
||||
$i.bannerUrl = null;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
});
|
||||
},
|
||||
},
|
||||
] : undefined,
|
||||
}).then(async (file) => {
|
||||
let originalOrCropped = file;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
|
|
@ -266,12 +311,18 @@ function changeBanner(ev) {
|
|||
});
|
||||
}
|
||||
|
||||
const i = await os.apiWithDialog('i/update', {
|
||||
await os.apiWithDialog('i/update', {
|
||||
bannerId: originalOrCropped.id,
|
||||
}, undefined, {
|
||||
'e148b34c-9f33-4300-93e0-7817008fb366': {
|
||||
title: i18n.ts.cannotSelectSensitiveMedia,
|
||||
text: i18n.ts.cannotSelectSensitiveMediaDescription,
|
||||
},
|
||||
}).then(() => {
|
||||
$i.bannerId = originalOrCropped.id;
|
||||
$i.bannerUrl = originalOrCropped.url;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
});
|
||||
$i.bannerId = i.bannerId;
|
||||
$i.bannerUrl = i.bannerUrl;
|
||||
globalEvents.emit('requestClearPageCache');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,20 @@
|
|||
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { uploadFile } from '@/scripts/upload.js';
|
||||
import { deepMerge } from '@/scripts/merge.js';
|
||||
|
||||
type SelectFileOptions = {
|
||||
multiple?: boolean;
|
||||
excludeSensitive?: boolean;
|
||||
additionalMenu?: MenuItem[];
|
||||
};
|
||||
|
||||
export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise<Misskey.entities.DriveFile[]> {
|
||||
return new Promise((res, rej) => {
|
||||
|
|
@ -39,9 +47,9 @@ export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promi
|
|||
});
|
||||
}
|
||||
|
||||
export function chooseFileFromDrive(multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
export function chooseFileFromDrive(multiple: boolean, excludeSensitive: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
return new Promise((res, rej) => {
|
||||
os.selectDriveFile(multiple).then(files => {
|
||||
os.selectDriveFile(multiple, excludeSensitive).then(files => {
|
||||
res(files);
|
||||
});
|
||||
});
|
||||
|
|
@ -80,7 +88,16 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
|||
});
|
||||
}
|
||||
|
||||
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
function select(src: HTMLElement | EventTarget | null, label: string | null, options?: SelectFileOptions): Promise<Misskey.entities.DriveFile[]> {
|
||||
const _options = deepMerge(options ?? {}, {
|
||||
multiple: false,
|
||||
|
||||
/** ドライブファイル選択時のみに適用 */
|
||||
excludeSensitive: false,
|
||||
|
||||
additionalMenu: [] as MenuItem[],
|
||||
});
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
|
||||
|
||||
|
|
@ -94,23 +111,23 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul
|
|||
}, {
|
||||
text: i18n.ts.upload,
|
||||
icon: 'ti ti-upload',
|
||||
action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)),
|
||||
action: () => chooseFileFromPc(_options.multiple, keepOriginal.value).then(files => res(files)),
|
||||
}, {
|
||||
text: i18n.ts.fromDrive,
|
||||
icon: 'ti ti-cloud',
|
||||
action: () => chooseFileFromDrive(multiple).then(files => res(files)),
|
||||
action: () => chooseFileFromDrive(_options.multiple, _options.excludeSensitive).then(files => res(files)),
|
||||
}, {
|
||||
text: i18n.ts.fromUrl,
|
||||
icon: 'ti ti-link',
|
||||
action: () => chooseFileFromUrl().then(file => res([file])),
|
||||
}], src);
|
||||
}, ...(_options.additionalMenu)], src);
|
||||
});
|
||||
}
|
||||
|
||||
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
||||
return select(src, label, false).then(files => files[0]);
|
||||
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null, options?: { excludeSensitive?: boolean; additionalMenu?: MenuItem[]; }): Promise<Misskey.entities.DriveFile> {
|
||||
return select(src, label, { ...(options ? options : {}), multiple: false }).then(files => files[0]);
|
||||
}
|
||||
|
||||
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
||||
return select(src, label, true);
|
||||
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null, options?: { excludeSensitive?: boolean; additionalMenu?: MenuItem[]; }): Promise<Misskey.entities.DriveFile[]> {
|
||||
return select(src, label, { ...(options ? options : {}), multiple: true });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue