UNPKG

use-page-headings-tree

Version:

A React hook to get tree data from a list of heading nodes

280 lines (224 loc) 7.94 kB
import crypto from 'crypto'; import { useEffect } from 'react'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function rng() { return crypto.randomBytes(16); } /** * Convert array of 16 byte values to UUID string format of the form: * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX */ var byteToHex = []; for (var i = 0; i < 256; ++i) { byteToHex[i] = (i + 0x100).toString(16).substr(1); } function bytesToUuid(buf, offset) { var i = offset || 0; var bth = byteToHex; // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4 return [bth[buf[i++]], bth[buf[i++]], bth[buf[i++]], bth[buf[i++]], '-', bth[buf[i++]], bth[buf[i++]], '-', bth[buf[i++]], bth[buf[i++]], '-', bth[buf[i++]], bth[buf[i++]], '-', bth[buf[i++]], bth[buf[i++]], bth[buf[i++]], bth[buf[i++]], bth[buf[i++]], bth[buf[i++]]].join(''); } function uuidToBytes(uuid) { // Note: We assume we're being passed a valid uuid string var bytes = []; uuid.replace(/[a-fA-F0-9]{2}/g, function (hex) { bytes.push(parseInt(hex, 16)); }); return bytes; } function stringToBytes(str) { str = unescape(encodeURIComponent(str)); // UTF8 escape var bytes = new Array(str.length); for (var i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i); } return bytes; } const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; function v35 (name, version, hashfunc) { var generateUUID = function (value, namespace, buf, offset) { var off = buf && offset || 0; if (typeof value == 'string') value = stringToBytes(value); if (typeof namespace == 'string') namespace = uuidToBytes(namespace); if (!Array.isArray(value)) throw TypeError('value must be an array of bytes'); if (!Array.isArray(namespace) || namespace.length !== 16) throw TypeError('namespace must be uuid string or an Array of 16 byte values'); // Per 4.3 var bytes = hashfunc(namespace.concat(value)); bytes[6] = bytes[6] & 0x0f | version; bytes[8] = bytes[8] & 0x3f | 0x80; if (buf) { for (var idx = 0; idx < 16; ++idx) { buf[off + idx] = bytes[idx]; } } return buf || bytesToUuid(bytes); }; // Function#name is not settable on some platforms (#270) try { generateUUID.name = name; } catch (err) {} // For CommonJS default export support generateUUID.DNS = DNS; generateUUID.URL = URL; return generateUUID; } function md5(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); } else if (typeof bytes === 'string') { bytes = Buffer.from(bytes, 'utf8'); } return crypto.createHash('md5').update(bytes).digest(); } const v3 = v35('v3', 0x30, md5); function v4(options, buf, offset) { var i = buf && offset || 0; if (typeof options == 'string') { buf = options === 'binary' ? new Array(16) : null; options = null; } options = options || {}; var rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = rnds[6] & 0x0f | 0x40; rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided if (buf) { for (var ii = 0; ii < 16; ++ii) { buf[i + ii] = rnds[ii]; } } return buf || bytesToUuid(rnds); } function sha1(bytes) { if (Array.isArray(bytes)) { bytes = Buffer.from(bytes); } else if (typeof bytes === 'string') { bytes = Buffer.from(bytes, 'utf8'); } return crypto.createHash('sha1').update(bytes).digest(); } const v5 = v35('v5', 0x50, sha1); var getParentMap = function getParentMap(headingLevel, latestRoots) { return Object.keys(latestRoots).map(function (k) { return latestRoots[k]; }).slice(0, headingLevel[1] - 2); }; var transformRootNode = function transformRootNode(node, expanded) { return _objectSpread2({}, node, { id: node.element.id, text: node.element.innerText || node.element.innerHTML, expanded: expanded }); }; var checkParent = function checkParent(parent) { if (!parent) { throw Error("(usePageHeadingsTree.js): One of the tree nodes is missing a parent.\n" + "You may have skipped a heading level in your document or your query may " + 'not contain a sequential list of heading nodes (Correct: "h2,h3,h4" Incorrect: "h2,h4")'); } }; var checkTag = function checkTag(tagName) { if (!/^H[2-6]$/.test(tagName)) { throw Error("(usePageHeadingsTree.js): <".concat(tagName.toLowerCase(), "> elements are not supported. ") + " Only heading elements (<h2> through <h6>) are supported (for now)."); } }; var getFlatNodeListFromHeadings = function getFlatNodeListFromHeadings(headings) { var latestRoots = {}; var roots = { H2: {}, H3: {}, H4: {}, H5: {}, H6: {} }; var getDefaultRoot = function getDefaultRoot(heading, index) { return { childrenCount: 0, rootId: latestRoots[heading.tagName], element: heading, childNodes: [], index: index }; }; headings.forEach(function (h, i) { checkTag(h.tagName); latestRoots[h.tagName] = v4(); if (h.tagName === "H2") { roots.H2[latestRoots.H2] = getDefaultRoot(h, i); return; } roots[h.tagName][latestRoots[h.tagName]] = _objectSpread2({}, getDefaultRoot(h, i), { parentMap: getParentMap(h.tagName, latestRoots) }); }); return roots; }; var getNodeTreeFromFlatNodeList = function getNodeTreeFromFlatNodeList(roots, shouldDefaultToExpand) { var rootKeys = Object.keys(roots); var _loop = function _loop(i) { var parents = roots[rootKeys[i - 1]]; var currentRoot = roots[rootKeys[i]]; if (currentRoot) { Object.keys(currentRoot).forEach(function (childKey) { var child = transformRootNode(currentRoot[childKey], shouldDefaultToExpand); if (child.parentMap) { var parentId = child.parentMap[child.parentMap.length - 1]; var parent = parents[parentId]; checkParent(parent); parent.childrenCount += child.childNodes.length + 1; parent.childNodes.push(child); } }); } }; for (var i = rootKeys.length - 1; i >= 0; i--) { _loop(i); } var finalRoots = Object.keys(roots.H2).map(function (k) { return transformRootNode(roots.H2[k], shouldDefaultToExpand); }); return finalRoots; }; var usePageHeadingsTree = function usePageHeadingsTree(headings, callback, shouldDefaultToExpand) { useEffect(function () { var roots = getFlatNodeListFromHeadings(headings); var finalRoots = getNodeTreeFromFlatNodeList(roots, shouldDefaultToExpand); callback(finalRoots); }, [headings, shouldDefaultToExpand]); }; export { usePageHeadingsTree }; //# sourceMappingURL=index.es.js.map