summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorYAVIIGI <118232419+YAVIIGI@users.noreply.github.com>2023-12-27 20:57:43 +0900
committerMarie <marie@kaifa.ch>2023-12-28 09:47:14 +0100
commitb0301dd2fbd48ada9c48be398e9b41865e6fef1f (patch)
tree445891770c8b7b94bec73a73723ac9cdb229623c /packages/frontend/src
parentFix(frontend): MFMでfgとbgに長い単語を使うと改行されない問... (diff)
downloadsharkey-b0301dd2fbd48ada9c48be398e9b41865e6fef1f.tar.gz
sharkey-b0301dd2fbd48ada9c48be398e9b41865e6fef1f.tar.bz2
sharkey-b0301dd2fbd48ada9c48be398e9b41865e6fef1f.zip
feat(frontend): 投稿ウインドウにMFM要素を追加するボタンの追加 (#12788)
* functionPicker の追加 * Update CHANGELOG.md * fix lint errors * Add addMfmFunction * add enableQuickAddMfmFunction setting * Update CHANGELOG.md issue 番号を追加 * Update index.d.ts * change 'functionPicker' to 'mfmFunctionPicker' * Change indent from 4 space to 1 tab --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/MkPostForm.vue12
-rw-r--r--packages/frontend/src/pages/settings/general.vue2
-rw-r--r--packages/frontend/src/scripts/mfm-function-picker.ts61
-rw-r--r--packages/frontend/src/store.ts4
4 files changed, 79 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 8838da15a9..aa37cef6c2 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -86,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ph-hash ph-bold ph-lg"></i></button>
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ph-plug ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ph-smiley ph-bold ph-lg"></i></button>
+ <button v-if="showAddMfmFunction" v-tooltip="i18n.ts.addMfmFunction" :class="['_button', $style.footerButton]" @click="insertMfmFunction"><i class="ph-palette ph-bold ph-lg"></i></button>
</div>
<div :class="$style.footerRight">
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ph-eye ph-bold ph-lg"></i></button>
@@ -127,6 +128,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
+import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
const modal = inject('modal');
@@ -184,6 +186,8 @@ const poll = ref<{
const useCw = ref<boolean>(!!props.initialCw);
const showPreview = ref(defaultStore.state.showPreview);
watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
+const showAddMfmFunction = ref(defaultStore.state.enableQuickAddMfmFunction);
+watch(showAddMfmFunction, () => defaultStore.set('enableQuickAddMfmFunction', showAddMfmFunction.value));
const cw = ref<string | null>(props.initialCw ?? null);
const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
@@ -870,6 +874,14 @@ async function insertEmoji(ev: MouseEvent) {
);
}
+async function insertMfmFunction(ev: MouseEvent) {
+ mfmFunctionPicker(
+ ev.currentTarget ?? ev.target,
+ textareaEl.value,
+ text,
+ );
+}
+
function showActions(ev) {
os.popupMenu(postFormActions.map(action => ({
text: action.title,
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 0839a65ebb..8eacdd32e6 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -52,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="expandLongNote">Always expand long notes</MkSwitch>
<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
+ <MkSwitch v-if="advancedMfm" v-model="enableQuickAddMfmFunction">{{ i18n.ts.enableQuickAddMfmFunction }}</MkSwitch>
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
<MkSwitch v-model="showTickerOnReplies">Show instance ticker on replies</MkSwitch>
@@ -296,6 +297,7 @@ const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm'));
const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
+const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts
new file mode 100644
index 0000000000..465926fe04
--- /dev/null
+++ b/packages/frontend/src/scripts/mfm-function-picker.ts
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Ref, nextTick } from 'vue';
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+import { MFM_TAGS } from '@/const.js';
+
+/**
+ * MFMの装飾のリストを表示する
+ */
+export function mfmFunctionPicker(src: any, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
+ return new Promise((res, rej) => {
+ os.popupMenu([{
+ text: i18n.ts.addMfmFunction,
+ type: 'label',
+ }, ...getFunctionList(textArea, textRef)], src);
+ });
+}
+
+function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) : object[] {
+ const ret: object[] = [];
+ MFM_TAGS.forEach(tag => {
+ ret.push({
+ text: tag,
+ icon: 'ti ti-icons',
+ action: () => add(textArea, textRef, tag),
+ });
+ });
+ return ret;
+}
+
+function add(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, type: string) {
+ const caretStart: number = textArea.selectionStart as number;
+ const caretEnd: number = textArea.selectionEnd as number;
+
+ MFM_TAGS.forEach(tag => {
+ if (type === tag) {
+ if (caretStart === caretEnd) {
+ // 単純にFunctionを追加
+ const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ]${textRef.value.substring(caretEnd)}`;
+ textRef.value = trimmedText;
+ } else {
+ // 選択範囲を囲むようにFunctionを追加
+ const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ${textRef.value.substring(caretStart, caretEnd)}]${textRef.value.substring(caretEnd)}`;
+ textRef.value = trimmedText;
+ }
+ }
+ });
+
+ const nextCaretStart: number = caretStart + 3 + type.length;
+ const nextCaretEnd: number = caretEnd + 3 + type.length;
+
+ // キャレットを戻す
+ nextTick(() => {
+ textArea.focus();
+ textArea.setSelectionRange(nextCaretStart, nextCaretEnd);
+ });
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index c86c3d01ad..18cfad2102 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -239,6 +239,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
+ enableQuickAddMfmFunction: {
+ where: 'device',
+ default: false,
+ },
loadRawImages: {
where: 'device',
default: false,