OSDN Git Service

ようやく再構成完了。閉じタグ補完修正の前に一旦保存。
authortnantoka <bornneet@livedoor.com>
Sun, 30 Aug 2009 13:06:40 +0000 (22:06 +0900)
committertnantoka <bornneet@livedoor.com>
Sun, 30 Aug 2009 13:06:40 +0000 (22:06 +0900)
jquery.tagget.js
tagget.css

index 9178b6c..dc12ee0 100644 (file)
  *
  * version 0.1.0
  */
 (function($) {
 
-       // $('textarea.tagget').tagget()とかで呼び出し
-       $.fn.tagget = function(conf) {
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+
+       /**
+        *
+        * 入力補完候補を保持、取得するオブジェクト
+        *
+        */
+       var Suggester = {
+       
+               // 入力データを利用した補完を行うか
+               byInputs: true,
 
-               // 入力補完候補
-               var keywords = {
+               keywords: {     
                
-                       // htmlのキーワード       
                        html: (function() {
-               
-                               var tags = [
-                                       '<a>#{cursor}</a>',
-                                       '<address>#{cursor}</address>',
-                                       '<![CDATA[ #{cursor} ]]>', 
-                                       '<img src="#{cursor}" />',
-                                       '<br />',
-                                       '<div>#{cursor}</div>\n',
-                                       '<span>#{cursor}</span>',
-
-                                       '<h1>#{cursor}</h1>\n',
-                                       '<h2>#{cursor}</h2>\n',
-                                       '<h3>#{cursor}</h3>\n',
-                                       '<h4>#{cursor}</h4>\n',
-                                       '<h5>#{cursor}</h5>\n',
-                                       '<h6>#{cursor}</h6>\n',
-
-                                       '<ul>\n<li>#{cursor}</li>\n</ul>\n',
-                                       '<ol>\n<li>#{cursor}</li>\n</ol>\n',
-                                       '<li>#{cursor}</li>\n',
-                                       '<dl>\n<dt>#{cursor}</dt>\n<dd></dd>\n</dl>\n',
-                                       '<dt>#{cursor}</dt>\n',
-                                       '<dt>#{cursor}</dd>\n',
-
-                                       '<strong>#{cursor}</strong>',
-                                       '<em>#{cursor}</em>',
-
-                                       '<form>\n#{cursor}\n</form>',
-                                       '<input type="#{cursor}" />',
-
-                                       '<!--',
-                                       '-->',
-                                       '<!-- #{cursor} -->'
-                               ];
 
+                               // <head>内の要素           
                                var header = [
-                                       '<?xml version="1.0" encoding="#{cursor}"?>\n', 
+                                       '<?xml version="1.0" encoding="#{c}"?>\n', 
                                        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n',
                                        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n',
                                        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n',
                                        '<!DOCTYPE html>', 
                                        '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">\n',
-                                       '<link rel="stylesheet" type="text/css" href="#{cursor}" />\n',
-                                       '<script type="text/javascript" src="#{cursor}"></script>\n',
-                                       '<script type="text/javascript">\n#{cursor}\n</script>\n',
-                                       '<style type="text/css">\n#{cursor}\n</style>\n',
+                                       '<link rel="stylesheet" type="text/css" href="#{c}" />\n',
+                                       '<script type="text/javascript" src="#{c}"></script>\n',
+                                       '<script type="text/javascript">\n#{c}\n</script>\n',
+                                       '<style type="text/css">\n#{c}\n</style>\n',
 
-                                       '<meta http-equiv="Content-Type" content="text/html; charset=#{cursor}" />\n',
+                                       '<meta http-equiv="Content-Type" content="text/html; charset=#{c}" />\n',
                                        '<meta http-equiv="Content-Style-Type" content="text/css" />\n',
                                        '<meta http-equiv="Content-Script-Type" content="text/javascript" />\n',
-                                       '<meta name="ROBOTS" content="#{cursor}" />\n',
-                                       '<meta name="description" content="#{cursor}" />\n',
-                                       '<meta name="keywords" content="#{cursor}" />\n',
+                                       '<meta name="ROBOTS" content="#{c}" />\n',
+                                       '<meta name="description" content="#{c}" />\n',
+                                       '<meta name="keywords" content="#{c}" />\n',
+
+                                       '<head>#{c}</head>\n',
+                                       '<title>#{c}</title>\n',
+                                       '<body>#{c}</body>\n',
+
+                                       '<link rel="alternate" type="application/atom+xml" title="#{c}" href="" />\n',
+                                       '<link rel="alternate" type="application/rss+xml" title="#{c}" href="" />\n',
+                                       '<link rel="EditURI" type="application/rsd+xml" title="#{c}" href="" />\n'
+                               ];
 
-                                       '<head>#{cursor}</head>\n',
-                                       '<title>#{cursor}</title>\n',
-                                       '<body>#{cursor}</body>\n',
+                               // <body>内の要素           
+                               var body = [
+                                       '<a>#{c}</a>',
+                                       '<address>#{c}</address>',
+                                       '<![CDATA[ #{c} ]]>', 
+                                       '<img src="#{c}" />',
+                                       '<br />',
+                                       '<div>#{c}</div>\n',
+                                       '<span>#{c}</span>',
+
+                                       '<h1>#{c}</h1>\n',
+                                       '<h2>#{c}</h2>\n',
+                                       '<h3>#{c}</h3>\n',
+                                       '<h4>#{c}</h4>\n',
+                                       '<h5>#{c}</h5>\n',
+                                       '<h6>#{c}</h6>\n',
+
+                                       '<ul>\n<li>#{c}</li>\n</ul>\n',
+                                       '<ol>\n<li>#{c}</li>\n</ol>\n',
+                                       '<li>#{c}</li>\n',
+                                       '<dl>\n<dt>#{c}</dt>\n<dd></dd>\n</dl>\n',
+                                       '<dt>#{c}</dt>\n',
+                                       '<dt>#{c}</dd>\n',
+
+                                       '<strong>#{c}</strong>',
+                                       '<em>#{c}</em>',
 
-                                       '<link rel="alternate" type="application/atom+xml" title="#{cursor}" href="" />\n',
-                                       '<link rel="alternate" type="application/rss+xml" title="#{cursor}" href="" />\n',
-                                       '<link rel="EditURI" type="application/rsd+xml" title="#{cursor}" href="" />\n'
+                                       '<form>\n#{c}\n</form>',
+                                       '<input type="#{c}" />',
+
+                                       '<!--',
+                                       '-->',
+                                       '<!-- #{c} -->'
                                ];
 
+                               // 属性               
                                var attributes = [
-                                       'href="#{cursor}"',
-                                       'id="#{cursor}"',
-                                       'class="#{cursor}"',
-                                       'style="#{cursor}"',
-
-                                       'onclick="#{cursor}"',
-                                       'type="#{cursor}"',
-                                       'value="#{cursor}"',
-                                       'name="#{cursor}"',
-                                       'action="#{cursor}"',
-
-                                       'xml:lang="#{cursor}"',
-                                       'lang="#{cursor}',
-                                       'xmlns="#{cursor}"'
+                                       'href="#{c}"',
+                                       'id="#{c}"',
+                                       'class="#{c}"',
+                                       'style="#{c}"',
+
+                                       'onload="#{c}"',
+                                       'onclick="#{c}"',
+                                       'onmouseover="#{c}"',
+                                       'onmouseout="#{c}"',
+                                       'ondblclick="#{c}"',
+                                       'onsubmit="#{c}"',
+                                       
+                                       'type="#{c}"',
+                                       'value="#{c}"',
+                                       'name="#{c}"',
+                                       'action="#{c}"',
+
+                                       'xml:lang="#{c}"',
+                                       'lang="#{c}',
+                                       'xmlns="#{c}"'
                                ];
                                
+                               // 属性値    
                                var values = [
                                        'UTF-8',
                                        'EUC-JP',
                                        'Shift-JIS',
+                                       'text',
+                                       'button',
+                                       'submit',
+                                       'reset',
                                        'ja',
                                        'http://www.w3.org/1999/xhtml'
                                ];
 
-//                             return tags.concat(header, attributes, values).sort();
-                               return tags.concat(header, attributes, values);
+                               return header.concat(body, attributes, values);
 
                        })(),
                        
-                       // JavaScript
                        js: (function() {
                        
                                var objs = [
-                                       'function() { #{cursor} }',
-                                       'if (#{cursor}) { }'
+                                       'function() { #{c} }',
+                                       'if (#{c}) { }'
                                ];
                                
                                var libs = [
-                                       'click(#{cursor});',
-                                       'html(#{cursor});'
+                                       'click(#{c});',
+                                       'html(#{c});'
                                ];
                                
                                return objs.concat(libs);               
                        
                        })(),
                        
-                       // CSS
                        css: (function() {
                        
                                var props = [
                                return props.concat(values);            
                        
                        })()
+                       
+                       
+               }, // keywords
+       
+       
+               /**
+                * キーワードを追加
+                * keywordsと同じ形式で渡す。
+                */
+               add: function(newwords) {
+       
+                       for(var type in newwords) {
+       
+                               if (this.keywords[type]) {
+                                       this.keywords[type].concat(newwords[type]);
+                               } else {
+                                       this.keywords[type] = newwords[type];
+                               }
+       
+                       }
+       
+               }, //add
+               
+               /**
+                * 補完候補を取得
+                */
+               get: function(t, s) {
+               
+                       if (!t || !s || !t.value) {
+                               return false;
+                       }
+                       
+                       var matches = {
+                               view: [],
+                               insert: []
+                       };
                
-               };              
+                       var words = [];
 
-               // 設定オブジェクト
-               conf = $.extend({
-                       tags: true, // タグボタン表示
-                       edit: true, // 編集機能表示
-                       jqui: true, // jQuery UI CSS Framework仕様
-                       vars: true // 入力内容を利用した補完
-               }, conf);
+                       // 入力内容で補完
+                       if (Wrapper.checkIntelli(t)) {
 
-               // 追加の入力候補
-               conf.keys = $.extend(conf.keys, keywords);
+                               var a = t.value.match(/[^<>\s '"#\=:;{}\(\)!?,*]+/g) || [];
 
-               // thisには$('textarea.tagget')が入ってくる
+                               // 重複削除
+                               var temp = [];                          
+                               for (var i = 0; i < a.length; i++) {
+                               
+                                       var v = a[i];
+                               
+                                       if (!(v in temp)) {
+                                               words.push(v);
+                                               temp[v] = true;
+                                       }
+                               
+                               }
+                               
+                       }
 
-               this.each(function() {
+                       for(var key in this.keywords) {
+                               if (Wrapper.checkType(t, key)) {
+                                       words = words.concat(this.keywords[key]);
+                               }
+                       }
 
-//                             var tmp = this.value;
-                               // 初期化中メッセージ
-//                             this.value = 'Initializing tagget...';
+                       for(var i = 0; i < words.length; i++) {
                        
-                               init(this, conf);
+                               if (words[i] != s && words[i].indexOf(s) == 0) {
+                                       matches.view.push(words[i].replace(/#\{c\}/g, ''));
+                                       // 既に入力されている部分を除いて挿入
+                                       matches.insert.push(words[i].slice(s.length));
+                               }
+                       }
                                
-                               // 初期化完了
-//                             this.value = tmp;
-                       });
-                       
-               // This is jQuery!!
-               return this;
+                       return (matches.view.length != 0) ? matches : false;
                
-       }; // $.fn.tagget
-
-       // contextに依存しない関数
+               }
        
-       // &, <, >(, ")を変換
-       var escapeHtml = function(s, quot) {
-               s = s.replace(/&/g, '&amp;')
-                       .replace(/</g, '&lt;')
-                       .replace(/>/g, '&gt;');
-               
-               return !quot ? s : s.replace(/"/g, '&quot;');
        };
+
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       /**
+        * taggetに必要なHTMLを生成・操作する
+        */
+       Wrapper = {
        
-       // &amp;, &lt;, &gt;(, &quot;)を戻す
-       var unescapeHtml = function(s, quot) {
-                                       
-               s = s.replace(/&amp;/g, '&')
-                       .replace(/&lt;/g, '<')
-                       .replace(/&gt;/g, '>');
+               // ツールバーを表示するか
+               isToolbar: true,
+       
+               // wrap処理
+               // textarea周囲にHTMLを追加する
+               wrap: function(t) {
                
-               return !quot ? s : s.replace(/&quot;/g, '"');
-       };
+                       if ($(t).parents().is('.tagget_wrapper')) {
+                               this.relate(t);
+                               return true;
+                       }
+                       
+                       // 全体の枠を作ってその参照を取得
+                       // wrapの戻り値はwrapされた要素なので、parentsでwrapした要素を取得
+                       var wrapper = $(t).wrap('<div class="tagget_wrapper"><p class="tagget_main"></p></div>')
+                               .parents('div.tagget_wrapper');
+
+                       // 必要に応じてツールバーを追加
+                       if (this.isToolbar) {
+                               var toolbar = 
+                                       wrapper.prepend(('<div class="tagget_toolbar"></div>')).children('div.tagget_toolbar');
+
+                               // 選択範囲変換
+                               toolbar.append(
+                                       $('<p class="tagget_encode"></p>').append(
+                                               $('<select title="選択範囲を変換"></select>')
+                                                       .append('<option selected="selected" value="">Encode Selection</option>')
+                                                       .append('<option value="entity">&amp &lt; &gt; → &amp;amp; &amp;lt; &amp;gt;</option>')
+                                                       .append('<option value="raw">&amp;amp; &amp;lt; &amp;gt; → &amp &lt; &gt;</option>')
+                                                       .append('<option value="enc">encodeURI()</option>')
+                                                       .append('<option value="encc">encodeURIComponent()</option>')
+                                                       .append('<option value="dec">decodeURI()</option>')
+                                                       .append('<option value="decc">decodeURIComponent()</option>')
+                                       )
+                               );
+
+                               // 置換
+                               toolbar.append(
+                                       $('<p class="tagget_replace"></p>')
+                                               .append('<input type="text" value="Before" title="置換前" />')
+                                               .append('<img src="img/v_arrow010102.gif" />')
+                                               .append('<input type="text" value="After" title="置換後" />')
+                                               .append('<input type="button" value="Replace All" title="全て置換" />')
+                               );
+
+                               // ファイルタイプ
+                               toolbar.append(
+                                       $('<p class="tagget_type"></p>').append(
+                                               $('<select title="選択範囲を変換"></select>')
+                                                       .append('<option selected="selected" value="html|css|js">HTML,CSS,JS</option>')
+                                                       .append('<option value="html|css">HTML,CSS</option>')
+                                                       .append('<option value="html">HTML</option>')
+                                                       .append('<option value="css">CSS</option>')
+                                                       .append('<option value="js">JS</option>')
+                                                       .append('<option value="css|js">CSS,JS</option>')
+                                                       .append('<option value="html|js">HTML,JS</option>')
+                                       )
+                               );
+
+                               // Cookie
+                               toolbar.append(
+                                       $('<p class="tagget_cookie"></p>')
+                                               .append('<input type="checkbox" checked="checked" title="Cookieに下書きを保存" id="tagget_check_cookie" /><label for="tagget_check_cookie" title="Cookieに下書きを保存">Cookie</label>')
+                               );
+
+                               // intelligent
+                               // 入力内容を使用した補完
+                               toolbar.append(
+                                       $('<p class="tagget_intelli"></p>')
+                                               .append('<input type="checkbox" checked="checked" title="入力内容から補完" id="tagget_check_intelli" /><label for="tagget_check_intelli" title="入力内容から補完">intelligent</label>')
+                               );
+
+                               // Status
+                               // Draft Saved At 1:17とか表示
+                               // 行、列を表示(line: , col:)
+                               wrapper.append(
+                                       $('<p class="tagget_status"><span class="tagget_time"></span><span class="tagget_line"></span>&nbsp;</p>')
+                               );
+
+                               // jQuery UI CSS Frameworkのclass名を設定  
+                               toolbar.addClass('ui-helper-clearfix');
+                               wrapper.addClass('ui-widget-header ui-corner-all')
+                                       .children('div, p').addClass('ui-widget-header');
 
-       // offsetを簡易算出
-       var getOffset = function(elm) {
+                       }
+               
+                       this.relate(t);
+               
+               },
 
-               var left, top;
 
-               if (elm.getBoundingClientRect) {
+               // wrap要素にeventを設定する。
+               // 既にHTMLがあった場合は、こちらだけを行う。
+               relate: function(t) {
+               
+                       var toolbar = $(t).parents('.tagget_wrapper').children('.tagget_toolbar');
 
-                       var rect = elm.getBoundingClientRect();
-                       left = Math.round(scrollX + rect.left);
-                       top = Math.round(scrollY + rect.top);
+                       // 選択範囲変換
+                       // onchangeイベント設定
+                       toolbar.find('.tagget_encode select').change(function() {
                        
-               } else {
-
-                       left = elm.offsetLeft;
-                       top  = elm.offsetTop;
-                       var offsetParent = elm.offsetParent;
+                               switch (this.value) {
+                               
+                                       case 'entity':
+                                               Cursor.encodeSelection(t, Util.escapeHtml);
+                                               break;
+                               
+                                       case 'raw':
+                                               Cursor.encodeSelection(t, Util.unescapeHtml);
+                                               break;                          
+                               
+                                       case 'enc':
+                                               Cursor.encodeSelection(t, encodeURI);
+                                               break;                          
+                               
+                                       case 'encc':
+                                               Cursor.encodeSelection(t, encodeURIComponent);
+                                               break;  
+                                                                       
+                                       case 'dec':
+                                               Cursor.encodeSelection(t, decodeURI);
+                                               break;                          
+                                                                       
+                                       case 'decc':
+                                               Cursor.encodeSelection(t, decodeURIComponent);
+                                               break;                          
+                               }
+                               
+                               this.value = '';
                        
-                       while (offsetParent) {
-                               left += offsetParent.offsetLeft;
-                               top  += offsetParent.offsetTop;             
-                               offsetParent = offsetParent.offsetParent;
-                       }
+                       });
                        
-               }
+                       
+                       // 置換ボタンクリックで置換
+                       // 正規表現使用可
+                       var inputs = toolbar.find('.tagget_replace input');
+                       inputs.filter('[type=button]').click(function() {
 
-               return {
-                       left: left,
-                       top: top
-               };
-               
-       };
+                               var val = t.value;
+                               
+                               var before = inputs.eq(0).val();
+                               var after = inputs.eq(1).val();
+                               var flag = 'g';
 
+                               if (before.match(/^\/.+\/([^\/]+)$/)) {
+                                       
+                                       flag = RegExp.$1;
+                                       before = before.replace(/^\/|\/[^\/]+?$/g, '');
+                               }
+                                                               
+                               if (before) {
+                                       t.value = val.replace(new RegExp(before, flag), after);
+                               }
+                               
+                       });     
 
-       // tagget初期化
-       var init = function(textarea, conf) {
-       
-               var t = $(textarea);
+               
+               },
 
-               // textarea or dummyを使う関数群
+               // textareaからID取得
+               getId: function(t) {
+                       return $(t).attr('class').match(/tagget_([0-9]+)/)[1];
+               },
                
-               // textareaのカーソル位置に文字列挿入
-               var insert = function(s) {
+               // Popup表示状態
+               isPopup: function(t) {
+                       var popup = this.getPopup(t);
+                       return popup.css('display') != 'none';
+               },
+
+               // Popup表示設定
+               setPopup: function(display) {
+                       var popup = this.getPopup(t);
+                       if (display) {
+                               popup.show();
+                       } else {
+                               popup.hide();
+                       }
+               },
+                               
+               checkType: function(t, type) {
+                       var currentType = $(t).parents('.tagget_wrapper')
+                               .find('.tagget_type select').val();
+                       return (new RegExp(currentType)).test(type);
+               },
+
+               checkIntelli: function(t) {
+                       var intelli = $(t).parents('.tagget_wrapper')
+                               .find('.tagget_intelli input').attr('checked');
+                       return intelli;
+               },
+               
+               // Popup,Dummyを生成
+               absolutes: function(t) {
+               
+                       var body = $(document.body);
+                       var id = this.getId(t);
+               
+                       // suggestion用の要素作成
+                       var popup = $('<ul class="tagget_popup tagget_popup' + id + '"></ul>');
+                       body.append(popup);
 
-                       // カーソル移動位置(#{cursor})を取得後、削除
-                       var cursor = s.indexOf('#{cursor}');
-                       s = s.replace('#{cursor}', '');
+                       // Firefox用dummy生成
+                       // カーソル座標取得に使用
+                       if (window.getComputedStyle) {
+                               
+                               var dummy = $('<pre class="tagget_dummy tagget_dummy' + id + '"></pre>');
+                               body.append(dummy);
 
-                       // focusしないとIEでbodyに挿入されたりする
-                       // Firefoxでもボタンで挿入後にfocusが戻らない
-                       textarea.focus(); 
+                       }
 
-                       // for IE
-                       if (document.selection) {
-                               
-                               // 選択範囲を取得
-                               var range = document.selection.createRange();
+               },
+               
+               // Dummyを取得
+               getDummy: function(t) {
 
-                               // 選択中のテキスト引数sで置き換え(現在のカーソル位置にsを挿入)
-                               range.text = s;
+                       var id = this.getId(t);
+                       var dummy = $('.tagget_dummy' + id);
+                       return dummy;
+                       
+               },
+               
+               // textareaのstyleをdummyにコピー
+               adjust: function(t) {
 
-                               // カーソルがrange.textの最後になるので戻す
-                               // #{cursor}指定がなければ最後のまま
-                               var back = s.length - (cursor != -1 ? cursor : s.length);
-                               range.move('character', -back);
+                       var dummy = this.getDummy(t);
 
-                               // 現在のカーソル位置を反映する(これやらないと水の泡)
-                               range.select();
-                       }
+                       if (window.getComputedStyle) {
+                               
+                               var org = getComputedStyle(t,'');
 
-                       // Firefox
-                       // inかundefinedあたりで判定しないとselectionStartが0の時ミスる
-                   else if ('selectionStart' in textarea) { 
+                               var props = [
+                                       'width', 'height',
+                                       'padding-left', 'padding-right', 'padding-top', 'padding-bottom', 
+                                       'border-left-style', 'border-right-style','border-top-style','border-bottom-style', 
+                                       'border-left-width', 'border-right-width','border-top-width','border-bottom-width', 
+                                       'font-family', 'font-size', 'line-height', 'letter-spacing', 'word-spacing'
+                               ];
+                       
+                           for(var i = 0; i < props.length; i++){
+                           
+                               var capitalized = props[i].replace(/-(.)/g, function(m, m1){
+                                               return m1.toUpperCase();
+                                       });
+                           
+                               dummy.css(capitalized, org.getPropertyValue(props[i]));
 
-                               // スクロールバーの位置を保存
-                               var top = textarea.scrollTop;
+                               }
 
-                               // 選択範囲の開始・終了位置を取得
-                       var start = textarea.selectionStart;
-                       var end = textarea.selectionEnd;
+                               var offset = Util.getOffset(t);
 
-                               // 開始位置と終了位置の間(現在のカーソル位置)にsを挿入
-                       textarea.value = textarea.value.slice(0, start) + s + textarea.value.slice(end);
+                           dummy.css({
+                               left: offset.left,
+                               top: offset.top
+                           });
+                           
+                           var $t = $(t);
+                           dummy.width($t.width())
+                               .height($t.height())
+                                       .scrollLeft($t.scrollLeft())
+                               .scrollTop($t.scrollTop());
 
-                               // カーソル移動位置に移動させる
-                               var index = start + (cursor != -1 ? cursor : s.length);
-                       textarea.setSelectionRange(index, index);
+                       }
+               },
+               
+               // suggestionを表示
+               showPopup: function(t) {
+       
+                       var popup = this.getPopup(t);
+                       var suggests = Suggester.get(t, Cursor.getText(t)[0]);
+                       
+                       if (suggests) {
+                       
+                               // 変化があった場合のみ再構成
+                               if (popup.text() != suggests.view.join('')) {
+                       
+                                       popup.html('');
+                       
+                                       for(var i = 0; i < suggests.view.length; i++) {
+                                               var li = $('<li></li>').attr('title', suggests.insert[i])
+                                                       .append('<a href="#"></a>').text(suggests.view[i])
+                                                       .hover(function() {
+                                                               popup.children('li').removeClass('tagget_current');
+                                                               $(this).addClass('tagget_current');
+                                                       })
+                                                       .click(function() {
+                                                               Cursor.insert(t, Util.unescapeHtml(popup.children('li.tagget_current').attr('title')));
+                                                               popup.hide();
+                                                       });
+                                               if (i == 0) {
+                                                       li.addClass('tagget_current');
+                                               }
+                                               popup.append(li);
+                                       }       
 
-                               // 改行がたくさんある場合スクロールバーを下にずらす
-                               if (/\n/g.test(s) && s.match(/\n/g).length > 2) {
-                                       top += parseInt(getComputedStyle(textarea, '').getPropertyValue('line-height'), 10);
+                               // 変化がなければ属性のみ変更
+                               // TODO:入力分の削除をincertに現在の文字渡してやるようにした方がよさそう。
+                               } else {
+
+                                       popup.children('li').each(function(i) {
+                                               $(this).attr('title', suggests.insert[i]);
+                                       });                     
                                }
                                
-                               // スクロールバーを戻す
-                           textarea.scrollTop = top;
-                   }
+                               var pos = Cursor.getPos(t);
+                               
+                               popup.css({
+                                       left: pos.x,
+                                       top: pos.y
+                               });
 
-                       return this;
+                               popup.show();
+                               
+                       } else {
                        
-               };
-
-               // 補完候補があるか?
-               var check = function() {
+                               popup.hide();
+                               
+                       }       
+               },
                
-                       var matches = {
-                               view: [],
-                               insert: []
-                       };
+               // popupを取得
+               getPopup: function(t) {
+                       var id = this.getId(t);
+                       var popup = $('.tagget_popup' + id);
+                       return popup;
+               },
                
-                       var text = getText()[0];
-
-                       if (text) { 
+               
+               // suggest選択
+               // TODO:もっとちゃんと書く
+               choice: function(t, d) {
 
-                               var words = [];
+                       var popup = this.getPopup(t);
+                       var lis = popup.children('li');
+               
+                       if(d == 1) {
                                
-                               if (conf.vars) {
-                                       var a = textarea.value.match(/[^<>\s '"#\=:;{}\(\)!?,*]+/g) || [];
-
-                                       // 重複削除
-                                       var temp = [];                          
-                                       for (var i = 0; i < a.length; i++) {
-                                       
-                                               var v = a[i];
-                                       
-                                               if (!(v in temp)) {
-                                                       words.push(v);
-                                                       temp[v] = true;
-                                               }
-                                       
-                                       }
-
+                               for(var i = 0; i < lis.length; i++) {
+                               
+                                       var li = lis.eq(i);
                                        
-                               }
-
-                               for(var key in conf.keys) {
-                                       if (t.hasClass('tagget_' + key) || t.hasClass(key)) {
-                                               words = words.concat(conf.keys[key]);
+                                       if(li.hasClass('tagget_current')) {
+                                               li.removeClass('tagget_current');
+                                               i = (i == 0) ? lis.length - 1 : i - 1;
+                                               lis.eq(i).addClass('tagget_current');
+                                               break;
                                        }
+                               
                                }
-
-// sortは重い原因になるので止め
-//                             words = words.sort();
-                       
-                               for(var i = 0; i < words.length; i++) {
+                       } else {
                                
-                                       if (words[i] != text && words[i].indexOf(text) == 0) {
-                                               matches.view.push(words[i].replace(/#\{cursor\}/g, ''));
-                                               matches.insert.push(escapeHtml(words[i].slice(text.length)));
+                               for(var i = 0; i < lis.length; i++) {
+                               
+                                       var li = lis.eq(i);
+                                       
+                                       if(li.hasClass('tagget_current')) {
+                                               li.removeClass('tagget_current');
+                                               i = (i == lis.length - 1 ) ? 0 : i + 1;
+                                               lis.eq(i).addClass('tagget_current');
+                                               break;
                                        }
-                               }
                                
+                               }                       
+                       
                        }
-               
-                       return (matches.view.length != 0) ? matches : false;
 
+               },
                
-               };
-
-               // カーソル位置の文字を取得
-               var getText = function(r, after) {
-
-//                     if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\n=]*?$/;
-                       if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\s '"#\.=;]*?$/;
+               // 選択中のsuggestionを取得
+               current: function(t) {
+               
+                       var popup = this.getPopup(t);
+                       return popup.children('li.tagget_current').attr('title');
+               },
+               
+               getStatus: function(t) {
+                       return $(t).parents('.tagget_wrapper').find('.tagget_status');
+               },
+               
+               setLine: function(t) {
+               
+                       var status = this.getStatus(t);
+                       
+                       //カーソル前の全文字列
+                       var s = Cursor.getText(t, /^[\s\S]*$/)[0] || '';
+                       
+                       //現在の行の行頭までの文字列
+                       //.は改行にはマッチしないので楽
+                       var thisLine = Cursor.getText(t, /.*$/)[0];
+                       
+                       var lineNum = (s.match(/\n/g) || []).length + 1;
+                       var cols = thisLine.length;
 
+                       status.children('.tagget_line').html('line: ' + lineNum + ' col: ' + cols);
+               
+               }
+               
+       
+       };
+
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       /**
+        * textarea内のテキストを操作
+        */
+       Cursor = {
+
+               // カーソル位置の文字を取得
+               getText: function(t, r, after) {
+
+//                     if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\n=]*?$/;
+                       if (!r) {
+                               r = /[^<>\s '"#\.=;]+?$|<[^<>\s '"#\.=;]*?$/;
+                       }
+                       
                        var start, end;
 
                        // IE
                        if (document.selection) {
 
-//                             textarea.focus(); // focusなしでもいける
+//                             t.focus(); // focusなしでもいける
 
                                // 選択範囲を取得
                                var range = document.selection.createRange();
 
                                // textarea内のテキスト全体を選択
                                // [clone start] text1 [range start] text2 [range end] text3 [clone end]
-                               clone.moveToElementText(textarea);
+                               clone.moveToElementText(t);
 
                                // cloneの選択範囲終点を、rangeの終点にあわせる
                                // [clone start] text1 [range start] text2 [range/clone end] text3
                        }
 
                        // Firefox
-                   else if ('selectionStart' in textarea) {
+                   else if ('selectionStart' in t) {
 
-                       start = textarea.selectionStart;
-                       end = textarea.selectionEnd;
+                       start = t.selectionStart;
+                       end = t.selectionEnd;
 
                    }
 
                        var text;
                        
                        if (!after) {
-                               text = textarea.value.slice(0, start).match(r);
+                               text = t.value.slice(0, start).match(r);
                        } else {
-                               text = textarea.value.slice(end).match(r);
+                               text = t.value.slice(end).match(r);
                        }
                        
                        return text || [];
                        
-               };
+               },
 
                // カーソルの座標を取得
-               var getPos = function() {
+               getPos: function(t) {
 
                        var x, y;
 
                        if (document.selection) {
-
+                       
                                var range = document.selection.createRange();
                                x = range.offsetLeft + 
                                        (document.body.scrollLeft || document.documentElement.scrollLeft) - 
                        
                        } else if (window.getComputedStyle) {
 
+                               var id = Wrapper.getId(t);
+                               var dummy = $('.tagget_dummy' + id);
+
                                var span = dummy.children('span');
                                
                                if(!span.is('span')) {
                                        span = $('<span></span>');
                                        span.html('|');
                                }
-                       
+               
                                dummy.html('');
-                               dummy.text(textarea.value.slice(0, textarea.selectionEnd));
+                               dummy.text(t.value.slice(0, t.selectionEnd));
                                dummy.append(span);
 
-                               var offset = getOffset(span.get(0));
+                               var offset = Util.getOffset(span.get(0));
 
-                               x = offset.left - textarea.scrollLeft;
-                               y = offset.top - textarea.scrollTop;
+                               x = offset.left - t.scrollLeft;
+                               y = offset.top - t.scrollTop;
                    }
 
                        return {
                                x: x,
                                y: y
                        };
-               };
-
-               // wrap処理
-               // textarea周囲にHTMLを追加する
+               },
                
-               // 全体の枠を作ってその参照を取得
-               // wrapの場合、普通にやると参照を取得できないのでparentsで取得
-               var wrapper = t.wrap('<div class="tagget_wrapper"><p class="tagget_main"></p></div>')
-                       .parents('div.tagget_wrapper');
-
-               // 必要に応じてツールバーを追加
-               var menu = (conf.tags || conf.edit) ?
-                       wrapper.prepend(('<div class="tagget_menu"></div>')).children('div.tagget_menu') : null;
-       
-               // タグ挿入ボタン
-               if (conf.tags) {
-                       var tags = $('<ul class="tagget_tags"></ul>')
-                               .append('<li><a href="#">a</a></li>')
-                               .append('<li><a href="#">p</a></li>')
-                               .append('<li><a href="#">ul</a></li>')
-                               .append('<li><a href="#">li</a></li>')
-                               .append('<li><a href="#">div</a></li>')
-                               .append('<li><a href="#">span</a></li>')
-                               .append('<li><a href="#">pre</a></li>')
-                               .append('<li><a href="#">code</a></li>')
-                               .append('<li><a href="#">blockquote</a></li>')
-                               .append('<li><a href="#">dl</a></li>')
-                               .append('<li><a href="#">dt</a></li>')
-                               .append('<li><a href="#">dd</a></li>')
-                               .append('<li><a href="#">link</a></li>')
-                               .append('<li><a href="#">script</a></li>')
-                               .append('<li><a href="#">frameset</a></li>')
-                               .append('<li><a href="#">frame</a></li>');
-                       menu.append(tags);
-               }       
-
-               // 編集機能
-               if (conf.edit) {
-                       var edit = $('<div class="tagget_edit"></div>');
-                       
-                       // 選択範囲変換
-                       edit.append(
-                               $('<p class="tagget_encode"></p>').append(
-                                       $('<select></selected>')
-                                               .append('<option selected="selected" value="">選択範囲を変換</option>')
-                                               .append('<option value="entity">&amp &lt; &gt; → &amp;amp; &amp;lt; &amp;gt;</option>')
-                                               .append('<option value="raw">&amp;amp; &amp;lt; &amp;gt; → &amp &lt; &gt;</option>')
-                                               .append('<option value="enc">encodeURI()</option>')
-                                               .append('<option value="encc">encodeURIComponent()</option>')
-                                               .append('<option value="dec">decodeURI()</option>')
-                                               .append('<option value="decc">decodeURIComponent()</option>')
-                               )
-                       );
-
-                       // 置換
-                       edit.append(
-                               $('<p class="tagget_replace"></p>')
-                                       .append('<input type="text" value="置換前" />')
-                                       .append(' → ')
-                                       .append('<input type="text" value="置換後" />')
-                                       .append('<input type="button" value="置換" />')
-                       );
-
-                       menu.append(edit);
-               }
-
-               // jQuery UI CSS Frameworkのclass名を設定  
-               if (conf.jqui) {
-                       menu.addClass('ui-helper-clearfix');
-                       wrapper.addClass('ui-widget-header ui-corner-all')
-                               .children('div, p').addClass('ui-widget-header')
-                                       .find('li').addClass('ui-state-default ui-corner-all').hover(function(){ 
-                                               $(this).addClass('ui-state-hover'); 
-                                       }, function(){ 
-                                               $(this).removeClass('ui-state-hover'); 
-                                       });     
-               }       
-
-               var body = $(document.body);
+               // textareaのカーソル位置に文字列挿入
+               insert: function(t, s) {
 
-               // suggestion用の要素作成
-               var suggest = $('<ul class="tagget_suggest"></ul>');
-               body.append(suggest);
+                       // カーソル移動位置(#{c})を取得後、削除
+                       var cursor = s.indexOf('#{c}');
+                       s = s.replace('#{c}', '');
 
-               // Firefox用dummy生成
-               // カーソル座標取得に使用
-               if (window.getComputedStyle) {
-                       
-                       var dummy = $('<pre class="tagget_dummy"></pre>');
+                       // focusしないとIEでbodyに挿入されたりする
+                       // Firefoxでもボタンで挿入後にfocusが戻らない
+                       t.focus(); 
 
-                       // textareaのstyleをdummyにコピー
-                       var onResize = function() {
+                       // for IE
+                       if (document.selection) {
                                
-                               var org = getComputedStyle(textarea,'');
-
-                               var props = [
-                                       'width', 'height',
-                                       'padding-left', 'padding-right', 'padding-top', 'padding-bottom', 
-                                       'border-left-style', 'border-right-style','border-top-style','border-bottom-style', 
-                                       'border-left-width', 'border-right-width','border-top-width','border-bottom-width', 
-                                       'font-family', 'font-size', 'line-height', 'letter-spacing', 'word-spacing'
-                               ];
-                       
-                           for(var i = 0; i < props.length; i++){
-                           
-                               var capitalized = props[i].replace(/-(.)/g, function(m, m1){
-                                               return m1.toUpperCase();
-                                       });
-                           
-                               dummy.css(capitalized, org.getPropertyValue(props[i]));
+                               // 選択範囲を取得
+                               var range = document.selection.createRange();
 
-                               }
+                               // 選択中のテキスト引数sで置き換え(現在のカーソル位置にsを挿入)
+                               range.text = s;
 
-                               var offset = getOffset(textarea);
+                               // カーソルがrange.textの最後になるので戻す
+                               // #{c}指定がなければ最後のまま
+                               var back = s.length - (cursor != -1 ? cursor : s.length);
+                               range.move('character', -back);
 
-                           dummy.css({
-                               left: offset.left,
-                               top: offset.top
-                           });
-                           
-                           dummy.width(t.width())
-                               .height(t.height())
-                                       .scrollLeft(t.scrollLeft())
-                               .scrollTop(t.scrollTop());
+                               // 現在のカーソル位置を反映する(これやらないと水の泡)
+                               range.select();
+                       }
 
-                       };
-                       
-                       // resize時にはtextareaのサイズも変わるので
-                       $(window).resize(function() {
-                               onResize();
-                       }).resize();
+                       // Firefox
+                       // inかundefinedあたりで判定しないとselectionStartが0の時ミスる
+                   else if ('selectionStart' in t) { 
 
-                       body.append(dummy);
+                               // スクロールバーの位置を保存
+                               var top = t.scrollTop;
 
-               }
-               
-               
-               // イベント設定
+                               // 選択範囲の開始・終了位置を取得
+                       var start = t.selectionStart;
+                       var end = t.selectionEnd;
 
-               // タグ挿入ボタン
-               if (conf.tags) {
+                               // 開始位置と終了位置の間(現在のカーソル位置)にsを挿入
+                       t.value = t.value.slice(0, start) + s + t.value.slice(end);
 
-                       // clickイベント設定
-                       var onClick = function(elm, s) {
+                               // カーソル移動位置に移動させる
+                               var index = start + (cursor != -1 ? cursor : s.length);
+                       t.setSelectionRange(index, index);
 
-                               $(elm).click(function() {
-                                       insert(s);
-                                       return false;
-                               });
+                               // 改行がたくさんある場合スクロールバーを下にずらす
+                               if (/\n/g.test(s) && s.match(/\n/g).length > 2) {
+                                       top += parseInt(getComputedStyle(t, '').getPropertyValue('line-height'), 10);
+                               }
                                
-                       };
+                               // スクロールバーを戻す
+                           t.scrollTop = top;
+                   }
 
-                       // タグボタンクリックで挿入
-                       tags.find('a').each(function() {
+                       return this;
+                       
+               },
+       
+               /**
+                * 渡された関数で選択範囲を変換
+                */
+               // TODO:変換後も選択した状態に
+               encodeSelection: function(t, func) {
+               
+                       t.focus(); 
 
-                               // htmlで判別してイベント設定
-                               // title属性に設定するほうがいいか?
-                               switch (this.innerHTML) {
-                               
-                                       case 'a':
-                                               onClick(this, '<a href="#{cursor}"></a>');
-                                               break;
-                               
-                                       case 'p':
-                                               onClick(this, '<p>#{cursor}</p>\n');
-                                               break;
-                               
-                                       case 'ul':
-                                               onClick(this, '<ul>\n<li>#{cursor}</li>\n</ul>\n');
-                                               break;
+                       // IE
+                       if (document.selection) {
+                       
+                               var range = document.selection.createRange();
+                               range.text = func(range.text);
+                               range.select();
                                
-                                       case 'li':
-                                               onClick(this, '<li>#{cursor}</li>\n');
-                                               break;
+                       } else if ('selectionStart' in t) { 
 
-                                       case 'dl':
-                                               onClick(this, '<dl>\n<dt>#{cursor}</dt>\n<dd></dd>\n</dl>\n');
-                                               break;
+                               var top = t.scrollTop;
 
-                                       case 'dt':
-                                               onClick(this, '<dt>#{cursor}</dt>\n');
-                                               break;
+                       var start = t.selectionStart;
+                       var end = t.selectionEnd;
 
-                                       case 'dd':
-                                               onClick(this, '<dd>#{cursor}</dd>\n');
-                                               break;
-
-                                       case 'pre':
-                                               onClick(this, '<pre>#{cursor}</pre>\n');
-                                               break;
-
-                                       case 'code':
-                                               onClick(this, '<code>#{cursor}</code>');
-                                               break;
+                               var before = t.value.slice(start, end);
+                               var after = func(before);
+                               
 
-                                       case 'blockquote':
-                                               onClick(this, '<blockquote>#{cursor}</blockquote>\n');
-                                               break;
+                               t.value = t.value.slice(0, start) + 
+                                       after + t.value.slice(end);
 
-                                       case 'div':
-                                               onClick(this, '<div>#{cursor}</div>\n');
-                                               break;
-
-                                       case 'span':
-                                               onClick(this, '<span>#{cursor}</span>\n');
-                                               break;
+                       t.setSelectionRange(end, end);
 
-                                       case 'link':
-                                               onClick(this, '<link rel="stylesheet" type="text/css" href="#{cursor}" />\n');
-                                               break;
+                           t.scrollTop = top;
+                   }
+                   
+               },
+               
+               /**
+                * 閉じタグ補完
+                */
+                //TODO: ロジック見直し
+                closeTag: function(t) {
+                
+                                               //                  <<-tag name-><----------attr-------------><->->
+                               var sTag = Cursor.getText(t, /<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g);
+                               var sTag2 = Cursor.getText(t, /<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g, true);
+                               var eTag = Cursor.getText(t, /<\s*\/\s*.+?\s*>/g);
+                               var eTag2 = Cursor.getText(t, /<\s*\/\s*.+?\s*>/g, true);
+/*
+                               console.log('s:'+sTag);
+                               console.log('s2:'+sTag2);
+                               console.log('e:'+eTag);
+                               console.log('e2:'+eTag2);
+*/
+                               // 整形式じゃなさそうなのはとりあえず無視
+                               if (sTag.length + sTag2.length > eTag.length + eTag2.length) {
+                               
+                                       for (var i = sTag.length - 1; i >= 0; i--) {
+                                       
+                                               var s = sTag[i].replace(/^.*<|[\s>].*/g, '');
+                                               if (!eTag || eTag.length == 0 || s != eTag.shift().replace(/^.*\/|>.*/g, '')) {
+                                                       Cursor.insert(t, '</' + s + '>');
+                                                       break;
+                                               }
+                                       }
 
-                                       case 'script':
-                                               onClick(this, '<script type="text/javascript" src="#{cursor}"></script>\n');
-                                               break;
-                                               
-                                       case 'frameset':
-                                               onClick(this, '<frameset>\n<frame src="#{cursor}" />\n</frameset>\n');
-                                               break;
-                                               
-                                       case 'frame':
-                                               onClick(this, '<frame src="#{cursor}" />\n');
-                                               break;
-                                               
                                }
-                       
-                       });
 
                }
+       
+       };
 
-               // 編集機能設定   
-               if (conf.edit) {
+
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       /*
+        * contextに依存しない関数群
+        */
+       Util = {
+       
+               // &, <, >[, "]を変換
+               escapeHtml: function(s, quot) {
+                       s = s.replace(/&/g, '&amp;')
+                               .replace(/</g, '&lt;')
+                               .replace(/>/g, '&gt;');
+                       
+                       return !quot ? s : s.replace(/"/g, '&quot;');
+               },
                
-                       // 選択範囲変換
+               // &amp;, &lt;, &gt;[, &quot;]を戻す
+               unescapeHtml:function(s, quot) {
+                                               
+                       s = s.replace(/&amp;/g, '&')
+                               .replace(/&lt;/g, '<')
+                               .replace(/&gt;/g, '>');
                        
-                       // 渡された関数で選択範囲を変換
-                       var onChange = function(func) {
-
-                               textarea.focus(); 
-
-                               if (document.selection) {
-                               
-                                       var range = document.selection.createRange();
-                                       range.text = func(range.text);
-                                       range.select();
-                                       
-                               } else if ('selectionStart' in textarea) { 
-
-                                       var top = textarea.scrollTop;
+                       return !quot ? s : s.replace(/&quot;/g, '"');
+               },
 
-                               var start = textarea.selectionStart;
-                               var end = textarea.selectionEnd;
+               // offset(要素の実xy座標)を簡易算出
+               getOffset: function(elm) {
 
-                                       textarea.value = textarea.value.slice(0, start) + 
-                                               func(textarea.value.slice(start, end)) + 
-                                                       textarea.value.slice(end);
+                       var left, top;
 
-                               textarea.setSelectionRange(end, end);
+                       if (elm.getBoundingClientRect) {
 
-                                   textarea.scrollTop = top;
-                           }
-                       };
-               
-                       // onchangeイベント設定
-                       edit.find('.tagget_encode select').change(function() {
-                       
-                               switch (this.value) {
-                               
-                                       case 'entity':
-                                               onChange(escapeHtml);
-                                               break;
-                               
-                                       case 'raw':
-                                               onChange(unescapeHtml);
-                                               break;                          
+                               var rect = elm.getBoundingClientRect();
+                               left = Math.round(scrollX + rect.left);
+                               top = Math.round(scrollY + rect.top);
                                
-                                       case 'enc':
-                                               onChange(encodeURI);
-                                               break;                          
+                       } else {
+
+                               left = elm.offsetLeft;
+                               top  = elm.offsetTop;
+                               var offsetParent = elm.offsetParent;
                                
-                                       case 'encc':
-                                               onChange(encodeURIComponent);
-                                               break;  
-                                                                       
-                                       case 'dec':
-                                               onChange(decodeURI);
-                                               break;                          
-                                                                       
-                                       case 'decc':
-                                               onChange(decodeURIComponent);
-                                               break;                          
+                               while (offsetParent) {
+                                       left += offsetParent.offsetLeft;
+                                       top  += offsetParent.offsetTop;             
+                                       offsetParent = offsetParent.offsetParent;
                                }
                                
-                               this.value = '';
-                       
-                       });
-                       
+                       }
+
+                       return {
+                               left: left,
+                               top: top
+                       };
                        
-                       // 置換ボタンクリックで置換
-                       // 正規表現使用可
-                       var inputs = edit.find('.tagget_replace input');
-                       inputs.filter('[type=button]').click(function() {
+               }
+       };
 
-                               var val = textarea.value;
-                               
-                               var before = inputs.eq(0).val();
-                               var after = inputs.eq(1).val();
-                               var flag = '';
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       /**
+        * Cookieに下書き保存
+        */
+       var Cookie = {
+       
+               load: function(key) {
+               
+                       var cookie = $.cookie(key);
+                       if (cookie) {
+                               return cookie;
+                       }
+               
+               },
+               
+               zip: function(s) {
 
-                               if (before.match(/^\/.+\/([^\/]+)$/)) {
-                                       
-                                       flag = RegExp.$1;
-                                       before = before.replace(/^\/|\/[^\/]+?$/g, '');
-                               }
-                                                               
-                               if (before) {
-                                       textarea.value = val.replace(new RegExp(before, flag), after);
-                               }
-                               
-                       });     
-                               
-               }
+               var data = utf16to8(s);
+               data = zip_deflate(data);
+               data = base64encode(data);
+                       return data;
+               
+               },
+
+               unzip: function(s) {
 
-               // メイン機能
-               // キー入力時のsuggestion設定
+                   var data = base64decode(s);
+                   data = zip_inflate(data);
+                   data = utf8to16(data);
+                       return data;
                
-               // keyup(発生タイミングが一番少ない)で候補表示
-               t.keyup(function(e) {
+               },
                
-                       var suggests = check();
-                       
-                       if (suggests) {
-                       
-                               if (suggest.text() != suggests.view.join('')) {
-                       
-                                       suggest.html('');
-                       
-                                       for(var i = 0; i < suggests.view.length; i++) {
-                                               var li = $('<li></li>').attr('title', suggests.insert[i])
-                                                       .append('<a href="#"></a>').text(suggests.view[i])
-                                                       .hover(function() {
-                                                               suggest.children('li').removeClass('tagget_current');
-                                                               $(this).addClass('tagget_current');
-                                                       })
-                                                       .click(function() {
-                                                               insert(unescapeHtml(suggest.children('li.tagget_current').attr('title')));
-                                                               suggest.hide();
-                                                       });
-                                               if (i == 0) li.addClass('tagget_current');
-                                               suggest.append(li);
-                                       }       
+               save: function(key, val) {
 
-                               } else {
+                       var size = val.length;
+               
+                       if(size <= 4000) {
+                               $.cookie(key, val, { 'expires': 1 });
+                       }
+               
+               }
+       
+       
+       };
 
-                                       suggest.children('li').each(function(i) {
-                                               $(this).attr('title', suggests.insert[i]);
-                                       });                     
-                               }
-                               
-                               var pos = getPos();
-                               
-                               suggest.css({
-                                       left: pos.x,
-                                       top: pos.y
-                               });
 
-                               suggest.show();
-                               
-                       } else {
-                               suggest.hide();
-                       }       
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       // tagget初期化
+       var init = function(t, i) {
+       
+               // 被らないコードを振る
+               while ($('textarea').is('.tagget_' + i)) {
+                       i = '0' + i;
+               }
+               $(t).addClass('tagget_' + i);
 
-                       if (conf.bind) $(conf.bind).html(textarea.value);
-               })
+               Wrapper.wrap(t);
+               Wrapper.absolutes(t);
+
+//             setTimeout(function() {
+//             
+//             });
+
+               // イベント設定
+
+               // keyup(発生タイミングが一番少ない)で候補表示
+               $(t).keyup(function(e) {
                
-               // click時にも候補表示
-               .click(function() {
-                       $(this).keyup();
+                       if(!(37 <= e.which && e.which <= 40)) {
+                               Wrapper.showPopup(t);
+                       } else if(e.which == 37 || e.which == 39) {
+                               Wrapper.getPopup(t).hide();
+                       }
+                       
+                       Wrapper.setLine(t);
                })
-               
+
+               // 入力キーに応じた処理               
                .keydown(function(e) {
                
                        // タブキャンセル
                        // keydown以外だとうまくいかない
                        if (e.which == 9) {
-                               insert('\t');
+                               Cursor.insert(t, '\t');
                                return false;
                        }
 
-                       // Shift+Enterで改行 or br入力
+                       // Shift+Enterで改行 or <br />入力
                        // 補完却下も可能
                        if (e.shiftKey && e.which == 13) {
 
-                               var n = '\n'
-                               
-                               if ((t.hasClass('html') || t.hasClass('tagget_html')) && suggest.css('display') == 'none') {
-                                       n = '<br />\n';
+                               var n = '\n';
+                               if (!Wrapper.isPopup(t) && Wrapper.checkType(t, 'html')) {
+                                       n = '<br />';
                                }
-                               
-                               insert(n);
+                               Cursor.insert(t, n);
                                
                                return false;
                        }
                        // Ctrl+Enterで閉じタグ補完
                        if (e.ctrlKey && e.which == 13) {
 
-                               //                  <<-tag name-><----------attr-------------><->->
-                               var sTag = getText(/<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g);
-                               var sTag2 = getText(/<\s*[^\/!?=]+?(?:\s*[^=>\s]+?\s*=\s*["']?.*?["']?\s*)*\s*>/g, true);
-                               var eTag = getText(/<\s*\/\s*.+?\s*>/g);
-                               var eTag2 = getText(/<\s*\/\s*.+?\s*>/g, true);
-/*
-                               console.log('s:'+sTag);
-                               console.log('s2:'+sTag2);
-                               console.log('e:'+eTag);
-                               console.log('e2:'+eTag2);
-*/
-                               // 整形式じゃなさそうなのはとりあえず無視
-                               if (sTag.length + sTag2.length > eTag.length + eTag2.length) {
-                               
-                                       for (var i = sTag.length - 1; i >= 0; i--) {
-                                       
-                                               var s = sTag[i].replace(/^.*<|[\s>].*/g, '');
-                                               if (!eTag || eTag.length == 0 || s != eTag.shift().replace(/^.*\/|>.*/g, '')) {
-                                                       insert('</' + s + '>');
-                                                       break;
-                                               }
-                                       }
-
-                               }
-
-                               
-                               // insert(n);
-                               
+                               Cursor.closeTag(t);
+                       
                                return false;
                        }
 
                        // 十字キーで候補選択
                        // upだとおしっぱにできない、pressだとおかしくなる
-                       if (suggest.css('display') != 'none') {
+                       if (Wrapper.isPopup(t)) {
 
 
                                switch (e.which) {
                                
+                                       // right
+//                                     case 37:
+
                                        // up
                                        case 38:
-                                               var lis = suggest.children('li');
-                                               
-                                               for(var i = 0; i < lis.length; i++) {
-                                               
-                                                       var li = lis.eq(i);
-                                                       
-                                                       if(li.hasClass('tagget_current')) {
-                                                               li.removeClass('tagget_current');
-                                                               i = (i == 0) ? lis.length - 1 : i - 1;
-                                                               lis.eq(i).addClass('tagget_current');
-                                                               break;
-                                                       }
-                                               
-                                               }
+
+                                               Wrapper.choice(t, 1);
                                                return false;
 
+                                       // left
+//                                     case 39:
+
                                        // down
                                        case 40:
-                                               var lis = suggest.children('li');
-                                               
-                                               for(var i = 0; i < lis.length; i++) {
-                                               
-                                                       var li = lis.eq(i);
-                                                       
-                                                       if(li.hasClass('tagget_current')) {
-                                                               li.removeClass('tagget_current');
-                                                               i = (i == lis.length - 1 ) ? 0 : i + 1;
-                                                               lis.eq(i).addClass('tagget_current');
-                                                               break;
-                                                       }
-                                               
-                                               }                       
+
+                                               Wrapper.choice(t, -1);
                                                return false;
 
                                        // Enterで補完
                                        case 13:
-                                               insert(unescapeHtml(suggest.children('li.tagget_current').attr('title')));
-                                               suggest.hide();
+                                               Cursor.insert(t, Util.unescapeHtml(Wrapper.current(t)));
+                                               Wrapper.getPopup(t).hide();
 
                                                return false;
                                }
 
                        } else {
-                       
+               
+                               // インデントを合わせる       
                                if (e.which == 13) {
-                       
-                                       var indent = getText(/^[\t ]*/mg);
-                                       insert('\n' + (indent ? indent[indent.length - 1] : ''));
 
+                                       var indent = Cursor.getText(t, /^[\t ]*/mg);
+                                       Cursor.insert(t, '\n' + (indent ? indent[indent.length - 1] : ''));
+
+                                       var $t = $(t);
                                        if (window.getComputedStyle) {
-                                               t.scrollTop(t.scrollTop() + parseInt(getComputedStyle(textarea, '').getPropertyValue('line-height'), 10));
+                                               $t.scrollTop($t.scrollTop() + parseInt(getComputedStyle(t, '').getPropertyValue('line-height'), 10));
                                        }
                                        
                                        return false;
                        }
 
                });
+
+               // 最初に1回だけ呼び出し。
+               t.value = Cookie.unzip(Cookie.load(Wrapper.getId(t)));
+               Wrapper.setLine(t);
        
        }; // init
+       
+
+    /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+       /**
+        * jqueryにプラグイン追加
+        * $('textarea.tagget').tagget()とかで呼び出し
+        */
+       $.fn.tagget = function(conf) {
+
+
+               // 設定オブジェクト
+               conf = $.extend({
+                       toolbar: true, // ツールバー表示
+                       cookie: true  // クッキーに保存
+               }, conf);
+
+               // thisには$('textarea.tagget')が入ってくる
+               this.each(function(i) {
+
+                               // textareaを初期化
+                               init(this, i);
+
+               });
+
+               // window全体のイベント設定
+               
+               var self = this;                
+               // リサイズ時にDummyを調整
+               $(window).resize(function() {
+
+                       self.each(function() {
+                               Wrapper.adjust(this);
+                       });
+
+               }).resize();
+               
+               // Cookie保存設定
+               var interval = 60000; // 60秒:1分
+               setTimeout(function timer() {
+
+                       self.each(function() {
+
+                               Cookie.save(Wrapper.getId(this), Cookie.zip(this.value));
+
+                               var fillZero = function(s) {
+                                   return ('0' + s).slice(-2); 
+                               };
+                               var status = Wrapper.getStatus(this);   
+                               var date = new Date();
+                               var y = date.getFullYear();
+                               var m = fillZero(date.getMonth() + 1);
+                               var d = fillZero(date.getDate());
+                               var h = fillZero(date.getHours());
+                               var min = fillZero(date.getMinutes());
+                               var sec = fillZero(date.getSeconds());
+                               status.children('.tagget_time')
+                                       .html('Draft Saved At ' + 
+                                               y + '/' + m + '/' + d + '/' + ' ' + h + ':' + min + ':' + sec);
+                               
+                               setTimeout(timer, interval);
+                       });             
+               }, interval);
+                       
+                       
+               // This is jQuery!!
+               return this;
+               
+       }; // $.fn.tagget
+
 
 })(jQuery);
index 2d07eec..d6bb48f 100644 (file)
@@ -23,43 +23,70 @@ div.tagget_wrapper {
 }
 
 /* menu */
-div.tagget_menu ul {
-       margin: 0 1px 0 0;
-       padding: 3px 4px;
-       list-style-type: none;
+div.tagget_toolbar p {
+       padding: 2px 4px;
        float: left;
+       height: 100%;
+       border-right: 1px solid #aaaaaa;
 }
 
-div.tagget_menu p {
-       margin: 0 1px 0 3px;
-       padding: 2px;
-       float: left;
-       height: 100%;
+/* selectの誤差調整 */
+div.tagget_toolbar p.tagget_encode,
+div.tagget_toolbar p.tagget_cookie,
+div.tagget_toolbar p.tagget_intelli,
+div.tagget_toolbar p.tagget_type {
+       padding-top: 4px;
+       padding-bottom: 3px;
 }
 
-div.tagget_menu li {
-       float: left;
-       margin-right: 2px;
+/* cookieのフォント調整 */
+div.tagget_toolbar p.tagget_intelli,
+div.tagget_toolbar p.tagget_cookie {
+       border-right: none;
+}
+div.tagget_toolbar p.tagget_intelli label,
+div.tagget_toolbar p.tagget_cookie label {
+       font-weight: normal;
+       vertical-align: top;
+}
+
+/* status */
+div.tagget_wrapper p.tagget_status span {
+       font-weight: normal;
+       vertical-align: top;
+       font-style: italic;
+       margin-left: 10px;
+}
+
+div.tagget_wrapper p.tagget_status {
+       text-align: right;
+       padding-right: 5px;
 }
 
-div.tagget_menu ul li a {
-       display: block;
-       padding: 1px 10px;
-       font-weight: bold;
+/* inputの余白調整 */
+div.tagget_toolbar p.tagget_replace input {
+       margin: 0px 3px;
 }
 
-/* タグボタン後で改行 */
-div.tagget_edit {
-       clear:both;
+div.tagget_toolbar p.tagget_replace img {
+       vertical-align: baseline;
 }
+
 /* main */
+p.tagget_main {
+}
+
 p.tagget_main textarea {
-       width: 99%;
        font-family: monospace;
+       width: 99%;
+       height: 100%;
+       overflow: scroll;
+       padding: 0 3px;
+       margin: 0;
 }
 
-/* suggest */
-ul.tagget_suggest {
+/* popup */
+ul.tagget_popup {
        padding: 0;
        margin: 1em 0 0 0;
        position: absolute;
@@ -69,17 +96,18 @@ ul.tagget_suggest {
        z-index: 1;
        list-style-type: none;
        border: 1px solid #ccc;
-       font-size: 88%;
+       /*font-size: 88%;*/
        font-family: monospace;
        background-color: #fcfcfc;
+       text-align: left;
 }
 
-ul.tagget_suggest li {
+ul.tagget_popup li {
        padding: 0 3px;
        margin: 0;
 }
 
-ul.tagget_suggest li.tagget_current {
+ul.tagget_popup li.tagget_current {
        background-color: #789;
        color: #fff;
 }