mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-30 03:23:08 +02:00
Feat(frontend): リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように (#12984)
* リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように * update CHANGELOG.md * fix locale & type errors * fix locale etc * fix * fix type * lint fixes * lint fixes(2)
This commit is contained in:
parent
d246f6c360
commit
19fe32bd7e
5 changed files with 146 additions and 14 deletions
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 新しいゲームを追加
|
- Feat: 新しいゲームを追加
|
||||||
|
- Feat: 絵文字の詳細ダイアログを追加
|
||||||
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
||||||
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
|
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
|
||||||
|
|
102
packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
Normal file
102
packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
|
||||||
|
<template #header>:{{ emoji.name }}:</template>
|
||||||
|
<template #default>
|
||||||
|
<MkSpacer>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
|
<div :class="$style.emojiImgWrapper">
|
||||||
|
<MkCustomEmoji :name="emoji.name" :normal="true" style="height: 100%;"></MkCustomEmoji>
|
||||||
|
</div>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.name }}</template>
|
||||||
|
<template #value>{{ emoji.name }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.tags }}</template>
|
||||||
|
<template #value>
|
||||||
|
<div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
|
||||||
|
<div v-else :class="$style.aliases">
|
||||||
|
<span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
|
||||||
|
{{ alias }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.category }}</template>
|
||||||
|
<template #value>{{ emoji.category ?? i18n.ts.none }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.sensitive }}</template>
|
||||||
|
<template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.localOnly }}</template>
|
||||||
|
<template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.license }}</template>
|
||||||
|
<template #value>{{ emoji.license ?? i18n.ts.none }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :copy="emoji.url">
|
||||||
|
<template #key>{{ i18n.ts.emojiUrl }}</template>
|
||||||
|
<template #value>
|
||||||
|
<a :href="emoji.url" target="_blank">{{ emoji.url }}</a>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</template>
|
||||||
|
</MkModalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { defineProps, shallowRef } from 'vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
|
const props = defineProps<{
|
||||||
|
emoji: Misskey.entities.EmojiDetailed,
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
|
||||||
|
(ev: 'cancel'): void;
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
|
const cancel = () => {
|
||||||
|
emit('cancel');
|
||||||
|
dialogEl.value!.close();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.emojiImgWrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 40cqh;
|
||||||
|
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aliases {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background-color: var(--X5);
|
||||||
|
border: solid 1px var(--divider);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
class="_button"
|
class="_button"
|
||||||
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
||||||
@click="toggleReaction()"
|
@click="toggleReaction()"
|
||||||
|
@contextmenu.prevent.stop="menu"
|
||||||
>
|
>
|
||||||
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||||
<span :class="$style.count">{{ count }}</span>
|
<span :class="$style.count">{{ count }}</span>
|
||||||
|
@ -21,6 +22,7 @@ import { computed, inject, onMounted, shallowRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import { useTooltip } from '@/scripts/use-tooltip.js';
|
import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||||
|
@ -98,6 +100,22 @@ async function toggleReaction() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function menu(ev) {
|
||||||
|
if (!canToggle.value) return;
|
||||||
|
if (!props.reaction.includes(":")) return;
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.ts.info,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
action: async () => {
|
||||||
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
|
emoji: await misskeyApiGet('emoji', {
|
||||||
|
name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
}
|
||||||
|
|
||||||
function anime() {
|
function anime() {
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
if (!defaultStore.state.animation) return;
|
if (!defaultStore.state.animation) return;
|
||||||
|
|
|
@ -24,9 +24,11 @@ import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { customEmojisMap } from '@/custom-emojis.js';
|
import { customEmojisMap } from '@/custom-emojis.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -93,7 +95,19 @@ function onClick(ev: MouseEvent) {
|
||||||
react(`:${props.name}:`);
|
react(`:${props.name}:`);
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
},
|
},
|
||||||
}] : [])], ev.currentTarget ?? ev.target);
|
}] : []), {
|
||||||
|
text: i18n.ts.info,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
action: async () => {
|
||||||
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
|
emoji: await misskeyApiGet('emoji', {
|
||||||
|
name: customEmojiName.value,
|
||||||
|
}),
|
||||||
|
}, {
|
||||||
|
anchor: ev.target,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -14,19 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji: {
|
emoji: Misskey.entities.EmojiSimple;
|
||||||
name: string;
|
|
||||||
aliases: string[];
|
|
||||||
category: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
|
@ -43,12 +39,13 @@ function menu(ev) {
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.info,
|
text: i18n.ts.info,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
action: () => {
|
action: async () => {
|
||||||
misskeyApiGet('emoji', { name: props.emoji.name }).then(res => {
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
os.alert({
|
emoji: await misskeyApiGet('emoji', {
|
||||||
type: 'info',
|
name: props.emoji.name,
|
||||||
text: `Name: ${res.name}\nAliases: ${res.aliases.join(' ')}\nCategory: ${res.category}\nisSensitive: ${res.isSensitive}\nlocalOnly: ${res.localOnly}\nLicense: ${res.license}\nURL: ${res.url}`,
|
})
|
||||||
});
|
}, {
|
||||||
|
anchor: ev.target,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
|
Loading…
Reference in a new issue