Extension:QuickLink
From MediaWiki.org
| This extension stores its source code on a wiki page. Please be aware that this code may be unreviewed or maliciously altered. They may contain security holes, outdated interfaces that are no longer compatible etc. Note: No localisation updates are provided for this extension by translatewiki.net. |
|
QuickLink Release status: beta |
|||
|---|---|---|---|
| Implementation | Search, Ajax | ||
| Description | Adds a fast link search-and-insert feature to the edit page. | ||
| Author(s) | Fran Rodríguez (Kanortalk) | ||
| Last version | 0.7 | ||
| MediaWiki | 1.15 | ||
| License | GPL | ||
| Download | Get the latest version in http://subtrama.net/quicklink/ | ||
| Example | Subtrama.net | ||
|
|||
| Check usage and version matrix | |||
QuickLink provides two efficient ways to search and insert internal links when editing a page:
- Typing '[[' in the textbox will trigger a search, showing the matching pages in your wiki as you type. Selecting a result inserts a link to the selected page.
- Also, search can be also triggered by pressing
CTRL+Enter. The search field will be then focused, allowing you to navigate results using the arrow keys and select one usingEnter.
You can see a live demo here.
Contents |
Usage [edit]
To perform a search follow these instructions:
- Go to the search box by:
- clicking in the search box
- pressing
CTRL+Enter
- Type a string contained in the title of the page you want to link. It doesn't need to be the first word in the title.
- When the results are shown:
- click one of them
- use the arrows and press
Enter
- Cancel search and go back to the textbox anytime by pressing
ESC
Also, inline search can be performed while writing in the textbox:
- Enter the internal link sequence '[[' to trigger the search
- Type the search string
- Results will be shown in the search area as you type
- When search is complete:
- Navigate results using the
upanddownarrows and insert selected result by pressingEnteror therightarrow. - Insert any result by clicking on it.
- Navigate results using the
- Cancel search anytime by pressing
ESC, theleftarrow key or entering the internal link closing sequence ']]'
Installation [edit]
- Download the latest version from http://subtrama.net/quicklink/.
- Extract the files and put them in
$IP/extensions/QuickLink/. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php. - Add the line
to your LocalSettings.php
require_once("$IP/extensions/QuickLink/QuickLink.php");
Known issues [edit]
Caret behaves strangely in IE in version 0.7 when inserting a link.
Changelog [edit]
- Version 0.7
- (Incomplete) IE support
- Improved navigation: ']' and arrow keys cancel in-text search
- Search extended to all namespaces
- Searchbox appearance in WebKit
- Version 0.6
- Search results can be selected with the arrows
- Pressing enter now selects the highlighted result
- New theme
- Version 0.5
- Search box in the toolbar
- Search triggered with CTRL+Enter and writing the '[[' sequence in the textarea
coding [edit]
QuickLink.php [edit]
<?php
/**
* QuickLink extension
* @author Fran Rodríguez
* @copyright © 2008 Adam Meyer, © 2009 Francisco J. R. Prados
*/
//See below under "function wfAjaxQuickLink" for configuration
if( !defined( 'MEDIAWIKI' ) ) {
echo( "This file is part of an extension to the MediaWiki software and cannot be used standalone.\n" );
die( 1 );
}
$wgExtensionFunctions[] = 'wfAjaxLink';
$wgAjaxExportList[] = 'wfAjaxQuickLink';
$wgHooks['AlternateEdit'][] = 'wfAjaxQuickLinkHeaders';
$wgExtensionCredits['other'][] = array(
'name'=> 'QuickLink',
'authors'=> 'Fran Rodríguez',
'version'=> '0.6',
'url' => 'http://www.mediawiki.org/wiki/Extension:QuickLink',
'description'=> 'Edit page built-in search box which provides a very quick and efficient way of inserting internal links.'
);
/*
* Finds string $str in array $array
* Used to find namespace name
*/
function wfQuickLinkNSSearch($str, $arr) {
$len = strlen($str);
foreach ($arr as $k => $v)
if (!strcasecmp($str, substr($v, 0, $len)))
return $k;
return FALSE;
}
function wfAjaxLink() {
global $wgUseAjax, $wgOut, $wgTitle;
if (!$wgUseAjax) {
wfDebug('wfAjaxLink: $wgUseAjax is not enabled, aborting extension setup.');
return;
}
}
function wfAjaxQuickLink( $term ) {
//configure the following to change settings
$limit = 8; //number of results to spit back
$location = 1; //set to 1 to search anywhere in the article name, set to 0 to search only at the begining
global $wgCanonicalNamespaceNames, $wgNamespaceAliases;
global $wgContLang, $wgOut;
$response = new AjaxResponse();
$db = wfGetDB( DB_SLAVE );
$l = new Linker;
$term = str_replace(' ', '_', $term);
// if ':' is found in the search term, limit search to that namespace
$use_ns = substr($term, 0, strpos($term, ':'));
$search_ns = "TRUE";
if ($use_ns) {
$term = preg_replace('/^[^:]+:/', '', $term);
$ns_number = wfQuickLinkNSSearch($use_ns, $wgCanonicalNamespaceNames);
if ($ns_number === FALSE)
$ns_number = wfQuickLinkNSSearch($use_ns, $wgNamespaceAliases);
if ($ns_number !== FALSE) {
$ons_number = $ns_number;
if ($ns_number == NS_MEDIA) $ns_number = NS_FILE;
$search_ns = "page_namespace=".$ns_number;
}
}
if($location == 1)
{
$res = $db->select( 'page', array('page_namespace', 'page_title'),
array(
$search_ns,
'page_namespace >=0',
"UPPER(CONVERT(page_title USING utf8)) LIKE '%" .$db->strencode( strtoupper ($term)). "%'"
),
"wfSajaxSearch",
array( 'LIMIT' => $limit )
);
}
else
{
$res = $db->select( 'page', array('page_namespace', 'page_title'),
array(
$search_ns,
'page_namespace >=0',
"UPPER(CONVERT(page_title USING utf8)) LIKE '". $db->strencode( strtoupper ($term)) ."%'"
),
"wfSajaxSearch",
array( 'LIMIT' => $limit )
);
}
// iterate through the results and create match list
$r = "";
$i = 0;
while ($row = $db->fetchObject( $res ) ){
$ns = "";
if($row->page_namespace > 0)
// For all namespaces except main, add 'namespace:' to link.
$ns = $wgCanonicalNamespaceNames[$row->page_namespace].":";
$r .= '<li><a name="QuickLinkResult'.$i.'" class="unselected" onmouseover="QuickLink.selectResult('.$i.')" onclick="javascript:QuickLink.insertLink(\''.$ns.addslashes(str_replace('_', ' ', $row->page_title)).'\')">'.$ns.str_replace('_', ' ', $row->page_title). "</a></li>\n";
$i++;
}
if ($r == "")
$html = "<p>No results found with the term '$term'</p><p>Edit to search again | Esc: cancel</p>";
else
$html = "<ul>$r</ul><p>Click to insert link | Esc: cancel</p>";
return $html;
}
function wfAjaxQuickLinkHeaders(){
global $wgOut;
wfAjaxSetQuickLinkHeaders($wgOut);
return true;
}
function wfAjaxSetQuickLinkHeaders($outputPage) {
global $wgJsMimeType, $wgStylePath, $wgScriptPath;
$outputPage->addLink(
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => $wgScriptPath.'/extensions/QuickLink/QuickLink.css'
)
);
//$outputPage->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$wgScriptPath/extensions/QuickLink/vegui.sk.formtools.js\">"."</script>\n");
$outputPage->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$wgScriptPath/extensions/QuickLink/QuickLink.js\">"."</script>\n");
$outputPage->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", QuickLink.load);</script>\n" );
}
/**
* QuickLink extension
* @author Fran Rodríguez
* @copyright © 2008 Adam Meyer, © 2009 Francisco J. R. Prados
*/
//See below under "function wfAjaxQuickLink" for configuration
if( !defined( 'MEDIAWIKI' ) ) {
echo( "This file is part of an extension to the MediaWiki software and cannot be used standalone.\n" );
die( 1 );
}
$wgExtensionFunctions[] = 'wfAjaxLink';
$wgAjaxExportList[] = 'wfAjaxQuickLink';
$wgHooks['AlternateEdit'][] = 'wfAjaxQuickLinkHeaders';
$wgExtensionCredits['other'][] = array(
'name'=> 'QuickLink',
'authors'=> 'Fran Rodríguez',
'version'=> '0.6',
'url' => 'http://www.mediawiki.org/wiki/Extension:QuickLink',
'description'=> 'Edit page built-in search box which provides a very quick and efficient way of inserting internal links.'
);
/*
* Finds string $str in array $array
* Used to find namespace name
*/
function wfQuickLinkNSSearch($str, $arr) {
$len = strlen($str);
foreach ($arr as $k => $v)
if (!strcasecmp($str, substr($v, 0, $len)))
return $k;
return FALSE;
}
function wfAjaxLink() {
global $wgUseAjax, $wgOut, $wgTitle;
if (!$wgUseAjax) {
wfDebug('wfAjaxLink: $wgUseAjax is not enabled, aborting extension setup.');
return;
}
}
function wfAjaxQuickLink( $term ) {
//configure the following to change settings
$limit = 8; //number of results to spit back
$location = 1; //set to 1 to search anywhere in the article name, set to 0 to search only at the begining
global $wgCanonicalNamespaceNames, $wgNamespaceAliases;
global $wgContLang, $wgOut;
$response = new AjaxResponse();
$db = wfGetDB( DB_SLAVE );
$l = new Linker;
$term = str_replace(' ', '_', $term);
// if ':' is found in the search term, limit search to that namespace
$use_ns = substr($term, 0, strpos($term, ':'));
$search_ns = "TRUE";
if ($use_ns) {
$term = preg_replace('/^[^:]+:/', '', $term);
$ns_number = wfQuickLinkNSSearch($use_ns, $wgCanonicalNamespaceNames);
if ($ns_number === FALSE)
$ns_number = wfQuickLinkNSSearch($use_ns, $wgNamespaceAliases);
if ($ns_number !== FALSE) {
$ons_number = $ns_number;
if ($ns_number == NS_MEDIA) $ns_number = NS_FILE;
$search_ns = "page_namespace=".$ns_number;
}
}
if($location == 1)
{
$res = $db->select( 'page', array('page_namespace', 'page_title'),
array(
$search_ns,
'page_namespace >=0',
"UPPER(CONVERT(page_title USING utf8)) LIKE '%" .$db->strencode( strtoupper ($term)). "%'"
),
"wfSajaxSearch",
array( 'LIMIT' => $limit )
);
}
else
{
$res = $db->select( 'page', array('page_namespace', 'page_title'),
array(
$search_ns,
'page_namespace >=0',
"UPPER(CONVERT(page_title USING utf8)) LIKE '". $db->strencode( strtoupper ($term)) ."%'"
),
"wfSajaxSearch",
array( 'LIMIT' => $limit )
);
}
// iterate through the results and create match list
$r = "";
$i = 0;
while ($row = $db->fetchObject( $res ) ){
$ns = "";
if($row->page_namespace > 0)
// For all namespaces except main, add 'namespace:' to link.
$ns = $wgCanonicalNamespaceNames[$row->page_namespace].":";
$r .= '<li><a name="QuickLinkResult'.$i.'" class="unselected" onmouseover="QuickLink.selectResult('.$i.')" onclick="javascript:QuickLink.insertLink(\''.$ns.addslashes(str_replace('_', ' ', $row->page_title)).'\')">'.$ns.str_replace('_', ' ', $row->page_title). "</a></li>\n";
$i++;
}
if ($r == "")
$html = "<p>No results found with the term '$term'</p><p>Edit to search again | Esc: cancel</p>";
else
$html = "<ul>$r</ul><p>Click to insert link | Esc: cancel</p>";
return $html;
}
function wfAjaxQuickLinkHeaders(){
global $wgOut;
wfAjaxSetQuickLinkHeaders($wgOut);
return true;
}
function wfAjaxSetQuickLinkHeaders($outputPage) {
global $wgJsMimeType, $wgStylePath, $wgScriptPath;
$outputPage->addLink(
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => $wgScriptPath.'/extensions/QuickLink/QuickLink.css'
)
);
//$outputPage->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$wgScriptPath/extensions/QuickLink/vegui.sk.formtools.js\">"."</script>\n");
$outputPage->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$wgScriptPath/extensions/QuickLink/QuickLink.js\">"."</script>\n");
$outputPage->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", QuickLink.load);</script>\n" );
}
QuickLink.css [edit]
#QuickLinkLabel {
margin: 0 0.2em 0 0.2em;
display: none;
}
#QuickLinkInput{
background-image: url('QuickLink.png');
background-repeat: no-repeat;
padding-left: 16px;
}
#QuickLinkBox {
text-align: right;
display: inline block;
float: right;
}
#QuickLinkResults {
/* Spotlight style */
position: absolute;
width: 20em;
right: 1em;
margin-top: 2em;
padding: 0.5em 0 0.5em 0;
/* almost-white transparent background */
background: #fafafa;
border: 1px solid #ccc;
filter:alpha(opacity=90);
-moz-opacity:0.9;
-khtml-opacity: 0.9;
opacity: 0.9;
}
#QuickLinkResults p {
font-style: italic;
}
#QuickLinkResults p,
#QuickLinkResults ul,
#QuickLinkResults li {
margin: 0;
padding: 0;
border: none;
list-style: none inside none;
}
#QuickLinkResults p,
#QuickLinkResults a {
padding: 0.1em 0.5em 0.1em 0.5em;
}
#QuickLinkResults a {
display: block;
color: #0066CC;
}
#QuickLinkResults a:hover {
text-decoration: none;
color: #0066CC;
}
#QuickLinkResults a.selected {
background-color: #0066CC;
color: white;
text-decoration: none;
}
margin: 0 0.2em 0 0.2em;
display: none;
}
#QuickLinkInput{
background-image: url('QuickLink.png');
background-repeat: no-repeat;
padding-left: 16px;
}
#QuickLinkBox {
text-align: right;
display: inline block;
float: right;
}
#QuickLinkResults {
/* Spotlight style */
position: absolute;
width: 20em;
right: 1em;
margin-top: 2em;
padding: 0.5em 0 0.5em 0;
/* almost-white transparent background */
background: #fafafa;
border: 1px solid #ccc;
filter:alpha(opacity=90);
-moz-opacity:0.9;
-khtml-opacity: 0.9;
opacity: 0.9;
}
#QuickLinkResults p {
font-style: italic;
}
#QuickLinkResults p,
#QuickLinkResults ul,
#QuickLinkResults li {
margin: 0;
padding: 0;
border: none;
list-style: none inside none;
}
#QuickLinkResults p,
#QuickLinkResults a {
padding: 0.1em 0.5em 0.1em 0.5em;
}
#QuickLinkResults a {
display: block;
color: #0066CC;
}
#QuickLinkResults a:hover {
text-decoration: none;
color: #0066CC;
}
#QuickLinkResults a.selected {
background-color: #0066CC;
color: white;
text-decoration: none;
}
QuickLink.js [edit]
/*
QuickLink.js
Copyright Francisco J. R. Prados 2009, Adam Meyer 2007
Thanks to: Simon Litt, Robinson Weijman and Pasi Kallinen for the comments and code
*/
var QuickLink = new Object();
QuickLink.searchTimeout = 500;
QuickLink.timer = 0;
QuickLink.counting = false;
QuickLink.searchBuffer = null;
QuickLink.searchBox = null;
QuickLink.textarea = null;
QuickLink.inputField = null;
QuickLink.caret = 0;
QuickLink.linkStart = -1;
QuickLink.lastSelectedItem = -1;
QuickLink.ctrlPressed = false;
/*
QuickLink.insertLink function
This function will be triggered when clicking one of the results
returned to the QuickLinkResults div by the wfAjaxQuickLink call
*/
QuickLink.insertLink = function(name) {
// if QuickLink.linkStart > 0, the search was triggered by the [[ sequence
// otherwise the search was triggered by the input search box
// QuickLink.caret must be up-to-date:
// -it is updated in onclick textarea event
// -it is updated in onkeydown textarea event
if(QuickLink.linkStart>0) {
QuickLink.textarea.value = QuickLink.textarea.value.substr(0, QuickLink.linkStart-2) + '[[' + name + ']]' + QuickLink.textarea.value.substr(QuickLink.caret);
QuickLink.setCaretAt(QuickLink.linkStart + name.length + 2);
}
else {
QuickLink.textarea.value = QuickLink.textarea.value.substr( 0, QuickLink.caret ) + '[[' + name + ']]' + QuickLink.textarea.value.substr(QuickLink.caret);
QuickLink.setCaretAt(QuickLink.caret + name.length + 4);
QuickLink.inputField.value = '';
}
QuickLink.resetSearchBox();
QuickLink.textarea.focus();
}
/*
OnClick function for textarea
OnFocus function for textarea
Updates caret, since it needs to be updated
(IE forgets carets when textarea goes out of focus)
*/
QuickLink.textareaOnClick = function(e) {
QuickLink.resetSearchBox();
QuickLink.caret = QuickLink.searchCaret();
}
/*
Init function
Creates all the elements neeeded for the search, attachtes
the events and calls QuickLink.resetSearchBox()
*/
QuickLink.load = function() {
QuickLink.textarea = document.getElementById( 'wpTextbox1');
// first check if the textbox is readonly
// (i.e. in protected pages)
if(!QuickLink.textarea.readOnly) {
var toolbar = document.getElementById("toolbar");
QuickLink.textarea.onkeyup = QuickLink.textSearch;
QuickLink.textarea.onkeydown = QuickLink.captureKeys;
QuickLink.textarea.onclick = QuickLink.textareaOnClick;
QuickLink.textarea.onfocus = QuickLink.textareaOnClick;
// create the input search box in toolbar
var box = document.createElement("div");
box.id = "QuickLinkBox";
toolbar.appendChild(box);
var label = document.createElement("span");
label.innerHTML = "QuickLink";
label.id = "QuickLinkLabel";
box.appendChild(label);
QuickLink.inputField = document.createElement("input");
QuickLink.inputField.id = "QuickLinkInput";
QuickLink.inputField.setAttribute("type", "search");
QuickLink.inputField.setAttribute("placeholder", "Type to insert link");
QuickLink.inputField.onkeyup = QuickLink.delayedSearch;
box.appendChild(QuickLink.inputField);
var help = document.createElement("a");
help.innerHTML = "?";
help.title = "Press Ctrl+Space while writing the article to trigger the search box. Type a string and the matching results will be shown. Press enter to select the first result, or Esc to return to the writing box.";
//box.appendChild(help);
// create the box for results
QuickLink.searchBox = document.createElement("div");
QuickLink.searchBox.id = "QuickLinkResults";
toolbar.appendChild(QuickLink.searchBox);
// reset the search box to the initial state
QuickLink.resetSearchBox();
}
}
QuickLink.searchCall = function(query) {
if( QuickLink.lastSelectedItem >= 0) {
}
//QuickLink.searchBox.innerHTML = "<p>Retrieving results for '"+query+"' ...</p>";
sajax_do_call("wfAjaxQuickLink", [query], QuickLink.AJAXStateChange);
}
/*
Callback called when request.readyState = 4
*/
QuickLink.AJAXStateChange = function(request) {
QuickLink.searchBox.innerHTML = request.responseText;
QuickLink.selectResult(0);
}
/*
Delayed search method for the QuickLink.inputField.onkeyup event
*/
QuickLink.delayedSearch = function(e) {
var event = (e) ? e : window.event;
var keyCode = event.keyCode ? event.keyCode : event.charCode ? event.charCode : event.which ? event.which : void 0;
if(keyCode == 27) {
QuickLink.resetSearchBox();
QuickLink.textarea.focus();
}
else if(keyCode == 13) {
QuickLink.insertSelectedResult();
}
else if(keyCode == 38) {
QuickLink.selectPreviousResult();
}
else if(keyCode == 40) {
QuickLink.selectNextResult();
}
else{
if(QuickLink.counting)
clearTimeout(QuickLink.timer);
QuickLink.timer = setTimeout("QuickLink.inputFieldSearch()",QuickLink.searchTimeout);
QuickLink.counting = true;
}
}
/*
Search method for the QuickLink.inputField.onkeyup event
*/
QuickLink.inputFieldSearch = function() {
// reset delayed search
QuickLink.counting = false;
var query = document.getElementById('QuickLinkInput').value;
if (query != QuickLink.searchBuffer) {
QuickLink.searchBuffer = query;
if(query.length > 0) {
QuickLink.initSearchBox();
if (query.length < 30 && query.length > 0 && query.value != "") {
QuickLink.searchCall(query);
}
}
else
QuickLink.resetSearchBox();
}
}
/*
Search method for the QuickLink.textarea.onkeyup event
Search will be triggered when typing the sequence [[, or placing the caret
by an existing sequence
@pre: QuickLink.caret is up to date - it is updated in textarea.onkeydown and textarea.mouseclick events
*/
QuickLink.textSearch = function(e) {
var evt = (e) ? e : window.event;
var code = evt.keyCode ? evt.keyCode : evt.charCode ? evt.charCode : evt.which ? evt.which : void 0;
if(code == 17)
QuickLink.ctrlPressed = false;
else if(code == 38 || code == 40)
return;
QuickLink.caret = QuickLink.searchCaret();
if(QuickLink.linkStart<0 || QuickLink.caret < QuickLink.linkStart) {
if(this.value.charAt(QuickLink.caret-1)=='[' && this.value.charAt(QuickLink.caret-2)=='[')
QuickLink.linkStart = QuickLink.caret;
else
QuickLink.resetSearchBox();
}
else if(this.value.charAt(QuickLink.caret-1)==']') {
QuickLink.resetSearchBox();
}
else {
QuickLink.initSearchBox();
var searchString = this.value.substr(QuickLink.linkStart,QuickLink.caret-QuickLink.linkStart);
//caretxy = vsk_frm_cursor_offset(QuickLink.textarea);
//console.log(caretxy);
//QuickLink.searchBox.style.position = 'absolute';
//QuickLink.searchBox.style.top = caretxy.y + this.offsetTop + "px";
//QuickLink.searchBox.style.left = caretxy.x + this.offsetLeft + "px";
QuickLink.inputField.value = searchString;
QuickLink.searchCall(searchString);
}
}
/*
Key capture method for the QuickLink.textarea.onkeydown event
Updates QuickLink.caret, since IE forgets caret when textarea goes out of focus
*/
QuickLink.captureKeys = function(e) {
var evt = (e) ? e : window.event;
var code = evt.keyCode ? evt.keyCode : evt.charCode ? evt.charCode : evt.which ? evt.which : void 0;
switch(code) {
// space
case 32:
if(QuickLink.ctrlPressed) {
// trigger search and breack event bubble
// update caret and break ctrlPressed flag
QuickLink.ctrlPressed = false;
QuickLink.caret = QuickLink.searchCaret();
QuickLink.inputField.focus();
return false;
}
break;
// enter
case 13:
if(QuickLink.linkStart>0) {
QuickLink.insertSelectedResult();
return false;
}
break;
// up
case 38:
if(QuickLink.linkStart>0) {
QuickLink.selectPreviousResult();
return false;
}
break;
// right
case 39:
if(QuickLink.linkStart>0) {
QuickLink.insertSelectedResult();
return false;
}
break;
// down
case 40:
if(QuickLink.linkStart>0) {
QuickLink.selectNextResult();
return false;
}
break;
// left
case 37:
// ']'
case 187:
// escape
case 27:
if(QuickLink.linkStart>0)
QuickLink.resetSearchBox();
break;
case 17:
QuickLink.ctrlPressed = true;
break;
//e.cancelBubble=true;
//e.stopPropagation()
}
}
/*
Search caret method - IE safe
*/
QuickLink.searchCaret = function() {
var caret = QuickLink.textarea.selectionEnd;
if(!caret) {
if( document.selection ){
// current selection
var range = document.selection.createRange();
var stored_range = range.duplicate();
// Select all text
stored_range.moveToElementText( QuickLink.textarea );
// move 'dummy' end point to end point of original range
stored_range.setEndPoint( 'EndToEnd', range );
// calculate caret position
caret = stored_range.text.length - range.text.length ;
} else {
caret = 0;
}
}
//alert(caret);
return caret;
}
/*
* Set caret at position i
*/
QuickLink.setCaretAt = function(i) {
if(QuickLink.textarea.setSelectionRange)
QuickLink.textarea.setSelectionRange(i,i);
else if(QuickLink.textarea.createTextRange) {
var range = document.selection.createRange();
var stored_range = range.duplicate();
stored_range.moveToElementText( QuickLink.textarea );
stored_range.collapse(true);
stored_range.moveStart( 'character', i );
stored_range.moveEnd( 'character', 0 );
stored_range.select();
}
}
QuickLink.initSearchBox = function() {
QuickLink.searchBox.style.visibility = 'visible';
QuickLink.lastSelectedItem = -1;
}
QuickLink.resetSearchBox = function() {
QuickLink.inputField.value = '';
QuickLink.searchBox.style.visibility = 'hidden';
QuickLink.linkStart = -1;
QuickLink.lastSelectedItem = -1;
}
QuickLink.selectResult = function(item) {
// if QuickLink.lastSelectedItem < 0 then notihng was selected
if(QuickLink.lastSelectedItem >= 0)
if(QuickLink.searchBox.getElementsByTagName('a').item(QuickLink.lastSelectedItem))
QuickLink.searchBox.getElementsByTagName('a')[QuickLink.lastSelectedItem].className = 'unselected';
// select an item only if item > -1
// if item is out of range, QuickLink.lastSelectedItem = -1
if(item >= 0)
if(QuickLink.searchBox.getElementsByTagName('a').item(item)) {
QuickLink.searchBox.getElementsByTagName('a')[item].className = 'selected';
QuickLink.lastSelectedItem = item;
}
else {
QuickLink.lastSelectedItem = -1;
}
}
QuickLink.insertSelectedResult = function() {
var selectedResult = QuickLink.searchBox.getElementsByTagName('a').item(QuickLink.lastSelectedItem);
if(selectedResult)
selectedResult.onclick();
}
QuickLink.selectNextResult = function() {
var max = QuickLink.searchBox.getElementsByTagName('a').length;
if(QuickLink.lastSelectedItem < max-1) {
QuickLink.selectResult(QuickLink.lastSelectedItem+1);
}
}
QuickLink.selectPreviousResult = function() {
if(QuickLink.lastSelectedItem > 0) {
QuickLink.selectResult(QuickLink.lastSelectedItem-1);
}
}
QuickLink.js
Copyright Francisco J. R. Prados 2009, Adam Meyer 2007
Thanks to: Simon Litt, Robinson Weijman and Pasi Kallinen for the comments and code
*/
var QuickLink = new Object();
QuickLink.searchTimeout = 500;
QuickLink.timer = 0;
QuickLink.counting = false;
QuickLink.searchBuffer = null;
QuickLink.searchBox = null;
QuickLink.textarea = null;
QuickLink.inputField = null;
QuickLink.caret = 0;
QuickLink.linkStart = -1;
QuickLink.lastSelectedItem = -1;
QuickLink.ctrlPressed = false;
/*
QuickLink.insertLink function
This function will be triggered when clicking one of the results
returned to the QuickLinkResults div by the wfAjaxQuickLink call
*/
QuickLink.insertLink = function(name) {
// if QuickLink.linkStart > 0, the search was triggered by the [[ sequence
// otherwise the search was triggered by the input search box
// QuickLink.caret must be up-to-date:
// -it is updated in onclick textarea event
// -it is updated in onkeydown textarea event
if(QuickLink.linkStart>0) {
QuickLink.textarea.value = QuickLink.textarea.value.substr(0, QuickLink.linkStart-2) + '[[' + name + ']]' + QuickLink.textarea.value.substr(QuickLink.caret);
QuickLink.setCaretAt(QuickLink.linkStart + name.length + 2);
}
else {
QuickLink.textarea.value = QuickLink.textarea.value.substr( 0, QuickLink.caret ) + '[[' + name + ']]' + QuickLink.textarea.value.substr(QuickLink.caret);
QuickLink.setCaretAt(QuickLink.caret + name.length + 4);
QuickLink.inputField.value = '';
}
QuickLink.resetSearchBox();
QuickLink.textarea.focus();
}
/*
OnClick function for textarea
OnFocus function for textarea
Updates caret, since it needs to be updated
(IE forgets carets when textarea goes out of focus)
*/
QuickLink.textareaOnClick = function(e) {
QuickLink.resetSearchBox();
QuickLink.caret = QuickLink.searchCaret();
}
/*
Init function
Creates all the elements neeeded for the search, attachtes
the events and calls QuickLink.resetSearchBox()
*/
QuickLink.load = function() {
QuickLink.textarea = document.getElementById( 'wpTextbox1');
// first check if the textbox is readonly
// (i.e. in protected pages)
if(!QuickLink.textarea.readOnly) {
var toolbar = document.getElementById("toolbar");
QuickLink.textarea.onkeyup = QuickLink.textSearch;
QuickLink.textarea.onkeydown = QuickLink.captureKeys;
QuickLink.textarea.onclick = QuickLink.textareaOnClick;
QuickLink.textarea.onfocus = QuickLink.textareaOnClick;
// create the input search box in toolbar
var box = document.createElement("div");
box.id = "QuickLinkBox";
toolbar.appendChild(box);
var label = document.createElement("span");
label.innerHTML = "QuickLink";
label.id = "QuickLinkLabel";
box.appendChild(label);
QuickLink.inputField = document.createElement("input");
QuickLink.inputField.id = "QuickLinkInput";
QuickLink.inputField.setAttribute("type", "search");
QuickLink.inputField.setAttribute("placeholder", "Type to insert link");
QuickLink.inputField.onkeyup = QuickLink.delayedSearch;
box.appendChild(QuickLink.inputField);
var help = document.createElement("a");
help.innerHTML = "?";
help.title = "Press Ctrl+Space while writing the article to trigger the search box. Type a string and the matching results will be shown. Press enter to select the first result, or Esc to return to the writing box.";
//box.appendChild(help);
// create the box for results
QuickLink.searchBox = document.createElement("div");
QuickLink.searchBox.id = "QuickLinkResults";
toolbar.appendChild(QuickLink.searchBox);
// reset the search box to the initial state
QuickLink.resetSearchBox();
}
}
QuickLink.searchCall = function(query) {
if( QuickLink.lastSelectedItem >= 0) {
}
//QuickLink.searchBox.innerHTML = "<p>Retrieving results for '"+query+"' ...</p>";
sajax_do_call("wfAjaxQuickLink", [query], QuickLink.AJAXStateChange);
}
/*
Callback called when request.readyState = 4
*/
QuickLink.AJAXStateChange = function(request) {
QuickLink.searchBox.innerHTML = request.responseText;
QuickLink.selectResult(0);
}
/*
Delayed search method for the QuickLink.inputField.onkeyup event
*/
QuickLink.delayedSearch = function(e) {
var event = (e) ? e : window.event;
var keyCode = event.keyCode ? event.keyCode : event.charCode ? event.charCode : event.which ? event.which : void 0;
if(keyCode == 27) {
QuickLink.resetSearchBox();
QuickLink.textarea.focus();
}
else if(keyCode == 13) {
QuickLink.insertSelectedResult();
}
else if(keyCode == 38) {
QuickLink.selectPreviousResult();
}
else if(keyCode == 40) {
QuickLink.selectNextResult();
}
else{
if(QuickLink.counting)
clearTimeout(QuickLink.timer);
QuickLink.timer = setTimeout("QuickLink.inputFieldSearch()",QuickLink.searchTimeout);
QuickLink.counting = true;
}
}
/*
Search method for the QuickLink.inputField.onkeyup event
*/
QuickLink.inputFieldSearch = function() {
// reset delayed search
QuickLink.counting = false;
var query = document.getElementById('QuickLinkInput').value;
if (query != QuickLink.searchBuffer) {
QuickLink.searchBuffer = query;
if(query.length > 0) {
QuickLink.initSearchBox();
if (query.length < 30 && query.length > 0 && query.value != "") {
QuickLink.searchCall(query);
}
}
else
QuickLink.resetSearchBox();
}
}
/*
Search method for the QuickLink.textarea.onkeyup event
Search will be triggered when typing the sequence [[, or placing the caret
by an existing sequence
@pre: QuickLink.caret is up to date - it is updated in textarea.onkeydown and textarea.mouseclick events
*/
QuickLink.textSearch = function(e) {
var evt = (e) ? e : window.event;
var code = evt.keyCode ? evt.keyCode : evt.charCode ? evt.charCode : evt.which ? evt.which : void 0;
if(code == 17)
QuickLink.ctrlPressed = false;
else if(code == 38 || code == 40)
return;
QuickLink.caret = QuickLink.searchCaret();
if(QuickLink.linkStart<0 || QuickLink.caret < QuickLink.linkStart) {
if(this.value.charAt(QuickLink.caret-1)=='[' && this.value.charAt(QuickLink.caret-2)=='[')
QuickLink.linkStart = QuickLink.caret;
else
QuickLink.resetSearchBox();
}
else if(this.value.charAt(QuickLink.caret-1)==']') {
QuickLink.resetSearchBox();
}
else {
QuickLink.initSearchBox();
var searchString = this.value.substr(QuickLink.linkStart,QuickLink.caret-QuickLink.linkStart);
//caretxy = vsk_frm_cursor_offset(QuickLink.textarea);
//console.log(caretxy);
//QuickLink.searchBox.style.position = 'absolute';
//QuickLink.searchBox.style.top = caretxy.y + this.offsetTop + "px";
//QuickLink.searchBox.style.left = caretxy.x + this.offsetLeft + "px";
QuickLink.inputField.value = searchString;
QuickLink.searchCall(searchString);
}
}
/*
Key capture method for the QuickLink.textarea.onkeydown event
Updates QuickLink.caret, since IE forgets caret when textarea goes out of focus
*/
QuickLink.captureKeys = function(e) {
var evt = (e) ? e : window.event;
var code = evt.keyCode ? evt.keyCode : evt.charCode ? evt.charCode : evt.which ? evt.which : void 0;
switch(code) {
// space
case 32:
if(QuickLink.ctrlPressed) {
// trigger search and breack event bubble
// update caret and break ctrlPressed flag
QuickLink.ctrlPressed = false;
QuickLink.caret = QuickLink.searchCaret();
QuickLink.inputField.focus();
return false;
}
break;
// enter
case 13:
if(QuickLink.linkStart>0) {
QuickLink.insertSelectedResult();
return false;
}
break;
// up
case 38:
if(QuickLink.linkStart>0) {
QuickLink.selectPreviousResult();
return false;
}
break;
// right
case 39:
if(QuickLink.linkStart>0) {
QuickLink.insertSelectedResult();
return false;
}
break;
// down
case 40:
if(QuickLink.linkStart>0) {
QuickLink.selectNextResult();
return false;
}
break;
// left
case 37:
// ']'
case 187:
// escape
case 27:
if(QuickLink.linkStart>0)
QuickLink.resetSearchBox();
break;
case 17:
QuickLink.ctrlPressed = true;
break;
//e.cancelBubble=true;
//e.stopPropagation()
}
}
/*
Search caret method - IE safe
*/
QuickLink.searchCaret = function() {
var caret = QuickLink.textarea.selectionEnd;
if(!caret) {
if( document.selection ){
// current selection
var range = document.selection.createRange();
var stored_range = range.duplicate();
// Select all text
stored_range.moveToElementText( QuickLink.textarea );
// move 'dummy' end point to end point of original range
stored_range.setEndPoint( 'EndToEnd', range );
// calculate caret position
caret = stored_range.text.length - range.text.length ;
} else {
caret = 0;
}
}
//alert(caret);
return caret;
}
/*
* Set caret at position i
*/
QuickLink.setCaretAt = function(i) {
if(QuickLink.textarea.setSelectionRange)
QuickLink.textarea.setSelectionRange(i,i);
else if(QuickLink.textarea.createTextRange) {
var range = document.selection.createRange();
var stored_range = range.duplicate();
stored_range.moveToElementText( QuickLink.textarea );
stored_range.collapse(true);
stored_range.moveStart( 'character', i );
stored_range.moveEnd( 'character', 0 );
stored_range.select();
}
}
QuickLink.initSearchBox = function() {
QuickLink.searchBox.style.visibility = 'visible';
QuickLink.lastSelectedItem = -1;
}
QuickLink.resetSearchBox = function() {
QuickLink.inputField.value = '';
QuickLink.searchBox.style.visibility = 'hidden';
QuickLink.linkStart = -1;
QuickLink.lastSelectedItem = -1;
}
QuickLink.selectResult = function(item) {
// if QuickLink.lastSelectedItem < 0 then notihng was selected
if(QuickLink.lastSelectedItem >= 0)
if(QuickLink.searchBox.getElementsByTagName('a').item(QuickLink.lastSelectedItem))
QuickLink.searchBox.getElementsByTagName('a')[QuickLink.lastSelectedItem].className = 'unselected';
// select an item only if item > -1
// if item is out of range, QuickLink.lastSelectedItem = -1
if(item >= 0)
if(QuickLink.searchBox.getElementsByTagName('a').item(item)) {
QuickLink.searchBox.getElementsByTagName('a')[item].className = 'selected';
QuickLink.lastSelectedItem = item;
}
else {
QuickLink.lastSelectedItem = -1;
}
}
QuickLink.insertSelectedResult = function() {
var selectedResult = QuickLink.searchBox.getElementsByTagName('a').item(QuickLink.lastSelectedItem);
if(selectedResult)
selectedResult.onclick();
}
QuickLink.selectNextResult = function() {
var max = QuickLink.searchBox.getElementsByTagName('a').length;
if(QuickLink.lastSelectedItem < max-1) {
QuickLink.selectResult(QuickLink.lastSelectedItem+1);
}
}
QuickLink.selectPreviousResult = function() {
if(QuickLink.lastSelectedItem > 0) {
QuickLink.selectResult(QuickLink.lastSelectedItem-1);
}
}
QuickLink.png [edit]
Credits [edit]
The code is based in the extension written by Adam Meyer, Link Suggest. Also thanks to Simon Litt, Robinson Weijman and Pasi Kallinen for the comments and code.
See also [edit]
- Link Suggest - the original extension from which this one has derived
- LinkSuggest, A rewritten Wikia's version of this extension used on all wikias, requires jQuery to work. Superior in that a new edit box is not required, wikilinks and templates are automatically changed in the edit box as the user types.