From 54eb188b0e6883239b0683dbcf276a778a00a3a8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Feb 2017 17:18:31 +0900 Subject: [PATCH] :v: --- src/web/app/desktop/mixins.ls | 2 +- src/web/app/desktop/scripts/autocomplete.js | 124 ++++++++++++++++++++ src/web/app/desktop/scripts/autocomplete.ls | 108 ----------------- 3 files changed, 125 insertions(+), 109 deletions(-) create mode 100644 src/web/app/desktop/scripts/autocomplete.js delete mode 100644 src/web/app/desktop/scripts/autocomplete.ls diff --git a/src/web/app/desktop/mixins.ls b/src/web/app/desktop/mixins.ls index 054acbe74..e32cb1ac4 100644 --- a/src/web/app/desktop/mixins.ls +++ b/src/web/app/desktop/mixins.ls @@ -38,7 +38,7 @@ module.exports = (me) ~> update-wallpaper: require './scripts/update-wallpaper.ls' riot.mixin \autocomplete do - Autocomplete: require './scripts/autocomplete.ls' + Autocomplete: require './scripts/autocomplete' riot.mixin \follow-scroll do Follower: require './scripts/follow-scroll.ls' diff --git a/src/web/app/desktop/scripts/autocomplete.js b/src/web/app/desktop/scripts/autocomplete.js new file mode 100644 index 000000000..54985874d --- /dev/null +++ b/src/web/app/desktop/scripts/autocomplete.js @@ -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; diff --git a/src/web/app/desktop/scripts/autocomplete.ls b/src/web/app/desktop/scripts/autocomplete.ls deleted file mode 100644 index 391fb312e..000000000 --- a/src/web/app/desktop/scripts/autocomplete.ls +++ /dev/null @@ -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