From a70dbb7e74356caed2fe03bec05c4e8d5bde4316 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 26 Sep 2021 02:55:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(client):=20MFM=E9=96=A2=E6=95=B0=E6=A7=8B?= =?UTF-8?q?=E6=96=87=E3=81=AE=E3=82=B5=E3=82=B8=E3=82=A7=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/client/components/autocomplete.vue | 47 ++++++++++++++++---------- src/client/scripts/autocomplete.ts | 29 +++++++++++++++- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5f497dd..9bbc09860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Improvements - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように +- クライアント: MFM関数構文のサジェストを実装 - ActivityPub: HTML -> MFMの変換を強化 ### Bugfixes diff --git a/src/client/components/autocomplete.vue b/src/client/components/autocomplete.vue index 065ee6de2..e2c1af335 100644 --- a/src/client/components/autocomplete.vue +++ b/src/client/components/autocomplete.vue @@ -10,12 +10,12 @@
  • {{ $ts.selectUser }}
  • -
      +
      1. {{ hashtag }}
      -
        +
        1. @@ -24,6 +24,11 @@ ({{ emoji.aliasOf }})
        +
          +
        1. + {{ tag }} +
        2. +
        @@ -106,6 +111,8 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); //#endregion +const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle']; + export default defineComponent({ props: { type: { @@ -137,11 +144,6 @@ export default defineComponent({ type: Number, required: true, }, - - showing: { - type: Boolean, - required: true - }, }, emits: ['done', 'closed'], @@ -154,18 +156,11 @@ export default defineComponent({ hashtags: [], emojis: [], items: [], + mfmTags: [], select: -1, } }, - watch: { - showing() { - if (!this.showing) { - this.$emit('closed'); - } - } - }, - updated() { this.setPosition(); this.items = (this.$refs.suggests as Element | undefined)?.children || []; @@ -236,7 +231,9 @@ export default defineComponent({ } } - if (this.type == 'user') { + console.log(this.type); + + if (this.type === 'user') { if (this.q == null) { this.users = []; this.fetching = false; @@ -262,7 +259,7 @@ export default defineComponent({ sessionStorage.setItem(cacheKey, JSON.stringify(users)); }); } - } else if (this.type == 'hashtag') { + } else if (this.type === 'hashtag') { if (this.q == null || this.q == '') { this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); this.fetching = false; @@ -286,7 +283,7 @@ export default defineComponent({ }); } } - } else if (this.type == 'emoji') { + } else if (this.type === 'emoji') { if (this.q == null || this.q == '') { // 最近使った絵文字をサジェスト this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); @@ -314,6 +311,14 @@ export default defineComponent({ } this.emojis = matched; + } else if (this.type === 'mfmTag') { + console.log(this.q); + if (this.q == null || this.q == '') { + this.mfmTags = MFM_TAGS; + return; + } + + this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); } }, @@ -490,5 +495,11 @@ export default defineComponent({ margin: 0 0 0 8px; } } + + > .mfmTags > li { + + .name { + } + } } diff --git a/src/client/scripts/autocomplete.ts b/src/client/scripts/autocomplete.ts index c4bcc4b72..e952ad390 100644 --- a/src/client/scripts/autocomplete.ts +++ b/src/client/scripts/autocomplete.ts @@ -70,11 +70,13 @@ export class Autocomplete { const mentionIndex = text.lastIndexOf('@'); const hashtagIndex = text.lastIndexOf('#'); const emojiIndex = text.lastIndexOf(':'); + const mfmTagIndex = text.lastIndexOf('$'); const max = Math.max( mentionIndex, hashtagIndex, - emojiIndex); + emojiIndex, + mfmTagIndex); if (max == -1) { this.close(); @@ -83,6 +85,7 @@ export class Autocomplete { const isMention = mentionIndex != -1; const isHashtag = hashtagIndex != -1; + const isMfmTag = mfmTagIndex != -1; const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; @@ -114,6 +117,14 @@ export class Autocomplete { } } + if (isMfmTag && !opened) { + const mfmTag = text.substr(mfmTagIndex + 1); + if (!mfmTag.includes(' ')) { + this.open('mfmTag', mfmTag); + opened = true; + } + } + if (!opened) { this.close(); } @@ -244,6 +255,22 @@ export class Autocomplete { const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); }); + } else if (type == 'mfmTag') { + const source = this.text; + + const before = source.substr(0, caret); + const trimmedBefore = before.substring(0, before.lastIndexOf('$')); + const after = source.substr(caret); + + // 挿入 + this.text = `${trimmedBefore}$[${value} ]${after}`; + + // キャレットを戻す + this.vm.$nextTick(() => { + this.textarea.focus(); + const pos = trimmedBefore.length + (value.length + 3); + this.textarea.setSelectionRange(pos, pos); + }); } } }