From 99899af3982317f3876717297f215df8f5534099 Mon Sep 17 00:00:00 2001 From: Giuseppe Di Terlizzi Date: Mon, 15 Jan 2018 23:16:35 +0100 Subject: [PATCH] Added Typeahead support for quick search using "qsearch" DokuWiki AJAX service and fixed action items functions --- assets/typeahead/bootstrap3-typeahead.min.js | 1 + css/template.less | 8 +- script.js | 49 +++++ tpl_functions.php | 183 ++++++++++--------- 4 files changed, 150 insertions(+), 91 deletions(-) create mode 100644 assets/typeahead/bootstrap3-typeahead.min.js diff --git a/assets/typeahead/bootstrap3-typeahead.min.js b/assets/typeahead/bootstrap3-typeahead.min.js new file mode 100644 index 00000000..2987abc8 --- /dev/null +++ b/assets/typeahead/bootstrap3-typeahead.min.js @@ -0,0 +1 @@ +(function(a,b){if(typeof module!=="undefined"&&module.exports){module.exports=b(require("jquery"))}else{if(typeof define==="function"&&define.amd){define(["jquery"],function(c){return b(c)})}else{b(a.jQuery)}}}(this,function(b){var c=function(e,d){this.$element=b(e);this.options=b.extend({},c.defaults,d);this.matcher=this.options.matcher||this.matcher;this.sorter=this.options.sorter||this.sorter;this.select=this.options.select||this.select;this.autoSelect=typeof this.options.autoSelect=="boolean"?this.options.autoSelect:true;this.highlighter=this.options.highlighter||this.highlighter;this.render=this.options.render||this.render;this.updater=this.options.updater||this.updater;this.displayText=this.options.displayText||this.displayText;this.itemLink=this.options.itemLink||this.itemLink;this.itemTitle=this.options.itemTitle||this.itemTitle;this.followLinkOnSelect=this.options.followLinkOnSelect||this.followLinkOnSelect;this.source=this.options.source;this.delay=this.options.delay;this.$menu=b(this.options.menu);this.$appendTo=this.options.appendTo?b(this.options.appendTo):null;this.fitToElement=typeof this.options.fitToElement=="boolean"?this.options.fitToElement:false;this.shown=false;this.listen();this.showHintOnFocus=typeof this.options.showHintOnFocus=="boolean"||this.options.showHintOnFocus==="all"?this.options.showHintOnFocus:false;this.afterSelect=this.options.afterSelect;this.afterEmptySelect=this.options.afterEmptySelect;this.addItem=false;this.value=this.$element.val()||this.$element.text();this.keyPressed=false;this.focused=this.$element.is(":focus")};c.prototype={constructor:c,setDefault:function(e){this.$element.data("active",e);if(this.autoSelect||e){var d=this.updater(e);if(!d){d=""}this.$element.val(this.displayText(d)||d).text(this.displayText(d)||d).change();this.afterSelect(d)}return this.hide()},select:function(){var e=this.$menu.find(".active").data("value");this.$element.data("active",e);if(this.autoSelect||e){var d=this.updater(e);if(!d){d=""}this.$element.val(this.displayText(d)||d).text(this.displayText(d)||d).change();this.afterSelect(d);if(this.followLinkOnSelect&&this.itemLink(e)){document.location=this.itemLink(e);this.afterSelect(d)}else{if(this.followLinkOnSelect&&!this.itemLink(e)){this.afterEmptySelect(d)}else{this.afterSelect(d)}}}else{this.afterEmptySelect(d)}return this.hide()},updater:function(d){return d},setSource:function(d){this.source=d},show:function(){var k=b.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});var h=typeof this.options.scrollHeight=="function"?this.options.scrollHeight.call():this.options.scrollHeight;var g;if(this.shown){g=this.$menu}else{if(this.$appendTo){g=this.$menu.appendTo(this.$appendTo);this.hasSameParent=this.$appendTo.is(this.$element.parent())}else{g=this.$menu.insertAfter(this.$element);this.hasSameParent=true}}if(!this.hasSameParent){g.css("position","fixed");var j=this.$element.offset();k.top=j.top;k.left=j.left}var e=b(g).parent().hasClass("dropup");var d=e?"auto":(k.top+k.height+h);var f=b(g).hasClass("dropdown-menu-right");var i=f?"auto":k.left;g.css({top:d,left:i}).show();if(this.options.fitToElement===true){g.css("width",this.$element.outerWidth()+"px")}this.shown=true;return this},hide:function(){this.$menu.hide();this.shown=false;return this},lookup:function(e){var d;if(typeof(e)!="undefined"&&e!==null){this.query=e}else{this.query=this.$element.val()}if(this.query.length0){this.$element.data("active",d[0])}else{this.$element.data("active",null)}if(this.options.items!="all"){d=d.slice(0,this.options.items)}if(this.options.addItem){d.push(this.options.addItem)}return this.render(d).show()},matcher:function(e){var d=this.displayText(e);return ~d.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(f){var g=[];var e=[];var d=[];var i;while((i=f.shift())){var h=this.displayText(i);if(!h.toLowerCase().indexOf(this.query.toLowerCase())){g.push(i)}else{if(~h.indexOf(this.query)){e.push(i)}else{d.push(i)}}}return g.concat(e,d)},highlighter:function(h){var l=this.query;if(l===""){return h}var j=h.match(/(>)([^<]*)(<)/g);var k=[];var e=[];var f;if(j&&j.length){for(f=0;f2){k.push(j[f])}}}else{k=[];k.push(h)}l=l.replace((/[\(\)\/\.\*\+\?\[\]]/g),function(i){return"\\"+i});var g=new RegExp(l,"g");var d;for(f=0;f0){e.push(k[f])}}for(f=0;f$&"))}return h},render:function(e){var g=this;var d=this;var f=false;var i=[];var h=g.options.separator;b.each(e,function(j,k){if(j>0&&k[h]!==e[j-1][h]){i.push({__type:"divider"})}if(k[h]&&(j===0||k[h]!==e[j-1][h])){i.push({__type:"category",name:k[h]})}i.push(k)});e=b(i).map(function(j,k){if((k.__type||false)=="category"){return b(g.options.headerHtml).text(k.name)[0]}if((k.__type||false)=="divider"){return b(g.options.headerDivider)[0]}var l=d.displayText(k);j=b(g.options.item).data("value",k);j.find(g.options.itemContentSelector).addBack(g.options.itemContentSelector).html(g.highlighter(l,k));if(this.followLinkOnSelect){j.find("a").attr("href",d.itemLink(k))}j.find("a").attr("title",d.itemTitle(k));if(l==d.$element.val()){j.addClass("active");d.$element.data("active",k);f=true}return j[0]});if(this.autoSelect&&!f){e.filter(":not(.dropdown-header)").first().addClass("active");this.$element.data("active",e.first().data("value"))}this.$menu.html(e);return this},displayText:function(d){return typeof d!=="undefined"&&typeof d.name!="undefined"?d.name:d},itemLink:function(d){return null},itemTitle:function(d){return null},next:function(f){var g=this.$menu.find(".active").removeClass("active");var e=g.next();if(!e.length){e=b(this.$menu.find("li")[0])}e.addClass("active");var d=this.updater(e.data("value"));this.$element.val(this.displayText(d)||d)},prev:function(f){var g=this.$menu.find(".active").removeClass("active");var e=g.prev();if(!e.length){e=this.$menu.find("li").last()}e.addClass("active");var d=this.updater(e.data("value"));this.$element.val(this.displayText(d)||d)},listen:function(){this.$element.on("focus.bootstrap3Typeahead",b.proxy(this.focus,this)).on("blur.bootstrap3Typeahead",b.proxy(this.blur,this)).on("keypress.bootstrap3Typeahead",b.proxy(this.keypress,this)).on("propertychange.bootstrap3Typeahead input.bootstrap3Typeahead",b.proxy(this.input,this)).on("keyup.bootstrap3Typeahead",b.proxy(this.keyup,this));if(this.eventSupported("keydown")){this.$element.on("keydown.bootstrap3Typeahead",b.proxy(this.keydown,this))}if("ontouchstart" in document.documentElement){this.$menu.on("touchstart","li",b.proxy(this.touchstart,this)).on("touchend","li",b.proxy(this.click,this))}else{this.$menu.on("click",b.proxy(this.click,this)).on("mouseenter","li",b.proxy(this.mouseenter,this)).on("mouseleave","li",b.proxy(this.mouseleave,this)).on("mousedown",b.proxy(this.mousedown,this))}},destroy:function(){this.$element.data("typeahead",null);this.$element.data("active",null);this.$element.unbind("focus.bootstrap3Typeahead").unbind("blur.bootstrap3Typeahead").unbind("keypress.bootstrap3Typeahead").unbind("propertychange.bootstrap3Typeahead input.bootstrap3Typeahead").unbind("keyup.bootstrap3Typeahead");if(this.eventSupported("keydown")){this.$element.unbind("keydown.bootstrap3-typeahead")}this.$menu.remove();this.destroyed=true},eventSupported:function(d){var e=d in this.$element;if(!e){this.$element.setAttribute(d,"return;");e=typeof this.$element[d]==="function"}return e},move:function(d){if(!this.shown){return}switch(d.keyCode){case 9:case 13:case 27:d.preventDefault();break;case 38:if(d.shiftKey){return}d.preventDefault();this.prev();break;case 40:if(d.shiftKey){return}d.preventDefault();this.next();break}},keydown:function(d){this.keyPressed=true;this.suppressKeyPressRepeat=~b.inArray(d.keyCode,[40,38,9,13,27]);if(!this.shown&&d.keyCode==40){this.lookup()}else{this.move(d)}},keypress:function(d){if(this.suppressKeyPressRepeat){return}this.move(d)},input:function(f){var d=this.$element.val()||this.$element.text();if(this.value!==d){this.value=d;this.lookup()}},keyup:function(d){if(this.destroyed){return}switch(d.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:if(!this.shown||(this.showHintOnFocus&&!this.keyPressed)){return}this.select();break;case 13:if(!this.shown){return}this.select();break;case 27:if(!this.shown){return}this.hide();break}},focus:function(d){if(!this.focused){this.focused=true;this.keyPressed=false;if(this.options.showHintOnFocus&&this.skipShowHintOnFocus!==true){if(this.options.showHintOnFocus==="all"){this.lookup("")}else{this.lookup()}}}if(this.skipShowHintOnFocus){this.skipShowHintOnFocus=false}},blur:function(d){if(!this.mousedover&&!this.mouseddown&&this.shown){this.select();this.hide();this.focused=false;this.keyPressed=false}else{if(this.mouseddown){this.skipShowHintOnFocus=true;this.$element.focus();this.mouseddown=false}}},click:function(d){d.preventDefault();this.skipShowHintOnFocus=true;this.select();this.$element.focus();this.hide()},mouseenter:function(d){this.mousedover=true;this.$menu.find(".active").removeClass("active");b(d.currentTarget).addClass("active")},mouseleave:function(d){this.mousedover=false;if(!this.focused&&this.shown){this.hide()}},mousedown:function(d){this.mouseddown=true;this.$menu.one("mouseup",function(f){this.mouseddown=false}.bind(this))},touchstart:function(d){d.preventDefault();this.$menu.find(".active").removeClass("active");b(d.currentTarget).addClass("active")},touchend:function(d){d.preventDefault();this.select();this.$element.focus()}};var a=b.fn.typeahead;b.fn.typeahead=function(e){var d=arguments;if(typeof e=="string"&&e=="getActive"){return this.data("active")}return this.each(function(){var h=b(this);var g=h.data("typeahead");var f=typeof e=="object"&&e;if(!g){h.data("typeahead",(g=new c(this,f)))}if(typeof e=="string"&&g[e]){if(d.length>1){g[e].apply(g,Array.prototype.slice.call(d,1))}else{g[e]()}}})};c.defaults={source:[],items:8,menu:'',item:'
  • ',itemContentSelector:"a",minLength:1,scrollHeight:0,autoSelect:true,afterSelect:b.noop,afterEmptySelect:b.noop,addItem:false,followLinkOnSelect:false,delay:0,separator:"category",headerHtml:'',headerDivider:''};b.fn.typeahead.Constructor=c;b.fn.typeahead.noConflict=function(){b.fn.typeahead=a;return this};b(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(f){var d=b(this);if(d.data("typeahead")){return}d.typeahead(d.data())})})); \ No newline at end of file diff --git a/css/template.less b/css/template.less index ce989c68..f595f1f5 100755 --- a/css/template.less +++ b/css/template.less @@ -129,9 +129,11 @@ header { outline: 0; } - #qsearch__out { - overflow-y: auto; - max-height: 400px; + /* Typeahead plugin */ + .typeahead a.dropdown-item { + font-size: 85%; + text-overflow:ellipsis; + overflow:hidden } } diff --git a/script.js b/script.js index 8d868668..8b0a7944 100644 --- a/script.js +++ b/script.js @@ -32,6 +32,55 @@ jQuery(document).ready(function() { jQuery(document).trigger('bootstrap3:toc-resize'); }); + + // Add typeahead support for quick seach + jQuery("#qsearch__in").typeahead({ + + source: function(query, process) { + + return jQuery.post(DOKU_BASE + 'lib/exe/ajax.php', { + call: 'qsearch', + q: encodeURI(query) + }, + + function(data) { + + var results = []; + + jQuery(data).find('a').each(function(){ + + var page = jQuery(this); + + results.push({ + name : page.text(), + href : page.attr('href'), + title : page.attr('title'), + }); + + }); + + return process(results); + + }); + }, + + itemLink: function (item) { + return item.href; + }, + + itemTitle: function (item) { + return item.title; + }, + + followLinkOnSelect : true, + autoSelect : false, + items : 50, + fitToElement : true, + delay : 500, + + }); + + // Replace ALL input[type=submit|reset|button] (with no events) to button[type=submit|reset|button] for CSS styling jQuery.fn.extend({ diff --git a/tpl_functions.php b/tpl_functions.php index 54256c35..0c920cbf 100755 --- a/tpl_functions.php +++ b/tpl_functions.php @@ -13,83 +13,6 @@ include_once(dirname(__FILE__) . '/inc/simple_html_dom.php'); -function bootstrap3_action($type, $icon = '', $wrapper = false, $return = false) { - - global $ACT; - global $ID; - - $output = ''; - - $custom_actions = array('purge', 'discussion'); - - if (in_array($type, $custom_actions)) { - - if ($wrapper) $output .= "<$wrapper>"; - - if ($type == 'purge') { - - $link = wl($ID, array('purge' => 'true')); - $title = tpl_getLang('purge_cache_page'); - - $output .= sprintf('%s%s', - $link, - $type, - (($ACT == $type) ? ' active': ''), - $title, - (($icon) ? " ": ''), - $title); - - } - - if ($type == 'discussion') { - - $discuss_page = str_replace('@ID@', $ID, tpl_getConf('discussionPage')); - $discuss_page_raw = str_replace('@ID@', '', tpl_getConf('discussionPage')); - $is_discussPage = strpos($ID, $discuss_page_raw) !== false; - $back_id = ':'.str_replace($discuss_page_raw, '', $ID); - - if ($is_discussPage) { - - $link = html_wikilink($back_id, tpl_getLang('back_to_article')); - $link = str_replace('title="', 'title="' . tpl_getLang('back_to_article') . ': ', $link); - - } else { - - $link = html_wikilink($discuss_page, tpl_getLang('discussion')); - $link = str_replace('title="', 'title="' . tpl_getLang('discussion') . ': ', $link); - - } - - $output .= str_replace(array('class="', 'wikilink1', 'wikilink2'), - array('class="action discussion ', '', ''), $link); - - if ($icon) { - $output = preg_replace('/()/m', '$1 ', $output); - } - - } - - if ($wrapper) $output .= ""; - - } else { - - $inner = ''; - - if ($icon) $inner = ''; - - $output .= tpl_actionlink($type, '', '', $inner, true, $wrapper); - - if ($type == $ACT) $output = str_replace('class="action ', 'class="action active ', $output); - - } - - if ($return) return $output; - echo $output; - -} - - - /** * copied from core (available since Detritus) */ @@ -139,6 +62,7 @@ function tpl_classes() { } + /** * copied from core (available since Detritus) */ @@ -172,6 +96,91 @@ function plugin_getRequestAdminPlugin(){ } +/** + * Create link for DokuWiki actions + * + * @param string $type action + * @param string $icon class + * @param boolean|string $wrapper + * @param boolean $return + */ +function bootstrap3_action($type, $icon = '', $wrapper = false, $return = false) { + + global $ACT; + global $ID; + global $lang; + + $output = ''; + + $custom_actions = array('purge', 'discussion'); + + if (in_array($type, $custom_actions)) { + + if ($wrapper) $output .= "<$wrapper>"; + + if ($type == 'purge') { + + $link = wl($ID, array('purge' => 'true')); + $title = tpl_getLang('purge_cache_page'); + + $output .= sprintf('%s%s', + $link, + $type, + (($ACT == $type) ? ' active': ''), + $title, + (($icon) ? " ": ''), + $title); + + } + + if ($type == 'discussion') { + + $discuss_page = str_replace('@ID@', $ID, tpl_getConf('discussionPage')); + $discuss_page_raw = str_replace('@ID@', '', tpl_getConf('discussionPage')); + $is_discussPage = strpos($ID, $discuss_page_raw) !== false; + $back_id = ':'.str_replace($discuss_page_raw, '', $ID); + + if ($is_discussPage) { + + $link = html_wikilink($back_id, tpl_getLang('back_to_article')); + $link = str_replace('title="', 'title="' . tpl_getLang('back_to_article') . ': ', $link); + + } else { + + $link = html_wikilink($discuss_page, tpl_getLang('discussion')); + $link = str_replace('title="', 'title="' . tpl_getLang('discussion') . ': ', $link); + + } + + $output .= str_replace(array('class="', 'wikilink1', 'wikilink2'), + array('class="action discussion ', '', ''), $link); + + if ($icon) { + $output = preg_replace('/()/m', '$1 ', $output); + } + + } + + if ($wrapper) $output .= ""; + + } else { + + $inner = $lang['btn_' . $type]; + + if ($icon) $inner = ' ' . $inner; + + $output .= tpl_actionlink($type, '', '', $inner, true, $wrapper); + + if ($type == $ACT) $output = str_replace('class="action ', 'class="action active ', $output); + + } + + if ($return) return $output; + echo $output; + +} + + /** * Create event for tools menus * @@ -323,8 +332,6 @@ function bootstrap3_sidebar_include($type) { function bootstrap3_action_item($action, $icon = null, $return = false) { global $ACT; - global $ID; - if ($action == 'purge') { @@ -639,7 +646,7 @@ function bootstrap3_dropdown_page($page) { * @param bool $autocomplete * @return bool */ -function bootstrap3_searchform($ajax = true, $autocomplete = true) { +function bootstrap3_searchform() { global $lang; global $ACT; @@ -654,14 +661,11 @@ function bootstrap3_searchform($ajax = true, $autocomplete = true) { print ''; + print 'id="qsearch__in" autocomplete="off" type="search" placeholder="'.$lang['btn_search'].'" accesskey="f" name="id" class="form-control" title="[F]" />'; print ''; print ''; - - if ($ajax) print '
    '; print ''; return true; @@ -829,11 +833,11 @@ function bootstrap3_tools_menu($add_icons = true) { */ function bootstrap3_toolbar() { - $tools = _tpl_tools(); + $tools = bootstrap3_tools(); - foreach ($tools as $id => $menu) { - foreach ($menu['items'] as $action => $item) { - $tools[$id]['menu'][$action] = str_replace('class="action ', 'class="action btn btn-default ', bootstrap3_action_item($action, $item['icon'], 1)); + foreach ($tools as $tool => $data) { + foreach($data['menu'] as $action => $item) { + $tools[$tool]['menu'][$action] = str_replace(array('class="action ', '
  • ', '
  • '), array('class="action btn btn-default ', '', ''), $item); } } @@ -1670,6 +1674,9 @@ function bootstrap3_metaheaders(Doku_Event &$event, $param) { 'type' => 'text/javascript', 'src' => tpl_basedir() . 'assets/anchorjs/anchor.min.js'); + $event->data['script'][] = array( + 'type' => 'text/javascript', + 'src' => tpl_basedir() . 'assets/typeahead/bootstrap3-typeahead.min.js'); // Apply some FIX if ($ACT || defined('DOKU_MEDIADETAIL')) {