diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2018-06-09 19:25:33 +0200 | 
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2018-06-09 19:25:33 +0200 | 
| commit | aca28de01e7327a45ce025303fc2acc5c3813406 (patch) | |
| tree | ad576b08f361675ee0d4a9eae73b400450431980 /static-files/journal/wymeditor/plugins | |
| parent | 9d5115e52c4c42af8249d8eb6f0ad3b8030f7c8d (diff) | |
Use WYMeditor as the article text editor.
WYMeditor produces much better XHTML than Trumbowyg.
Diffstat (limited to 'static-files/journal/wymeditor/plugins')
| -rw-r--r-- | static-files/journal/wymeditor/plugins/fullscreen/icon_fullscreen.gif | bin | 0 -> 509 bytes | |||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js | 126 | ||||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/list/jquery.wymeditor.list.js | 44 | ||||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js | 192 | ||||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/jquery.wymeditor.table.js | 761 | ||||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/table_delete_column.png | bin | 0 -> 744 bytes | |||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/table_delete_row.png | bin | 0 -> 683 bytes | |||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/table_insert_column.png | bin | 0 -> 720 bytes | |||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/table_insert_row.png | bin | 0 -> 667 bytes | |||
| -rw-r--r-- | static-files/journal/wymeditor/plugins/table/table_join_row.png | bin | 0 -> 685 bytes | 
10 files changed, 1123 insertions, 0 deletions
| diff --git a/static-files/journal/wymeditor/plugins/fullscreen/icon_fullscreen.gif b/static-files/journal/wymeditor/plugins/fullscreen/icon_fullscreen.gifBinary files differ new file mode 100644 index 0000000..d2a8b0a --- /dev/null +++ b/static-files/journal/wymeditor/plugins/fullscreen/icon_fullscreen.gif diff --git a/static-files/journal/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js b/static-files/journal/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js new file mode 100644 index 0000000..faaa693 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js @@ -0,0 +1,126 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + *        http://www.wymeditor.org/ + * + * File Name: + *        jquery.wymeditor.fullscreen.js + *        Fullscreen plugin for WYMeditor + * + * File Authors: + *        Luis Santos (luis.santos a-t openquest dotpt) + *        Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + *        Gerd Riesselmann (gerd a-t gyro-php dot org) : Fixed issue with new skin layout + *        Philipp Cordes (pc a-t irgendware dotnet) + */ + +//Extend WYMeditor +WYMeditor.editor.prototype.fullscreen = function() { +    var wym = this, +        $box = jQuery(wym._box), +        $iframe = jQuery(wym._iframe), +        $overlay = null, +        $window = jQuery(window), + +        editorMargin = 15;     // Margin from window (without padding) + + +    //construct the button's html +    var html = '' + +        "<li class='wym_tools_fullscreen'>" + +            "<a name='Fullscreen' href='#' " + +                "title='Fullscreen' " + +                "style='background-image: url(" + +                    wym._options.basePath + +                    "plugins/fullscreen/icon_fullscreen.gif)'>" + +                "Fullscreen" + +            "</a>" + +        "</li>"; +    //add the button to the tools box +    $box.find(wym._options.toolsSelector + wym._options.toolsListSelector) +        .append(html); + +    function resize () { +        // Calculate margins +        var uiHeight = $box.outerHeight(true) - $iframe.outerHeight(true); +        var editorPadding = $box.outerWidth() - $box.width(); + +        // Calculate heights +        var screenHeight = $window.height(); +        var iframeHeight = (screenHeight - uiHeight - (editorMargin * 2)) + 'px'; + +        // Calculate witdths +        var screenWidth = $window.width(); +        var boxWidth = (screenWidth - editorPadding - (editorMargin * 2)) + 'px'; + +        $box.css('width', boxWidth); +        $iframe.css('height', iframeHeight); +        $overlay.css({ +            'height': screenHeight + 'px', +            'width': screenWidth + 'px' +        }); +    } + +    //handle click event +    $box.find('li.wym_tools_fullscreen a').click(function() { +        if ($box.css('position') != 'fixed') { +            // Store previous inline styles +            $box.data('wym-inline-css', $box.attr('style')); +            $iframe.data('wym-inline-css', $iframe.attr('style')); + +            // Create overlay +            $overlay = jQuery('<div id="wym-fullscreen-overlay"></div>') +                .appendTo('body').css({ +                    'position': 'fixed', +                    'background-color': 'rgb(0, 0, 0)', +                    'opacity': '0.75', +                    'z-index': '98', +                    'top': '0px', +                    'left': '0px' +                }); + +            // Possition the editor +            $box.css({ +                'position': 'fixed', +                'z-index': '99', +                'top': editorMargin + 'px', +                'left': editorMargin + 'px' +            }); + +            // Bind event listeners +            $window.bind('resize', resize); +            $box.find('li.wym_tools_html a').bind('click', resize); + +            // Force resize +            resize(); +        } else { +            // Unbind event listeners +            $window.unbind('resize', resize); +            $box.find('li.wym_tools_html a').unbind('click', resize); + +            // Remove inline styles +            $box.css({ +                'position': 'static', +                'z-index': '', +                'width': '', +                'top': '', +                'left': '' +            }); +            $iframe.css('height', ''); + +            // Remove overlay +            $overlay.remove(); +            $overlay = null; + +            // Retore previous inline styles +            $box.attr('style', $box.data('wym-inline-css')); +            $iframe.attr('style', $iframe.data('wym-inline-css')); +        } + +        return false; +    }); +}; diff --git a/static-files/journal/wymeditor/plugins/list/jquery.wymeditor.list.js b/static-files/journal/wymeditor/plugins/list/jquery.wymeditor.list.js new file mode 100644 index 0000000..b009e25 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/list/jquery.wymeditor.list.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011 PolicyStat LLC. + * MIT licensed (MIT-license.txt) + * + * This plugin adds the ability to use tab and shift+tab to indent/outdent + * lists, mimicking a user's expected behavior when inside an editor. + * + * @author Wes Winham (winhamwr@gmail.com) + */ + +function ListPlugin(options, wym) { +    var listPlugin = this; +    ListPlugin._options = jQuery.extend({}, options); +    listPlugin._wym = wym; + +    listPlugin.init(); +} + +ListPlugin.prototype.init = function() { +    var listPlugin = this; +    listPlugin._wym.listPlugin = listPlugin; + +    listPlugin.bindEvents(); +}; + +ListPlugin.prototype.bindEvents = function() { +    var listPlugin = this, +        wym = listPlugin._wym; + +    wym.keyboard.combokeys.bind( +        "tab", +        function () { +            wym.indent(); +            return false; +        } +    ); +    wym.keyboard.combokeys.bind( +        "shift+tab", +        function () { +            wym.outdent(); +            return false; +        } +    ); +}; diff --git a/static-files/journal/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js b/static-files/journal/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js new file mode 100644 index 0000000..2323440 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js @@ -0,0 +1,192 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2011 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + *        http://www.wymeditor.org/ + * + * File Name: + *        jquery.wymeditor.rdfa.js + *        RDFa plugin for WYMeditor + * + * File Authors: + *        Jean-Francois Hovinne (@jfhovinne) + */ + +//Extend WYMeditor +WYMeditor.editor.prototype.rdfa = function (options) { +    var wym = this, +        rdfa = new WYMeditor.RDFa(options, wym); +    return rdfa; +}; + +//RDFa constructor +WYMeditor.RDFa = function (options, wym) { +    var rdfa = this; +    options = jQuery.extend({ +        setStdNameSpaces: true, +        extendXHTMLParser: true, +        buttons: {} +    }, options); + +    rdfa._options = options; +    rdfa._wym = wym; +    rdfa.init(); +}; + +//RDFa plugin init +WYMeditor.RDFa.prototype.init = function () { +    var rdfa = this; +    if (rdfa._options.setStdNameSpaces) { +        rdfa.setStdNameSpaces(); +    } +    if (rdfa._options.extendXHTMLParser) { +        rdfa.extendXHTMLParser(); +    } +    rdfa.setButtons(); +}; + +//Adding the namespaces to the document +WYMeditor.RDFa.prototype.setStdNameSpaces = function () { +    var rdfa = this; +    rdfa.addNameSpace('xmlns', 'http://www.w3.org/1999/xhtml'); +    rdfa.addNameSpace('version', 'XHTML+RDFa 1.0'); +}; + +WYMeditor.RDFa.prototype.addNameSpace = function (attr, value) { +    var rdfa = this; +    jQuery('html', rdfa._wym._doc) +        .attr(attr, value); +}; + +WYMeditor.RDFa.prototype.extendXHTMLParser = function () { +    var rdfa = this; +    rdfa.extendAttributes(); +    rdfa.setStdVocabularies(); +    rdfa.extendLinkAttributes(); +}; + +WYMeditor.RDFa.prototype.extendAttributes = function () { +    //Add the RDFa attributes +    WYMeditor.XhtmlValidator._attributes.core.attributes.push( +        'rel', +        'rev', +        'content', +        'href', +        'src', +        'about', +        'property', +        'resource', +        'datatype', +        'typeof' +    ); +}; + +WYMeditor.RDFa.prototype.setStdVocabularies = function () { +    var rdfa = this; +    //Add the 'standard' vocabularies +    vocabularies = [ +        'xmlns:biblio', +        'xmlns:cc', +        'xmlns:dbp', +        'xmlns:dbr', +        'xmlns:dc', +        'xmlns:ex', +        'xmlns:foaf', +        'xmlns:rdf', +        'xmlns:rdfs', +        'xmlns:taxo', +        'xmlns:xhv', +        'xmlns:xsd' +    ]; +    jQuery.each(vocabularies, function (index, vocabulary) { +        rdfa.addVocabulary(vocabulary); +    }); +}; + +WYMeditor.RDFa.prototype.addVocabulary = function (vocabulary) { +    WYMeditor.XhtmlValidator._attributes.core.attributes.push(vocabulary); +}; + +WYMeditor.RDFa.prototype.extendLinkAttributes = function () { +    //Overwrite the <a> attributes 'rel' and 'rev' +    WYMeditor.XhtmlValidator._tags.a = { +        "attributes": { +            "0": "charset", +            "1": "coords", +            "2": "href", +            "3": "hreflang", +            "4": "name", +            "5": "rel", +            "6": "rev", +            "shape": /^(rect|rectangle|circ|circle|poly|polygon)$/, +            "7": "type" +        } +    }; +}; + +WYMeditor.RDFa.prototype.setButtons = function () { +    var rdfa = this, +        list = jQuery(rdfa._wym._box).find('div.wym_classes ul'); +    jQuery.each(rdfa._options.buttons, function (index, button) { +        list +            .append('<li></li>') +            .children(':last') +            .append('<a></a>') +            .children(':last') +            .attr('href', '#') +            .text(button.title) +            .bind('click', +                { +                    instance: rdfa._wym, +                    button: button, +                    ns: button.ns, +                    attr: button.attr, +                    value: button.value +                }, +                rdfa.clickButtonHandler); +    }); +}; + +WYMeditor.RDFa.prototype.clickButtonHandler = function (evt) { +    var wym = evt.data.instance, +        selected  = wym.selectedContainer(); + +    //the attribute already exists, remove it +    if (typeof jQuery(selected).attr(evt.data.attr) !== 'undefined' && +            jQuery(selected).attr(evt.data.attr) !== '') { +        WYMeditor.console.log( +            'attribute already exists, remove it:', +            evt.data.attr, +            jQuery(selected).attr(evt.data.attr) +        ); +        jQuery(selected) +            .removeAttr(evt.data.attr) +            .removeClass(evt.data.ns) +            .removeClass(evt.data.attr) +            .removeClass(evt.data.value); + +    //else, add it +    } else { +        WYMeditor.console.log('attribute does not exist, add it:', evt.data.attr, evt.data.value); +        if (evt.data.value) { //value available +            jQuery(selected) +                .attr(evt.data.attr, evt.data.ns + ':' + evt.data.value) +                .addClass(evt.data.ns) +                .addClass(evt.data.attr) +                .addClass(evt.data.value); +        } else { //value not available +            evt.data.value = prompt('Value', ''); +            if (evt.data.value !== null) { +                jQuery(selected) +                    .attr(evt.data.attr, evt.data.value) +                    .addClass(evt.data.ns) +                    .addClass(evt.data.attr) +                    .addClass(evt.data.value); +            } +        } +    } +    return false; +}; diff --git a/static-files/journal/wymeditor/plugins/table/jquery.wymeditor.table.js b/static-files/journal/wymeditor/plugins/table/jquery.wymeditor.table.js new file mode 100644 index 0000000..d258cb5 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/jquery.wymeditor.table.js @@ -0,0 +1,761 @@ +/* global rangy */ +"use strict"; + +// Fugue icons by Yusuke Kamiyamane http://p.yusukekamiyamane.com/ +// and licensed under Creative Commons Attribution + +/** + * A Table editing plugin that gives the user ability to add and remove + * rows and columns as well as merge rows and columns. + * + * @param options A configuration object. + * @param wym The WYMeditor instance to which the TableEditor should attach. + * @class + */ +function TableEditor(options, wym) { +    var tableEditor = this; +    options = jQuery.extend({ +        sMergeRowButtonHtml: String() + +            '<li class="wym_tools_merge_row">' + +                '<a name="merge_row" href="#" title="Merge Cells" ' + +                    'style="background-image: ' + +                        "url('" + wym._options.basePath + +                            "plugins/table/table_join_row.png')" + '">' + +                    'Merge Table Row' + +                '</a>' + +            '</li>', + +        sMergeRowButtonSelector: "li.wym_tools_merge_row a", + +        sAddRowButtonHtml: String() + +            "<li class='wym_tools_add_row'>" + +                "<a name='add_row' href='#' " + +                    "title='Add Row' " + +                    "style='background-image:" + +                        " url(" + wym._options.basePath + +                            "plugins/table/table_insert_row.png)'>" + +                    "Add Table Row" + +                "</a>" + +            "</li>", +        sAddRowButtonSelector: "li.wym_tools_add_row a", + +        sRemoveRowButtonHtml: String() + +            "<li class='wym_tools_remove_row'>" + +                "<a name='remove_row' href='#' " + +                    "title='Remove Row' " + +                    "style='background-image: " + +                        "url(" + wym._options.basePath + +                            "plugins/table/table_delete_row.png)'>" + +                    "Remove Table Row" + +                "</a>" + +            "</li>", +        sRemoveRowButtonSelector: "li.wym_tools_remove_row a", + +        sAddColumnButtonHtml: String() + +            "<li class='wym_tools_add_column'>" + +                "<a name='add_column' href='#' " + +                    "title='Add Column' " + +                    "style='background-image: " + +                        "url(" + wym._options.basePath + +                            "plugins/table/table_insert_column.png)'>" + +                    "Add Table Column" + +                "</a>" + +            "</li>", +        sAddColumnButtonSelector: "li.wym_tools_add_column a", + +        sRemoveColumnButtonHtml: String() + +            "<li class='wym_tools_remove_column'>" + +                "<a name='remove_column' href='#' " + +                    "title='Remove Column' " + +                    "style='background-image: " + +                        "url(" + wym._options.basePath + +                            "plugins/table/table_delete_column.png)'>" + +                    "Remove Table Column" + +                "</a>" + +            "</li>", +        sRemoveColumnButtonSelector: "li.wym_tools_remove_column a", + +        enableCellTabbing: true + +    }, options); + +    tableEditor._options = options; +    tableEditor._wym = wym; + +    tableEditor.init(); +} + +/** + * Construct and return a table objects using the given options object. + * + * @param options The configuration object. + */ +WYMeditor.editor.prototype.table = function (options) { +    var wym = this, +        tableEditor = new TableEditor(options, wym); +    wym.tableEditor = tableEditor; + +    return tableEditor; +}; + +/** + * Initialize the TableEditor object by adding appropriate toolbar buttons and + * binding any required event listeners. + */ +TableEditor.prototype.init = function () { +    var tableEditor = this, +        wym = tableEditor._wym, +    // Add the tool panel buttons +        tools = jQuery(wym._box).find( +            wym._options.toolsSelector + wym._options.toolsListSelector +        ); + +    tools.append(tableEditor._options.sMergeRowButtonHtml); +    tools.append(tableEditor._options.sAddRowButtonHtml); +    tools.append(tableEditor._options.sRemoveRowButtonHtml); +    tools.append(tableEditor._options.sAddColumnButtonHtml); +    tools.append(tableEditor._options.sRemoveColumnButtonHtml); + +    tableEditor.bindEvents(); +    rangy.init(); +}; + +/** + * Bind all required event listeners, including button listeners and support + * for tabbing through table cells if enableCellTabbing is true. + */ +TableEditor.prototype.bindEvents = function () { +    var tableEditor = this, +        wym = tableEditor._wym; + +    // Handle tool button click +    jQuery(wym._box).find( +        tableEditor._options.sMergeRowButtonSelector +    ).click(function () { +        tableEditor.mergeRow(); +        return false; +    }); +    jQuery(wym._box).find( +        tableEditor._options.sAddRowButtonSelector +    ).click(function () { +        return tableEditor.addRow(wym.selectedContainer()); +    }); +    jQuery(wym._box).find( +        tableEditor._options.sRemoveRowButtonSelector +    ).click(function () { +        return tableEditor.removeRow(wym.selectedContainer()); +    }); +    jQuery(wym._box).find( +        tableEditor._options.sAddColumnButtonSelector +    ).click(function () { +        return tableEditor.addColumn(wym.selectedContainer()); +    }); +    jQuery(wym._box).find( +        tableEditor._options.sRemoveColumnButtonSelector +    ).click(function () { +        return tableEditor.removeColumn(wym.selectedContainer()); +    }); + +    // Handle tab clicks +    if (tableEditor._options.enableCellTabbing) { +        jQuery(wym._doc).bind('keydown', tableEditor.keyDown); +    } +}; + +/** + * Get the number of columns in a given tr element, accounting for colspan and + * rowspan. This function assumes that the table structure is valid, and will + * return incorrect results for uneven tables. + * + * @param tr The <tr> node whose number of columns we need to count. + * + * @returns {Number} The number of columns in the given tr, accounting for + * colspan and rowspan. + */ +TableEditor.prototype.getNumColumns = function (tr) { +    var tableEditor = this, +        wym = tableEditor._wym, +        numColumns = 0, +        table, +        firstTr; + +    table = wym.findUp(tr, 'table'); +    firstTr = jQuery(table).find('tr:eq(0)'); + +    // Count the tds and ths in the FIRST ROW of this table, accounting for +    // colspan. We count the first td because it won't have any rowspan's +    // before it to complicate things +    jQuery(firstTr).children('td,th').each(function (index, elmnt) { +        numColumns += TableEditor.GET_COLSPAN_PROP(elmnt); +    }); + +    return numColumns; +}; + +/** +    TableEditor.GET_COLSPAN_PROP +    ============================ + +    Get the integer value of the inferred colspan property on the given cell in +    a cross-browser compatible way that's also compatible across jquery +    versions. + +    jquery 1.6 changed the way .attr works, which affected certain browsers +    differently with regard to colspan and rowspan for cells that didn't +    explicitly have that attribute set. +*/ +TableEditor.GET_COLSPAN_PROP = function (cell) { +    var colspan = jQuery(cell).attr('colspan'); +    if (typeof colspan === 'undefined') { +        colspan = 1; +    } +    return parseInt(colspan, 10); +}; + +/** +    TableEditor.GET_ROWSPAN_PROP +    ============================ + +    Get the integer value of the inferred rowspan property on the given cell in +    a cross-browser compatible way that's also compatible across jquery +    versions. + +    See GET_COLSPAN_PROP for details +*/ +TableEditor.GET_ROWSPAN_PROP = function (cell) { +    var rowspan = jQuery(cell).attr('rowspan'); +    if (typeof rowspan === 'undefined') { +        rowspan = 1; +    } +    return parseInt(rowspan, 10); +}; +/** + * Get the X grid index of the given td or th table cell (0-indexed). This + * takes in to account all colspans and rowspans. + * + * @param cell The td or th node whose X index we're returning. + */ +TableEditor.prototype.getCellXIndex = function (cell) { +    var tableEditor = this, +        i, +        parentTr, +        baseRowColumns, +        rowColCount, +        missingCells, +        rowspanIndexes, +        checkTr, +        rowOffset, +        trChildren, +        elmnt, +        colspan, +        indexCounter, +        cellIndex; +    parentTr = jQuery(cell).parent('tr')[0]; + +    baseRowColumns = tableEditor.getNumColumns(parentTr); + +    // Figure out how many explicit cells are missing which is how many +    // rowspans we're affected by +    rowColCount = 0; +    jQuery(parentTr).children('td,th').each(function (index, elmnt) { +        rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt); +    }); + +    missingCells = baseRowColumns - rowColCount; +    rowspanIndexes = []; +    checkTr = parentTr; +    rowOffset = 1; + +    // If this cell is affected by a rowspan from farther up the table, +    // we need to take in to account any possible colspan attributes on that +    // cell. Store the real X index of the cells to the left of our cell to use +    // in the colspan calculation. +    while (missingCells > 0) { +        checkTr = jQuery(checkTr).prev('tr'); +        rowOffset += 1; +        trChildren = jQuery(checkTr).children('td,th'); +        for (i = 0; i < trChildren.length; i++) { +            elmnt = trChildren[i]; +            if (TableEditor.GET_ROWSPAN_PROP(elmnt) >= rowOffset) { +                // Actually affects our source row +                missingCells -= 1; +                colspan = TableEditor.GET_COLSPAN_PROP(elmnt); +                rowspanIndexes[tableEditor.getCellXIndex(elmnt)] = colspan; +            } +        } +    } + +    indexCounter = 0; +    cellIndex = null; +    // Taking in to account the real X indexes of all of the columns to the +    // left of this cell, determine the real X index. +    jQuery(parentTr).children('td,th').each(function (index, elmnt) { +        if (cellIndex !== null) { +            // We've already iterated to the cell we're checking +            return; +        } +        // Account for an inferred colspan created by a rowspan from above +        while (typeof rowspanIndexes[indexCounter] !== 'undefined') { +            indexCounter += parseInt(rowspanIndexes[indexCounter], 10); +        } +        if (elmnt === cell) { +            // We're at our cell, no need to keep moving to the right. +            // Signal this by setting the cellIndex +            cellIndex = indexCounter; +            return; +        } +        // Account for an explicit colspan on this cell +        indexCounter += TableEditor.GET_COLSPAN_PROP(elmnt); +    }); + +    if (cellIndex === null) { +        // Somehow, we never found the cell when iterating over its row. +        throw "Cell index not found"; +    } +    return cellIndex; +}; + +/** + * Get the number of columns represented by the given array of contiguous cell + * (td/th) nodes. + * Accounts for colspan and rowspan attributes. + * + * @param cells An array of td/th nodes whose total column span we're checking. + * + * @return {Number} The number of columns represented by the "cells" + */ +TableEditor.prototype.getTotalColumns = function (cells) { +    var tableEditor = this, +        rootTr = tableEditor.getCommonParentTr(cells), +        baseRowColumns, +        colspanCount, +        rowColCount, +        lastCell, +        firstCell; + +    if (rootTr === null) { +        // Non-contiguous columns +        throw "getTotalColumns only allowed for contiguous cells"; +    } + +    baseRowColumns = tableEditor.getNumColumns(rootTr); + +    // Count the number of simple columns, not accounting for rowspans +    colspanCount = 0; +    jQuery(cells).each(function (index, elmnt) { +        colspanCount += TableEditor.GET_COLSPAN_PROP(elmnt); +    }); + +    // Determine if we're affected by rowspans. If the number of simple columns +    // in the row equals the number of columns in the first row, we don't have +    // any rowspans +    rowColCount = 0; +    jQuery(rootTr).children('td,th').each(function (index, elmnt) { +        rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt); +    }); + +    if (rowColCount === baseRowColumns) { +        // Easy case. No rowspans to deal with +        return colspanCount; +    } else { +        if (cells.length === 1) { +            // Easy. Just the colspan +            return TableEditor.GET_COLSPAN_PROP(cells[0]); +        } else { +            lastCell = jQuery(cells).eq(cells.length - 1)[0]; +            firstCell = jQuery(cells).eq(0)[0]; +            // On jQuery 1.4 upgrade, jQuery(cells).eq(-1) +            return 1 + tableEditor.getCellXIndex(lastCell) - +                tableEditor.getCellXIndex(firstCell); +        } +    } +}; + +/** + * Merge the table cells in the given selection using a colspan. + * + * @return {Boolean} true if changes are made, false otherwise + */ +TableEditor.prototype.mergeRow = function () { +    var tableEditor = this, +        wym = tableEditor._wym, +        // Get all of the affected nodes in the range +        nodes = wym._getSelectedNodes(), +        cells, +        rootTr, +        mergeCell, +        $elmnt, +        rowspanProp, +        newContent, +        combinedColspan; + +    wym.deselect(); + +    // Just use the td and th nodes +    cells = jQuery(nodes).filter('td,th'); +    if (cells.length === 0) { +        return false; +    } + +    // If the selection is across multiple tables, don't merge +    rootTr = tableEditor.getCommonParentTr(cells); +    if (rootTr === null) { +        return false; +    } + +    mergeCell = cells[0]; +    // If any of the cells have a rowspan, create the inferred cells +    jQuery(cells).each(function (i, elmnt) { +        $elmnt = jQuery(elmnt); +        rowspanProp = TableEditor.GET_ROWSPAN_PROP(elmnt); +        if (rowspanProp <= 1) { +            // We don't care about cells without a rowspan +            return; +        } + +        // This cell has an actual rowspan, we need to account for it +        // Figure out the x index for this cell in the table grid +        var index = tableEditor.getCellXIndex(elmnt), +            // Create the previously-inferred cell in the appropriate index +            // with one less rowspan +            newRowspan = rowspanProp - 1, +            newTd, +            insertionIndex, +            insertionCells, +            cellInserted, +            xIndex; +        if (newRowspan === 1) { +            newTd = '<td>' + $elmnt.html() + '</td>'; +        } else { +            newTd = String() + +                '<td rowspan="' + newRowspan + '">' + +                $elmnt.html() + +                '</td>'; +        } +        if (index === 0) { +            $elmnt.parent('tr') +                .next('tr') +                .prepend(newTd); +        } else { +            // TODO: account for colspan/rowspan with insertion +            // Account for colspan/rowspan by walking from right to left +            // looking for the cell closest to the desired index to APPEND to +            insertionIndex = index - 1; +            insertionCells = $elmnt.parent('tr').next('tr').find('td,th'); +            cellInserted = false; +            for (i = insertionCells.length - 1; i >= 0; i--) { +                xIndex = tableEditor.getCellXIndex(insertionCells[i]); +                if (xIndex <= insertionIndex) { +                    jQuery(insertionCells[i]).after(newTd); +                    cellInserted = true; +                    break; +                } +            } +            if (!cellInserted) { +                // Bail out now before we clear HTML and break things +                throw "Cell rowspan invalid"; +            } +        } + +        // Clear the cell's html, since we just moved it down +        $elmnt.html(''); +    }); + +    // Remove any rowspan from the mergecell now that we've shifted rowspans +    // down +    // ie fails when we try to remove a rowspan for some reason +    try { +        jQuery(mergeCell).removeAttr('rowspan'); +    } catch (err) { +        jQuery(mergeCell).attr('rowspan', 1); +    } + +    // Build the content of the new combined cell from all of the included +    // cells +    newContent = ''; +    jQuery(cells).each(function (index, elmnt) { +        newContent += jQuery(elmnt).html(); +    }); + +    // Add a colspan to the farthest-left cell +    combinedColspan = tableEditor.getTotalColumns(cells); +    if (jQuery.browser.msie) { +        // jQuery.attr doesn't work for colspan in ie +        mergeCell.colSpan = combinedColspan; +    } else { +        jQuery(mergeCell).attr('colspan', combinedColspan); +    } + +    // Delete the rest of the cells +    jQuery(cells).each(function (index, elmnt) { +        if (index !== 0) { +            jQuery(elmnt).remove(); +        } +    }); + +    // Change the content in our newly-merged cell +    jQuery(mergeCell).html(newContent); + +    tableEditor.selectElement(mergeCell); + +    wym.registerModification(); +    return true; +}; + +/** + * Add a row to the given elmnt (representing a <tr> or a child of a <tr>). + * + * @param The node which will have a row appended after its parent row. + */ +TableEditor.prototype.addRow = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        tr = tableEditor._wym.findUp(elmnt, 'tr'), +        numColumns, +        tdHtml, +        i; + +    if (tr === null) { +        return false; +    } + +    numColumns = tableEditor.getNumColumns(tr); + +    tdHtml = ''; +    for (i = 0; i < numColumns; i++) { +        tdHtml += '<td> </td>'; +    } +    jQuery(tr).after('<tr>' + tdHtml + '</tr>'); + +    wym.registerModification(); +    return false; +}; + +/** + * Remove the given table if it doesn't have any rows/columns. + * + * @param table The table to delete if it is empty. + */ +TableEditor.prototype.removeEmptyTable = function (table) { +    var tableEditor = this, +        wym = tableEditor._wym, +        cells = jQuery(table).find('td,th'), +        $table; +    if (cells.length === 0) { +        $table = jQuery(table); +        $table.prev('br.' + WYMeditor.BLOCKING_ELEMENT_SPACER_CLASS).remove(); +        $table.next('br.' + WYMeditor.BLOCKING_ELEMENT_SPACER_CLASS).remove(); +        $table.remove(); +        wym.prepareDocForEditing(); +    } +}; + +/** + * Remove the row for the given element (representing a <tr> or a child + * of a <tr>). + * + * @param elmnt The node whose parent tr will be removed. + */ +TableEditor.prototype.removeRow = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        tr = wym.findUp(elmnt, 'tr'), +        table; + +    if (tr === null) { +        return false; +    } +    table = wym.findUp(elmnt, 'table'); +    if ( +        wym.hasSelection() === true && +        wym.doesElementContainSelection(elmnt) === true +    ) { +        wym.deselect(); +    } +    jQuery(tr).remove(); +    tableEditor.removeEmptyTable(table); + +    wym.registerModification(); +    return false; +}; + +/** + * Add a column to the given elmnt (representing a <td> or a child of a <td>). + * + * @param elmnt The node which will have a column appended afterward. + */ +TableEditor.prototype.addColumn = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        td = wym.findUp(elmnt, ['td', 'th']), +        prevTds, +        tdIndex, +        tr, +        newTd = '<td> </td>', +        newTh = '<th> </th>', +        insertionElement; + +    if (td === null) { +        return false; +    } +    prevTds = jQuery(td).prevAll(); +    tdIndex = prevTds.length; + +    tr = wym.findUp(td, 'tr'); +    jQuery(tr).siblings('tr').andSelf().each(function (index, element) { +        insertionElement = newTd; +        if (jQuery(element).find('th').length > 0) { +            // The row has a TH, so insert a th +            insertionElement = newTh; +        } + +        jQuery(element).find('td,th').eq(tdIndex).after(insertionElement); +    }); + +    wym.registerModification(); +    return false; +}; + +/** + * Remove the column to the right of the given elmnt (representing a <td> or a + * child of a <td>). + */ +TableEditor.prototype.removeColumn = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        td = wym.findUp(elmnt, ['td', 'th']), +        table, +        prevTds, +        tdIndex, +        tr; +    if (td === null) { +        return false; +    } +    table = wym.findUp(elmnt, 'table'); +    prevTds = jQuery(td).prevAll(); +    tdIndex = prevTds.length; + +    tr = wym.findUp(td, 'tr'); +    jQuery(tr).siblings('tr').addBack().each(function (index, element) { +        var $cell = jQuery(element).find("td, th").eq(tdIndex); +        if ( +            wym.hasSelection() === true && +            wym.doesElementContainSelection($cell[0]) === true +        ) { +            wym.deselect(); +        } +        $cell.remove(); +    }); +    tableEditor.removeEmptyTable(table); + +    wym.registerModification(); +    return false; +}; + +/** + * keyDown event handler used for consistent tab key cell movement. + */ +TableEditor.prototype.keyDown = function (evt) { +    var doc = this, +        wym = WYMeditor.INSTANCES[doc.title], +        tableEditor = wym.tableEditor; + +    if (evt.which === WYMeditor.KEY_CODE.TAB) { +        return tableEditor.selectNextCell(wym.selectedContainer()); +    } + +    return null; +}; + +/** + * Move the focus to the next cell. + */ +TableEditor.prototype.selectNextCell = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        cell = wym.findUp(elmnt, ['td', 'th']), +        nextCells, +        tr, +        nextRows; + +    if (cell === null) { +        return null; +    } + +    // Try moving to the next cell to the right +    nextCells = jQuery(cell).next('td,th'); +    if (nextCells.length > 0) { +        tableEditor.selectElement(nextCells[0]); +        return false; +    } + +    // There was no cell to the right, use the first cell in the next row +    tr = wym.findUp(cell, 'tr'); +    nextRows = jQuery(tr).next('tr'); +    if (nextRows.length !== 0) { +        nextCells = jQuery(nextRows).children('td,th'); +        if (nextCells.length > 0) { +            tableEditor.selectElement(nextCells[0]); +            return false; +        } +    } + +    // There is no next row. Do a normal tab +    return null; +}; + +/** + * Select the given element using rangy selectors. + */ +TableEditor.prototype.selectElement = function (elmnt) { +    var tableEditor = this, +        wym = tableEditor._wym, +        sel = wym.selection(), +        range = rangy.createRange(wym._doc); + +    range.setStart(elmnt, 0); +    range.setEnd(elmnt, 0); +    range.collapse(false); + +    try { +        sel.setSingleRange(range); +    } catch (err) { +        // ie8 can raise an "unkown runtime error" trying to empty the range +    } +    // Old IE selection hack +    if (WYMeditor.isInternetExplorerPre11()) { +        wym._saveCaret(); +    } +}; + +/** + * Get the common parent tr for the given table cell nodes. If the closest + * parent tr for each cell isn't the same, returns null. + */ +TableEditor.prototype.getCommonParentTr = function (cells) { +    var firstCell, +        parentTrList, +        rootTr; + +    cells = jQuery(cells).filter('td,th'); +    if (cells.length === 0) { +        return null; +    } +    firstCell = cells[0]; +    parentTrList = jQuery(firstCell).parent('tr'); + +    if (parentTrList.length === 0) { +        return null; +    } +    rootTr = parentTrList[0]; + +    // Ensure that all of the cells have the same parent tr +    jQuery(cells).each(function (index, elmnt) { +        parentTrList = jQuery(elmnt).parent('tr'); +        if (parentTrList.length === 0 || parentTrList[0] !== rootTr) { +            return null; +        } +    }); + +    return rootTr; +}; diff --git a/static-files/journal/wymeditor/plugins/table/table_delete_column.png b/static-files/journal/wymeditor/plugins/table/table_delete_column.pngBinary files differ new file mode 100644 index 0000000..9022d68 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/table_delete_column.png diff --git a/static-files/journal/wymeditor/plugins/table/table_delete_row.png b/static-files/journal/wymeditor/plugins/table/table_delete_row.pngBinary files differ new file mode 100644 index 0000000..f9d956f --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/table_delete_row.png diff --git a/static-files/journal/wymeditor/plugins/table/table_insert_column.png b/static-files/journal/wymeditor/plugins/table/table_insert_column.pngBinary files differ new file mode 100644 index 0000000..49226ec --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/table_insert_column.png diff --git a/static-files/journal/wymeditor/plugins/table/table_insert_row.png b/static-files/journal/wymeditor/plugins/table/table_insert_row.pngBinary files differ new file mode 100644 index 0000000..da435c3 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/table_insert_row.png diff --git a/static-files/journal/wymeditor/plugins/table/table_join_row.png b/static-files/journal/wymeditor/plugins/table/table_join_row.pngBinary files differ new file mode 100644 index 0000000..70e5983 --- /dev/null +++ b/static-files/journal/wymeditor/plugins/table/table_join_row.png | 
