Pomofocus Projects Filter

Increase productivity by filtering your list of tasks. Requires pomofocus premium plan.

// ==UserScript==
// @name         Pomofocus Projects Filter
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Increase productivity by filtering your list of tasks. Requires pomofocus premium plan.
// @author       mat k
// @match        https://pomofocus.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pomofocus.io
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function getElementByXpath(path) {
        return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }

    function refreshTaskList(taskNodes) {
        console.log(taskNodes)
        let tree = analyseNodeTree(taskNodes)

        document.querySelector('div[class="filter-div"]')?.remove()
        renderFilterButtons(tree)

        return tree
    }

    function analyseTreeForProjectNames(tree) {
        let projects = []

        tree.forEach(object => {
            if (projects.includes(object.project)) {
            } else if (object.project == undefined) { } else {
                projects.push(object.project)
            }
        })

        projects.sort((a, b) => a.localeCompare(b))

        projects.splice(0,0,'Unassigned')
        projects.splice(0,0,'All')

        return projects
    }
        // Function to render the filter buttons using project names in the projectTasks object

    function styledButton(btn) {
        btn.style.cursor = 'pointer'
        btn.style.border = 'none'
        btn.style.margin = '10px 5px'
        btn.style.padding = '0 12px'
        btn.style.borderRadius = '4px'
        btn.style.boxShadow = 'rgb(235 235 235) 0 6px 0'
        btn.style.fontFamily = 'ArialRounded'
        btn.style.fontSize = '16px'
        btn.style.minHeight = '22px'
        btn.style.fontWeight = 'bold'
        //btn.style.width = '100px'
        btn.style.color = 'rgb(186, 73, 73)'
        btn.style.backgroundColor = 'white'
        btn.style.transition = 'color 0.5s ease-in-out 0s'

        btn.style.whiteSpace = 'nowrap'
        btn.style.overflow = 'clip'
        btn.style.textOverflow = 'ellipsis'
        btn.style.flex = '1 1 30%'

        return btn
    }

    function renderFilterButtons(tree) {

        let projectTasks = analyseTreeForProjectNames(tree)
        let filterDiv = document.createElement('div')
        filterDiv.style.display = 'flex'
        filterDiv.style.flexDirection = 'column'
        filterDiv.style.marginBottom = '14px'
        filterDiv.classList.add('filter-div')

        let filterTitle = document.createElement('div')
        filterTitle.innerText = 'Filter by Project'
        filterTitle.style.fontFamily = 'ArialRounded'

        filterDiv.appendChild(filterTitle)

        let btnStatic = document.createElement('div')
        btnStatic.style.display = 'flex'
        filterDiv.appendChild(btnStatic)

        let btnDynamic = document.createElement('div')
        btnDynamic.style.display = 'flex'
        btnDynamic.style.flexWrap = 'wrap'
        filterDiv.appendChild(btnDynamic)

        for (let index in projectTasks) {
            projectTasks[index]
            console.log()
            let filterButton = document.createElement('button')

            filterButton = styledButton(filterButton)

            filterButton.innerText = projectTasks[index]

            if (index == 0 | index == 1) {
                btnStatic.appendChild(filterButton)
            } else {
                btnDynamic.appendChild(filterButton)
            }

            filterButton.addEventListener('click', function(e) {
                // Toggle the visibility of tasks for the selected project
                if (index == 0) {
                    tree.forEach(taskObject => {
                        taskObject.parentNode.style.display = 'block'
                    })
                } else if (index == 1) {
                    tree.forEach(taskObject => {
                        taskObject.parentNode.style.display = 'block'
                        if (taskObject.project == undefined) {
                        } else {
                            taskObject.parentNode.style.display = 'none'
                        }
                    })
                } else {
                    tree.forEach(taskObject => {
                        taskObject.parentNode.style.display = 'block'
                        if (taskObject.project == e.target.outerText) {
                        } else {
                            taskObject.parentNode.style.display = 'none'
                        }
                    })
                }
            })
        }

        // Add the filter div above the task list
        taskDiv.insertBefore(filterDiv, taskDiv.firstChild)
    }


    function analyseNodeTree(tree) {

        const objectArray = []

        tree.childNodes.forEach(task => {
            // gets metadata about tasks in the task list.
            // it is a bit overkill, but left here in case of future development

            let wholeTask = task.childNodes[0].childNodes

            let description
            if (wholeTask.length > 1) {
                description = wholeTask[1].childNodes[0].childNodes[0].data
            }

            // taskHeader = [div = taskOverview, div = activityPomos]
            let taskHeader = wholeTask[0].childNodes

            // taskOverview = [div = checkBox, div = details]
            let taskOverview = taskHeader[0].childNodes

            let checkBox = taskOverview[0]

            // details = [span = project, text, text = title]
            let details = taskOverview[1].childNodes

            let project = details[0].childNodes[0]?.data
            let title = details[2].data

            // pomos = [div = current, div, div=target]
            let pomos = taskHeader[1].childNodes[0].childNodes

            let currentPomos = pomos[0].data
            let targetPomos = pomos[1].childNodes[1].data

            let object = {
                parentNode: task,
                title: title,
                project: project,
                description: description,
                currentPomos: currentPomos,
                targetPomos: targetPomos,
            }

            objectArray.push(object)
        })

        return objectArray
    }

    let taskDiv
    let taskNodes

    let waitForTaskList = setInterval(function() {
        taskDiv = getElementByXpath("//*[@id='target']/div/div[1]/div[2]/div/div[3]")
        if (taskDiv) {
            taskNodes = taskDiv.childNodes[1]
            clearInterval(waitForTaskList);
            refreshTaskList(taskNodes);

            // Observe the task list for changes
            let taskListObserver = new MutationObserver(() => refreshTaskList(taskNodes));
            taskListObserver.observe(taskNodes, {childList: true});
        }
    }, 200);
    setTimeout(function() {
        clearInterval(waitForTaskList);
    }, 2000);

    // Your code here...
})();