![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/CoreBundle/Assets/js/libraries/froala/plugins/ |
/*! * froala_editor v2.4.2 (https://www.froala.com/wysiwyg-editor) * License https://froala.com/wysiwyg-editor/terms/ * Copyright 2014-2017 Froala Labs */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS module.exports = function( root, jQuery ) { if ( jQuery === undefined ) { // require('jQuery') returns a factory that requires window to // build a jQuery instance, we normalize how we use modules // that require this pattern but the window provided is a noop // if it's defined (how jquery works) if ( typeof window !== 'undefined' ) { jQuery = require('jquery'); } else { jQuery = require('jquery')(root); } } factory(jQuery); return jQuery; }; } else { // Browser globals factory(jQuery); } }(function ($) { $.extend($.FE.POPUP_TEMPLATES, { 'table.insert': '[_BUTTONS_][_ROWS_COLUMNS_]', 'table.edit': '[_BUTTONS_]', 'table.colors': '[_BUTTONS_][_COLORS_]' }) // Extend defaults. $.extend($.FE.DEFAULTS, { tableInsertMaxSize: 10, tableEditButtons: ['tableHeader', 'tableRemove', '|', 'tableRows', 'tableColumns', 'tableStyle', '-', 'tableCells', 'tableCellBackground', 'tableCellVerticalAlign', 'tableCellHorizontalAlign', 'tableCellStyle'], tableInsertButtons: ['tableBack', '|'], tableResizer: true, tableResizerOffset: 5, tableResizingLimit: 30, tableColorsButtons: ['tableBack', '|'], tableColors: [ '#61BD6D', '#1ABC9C', '#54ACD2', '#2C82C9', '#9365B8', '#475577', '#CCCCCC', '#41A85F', '#00A885', '#3D8EB9', '#2969B0', '#553982', '#28324E', '#000000', '#F7DA64', '#FBA026', '#EB6B56', '#E25041', '#A38F84', '#EFEFEF', '#FFFFFF', '#FAC51C', '#F37934', '#D14841', '#B8312F', '#7C706B', '#D1D5D8', 'REMOVE' ], tableColorsStep: 7, tableCellStyles: { 'fr-highlighted': 'Highlighted', 'fr-thick': 'Thick' }, tableStyles: { 'fr-dashed-borders': 'Dashed Borders', 'fr-alternate-rows': 'Alternate Rows' }, tableCellMultipleStyles: true, tableMultipleStyles: true, tableInsertHelper: true, tableInsertHelperOffset: 15 }); $.FE.PLUGINS.table = function (editor) { var $resizer; var $insert_helper; var mouseDownCellFlag; var mouseDownFlag; var mouseDownCell; var mouseMoveTimer; var resizingFlag; /* * Show the insert table popup. */ function _showInsertPopup () { var $btn = editor.$tb.find('.fr-command[data-cmd="insertTable"]'); var $popup = editor.popups.get('table.insert'); if (!$popup) $popup = _initInsertPopup(); if (!$popup.hasClass('fr-active')) { // Insert table popup editor.popups.refresh('table.insert'); editor.popups.setContainer('table.insert', editor.$tb); // Insert table left and top position. var left = $btn.offset().left + $btn.outerWidth() / 2; var top = $btn.offset().top + (editor.opts.toolbarBottom ? 10 : $btn.outerHeight() - 10); editor.popups.show('table.insert', left, top, $btn.outerHeight()); } } /* * Show the table edit popup. */ function _showEditPopup () { // Set popup position. var map = _tableMap(); if (map) { var $popup = editor.popups.get('table.edit'); if (!$popup) $popup = _initEditPopup(); editor.popups.setContainer('table.edit', editor.$sc); var offset = _selectionOffset(map); var left = (offset.left + offset.right) / 2; var top = offset.bottom; editor.popups.show('table.edit', left, top, offset.bottom - offset.top); // Disable toolbar buttons only if there are more than one cells selected. if (editor.edit.isDisabled()) { // Disable toolbar. editor.toolbar.disable(); // Allow text selection. editor.$el.removeClass('fr-no-selection'); editor.edit.on(); editor.button.bulkRefresh(); // Place selection in last selected table cell. editor.selection.setAtEnd(editor.$el.find('.fr-selected-cell:last').get(0)); editor.$el.focus() editor.selection.restore(); } } } /* * Show the table colors popup. */ function _showColorsPopup () { // Set popup position. var map = _tableMap(); if (map) { var $popup = editor.popups.get('table.colors'); if (!$popup) $popup = _initColorsPopup(); editor.popups.setContainer('table.colors', editor.$sc); var offset = _selectionOffset(map); var left = (offset.left + offset.right) / 2; var top = offset.bottom; // Refresh selected color. _refreshColor(); editor.popups.show('table.colors', left, top, offset.bottom - offset.top); } } /* * Called on table edit popup hide. */ function _hideEditPopup () { // Enable toolbar. if (selectedCells().length === 0) { editor.toolbar.enable(); } } /** * Init the insert table popup. */ function _initInsertPopup (delayed) { if (delayed) { editor.popups.onHide('table.insert', function () { // Clear previous cell selection. editor.popups.get('table.insert').find('.fr-table-size .fr-select-table-size > span[data-row="1"][data-col="1"]').trigger('mouseenter'); }); return true; } // Table buttons. var table_buttons = ''; if (editor.opts.tableInsertButtons.length > 0) { table_buttons = '<div class="fr-buttons">' + editor.button.buildList(editor.opts.tableInsertButtons) + '</div>'; } var template = { buttons: table_buttons, rows_columns: _insertTableHtml() }; var $popup = editor.popups.create('table.insert', template); // Initialize insert table grid events. editor.events.$on($popup, 'mouseenter', '.fr-table-size .fr-select-table-size .fr-table-cell', function (e) { _hoverCell($(e.currentTarget)); }, true); _addAccessibility($popup); return $popup; } /* * Hover table cell. */ function _hoverCell ($table_cell) { var row = $table_cell.data('row'); var col = $table_cell.data('col'); var $select_size = $table_cell.parent(); // Update size in title. $select_size.siblings('.fr-table-size-info').html(row + ' × ' + col); // Remove hover and fr-active-item class from all cells. $select_size.find('> span').removeClass('hover fr-active-item'); // Add hover class only to the correct cells. for (var i = 1; i <= editor.opts.tableInsertMaxSize; i++) { for (var j = 0; j <= editor.opts.tableInsertMaxSize; j++) { var $cell = $select_size.find('> span[data-row="' + i + '"][data-col="' + j + '"]'); if (i <= row && j <= col) { $cell.addClass('hover'); } else if ((i <= row + 1 || (i <= 2 && !editor.helpers.isMobile()))) { $cell.css('display', 'inline-block'); } else if (i > 2 && !editor.helpers.isMobile()) { $cell.css('display', 'none'); } } } // Mark table cell as the active item. $table_cell.addClass('fr-active-item'); } /* * The HTML for insert table grid. */ function _insertTableHtml () { // Grid html var rows_columns = '<div class="fr-table-size"><div class="fr-table-size-info">1 × 1</div><div class="fr-select-table-size">' for (var i = 1; i <= editor.opts.tableInsertMaxSize; i++) { for (var j = 1; j <= editor.opts.tableInsertMaxSize; j++) { var display = 'inline-block'; // Display only first 2 rows. if (i > 2 && !editor.helpers.isMobile()) { display = 'none'; } var cls = 'fr-table-cell '; if (i == 1 && j == 1) { cls += ' hover'; } rows_columns += '<span class="fr-command ' + cls + '" tabIndex="-1" data-cmd="tableInsert" data-row="' + i + '" data-col="' + j + '" data-param1="' + i + '" data-param2="' + j + '" style="display: ' + display + ';" role="button"><span></span><span class="fr-sr-only">' + i + ' × ' + j + ' </span></span>'; } rows_columns += '<div class="new-line"></div>'; } rows_columns += '</div></div>'; return rows_columns; } /* * Register keyboard events. */ function _addAccessibility ($popup) { // Hover cell when table.insert cells are focused. editor.events.$on($popup, 'focus', '[tabIndex]', function (e) { var $focused_el = $(e.currentTarget); _hoverCell($focused_el); }); // Register popup event. editor.events.on('popup.tab', function (e) { var $focused_item = $(e.currentTarget); // Skip if popup is not visible or focus is elsewere. if (!editor.popups.isVisible('table.insert') || !$focused_item.is('span, a')) { return true; } var key_code = e.which; var status; if ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code || $.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code) { if ($focused_item.is('span.fr-table-cell')) { // Get all current cells. var $cells = $focused_item.parent().find('span.fr-table-cell'); // Get focused item position. var index = $cells.index($focused_item); // Get cell matrix dimensions. var columns = editor.opts.tableInsertMaxSize; // Get focused item coordinates. var column = index % columns; var line = Math.floor(index / columns); // Calculate next coordinates. Go to the other opposite site of the matrix if there is no next adjacent element. if ($.FE.KEYCODE.ARROW_UP == key_code) { line = Math.max(0, line - 1); } else if ($.FE.KEYCODE.ARROW_DOWN == key_code) { line = Math.min(editor.opts.tableInsertMaxSize - 1, line + 1); } else if ($.FE.KEYCODE.ARROW_LEFT == key_code) { column = Math.max(0, column - 1); } else if ($.FE.KEYCODE.ARROW_RIGHT == key_code) { column = Math.min(editor.opts.tableInsertMaxSize - 1, column + 1); } // Get the next element based on the new coordinates. var nextIndex = line * columns + column; var $el = $($cells.get(nextIndex)); // Hover cell _hoverCell($el); // Focus. editor.events.disableBlur(); $el.focus(); status = false; } } // ENTER or SPACE. else if ($.FE.KEYCODE.ENTER == key_code) { editor.button.exec($focused_item); status = false; } // Prevent propagation. if (status === false) { e.preventDefault(); e.stopPropagation(); } return status; }, true); } /** * Init the table edit popup. */ function _initEditPopup (delayed) { if (delayed) { editor.popups.onHide('table.edit', _hideEditPopup); return true; } // Table buttons. var table_buttons = ''; if (editor.opts.tableEditButtons.length > 0) { table_buttons = '<div class="fr-buttons">' + editor.button.buildList(editor.opts.tableEditButtons) + '</div>'; } var template = { buttons: table_buttons }; var $popup = editor.popups.create('table.edit', template); editor.events.$on(editor.$wp, 'scroll.table-edit', function () { if (editor.popups.isVisible('table.edit')) { _showEditPopup(); } }); return $popup; } /* * Init the table cell background popup. */ function _initColorsPopup () { // Table colors buttons. var table_buttons = ''; if (editor.opts.tableColorsButtons.length > 0) { table_buttons = '<div class="fr-buttons fr-table-colors-buttons">' + editor.button.buildList(editor.opts.tableColorsButtons) + '</div>'; } var template = { buttons: table_buttons, colors: _colorsHTML() }; var $popup = editor.popups.create('table.colors', template); editor.events.$on(editor.$wp, 'scroll.table-colors', function () { if (editor.popups.isVisible('table.colors')) { _showColorsPopup(); } }); _addColorsAccessibility($popup); return $popup; } /* * HTML for the table colors. */ function _colorsHTML () { // Create colors html. var colors_html = '<div class="fr-table-colors">'; // Add colors. for (var i = 0; i < editor.opts.tableColors.length; i++) { if (i !== 0 && i % editor.opts.tableColorsStep === 0) { colors_html += '<br>'; } if (editor.opts.tableColors[i] != 'REMOVE') { colors_html += '<span class="fr-command" style="background: ' + editor.opts.tableColors[i] + ';" tabIndex="-1" role="button" data-cmd="tableCellBackgroundColor" data-param1="' + editor.opts.tableColors[i] + '"><span class="fr-sr-only">' + editor.language.translate('Color') + ' ' + editor.opts.tableColors[i] + ' </span></span>'; } else { colors_html += '<span class="fr-command" data-cmd="tableCellBackgroundColor" tabIndex="-1" role="button" data-param1="REMOVE" title="' + editor.language.translate('Clear Formatting') + '"><i class="ri-eraser-line"></i><span class="fr-sr-only">' + editor.language.translate('Clear Formatting') + '</span></span>'; } } colors_html += '</div>'; return colors_html; } /* * Register keyboard events for colors. */ function _addColorsAccessibility ($popup) { // Register popup event. editor.events.on('popup.tab', function (e) { var $focused_item = $(e.currentTarget); // Skip if popup is not visible or focus is elsewere. if (!editor.popups.isVisible('table.colors') || !$focused_item.is('span')) { return true; } var key_code = e.which; var status = true; // Tabbing. if ($.FE.KEYCODE.TAB == key_code) { var $tb = $popup.find('.fr-buttons'); // Focus back the popup's toolbar if exists. status = !editor.accessibility.focusToolbar($tb, (e.shiftKey ? true : false)); } // Arrows. else if ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code || $.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code) { // Get all current colors. var $colors = $focused_item.parent().find('span.fr-command'); // Get focused item position. var index = $colors.index($focused_item); // Get color matrix dimensions. var columns = editor.opts.colorsStep; var lines = Math.floor($colors.length / columns); // Get focused item coordinates. var column = index % columns; var line = Math.floor(index / columns); var nextIndex = line * columns + column; var dimension = lines * columns; // Calculate next index. Go to the other opposite site of the matrix if there is no next adjacent element. // Up/Down: Traverse matrix lines. // Left/Right: Traverse the matrix as it is a vector. if ($.FE.KEYCODE.ARROW_UP == key_code) { nextIndex = (((nextIndex - columns) % dimension) + dimension) % dimension; // Javascript negative modulo bug. } else if ($.FE.KEYCODE.ARROW_DOWN == key_code) { nextIndex = (nextIndex + columns) % dimension; } else if ($.FE.KEYCODE.ARROW_LEFT == key_code) { nextIndex = (((nextIndex - 1) % dimension) + dimension) % dimension; // Javascript negative modulo bug. } else if ($.FE.KEYCODE.ARROW_RIGHT == key_code) { nextIndex = (nextIndex + 1) % dimension; } // Get the next element based on the new index. var $el = $($colors.get(nextIndex)); // Focus. editor.events.disableBlur(); $el.focus(); status = false; } // ENTER or SPACE. else if ($.FE.KEYCODE.ENTER == key_code) { editor.button.exec($focused_item); status = false; } // Prevent propagation. if (status === false) { e.preventDefault(); e.stopPropagation(); } return status; }, true); } /* * Show the current selected color. */ function _refreshColor () { var $popup = editor.popups.get('table.colors'); var $cell = editor.$el.find('.fr-selected-cell:first'); // Remove current color selection. $popup.find('.fr-selected-color').removeClass('fr-selected-color fr-active-item'); // Find the selected color. $popup.find('span[data-param1="' + editor.helpers.RGBToHex($cell.css('background-color')) + '"]').addClass('fr-selected-color fr-active-item'); } /* * Insert table method. */ function insert (rows, cols) { // Create table HTML. var table = '<table style="width: 100%;"><tbody>'; var cell_width = 100 / cols; var i; var j; for (i = 0; i < rows; i++) { table += '<tr>'; for (j = 0; j < cols; j++) { table += '<td style="width: ' + cell_width.toFixed(4) + '%;">'; if (i === 0 && j === 0)table += $.FE.MARKERS; table += '<br></td>'; } table += '</tr>'; } table += '</tbody></table>'; editor.html.insert(table); // Update cursor position. editor.selection.restore() } /* * Delete table method. */ function remove () { if (selectedCells().length > 0) { var $current_table = selectedTable(); // Update cursor position. editor.selection.setBefore($current_table.get(0)) || editor.selection.setAfter($current_table.get(0)); editor.selection.restore(); // Hide table edit popup. editor.popups.hide('table.edit'); // Delete table. $current_table.remove(); // Enable toolbar. editor.toolbar.enable(); } } /* * Add table header. */ function addHeader () { var $table = selectedTable(); // If there is a selection in the table and the table doesn't have a header already. if ($table.length > 0 && $table.find('th').length === 0) { // Create header HTML. var thead = '<thead><tr>'; var i; var col = 0; // Get first row and count table columns. $table.find('tr:first > td').each (function () { var $td = $(this); col += parseInt($td.attr('colspan'), 10) || 1; }); // Add cells. for (i = 0; i < col; i++) { thead += '<th><br></th>'; } thead += '</tr></thead>' $table.prepend(thead); // Reposition table edit popup. _showEditPopup(); } } /* * Remove table header. */ function removeHeader () { var $current_table = selectedTable(); var $table_header = $current_table.find('thead'); // Table has a header. if ($table_header.length > 0) { // If table does not have any other rows then delete table. if ($current_table.find('tbody tr').length === 0) { // Remove table. remove(); } else { $table_header.remove(); // Reposition table edit popup if there any more selected celss. if (selectedCells().length > 0) { _showEditPopup(); } else { // Hide popup. editor.popups.hide('table.edit'); // Update cursor position. var td = $current_table.find('tbody tr:first td:first').get(0); if (td) { editor.selection.setAtEnd(td); editor.selection.restore(); } } } } } /* * Insert row method. */ function insertRow (position) { var $table = selectedTable(); // We have selection in a table. if ($table.length > 0) { // Cannot insert row above the table header. if (editor.$el.find('th.fr-selected-cell').length > 0 && position == 'above') { return; } var i; var ref_row; // Create a table map. var map = _tableMap(); // Get selected cells from the table. var selection = _currentSelection(map); // Reference row. if (position == 'above') { ref_row = selection.min_i; } else { ref_row = selection.max_i; } // Create row HTML. var tr = '<tr>'; // Add cells. for (i = 0; i < map[ref_row].length; i++) { // If cell has rowspan we should increase it. if ((position == 'below' && ref_row < map.length - 1 && map[ref_row][i] == map[ref_row + 1][i]) || (position == 'above' && ref_row > 0 && map[ref_row][i] == map[ref_row - 1][i])) { // Don't increase twice for colspan. if (i === 0 || (i > 0 && map[ref_row][i] != map[ref_row][i - 1])) { var $cell = $(map[ref_row][i]); $cell.attr('rowspan', parseInt($cell.attr('rowspan'), 10) + 1); } } else { tr += '<td><br></td>'; } } // Close row tag. tr += '</tr>'; var $ref_row = $($table.find('tr').not($table.find('table tr')).get(ref_row)); // Insert new row. if (position == 'below') { // Table edit popup should not change position. $ref_row.after(tr); } else if (position == 'above') { $ref_row.before(tr); // Reposition table edit popup. if (editor.popups.isVisible('table.edit')) { _showEditPopup(); } } } } /* * Delete row method. */ function deleteRow () { var $table = selectedTable(); // We have selection in a table. if ($table.length > 0) { var i; var j; var $row; // Create a table map. var map = _tableMap(); // Get selected cells from the table. var selection = _currentSelection(map); // If all the rows are selected then delete the entire table. if (selection.min_i === 0 && selection.max_i == map.length - 1) { remove(); } else { // We should delete selected rows. for (i = selection.max_i; i >= selection.min_i; i--) { $row = $($table.find('tr').not($table.find('table tr')).get(i)); // Go through the table map to check for rowspan on the row to delete. for (j = 0; j < map[i].length; j++) { // Don't do this twice if we have a colspan. if (j === 0 || map[i][j] != map[i][j - 1]) { var $cell = $(map[i][j]); // We should decrease rowspan. if (parseInt($cell.attr('rowspan'), 10) > 1) { var rowspan = parseInt($cell.attr('rowspan'), 10) - 1; if (rowspan == 1) { $cell.removeAttr('rowspan'); } else { $cell.attr('rowspan', rowspan); } } // We might need to move tds on the row below if we have a rowspan that starts here. if (i < map.length - 1 && map[i][j] == map[i + 1][j] && (i === 0 || map[i][j] != map[i - 1][j])) { // Move td to the row below. var td = map[i][j]; var col = j; // Go back until we get the last element (we might have colspan). while (col > 0 && map[i][col] == map[i][col - 1]) { col--; } // Add td at the beginning of the row below. if (col === 0) { $($table.find('tr').not($table.find('table tr')).get(i + 1)).prepend(td); } else { $(map[i + 1][col - 1]).after(td); } } } } // Remove tbody or thead if there are no more rows. var $row_parent = $row.parent(); $row.remove(); if ($row_parent.find('tr').length === 0) { $row_parent.remove(); } // Table has changed. map = _tableMap($table); } _updateCellSpan(0, map.length - 1, 0, map[0].length - 1, $table); // Update cursor position if (selection.min_i > 0) { // Place cursor in the row above selection. editor.selection.setAtEnd(map[selection.min_i - 1][0]); } else { // Place cursor in the row below selection. editor.selection.setAtEnd(map[0][0]); } editor.selection.restore(); // Hide table edit popup. editor.popups.hide('table.edit'); } } } /* * Insert column method. */ function insertColumn (position) { var $table = selectedTable(); // We have selection in a table. if ($table.length > 0) { // Create a table map. var map = _tableMap(); // Get selected cells from the table. var selection = _currentSelection(map); // Reference row. var ref_col; if (position == 'before') { ref_col = selection.min_j; } else { ref_col = selection.max_j; } // Old and new column widths. var old_width = 100 / map[0].length; var new_width = 100 / (map[0].length + 1); // Go through all cells and get their initial (old) widths. var $cell; $table.find('th, td').each (function () { $cell = $(this); $cell.data('old-width', $cell.outerWidth() / $table.outerWidth() * 100); }); // Parse each row to insert a new td. $table.find('tr').not($table.find('table tr')).each (function (index) { // Get the exact td index before / after which we have to add the new td. // ref_col means the table column number, but this is not the same with the td number in a row. // We might have colspan or rowspan greater than 1. var $row = $(this); var col_no = 0; var td_no = 0; var ref_td; // Start with the first td (td_no = 0) in the current row. // Sum colspans (col_no) to see when we reach ref_col. // Summing colspans we get the same number with the table column number. while (col_no - 1 < ref_col) { // Get current td. ref_td = $row.find('> th, > td').get(td_no); // There are no more tds in this row. if (!ref_td) { ref_td = null; break; } // The current td is the same with the td from the table map. if (ref_td == map[index][col_no]) { // The current td might have colspan. col_no += parseInt($(ref_td).attr('colspan'), 10) || 1; // Go to the next td on the current row. td_no++; } // If current td is not the same with the td from the table map. // This means that this table cell (map[index][td_no]) has rowspan. // There is at least one td less on this row due to rowspan (based on map[index][td_no] colspan value). // We want to count this as a column as well. else { col_no += parseInt($(map[index][col_no]).attr('colspan'), 10) || 1; // ref_td is one td ahead. Get previous td if we want to insert column after. if (position == 'after') { // There is a rowspan and so ref_td is the first td, but it is not in the first column. if (td_no === 0) { ref_td = -1; } else { ref_td = $row.find('> th, > td').get(td_no - 1); } } } } var $ref_td = $(ref_td); // If the computed column number is higher than the reference number // then on this row we have a colspan longer than the reference column. // When adding a column after we should increase colspan on this row. // // If we want to add a column before, but the td on the reference column is // the same with the previous one then we have a td with colspan that // starts before the column reference number. // When adding a column before we should increase colspan on this row. if ((position == 'after' && col_no - 1 > ref_col) || (position == 'before' && ref_col > 0 && map[index][ref_col] == map[index][ref_col - 1])) { // Don't increase twice for rowspan. if (index === 0 || (index > 0 && map[index][ref_col] != map[index - 1][ref_col])) { var colspan = parseInt($ref_td.attr('colspan'), 10) + 1; $ref_td.attr('colspan', colspan) // Update this cell's width. $ref_td.css('width', ($ref_td.data('old-width') * new_width / old_width + new_width).toFixed(4) + '%'); $ref_td.removeData('old-width'); } } else { // Create HTML for a new cell. var td; // Might be a td or a th. if ($row.find('th').length > 0) { td = '<th style="width: ' + new_width.toFixed(4) + '%;"><br></th>'; } else { td = '<td style="width: ' + new_width.toFixed(4) + '%;"><br></td>'; } // Insert exactly at the beginning. if (ref_td == -1) { $row.prepend(td); // Insert exactly at the end. } else if (ref_td == null) { $row.append(td); // Insert td on the current row. } else { if (position == 'before') { $ref_td.before(td); } else if (position == 'after') { $ref_td.after(td); } } } }); // Parse each row to update cells' width. $table.find('th, td').each (function () { $cell = $(this); // Update width and remove data. if ($cell.data('old-width')) { $cell.css('width', ($cell.data('old-width') * new_width / old_width).toFixed(4) + '%'); $cell.removeData('old-width') } }); // Reposition table edit popup. if (editor.popups.isVisible('table.edit')) { _showEditPopup(); } } } /* * Delete column method. */ function deleteColumn () { var $table = selectedTable(); // We have selection in a table. if ($table.length > 0) { var i; var j; var $cell; // Create a table map. var map = _tableMap(); // Get selected cells from the table. var selection = _currentSelection(map); // If all the rows are selected then delete the entire table. if (selection.min_j === 0 && selection.max_j == map[0].length - 1) { remove(); } else { // Old and new column widths. var old_width = 100 / map[0].length; var new_width = 100 / (map[0].length - selection.max_j + selection.min_j - 1); // Go through all cells and get their initial (old) widths. $table.find('th, td').each (function () { $cell = $(this); if (!$cell.hasClass('fr-selected-cell')) { $cell.data('old-width', $cell.outerWidth() / $table.outerWidth() * 100); } }); // We should delete selected columns. for (j = selection.max_j; j >= selection.min_j; j--) { // Go through the table map to check for colspan. for (i = 0; i < map.length; i++) { // Don't do this twice if we have a rowspan. if (i === 0 || map[i][j] != map[i - 1][j]) { // We should decrease colspan. $cell = $(map[i][j]); if ((parseInt($cell.attr('colspan'), 10) || 1) > 1) { var colspan = parseInt($cell.attr('colspan'), 10) - 1; if (colspan == 1) { $cell.removeAttr('colspan'); } else { $cell.attr('colspan', colspan); } // Update cell width. $cell.css('width', (($cell.data('old-width') - _columnWidth(j, map)) * new_width / old_width).toFixed(4) + '%'); $cell.removeData('old-width'); // If there is no colspan delete the cell. } else { // We might need to delete the row too if it is empty. var $row = $($cell.parent().get(0)); $cell.remove(); // Check if there are any more tds in the current row. if ($row.find('> th, > td').length === 0) { // Check if it is okay to delete the tr. if ($row.prev().length === 0 || $row.next().length === 0 || $row.prev().find('> th[rowspan], > td[rowspan]').length < $row.prev().find('> th, > td').length) { $row.remove(); } } } } } } _updateCellSpan(0, map.length - 1, 0, map[0].length - 1, $table); // Update cursor position if (selection.min_j > 0) { // Place cursor in the column before selection. editor.selection.setAtEnd(map[selection.min_i][selection.min_j - 1]); } else { // Place cursor in the column after selection. editor.selection.setAtEnd(map[selection.min_i][0]); } editor.selection.restore(); // Hide table edit popup. editor.popups.hide('table.edit'); // Scale current cells' width after column has been deleted. $table.find('th, td').each (function () { $cell = $(this); // Update width and remove data. if ($cell.data('old-width')) { $cell.css('width', ($cell.data('old-width') * new_width / old_width).toFixed(4) + '%'); $cell.removeData('old-width') } }); } } } /* * Update or remove colspan attribute. */ function _updateColspan (min_j, max_j, $table) { var i; var j; var k; var first_span; var span; var decrease = 0; // Create a table map. var map = _tableMap($table); // A column might have been deleted. max_j = Math.min(max_j, map[0].length - 1); // Find out how much we should decrease colspan. // Parsing only the first row is enough. for (j = min_j; j <= max_j; j++) { // This cell has colspan and has already been checked. if (j > min_j && map[0][j] == map[0][j - 1]) { continue; } // Current cell colspan first_span = parseInt(map[0][j].getAttribute('colspan'), 10) || 1; // Cell has colspan between min_j and max_j. /* j + 1 will never exceed the number of columns in a table. * A colspan is detected before the last column and all next cells on that row are skipped. */ if (first_span > 1 && map[0][j] == map[0][j + 1]) { // The value we should decrease rowspan with. decrease = first_span - 1; // Check all columns on the current row. // We found a colspan on the first row (i = 0), continue with second row (i = 1). for (i = 1; i < map.length; i++) { // This cell has rowspan and has already been checked. if (map[i][j] == map[i - 1][j]) { continue; } // Look for a colspan on the same columns. for (k = j; k < j + first_span; k++) { span = parseInt(map[i][k].getAttribute('colspan'), 10) || 1; // There are other cells with colspan on this column. /* k + 1 will never exceed the number of columns in a table. * A colspan is detected before the last column and all next cells on that row are skipped. */ if (span > 1 && map[i][k] == map[i][k + 1]) { decrease = Math.min(decrease, span - 1); // Skip colspan. k += decrease; } else { decrease = Math.max (0, decrease - 1); // Stop if decrease reaches 0. if (!decrease) { break; } } } // Stop looking on the next columns if decrease reaches 0. if (!decrease) { break; } } } } // Update colspan attribute. if (decrease) { _decreaseCellSpan(map, decrease, 'colspan', 0, map.length - 1, min_j, max_j); } } /* * Update or remove rowspan attribute. */ function _updateRowspan (min_i, max_i, $table) { var i; var j; var k; var first_span; var span; var decrease = 0; // Create a table map. var map = _tableMap($table); // A row might have been deleted. max_i = Math.min(max_i, map.length - 1); // Find out how much we should decrease rowspan. // Parsing only the first column is enough. for (i = min_i; i <= max_i; i++) { // This cell has rowspan and has already been checked. if (i > min_i && map[i][0] == map[i - 1][0]) { continue; } // Current cell rowspan first_span = parseInt(map[i][0].getAttribute('rowspan'), 10) || 1; // Cell has rowspan between min_i and max_i. /* i + 1 will never exceed the number of rows in a table. * A rowspan is detected before the last row and all next cells on that column are skipped. */ if (first_span > 1 && map[i][0] == map[i + 1][0]) { // The value we should decrease rowspan with. decrease = first_span - 1; // Check all columns on the current row. // We found a rowspan on the first column (j = 0), continue with second column (j = 1). for (j = 1; j < map[0].length; j++) { // This cell has colspan and has already been checked. if (map[i][j] == map[i][j - 1]) { continue; } // Look for a rowspan on the same rows. for (k = i; k < i + first_span; k++) { span = parseInt(map[k][j].getAttribute('rowspan'), 10) || 1; // There are other cells with rowspan on this row. /* k + 1 will never exceed the number of rows in a table. * A rowspan is detected before the last row and all next cells on that column are skipped. */ if (span > 1 && map[k][j] == map[k + 1][j]) { decrease = Math.min(decrease, span - 1); // Skip rowspan. k += decrease; } else { decrease = Math.max (0, decrease - 1); // Stop if decrease reaches 0. if (!decrease) { break; } } } // Stop looking on the next columns if decrease reaches 0. if (!decrease) { break; } } } } // Update rowspan attribute. if (decrease) { _decreaseCellSpan(map, decrease, 'rowspan', min_i, max_i, 0, map[0].length - 1); } } /* * Decrease the colspan or rowspan with the amount specified. */ function _decreaseCellSpan (map, decrease, span_type, min_i, max_i, min_j, max_j) { // Update span attribute. var i; var j; var span; // Go only through lines and columns that need to be updated. for (i = min_i; i <= max_i; i++) { for (j = min_j; j <= max_j; j++) { // This cell has rowspan or colspan and has already been checked. if ((i > min_i && map[i][j] == map[i - 1][j]) || (j > min_j && map[i][j] == map[i][j - 1])) { continue; } span = parseInt(map[i][j].getAttribute(span_type), 10) || 1; // Update cell span. if (span > 1) { if (span - decrease > 1) map[i][j].setAttribute(span_type, span - decrease); else map[i][j].removeAttribute(span_type); } } } } /* * Update or remove colspan and rowspan attributes. */ function _updateCellSpan (min_i, max_i, min_j, max_j, $table) { _updateRowspan(min_i, max_i, $table); _updateColspan(min_j, max_j, $table); } /* * Merge selected cells method. */ function mergeCells () { // We have more than one cell selected in a table. Cannot merge td and th. if (selectedCells().length > 1 && (editor.$el.find('th.fr-selected-cell').length === 0 || editor.$el.find('td.fr-selected-cell').length === 0)) { // Create a table map. var map = _tableMap(); // Get selected cells. var selection = _currentSelection(map); var i; var $cell; var cells = editor.$el.find('.fr-selected-cell'); var $first_cell = $(cells[0]); var $first_row = $first_cell.parent(); var first_row_cells = $first_row.find('.fr-selected-cell'); var $current_table = $first_cell.closest('table'); var content = $first_cell.html(); var width = 0; // Update cell width. for (i = 0; i < first_row_cells.length; i++) { width += $(first_row_cells[i]).outerWidth(); } $first_cell.css('width', (width / $current_table.outerWidth() * 100).toFixed(4) + '%'); // Set the colspan for the merged cells. if (selection.min_j < selection.max_j) { $first_cell.attr('colspan', selection.max_j - selection.min_j + 1); } // Set the rowspan for the merged cells. if (selection.min_i < selection.max_i) { $first_cell.attr('rowspan', selection.max_i - selection.min_i + 1); } // Go through all selected cells to merge their content. for (i = 1; i < cells.length; i++) { $cell = $(cells[i]) // If cell is empty, don't add only <br> tags. if ($cell.html() != '<br>' && $cell.html() !== '') { content += '<br>' + $cell.html(); } // Remove cell. $cell.remove(); } // Set the HTML content. $first_cell.html(content); editor.selection.setAtEnd($first_cell.get(0)); editor.selection.restore(); // Enable toolbar. editor.toolbar.enable(); // Update rowspan before removing empty rows (otherwise table map is not correct). _updateRowspan(selection.min_i, selection.max_i, $current_table); // Merge is done, check if we have empty trs to clean. var empty_trs = $current_table.find('tr:empty'); for (i = empty_trs.length - 1; i >= 0; i--) { $(empty_trs[i]).remove(); } // Update colspan after removing empty rows and updating rowspan. _updateColspan(selection.min_j, selection.max_j, $current_table); // Reposition table edit popup. _showEditPopup(); } } /* * Split cell horizontally method. */ function splitCellHorizontally () { // We have only one cell selected in a table. if (selectedCells().length == 1) { var $selected_cell = editor.$el.find('.fr-selected-cell'); var $current_row = $selected_cell.parent(); var $current_table = $selected_cell.closest('table'); var current_rowspan = parseInt($selected_cell.attr('rowspan'), 10); // Create a table map. var map = _tableMap(); var cell_origin = _cellOrigin($selected_cell.get(0), map); // Create new td. var $new_td = $selected_cell.clone().html('<br>'); // Cell has rowspan. if (current_rowspan > 1) { // Split current cell's rowspan. var new_rowspan = Math.ceil(current_rowspan / 2); if (new_rowspan > 1) { $selected_cell.attr('rowspan', new_rowspan); } else { $selected_cell.removeAttr('rowspan'); } // Update new td's rowspan. if (current_rowspan - new_rowspan > 1) { $new_td.attr('rowspan', current_rowspan - new_rowspan); } else { $new_td.removeAttr('rowspan'); } // Find where we should insert the new td. var row = cell_origin.row + new_rowspan; var col = cell_origin.col === 0 ? cell_origin.col : cell_origin.col - 1; // Go back until we find a td on this row. We might have colspans and rowspans. while (col >= 0 && (map[row][col] == map[row][col - 1] || (row > 0 && map[row][col] == map[row - 1][col]))) { col--; } if (col == -1) { // We couldn't find a previous td on this row. Prepend the new td. $($current_table.find('tr').not($current_table.find('table tr')).get(row)).prepend($new_td); } else { $(map[row][col]).after($new_td); } } else { // Add new row bellow with only one cell. var $row = $('<tr>').append($new_td); var i; // Increase rowspan for all cells on the current row. for (i = 0; i < map[0].length; i++) { // Don't do this twice if we have a colspan. if (i === 0 || map[cell_origin.row][i] != map[cell_origin.row][i - 1]) { var $cell = $(map[cell_origin.row][i]); if (!$cell.is($selected_cell)) { $cell.attr('rowspan', (parseInt($cell.attr('rowspan'), 10) || 1) + 1); } } } $current_row.after($row); } // Remove selection _removeSelection(); // Hide table edit popup. editor.popups.hide('table.edit'); } } /* * Split cell vertically method. */ function splitCellVertically () { // We have only one cell selected in a table. if (selectedCells().length == 1) { var $selected_cell = editor.$el.find('.fr-selected-cell'); var current_colspan = parseInt($selected_cell.attr('colspan'), 10) || 1; var parent_width = $selected_cell.parent().outerWidth(); var width = $selected_cell.outerWidth(); // Create new td. var $new_td = $selected_cell.clone().html('<br>'); // Create a table map. var map = _tableMap(); var cell_origin = _cellOrigin($selected_cell.get(0), map); if (current_colspan > 1) { // Split current colspan. var new_colspan = Math.ceil(current_colspan / 2); width = _columnsWidth(cell_origin.col, cell_origin.col + new_colspan - 1, map) / parent_width * 100; var new_width = _columnsWidth(cell_origin.col + new_colspan, cell_origin.col + current_colspan - 1, map) / parent_width * 100; // Update colspan for current cell. if (new_colspan > 1) { $selected_cell.attr('colspan', new_colspan); } else { $selected_cell.removeAttr('colspan'); } // Update new td's colspan. if (current_colspan - new_colspan > 1) { $new_td.attr('colspan', current_colspan - new_colspan); } else { $new_td.removeAttr('colspan'); } // Update cell width. $selected_cell.css('width', width.toFixed(4) + '%'); $new_td.css('width', new_width.toFixed(4) + '%'); // Increase colspan for all cells on the current column. } else { var i; for (i = 0; i < map.length; i++) { // Don't do this twice if we have a rowspan. if (i === 0 || map[i][cell_origin.col] != map[i - 1][cell_origin.col]) { var $cell = $(map[i][cell_origin.col]); if (!$cell.is($selected_cell)) { var colspan = (parseInt($cell.attr('colspan'), 10) || 1) + 1; $cell.attr('colspan', colspan); } } } // Update cell width. width = width / parent_width * 100 / 2; $selected_cell.css('width', width.toFixed(4) + '%'); $new_td.css('width', width.toFixed(4) + '%'); } // Add a new td after the current one. $selected_cell.after($new_td); // Remove selection _removeSelection(); // Hide table edit popup. editor.popups.hide('table.edit'); } } /* * Set background color to selected cells. */ function setBackground (color) { // Set background color. if (color != 'REMOVE') { editor.$el.find('.fr-selected-cell').css('background-color', editor.helpers.HEXtoRGB(color)); } // Remove background color. else { editor.$el.find('.fr-selected-cell').css('background-color', ''); } } /* * Set vertical align to selected cells. */ function verticalAlign (val) { editor.$el.find('.fr-selected-cell').css('vertical-align', val); } /* * Apply horizontal alignment to selected cells. */ function horizontalAlign (val) { editor.$el.find('.fr-selected-cell').css('text-align', val); } /** * Apply specific style to selected table or selected cells. * val class to apply. * obj table or selected cells. * multiple_styles editor.opts.tableStyles or editor.opts.tableCellStyles. * style editor.opts.tableStyles or editor.opts.tableCellStyles */ function applyStyle (val, obj, multiple_styles, styles) { if (obj.length > 0) { // Remove multiple styles. if (!multiple_styles) { var classes = Object.keys(styles); classes.splice(classes.indexOf(val), 1); obj.removeClass(classes.join(' ')); } obj.toggleClass(val); } } /* * Create a table map. */ function _tableMap ($table) { $table = $table || null; var map = []; if ($table == null && selectedCells().length > 0) { $table = selectedTable(); } if ($table) { $table.find('tr').not($table.find('table tr')).each (function (row, tr) { var $tr = $(tr); var c_index = 0; $tr.find('> th, > td').each (function (col, td) { var $td = $(td); var cspan = parseInt($td.attr('colspan'), 10) || 1; var rspan = parseInt($td.attr('rowspan'), 10) || 1; for (var i = row; i < row + rspan; i++) { for (var j = c_index; j < c_index + cspan; j++) { if (!map[i]) map[i] = []; if (!map[i][j]) { map[i][j] = td; } else { c_index++; } } } c_index += cspan; }) }); return map; } } /* * Get the i, j coordinates of a cell in the table map. * These are the coordinates where the cell starts. */ function _cellOrigin (td, map) { for (var i = 0; i < map.length; i++) { for (var j = 0; j < map[i].length; j++) { if (map[i][j] == td) { return { row: i, col: j }; } } } } /* * Get the i, j coordinates where a cell ends in the table map. */ function _cellEnds (origin_i, origin_j, map) { var max_i = origin_i + 1; var max_j = origin_j + 1; // Compute max_i while (max_i < map.length) { if (map[max_i][origin_j] != map[origin_i][origin_j]) { max_i--; break; } max_i++; } if (max_i == map.length) { max_i--; } // Compute max_j while (max_j < map[origin_i].length) { if (map[origin_i][max_j] != map[origin_i][origin_j]) { max_j--; break; } max_j++; } if (max_j == map[origin_i].length) { max_j--; } return { row: max_i, col: max_j }; } /* * Remove selection from cells. */ function _removeSelection () { var cells = editor.$el.find('.fr-selected-cell'); if (cells.length > 0) { // Remove selection. cells.each (function () { var $cell = $(this); $cell.removeClass('fr-selected-cell'); if ($cell.attr('class') === '') { $cell.removeAttr('class'); } }); } // Remove keyboard selection handlers. if (editor.el.querySelector('.fr-cell-fixed')) { editor.el.querySelector('.fr-cell-fixed').classList.remove('fr-cell-fixed'); editor.el.querySelector('.fr-cell-handler').classList.remove('fr-cell-handler'); } } /* * Clear selection to prevent Firefox cell selection. */ function _clearSelection () { // Timeout is needed when selecting cells using shift. setTimeout(function () { editor.selection.clear(); // Prevent text selection while selecting multiple cells. // Happens in Chrome. editor.$el.addClass('fr-no-selection'); // Cursor will not appear if we don't make blur. editor.$el.blur(); }, 0); } /* * Get current selected cells coordintates. */ function _currentSelection (map) { var cells = editor.$el.find('.fr-selected-cell'); if (cells.length > 0) { var min_i = map.length; var max_i = 0; var min_j = map[0].length; var max_j = 0; var i; for (i = 0; i < cells.length; i++) { var cellOrigin = _cellOrigin(cells[i], map); var cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map); min_i = Math.min(cellOrigin.row, min_i); max_i = Math.max(cellEnd.row, max_i); min_j = Math.min(cellOrigin.col, min_j); max_j = Math.max(cellEnd.col, max_j); } return { min_i: min_i, max_i: max_i, min_j: min_j, max_j: max_j }; } else { return null; } } /* * Minimum and maximum coordinates for the selection in the table map. */ function _selectionLimits (min_i, max_i, min_j, max_j, map) { var first_i = min_i; var last_i = max_i; var first_j = min_j; var last_j = max_j; var i; var j; var cellOrigin; var cellEnd; // Go through first and last columns. for (i = first_i; i <= last_i; i++) { // First column. if ((parseInt($(map[i][first_j]).attr('rowspan'), 10) || 1) > 1 || (parseInt($(map[i][first_j]).attr('colspan'), 10) || 1) > 1) { cellOrigin = _cellOrigin(map[i][first_j], map); cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map); first_i = Math.min(cellOrigin.row, first_i); last_i = Math.max(cellEnd.row, last_i); first_j = Math.min(cellOrigin.col, first_j); last_j = Math.max(cellEnd.col, last_j); } // Last column. if ((parseInt($(map[i][last_j]).attr('rowspan'), 10) || 1) > 1 || (parseInt($(map[i][last_j]).attr('colspan'), 10) || 1) > 1) { cellOrigin = _cellOrigin(map[i][last_j], map); cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map); first_i = Math.min(cellOrigin.row, first_i); last_i = Math.max(cellEnd.row, last_i); first_j = Math.min(cellOrigin.col, first_j); last_j = Math.max(cellEnd.col, last_j); } } // Go through first and last rows. for (j = first_j; j <= last_j; j++) { // First row. if ((parseInt($(map[first_i][j]).attr('rowspan'), 10) || 1) > 1 || (parseInt($(map[first_i][j]).attr('colspan'), 10) || 1) > 1) { cellOrigin = _cellOrigin(map[first_i][j], map); cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map); first_i = Math.min(cellOrigin.row, first_i); last_i = Math.max(cellEnd.row, last_i); first_j = Math.min(cellOrigin.col, first_j); last_j = Math.max(cellEnd.col, last_j); } // Last column. if ((parseInt($(map[last_i][j]).attr('rowspan'), 10) || 1) > 1 || (parseInt($(map[last_i][j]).attr('colspan'), 10) || 1) > 1) { cellOrigin = _cellOrigin(map[last_i][j], map); cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map); first_i = Math.min(cellOrigin.row, first_i); last_i = Math.max(cellEnd.row, last_i); first_j = Math.min(cellOrigin.col, first_j); last_j = Math.max(cellEnd.col, last_j); } } if (first_i == min_i && last_i == max_i && first_j == min_j && last_j == max_j) { return { min_i: min_i, max_i: max_i, min_j: min_j, max_j: max_j }; } else { return _selectionLimits(first_i, last_i, first_j, last_j, map); } } /* * Get the left and right offset position for the current selection. */ function _selectionOffset (map) { var selection = _currentSelection(map); // Top left cell. var $tl = $(map[selection.min_i][selection.min_j]); // Top right cell. var $tr = $(map[selection.min_i][selection.max_j]); // Bottom left cell. var $bl = $(map[selection.max_i][selection.min_j]); var left = $tl.offset().left var right = $tr.offset().left + $tr.outerWidth(); var top = $tl.offset().top; var bottom = $bl.offset().top + $bl.outerHeight(); return { left: left, right: right, top: top, bottom: bottom }; } /* * Select table cells. * firstCell is either the top left corner or the fr-cell-fixed corner of the selection. * lastCell is either the bottom right corner ot the fr-cell-handler of the selection. */ function _selectCells (firstCell, lastCell) { // If the first and last cells are the same then just select it. if ($(firstCell).is(lastCell)) { // Remove previous selection. _removeSelection(); // Enable editor toolbar. editor.edit.on(); $(firstCell).addClass('fr-selected-cell'); // Select multiple cells. } else { // Prevent Firefox cell selection. _clearSelection(); // Turn editor toolbar off. editor.edit.off(); // Create a table map. var map = _tableMap(); // Get first and last cell's i and j map coordinates. var firstCellOrigin = _cellOrigin(firstCell, map); var lastCellOrigin = _cellOrigin(lastCell, map); // Some cells between these coordinates might have colspan or rowspan. // The selected area exceeds first and last cells' coordinates. var limits = _selectionLimits(Math.min(firstCellOrigin.row, lastCellOrigin.row), Math.max(firstCellOrigin.row, lastCellOrigin.row), Math.min(firstCellOrigin.col, lastCellOrigin.col), Math.max(firstCellOrigin.col, lastCellOrigin.col), map); // Remove previous selection. _removeSelection(); // We always need to set the selection handler classes as user may use keyboard to select at anytime. firstCell.classList.add('fr-cell-fixed'); lastCell.classList.add('fr-cell-handler'); // Select all cells between the first and last cell. for (var i = limits.min_i; i <= limits.max_i; i++) { for (var j = limits.min_j; j <= limits.max_j; j++) { $(map[i][j]).addClass('fr-selected-cell'); } } } } /* * Get the cell under the mouse cursor. */ function _getCellUnder (e) { var cell = null; var $target = $(e.target); if (e.target.tagName == 'TD' || e.target.tagName == 'TH') { cell = e.target; } else if ($target.closest('td').length > 0) { cell = $target.closest('td').get(0); } else if ($target.closest('th').length > 0) { cell = $target.closest('th').get(0); } // Cell should reside inside editor. if (editor.$el.find(cell).length === 0) return null; return cell; } /* * Stop table cell editing and allow text editing. */ function _stopEdit () { // Clear previous selection. _removeSelection(); // Hide table edit popup. editor.popups.hide('table.edit'); } /* * Mark that mouse is down. */ function _mouseDown (e) { var cell = _getCellUnder(e); // Stop table editing if user clicks outside the table. if (selectedCells().length > 0 && !cell) { _stopEdit(); } // Only do mouseDown if the editor is not disabled by user. if (!editor.edit.isDisabled() || editor.popups.isVisible('table.edit')) { // On left click. if (e.which == 1 && !(e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) { mouseDownFlag = true; // User clicked on a table cell. if (cell) { // We always have to clear previous selection except when using shift key to select multiple cells. if (selectedCells().length > 0 && !e.shiftKey) { _stopEdit(); } e.stopPropagation(); editor.events.trigger('image.hideResizer'); editor.events.trigger('video.hideResizer'); // Keep record of left mouse click being down mouseDownCellFlag = true; var tag_name = cell.tagName.toLowerCase(); // Select multiple cells using Shift key if (e.shiftKey && editor.$el.find(tag_name + '.fr-selected-cell').length > 0) { // Cells must be in the same table. if ($(editor.$el.find(tag_name + '.fr-selected-cell').closest('table')).is($(cell).closest('table'))) { // Select cells between. _selectCells(mouseDownCell, cell); // Do nothing if cells are not in the same table. } else { // Prevent Firefox selection. _clearSelection(); } } else { // Prevent Firefox selection for ctrl / cmd key. // https://github.com/froala/wysiwyg-editor/issues/1323: // - we have more than one cell selected or // - selection is starting in another cell than the one we clicked on. if ((editor.keys.ctrlKey(e) || e.shiftKey) && (selectedCells().length > 1 || ($(cell).find(editor.selection.element()).length === 0 && !$(cell).is(editor.selection.element())))) { _clearSelection(); } // Save cell where mouse has been clicked mouseDownCell = cell; // Select cell. _selectCells(mouseDownCell, mouseDownCell); } } } // On right click stop table editing. else if ((e.which == 3 || (e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) && cell) { _stopEdit(); } } } /* * Notify that mouse is no longer pressed. */ function _mouseUp (e) { // User clicked somewhere else in the editor (except the toolbar). // We need this because mouse down is not triggered outside the editor. if (!mouseDownCellFlag && !editor.$tb.is(e.target) && !editor.$tb.is($(e.target).closest(editor.$tb.get(0)))) { if (selectedCells().length > 0) { editor.toolbar.enable(); } _removeSelection(); } // On left click. if (e.which == 1 && !(e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) { mouseDownFlag = false; // Mouse down was in a table cell. if (mouseDownCellFlag) { // Left click is no longer pressed. mouseDownCellFlag = false; var cell = _getCellUnder(e); // If we have one selected cell and mouse is lifted somewhere else. if (!cell && selectedCells().length == 1) { // We have a text selection and not cell selection. _removeSelection(); } // If there are selected cells then show table edit popup. else if (selectedCells().length > 0) { if (editor.selection.isCollapsed()) { _showEditPopup(); } // No text selection. else { _removeSelection(); } } } // Resizing stops. if (resizingFlag) { resizingFlag = false; $resizer.removeClass('fr-moving'); // Allow text selection. editor.$el.removeClass('fr-no-selection'); editor.edit.on(); // Set release Y coordinate. var left = parseFloat($resizer.css('left')) + editor.opts.tableResizerOffset; if (editor.opts.iframe) { left -= editor.$iframe.offset().left; } $resizer.data('release-position', left); // Clear resizing limits. $resizer.removeData('max-left'); $resizer.removeData('max-right'); // Resize. _resize(e); // Hide resizer. _hideResizer(); } } } /* * User drags mouse over multiple cells to select them. */ function _mouseEnter (e) { if (mouseDownCellFlag === true) { var $cell = $(e.currentTarget); // Cells should be in the same table. if ($cell.closest('table').is(selectedTable())) { // Don't select both ths and tds. if (e.currentTarget.tagName == 'TD' && editor.$el.find('th.fr-selected-cell').length === 0) { // Select cells between. _selectCells(mouseDownCell, e.currentTarget); return; } else if (e.currentTarget.tagName == 'TH' && editor.$el.find('td.fr-selected-cell').length === 0) { // Select cells between. _selectCells(mouseDownCell, e.currentTarget); return; } } // Prevent firefox selection. _clearSelection(); } } /* * Move cursor in a nested table. */ function _moveInNestedTable (cell, direction) { var table = cell; // Get parent table (editor might be initialized inside cell). while (table && table.tagName != 'TABLE' && table.parentNode != editor.el) { table = table.parentNode; } if (table && table.tagName == 'TABLE') { var new_map = _tableMap($(table)); // Move up in the parent table. if (direction == 'up') _moveUp(_cellOrigin(cell, new_map), table, new_map); else if (direction == 'down') _moveDown(_cellOrigin(cell, new_map), table, new_map); } } /* * Move cursor up or down outside table. */ function _moveWithArrows (origin, table, map, direction) { var up = table; var sibling; // Look up in DOM for the previous or next element. while (up != editor.el) { // Nested table. if (up.tagName == 'TD' || up.tagName == 'TH') { break; } // The table has a sibling element. if (direction == 'up') sibling = up.previousElementSibling; else if (direction == 'down') sibling = up.nextElementSibling; if (sibling) { break; } // Table might be in a block tag. up = up.parentNode; } // We have another table (nested). if (up.tagName == 'TD' || up.tagName == 'TH') { _moveInNestedTable(up, direction); } // Table has a sibling. else if (sibling) { if (direction == 'up') editor.selection.setAtEnd(sibling); if (direction == 'down') editor.selection.setAtStart(sibling); } } /* * Move cursor up while in table cell. */ function _moveUp (origin, table, map) { // Not the first line. if (origin.row > 0) { editor.selection.setAtEnd(map[origin.row - 1][origin.col]) } // First line. else { _moveWithArrows(origin, table, map, 'up'); } } /* * Move cursor down while in table cell. */ function _moveDown (origin, table, map) { // Cell might have rowspan. var row = parseInt(map[origin.row][origin.col].getAttribute('rowspan'), 10) || 1; // Not the last line. if (origin.row < map.length - row) { editor.selection.setAtStart(map[origin.row + row][origin.col]); } // Last line. else { _moveWithArrows(origin, table, map, 'down'); } } /* * Using the arrow keys to move the cursor through the table will not select cells. */ function _navigateWithArrows (e) { var key_code = e.which; // Get current selection. var sel = editor.selection.blocks(); if (sel.length) { sel = sel[0]; // Selection should be in a table cell. if (sel.tagName == 'TD' || sel.tagName == 'TH') { var table = sel; // Get parent table (editor might be initialized inside cell). while (table && table.tagName != 'TABLE' && table.parentNode != editor.el) { table = table.parentNode; } if (table && table.tagName == 'TABLE') { if ($.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code) { if (selectedCells().length > 0) { _stopEdit(); } // Up and down in Webkit. if (editor.browser.webkit && ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code)) { var node = editor.selection.ranges(0).startContainer; if (node.nodeType == Node.TEXT_NODE && (($.FE.KEYCODE.ARROW_UP == key_code && node.previousSibling) || ($.FE.KEYCODE.ARROW_DOWN == key_code && node.nextSibling))) { return; } e.preventDefault(); e.stopPropagation(); // Table map. var map = _tableMap($(table)); // Current cell map coordinates. var origin = _cellOrigin(sel, map); // Arrow up if ($.FE.KEYCODE.ARROW_UP == key_code) { _moveUp(origin, table, map); } // Arrow down else if ($.FE.KEYCODE.ARROW_DOWN == key_code) { _moveDown(origin, table, map); } // Update cursor position. editor.selection.restore(); return false; } } } } } } /* * Initilize table resizer. */ function _initResizer () { // Append resizer HTML to editor wrapper. if (!editor.shared.$table_resizer) editor.shared.$table_resizer = $('<div class="fr-table-resizer"><div></div></div>'); $resizer = editor.shared.$table_resizer; // Resize table. Mousedown. editor.events.$on($resizer, 'mousedown', function (e) { if (!editor.core.sameInstance($resizer)) return true; // Stop table editing. if (selectedCells().length > 0) { _stopEdit(); } // Resize table only using left click. if (e.which == 1) { // Save selection so that we can put cursor back at the end. editor.selection.save(); resizingFlag = true; $resizer.addClass('fr-moving'); // Prevent text selection while dragging the table resizer. _clearSelection(); // Turn editor toolbar off while resizing. editor.edit.off(); // Show resizer. $resizer.find('div').css('opacity', 1); // Prevent selecting text when doing resize. return false; } }); // Mousemove on table resizer. editor.events.$on($resizer, 'mousemove', function (e) { if (!editor.core.sameInstance($resizer)) return true; if (resizingFlag) { if (editor.opts.iframe) { e.pageX -= editor.$iframe.offset().left; } _mouseMove(e); } }) // Editor destroy. editor.events.on('shared.destroy', function () { $resizer.html('').removeData().remove(); $resizer = null; }, true); editor.events.on('destroy', function () { editor.$el.find('.fr-selected-cell').removeClass('fr-selected-cell'); $resizer.hide().appendTo($('body')); }, true); } /* * Also clears top and left values, so it doesn't interfer with the insert helper. */ function _hideResizer () { if ($resizer) { $resizer.find('div').css('opacity', 0); $resizer.css('top', 0); $resizer.css('left', 0); $resizer.css('height', 0); $resizer.find('div').css('height', 0); $resizer.hide(); } } /** * Hide the insert helper. */ function _hideInsertHelper () { if ($insert_helper) $insert_helper.removeClass('fr-visible').css('left', '-9999px'); } /* * Place the table resizer between the columns where the mouse is. */ function _placeResizer (e, tag_under) { var $tag_under = $(tag_under); var $table = $tag_under.closest('table'); var $table_parent = $table.parent(); // We might have another tag inside the table cell. if (tag_under && (tag_under.tagName != 'TD' && tag_under.tagName != 'TH')) { if ($tag_under.closest('td').length > 0) { tag_under = $tag_under.closest('td'); } else if ($tag_under.closest('th').length > 0) { tag_under = $tag_under.closest('th'); } } // The tag should be a table cell (TD or TH). if (tag_under && (tag_under.tagName == 'TD' || tag_under.tagName == 'TH')) { $tag_under = $(tag_under); // https://github.com/froala/wysiwyg-editor/issues/786. if (editor.$el.find($tag_under).length === 0) return false; // Tag's left and right coordinate. var tag_left = $tag_under.offset().left - 1; var tag_right = tag_left + $tag_under.outerWidth(); // Only if the mouse is close enough to the left or right edges. if (Math.abs(e.pageX - tag_left) <= editor.opts.tableResizerOffset || Math.abs(tag_right - e.pageX) <= editor.opts.tableResizerOffset) { // Create a table map. var map = _tableMap($table); var tag_origin = _cellOrigin(tag_under, map); var tag_end = _cellEnds(tag_origin.row, tag_origin.col, map); // The column numbers from the map that have to be resized. var first; var second; // Table resizer position and height. var resizer_top = $table.offset().top; var resizer_height = $table.outerHeight() - 1; var resizer_left; // The left and right limits between which the resizer can be moved. var max_left; var max_right; if (editor.opts.direction != 'rtl') { // Mouse is near the cells's left margin. if (e.pageX - tag_left <= editor.opts.tableResizerOffset) { // Table resizer's left position. resizer_left = tag_left; // Resize cells. if (tag_origin.col > 0) { // Left limit. max_left = tag_left - _columnWidth(tag_origin.col - 1, map) + editor.opts.tableResizingLimit; // Right limit. max_right = tag_left + _columnWidth(tag_origin.col, map) - editor.opts.tableResizingLimit; // Columns to resize. first = tag_origin.col - 1; second = tag_origin.col; } // Resize table. else { // Columns to resize. first = null; second = 0; // Resizer limits. max_left = $table.offset().left - 1 - parseInt($table.css('margin-left'), 10); max_right = $table.offset().left - 1 + $table.width() - map[0].length * editor.opts.tableResizingLimit; } } // Mouse is near the cell's right margin. else if (tag_right - e.pageX <= editor.opts.tableResizerOffset) { // Table resizer's left possition. resizer_left = tag_right; // Check for next td. if (tag_end.col < map[tag_end.row].length && map[tag_end.row][tag_end.col + 1]) { // Left limit. max_left = tag_right - _columnWidth(tag_end.col, map) + editor.opts.tableResizingLimit; // Right limit. max_right = tag_right + _columnWidth(tag_end.col + 1, map) - editor.opts.tableResizingLimit; // Columns to resize. first = tag_end.col; second = tag_end.col + 1; } // Resize table. else { // Columns to resize. first = tag_end.col; second = null; // Resizer limits. max_left = $table.offset().left - 1 + map[0].length * editor.opts.tableResizingLimit; max_right = $table_parent.offset().left - 1 + $table_parent.width() + parseFloat($table_parent.css('padding-left')); } } } // RTL else { // Mouse is near the cell's right margin. if (tag_right - e.pageX <= editor.opts.tableResizerOffset) { // Table resizer's left position. resizer_left = tag_right; // Resize cells. if (tag_origin.col > 0) { // Left limit. max_left = tag_right - _columnWidth(tag_origin.col, map) + editor.opts.tableResizingLimit; // Right limit. max_right = tag_right + _columnWidth(tag_origin.col - 1, map) - editor.opts.tableResizingLimit; // Columns to resize. first = tag_origin.col; second = tag_origin.col - 1; } // Resize table. else { first = null; second = 0; // Resizer limits. max_left = $table.offset().left + map[0].length * editor.opts.tableResizingLimit; max_right = $table_parent.offset().left - 1 + $table_parent.width() + parseFloat($table_parent.css('padding-left')); } } // Mouse is near the cell's left margin. else if (e.pageX - tag_left <= editor.opts.tableResizerOffset) { // Table resizer's left position. resizer_left = tag_left; // Check for next td. if (tag_end.col < map[tag_end.row].length && map[tag_end.row][tag_end.col + 1]) { // Left limit. max_left = tag_left - _columnWidth(tag_end.col + 1, map) + editor.opts.tableResizingLimit; // Right limit. max_right = tag_left + _columnWidth(tag_end.col, map) - editor.opts.tableResizingLimit; // Columns to resize. first = tag_end.col + 1; second = tag_end.col; } // Resize table. else { // Columns to resize. first = tag_end.col; second = null; // Resizer limits. max_left = $table_parent.offset().left + parseFloat($table_parent.css('padding-left')); max_right = $table.offset().left - 1 + $table.width() - map[0].length * editor.opts.tableResizingLimit; } } } if (!$resizer) _initResizer(); // Save table. $resizer.data('table', $table); // Save columns to resize. $resizer.data('first', first); $resizer.data('second', second); $resizer.data('instance', editor); editor.$wp.append($resizer); var left = resizer_left - editor.win.pageXOffset - editor.opts.tableResizerOffset; var top = resizer_top - editor.win.pageYOffset; if (editor.opts.iframe) { left += editor.$iframe.offset().left - editor.helpers.scrollLeft(); top += editor.$iframe.offset().top - editor.helpers.scrollTop(); max_left += editor.$iframe.offset().left; max_right += editor.$iframe.offset().left; } // Set resizing limits. $resizer.data('max-left', max_left); $resizer.data('max-right', max_right); // Initial position of the resizer $resizer.data('origin', resizer_left - editor.win.pageXOffset); // Set table resizer's top, left and height. $resizer.css('top', top); $resizer.css('left', left); $resizer.css('height', resizer_height); $resizer.find('div').css('height', resizer_height); // Set padding according to tableResizerOffset. $resizer.css('padding-left', editor.opts.tableResizerOffset); $resizer.css('padding-right', editor.opts.tableResizerOffset); // Show table resizer. $resizer.show(); } // Hide resizer when the mouse moves away from the cell's border. else { if (editor.core.sameInstance($resizer)) _hideResizer(); } } // Hide resizer if mouse is no longer over it. else if ($resizer && $tag_under.get(0) != $resizer.get(0) && $tag_under.parent().get(0) != $resizer.get(0)) { if (editor.core.sameInstance($resizer)) _hideResizer(); } } /* * Show the insert column helper button. */ function _showInsertColHelper (e, table) { if (editor.$box.find('.fr-line-breaker').is(':visible')) return false; // Insert Helper. if (!$insert_helper) _initInsertHelper(); editor.$box.append($insert_helper); $insert_helper.data('instance', editor); var $table = $(table); var $row = $table.find('tr:first'); var mouseX = e.pageX; var left = 0; var top = 0; if (editor.opts.iframe) { left += editor.$iframe.offset().left - editor.helpers.scrollLeft(); top += editor.$iframe.offset().top - editor.helpers.scrollTop(); } // Check where the column should be inserted. var btn_width; $row.find('th, td').each (function () { var $td = $(this); // Insert before this td. if ($td.offset().left <= mouseX && mouseX < $td.offset().left + $td.outerWidth() / 2) { btn_width = parseInt($insert_helper.find('a').css('width'), 10); $insert_helper.css('top', top + $td.offset().top - editor.win.pageYOffset - btn_width - 5); $insert_helper.css('left', left + $td.offset().left - editor.win.pageXOffset - btn_width / 2); $insert_helper.data('selected-cell', $td); $insert_helper.data('position', 'before'); $insert_helper.addClass('fr-visible'); return false; // Insert after this td. } else if ($td.offset().left + $td.outerWidth() / 2 <= mouseX && mouseX < $td.offset().left + $td.outerWidth()) { btn_width = parseInt($insert_helper.find('a').css('width'), 10); $insert_helper.css('top', top + $td.offset().top - editor.win.pageYOffset - btn_width - 5); $insert_helper.css('left', left + $td.offset().left + $td.outerWidth() - editor.win.pageXOffset - btn_width / 2); $insert_helper.data('selected-cell', $td); $insert_helper.data('position', 'after'); $insert_helper.addClass('fr-visible'); return false; } }); } /* * Show the insert row helper button. */ function _showInsertRowHelper (e, table) { if (editor.$box.find('.fr-line-breaker').is(':visible')) return false; if (!$insert_helper) _initInsertHelper(); editor.$box.append($insert_helper); $insert_helper.data('instance', editor); var $table = $(table); var mouseY = e.pageY; var left = 0; var top = 0; if (editor.opts.iframe) { left += editor.$iframe.offset().left - editor.helpers.scrollLeft(); top += editor.$iframe.offset().top - editor.helpers.scrollTop(); } // Check where the row should be inserted. var btn_width; $table.find('tr').each (function () { var $tr = $(this); // Insert above this tr. if ($tr.offset().top <= mouseY && mouseY < $tr.offset().top + $tr.outerHeight() / 2) { btn_width = parseInt($insert_helper.find('a').css('width'), 10); $insert_helper.css('top', top + $tr.offset().top - editor.win.pageYOffset - btn_width / 2); $insert_helper.css('left', left + $tr.offset().left - editor.win.pageXOffset - btn_width - 5); $insert_helper.data('selected-cell', $tr.find('td:first')); $insert_helper.data('position', 'above'); $insert_helper.addClass('fr-visible'); return false; // Insert below this tr. } else if ($tr.offset().top + $tr.outerHeight() / 2 <= mouseY && mouseY < $tr.offset().top + $tr.outerHeight()) { btn_width = parseInt($insert_helper.find('a').css('width'), 10); $insert_helper.css('top', top + $tr.offset().top + $tr.outerHeight() - editor.win.pageYOffset - btn_width / 2); $insert_helper.css('left', left + $tr.offset().left - editor.win.pageXOffset - btn_width - 5); $insert_helper.data('selected-cell', $tr.find('td:first')); $insert_helper.data('position', 'below'); $insert_helper.addClass('fr-visible'); return false; } }); } /* * Check if should show the insert column / row helper button. */ function _insertHelper (e, tag_under) { // Don't show the insert helper if there are table cells selected. if (selectedCells().length === 0) { var i; var tag_below; var tag_right; // Tag is the editor element or body (inline toolbar). Look for closest tag bellow and at the right. if (tag_under && (tag_under.tagName == 'HTML' || tag_under.tagName == 'BODY' || editor.node.isElement(tag_under))) { // Look 1px down until a table tag is found or the insert helper offset is reached. for (i = 1; i <= editor.opts.tableInsertHelperOffset; i++) { // Look for tag below. tag_below = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset, e.pageY - editor.win.pageYOffset + i); // We're on tooltip. if ($(tag_below).hasClass('fr-tooltip')) return true; // We found a tag bellow. if (tag_below && ((tag_below.tagName == 'TH' || tag_below.tagName == 'TD' || tag_below.tagName == 'TABLE') && ($(tag_below).parents('.fr-wrapper').length || editor.opts.iframe))) { // Show the insert column helper button. _showInsertColHelper (e, $(tag_below).closest('table')); return true; } // Look for tag at the right. tag_right = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset + i, e.pageY - editor.win.pageYOffset); // We're on tooltip. if ($(tag_right).hasClass('fr-tooltip')) return true; // We found a tag at the right. if (tag_right && ((tag_right.tagName == 'TH' || tag_right.tagName == 'TD' || tag_right.tagName == 'TABLE') && ($(tag_right).parents('.fr-wrapper').length || editor.opts.iframe))) { // Show the insert row helper button. _showInsertRowHelper (e, $(tag_right).closest('table')); return true; } } } // Hide insert helper. if (editor.core.sameInstance($insert_helper)) { _hideInsertHelper(); } } } /* * Check tag under the mouse on mouse move. */ function _tagUnder (e) { mouseMoveTimer = null; // The tag under the mouse cursor. var tag_under = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset, e.pageY - editor.win.pageYOffset); // Place table resizer if necessary. if (editor.opts.tableResizer && (!editor.popups.areVisible() || (editor.popups.areVisible() && editor.popups.isVisible('table.edit')))) { _placeResizer(e, tag_under); } // Show the insert column / row helper button. if (editor.opts.tableInsertHelper && !editor.popups.areVisible() && !(editor.$tb.hasClass('fr-inline') && editor.$tb.is(':visible'))) { _insertHelper(e, tag_under); } } /* * Repositon the resizer if the user scrolls while resizing. */ function _repositionResizer () { if (resizingFlag) { var $table = $resizer.data('table'); var top = $table.offset().top - editor.win.pageYOffset; if (editor.opts.iframe) { top += editor.$iframe.offset().top - editor.helpers.scrollTop(); } $resizer.css('top', top); } } /* * Resize table method. */ function _resize () { // Resizer initial position. var initial_positon = $resizer.data('origin'); // Resizer release position. var release_position = $resizer.data('release-position'); // Do resize only if the resizer's position has changed. if (initial_positon !== release_position) { // Columns that have to be resized. var first = $resizer.data('first'); var second = $resizer.data('second'); var $table = $resizer.data('table'); var table_width = $table.outerWidth(); // Resize columns and not the table. if (first !== null && second !== null) { // Create a table map. var map = _tableMap($table); // Got through all cells on these columns and get their initial width. var first_widths = []; var first_percentages = []; var second_widths = []; var second_percentages = []; var i; var $first_cell; var $second_cell; // We must do this before updating widths. for (i = 0; i < map.length; i++) { $first_cell = $(map[i][first]); $second_cell = $(map[i][second]); // Widths in px. first_widths[i] = $first_cell.outerWidth(); second_widths[i] = $second_cell.outerWidth(); // Widths in percentages. first_percentages[i] = first_widths[i] / table_width * 100; second_percentages[i] = second_widths[i] / table_width * 100; } // Got through all cells on these columns and update their widths. for (i = 0; i < map.length; i++) { $first_cell = $(map[i][first]); $second_cell = $(map[i][second]); // New percentage for the first cell. var first_cell_percentage = (first_percentages[i] * (first_widths[i] + release_position - initial_positon) / first_widths[i]).toFixed(4); $first_cell.css('width', first_cell_percentage + '%'); $second_cell.css('width', (first_percentages[i] + second_percentages[i] - first_cell_percentage).toFixed(4) + '%'); } } // Resize the table. else { var $table_parent = $table.parent(); var table_percentage = table_width / $table_parent.width() * 100; var left_margin = (parseInt($table.css('margin-left'), 10) || 0) / $table_parent.width() * 100; var right_margin = (parseInt($table.css('margin-right'), 10) || 0) / $table_parent.width() * 100; var width; // Right border RTL or LTR. if ((editor.opts.direction == 'rtl' && second === 0) || (editor.opts.direction != 'rtl' && second !== 0)) { width = (table_width + release_position - initial_positon) / table_width * table_percentage; $table.css('margin-right', 'calc(100% - ' + Math.round(width).toFixed(4) + '% - ' + Math.round(left_margin).toFixed(4) + '%)'); } // Left border RTL or LTR. else if ((editor.opts.direction == 'rtl' && second !== 0) || (editor.opts.direction != 'rtl' && second === 0)) { width = (table_width - release_position + initial_positon) / table_width * table_percentage; $table.css('margin-left', 'calc(100% - ' + Math.round(width).toFixed(4) + '% - ' + Math.round(right_margin).toFixed(4) + '%)'); } // Update table width. $table.css('width', Math.round(width).toFixed(4) + '%'); } editor.selection.restore(); editor.undo.saveStep(); } // Clear resizer data. $resizer.removeData('origin'); $resizer.removeData('release-position'); $resizer.removeData('first'); $resizer.removeData('second'); $resizer.removeData('table'); } /* * Get the width of the column. (columns may have colspan) */ function _columnWidth (col, map) { var i; var width = $(map[0][col]).outerWidth(); for (i = 1; i < map.length; i++) { width = Math.min(width, $(map[i][col]).outerWidth()); } return width; } /* * Get the width of the columns between specified indexes. */ function _columnsWidth(col1, col2, map) { var i; var width = 0; // Sum all columns widths. for (i = col1; i <= col2; i++) { width += _columnWidth(i, map); } return width; } /* * Set mouse timer to improve performance. */ function _mouseMove (e) { // Prevent selecting text when we have cells selected. if (selectedCells().length > 1 && mouseDownFlag) { _clearSelection(); } // Reset or set timer. if (mouseDownFlag === false && mouseDownCellFlag === false && resizingFlag === false) { if (mouseMoveTimer) { clearTimeout(mouseMoveTimer); } // Only resize table if the editor is not disabled by user. if (!editor.edit.isDisabled() || editor.popups.isVisible('table.edit')) { // Check tag under in order to place the table resizer or insert helper button. mouseMoveTimer = setTimeout(_tagUnder, 30, e); } // Move table resizer. } else if (resizingFlag) { // Cursor position. var pos = e.pageX - editor.win.pageXOffset; if (editor.opts.iframe) { pos += editor.$iframe.offset().left; } // Left and right limits. var left_limit = $resizer.data('max-left'); var right_limit = $resizer.data('max-right'); // Cursor is between the left and right limits. if (pos >= left_limit && pos <= right_limit) { $resizer.css('left', pos - editor.opts.tableResizerOffset); // Cursor has exceeded the left limit. Don't update if it already has the correct value. } else if (pos < left_limit && parseFloat($resizer.css('left'), 10) > left_limit - editor.opts.tableResizerOffset) { $resizer.css('left', left_limit - editor.opts.tableResizerOffset); // Cursor has exceeded the right limit. Don't update if it already has the correct value. } else if (pos > right_limit && parseFloat($resizer.css('left'), 10) < right_limit - editor.opts.tableResizerOffset) { $resizer.css('left', right_limit - editor.opts.tableResizerOffset); } } else if (mouseDownFlag) { _hideInsertHelper(); } } /* * Place selection markers in a table cell. */ function _addMarkersInCell ($cell) { if (editor.node.isEmpty($cell.get(0))) { $cell.prepend($.FE.MARKERS); } else { $cell.prepend($.FE.START_MARKER).append($.FE.END_MARKER); } } /* * Use TAB key to navigate through cells. */ function _useTab (e) { var key_code = e.which; if (key_code == $.FE.KEYCODE.TAB) { // Get starting cell. var $cell; if (selectedCells().length > 0) { $cell = editor.$el.find('.fr-selected-cell:last') } else { var cell = editor.selection.element(); if (cell.tagName == 'TD' || cell.tagName == 'TH') { $cell = $(cell); } else if ($(cell).closest('td').length > 0) { $cell = $(cell).closest('td'); } else if ($(cell).closest('th').length > 0) { $cell = $(cell).closest('th'); } } if ($cell) { e.preventDefault(); _stopEdit(); // Go backwards. if (e.shiftKey) { // Go to previous cell. if ($cell.prev().length > 0) { _addMarkersInCell($cell.prev()); } // Go to prev row, last cell. else if ($cell.closest('tr').length > 0 && $cell.closest('tr').prev().length > 0) { _addMarkersInCell($cell.closest('tr').prev().find('td:last')); } // Go in THEAD, last cell. else if ($cell.closest('tbody').length > 0 && $cell.closest('table').find('thead tr').length > 0) { _addMarkersInCell($cell.closest('table').find('thead tr th:last')); } } // Go forward. else { // Go to next cell. if ($cell.next().length > 0) { _addMarkersInCell($cell.next()); } // Go to next row, first cell. else if ($cell.closest('tr').length > 0 && $cell.closest('tr').next().length > 0) { _addMarkersInCell($cell.closest('tr').next().find('td:first')); } // Cursor is in THEAD. Go to next row in TBODY else if ($cell.closest('thead').length > 0 && $cell.closest('table').find('tbody tr').length > 0) { _addMarkersInCell($cell.closest('table').find('tbody tr td:first')); } // Add new row. else { $cell.addClass('fr-selected-cell'); insertRow('below'); _removeSelection(); _addMarkersInCell($cell.closest('tr').next().find('td:first')); } } // Update cursor position. editor.selection.restore(); // Prevent event propagation. return false; } } } /* * Initilize insert helper. */ function _initInsertHelper () { // Append insert helper HTML to editor wrapper. if (!editor.shared.$ti_helper) { editor.shared.$ti_helper = $('<div class="fr-insert-helper"><a class="fr-floating-btn" role="button" tabIndex="-1" title="' + editor.language.translate('Insert') + '"><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M22,16.75 L16.75,16.75 L16.75,22 L15.25,22.000 L15.25,16.75 L10,16.75 L10,15.25 L15.25,15.25 L15.25,10 L16.75,10 L16.75,15.25 L22,15.25 L22,16.75 Z"/></svg></a></div>'); // Click on insert helper. editor.events.bindClick(editor.shared.$ti_helper, 'a', function () { var $td = $insert_helper.data('selected-cell'); var position = $insert_helper.data('position'); var inst = $insert_helper.data('instance') || editor; if (position == 'before') { $td.addClass('fr-selected-cell'); inst.table.insertColumn(position); $td.removeClass('fr-selected-cell'); } else if (position == 'after') { $td.addClass('fr-selected-cell'); inst.table.insertColumn(position); $td.removeClass('fr-selected-cell'); } else if (position == 'above') { $td.addClass('fr-selected-cell'); inst.table.insertRow(position); $td.removeClass('fr-selected-cell'); } else if (position == 'below') { $td.addClass('fr-selected-cell'); inst.table.insertRow(position); $td.removeClass('fr-selected-cell'); } // Hide the insert helper so it will reposition. _hideInsertHelper(); }); // Editor destroy. editor.events.on('shared.destroy', function () { editor.shared.$ti_helper.html('').removeData().remove(); editor.shared.$ti_helper = null; }, true); // Prevent the insert helper hide when mouse is over it. editor.events.$on(editor.shared.$ti_helper, 'mousemove', function (e) { e.stopPropagation(); }, true); // Hide the insert helper if the page is scrolled. editor.events.$on($(editor.o_win), 'scroll', function () { _hideInsertHelper(); }, true); editor.events.$on(editor.$wp, 'scroll', function () { _hideInsertHelper(); }, true); } $insert_helper = editor.shared.$ti_helper; editor.events.on('destroy', function () { $insert_helper = null; }); // Table insert helper tooltip. editor.tooltip.bind(editor.$box, '.fr-insert-helper > a.fr-floating-btn'); } /** * Destroy */ function _destroy () { mouseDownCell = null; clearTimeout(mouseMoveTimer); } /* * Go back to the table edit popup. */ function back () { if (selectedCells().length > 0) { _showEditPopup(); } else { editor.popups.hide('table.insert'); editor.toolbar.showInline(); } } /** * Return selected cells. */ function selectedCells () { return editor.el.querySelectorAll('.fr-selected-cell'); } /** * Return selected table. */ function selectedTable () { var cells = selectedCells(); if (cells.length) { var cell = cells[0]; while (cell && cell.tagName != 'TABLE' && cell.parentNode != editor.el) { cell = cell.parentNode; } if (cell && cell.tagName == 'TABLE') return $(cell); return $([]); } return $([]); } /** * Select table cell with alt + space. */ function _selectCellWithKeyboard (e) { // Alt+space was hit. Try to select cell. if (e.altKey && e.which == $.FE.KEYCODE.SPACE) { var cell; var el = editor.selection.element(); // Get cell where cursor is. if (el.tagName == 'TD' || el.tagName == 'TH') { cell = el; } else if ($(el).closest('td').length > 0) { cell = $(el).closest('td').get(0); } else if ($(el).closest('th').length > 0) { cell = $(el).closest('th').get(0); } // Select this cell. if (cell) { e.preventDefault(); _selectCells(cell, cell); _showEditPopup(); return false; } } } /** * Select table cells using arrows. */ function _selectCellsWithKeyboard (e) { var selection = selectedCells(); // There are some selected cells. if (selection.length > 0) { var map = _tableMap(); var key_code = e.which; var fixedCell; var handlerCell; // Only one cell is selected. if (selection.length == 1) { fixedCell = selection[0]; handlerCell = fixedCell; } else { fixedCell = editor.el.querySelector('.fr-cell-fixed'); handlerCell = editor.el.querySelector('.fr-cell-handler'); } var handlerOrigin = _cellOrigin(handlerCell, map); // Select column at the right. if ($.FE.KEYCODE.ARROW_RIGHT == key_code) { if (handlerOrigin.col < map[0].length - 1) { _selectCells(fixedCell, map[handlerOrigin.row][handlerOrigin.col + 1]); return false; } } // Select row below. else if ($.FE.KEYCODE.ARROW_DOWN == key_code) { if (handlerOrigin.row < map.length - 1) { _selectCells(fixedCell, map[handlerOrigin.row + 1][handlerOrigin.col]); return false; } } // Select column at the left. else if ($.FE.KEYCODE.ARROW_LEFT == key_code) { if (handlerOrigin.col > 0) { _selectCells(fixedCell, map[handlerOrigin.row][handlerOrigin.col - 1]); return false; } } // Select row above. else if ($.FE.KEYCODE.ARROW_UP == key_code) { if (handlerOrigin.row > 0) { _selectCells(fixedCell, map[handlerOrigin.row - 1][handlerOrigin.col]); return false; } } } } /* * Init table. */ function _init () { if (!editor.$wp) return false; // Do cell selection only on desktops (no touch devices) if (!editor.helpers.isMobile()) { // Remember if mouse is clicked. mouseDownFlag = false; mouseDownCellFlag = false; resizingFlag = false; // Mouse is down in a table cell. editor.events.$on(editor.$el, 'mousedown', _mouseDown); // Deselect table cells when user clicks on an image. editor.popups.onShow('image.edit', function () { _removeSelection(); mouseDownFlag = false; mouseDownCellFlag = false; }); // Deselect table cells when user clicks on a link. editor.popups.onShow('link.edit', function () { _removeSelection(); mouseDownFlag = false; mouseDownCellFlag = false; }); // Deselect table cells when a command is run. editor.events.on('commands.mousedown', function ($btn) { if ($btn.parents('.fr-toolbar').length > 0) { _removeSelection(); } }); // Mouse enter's a table cell. editor.events.$on(editor.$el, 'mouseenter', 'th, td', _mouseEnter); // Mouse is no longer pressed. editor.events.$on(editor.$win, 'mouseup', _mouseUp); // Iframe mouseup. if (editor.opts.iframe) { editor.events.$on($(editor.o_win), 'mouseup', _mouseUp); } // Check tags under the mouse to see if the resizer needs to be shown. editor.events.$on(editor.$win, 'mousemove', _mouseMove); // Update resizer's position on scroll. editor.events.$on($(editor.o_win), 'scroll', _repositionResizer); // Reposition table edit popup when table cell content changes. editor.events.on('contentChanged', function () { if (selectedCells().length > 0) { _showEditPopup(); // Make sure we reposition on image load. editor.$el.find('img').on('load.selected-cells', function () { $(this).off('load.selected-cells'); if (selectedCells().length > 0) { _showEditPopup(); } }); } }); // Reposition table edit popup on window resize. editor.events.$on($(editor.o_win), 'resize', function () { _removeSelection(); }); editor.events.on('toolbar.esc', function () { if (selectedCells().length > 0) { editor.events.disableBlur(); editor.events.focus(); return false; } }, true); // Selecting cells with keyboard or moving cursor with arrow keys. editor.events.$on(editor.$el, 'keydown', function (e) { if (e.shiftKey) { if (_selectCellsWithKeyboard(e) === false) { // Timeout needed due to clearSelection timeout. setTimeout(function () { _showEditPopup(); }, 0); } } else { _navigateWithArrows(e); } }); // Prevent backspace from doing browser back. editor.events.on('keydown', function (e) { // Tab in cell. if (_useTab(e) === false) return false; var selected_cells = selectedCells(); if (selected_cells.length > 0) { // ESC clear table cell selection. if (e.which == $.FE.KEYCODE.ESC) { if (editor.popups.isVisible('table.edit')) { _removeSelection(); editor.popups.hide('table.edit'); e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); selected_cells = []; return false; } } // Backspace clears selected cells content. if (selected_cells.length > 1 && e.which == $.FE.KEYCODE.BACKSPACE) { editor.undo.saveStep(); for (var i = 0; i < selected_cells.length; i++) { $(selected_cells[i]).html('<br>'); if (i == selected_cells.length - 1) { $(selected_cells[i]).prepend($.FE.MARKERS); } } editor.selection.restore(); editor.undo.saveStep(); selected_cells = []; return false; } // Prevent typing if cells are selected. (Allow browser refresh using keyboard) if (selected_cells.length > 1 && e.which != $.FE.KEYCODE.F10 && !editor.keys.isBrowserAction(e)) { e.preventDefault(); selected_cells = []; return false; } } // We may want to select a cell with keyboard. else { // Garbage collector. selected_cells = []; if (_selectCellWithKeyboard(e) === false) return false; } }, true); // Clean selected cells. var c_selected_cells = []; editor.events.on('html.beforeGet', function () { c_selected_cells = selectedCells(); for (var i = 0; i < c_selected_cells.length; i++) { c_selected_cells[i].className = (c_selected_cells[i].className || '').replace(/fr-selected-cell/g, ''); } }); editor.events.on('html.afterGet', function () { for (var i = 0; i < c_selected_cells.length; i++) { c_selected_cells[i].className = (c_selected_cells[i].className ? c_selected_cells[i].className.trim() + ' ' : '') + 'fr-selected-cell'; } c_selected_cells = []; }); _initInsertPopup(true); _initEditPopup(true); } editor.events.on('destroy', _destroy); } return { _init: _init, insert: insert, remove: remove, insertRow: insertRow, deleteRow: deleteRow, insertColumn: insertColumn, deleteColumn: deleteColumn, mergeCells: mergeCells, splitCellVertically: splitCellVertically, splitCellHorizontally: splitCellHorizontally, addHeader: addHeader, removeHeader: removeHeader, setBackground: setBackground, showInsertPopup: _showInsertPopup, showEditPopup: _showEditPopup, showColorsPopup: _showColorsPopup, back: back, verticalAlign: verticalAlign, horizontalAlign: horizontalAlign, applyStyle: applyStyle, selectedTable: selectedTable, selectedCells: selectedCells } }; // Insert table button. $.FE.DefineIcon('insertTable', { NAME: 'table' }); $.FE.RegisterCommand('insertTable', { title: 'Insert Table', undo: false, focus: true, refreshOnCallback: false, popup: true, callback: function () { if (!this.popups.isVisible('table.insert')) { this.table.showInsertPopup(); } else { if (this.$el.find('.fr-marker').length) { this.events.disableBlur(); this.selection.restore(); } this.popups.hide('table.insert'); } }, plugin: 'table' }); $.FE.RegisterCommand('tableInsert', { callback: function (cmd, rows, cols) { this.table.insert(rows, cols); this.popups.hide('table.insert'); } }) // Table header button. $.FE.DefineIcon('tableHeader', { NAME: 'header' }) $.FE.RegisterCommand('tableHeader', { title: 'Table Header', focus: false, toggle: true, callback: function () { var $btn = this.popups.get('table.edit').find('.fr-command[data-cmd="tableHeader"]'); // If button is active the table has a header, if ($btn.hasClass('fr-active')) { this.table.removeHeader(); } // Add table header. else { this.table.addHeader(); } }, refresh: function ($btn) { var $table = this.table.selectedTable(); if ($table.length > 0) { // If table doesn't have a header. if ($table.find('th').length === 0) { $btn.removeClass('fr-active').attr('aria-pressed', false); } // Header button is active if table has header. else { $btn.addClass('fr-active').attr('aria-pressed', true); } } } }); // Table rows action dropdown. $.FE.DefineIcon('tableRows', { NAME: 'bars' }) $.FE.RegisterCommand('tableRows', { type: 'dropdown', focus: false, title: 'Row', options: { above: 'Insert row above', below: 'Insert row below', 'delete': 'Delete row' }, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = $.FE.COMMANDS.tableRows.options; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableRows" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { if (val == 'above' || val == 'below') { this.table.insertRow(val); } else { this.table.deleteRow(); } } }); // Table columns action dropdown. $.FE.DefineIcon('tableColumns', { NAME: 'bars fa-rotate-90' }) $.FE.RegisterCommand('tableColumns', { type: 'dropdown', focus: false, title: 'Column', options: { before: 'Insert column before', after: 'Insert column after', 'delete': 'Delete column' }, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = $.FE.COMMANDS.tableColumns.options; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableColumns" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { if (val == 'before' || val == 'after') { this.table.insertColumn(val); } else { this.table.deleteColumn(); } } }); // Table cells action dropdown. $.FE.DefineIcon('tableCells', { NAME: 'square-o' }) $.FE.RegisterCommand('tableCells', { type: 'dropdown', focus: false, title: 'Cell', options: { merge: 'Merge cells', 'vertical-split': 'Vertical split', 'horizontal-split': 'Horizontal split' }, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = $.FE.COMMANDS.tableCells.options; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCells" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { if (val == 'merge') { this.table.mergeCells(); } else if (val == 'vertical-split') { this.table.splitCellVertically(); } // 'horizontal-split' else { this.table.splitCellHorizontally(); } }, refreshOnShow: function ($btn, $dropdown) { // More than one cell selected. if (this.$el.find('.fr-selected-cell').length > 1) { $dropdown.find('a[data-param1="vertical-split"]').addClass('fr-disabled').attr('aria-disabled', true); $dropdown.find('a[data-param1="horizontal-split"]').addClass('fr-disabled').attr('aria-disabled', true); $dropdown.find('a[data-param1="merge"]').removeClass('fr-disabled').attr('aria-disabled', false); } // Only one selected cell. else { $dropdown.find('a[data-param1="merge"]').addClass('fr-disabled').attr('aria-disabled', true); $dropdown.find('a[data-param1="vertical-split"]').removeClass('fr-disabled').attr('aria-disabled', false); $dropdown.find('a[data-param1="horizontal-split"]').removeClass('fr-disabled').attr('aria-disabled', false); } } }); // Remove table button. $.FE.DefineIcon('tableRemove', { NAME: 'trash' }) $.FE.RegisterCommand('tableRemove', { title: 'Remove Table', focus: false, callback: function () { this.table.remove(); } }); // Table styles. $.FE.DefineIcon('tableStyle', { NAME: 'paint-brush' }) $.FE.RegisterCommand('tableStyle', { title: 'Table Style', type: 'dropdown', focus: false, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = this.opts.tableStyles; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableStyle" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { this.table.applyStyle(val, this.$el.find('.fr-selected-cell').closest('table'), this.opts.tableMultipleStyles, this.opts.tableStyles); }, refreshOnShow: function ($btn, $dropdown) { var $table = this.$el.find('.fr-selected-cell').closest('table'); if ($table) { $dropdown.find('.fr-command').each (function () { var cls = $(this).data('param1'); var active = $table.hasClass(cls); $(this).toggleClass('fr-active', active).attr('aria-selected', active); }) } } }); // Table cell background color button. $.FE.DefineIcon('tableCellBackground', { NAME: 'tint' }) $.FE.RegisterCommand('tableCellBackground', { title: 'Cell Background', focus: false, popup: true, callback: function () { this.table.showColorsPopup(); } }); // Select table cell background color command. $.FE.RegisterCommand('tableCellBackgroundColor', { undo: true, focus: false, callback: function (cmd, val) { this.table.setBackground(val); } }); // Table back. $.FE.DefineIcon('tableBack', { NAME: 'arrow-left' }); $.FE.RegisterCommand('tableBack', { title: 'Back', undo: false, focus: false, back: true, callback: function () { this.table.back(); }, refresh: function ($btn) { if (this.table.selectedCells().length === 0 && !this.opts.toolbarInline) { $btn.addClass('fr-hidden'); $btn.next('.fr-separator').addClass('fr-hidden'); } else { $btn.removeClass('fr-hidden'); $btn.next('.fr-separator').removeClass('fr-hidden'); } } }); // Table vertical align dropdown. $.FE.DefineIcon('tableCellVerticalAlign', { NAME: 'arrows-v' }) $.FE.RegisterCommand('tableCellVerticalAlign', { type: 'dropdown', focus: false, title: 'Vertical Align', options: { Top: 'Align Top', Middle: 'Align Middle', Bottom: 'Align Bottom' }, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = $.FE.COMMANDS.tableCellVerticalAlign.options; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCellVerticalAlign" data-param1="' + val.toLowerCase() + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(val) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { this.table.verticalAlign(val); }, refreshOnShow: function ($btn, $dropdown) { $dropdown.find('.fr-command[data-param1="' + this.$el.find('.fr-selected-cell').css('vertical-align') + '"]').addClass('fr-active').attr('aria-selected', true); } }); // Table horizontal align dropdown. $.FE.DefineIcon('tableCellHorizontalAlign', { NAME: 'align-left' }); $.FE.DefineIcon('align-left', { NAME: 'align-left' }); $.FE.DefineIcon('align-right', { NAME: 'align-right' }); $.FE.DefineIcon('align-center', { NAME: 'align-center' }); $.FE.DefineIcon('align-justify', { NAME: 'align-justify' }); $.FE.RegisterCommand('tableCellHorizontalAlign', { type: 'dropdown', focus: false, title: 'Horizontal Align', options: { left: 'Align Left', center: 'Align Center', right: 'Align Right', justify: 'Align Justify' }, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = $.FE.COMMANDS.tableCellHorizontalAlign.options; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command fr-title" tabIndex="-1" role="option" data-cmd="tableCellHorizontalAlign" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.icon.create('align-' + val) + '<span class="fr-sr-only">' + this.language.translate(options[val]) + '</span></a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { this.table.horizontalAlign(val); }, refresh: function ($btn) { var selected_cells = this.table.selectedCells(); if (selected_cells.length) { $btn.find('> *:first').replaceWith(this.icon.create('align-' + this.helpers.getAlignment($(selected_cells[0])))); } }, refreshOnShow: function ($btn, $dropdown) { $dropdown.find('.fr-command[data-param1="' + this.helpers.getAlignment(this.$el.find('.fr-selected-cell:first')) + '"]').addClass('fr-active').attr('aria-selected', true); } }); // Table cell styles. $.FE.DefineIcon('tableCellStyle', { NAME: 'magic' }) $.FE.RegisterCommand('tableCellStyle', { title: 'Cell Style', type: 'dropdown', focus: false, html: function () { var c = '<ul class="fr-dropdown-list" role="presentation">'; var options = this.opts.tableCellStyles; for (var val in options) { if (options.hasOwnProperty(val)) { c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCellStyle" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>'; } } c += '</ul>'; return c; }, callback: function (cmd, val) { this.table.applyStyle(val, this.$el.find('.fr-selected-cell'), this.opts.tableCellMultipleStyles, this.opts.tableCellStyles); }, refreshOnShow: function ($btn, $dropdown) { var $cell = this.$el.find('.fr-selected-cell:first'); if ($cell) { $dropdown.find('.fr-command').each (function () { var cls = $(this).data('param1'); var active = $cell.hasClass(cls); $(this).toggleClass('fr-active', active).attr('aria-selected', active); }) } } }); }));