This commit is contained in:
syuilo 2023-05-18 20:17:32 +09:00
parent db1098a180
commit 231506772a
6 changed files with 73 additions and 50 deletions

View File

@ -1052,6 +1052,7 @@ failedToPreviewUrl: "プレビューできません"
update: "更新" update: "更新"
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール" rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。" rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!" accountCreated: "アカウントの作成が完了しました!"

View File

@ -26,7 +26,8 @@ export class EmojiEntityService {
category: emoji.category, category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ) // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl, url: emoji.publicUrl || emoji.originalUrl,
isSensitive: emoji.isSensitive, isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
}; };
} }

View File

@ -24,7 +24,16 @@ export const packedEmojiSimpleSchema = {
}, },
isSensitive: { isSensitive: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: true, nullable: false,
},
roleIdsThatCanBeUsedThisEmojiAsReaction: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
}, },
}, },
} as const; } as const;

View File

@ -3,11 +3,11 @@ import * as misskey from 'misskey-js';
import { showSuspendedDialog } from './scripts/show-suspended-dialog'; import { showSuspendedDialog } from './scripts/show-suspended-dialog';
import { i18n } from './i18n'; import { i18n } from './i18n';
import { miLocalStorage } from './local-storage'; import { miLocalStorage } from './local-storage';
import { MenuButton } from './types/menu';
import { del, get, set } from '@/scripts/idb-proxy'; import { del, get, set } from '@/scripts/idb-proxy';
import { apiUrl } from '@/config'; import { apiUrl } from '@/config';
import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { waiting, api, popup, popupMenu, success, alert } from '@/os';
import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
import { MenuButton } from './types/menu';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -101,57 +101,57 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => { .then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
if (res.status >= 500 && res.status < 600) { if (res.status >= 500 && res.status < 600) {
// サーバーエラー(5xx)の場合をrejectとする // サーバーエラー(5xx)の場合をrejectとする
// 認証エラーなど4xxはresolve // 認証エラーなど4xxはresolve
return fail2(res); return fail2(res);
} }
res.json().then(done2, fail2); res.json().then(done2, fail2);
})) }))
.then(async res => { .then(async res => {
if (res.error) { if (res.error) {
if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
// SUSPENDED // SUSPENDED
if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
await showSuspendedDialog(); await showSuspendedDialog();
} }
} else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') { } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') {
// USER_IS_DELETED // USER_IS_DELETED
// アカウントが削除されている // アカウントが削除されている
if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
await alert({ await alert({
type: 'error', type: 'error',
title: i18n.ts.accountDeleted, title: i18n.ts.accountDeleted,
text: i18n.ts.accountDeletedDescription, text: i18n.ts.accountDeletedDescription,
}); });
} }
} else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') { } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') {
// AUTHENTICATION_FAILED // AUTHENTICATION_FAILED
// トークンが無効化されていたりアカウントが削除されたりしている // トークンが無効化されていたりアカウントが削除されたりしている
if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
await alert({
type: 'error',
title: i18n.ts.tokenRevoked,
text: i18n.ts.tokenRevokedDescription,
});
}
} else {
await alert({ await alert({
type: 'error', type: 'error',
title: i18n.ts.tokenRevoked, title: i18n.ts.failedToFetchAccountInformation,
text: i18n.ts.tokenRevokedDescription, text: JSON.stringify(res.error),
}); });
} }
} else {
await alert({
type: 'error',
title: i18n.ts.failedToFetchAccountInformation,
text: JSON.stringify(res.error),
});
}
// rejectかつ理由がtrueの場合、削除対象であることを示す // rejectかつ理由がtrueの場合、削除対象であることを示す
fail(true); fail(true);
} else { } else {
(res as Account).token = token; (res as Account).token = token;
done(res as Account); done(res as Account);
} }
}) })
.catch(fail); .catch(fail);
}); });
} }
@ -305,3 +305,7 @@ export async function openAccountMenu(opts: {
}); });
} }
} }
if (_DEV_) {
(window as any).$i = $i;
}

View File

@ -69,8 +69,8 @@
<XSection <XSection
v-for="category in customEmojiCategories" v-for="category in customEmojiCategories"
:key="`custom:${category}`" :key="`custom:${category}`"
:initial-shown="false" :initialShown="false"
:emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).map(e => `:${e.name}:`))" :emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).filter(filterAvailable).map(e => `:${e.name}:`))"
@chosen="chosen" @chosen="chosen"
> >
{{ category || i18n.ts.other }} {{ category || i18n.ts.other }}
@ -102,6 +102,7 @@ import { deviceKind } from '@/scripts/device-kind';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { customEmojiCategories, customEmojis } from '@/custom-emojis'; import { customEmojiCategories, customEmojis } from '@/custom-emojis';
import { $i } from '@/account';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
@ -274,10 +275,14 @@ watch(q, () => {
return matches; return matches;
}; };
searchResultCustom.value = Array.from(searchCustom()); searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable);
searchResultUnicode.value = Array.from(searchUnicode()); searchResultUnicode.value = Array.from(searchUnicode());
}); });
function filterAvailable(emoji: Misskey.entities.CustomEmoji): boolean {
return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
}
function focus() { function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
searchEl.value?.focus({ searchEl.value?.focus({

View File

@ -41,16 +41,19 @@
</MkInput> </MkInput>
<MkFolder> <MkFolder>
<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template> <template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
<div class="_gaps"> <template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
<MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo>
<div class="_gaps">
<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> <MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem"> <div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem">
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false"/> <MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button> <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
</div> </div>
<MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo>
<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
</div> </div>
</MkFolder> </MkFolder>
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch> <MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
@ -128,8 +131,8 @@ async function removeRole(role, ev) {
async function done() { async function done() {
const params = { const params = {
name, name,
category, category: category === '' ? null : category,
aliases: aliases.split(' '), aliases: aliases.split(' ').filter(x => x !== ''),
license: license === '' ? null : license, license: license === '' ? null : license,
isSensitive, isSensitive,
localOnly, localOnly,