HTMLOutline bookmarklet. For use with http://backtick.io
See:
HTMLOutline bookmarklet. For use with http://backtick.io
See:
| (function() { | |
| /*____BEGIN_OPTIONS____*/ | |
| var numbering = 0001, linkColor = '', clickOutside = true, showDetails = false; | |
| /*_____END_OPTIONS_____*/ | |
| /*_____BEGIN_CSS_____*/ | |
| var CSSRules = [ | |
| // CSS reset (except: text-rendering, link color) | |
| // directional properties should not be reset, but no browser implements | |
| // the abstractions of the CSS3 writing mode module | |
| // and not setting direction:ltr breaks the layout... | |
| "#h5o-outside,#h5o-outside *{\ | |
| background-color:transparent;\ | |
| border:none;\ | |
| border-radius:0;\ | |
| bottom:auto;\ | |
| box-shadow:none;\ | |
| box-sizing:content-box;\ | |
| clear:none;\ | |
| " + (linkColor ? "color:" + linkColor + ";" : "") + "\ | |
| cursor:auto;\ | |
| direction:ltr;\ | |
| float:none;\ | |
| font-family:sans-serif;\ | |
| font-size:small;\ | |
| font-stretch:normal;\ | |
| font-style:normal;\ | |
| font-variant:normal;\ | |
| font-weight:normal;\ | |
| height:auto;\ | |
| left:auto;\ | |
| letter-spacing:normal;\ | |
| line-height:normal;\ | |
| margin:0;\ | |
| opacity:1;\ | |
| outline:none;\ | |
| overflow:visible;\ | |
| padding:0;\ | |
| position:static;\ | |
| right:auto;\ | |
| text-align:left;\ | |
| text-decoration:none;\ | |
| text-indent:0;\ | |
| text-overflow:clip;\ | |
| text-shadow:none;\ | |
| text-transform:none;\ | |
| top:auto;\ | |
| vertical-align:baseline;\ | |
| visibility:visible;\ | |
| white-space:normal;\ | |
| width:auto;\ | |
| word-break:normal;\ | |
| word-spacing:normal;\ | |
| word-wrap:normal;\ | |
| zoom:normal;\ | |
| }", | |
| "#h5o-inside *:not([href]){\ | |
| color:black;\ | |
| }", | |
| "#h5o-outside{\ | |
| background-color:transparent;\ | |
| box-sizing:border-box;\ | |
| display:block;\ | |
| height:100%;\ | |
| padding:10px;\ | |
| pointer-events:none;\ | |
| position:fixed;\ | |
| right:0px;\ | |
| top:0px;\ | |
| max-width:500px;\ | |
| z-index:2147483647;\ | |
| }", | |
| "#h5o-inside{\ | |
| background-color:white;\ | |
| border:2px solid black;\ | |
| box-sizing:border-box;\ | |
| display:block;\ | |
| max-width:100%;\ | |
| max-height:100%;\ | |
| opacity:.9;\ | |
| overflow:auto;\ | |
| padding:10px;\ | |
| pointer-events:auto;\ | |
| }", | |
| "#h5o-inside ol{\ | |
| counter-reset:li;\ | |
| display:block;\ | |
| margin:0;\ | |
| padding:0;\ | |
| }", | |
| "#h5o-inside li{\ | |
| counter-increment:li;\ | |
| display:list-item;\ | |
| list-style-type:none;\ | |
| margin-left:" + (numbering === 2 ? "2em" : "1.5em") + ";\ | |
| position:relative;\ | |
| }", | |
| "#h5o-inside a{\ | |
| display:inline;\ | |
| }", | |
| "#h5o-inside a:hover{\ | |
| text-decoration:underline;\ | |
| }", | |
| "#h5o-inside li.h5o-notitle>a{\ | |
| font-style:italic;\ | |
| }", | |
| ]; | |
| if(numbering > 0) CSSRules.push("#h5o-inside li::before{\ | |
| content:" + (numbering === 2 ? "counters(li,\".\")" : "counter(li)") + "\".\";\ | |
| display:block;\ | |
| left:-10.5em;\ | |
| position:absolute;\ | |
| text-align:right;\ | |
| width:10em;\ | |
| display:block;\ | |
| max-width:100%;\ | |
| max-height:100%;\ | |
| }"); | |
| else CSSRules.push("#h5o-inside>ol>li{margin-left:0;}"); | |
| /*_____END_CSS_____*/ | |
| // This must be global so that event listener can be removed when clicking on bookmarklet again | |
| if(!window.h5o_sdWoNJpsAgQGAaf) window.h5o_sdWoNJpsAgQGAaf = function() { | |
| document.removeEventListener("click", h5o_sdWoNJpsAgQGAaf, false); | |
| document.body.removeChild(document.getElementById("h5o-outside")); | |
| }; | |
| if(document.getElementById("h5o-outside")) { | |
| h5o_sdWoNJpsAgQGAaf(); | |
| return; | |
| } | |
| var toc = document.createElement("div"); | |
| toc.id = "h5o-outside"; | |
| // Stylesheet | |
| var style = document.createElement("style"); | |
| toc.appendChild(style); | |
| document.body.appendChild(toc); | |
| for(var i = 0; i < CSSRules.length; i++) { | |
| try { | |
| style.sheet.insertRule(CSSRules[i].replace(/;/g, " !important;"), i); | |
| } catch(e) {} | |
| } | |
| var inside = document.createElement("div"); | |
| inside.id = "h5o-inside"; | |
| if(clickOutside) { | |
| inside.addEventListener("click", function(event) {event.stopPropagation();}, false); | |
| document.addEventListener("click", h5o_sdWoNJpsAgQGAaf, false); | |
| } | |
| // Create outline | |
| HTMLOutline(document.body); | |
| if(!document.body.sectionList) return; // HTML4 frameset documents | |
| inside.appendChild(printOutline(document.body.sectionList)); | |
| toc.appendChild(inside); | |
| function printOutline(outline) { | |
| var ol = document.createElement("ol"); | |
| for(var i = 0; i < outline.length; i++) { | |
| ol.appendChild(printSection(outline[i])); | |
| } | |
| return ol; | |
| } | |
| function printSection(section) { | |
| var li = document.createElement("li"); | |
| var title = document.createElement("a"); | |
| li.appendChild(title); | |
| if(section.heading === null || /^[ \r\n\t]*$/.test(section.heading.text)) { | |
| li.className = "h5o-notitle"; | |
| switch(section.associatedNodes[0].nodeName.toLowerCase()) { | |
| case "body": title.textContent = "Document"; break; | |
| case "article": title.textContent = "Article"; break; | |
| case "aside": title.textContent = "Aside"; break; | |
| case "nav": title.textContent = "Navigation"; break; | |
| case "section": title.textContent = "Section"; break; | |
| default: title.textContent = "Empty title"; | |
| } | |
| } else title.textContent = section.heading.text; | |
| var node = section.associatedNodes[0]; | |
| if(node.sectionType !== 1 && node.sectionType !== 2) node = section.heading; | |
| title.href = "#" + node.id; | |
| title.addEventListener("click", function(event) { | |
| event.preventDefault(); | |
| node.scrollIntoView(); | |
| }, false); | |
| if(showDetails) { | |
| var details = ""; | |
| if(section.associatedNodes[0].sectionType) details += "<" + section.associatedNodes[0].nodeName.toLowerCase() + ">, "; | |
| if(section.heading) details += "rank:−" + (-section.heading.rank) + ", depth:" + section.heading.depth + ", "; | |
| details += "#nodes:" + section.associatedNodes.length; | |
| title.title = details; | |
| } | |
| li.appendChild(printOutline(section.childSections)); | |
| return li; | |
| } | |
| // Section class | |
| function Section() { | |
| this.parentSection = null; | |
| this.childSections = []; | |
| this.firstChild = null; | |
| this.lastChild = null; | |
| this.appendChild = function(section) { | |
| section.parentSection = this; | |
| this.childSections.push(section); | |
| if(this.firstChild === null) this.firstChild = section; | |
| this.lastChild = section; | |
| }; | |
| this.heading = null; // heading element associated with the section, if any | |
| this.associatedNodes = []; // DOM nodes associated with the section | |
| } | |
| // Main function | |
| function HTMLOutline(root) { | |
| // BEGIN OUTLINE ALGORITHM | |
| // STEP 1 | |
| var currentOutlinee = null; // element whose outline is being created | |
| // STEP 2 | |
| var currentSection = null; // current section | |
| // STEP 3 | |
| // Minimal stack object | |
| var stack = {"lastIndex": -1}; | |
| stack.isEmpty = function() { | |
| return stack.lastIndex === -1; | |
| }; | |
| stack.push = function(e) { | |
| stack[++stack.lastIndex] = e; | |
| stack.top = e; | |
| }; | |
| stack.pop = function() { | |
| var e = stack.top; | |
| delete stack[stack.lastIndex--]; | |
| stack.top = stack[stack.lastIndex]; | |
| return e; | |
| }; | |
| // STEP 4 (minus DOM walk which is at the end) | |
| function enter(node) { | |
| if(isElement(node)) { | |
| if(!stack.isEmpty() && (isHeadingElement(stack.top) || isHidden(stack.top))) { | |
| // Do nothing | |
| } else if(isHidden(node)) { | |
| stack.push(node); | |
| } else if(isSectioningContentElement(node) || isSectioningRootElement(node)) { | |
| // if(currentOutlinee !== null && currentSection.heading === null) { | |
| // Create implied heading | |
| // } | |
| if(currentOutlinee !== null) stack.push(currentOutlinee); | |
| currentOutlinee = node; | |
| currentSection = new Section(); | |
| associateNodeWithSection(currentOutlinee, currentSection); | |
| currentOutlinee.appendSection(currentSection); | |
| } else if(currentOutlinee === null) { | |
| // Do nothing | |
| } else if(isHeadingElement(node)) { | |
| if(currentSection.heading === null) currentSection.heading = node; | |
| else if(currentOutlinee.lastSection.heading === null || node.rank >= currentOutlinee.lastSection.heading.rank) { | |
| currentSection = new Section(); | |
| currentSection.heading = node; | |
| currentOutlinee.appendSection(currentSection); | |
| } else { | |
| var candidateSection = currentSection; | |
| do { | |
| if(node.rank < candidateSection.heading.rank) { | |
| currentSection = new Section(); | |
| currentSection.heading = node; | |
| candidateSection.appendChild(currentSection); | |
| break; | |
| } | |
| var newCandidate = candidateSection.parentSection; | |
| candidateSection = newCandidate; | |
| } while(true); | |
| } | |
| stack.push(node); | |
| } // else { | |
| // Do nothing | |
| // } | |
| } | |
| } | |
| function exit(node) { | |
| if(isElement(node)) { | |
| if(!stack.isEmpty() && node === stack.top) stack.pop(); | |
| else if(!stack.isEmpty() && (isHeadingElement(stack.top) || isHidden(stack.top))) { | |
| // Do nothing | |
| } else if(!stack.isEmpty() && isSectioningContentElement(node)) { | |
| // if(currentSection.heading === null) { | |
| // Create implied heading | |
| // } | |
| currentOutlinee = stack.pop(); | |
| currentSection = currentOutlinee.lastSection; | |
| for(var i = 0; i < node.sectionList.length; i++) { | |
| currentSection.appendChild(node.sectionList[i]); | |
| } | |
| } else if(!stack.isEmpty() && isSectioningRootElement(node)) { | |
| // if(currentSection.heading === null) { | |
| // Create implied heading | |
| // } | |
| currentOutlinee = stack.pop(); | |
| currentSection = currentOutlinee.lastSection; | |
| while(currentSection.childSections.length > 0) { | |
| currentSection = currentSection.lastChild; | |
| } | |
| } else if(isSectioningContentElement(node) || isSectioningRootElement(node)) { | |
| // if(currentSection.heading === null) { | |
| // Create implied heading | |
| // } | |
| // The algorith says to end the walk here, but that's assuming root is a sectioning element | |
| // Instead we reset the algorithm for subsequent top-level sectioning elements | |
| currentOutlinee = null; | |
| currentSection = null; | |
| } // else { | |
| // Do nothing | |
| // } | |
| } | |
| if(node.associatedSection === null && currentSection !== null) associateNodeWithSection(node, currentSection); | |
| } | |
| // STEP 5 | |
| // The heading associated to node is node.associatedSection.heading, if any | |
| // END OUTLINE ALGORITHM | |
| // Now we must make the necessary definitions for the above to make sense | |
| function associateNodeWithSection(node, section) { | |
| section.associatedNodes.push(node); | |
| node.associatedSection = section; | |
| } | |
| function isElement(node) { | |
| return node.nodeType === 1; | |
| } | |
| function isHidden(node) { | |
| return node.hidden; | |
| } | |
| function isSectioningContentElement(node) { | |
| return node.sectionType === 1; | |
| } | |
| function isSectioningRootElement(node) { | |
| return node.sectionType === 2; | |
| } | |
| function isHeadingElement(node) { | |
| return node.rank !== undefined; | |
| } | |
| function extend(node) { | |
| if(node.nodeType === 1) { | |
| switch(node.nodeName.toLowerCase()) { | |
| case "blockquote": case "body": case "details": case "dialog": case "fieldset": case "figure": case "td": | |
| extendSectioningRootElement(node); | |
| break; | |
| case "article": case "aside": case "nav": case "section": | |
| extendSectioningContentElement(node); | |
| break; | |
| case "h1": case "h2": case "h3": case "h4": case "h5": case "h6": | |
| extendHeadingElement(node); | |
| break; | |
| case "hgroup": | |
| extendHeadingGroupElement(node); | |
| break; | |
| default: | |
| extendNode(node); | |
| } | |
| } else extendNode(node); | |
| } | |
| function extendNode(node) { | |
| node.associatedSection = null; | |
| } | |
| function extendSectioningElement(node) { | |
| extendNode(node); | |
| node.sectionList = []; | |
| node.firstSection = null; | |
| node.lastSection = null; | |
| node.appendSection = function(section) { | |
| this.sectionList.push(section); | |
| if(this.firstSection === null) this.firstSection = section; | |
| this.lastSection = section; | |
| }; | |
| } | |
| function extendSectioningContentElement(node) { | |
| extendSectioningElement(node); | |
| node.sectionType = 1; | |
| } | |
| function extendSectioningRootElement(node) { | |
| extendSectioningElement(node); | |
| node.sectionType = 2; | |
| } | |
| function extendHeadingContentElement(node) { | |
| extendNode(node); | |
| Object.defineProperty(node, "depth", {"get": function() { | |
| var section = node.associatedSection; | |
| var depth = 1; | |
| if(section !== null) { | |
| while(section = section.parentSection) ++depth; | |
| } | |
| return depth; | |
| }, "configurable": true, "enumerable": true}); | |
| } | |
| function extendHeadingElement(node) { | |
| extendHeadingContentElement(node); | |
| node.rank = -parseInt(node.nodeName.charAt(1)); | |
| node.text = node.textContent; | |
| } | |
| function extendHeadingGroupElement(node) { | |
| extendHeadingContentElement(node); | |
| for(var i = 1; i <= 6; i++) { | |
| var h = node.getElementsByTagName("h" + i); | |
| if(h.length > 0) { | |
| node.rank = -i; | |
| node.text = h[0].textContent; | |
| break; | |
| } | |
| } | |
| if(node.rank === undefined) { | |
| node.rank = -1; | |
| node.text = ""; | |
| } | |
| } | |
| // Walk the DOM subtree of root | |
| var node = root; | |
| start: while(node) { | |
| extend(node); | |
| enter(node); | |
| if(node.firstChild) { | |
| node = node.firstChild; | |
| continue start; | |
| } | |
| while(node) { | |
| exit(node); | |
| if(node === root) break start; | |
| if(node.nextSibling) { | |
| node = node.nextSibling; | |
| continue start; | |
| } | |
| node = node.parentNode; | |
| } | |
| } | |
| } | |
| })(); |
| { | |
| "name": "Table of contents", | |
| "description": "Inject table of contents into current page", | |
| "icon": "http://newsroom.unl.edu/announce/files/file36649.jpg", | |
| "link": "https://gist.github.com/dergachev/7792914" | |
| } |