| @@ -131,6 +131,7 @@ common: | |||||||
|   show-full-acct: "ユーザー名のホストを省略しない" |   show-full-acct: "ユーザー名のホストを省略しない" | ||||||
|   reduce-motion: "UIの動きを減らす" |   reduce-motion: "UIの動きを減らす" | ||||||
|   this-setting-is-this-device-only: "このデバイスのみ" |   this-setting-is-this-device-only: "このデバイスのみ" | ||||||
|  |   use-os-default-emojis: "OS標準の絵文字を使用" | ||||||
|  |  | ||||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' |   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,7 +14,8 @@ | |||||||
| 	</ol> | 	</ol> | ||||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | 	<ol class="emojis" ref="suggests" v-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.url"><img :src="emoji.url" :alt="emoji.emoji"/></span> | 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||||
|  | 			<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||||
| 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | ||||||
| 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | ||||||
| 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | ||||||
| @@ -33,6 +34,7 @@ type EmojiDef = { | |||||||
| 	name: string; | 	name: string; | ||||||
| 	aliasOf?: string; | 	aliasOf?: string; | ||||||
| 	url?: string; | 	url?: string; | ||||||
|  | 	isCustomEmoji?: boolean; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const lib = Object.entries(emojilib.lib).filter((x: any) => { | const lib = Object.entries(emojilib.lib).filter((x: any) => { | ||||||
| @@ -42,7 +44,8 @@ const lib = Object.entries(emojilib.lib).filter((x: any) => { | |||||||
| const emjdb: EmojiDef[] = lib.map((x: any) => ({ | const emjdb: EmojiDef[] = lib.map((x: any) => ({ | ||||||
| 	emoji: x[1].char, | 	emoji: x[1].char, | ||||||
| 	name: x[0], | 	name: x[0], | ||||||
| 	aliasOf: null | 	aliasOf: null, | ||||||
|  | 	url: `https://twemoji.maxcdn.com/2/svg/${x[1].char.codePointAt(0).toString(16)}.svg` | ||||||
| })); | })); | ||||||
|  |  | ||||||
| lib.forEach((x: any) => { | lib.forEach((x: any) => { | ||||||
| @@ -51,7 +54,8 @@ lib.forEach((x: any) => { | |||||||
| 			emjdb.push({ | 			emjdb.push({ | ||||||
| 				emoji: x[1].char, | 				emoji: x[1].char, | ||||||
| 				name: k, | 				name: k, | ||||||
| 				aliasOf: x[0] | 				aliasOf: x[0], | ||||||
|  | 				url: `https://twemoji.maxcdn.com/2/svg/${x[1].char.codePointAt(0).toString(16)}.svg` | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| @@ -77,6 +81,10 @@ export default Vue.extend({ | |||||||
| 	computed: { | 	computed: { | ||||||
| 		items(): HTMLCollection { | 		items(): HTMLCollection { | ||||||
| 			return (this.$refs.suggests as Element).children; | 			return (this.$refs.suggests as Element).children; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		useOsDefaultEmojis(): boolean { | ||||||
|  | 			return this.$store.state.device.useOsDefaultEmojis; | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -107,7 +115,8 @@ export default Vue.extend({ | |||||||
| 			emojiDefinitions.push({ | 			emojiDefinitions.push({ | ||||||
| 				name: x.name, | 				name: x.name, | ||||||
| 				emoji: `:${x.name}:`, | 				emoji: `:${x.name}:`, | ||||||
| 				url: x.url | 				url: x.url, | ||||||
|  | 				isCustomEmoji: true | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			if (x.aliases) { | 			if (x.aliases) { | ||||||
| @@ -116,7 +125,8 @@ export default Vue.extend({ | |||||||
| 						name: alias, | 						name: alias, | ||||||
| 						aliasOf: x.name, | 						aliasOf: x.name, | ||||||
| 						emoji: `:${x.name}:`, | 						emoji: `:${x.name}:`, | ||||||
| 						url: x.url | 						url: x.url, | ||||||
|  | 						isCustomEmoji: true | ||||||
| 					}); | 					}); | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								src/client/app/common/views/components/emoji.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/client/app/common/views/components/emoji.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | <template> | ||||||
|  | <img v-if="customEmoji" class="fvgwvorwhxigeolkkrcderjzcawqrscl custom" :src="url" :alt="alt" :title="alt"/> | ||||||
|  | <img v-else-if="char && !useOsDefaultEmojis" class="fvgwvorwhxigeolkkrcderjzcawqrscl" :src="url" :alt="alt" :title="alt"/> | ||||||
|  | <span v-else-if="char && useOsDefaultEmojis">{{ char }}</span> | ||||||
|  | <span v-else>:{{ name }}:</span> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { lib } from 'emojilib'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	props: { | ||||||
|  | 		name: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		emoji: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		customEmojis: { | ||||||
|  | 			required: false, | ||||||
|  | 			default: [] | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			url: null, | ||||||
|  | 			char: null, | ||||||
|  | 			customEmoji: null | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		alt(): string { | ||||||
|  | 			return this.customEmoji ? `:${this.customEmoji.name}:` : this.char; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		useOsDefaultEmojis(): boolean { | ||||||
|  | 			return this.$store.state.device.useOsDefaultEmojis; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	created() { | ||||||
|  | 		if (this.name) { | ||||||
|  | 			const customEmoji = this.customEmojis.find(x => x.name == this.name); | ||||||
|  | 			if (customEmoji) { | ||||||
|  | 				this.customEmoji = customEmoji; | ||||||
|  | 				this.url = customEmoji.url; | ||||||
|  | 			} else { | ||||||
|  | 				const emoji = lib[this.name]; | ||||||
|  | 				if (emoji) { | ||||||
|  | 					this.char = emoji.char; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			this.char = this.emoji; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this.char) { | ||||||
|  | 			this.url = `https://twemoji.maxcdn.com/2/svg/${this.char.codePointAt(0).toString(16)}.svg`; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .fvgwvorwhxigeolkkrcderjzcawqrscl | ||||||
|  | 	height 1em | ||||||
|  |  | ||||||
|  | 	&.custom | ||||||
|  | 		height 2.5em | ||||||
|  | 		vertical-align middle | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -39,6 +39,7 @@ import urlPreview from './url-preview.vue'; | |||||||
| import twitterSetting from './twitter-setting.vue'; | import twitterSetting from './twitter-setting.vue'; | ||||||
| import githubSetting from './github-setting.vue'; | import githubSetting from './github-setting.vue'; | ||||||
| import fileTypeIcon from './file-type-icon.vue'; | import fileTypeIcon from './file-type-icon.vue'; | ||||||
|  | import emoji from './emoji.vue'; | ||||||
| import Reversi from './games/reversi/reversi.vue'; | import Reversi from './games/reversi/reversi.vue'; | ||||||
| import welcomeTimeline from './welcome-timeline.vue'; | import welcomeTimeline from './welcome-timeline.vue'; | ||||||
| import uiInput from './ui/input.vue'; | import uiInput from './ui/input.vue'; | ||||||
| @@ -93,6 +94,7 @@ Vue.component('mk-url-preview', urlPreview); | |||||||
| Vue.component('mk-twitter-setting', twitterSetting); | Vue.component('mk-twitter-setting', twitterSetting); | ||||||
| Vue.component('mk-github-setting', githubSetting); | Vue.component('mk-github-setting', githubSetting); | ||||||
| Vue.component('mk-file-type-icon', fileTypeIcon); | Vue.component('mk-file-type-icon', fileTypeIcon); | ||||||
|  | Vue.component('mk-emoji', emoji); | ||||||
| Vue.component('mk-reversi', Reversi); | Vue.component('mk-reversi', Reversi); | ||||||
| Vue.component('mk-welcome-timeline', welcomeTimeline); | Vue.component('mk-welcome-timeline', welcomeTimeline); | ||||||
| Vue.component('ui-input', uiInput); | Vue.component('ui-input', uiInput); | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import Vue, { VNode } from 'vue'; | import Vue, { VNode } from 'vue'; | ||||||
| import * as emojilib from 'emojilib'; |  | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import parse from '../../../../../mfm/parse'; | import parse from '../../../../../mfm/parse'; | ||||||
| import getAcct from '../../../../../misc/acct/render'; | import getAcct from '../../../../../misc/acct/render'; | ||||||
| @@ -188,25 +187,16 @@ export default Vue.component('misskey-flavored-markdown', { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				case 'emoji': { | 				case 'emoji': { | ||||||
| 					//#region カスタム絵文字 | 					return [createElement('mk-emoji', { | ||||||
| 					if (this.customEmojis != null) { |  | ||||||
| 						const customEmoji = this.customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji)); |  | ||||||
| 						if (customEmoji) { |  | ||||||
| 							return [createElement('img', { |  | ||||||
| 						attrs: { | 						attrs: { | ||||||
| 									src: customEmoji.url, | 							emoji: token.emoji, | ||||||
| 									alt: token.emoji, | 							name: token.name | ||||||
| 									title: token.emoji, | 						}, | ||||||
| 									style: 'height: 2.5em; vertical-align: middle;' | 						props: { | ||||||
|  | 							customEmojis: this.customEmojis | ||||||
| 						} | 						} | ||||||
| 					})]; | 					})]; | ||||||
| 				} | 				} | ||||||
| 					} |  | ||||||
| 					//#endregion |  | ||||||
|  |  | ||||||
| 					const emoji = emojilib.lib[token.emoji]; |  | ||||||
| 					return [createElement('span', emoji ? emoji.char : token.content)]; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				case 'search': { | 				case 'search': { | ||||||
| 					return [createElement(MkGoogle, { | 					return [createElement(MkGoogle, { | ||||||
|   | |||||||
| @@ -145,6 +145,7 @@ class Autocomplete { | |||||||
| 		} else { | 		} else { | ||||||
| 			// サジェスト要素作成 | 			// サジェスト要素作成 | ||||||
| 			this.suggestion = new MkAutocomplete({ | 			this.suggestion = new MkAutocomplete({ | ||||||
|  | 				parent: this.vm, | ||||||
| 				propsData: { | 				propsData: { | ||||||
| 					textarea: this.textarea, | 					textarea: this.textarea, | ||||||
| 					complete: this.complete, | 					complete: this.complete, | ||||||
|   | |||||||
| @@ -115,6 +115,7 @@ | |||||||
| 				<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion%</ui-switch> | 				<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion%</ui-switch> | ||||||
| 				<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch> | 				<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch> | ||||||
| 				<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch> | 				<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch> | ||||||
|  | 				<ui-switch v-model="useOsDefaultEmojis">%i18n:common.use-os-default-emojis%</ui-switch> | ||||||
| 				<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | 				<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | ||||||
| 			</section> | 			</section> | ||||||
| 			<section> | 			<section> | ||||||
| @@ -324,6 +325,11 @@ export default Vue.extend({ | |||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
|  | 		useOsDefaultEmojis: { | ||||||
|  | 			get() { return this.$store.state.device.useOsDefaultEmojis; }, | ||||||
|  | 			set(value) { this.$store.commit('device/set', { key: 'useOsDefaultEmojis', value }); } | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		reduceMotion: { | 		reduceMotion: { | ||||||
| 			get() { return this.$store.state.device.reduceMotion; }, | 			get() { return this.$store.state.device.reduceMotion; }, | ||||||
| 			set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); } | 			set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); } | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ | |||||||
| 					<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | 					<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | ||||||
| 					<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch> | 					<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch> | ||||||
| 					<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch> | 					<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch> | ||||||
|  | 					<ui-switch v-model="useOsDefaultEmojis">%i18n:common.use-os-default-emojis%</ui-switch> | ||||||
| 					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | 					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | ||||||
| 					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> | 					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> | ||||||
| 					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | 					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | ||||||
| @@ -199,6 +200,11 @@ export default Vue.extend({ | |||||||
| 			set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); } | 			set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); } | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		useOsDefaultEmojis: { | ||||||
|  | 			get() { return this.$store.state.device.useOsDefaultEmojis; }, | ||||||
|  | 			set(value) { this.$store.commit('device/set', { key: 'useOsDefaultEmojis', value }); } | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		reduceMotion: { | 		reduceMotion: { | ||||||
| 			get() { return this.$store.state.device.reduceMotion; }, | 			get() { return this.$store.state.device.reduceMotion; }, | ||||||
| 			set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); } | 			set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); } | ||||||
|   | |||||||
| @@ -62,7 +62,8 @@ const defaultDeviceSettings = { | |||||||
| 	deckColumnAlign: 'center', | 	deckColumnAlign: 'center', | ||||||
| 	mobileNotificationPosition: 'bottom', | 	mobileNotificationPosition: 'bottom', | ||||||
| 	deckTemporaryColumn: null, | 	deckTemporaryColumn: null, | ||||||
| 	deckDefault: false | 	deckDefault: false, | ||||||
|  | 	useOsDefaultEmojis: false | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default (os: MiOS) => new Vuex.Store({ | export default (os: MiOS) => new Vuex.Store({ | ||||||
|   | |||||||
| @@ -5,16 +5,29 @@ | |||||||
| export type TextElementEmoji = { | export type TextElementEmoji = { | ||||||
| 	type: 'emoji'; | 	type: 'emoji'; | ||||||
| 	content: string; | 	content: string; | ||||||
| 	emoji: string; | 	emoji?: string; | ||||||
|  | 	name?: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const emojiRegex = /^[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/ug; | ||||||
|  |  | ||||||
| export default function(text: string) { | export default function(text: string) { | ||||||
| 	const match = text.match(/^:([a-zA-Z0-9+_-]+):/); | 	const name = text.match(/^:([a-zA-Z0-9+_-]+):/); | ||||||
| 	if (!match) return null; | 	if (name) { | ||||||
| 	const emoji = match[0]; |  | ||||||
| 		return { | 		return { | ||||||
| 			type: 'emoji', | 			type: 'emoji', | ||||||
| 		content: emoji, | 			content: name[0], | ||||||
| 		emoji: match[1] | 			name: name[1] | ||||||
| 		} as TextElementEmoji; | 		} as TextElementEmoji; | ||||||
|  | 	} | ||||||
|  | 	const unicode = text.match(emojiRegex); | ||||||
|  | 	if (unicode) { | ||||||
|  | 		const [content] = unicode; | ||||||
|  | 		return { | ||||||
|  | 			type: 'emoji', | ||||||
|  | 			content, | ||||||
|  | 			emoji: content | ||||||
|  | 		} as TextElementEmoji; | ||||||
|  | 	} | ||||||
|  | 	return null; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| import parse from '../../../mfm/parse'; |  | ||||||
|  |  | ||||||
| export default function(text: string) { |  | ||||||
| 	if (!text) return []; |  | ||||||
| 	return parse(text).filter(t => t.type === 'emoji').map(t => (t as any).emoji); |  | ||||||
| } |  | ||||||
| @@ -8,9 +8,7 @@ import Note, { INote } from '../../../models/note'; | |||||||
| import User from '../../../models/user'; | import User from '../../../models/user'; | ||||||
| import toHtml from '../misc/get-note-html'; | import toHtml from '../misc/get-note-html'; | ||||||
| import parseMfm from '../../../mfm/parse'; | import parseMfm from '../../../mfm/parse'; | ||||||
| import getEmojiNames from '../misc/get-emoji-names'; |  | ||||||
| import Emoji, { IEmoji } from '../../../models/emoji'; | import Emoji, { IEmoji } from '../../../models/emoji'; | ||||||
| import { unique } from '../../../prelude/array'; |  | ||||||
|  |  | ||||||
| export default async function renderNote(note: INote, dive = true): Promise<any> { | export default async function renderNote(note: INote, dive = true): Promise<any> { | ||||||
| 	const promisedFiles: Promise<IDriveFile[]> = note.fileIds | 	const promisedFiles: Promise<IDriveFile[]> = note.fileIds | ||||||
| @@ -110,8 +108,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any> | |||||||
|  |  | ||||||
| 	const content = toHtml(Object.assign({}, note, { text })); | 	const content = toHtml(Object.assign({}, note, { text })); | ||||||
|  |  | ||||||
| 	const emojiNames = unique(getEmojiNames(content)); | 	const emojis = await getEmojis(note.emojis); | ||||||
| 	const emojis = await getEmojis(emojiNames); |  | ||||||
| 	const apemojis = emojis.map(emoji => renderEmoji(emoji)); | 	const apemojis = emojis.map(emoji => renderEmoji(emoji)); | ||||||
|  |  | ||||||
| 	const tag = [ | 	const tag = [ | ||||||
| @@ -141,12 +138,10 @@ async function getEmojis(names: string[]): Promise<IEmoji[]> { | |||||||
| 	if (names == null || names.length < 1) return []; | 	if (names == null || names.length < 1) return []; | ||||||
|  |  | ||||||
| 	const emojis = await Promise.all( | 	const emojis = await Promise.all( | ||||||
| 		names.map(async name => { | 		names.map(name => Emoji.findOne({ | ||||||
| 			return await Emoji.findOne({ |  | ||||||
| 			name, | 			name, | ||||||
| 			host: null | 			host: null | ||||||
| 			}); | 		})) | ||||||
| 		}) |  | ||||||
| 	); | 	); | ||||||
|  |  | ||||||
| 	return emojis.filter(emoji => emoji != null); | 	return emojis.filter(emoji => emoji != null); | ||||||
|   | |||||||
| @@ -456,8 +456,8 @@ function extractHashtags(tokens: ReturnType<typeof parse>): string[] { | |||||||
| function extractEmojis(tokens: ReturnType<typeof parse>): string[] { | function extractEmojis(tokens: ReturnType<typeof parse>): string[] { | ||||||
| 	// Extract emojis | 	// Extract emojis | ||||||
| 	const emojis = tokens | 	const emojis = tokens | ||||||
| 		.filter(t => t.type == 'emoji') | 		.filter(t => t.type == 'emoji' && t.name) | ||||||
| 		.map(t => (t as TextElementEmoji).emoji) | 		.map(t => (t as TextElementEmoji).name) | ||||||
| 		.filter(emoji => emoji.length <= 100); | 		.filter(emoji => emoji.length <= 100); | ||||||
|  |  | ||||||
| 	return unique(emojis); | 	return unique(emojis); | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								test/mfm.ts
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								test/mfm.ts
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ describe('Text', () => { | |||||||
| 			{ type: 'text', content: ' '}, | 			{ type: 'text', content: ' '}, | ||||||
| 			{ type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }, | 			{ type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }, | ||||||
| 			{ type: 'text', content: ' お腹ペコい ' }, | 			{ type: 'text', content: ' お腹ペコい ' }, | ||||||
| 			{ type: 'emoji', content: ':cat:', emoji: 'cat'}, | 			{ type: 'emoji', content: ':cat:', name: 'cat'}, | ||||||
| 			{ type: 'text', content: ' '}, | 			{ type: 'text', content: ' '}, | ||||||
| 			{ type: 'hashtag', content: '#yryr', hashtag: 'yryr' } | 			{ type: 'hashtag', content: '#yryr', hashtag: 'yryr' } | ||||||
| 		], tokens); | 		], tokens); | ||||||
| @@ -182,15 +182,20 @@ describe('Text', () => { | |||||||
| 		it('emoji', () => { | 		it('emoji', () => { | ||||||
| 			const tokens1 = analyze(':cat:'); | 			const tokens1 = analyze(':cat:'); | ||||||
| 			assert.deepEqual([ | 			assert.deepEqual([ | ||||||
| 				{ type: 'emoji', content: ':cat:', emoji: 'cat'} | 				{ type: 'emoji', content: ':cat:', name: 'cat' } | ||||||
| 			], tokens1); | 			], tokens1); | ||||||
|  |  | ||||||
| 			const tokens2 = analyze(':cat::cat::cat:'); | 			const tokens2 = analyze(':cat::cat::cat:'); | ||||||
| 			assert.deepEqual([ | 			assert.deepEqual([ | ||||||
| 				{ type: 'emoji', content: ':cat:', emoji: 'cat'}, | 				{ type: 'emoji', content: ':cat:', name: 'cat' }, | ||||||
| 				{ type: 'emoji', content: ':cat:', emoji: 'cat'}, | 				{ type: 'emoji', content: ':cat:', name: 'cat' }, | ||||||
| 				{ type: 'emoji', content: ':cat:', emoji: 'cat'} | 				{ type: 'emoji', content: ':cat:', name: 'cat' } | ||||||
| 			], tokens2); | 			], tokens2); | ||||||
|  |  | ||||||
|  | 			const tokens3 = analyze('🍎'); | ||||||
|  | 			assert.deepEqual([ | ||||||
|  | 				{ type: 'emoji', content: '🍎', emoji: '🍎' } | ||||||
|  | 			], tokens3); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		it('block code', () => { | 		it('block code', () => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo