Convert Mikrotik Table Row Comments into Inline Comments

Converts Mikrotik webfig's full-row table comments into inline comments similar to WinBox

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Convert Mikrotik Table Row Comments into Inline Comments
// @homepage     https://github.com/myleskeller/Userscripts/tree/main
// @supportURL   https://github.com/myleskeller/Userscripts/issues
// @version      1.0
// @author       Myles Keller
// @description  Converts Mikrotik webfig's full-row table comments into inline comments similar to WinBox
// @match        http://192.168.88.1/webfig/
// @grant        none
// @namespace    https://github.com/myleskeller/
// @license MIT
// @noframes
// ==/UserScript==

//@match (above) should include your Mikrotik router's IP address if you have it configured from default
//loadDelay (below) changes the delay giving the page time to load before running the script the first time


let table = document.querySelector('table.table');
let loadDelay = 5;

(function() {
    'use strict';

    // Function to add 'Comment' column header to the table
    function addCommentHeader(table) {
        if (!table.classList.contains('inline_comment') && hasComments(table)) {
            // console.log("comment header not already found. adding new one.")
            let thead = table.querySelector('thead tr');
            let commentHeader = document.createElement('th');
            commentHeader.title = 'Comment';
            commentHeader.style.width = autofitLastTableColumn(table) + 'ch';
            commentHeader.innerHTML = "<span>Comment</span>";
            table.classList.add("inline_comment");
            thead.appendChild(commentHeader);
        }
    }

    // Function to process each tbody element
    function processTbody(tbody) {
        let commentRow = tbody.querySelector('tr.comment');
        if (commentRow) {
            let commentText = removePrepend(commentRow.querySelector('td').textContent.trim());
            let dataRow = tbody.querySelector('tr:not(.comment)');

            if (dataRow) {
                let commentCell = document.createElement('td');
                commentCell.textContent = commentText;
                dataRow.appendChild(commentCell);
            }
            commentRow.style.display = 'none';
        } else {
            let dataRow = tbody.querySelector('tr:not(.comment)');
            let commentCell = document.createElement('td');
            dataRow.appendChild(commentCell);
        }
    }

    function removePrepend(inputString) {
        // Remove ";;; " by taking a substring starting from the fourth character`
        if (inputString.startsWith(";;; ")) {
            return inputString.substring(4);
        }
    }

    function autofitLastTableColumn(table) {
        // Get the table headers (th elements)
        const headers = table.querySelectorAll('th');
        // Calculate the maximum width for the last column
        let maxColumnWidth = 0;
        // Loop through each row in the table to find the maximum width for the last column
        table.querySelectorAll('tr').forEach((row) => {
            const lastCell = row.lastElementChild;

            if (lastCell && lastCell.tagName === 'TD') {
                const cellText = lastCell.textContent;
                maxColumnWidth = Math.max(maxColumnWidth, cellText.length);
            }
        });
        // Apply the calculated maximum width to the last column header
        return maxColumnWidth;
    }

    function hasComments(table) {
        return table.querySelectorAll('tr.comment').length != 0;
    }

    // Function to reset the MutationObserver
    function resetMutationObserver() {
        if (observer) {
            observer.disconnect(); // Disconnect the existing observer
        }
        observer = setupMutationObserver(); // Set up a new observer
        pageLoaded(1) // doesn't seem necessary to wait longer since main interface is already loaded
    }

    // Create a MutationObserver to watch for changes to the table
    function setupMutationObserver() {
        let newObserver = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList' && mutation.target instanceof HTMLElement) {
                    // Check if the table or its children were modified
                    let modifiedTable = mutation.target.querySelector('table.table');

                    if (modifiedTable) {
                        // Re-run the script on table modification
                        addCommentHeader(modifiedTable);
                        let modifiedTbodies = modifiedTable.querySelectorAll('tbody');
                        modifiedTbodies.forEach(processTbody);
                    }
                }
            });
        });
        const config = {
            childList: true,
            subtree: true
        };
        newObserver.observe(document.body, config);
        return newObserver;
    }

    // perform initial scan for table and add comment header if necessary
    function pageLoaded(delay) {
        setTimeout(function() {
            // Find the table with the class "table"
            table = document.querySelector('table.table');

            if (table) {
                // Add 'Comment' column header
                addCommentHeader(table);
                // Process each tbody element
                let tbodies = table.querySelectorAll('tbody');
                tbodies.forEach(processTbody);
            }
        }, delay * 1000); // convert milliseconds to seconds
    }


    let observer = setupMutationObserver();
    pageLoaded(loadDelay);
    window.addEventListener('hashchange', resetMutationObserver);
})();