enhanced the table editing in the extended wiki editor (medium-editor-tables)

disabled firefox object resizing and inline table editing
This commit is contained in:
hyung-hwan 2016-01-19 15:27:52 +00:00
parent b3cbcf09ab
commit 06aa8bff25
7 changed files with 753 additions and 9 deletions

View File

@ -28,8 +28,8 @@
<link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor.min.css')?>" /> <link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor.min.css')?>" />
<link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor-theme.min.css')?>" /> <link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor-theme.min.css')?>" />
<script type="text/javascript" src="<?php print base_url_make('/js/medium-editor-tables.min.js')?>"></script> <script type="text/javascript" src="<?php print base_url_make('/js/medium-editor-tables.js')?>"></script>
<link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor-tables.min.css')?>" /> <link type="text/css" rel="stylesheet" href="<?php print base_url_make('/css/medium-editor-tables.min.css')?>" />
<?php <?php
$hex_wikiname = $this->converter->AsciiToHex ($wiki->name); $hex_wikiname = $this->converter->AsciiToHex ($wiki->name);
@ -365,7 +365,8 @@ $(function () {
toolbar: { toolbar: {
allowMultiParagraphSelection: true, allowMultiParagraphSelection: true,
buttons: ['bold', 'italic', 'underline', 'strikethrough', buttons: ['fontname', 'fontsize',
'bold', 'italic', 'underline', 'strikethrough',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'subscript', 'superscript', 'removeFormat', 'subscript', 'superscript', 'removeFormat',
'pre', 'anchor', 'image', 'pre', 'anchor', 'image',
@ -526,6 +527,14 @@ $(function () {
// quirkiness of medium editor's toolbar // quirkiness of medium editor's toolbar
resize_editor (); resize_editor ();
resize_editor (); resize_editor ();
// to diable firefox's object resizing.
try
{
document.execCommand("enableInlineTableEditing", false, false);
document.execCommand("enableObjectResizing", false, false);
}
catch (err) { }
}); });
</script> </script>

View File

@ -1,4 +1,6 @@
<html> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

View File

@ -952,11 +952,13 @@ pre.codepot-line-numbered span.codepot-line-number-clear {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
margin: 0.1em 0 0.1em 0; padding: 0.3em 1em 0.3em 1em;
padding: 0; margin: 0;
max-width: 100%; max-width: 100%;
border: 1px solid #DFDFDF; border: 1px solid #DFDFDF;
table-layout: auto !important;
} }
.codepot-styled-text-view table tr, .codepot-styled-text-view table tr,

View File

@ -25,7 +25,7 @@ www_DATA = \
jquery.flot.tickrotor.js \ jquery.flot.tickrotor.js \
jqueryui-editable.min.js \ jqueryui-editable.min.js \
medium-editor.min.js \ medium-editor.min.js \
medium-editor-tables.min.js \ medium-editor-tables.js \
showdown.js \ showdown.js \
d3.min.js \ d3.min.js \
CodeFlower.js CodeFlower.js

View File

@ -178,7 +178,7 @@ www_DATA = \
jquery.flot.tickrotor.js \ jquery.flot.tickrotor.js \
jqueryui-editable.min.js \ jqueryui-editable.min.js \
medium-editor.min.js \ medium-editor.min.js \
medium-editor-tables.min.js \ medium-editor-tables.js \
showdown.js \ showdown.js \
d3.min.js \ d3.min.js \
CodeFlower.js CodeFlower.js

View File

@ -0,0 +1,732 @@
(function (root, factory) {
'use strict';
if (typeof module === 'object') {
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root.MediumEditorTable = factory;
}
}(this, function () {
'use strict';
function extend(dest, source) {
var prop;
dest = dest || {};
for (prop in source) {
if (source.hasOwnProperty(prop) && !dest.hasOwnProperty(prop)) {
dest[prop] = source[prop];
}
}
return dest;
}
function getSelectionText(doc) {
if (doc.getSelection) {
return doc.getSelection().toString();
}
if (doc.selection && doc.selection.type !== 'Control') {
return doc.selection.createRange().text;
}
return '';
}
function getSelectionStart(doc) {
var node = doc.getSelection().anchorNode,
startNode = (node && node.nodeType === 3 ? node.parentNode : node);
return startNode;
}
function placeCaretAtNode(doc, node, before) {
if (doc.getSelection !== undefined && node) {
var range = doc.createRange(),
selection = doc.getSelection();
if (before) {
range.setStartBefore(node);
} else {
range.setStartAfter(node);
}
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
}
function isInsideElementOfTag(node, tag) {
if (!node) {
return false;
}
var parentNode = node.parentNode,
tagName = parentNode.tagName.toLowerCase();
while (tagName !== 'body') {
if (tagName === tag) {
return true;
}
parentNode = parentNode.parentNode;
if (parentNode && parentNode.tagName) {
tagName = parentNode.tagName.toLowerCase();
} else {
return false;
}
}
return false;
}
function getParentOf(el, tagTarget) {
var tagName = el && el.tagName ? el.tagName.toLowerCase() : false;
if (!tagName) {
return false;
}
while (tagName && tagName !== 'body') {
if (tagName === tagTarget) {
return el;
}
el = el.parentNode;
tagName = el && el.tagName ? el.tagName.toLowerCase() : false;
}
}
function Grid(el, callback, rows, columns) {
return this.init(el, callback, rows, columns);
}
Grid.prototype = {
init: function (el, callback, rows, columns) {
this._root = el;
this._callback = callback;
this.rows = rows;
this.columns = columns;
return this._render();
},
setCurrentCell: function (cell) {
this._currentCell = cell;
},
markCells: function () {
[].forEach.call(this._cellsElements, function (el) {
var cell = {
column: parseInt(el.dataset.column, 10),
row: parseInt(el.dataset.row, 10)
},
active = this._currentCell &&
cell.row <= this._currentCell.row &&
cell.column <= this._currentCell.column;
if (active === true) {
el.classList.add('active');
} else {
el.classList.remove('active');
}
}.bind(this));
},
_generateCells: function () {
this._cells = [];
for (var i = 0; i < this.rows * this.columns; i++) {
var column = i % this.columns,
row = Math.floor(i / this.rows);
this._cells.push({
column: column,
row: row,
active: false
});
}
},
_html: function () {
var html = '<div class="medium-editor-table-builder-grid clearfix">';
html += this._cellsHTML();
html += '</div>';
return html;
},
_cellsHTML: function () {
var html = '';
this._generateCells();
this._cells.map(function (cell) {
html += '<a href="#" class="medium-editor-table-builder-cell' +
(cell.active === true ? ' active' : '') +
'" ' + 'data-row="' + cell.row +
'" data-column="' + cell.column + '">';
html += '</a>';
});
return html;
},
_render: function () {
this._root.innerHTML = this._html();
this._cellsElements = this._root.querySelectorAll('a');
this._bindEvents();
},
_bindEvents: function () {
[].forEach.call(this._cellsElements, function (el) {
this._onMouseEnter(el);
this._onClick(el);
}.bind(this));
},
_onMouseEnter: function (el) {
var self = this,
timer;
el.addEventListener('mouseenter', function () {
clearTimeout(timer);
var dataset = this.dataset;
timer = setTimeout(function () {
self._currentCell = {
column: parseInt(dataset.column, 10),
row: parseInt(dataset.row, 10)
};
self.markCells();
}, 50);
});
},
_onClick: function (el) {
var self = this;
el.addEventListener('click', function (e) {
e.preventDefault();
self._callback(this.dataset.row, this.dataset.column);
});
}
};
function Builder(options) {
return this.init(options);
}
Builder.prototype = {
init: function (options) {
this.options = options;
this._doc = options.ownerDocument || document;
this._root = this._doc.createElement('div');
this._root.className = 'medium-editor-table-builder';
this.grid = new Grid(
this._root,
this.options.onClick,
this.options.rows,
this.options.columns
);
this._range = null;
this._toolbar = this._doc.createElement('div');
this._toolbar.className = 'medium-editor-table-builder-toolbar';
var spanRow = this._doc.createElement('span');
spanRow.innerHTML = 'Row:';
this._toolbar.appendChild(spanRow);
var addRowBefore = this._doc.createElement('button');
addRowBefore.title = 'Add row before';
addRowBefore.innerHTML = '<i class="fa fa-long-arrow-up"></i>';
addRowBefore.onclick = this.addRow.bind(this, true);
this._toolbar.appendChild(addRowBefore);
var addRowAfter = this._doc.createElement('button');
addRowAfter.title = 'Add row after';
addRowAfter.innerHTML = '<i class="fa fa-long-arrow-down"></i>';
addRowAfter.onclick = this.addRow.bind(this, false);
this._toolbar.appendChild(addRowAfter);
var remRow = this._doc.createElement('button');
remRow.title = 'Remove row';
remRow.innerHTML = '<i class="fa fa-close"></i>';
remRow.onclick = this.removeRow.bind(this);
this._toolbar.appendChild(remRow);
var spanCol = this._doc.createElement('span');
spanCol.innerHTML = 'Column:';
this._toolbar.appendChild(spanCol);
var addColumnBefore = this._doc.createElement('button');
addColumnBefore.title = 'Add column before';
addColumnBefore.innerHTML = '<i class="fa fa-long-arrow-left"></i>';
addColumnBefore.onclick = this.addColumn.bind(this, true);
this._toolbar.appendChild(addColumnBefore);
var addColumnAfter = this._doc.createElement('button');
addColumnAfter.title = 'Add column after';
addColumnAfter.innerHTML = '<i class="fa fa-long-arrow-right"></i>';
addColumnAfter.onclick = this.addColumn.bind(this, false);
this._toolbar.appendChild(addColumnAfter);
var remColumn = this._doc.createElement('button');
remColumn.title = 'Remove column';
remColumn.innerHTML = '<i class="fa fa-close"></i>';
remColumn.onclick = this.removeColumn.bind(this);
this._toolbar.appendChild(remColumn);
var remTable = this._doc.createElement('button');
remTable.title = 'Remove table';
remTable.innerHTML = '<i class="fa fa-trash-o"></i>';
remTable.onclick = this.removeTable.bind(this);
this._toolbar.appendChild(remTable);
var grid = this._root.childNodes[0];
this._root.insertBefore(this._toolbar, grid);
//console.log(this._root);
},
getElement: function () {
return this._root;
},
hide: function () {
this._root.style.display = '';
this.grid.setCurrentCell({ column: -1, row: -1 });
this.grid.markCells();
},
show: function (left) {
this._root.style.display = 'block';
this._root.style.left = left + 'px';
},
setEditor: function (range) {
this._range = range;
this._doc.getElementsByClassName('medium-editor-table-builder-toolbar')[0].style.display = 'block';
},
setBuilder: function () {
this._range = null;
this._doc.getElementsByClassName('medium-editor-table-builder-toolbar')[0].style.display = 'none';
var elements = this._doc.getElementsByClassName('medium-editor-table-builder-grid');
for (var i = 0; i < elements.length; i++) {
// TODO: what is 16 and what is 2?
elements[i].style.height = (16 * this.rows + 2) + 'px';
elements[i].style.width = (16 * this.columns + 2) + 'px';
}
},
addRow: function (before, e) {
e.preventDefault();
e.stopPropagation();
// codepot
//var tbody = this._range.parentNode.parentNode,
// tr = this._doc.createElement('tr'),
// td;
var num_cols;
var tbody, tr, td;
tr = this._doc.createElement('tr');
var in_thead = this._range.parentNode.parentNode.tagName.toLowerCase() == 'thead';
if (in_thead)
{
// the current cell is the header.
var thead = this._range.parentNode.parentNode;
var table = thead.parentNode;
tbody = table.childNodes[1];
}
else
{
tbody = this._range.parentNode.parentNode;
}
if (tbody.childNodes[0] && tbody.childNodes[0].childNodes[0] && tbody.childNodes[0].childNodes[0].tagName.toLowerCase() == 'th')
{
td = this._doc.createElement('th');
tr.appendChild(td);
num_cols = this._range.parentNode.childNodes.length - 1;
}
else
{
num_cols = this._range.parentNode.childNodes.length;
}
// end codepot
for (var i = 0; i < num_cols /*this._range.parentNode.childNodes.length*/; i++) {
td = this._doc.createElement('td');
td.appendChild(this._doc.createElement('br'));
tr.appendChild(td);
}
// codepot
if (in_thead) tbody.appendChild(tr);
// end codepot
else if (before !== true && this._range.parentNode.nextSibling) {
tbody.insertBefore(tr, this._range.parentNode.nextSibling);
} else if (before === true) {
tbody.insertBefore(tr, this._range.parentNode);
} else {
tbody.appendChild(tr);
}
// codepot
//this.options.onClick(0, 0);
this.options.onClick(-1, -1);
// end codepot
},
removeRow: function (e) {
e.preventDefault();
e.stopPropagation();
// codepot
if (this._range.parentNode.parentNode.tagName.toLowerCase() == 'thead') // remove the whole thead
this._range.parentNode.parentNode.parentNode.removeChild(this._range.parentNode.parentNode);
else
// end codepot
this._range.parentNode.parentNode.removeChild(this._range.parentNode);
// codepot
//this.options.onClick(0, 0);
this.options.onClick(-1, -1);
// end codepot
},
addColumn: function (before, e) {
e.preventDefault();
e.stopPropagation();
var cell = Array.prototype.indexOf.call(this._range.parentNode.childNodes, this._range);
var td;
// codepot
//var tbody = this._range.parentNode.parentNode;
var tbody;
var table = this._range.parentNode.parentNode.parentNode;
if (table.childNodes[0].tagName.toLowerCase() == 'thead')
{
var thead = table.childNodes[0];
if (thead.childNodes[0])
{
// tr is expected inside thead. if no <tr> exists, don't add a column
td = this._doc.createElement('th');
if (before)
thead.childNodes[0].insertBefore(td, thead.childNodes[0].childNodes[cell]);
else if (thead.childNodes[0].childNodes[cell].nextSibling)
thead.childNodes[0].insertBefore(td, thead.childNodes[0].childNodes[cell].nextSibling);
else
thead.childNodes[0].appendChild(td);
}
}
var in_thead = this._range.parentNode.parentNode.tagName.toLowerCase() == 'thead';
if (in_thead)
{
// the current cell is the header.
var thead = this._range.parentNode.parentNode;
var table = thead.parentNode;
tbody = table.childNodes[1];
}
else
{
tbody = this._range.parentNode.parentNode;
}
// end codepot
for (var i = 0; i < tbody.childNodes.length; i++) {
td = this._doc.createElement('td');
td.appendChild(this._doc.createElement('br'));
if (before === true) {
tbody.childNodes[i].insertBefore(td, tbody.childNodes[i].childNodes[cell]);
// codepot
//} else if (this._range.parentNode.parentNode.childNodes[i].childNodes[cell].nextSibling) {
} else if (tbody.childNodes[i].childNodes[cell].nextSibling) {
// end codepot
tbody.childNodes[i].insertBefore(td, tbody.childNodes[i].childNodes[cell].nextSibling);
} else {
tbody.childNodes[i].appendChild(td);
}
}
// codepot
//this.options.onClick(0, 0);
this.options.onClick(-1, -1);
// end codepot
},
removeColumn: function (e) {
e.preventDefault();
e.stopPropagation();
// codepot
//var cell = Array.prototype.indexOf.call(this._range.parentNode.childNodes, this._range),
// tbody = this._range.parentNode.parentNode,
// rows = tbody.childNodes.length;
var table, thead, tbody, rows;
var cell = Array.prototype.indexOf.call(this._range.parentNode.childNodes, this._range);
if (this._range.parentNode.parentNode.tagName.toLowerCase() == 'thead')
{
// the current cell is the header.
thead = this._range.parentNode.parentNode;
table = thead.parentNode;
tbody = table.childNodes[1];
}
else
{
tbody = this._range.parentNode.parentNode;
table = tbody.parentNode;
}
rows = tbody.childNodes.length;
if (table.childNodes[0].tagName.toLowerCase() == 'thead')
{
var thead = table.childNodes[0];
if (thead.childNodes[0])
{
// if no <tr> is inside <thead>, don't delete any.
thead.childNodes[0].removeChild (thead.childNodes[0].childNodes[cell]);
}
}
// end codepot
for (var i = 0; i < rows; i++) {
tbody.childNodes[i].removeChild(tbody.childNodes[i].childNodes[cell]);
}
// codepot
//this.options.onClick(0, 0);
this.options.onClick(-1, -1);
// end codepot
},
removeTable: function (e) {
e.preventDefault();
e.stopPropagation();
var cell = Array.prototype.indexOf.call(this._range.parentNode.childNodes, this._range),
table = this._range.parentNode.parentNode.parentNode;
table.parentNode.removeChild(table);
this.options.onClick(0, 0);
}
};
function Table(editor) {
return this.init(editor);
}
var TAB_KEY_CODE = 9;
Table.prototype = {
init: function (editor) {
this._editor = editor;
this._doc = this._editor.options.ownerDocument;
this._bindTabBehavior();
},
insert: function (rows, cols) {
var html = this._html(rows, cols);
// codepot
var header = this._header(rows, cols);
// end codepot
this._editor.pasteHTML(
'<table class="medium-editor-table" id="medium-editor-table"' +
' >' +
// codepot
'<thead>' +
header +
'</thead>' +
// end codepot
'<tbody>' +
html +
'</tbody>' +
'</table>', {
cleanAttrs: [],
cleanTags: []
}
);
var table = this._doc.getElementById('medium-editor-table');
table.removeAttribute('id');
placeCaretAtNode(this._doc, table.querySelector('td'), true);
this._editor.checkSelection();
},
// codepot
_header: function (rows, cols) {
var html = '', x, y;
for (x = 0; x < 1; x++) {
html += '<tr><th></th>';
for (y = 0; y <= cols; y++) {
html += '<th>' + y + '</th>';
}
html += '</tr>';
}
return html;
},
// end codepot
_html: function (rows, cols) {
var html = '',
x, y,
text = getSelectionText(this._doc);
for (x = 0; x <= rows; x++) {
// codepot
html += '<tr><th>' + x + '</th>';
// end codepot
for (y = 0; y <= cols; y++) {
html += '<td>' + (x === 0 && y === 0 ? text : '<br />') + '</td>';
}
html += '</tr>';
}
return html;
},
_bindTabBehavior: function () {
var self = this;
[].forEach.call(this._editor.elements, function (el) {
el.addEventListener('keydown', function (e) {
self._onKeyDown(e);
});
});
},
_onKeyDown: function (e) {
var el = getSelectionStart(this._doc),
table;
if (e.which === TAB_KEY_CODE && isInsideElementOfTag(el, 'table')) {
e.preventDefault();
e.stopPropagation();
table = this._getTableElements(el);
if (e.shiftKey) {
this._tabBackwards(el.previousSibling, table.row);
} else {
// codepot - note table.row or table.cell tends to become null in firefox
// when moving between cells with a tab key. the el passed
// seems to get wrong in firefox.
if (this._isLastCell(el, table.row, table.root)) {
// codepot
if (table.row.cells[0].tagName.toLowerCase() == 'th')
this._insertRow(getParentOf(el, 'tbody'), table.row.cells.length - 1, 1);
else
// end codepot
this._insertRow(getParentOf(el, 'tbody'), table.row.cells.length, 0);
}
placeCaretAtNode(this._doc, el);
}
}
},
_getTableElements: function (el) {
return {
// codepot
//cell: getParentOf(el, 'td'),
cell: getParentOf(el, el.tagName.toLowerCase()),
// end codepot
row: getParentOf(el, 'tr'),
root: getParentOf(el, 'table')
};
},
_tabBackwards: function (el, row) {
el = el || this._getPreviousRowLastCell(row);
placeCaretAtNode(this._doc, el, true);
},
_insertRow: function (tbody, cols, prepend_th) {
var tr = document.createElement('tr'),
html = '',
i;
if (prepend_th) html += '<th><br /></th>';
for (i = 0; i < cols; i += 1) {
html += '<td><br /></td>';
}
tr.innerHTML = html;
tbody.appendChild(tr);
},
_isLastCell: function (el, row, table) {
return (
(row.cells.length - 1) === el.cellIndex &&
(table.rows.length - 1) === row.rowIndex
);
},
_getPreviousRowLastCell: function (row) {
row = row.previousSibling;
if (row) {
return row.cells[row.cells.length - 1];
}
}
};
var MediumEditorTable = MediumEditor.extensions.form.extend({
name: 'table',
aria: 'create table',
action: 'table',
contentDefault: 'TBL',
contentFA: '<i class="fa fa-table"></i>',
handleClick: function (event) {
event.preventDefault();
event.stopPropagation();
this[this.isActive() === true ? 'hide' : 'show']();
},
hide: function () {
this.setInactive();
this.builder.hide();
},
show: function () {
this.setActive();
var range = MediumEditor.selection.getSelectionRange(this.document);
if (range.startContainer.nodeName.toLowerCase() === 'td' ||
range.endContainer.nodeName.toLowerCase() === 'td' ||
MediumEditor.util.getClosestTag(MediumEditor.selection.getSelectedParentElement(range), 'td')) {
this.builder.setEditor(MediumEditor.selection.getSelectedParentElement(range));
// codepot
} else if (range.startContainer.nodeName.toLowerCase() === 'th' ||
range.endContainer.nodeName.toLowerCase() === 'th' ||
MediumEditor.util.getClosestTag(MediumEditor.selection.getSelectedParentElement(range), 'th')) {
this.builder.setEditor(MediumEditor.selection.getSelectedParentElement(range));
// end codepot
} else {
this.builder.setBuilder();
}
this.builder.show(this.button.offsetLeft);
},
getForm: function () {
this.builder = new Builder({
onClick: function (rows, columns) {
// codepot
//if (rows > 0 && columns > 0) {
if (rows >= 0 && columns >= 0) {
// end codepot
this.table.insert(rows, columns);
}
this.hide();
}.bind(this),
ownerDocument: this.document,
rows: this.rows || 10,
columns: this.columns || 10
});
this.table = new Table(this.base);
return this.builder.getElement();
}
});
return MediumEditorTable;
}()));

File diff suppressed because one or more lines are too long