1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
const getCaretCoordinates = require('textarea-caret');
const riot = require('riot');
/**
* オートコンプリートを管理するクラス。
*/
class Autocomplete {
/**
* 対象のテキストエリアを与えてインスタンスを初期化します。
*/
constructor(textarea) {
// BIND ---------------------------------
this.onInput = this.onInput.bind(this);
this.complete = this.complete.bind(this);
this.close = this.close.bind(this);
// --------------------------------------
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 tag = 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;
tag.style.left = x + 'px';
tag.style.top = y + 'px';
// 要素追加
const el = document.body.appendChild(tag);
// マウント
this.suggestion = riot.mount(el, {
textarea: this.textarea,
complete: this.complete,
close: this.close,
type: type,
q: q
})[0];
}
/**
* [Private] サジェストを閉じます。
*/
close() {
if (this.suggestion == null) 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;
|