forked from GNUsocial/gnu-social
Merge branch '0.9.x' into openidplugin
Conflicts: actions/login.php actions/register.php
This commit is contained in:
38
plugins/Autocomplete/Autocomplete.js
Normal file
38
plugins/Autocomplete/Autocomplete.js
Normal file
@@ -0,0 +1,38 @@
|
||||
$(document).ready(function(){
|
||||
$.getJSON($('address .url')[0].href+'/api/statuses/friends.json?user_id=' + current_user['id'] + '&lite=true&callback=?',
|
||||
function(friends){
|
||||
$('#notice_data-text').autocomplete(friends, {
|
||||
multiple: true,
|
||||
multipleSeparator: " ",
|
||||
minChars: 1,
|
||||
formatItem: function(row, i, max){
|
||||
return '@' + row.screen_name + ' (' + row.name + ')';
|
||||
},
|
||||
formatMatch: function(row, i, max){
|
||||
return '@' + row.screen_name;
|
||||
},
|
||||
formatResult: function(row){
|
||||
return '@' + row.screen_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
$.getJSON($('address .url')[0].href+'/api/laconica/groups/list.json?user_id=' + current_user['id'] + '&callback=?',
|
||||
function(groups){
|
||||
$('#notice_data-text').autocomplete(groups, {
|
||||
multiple: true,
|
||||
multipleSeparator: " ",
|
||||
minChars: 1,
|
||||
formatItem: function(row, i, max){
|
||||
return '!' + row.nickname + ' (' + row.fullname + ')';
|
||||
},
|
||||
formatMatch: function(row, i, max){
|
||||
return '!' + row.nickname;
|
||||
},
|
||||
formatResult: function(row){
|
||||
return '!' + row.nickname;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
63
plugins/Autocomplete/AutocompletePlugin.php
Normal file
63
plugins/Autocomplete/AutocompletePlugin.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Plugin to enable nickname completion in the enter status box
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package Laconica
|
||||
* @author Craig Andrews <candrews@integralblue.com>
|
||||
* @copyright 2009 Craig Andrews http://candrews.integralblue.com
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class AutocompletePlugin extends Plugin
|
||||
{
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function onEndShowScripts($action){
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
$js_string = <<<EOT
|
||||
<script type="text/javascript">
|
||||
var current_user = { id: '$current_user->id' };
|
||||
</script>
|
||||
EOT;
|
||||
$action->raw($js_string);
|
||||
$action->script('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.pack.js');
|
||||
$action->script('plugins/Autocomplete/Autocomplete.js');
|
||||
}
|
||||
}
|
||||
|
||||
function onEndShowLaconicaStyles($action)
|
||||
{
|
||||
if (common_logged_in()) {
|
||||
$action->cssLink('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.css');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
20
plugins/Autocomplete/jquery-autocomplete/changelog.txt
Normal file
20
plugins/Autocomplete/jquery-autocomplete/changelog.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
1.0.2
|
||||
-----
|
||||
* Fixed missing semicolon
|
||||
|
||||
1.0.1
|
||||
-----
|
||||
* Fixed element creation (<ul> to <ul/> and <li> to </li>)
|
||||
* Fixed ac_even class (was ac_event)
|
||||
* Fixed bgiframe usage: now its really optional
|
||||
* Removed the blur-on-return workaround, added a less obtrusive one only for Opera
|
||||
* Fixed hold cursor keys: Opera needs keypress, everyone else keydown to scroll through result list when holding cursor key
|
||||
* Updated package to jQuery 1.2.5, removing dimensions
|
||||
* Fixed multiple-mustMatch: Remove only the last term when no match is found
|
||||
* Fixed multiple without mustMatch: Don't select the last active when no match is found (on tab/return)
|
||||
* Fixed multiple cursor position: Put cursor at end of input after selecting a value
|
||||
|
||||
1.0
|
||||
---
|
||||
|
||||
* First release.
|
@@ -0,0 +1,48 @@
|
||||
.ac_results {
|
||||
padding: 0px;
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ac_results ul {
|
||||
width: 100%;
|
||||
list-style-position: outside;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ac_results li {
|
||||
margin: 0px;
|
||||
padding: 2px 5px;
|
||||
cursor: default;
|
||||
display: block;
|
||||
/*
|
||||
if width will be 100% horizontal scrollbar will apear
|
||||
when scroll mode will be used
|
||||
*/
|
||||
/*width: 100%;*/
|
||||
font: menu;
|
||||
font-size: 12px;
|
||||
/*
|
||||
it is very important, if line-height not setted or setted
|
||||
in relative units scroll will be broken in firefox
|
||||
*/
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ac_loading {
|
||||
background: white url('indicator.gif') right center no-repeat;
|
||||
}
|
||||
|
||||
.ac_odd {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.ac_over {
|
||||
background-color: #0A246A;
|
||||
color: white;
|
||||
}
|
759
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.js
Normal file
759
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.js
Normal file
@@ -0,0 +1,759 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
autocomplete: function(urlOrData, options) {
|
||||
var isUrl = typeof urlOrData == "string";
|
||||
options = $.extend({}, $.Autocompleter.defaults, {
|
||||
url: isUrl ? urlOrData : null,
|
||||
data: isUrl ? null : urlOrData,
|
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
||||
max: options && !options.scroll ? 10 : 150
|
||||
}, options);
|
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; };
|
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem;
|
||||
|
||||
return this.each(function() {
|
||||
new $.Autocompleter(this, options);
|
||||
});
|
||||
},
|
||||
result: function(handler) {
|
||||
return this.bind("result", handler);
|
||||
},
|
||||
search: function(handler) {
|
||||
return this.trigger("search", [handler]);
|
||||
},
|
||||
flushCache: function() {
|
||||
return this.trigger("flushCache");
|
||||
},
|
||||
setOptions: function(options){
|
||||
return this.trigger("setOptions", [options]);
|
||||
},
|
||||
unautocomplete: function() {
|
||||
return this.trigger("unautocomplete");
|
||||
}
|
||||
});
|
||||
|
||||
$.Autocompleter = function(input, options) {
|
||||
|
||||
var KEY = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
DEL: 46,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
COMMA: 188,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
BACKSPACE: 8
|
||||
};
|
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
||||
|
||||
var timeout;
|
||||
var previousValue = "";
|
||||
var cache = $.Autocompleter.Cache(options);
|
||||
var hasFocus = 0;
|
||||
var lastKeyPressCode;
|
||||
var config = {
|
||||
mouseDownOnSelect: false
|
||||
};
|
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
||||
|
||||
var blockSubmit;
|
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
||||
if (blockSubmit) {
|
||||
blockSubmit = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
|
||||
case KEY.UP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.prev();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.DOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.next();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEUP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageUp();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEDOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageDown();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
if( selectCurrent() ) {
|
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault();
|
||||
blockSubmit = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
select.hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(onChange, options.delay);
|
||||
break;
|
||||
}
|
||||
}).focus(function(){
|
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++;
|
||||
}).blur(function() {
|
||||
hasFocus = 0;
|
||||
if (!config.mouseDownOnSelect) {
|
||||
hideResults();
|
||||
}
|
||||
}).click(function() {
|
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
}).bind("search", function() {
|
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
||||
function findValueCallback(q, data) {
|
||||
var result;
|
||||
if( data && data.length ) {
|
||||
for (var i=0; i < data.length; i++) {
|
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
||||
result = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( typeof fn == "function" ) fn(result);
|
||||
else $input.trigger("result", result && [result.data, result.value]);
|
||||
}
|
||||
$.each(trimWords($input.val()), function(i, value) {
|
||||
request(value, findValueCallback, findValueCallback);
|
||||
});
|
||||
}).bind("flushCache", function() {
|
||||
cache.flush();
|
||||
}).bind("setOptions", function() {
|
||||
$.extend(options, arguments[1]);
|
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] )
|
||||
cache.populate();
|
||||
}).bind("unautocomplete", function() {
|
||||
select.unbind();
|
||||
$input.unbind();
|
||||
$(input.form).unbind(".autocomplete");
|
||||
});
|
||||
|
||||
|
||||
function selectCurrent() {
|
||||
var selected = select.selected();
|
||||
if( !selected )
|
||||
return false;
|
||||
|
||||
var v = selected.result;
|
||||
previousValue = v;
|
||||
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
|
||||
$input.val(v);
|
||||
hideResultsNow();
|
||||
$input.trigger("result", [selected.data, selected.value]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onChange(crap, skipPrevCheck) {
|
||||
if( lastKeyPressCode == KEY.DEL ) {
|
||||
select.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValue = $input.val();
|
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue )
|
||||
return;
|
||||
|
||||
previousValue = currentValue;
|
||||
|
||||
currentValue = lastWord(currentValue);
|
||||
if ( currentValue.length >= options.minChars) {
|
||||
$input.addClass(options.loadingClass);
|
||||
if (!options.matchCase)
|
||||
currentValue = currentValue.toLowerCase();
|
||||
request(currentValue, receiveData, hideResultsNow);
|
||||
} else {
|
||||
stopLoading();
|
||||
select.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if ( !value ) {
|
||||
return [""];
|
||||
}
|
||||
var words = value.split( options.multipleSeparator );
|
||||
var result = [];
|
||||
$.each(words, function(i, value) {
|
||||
if ( $.trim(value) )
|
||||
result[i] = $.trim(value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){
|
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
function hideResults() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(hideResultsNow, 200);
|
||||
};
|
||||
|
||||
function hideResultsNow() {
|
||||
var wasVisible = select.visible();
|
||||
select.hide();
|
||||
clearTimeout(timeout);
|
||||
stopLoading();
|
||||
if (options.mustMatch) {
|
||||
// call search and run callback
|
||||
$input.search(
|
||||
function (result){
|
||||
// if no value found, clear the input box
|
||||
if( !result ) {
|
||||
if (options.multiple) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else
|
||||
$input.val( "" );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (wasVisible)
|
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
if ( data && data.length && hasFocus ) {
|
||||
stopLoading();
|
||||
select.display(data, q);
|
||||
autoFill(q, data[0].value);
|
||||
select.show();
|
||||
} else {
|
||||
hideResultsNow();
|
||||
}
|
||||
};
|
||||
|
||||
function request(term, success, failure) {
|
||||
if (!options.matchCase)
|
||||
term = term.toLowerCase();
|
||||
var data = cache.load(term);
|
||||
// recieve the cached data
|
||||
if (data && data.length) {
|
||||
success(term, data);
|
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
||||
|
||||
var extraParams = {
|
||||
timestamp: +new Date()
|
||||
};
|
||||
$.each(options.extraParams, function(key, param) {
|
||||
extraParams[key] = typeof param == "function" ? param() : param;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort",
|
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
limit: options.max
|
||||
}, extraParams),
|
||||
success: function(data) {
|
||||
var parsed = options.parse && options.parse(data) || parse(data);
|
||||
cache.add(term, parsed);
|
||||
success(term, parsed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList();
|
||||
failure(term);
|
||||
}
|
||||
};
|
||||
|
||||
function parse(data) {
|
||||
var parsed = [];
|
||||
var rows = data.split("\n");
|
||||
for (var i=0; i < rows.length; i++) {
|
||||
var row = $.trim(rows[i]);
|
||||
if (row) {
|
||||
row = row.split("|");
|
||||
parsed[parsed.length] = {
|
||||
data: row,
|
||||
value: row[0],
|
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
function stopLoading() {
|
||||
$input.removeClass(options.loadingClass);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
$.Autocompleter.defaults = {
|
||||
inputClass: "ac_input",
|
||||
resultsClass: "ac_results",
|
||||
loadingClass: "ac_loading",
|
||||
minChars: 1,
|
||||
delay: 400,
|
||||
matchCase: false,
|
||||
matchSubset: true,
|
||||
matchContains: false,
|
||||
cacheLength: 10,
|
||||
max: 100,
|
||||
mustMatch: false,
|
||||
extraParams: {},
|
||||
selectFirst: true,
|
||||
formatItem: function(row) { return row[0]; },
|
||||
formatMatch: null,
|
||||
autoFill: false,
|
||||
width: 0,
|
||||
multiple: false,
|
||||
multipleSeparator: ", ",
|
||||
highlight: function(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
||||
},
|
||||
scroll: true,
|
||||
scrollHeight: 180
|
||||
};
|
||||
|
||||
$.Autocompleter.Cache = function(options) {
|
||||
|
||||
var data = {};
|
||||
var length = 0;
|
||||
|
||||
function matchSubset(s, sub) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
|
||||
function add(q, value) {
|
||||
if (length > options.cacheLength){
|
||||
flush();
|
||||
}
|
||||
if (!data[q]){
|
||||
length++;
|
||||
}
|
||||
data[q] = value;
|
||||
}
|
||||
|
||||
function populate(){
|
||||
if( !options.data ) return false;
|
||||
// track the matches
|
||||
var stMatchSets = {},
|
||||
nullData = 0;
|
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1;
|
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = [];
|
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
||||
var rawValue = options.data[i];
|
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
||||
if ( value === false )
|
||||
continue;
|
||||
|
||||
var firstChar = value.charAt(0).toLowerCase();
|
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = [];
|
||||
|
||||
// if the match is a string
|
||||
var row = {
|
||||
value: value,
|
||||
data: rawValue,
|
||||
result: options.formatResult && options.formatResult(rawValue) || value
|
||||
};
|
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row);
|
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) {
|
||||
stMatchSets[""].push(row);
|
||||
}
|
||||
};
|
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) {
|
||||
// increase the cache size
|
||||
options.cacheLength++;
|
||||
// add to the cache
|
||||
add(i, value);
|
||||
});
|
||||
}
|
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25);
|
||||
|
||||
function flush(){
|
||||
data = {};
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
flush: flush,
|
||||
add: add,
|
||||
populate: populate,
|
||||
load: function(q) {
|
||||
if (!options.cacheLength || !length)
|
||||
return null;
|
||||
/*
|
||||
* if dealing w/local data and matchContains than we must make sure
|
||||
* to loop through all the data collections looking for matches
|
||||
*/
|
||||
if( !options.url && options.matchContains ){
|
||||
// track all matches
|
||||
var csub = [];
|
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){
|
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){
|
||||
var c = data[k];
|
||||
$.each(c, function(i, x) {
|
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return csub;
|
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){
|
||||
return data[q];
|
||||
} else
|
||||
if (options.matchSubset) {
|
||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
||||
var c = data[q.substr(0, i)];
|
||||
if (c) {
|
||||
var csub = [];
|
||||
$.each(c, function(i, x) {
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub[csub.length] = x;
|
||||
}
|
||||
});
|
||||
return csub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) {
|
||||
var CLASSES = {
|
||||
ACTIVE: "ac_over"
|
||||
};
|
||||
|
||||
var listItems,
|
||||
active = -1,
|
||||
data,
|
||||
term = "",
|
||||
needsInit = true,
|
||||
element,
|
||||
list;
|
||||
|
||||
// Create results
|
||||
function init() {
|
||||
if (!needsInit)
|
||||
return;
|
||||
element = $("<div/>")
|
||||
.hide()
|
||||
.addClass(options.resultsClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(document.body);
|
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
}
|
||||
}).click(function(event) {
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
select();
|
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus();
|
||||
return false;
|
||||
}).mousedown(function() {
|
||||
config.mouseDownOnSelect = true;
|
||||
}).mouseup(function() {
|
||||
config.mouseDownOnSelect = false;
|
||||
});
|
||||
|
||||
if( options.width > 0 )
|
||||
element.css("width", options.width);
|
||||
|
||||
needsInit = false;
|
||||
}
|
||||
|
||||
function target(event) {
|
||||
var element = event.target;
|
||||
while(element && element.tagName != "LI")
|
||||
element = element.parentNode;
|
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element)
|
||||
return [];
|
||||
return element;
|
||||
}
|
||||
|
||||
function moveSelect(step) {
|
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
||||
movePosition(step);
|
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
||||
if(options.scroll) {
|
||||
var offset = 0;
|
||||
listItems.slice(0, active).each(function() {
|
||||
offset += this.offsetHeight;
|
||||
});
|
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
||||
} else if(offset < list.scrollTop()) {
|
||||
list.scrollTop(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function movePosition(step) {
|
||||
active += step;
|
||||
if (active < 0) {
|
||||
active = listItems.size() - 1;
|
||||
} else if (active >= listItems.size()) {
|
||||
active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function limitNumberOfItems(available) {
|
||||
return options.max && options.max < available
|
||||
? options.max
|
||||
: available;
|
||||
}
|
||||
|
||||
function fillList() {
|
||||
list.empty();
|
||||
var max = limitNumberOfItems(data.length);
|
||||
for (var i=0; i < max; i++) {
|
||||
if (!data[i])
|
||||
continue;
|
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
||||
if ( formatted === false )
|
||||
continue;
|
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
||||
$.data(li, "ac_data", data[i]);
|
||||
}
|
||||
listItems = list.find("li");
|
||||
if ( options.selectFirst ) {
|
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
||||
active = 0;
|
||||
}
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
list.bgiframe();
|
||||
}
|
||||
|
||||
return {
|
||||
display: function(d, q) {
|
||||
init();
|
||||
data = d;
|
||||
term = q;
|
||||
fillList();
|
||||
},
|
||||
next: function() {
|
||||
moveSelect(1);
|
||||
},
|
||||
prev: function() {
|
||||
moveSelect(-1);
|
||||
},
|
||||
pageUp: function() {
|
||||
if (active != 0 && active - 8 < 0) {
|
||||
moveSelect( -active );
|
||||
} else {
|
||||
moveSelect(-8);
|
||||
}
|
||||
},
|
||||
pageDown: function() {
|
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
||||
moveSelect( listItems.size() - 1 - active );
|
||||
} else {
|
||||
moveSelect(8);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
element && element.hide();
|
||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
||||
active = -1;
|
||||
},
|
||||
visible : function() {
|
||||
return element && element.is(":visible");
|
||||
},
|
||||
current: function() {
|
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
||||
},
|
||||
show: function() {
|
||||
var offset = $(input).offset();
|
||||
element.css({
|
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
||||
top: offset.top + input.offsetHeight,
|
||||
left: offset.left
|
||||
}).show();
|
||||
if(options.scroll) {
|
||||
list.scrollTop(0);
|
||||
list.css({
|
||||
maxHeight: options.scrollHeight,
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
||||
var listHeight = 0;
|
||||
listItems.each(function() {
|
||||
listHeight += this.offsetHeight;
|
||||
});
|
||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
||||
if (!scrollbarsVisible) {
|
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
selected: function() {
|
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
||||
},
|
||||
emptyList: function (){
|
||||
list && list.empty();
|
||||
},
|
||||
unbind: function() {
|
||||
element && element.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) {
|
||||
if( field.createTextRange ){
|
||||
var selRange = field.createTextRange();
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
} else if( field.setSelectionRange ){
|
||||
field.setSelectionRange(start, end);
|
||||
} else {
|
||||
if( field.selectionStart ){
|
||||
field.selectionStart = start;
|
||||
field.selectionEnd = end;
|
||||
}
|
||||
}
|
||||
field.focus();
|
||||
};
|
||||
|
||||
})(jQuery);
|
15
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.min.js
vendored
Normal file
15
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
116
plugins/Autocomplete/jquery-autocomplete/lib/jquery.ajaxQueue.js
Normal file
116
plugins/Autocomplete/jquery-autocomplete/lib/jquery.ajaxQueue.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Ajax Queue Plugin
|
||||
*
|
||||
* Homepage: http://jquery.com/plugins/project/ajaxqueue
|
||||
* Documentation: http://docs.jquery.com/AjaxQueue
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
jQuery.ajaxQueue({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append(html); }
|
||||
});
|
||||
jQuery.ajaxQueue({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append(html); }
|
||||
});
|
||||
jQuery.ajaxSync({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
||||
});
|
||||
jQuery.ajaxSync({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<ul style="position: absolute; top: 5px; right: 5px;"></ul>
|
||||
|
||||
*/
|
||||
/*
|
||||
* Queued Ajax requests.
|
||||
* A new Ajax request won't be started until the previous queued
|
||||
* request has finished.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Synced Ajax requests.
|
||||
* The Ajax request will happen as soon as you call this method, but
|
||||
* the callbacks (success/error/complete) won't fire until all previous
|
||||
* synced requests have been completed.
|
||||
*/
|
||||
|
||||
|
||||
(function($) {
|
||||
|
||||
var ajax = $.ajax;
|
||||
|
||||
var pendingRequests = {};
|
||||
|
||||
var synced = [];
|
||||
var syncedData = [];
|
||||
|
||||
$.ajax = function(settings) {
|
||||
// create settings for compatibility with ajaxSetup
|
||||
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
|
||||
|
||||
var port = settings.port;
|
||||
|
||||
switch(settings.mode) {
|
||||
case "abort":
|
||||
if ( pendingRequests[port] ) {
|
||||
pendingRequests[port].abort();
|
||||
}
|
||||
return pendingRequests[port] = ajax.apply(this, arguments);
|
||||
case "queue":
|
||||
var _old = settings.complete;
|
||||
settings.complete = function(){
|
||||
if ( _old )
|
||||
_old.apply( this, arguments );
|
||||
jQuery([ajax]).dequeue("ajax" + port );;
|
||||
};
|
||||
|
||||
jQuery([ ajax ]).queue("ajax" + port, function(){
|
||||
ajax( settings );
|
||||
});
|
||||
return;
|
||||
case "sync":
|
||||
var pos = synced.length;
|
||||
|
||||
synced[ pos ] = {
|
||||
error: settings.error,
|
||||
success: settings.success,
|
||||
complete: settings.complete,
|
||||
done: false
|
||||
};
|
||||
|
||||
syncedData[ pos ] = {
|
||||
error: [],
|
||||
success: [],
|
||||
complete: []
|
||||
};
|
||||
|
||||
settings.error = function(){ syncedData[ pos ].error = arguments; };
|
||||
settings.success = function(){ syncedData[ pos ].success = arguments; };
|
||||
settings.complete = function(){
|
||||
syncedData[ pos ].complete = arguments;
|
||||
synced[ pos ].done = true;
|
||||
|
||||
if ( pos == 0 || !synced[ pos-1 ] )
|
||||
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
|
||||
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
|
||||
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
|
||||
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
|
||||
|
||||
synced[i] = null;
|
||||
syncedData[i] = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return ajax.apply(this, arguments);
|
||||
};
|
||||
|
||||
})(jQuery);
|
10
plugins/Autocomplete/jquery-autocomplete/lib/jquery.bgiframe.min.js
vendored
Normal file
10
plugins/Autocomplete/jquery-autocomplete/lib/jquery.bgiframe.min.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
|
||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*
|
||||
* $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
|
||||
* $Rev: 2447 $
|
||||
*
|
||||
* Version 2.1.1
|
||||
*/
|
||||
(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
|
3558
plugins/Autocomplete/jquery-autocomplete/lib/jquery.js
vendored
Normal file
3558
plugins/Autocomplete/jquery-autocomplete/lib/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10
plugins/Autocomplete/jquery-autocomplete/lib/thickbox-compressed.js
vendored
Normal file
10
plugins/Autocomplete/jquery-autocomplete/lib/thickbox-compressed.js
vendored
Normal file
File diff suppressed because one or more lines are too long
163
plugins/Autocomplete/jquery-autocomplete/lib/thickbox.css
Normal file
163
plugins/Autocomplete/jquery-autocomplete/lib/thickbox.css
Normal file
@@ -0,0 +1,163 @@
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
/* ---------->>> global settings needed for thickbox <<<-----------------------------------------------------------*/
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
*{padding: 0; margin: 0;}
|
||||
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
/* ---------->>> thickbox specific link and font settings <<<------------------------------------------------------*/
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
#TB_window {
|
||||
font: 12px Arial, Helvetica, sans-serif;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
#TB_secondLine {
|
||||
font: 10px Arial, Helvetica, sans-serif;
|
||||
color:#666666;
|
||||
}
|
||||
|
||||
#TB_window a:link {color: #666666;}
|
||||
#TB_window a:visited {color: #666666;}
|
||||
#TB_window a:hover {color: #000;}
|
||||
#TB_window a:active {color: #666666;}
|
||||
#TB_window a:focus{color: #666666;}
|
||||
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
/* ---------->>> thickbox settings <<<-----------------------------------------------------------------------------*/
|
||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
||||
#TB_overlay {
|
||||
position: fixed;
|
||||
z-index:100;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.TB_overlayMacFFBGHack {background: url(macFFBgHack.png) repeat;}
|
||||
.TB_overlayBG {
|
||||
background-color:#000;
|
||||
filter:alpha(opacity=75);
|
||||
-moz-opacity: 0.75;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
* html #TB_overlay { /* ie6 hack */
|
||||
position: absolute;
|
||||
height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
|
||||
}
|
||||
|
||||
#TB_window {
|
||||
position: fixed;
|
||||
background: #ffffff;
|
||||
z-index: 102;
|
||||
color:#000000;
|
||||
display:none;
|
||||
border: 4px solid #525252;
|
||||
text-align:left;
|
||||
top:50%;
|
||||
left:50%;
|
||||
}
|
||||
|
||||
* html #TB_window { /* ie6 hack */
|
||||
position: absolute;
|
||||
margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px');
|
||||
}
|
||||
|
||||
#TB_window img#TB_Image {
|
||||
display:block;
|
||||
margin: 15px 0 0 15px;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-top: 1px solid #666;
|
||||
border-left: 1px solid #666;
|
||||
}
|
||||
|
||||
#TB_caption{
|
||||
height:25px;
|
||||
padding:7px 30px 10px 25px;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#TB_closeWindow{
|
||||
height:25px;
|
||||
padding:11px 25px 10px 0;
|
||||
float:right;
|
||||
}
|
||||
|
||||
#TB_closeAjaxWindow{
|
||||
padding:7px 10px 5px 0;
|
||||
margin-bottom:1px;
|
||||
text-align:right;
|
||||
float:right;
|
||||
}
|
||||
|
||||
#TB_ajaxWindowTitle{
|
||||
float:left;
|
||||
padding:7px 0 5px 10px;
|
||||
margin-bottom:1px;
|
||||
}
|
||||
|
||||
#TB_title{
|
||||
background-color:#e8e8e8;
|
||||
height:27px;
|
||||
}
|
||||
|
||||
#TB_ajaxContent{
|
||||
clear:both;
|
||||
padding:2px 15px 15px 15px;
|
||||
overflow:auto;
|
||||
text-align:left;
|
||||
line-height:1.4em;
|
||||
}
|
||||
|
||||
#TB_ajaxContent.TB_modal{
|
||||
padding:15px;
|
||||
}
|
||||
|
||||
#TB_ajaxContent p{
|
||||
padding:5px 0px 5px 0px;
|
||||
}
|
||||
|
||||
#TB_load{
|
||||
position: fixed;
|
||||
display:none;
|
||||
height:13px;
|
||||
width:208px;
|
||||
z-index:103;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */
|
||||
}
|
||||
|
||||
* html #TB_load { /* ie6 hack */
|
||||
position: absolute;
|
||||
margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px');
|
||||
}
|
||||
|
||||
#TB_HideSelect{
|
||||
z-index:99;
|
||||
position:fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color:#fff;
|
||||
border:none;
|
||||
filter:alpha(opacity=0);
|
||||
-moz-opacity: 0;
|
||||
opacity: 0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
* html #TB_HideSelect { /* ie6 hack */
|
||||
position: absolute;
|
||||
height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
|
||||
}
|
||||
|
||||
#TB_iframeContent{
|
||||
clear:both;
|
||||
border:none;
|
||||
margin-bottom:-1px;
|
||||
margin-top:1px;
|
||||
_margin-bottom:1px;
|
||||
}
|
166
plugins/Autocomplete/jquery-autocomplete/todo
Normal file
166
plugins/Autocomplete/jquery-autocomplete/todo
Normal file
@@ -0,0 +1,166 @@
|
||||
TODO
|
||||
|
||||
- test formatItem implementation that returns (clickable) anchors
|
||||
- bug: handle del key; eg. type a letter, remove it using del, type same letter again: nothing happens
|
||||
- handle up/down keys in textarea (prevent default while select is open?)
|
||||
- docs: max:0 works, too, "removing" it(??)
|
||||
- fix ac_loading/options.loadingClass
|
||||
- support/enable request urls like foo/bar/10 instead of foo/q=10
|
||||
- urlencode request term before passing to $.ajax/data; evaluate why $.ajax doesn't handle that itself, if at all; try with umlauts, russian/danish/chinese characeters (see validate)
|
||||
- test what happens when an element gets focused programmatically (maybe even before then autcomplete is applied)
|
||||
- check if blur on selecting can be removed
|
||||
- fix keyhandling to ignore metakeys, eg. shift; especially important for chinese characters that need more then one key
|
||||
- enhance mustMatch: provide event/callback when a value gets deleted
|
||||
- handle tab key different then enter, eg. don't blur field or prevent default, just let it move on; in any case, no need to blur the field when selecting a value via tab, unlike return
|
||||
- prevent redundant requests on
|
||||
- superstring returned no result, no need to query again for substring, eg. pete returned nothing, peter won't either
|
||||
- previous query mustn't be requested again, eg. pete returns 10 lines, peter nothing, backspace to pete should get the 10 lines from cache (may need TimeToLive setting for cache to invalidate it)
|
||||
- incorporate improvements and suggestions by Hector: http://beta.winserver.com/public/test/MultiSuggestTest.wct
|
||||
- json support: An optional JSON format, that assumes a certain JSON format as default and just looks for a dataType "json" to be activated; [records], where each record is { id:String, label:String, moreOptionalValues... }
|
||||
- accept callback as first argument to let users implement their own dynamic data (no caching) - consider async API
|
||||
- allow users to keep their incomplete value when pressing tab, just mimic the default-browser-autocomplete: tab doesn't select any proposed value -> tab closes the select and works normal otherwise
|
||||
- small bug in your autocomplete, When setting autoFill:true I would expect formatResult to be called on autofill, it seems not to be the case.
|
||||
- add a callback to allow decoding the response
|
||||
- allow modification of not-last value in multiple-fields
|
||||
@option Number size Limit the number of items to show at once. Default:
|
||||
@option Function parse - TEST AND DOCUMENT ME
|
||||
- add option to display selectbox on focus
|
||||
|
||||
$input.bind("show", function() {
|
||||
if ( !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
});
|
||||
|
||||
- reference: http://capxous.com/
|
||||
- add "try ..." hints to demo
|
||||
- check out demos
|
||||
- reference: http://createwebapp.com/demo/
|
||||
|
||||
- add option to hide selectbox when no match is found - see comment by Ian on plugin page (14. Juli 2007 04:31)
|
||||
- add example for reinitializing an autocomplete using unbind()
|
||||
|
||||
- Add option to pass through additional arguments to $.ajax, like type to use POST instead of GET
|
||||
|
||||
- I found out that the problem with UTF-8 not being correctly sent can be solved on the server side by applying (PHP) rawurldecode() function, which decodes the Unicode characters sent by GET method and therefore URL-encoded.
|
||||
-> add that hint to docs and examples
|
||||
|
||||
But I am trying this with these three values: “foo bar”, “foo foo”, and “foo far”, and if I enter “b” (or “ba”) nothing matches, if I enter “f” all three do match, and if I enter “fa” the last one matches.
|
||||
The problem seems to be that the cache is implemented with a first-character hashtable, so only after matching the first character, the latter ones are searched for.
|
||||
|
||||
xml example:
|
||||
<script type="text/javascript">
|
||||
function parseXML(data) {
|
||||
var results = [];
|
||||
var branches = $(data).find('item');
|
||||
$(branches).each(function() {
|
||||
var text = $.trim($(this).find('text').text());
|
||||
var value = $.trim($(this).find('value').text());
|
||||
//console.log(text);
|
||||
//console.log(value);
|
||||
results[results.length] = {'data': this, 'result': value, 'value': text};
|
||||
});
|
||||
$(results).each(function() {
|
||||
//console.log('value', this.value);
|
||||
//console.log('text', this.text);
|
||||
});
|
||||
//console.log(results);
|
||||
return results;
|
||||
};
|
||||
$(YourOojHere).autocomplete(SERVER_AJAX_URL, {parse: parseXML});
|
||||
</script>
|
||||
<?xml version="1.0"?>
|
||||
<ajaxresponse>
|
||||
<item>
|
||||
<text>
|
||||
<![CDATA[<b>FreeNode:</b> irc.freenode.net:6667]]>
|
||||
</text>
|
||||
<value><![CDATA[irc.freenode.net:6667]]></value>
|
||||
</item><item>
|
||||
<text>
|
||||
<![CDATA[<b>irc.oftc.net</b>:6667]]>
|
||||
</text>
|
||||
<value><![CDATA[irc.oftc.net:6667]]></value>
|
||||
</item><item>
|
||||
<text>
|
||||
<![CDATA[<b>irc.undernet.org</b>:6667]]>
|
||||
</text>
|
||||
<value><![CDATA[irc.undernet.org:6667]]></value>
|
||||
</item>
|
||||
</ajaxresponse>
|
||||
|
||||
|
||||
|
||||
Hi all,
|
||||
|
||||
I use Autocomplete 1.0 Alpha mostly for form inputs bound to foreign
|
||||
key columns. For instance I have a user_position table with two
|
||||
columns: user_id and position_id. On new appointment form I have two
|
||||
autocomplete text inputs with the following code:
|
||||
|
||||
<input type="text" id="user_id" class="ac_input" tabindex="1" />
|
||||
<input type="text" id="position_id" class="ac_input" tabindex="2" />
|
||||
|
||||
As you can see the inputs do not have a name attribute, and when the
|
||||
form is submitted their values are not sent, which is all right since
|
||||
they will contain strings like:
|
||||
|
||||
'John Doe'
|
||||
'Sales Manager'
|
||||
|
||||
whereas our backend expects something like:
|
||||
|
||||
23
|
||||
14
|
||||
|
||||
which are the user_id for John Doe and position_id for Sales Manager.
|
||||
To send these values I have two hidden inputs in the form like this:
|
||||
|
||||
<input type="hidden" name="user_id" value="">
|
||||
<input type="hidden" name="position_id" value="">
|
||||
|
||||
Also I have the following code in the $().ready function:
|
||||
|
||||
$("#user_id").result(function(event, data, formatted) {
|
||||
$("input[@name=user_id]").val(data[1]);
|
||||
});
|
||||
$("#position_id").result(function(event, data, formatted) {
|
||||
$("input[@name=position_id]").val(data[1]);
|
||||
});
|
||||
|
||||
As could be seen these functions stuff user_id and position_id values
|
||||
(in our example 23 and 14) into the hidden inputs, and when the form
|
||||
is submitted these values are sent:
|
||||
|
||||
user_id = 23
|
||||
position_id = 14
|
||||
|
||||
The backend script then takes care of adding a record to our
|
||||
user_position table containing those values.
|
||||
|
||||
I wonder how could the plugin code be modified to simplify the setup
|
||||
by taking care of adding hidden inputs and updating the value of
|
||||
hidden inputs as default behavior. I have successfully attempted a
|
||||
simpler solution - writing a wrapper to perform these additional tasks
|
||||
and invoke autocomplete as well. I hope my intention is clear enough,
|
||||
if not, this is exactly the expected outcome:
|
||||
|
||||
Before:
|
||||
|
||||
<script type="text/javascript"
|
||||
src="jquery.autocomplete-modified.js"></script>
|
||||
<input type="text" name="user_id" class="ac_input" tabindex="1" />
|
||||
|
||||
After:
|
||||
|
||||
<input type="text" id="user_id" class="ac_input" tabindex="1" />
|
||||
<input type="hidden" name="user_id" value="23">
|
||||
|
||||
|
||||
Last word, I know this looks like a tall order, and I do not hope
|
||||
someone will make a complete working mod for me, but rather would very
|
||||
much appreciate helpful advise and directions.
|
||||
|
||||
Many thanks in advance
|
||||
Majid
|
||||
|
6
plugins/Autocomplete/readme.txt
Normal file
6
plugins/Autocomplete/readme.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Autocomplete allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user's friends' screen names.
|
||||
|
||||
Installation
|
||||
============
|
||||
Add "addPlugin('Autocomplete');" to the bottom of your config.php
|
||||
That's it!
|
@@ -47,9 +47,7 @@ class FBC_XDReceiverAction extends Action
|
||||
header('Expires:');
|
||||
header('Pragma:');
|
||||
|
||||
$this->startXML('html',
|
||||
'-//W3C//DTD XHTML 1.0 Strict//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
|
||||
$this->startXML('html');
|
||||
|
||||
$language = $this->getLanguage();
|
||||
|
||||
@@ -58,10 +56,7 @@ class FBC_XDReceiverAction extends Action
|
||||
'lang' => $language));
|
||||
$this->elementStart('head');
|
||||
$this->element('title', null, 'cross domain receiver page');
|
||||
$this->element('script',
|
||||
array('src' =>
|
||||
'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js',
|
||||
'type' => 'text/javascript'), '');
|
||||
$this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js');
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$this->elementEnd('body');
|
||||
|
@@ -31,27 +31,25 @@ require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
|
||||
|
||||
class FBConnectauthAction extends Action
|
||||
{
|
||||
|
||||
var $fbuid = null;
|
||||
var $fb_fields = null;
|
||||
|
||||
function prepare($args) {
|
||||
parent::prepare($args);
|
||||
|
||||
try {
|
||||
$this->fbuid = getFacebook()->get_loggedin_user();
|
||||
|
||||
$this->fbuid = getFacebook()->get_loggedin_user();
|
||||
if ($this->fbuid > 0) {
|
||||
$this->fb_fields = $this->getFacebookFields($this->fbuid,
|
||||
array('first_name', 'last_name', 'name'));
|
||||
} else {
|
||||
list($proxy, $ip) = common_client_ip();
|
||||
|
||||
if ($this->fbuid > 0) {
|
||||
$this->fb_fields = $this->getFacebookFields($this->fbuid,
|
||||
array('first_name', 'last_name', 'name'));
|
||||
} else {
|
||||
common_debug("No Facebook User found.");
|
||||
}
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
"Failed auth attempt, proxy = $proxy, ip = $ip.");
|
||||
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, 'Problem getting Facebook uid: ' .
|
||||
$e->getMessage());
|
||||
$this->clientError(_('You must be logged into Facebook to ' .
|
||||
'use Facebook Connect.'));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -69,8 +67,9 @@ class FBConnectauthAction extends Action
|
||||
if (!empty($flink)) {
|
||||
|
||||
// User already has a linked Facebook account and shouldn't be here
|
||||
common_debug('There is already a local user (' . $flink->user_id .
|
||||
') linked with this Facebook (' . $this->fbuid . ').');
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
'There is already a local user (' . $flink->user_id .
|
||||
') linked with this Facebook (' . $this->fbuid . ').');
|
||||
|
||||
// We don't want these cookies
|
||||
getFacebook()->clear_cookie_state();
|
||||
@@ -101,7 +100,8 @@ class FBConnectauthAction extends Action
|
||||
} else if ($this->arg('connect')) {
|
||||
$this->connectNewUser();
|
||||
} else {
|
||||
common_debug(print_r($this->args, true), __FILE__);
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
print_r($this->args, true));
|
||||
$this->showForm(_('Something weird happened.'),
|
||||
$this->trimmed('newname'));
|
||||
}
|
||||
@@ -211,7 +211,6 @@ class FBConnectauthAction extends Action
|
||||
|
||||
function createNewUser()
|
||||
{
|
||||
|
||||
if (common_config('site', 'closed')) {
|
||||
$this->clientError(_('Registration not allowed.'));
|
||||
return;
|
||||
@@ -238,7 +237,7 @@ class FBConnectauthAction extends Action
|
||||
|
||||
if (!Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +273,8 @@ class FBConnectauthAction extends Action
|
||||
common_set_user($user);
|
||||
common_real_login(true);
|
||||
|
||||
common_debug("Registered new user $user->id from Facebook user $this->fbuid");
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"Registered new user $user->id from Facebook user $this->fbuid");
|
||||
|
||||
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
|
||||
303);
|
||||
@@ -292,8 +292,9 @@ class FBConnectauthAction extends Action
|
||||
|
||||
$user = User::staticGet('nickname', $nickname);
|
||||
|
||||
if ($user) {
|
||||
common_debug("Legit user to connect to Facebook: $nickname");
|
||||
if (!empty($user)) {
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"Legit user to connect to Facebook: $nickname");
|
||||
}
|
||||
|
||||
$result = $this->flinkUser($user->id, $this->fbuid);
|
||||
@@ -303,7 +304,8 @@ class FBConnectauthAction extends Action
|
||||
return;
|
||||
}
|
||||
|
||||
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
|
||||
common_debug('Facebook Connnect Plugin - ' .
|
||||
"Connected Facebook user $this->fbuid to local user $user->id");
|
||||
|
||||
common_set_user($user);
|
||||
common_real_login(true);
|
||||
@@ -317,12 +319,13 @@ class FBConnectauthAction extends Action
|
||||
|
||||
$result = $this->flinkUser($user->id, $this->fbuid);
|
||||
|
||||
if (!$result) {
|
||||
if (empty($result)) {
|
||||
$this->serverError(_('Error connecting user to Facebook.'));
|
||||
return;
|
||||
}
|
||||
|
||||
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"Connected Facebook user $this->fbuid to local user $user->id");
|
||||
|
||||
// Return to Facebook connection settings tab
|
||||
common_redirect(common_local_url('FBConnectSettings'), 303);
|
||||
@@ -330,16 +333,18 @@ class FBConnectauthAction extends Action
|
||||
|
||||
function tryLogin()
|
||||
{
|
||||
common_debug("Trying Facebook Login...");
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"Trying login for Facebook user $this->fbuid.");
|
||||
|
||||
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
|
||||
|
||||
if ($flink) {
|
||||
if (!empty($flink)) {
|
||||
$user = $flink->getUser();
|
||||
|
||||
if (!empty($user)) {
|
||||
|
||||
common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
|
||||
|
||||
common_set_user($user);
|
||||
common_real_login(true);
|
||||
@@ -348,7 +353,8 @@ class FBConnectauthAction extends Action
|
||||
|
||||
} else {
|
||||
|
||||
common_debug("No flink found for fbuid: $this->fbuid");
|
||||
common_debug('Facebook Connect Plugin - ' .
|
||||
"No flink found for fbuid: $this->fbuid - new user");
|
||||
|
||||
$this->showForm(null, $this->bestNewNickname());
|
||||
}
|
||||
@@ -418,7 +424,7 @@ class FBConnectauthAction extends Action
|
||||
{
|
||||
if (!Validate::string($str, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
return false;
|
||||
}
|
||||
if (!User::allowed_nickname($str)) {
|
||||
@@ -444,7 +450,8 @@ class FBConnectauthAction extends Action
|
||||
return reset($infos);
|
||||
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, "Facebook client failure when requesting " .
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
"Facebook client failure when requesting " .
|
||||
join(",", $fields) . " on uid " . $fb_uid .
|
||||
" : ". $e->getMessage());
|
||||
return null;
|
||||
|
@@ -82,9 +82,7 @@ class FBConnectPlugin extends Plugin
|
||||
|
||||
$action->extraHeaders();
|
||||
|
||||
$action->startXML('html',
|
||||
'-//W3C//DTD XHTML 1.0 Strict//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
|
||||
$action->startXML('html');
|
||||
|
||||
$language = $action->getLanguage();
|
||||
|
||||
@@ -118,13 +116,13 @@ class FBConnectPlugin extends Plugin
|
||||
// but we actually do, for IE and Safari. Gar.
|
||||
|
||||
$html = sprintf('<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
$(document).ready(function () {
|
||||
FB_RequireFeatures(
|
||||
["XFBML"],
|
||||
function() {
|
||||
FB.init("%s", "../xd_receiver.html");
|
||||
}
|
||||
); }
|
||||
); });
|
||||
|
||||
function goto_login() {
|
||||
window.location = "%s";
|
||||
@@ -146,11 +144,7 @@ class FBConnectPlugin extends Plugin
|
||||
function onEndShowFooter($action)
|
||||
{
|
||||
if ($this->reqFbScripts($action)) {
|
||||
|
||||
$action->element('script',
|
||||
array('type' => 'text/javascript',
|
||||
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
|
||||
'');
|
||||
$action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,10 +152,7 @@ class FBConnectPlugin extends Plugin
|
||||
{
|
||||
|
||||
if ($this->reqFbScripts($action)) {
|
||||
|
||||
$action->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => common_path('plugins/FBConnect/FBConnectPlugin.css')));
|
||||
$action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +214,7 @@ class FBConnectPlugin extends Plugin
|
||||
$fbuid = $facebook->get_loggedin_user();
|
||||
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING,
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
'Problem getting Facebook user: ' .
|
||||
$e->getMessage());
|
||||
}
|
||||
@@ -351,7 +342,7 @@ class FBConnectPlugin extends Plugin
|
||||
}
|
||||
|
||||
function onStartLogout($action)
|
||||
{
|
||||
{
|
||||
$action->logout();
|
||||
$fbuid = $this->loggedIn();
|
||||
|
||||
@@ -360,8 +351,9 @@ class FBConnectPlugin extends Plugin
|
||||
$facebook = getFacebook();
|
||||
$facebook->expire_session();
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
|
||||
$e->getMessage());
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
'Could\'t logout of Facebook: ' .
|
||||
$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +377,8 @@ class FBConnectPlugin extends Plugin
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, "Facebook client failure requesting profile pic!");
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
"Facebook client failure requesting profile pic!");
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
@@ -186,9 +186,9 @@ class FBConnectSettingsAction extends ConnectSettingsAction
|
||||
$facebook->clear_cookie_state();
|
||||
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING,
|
||||
'Couldn\'t clear Facebook cookies: ' .
|
||||
$e->getMessage());
|
||||
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
||||
'Couldn\'t clear Facebook cookies: ' .
|
||||
$e->getMessage());
|
||||
}
|
||||
|
||||
$this->showForm(_('You have disconnected from Facebook.'), true);
|
||||
|
@@ -43,8 +43,7 @@ API key and secret to your Laconica config.php file:
|
||||
Finally, to enable the plugin, add the following stanza to your
|
||||
config.php:
|
||||
|
||||
require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
|
||||
$fbc = new FBConnectPlugin();
|
||||
addPlugin('FBConnect');
|
||||
|
||||
To try out the plugin, fire up your browser and connect to:
|
||||
|
||||
|
46
plugins/InfiniteScroll/InfiniteScrollPlugin.php
Normal file
46
plugins/InfiniteScroll/InfiniteScrollPlugin.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Plugin to enable Infinite Scrolling
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package Laconica
|
||||
* @author Craig Andrews <candrews@integralblue.com>
|
||||
* @copyright 2009 Craig Andrews http://candrews.integralblue.com
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class InfiniteScrollPlugin extends Plugin
|
||||
{
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function onEndShowScripts($action)
|
||||
{
|
||||
$action->script('plugins/InfiniteScroll/jquery.infinitescroll.min.js');
|
||||
$action->script('plugins/InfiniteScroll/infinitescroll.js');
|
||||
}
|
||||
}
|
BIN
plugins/InfiniteScroll/ajax-loader.gif
Normal file
BIN
plugins/InfiniteScroll/ajax-loader.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
15
plugins/InfiniteScroll/infinitescroll.js
Normal file
15
plugins/InfiniteScroll/infinitescroll.js
Normal file
@@ -0,0 +1,15 @@
|
||||
jQuery(document).ready(function($){
|
||||
$('notices_primary').infinitescroll({
|
||||
debug: true,
|
||||
nextSelector : "li.nav_next a",
|
||||
loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif',
|
||||
text : "<em>Loading the next set of posts...</em>",
|
||||
donetext : "<em>Congratulations, you\'ve reached the end of the Internet.</em>",
|
||||
navSelector : "div.pagination",
|
||||
contentSelector : "#notices_primary ol.notices",
|
||||
itemSelector : "#notices_primary ol.notices li"
|
||||
},function(){
|
||||
NoticeAttachments();
|
||||
});
|
||||
});
|
||||
|
251
plugins/InfiniteScroll/jquery.infinitescroll.js
Normal file
251
plugins/InfiniteScroll/jquery.infinitescroll.js
Normal file
@@ -0,0 +1,251 @@
|
||||
|
||||
/*!
|
||||
// Infinite Scroll jQuery plugin
|
||||
// copyright Paul Irish, licensed GPL & MIT
|
||||
// version 1.2.090804
|
||||
|
||||
// home and docs: http://www.infinite-scroll.com
|
||||
*/
|
||||
|
||||
// todo: add preloading option.
|
||||
|
||||
;(function($){
|
||||
|
||||
$.fn.infinitescroll = function(options,callback){
|
||||
|
||||
// console log wrapper.
|
||||
function debug(){
|
||||
if (opts.debug) { window.console && console.log.call(console,arguments)}
|
||||
}
|
||||
|
||||
// grab each selector option and see if any fail.
|
||||
function areSelectorsValid(opts){
|
||||
for (var key in opts){
|
||||
if (key.indexOf && key.indexOf('Selector') && $(opts[key]).length === 0){
|
||||
debug('Your ' + key + ' found no elements.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// find the number to increment in the path.
|
||||
function determinePath(path){
|
||||
|
||||
path.match(relurl) ? path.match(relurl)[2] : path;
|
||||
|
||||
// there is a 2 in the url surrounded by slashes, e.g. /page/2/
|
||||
if ( path.match(/^(.*?)\b2\b(.*?$)/) ){
|
||||
path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
|
||||
} else
|
||||
// if there is any 2 in the url at all.
|
||||
if (path.match(/^(.*?)2(.*?$)/)){
|
||||
debug('Trying backup next selector parse technique. Treacherous waters here, matey.');
|
||||
path = path.match(/^(.*?)2(.*?$)/).slice(1);
|
||||
} else {
|
||||
debug('Sorry, we couldn\'t parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.');
|
||||
props.isInvalidPage = true; //prevent it from running on this page.
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
// 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
|
||||
function getDocumentHeight(){
|
||||
// weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
|
||||
return opts.localMode ? ($(props.container)[0].scrollHeight && $(props.container)[0].scrollHeight)
|
||||
// needs to be document's height. (not props.container's) html's height is wrong in IE.
|
||||
: $(document).height()
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isNearBottom(opts,props){
|
||||
|
||||
// distance remaining in the scroll
|
||||
// computed as: document height - distance already scroll - viewport height - buffer
|
||||
var pixelsFromWindowBottomToBottom = getDocumentHeight() -
|
||||
(opts.localMode ? $(props.container).scrollTop() :
|
||||
// have to do this bs because safari doesnt report a scrollTop on the html element
|
||||
($(props.container).scrollTop() || $(props.container.ownerDocument.body).scrollTop())) -
|
||||
$(opts.localMode ? props.container : window).height();
|
||||
|
||||
debug('math:',pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
|
||||
|
||||
// if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
|
||||
return (pixelsFromWindowBottomToBottom - opts.bufferPx < props.pixelsFromNavToBottom);
|
||||
}
|
||||
|
||||
function showDoneMsg(){
|
||||
props.loadingMsg
|
||||
.find('img').hide()
|
||||
.parent()
|
||||
.find('div').html(opts.donetext).animate({opacity: 1},2000).fadeOut('normal');
|
||||
|
||||
// user provided callback when done
|
||||
opts.errorCallback();
|
||||
}
|
||||
|
||||
function infscrSetup(path,opts,props,callback){
|
||||
|
||||
if (props.isDuringAjax || props.isInvalidPage || props.isDone) return;
|
||||
|
||||
if ( !isNearBottom(opts,props) ) return;
|
||||
|
||||
// we dont want to fire the ajax multiple times
|
||||
props.isDuringAjax = true;
|
||||
|
||||
// show the loading message and hide the previous/next links
|
||||
props.loadingMsg.appendTo( opts.contentSelector ).show();
|
||||
$( opts.navSelector ).hide();
|
||||
|
||||
// increment the URL bit. e.g. /page/3/
|
||||
props.currPage++;
|
||||
|
||||
debug('heading into ajax',path);
|
||||
|
||||
// if we're dealing with a table we can't use DIVs
|
||||
var box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');
|
||||
|
||||
box
|
||||
.attr('id','infscr-page-'+props.currPage)
|
||||
.addClass('infscr-pages')
|
||||
.appendTo( opts.contentSelector )
|
||||
.load( path.join( props.currPage ) + ' ' + opts.itemSelector,null,function(){
|
||||
|
||||
// if we've hit the last page...
|
||||
if (props.isDone){
|
||||
showDoneMsg();
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// if it didn't return anything
|
||||
if (box.children().length == 0){
|
||||
// fake an ajaxError so we can quit.
|
||||
$.event.trigger( "ajaxError", [{status:404}] );
|
||||
}
|
||||
|
||||
// fadeout currently makes the <em>'d text ugly in IE6
|
||||
props.loadingMsg.fadeOut('normal' );
|
||||
|
||||
// smooth scroll to ease in the new content
|
||||
if (opts.animate){
|
||||
var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
|
||||
$('html,body').animate({scrollTop: scrollTo}, 800,function(){ props.isDuringAjax = false; });
|
||||
}
|
||||
|
||||
// pass in the new DOM element as context for the callback
|
||||
callback.call( box[0] );
|
||||
|
||||
if (!opts.animate) props.isDuringAjax = false; // once the call is done, we can allow it again.
|
||||
}
|
||||
}); // end of load()
|
||||
|
||||
|
||||
} // end of infscrSetup()
|
||||
|
||||
|
||||
|
||||
|
||||
// lets get started.
|
||||
|
||||
var opts = $.extend({}, $.infinitescroll.defaults, options);
|
||||
var props = $.infinitescroll; // shorthand
|
||||
callback = callback || function(){};
|
||||
|
||||
if (!areSelectorsValid(opts)){ return false; }
|
||||
|
||||
// we doing this on an overflow:auto div?
|
||||
props.container = opts.localMode ? this : document.documentElement;
|
||||
|
||||
// contentSelector we'll use for our .load()
|
||||
opts.contentSelector = opts.contentSelector || this;
|
||||
|
||||
|
||||
// get the relative URL - everything past the domain name.
|
||||
var relurl = /(.*?\/\/).*?(\/.*)/;
|
||||
var path = $(opts.nextSelector).attr('href');
|
||||
|
||||
|
||||
if (!path) { debug('Navigation selector not found'); return; }
|
||||
|
||||
// set the path to be a relative URL from root.
|
||||
path = determinePath(path);
|
||||
|
||||
|
||||
// reset scrollTop in case of page refresh:
|
||||
if (opts.localMode) $(props.container)[0].scrollTop = 0;
|
||||
|
||||
// distance from nav links to bottom
|
||||
// computed as: height of the document + top offset of container - top offset of nav link
|
||||
props.pixelsFromNavToBottom = getDocumentHeight() +
|
||||
$(props.container).offset().top -
|
||||
$(opts.navSelector).offset().top;
|
||||
|
||||
// define loading msg
|
||||
props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+
|
||||
opts.loadingImg+'" /><div>'+opts.loadingText+'</div></div>');
|
||||
// preload the image
|
||||
(new Image()).src = opts.loadingImg;
|
||||
|
||||
|
||||
|
||||
// set up our bindings
|
||||
$(document).ajaxError(function(e,xhr,opt){
|
||||
debug('Page not found. Self-destructing...');
|
||||
|
||||
// die if we're out of pages.
|
||||
if (xhr.status == 404){
|
||||
showDoneMsg();
|
||||
props.isDone = true;
|
||||
$(opts.localMode ? this : window).unbind('scroll.infscr');
|
||||
}
|
||||
});
|
||||
|
||||
// bind scroll handler to element (if its a local scroll) or window
|
||||
$(opts.localMode ? this : window)
|
||||
.bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
|
||||
.trigger('scroll.infscr'); // trigger the event, in case it's a short page
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
} // end of $.fn.infinitescroll()
|
||||
|
||||
|
||||
|
||||
// options and read-only properties object
|
||||
|
||||
$.infinitescroll = {
|
||||
defaults : {
|
||||
debug : false,
|
||||
preload : false,
|
||||
nextSelector : "div.navigation a:first",
|
||||
loadingImg : "http://www.infinite-scroll.com/loading.gif",
|
||||
loadingText : "<em>Loading the next set of posts...</em>",
|
||||
donetext : "<em>Congratulations, you've reached the end of the internet.</em>",
|
||||
navSelector : "div.navigation",
|
||||
contentSelector : null, // not really a selector. :) it's whatever the method was called on..
|
||||
extraScrollPx : 150,
|
||||
itemSelector : "div.post",
|
||||
animate : false,
|
||||
localMode : false,
|
||||
bufferPx : 40,
|
||||
errorCallback : function(){}
|
||||
},
|
||||
loadingImg : undefined,
|
||||
loadingMsg : undefined,
|
||||
container : undefined,
|
||||
currPage : 1,
|
||||
currDOMChunk : null, // defined in setup()'s load()
|
||||
isDuringAjax : false,
|
||||
isInvalidPage : false,
|
||||
isDone : false // for when it goes all the way through the archive.
|
||||
};
|
||||
|
||||
|
||||
|
||||
})(jQuery);
|
8
plugins/InfiniteScroll/jquery.infinitescroll.min.js
vendored
Normal file
8
plugins/InfiniteScroll/jquery.infinitescroll.min.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
// Infinite Scroll jQuery plugin
|
||||
// copyright Paul Irish, licensed GPL & MIT
|
||||
// version 1.2.090804
|
||||
|
||||
// home and docs: http://www.infinite-scroll.com
|
||||
*/
|
||||
(function(A){A.fn.infinitescroll=function(N,L){function E(){if(B.debug){window.console&&console.log.call(console,arguments)}}function G(P){for(var O in P){if(O.indexOf&&O.indexOf("Selector")&&A(P[O]).length===0){E("Your "+O+" found no elements.");return false}return true}}function K(O){O.match(C)?O.match(C)[2]:O;if(O.match(/^(.*?)\b2\b(.*?$)/)){O=O.match(/^(.*?)\b2\b(.*?$)/).slice(1)}else{if(O.match(/^(.*?)2(.*?$)/)){E("Trying backup next selector parse technique. Treacherous waters here, matey.");O=O.match(/^(.*?)2(.*?$)/).slice(1)}else{E("Sorry, we couldn't parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.");H.isInvalidPage=true}}return O}function I(){return B.localMode?(A(H.container)[0].scrollHeight&&A(H.container)[0].scrollHeight):A(document).height()}function F(Q,P){var O=I()-(Q.localMode?A(P.container).scrollTop():(A(P.container).scrollTop()||A(P.container.ownerDocument.body).scrollTop()))-A(Q.localMode?P.container:window).height();E("math:",O,P.pixelsFromNavToBottom);return(O-Q.bufferPx<P.pixelsFromNavToBottom)}function J(){H.loadingMsg.find("img").hide().parent().find("div").html(B.donetext).animate({opacity:1},2000).fadeOut("normal");B.errorCallback()}function D(R,Q,O,S){if(O.isDuringAjax||O.isInvalidPage||O.isDone){return }if(!F(Q,O)){return }O.isDuringAjax=true;O.loadingMsg.appendTo(Q.contentSelector).show();A(Q.navSelector).hide();O.currPage++;E("heading into ajax",R);var P=A(Q.contentSelector).is("table")?A("<tbody/>"):A("<div/>");P.attr("id","infscr-page-"+O.currPage).addClass("infscr-pages").appendTo(Q.contentSelector).load(R.join(O.currPage)+" "+Q.itemSelector,null,function(){if(O.isDone){J();return false}else{if(P.children().length==0){A.event.trigger("ajaxError",[{status:404}])}O.loadingMsg.fadeOut("normal");if(Q.animate){var T=A(window).scrollTop()+A("#infscr-loading").height()+Q.extraScrollPx+"px";A("html,body").animate({scrollTop:T},800,function(){O.isDuringAjax=false})}S.call(P[0]);if(!Q.animate){O.isDuringAjax=false}}})}var B=A.extend({},A.infinitescroll.defaults,N);var H=A.infinitescroll;L=L||function(){};if(!G(B)){return false}H.container=B.localMode?this:document.documentElement;B.contentSelector=B.contentSelector||this;var C=/(.*?\/\/).*?(\/.*)/;var M=A(B.nextSelector).attr("href");if(!M){E("Navigation selector not found");return }M=K(M);if(B.localMode){A(H.container)[0].scrollTop=0}H.pixelsFromNavToBottom=I()+A(H.container).offset().top-A(B.navSelector).offset().top;H.loadingMsg=A('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+B.loadingImg+'" /><div>'+B.loadingText+"</div></div>");(new Image()).src=B.loadingImg;A(document).ajaxError(function(P,Q,O){E("Page not found. Self-destructing...");if(Q.status==404){J();H.isDone=true;A(B.localMode?this:window).unbind("scroll.infscr")}});A(B.localMode?this:window).bind("scroll.infscr",function(){D(M,B,H,L)}).trigger("scroll.infscr");return this};A.infinitescroll={defaults:{debug:false,preload:false,nextSelector:"div.navigation a:first",loadingImg:"http://www.infinite-scroll.com/loading.gif",loadingText:"<em>Loading the next set of posts...</em>",donetext:"<em>Congratulations, you've reached the end of the internet.</em>",navSelector:"div.navigation",contentSelector:null,extraScrollPx:150,itemSelector:"div.post",animate:false,localMode:false,bufferPx:40,errorCallback:function(){}},loadingImg:undefined,loadingMsg:undefined,container:undefined,currPage:1,currDOMChunk:null,isDuringAjax:false,isInvalidPage:false,isDone:false}})(jQuery);
|
6
plugins/InfiniteScroll/readme.txt
Normal file
6
plugins/InfiniteScroll/readme.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Infinite Scroll adds the following functionality to your Laconica installation: When a user scrolls towards the bottom of the page, the next page of notices is automatically retrieved and appended. This means they never need to click "Next Page", which dramatically increases stickiness.
|
||||
|
||||
Installation
|
||||
============
|
||||
Add "addPlugin('InfiniteScroll');" to the bottom of your config.php
|
||||
That's it!
|
@@ -30,7 +30,9 @@ class FinishopenidloginAction extends Action
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (common_is_real_login()) {
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else if (common_is_real_login()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$token = $this->trimmed('token');
|
||||
@@ -217,7 +219,7 @@ class FinishopenidloginAction extends Action
|
||||
|
||||
if (!Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
||||
return;
|
||||
}
|
||||
@@ -389,7 +391,7 @@ class FinishopenidloginAction extends Action
|
||||
{
|
||||
if (!Validate::string($str, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
return false;
|
||||
}
|
||||
if (!User::allowed_nickname($str)) {
|
||||
|
@@ -26,7 +26,9 @@ class OpenidloginAction extends Action
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (common_is_real_login()) {
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else if (common_is_real_login()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$openid_url = $this->trimmed('openid_url');
|
||||
|
@@ -82,6 +82,12 @@ class OpenidsettingsAction extends AccountSettingsAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_('OpenID is not available.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
|
@@ -84,9 +84,7 @@ class RealtimePlugin extends Plugin
|
||||
$scripts = $this->_getScripts();
|
||||
|
||||
foreach ($scripts as $script) {
|
||||
$action->element('script', array('type' => 'text/javascript',
|
||||
'src' => $script),
|
||||
' ');
|
||||
$action->script($script);
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
@@ -201,8 +199,8 @@ class RealtimePlugin extends Plugin
|
||||
|
||||
function _getScripts()
|
||||
{
|
||||
return array(common_path('plugins/Realtime/realtimeupdate.js'),
|
||||
common_path('plugins/Realtime/json2.js'));
|
||||
return array('plugins/Realtime/realtimeupdate.js',
|
||||
'plugins/Realtime/json2.js');
|
||||
}
|
||||
|
||||
function _updateInitialize($timeline, $user_id)
|
||||
|
@@ -65,9 +65,7 @@ class recaptcha extends Plugin
|
||||
|
||||
$action->extraHeaders();
|
||||
|
||||
$action->startXML('html',
|
||||
'-//W3C//DTD XHTML 1.0 Strict//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
|
||||
$action->startXML('html');
|
||||
|
||||
$action->raw('<style type="text/css">#recaptcha_area{float:left;}</style>');
|
||||
return false;
|
||||
|
Reference in New Issue
Block a user