WaniKani Markdown Notes

Allows you to write Markdown in the notes, which will be rendered as HTML when the page loads.

// ==UserScript==
// @name         WaniKani Markdown Notes
// @namespace    rfindley
// @description  Allows you to write Markdown in the notes, which will be rendered as HTML when the page loads.
// @version      1.4
// @require      https://cdnjs.cloudflare.com/ajax/libs/showdown/1.3.0/showdown.min.js
// @include      https://www.wanikani.com/level*
// @include      https://www.wanikani.com/radical*
// @include      https://www.wanikani.com/kanji*
// @include      https://www.wanikani.com/vocabulary*
// @include      https://www.wanikani.com/review/session*
// @copyright    2013, Jeshua
// @run-at       document-end
// @grant        none
// ==/UserScript==

wkmdnotes = {};

(function() {
    
    /**
    * Render the given markdown text.
    */
    function render(text) {
        // Do some custom replacements.
        text = text.replace(/#kan#/g, '<span class="kanji-highlight highlight-kanji" rel="tooltip" data-original-title="Kanji">');
        text = text.replace(/#\/kan#/g, '</span>');

        text = text.replace(/#rad#/g, '<span class="radical-highlight highlight-radical" rel="tooltip" data-original-title="Radical">');
        text = text.replace(/#\/rad#/g, '</span>');

        text = text.replace(/#read#/g, '<span class="reading-highlight highlight-reading" rel="tooltip" data-original-title="Reading">');
        text = text.replace(/#\/read#/g, '</span>');

        text = text.replace(/#voc#/g, '<span class="vocabulary-highlight highlight-vocabulary" rel="tooltip" data-original-title="Vocabulary">');
        text = text.replace(/#\/voc#/g, '</span>');

        // Render the rest as markdown.
        return (new showdown.Converter()).makeHtml(text);
    }

    /**
    * Find all of the tooltips in the given container and tooltipify them.
    */
    function activateTooltips(container) {
        if (container.tooltip) {
            container.find('span[rel="tooltip"]').tooltip();
        }
    }

    /**
    * Setup the given note field with the required callbacks.
    */
    function setupNoteField(note) {
        // Save the markdown and render the content.
        var html = note.html();
        if (typeof html === 'undefined') html = '';
        note.data('noteContent', html.replace(/<br>/g,'\n'));
        note.html(render(html));
        activateTooltips(note);

        note.click(function(e) {
            if (e.target.tagName.toLowerCase() === 'textarea') {
                return;
            }

            // If the target is the div, they are going from display --> edit.
            if (e.target.tagName.toLowerCase() !== 'button') {
                var interval = setInterval(function() {
                    // If we can find a textarea, they must have clicked to edit the text field.
                    // So, we want to display the markdown content.
                    if (note.find('textarea')) {
                        clearInterval(interval);
                        if (note.data('noteContent') === 'Click to add note') {
                            note.find('textarea').val('');
                        } else {
                            note.find('textarea').val(note.data('noteContent'));
                        }
                    }
                }, 50);
            } 

            // Otherwise, they are going from edit --> display.
            else {
                var textarea = note.find('textarea');
                var str = textarea.val().replace(/\n/g,'\n');
                textarea.html(str);
                var interval = setInterval(function() {
                    // Keep waiting until there is no text area. Then, save the changed markdown
                    // value to the data. Also re-render the note.
                    if (note.find('textarea').length === 0) {
                        clearInterval(interval);
                        note.data('noteContent', note.html().replace(/<br>/g,'\n'));
                        note.html(render(note.html()));
                        activateTooltips(note);
                    }
                }, 50);
            }        
        });
    }
    
    function main() {
        // Convert the text in the meaning note.
        var noteFields = ['.note-meaning', '.note-reading'];
        $.each(noteFields, function(i, noteSelector) {
            // During reviews, we have to wait for the field to be added to the dom first.
            // Then, we can add a listener to the note selector.
            $('#option-item-info').click(function() {
                var interval = setInterval(function() {
                    if ($(noteSelector).length !== 0) {
                        clearInterval(interval);
                        setupNoteField($(noteSelector));
                    }
                }, 50);
            });

            // Setup the note field if it is on the page already.
            setupNoteField($(noteSelector));
        });
    }
    
    // Run startup() after window.onload event.
    if (document.readyState === 'complete')
        main();
    else
        window.addEventListener("load", main, false);
})(wkmdnotes);