This commit is contained in:
parent
f7444c6c0b
commit
54eb188b0e
|
@ -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'
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in New Issue