-
-
+
+
+
+
+
{{ i18n.ts.save }}
diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue
index 0cdaf7c9bd..4a6d2dfba2 100644
--- a/packages/frontend/src/components/MkCwButton.vue
+++ b/packages/frontend/src/components/MkCwButton.vue
@@ -16,7 +16,23 @@ import MkButton from '@/components/MkButton.vue';
const props = defineProps<{
modelValue: boolean;
- note: Misskey.entities.Note;
+ text: string | null;
+ renote: Misskey.entities.Note | null;
+ files: Misskey.entities.DriveFile[];
+ poll?: {
+ expiresAt: string | null;
+ multiple: boolean;
+ choices: {
+ isVoted: boolean;
+ text: string;
+ votes: number;
+ }[];
+ } | {
+ choices: string[];
+ multiple: boolean;
+ expiresAt: string | null;
+ expiredAfter: string | null;
+ };
}>();
const emit = defineEmits<{
@@ -25,9 +41,10 @@ const emit = defineEmits<{
const label = computed(() => {
return concat([
- props.note.text ? [i18n.t('_cw.chars', { count: props.note.text.length })] : [],
- props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length })] : [],
- props.note.poll != null ? [i18n.ts.poll] : [],
+ props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [],
+ props.renote ? [i18n.ts.quote] : [],
+ props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [],
+ props.poll != null ? [i18n.ts.poll] : [],
] as string[][]).join(' / ');
});
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index e5bdd3781b..0a71b689fe 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 5b420c499e..84424c58ed 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
- {{ category }}
+ {{ category }}
@@ -100,7 +102,14 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, shallowRef, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import XSection from '@/components/MkEmojiPicker.section.vue';
-import { emojilist, emojiCharByCategory, UnicodeEmojiDef, unicodeEmojiCategories as categories, getEmojiName } from '@/scripts/emojilist.js';
+import {
+ emojilist,
+ emojiCharByCategory,
+ UnicodeEmojiDef,
+ unicodeEmojiCategories as categories,
+ getEmojiName,
+ CustomEmojiFolderTree,
+} from '@/scripts/emojilist.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
import { isTouchUsing } from '@/scripts/touch.js';
@@ -112,10 +121,11 @@ import { $i } from '@/account.js';
const props = withDefaults(defineProps<{
showPinned?: boolean;
- asReactionPicker?: boolean;
+ pinnedEmojis?: string[];
maxHeight?: number;
asDrawer?: boolean;
asWindow?: boolean;
+ asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
}>(), {
showPinned: true,
});
@@ -128,22 +138,50 @@ const searchEl = shallowRef
();
const emojisEl = shallowRef();
const {
- reactions: pinned,
- reactionPickerSize,
- reactionPickerWidth,
- reactionPickerHeight,
- disableShowingAnimatedImages,
+ emojiPickerScale,
+ emojiPickerWidth,
+ emojiPickerHeight,
recentlyUsedEmojis,
} = defaultStore.reactiveState;
-const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
-const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
-const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
+const pinned = computed(() => props.pinnedEmojis);
+const size = computed(() => emojiPickerScale.value);
+const width = computed(() => emojiPickerWidth.value);
+const height = computed(() => emojiPickerHeight.value);
const q = ref('');
-const searchResultCustom = ref([]);
+const searchResultCustom = ref([]);
const searchResultUnicode = ref([]);
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
+const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] };
+
+function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): CustomEmojiFolderTree {
+ const parts = input.split('/').map(p => p.trim());
+ let currentNode: CustomEmojiFolderTree = root;
+
+ for (const part of parts) {
+ let existingNode = currentNode.children.find((node) => node.value === part);
+
+ if (!existingNode) {
+ const newNode: CustomEmojiFolderTree = { value: part, category: input, children: [] };
+ currentNode.children.push(newNode);
+ existingNode = newNode;
+ }
+
+ currentNode = existingNode;
+ }
+
+ return currentNode;
+}
+
+customEmojiCategories.value.forEach(ec => {
+ if (ec !== null) {
+ parseAndMergeCategories(ec, customEmojiFolderRoot);
+ }
+});
+
+parseAndMergeCategories('', customEmojiFolderRoot);
+
watch(q, () => {
if (emojisEl.value) emojisEl.value.scrollTop = 0;
@@ -158,7 +196,7 @@ watch(q, () => {
const searchCustom = () => {
const max = 100;
const emojis = customEmojis.value;
- const matches = new Set();
+ const matches = new Set();
const exactMatch = emojis.find(emoji => emoji.name === newQ);
if (exactMatch) matches.add(exactMatch);
@@ -183,6 +221,19 @@ watch(q, () => {
}
}
} else {
+ if (customEmojisMap.has(newQ)) {
+ matches.add(customEmojisMap.get(newQ)!);
+ }
+ if (matches.size >= max) return matches;
+
+ for (const emoji of emojis) {
+ if (emoji.aliases.some(alias => alias === newQ)) {
+ matches.add(emoji);
+ if (matches.size >= max) break;
+ }
+ }
+ if (matches.size >= max) return matches;
+
for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) {
matches.add(emoji);
@@ -288,7 +339,7 @@ watch(q, () => {
searchResultUnicode.value = Array.from(searchUnicode());
});
-function filterAvailable(emoji: Misskey.entities.CustomEmoji): boolean {
+function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
}
@@ -305,7 +356,7 @@ function reset() {
q.value = '';
}
-function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string {
+function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): string {
return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
}
@@ -329,7 +380,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
emit('chosen', key);
// 最近使った絵文字更新
- if (!pinned.value.includes(key)) {
+ if (!pinned.value?.includes(key)) {
let recents = defaultStore.state.recentlyUsedEmojis;
recents = recents.filter((emoji: any) => emoji !== key);
recents.unshift(key);
@@ -572,8 +623,7 @@ defineExpose({
position: sticky;
top: 0;
left: 0;
- height: 32px;
- line-height: 32px;
+ line-height: 28px;
z-index: 1;
padding: 0 8px;
font-size: 12px;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 9d3132c540..6660dcf1ed 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="modal"
v-slot="{ type, maxHeight }"
:zPriority="'middle'"
- :preferType="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'"
+ :preferType="defaultStore.state.emojiPickerUseDrawerForMobile === false ? 'popup' : 'auto'"
:transparentBg="true"
:manualShowing="manualShowing"
:src="src"
@@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
class="_popup _shadow"
:class="{ [$style.drawer]: type === 'drawer' }"
:showPinned="showPinned"
+ :pinnedEmojis="pinnedEmojis"
:asReactionPicker="asReactionPicker"
:asDrawer="type === 'drawer'"
:max-height="maxHeight"
@@ -36,15 +37,19 @@ import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
import { defaultStore } from '@/store.js';
-withDefaults(defineProps<{
+const props = withDefaults(defineProps<{
manualShowing?: boolean | null;
src?: HTMLElement;
showPinned?: boolean;
+ pinnedEmojis?: string[],
asReactionPicker?: boolean;
+ choseAndClose?: boolean;
}>(), {
manualShowing: null,
showPinned: true,
+ pinnedEmojis: undefined,
asReactionPicker: false,
+ choseAndClose: true,
});
const emit = defineEmits<{
@@ -58,7 +63,9 @@ const picker = shallowRef>();
function chosen(emoji: any) {
emit('done', emoji);
- modal.value?.close();
+ if (props.choseAndClose) {
+ modal.value?.close();
+ }
}
function opening() {
diff --git a/packages/frontend/src/components/MkFeaturedPhotos.vue b/packages/frontend/src/components/MkFeaturedPhotos.vue
index cef1943d5c..8a23d7d4bf 100644
--- a/packages/frontend/src/components/MkFeaturedPhotos.vue
+++ b/packages/frontend/src/components/MkFeaturedPhotos.vue
@@ -10,11 +10,11 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index 28888fb9c8..922089a78b 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue
index ab435585d9..1be8a6d8f7 100644
--- a/packages/frontend/src/components/MkFlashPreview.vue
+++ b/packages/frontend/src/components/MkFlashPreview.vue
@@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ flash.summary.length > 85 ? flash.summary.slice(0, 85) + '…' : flash.summary }}
+
+
+