/logesq-js-snippets

Collection of javascript snippets for Logseq

Overview

This is my collection of javascript snippets i've built for Logseq. These may either extend functionality or provide new styles for the overall look of the application.

Abbreviated Namespaces

Eliminates leading characters in front of the last / character, except for on hover, where it'll display the full namespace.

function collapseAndAbbreviateNamespaceRefs() {
    const observer = new MutationObserver((mutationList) => {
        for (const mutation of mutationList) {
            for (const node of mutation.addedNodes) {
                if (!node.querySelectorAll) continue;

                const initialNamespaceRefs = node.querySelectorAll(
                    '.ls-block a.page-ref[data-ref*="/"], .foldable-title .page-ref[data-ref*="/"], li[title*="root/"] .page-title, a.tag[data-ref*="/"]'
                );
                const pageTitleRefs = node.querySelectorAll('.page-title');
                const filteredPageTitleRefs = Array.from(pageTitleRefs).filter((pageTitleRef) =>
                    Array.from(pageTitleRef.childNodes).some((child) => child.nodeType === 3 && child.textContent.includes('/'))
                );
                const namespaceRefs = [...initialNamespaceRefs, ...filteredPageTitleRefs];

                for (const namespaceRef of namespaceRefs) {
                    const text = namespaceRef.textContent;
                    const testText = namespaceRef.classList.contains("tag")
                        ? text.substring(1).toLowerCase()
                        : text.toLowerCase();
                    if (testText !== namespaceRef.dataset.ref) continue;

                    // Perform collapsing.
                    let abbreviatedText;
                    if (namespaceRef.classList.contains("tag")) {
                        const parts = text.split('/');
                        abbreviatedText = "#" + parts[parts.length - 1]; // Retain the '#' and get the last part of the path
                    } else {
                        const parts = text.split('/');
                        abbreviatedText = parts[parts.length - 1];
                    }

                    namespaceRef.dataset.origText = text;
                    namespaceRef.textContent = abbreviatedText;
                }
            }
        }
    });

    observer.observe(document.getElementById("app-container"), {
        subtree: true,
        childList: true,
    });
}

collapseAndAbbreviateNamespaceRefs();

Descriptor and Collector Blocks

This bit of code will create two new classes for the ls-block elements, one that is called the "descriptor-block" and another that is called the "collector-block".

Descriptor blocks are blocks that contain only a page-ref element that starts with the character ~.

Collectors are blocks that contain a single element of page-ref followed by child elements that also contain page-ref elements.

function addDescriptorFoundationBlocks() {
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' || mutation.type === 'attributes') {
                updatePageReferences();
            }
        }
    });

    function updatePageReferences() {
        const blockElements = document.querySelectorAll('.ls-block > .block-main-container')
        blockElements.forEach((block) => {
            const childElement = block.parentNode.querySelector('.block-children-container')
            const pageReferenceElement = block.querySelector('.page-ref');
            let childRefElements = childElement ? childElement.querySelector('.page-ref') : null;
            let inlineElement = block.querySelector('.inline > .flex > .flex-1 > .inline')
            if (pageReferenceElement) {
                const textContent = pageReferenceElement.getAttribute('data-ref')
                if (textContent.startsWith('~')) {
                    block.parentNode.classList.add('is-descriptor');
                }
            }
            if (inlineElement && inlineElement.firstChild.tagName === 'B' && inlineElement.firstChild.querySelector('.page-ref')) {
                block.parentNode.classList.add('is-foundation')
            } else if (inlineElement && inlineElement.childNodes.length === 1 && inlineElement.firstChild.nodeType === Node.ELEMENT_NODE && childRefElements) {
                const pageReference = inlineElement.childNodes[0].querySelector('.page-ref')
                if (pageReference) {
                    block.parentNode.classList.add('is-collector')
                }
            }
        });
    }

    observer.observe(document.body, {
        subtree: true,
        childList: true,
        characterData: true,
    });

    // Apply the class to the elements immediately and also check for any mismatches
    updatePageReferences();
}

Removing Tagged References

Tagged References have always been an annoyance for me due to the fact that it was always repetitive to have a reference plus a hard link in the tagged section. So this snippet removes those references from the reference panel:

function updateAndRemoveTaggedReferences() {
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' || mutation.type === 'attributes') {
                findAllMatchedElements();
            }
        }
    });

    function findAllMatchedElements() {
        /* This function will iterate through every page + block reference to get a count for how many tags
        vs page references exists for a page reference. If the tags count is equal to the total page reference
        count, then we delete the entire page element, otherwise we only remove the tag reference. */
        let pageNameElement = parent.document.querySelector('.title .title').getAttribute('data-ref')
        let allPageReferences = parent.document.querySelectorAll('.references-blocks-wrap')
        let pageRef = parent.document.querySelectorAll('.references-blocks-wrap>.lazy-visibility')

        // Iterate through each page element
        for (let page of pageRef) {
            let matchedReferences = 0;
            let matchedTagged = 0
            let matchedRefElement = null;
            // Iterate through each block references
            let allReferences = page.querySelectorAll('.references-blocks-wrap .page-ref')
            let currentReferences = Array.from(allReferences).filter(el => el.innerText.trim().toLowerCase().includes(pageNameElement.toLowerCase()));
            for (let currentRef of currentReferences) {
                let currentClassList = currentRef.parentNode.parentNode
                if (currentClassList.classList.contains('page-reference')) {
                    matchedReferences += 1
                }
                else if (currentClassList.classList.contains('page-property-value')) {
                    matchedTagged += 1
                    // set 11 parent levels up
                    let matchedRefElement = currentClassList;
                    // Loop 11 times to move up 11 levels
                    for (let i = 0; i < 11; i++) {
                        if (matchedRefElement.parentNode) {
                            matchedRefElement = matchedRefElement.parentNode;
                        }
                    }
                    removeElement(matchedRefElement)
                }
                if (matchedTagged >= 1 & matchedReferences == 0) {
                    removeElement(page)
                }
                console.log(matchedReferences, matchedTagged)
            }
        }
    }

    function removeElement(element) {
        if (element && element.parentNode) {  // Check if the element exists and has a parent
            element.parentNode.removeChild(element);
        }
    }

    observer.observe(document.body, {
        subtree: true,
        childList: true,
        characterData: true,
    });

    findAllMatchedElements();
}

updateAndRemoveTaggedReferences();