feat(client): MFM関数構文のサジェストを実装
This commit is contained in:
		
							parent
							
								
									a75f3fb87c
								
							
						
					
					
						commit
						a70dbb7e74
					
				
					 3 changed files with 58 additions and 19 deletions
				
			
		|  | @ -11,6 +11,7 @@ | |||
| 
 | ||||
| ### Improvements | ||||
| - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように | ||||
| - クライアント: MFM関数構文のサジェストを実装 | ||||
| - ActivityPub: HTML -> MFMの変換を強化 | ||||
| 
 | ||||
| ### Bugfixes | ||||
|  |  | |||
|  | @ -10,12 +10,12 @@ | |||
| 		</li> | ||||
| 		<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li> | ||||
| 	</ol> | ||||
| 	<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0"> | ||||
| 	<ol class="hashtags" ref="suggests" v-else-if="hashtags.length > 0"> | ||||
| 		<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<span class="name">{{ hashtag }}</span> | ||||
| 		</li> | ||||
| 	</ol> | ||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||
| 	<ol class="emojis" ref="suggests" v-else-if="emojis.length > 0"> | ||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> | ||||
| 			<span class="emoji" v-else-if="!$store.state.useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||
|  | @ -24,6 +24,11 @@ | |||
| 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | ||||
| 		</li> | ||||
| 	</ol> | ||||
| 	<ol class="mfmTags" ref="suggests" v-else-if="mfmTags.length > 0"> | ||||
| 		<li v-for="tag in mfmTags" @click="complete(type, tag)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<span class="tag">{{ tag }}</span> | ||||
| 		</li> | ||||
| 	</ol> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -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 { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -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); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue