Better Notes Mod



  • Hello all,
    In reference to my feature request here:
    @lonm said in Feature requests for Vivaldi 1.14:
    I decided it would be nice to make a mod to tide me over in the meantime.
    I have also decided to extend this mod to include other features I find useful when writing notes

    Features

    • Word count, plus line and character count
    • Button to quickly toggle between monospace and regular fonts
    • Button to quickly toggle the line wrapping

    Preview
    0_1512302143718_demo.gif

    Installation
    This is installed as a custom.js mod.

    The code can be found here:

    /*
    * BETTER NOTES (a mod for Vivaldi)
    * Written by LonM
    * No Copyright Reserved
    */
    "use strict";
    
    // Observers
    const panelChangeObserver = new MutationObserver(panelChanged);
    const noteChangeObserver = new MutationObserver(newNoteSelected);
    const noteTextChangeObserver = new MutationObserver(noteTextChanged);
    
    // Globals
    const monospaceFontDeclaration = "DejaVu Sans Mono, Courier New, monospace";
    const childListObserverConfig = { childList: true };
    let fontState = "regular";
    let wrapState = "normal";
    
    
    /*************************************
    * OBSERVER INITIALISERS
    **************************************/
    // BROWSER LOADED - entry point
    // Register the observer once the browser is fully loaded
    setTimeout(function observePanelChanges(){
        const panels = document.querySelector("#panels > div.panel-group > div");
        if (panels) {
            panelChangeObserver.observe(panels, childListObserverConfig);
            panelChanged();
        } else {
            setTimeout(observePanelChanges, 500);
        }
    }, 500)
    
    // Look for if a user selectes a new note or folder
    function observeNoteChanges(){
        const notecard = document.querySelector("#notes-panel > div > div");
        noteChangeObserver.observe(notecard, childListObserverConfig);
        newNoteSelected();
    }
    
    // Look for if a user edits a note
    function observeNoteTextChanges(){
        const notetext = document.querySelector("#notes-panel > div > div > textarea");
        if(notetext){
            noteTextChangeObserver.observe(notetext, childListObserverConfig);
            noteTextChanged();
        }
    }
    
    
    /**************************************
    * OBSERVER REACTIONS
    ***************************************/
    function panelChanged(m, o){
        // Clean up observers
        noteChangeObserver.disconnect();
        noteTextChangeObserver.disconnect();
        //Identify Panel
        const notes = document.querySelector("#notes-panel");
        if(notes){
            // Add DOM Elements
            addWordCountSpan();
            fontToggleAddButton();
            wrapToggleAddButton();
            // Observe note changes
            observeNoteChanges();
        }
    }
    
    function newNoteSelected(m, o){
        noteTextChangeObserver.disconnect();
        updateNoteFont();
        updateWrapState();
        observeNoteTextChanges();
    }
    
    function noteTextChanged(m, o){
        updateWordCount();
    }
    
    
    /**************************************
    * GENERAL DOM GENERATOR
    ***************************************/
    function makeButton(id, className, innerHtml, title, clickEvent){    
        const newButton = document.createElement("button");
        newButton.id = id;
        newButton.className = className;
        newButton.style.width = "28px";
        newButton.style.height = "28px";
        newButton.style.border = "1px solid var(--colorBg)";
        newButton.style.padding = "5px";
        newButton.style.borderRadius = "var(--radius)";
        newButton.style.backgroundColor = "var(--colorBg)";
        newButton.innerHTML = innerHtml;
        newButton.title = title;
        newButton.addEventListener("click", clickEvent);
        return newButton;
    }
    
    function attachButton(newButton){
        const controlwrapper = document.querySelector("#notes-panel > div > div > div.add-attachments-wrapper");
        controlwrapper.appendChild(newButton);
    }
    
    
    /**************************************
    * FONT TOGGLER METHODS
    ***************************************/
    // Create the font toggle dom element
    function fontToggleAddButton(){
        let newButton = makeButton(
            "notes-font-toggle",
            "",
            "A",
            "Switch between monospace and regular font",
            fontToggleClicked
        );
        newButton.style.fontFamily = monospaceFontDeclaration;
        newButton.style.fontSize = "16px";
        attachButton(newButton);
    }
    
    // User clicked the font toggle button
    function fontToggleClicked(){
        fontState = fontState==="regular" ? "mono" : "regular";
        updateNoteFont();
    }
    
    // Make the font match what the button state is
    function updateNoteFont(){
        const noteTextArea = document.querySelector("#notes-panel > div > div > textarea");
        if(!noteTextArea){ // A folder was selected
            return;
        }
        noteTextArea.style.fontFamily = fontState==="regular" ? "" : monospaceFontDeclaration;
    }
    
    
    /**************************************
    * WORD WRAP TOGGLER
    ***************************************/
    
    // Create the font toggle dom element
    function wrapToggleAddButton(){
        let newButton = makeButton(
            "notes-wrap-toggle",
            "",
            "↵",
            "Toggle the line wrapping on the note",
            wrapToggleClicked
        );
        newButton.style.fontSize = "16px";
        attachButton(newButton);
    }
    
    // User clicked the wrap toggle button
    function wrapToggleClicked(){
        wrapState = wrapState==="normal" ? "pre" : "normal";
        updateWrapState()
    }
    
    // Make the wrap match what the button state is
    function updateWrapState(){
        const noteTextArea = document.querySelector("#notes-panel > div > div > textarea");
        if(!noteTextArea){ // A folder was selected
            return;
        }
        noteTextArea.style.whiteSpace = wrapState;
    }
    
    
    /**************************************
    * WORD COUNT METHODS
    ***************************************/
    // Create the word count dom element
    function addWordCountSpan(){
        const metawrapper = document.querySelector("#notes-panel > div > div > div.meta-data-wrapper");
        const wordcountwrapper = document.createElement("div");
        wordcountwrapper.className = "dateCreated";
        wordcountwrapper.id = "note-word-count-container";
        //icon from https://commons.wikimedia.org/wiki/File:Revision_of_policy.svg 
        wordcountwrapper.innerHTML = `
            <div class="meta-icon word-count fieldset" title="Word Count">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 40 37">
                    <path fill="none" stroke="var(--colorFg)" stroke-width="3" d="m10,18 9-9 14,14-19-19-9,9 19,19 11,2-2-11-1-1m1,4-6,6 3,1 4-4"/>
                </svg>
            </div>
            <span id="note-word-count" title="Word Count">Select a note to see word count</span>`;
        metawrapper.appendChild(wordcountwrapper);
    }
    
    // Trigger a word count update
    function updateWordCount(){
        const wordcountspan = document.querySelector("#note-word-count");
        if(!wordcountspan){
            return;
        }
        const notetextarea = document.querySelector("#notes-panel > div > div > textarea");
        if(!notetextarea || !notetextarea.value || !notetextarea.value.length){
            wordcountspan.textContent = "Chars: 0 Words: 0 Lines: 0";
            return;
        }
        const notetext = notetextarea.value;
        const characters = notetext.length;
        const wordsMatches = notetext.match(/\S+/g);
        const words = wordsMatches ? wordsMatches.length : 0;
        const lineMatches = notetext.match(/\n/g);
        const lines = lineMatches ? lineMatches.length+1 : (characters==0 ? 0 : 1);
        const message = "Chars: "+characters+" Words: "+words+" Lines: "+lines;
        wordcountspan.textContent = message;
    }
    

    Usage

    • Once installed the browser should be restarted
    • you can get a summary of the Character, Word and Line count of a note. This is displayed in the metadata area of a note.
    • You can also use the font button to toggle the font. You can choose a different font by editing the line in the script with the monospaceFontDeclaration
    • You can use the wrap button to toggle word wrapping
    • The toggles change the display of notes, an do not affect notes on an individual level.

    Last Words
    If this is useful to you, please enjoy it and let me know if there are any problems, or anything else you think should be added.

    Updates

    1. Automatic updates to word count, null & regex improvements as suggested by @iAN-CooG and added an icon
    2. Corrected line counting rule, make icon follow theme colours
    3. Slightly refactor, add font toggle and wrap toggle


  • That's a pretty cool mod.
    It's not immediately apparent that you have to click each time for a new count, and for that matter, where to click. It's not really a problem, but maybe a small button/icon would clarify this, instead of "(check word count") sentence and then just the count for the remainder of usage.



  • @lonm nice idea but it doesn't work very well for me, most of the times the click on the (check word count) doesn't trigger. If I press enough times enter to fill lines and then the scroll bar appear, click on them few times, then something happens clicking on the check count. Deleting the extra lines, the scroll bar disappears, the count now don't sense the new clicks anymore so don't update. Seems it needs a couple of lines at least to start sensing the clicks.
    I've tried with no other css or js mods, just this alone, to be sure there was no interference.

    EDIT: in console it shows an error
    Uncaught TypeError: Cannot read property 'length' of null
    at HTMLSpanElement.updateWordCount (custom.js:129)

    which for me is
    const words = notetext.match(/ /g).length + 1; <-



  • This is how I tried to fix it. Not understanding why the consts, I've used some vars instead, and conditioned them of being not null.
    Also the count of words is better replaced with a regexp to find any count of spaces followed by any count of non whitespace chars.

    function updateWordCount(){
        const wordcountspan = document.querySelector("#note-word-count");
        const notetextarea = document.querySelector("#notes-panel > div > div > textarea");
        if(wordcountspan === null || notetextarea===null){
            wordcountspan.textContent = "(check word count)";
            return;
        }
        const notetext = notetextarea.value;
        const characters = notetext.length;
        var matchspace=notetext.match(/ *\S+/g);
        var words = 0;
        var lines = 0;
        if(matchspace === null) {
            if (characters>0) {
               words = 1;
            }
        } else {
            words = matchspace.length;
        }
        matchspace=notetext.match(/\n/g);
        if(matchspace === null) {
            if (characters>0) {
                lines = 1;
            }
        } else {
            lines = matchspace.length + 1;
        }
        const message = "C: "+characters+" W: "+words+" L: "+lines;
        wordcountspan.textContent = message;
    }
    


  • I've updated the original post with some fixes, and it can now auto-update the counting rather than relying on a click.



  • @lonm that's better, one more change to fix the lines counter

    const lines = lineMatches ? lineMatches.length+1 : (characters==0 ? 0 : 1);
    

    this way it's 0 if you delete all text, at least 1 for at least 1 char, and (number of CR found) +1, else it would be counting 1 even for 2 lines

    For my convenience I changed the
    stroke="#321"
    to
    stroke="#777"
    so the icon is visible in both light and dark themes. I've no idea how to set it to the actual theme colors =)



  • @ian-coog Good call on the line counting. I've also updated it to use the browser theme colour variables.



  • @lonm Nice, now that's perfect IMHO. It goes in my CustomCSS pack.



  • Thanks you. Awesome mod dude :D



  • I have updated to include new features - such as a font toggle and a word wrap toggle.


Log in to reply
 

Looks like your connection to Vivaldi Forum was lost, please wait while we try to reconnect.