Skip to content

Commit

Permalink
Merge pull request #207 from tdiary/add-category-autocomplete
Browse files Browse the repository at this point in the history
Add category autocomplete
  • Loading branch information
hsbt committed Oct 6, 2012
2 parents 8f83db7 + 0320cf5 commit 98021e7
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2012-10-06 SHIBATA Hiroshi <shibata.hiroshi@gmail.com>
* tdiary/00default.rb: support to embedded external javascripts.
* misc/plugin/category_autocomplete.rb, js/category_autocomplete.js: added category_autocomplete.js, thanks for tamoot

2012-10-02 SHIBATA Hiroshi <shibata.hiroshi@gmail.com>
* tdiary/io/rdb.rb: support auto migration for database
* misc/style/gfm/gfm_style.rb: support Emoji
Expand Down
170 changes: 170 additions & 0 deletions js/caretposition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
caretposition.js
Copyright (c) 2012- Hiroki Akiyama http://akiroom.com/
caretposition.js is free software distributed under the terms of the MIT license.
*/

Measurement = new function() {
this.caretPos = function(textarea, mode) {
var targetElement = textarea;
if (typeof jQuery != 'undefined') {
if (textarea instanceof jQuery) {
targetElement = textarea.get(0);
}
}
// HTML Sanitizer
var escapeHTML = function (s) {
var obj = document.createElement('pre');
obj[typeof obj.textContent != 'undefined'?'textContent':'innerText'] = s;
return obj.innerHTML;
};

// Get caret character position.
var getCaretPosition = function (element) {
var CaretPos = 0;
var startpos = -1;
var endpos = -1;
if (document.selection) {
// IE Support(not yet)
var docRange = document.selection.createRange();
var textRange = document.body.createTextRange();
textRange.moveToElementText(element);

var range = textRange.duplicate();
range.setEndPoint('EndToStart', docRange);
startpos = range.text.length;

var range = textRange.duplicate();
range.setEndPoint('EndToEnd', docRange);
endpos = range.text.length;
} else if (element.selectionStart || element.selectionStart == '0') {
// Firefox support
startpos = element.selectionStart;
endpos = element.selectionEnd;
}
return {start: startpos, end: endpos};
};

// Get element css style.
var getStyle = function (element) {
var style = element.currentStyle || document.defaultView.getComputedStyle(element, '');
return style;
};

// Get element absolute position
var getElementPosition = function (element) {
// Get scroll amount.
var html = document.documentElement;
var body = document.body;
var scrollLeft = (body.scrollLeft || html.scrollLeft);
var scrollTop = (body.scrollTop || html.scrollTop);

// Adjust "IE 2px bugfix" and scroll amount.
var rect = element.getBoundingClientRect();
var left = rect.left - html.clientLeft + scrollLeft;
var top = rect.top - html.clientTop + scrollTop;
var right = rect.right - html.clientLeft + scrollLeft;
var bottom = rect.bottom - html.clientTop + scrollTop;
return {left: parseInt(left), top: parseInt(top),
right: parseInt(right), bottom:parseInt(bottom)};
};

/***************************\
* Main function start here! *
\***************************/

var undefined;
var salt = "salt.akiroom.com";
var textAreaPosition = getElementPosition(targetElement);
var dummyName = targetElement.id + "_dummy";
var dummyTextArea = document.getElementById(dummyName);
if (!dummyTextArea) {
// Generate dummy textarea.
dummyTextArea = document.createElement("div");
dummyTextArea.id = dummyName;
var textAreaStyle = getStyle(targetElement)
dummyTextArea.style.cssText = textAreaStyle.cssText;

// Fix for browser differece.
var isWordWrap = false;
if (targetElement.wrap == "off") {
// chrome, firefox wordwrap=off
dummyTextArea.style.overflow = "auto";
dummyTextArea.style.whiteSpace = "pre";
isWordWrap = false;
} else if (targetElement.wrap == undefined) {
if (textAreaStyle.wordWrap == "break-word")
// safari, wordwrap=on
isWordWrap = true;
else
// safari, wordwrap=off
isWordWrap = false;
} else {
// firefox wordwrap=on
dummyTextArea.style.overflowY = "auto";
isWordWrap = true;
}
dummyTextArea.style.visibility = 'hidden';
dummyTextArea.style.position = 'absolute';
dummyTextArea.style.top = '0px';
dummyTextArea.style.left = '0px';

// Firefox Support
dummyTextArea.style.width = textAreaStyle.width;
dummyTextArea.style.height = textAreaStyle.height;
dummyTextArea.style.fontSize = textAreaStyle.fontSize;
dummyTextArea.style.maxWidth = textAreaStyle.width;
dummyTextArea.style.backgroundColor = textAreaStyle.backgroundColor;
dummyTextArea.style.fontFamily = textAreaStyle.fontFamily;

targetElement.parentNode.appendChild(dummyTextArea);
}

// Set scroll amount to dummy textarea.
dummyTextArea.scrollLeft = targetElement.scrollLeft;
dummyTextArea.scrollTop = targetElement.scrollTop;

// Set code strings.
var codeStr = targetElement.value;

// Get caret character position.
var selPos = getCaretPosition(targetElement);
var leftText = codeStr.slice(0, selPos.start);
var selText = codeStr.slice(selPos.start, selPos.end);
var rightText = codeStr.slice(selPos.end, codeStr.length);
if (selText == '') selText = "a";

// Set keyed text.
var processText = function (text) {
// Get array of [Character reference] or [Character] or [NewLine].
var m = escapeHTML(text).match(/((&amp;|&lt;|&gt;|&#34;|&#39;)|.|\n)/g);
if (m)
return m.join('<wbr>').replace(/\n/g, '<br>');
else
return '';
};

// Set calculation text for in dummy text area.
dummyTextArea.innerHTML = (processText(leftText) +
'<wbr><span id="' + dummyName + '_i">' + processText(selText) + '</span><wbr>' +
processText(rightText));

// Get caret absolutely pixel position.
var dummyTextAreaPos = getElementPosition(dummyTextArea);
var caretPos = getElementPosition(document.getElementById(dummyName+"_i"));
switch (mode) {
case 'self':
// Return absolutely pixel position - (0,0) is most top-left of TEXTAREA.
return {left: caretPos.left-dummyTextAreaPos.left, top: caretPos.top-dummyTextAreaPos.top};
case 'body':
case 'screen':
case 'stage':
case 'page':
default:
// Return absolutely pixel position - (0,0) is most top-left of PAGE.
return {left: textAreaPosition.left+caretPos.left-dummyTextAreaPos.left, top: textAreaPosition.top+caretPos.top-dummyTextAreaPos.top};
}
};
};
95 changes: 95 additions & 0 deletions js/category_autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* category_autocomplete.js : Support the automatic input of the category,
* using jQuery UI autocomplete.
*
* Copyright (C) 2012 by tamoot <tamoot+tdiary@gmail.com>
* You can distribute it under GPL.
*/

$(function() {
var config = $tDiary.plugin.category_autocomplete;
var support = false;
var regrep = ""

if ( $tDiary.style == "tdiary" ){
support = true;
regrep = "^\\[.*";

} else if ( $tDiary.style == "wiki" ){
support = true;
regrep = "^! *\\[.*";

}

function widgetPosition(){
var caretPosition = Measurement.caretPos($("#body"));
return {left: caretPosition.left + "px",
top: caretPosition.top + 20 + "px",
width: "auto"};
}

function matchedCategory(val){
if(support == false){
return false;
}

terms = val.split("\n");
term = terms[ terms.length - 1];
var matched = term.match(regrep);
if( matched == null ){
return false;
}

return true;

}

function typedCategory( term ) {
var array = term.split("[");
return array[ array.length - 1 ];
}

$( "#body" )
.bind( "keydown", function( event ) {
if ( event.keyCode === $.ui.keyCode.TAB &&
$( this ).data( "autocomplete" ).menu.active ) {
event.preventDefault();
}
})
.autocomplete({

delay: 500,

//filtering
source: function( request, response ) {
if(matchedCategory(request.term)){
response( $.ui.autocomplete.filter(
config.candidates, typedCategory( request.term ) ) );
}
},

// prevent value inserted on focus
focus: function() {
return false;
},

// replace textarea
select: function( event, ui ) {
var terms = this.value.split("[");
// remove the current typed category
terms.pop();
// add the selected item
terms.push( ui.item.value );
this.value = terms.join( "[" ) + "]";
return false;
},

// re-positioning supports excluding IE.
open: function(){
if (! document.uniqueID) {
$(".ui-autocomplete").css(widgetPosition())
}
}
});
});

3 changes: 3 additions & 0 deletions misc/plugin/category_autocomplete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
enable_js('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js')
enable_js('caretposition.js')
enable_js('category_autocomplete.js')
7 changes: 6 additions & 1 deletion plugin/00default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,14 @@ def js_url
end

def script_tag
require 'uri'
query = script_tag_query_string
html = @javascripts.sort.map {|script|
%Q|<script src="#{js_url}/#{script}#{query}" type="text/javascript"></script>|
if URI(script).scheme
%Q|<script src="#{script}" type="text/javascript"></script>|
else
%Q|<script src="#{js_url}/#{script}#{query}" type="text/javascript"></script>|
end
}.join( "\n\t" )
html << "\n" << <<-HEAD
<script type="text/javascript"><!--
Expand Down
16 changes: 16 additions & 0 deletions spec/acceptance/save_conf_plugin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@
click_link 'プラグイン選択'
page.should_not have_content 'rspec.rb'
end

scenario '外部の Javascript を追加するプラグインを有効にする' do
visit '/update.rb?conf=sp'

check "sp.category_autocomplete.rb"
click_button 'OK'

visit '/'

scripts = page.all(:xpath, '//head//script').map{|s| s[:src]}.join

scripts.should be_include('caretposition.js')
scripts.should be_include('category_autocomplete.js')
scripts.should be_include('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js')
scripts.should_not be_include('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js?')
end
end

# Local Variables:
Expand Down

0 comments on commit 98021e7

Please sign in to comment.