Skip to content

Instantly share code, notes, and snippets.

@lockcp
Forked from Luckz/tabsoutlinerdupes.js
Created July 30, 2022 19:10
Show Gist options
  • Select an option

  • Save lockcp/5fc3dfa286892d8b7f43d4a70885577d to your computer and use it in GitHub Desktop.

Select an option

Save lockcp/5fc3dfa286892d8b7f43d4a70885577d to your computer and use it in GitHub Desktop.

Revisions

  1. @Luckz Luckz created this gist Sep 20, 2021.
    79 changes: 79 additions & 0 deletions tabsoutlinerdupes.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    // ctrl - shift - J on Tabs Outliner to open dev tools,
    // then Sources -> Snippets -> New Snippet.
    // Rightclick -> Run to execute.

    // cyrb53 stolen from https://stackoverflow.com/a/52171480
    const cyrb53 = function(str, seed = 0) {
    let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
    for (let i = 0, ch; i < str.length; i++) {
    ch = str.charCodeAt(i);
    h1 = Math.imul(h1 ^ ch, 2654435761);
    h2 = Math.imul(h2 ^ ch, 1597334677);
    }
    h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
    h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
    return 4294967296 * (2097151 & h2) + (h1>>>0);
    };

    function nodeterate(currentNode, stringBuilder) {
    if (currentNode.type == "separatorline") stringBuilder.push("separatorline");
    else if (currentNode.type == "textnote") stringBuilder.push(`textnote ${currentNode?.persistentData?.note}`);
    else if (currentNode.type == "win" || currentNode.type == "savedwin") stringBuilder.push("window");
    else stringBuilder.push(currentNode.getHref());

    for (const sn of currentNode.subnodes) {
    nodeterate(sn, stringBuilder);
    }
    }

    function nukeNodeArray(nodeArray) {
    const DUMMY = false;
    for (const currentNode of nodeArray) {
    if (currentNode.type !== "win" && currentNode.type !== "savedwin") {
    console.log("this node should not have almost died:");
    console.log(currentNode);
    continue;
    }
    if (DUMMY) {
    console.log("would now kill:");
    console.log(currentNode);
    } else {
    console.log("removing node:");
    console.log(currentNode);
    currentNode.removeOwnTreeFromParent();
    }
    }
    }

    function killDupes(killActive = false) {
    let dupes = {};

    for (const nod of treeView.treeModel.currentSession_rootNode.subnodes) { // only check root level nodes, don't do complex recursive checks for "sub-roots"
    const strbld = [];
    nodeterate(nod, strbld);
    const hash = cyrb53(strbld.join());
    if (dupes.hasOwnProperty(hash)) dupes[hash].push(nod);
    else dupes[hash] = [nod];
    }

    for (const duperino of Object.values(dupes)) {
    if (duperino.length < 2) continue;

    const inactiveNodes = duperino.filter(x => x.type !== "win"); // keep loaded window(s) alive
    if (inactiveNodes.length < duperino.length) {
    if ((duperino.length - inactiveNodes.length) > 1) {
    console.log("WARNING: duplicate active windows ->");
    for (const loadedWindow of duperino.filter(x => x.type == "win")) console.log(loadedWindow);
    }
    nukeNodeArray(inactiveNodes);
    if (killActive) nukeNodeArray(duperino.filter(x => x.type == "win").slice(1));
    }
    else { // keep oldest
    nukeNodeArray(duperino.slice(1));
    }
    }
    }

    killDupes();
    // to also prune duplicate *loaded* windows down to a single one, try: killDupes(true);
    // note that this will disregard how many subnodes are loaded!