<textarea id="the-compose-box-input-{realm}" class="compose-box-input" placeholder="What's on your mind?" ref:textarea bind:value=rawText on:blur="onBlur()" on:focus="onFocus()" on:selectionChange="onSelectionChange(event)" on:keydown="onKeydown(event)" ></textarea> <label for="the-compose-box-input-{realm}" class="sr-only"> What's on your mind? </label> <style> .compose-box-input { grid-area: input; margin: 10px 0 0 5px; padding: 10px; border: 1px solid var(--input-border); min-height: 75px; resize: none; overflow: hidden; word-wrap: break-word; /* Text must be at least 16px or else iOS Safari zooms in */ font-size: 1.2em; /* Hack to make Edge stretch the element all the way to the right. * Also desktop Safari makes the gauge stretch too far to the right without it. */ width: calc(100% - 5px); } </style> <script> import { store } from '../../_store/store' import { autosize } from '../../_thirdparty/autosize/autosize' import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' import debounce from 'lodash-es/debounce' import { mark, stop } from '../../_utils/marks' import { selectionChange } from '../../_utils/events' import { clickSelectedAutosuggestionUsername, clickSelectedAutosuggestionEmoji } from '../../_actions/autosuggest' import { observe } from 'svelte-extras' export default { oncreate () { this.setupSyncFromStore() this.setupSyncToStore() this.setupAutosize() }, ondestroy () { this.teardownAutosize() }, methods: { observe, setupSyncFromStore () { let textarea = this.refs.textarea let firstTime = true this.observe('text', text => { let { rawText } = this.get() if (rawText !== text) { this.set({ rawText: text }) // this next autosize is required to resize after // the user clicks the "toot" button mark('autosize.update()') autosize.update(textarea) stop('autosize.update()') } if (firstTime) { firstTime = false let { autoFocus } = this.get() if (autoFocus) { requestAnimationFrame(() => textarea.focus()) } } }) }, setupSyncToStore () { const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) this.observe('rawText', rawText => { mark('observe rawText') let { realm } = this.get() this.store.setComposeData(realm, { text: rawText }) saveStore() stop('observe rawText') }, { init: false }) }, setupAutosize () { let textarea = this.refs.textarea requestAnimationFrame(() => { mark('autosize()') autosize(textarea) stop('autosize()') }) }, teardownAutosize () { mark('autosize.destroy()') autosize.destroy(this.refs.textarea) stop('autosize.destroy()') }, onBlur () { scheduleIdleTask(() => { this.store.setForCurrentAutosuggest({ composeFocused: false }) }) }, onFocus () { scheduleIdleTask(() => { let { realm } = this.get() this.store.set({ currentComposeRealm: realm }) this.store.setForCurrentAutosuggest({ composeFocused: true }) }) }, onSelectionChange (selectionStart) { scheduleIdleTask(() => { this.store.setForCurrentAutosuggest({ composeSelectionStart: selectionStart }) }) }, onKeydown (e) { let { keyCode } = e // ctrl or cmd (on macs) was pressed; ctrl-enter means post a toot const ctrlPressed = e.getModifierState('Control') || e.getModifierState('Meta') switch (keyCode) { case 9: // tab this.clickSelectedAutosuggestion(e) break case 13: // enter const autosuggestionClicked = this.clickSelectedAutosuggestion(e) if (!autosuggestionClicked && ctrlPressed) { this.fire('postAction') } break case 38: // up this.incrementAutosuggestSelected(-1, e) break case 40: // down this.incrementAutosuggestSelected(1, e) break case 27: // escape this.clearAutosuggestions(e) break default: } }, clickSelectedAutosuggestion (event) { let { autosuggestShown, autosuggestType } = this.store.get() if (!autosuggestShown) { return false } let { realm } = this.get() if (autosuggestType === 'account') { /* no await */ clickSelectedAutosuggestionUsername(realm) } else { // emoji /* no await */ clickSelectedAutosuggestionEmoji(realm) } event.preventDefault() event.stopPropagation() return true }, incrementAutosuggestSelected (increment, event) { let { autosuggestShown, autosuggestSelected, autosuggestSearchResults } = this.store.get() if (!autosuggestShown) { return } autosuggestSelected += increment if (autosuggestSelected >= 0) { autosuggestSelected = autosuggestSelected % autosuggestSearchResults.length } else { autosuggestSelected = autosuggestSearchResults.length + autosuggestSelected } this.store.setForCurrentAutosuggest({ autosuggestSelected }) event.preventDefault() event.stopPropagation() }, clearAutosuggestions (event) { let { autosuggestShown } = this.store.get() if (!autosuggestShown) { return } this.store.setForCurrentAutosuggest({ autosuggestSearchResults: [], autosuggestSelected: 0 }) event.preventDefault() event.stopPropagation() } }, store: () => store, data: () => ({ rawText: '' }), events: { selectionChange } } </script>