diff --git a/servicenow-kb-toc.user.js b/servicenow-kb-toc.user.js index 5ae9eb9..c620b7e 100644 --- a/servicenow-kb-toc.user.js +++ b/servicenow-kb-toc.user.js @@ -1,447 +1,447 @@ // ==UserScript== // @name ServiceNow - Table of Contents // @namespace support.epfl.ch -// @version 0.6 +// @version 0.7 // @description Runs when a knowledge article is edited. Add a button in the Wysiwyg editor (TinyMCE) "Create or update table of contents". When pressed, creates unique ids on titles contained in the text editor and then insert a table of content with anchor links. // @author Laurent Indermühle, Julien Grondier, Frederik Künstner // @match https://support.epfl.ch/kb_knowledge.do* // @match https://support-test.epfl.ch/kb_knowledge.do* // @match https://epfl.service-now.com/kb_knowledge.do* // @match https://epfltest.service-now.com/kb_knowledge.do* // @match https://epfldev.service-now.com/kb_knowledge.do* // @grant none // ==/UserScript== /** * COMPATIBILITY * * V0.7 : ServiceNow New-York * V0.6 : ServiceNow Jakarta - Chrome (Firefox doesn't match, look at document.top documentation) * V0.5 : ServiceNow Helsinki * V0.4 : ServiceNow Fuji */ /** * LOG * * 2020.09.29 F.Limat : URL has changed from support.epfl.ch to epfl.service-now.com * 2018.03.23 L.Indermühle: URL has changed from it.epfl.ch to support.epfl.ch * 2017.08.28 L.Indermühle: Bump to version 0.6 (no changes to code, only fixed @match URLs) * 2016.10.12 L.Indermühle: Bump to version 0.5 * 2016.08.09 L.Indermühle: Bump to version 0.4 * 2016.08.09 L.Indermühle: Bump to version 0.3 * 2016.08.09 L.Indermühle: Bump to version 0.2 * 2016.08.09 L.Indermühle: Published on C4Science.ch * 2016.08.08 L.Indermühle: Initial version */ (function() { 'use strict'; /** * Window = kb_knowledge.do (because of the @match in head of this script) * * The wysiwyg editor's iframe container has the id #kb_knowledge.text_ifr * ServiceNow Fuji (early 2016) : window.frames[1] * This script is in the scope of a parent iframe called "gsft_main". */ function getDocument() { var frame_id = 'kb_knowledge.text_ifr'; for (var i = 0; i < window.frames.length; i++) { if (window.frames[i].frameElement.id == frame_id) { return window.frames[i].document; } } } /** * @return string 'fr' or 'en' */ function getLang() { return document.getElementById('kb_knowledge.language').value; } /** * tocTitle prints the header of the navigation bloc * @return {string} [description] */ function tocTitle() { if (getLang() == 'fr') { return 'Sommaire'; } else if (getLang() == 'en') { return 'Table of contents'; } } /** * Generate a unique hash */ function generateUID() { return ( "0000" + (Math.random()*Math.pow(36,4) << 0).toString(36)).slice(-4); } /** * Count

*/ function countH1(nodelist) { var h1count = 0; for (var i of nodelist) { if (i.tagName === 'H1') { h1count += 1; } } return h1count; } /** * Get all titles html element * @param {NodeList} body * @return {NodeList or null} */ function getTitles(body) { var b = body.querySelectorAll("h1, h2, h3, h4, h5, h6"); if (b.length === 0) { throw new Error("Titles not found."); } else { if (countH1(b) === 0) { throw new Error("No H1 title found."); } else if (countH1(b) > 1) { throw new Error("More than one H1 title found."); } else { return b; } } } /** * Generate ids based on the title text. Example: * Mon premier titre => monpremiertitre-4gu2 * Also strip specials characters: * Ça va être-épique ! => cavaetreepique-9u1o */ function generateId(title) { title = title.replace(/[ë|é|è|ê]/gi, 'e'); title = title.replace(/[ï|í|ì|î]/gi, 'i'); title = title.replace(/[ü|ú|ù|û]/gi, 'u'); title = title.replace(/[ä|á|à|â]/gi, 'a'); title = title.replace(/[ç|Ç]/g, 'c'); title = title.replace(/\s|\'|\|\.|\;|\:"/g, ''); // \s = space title = title.replace(/^([^a-z])/i, 'a'); // first char must be alpha title = title.replace(/[^a-z|^0-9]/gi, ''); return title + "-" + generateUID(); } /* Tiny add a br for every element that you add. They do that * because Firefox don't let you move the caret on empty elements in * a text field. * That's fine but the problem is that this behavior also trigger when * we add an id. If you look in the source-code provided by TinyMCE, * the one you see when pressing the <> button, you'll see: *

... * And if you console.log(title.id): * MyId * So an HTML entitized function happen. We now know what to search for. */ function removeMceBogus(string) { return string.replace('', ''); } /** * If the title already has an id, we keep it. * So external referers will continue to work. * In regards to duplicates ids, we had a unique hash at the end, so if * duplicates ids already exists in the article, it's not our fault. */ function insertTitlesIds(titles) { for (var title of titles) { if (title.id === "") { title.id = generateId(title.textContent); } else { title.id = removeMceBogus(title.id); } } } /** * Table of Contents looks like that: * *
*

[Sommaire|Table of contents]

*
*/ function buildToC() { var n = getDocument().createElement('div'); n.id = 'psdn_toc_container'; var t = getDocument().createElement('p'); t.setAttribute('style', 'font-weight:bold;'); t.textContent = tocTitle(); n.appendChild(t); var u = getDocument().createElement('ul'); u.id = 'psdn_toc_top_ul'; n.appendChild(u); return n; } /** *

==> 2 */ function extractLevel(html_tag) { return parseInt(html_tag.replace(/[^\d]/i, "")); } /** * higher level means smallest digit, because

is top level * * This will serve to construct the hierarchy, especially in the case * the first title is not a top level one : *

Titre

*

Un autre titre

*

Un autre sous titre

* In this case, the loop will begin with a title level 3, then receive a * level 2 but can't shift left because we should have add an * extra
    in the previous loop */ function higherLevel(lst){ var higher_level = 9; for (var i of lst) { var level = extractLevel(i.tagName); // H1 is ignored if (level < higher_level && level != 1) { higher_level = level; } } return higher_level; } /** * InsertToC finds the position where to insert the Toc */ function insertToC() { var childN; // Childs Nodes of the document var new_pos; // New position where to insert the ToC var old_pos; // Old position where the ToC was var titlestags; // List of HTML titles tag to match var toc; // the
    html element in the DOM which serves the role of a