summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
author1Step621 <86859447+1STEP621@users.noreply.github.com>2024-01-19 18:50:26 +0900
committerGitHub <noreply@github.com>2024-01-19 18:50:26 +0900
commit678dba92451ef9b94bc40ea7de5ddd0e24249829 (patch)
treea791d89171a2fdd089382ff79e7bf6edc4ffeeba /packages
parentfix: Some fixes for #12850 (#12862) (diff)
downloadsharkey-678dba92451ef9b94bc40ea7de5ddd0e24249829.tar.gz
sharkey-678dba92451ef9b94bc40ea7de5ddd0e24249829.tar.bz2
sharkey-678dba92451ef9b94bc40ea7de5ddd0e24249829.zip
Enhance(frontend): MFMの属性にオートコンプリートが利用できるように (#12803)
* MFMのパラメータでオートコンプリートできるように * tweak conditions & refactor * ファイル末尾の改行忘れ * remove console.log & refactor * 型付けに敗北 * fix * update CHANGELOG.md * tweak conditions * CHANGELOGの様式ミス * CHANGELOGを書く場所を間違えていたので修正 * move changelog * move changelog * typeof MFM_TAGS[number] Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * $[border.noclip ]対応 * Update const.ts --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages')
-rw-r--r--packages/frontend/src/components/MkAutocomplete.vue17
-rw-r--r--packages/frontend/src/const.ts24
-rw-r--r--packages/frontend/src/scripts/autocomplete.ts39
3 files changed, 73 insertions, 7 deletions
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 15eda4499f..9d03eab2cb 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -35,6 +35,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<span>{{ tag }}</span>
</li>
</ol>
+ <ol v-else-if="mfmParams.length > 0" ref="suggests" :class="$style.list">
+ <li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
+ <span>{{ param }}</span>
+ </li>
+ </ol>
</div>
</template>
@@ -51,7 +56,7 @@ import { emojilist, getEmojiName } from '@/scripts/emojilist.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
-import { MFM_TAGS } from '@/const.js';
+import { MFM_TAGS, MFM_PARAMS } from '@/const.js';
type EmojiDef = {
emoji: string;
@@ -130,7 +135,7 @@ export default {
<script lang="ts" setup>
const props = defineProps<{
type: string;
- q: string | null;
+ q: any;
textarea: HTMLTextAreaElement;
close: () => void;
x: number;
@@ -151,6 +156,7 @@ const hashtags = ref<any[]>([]);
const emojis = ref<(EmojiDef)[]>([]);
const items = ref<Element[] | HTMLCollection>([]);
const mfmTags = ref<string[]>([]);
+const mfmParams = ref<string[]>([]);
const select = ref(-1);
const zIndex = os.claimZIndex('high');
@@ -251,6 +257,13 @@ function exec() {
}
mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q ?? ''));
+ } else if (props.type === 'mfmParam') {
+ if (props.q.params.at(-1) === '') {
+ mfmParams.value = MFM_PARAMS[props.q.tag] ?? [];
+ return;
+ }
+
+ mfmParams.value = MFM_PARAMS[props.q.tag].filter(param => param.startsWith(props.q.params.at(-1) ?? ''));
}
}
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index ea07204456..38d0d85eef 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -109,3 +109,27 @@ export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-foun
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
+export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
+ tada: ['speed=', 'delay='],
+ jelly: ['speed=', 'delay='],
+ twitch: ['speed=', 'delay='],
+ shake: ['speed=', 'delay='],
+ spin: ['speed=', 'delay=', 'left', 'alternate', 'x', 'y'],
+ jump: ['speed=', 'delay='],
+ bounce: ['speed=', 'delay='],
+ flip: ['h', 'v'],
+ x2: [],
+ x3: [],
+ x4: [],
+ scale: ['x=', 'y='],
+ position: ['x=', 'y='],
+ fg: ['color='],
+ bg: ['color='],
+ border: ['width=', 'style=', 'color=', 'radius=', 'noclip'],
+ font: ['serif', 'monospace', 'cursive', 'fantasy', 'emoji', 'math'],
+ blur: [],
+ rainbow: ['speed=', 'delay='],
+ rotate: ['deg='],
+ ruby: [],
+ unixtime: [],
+};
diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts
index 2a9a42ace5..b0c36cb927 100644
--- a/packages/frontend/src/scripts/autocomplete.ts
+++ b/packages/frontend/src/scripts/autocomplete.ts
@@ -8,13 +8,13 @@ import getCaretCoordinates from 'textarea-caret';
import { toASCII } from 'punycode/';
import { popup } from '@/os.js';
-export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag';
+export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
export class Autocomplete {
private suggestion: {
x: Ref<number>;
y: Ref<number>;
- q: Ref<string | null>;
+ q: Ref<any>;
close: () => void;
} | null;
private textarea: HTMLInputElement | HTMLTextAreaElement;
@@ -49,7 +49,7 @@ export class Autocomplete {
this.textarea = textarea;
this.textRef = textRef;
this.opening = false;
- this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag'];
+ this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag', 'mfmParam'];
this.attach();
}
@@ -80,6 +80,7 @@ export class Autocomplete {
const hashtagIndex = text.lastIndexOf('#');
const emojiIndex = text.lastIndexOf(':');
const mfmTagIndex = text.lastIndexOf('$');
+ const mfmParamIndex = text.lastIndexOf('.');
const max = Math.max(
mentionIndex,
@@ -94,7 +95,8 @@ export class Autocomplete {
const isMention = mentionIndex !== -1;
const isHashtag = hashtagIndex !== -1;
- const isMfmTag = mfmTagIndex !== -1;
+ const isMfmParam = mfmParamIndex !== -1 && text.split(/\$\[[a-zA-Z]+/).pop()?.includes('.');
+ const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
let opened = false;
@@ -134,6 +136,17 @@ export class Autocomplete {
}
}
+ if (isMfmParam && !opened && this.onlyType.includes('mfmParam')) {
+ const mfmParam = text.substring(mfmParamIndex + 1);
+ if (!mfmParam.includes(' ')) {
+ this.open('mfmParam', {
+ tag: text.substring(mfmTagIndex + 2, mfmParamIndex),
+ params: mfmParam.split(','),
+ });
+ opened = true;
+ }
+ }
+
if (!opened) {
this.close();
}
@@ -142,7 +155,7 @@ export class Autocomplete {
/**
* サジェストを提示します。
*/
- private async open(type: string, q: string | null) {
+ private async open(type: string, q: any) {
if (type !== this.currentType) {
this.close();
}
@@ -280,6 +293,22 @@ export class Autocomplete {
const pos = trimmedBefore.length + (value.length + 3);
this.textarea.setSelectionRange(pos, pos);
});
+ } else if (type === 'mfmParam') {
+ const source = this.text;
+
+ const before = source.substring(0, caret);
+ const trimmedBefore = before.substring(0, before.lastIndexOf('.'));
+ const after = source.substring(caret);
+
+ // 挿入
+ this.text = `${trimmedBefore}.${value}${after}`;
+
+ // キャレットを戻す
+ nextTick(() => {
+ this.textarea.focus();
+ const pos = trimmedBefore.length + (value.length + 1);
+ this.textarea.setSelectionRange(pos, pos);
+ });
}
}
}