From 73e117f181dd61adc82522b11ae59b7d2ae270e3 Mon Sep 17 00:00:00 2001 From: Rob Miller Date: Fri, 7 Feb 2020 16:15:55 -0500 Subject: [PATCH] Refactored jump-links into a separate JavaScript file --- web/handout/handout-page.js | 1 + web/handout/handout-render.js | 77 +++++------------------------------ web/handout/jump-links.js | 67 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 66 deletions(-) create mode 100644 web/handout/jump-links.js diff --git a/web/handout/handout-page.js b/web/handout/handout-page.js index 3d08819..a9f2998 100644 --- a/web/handout/handout-page.js +++ b/web/handout/handout-page.js @@ -41,6 +41,7 @@ HANDOUT_SCRIPTDIR = document.querySelector('script[src*=handout-page]').getAttri require('https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js', function () { var stages = [ [ './course-setup.js#render', + './jump-links.js#render', './handout-render.js', './render/Markdown.Converter.js', 'https://cdnjs.cloudflare.com/ajax/libs/pluralize/7.0.0/pluralize.min.js#render', diff --git a/web/handout/handout-render.js b/web/handout/handout-render.js index a3ced88..dd70443 100644 --- a/web/handout/handout-render.js +++ b/web/handout/handout-render.js @@ -147,8 +147,17 @@ function renderPage() { if ( ! this.id) { this.id = uniqueIdentifier('id', '^', this.dataset.structureText); } }); - // assign IDs to content chunks - identifyChunks($('.table-of-contents').length); + // assign IDs to content chunks and create # links + makeJumpLinks({ + jumpable: 'h1, h2, h3, h4, h5, h6, .panel-heading' + + ($('.table-of-contents').length ? ', p, pre, ol:not(li ol), ul:not(li ul), dl, table, .exercise-part-heading' : ''), + exclude: '.exercise-explain *, .exercise-choice *, .faq h3 + div > p:first-child', + nest: { + 'ol, ul': 'li', + 'dl': 'dt', + 'table': 'th, td', + }, + }); // build table of contents $('.table-of-contents').each(function() { @@ -452,70 +461,6 @@ function convertExercise(container, category, node) { body.append(foot); } -function identifyChunks(dense) { - var jumpable = 'h1, h2, h3, h4, h5, h6, .panel-heading' + (dense ? ', p, pre, ol:not(li ol), ul:not(li ul), dl, table, .exercise-part-heading' : ''); - var exclude = '.exercise-explain *, .faq h3 + div > p:first-child'; - var nest = { - 'ol, ul': 'li', - 'dl': 'dt', - 'table': 'th, td', - }; - var elements = $(jumpable).not(':has(' + jumpable + ')').not(exclude); - var chunks = {}; - var stopwords = [ - 'a', 'an', 'and', 'are', 'as', 'at', 'by', 'for', 'from', 'has', 'have', 'how', - 'in', 'is', 'it', 'it-s', 'its', 'let-s', 'of', 'on', 'or', 'that', 'the', 'this', 'to', - 'was', 'we', 'were', 'we-ll', 'we-re', 'what', 'when', 'where', 'who', 'will', 'with', - ]; - elements.map(function(idx, elt) { - var words = $(this).text().toLowerCase().split(/\s+/).map(function(word) { - return word.replace(/^\W+|\W+$/g, '').replace(/\W+/g, '-'); - }).filter(function(word) { - return word && (stopwords.indexOf(word) < 0); - }); - return { $elt: this, $words: words }; - }).each(function() { - var here = chunks; - while (this.$words.length) { - var word = this.$words.shift(); - if ( ! here[word]) { - here[word] = this; - return; - } - if (here[word].$elt) { - var current = here[word]; - here[word] = {}; - if ( ! current.$words.length) { return; } - here[word][current.$words.shift()] = current; - } - here = here[word]; - } - }); - var size = 3; - (function labeler(labels, tree) { - if (tree.$elt) { - if ( ! tree.$elt.id) { - Array.prototype.push.apply(labels, tree.$words.slice(0, (size - (labels.length % size)) % size)); - tree.$elt.id = '@' + labels.join('_'); - } - } else { - Object.keys(tree).forEach(function(key) { - labeler(labels.concat(key), tree[key]); - }); - } - })([], chunks); - elements.filter('[id]').each(function(idx, elt) { - var parent = $(elt); - $.each(nest, function(outer, inner) { - if (parent.is(outer)) { - parent = $(inner, parent).not(':empty').first(); - return false; - } - }); - parent.prepend($('').addClass('jump').attr('href', '#' + elt.id)); - }); -} - function uniqueIdentifier(attr, prefix, text, context) { var base = prefix + text.toLowerCase().replace(/\s/g, '_').replace(/[^\w-]/g, '').replace(/([_-])\1+/g, '$1'); var val = base; diff --git a/web/handout/jump-links.js b/web/handout/jump-links.js new file mode 100644 index 0000000..bc9ed39 --- /dev/null +++ b/web/handout/jump-links.js @@ -0,0 +1,67 @@ + +// assign IDs to content chunks, and create # links in the margin so that user can obtain a link there + +// spec: { +// jumpable: string, jQuery selector of elements that should get jump links +// exclude: string, jQuery selector of elements to exclude from jumpable +// nest: { selector:string -> selector:string } +// } +function makeJumpLinks(spec) { + var jumpable = spec.jumpable; + var exclude = spec.exclude; + var nest = spec.nest; + var elements = $(jumpable).not(':has(' + jumpable + ')').not(exclude); + var chunks = {}; + var stopwords = [ + 'a', 'an', 'and', 'are', 'as', 'at', 'by', 'for', 'from', 'has', 'have', 'how', + 'in', 'is', 'it', 'it-s', 'its', 'let-s', 'of', 'on', 'or', 'that', 'the', 'this', 'to', + 'was', 'we', 'were', 'we-ll', 'we-re', 'what', 'when', 'where', 'who', 'will', 'with', + ]; + elements.map(function(idx, elt) { + var words = $(this).text().toLowerCase().split(/\s+/).map(function(word) { + return word.replace(/^\W+|\W+$/g, '').replace(/\W+/g, '-'); + }).filter(function(word) { + return word && (stopwords.indexOf(word) < 0); + }); + return { $elt: this, $words: words }; + }).each(function() { + var here = chunks; + while (this.$words.length) { + var word = this.$words.shift(); + if ( ! here[word]) { + here[word] = this; + return; + } + if (here[word].$elt) { + var current = here[word]; + here[word] = {}; + if ( ! current.$words.length) { return; } + here[word][current.$words.shift()] = current; + } + here = here[word]; + } + }); + var size = 3; + (function labeler(labels, tree) { + if (tree.$elt) { + if ( ! tree.$elt.id) { + Array.prototype.push.apply(labels, tree.$words.slice(0, (size - (labels.length % size)) % size)); + tree.$elt.id = '@' + labels.join('_'); + } + } else { + Object.keys(tree).forEach(function(key) { + labeler(labels.concat(key), tree[key]); + }); + } + })([], chunks); + elements.filter('[id]').each(function(idx, elt) { + var parent = $(elt); + $.each(nest, function(outer, inner) { + if (parent.is(outer)) { + parent = $(inner, parent).not(':empty').first(); + return false; + } + }); + parent.prepend($('').addClass('jump').attr('href', '#' + elt.id)); + }); +}