✌️
This commit is contained in:
		| @@ -38,7 +38,7 @@ module.exports = (me) ~> | |||||||
| 		update-wallpaper: require './scripts/update-wallpaper.ls' | 		update-wallpaper: require './scripts/update-wallpaper.ls' | ||||||
|  |  | ||||||
| 	riot.mixin \autocomplete do | 	riot.mixin \autocomplete do | ||||||
| 		Autocomplete: require './scripts/autocomplete.ls' | 		Autocomplete: require './scripts/autocomplete' | ||||||
|  |  | ||||||
| 	riot.mixin \follow-scroll do | 	riot.mixin \follow-scroll do | ||||||
| 		Follower: require './scripts/follow-scroll.ls' | 		Follower: require './scripts/follow-scroll.ls' | ||||||
|   | |||||||
							
								
								
									
										124
									
								
								src/web/app/desktop/scripts/autocomplete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/web/app/desktop/scripts/autocomplete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | const getCaretCoordinates = require('textarea-caret'); | ||||||
|  | const riot = require('riot'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * オートコンプリートを管理するクラス。 | ||||||
|  |  */ | ||||||
|  | class Autocomplete { | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 対象のテキストエリアを与えてインスタンスを初期化します。 | ||||||
|  | 	 */ | ||||||
|  | 	constructor(textarea) { | ||||||
|  | 		this.suggestion = null; | ||||||
|  | 		this.textarea = textarea; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 | ||||||
|  | 	 */ | ||||||
|  | 	attach() { | ||||||
|  | 		this.textarea.addEventListener('input', this.onInput); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 | ||||||
|  | 	 */ | ||||||
|  | 	detach() { | ||||||
|  | 		this.textarea.removeEventListener('input', this.onInput); | ||||||
|  | 		this.close(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * [Private] テキスト入力時 | ||||||
|  | 	 */ | ||||||
|  | 	onInput() { | ||||||
|  | 		this.close(); | ||||||
|  |  | ||||||
|  | 		const caret = this.textarea.selectionStart; | ||||||
|  | 		const text = this.textarea.value.substr(0, caret); | ||||||
|  |  | ||||||
|  | 		const mentionIndex = text.lastIndexOf('@'); | ||||||
|  |  | ||||||
|  | 		if (mentionIndex == -1) return; | ||||||
|  |  | ||||||
|  | 		const username = text.substr(mentionIndex + 1); | ||||||
|  |  | ||||||
|  | 		if (!username.match(/^[a-zA-Z0-9-]+$/)) return; | ||||||
|  |  | ||||||
|  | 		this.open('user', username); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * [Private] サジェストを提示します。 | ||||||
|  | 	 */ | ||||||
|  | 	open(type, q) { | ||||||
|  | 		// 既に開いているサジェストは閉じる | ||||||
|  | 		this.close(); | ||||||
|  |  | ||||||
|  | 		// サジェスト要素作成 | ||||||
|  | 		const suggestion = document.createElement('mk-autocomplete-suggestion'); | ||||||
|  |  | ||||||
|  | 		// ~ サジェストを表示すべき位置を計算 ~ | ||||||
|  |  | ||||||
|  | 		const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); | ||||||
|  |  | ||||||
|  | 		const rect = this.textarea.getBoundingClientRect(); | ||||||
|  |  | ||||||
|  | 		const x = rect.left + window.pageXOffset + caretPosition.left; | ||||||
|  | 		const y = rect.top + window.pageYOffset + caretPosition.top; | ||||||
|  |  | ||||||
|  | 		suggestion.style.left = x + 'px'; | ||||||
|  | 		suggestion.style.top = y + 'px'; | ||||||
|  |  | ||||||
|  | 		// 要素追加 | ||||||
|  | 		const el = document.body.appendChild(suggestion); | ||||||
|  |  | ||||||
|  | 		// マウント | ||||||
|  | 		this.suggestion = riot.mount(el, { | ||||||
|  | 			textarea: this.textarea, | ||||||
|  | 			complete: this.complete, | ||||||
|  | 			close: this.close, | ||||||
|  | 			type: type, | ||||||
|  | 			q: q | ||||||
|  | 		})[0]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * [Private] サジェストを閉じます。 | ||||||
|  | 	 */ | ||||||
|  | 	close() { | ||||||
|  | 		if (this.suggestion == nul) return; | ||||||
|  |  | ||||||
|  | 		this.suggestion.unmount(); | ||||||
|  | 		this.suggestion = null; | ||||||
|  |  | ||||||
|  | 		this.textarea.focus(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * [Private] オートコンプリートする | ||||||
|  | 	 */ | ||||||
|  | 	complete(user) { | ||||||
|  | 		this.close(); | ||||||
|  |  | ||||||
|  | 		const value = user.username; | ||||||
|  |  | ||||||
|  | 		const caret = this.textarea.selectionStart; | ||||||
|  | 		const source = this.textarea.value; | ||||||
|  |  | ||||||
|  | 		const before = source.substr(0, caret); | ||||||
|  | 		const trimedBefore = before.substring(0, before.lastIndexOf('@')); | ||||||
|  | 		const after = source.substr(caret); | ||||||
|  |  | ||||||
|  | 		// 結果を挿入する | ||||||
|  | 		this.textarea.value = trimedBefore + '@' + value + ' ' + after; | ||||||
|  |  | ||||||
|  | 		// キャレットを戻す | ||||||
|  | 		this.textarea.focus(); | ||||||
|  | 		const pos = caret + value.length; | ||||||
|  | 		this.textarea.setSelectionRange(pos, pos); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Autocomplete; | ||||||
| @@ -1,108 +0,0 @@ | |||||||
| # Autocomplete |  | ||||||
| #================================ |  | ||||||
|  |  | ||||||
| get-caret-coordinates = require 'textarea-caret' |  | ||||||
| riot = require 'riot' |  | ||||||
|  |  | ||||||
| # オートコンプリートを管理するクラスです。 |  | ||||||
| class Autocomplete |  | ||||||
|  |  | ||||||
| 	@textarea = null |  | ||||||
| 	@suggestion = null |  | ||||||
|  |  | ||||||
| 	# 対象のテキストエリアを与えてインスタンスを初期化します。 |  | ||||||
| 	(textarea) ~> |  | ||||||
| 		@textarea = textarea |  | ||||||
|  |  | ||||||
| 	# このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 |  | ||||||
| 	attach: ~> |  | ||||||
| 		@textarea.add-event-listener \input @on-input |  | ||||||
|  |  | ||||||
| 	# このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 |  | ||||||
| 	detach: ~> |  | ||||||
| 		@textarea.remove-event-listener \input @on-input |  | ||||||
| 		@close! |  | ||||||
|  |  | ||||||
| 	# テキスト入力時 |  | ||||||
| 	on-input: ~> |  | ||||||
| 		@close! |  | ||||||
|  |  | ||||||
| 		caret = @textarea.selection-start |  | ||||||
| 		text = @textarea.value.substr 0 caret |  | ||||||
|  |  | ||||||
| 		mention-index = text.last-index-of \@ |  | ||||||
|  |  | ||||||
| 		if mention-index == -1 |  | ||||||
| 			return |  | ||||||
|  |  | ||||||
| 		username = text.substr mention-index + 1 |  | ||||||
|  |  | ||||||
| 		if not username.match /^[a-zA-Z0-9-]+$/ |  | ||||||
| 			return |  | ||||||
|  |  | ||||||
| 		@open \user username |  | ||||||
|  |  | ||||||
| 	# サジェストを提示します。 |  | ||||||
| 	open: (type, q) ~> |  | ||||||
| 		# 既に開いているサジェストは閉じる |  | ||||||
| 		@close! |  | ||||||
|  |  | ||||||
| 		# サジェスト要素作成 |  | ||||||
| 		suggestion = document.create-element \mk-autocomplete-suggestion |  | ||||||
|  |  | ||||||
| 		# ~ サジェストを表示すべき位置を計算 ~ |  | ||||||
|  |  | ||||||
| 		caret-position = get-caret-coordinates @textarea, @textarea.selection-start |  | ||||||
|  |  | ||||||
| 		rect = @textarea.get-bounding-client-rect! |  | ||||||
|  |  | ||||||
| 		x = rect.left + window.page-x-offset + caret-position.left |  | ||||||
| 		y = rect.top + window.page-y-offset + caret-position.top |  | ||||||
|  |  | ||||||
| 		suggestion.style.left = x + \px |  | ||||||
| 		suggestion.style.top = y + \px |  | ||||||
|  |  | ||||||
| 		# 要素追加 |  | ||||||
| 		el = document.body.append-child suggestion |  | ||||||
|  |  | ||||||
| 		# マウント |  | ||||||
| 		mounted = riot.mount el, do |  | ||||||
| 			textarea: @textarea |  | ||||||
| 			complete: @complete |  | ||||||
| 			close: @close |  | ||||||
| 			type: type |  | ||||||
| 			q: q |  | ||||||
|  |  | ||||||
| 		@suggestion = mounted.0 |  | ||||||
|  |  | ||||||
| 	# サジェストを閉じます。 |  | ||||||
| 	close: ~> |  | ||||||
| 		if !@suggestion? |  | ||||||
| 			return |  | ||||||
|  |  | ||||||
| 		@suggestion.unmount! |  | ||||||
| 		@suggestion = null |  | ||||||
|  |  | ||||||
| 		@textarea.focus! |  | ||||||
|  |  | ||||||
| 	# オートコンプリートする |  | ||||||
| 	complete: (user) ~> |  | ||||||
| 		@close! |  | ||||||
| 		value = user.username |  | ||||||
|  |  | ||||||
| 		caret = @textarea.selection-start |  | ||||||
| 		source = @textarea.value |  | ||||||
|  |  | ||||||
| 		before = source.substr 0 caret |  | ||||||
| 		trimed-before = before.substring 0 before.last-index-of \@ |  | ||||||
| 		after = source.substr caret |  | ||||||
|  |  | ||||||
| 		# 結果を挿入する |  | ||||||
| 		@textarea.value = trimed-before + \@ + value + ' ' + after |  | ||||||
|  |  | ||||||
| 		# キャレットを戻す |  | ||||||
| 		@textarea.focus! |  | ||||||
| 		pos = caret + value.length |  | ||||||
| 		@textarea.set-selection-range pos, pos |  | ||||||
|  |  | ||||||
| module.exports = Autocomplete |  | ||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo