/**
- * tagget: Not WYSIWYG Editor (jQuery Plugin)
- * Simple interface and good suggestions.
+ * tagget: The Simple HTML Editor (jQuery Plugin)
*
* http://tagget.org/
*
* Licensed under the MIT license.
* Copyright (c) 2009 tagget.org
*
- * version 0.1.0
+ * version 0.1.1
*/
-
(function($) {
- // $('textarea.tagget').tagget()とかで呼び出し
- $.fn.tagget = function(conf) {
+ /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
- // 入力補完候補
- var keywords = {
+ /**
+ *
+ * 入力補完候補を保持、取得するオブジェクト
+ *
+ */
+ var Suggester = {
+
+ 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>#{cursor}</head>\n',
- '<title>#{cursor}</title>\n',
- '<body>#{cursor}</body>\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'
+ ];
+
+ // <body>内の要素
+ var body = [
+ '<a>#{c}</a>',
+ '<address>#{c}</address>',
+ '<![CDATA[ #{c} ]]>',
+ '<img src="#{c}" />',
+ '<br />',
+ '<div>#{c}</div>\n',
+ '<span>#{c}</span>',
+ '<p>#{c}</p>\n',
+
+ '<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',
+
+ '<table>\n#{c}\n</table>\n',
+ '<tr>#{c}</tr>',
+ '<th>#{c}</th>',
+ '<td>#{c}</td>',
+
+ '<strong>#{c}</strong>',
+ '<em>#{c}</em>',
+
+ '<form>\n#{c}\n</form>',
+ '<fieldset>#{c}</fieldset>',
+ '<input type="#{c}" />',
- '<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'
+ '<!--',
+ '-->',
+ '<!-- #{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}"',
+ 'src="#{c}"',
+ 'cols="#{c}"',
+ 'rows="#{c}"',
+ 'id="#{c}"',
+ 'class="#{c}"',
+ 'style="#{c}"',
+
+ 'colspan="#{c}"',
+ 'rowspan="#{c}"',
+ 'border="#{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 = [
'margin-left: ',
'margin-top: ',
'margin-bottom: ',
+
'padding: ',
+ 'padding-right: ',
+ 'padding-left: ',
+ 'padding-top: ',
+ 'padding-bottom: ',
+
+ 'border: ',
+ 'border-right: ',
+ 'border-left: ',
+ 'border-top: ',
+ 'border-bottom: ',
+
+ 'background: ',
+ 'background-color: ',
+ 'background-repeat: ',
+
'float: ',
'clear: '
];
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 '"#\=:;{}\(\)!?,*]{2,}/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, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>');
-
- return !quot ? s : s.replace(/"/g, '"');
};
+
+ /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+ /**
+ * taggetに必要なHTMLを生成・操作する
+ */
+ Wrapper = {
- // &, <, >(, ")を戻す
- var unescapeHtml = function(s, quot) {
-
- s = s.replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>');
+ // ツールバーを表示するか
+ isToolbar: true,
+
+ // wrap処理
+ // textarea周囲にHTMLを追加する
+ wrap: function(t) {
- return !quot ? s : s.replace(/"/g, '"');
- };
-
- // offsetを簡易算出
- var getOffset = function(elm) {
+ 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;</option>')
+ .append('<option value="raw">&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="tagget/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> </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');
- var left, top;
+ }
+
+ this.relate(t);
+
+ },
- if (elm.getBoundingClientRect) {
+ getToolbar: function(t) {
+
+ return $(t).parents('.tagget_wrapper').children('.tagget_toolbar');
+
+ },
- var rect = elm.getBoundingClientRect();
- left = Math.round(scrollX + rect.left);
- top = Math.round(scrollY + rect.top);
+ // wrap要素にeventを設定する。
+ // 既にHTMLがあった場合は、こちらだけを行う。
+ relate: function(t) {
+
+ var toolbar = this.getToolbar(t);
- } else {
-
- left = elm.offsetLeft;
- top = elm.offsetTop;
- var offsetParent = elm.offsetParent;
+ // 選択範囲変換
+ // onchangeイベント設定
+ toolbar.find('.tagget_encode select').change(function() {
- while (offsetParent) {
- left += offsetParent.offsetLeft;
- top += offsetParent.offsetTop;
- offsetParent = offsetParent.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 = '';
- }
+ });
+
+
+ // 置換ボタンクリックで置換
+ // 正規表現使用可
+ 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();
+ // フラグはデフォルトでg(Replace All)
+ var flag = 'g';
+ // TODO: 一応動くけどもっとみやすいコードに
- // tagget初期化
- var init = function(textarea, conf) {
-
- var t = $(textarea);
+ // /before/gim形式で入力されたらフラグを抽出
+ // それ以外は全体を正規表現として扱う
+ if (before.match(/^\/.+\/([^\/]+)$/)) {
+
+ // フラグ上書き
+ flag = RegExp.$1;
+ // 行頭の/、/以降の文字(フラグ、スラッシュ連続など形式外の入力)を除去
+ before = before.replace(/^\/|\/[^\/]+?$/g, '');
+ }
+
+ if (before) {
+ t.value = val.replace(new RegExp(before, flag), after);
+ }
+
+ });
- // textarea or dummyを使う関数群
- // textareaのカーソル位置に文字列挿入
- var insert = function(s) {
-
- // カーソル移動位置(#{cursor})を取得後、削除
- var cursor = s.indexOf('#{cursor}');
- s = s.replace('#{cursor}', '');
-
- // focusしないとIEでbodyに挿入されたりする
- // Firefoxでもボタンで挿入後にfocusが戻らない
- textarea.focus();
+ },
- // for IE
- if (document.selection) {
+ // textareaからID取得
+ getId: function(t) {
+ return $(t).attr('class').match(/tagget_([0-9]+)/)[1];
+ },
+
+ // 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();
+ }
+ },
- // 選択範囲を取得
- var range = document.selection.createRange();
+ checkType: function(t, type) {
+ var currentType = $(t).parents('.tagget_wrapper')
+ .find('.tagget_type select').val();
+ return (new RegExp(currentType)).test(type);
+ },
- // 選択中のテキスト引数sで置き換え(現在のカーソル位置にsを挿入)
- range.text = s;
+
+ checkCookie: function(t) {
+ var cookie = $(t).parents('.tagget_wrapper')
+ .find('.tagget_cookie input').attr('checked');
+ return cookie;
+ },
+
+ // 入力データを利用した補完を行うか
+ 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);
- // カーソルがrange.textの最後になるので戻す
- // #{cursor}指定がなければ最後のまま
- var back = s.length - (cursor != -1 ? cursor : s.length);
- range.move('character', -back);
+ // Firefox用dummy生成
+ // カーソル座標取得に使用
+ if (window.getComputedStyle) {
+
+ var dummy = $('<pre class="tagget_dummy tagget_dummy' + id + '"></pre>');
+ body.append(dummy);
- // 現在のカーソル位置を反映する(これやらないと水の泡)
- range.select();
}
- // Firefox
- // inかundefinedあたりで判定しないとselectionStartが0の時ミスる
- else if ('selectionStart' in textarea) {
-
- // スクロールバーの位置を保存
- var top = textarea.scrollTop;
-
- // 選択範囲の開始・終了位置を取得
- var start = textarea.selectionStart;
- var end = textarea.selectionEnd;
+ },
+
+ // Dummyを取得
+ getDummy: function(t) {
- // 開始位置と終了位置の間(現在のカーソル位置)にsを挿入
- textarea.value = textarea.value.slice(0, start) + s + textarea.value.slice(end);
+ var id = this.getId(t);
+ var dummy = $('.tagget_dummy' + id);
+ return dummy;
+
+ },
+
+ // textareaのstyleをdummyにコピー
+ adjust: function(t) {
- // カーソル移動位置に移動させる
- var index = start + (cursor != -1 ? cursor : s.length);
- textarea.setSelectionRange(index, index);
+ var dummy = this.getDummy(t);
- // 改行がたくさんある場合スクロールバーを下にずらす
- if (/\n/g.test(s) && s.match(/\n/g).length > 2) {
- top += parseInt(getComputedStyle(textarea, '').getPropertyValue('line-height'), 10);
- }
+ if (window.getComputedStyle) {
- // スクロールバーを戻す
- textarea.scrollTop = top;
- }
+ var org = getComputedStyle(t,'');
- return this;
+ 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 check = function() {
-
- var matches = {
- view: [],
- insert: []
- };
-
- var text = getText()[0];
+ }
- if (text) {
+ var offset = Util.getOffset(t);
- var words = [];
-
- if (conf.vars) {
- var a = textarea.value.match(/[^<>\s '"#\=:;{}\(\)!?,*]+/g) || [];
+ 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 temp = [];
- for (var i = 0; i < a.length; i++) {
-
- var v = a[i];
-
- if (!(v in temp)) {
- words.push(v);
- temp[v] = true;
+ }
+ },
+
+ // suggestionを表示
+ // TODO: 候補数制限 or 高さ設定してスクロール
+ 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);
+ }
-
- }
+ // 変化がなければ属性のみ変更
+ // TODO:入力分の削除をincertに現在の文字渡してやるようにした方がよさそう。
+ } else {
- for(var key in conf.keys) {
- if (t.hasClass('tagget_' + key) || t.hasClass(key)) {
- words = words.concat(conf.keys[key]);
- }
+ popup.children('li').each(function(i) {
+ $(this).attr('title', suggests.insert[i]);
+ });
}
+
+ var pos = Cursor.getPos(t);
+
+ popup.css({
+ left: pos.x,
+ top: pos.y
+ });
-// sortは重い原因になるので止め
-// words = words.sort();
+ popup.show();
+
+ } else {
- for(var i = 0; i < words.length; i++) {
+ popup.hide();
+
+ }
+ },
+
+ // popupを取得
+ getPopup: function(t) {
+ var id = this.getId(t);
+ var popup = $('.tagget_popup' + id);
+ return popup;
+ },
+
+
+ // suggest選択
+ // TODO:もっとちゃんと書く
+ choice: function(t, d) {
+
+ var popup = this.getPopup(t);
+ var lis = popup.children('li');
+
+ if(d == 1) {
- 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 == 0) ? lis.length - 1 : i - 1;
+ lis.eq(i).addClass('tagget_current');
+ break;
}
+
}
+ } else {
+
+ 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;
+ },
- };
+ // 選択中の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 = {
// カーソル位置の文字を取得
- var getText = function(r, after) {
+ getText: function(t, r, after) {
// if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\n=]*?$/;
- if (!r) r = /[^<>\s '"#\.=;]+?$|<[^<>\s '"#\.=;]*?$/;
-
+ 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');
+ // textareaのカーソル位置に文字列挿入
+ insert: function(t, s) {
- // 必要に応じてツールバーを追加
- 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;</option>')
- .append('<option value="raw">&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);
- }
+ // カーソル移動位置(#{c})を取得後、削除
+ var cursor = s.indexOf('#{c}');
+ s = s.replace('#{c}', '');
- // 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');
- });
- }
+ // focusしないとIEでbodyに挿入されたりする
+ // Firefoxでもボタンで挿入後にfocusが戻らない
+ t.focus();
- var body = $(document.body);
+ // for IE
+ if (document.selection) {
+
+ // 選択範囲を取得
+ var range = document.selection.createRange();
- // suggestion用の要素作成
- var suggest = $('<ul class="tagget_suggest"></ul>');
- body.append(suggest);
+ // 選択中のテキスト引数sで置き換え(現在のカーソル位置にsを挿入)
+ range.text = s;
- // Firefox用dummy生成
- // カーソル座標取得に使用
- if (window.getComputedStyle) {
-
- var dummy = $('<pre class="tagget_dummy"></pre>');
+ // カーソルがrange.textの最後になるので戻す
+ // #{c}指定がなければ最後のまま
+ var back = s.length - (cursor != -1 ? cursor : s.length);
+ range.move('character', -back);
- // textareaのstyleをdummyにコピー
- var onResize = function() {
-
- var org = getComputedStyle(textarea,'');
+ // 現在のカーソル位置を反映する(これやらないと水の泡)
+ range.select();
+ }
- 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]));
+ // Firefox
+ // inかundefinedあたりで判定しないとselectionStartが0の時ミスる
+ else if ('selectionStart' in t) {
- }
+ // スクロールバーの位置を保存
+ var top = t.scrollTop;
- var offset = getOffset(textarea);
+ // 選択範囲の開始・終了位置を取得
+ var start = t.selectionStart;
+ var end = t.selectionEnd;
- dummy.css({
- left: offset.left,
- top: offset.top
- });
-
- dummy.width(t.width())
- .height(t.height())
- .scrollLeft(t.scrollLeft())
- .scrollTop(t.scrollTop());
+ // 開始位置と終了位置の間(現在のカーソル位置)にsを挿入
+ t.value = t.value.slice(0, start) + s + t.value.slice(end);
- };
-
- // resize時にはtextareaのサイズも変わるので
- $(window).resize(function() {
- onResize();
- }).resize();
+ // カーソル移動位置に移動させる
+ var index = start + (cursor != -1 ? cursor : s.length);
+ t.setSelectionRange(index, index);
- body.append(dummy);
+ // 改行がたくさんある場合スクロールバーを下にずらす
+ if (/\n/g.test(s) && s.match(/\n/g).length > 2) {
+ top += parseInt(getComputedStyle(t, '').getPropertyValue('font-size'), 10) + 3;
+ }
+
+ // スクロールバーを戻す
+ t.scrollTop = top;
+ }
- }
-
+ return this;
+
+ },
+
+ /**
+ * 渡された関数で選択範囲を変換
+ */
+ // TODO:変換後も選択した状態に
+ encodeSelection: function(t, func) {
- // イベント設定
+ t.focus();
- // タグ挿入ボタン
- if (conf.tags) {
-
- // clickイベント設定
- var onClick = function(elm, s) {
-
- $(elm).click(function() {
- insert(s);
- return false;
- });
+ // IE
+ if (document.selection) {
+
+ var range = document.selection.createRange();
+ range.text = func(range.text);
+ range.select();
- };
+ } else if ('selectionStart' in t) {
- // タグボタンクリックで挿入
- tags.find('a').each(function() {
+ var top = t.scrollTop;
- // 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;
+ var start = t.selectionStart;
+ var end = t.selectionEnd;
+
+ var before = t.value.slice(start, end);
+ var after = func(before);
- case 'li':
- onClick(this, '<li>#{cursor}</li>\n');
- break;
- case 'dl':
- onClick(this, '<dl>\n<dt>#{cursor}</dt>\n<dd></dd>\n</dl>\n');
- break;
+ t.value = t.value.slice(0, start) +
+ after + t.value.slice(end);
- case 'dt':
- onClick(this, '<dt>#{cursor}</dt>\n');
- break;
+ t.setSelectionRange(end, end);
- case 'dd':
- onClick(this, '<dd>#{cursor}</dd>\n');
- break;
+ t.scrollTop = top;
+ }
+
+ },
+
+ /**
+ * 閉じタグ補完
+ */
+ // カーソル位置より前にある開始タグをさかのぼって見ていく
+ // 閉じタグが何もなければ直近のタグを閉じる
+ // 閉じタグとマッチしないタグがあれば閉じる
+ // カーソル後に閉じタグがあるとかは無視
+ // 整形式じゃないのも無視
+ closeTag: function(t) {
+
+ // カーソルより前の開始タグ一覧
+ // <! <? </ 空要素/>は除く
+ // TODO:できれば一文でやりたい
+ var sTags = Cursor.getText(t, /^[\s\S]*$/)[0]
+ .replace(/<[!\?\/][\s\S]*?>/g, '') // <!?/を除去
+ .replace(/<[^>]*?\/>/g, '') // 空要素/>を除去
+ .match(/<[^>]+?>/g); // タグ抽出
+ var eTags = Cursor.getText(t, /<\/[^?]+?>/g);
+
+ if (sTags) {
+
+ var i = sTags.length - 1;
+
+ for (; i >= 0 && eTags.length > 0; i--) {
+
+ // <までとタグ名以降を除去
+ // >と\s始まり(属性と閉じ括弧)を除去
+ // TODO:もうちょっと改善すれば改行された要素にも対応できる?
+ var sTag = sTags[i].replace(/^.*<|[\s>].*/g, '');
- case 'pre':
- onClick(this, '<pre>#{cursor}</pre>\n');
- break;
+ var j = 0;
+ for (; j < eTags.length; j++) {
- case 'code':
- onClick(this, '<code>#{cursor}</code>');
- break;
+ var eTag = eTags[j].replace(/^.*<\/|[\s>].*/g, '');
- case 'blockquote':
- onClick(this, '<blockquote>#{cursor}</blockquote>\n');
- break;
+ // マッチしたらその閉じタグを配列から消して除外
+ if (sTag == eTag) {
+ break;
+ }
+ }
- case 'div':
- onClick(this, '<div>#{cursor}</div>\n');
- break;
+ if (j < eTags.length) {
+ eTags.splice(j, 1);
+
+ // マッチしなかったらその開始タグを閉じる
+ } else {
+ break;
+ }
- case 'span':
- onClick(this, '<span>#{cursor}</span>\n');
- break;
+ }
- case 'link':
- onClick(this, '<link rel="stylesheet" type="text/css" href="#{cursor}" />\n');
- break;
+ // 現在の開始タグを閉じる
+ // 全てマッチしたら何もしない
+ if (i >= 0) {
+ Cursor.insert(t, '</' + sTags[i].replace(/^.*<|[\s>].*/g, '') + '>');
+ }
- 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, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+
+ return !quot ? s : s.replace(/"/g, '"');
+ },
- // 選択範囲変換
+ // &, <, >[, "]を戻す
+ unescapeHtml:function(s, quot) {
+
+ s = s.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
- // 渡された関数で選択範囲を変換
- var onChange = function(func) {
+ return !quot ? s : s.replace(/"/g, '"');
+ },
- textarea.focus();
+ // offset(要素の実xy座標)を簡易算出
+ getOffset: function(elm) {
- if (document.selection) {
-
- var range = document.selection.createRange();
- range.text = func(range.text);
- range.select();
-
- } else if ('selectionStart' in textarea) {
+ var left, top;
- var top = textarea.scrollTop;
+ if (elm.getBoundingClientRect) {
- var start = textarea.selectionStart;
- var end = textarea.selectionEnd;
-
- textarea.value = textarea.value.slice(0, start) +
- func(textarea.value.slice(start, end)) +
- textarea.value.slice(end);
-
- textarea.setSelectionRange(end, end);
-
- textarea.scrollTop = top;
- }
- };
-
- // onchangeイベント設定
- edit.find('.tagget_encode select').change(function() {
-
- switch (this.value) {
-
- case 'entity':
- onChange(escapeHtml);
- break;
+ var rect = elm.getBoundingClientRect();
+ left = Math.round(scrollX + rect.left);
+ top = Math.round(scrollY + rect.top);
- case 'raw':
- onChange(unescapeHtml);
- break;
-
- 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() {
+ },
+
+ fillZero: function(s) {
+ return ('0' + s).slice(-2);
+ },
+
+ now: function() {
+
+ var date = new Date();
+ var y = date.getFullYear();
+ var m = this.fillZero(date.getMonth() + 1);
+ var d = this.fillZero(date.getDate());
+ var h = this.fillZero(date.getHours());
+ var min = this.fillZero(date.getMinutes());
+ var sec = this.fillZero(date.getSeconds());
- var val = textarea.value;
-
- var before = inputs.eq(0).val();
- var after = inputs.eq(1).val();
- var flag = '';
+ return y + '/' + m + '/' + d + '/' + ' ' + h + ':' + min + ':' + sec;
- if (before.match(/^\/.+\/([^\/]+)$/)) {
-
- flag = RegExp.$1;
- before = before.replace(/^\/|\/[^\/]+?$/g, '');
- }
-
- if (before) {
- textarea.value = val.replace(new RegExp(before, flag), after);
- }
-
- });
-
}
+
+ };
- // メイン機能
- // キー入力時のsuggestion設定
+ /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+ /**
+ * Cookieに下書き保存
+ */
+ var Cookie = {
+
+ load: function(key) {
- // keyup(発生タイミングが一番少ない)で候補表示
- t.keyup(function(e) {
+ var cookie = $.cookie(key);
+ if (cookie) {
+ return cookie;
+ }
- 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);
- }
+ },
+
+ zip: function(s) {
- } else {
+ var data = utf16to8(s);
+ data = zip_deflate(data);
+ data = base64encode(data);
+ return data;
+
+ },
- suggest.children('li').each(function(i) {
- $(this).attr('title', suggests.insert[i]);
- });
- }
-
- var pos = getPos();
-
- suggest.css({
- left: pos.x,
- top: pos.y
- });
+ unzip: function(s) {
- suggest.show();
-
- } else {
- suggest.hide();
- }
+ var data = base64decode(s);
+ data = zip_inflate(data);
+ data = utf8to16(data);
+ return data;
+
+ },
+
+ save: function(key, val) {
+
+ var size = val.length;
+
+ if(size <= 4000) {
+ $.cookie(key, val, { 'expires': 1 });
+ }
+
+ }
+
+
+ };
- if (conf.bind) $(conf.bind).html(textarea.value);
- })
+ /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+ /**
+ * textareaのEventに設定する関数群
+ */
+ Event = {
+
+ // keyupは発生タイミングが一番少ない
+ // 候補表示に使用
+ keyup: function(e) {
+
+ var t = this;
+
+ // 十字キー、Enterの時は補完を表示しない
+ if(!(37 <= e.which && e.which <= 40) && !(e.which == 13)) {
+ Wrapper.showPopup(t);
+
+ }
+ // 左右キーで補完を非表示化
+ else if(e.which == 37 || e.which == 39) {
+ Wrapper.getPopup(t).hide();
+ }
+
+ Wrapper.setLine(t);
+ },
- // click時にも候補表示
- .click(function() {
- $(this).keyup();
- })
+ // 入力内容に応じた処理はdownで
+ // press,upだとおかしくなるものもこっちで
+ keydown: function(e) {
- .keydown(function(e) {
+ var t = this;
// タブキャンセル
// 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.checkType(t, 'html')) {
+ n = '<br />';
}
+ Cursor.insert(t, n);
- insert(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;
- }
- }
+ if (Wrapper.checkType(t, 'html')) {
- }
+ Cursor.closeTag(t);
+ return false;
-
- // insert(n);
-
- 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] : ''));
+
+ // TODO:もっとちゃんとスクロールの高さ直す
+ 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('font-size'), 10) + 3);
}
return false;
}
- });
+ }
+
+ };
+
+
+ /* ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- */
+ // tagget初期化
+ var init = function(t, i) {
+
+ // 被らないコードを振る
+ while ($('textarea').is('.tagget_' + i)) {
+ i = '0' + i;
+ }
+ $(t).addClass('tagget_' + i);
+
+ Wrapper.wrap(t);
+ Wrapper.absolutes(t);
+
+ // イベント設定
+
+ // keyup(発生タイミングが一番少ない)で候補表示
+ $(t).keyup(Event.keyup)
+
+ // 入力キーに応じた処理
+ .keydown(Event.keydown);
+
+ // 最初に1回だけ呼び出し。
+ var data = Cookie.load(Wrapper.getId(t));
+ if (data) {
+ t.value = Cookie.unzip(data);
+ }
+ 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() {
+
+ if (Wrapper.checkCookie(this)) {
+
+ Cookie.save(Wrapper.getId(this), Cookie.zip(this.value));
+
+ var status = Wrapper.getStatus(this);
+ status.children('.tagget_time').html('Draft Saved At ' + Util.now());
+
+ }
+
+ setTimeout(timer, interval);
+ });
+ }, interval);
+
+ // This is jQuery!!
+ return this;
+
+ }; // $.fn.tagget
+
})(jQuery);