| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  | <div class="compose-autosuggest {{shown ? 'shown' : ''}} {{realm === 'dialog' ? 'is-dialog' : ''}}" | 
					
						
							|  |  |  |        aria-hidden="true" > | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |   <ComposeAutosuggestionList | 
					
						
							|  |  |  |     items="{{searchResults}}" | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |     on:click="onClick(event)" | 
					
						
							|  |  |  |     :type | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |     :selected | 
					
						
							|  |  |  |   /> | 
					
						
							|  |  |  | </div> | 
					
						
							|  |  |  | <style> | 
					
						
							|  |  |  |   .compose-autosuggest { | 
					
						
							| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  |     position: absolute; | 
					
						
							|  |  |  |     left: 5px; | 
					
						
							|  |  |  |     top: 0; | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |     pointer-events: none; | 
					
						
							| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  |     opacity: 0; | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |     transition: opacity 0.1s linear; | 
					
						
							|  |  |  |     min-width: 400px; | 
					
						
							|  |  |  |     max-width: calc(100vw - 20px); | 
					
						
							| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  |     z-index: 7000; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   .compose-autosuggest.is-dialog { | 
					
						
							|  |  |  |     z-index: 11000; | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   .compose-autosuggest.shown { | 
					
						
							|  |  |  |     pointer-events: auto; | 
					
						
							| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  |     opacity: 1; | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @media (max-width: 479px) { | 
					
						
							|  |  |  |     .compose-autosuggest { | 
					
						
							|  |  |  |       /* hack: move this over to the left on mobile so it's easier to see */ | 
					
						
							|  |  |  |       transform: translateX(-58px); /* avatar size 48px + 10px padding */ | 
					
						
							| 
									
										
										
										
											2018-04-05 21:35:22 -07:00
										 |  |  |       min-width: 0; | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |       width: calc(100vw - 20px); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </style> | 
					
						
							|  |  |  | <script> | 
					
						
							|  |  |  |   import { store } from '../../_store/store' | 
					
						
							|  |  |  |   import { database } from '../../_database/database' | 
					
						
							|  |  |  |   import { insertUsername } from '../../_actions/compose' | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |   import { insertEmojiAtPosition } from '../../_actions/emoji' | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |   import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' | 
					
						
							|  |  |  |   import { once } from '../../_utils/once' | 
					
						
							|  |  |  |   import ComposeAutosuggestionList from './ComposeAutosuggestionList.html' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const SEARCH_RESULTS_LIMIT = 4 | 
					
						
							|  |  |  |   const DATABASE_SEARCH_RESULTS_LIMIT = 30 | 
					
						
							|  |  |  |   const MIN_PREFIX_LENGTH = 1 | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |   const ACCOUNT_SEARCH_REGEX = new RegExp(`(?:\\s|^)(@\\S{${MIN_PREFIX_LENGTH},})$`) | 
					
						
							|  |  |  |   const EMOJI_SEARCH_REGEX = new RegExp(`(?:\\s|^)(:[^:]{${MIN_PREFIX_LENGTH},})$`) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   export default { | 
					
						
							|  |  |  |     oncreate() { | 
					
						
							|  |  |  |       // perf improves for input responsiveness | 
					
						
							|  |  |  |       this.observe('composeSelectionStart', () => { | 
					
						
							|  |  |  |         scheduleIdleTask(() => { | 
					
						
							|  |  |  |           this.set({composeSelectionStartDeferred: this.get('composeSelectionStart')}) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       this.observe('composeFocused', (composeFocused) => { | 
					
						
							|  |  |  |         let updateFocusedState = () => { | 
					
						
							|  |  |  |           scheduleIdleTask(() => { | 
					
						
							|  |  |  |             this.set({composeFocusedDeferred: this.get('composeFocused')}) | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // TODO: hack so that when the user clicks the button, and the textarea blurs, | 
					
						
							|  |  |  |         // we don't immediately hide the dropdown which would cause the click to get lost | 
					
						
							|  |  |  |         if (composeFocused) { | 
					
						
							|  |  |  |           updateFocusedState() | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           Promise.race([ | 
					
						
							|  |  |  |             new Promise(resolve => setTimeout(resolve, 200)), | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |             new Promise(resolve => this.once('autosuggestItemSelected', resolve)) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |           ]).then(updateFocusedState) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       this.observe('searchText', async searchText => { | 
					
						
							|  |  |  |         if (!searchText) { | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |         let type = searchText.startsWith('@') ? 'account' : 'emoji' | 
					
						
							|  |  |  |         let results = (type === 'account') | 
					
						
							|  |  |  |           ? await this.searchAccounts(searchText) | 
					
						
							|  |  |  |           : await this.searchEmoji(searchText) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         this.store.set({ | 
					
						
							|  |  |  |           composeAutosuggestionSelected: 0, | 
					
						
							|  |  |  |           composeAutosuggestionSearchText: searchText, | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |           composeAutosuggestionSearchResults: results, | 
					
						
							|  |  |  |           composeAutosuggestionType: type, | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         }) | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       this.observe('shown', shown => { | 
					
						
							|  |  |  |         this.store.set({composeAutosuggestionShown: shown}) | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     methods: { | 
					
						
							|  |  |  |       once: once, | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |       onClick(item) { | 
					
						
							|  |  |  |         this.fire('autosuggestItemSelected') | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         let realm = this.get('realm') | 
					
						
							|  |  |  |         let selectionStart = this.store.get('composeSelectionStart') | 
					
						
							|  |  |  |         let searchText = this.store.get('composeAutosuggestionSearchText') | 
					
						
							|  |  |  |         let startIndex = selectionStart - searchText.length | 
					
						
							|  |  |  |         let endIndex = selectionStart | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |         if (item.acct) { | 
					
						
							|  |  |  |           /* no await */ insertUsername(realm, item.acct, startIndex, endIndex) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           /* no await */ insertEmojiAtPosition(realm, item, startIndex, endIndex) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |       async searchAccounts(searchText) { | 
					
						
							|  |  |  |         searchText = searchText.substring(1) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         let currentInstance = this.store.get('currentInstance') | 
					
						
							|  |  |  |         let results = await database.searchAccountsByUsername( | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |           currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         return results.slice(0, SEARCH_RESULTS_LIMIT) | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |       }, | 
					
						
							|  |  |  |       searchEmoji(searchText) { | 
					
						
							|  |  |  |         searchText = searchText.toLowerCase().substring(1) | 
					
						
							|  |  |  |         let customEmoji = this.store.get('currentCustomEmoji') | 
					
						
							|  |  |  |         let results = customEmoji.filter(emoji => emoji.shortcode.toLowerCase().startsWith(searchText)) | 
					
						
							|  |  |  |           .sort((a, b) => a.shortcode.toLowerCase() < b.shortcode.toLowerCase() ? -1 : 1) | 
					
						
							|  |  |  |           .slice(0, SEARCH_RESULTS_LIMIT) | 
					
						
							|  |  |  |         return results | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     computed: { | 
					
						
							|  |  |  |       composeSelectionStart: ($composeSelectionStart) => $composeSelectionStart, | 
					
						
							|  |  |  |       composeFocused: ($composeFocused) => $composeFocused, | 
					
						
							|  |  |  |       searchResults: ($composeAutosuggestionSearchResults) => $composeAutosuggestionSearchResults || [], | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |       type: ($composeAutosuggestionType) => $composeAutosuggestionType || 'account', | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |       selected: ($composeAutosuggestionSelected) => $composeAutosuggestionSelected || 0, | 
					
						
							|  |  |  |       searchText: (text, composeSelectionStartDeferred) => { | 
					
						
							|  |  |  |         let selectionStart = composeSelectionStartDeferred || 0 | 
					
						
							|  |  |  |         if (!text || selectionStart < MIN_PREFIX_LENGTH) { | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 12:24:38 -07:00
										 |  |  |         let textUpToCursor = text.substring(0, selectionStart) | 
					
						
							|  |  |  |         let match = textUpToCursor.match(ACCOUNT_SEARCH_REGEX) || textUpToCursor.match(EMOJI_SEARCH_REGEX) | 
					
						
							| 
									
										
										
										
											2018-03-24 18:04:54 -07:00
										 |  |  |         return match && match[1] | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       shown: (composeFocusedDeferred, searchText, searchResults) => { | 
					
						
							|  |  |  |         return !!(composeFocusedDeferred && | 
					
						
							|  |  |  |           searchText && | 
					
						
							|  |  |  |           searchResults.length) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     store: () => store, | 
					
						
							|  |  |  |     components: { | 
					
						
							|  |  |  |       ComposeAutosuggestionList | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | </script> |