From 68807cd48573e60bd205aeab75f561f081eaab77 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 9 Apr 2014 14:14:24 -0700 Subject: [PATCH 01/17] Fixes some leakage in the language selector Signed-off-by: Christopher Rogers --- source/javascripts/lang_selector.js | 70 ++++++++++++++++------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/source/javascripts/lang_selector.js b/source/javascripts/lang_selector.js index 4e3dbc9..1d0685f 100644 --- a/source/javascripts/lang_selector.js +++ b/source/javascripts/lang_selector.js @@ -13,40 +13,46 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -languages = [] -function activateLanguage(language) { - $("#lang-selector a").removeClass('active'); - $("#lang-selector a[data-language-name='" + language + "']").addClass('active'); - for (var i=0; i < languages.length; i++) { - $(".highlight." + languages[i]).hide(); - } - $(".highlight." + language).show(); +(function (global) { + var languages = []; -} + global.setupLanguages = setupLanguages; + global.activateLanguage = activateLanguage; -function setupLanguages(l) { - languages = l; - currentLanguage = languages[0]; - defaultLanguage = localStorage.getItem("language"); - - if ((location.search.substr(1) != "") && (jQuery.inArray(location.search.substr(1), languages)) != -1) { - // the language is in the URL, so use that language! - activateLanguage(location.search.substr(1)); - - // set this language as the default for next time, if the URL has no language - localStorage.setItem("language", location.search.substr(1)); - } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { - // the language was the last selected one saved in localstorage, so use that language! - activateLanguage(defaultLanguage); - } else { - // no language selected, so use the default - activateLanguage(languages[0]); + function activateLanguage(language) { + $("#lang-selector a").removeClass('active'); + $("#lang-selector a[data-language-name='" + language + "']").addClass('active'); + for (var i=0; i < languages.length; i++) { + $(".highlight." + languages[i]).hide(); + } + $(".highlight." + language).show(); } - // if we click on a language tab, reload the page with that language in the URL - $("#lang-selector a").bind("click", function() { - window.location.replace("?" + $(this).data("language-name") + window.location.hash); - return false; - }); + function setupLanguages(l) { + var currentLanguage = l[0]; + var defaultLanguage = localStorage.getItem("language"); -} + languages = l; + + if ((location.search.substr(1) != "") && (jQuery.inArray(location.search.substr(1), languages)) != -1) { + // the language is in the URL, so use that language! + activateLanguage(location.search.substr(1)); + + // set this language as the default for next time, if the URL has no language + localStorage.setItem("language", location.search.substr(1)); + } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { + // the language was the last selected one saved in localstorage, so use that language! + activateLanguage(defaultLanguage); + } else { + // no language selected, so use the default + activateLanguage(languages[0]); + } + + // if we click on a language tab, reload the page with that language in the URL + $("#lang-selector a").bind("click", function() { + window.location.replace("?" + $(this).data("language-name") + window.location.hash); + return false; + }); + + } +})(window); From adf94ffbf6282153eb081b3f1e3730837893a01f Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 9 Apr 2014 14:45:41 -0700 Subject: [PATCH 02/17] Simplifies layout language selection and js init logic Signed-off-by: Christopher Rogers --- source/javascripts/init.js | 24 +++++++++++++ source/layouts/layout.erb | 74 ++++++++++++-------------------------- 2 files changed, 46 insertions(+), 52 deletions(-) create mode 100644 source/javascripts/init.js diff --git a/source/javascripts/init.js b/source/javascripts/init.js new file mode 100644 index 0000000..3bf546d --- /dev/null +++ b/source/javascripts/init.js @@ -0,0 +1,24 @@ +$(function() { + var toc = $("#toc").tocify({ + selectors: "h1,h2", + extendPage: false, + theme: "none", + smoothScroll: false, + showEffectSpeed: 0, + hideEffectSpeed: 180, + ignoreSelector: ".toc-ignore", + hashGenerator: 'pretty', + highlightOffset: 60, + scrollTo: -2, + scrollHistory: true, + hashGenerator: function(text, element) { + return element[0].getAttribute('id'); + } + }).data("toc-tocify"); + + // Hack to make already open sections to start opened, + // instead of displaying an ugly animation + setTimeout(function() { + toc.setOption("showEffectSpeed", 180); + },50); +}); diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index bcceb15..23aa1f3 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -13,6 +13,7 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. %> +<% language_tabs = current_page.data.language_tabs %> @@ -26,45 +27,16 @@ under the License. <%= stylesheet_link_tag "screen", media: 'screen' %> <%= stylesheet_link_tag "print", media: 'print' %> - + <%= javascript_include_tag "all" %> - + <% if language_tabs %> + + <% end %> @@ -73,35 +45,33 @@ under the License.
<% if current_page.data.toc_footers %> - + <% end %>
<%= yield %> - <% if current_page.data.includes %> - <% current_page.data.includes.each do |include| %> - <%= partial "includes/#{include}" %> - <% end %> + <% current_page.data.includes && current_page.data.includes.each do |include| %> + <%= partial "includes/#{include}" %> <% end %>
-
- <% if current_page.data.language_tabs %> - <% current_page.data.language_tabs.each do |lang| %> + <% if language_tabs %> +
+ <% language_tabs.each do |lang| %> <% if lang.is_a? Hash %> <%= lang.values[0] %> <% else %> <%= lang %> <% end %> <% end %> - <% end %> -
+
+ <% end %>
- \ No newline at end of file + From b171d3c48af14ceefa3047ff7c9a87f6a5ca75c5 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 9 Apr 2014 14:54:11 -0700 Subject: [PATCH 03/17] Organizes javascript into libraries and application code Signed-off-by: Christopher Rogers --- source/javascripts/all.js | 4 +- .../{lang_selector.js => app/lang.js} | 0 source/javascripts/app/search.js | 41 + source/javascripts/{init.js => app/toc.js} | 2 +- source/javascripts/{ => lib}/jquery.tocify.js | 3 +- source/javascripts/{ => lib}/jquery_ui.js | 0 source/javascripts/lib/lunr.js | 1880 +++++++++++++++++ 7 files changed, 1926 insertions(+), 4 deletions(-) rename source/javascripts/{lang_selector.js => app/lang.js} (100%) create mode 100644 source/javascripts/app/search.js rename source/javascripts/{init.js => app/toc.js} (98%) rename source/javascripts/{ => lib}/jquery.tocify.js (99%) rename source/javascripts/{ => lib}/jquery_ui.js (100%) create mode 100644 source/javascripts/lib/lunr.js diff --git a/source/javascripts/all.js b/source/javascripts/all.js index 9eae26e..534eae9 100644 --- a/source/javascripts/all.js +++ b/source/javascripts/all.js @@ -1,2 +1,2 @@ -//= require './jquery_ui' -//= require_tree . \ No newline at end of file +//= require_tree ./lib +//= require_tree ./app diff --git a/source/javascripts/lang_selector.js b/source/javascripts/app/lang.js similarity index 100% rename from source/javascripts/lang_selector.js rename to source/javascripts/app/lang.js diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js new file mode 100644 index 0000000..1479b4e --- /dev/null +++ b/source/javascripts/app/search.js @@ -0,0 +1,41 @@ +(function (global) { + + window.topic = topic; + + var index = lunr(function () { + this.field('title', { boost: 10 }); + this.field('tags', { boost: 100 }); + this.field('body'); + this.ref('id'); + }); + + $(bindSearch); + + function bindSearch () { + $('#search').on('keyup', function () { + if (this.value) { + var items = index.search(this.value); + $('article, nav li').hide(); + items.forEach(function (item) { + $('#' + item.ref + ', #' + item.ref + '-nav').show(); + }); + } else { + $('article, nav li').show(); + } + }); + + $('form').on('submit', function (event) { + event.preventDefault(); + }); + } + + function topic (title, tags, handle) { + index.add({ + id: handle, + title: title, + tags: tags, + body: $('#' + handle + '-body').text() + }); + } + +})(window); diff --git a/source/javascripts/init.js b/source/javascripts/app/toc.js similarity index 98% rename from source/javascripts/init.js rename to source/javascripts/app/toc.js index 3bf546d..b56ecb3 100644 --- a/source/javascripts/init.js +++ b/source/javascripts/app/toc.js @@ -20,5 +20,5 @@ $(function() { // instead of displaying an ugly animation setTimeout(function() { toc.setOption("showEffectSpeed", 180); - },50); + }, 50); }); diff --git a/source/javascripts/jquery.tocify.js b/source/javascripts/lib/jquery.tocify.js similarity index 99% rename from source/javascripts/jquery.tocify.js rename to source/javascripts/lib/jquery.tocify.js index 6f4c927..a48d8be 100644 --- a/source/javascripts/jquery.tocify.js +++ b/source/javascripts/lib/jquery.tocify.js @@ -1,3 +1,4 @@ +//= require ./jquery_ui /* jquery Tocify - v1.8.0 - 2013-09-16 * http://www.gregfranko.com/jquery.tocify.js/ * Copyright (c) 2013 Greg Franko; Licensed MIT @@ -1022,4 +1023,4 @@ }); -})); //end of plugin \ No newline at end of file +})); //end of plugin diff --git a/source/javascripts/jquery_ui.js b/source/javascripts/lib/jquery_ui.js similarity index 100% rename from source/javascripts/jquery_ui.js rename to source/javascripts/lib/jquery_ui.js diff --git a/source/javascripts/lib/lunr.js b/source/javascripts/lib/lunr.js new file mode 100644 index 0000000..bd221b7 --- /dev/null +++ b/source/javascripts/lib/lunr.js @@ -0,0 +1,1880 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.2 + * Copyright (C) 2014 Oliver Nightingale + * MIT Licensed + * @license + */ + +(function(){ + +/** + * Convenience function for instantiating a new lunr index and configuring it + * with the default pipeline functions and the passed config function. + * + * When using this convenience function a new index will be created with the + * following functions already in the pipeline: + * + * lunr.StopWordFilter - filters out any stop words before they enter the + * index + * + * lunr.stemmer - stems the tokens before entering the index. + * + * Example: + * + * var idx = lunr(function () { + * this.field('title', 10) + * this.field('tags', 100) + * this.field('body') + * + * this.ref('cid') + * + * this.pipeline.add(function () { + * // some custom pipeline function + * }) + * + * }) + * + * @param {Function} config A function that will be called with the new instance + * of the lunr.Index as both its context and first parameter. It can be used to + * customize the instance of new lunr.Index. + * @namespace + * @module + * @returns {lunr.Index} + * + */ +var lunr = function (config) { + var idx = new lunr.Index + + idx.pipeline.add( + lunr.trimmer, + lunr.stopWordFilter, + lunr.stemmer + ) + + if (config) config.call(idx, idx) + + return idx +} + +lunr.version = "0.5.2" +/*! + * lunr.utils + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * A namespace containing utils for the rest of the lunr library + */ +lunr.utils = {} + +/** + * Print a warning message to the console. + * + * @param {String} message The message to be printed. + * @memberOf Utils + */ +lunr.utils.warn = (function (global) { + return function (message) { + if (global.console && console.warn) { + console.warn(message) + } + } +})(this) + +/*! + * lunr.EventEmitter + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers. + * + * @constructor + */ +lunr.EventEmitter = function () { + this.events = {} +} + +/** + * Binds a handler function to a specific event(s). + * + * Can bind a single function to many different events in one call. + * + * @param {String} [eventName] The name(s) of events to bind this function to. + * @param {Function} handler The function to call when an event is fired. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.addListener = function () { + var args = Array.prototype.slice.call(arguments), + fn = args.pop(), + names = args + + if (typeof fn !== "function") throw new TypeError ("last argument must be a function") + + names.forEach(function (name) { + if (!this.hasHandler(name)) this.events[name] = [] + this.events[name].push(fn) + }, this) +} + +/** + * Removes a handler function from a specific event. + * + * @param {String} eventName The name of the event to remove this function from. + * @param {Function} handler The function to remove from an event. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.removeListener = function (name, fn) { + if (!this.hasHandler(name)) return + + var fnIndex = this.events[name].indexOf(fn) + this.events[name].splice(fnIndex, 1) + + if (!this.events[name].length) delete this.events[name] +} + +/** + * Calls all functions bound to the given event. + * + * Additional data can be passed to the event handler as arguments to `emit` + * after the event name. + * + * @param {String} eventName The name of the event to emit. + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.emit = function (name) { + if (!this.hasHandler(name)) return + + var args = Array.prototype.slice.call(arguments, 1) + + this.events[name].forEach(function (fn) { + fn.apply(undefined, args) + }) +} + +/** + * Checks whether a handler has ever been stored against an event. + * + * @param {String} eventName The name of the event to check. + * @private + * @memberOf EventEmitter + */ +lunr.EventEmitter.prototype.hasHandler = function (name) { + return name in this.events +} + +/*! + * lunr.tokenizer + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * A function for splitting a string into tokens ready to be inserted into + * the search index. + * + * @module + * @param {String} obj The string to convert into tokens + * @returns {Array} + */ +lunr.tokenizer = function (obj) { + if (!arguments.length || obj == null || obj == undefined) return [] + if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() }) + + var str = obj.toString().replace(/^\s+/, '') + + for (var i = str.length - 1; i >= 0; i--) { + if (/\S/.test(str.charAt(i))) { + str = str.substring(0, i + 1) + break + } + } + + return str + .split(/\s+/) + .map(function (token) { + return token.toLowerCase() + }) +} +/*! + * lunr.Pipeline + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Pipelines maintain an ordered list of functions to be applied to all + * tokens in documents entering the search index and queries being ran against + * the index. + * + * An instance of lunr.Index created with the lunr shortcut will contain a + * pipeline with a stop word filter and an English language stemmer. Extra + * functions can be added before or after either of these functions or these + * default functions can be removed. + * + * When run the pipeline will call each function in turn, passing a token, the + * index of that token in the original list of all tokens and finally a list of + * all the original tokens. + * + * The output of functions in the pipeline will be passed to the next function + * in the pipeline. To exclude a token from entering the index the function + * should return undefined, the rest of the pipeline will not be called with + * this token. + * + * For serialisation of pipelines to work, all functions used in an instance of + * a pipeline should be registered with lunr.Pipeline. Registered functions can + * then be loaded. If trying to load a serialised pipeline that uses functions + * that are not registered an error will be thrown. + * + * If not planning on serialising the pipeline then registering pipeline functions + * is not necessary. + * + * @constructor + */ +lunr.Pipeline = function () { + this._stack = [] +} + +lunr.Pipeline.registeredFunctions = {} + +/** + * Register a function with the pipeline. + * + * Functions that are used in the pipeline should be registered if the pipeline + * needs to be serialised, or a serialised pipeline needs to be loaded. + * + * Registering a function does not add it to a pipeline, functions must still be + * added to instances of the pipeline for them to be used when running a pipeline. + * + * @param {Function} fn The function to check for. + * @param {String} label The label to register this function with + * @memberOf Pipeline + */ +lunr.Pipeline.registerFunction = function (fn, label) { + if (label in this.registeredFunctions) { + lunr.utils.warn('Overwriting existing registered function: ' + label) + } + + fn.label = label + lunr.Pipeline.registeredFunctions[fn.label] = fn +} + +/** + * Warns if the function is not registered as a Pipeline function. + * + * @param {Function} fn The function to check for. + * @private + * @memberOf Pipeline + */ +lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { + var isRegistered = fn.label && (fn.label in this.registeredFunctions) + + if (!isRegistered) { + lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) + } +} + +/** + * Loads a previously serialised pipeline. + * + * All functions to be loaded must already be registered with lunr.Pipeline. + * If any function from the serialised data has not been registered then an + * error will be thrown. + * + * @param {Object} serialised The serialised pipeline to load. + * @returns {lunr.Pipeline} + * @memberOf Pipeline + */ +lunr.Pipeline.load = function (serialised) { + var pipeline = new lunr.Pipeline + + serialised.forEach(function (fnName) { + var fn = lunr.Pipeline.registeredFunctions[fnName] + + if (fn) { + pipeline.add(fn) + } else { + throw new Error ('Cannot load un-registered function: ' + fnName) + } + }) + + return pipeline +} + +/** + * Adds new functions to the end of the pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} functions Any number of functions to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.add = function () { + var fns = Array.prototype.slice.call(arguments) + + fns.forEach(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + this._stack.push(fn) + }, this) +} + +/** + * Adds a single function after a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} existingFn A function that already exists in the pipeline. + * @param {Function} newFn The new function to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.after = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + 1 + this._stack.splice(pos, 0, newFn) +} + +/** + * Adds a single function before a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {Function} existingFn A function that already exists in the pipeline. + * @param {Function} newFn The new function to add to the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.before = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + this._stack.splice(pos, 0, newFn) +} + +/** + * Removes a function from the pipeline. + * + * @param {Function} fn The function to remove from the pipeline. + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.remove = function (fn) { + var pos = this._stack.indexOf(fn) + this._stack.splice(pos, 1) +} + +/** + * Runs the current list of functions that make up the pipeline against the + * passed tokens. + * + * @param {Array} tokens The tokens to run through the pipeline. + * @returns {Array} + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.run = function (tokens) { + var out = [], + tokenLength = tokens.length, + stackLength = this._stack.length + + for (var i = 0; i < tokenLength; i++) { + var token = tokens[i] + + for (var j = 0; j < stackLength; j++) { + token = this._stack[j](token, i, tokens) + if (token === void 0) break + }; + + if (token !== void 0) out.push(token) + }; + + return out +} + +/** + * Resets the pipeline by removing any existing processors. + * + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.reset = function () { + this._stack = [] +} + +/** + * Returns a representation of the pipeline ready for serialisation. + * + * Logs a warning if the function has not been registered. + * + * @returns {Array} + * @memberOf Pipeline + */ +lunr.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + + return fn.label + }) +} +/*! + * lunr.Vector + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Vectors implement vector related operations for + * a series of elements. + * + * @constructor + */ +lunr.Vector = function () { + this._magnitude = null + this.list = undefined + this.length = 0 +} + +/** + * lunr.Vector.Node is a simple struct for each node + * in a lunr.Vector. + * + * @private + * @param {Number} The index of the node in the vector. + * @param {Object} The data at this node in the vector. + * @param {lunr.Vector.Node} The node directly after this node in the vector. + * @constructor + * @memberOf Vector + */ +lunr.Vector.Node = function (idx, val, next) { + this.idx = idx + this.val = val + this.next = next +} + +/** + * Inserts a new value at a position in a vector. + * + * @param {Number} The index at which to insert a value. + * @param {Object} The object to insert in the vector. + * @memberOf Vector. + */ +lunr.Vector.prototype.insert = function (idx, val) { + var list = this.list + + if (!list) { + this.list = new lunr.Vector.Node (idx, val, list) + return this.length++ + } + + var prev = list, + next = list.next + + while (next != undefined) { + if (idx < next.idx) { + prev.next = new lunr.Vector.Node (idx, val, next) + return this.length++ + } + + prev = next, next = next.next + } + + prev.next = new lunr.Vector.Node (idx, val, next) + return this.length++ +} + +/** + * Calculates the magnitude of this vector. + * + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.magnitude = function () { + if (this._magniture) return this._magnitude + var node = this.list, + sumOfSquares = 0, + val + + while (node) { + val = node.val + sumOfSquares += val * val + node = node.next + } + + return this._magnitude = Math.sqrt(sumOfSquares) +} + +/** + * Calculates the dot product of this vector and another vector. + * + * @param {lunr.Vector} otherVector The vector to compute the dot product with. + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.dot = function (otherVector) { + var node = this.list, + otherNode = otherVector.list, + dotProduct = 0 + + while (node && otherNode) { + if (node.idx < otherNode.idx) { + node = node.next + } else if (node.idx > otherNode.idx) { + otherNode = otherNode.next + } else { + dotProduct += node.val * otherNode.val + node = node.next + otherNode = otherNode.next + } + } + + return dotProduct +} + +/** + * Calculates the cosine similarity between this vector and another + * vector. + * + * @param {lunr.Vector} otherVector The other vector to calculate the + * similarity with. + * @returns {Number} + * @memberOf Vector + */ +lunr.Vector.prototype.similarity = function (otherVector) { + return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude()) +} +/*! + * lunr.SortedSet + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.SortedSets are used to maintain an array of uniq values in a sorted + * order. + * + * @constructor + */ +lunr.SortedSet = function () { + this.length = 0 + this.elements = [] +} + +/** + * Loads a previously serialised sorted set. + * + * @param {Array} serialisedData The serialised set to load. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.load = function (serialisedData) { + var set = new this + + set.elements = serialisedData + set.length = serialisedData.length + + return set +} + +/** + * Inserts new items into the set in the correct position to maintain the + * order. + * + * @param {Object} The objects to add to this set. + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.add = function () { + Array.prototype.slice.call(arguments).forEach(function (element) { + if (~this.indexOf(element)) return + this.elements.splice(this.locationFor(element), 0, element) + }, this) + + this.length = this.elements.length +} + +/** + * Converts this sorted set into an array. + * + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.toArray = function () { + return this.elements.slice() +} + +/** + * Creates a new array with the results of calling a provided function on every + * element in this sorted set. + * + * Delegates to Array.prototype.map and has the same signature. + * + * @param {Function} fn The function that is called on each element of the + * set. + * @param {Object} ctx An optional object that can be used as the context + * for the function fn. + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.map = function (fn, ctx) { + return this.elements.map(fn, ctx) +} + +/** + * Executes a provided function once per sorted set element. + * + * Delegates to Array.prototype.forEach and has the same signature. + * + * @param {Function} fn The function that is called on each element of the + * set. + * @param {Object} ctx An optional object that can be used as the context + * @memberOf SortedSet + * for the function fn. + */ +lunr.SortedSet.prototype.forEach = function (fn, ctx) { + return this.elements.forEach(fn, ctx) +} + +/** + * Returns the index at which a given element can be found in the + * sorted set, or -1 if it is not present. + * + * @param {Object} elem The object to locate in the sorted set. + * @param {Number} start An optional index at which to start searching from + * within the set. + * @param {Number} end An optional index at which to stop search from within + * the set. + * @returns {Number} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.indexOf = function (elem, start, end) { + var start = start || 0, + end = end || this.elements.length, + sectionLength = end - start, + pivot = start + Math.floor(sectionLength / 2), + pivotElem = this.elements[pivot] + + if (sectionLength <= 1) { + if (pivotElem === elem) { + return pivot + } else { + return -1 + } + } + + if (pivotElem < elem) return this.indexOf(elem, pivot, end) + if (pivotElem > elem) return this.indexOf(elem, start, pivot) + if (pivotElem === elem) return pivot +} + +/** + * Returns the position within the sorted set that an element should be + * inserted at to maintain the current order of the set. + * + * This function assumes that the element to search for does not already exist + * in the sorted set. + * + * @param {Object} elem The elem to find the position for in the set + * @param {Number} start An optional index at which to start searching from + * within the set. + * @param {Number} end An optional index at which to stop search from within + * the set. + * @returns {Number} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.locationFor = function (elem, start, end) { + var start = start || 0, + end = end || this.elements.length, + sectionLength = end - start, + pivot = start + Math.floor(sectionLength / 2), + pivotElem = this.elements[pivot] + + if (sectionLength <= 1) { + if (pivotElem > elem) return pivot + if (pivotElem < elem) return pivot + 1 + } + + if (pivotElem < elem) return this.locationFor(elem, pivot, end) + if (pivotElem > elem) return this.locationFor(elem, start, pivot) +} + +/** + * Creates a new lunr.SortedSet that contains the elements in the intersection + * of this set and the passed set. + * + * @param {lunr.SortedSet} otherSet The set to intersect with this set. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.intersect = function (otherSet) { + var intersectSet = new lunr.SortedSet, + i = 0, j = 0, + a_len = this.length, b_len = otherSet.length, + a = this.elements, b = otherSet.elements + + while (true) { + if (i > a_len - 1 || j > b_len - 1) break + + if (a[i] === b[j]) { + intersectSet.add(a[i]) + i++, j++ + continue + } + + if (a[i] < b[j]) { + i++ + continue + } + + if (a[i] > b[j]) { + j++ + continue + } + }; + + return intersectSet +} + +/** + * Makes a copy of this set + * + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.clone = function () { + var clone = new lunr.SortedSet + + clone.elements = this.toArray() + clone.length = clone.elements.length + + return clone +} + +/** + * Creates a new lunr.SortedSet that contains the elements in the union + * of this set and the passed set. + * + * @param {lunr.SortedSet} otherSet The set to union with this set. + * @returns {lunr.SortedSet} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.union = function (otherSet) { + var longSet, shortSet, unionSet + + if (this.length >= otherSet.length) { + longSet = this, shortSet = otherSet + } else { + longSet = otherSet, shortSet = this + } + + unionSet = longSet.clone() + + unionSet.add.apply(unionSet, shortSet.toArray()) + + return unionSet +} + +/** + * Returns a representation of the sorted set ready for serialisation. + * + * @returns {Array} + * @memberOf SortedSet + */ +lunr.SortedSet.prototype.toJSON = function () { + return this.toArray() +} +/*! + * lunr.Index + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Index is object that manages a search index. It contains the indexes + * and stores all the tokens and document lookups. It also provides the main + * user facing API for the library. + * + * @constructor + */ +lunr.Index = function () { + this._fields = [] + this._ref = 'id' + this.pipeline = new lunr.Pipeline + this.documentStore = new lunr.Store + this.tokenStore = new lunr.TokenStore + this.corpusTokens = new lunr.SortedSet + this.eventEmitter = new lunr.EventEmitter + + this._idfCache = {} + + this.on('add', 'remove', 'update', (function () { + this._idfCache = {} + }).bind(this)) +} + +/** + * Bind a handler to events being emitted by the index. + * + * The handler can be bound to many events at the same time. + * + * @param {String} [eventName] The name(s) of events to bind the function to. + * @param {Function} handler The serialised set to load. + * @memberOf Index + */ +lunr.Index.prototype.on = function () { + var args = Array.prototype.slice.call(arguments) + return this.eventEmitter.addListener.apply(this.eventEmitter, args) +} + +/** + * Removes a handler from an event being emitted by the index. + * + * @param {String} eventName The name of events to remove the function from. + * @param {Function} handler The serialised set to load. + * @memberOf Index + */ +lunr.Index.prototype.off = function (name, fn) { + return this.eventEmitter.removeListener(name, fn) +} + +/** + * Loads a previously serialised index. + * + * Issues a warning if the index being imported was serialised + * by a different version of lunr. + * + * @param {Object} serialisedData The serialised set to load. + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.load = function (serialisedData) { + if (serialisedData.version !== lunr.version) { + lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version) + } + + var idx = new this + + idx._fields = serialisedData.fields + idx._ref = serialisedData.ref + + idx.documentStore = lunr.Store.load(serialisedData.documentStore) + idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore) + idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens) + idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline) + + return idx +} + +/** + * Adds a field to the list of fields that will be searchable within documents + * in the index. + * + * An optional boost param can be passed to affect how much tokens in this field + * rank in search results, by default the boost value is 1. + * + * Fields should be added before any documents are added to the index, fields + * that are added after documents are added to the index will only apply to new + * documents added to the index. + * + * @param {String} fieldName The name of the field within the document that + * should be indexed + * @param {Number} boost An optional boost that can be applied to terms in this + * field. + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.prototype.field = function (fieldName, opts) { + var opts = opts || {}, + field = { name: fieldName, boost: opts.boost || 1 } + + this._fields.push(field) + return this +} + +/** + * Sets the property used to uniquely identify documents added to the index, + * by default this property is 'id'. + * + * This should only be changed before adding documents to the index, changing + * the ref property without resetting the index can lead to unexpected results. + * + * @param {String} refName The property to use to uniquely identify the + * documents in the index. + * @param {Boolean} emitEvent Whether to emit add events, defaults to true + * @returns {lunr.Index} + * @memberOf Index + */ +lunr.Index.prototype.ref = function (refName) { + this._ref = refName + return this +} + +/** + * Add a document to the index. + * + * This is the way new documents enter the index, this function will run the + * fields from the document through the index's pipeline and then add it to + * the index, it will then show up in search results. + * + * An 'add' event is emitted with the document that has been added and the index + * the document has been added to. This event can be silenced by passing false + * as the second argument to add. + * + * @param {Object} doc The document to add to the index. + * @param {Boolean} emitEvent Whether or not to emit events, default true. + * @memberOf Index + */ +lunr.Index.prototype.add = function (doc, emitEvent) { + var docTokens = {}, + allDocumentTokens = new lunr.SortedSet, + docRef = doc[this._ref], + emitEvent = emitEvent === undefined ? true : emitEvent + + this._fields.forEach(function (field) { + var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name])) + + docTokens[field.name] = fieldTokens + lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens) + }, this) + + this.documentStore.set(docRef, allDocumentTokens) + lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray()) + + for (var i = 0; i < allDocumentTokens.length; i++) { + var token = allDocumentTokens.elements[i] + var tf = this._fields.reduce(function (memo, field) { + var fieldLength = docTokens[field.name].length + + if (!fieldLength) return memo + + var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length + + return memo + (tokenCount / fieldLength * field.boost) + }, 0) + + this.tokenStore.add(token, { ref: docRef, tf: tf }) + }; + + if (emitEvent) this.eventEmitter.emit('add', doc, this) +} + +/** + * Removes a document from the index. + * + * To make sure documents no longer show up in search results they can be + * removed from the index using this method. + * + * The document passed only needs to have the same ref property value as the + * document that was added to the index, they could be completely different + * objects. + * + * A 'remove' event is emitted with the document that has been removed and the index + * the document has been removed from. This event can be silenced by passing false + * as the second argument to remove. + * + * @param {Object} doc The document to remove from the index. + * @param {Boolean} emitEvent Whether to emit remove events, defaults to true + * @memberOf Index + */ +lunr.Index.prototype.remove = function (doc, emitEvent) { + var docRef = doc[this._ref], + emitEvent = emitEvent === undefined ? true : emitEvent + + if (!this.documentStore.has(docRef)) return + + var docTokens = this.documentStore.get(docRef) + + this.documentStore.remove(docRef) + + docTokens.forEach(function (token) { + this.tokenStore.remove(token, docRef) + }, this) + + if (emitEvent) this.eventEmitter.emit('remove', doc, this) +} + +/** + * Updates a document in the index. + * + * When a document contained within the index gets updated, fields changed, + * added or removed, to make sure it correctly matched against search queries, + * it should be updated in the index. + * + * This method is just a wrapper around `remove` and `add` + * + * An 'update' event is emitted with the document that has been updated and the index. + * This event can be silenced by passing false as the second argument to update. Only + * an update event will be fired, the 'add' and 'remove' events of the underlying calls + * are silenced. + * + * @param {Object} doc The document to update in the index. + * @param {Boolean} emitEvent Whether to emit update events, defaults to true + * @see Index.prototype.remove + * @see Index.prototype.add + * @memberOf Index + */ +lunr.Index.prototype.update = function (doc, emitEvent) { + var emitEvent = emitEvent === undefined ? true : emitEvent + + this.remove(doc, false) + this.add(doc, false) + + if (emitEvent) this.eventEmitter.emit('update', doc, this) +} + +/** + * Calculates the inverse document frequency for a token within the index. + * + * @param {String} token The token to calculate the idf of. + * @see Index.prototype.idf + * @private + * @memberOf Index + */ +lunr.Index.prototype.idf = function (term) { + var cacheKey = "@" + term + if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey] + + var documentFrequency = this.tokenStore.count(term), + idf = 1 + + if (documentFrequency > 0) { + idf = 1 + Math.log(this.tokenStore.length / documentFrequency) + } + + return this._idfCache[cacheKey] = idf +} + +/** + * Searches the index using the passed query. + * + * Queries should be a string, multiple words are allowed and will lead to an + * AND based query, e.g. `idx.search('foo bar')` will run a search for + * documents containing both 'foo' and 'bar'. + * + * All query tokens are passed through the same pipeline that document tokens + * are passed through, so any language processing involved will be run on every + * query term. + * + * Each query term is expanded, so that the term 'he' might be expanded to + * 'hello' and 'help' if those terms were already included in the index. + * + * Matching documents are returned as an array of objects, each object contains + * the matching document ref, as set for this index, and the similarity score + * for this document against the query. + * + * @param {String} query The query to search the index with. + * @returns {Object} + * @see Index.prototype.idf + * @see Index.prototype.documentVector + * @memberOf Index + */ +lunr.Index.prototype.search = function (query) { + var queryTokens = this.pipeline.run(lunr.tokenizer(query)), + queryVector = new lunr.Vector, + documentSets = [], + fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0) + + var hasSomeToken = queryTokens.some(function (token) { + return this.tokenStore.has(token) + }, this) + + if (!hasSomeToken) return [] + + queryTokens + .forEach(function (token, i, tokens) { + var tf = 1 / tokens.length * this._fields.length * fieldBoosts, + self = this + + var set = this.tokenStore.expand(token).reduce(function (memo, key) { + var pos = self.corpusTokens.indexOf(key), + idf = self.idf(key), + similarityBoost = 1, + set = new lunr.SortedSet + + // if the expanded key is not an exact match to the token then + // penalise the score for this key by how different the key is + // to the token. + if (key !== token) { + var diff = Math.max(3, key.length - token.length) + similarityBoost = 1 / Math.log(diff) + } + + // calculate the query tf-idf score for this token + // applying an similarityBoost to ensure exact matches + // these rank higher than expanded terms + if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost) + + // add all the documents that have this key into a set + Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) }) + + return memo.union(set) + }, new lunr.SortedSet) + + documentSets.push(set) + }, this) + + var documentSet = documentSets.reduce(function (memo, set) { + return memo.intersect(set) + }) + + return documentSet + .map(function (ref) { + return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) } + }, this) + .sort(function (a, b) { + return b.score - a.score + }) +} + +/** + * Generates a vector containing all the tokens in the document matching the + * passed documentRef. + * + * The vector contains the tf-idf score for each token contained in the + * document with the passed documentRef. The vector will contain an element + * for every token in the indexes corpus, if the document does not contain that + * token the element will be 0. + * + * @param {Object} documentRef The ref to find the document with. + * @returns {lunr.Vector} + * @private + * @memberOf Index + */ +lunr.Index.prototype.documentVector = function (documentRef) { + var documentTokens = this.documentStore.get(documentRef), + documentTokensLength = documentTokens.length, + documentVector = new lunr.Vector + + for (var i = 0; i < documentTokensLength; i++) { + var token = documentTokens.elements[i], + tf = this.tokenStore.get(token)[documentRef].tf, + idf = this.idf(token) + + documentVector.insert(this.corpusTokens.indexOf(token), tf * idf) + }; + + return documentVector +} + +/** + * Returns a representation of the index ready for serialisation. + * + * @returns {Object} + * @memberOf Index + */ +lunr.Index.prototype.toJSON = function () { + return { + version: lunr.version, + fields: this._fields, + ref: this._ref, + documentStore: this.documentStore.toJSON(), + tokenStore: this.tokenStore.toJSON(), + corpusTokens: this.corpusTokens.toJSON(), + pipeline: this.pipeline.toJSON() + } +} + +/** + * Applies a plugin to the current index. + * + * A plugin is a function that is called with the index as its context. + * Plugins can be used to customise or extend the behaviour the index + * in some way. A plugin is just a function, that encapsulated the custom + * behaviour that should be applied to the index. + * + * The plugin function will be called with the index as its argument, additional + * arguments can also be passed when calling use. The function will be called + * with the index as its context. + * + * Example: + * + * var myPlugin = function (idx, arg1, arg2) { + * // `this` is the index to be extended + * // apply any extensions etc here. + * } + * + * var idx = lunr(function () { + * this.use(myPlugin, 'arg1', 'arg2') + * }) + * + * @param {Function} plugin The plugin to apply. + * @memberOf Index + */ +lunr.Index.prototype.use = function (plugin) { + var args = Array.prototype.slice.call(arguments, 1) + args.unshift(this) + plugin.apply(this, args) +} +/*! + * lunr.Store + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.Store is a simple key-value store used for storing sets of tokens for + * documents stored in index. + * + * @constructor + * @module + */ +lunr.Store = function () { + this.store = {} + this.length = 0 +} + +/** + * Loads a previously serialised store + * + * @param {Object} serialisedData The serialised store to load. + * @returns {lunr.Store} + * @memberOf Store + */ +lunr.Store.load = function (serialisedData) { + var store = new this + + store.length = serialisedData.length + store.store = Object.keys(serialisedData.store).reduce(function (memo, key) { + memo[key] = lunr.SortedSet.load(serialisedData.store[key]) + return memo + }, {}) + + return store +} + +/** + * Stores the given tokens in the store against the given id. + * + * @param {Object} id The key used to store the tokens against. + * @param {Object} tokens The tokens to store against the key. + * @memberOf Store + */ +lunr.Store.prototype.set = function (id, tokens) { + this.store[id] = tokens + this.length = Object.keys(this.store).length +} + +/** + * Retrieves the tokens from the store for a given key. + * + * @param {Object} id The key to lookup and retrieve from the store. + * @returns {Object} + * @memberOf Store + */ +lunr.Store.prototype.get = function (id) { + return this.store[id] +} + +/** + * Checks whether the store contains a key. + * + * @param {Object} id The id to look up in the store. + * @returns {Boolean} + * @memberOf Store + */ +lunr.Store.prototype.has = function (id) { + return id in this.store +} + +/** + * Removes the value for a key in the store. + * + * @param {Object} id The id to remove from the store. + * @memberOf Store + */ +lunr.Store.prototype.remove = function (id) { + if (!this.has(id)) return + + delete this.store[id] + this.length-- +} + +/** + * Returns a representation of the store ready for serialisation. + * + * @returns {Object} + * @memberOf Store + */ +lunr.Store.prototype.toJSON = function () { + return { + store: this.store, + length: this.length + } +} + +/*! + * lunr.stemmer + * Copyright (C) 2014 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.stemmer is an english language stemmer, this is a JavaScript + * implementation of the PorterStemmer taken from http://tartaurs.org/~martin + * + * @module + * @param {String} str The string to stem + * @returns {String} + * @see lunr.Pipeline + */ +lunr.stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + return function (w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = /.$/; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) { w = stem + "i"; } + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + return w; + } +})(); + +lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') +/*! + * lunr.stopWordFilter + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.stopWordFilter is an English language stop word list filter, any words + * contained in the list will not be passed through the filter. + * + * This is intended to be used in the Pipeline. If the token does not pass the + * filter then undefined will be returned. + * + * @module + * @param {String} token The token to pass through the filter + * @returns {String} + * @see lunr.Pipeline + */ +lunr.stopWordFilter = function (token) { + if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token +} + +lunr.stopWordFilter.stopWords = new lunr.SortedSet +lunr.stopWordFilter.stopWords.length = 119 +lunr.stopWordFilter.stopWords.elements = [ + "", + "a", + "able", + "about", + "across", + "after", + "all", + "almost", + "also", + "am", + "among", + "an", + "and", + "any", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "do", + "does", + "either", + "else", + "ever", + "every", + "for", + "from", + "get", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "in", + "into", + "is", + "it", + "its", + "just", + "least", + "let", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "only", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "where", + "which", + "while", + "who", + "whom", + "why", + "will", + "with", + "would", + "yet", + "you", + "your" +] + +lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') +/*! + * lunr.trimmer + * Copyright (C) 2014 Oliver Nightingale + */ + +/** + * lunr.trimmer is a pipeline function for trimming non word + * characters from the begining and end of tokens before they + * enter the index. + * + * This implementation may not work correctly for non latin + * characters and should either be removed or adapted for use + * with languages with non-latin characters. + * + * @module + * @param {String} token The token to pass through the filter + * @returns {String} + * @see lunr.Pipeline + */ +lunr.trimmer = function (token) { + return token + .replace(/^\W+/, '') + .replace(/\W+$/, '') +} + +lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') +/*! + * lunr.stemmer + * Copyright (C) 2014 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.TokenStore is used for efficient storing and lookup of the reverse + * index of token to document ref. + * + * @constructor + */ +lunr.TokenStore = function () { + this.root = { docs: {} } + this.length = 0 +} + +/** + * Loads a previously serialised token store + * + * @param {Object} serialisedData The serialised token store to load. + * @returns {lunr.TokenStore} + * @memberOf TokenStore + */ +lunr.TokenStore.load = function (serialisedData) { + var store = new this + + store.root = serialisedData.root + store.length = serialisedData.length + + return store +} + +/** + * Adds a new token doc pair to the store. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to store the doc under + * @param {Object} doc The doc to store against the token + * @param {Object} root An optional node at which to start looking for the + * correct place to enter the doc, by default the root of this lunr.TokenStore + * is used. + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.add = function (token, doc, root) { + var root = root || this.root, + key = token[0], + rest = token.slice(1) + + if (!(key in root)) root[key] = {docs: {}} + + if (rest.length === 0) { + root[key].docs[doc.ref] = doc + this.length += 1 + return + } else { + return this.add(rest, doc, root[key]) + } +} + +/** + * Checks whether this key is contained within this lunr.TokenStore. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to check for + * @param {Object} root An optional node at which to start + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.has = function (token) { + if (!token) return false + + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!node[token[i]]) return false + + node = node[token[i]] + } + + return true +} + +/** + * Retrieve a node from the token store for a given token. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the node for. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @see TokenStore.prototype.get + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.getNode = function (token) { + if (!token) return {} + + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!node[token[i]]) return {} + + node = node[token[i]] + } + + return node +} + +/** + * Retrieve the documents for a node for the given token. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the documents for. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.get = function (token, root) { + return this.getNode(token, root).docs || {} +} + +lunr.TokenStore.prototype.count = function (token, root) { + return Object.keys(this.get(token, root)).length +} + +/** + * Remove the document identified by ref from the token in the store. + * + * By default this function starts at the root of the current store, however + * it can start at any node of any token store if required. + * + * @param {String} token The token to get the documents for. + * @param {String} ref The ref of the document to remove from this token. + * @param {Object} root An optional node at which to start. + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.remove = function (token, ref) { + if (!token) return + var node = this.root + + for (var i = 0; i < token.length; i++) { + if (!(token[i] in node)) return + node = node[token[i]] + } + + delete node.docs[ref] +} + +/** + * Find all the possible suffixes of the passed token using tokens + * currently in the store. + * + * @param {String} token The token to expand. + * @returns {Array} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.expand = function (token, memo) { + var root = this.getNode(token), + docs = root.docs || {}, + memo = memo || [] + + if (Object.keys(docs).length) memo.push(token) + + Object.keys(root) + .forEach(function (key) { + if (key === 'docs') return + + memo.concat(this.expand(token + key, memo)) + }, this) + + return memo +} + +/** + * Returns a representation of the token store ready for serialisation. + * + * @returns {Object} + * @memberOf TokenStore + */ +lunr.TokenStore.prototype.toJSON = function () { + return { + root: this.root, + length: this.length + } +} + + + /** + * export the module via AMD, CommonnJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ + ;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like enviroments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + root.lunr = factory() + } + }(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + return lunr + })) +})() From d1b98be1823cfbb7da12ffe28725c0b5c018acd8 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 9 Apr 2014 18:12:05 -0700 Subject: [PATCH 04/17] Cleans up layout Signed-off-by: Christopher Rogers --- source/layouts/layout.erb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index 23aa1f3..62bd593 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -18,15 +18,11 @@ under the License. - - - - <%= current_page.data.title || "API Documentation" %> - <%= stylesheet_link_tag "screen", media: 'screen' %> - <%= stylesheet_link_tag "print", media: 'print' %> + <%= stylesheet_link_tag :screen, media: :screen %> + <%= stylesheet_link_tag :print, media: :print %> <%= javascript_include_tag "all" %> @@ -42,6 +38,7 @@ under the License.
<%= image_tag "logo.png" %> +
<% if current_page.data.toc_footers %> @@ -64,7 +61,7 @@ under the License.
<% language_tabs.each do |lang| %> <% if lang.is_a? Hash %> - <%= lang.values[0] %> + <%= lang.values.first %> <% else %> <%= lang %> <% end %> From e669a0d4ac93719306b8ae95ad5ea5ecb8564555 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 9 Apr 2014 18:12:55 -0700 Subject: [PATCH 05/17] Adds rudimentary search support Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 56 +++--- source/stylesheets/screen.css.scss | 296 ++++++++++++++--------------- 2 files changed, 178 insertions(+), 174 deletions(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index 1479b4e..9c8db96 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -1,40 +1,44 @@ (function (global) { - window.topic = topic; - var index = lunr(function () { - this.field('title', { boost: 10 }); - this.field('tags', { boost: 100 }); - this.field('body'); this.ref('id'); + this.field('title', { boost: 10 }); + // this.field('tags', { boost: 100 }); + this.field('body'); }); - $(bindSearch); + $(populate); + $(bind); - function bindSearch () { - $('#search').on('keyup', function () { - if (this.value) { - var items = index.search(this.value); - $('article, nav li').hide(); - items.forEach(function (item) { - $('#' + item.ref + ', #' + item.ref + '-nav').show(); - }); - } else { - $('article, nav li').show(); - } - }); + function populate () { + $('h1').each(function () { + var title = $(this); + var body = title.nextUntil('h1'); + var wrapper = $('
'); - $('form').on('submit', function (event) { - event.preventDefault(); + title.after(wrapper.append(body)); + wrapper.prepend(title); + + index.add({ + id: title.prop('id'), + title: title.text(), + // tags: tags, + body: body.text() + }); }); } - function topic (title, tags, handle) { - index.add({ - id: handle, - title: title, - tags: tags, - body: $('#' + handle + '-body').text() + function bind () { + $('#search').on('keyup', function () { + if (this.value) { + var items = index.search(this.value); + $('section, #toc .tocify-item').hide(); + items.forEach(function (item) { + $('#section-' + item.ref + ', .tocify-item[data-unique=' + item.ref).show(); + }); + } else { + $('section, #toc .tocify-item').show(); + } }); } diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index debca3d..e007db8 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -213,178 +213,178 @@ html, body { position: relative; z-index: 30; + section { + &>h1, &>h2, &>h3, &>p, &>table, &>ul, &>ol, &>aside, &>dl { + margin-right: $examples-width; + padding: 0 $main-padding; + @include box-sizing(border-box); + display: block; + @include text-shadow($main-embossed-text-shadow); + } - &>h1, &>h2, &>h3, &>p, &>table, &>ul, &>ol, &>aside, &>dl { - margin-right: $examples-width; - padding: 0 $main-padding; - @include box-sizing(border-box); - display: block; - @include text-shadow($main-embossed-text-shadow); - } + &>ul, &>ol { + padding-left: $main-padding + 15px; + } - &>ul, &>ol { - padding-left: $main-padding + 15px; - } + // the div is the tocify hidden div for placeholding stuff + &>h1, &>h2, &>div { + clear:both; + } - // the div is the tocify hidden div for placeholding stuff - &>h1, &>h2, &>div { - clear:both; - } + h1 { + @extend %header-font; + font-size: 30px; + padding-top: 0.5em; + padding-bottom: 0.5em; + border-bottom: 1px solid #ccc; + margin-top: 2em; + margin-bottom: $h1-margin-bottom; + border-top: 1px solid #ddd; + @include background-image( + linear-gradient(top, #fff, #f9f9f9) + ); + } - h1 { - @extend %header-font; - font-size: 30px; - padding-top: 0.5em; - padding-bottom: 0.5em; - border-bottom: 1px solid #ccc; - margin-top: 2em; - margin-bottom: $h1-margin-bottom; - border-top: 1px solid #ddd; - @include background-image( - linear-gradient(top, #fff, #f9f9f9) - ); - } + // The header at the very top of the page + // shouldn't have top margin. + // (the div is because of tocify) + h1:first-child, div:first-child + h1 { + margin-top: 0; + } - // The header at the very top of the page - // shouldn't have top margin. - // (the div is because of tocify) - h1:first-child, div:first-child + h1 { - margin-top: 0; - } + h2 { + @extend %header-font; + font-size: 20px; + margin-top: 4em; + margin-bottom: 0; + border-top: 1px solid #ccc; + padding-top: 1.2em; + padding-bottom: 1.2em; + @include background-image( + linear-gradient(top, rgba(#fff,0.4), rgba(#fff, 0)) + ); + } - h2 { - @extend %header-font; - font-size: 20px; - margin-top: 4em; - margin-bottom: 0; - border-top: 1px solid #ccc; - padding-top: 1.2em; - padding-bottom: 1.2em; - @include background-image( - linear-gradient(top, rgba(#fff,0.4), rgba(#fff, 0)) - ); - } + // h2s right after h1s should bump right up + // against the h1s. + h1 + h2, h1 + div + h2 { + margin-top: $h1-margin-bottom * -1; + border-top: none; + } - // h2s right after h1s should bump right up - // against the h1s. - h1 + h2, h1 + div + h2 { - margin-top: $h1-margin-bottom * -1; - border-top: none; - } + h3 { + @extend %header-font; + font-size: 12px; + margin-top: 2.5em; + margin-bottom: 0.8em; + text-transform: uppercase; + } - h3 { - @extend %header-font; - font-size: 12px; - margin-top: 2.5em; - margin-bottom: 0.8em; - text-transform: uppercase; - } + hr { + margin: 2em 0; + border-top: 2px solid $examples-bg; + border-bottom: 2px solid $main-bg; + } - hr { - margin: 2em 0; - border-top: 2px solid $examples-bg; - border-bottom: 2px solid $main-bg; - } + table { + margin-bottom: 1em; + overflow: auto; + th,td { + text-align: left; + vertical-align: top; + line-height: 1.6; + } - table { - margin-bottom: 1em; - overflow: auto; - th,td { - text-align: left; - vertical-align: top; + th { + padding: 5px 10px; + border-bottom: 1px solid #ccc; + vertical-align: bottom; + } + + td { + padding: 10px; + } + + tr:last-child { + border-bottom: 1px solid #ccc; + } + + tr:nth-child(odd)>td { + background-color: lighten($main-bg,4.2%); + } + + tr:nth-child(even)>td { + background-color: lighten($main-bg,2.4%); + } + } + + dt { + font-weight: bold; + } + + dd { + margin-left: 15px; + } + + p, li, dt, dd { line-height: 1.6; + margin-top: 0; } - th { - padding: 5px 10px; - border-bottom: 1px solid #ccc; - vertical-align: bottom; + img { + max-width: 100%; } - td { - padding: 10px; + code { + background-color: rgba(0,0,0,0.05); + padding: 3px; + border-radius: 3px; + @extend %break-words; + @extend %code-font; } - tr:last-child { - border-bottom: 1px solid #ccc; + aside { + padding-top: 1em; + padding-bottom: 1em; + text-shadow: 0 1px 0 lighten($aside-notice-bg, 15%); + margin-top: 1.5em; + margin-bottom: 1.5em; + background: $aside-notice-bg; + line-height: 1.6; + + &.warning { + background-color: $aside-warning-bg; + text-shadow: 0 1px 0 lighten($aside-warning-bg, 15%); + } + + &.success { + background-color: $aside-success-bg; + text-shadow: 0 1px 0 lighten($aside-success-bg, 15%); + } } - tr:nth-child(odd)>td { - background-color: lighten($main-bg,4.2%); + + aside.warning { } - tr:nth-child(even)>td { - background-color: lighten($main-bg,2.4%); - } - } - - dt { - font-weight: bold; - } - - dd { - margin-left: 15px; - } - - p, li, dt, dd { - line-height: 1.6; - margin-top: 0; - } - - img { - max-width: 100%; - } - - code { - background-color: rgba(0,0,0,0.05); - padding: 3px; - border-radius: 3px; - @extend %break-words; - @extend %code-font; - } - - aside { - padding-top: 1em; - padding-bottom: 1em; - text-shadow: 0 1px 0 lighten($aside-notice-bg, 15%); - margin-top: 1.5em; - margin-bottom: 1.5em; - background: $aside-notice-bg; - line-height: 1.6; - - &.warning { - background-color: $aside-warning-bg; - text-shadow: 0 1px 0 lighten($aside-warning-bg, 15%); + aside:before { + vertical-align: middle; + padding-right: 0.5em; + font-size: 14px; } - &.success { - background-color: $aside-success-bg; - text-shadow: 0 1px 0 lighten($aside-success-bg, 15%); + aside.notice:before { + @extend %icon-info-sign; + } + + aside.warning:before { + @extend %icon-exclamation-sign; + } + + aside.success:before { + @extend %icon-ok-sign; } } - - - aside.warning { - } - - aside:before { - vertical-align: middle; - padding-right: 0.5em; - font-size: 14px; - } - - aside.notice:before { - @extend %icon-info-sign; - } - - aside.warning:before { - @extend %icon-exclamation-sign; - } - - aside.success:before { - @extend %icon-ok-sign; - } - } @@ -431,4 +431,4 @@ html, body { border-bottom: 1px solid #404040; } } -} \ No newline at end of file +} From 02e48810bac400fc05d10199fc57f26d82c7eab7 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Thu, 10 Apr 2014 17:05:42 -0700 Subject: [PATCH 06/17] Adds .ruby-version Signed-off-by: Christopher Rogers --- .ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..7a895c2 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +1.9.3-p484 From f2d138797195956091d0132cfecb3502e77b6d9c Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Thu, 10 Apr 2014 17:46:39 -0700 Subject: [PATCH 07/17] Adds search icon Signed-off-by: Christopher Rogers --- source/fonts/icomoon.eot | Bin 2480 -> 2780 bytes source/fonts/icomoon.svg | 23 ++++++++++++----------- source/fonts/icomoon.ttf | Bin 2316 -> 2616 bytes source/fonts/icomoon.woff | Bin 2392 -> 2816 bytes source/stylesheets/icon-font.scss | 4 ++++ 5 files changed, 16 insertions(+), 11 deletions(-) mode change 100644 => 100755 source/fonts/icomoon.eot mode change 100644 => 100755 source/fonts/icomoon.svg mode change 100644 => 100755 source/fonts/icomoon.ttf mode change 100644 => 100755 source/fonts/icomoon.woff diff --git a/source/fonts/icomoon.eot b/source/fonts/icomoon.eot old mode 100644 new mode 100755 index 109f6c1cf63e91411c83ba93b243042e25b18d85..212835a5243aabdf5b8a4d029f41b6471ac38ff8 GIT binary patch literal 2780 zcmb7GOKg)z5T5xTyT9#VC&aG=+lg#!Bb7Sf-w&cZh$juMw=f1@e6zY-yV!UfISWx3^2#b0=6(JX<3)`3udZ+1 z2zdcI%Fdy+`clmom**k>3gglB+Ko+Ehiyl#KfY00Up^MUdmrNmSYvXtesv3eqWR-W zPNqS8i)_di`4TuMoA^16&}T}oqcPm>ZgWJBBtLB6#}8o*W;ztqn7oZsD~7SIl7`8m zBfx%gAj9%B{zx|dZZyCn(B#qF_-)i`&hP{PObo4%XGQZ+6xB&@$@aRh<|$9Ie2;f1 zPOsA&M8m~cG!~77rH~?%&gb&Ea;aLWR@~uoDW7|d(^4ck9E(nIv05qTtGmodti{}! zc8keWDp_==r`%#HnM@VksjO*ORnu!#%P?7(rghXZO&hqZ7(2cy88 z=s=|c6~nYH+%XL_^A1M%lec%;|1d40NGaRTP32`imx1qN9ChJ1;z67`%C3OCSe0D; z%CUc^L8pgNC|1={P5>F^!r)*mK5{&3iJW0sX2|Cc1^oeG2ls|TO%);hKHGL28}f~w z(&%snd z51ACBJ-*&QPLx8kjt|mtkfjNR!+<%N3vu3qA`%{B>A`5Ma+Fc%fR%F9+hvIg*)8^1 z#XAH%Z8S@Cq@@0G$Vw;B%a4jiLAtWJ( zJEQ1~PJO4^dlkkvN1xbyM;!m{`;PV*=#{><{rew2jgpH`tE!w#T2+pMx@O#>s+N}@ zg1SAbD&pFlz?np)tlH8Mx9$0U+2jI>Nrmq>M368I|fR7dW3y7mRXg7NQhXf<}g)ZreV;&k76; zh*assfdeN>DTVi@$C7*4u!OxLhK7cu5bVeozwRq!!r@F|-!4A5VhhXQJ;|~3-rcDR zi-Dnl1-tgPbXW)p6Jo1#qB79B2jf58zWY&V`Z)zu5fJ^d_Q!l1H>1Sm?A3@8;2u-= zsV*AO&pK{9B_|rtNJDk)@x7OKy(*OaxOY?Z2?zO8eqwxJd~G~6Ppir319VBh30hu& z*=j-hw&?@osibEp_kwh?$u~joZ_*a%GfmpYLn_#$1xYG4X+Isn6AZ5b%Lk6KscCIf z)7qw{wM|WHo0=AwJK3D8ZFWD-`aj~jbS^K|*X#9-o;P9ld$4N)f?mdpP`wUov;};T Y#_0lW11@;)$~Rl@%4SYmiR>l*0j~m3Z~y=R literal 2480 zcmai0&2Jk;6o2FO?%KPtH^E*T5{R89u~7pg@+VP>s1OKQEduc+1XR?xu2bZXC<$td zP-)K`2swd7{{^T7k}o~4yESzcGh0o*@RimypMVB&HI?0 znJZzU33NdL8f}7h1apqRFv|C+n}?ezkLqJlIzu(;Pz$|7ZLp*CBGsuu?^2VhvH4`%JVNR=b~Ty$)Z=$Pu5!1jc@**z6_xUK_6*U zw>AJLLB9qVYBbNEzJ4tACFqkx>Sycq>Z#tTqn{CpcmF#+vld8 zROqa~(CpNz>YLgxn13I1yj4B7K@nO9y#qSkuD0q=C%^h0^cAeJy3yI%hM#DmKX{&$ zAc-APi2|Pnz6Iwa3B~V}7s&hnw4;>1ttr=f_>^mObWA$!(CjU*7{|z&ic^pt2Gjt# ztgz7QfllA1Ua$8vqGKs;f&^x)0SzaK%B#*aTdXK;rAznIqx2MnbIC+9VaIh%*X9pp zGuceBP%f2A`B<@#%`AySy7rKWClhNTS1uK^u|Cs|^_gYSJzKA@q1EeWJ*=XtdRy1f z08~{Nnijec(lmzeguqa`V;YX`zvUJtq11w|s)oK6HC<6vMZb4h3!-T8;S9mj^F zML$>B(`;rQ{-;DD59bjRVwI7O1m(ob^ZXT)ds87(6d<_MhI201O{bTab01d1kz+wU ztj@+_vop4BnllTtv#!X*MC?Apn3@WQb^VrKw0DN=K}TH7EiEOJ??sJOMbAc~mK94x zB9Y0txr{9rHm9acWH1_yL~Q$kwwG=Xb9Rl)Plnd$H8CkTl+}6%8Q=Y z9ya8E9A^s&_I!SBbdT+ybN2HdFAFHTfCqR65m(aRli__aO?kO5#&-lC3gV#u{U@Wq zwg2@Jj|Ic{j-exY8YOFb04>>+%)g3oaf6{)&sMcpOAsTqB)wE^gpX ztMLsN41>cqB`$4BT-ublv?+0EQ{ozQS6y9cbM(RC|1cSmIaBMjI-T~|OJ($pGopZ? i>v-kxi>FH4fTt);Z_{~<73Zz=+~BR`=2T|UXZ#Nu4tLuC diff --git a/source/fonts/icomoon.svg b/source/fonts/icomoon.svg old mode 100644 new mode 100755 index e23b01c..2449358 --- a/source/fonts/icomoon.svg +++ b/source/fonts/icomoon.svg @@ -3,15 +3,16 @@ Generated by IcoMoon - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/source/fonts/icomoon.ttf b/source/fonts/icomoon.ttf old mode 100644 new mode 100755 index 455bd6deba9b8c4af7fc99a31a7650ea539cb9dc..360b3d751620cc106382cda4c2d548656db343f7 GIT binary patch literal 2616 zcmb7GO>7%Q6n<}iX4ZCUCyoD-+D>FQb|s42?%H0W{4_%SNvZ%+p$OCpaMLuYl*AED z)#idK#E}EyS`G+t=1^7Y1q2e;N^t5W94aC8fJ)UvdxM&9b~dpS+#=T5_cw38dGF1e zoe&dIjBb)iN8ft;i0c&p`T>^j@qVQjUPV%8eqB{lul&eRVyYz)o79|dJQ~E4#u!NjXsp^zuRrt2r>mS zw|^hD!WqE;2ou9A5uj)ru>J91$fX3`Vv`i*+U-!`T)JI1X^&&?MKdDokq@Z3T^UvRyNG1IVo zGkAQ{JRXgJd8%Md`krZW|(vR{h930 z$uUcm4a+hUk!T_wjR`xxKamJsgos9L+i`4+uk9&LP_XL?r~~`cP%7OQn-U^tSc6hJ zG8&aqI0OBIG3_Uuh(PsVl(FbOe^2Ft8g|#GOcV6ZPJQ-PDW!_jwXx=22esQTF2@+Q z=m28BphL^$G{1?>3pTz}sk=Q>ufqRJ?WDx>fU0VhNf|o&_1<}+6~aC~OeZ1MjxYiS zEz5F(D*+X$j7A4-)cs(WB|2og*)xsc5b&&BtJ(Ij?MPAcgmfTwEM84p zTD>y*Q3|haMTE>sX6d?$W~GQ&^$>?yA%OyYMjewJb50$UsbjTVfw_>Y9h=f$3*Fy3 zFSRWdscBj5m<%Y+o}Wnt6n#6z8P&ZyuZrm8<>P3h?fEv4z;B+826d z^jJAFpAphxX3UsHN>-#4v5?y=Kw!0`ID?dg9PG}bJ3IBA>g-h#-yD5nD_wc~ckEj_ zX0VsW+KKOf#57DEKCP;AN=4NW6R*h0IZlsb0!*iz%UlfcH zE*K4enyXwHD^`tqZ5l%bxWtVTKiRY+(TEir=o5w7sY8cO)e4I5FOKB*v0({&Nem1O zNFmsf&wo8oEhUqs>Va*3c*z!)!Ta(f#r@k;6&HO2F$=i%rgT^c2?(*#Jy8Md+=JPl zZ{7JYG5L&QstJgGS@~nO!^13bIR`zW25^t5`&2g#&-li8VxCo#@%GVW{XfA@6fo{Qar!Qdfhi8FzG)4}r$gCB?!@JlpG V7ikl8E&zTbTs+8Xr-;3Te*iBhKi>cV literal 2316 zcmaJ?-ESL35TCvC-Pw1wFUCF_5=fjTu@M0h`Rj;MM1?@eDhP=$A)unhb)BI295q30 z5i0F74}?5{ME?aq0?99Z=nF3el^2ANDv*#+B|g*_gy5mn{O0zYvlBae+MVCb&dkot z?(LEwB8x7OO3Tl`xKvDh{?{@jSJ6*3+V#zE{+YZ&B<=@)tX1FI1f2l?DrmH|apBDM z6Ek0eKSiW{y4I|p?p^)&cOv}`=BsNE7|ZH)BBKm`eyzQIeo_G~dX0|n)6MxjN6RawF>KdJ(!KNuJqhJvHj~XbX+t;kxub=AAzvxGRkvD7Rmz3@ zqBv^k4~TR&vnq;iwNgm+g-)t3bVc`Ev$=}aY@Q2IP16j|(9u9NO_;hKy%^PXrf)|f zP`mpqeB1xrHYQ=zhOMSWzZMNc)il+(dqt0+>9Q&^hcrD)O`?TSp>m{!{2bz+5t$N# zM^4C9W;&FVpD)kzSIHhMg)EUm2**Qk0ol#vmX?Yi)MD`y5hJEer&7~Xj$>I<^V8FT z%8?_fdrUK#jKvJ&=BR9M5BP(PxKvzR%x2$Bm@BGLNF;1Km5ImW6SK2=JI6f}T^5&|e%BYPk2R1nBqIYFvt}Ce^ zP@Wq|O?V@de;t})L7Fk{!ip(~LIDWK?J&O(;VkoCB6EG1p2le|F9~FV5j$@S-hW%@ zPI^Wd{)mI{gvk11wc?IV?IWwzvaFJ2+YB(4tb>xDM&%^lKg^`>ORWKan-M@KSvE+(lY&&B=|!Iy$K?0^5+Ec4#~cA3YrX@194RLz}H z8?T(4FmXztY?aIj8_KL|pA730BL9v?P((8u4}Jfpee7?tA`lYY`1rM{LF(Vscy#>Q z@&s)7-5@*y+*EJimiHy(zdrus;ra8dbD;aGCT-9iaaR1OJf>V!Zup*JbVj~&dn-z> za!k-?!Fqxc5Xl!u0e&XHD)4-OBfu{QSfdO$sjrhk#Q>Xl(;EE3MPiV!Pl=^ZiKS18 zrB8{aPl8v+8?M}xV7SanaY|%R? mRUHn743KmauGw*&wn0x*j^3gR7;FCG&jpM7S;-2H8UF{OFlWU8 diff --git a/source/fonts/icomoon.woff b/source/fonts/icomoon.woff old mode 100644 new mode 100755 index 3b3ee94df0c875e3cb601022a06f4c1cff79a0b8..9a213b112669b937374d2cd9030609d6041169ea GIT binary patch literal 2816 zcmcImYiv|S6rS7N+fGYqLA!*9nxbfoCbm?HV9=Oaiv~zZm?VEYdGMnOxroO^Rm?S9y@IrKCwXxLr53$_KYtrnEL8raqVBW$liUm?hM zLAut^)ZPi(KIp}Q@+HQaYC8p940@(ZxtmyXZ4>;;KFXN1R#5rkuP?L&_qQXai&#_W zk`FV??`M)E@y5DfQ!v=fqzu@ek3W>`3a9)m3*vlsCF5z|3;tz(M>aCoWPWlpn&qnI3N84Zo|&f~okbb?L{PaHWu zGJb3nY-A$(74eBgJU~CnWa@@yfFdgKYLYe5bPYq-NaeIeCQOKF5b?7ln`A^H5yOZW zWODslV8E%6$=$CR*g||G$tnQ_5Wvv5oxlP;f?2H6P2xY0jJ;`)2uEa5XI_BL3LI05 z-0}rD@I}{k2%M^AQX?9GJedFbGVN3cC>B1{^KtK?t`80j9T+@dy+=E!A=I{0RU(R| z@GpZh(BXp!!Z3$F8X6j~2dyD@9 zIrX^t5H7l*qo>3lsg%4_pYV8 zy>i?u-R(Ufa(G(g@U&M8xaZchg~)j#U1H=88kVLN!>YeDVvu~@PIA|8+j`edkBoM&8%*e0K<^b86t(v@Ma&l5OO~WSq zG)uL3>rq*?!bX_Fq-cu5YqoAZBcn-NOITAC-r6QBimqTmI1;v0K3P*!BP*&dwuIe& z8f}wJ1qT4sGAxUqIWxIcwiL^?*oJBG(W5fDz#^MFzH|3Bx<8%10DU2ot=o#}InK+7 zreP5Q!iG?IJ;`o>s){Hn%&DwQSVVw|szKniM<(RKQoT_4G*Owb2vAjo!Y4>}12j{i z(DZc5Vp(Pc(eo>Z?|*Cwbmd(gcd)=K#QpIq?sLutXaLDIdF$wGuejZl$WHJLKh3qF zf4rpAK_pYb-C-qF1tq`8;_l&Z)v9V z(sEi(rKMoYFO|-~bTT$RruW!b#;+&l%dyayemE98tdE6aJTc|)Ksi-S(Rn&QH3ddf zRa8#pRaNE0^WxEhIR58z-KT^PSD4LY+_)R1dfb=mQ{Nf3g!M!J_mIRhV9t1nvCCP~ z#Uy^xMfdi`y+1n&T`2<`H##c-7qbPpG1uUmgI7ideiAFZoR$1LnS_l&X09wHe_QHL z?Qkc+Bqm{1zKa6&_chy!-pXG$!{&)282fqOAL31t`X;YRH{g?a%XrgbKNG&<_J$eq zjWm+03YI%7K@iSLr|wK1%BJ+LJob4CPhxqg?c%6BwmxO|L0*xP{gAh$WSK33=4CDu zuZfbBoWq`Ebs`7;Y*+-QCwrKl>|uJchv~^4rYC1&@7mN}53>=tLDmEcvS#)M+s)e8 ze%9z}g={5z2Cuj|YtqlKIYw56jeA%J+sA4FFn4Y}=9*pLTGo#5E>_5PvUkB>a}R$# Lb+{W9*qz5e&1UyY literal 2392 zcmaJ?O>7%Q6n?Yo-L-dPZ;byS5sA|zHX=YG|29ey6@r3QP$m9^fC`N3+Ci~hH9=`P zl=jSlk`qYu#u32*Bwsl6QVA7QE(jqNkdRO%KN)Id{RBQOAxzD7wN;1g4)$JUcp~-X4`(`?TgdnDuAUA` z50JP_29frW_AU7L6&8O|UWC9OJdRTOmZ@Lu;nT0u(LKs($s=mwo#L3e;&BSl1HdM* zAdNlrdf?NysMqWLiY!@+haiC!cb7+yMCDbv%pNOxTkq1n^e{aR&q*;rqgi1meK(cP}s*D&h!ZJ(+bhSj!A z3?Rc0wrK`01Wl9iTR{l)?j8$i`(L?*MHsbUYZ$?6qGsuap<8#am;nq^RYm0xr)LQL zS{xNBN1DydBmP;D%p-W@gj`joLrKYed7ZyvYJVwIi8MkuZ3O3`opgG6Irm;A6h0oX zLdHxiHZz?_IL`FK%#5cpF%i4RwxiKd$g*yZ%J%kvKiG(ixuvC4>Ya$as$1De#C2oI za5y|UH2RWrYNt@#oiOamx4IxfB(fU zaPNP=#B;&6zhf*)_T^EH*G^8_I3-Ya^7f<)Wma`h`t|WCc}F8CqS=iHzkl65@;5mF z2#Icd_{#JkmNzxOiG0p^z=q!q!Xv;#Zx7t^QbO|eVRfvH1*vM&2Hg>7#gE!y?Sghg zdJ55Ld4H%1Dv$mgq0hYi1VtcLFOCNKDUa4cFL-nS^ot&CP!gQdH_4)$N85PQYW%_l zLXfaeMJu0*Rz4N2d@5S`RI~}(RnJ!WT!-HdwE!Jz(@S_K&eA5WOD;_-^ax-`XT8>G xbvo@~Aw37f2EC0^RpDUB07=*3njKeZ3-}bJ=}kJ1xgs}z*4tcW Date: Thu, 10 Apr 2014 17:47:01 -0700 Subject: [PATCH 08/17] Updates search field markup and style Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 2 +- source/layouts/layout.erb | 4 +++- source/stylesheets/screen.css.scss | 30 +++++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index 9c8db96..0ce6eb4 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -29,7 +29,7 @@ } function bind () { - $('#search').on('keyup', function () { + $('#input-search').on('keyup', function () { if (this.value) { var items = index.search(this.value); $('section, #toc .tocify-item').hide(); diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index 62bd593..a09fc03 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -38,7 +38,9 @@ under the License.
<%= image_tag "logo.png" %> - +
<% if current_page.data.toc_footers %> diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index e007db8..bbebcd0 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -54,13 +54,37 @@ html, body { // This is the logo at the top of the ToC &>img { display: block; - margin-bottom: $logo-margin; + } + + &>.search { + position: relative; + + input { + background: $nav-bg; + border-width: 0 0 1px 0; + border-color: $nav-footer-border-color; + padding: 6px 0 6px 20px; + box-sizing: border-box; + margin: 10px 15px; + width: $nav-width - 30; + outline: none; + color: $nav-text; + letter-spacing: 0.07em; + } + + &:before { + @extend %icon-search; + position: absolute; + top: 16px; + left: 15px; + color: $nav-text; + } } .tocify-item>a, .toc-footer li { padding: 0 $nav-padding 0 $nav-padding; - display:block; - overflow-x:hidden; + display: block; + overflow-x: hidden; white-space: nowrap; text-overflow: ellipsis; } From 5892700b5cee3db59036d98e8c33775edbaa965a Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Thu, 10 Apr 2014 14:55:00 -0700 Subject: [PATCH 09/17] Fixes toc alignment on search and adds highlight library Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 32 +++--- source/javascripts/app/toc.js | 55 +++++++---- source/javascripts/lib/jquery.highlight.js | 108 +++++++++++++++++++++ source/stylesheets/screen.css.scss | 4 + 4 files changed, 167 insertions(+), 32 deletions(-) create mode 100644 source/javascripts/lib/jquery.highlight.js diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index 0ce6eb4..a7441bf 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -1,5 +1,6 @@ (function (global) { + var $global = $(global); var index = lunr(function () { this.ref('id'); this.field('title', { boost: 10 }); @@ -29,17 +30,26 @@ } function bind () { - $('#input-search').on('keyup', function () { - if (this.value) { - var items = index.search(this.value); - $('section, #toc .tocify-item').hide(); - items.forEach(function (item) { - $('#section-' + item.ref + ', .tocify-item[data-unique=' + item.ref).show(); - }); - } else { - $('section, #toc .tocify-item').show(); - } - }); + $('#input-search').on('keyup', search); + } + + function search () { + var sections = $('section, #toc .tocify-header'); + + if (this.value) { + var items = index.search(this.value); + sections.hide(); + items.forEach(function (item) { + $('#section-' + item.ref).show(); + $('.tocify-item[data-unique=' + item.ref + ']').closest('.tocify-header').show(); + }); + } else { + sections.show(); + } + + // HACK trigger tocify height recalculation + $global.triggerHandler('scroll.tocify'); + $global.triggerHandler('resize'); } })(window); diff --git a/source/javascripts/app/toc.js b/source/javascripts/app/toc.js index b56ecb3..3761c2b 100644 --- a/source/javascripts/app/toc.js +++ b/source/javascripts/app/toc.js @@ -1,24 +1,37 @@ -$(function() { - var toc = $("#toc").tocify({ - selectors: "h1,h2", - extendPage: false, - theme: "none", - smoothScroll: false, - showEffectSpeed: 0, - hideEffectSpeed: 180, - ignoreSelector: ".toc-ignore", - hashGenerator: 'pretty', - highlightOffset: 60, - scrollTo: -2, - scrollHistory: true, - hashGenerator: function(text, element) { - return element[0].getAttribute('id'); - } - }).data("toc-tocify"); +(function (global) { + + var toc; + + global.toc = toc; + + $(toc); + $(animate); + + function toc () { + toc = $("#toc").tocify({ + selectors: 'h1, h2', + extendPage: false, + theme: 'none', + smoothScroll: false, + showEffectSpeed: 0, + hideEffectSpeed: 180, + ignoreSelector: '.toc-ignore', + highlightOffset: 60, + scrollTo: -2, + scrollHistory: true, + hashGenerator: function (text, element) { + return element.prop('id'); + } + }).data('toc-tocify'); + } // Hack to make already open sections to start opened, // instead of displaying an ugly animation - setTimeout(function() { - toc.setOption("showEffectSpeed", 180); - }, 50); -}); + function animate () { + setTimeout(function() { + toc.setOption('showEffectSpeed', 180); + }, 50); + } + +})(window); + diff --git a/source/javascripts/lib/jquery.highlight.js b/source/javascripts/lib/jquery.highlight.js new file mode 100644 index 0000000..9dcf3c7 --- /dev/null +++ b/source/javascripts/lib/jquery.highlight.js @@ -0,0 +1,108 @@ +/* + * jQuery Highlight plugin + * + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * + * Code a little bit refactored and cleaned (in my humble opinion). + * Most important changes: + * - has an option to highlight only entire words (wordsOnly - false by default), + * - has an option to be case sensitive (caseSensitive - false by default) + * - highlight element tag and class names can be specified in options + * + * Usage: + * // wrap every occurrance of text 'lorem' in content + * // with (default options) + * $('#content').highlight('lorem'); + * + * // search for and highlight more terms at once + * // so you can save some time on traversing DOM + * $('#content').highlight(['lorem', 'ipsum']); + * $('#content').highlight('lorem ipsum'); + * + * // search only for entire word 'lorem' + * $('#content').highlight('lorem', { wordsOnly: true }); + * + * // don't ignore case during search of term 'lorem' + * $('#content').highlight('lorem', { caseSensitive: true }); + * + * // wrap every occurrance of term 'ipsum' in content + * // with + * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); + * + * // remove default highlight + * $('#content').unhighlight(); + * + * // remove custom highlight + * $('#content').unhighlight({ element: 'em', className: 'important' }); + * + * + * Copyright (c) 2009 Bartek Szopka + * + * Licensed under MIT license. + * + */ + +jQuery.extend({ + highlight: function (node, re, nodeName, className) { + if (node.nodeType === 3) { + var match = node.data.match(re); + if (match) { + var highlight = document.createElement(nodeName || 'span'); + highlight.className = className || 'highlight'; + var wordNode = node.splitText(match.index); + wordNode.splitText(match[0].length); + var wordClone = wordNode.cloneNode(true); + highlight.appendChild(wordClone); + wordNode.parentNode.replaceChild(highlight, wordNode); + return 1; //skip added node in parent + } + } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children + !/(script|style)/i.test(node.tagName) && // ignore script and style nodes + !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted + for (var i = 0; i < node.childNodes.length; i++) { + i += jQuery.highlight(node.childNodes[i], re, nodeName, className); + } + } + return 0; + } +}); + +jQuery.fn.unhighlight = function (options) { + var settings = { className: 'highlight', element: 'span' }; + jQuery.extend(settings, options); + + return this.find(settings.element + "." + settings.className).each(function () { + var parent = this.parentNode; + parent.replaceChild(this.firstChild, this); + parent.normalize(); + }).end(); +}; + +jQuery.fn.highlight = function (words, options) { + var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; + jQuery.extend(settings, options); + + if (words.constructor === String) { + words = [words]; + } + words = jQuery.grep(words, function(word, i){ + return word != ''; + }); + words = jQuery.map(words, function(word, i) { + return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }); + if (words.length == 0) { return this; }; + + var flag = settings.caseSensitive ? "" : "i"; + var pattern = "(" + words.join("|") + ")"; + if (settings.wordsOnly) { + pattern = "\\b" + pattern + "\\b"; + } + var re = new RegExp(pattern, flag); + + return this.each(function () { + jQuery.highlight(this, re, settings.element, settings.className); + }); +}; + diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index bbebcd0..eb8b562 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -35,6 +35,10 @@ html, body { background-color: $main-bg; } +.highlight { + background-color: #FFFF88; +} + //////////////////////////////////////////////////////////////////////////////// // TABLE OF CONTENTS //////////////////////////////////////////////////////////////////////////////// From 535a3b04869405efe5679b8eb274f505e5e2c7ea Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Fri, 11 Apr 2014 18:01:55 -0700 Subject: [PATCH 10/17] Adds 'no results found' message to search Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 30 +++++++++++++++++++++--------- source/layouts/layout.erb | 7 ++++++- source/stylesheets/screen.css.scss | 25 +++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index a7441bf..451eaa6 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -14,7 +14,7 @@ function populate () { $('h1').each(function () { var title = $(this); - var body = title.nextUntil('h1'); + var body = title.nextUntil('h1, .search-nothing-found'); var wrapper = $('
'); title.after(wrapper.append(body)); @@ -33,18 +33,30 @@ $('#input-search').on('keyup', search); } - function search () { - var sections = $('section, #toc .tocify-header'); + function search (event) { + var $sections = $('section, #toc .tocify-header'); + var $content = $('.content'); + var opts = { element: 'span', className: 'search-highlight' }; + + $content.unhighlight(opts); + + // esc clears the field + if (event.keyCode === 27) this.value = ''; if (this.value) { var items = index.search(this.value); - sections.hide(); - items.forEach(function (item) { - $('#section-' + item.ref).show(); - $('.tocify-item[data-unique=' + item.ref + ']').closest('.tocify-header').show(); - }); + $sections.hide(); + if (items.length) { + items.forEach(function (item) { + $('#section-' + item.ref).show(); + $('.tocify-item[data-unique=' + item.ref + ']').closest('.tocify-header').show(); + }); + $content.highlight(this.value, opts); + } else { + $sections.filter('.search-nothing-found').show(); + } } else { - sections.show(); + $sections.show(); } // HACK trigger tocify height recalculation diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index a09fc03..2ba2257 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -23,7 +23,7 @@ under the License. <%= stylesheet_link_tag :screen, media: :screen %> <%= stylesheet_link_tag :print, media: :print %> - + <%= javascript_include_tag "all" %> <% if language_tabs %> @@ -57,6 +57,11 @@ under the License. <% current_page.data.includes && current_page.data.includes.each do |include| %> <%= partial "includes/#{include}" %> <% end %> +
+ +
<% if language_tabs %> diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index eb8b562..5bdc7cc 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -33,10 +33,16 @@ html, body { -moz-osx-font-smoothing: grayscale; @extend %default-font; background-color: $main-bg; + height: 100%; } -.highlight { - background-color: #FFFF88; +.search-highlight { + background-image: linear-gradient(to bottom right, #F7E633 0%, #F1D32F 100%); + padding: 2px; + margin: -2px; + border-radius: 4px; + border: 1px solid #F7E633; + text-shadow: 1px 1px 0 #666; } //////////////////////////////////////////////////////////////////////////////// @@ -180,6 +186,7 @@ html, body { position: relative; z-index: 10; background-color: $main-bg; + min-height: 100%; padding-bottom: 1px; // prevent margin overflow @@ -412,6 +419,20 @@ html, body { aside.success:before { @extend %icon-ok-sign; } + + aside.search-info { + font-size: 1.2em; + margin-top: 0; + height: 52px; + &:before { + @extend %icon-search; + font-size: 1.2em; + } + } + + .search-nothing-found { + display: none; + } } } From 4d568401a3ac66e7c4e3f96585145378804e5256 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Fri, 11 Apr 2014 20:13:32 -0700 Subject: [PATCH 11/17] Improves no results notice and removes highlight and notice on search field blur Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 52 ++++++++++++++++++++++-------- source/layouts/layout.erb | 6 +--- source/stylesheets/screen.css.scss | 36 +++++++++++++-------- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index 451eaa6..4edd487 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -1,6 +1,8 @@ (function (global) { var $global = $(global); + var content, searchInfo; + var highlightOpts = { element: 'span', className: 'search-highlight' }; var index = lunr(function () { this.ref('id'); this.field('title', { boost: 10 }); @@ -14,7 +16,7 @@ function populate () { $('h1').each(function () { var title = $(this); - var body = title.nextUntil('h1, .search-nothing-found'); + var body = title.nextUntil('h1'); var wrapper = $('
'); title.after(wrapper.append(body)); @@ -30,33 +32,38 @@ } function bind () { - $('#input-search').on('keyup', search); + content = $('.content'); + searchInfo = $('.search-info'); + $('#input-search') + .on('keyup', search) + .on('focus', active) + .on('blur', inactive); } function search (event) { - var $sections = $('section, #toc .tocify-header'); - var $content = $('.content'); - var opts = { element: 'span', className: 'search-highlight' }; + var sections = $('section, #toc .tocify-header'); - $content.unhighlight(opts); + searchInfo.hide(); + unhighlight(); - // esc clears the field + // ESC clears the field if (event.keyCode === 27) this.value = ''; if (this.value) { - var items = index.search(this.value); - $sections.hide(); - if (items.length) { - items.forEach(function (item) { + sections.hide(); + var results = index.search(this.value); + if (results.length) { + $.each(results, function (index, item) { $('#section-' + item.ref).show(); $('.tocify-item[data-unique=' + item.ref + ']').closest('.tocify-header').show(); }); - $content.highlight(this.value, opts); + highlight.call(this); } else { - $sections.filter('.search-nothing-found').show(); + sections.show(); + searchInfo.text('No Results Found for "' + this.value + '"').show(); } } else { - $sections.show(); + sections.show(); } // HACK trigger tocify height recalculation @@ -64,4 +71,21 @@ $global.triggerHandler('resize'); } + function active () { + search.call(this, {}); + } + + function inactive () { + unhighlight(); + searchInfo.hide(); + } + + function highlight () { + if (this.value) content.highlight(this.value, highlightOpts); + } + + function unhighlight () { + content.unhighlight(highlightOpts); + } + })(window); diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index 2ba2257..7ae03c3 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -52,16 +52,12 @@ under the License. <% end %>
+
<%= yield %> <% current_page.data.includes && current_page.data.includes.each do |include| %> <%= partial "includes/#{include}" %> <% end %> -
- -
<% if language_tabs %> diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index 5bdc7cc..22b5d35 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -243,6 +243,28 @@ html, body { //////////////////////////////////////////////////////////////////////////////// // This is all the stuff with the light background in the left half of the page +.search-info { + margin-top: 0; + min-height: 52px; + padding: 1em 1.75em; + font-size: 1.2em; + font-weight: bold; + text-shadow: 0 1px 0 lighten($aside-notice-bg, 15%); + background: $aside-notice-bg; // TODO: color + position: fixed; + z-index: 75; + display: none; + + @include box-sizing(border-box); + + &:before { + @extend %icon-search; + vertical-align: middle; + padding-right: 0.5em; + font-size: 1.2em; + } +} + .content { // to place content above the dark box position: relative; @@ -419,20 +441,6 @@ html, body { aside.success:before { @extend %icon-ok-sign; } - - aside.search-info { - font-size: 1.2em; - margin-top: 0; - height: 52px; - &:before { - @extend %icon-search; - font-size: 1.2em; - } - } - - .search-nothing-found { - display: none; - } } } From 477146e5bf876b0baf72d8836f2fdc43f7bf2cf7 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 15 Apr 2014 11:02:32 -0500 Subject: [PATCH 12/17] Removes .ruby-version Signed-off-by: Christopher Rogers --- .ruby-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 7a895c2..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -1.9.3-p484 From 0a0e84b8e82ebfa133a2bc4dd6f2d0cd730f6323 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 15 Apr 2014 13:40:45 -0500 Subject: [PATCH 13/17] Standardizes search styles Signed-off-by: Christopher Rogers --- source/stylesheets/screen.css.scss | 63 +++++++++++++++--------------- source/stylesheets/variables.scss | 2 + 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index 22b5d35..2935e18 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -36,15 +36,6 @@ html, body { height: 100%; } -.search-highlight { - background-image: linear-gradient(to bottom right, #F7E633 0%, #F1D32F 100%); - padding: 2px; - margin: -2px; - border-radius: 4px; - border: 1px solid #F7E633; - text-shadow: 1px 1px 0 #666; -} - //////////////////////////////////////////////////////////////////////////////// // TABLE OF CONTENTS //////////////////////////////////////////////////////////////////////////////// @@ -72,7 +63,7 @@ html, body { input { background: $nav-bg; border-width: 0 0 1px 0; - border-color: $nav-footer-border-color; + border-color: $search-box-border-color; padding: 6px 0 6px 20px; box-sizing: border-box; margin: 10px 15px; @@ -243,28 +234,6 @@ html, body { //////////////////////////////////////////////////////////////////////////////// // This is all the stuff with the light background in the left half of the page -.search-info { - margin-top: 0; - min-height: 52px; - padding: 1em 1.75em; - font-size: 1.2em; - font-weight: bold; - text-shadow: 0 1px 0 lighten($aside-notice-bg, 15%); - background: $aside-notice-bg; // TODO: color - position: fixed; - z-index: 75; - display: none; - - @include box-sizing(border-box); - - &:before { - @extend %icon-search; - vertical-align: middle; - padding-right: 0.5em; - font-size: 1.2em; - } -} - .content { // to place content above the dark box position: relative; @@ -442,8 +411,38 @@ html, body { @extend %icon-ok-sign; } } + + .search-highlight { + padding: 2px; + margin: -2px; + border-radius: 4px; + border: 1px solid #F7E633; + text-shadow: 1px 1px 0 #666; + @include background(linear-gradient(to bottom right, #F7E633 0%, #F1D32F 100%)); + } } +.search-info { + margin-top: 0; + min-height: 52px; + padding: 1em 1.75em; + font-size: 1.2em; + font-weight: bold; + text-shadow: 0 1px 0 lighten($search-notice-bg, 15%); + background: $search-notice-bg; + position: fixed; + z-index: 75; + display: none; + + @include box-sizing(border-box); + + &:before { + @extend %icon-search; + vertical-align: middle; + padding-right: 0.5em; + font-size: 1.2em; + } +} //////////////////////////////////////////////////////////////////////////////// // CODE SAMPLE STYLES diff --git a/source/stylesheets/variables.scss b/source/stylesheets/variables.scss index 5aab15b..6b9021a 100644 --- a/source/stylesheets/variables.scss +++ b/source/stylesheets/variables.scss @@ -37,6 +37,7 @@ $main-bg: #eaf2f6; $aside-notice-bg: #8fbcd4; $aside-warning-bg: #c97a7e; $aside-success-bg: #6ac174; +$search-notice-bg: #8fbcd4; // TEXT COLORS @@ -86,6 +87,7 @@ $nav-footer-border-color: #666; $nav-embossed-border-top: 1px solid #000; $nav-embossed-border-bottom: 1px solid #404040; $main-embossed-text-shadow: 0px 1px 0px #fff; +$search-box-border-color: #666; //////////////////////////////////////////////////////////////////////////////// From 3c4e0d0bae16d61adbdbab444f2b2caed0d67bde Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 15 Apr 2014 13:44:56 -0500 Subject: [PATCH 14/17] Switches jQuery to load through Google CDN Signed-off-by: Christopher Rogers --- source/layouts/layout.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/layouts/layout.erb b/source/layouts/layout.erb index 7ae03c3..0f98aaf 100644 --- a/source/layouts/layout.erb +++ b/source/layouts/layout.erb @@ -23,7 +23,7 @@ under the License. <%= stylesheet_link_tag :screen, media: :screen %> <%= stylesheet_link_tag :print, media: :print %> - + <%= javascript_include_tag "all" %> <% if language_tabs %> From 440c799c4ccade907bd5e49d0a8f6a90646bbbe3 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 16 Apr 2014 14:03:58 -0700 Subject: [PATCH 15/17] Removes lunr stemmer from search index pipeline Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index 4edd487..e1fca62 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -3,12 +3,13 @@ var $global = $(global); var content, searchInfo; var highlightOpts = { element: 'span', className: 'search-highlight' }; - var index = lunr(function () { - this.ref('id'); - this.field('title', { boost: 10 }); - // this.field('tags', { boost: 100 }); - this.field('body'); - }); + + var index = new lunr.Index; + + index.ref('id'); + index.field('title', { boost: 10 }); + index.field('body'); + index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); $(populate); $(bind); @@ -25,7 +26,6 @@ index.add({ id: title.prop('id'), title: title.text(), - // tags: tags, body: body.text() }); }); From 0ebbae94fed7e2f552326f9c2892ee360bb50787 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 16 Apr 2014 14:11:43 -0700 Subject: [PATCH 16/17] Cleans up some css Signed-off-by: Christopher Rogers --- source/stylesheets/screen.css.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/stylesheets/screen.css.scss b/source/stylesheets/screen.css.scss index 2935e18..e061c7f 100644 --- a/source/stylesheets/screen.css.scss +++ b/source/stylesheets/screen.css.scss @@ -389,10 +389,6 @@ html, body { } } - - aside.warning { - } - aside:before { vertical-align: middle; padding-right: 0.5em; From a4e1fb3c24088b3bb57c3b3f41fd28aba07f7321 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 16 Apr 2014 15:05:05 -0700 Subject: [PATCH 17/17] Adds js handler for setting search info box width equal to content width Signed-off-by: Christopher Rogers --- source/javascripts/app/search.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/javascripts/app/search.js b/source/javascripts/app/search.js index e1fca62..d443a39 100644 --- a/source/javascripts/app/search.js +++ b/source/javascripts/app/search.js @@ -1,7 +1,7 @@ (function (global) { var $global = $(global); - var content, searchInfo; + var content, darkBox, searchInfo; var highlightOpts = { element: 'span', className: 'search-highlight' }; var index = new lunr.Index; @@ -33,11 +33,16 @@ function bind () { content = $('.content'); + darkBox = $('.dark-box'); searchInfo = $('.search-info'); + $('#input-search') .on('keyup', search) .on('focus', active) .on('blur', inactive); + + $global.on('resize', resize); + resize(); } function search (event) { @@ -88,4 +93,8 @@ content.unhighlight(highlightOpts); } + function resize () { + searchInfo.innerWidth(content.innerWidth() - darkBox.innerWidth()); + } + })(window);