diff options
| author | 1Step621 <86859447+1STEP621@users.noreply.github.com> | 2024-01-19 18:50:26 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-19 18:50:26 +0900 |
| commit | 678dba92451ef9b94bc40ea7de5ddd0e24249829 (patch) | |
| tree | a791d89171a2fdd089382ff79e7bf6edc4ffeeba /packages | |
| parent | fix: Some fixes for #12850 (#12862) (diff) | |
| download | sharkey-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.vue | 17 | ||||
| -rw-r--r-- | packages/frontend/src/const.ts | 24 | ||||
| -rw-r--r-- | packages/frontend/src/scripts/autocomplete.ts | 39 |
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); + }); } } } |