色々fix

This commit is contained in:
mattyatea 2023-10-12 15:25:41 +09:00
parent 805280874b
commit 592c6e60a9
11 changed files with 388 additions and 6 deletions

1
locales/index.d.ts vendored
View File

@ -14,6 +14,7 @@ export interface Locale {
"forgotPassword": string; "forgotPassword": string;
"fetchingAsApObject": string; "fetchingAsApObject": string;
"ok": string; "ok": string;
"ruby": string;
"gotIt": string; "gotIt": string;
"cancel": string; "cancel": string;
"noThankYou": string; "noThankYou": string;

View File

@ -11,6 +11,7 @@ password: "パスワード"
forgotPassword: "パスワードを忘れた" forgotPassword: "パスワードを忘れた"
fetchingAsApObject: "連合に照会中" fetchingAsApObject: "連合に照会中"
ok: "OK" ok: "OK"
ruby: "ルビ"
gotIt: "わかった" gotIt: "わかった"
cancel: "キャンセル" cancel: "キャンセル"
noThankYou: "やめておく" noThankYou: "やめておく"

View File

@ -330,6 +330,7 @@ import * as ep___users_following from './endpoints/users/following.js';
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
import * as ep___users_user_stats from './endpoints/users/stats.js';
import * as ep___users_lists_create from './endpoints/users/lists/create.js'; import * as ep___users_lists_create from './endpoints/users/lists/create.js';
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
import * as ep___users_lists_list from './endpoints/users/lists/list.js'; import * as ep___users_lists_list from './endpoints/users/lists/list.js';
@ -567,6 +568,7 @@ const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep_
const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default }; const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default }; const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default }; const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
const $i_userstats: Provider = { provide: 'ep:i/stats', useClass: ep___users_user_stats.default }
const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default }; const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default }; const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default }; const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default };
@ -816,6 +818,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_timeline, $channels_timeline,
$channels_unfollow, $channels_unfollow,
$channels_update, $channels_update,
$i_userstats,
$channels_favorite, $channels_favorite,
$channels_unfavorite, $channels_unfavorite,
$channels_myFavorites, $channels_myFavorites,
@ -1103,6 +1106,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_emoji_setisSensitiveBulk, $admin_emoji_setisSensitiveBulk,
$admin_emoji_update, $admin_emoji_update,
$admin_federation_deleteAllFiles, $admin_federation_deleteAllFiles,
$i_userstats,
$admin_federation_refreshRemoteInstanceMetadata, $admin_federation_refreshRemoteInstanceMetadata,
$admin_federation_removeAllFollowing, $admin_federation_removeAllFollowing,
$admin_federation_updateInstance, $admin_federation_updateInstance,

View File

@ -237,6 +237,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
import * as ep___i_user_stats from './endpoints/users/stats.js';
import * as ep___invite_create from './endpoints/invite/create.js'; import * as ep___invite_create from './endpoints/invite/create.js';
import * as ep___invite_delete from './endpoints/invite/delete.js'; import * as ep___invite_delete from './endpoints/invite/delete.js';
import * as ep___invite_list from './endpoints/invite/list.js'; import * as ep___invite_list from './endpoints/invite/list.js';
@ -589,6 +590,7 @@ const eps = [
['i/webhooks/show', ep___i_webhooks_show], ['i/webhooks/show', ep___i_webhooks_show],
['i/webhooks/update', ep___i_webhooks_update], ['i/webhooks/update', ep___i_webhooks_update],
['i/webhooks/delete', ep___i_webhooks_delete], ['i/webhooks/delete', ep___i_webhooks_delete],
['i/stats', ep___i_user_stats],
['invite/create', ep___invite_create], ['invite/create', ep___invite_create],
['invite/delete', ep___invite_delete], ['invite/delete', ep___invite_delete],
['invite/list', ep___invite_list], ['invite/list', ep___invite_list],

View File

@ -0,0 +1,228 @@
import { Inject, Injectable } from '@nestjs/common';
import { awaitAll } from '@/misc/prelude/await-all.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { DI } from '@/di-symbols.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, DriveFilesRepository, NoteReactionsRepository, PageLikesRepository, NoteFavoritesRepository, PollVotesRepository } from '@/models/index.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['users'],
requireCredential: false,
description: 'Show statistics about a user.',
errors: {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '9e638e45-3b25-4ef7-8f95-07e8498f1819',
},
},
res: {
type: 'object',
optional: false, nullable: false,
properties: {
notesCount: {
type: 'integer',
optional: false, nullable: false,
},
repliesCount: {
type: 'integer',
optional: false, nullable: false,
},
renotesCount: {
type: 'integer',
optional: false, nullable: false,
},
repliedCount: {
type: 'integer',
optional: false, nullable: false,
},
renotedCount: {
type: 'integer',
optional: false, nullable: false,
},
pollVotesCount: {
type: 'integer',
optional: false, nullable: false,
},
pollVotedCount: {
type: 'integer',
optional: false, nullable: false,
},
localFollowingCount: {
type: 'integer',
optional: false, nullable: false,
},
remoteFollowingCount: {
type: 'integer',
optional: false, nullable: false,
},
localFollowersCount: {
type: 'integer',
optional: false, nullable: false,
},
remoteFollowersCount: {
type: 'integer',
optional: false, nullable: false,
},
followingCount: {
type: 'integer',
optional: false, nullable: false,
},
followersCount: {
type: 'integer',
optional: false, nullable: false,
},
sentReactionsCount: {
type: 'integer',
optional: false, nullable: false,
},
receivedReactionsCount: {
type: 'integer',
optional: false, nullable: false,
},
noteFavoritesCount: {
type: 'integer',
optional: false, nullable: false,
},
pageLikesCount: {
type: 'integer',
optional: false, nullable: false,
},
pageLikedCount: {
type: 'integer',
optional: false, nullable: false,
},
driveFilesCount: {
type: 'integer',
optional: false, nullable: false,
},
driveUsage: {
type: 'integer',
optional: false, nullable: false,
description: 'Drive usage in bytes',
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.noteReactionsRepository)
private noteReactionsRepository: NoteReactionsRepository,
@Inject(DI.pageLikesRepository)
private pageLikesRepository: PageLikesRepository,
@Inject(DI.noteFavoritesRepository)
private noteFavoritesRepository: NoteFavoritesRepository,
@Inject(DI.pollVotesRepository)
private pollVotesRepository: PollVotesRepository,
private driveFileEntityService: DriveFileEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
throw new ApiError(meta.errors.noSuchUser);
}
const result = await awaitAll({
notesCount: this.notesRepository.createQueryBuilder('note')
.where('note.userId = :userId', { userId: user.id })
.getCount(),
repliesCount: this.notesRepository.createQueryBuilder('note')
.where('note.userId = :userId', { userId: user.id })
.andWhere('note.replyId IS NOT NULL')
.getCount(),
renotesCount: this.notesRepository.createQueryBuilder('note')
.where('note.userId = :userId', { userId: user.id })
.andWhere('note.renoteId IS NOT NULL')
.getCount(),
repliedCount: this.notesRepository.createQueryBuilder('note')
.where('note.replyUserId = :userId', { userId: user.id })
.getCount(),
renotedCount: this.notesRepository.createQueryBuilder('note')
.where('note.renoteUserId = :userId', { userId: user.id })
.getCount(),
pollVotesCount: this.pollVotesRepository.createQueryBuilder('vote')
.where('vote.userId = :userId', { userId: user.id })
.getCount(),
pollVotedCount: this.pollVotesRepository.createQueryBuilder('vote')
.innerJoin('vote.note', 'note')
.where('note.userId = :userId', { userId: user.id })
.getCount(),
localFollowingCount: this.followingsRepository.createQueryBuilder('following')
.where('following.followerId = :userId', { userId: user.id })
.andWhere('following.followeeHost IS NULL')
.getCount(),
remoteFollowingCount: this.followingsRepository.createQueryBuilder('following')
.where('following.followerId = :userId', { userId: user.id })
.andWhere('following.followeeHost IS NOT NULL')
.getCount(),
localFollowersCount: this.followingsRepository.createQueryBuilder('following')
.where('following.followeeId = :userId', { userId: user.id })
.andWhere('following.followerHost IS NULL')
.getCount(),
remoteFollowersCount: this.followingsRepository.createQueryBuilder('following')
.where('following.followeeId = :userId', { userId: user.id })
.andWhere('following.followerHost IS NOT NULL')
.getCount(),
sentReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction')
.where('reaction.userId = :userId', { userId: user.id })
.getCount(),
receivedReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction')
.innerJoin('reaction.note', 'note')
.where('note.userId = :userId', { userId: user.id })
.getCount(),
noteFavoritesCount: this.noteFavoritesRepository.createQueryBuilder('favorite')
.where('favorite.userId = :userId', { userId: user.id })
.getCount(),
pageLikesCount: this.pageLikesRepository.createQueryBuilder('like')
.where('like.userId = :userId', { userId: user.id })
.getCount(),
pageLikedCount: this.pageLikesRepository.createQueryBuilder('like')
.innerJoin('like.page', 'page')
.where('page.userId = :userId', { userId: user.id })
.getCount(),
driveFilesCount: this.driveFilesRepository.createQueryBuilder('file')
.where('file.userId = :userId', { userId: user.id })
.getCount(),
driveUsage: this.driveFileEntityService.calcDriveUsageOf(user),
});
return {
...result,
followingCount: result.localFollowingCount + result.remoteFollowingCount,
followersCount: result.localFollowersCount + result.remoteFollowersCount,
};
});
}
}

View File

@ -86,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button> <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button> <button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
<button v-tooltip="i18n.ts.mfm" :class="['_button', $style.footerButton]" @click="insertMfm"><i class="ti ti-wand"></i></button> <button v-tooltip="i18n.ts.mfm" :class="['_button', $style.footerButton]" @click="insertMfm"><i class="ti ti-wand"></i></button>
<button v-tooltip="i18n.ts.ruby" :class="['_button', $style.footerButton]" @click="insertRuby"><i class="ti ti-abc"></i></button>
</div> </div>
<div :class="$style.footerRight"> <div :class="$style.footerRight">
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button> <button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button>
@ -849,6 +850,9 @@ async function insertEmoji(ev: MouseEvent) {
function insertMfm(){ function insertMfm(){
insertTextAtCursor(textareaEl, '$'); insertTextAtCursor(textareaEl, '$');
} }
function insertRuby() {
insertTextAtCursor(textareaEl, '$[ruby 本文 上につくやつ]');
}
function showActions(ev) { function showActions(ev) {
os.popupMenu(postFormActions.map(action => ({ os.popupMenu(postFormActions.map(action => ({
text: action.title, text: action.title,

View File

@ -340,9 +340,24 @@ export default function(props: {
text:base[1] text:base[1]
}); });
}else if(token.children.length === 2){ }else if(token.children.length === 2){
let txt,base;
console.log(token.children) console.log(token.children)
const base = token.children[0].type !== 'unicodeEmoji' ? token.children[0].props.text : token.children[0].props.emoji; if (token.children[1].type === 'emojiCode'){
const txt = token.children[1].type !== 'unicodeEmoji' ? token.children[1].props.text : token.children[1].props.emoji; txt = token.children[1].props.name
}else if(token.children[1].type === 'unicodeEmoji'){
txt = token.children[1].props.emoji
}else {
txt = token.children[1].props.text
}
if (token.children[0].type === 'emojiCode'){
base = token.children[0].props.name
}else if(token.children[0].type === 'unicodeEmoji'){
base = token.children[0].props.emoji
}else {
base = token.children[0].props.text
}
return h(MkRuby,{ return h(MkRuby,{
base:base, base:base,
basetype:token.children[0].type, basetype:token.children[0].type,

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import MkEmoji from './MkEmoji.vue'; import MkEmoji from './MkEmoji.vue';
import MkCustomEmoji from './MkCustomEmoji.vue';
const props = defineProps<{ const props = defineProps<{
base: string; base: string;
text: string; text: string;
@ -10,9 +11,11 @@ const props = defineProps<{
<template> <template>
<ruby> <ruby>
<MkEmoji v-if="basetype === 'unicodeEmoji' " class="emoji" :emoji="base" :normal="true" />
<MkEmoji v-if="basetype === 'unicodeEmoji' " class="emoji" :emoji="base" :normal="true"/> <MkCustomEmoji v-else-if="basetype === 'emojiCode' " :name="base"/>
<p v-else >{{base}}</p> <span style="white-space: pre-wrap;" v-else >{{base}}</span>
<rt>{{text}}</rt> <rt>{{text}}</rt>
</ruby> </ruby>
</template> </template>
<style>
</style>

View File

@ -0,0 +1,119 @@
<template>
<div class="_gaps_m">
<FormSection v-if="stats" first>
<template #label>{{ i18n.ts.statistics }}</template>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.notesCount }}</template>
<template #value>{{ number(stats.notesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.repliesCount }}</template>
<template #value>{{ number(stats.repliesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.renotesCount }}</template>
<template #value>{{ number(stats.renotesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.repliedCount }}</template>
<template #value>{{ number(stats.repliedCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.renotedCount }}</template>
<template #value>{{ number(stats.renotedCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.pollVotesCount }}</template>
<template #value>{{ number(stats.pollVotesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.pollVotedCount }}</template>
<template #value>{{ number(stats.pollVotedCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.sentReactionsCount }}</template>
<template #value>{{ number(stats.sentReactionsCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.receivedReactionsCount }}</template>
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.noteFavoritesCount }}</template>
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followingCount }}</template>
<template #value>{{ number(stats.followingCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template>
<template #value>{{ number(stats.localFollowingCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followersCount }}</template>
<template #value>{{ number(stats.followersCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template>
<template #value>{{ number(stats.localFollowersCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template>
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.pageLikesCount }}</template>
<template #value>{{ number(stats.pageLikesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.pageLikedCount }}</template>
<template #value>{{ number(stats.pageLikedCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.driveFilesCount }}</template>
<template #value>{{ number(stats.driveFilesCount) }}</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.driveUsage }}</template>
<template #value>{{ bytes(stats.driveUsage) }}</template>
</MkKeyValue>
</FormSection>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref ,computed } from 'vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
const stats = ref<any>({});
onMounted(() => {
os.api('i/stats', {
userId: $i!.id,
}).then(response => {
stats.value = response;
});
});
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.accountInfo,
icon: 'ti ti-info-circle',
});
</script>

View File

@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #key>{{ i18n.ts.registeredDate }}</template> <template #key>{{ i18n.ts.registeredDate }}</template>
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
</MkKeyValue> </MkKeyValue>
<FormLink to="/settings/account-stats"><template #icon><i class="ti ti-info-circle"></i></template>{{ i18n.ts.statistics }}</FormLink>
</div> </div>
</MkFolder> </MkFolder>

View File

@ -174,7 +174,11 @@ export const routes = [{
path: '/other', path: '/other',
name: 'other', name: 'other',
component: page(() => import('./pages/settings/other.vue')), component: page(() => import('./pages/settings/other.vue')),
}, { },{
path: '/account-stats',
name: 'other',
component: page(() => import('./pages/settings/account-stats.vue')),
},{
path: '/', path: '/',
component: page(() => import('./pages/_empty_.vue')), component: page(() => import('./pages/_empty_.vue')),
}], }],