feat(client): make possible to switch account instantly in post form

This commit is contained in:
syuilo 2022-01-21 20:17:31 +09:00
parent b388b78892
commit 331afcb96a
8 changed files with 119 additions and 40 deletions

View file

@ -21,6 +21,7 @@
### Improvements ### Improvements
- カスタム絵文字一括編集機能 - カスタム絵文字一括編集機能
- カスタム絵文字一括インポート - カスタム絵文字一括インポート
- 投稿フォームで一時的に投稿するアカウントを切り替えられるように
### Bugfixes ### Bugfixes

View file

@ -129,7 +129,12 @@ export async function login(token: Account['token'], redirect?: string) {
unisonReload(); unisonReload();
} }
export async function openAccountMenu(ev: MouseEvent) { export async function openAccountMenu(opts: {
includeCurrentAccount?: boolean;
withExtraOperation: boolean;
active?: misskey.entities.UserDetailed['id'];
onChoose?: (account: misskey.entities.UserDetailed) => void;
}, ev: MouseEvent) {
function showSigninDialog() { function showSigninDialog() {
popup(import('@/components/signin-dialog.vue'), {}, { popup(import('@/components/signin-dialog.vue'), {}, {
done: res => { done: res => {
@ -148,7 +153,7 @@ export async function openAccountMenu(ev: MouseEvent) {
}, 'closed'); }, 'closed');
} }
async function switchAccount(account: any) { async function switchAccount(account: misskey.entities.UserDetailed) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
const token = storedAccounts.find(x => x.id === account.id).token; const token = storedAccounts.find(x => x.id === account.id).token;
switchAccountWithToken(token); switchAccountWithToken(token);
@ -161,41 +166,58 @@ export async function openAccountMenu(ev: MouseEvent) {
const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id));
const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) });
function createItem(account: misskey.entities.UserDetailed) {
return {
type: 'user',
user: account,
active: opts.active != null ? opts.active === account.id : false,
action: () => {
if (opts.onChoose) {
opts.onChoose(account);
} else {
switchAccount(account);
}
},
};
}
const accountItemPromises = storedAccounts.map(a => new Promise(res => { const accountItemPromises = storedAccounts.map(a => new Promise(res => {
accountsPromise.then(accounts => { accountsPromise.then(accounts => {
const account = accounts.find(x => x.id === a.id); const account = accounts.find(x => x.id === a.id);
if (account == null) return res(null); if (account == null) return res(null);
res({ res(createItem(account));
type: 'user',
user: account,
action: () => { switchAccount(account); }
});
}); });
})); }));
popupMenu([...[{ if (opts.withExtraOperation) {
type: 'link', popupMenu([...[{
text: i18n.locale.profile, type: 'link',
to: `/@${ $i.username }`, text: i18n.locale.profile,
avatar: $i, to: `/@${ $i.username }`,
}, null, ...accountItemPromises, { avatar: $i,
icon: 'fas fa-plus', }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
text: i18n.locale.addAccount, icon: 'fas fa-plus',
action: () => { text: i18n.locale.addAccount,
popupMenu([{ action: () => {
text: i18n.locale.existingAccount, popupMenu([{
action: () => { showSigninDialog(); }, text: i18n.locale.existingAccount,
}, { action: () => { showSigninDialog(); },
text: i18n.locale.createAccount, }, {
action: () => { createAccount(); }, text: i18n.locale.createAccount,
}], ev.currentTarget || ev.target); action: () => { createAccount(); },
}, }], ev.currentTarget || ev.target);
}, { },
type: 'link', }, {
icon: 'fas fa-users', type: 'link',
text: i18n.locale.manageAccounts, icon: 'fas fa-users',
to: `/settings/accounts`, text: i18n.locale.manageAccounts,
}]], ev.currentTarget || ev.target, { to: `/settings/accounts`,
align: 'left' }]], ev.currentTarget || ev.target, {
}); align: 'left'
});
} else {
popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget || ev.target, {
align: 'left'
});
}
} }

View file

@ -8,6 +8,9 @@
> >
<header> <header>
<button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button>
<button v-click-anime class="account _button" @click="openAccountMenu">
<MkAvatar :user="postAccount ?? $i" class="avatar"/>
</button>
<div> <div>
<span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span>
<span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span>
@ -83,7 +86,7 @@ import { throttle } from 'throttle-debounce';
import MkInfo from '@/components/ui/info.vue'; import MkInfo from '@/components/ui/info.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { $i } from '@/account'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
const modal = inject('modal'); const modal = inject('modal');
@ -553,8 +556,15 @@ async function post() {
} }
} }
let token = undefined;
if (postAccount) {
const storedAccounts = await getAccounts();
token = storedAccounts.find(x => x.id === postAccount.id)?.token;
}
posting = true; posting = true;
os.api('notes/create', data).then(() => { os.api('notes/create', data, token).then(() => {
clear(); clear();
nextTick(() => { nextTick(() => {
deleteDraft(); deleteDraft();
@ -585,7 +595,7 @@ function insertMention() {
}); });
} }
async function insertEmoji(ev) { async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl);
} }
@ -602,6 +612,23 @@ function showActions(ev) {
})), ev.currentTarget || ev.target); })), ev.currentTarget || ev.target);
} }
let postAccount = $ref<misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id,
onChoose: (account) => {
if (account.id === $i.id) {
postAccount = null;
} else {
postAccount = account;
}
},
}, ev);
}
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {
focus(); focus();
@ -678,6 +705,19 @@ onMounted(() => {
line-height: 66px; line-height: 66px;
} }
> .account {
height: 100%;
aspect-ratio: 1/1;
display: inline-flex;
vertical-align: bottom;
> .avatar {
width: 28px;
height: 28px;
margin: auto;
}
}
> div { > div {
position: absolute; position: absolute;
top: 0; top: 0;

View file

@ -24,7 +24,7 @@
<span>{{ item.text }}</span> <span>{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
</a> </a>
<button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" @click="clicked(item.action, $event)"> <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)">
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
</button> </button>

View file

@ -61,7 +61,11 @@ export default defineComponent({
otherMenuItemIndicated, otherMenuItemIndicated,
post: os.post, post: os.post,
search, search,
openAccountMenu, openAccountMenu:(ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);
},
more: () => { more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, { os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed'); }, 'closed');

View file

@ -76,7 +76,11 @@ export default defineComponent({
iconOnly, iconOnly,
post: os.post, post: os.post,
search, search,
openAccountMenu, openAccountMenu:(ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);
},
more: () => { more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, { os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed'); }, 'closed');

View file

@ -105,7 +105,11 @@ export default defineComponent({
}, 'closed'); }, 'closed');
}, },
openAccountMenu, openAccountMenu:(ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);
},
} }
}); });
</script> </script>

View file

@ -125,7 +125,11 @@ export default defineComponent({
}, 'closed'); }, 'closed');
}, },
openAccountMenu, openAccountMenu:(ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);
},
} }
}); });
</script> </script>