mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-03 23:14:13 +00:00 
			
		
		
		
	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
 | 
					### Improvements
 | 
				
			||||||
- クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように
 | 
					- クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように
 | 
				
			||||||
 | 
					- クライアント: MFM関数構文のサジェストを実装
 | 
				
			||||||
- ActivityPub: HTML -> MFMの変換を強化
 | 
					- ActivityPub: HTML -> MFMの変換を強化
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Bugfixes
 | 
					### Bugfixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,12 +10,12 @@
 | 
				
			||||||
		</li>
 | 
							</li>
 | 
				
			||||||
		<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li>
 | 
							<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li>
 | 
				
			||||||
	</ol>
 | 
						</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">
 | 
							<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1">
 | 
				
			||||||
			<span class="name">{{ hashtag }}</span>
 | 
								<span class="name">{{ hashtag }}</span>
 | 
				
			||||||
		</li>
 | 
							</li>
 | 
				
			||||||
	</ol>
 | 
						</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">
 | 
							<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-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>
 | 
								<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>
 | 
								<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
 | 
				
			||||||
		</li>
 | 
							</li>
 | 
				
			||||||
	</ol>
 | 
						</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>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,6 +111,8 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
 | 
				
			||||||
const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
 | 
					const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
 | 
				
			||||||
//#endregion
 | 
					//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		type: {
 | 
							type: {
 | 
				
			||||||
| 
						 | 
					@ -137,11 +144,6 @@ export default defineComponent({
 | 
				
			||||||
			type: Number,
 | 
								type: Number,
 | 
				
			||||||
			required: true,
 | 
								required: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		showing: {
 | 
					 | 
				
			||||||
			type: Boolean,
 | 
					 | 
				
			||||||
			required: true
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	emits: ['done', 'closed'],
 | 
						emits: ['done', 'closed'],
 | 
				
			||||||
| 
						 | 
					@ -154,18 +156,11 @@ export default defineComponent({
 | 
				
			||||||
			hashtags: [],
 | 
								hashtags: [],
 | 
				
			||||||
			emojis: [],
 | 
								emojis: [],
 | 
				
			||||||
			items: [],
 | 
								items: [],
 | 
				
			||||||
 | 
								mfmTags: [],
 | 
				
			||||||
			select: -1,
 | 
								select: -1,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		showing() {
 | 
					 | 
				
			||||||
			if (!this.showing) {
 | 
					 | 
				
			||||||
				this.$emit('closed');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	updated() {
 | 
						updated() {
 | 
				
			||||||
		this.setPosition();
 | 
							this.setPosition();
 | 
				
			||||||
		this.items = (this.$refs.suggests as Element | undefined)?.children || [];
 | 
							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) {
 | 
									if (this.q == null) {
 | 
				
			||||||
					this.users = [];
 | 
										this.users = [];
 | 
				
			||||||
					this.fetching = false;
 | 
										this.fetching = false;
 | 
				
			||||||
| 
						 | 
					@ -262,7 +259,7 @@ export default defineComponent({
 | 
				
			||||||
						sessionStorage.setItem(cacheKey, JSON.stringify(users));
 | 
											sessionStorage.setItem(cacheKey, JSON.stringify(users));
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else if (this.type == 'hashtag') {
 | 
								} else if (this.type === 'hashtag') {
 | 
				
			||||||
				if (this.q == null || this.q == '') {
 | 
									if (this.q == null || this.q == '') {
 | 
				
			||||||
					this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
 | 
										this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
 | 
				
			||||||
					this.fetching = false;
 | 
										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 == '') {
 | 
									if (this.q == null || this.q == '') {
 | 
				
			||||||
					// 最近使った絵文字をサジェスト
 | 
										// 最近使った絵文字をサジェスト
 | 
				
			||||||
					this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null);
 | 
										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;
 | 
									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;
 | 
								margin: 0 0 0 8px;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .mfmTags > li {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.name {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,11 +70,13 @@ export class Autocomplete {
 | 
				
			||||||
		const mentionIndex = text.lastIndexOf('@');
 | 
							const mentionIndex = text.lastIndexOf('@');
 | 
				
			||||||
		const hashtagIndex = text.lastIndexOf('#');
 | 
							const hashtagIndex = text.lastIndexOf('#');
 | 
				
			||||||
		const emojiIndex = text.lastIndexOf(':');
 | 
							const emojiIndex = text.lastIndexOf(':');
 | 
				
			||||||
 | 
							const mfmTagIndex = text.lastIndexOf('$');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const max = Math.max(
 | 
							const max = Math.max(
 | 
				
			||||||
			mentionIndex,
 | 
								mentionIndex,
 | 
				
			||||||
			hashtagIndex,
 | 
								hashtagIndex,
 | 
				
			||||||
			emojiIndex);
 | 
								emojiIndex,
 | 
				
			||||||
 | 
								mfmTagIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (max == -1) {
 | 
							if (max == -1) {
 | 
				
			||||||
			this.close();
 | 
								this.close();
 | 
				
			||||||
| 
						 | 
					@ -83,6 +85,7 @@ export class Autocomplete {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const isMention = mentionIndex != -1;
 | 
							const isMention = mentionIndex != -1;
 | 
				
			||||||
		const isHashtag = hashtagIndex != -1;
 | 
							const isHashtag = hashtagIndex != -1;
 | 
				
			||||||
 | 
							const isMfmTag = mfmTagIndex != -1;
 | 
				
			||||||
		const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
 | 
							const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let opened = false;
 | 
							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) {
 | 
							if (!opened) {
 | 
				
			||||||
			this.close();
 | 
								this.close();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -244,6 +255,22 @@ export class Autocomplete {
 | 
				
			||||||
				const pos = trimmedBefore.length + value.length;
 | 
									const pos = trimmedBefore.length + value.length;
 | 
				
			||||||
				this.textarea.setSelectionRange(pos, pos);
 | 
									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
		
		Reference in a new issue