@@ -39,16 +40,17 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -57,15 +59,16 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+
+
@@ -76,7 +79,8 @@ SPDX-License-Identifier: AGPL-3.0-only
v-for="child in customEmojiFolderRoot.children"
:key="`custom:${child.value}`"
:initialShown="false"
- :emojis="computed(() => customEmojis.filter(e => child.value === '' ? (e.category === 'null' || !e.category) : e.category === child.value).filter(filterAvailable).map(e => `:${e.name}:`))"
+ :emojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).map(e => `:${e.name}:`))"
+ :disabledEmojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).filter(e => !canReact(e)).map(e => `:${e.name}:`))"
:hasChildSection="child.children.length !== 0"
:customEmojiTree="child.children"
@chosen="chosen"
@@ -109,6 +113,7 @@ import {
unicodeEmojiCategories as categories,
getEmojiName,
CustomEmojiFolderTree,
+ getUnicodeEmoji,
} from '@/scripts/emojilist.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
@@ -146,6 +151,13 @@ const {
recentlyUsedEmojis,
} = defaultStore.reactiveState;
+const recentlyUsedEmojisDef = computed(() => {
+ return recentlyUsedEmojis.value.map(getDef).filter(x => x != null);
+});
+const pinnedEmojisDef = computed(() => {
+ return pinned.value?.map(getDef).filter(x => x != null);
+});
+
const pinned = computed(() => props.pinnedEmojis);
const size = computed(() => emojiPickerScale.value);
const width = computed(() => emojiPickerWidth.value);
@@ -337,14 +349,18 @@ watch(q, () => {
return matches;
};
- searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable);
+ searchResultCustom.value = Array.from(searchCustom());
searchResultUnicode.value = Array.from(searchUnicode());
});
-function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
+function canReact(emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
}
+function filterCategory(emoji: Misskey.entities.EmojiSimple, category: string): boolean {
+ return category === '' ? (emoji.category === 'null' || !emoji.category) : emoji.category === category;
+}
+
function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
searchEl.value?.focus({
@@ -362,11 +378,22 @@ function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef):
return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
}
+function getDef(emoji: string): string | Misskey.entities.EmojiSimple | UnicodeEmojiDef {
+ if (emoji.includes(':')) {
+ // カスタム絵文字が存在する場合はその情報を持つオブジェクトを返し、
+ // サーバの管理画面から削除された等で情報が見つからない場合は名前の文字列をそのまま返しておく(undefinedを返すとエラーになるため)
+ const name = emoji.replaceAll(':', '');
+ return customEmojisMap.get(name) ?? emoji;
+ } else {
+ return getUnicodeEmoji(emoji);
+ }
+}
+
/** @see MkEmojiPicker.section.vue */
function computeButtonTitle(ev: MouseEvent): void {
const elm = ev.target as HTMLElement;
const emoji = elm.dataset.emoji as string;
- elm.title = getEmojiName(emoji) ?? emoji;
+ elm.title = getEmojiName(emoji);
}
function chosen(emoji: any, ev?: MouseEvent) {
@@ -526,6 +553,18 @@ defineExpose({
width: auto;
height: auto;
min-width: 0;
+
+ &:disabled {
+ cursor: not-allowed;
+ background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+ opacity: 1;
+
+ > .emoji {
+ filter: grayscale(1);
+ mix-blend-mode: exclusion;
+ opacity: 0.8;
+ }
+ }
}
}
}
@@ -548,6 +587,18 @@ defineExpose({
width: auto;
height: auto;
min-width: 0;
+
+ &:disabled {
+ cursor: not-allowed;
+ background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+ opacity: 1;
+
+ > .emoji {
+ filter: grayscale(1);
+ mix-blend-mode: exclusion;
+ opacity: 0.8;
+ }
+ }
}
}
}
@@ -663,6 +714,18 @@ defineExpose({
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
}
+ &:disabled {
+ cursor: not-allowed;
+ background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+ opacity: 1;
+
+ > .emoji {
+ filter: grayscale(1);
+ mix-blend-mode: exclusion;
+ opacity: 0.8;
+ }
+ }
+
> .emoji {
height: 1.25em;
vertical-align: -.25em;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 59577f8070..c6b3896989 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
- (ev: 'done', v: any): void;
+ (ev: 'done', v: string): void;
(ev: 'close'): void;
(ev: 'closed'): void;
}>();
@@ -64,7 +64,7 @@ const emit = defineEmits<{
const modal = shallowRef
>();
const picker = shallowRef>();
-function chosen(emoji: any) {
+function chosen(emoji: string) {
emit('done', emoji);
if (props.choseAndClose) {
modal.value?.close();
diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue
deleted file mode 100644
index 6952943345..0000000000
--- a/packages/frontend/src/components/MkEmojiPickerWindow.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index 0d8734799c..deedc5badb 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -21,37 +21,37 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
- ({{ i18n.ts.optional }})
- {{ form[item].description }}
+
+
+ ({{ i18n.ts.optional }})
+ {{ v.description }}
-
- ({{ i18n.ts.optional }})
- {{ form[item].description }}
+
+ ({{ i18n.ts.optional }})
+ {{ v.description }}
-
- ({{ i18n.ts.optional }})
- {{ form[item].description }}
+
+ ({{ i18n.ts.optional }})
+ {{ v.description }}
-
-
- {{ form[item].description }}
+
+
+ {{ v.description }}
-
- ({{ i18n.ts.optional }})
- {{ option.label }}
+
+ ({{ i18n.ts.optional }})
+ {{ option.label }}
-
- ({{ i18n.ts.optional }})
- {{ option.label }}
+
+ ({{ i18n.ts.optional }})
+ {{ option.label }}
-
- ({{ i18n.ts.optional }})
- {{ form[item].description }}
+
+ ({{ i18n.ts.optional }})
+ {{ v.description }}
-
-
+
+
@@ -72,19 +72,21 @@ import MkSelect from './MkSelect.vue';
import MkRange from './MkRange.vue';
import MkButton from './MkButton.vue';
import MkRadios from './MkRadios.vue';
+import type { Form } from '@/scripts/form.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
const props = defineProps<{
title: string;
- form: any;
+ form: Form;
}>();
const emit = defineEmits<{
(ev: 'done', v: {
- canceled?: boolean;
- result?: any;
+ canceled: true;
+ } | {
+ result: Record;
}): void;
(ev: 'closed'): void;
}>();
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 52eaa84fca..3d15f69f73 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -166,7 +166,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.reactions }}