// Perforce Documentation Javascript !function ($) { $(function() { var $window = $(window), $body = $(document.body), navHeight = $('header').outerHeight(true) + 10; // if we get this far, we don't need the noscript block $("noscript").remove(); // but we do need to see the other elements $("header").css("display", "block"); $("footer").css("display", "block"); $(".body").css("display", "block"); $("#content").scrollspy({ target: 'nav.toc', offset: navHeight }); // hookup swiping for navigation $(window).touchwipe({ wipeLeft: function() { if (!$("link[rel='next']").length) return; e.preventDefault(); window.location = $("link[rel='next']").attr("href"); }, wipeRight: function() { if (!$("link[rel='prev']").length) return; e.preventDefault(); window.location = $("link[rel='prev']").attr("href"); }, preventDefaultEvents: false }); // hookup menu $("button.toc").on("click", function (e) { e.preventDefault(); p4.toggleMenu(); }); $("a.expander").on("click", function (e) { e.preventDefault(); var pNode = $(this).parent("li"), ulNode = $(this).next("ul"); if (pNode.hasClass("expanded")) { pNode.removeClass("expanded"); ulNode.css("maxHeight", ""); } else { pNode.addClass("expanded"); var ulHeight = 0; ulNode.find("li").each(function () { ulHeight += $(this).height(); }); ulNode.css("maxHeight", ulHeight + "px"); } }); // hookup search $("button.search").on("click", function (e) { e.preventDefault(); p4.toggleSearch(); }); $("button.clear").on("click", function (e) { e.preventDefault(); $("#search-text").val(""); p4.search.unhighlight(); }); var searchCookie = "textToSearch"; $("#search-text").on("keyup", function (e) { var results = p4.search.start($("#search-text").val()); p4.search.unhighlight(); p4.search.render(results); p4.search.highlight(p4.search.theWords); }); $(".search-highlight a").click(function (e) { e.preventDefault(); p4.search.toggleHighlight(); }); p4.search.highlight(); // hookup n/p, and left/right arrows, for keyboard doc navigation $(window).on('keydown', function(e) { // don't act on special keys, already handled events, or text input areas if (e.altKey || e.ctrlKey || e.metaKey || e.isDefaultPrevented() || $(e.target).is('input, textarea, select') ) { return; } if (e.which === 78 || e.which === 39) { // n or right-arrow for next if (!$("link[rel='next']").length) return; e.preventDefault(); window.location = $("link[rel='next']").attr("href"); } else if (e.which === 80 || e.which === 37) { // p or left-arrow for previous if (!$("link[rel='prev']").length) return; e.preventDefault(); window.location = $("link[rel='prev']").attr("href"); } else if (e.which === 85) { // u if (!$("link[rel='up']").length) return; e.preventDefault(); window.location = $("link[rel='up']").attr("href"); } else if (e.which === 72) { // h if (!$("link[rel='top']").length) return; e.preventDefault(); window.location = $("link[rel='top']").attr("href"); } else if (e.which === 84) { // t e.preventDefault(); document.body.scrollTop = document.documentElement.scrollTop = 0; } else if (e.which === 70 || e.which === 83) { // f or s e.preventDefault(); p4.toggleSearch(); } }); }); }(window.jQuery); // ==================================================================== // Customized Bootstrap ScrollSpy for Perforce documentation navigation // ==================================================================== +function ($) { "use strict"; // SCROLLSPY CLASS DEFINITION // ========================== function ScrollSpy(element, options) { var href var process = $.proxy(this.process, this) this.$element = $(element).is('body') ? $(window) : $(element) this.$body = $('body') this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process) this.options = $.extend({}, ScrollSpy.DEFAULTS, options) this.selector = (this.options.target || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 || '') + ' .nav li > a' this.offsets = $([]) this.targets = $([]) this.activeTarget = null this.refresh() this.process() } ScrollSpy.DEFAULTS = { offset: 10 } ScrollSpy.prototype.refresh = function () { var offsetMethod = this.$element[0] == window ? 'offset' : 'position' this.offsets = $([]) this.targets = $([]) var dURL = window.location.pathname.match(/\/?([^\/]+)$/) var self = this var $targets = this.$body .find(this.selector) .map(function () { var $el = $(this) var href = $el.data('target') || $el.attr('href') var $href = /^#\w/.test(href) && $(href) var href2 = /\/?([^/#]+)(#.+)$/.exec(href); var $href2 = href2 && href2[2] && dURL && href2[1] === dURL[1] && $(href2[2]); if (!$href2 || !$href2.length) { return null; } return ( [[ $href2[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href2[2] ]] ) || null; }) .sort(function (a, b) { return a[0] - b[0] }) .each(function () { self.offsets.push(this[0]) self.targets.push(this[1]) }) } ScrollSpy.prototype.process = function () { var scrollTop = this.$scrollElement.scrollTop() + this.options.offset var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight var maxScroll = scrollHeight - this.$scrollElement.height() var offsets = this.offsets var targets = this.targets var activeTarget = this.activeTarget var i if (scrollTop >= maxScroll) { return activeTarget != (i = targets.last()[0]) && this.activate(i) } for (i = offsets.length; i--;) { activeTarget != targets[i] && scrollTop >= offsets[i] && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) && this.activate( targets[i] ) } } ScrollSpy.prototype.activate = function (target) { this.activeTarget = target $(this.selector) .parents('.active') .removeClass('active') var selector = this.selector + '[data-target$="' + target + '"],' + this.selector + '[href$="' + target + '"]' var active = $(selector) .parents('li') .addClass('active') if (active.parent('.dropdown-menu').length) { active = active .closest('li.dropdown') .addClass('active') } active.trigger('activate') } // SCROLLSPY PLUGIN DEFINITION // =========================== var old = $.fn.scrollspy $.fn.scrollspy = function (option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.scrollspy') var options = typeof option == 'object' && option if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.scrollspy.Constructor = ScrollSpy // SCROLLSPY NO CONFLICT // ===================== $.fn.scrollspy.noConflict = function () { $.fn.scrollspy = old return this } // SCROLLSPY DATA-API // ================== $(window).on('load', function () { $('[data-spy="scroll"]').each(function () { var $spy = $(this) $spy.scrollspy($spy.data()) }) }) }(window.jQuery); // ==================================================================== // Customized Search facility, adapted from DocBook WebHelp code // ==================================================================== p4 = {}; p4.toggleMenu = function() { var elem = $("nav"); if (elem.hasClass("open")) { elem.removeClass("open"); $("footer").removeClass("close"); $("#content").removeClass("nofooter"); $("button.toc").removeClass("active"); } else { elem.addClass("open"); $("footer").addClass("close"); $("#content").addClass("nofooter"); $("button.toc").addClass("active"); $(".search-interface").removeClass("open"); $("button.search").removeClass("active"); } }; p4.toggleSearch = function() { var elem = $(".search-interface"); if (elem.hasClass("open")) { elem.removeClass("open"); $("footer").removeClass("close"); $("#content").removeClass("nofooter"); $("button.search").removeClass("active"); $("#search-text").blur(); } else { elem.addClass("open"); $("footer").addClass("close"); $("#content").addClass("nofooter"); $("button.search").addClass("active"); $("#search-text").focus(); $("nav").removeClass("open"); $("button.toc").removeClass("active"); } }; p4.search = { cookieName: "p4doc_search", reAlpha: /[^a-zA-Z]/g, reNumeric: /[^0-9]/g, theWords: "", start: function(searchFor) { if (searchFor === undefined || searchFor.length < 3) return; var wordList = this.wordList(searchFor), stemmed = this.stemmed(wordList), stemmedWordList = stemmed[0], stemmedMap = stemmed[1]; // compute scores per matching file var fileScores = {}; for (i = 0; i < stemmedWordList.length; i++) { var stemmedWord = stemmedWordList[i].toString(), fileScoreString = w[stemmedWord], fileScoreList = fileScoreString !== undefined ? fileScoreString.split(",") : []; for (j = 0; j < fileScoreList.length; j++) { var fileAndScore = fileScoreList[j].toString().split("*"), file = fileAndScore[0].toString(), score = parseInt(fileAndScore[1], 10); var fileScore = fileScores[file]; if (fileScore === undefined) { var bits = fil[file].split("@@@"); fileScores[file] = { url: bits[0], title: bits[1], description: bits[2], words: [], stemmed: [], score: 0 } } fileScores[file].score += score; fileScores[file].stemmed.push(stemmedWord); fileScores[file].words.push(stemmedMap[stemmedWord]); } } // collect results var results = []; for (i in fileScores) { if (fileScores.hasOwnProperty(i)) { results.push(fileScores[i]); } } // sort results, by score, by word count, finally by title results.sort(function(a, b) { // compare scores var diff = b.score - a.score; if (diff !== 0) return diff; // compare word count diff = b.words.length - a.words.length; if (diff !== 0) return diff; // compare titles var aTitle = a.title.replace(this.reAlpha, ""), bTitle = b.title.replace(this.reAlpha, ""); if (aTitle === bTitle) { // compare titles numerically aTitle = parseInt(a.title.replace(this.reNumeric, ""), 10); bTitle = parseInt(b.title.replace(this.reNumeric, ""), 10); return aTitle === bTitle ? 0 : aTitle > bTitle ? 1 : -1; } else { return aTitle > bTitle ? 1 : -1; } }); return results; }, // turn on highlighting of search terms highlight: function(searchText) { if (searchText === undefined) { searchText = $.cookie(this.cookieName); $("#search-text").val(searchText); } if (searchText !== undefined) { var wordList = this.wordList(searchText), stemmed = this.stemmed(wordList); wordList = this.unique(wordList.concat(stemmed[0])); $("#content").highlight(wordList); } }, // render search results render: function(results) { if (results === undefined) results = []; var html = ''; if (results.length > 0) { var barMax = results[0].score, i; for (i = 0; i < results.length; i++) { var result = results[i]; result.width = (result.score / barMax * 100).toFixed(); html += $.templates( '<li>' + '<a href="{{:url}}">' + '<div class="title">{{:title}}</div>' + '</a>' + '</li>' ).render(result); } } $(".search-interface .results").html('').append($(html)); $(".search-interface .count .number").html(results.length); }, // compute word stems for each search term stemmed: function(wordList) { var i, j, stemmedMap = [] stemmedWordList = []; for (i = 0; i < wordList.length; i++) { var word = wordList[i], stemmed = p4.search.stemmer(word), target = w[stemmed] !== undefined ? stemmed : word; stemmedMap[target] = word; stemmedWordList.push(target); } return [ this.unique(stemmedWordList), stemmedMap ]; }, // toggle highlighting of search terms toggleHighlight: function () { if ($(".highlight").length) { this.unhighlight(); } else { this.highlight(); } }, // turn off highlighting of search terms unhighlight: function () { $("#content").unhighlight(); }, // remove duplicate array entries unique: function(list) { var hash = {}, uniq = [], i, l; for (i = 0, l = list.length; i < l; ++i) { if (hash.hasOwnProperty(list[i])) continue; uniq.push(list[i]); hash[list[i]] = 1; } return uniq; }, // convert a string into a space-separated list of words wordList: function(searchString) { var string = this.theWords; if (searchString !== undefined) { string = $.trim( searchString.replace(/[^ -z]/g, "") .replace(/['"\/]/g, " ") .replace(/\.$/, "") .replace(/ +/g, " ") ); this.theWords = string; $.cookie(this.cookieName, string); } return string.split(" ").sort(); } }; p4.search.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, origword = w; 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 = new RegExp("^(.+" + c + ")y$"); if (re.test(w)) { var fp = re.exec(w); stem = fp[1]; 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); } // See http://snowball.tartarus.org/algorithms/english/stemmer.html // "Exceptional forms in general" var specialWords = { "skis": "ski", "skies": "sky", "dying": "die", "lying": "lie", "tying": "tie", "idly": "idl", "gently": "gentl", "ugly" : "ugli", "early": "earli", "only": "onli", "singly": "singl" }; if(specialWords[origword]){ w = specialWords[origword]; } if ("sky news howe atlas cosmos bias andes inning outing canning herring earring proceed exceed succeed".indexOf(origword) !== -1) { w = origword; } // Address words overstemmed as gener- re = /.*generate?s?d?(ing)?$/; if (re.test(origword)) { w = w + 'at'; } re = /.*general(ly)?$/; if (re.test(origword)) { w = w + 'al'; } re = /.*generic(ally)?$/; if (re.test(origword)) { w = w + 'ic'; } re = /.*generous(ly)?$/; if (re.test(origword)) { w = w + 'ous'; } // Address words overstemmed as commun- re = /.*communit(ies)?y?/; if (re.test(origword)) { w = w + 'iti'; } return w; } })(); // prime the word index var w = new Object();
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 13895 | Paul Allen | Copying using p4convert-docbook | ||
//guest/perforce_software/doc_build/main/perforce/assets/webhelp/js/perforce.js | |||||
#7 | 12727 | eedwards |
Further presentation tweaks to the prototype HTML docs: - improve the presentation of the noscript warning when javascript is disabled. - customize the javascript disabled text to refer to 'documentation' instead of 'this site' - remove some stray styles and cruft that interfered with the intended presentation |
||
#6 | 12726 | eedwards |
Tweaks to the TOC presentation in prototype HTML for docs: - Animated open/close of TOC section - Reflect open/close state in TOC section expansion button - Made section nesting more visually apparent |
||
#5 | 12725 | eedwards |
Fixes for prototype HTML presentation: - don't attempt to highlight search terms when there are none - adjust inline-block styling in the header so that the text-overflow property takes effect as expected - use smaller font sizes for headings for narrower browsers, and apply transitions to heading font sizes - unroll transition styles to include all browser-specific transition styles. |
||
#4 | 12723 | eedwards |
Tweaks to search interface in HTML prototype for docs: - hookup the search field's clear button - adjust how the wordList is managed to allow for dynamic highlighting in the current page - highlight search terms in the current page |
||
#3 | 12722 | eedwards | Fix prototype HTML's javascript, to handle the implicit index.html case. | ||
#2 | 12721 | eedwards |
Tweaks to the prototype HTML presentation for docs: - use CSS classes rather than hard-coded values in javascript to implement sidebar open/close - made the search sidebar disappear when not open, not just to the right where it can be seen in wide browsers - made the TOC sidebar disappear when the search sidebar appears (at narrow browser widths), and vice-versa. |
||
#1 | 12720 | eedwards |
Apply better organization for DocBook transformation assets. In the future, other output target assets will appear as peers of webhelp, within the assets directory. |
||
//guest/perforce_software/doc_build/main/perforce/webhelp_assets/js/perforce.js | |||||
#3 | 12719 | eedwards |
Minor updates to prototype HTML presentation: - make ScrollSpy work, so that the current section of the documentation is highlighted in the sidebar TOC. - add expander buttons to the TOC so that users can manually explore the TOC. - remove the now-unused SIDR code. - remove some cruft. |
||
#2 | 12718 | eedwards |
WebHelp transformation improvements: - consolidate the Java indexer's output files into a single file to reduce HTTP requests, and tidy up the search assets the indexer created - move the search interface and results into their own pane on the right side of the display - make search incremental as users type - replace existing navigation links with a TOC sidebar that is displayed when the browser is wide enough, or use a footer with prev/next links when the browser is too narrow. In the narrow presentation, the TOC sidebar can be toggled open. - Various improvements to colours, formatting, at various browser sizes. |
||
#1 | 12717 | eedwards |
Initial prototype of new HTML generation intended to exist on the Perforce public web site. The updates features a more responsive design, removal of frames, styling improvements. More work needs to be done to ensure robust presentation across various devices, and further design tweaks may be required once various stakeholders get a chance to have a look. Future infrastructure work will need to be done: - incorporate PDF generation - some organizational tweaks to make additional output targets easy to add - HTML validation assets need to be added/incorporated |