OSDN Git Service

tagget\8dÄ\8en\93®\8f\80\94õ
[ligheditor/tagget.git] / jquery.tagget.js
1 /**
2  * tagget: Not WYSIWYG Editor (jQuery Plugin)
3  * Simple interface and good suggestions.
4  *
5  * http://tagget.org/
6  *
7  * Licensed under the MIT license.
8  * Copyright (c) 2009 tagget.org
9  *
10  * version 0.1.0
11  */
12  
13 (function($) {
14
15         // $('textarea.tagget').tagget()とかで呼び出し
16         $.fn.tagget = function(conf) {
17
18                 // 入力補完候補
19                 var keywords = {
20                 
21                         // htmlのキーワード       
22                         html: (function() {
23                 
24                                 var tags = [
25                                         '<a>#{cursor}</a>',
26                                         '<address>#{cursor}</address>',
27                                         '<![CDATA[ #{cursor} ]]>', 
28                                         '<img src="#{cursor}" />',
29                                         '<br />',
30                                         '<div>#{cursor}</div>\n',
31                                         '<span>#{cursor}</span>',
32
33                                         '<h1>#{cursor}</h1>\n',
34                                         '<h2>#{cursor}</h2>\n',
35                                         '<h3>#{cursor}</h3>\n',
36                                         '<h4>#{cursor}</h4>\n',
37                                         '<h5>#{cursor}</h5>\n',
38                                         '<h6>#{cursor}</h6>\n',
39
40                                         '<ul>\n<li>#{cursor}</li>\n</ul>\n',
41                                         '<ol>\n<li>#{cursor}</li>\n</ol>\n',
42                                         '<li>#{cursor}</li>\n',
43                                         '<dl>\n<dt>#{cursor}</dt>\n<dd></dd>\n</dl>\n',
44                                         '<dt>#{cursor}</dt>\n',
45                                         '<dt>#{cursor}</dd>\n',
46
47                                         '<strong>#{cursor}</strong>',
48                                         '<em>#{cursor}</em>',
49
50                                         '<form>\n#{cursor}\n</form>',
51                                         '<input type="#{cursor}" />',
52
53                                         '<!--',
54                                         '-->',
55                                         '<!-- #{cursor} -->'
56                                 ];
57
58                                 var header = [
59                                         '<?xml version="1.0" encoding="#{cursor}"?>\n', 
60                                         '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n',
61                                         '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n',
62                                         '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n',
63                                         '<!DOCTYPE html>', 
64                                         '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">\n',
65                                         '<link rel="stylesheet" type="text/css" href="#{cursor}" />\n',
66                                         '<script type="text/javascript" src="#{cursor}"></script>\n',
67                                         '<script type="text/javascript">\n#{cursor}\n</script>\n',
68                                         '<style type="text/css">\n#{cursor}\n</style>\n',
69
70                                         '<meta http-equiv="Content-Type" content="text/html; charset=#{cursor}" />\n',
71                                         '<meta http-equiv="Content-Style-Type" content="text/css" />\n',
72                                         '<meta http-equiv="Content-Script-Type" content="text/javascript" />\n',
73                                         '<meta name="ROBOTS" content="#{cursor}" />\n',
74                                         '<meta name="description" content="#{cursor}" />\n',
75                                         '<meta name="keywords" content="#{cursor}" />\n',
76
77                                         '<head>#{cursor}</head>\n',
78                                         '<title>#{cursor}</title>\n',
79                                         '<body>#{cursor}</body>\n',
80
81                                         '<link rel="alternate" type="application/atom+xml" title="#{cursor}" href="" />\n',
82                                         '<link rel="alternate" type="application/rss+xml" title="#{cursor}" href="" />\n',
83                                         '<link rel="EditURI" type="application/rsd+xml" title="#{cursor}" href="" />\n'
84                                 ];
85
86                                 var attributes = [
87                                         'href="#{cursor}"',
88                                         'id="#{cursor}"',
89                                         'class="#{cursor}"',
90                                         'style="#{cursor}"',
91
92                                         'onclick="#{cursor}"',
93                                         'type="#{cursor}"',
94                                         'value="#{cursor}"',
95                                         'name="#{cursor}"',
96                                         'action="#{cursor}"',
97
98                                         'xml:lang="#{cursor}"',
99                                         'lang="#{cursor}',
100                                         'xmlns="#{cursor}"'
101                                 ];
102                                 
103                                 var values = [
104                                         'UTF-8',
105                                         'EUC-JP',
106                                         'Shift-JIS',
107                                         'ja',
108                                         'http://www.w3.org/1999/xhtml'
109                                 ];
110
111 //                              return tags.concat(header, attributes, values).sort();
112                                 return tags.concat(header, attributes, values);
113
114                         })(),
115                         
116                         // JavaScript
117                         js: (function() {
118                         
119                                 var objs = [
120                                         'function() { #{cursor} }',
121                                         'if (#{cursor}) { }'
122                                 ];
123                                 
124                                 var libs = [
125                                         'click(#{cursor});',
126                                         'html(#{cursor});'
127                                 ];
128                                 
129                                 return objs.concat(libs);               
130                         
131                         })(),
132                         
133                         // CSS
134                         css: (function() {
135                         
136                                 var props = [
137                                         'margin: ',
138                                         'margin-right: ',
139                                         'margin-left: ',
140                                         'margin-top: ',
141                                         'margin-bottom: ',
142                                         'padding: ',
143                                         'float: ',
144                                         'clear: '
145                                 ];
146                                                         
147                                 var values = [
148                                         'auto',
149                                         'center',
150                                         'right',
151                                         'left',
152                                         'both'
153                                 ];
154
155                                 return props.concat(values);            
156                         
157                         })()
158                 
159                 };              
160
161                 // 設定オブジェクト
162                 conf = $.extend({
163                         tags: true, // タグボタン表示
164                         edit: true, // 編集機能表示
165                         jqui: true, // jQuery UI CSS Framework仕様
166                         vars: true // 入力内容を利用した補完
167                 }, conf);
168
169                 // 追加の入力候補
170                 conf.keys = $.extend(conf.keys, keywords);
171
172                 // thisには$('textarea.tagget')が入ってくる
173
174                 this.each(function() {
175
176 //                              var tmp = this.value;
177                                 // 初期化中メッセージ
178 //                              this.value = 'Initializing tagget...';
179                         
180                                 init(this, conf);
181                                 
182                                 // 初期化完了
183 //                              this.value = tmp;
184                         });
185                         
186                 // This is jQuery!!
187                 return this;
188                 
189         }; // $.fn.tagget
190
191         // contextに依存しない関数
192         
193         // &, <, >(, ")を変換
194         var escapeHtml = function(s, quot) {
195                 s = s.replace(/&/g, '&amp;')
196                         .replace(/</g, '&lt;')
197                         .replace(/>/g, '&gt;');
198                 
199                 return !quot ? s : s.replace(/"/g, '&quot;');
200         };
201         
202         // &amp;, &lt;, &gt;(, &quot;)を戻す
203         var unescapeHtml = function(s, quot) {
204                                         
205                 s = s.replace(/&amp;/g, '&')
206                         .replace(/&lt;/g, '<')
207                         .replace(/&gt;/g, '>');
208                 
209                 return !quot ? s : s.replace(/&quot;/g, '"');
210         };
211
212         // offsetを簡易算出
213         var getOffset = function(elm) {
214
215                 var left, top;
216
217                 if (elm.getBoundingClientRect) {
218
219                         var rect = elm.getBoundingClientRect();
220                         left = Math.round(scrollX + rect.left);
221                         top = Math.round(scrollY + rect.top);
222                         
223                 } else {
224
225                         left = elm.offsetLeft;
226                         top  = elm.offsetTop;
227                         var offsetParent = elm.offsetParent;
228                         
229                         while (offsetParent) {
230                                 left += offsetParent.offsetLeft;
231                                 top  += offsetParent.offsetTop;             
232                                 offsetParent = offsetParent.offsetParent;
233                         }
234                         
235                 }
236
237                 return {
238                         left: left,
239                         top: top
240                 };
241                 
242         };
243
244
245         // tagget初期化
246         var init = function(textarea, conf) {
247         
248                 var t = $(textarea);
249
250                 // textarea or dummyを使う関数群
251                 
252                 // textareaのカーソル位置に文字列挿入
253                 var insert = function(s) {
254
255                         // カーソル移動位置(#{cursor})を取得後、削除
256                         var cursor = s.indexOf('#{cursor}');
257                         s = s.replace('#{cursor}', '');
258
259                         // focusしないとIEでbodyに挿入されたりする
260                         // Firefoxでもボタンで挿入後にfocusが戻らない
261                         textarea.focus(); 
262
263                         // for IE
264                         if (document.selection) {
265                                 
266                                 // 選択範囲を取得
267                                 var range = document.selection.createRange();
268
269                                 // 選択中のテキスト引数sで置き換え(現在のカーソル位置にsを挿入)
270                                 range.text = s;
271
272                                 // カーソルがrange.textの最後になるので戻す
273                                 // #{cursor}指定がなければ最後のまま
274                                 var back = s.length - (cursor != -1 ? cursor : s.length);
275                                 range.move('character', -back);
276
277                                 // 現在のカーソル位置を反映する(これやらないと水の泡)
278                                 range.select();
279                         }
280
281                         // Firefox
282                         // inかundefinedあたりで判定しないとselectionStartが0の時ミスる
283                     else if ('selectionStart' in textarea) { 
284
285                                 // スクロールバーの位置を保存
286                                 var top = textarea.scrollTop;
287
288                                 // 選択範囲の開始・終了位置を取得
289                         var start = textarea.selectionStart;
290                         var end = textarea.selectionEnd;
291
292                                 // 開始位置と終了位置の間(現在のカーソル位置)にsを挿入
293                         textarea.value = textarea.value.slice(0, start) + s + textarea.value.slice(end);
294
295                                 // カーソル移動位置に移動させる
296                                 var index = start + (cursor != -1 ? cursor : s.length);
297                         textarea.setSelectionRange(index, index);
298
299                                 // 改行がたくさんある場合スクロールバーを下にずらす
300                                 if (/\n/g.test(s) && s.match(/\n/g).length > 2) {
301                                         top += parseInt(getComputedStyle(textarea, '').getPropertyValue('line-height'), 10);
302                                 }
303                                 
304                                 // スクロールバーを戻す
305                             textarea.scrollTop = top;
306                     }
307
308                         return this;
309                         
310                 };
311
312                 // 補完候補があるか?
313                 var check = function() {
314                 
315                         var matches = {
316                                 view: [],
317                                 insert: []
318                         };
319                 
320                         var text = getText()[0];
321
322                         if (text) { 
323
324                                 var words = [];
325                                 
326                                 if (conf.vars) {
327                                         var a = textarea.value.match(/[^<>\s '"#\=:;{}\(\)!?,*]+/g) || [];
328
329                                         // 重複削除
330                                         var temp = [];                          
331                                         for (var i = 0; i < a.length; i++) {
332                                         
333                                                 var v = a[i];
334                                         
335                                                 if (!(v in temp)) {
336                                                         words.push(v);
337                                                         temp[v] = true;
338                                                 }
339                                         
340                                         }
341
342                                         
343                                 }
344
345                                 for(var key in conf.keys) {
346                                         if (t.hasClass('tagget_' + key) || t.hasClass(key)) {
347                                                 words = words.concat(conf.keys[key]);
348                                         }
349                                 }
350
351 // sortは重い原因になるので止め
352 //                              words = words.sort();
353                         
354                                 for(var i = 0; i < words.length; i++) {
355                                 
356                                         if (words[i] != text && words[i].indexOf(text) == 0) {
357                                                 matches.view.push(words[i].replace(/#\{cursor\}/g, ''));
358                                                 matches.insert.push(escapeHtml(words[i].slice(text.length)));
359                                         }
360                                 }
361                                 
362                         }
363                 
364                         return (matches.view.length != 0) ? matches : false;
365
366                 
367                 };
368
369                 // カーソル位置の文字を取得
370                 var getText = function(r, after) {
371
372 //                      if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\n=]*?$/;
373                         if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\s '"#\.=;]*?$/;
374
375                         var start, end;
376
377                         // IE
378                         if (document.selection) {
379
380 //                              textarea.focus(); // focusなしでもいける
381
382                                 // 選択範囲を取得
383                                 var range = document.selection.createRange();
384
385                                 // 選択範囲の複製を作成
386                                 var clone = range.duplicate();
387
388                                 // textarea内のテキスト全体を選択
389                                 // [clone start] text1 [range start] text2 [range end] text3 [clone end]
390                                 clone.moveToElementText(textarea);
391
392                                 // cloneの選択範囲終点を、rangeの終点にあわせる
393                                 // [clone start] text1 [range start] text2 [range/clone end] text3
394                                 clone.setEndPoint('EndToEnd', range);
395
396                                 // 選択範囲始点を求める
397                                 // [clone start] text1 [range start] text2 [range/clone end] text3
398                                 // --------------------------------------------------------- clone.text.length == end
399                                 //                     ------------------------------------- range.text.length
400                                 // -------------------- clone.text.length - range.text.length = start
401                                 start = clone.text.length - range.text.length;
402                                 end = clone.text.length;
403
404                         }
405
406                         // Firefox
407                     else if ('selectionStart' in textarea) {
408
409                         start = textarea.selectionStart;
410                         end = textarea.selectionEnd;
411
412                     }
413
414                         var text;
415                         
416                         if (!after) {
417                                 text = textarea.value.slice(0, start).match(r);
418                         } else {
419                                 text = textarea.value.slice(end).match(r);
420                         }
421                         
422                         return text || [];
423                         
424                 };
425
426                 // カーソルの座標を取得
427                 var getPos = function() {
428
429                         var x, y;
430
431                         if (document.selection) {
432
433                                 var range = document.selection.createRange();
434                                 x = range.offsetLeft + 
435                                         (document.body.scrollLeft || document.documentElement.scrollLeft) - 
436                                                 document.documentElement.clientLeft;
437                                 y = range.offsetTop + 
438                                         (document.body.scrollTop || document.documentElement.scrollTop) - 
439                                                 document.documentElement.clientTop;
440                         
441                         } else if (window.getComputedStyle) {
442
443                                 var span = dummy.children('span');
444                                 
445                                 if(!span.is('span')) {
446                                         span = $('<span></span>');
447                                         span.html('|');
448                                 }
449                         
450                                 dummy.html('');
451                                 dummy.text(textarea.value.slice(0, textarea.selectionEnd));
452                                 dummy.append(span);
453
454                                 var offset = getOffset(span.get(0));
455
456                                 x = offset.left - textarea.scrollLeft;
457                                 y = offset.top - textarea.scrollTop;
458                     }
459
460                         return {
461                                 x: x,
462                                 y: y
463                         };
464                 };
465
466                 // wrap処理
467                 // textarea周囲にHTMLを追加する
468                 
469                 // 全体の枠を作ってその参照を取得
470                 // wrapの場合、普通にやると参照を取得できないのでparentsで取得
471                 var wrapper = t.wrap('<div class="tagget_wrapper"><p class="tagget_main"></p></div>')
472                         .parents('div.tagget_wrapper');
473
474                 // 必要に応じてツールバーを追加
475                 var menu = (conf.tags || conf.edit) ?
476                         wrapper.prepend(('<div class="tagget_menu"></div>')).children('div.tagget_menu') : null;
477         
478                 // タグ挿入ボタン
479                 if (conf.tags) {
480                         var tags = $('<ul class="tagget_tags"></ul>')
481                                 .append('<li><a href="#">a</a></li>')
482                                 .append('<li><a href="#">p</a></li>')
483                                 .append('<li><a href="#">ul</a></li>')
484                                 .append('<li><a href="#">li</a></li>')
485                                 .append('<li><a href="#">div</a></li>')
486                                 .append('<li><a href="#">span</a></li>')
487                                 .append('<li><a href="#">pre</a></li>')
488                                 .append('<li><a href="#">code</a></li>')
489                                 .append('<li><a href="#">blockquote</a></li>')
490                                 .append('<li><a href="#">dl</a></li>')
491                                 .append('<li><a href="#">dt</a></li>')
492                                 .append('<li><a href="#">dd</a></li>')
493                                 .append('<li><a href="#">link</a></li>')
494                                 .append('<li><a href="#">script</a></li>')
495                                 .append('<li><a href="#">frameset</a></li>')
496                                 .append('<li><a href="#">frame</a></li>');
497                         menu.append(tags);
498                 }       
499
500                 // 編集機能
501                 if (conf.edit) {
502                         var edit = $('<div class="tagget_edit"></div>');
503                         
504                         // 選択範囲変換
505                         edit.append(
506                                 $('<p class="tagget_encode"></p>').append(
507                                         $('<select></selected>')
508                                                 .append('<option selected="selected" value="">選択範囲を変換</option>')
509                                                 .append('<option value="entity">&amp &lt; &gt; → &amp;amp; &amp;lt; &amp;gt;</option>')
510                                                 .append('<option value="raw">&amp;amp; &amp;lt; &amp;gt; → &amp &lt; &gt;</option>')
511                                                 .append('<option value="enc">encodeURI()</option>')
512                                                 .append('<option value="encc">encodeURIComponent()</option>')
513                                                 .append('<option value="dec">decodeURI()</option>')
514                                                 .append('<option value="decc">decodeURIComponent()</option>')
515                                 )
516                         );
517
518                         // 置換
519                         edit.append(
520                                 $('<p class="tagget_replace"></p>')
521                                         .append('<input type="text" value="置換前" />')
522                                         .append(' → ')
523                                         .append('<input type="text" value="置換後" />')
524                                         .append('<input type="button" value="置換" />')
525                         );
526
527                         menu.append(edit);
528                 }
529
530                 // jQuery UI CSS Frameworkのclass名を設定  
531                 if (conf.jqui) {
532                         menu.addClass('ui-helper-clearfix');
533                         wrapper.addClass('ui-widget-header ui-corner-all')
534                                 .children('div, p').addClass('ui-widget-header')
535                                         .find('li').addClass('ui-state-default ui-corner-all').hover(function(){ 
536                                                 $(this).addClass('ui-state-hover'); 
537                                         }, function(){ 
538                                                 $(this).removeClass('ui-state-hover'); 
539                                         });     
540                 }       
541
542                 var body = $(document.body);
543
544                 // suggestion用の要素作成
545                 var suggest = $('<ul class="tagget_suggest"></ul>');
546                 body.append(suggest);
547
548                 // Firefox用dummy生成
549                 // カーソル座標取得に使用
550                 if (window.getComputedStyle) {
551                         
552                         var dummy = $('<pre class="tagget_dummy"></pre>');
553
554                         // textareaのstyleをdummyにコピー
555                         var onResize = function() {
556                                 
557                                 var org = getComputedStyle(textarea,'');
558
559                                 var props = [
560                                         'width', 'height',
561                                         'padding-left', 'padding-right', 'padding-top', 'padding-bottom', 
562                                         'border-left-style', 'border-right-style','border-top-style','border-bottom-style', 
563                                         'border-left-width', 'border-right-width','border-top-width','border-bottom-width', 
564                                         'font-family', 'font-size', 'line-height', 'letter-spacing', 'word-spacing'
565                                 ];
566                         
567                             for(var i = 0; i < props.length; i++){
568                             
569                                 var capitalized = props[i].replace(/-(.)/g, function(m, m1){
570                                                 return m1.toUpperCase();
571                                         });
572                             
573                                 dummy.css(capitalized, org.getPropertyValue(props[i]));
574
575                                 }
576
577                                 var offset = getOffset(textarea);
578
579                             dummy.css({
580                                 left: offset.left,
581                                 top: offset.top
582                             });
583                             
584                             dummy.width(t.width())
585                                 .height(t.height())
586                                         .scrollLeft(t.scrollLeft())
587                                 .scrollTop(t.scrollTop());
588
589                         };
590                         
591                         // resize時にはtextareaのサイズも変わるので
592                         $(window).resize(function() {
593                                 onResize();
594                         }).resize();
595
596                         body.append(dummy);
597
598                 }
599                 
600                 
601                 // イベント設定
602
603                 // タグ挿入ボタン
604                 if (conf.tags) {
605
606                         // clickイベント設定
607                         var onClick = function(elm, s) {
608
609                                 $(elm).click(function() {
610                                         insert(s);
611                                         return false;
612                                 });
613                                 
614                         };
615
616                         // タグボタンクリックで挿入
617                         tags.find('a').each(function() {
618
619                                 // htmlで判別してイベント設定
620                                 // title属性に設定するほうがいいか?
621                                 switch (this.innerHTML) {
622                                 
623                                         case 'a':
624                                                 onClick(this, '<a href="#{cursor}"></a>');
625                                                 break;
626                                 
627                                         case 'p':
628                                                 onClick(this, '<p>#{cursor}</p>\n');
629                                                 break;
630                                 
631                                         case 'ul':
632                                                 onClick(this, '<ul>\n<li>#{cursor}</li>\n</ul>\n');
633                                                 break;
634                                 
635                                         case 'li':
636                                                 onClick(this, '<li>#{cursor}</li>\n');
637                                                 break;
638
639                                         case 'dl':
640                                                 onClick(this, '<dl>\n<dt>#{cursor}</dt>\n<dd></dd>\n</dl>\n');
641                                                 break;
642
643                                         case 'dt':
644                                                 onClick(this, '<dt>#{cursor}</dt>\n');
645                                                 break;
646
647                                         case 'dd':
648                                                 onClick(this, '<dd>#{cursor}</dd>\n');
649                                                 break;
650
651                                         case 'pre':
652                                                 onClick(this, '<pre>#{cursor}</pre>\n');
653                                                 break;
654
655                                         case 'code':
656                                                 onClick(this, '<code>#{cursor}</code>');
657                                                 break;
658
659                                         case 'blockquote':
660                                                 onClick(this, '<blockquote>#{cursor}</blockquote>\n');
661                                                 break;
662
663                                         case 'div':
664                                                 onClick(this, '<div>#{cursor}</div>\n');
665                                                 break;
666
667                                         case 'span':
668                                                 onClick(this, '<span>#{cursor}</span>\n');
669                                                 break;
670
671                                         case 'link':
672                                                 onClick(this, '<link rel="stylesheet" type="text/css" href="#{cursor}" />\n');
673                                                 break;
674
675                                         case 'script':
676                                                 onClick(this, '<script type="text/javascript" src="#{cursor}"></script>\n');
677                                                 break;
678                                                 
679                                         case 'frameset':
680                                                 onClick(this, '<frameset>\n<frame src="#{cursor}" />\n</frameset>\n');
681                                                 break;
682                                                 
683                                         case 'frame':
684                                                 onClick(this, '<frame src="#{cursor}" />\n');
685                                                 break;
686                                                 
687                                 }
688                         
689                         });
690
691                 }
692
693                 // 編集機能設定   
694                 if (conf.edit) {
695                 
696                         // 選択範囲変換
697                         
698                         // 渡された関数で選択範囲を変換
699                         var onChange = function(func) {
700
701                                 textarea.focus(); 
702
703                                 if (document.selection) {
704                                 
705                                         var range = document.selection.createRange();
706                                         range.text = func(range.text);
707                                         range.select();
708                                         
709                                 } else if ('selectionStart' in textarea) { 
710
711                                         var top = textarea.scrollTop;
712
713                                 var start = textarea.selectionStart;
714                                 var end = textarea.selectionEnd;
715
716                                         textarea.value = textarea.value.slice(0, start) + 
717                                                 func(textarea.value.slice(start, end)) + 
718                                                         textarea.value.slice(end);
719
720                                 textarea.setSelectionRange(end, end);
721
722                                     textarea.scrollTop = top;
723                             }
724                         };
725                 
726                         // onchangeイベント設定
727                         edit.find('.tagget_encode select').change(function() {
728                         
729                                 switch (this.value) {
730                                 
731                                         case 'entity':
732                                                 onChange(escapeHtml);
733                                                 break;
734                                 
735                                         case 'raw':
736                                                 onChange(unescapeHtml);
737                                                 break;                          
738                                 
739                                         case 'enc':
740                                                 onChange(encodeURI);
741                                                 break;                          
742                                 
743                                         case 'encc':
744                                                 onChange(encodeURIComponent);
745                                                 break;  
746                                                                         
747                                         case 'dec':
748                                                 onChange(decodeURI);
749                                                 break;                          
750                                                                         
751                                         case 'decc':
752                                                 onChange(decodeURIComponent);
753                                                 break;                          
754                                 }
755                                 
756                                 this.value = '';
757                         
758                         });
759                         
760                         
761                         // 置換ボタンクリックで置換
762                         // 正規表現使用可
763                         var inputs = edit.find('.tagget_replace input');
764                         inputs.filter('[type=button]').click(function() {
765
766                                 var val = textarea.value;
767                                 
768                                 var before = inputs.eq(0).val();
769                                 var after = inputs.eq(1).val();
770                                 var flag = '';
771
772                                 if (before.match(/^\/.+\/([^\/]+)$/)) {
773                                         
774                                         flag = RegExp.$1;
775                                         before = before.replace(/^\/|\/[^\/]+?$/g, '');
776                                 }
777                                                                 
778                                 if (before) {
779                                         textarea.value = val.replace(new RegExp(before, flag), after);
780                                 }
781                                 
782                         });     
783                                 
784                 }
785
786                 // メイン機能
787                 // キー入力時のsuggestion設定
788                 
789                 // keyup(発生タイミングが一番少ない)で候補表示
790                 t.keyup(function(e) {
791                 
792                         var suggests = check();
793                         
794                         if (suggests) {
795                         
796                                 if (suggest.text() != suggests.view.join('')) {
797                         
798                                         suggest.html('');
799                         
800                                         for(var i = 0; i < suggests.view.length; i++) {
801                                                 var li = $('<li></li>').attr('title', suggests.insert[i])
802                                                         .append('<a href="#"></a>').text(suggests.view[i])
803                                                         .hover(function() {
804                                                                 suggest.children('li').removeClass('tagget_current');
805                                                                 $(this).addClass('tagget_current');
806                                                         })
807                                                         .click(function() {
808                                                                 insert(unescapeHtml(suggest.children('li.tagget_current').attr('title')));
809                                                                 suggest.hide();
810                                                         });
811                                                 if (i == 0) li.addClass('tagget_current');
812                                                 suggest.append(li);
813                                         }       
814
815                                 } else {
816
817                                         suggest.children('li').each(function(i) {
818                                                 $(this).attr('title', suggests.insert[i]);
819                                         });                     
820                                 }
821                                 
822                                 var pos = getPos();
823                                 
824                                 suggest.css({
825                                         left: pos.x,
826                                         top: pos.y
827                                 });
828
829                                 suggest.show();
830                                 
831                         } else {
832                                 suggest.hide();
833                         }       
834
835                         if (conf.bind) $(conf.bind).html(textarea.value);
836                 })
837                 
838                 // click時にも候補表示
839                 .click(function() {
840                         $(this).keyup();
841                 })
842                 
843                 .keydown(function(e) {
844                 
845                         // タブキャンセル
846                         // keydown以外だとうまくいかない
847                         if (e.which == 9) {
848                                 insert('\t');
849                                 return false;
850                         }
851
852                         // Shift+Enterで改行 or br入力
853                         // 補完却下も可能
854                         if (e.shiftKey && e.which == 13) {
855
856                                 var n = '\n'
857                                 
858                                 if ((t.hasClass('html') || t.hasClass('tagget_html')) && suggest.css('display') == 'none') {
859                                         n = '<br />\n';
860                                 }
861                                 
862                                 insert(n);
863                                 
864                                 return false;
865                         }
866
867                         // Ctrl+Enterで閉じタグ補完
868                         if (e.ctrlKey && e.which == 13) {
869
870                                 //                  <<-tag name-><----------attr-------------><->->
871                                 var sTag = getText(/<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g);
872                                 var sTag2 = getText(/<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g, true);
873                                 var eTag = getText(/<\s*\/\s*.+?\s*>/g);
874                                 var eTag2 = getText(/<\s*\/\s*.+?\s*>/g, true);
875 /*
876                                 console.log('s:'+sTag);
877                                 console.log('s2:'+sTag2);
878                                 console.log('e:'+eTag);
879                                 console.log('e2:'+eTag2);
880 */
881                                 // 整形式じゃなさそうなのはとりあえず無視
882                                 if (sTag.length + sTag2.length > eTag.length + eTag2.length) {
883                                 
884                                         for (var i = sTag.length - 1; i >= 0; i--) {
885                                         
886                                                 var s = sTag[i].replace(/^.*<|[\s>].*/g, '');
887                                                 if (!eTag || eTag.length == 0 || s != eTag.shift().replace(/^.*\/|>.*/g, '')) {
888                                                         insert('</' + s + '>');
889                                                         break;
890                                                 }
891                                         }
892
893                                 }
894
895                                 
896                                 // insert(n);
897                                 
898                                 return false;
899                         }
900
901                         // 十字キーで候補選択
902                         // upだとおしっぱにできない、pressだとおかしくなる
903                         if (suggest.css('display') != 'none') {
904
905
906                                 switch (e.which) {
907                                 
908                                         // up
909                                         case 38:
910                                                 var lis = suggest.children('li');
911                                                 
912                                                 for(var i = 0; i < lis.length; i++) {
913                                                 
914                                                         var li = lis.eq(i);
915                                                         
916                                                         if(li.hasClass('tagget_current')) {
917                                                                 li.removeClass('tagget_current');
918                                                                 i = (i == 0) ? lis.length - 1 : i - 1;
919                                                                 lis.eq(i).addClass('tagget_current');
920                                                                 break;
921                                                         }
922                                                 
923                                                 }
924                                                 return false;
925
926                                         // down
927                                         case 40:
928                                                 var lis = suggest.children('li');
929                                                 
930                                                 for(var i = 0; i < lis.length; i++) {
931                                                 
932                                                         var li = lis.eq(i);
933                                                         
934                                                         if(li.hasClass('tagget_current')) {
935                                                                 li.removeClass('tagget_current');
936                                                                 i = (i == lis.length - 1 ) ? 0 : i + 1;
937                                                                 lis.eq(i).addClass('tagget_current');
938                                                                 break;
939                                                         }
940                                                 
941                                                 }                       
942                                                 return false;
943
944                                         // Enterで補完
945                                         case 13:
946                                                 insert(unescapeHtml(suggest.children('li.tagget_current').attr('title')));
947                                                 suggest.hide();
948
949                                                 return false;
950                                 }
951
952                         } else {
953                         
954                                 if (e.which == 13) {
955                         
956                                         var indent = getText(/^[\t ]*/mg);
957                                         insert('\n' + (indent ? indent[indent.length - 1] : ''));
958
959                                         if (window.getComputedStyle) {
960                                                 t.scrollTop(t.scrollTop() + parseInt(getComputedStyle(textarea, '').getPropertyValue('line-height'), 10));
961                                         }
962                                         
963                                         return false;
964                         
965                                 }                       
966                         
967                         }
968
969                 });
970         
971         }; // init
972
973 })(jQuery);