Merge 41750dd0f4
into a5fa9a2cef
This commit is contained in:
commit
3689964640
|
@ -42,6 +42,8 @@
|
||||||
- Enhance: テーマでページヘッダーの色を変更できるように
|
- Enhance: テーマでページヘッダーの色を変更できるように
|
||||||
- Enhance: スワイプでのタブ切り替えを強化
|
- Enhance: スワイプでのタブ切り替えを強化
|
||||||
- Enhance: デザインのブラッシュアップ
|
- Enhance: デザインのブラッシュアップ
|
||||||
|
- Enhance: 投稿フォームの絵文字ピッカーに独立したウィンドウを使用できるように
|
||||||
|
- リアクションピッカーと絵文字ピッカーで表示スタイルの設定が分離しました。絵文字ピッカーでのみウィンドウスタイルを使用可能です。
|
||||||
- Fix: ログアウトした際に処理が終了しない問題を修正
|
- Fix: ログアウトした際に処理が終了しない問題を修正
|
||||||
- Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
|
- Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
|
||||||
- Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
|
- Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
|
||||||
|
|
|
@ -130,6 +130,10 @@ export interface Locale extends ILocale {
|
||||||
* ウィンドウで開く
|
* ウィンドウで開く
|
||||||
*/
|
*/
|
||||||
"openInWindow": string;
|
"openInWindow": string;
|
||||||
|
/**
|
||||||
|
* ウィンドウ
|
||||||
|
*/
|
||||||
|
"window": string;
|
||||||
/**
|
/**
|
||||||
* プロフィール
|
* プロフィール
|
||||||
*/
|
*/
|
||||||
|
@ -542,6 +546,10 @@ export interface Locale extends ILocale {
|
||||||
* 絵文字ピッカー
|
* 絵文字ピッカー
|
||||||
*/
|
*/
|
||||||
"emojiPicker": string;
|
"emojiPicker": string;
|
||||||
|
/**
|
||||||
|
* リアクションピッカー
|
||||||
|
*/
|
||||||
|
"reactionPicker": string;
|
||||||
/**
|
/**
|
||||||
* リアクション時にピン留め表示する絵文字を設定できます
|
* リアクション時にピン留め表示する絵文字を設定できます
|
||||||
*/
|
*/
|
||||||
|
@ -3186,10 +3194,6 @@ export interface Locale extends ILocale {
|
||||||
* 設定はページリロード後に反映されます。
|
* 設定はページリロード後に反映されます。
|
||||||
*/
|
*/
|
||||||
"reloadToApplySetting": string;
|
"reloadToApplySetting": string;
|
||||||
/**
|
|
||||||
* 反映には再起動が必要です。
|
|
||||||
*/
|
|
||||||
"needReloadToApply": string;
|
|
||||||
/**
|
/**
|
||||||
* タイトルバーを表示する
|
* タイトルバーを表示する
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,6 +28,7 @@ notificationSettings: "通知の設定"
|
||||||
basicSettings: "基本設定"
|
basicSettings: "基本設定"
|
||||||
otherSettings: "その他の設定"
|
otherSettings: "その他の設定"
|
||||||
openInWindow: "ウィンドウで開く"
|
openInWindow: "ウィンドウで開く"
|
||||||
|
window: "ウィンドウ"
|
||||||
profile: "プロフィール"
|
profile: "プロフィール"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
noAccountDescription: "自己紹介はありません"
|
noAccountDescription: "自己紹介はありません"
|
||||||
|
@ -131,6 +132,7 @@ add: "追加"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
reactions: "リアクション"
|
reactions: "リアクション"
|
||||||
emojiPicker: "絵文字ピッカー"
|
emojiPicker: "絵文字ピッカー"
|
||||||
|
reactionPicker: "リアクションピッカー"
|
||||||
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
|
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
|
||||||
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
|
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
|
||||||
emojiPickerDisplay: "ピッカーの表示"
|
emojiPickerDisplay: "ピッカーの表示"
|
||||||
|
@ -792,7 +794,6 @@ center: "中央"
|
||||||
wide: "広い"
|
wide: "広い"
|
||||||
narrow: "狭い"
|
narrow: "狭い"
|
||||||
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
||||||
needReloadToApply: "反映には再起動が必要です。"
|
|
||||||
showTitlebar: "タイトルバーを表示する"
|
showTitlebar: "タイトルバーを表示する"
|
||||||
clearCache: "キャッシュをクリア"
|
clearCache: "キャッシュをクリア"
|
||||||
onlineUsersCount: "{n}人がオンライン"
|
onlineUsersCount: "{n}人がオンライン"
|
||||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
ref="modal"
|
ref="modal"
|
||||||
v-slot="{ type, maxHeight }"
|
v-slot="{ type, maxHeight }"
|
||||||
:zPriority="'middle'"
|
:zPriority="'middle'"
|
||||||
:preferType="prefer.s.emojiPickerStyle"
|
:preferType="asReactionPicker ? prefer.s.reactionPickerStyle : prefer.s.emojiPickerStyle === 'window' ? 'auto' : prefer.s.emojiPickerStyle"
|
||||||
:hasInteractionWithOtherFocusTrappedEls="true"
|
:hasInteractionWithOtherFocusTrappedEls="true"
|
||||||
:transparentBg="true"
|
:transparentBg="true"
|
||||||
:manualShowing="manualShowing"
|
:manualShowing="manualShowing"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MkEmojiPickerWindow from './MkEmojiPickerWindow.vue';
|
||||||
|
void MkEmojiPickerWindow;
|
|
@ -0,0 +1,68 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkWindow
|
||||||
|
ref="window"
|
||||||
|
:initialWidth="300"
|
||||||
|
:initialHeight="290"
|
||||||
|
:canResize="true"
|
||||||
|
:mini="true"
|
||||||
|
:front="true"
|
||||||
|
@closed="emit('closed')"
|
||||||
|
>
|
||||||
|
<MkEmojiPicker
|
||||||
|
:showPinned="showPinned"
|
||||||
|
:asReactionPicker="asReactionPicker"
|
||||||
|
:targetNote="targetNote"
|
||||||
|
asWindow
|
||||||
|
:class="$style.picker"
|
||||||
|
@chosen="chosen"
|
||||||
|
/>
|
||||||
|
</MkWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { globalEvents } from '@/events.js';
|
||||||
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
|
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
src?: HTMLElement;
|
||||||
|
showPinned?: boolean;
|
||||||
|
pinnedEmojis?: string[];
|
||||||
|
asReactionPicker?: boolean;
|
||||||
|
targetNote?: Misskey.entities.Note;
|
||||||
|
}>(), {
|
||||||
|
showPinned: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'chosen', v: string): void;
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function chosen(emoji: string) {
|
||||||
|
emit('chosen', emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowEl = useTemplateRef('window');
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
windowEl.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.picker {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -99,13 +99,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue';
|
import { inject, watch, nextTick, onMounted, onBeforeUnmount, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue';
|
||||||
|
import type { ShallowRef } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||||
import { toASCII } from 'punycode.js';
|
import { toASCII } from 'punycode.js';
|
||||||
import { host, url } from '@@/js/config.js';
|
import { host, url } from '@@/js/config.js';
|
||||||
import type { ShallowRef } from 'vue';
|
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
||||||
|
@ -137,6 +137,7 @@ import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
import { globalEvents } from '@/events.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -974,20 +975,20 @@ async function insertEmoji(ev: MouseEvent) {
|
||||||
|
|
||||||
let pos = textareaEl.value?.selectionStart ?? 0;
|
let pos = textareaEl.value?.selectionStart ?? 0;
|
||||||
let posEnd = textareaEl.value?.selectionEnd ?? text.value.length;
|
let posEnd = textareaEl.value?.selectionEnd ?? text.value.length;
|
||||||
emojiPicker.show(
|
emojiPicker.show({
|
||||||
target as HTMLElement,
|
src: target as HTMLElement,
|
||||||
emoji => {
|
onChosen: emoji => {
|
||||||
const textBefore = text.value.substring(0, pos);
|
const textBefore = text.value.substring(0, pos);
|
||||||
const textAfter = text.value.substring(posEnd);
|
const textAfter = text.value.substring(posEnd);
|
||||||
text.value = textBefore + emoji + textAfter;
|
text.value = textBefore + emoji + textAfter;
|
||||||
pos += emoji.length;
|
pos += emoji.length;
|
||||||
posEnd += emoji.length;
|
posEnd += emoji.length;
|
||||||
},
|
},
|
||||||
() => {
|
onClosed: () => {
|
||||||
textAreaReadOnly.value = false;
|
textAreaReadOnly.value = false;
|
||||||
nextTick(() => focus());
|
nextTick(() => focus());
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertMfmFunction(ev: MouseEvent) {
|
async function insertMfmFunction(ev: MouseEvent) {
|
||||||
|
@ -1102,6 +1103,10 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
emojiPicker.closeWindow();
|
||||||
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
clear,
|
clear,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
ref="modal"
|
ref="modal"
|
||||||
:preferType="'dialog'"
|
:preferType="'dialog'"
|
||||||
@click="modal?.close()"
|
@click="modal?.close()"
|
||||||
|
@close="onModalClose()"
|
||||||
@closed="onModalClosed()"
|
@closed="onModalClosed()"
|
||||||
@esc="modal?.close()"
|
@esc="modal?.close()"
|
||||||
>
|
>
|
||||||
|
@ -26,9 +27,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTemplateRef } from 'vue';
|
import { useTemplateRef } from 'vue';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
|
||||||
import MkModal from '@/components/MkModal.vue';
|
import MkModal from '@/components/MkModal.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||||
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<PostFormProps & {
|
const props = withDefaults(defineProps<PostFormProps & {
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
|
@ -50,6 +52,10 @@ function onPosted() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
emojiPicker.closeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
function onModalClosed() {
|
function onModalClosed() {
|
||||||
emit('closed');
|
emit('closed');
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
|
|
||||||
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
|
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
|
||||||
|
|
||||||
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
import { markRaw, ref, shallowRef, defineAsyncComponent, nextTick } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref, ShallowRef } from 'vue';
|
||||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
@ -141,6 +141,7 @@ let popupIdCount = 0;
|
||||||
export const popups = ref<{
|
export const popups = ref<{
|
||||||
id: number;
|
id: number;
|
||||||
component: Component;
|
component: Component;
|
||||||
|
componentRef: ShallowRef<Component | null>;
|
||||||
props: Record<string, any>;
|
props: Record<string, any>;
|
||||||
events: Record<string, any>;
|
events: Record<string, any>;
|
||||||
}[]>([]);
|
}[]>([]);
|
||||||
|
@ -178,13 +179,21 @@ type EmitsExtractor<T> = {
|
||||||
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function popup<T extends Component>(
|
export function popup<
|
||||||
|
T extends Component,
|
||||||
|
TI extends T extends new (...args: unknown[]) => infer I ? I : T,
|
||||||
|
>(
|
||||||
component: T,
|
component: T,
|
||||||
props: ComponentProps<T>,
|
props: ComponentProps<T>,
|
||||||
events: Partial<ComponentEmit<T>> = {},
|
events: Partial<ComponentEmit<T>> = {},
|
||||||
): { dispose: () => void } {
|
): {
|
||||||
|
dispose: () => void;
|
||||||
|
componentRef: ShallowRef<TI | null>;
|
||||||
|
} {
|
||||||
markRaw(component);
|
markRaw(component);
|
||||||
|
|
||||||
|
const componentRef = shallowRef<TI | null>(null);
|
||||||
|
|
||||||
const id = ++popupIdCount;
|
const id = ++popupIdCount;
|
||||||
const dispose = () => {
|
const dispose = () => {
|
||||||
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
||||||
|
@ -194,6 +203,7 @@ export function popup<T extends Component>(
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
component,
|
component,
|
||||||
|
componentRef,
|
||||||
props,
|
props,
|
||||||
events,
|
events,
|
||||||
id,
|
id,
|
||||||
|
@ -203,6 +213,7 @@ export function popup<T extends Component>(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispose,
|
dispose,
|
||||||
|
componentRef,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,8 +100,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<SearchMarker :keywords="['emoji', 'picker', 'style']">
|
<SearchMarker :keywords="['emoji', 'picker', 'style']">
|
||||||
<MkPreferenceContainer k="emojiPickerStyle">
|
<MkPreferenceContainer k="emojiPickerStyle">
|
||||||
<MkSelect v-model="emojiPickerStyle">
|
<MkSelect v-model="emojiPickerStyle">
|
||||||
<template #label><SearchLabel>{{ i18n.ts.style }}</SearchLabel></template>
|
<template #label><SearchLabel>{{ i18n.ts.style }} ({{ i18n.ts.emojiPicker }})</SearchLabel></template>
|
||||||
<template #caption>{{ i18n.ts.needReloadToApply }}</template>
|
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||||
|
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||||
|
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||||
|
<option value="window">{{ i18n.ts.window }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
</MkPreferenceContainer>
|
||||||
|
</SearchMarker>
|
||||||
|
|
||||||
|
<SearchMarker :keywords="['reaction', 'emoji', 'picker', 'style']">
|
||||||
|
<MkPreferenceContainer k="emojiPickerStyle">
|
||||||
|
<MkSelect v-model="emojiPickerStyle">
|
||||||
|
<template #label><SearchLabel>{{ i18n.ts.style }} ({{ i18n.ts.reactionPicker }})</SearchLabel></template>
|
||||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||||
|
@ -133,13 +144,14 @@ import { prefer } from '@/preferences.js';
|
||||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { emojiPicker } from '@/utility/emoji-picker.js';
|
import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||||
|
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||||
const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction');
|
const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction');
|
||||||
const emojiPaletteForMain = prefer.model('emojiPaletteForMain');
|
const emojiPaletteForMain = prefer.model('emojiPaletteForMain');
|
||||||
const emojiPickerScale = prefer.model('emojiPickerScale');
|
const emojiPickerScale = prefer.model('emojiPickerScale');
|
||||||
const emojiPickerWidth = prefer.model('emojiPickerWidth');
|
const emojiPickerWidth = prefer.model('emojiPickerWidth');
|
||||||
const emojiPickerHeight = prefer.model('emojiPickerHeight');
|
const emojiPickerHeight = prefer.model('emojiPickerHeight');
|
||||||
const emojiPickerStyle = prefer.model('emojiPickerStyle');
|
const emojiPickerStyle = prefer.model('emojiPickerStyle');
|
||||||
|
const reactionPickerStyle = prefer.model('reactionPickerStyle');
|
||||||
|
|
||||||
const palettesSyncEnabled = ref(prefer.isSyncEnabled('emojiPalettes'));
|
const palettesSyncEnabled = ref(prefer.isSyncEnabled('emojiPalettes'));
|
||||||
|
|
||||||
|
@ -214,6 +226,13 @@ function previewPicker(ev: MouseEvent) {
|
||||||
emojiPicker.show(getHTMLElement(ev));
|
emojiPicker.show(getHTMLElement(ev));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch([
|
||||||
|
emojiPickerStyle,
|
||||||
|
reactionPickerStyle,
|
||||||
|
], async () => {
|
||||||
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
|
});
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: i18n.ts.emojiPalette,
|
title: i18n.ts.emojiPalette,
|
||||||
icon: 'ti ti-mood-happy',
|
icon: 'ti ti-mood-happy',
|
||||||
|
|
|
@ -230,6 +230,9 @@ export const PREF_DEF = {
|
||||||
default: 3,
|
default: 3,
|
||||||
},
|
},
|
||||||
emojiPickerStyle: {
|
emojiPickerStyle: {
|
||||||
|
default: 'auto' as 'auto' | 'popup' | 'drawer' | 'window',
|
||||||
|
},
|
||||||
|
reactionPickerStyle: {
|
||||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
||||||
},
|
},
|
||||||
squareAvatars: {
|
squareAvatars: {
|
||||||
|
|
|
@ -312,7 +312,7 @@ export const store = markRaw(new Pizzax('base', {
|
||||||
},
|
},
|
||||||
emojiPickerStyle: {
|
emojiPickerStyle: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
default: 'auto' as 'auto' | 'popup' | 'drawer' | 'window',
|
||||||
},
|
},
|
||||||
reportError: {
|
reportError: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
|
@ -61,6 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:is="popup.component"
|
:is="popup.component"
|
||||||
v-for="popup in popups"
|
v-for="popup in popups"
|
||||||
:key="popup.id"
|
:key="popup.id"
|
||||||
|
:ref="(el: Component | null) => popup.componentRef = el"
|
||||||
v-bind="popup.props"
|
v-bind="popup.props"
|
||||||
v-on="popup.events"
|
v-on="popup.events"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineAsyncComponent, ref, watch } from 'vue';
|
import { defineAsyncComponent, ref, shallowRef, watch } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref, ShallowRef } from 'vue';
|
||||||
|
import type MkEmojiPickerWindow_TypeOnly from '@/components/MkEmojiPickerWindow.vue';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
|
@ -16,7 +17,15 @@ import { prefer } from '@/preferences.js';
|
||||||
*/
|
*/
|
||||||
class EmojiPicker {
|
class EmojiPicker {
|
||||||
private src: Ref<HTMLElement | null> = ref(null);
|
private src: Ref<HTMLElement | null> = ref(null);
|
||||||
private manualShowing = ref(false);
|
|
||||||
|
private isWindow: boolean = false;
|
||||||
|
private windowComponentEl: ShallowRef<InstanceType<typeof MkEmojiPickerWindow_TypeOnly> | null> = shallowRef(null);
|
||||||
|
private windowShowing: boolean = false;
|
||||||
|
|
||||||
|
private dialogShowing = ref(false);
|
||||||
|
|
||||||
|
private emojisRef = ref<string[]>([]);
|
||||||
|
|
||||||
private onChosen?: (emoji: string) => void;
|
private onChosen?: (emoji: string) => void;
|
||||||
private onClosed?: () => void;
|
private onClosed?: () => void;
|
||||||
|
|
||||||
|
@ -25,26 +34,30 @@ class EmojiPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
const emojisRef = ref<string[]>([]);
|
|
||||||
|
|
||||||
watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
|
watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
|
||||||
emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
|
this.emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (prefer.s.emojiPickerStyle === 'window') {
|
||||||
|
// init後にemojiPickerStyleが変わった場合、drawer/popup用の初期化をスキップするため、
|
||||||
|
// 正常に絵文字ピッカーが表示されない。
|
||||||
|
// なので一度initされたらwindow表示で固定する(設定を変更したら要リロード)
|
||||||
|
this.isWindow = true;
|
||||||
|
} else {
|
||||||
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
|
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
|
||||||
src: this.src,
|
src: this.src,
|
||||||
pinnedEmojis: emojisRef,
|
pinnedEmojis: this.emojisRef,
|
||||||
asReactionPicker: false,
|
asReactionPicker: false,
|
||||||
manualShowing: this.manualShowing,
|
manualShowing: this.dialogShowing,
|
||||||
choseAndClose: false,
|
choseAndClose: false,
|
||||||
}, {
|
}, {
|
||||||
done: emoji => {
|
done: emoji => {
|
||||||
if (this.onChosen) this.onChosen(emoji);
|
if (this.onChosen) this.onChosen(emoji);
|
||||||
},
|
},
|
||||||
close: () => {
|
close: () => {
|
||||||
this.manualShowing.value = false;
|
this.dialogShowing.value = false;
|
||||||
},
|
},
|
||||||
closed: () => {
|
closed: () => {
|
||||||
this.src.value = null;
|
this.src.value = null;
|
||||||
|
@ -52,16 +65,43 @@ class EmojiPicker {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public show(
|
public show(opts: {
|
||||||
src: HTMLElement,
|
src: HTMLElement,
|
||||||
onChosen?: EmojiPicker['onChosen'],
|
onChosen?: EmojiPicker['onChosen'],
|
||||||
onClosed?: EmojiPicker['onClosed'],
|
onClosed?: EmojiPicker['onClosed'],
|
||||||
) {
|
}) {
|
||||||
this.src.value = src;
|
if (this.isWindow) {
|
||||||
this.manualShowing.value = true;
|
if (this.windowShowing) return;
|
||||||
this.onChosen = onChosen;
|
this.windowShowing = true;
|
||||||
this.onClosed = onClosed;
|
const { dispose, componentRef } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerWindow.vue')), {
|
||||||
|
src: opts.src,
|
||||||
|
pinnedEmojis: this.emojisRef,
|
||||||
|
asReactionPicker: false,
|
||||||
|
}, {
|
||||||
|
chosen: (emoji) => {
|
||||||
|
if (opts.onChosen) opts.onChosen(emoji);
|
||||||
|
},
|
||||||
|
closed: () => {
|
||||||
|
if (opts.onClosed) opts.onClosed();
|
||||||
|
this.windowShowing = false;
|
||||||
|
dispose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.windowComponentEl.value = componentRef.value;
|
||||||
|
} else {
|
||||||
|
this.src.value = opts.src;
|
||||||
|
this.dialogShowing.value = true;
|
||||||
|
this.onChosen = opts.onChosen;
|
||||||
|
this.onClosed = opts.onClosed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeWindow() {
|
||||||
|
if (this.isWindow && this.windowComponentEl.value) {
|
||||||
|
this.windowComponentEl.value.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue