ニックネーム機能

close #25
This commit is contained in:
Ebise Lutica 2023-04-13 15:26:05 +09:00 committed by NoriDev
parent 8d86f67a93
commit 105f41796b
8 changed files with 196 additions and 7 deletions

View file

@ -37,6 +37,7 @@ import { useStream } from '@/stream';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { claimAchievement } from '@/scripts/achievements'; import { claimAchievement } from '@/scripts/achievements';
import { $i } from '@/account'; import { $i } from '@/account';
import {userName} from "@/filters/user";
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed, user: Misskey.entities.UserDetailed,
@ -73,7 +74,7 @@ async function onClick() {
if (isFollowing) { if (isFollowing) {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',
text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), text: i18n.t('unfollowConfirm', { name: userName(props.user) }),
}); });
if (canceled) return; if (canceled) return;

View file

@ -1,10 +1,11 @@
<template> <template>
<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/> <Mfm :text="userName(user)" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import * as misskey from 'cherrypick-js'; import * as misskey from 'cherrypick-js';
import { userName } from '@/filters/user';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
user: misskey.entities.User; user: misskey.entities.User;

View file

@ -1,13 +1,17 @@
import * as misskey from 'cherrypick-js'; import * as misskey from 'cherrypick-js';
import * as Acct from 'cherrypick-js/built/acct'; import * as Acct from 'cherrypick-js/built/acct';
import { url } from '@/config'; import { url } from '@/config';
import {defaultStore} from "@/store";
export const acct = (user: misskey.Acct) => { export const acct = (user: misskey.Acct) => {
return Acct.toString(user); return Acct.toString(user);
}; };
export const userName = (user: misskey.entities.User) => { export const userName = (user: misskey.entities.User) => {
return user.name || user.username; if (!defaultStore.state.nicknameEnabled) {
return user.name || user.username;
}
return defaultStore.reactiveState.nicknameMap.value[user.id] || user.name || user.username;
}; };
export const userPage = (user: misskey.Acct, path?, absolute = false) => { export const userPage = (user: misskey.Acct, path?, absolute = false) => {

View file

@ -9,11 +9,12 @@ import * as Acct from 'cherrypick-js/built/acct';
import * as os from '@/os'; import * as os from '@/os';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import {userName} from "@/filters/user";
async function follow(user): Promise<void> { async function follow(user): Promise<void> {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'question', type: 'question',
text: i18n.t('followConfirm', { name: user.name || user.username }), text: i18n.t('followConfirm', { name: userName(user) }),
}); });
if (canceled) { if (canceled) {

View file

@ -0,0 +1,135 @@
<template>
<div class="_gaps_m">
<h1>CherryPick</h1>
<FormSection>
<template #label>独自機能</template>
<div class="_gaps_m">
<div>Ebisskeyが追加する独自機能を有効無効にします</div>
<MkSwitch v-model="nicknameEnabled">
ニックネーム機能
<template #caption>
ユーザーの名前を任意に変更できるようになります変更は自分にのみ反映されます<br>
頻繁に名前を変更するユーザーを識別するときなどに使えます
</template>
</MkSwitch>
<MkSwitch v-model="numberQuoteEnabled">
数字引用機能
<template #caption>
ノートをコピーした上で末尾に数字をつけて投稿する機能数字が本文の末尾にある場合はそれ+1なければ2になります
</template>
</MkSwitch>
<MkSwitch v-model="stealEnabled">
パクる機能
<template #caption>
ノートをコピーしてそのまま投稿する機能
</template>
</MkSwitch>
</div>
</FormSection>
<FormSection>
<template #label>パッチ</template>
<div class="_gaps_m">
<div>Misskeyの機能に変更を加えます</div>
<MkSwitch v-model="infoButtonForNoteActionsEnabled">
ノートに詳細表示ボタンを表示する
<template #caption>
オプションノートの操作部をホバー時のみ表示するをオンにしたときに適用されます
</template>
</MkSwitch>
<MkSwitch v-model="rememberPostFormToggleStateEnabled">
投稿フォームにてプレビューのオンオフを記憶する
</MkSwitch>
<MkSwitch v-model="reactableRemoteReactionEnabled">
リモートのカスタム絵文字リアクションでもこのサーバーに同じ名前の絵文字があればリアクションできるようにする
</MkSwitch>
</div>
</FormSection>
<FormSection>
<template #label><i class="ti ti-flask"/> Ebisskey Labs</template>
<div class="_gaps_m">
<div>まだ開発中の機能を試してみませんか一部の機能はちゃんと動かないかもしれません</div>
<MkSwitch v-model="usePostFormWindow">
投稿フォームをウィンドウとして表示
</MkSwitch>
<MkSwitch v-model="ebiNoteViewEnabled">
新しいートUIを試す
</MkSwitch>
<MkNote :note="noteMock" />
</div>
</FormSection>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue';
import { defaultStore } from '@/store';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkNote from "@/components/MkNote.vue";
import {Note, User} from "misskey-js/built/entities";
import {$i} from "@/account";
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
}
const nicknameEnabled = computed(defaultStore.makeGetterSetter('nicknameEnabled'));
const numberQuoteEnabled = computed(defaultStore.makeGetterSetter('numberQuoteEnabled'));
const stealEnabled = computed(defaultStore.makeGetterSetter('stealEnabled'));
const infoButtonForNoteActionsEnabled = computed(defaultStore.makeGetterSetter('infoButtonForNoteActionsEnabled'));
const reactableRemoteReactionEnabled = computed(defaultStore.makeGetterSetter('reactableRemoteReactionEnabled'));
const rememberPostFormToggleStateEnabled = computed(defaultStore.makeGetterSetter('rememberPostFormToggleStateEnabled'));
const usePostFormWindow = computed(defaultStore.makeGetterSetter('usePostFormWindow'));
const ebiNoteViewEnabled = computed(defaultStore.makeGetterSetter('ebiNoteViewEnabledLab'));
const noteMock: Note = {
id: 'abc',
createdAt: new Date().toISOString(),
text: '> **エビ**(海老・蝦・魵)は、節足動物門・甲殻亜門・軟甲綱・十脚目(エビ目)のうち、カニ下目(短尾類)とヤドカリ下目(異尾類)以外の全ての種の総称である。すなわち、かつての**長尾類**(長尾亜目 Macruraにあたる。現在、長尾亜目という分類群は廃止されており、学術的な分類ではなく便宜上の区分である。\n\n出典https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%93',
cw: null,
user: $i as User,
userId: $i.id,
replyId: '',
renoteId: '',
files: [],
fileIds: [],
visibility: 'home',
reactions: {},
renoteCount: 20,
repliesCount: 10,
emojis: [],
localOnly: true,
};
watch([
numberQuoteEnabled,
stealEnabled,
infoButtonForNoteActionsEnabled,
reactableRemoteReactionEnabled,
], async () => {
await reloadAsk();
});
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: 'Ebisskey',
icon: 'ti ti-bulb-filled',
});
</script>

View file

@ -15,7 +15,12 @@
<div ref="bannerEl" class="banner" :style="style"></div> <div ref="bannerEl" class="banner" :style="style"></div>
<div class="fade"></div> <div class="fade"></div>
<div class="title"> <div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/> <div class="name">
<MkUserName :user="user" :nowrap="true"/>
<button v-if="defaultStore.reactiveState.nicknameEnabled.value" v-tooltip="'ニックネームを編集…'" class="_button nickname-button" @click="editNickname(props.user)">
<i class="ti ti-edit"/>
</button>
</div>
<div class="bottom"> <div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true"/></span> <span class="username"><MkAcct :user="user" :detail="true"/></span>
<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
@ -185,6 +190,7 @@ import { api } from '@/os';
import { isFfVisibility } from '@/scripts/is-ff-visibility'; import { isFfVisibility } from '@/scripts/is-ff-visibility';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { miLocalStorage } from '@/local-storage'; import { miLocalStorage } from '@/local-storage';
import { editNickname } from '@/scripts/edit-nickname';
const XPhotos = defineAsyncComponent(() => import('./index.photos.vue')); const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
@ -409,12 +415,25 @@ onUnmounted(() => {
color: #fff; color: #fff;
> .name { > .name {
display: block; display: flex;
gap: 8px;
margin: 0; margin: 0;
line-height: 32px; line-height: 32px;
font-weight: bold; font-weight: bold;
font-size: 1.8em; font-size: 1.8em;
text-shadow: 0 0 8px #000; text-shadow: 0 0 8px #000;
> .nickname-button {
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
background: rgba(0, 0, 0, 0.2);
color: #ccc;
font-size: 0.7em;
line-height: 1;
width: 1.8em;
height: 1.8em;
border-radius: 100%;
}
} }
> .bottom { > .bottom {
@ -607,7 +626,7 @@ onUnmounted(() => {
font-size: 70%; font-size: 70%;
} }
} }
>div { >div {
flex: 1; flex: 1;
text-align: center; text-align: center;

View file

@ -0,0 +1,19 @@
import {User} from "misskey-js/built/entities";
import {defaultStore} from "@/store";
import * as os from '@/os';
export async function editNickname(user: User) {
const { result, canceled } = await os.inputText({
title: 'ニックネームを編集',
placeholder: user.name || user.username,
default: defaultStore.state.nicknameMap[user.id] ?? null,
});
if (canceled) return
const newMap = {...defaultStore.state.nicknameMap};
if (result) {
newMap[user.id] = result;
} else {
delete newMap[user.id];
}
await defaultStore.set('nicknameMap', newMap);
}

View file

@ -376,6 +376,15 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
nicknameEnabled: {
where: 'account',
default: true,
},
nicknameMap: {
where: 'account',
default: {} as Record<string, string>,
},
// #endregion
})); }));
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期