ep_tables4
Version:
Insert tables into your Etherpad documents
1,152 lines (1,129 loc) • 58.9 kB
JavaScript
var _ = require('ep_etherpad-lite/static/js/underscore');
// CommonJS
if (typeof (require) != 'undefined') {
if (typeof (Changeset) == 'undefined') {
Changeset = require('ep_etherpad-lite/static/js/Changeset');
}
}
exports.acePostWriteDomLineHTML = function (hook_name, args, cb) {
var node = args.node;
// var initialNode = args.node;
// Do nothing if the line is not a table line
if (node.innerHTML.indexOf("data-tables") == -1) return;
// Sometime, on initialisation, the table is rendered two times
// If <tbody> exist inside DOM, that's means that the table has already been rendered
if (node.innerHTML.indexOf("<tbody>") != -1) return;
$node = $(args.node);
// console.log("--- STARTS ---");
// console.log("HTML", $node.html());
// if (!args.node.tagName && args.node.innerHTML && args.node.innerHTML.indexOf("data-tables") != -1){
// // For the Timeslider
// var dtAttrs = typeof (exports.Datatables) != 'undefined' ? exports.Datatables.attributes : null;
// DatatablesRenderer.render("timeslider", args.node, dtAttrs);
// } else {
// For the Pad
while(node.children.length == 1) {
node = node.children[0];
// console.log("SKIP", node.tagName);
}
// CONCATENATE MULTIPLE NODES INTO ONE
if (node.children.length > 0) {
// console.log("CONCATENATE CHILDREN");
// Case when etherpad interact with the content of a cell and breaks the block into several blocks (like when applying bold ot italic style)
// In that case, we have to regroup everything before rendering the line.
var parts = node.children[0].innerHTML.split('{"payload":[["');
node.children[0].innerHTML = '{"payload":[["' + parts.join('');
// console.log("CHILD 0", node.children[0].innerHTML);
for (var i = 1; i < node.children.length; i++) {
innerHTML = node.children[i].innerHTML;
innerHTML = innerHTML.replace(/(<.+>)","/ig, '","\$1');
// console.log("CHILD " + i, innerHTML);
node.children[0].innerHTML += innerHTML;
node.children[i].innerHTML = "";
node.children[i] = null;
}
node = node.children[0];
}
// RENDER NODE
var dtAttrs = typeof (exports.Datatables) != 'undefined' ? exports.Datatables.attributes : null;
dtAttrs = dtAttrs || "";
DatatablesRenderer.render({}, node, dtAttrs);
exports.Datatables.attributes = null;
// initialNode.innerHTML = node.innerHTML;
// }
}
exports.aceAttribsToClasses = function (hook, context) {
Datatables.attributes = null;
if (context.key == 'tblProp') {
Datatables.attributes = context.value;
return ['tblProp:' + context.value];
}
}
exports.aceStartLineAndCharForPoint = function (hook, context) {
var selStart = null;
try {
Datatables.context = context;
if (Datatables.isFocused()) {
selStart = Datatables.getLineAndCharForPoint();
return selStart;
}
} catch (error) {
//console.log('error ' + error);
}
};
exports.aceEndLineAndCharForPoint = function (hook, context) {
var selEndLine = null;
try {
Datatables.context = context;
if (Datatables.isFocused()) {
selEndLine = Datatables.getLineAndCharForPoint();
return selEndLine;
}
} catch (error) {
//console.log('error ' + error);
}
};
// Once ace is initialized, we set ace_doDatatableOptions and bind it to the context
exports.aceInitialized = function (hook, context) {
var editorInfo = context.editorInfo;
editorInfo.ace_doDatatableOptions = _(Datatables.doDatatableOptions).bind(context);
};
exports.aceKeyEvent = function (hook, context) {
var specialHandled = false;
var currLineNumber = context.rep.selStart[0];
// if trying to delete the line after table, we prevent this, refs Issue #22
if (context.evt.key == "Backspace" && currLineNumber > 0) {
var currentLine = context.rep.lines.atIndex(currLineNumber);
var previousLine = context.rep.lines.atIndex(currLineNumber -1);
// if current line is empty and previous line is a Table, then we prevent the backspace action
if (currentLine.text.length < 1 && previousLine.text.indexOf("data-tables") != -1) {
context.evt.preventDefault();
return true;
}
}
try {
Datatables.context = context;
if (Datatables.isFocused()) {
var evt = context.evt;
var type = evt.type;
var keyCode = evt.keyCode;
var isTypeForSpecialKey = (type == "keydown" || type == "keypress");
var which = evt.which;
if ((!specialHandled) && isTypeForSpecialKey && keyCode == 9 && !(evt.metaKey || evt.ctrlKey)) {
context.editorInfo.ace_fastIncorp(5);
evt.preventDefault();
Datatables.performDocumentTableTabKey();
specialHandled = true;
}
if ((!specialHandled) && isTypeForSpecialKey && keyCode == 13) {
// return key, handle specially;
context.editorInfo.ace_fastIncorp(5);
evt.preventDefault();
Datatables.doReturnKey();
specialHandled = true;
}
if ((!specialHandled) && isTypeForSpecialKey && ((type == "keydown" && keyCode == Datatables.vars.JS_KEY_CODE_DEL) || keyCode == Datatables.vars.JS_KEY_CODE_BS || (String.fromCharCode(which).toLowerCase() == "h" && (evt.ctrlKey)))) {
context.editorInfo.ace_fastIncorp(20);
evt.preventDefault();
specialHandled = true;
if (Datatables.isCellDeleteOk(keyCode)) {
Datatables.doDeleteKey();
}
}
}
} catch (error) {}
//console.log(' ace key evt specialHandled ',specialHandled);
return specialHandled;
};
if (typeof (Datatables) == 'undefined') var Datatables = function () {
// Get the text within an element
// Doesn't do any normalising, returns a string
// of text as found.
function nodeText(n) {
var text = [];
var self = arguments.callee;
var el, els = n.childNodes;
var excluded = {
'noscript': 'noscript',
'script': 'script',
};
for (var i = 0, iLen = els.length; i < iLen; i++) {
el = els[i];
// May need to add other node types here
if (el.nodeType == 1 && !(el.tagName.toLowerCase() in excluded)) {
text.push(self(el));
// If working with XML, add nodeType 4 to get text from CDATA nodes
} else if (el.nodeType == 3) {
// Deal with extra whitespace and returns in text here.
text.push(el.data);
}
}
return text.join('');
}
var dt = {
defaults: {
tblProps: {
borderWidth: "1",
cellAttrs: [],
width: "100",
rowAttrs: {},
colAttrs: [],
authors: {}
}
},
config: {},
/** Internal 'global' variables. */
vars: {
OVERHEAD_LEN_PRE: '{"payload":[["'.length,
OVERHEAD_LEN_MID: '","'.length,
OVERHEAD_LEN_ROW_START: '["'.length,
OVERHEAD_LEN_ROW_END: '"],'.length,
JS_KEY_CODE_BS: 8,
JS_KEY_CODE_DEL: 46,
TBL_OPTIONS: ['addTbl', 'addTblRowA', 'addTblRowB', 'addTblColL', 'addTblColR', 'delTbl', 'delTblRow', 'delTblCol']
},
/* passed parameters */
context: null
}; // end of dt
dt.isFocused = function () {
if (!this.context.rep.selStart || !this.context.rep.selEnd) return false;
var line = this.context.rep.lines.atIndex(this.context.rep.selStart[0]);
if (!line) return false;
var currLineText = line.text || '';
if (currLineText.indexOf("data-tables") == -1) {
return false;
}
return true;
};
/* Helper function. not meant to be used as a standalone function
requires rowStartOffset
*/
dt._getRowEndOffset = function (rowStartOffset, tds) {
var rowEndOffset = rowStartOffset + this.vars.OVERHEAD_LEN_ROW_START;
for (var i = 0, len = tds.length; i < len; i++) {
var overHeadLen = this.vars.OVERHEAD_LEN_MID;
if (i == len - 1) {
overHeadLen = this.vars.OVERHEAD_LEN_ROW_END;
}
rowEndOffset += tds[i].length + overHeadLen;
}
return rowEndOffset;
}
/**
current row index,
td index ,
the length of leftover text of the current cell,
current row start offset,
current row end offset,
current td start offset,
current td end offset,
and cellCaretPos
*/
dt.getFocusedTdInfo = function (payload, colStart) {
var payloadOffset = colStart - this.vars.OVERHEAD_LEN_PRE;
var rowStartOffset = 0;
var payloadSum = 0;
for (var rIndex = 0, rLen = payload.length; rIndex < rLen; rIndex++) {
var tds = payload[rIndex];
for (var tIndex = 0, tLen = tds.length; tIndex < tLen; tIndex++) {
var overHeadLen = this.vars.OVERHEAD_LEN_MID;
if (tIndex == tLen - 1) {
overHeadLen = this.vars.OVERHEAD_LEN_ROW_END;
}
payloadSum += tds[tIndex].length + overHeadLen;
if (payloadSum >= payloadOffset) {
if (payloadSum == payloadOffset) {
tIndex++;
}
var leftOverTdTxtLen = payloadSum - payloadOffset == 0 ? payload[rIndex][tIndex].length + this.vars.OVERHEAD_LEN_MID : payloadSum - payloadOffset;
var cellCaretPos = tds[tIndex].length - (leftOverTdTxtLen - overHeadLen);
var rowEndOffset = this._getRowEndOffset(rowStartOffset, tds);
return {
row: rIndex,
td: tIndex,
leftOverTdTxtLen: leftOverTdTxtLen,
rowStartOffset: rowStartOffset,
rowEndOffset: rowEndOffset,
cellStartOffset: payloadSum - tds[tIndex].length - overHeadLen,
cellEndOffset: payloadSum,
cellCaretPos: cellCaretPos
};
}
}
rowStartOffset = payloadSum;
payloadSum += this.vars.OVERHEAD_LEN_ROW_START;
}
};
dt.printCaretPos = function (start, end) {
top.console.log(JSON.stringify(start));
top.console.log(JSON.stringify(end));
};
dt.doDatatableOptions = function (cmd, xByY) {
Datatables.context = this; // the scope is still ep lite and not DataTables because of the binding we have to do in exports.aceInitialized
if ((typeof (cmd) == 'object' && cmd.tblPropertyChange)) {
Datatables.updateTableProperties(cmd);
} else {
switch (cmd) {
case Datatables.vars.TBL_OPTIONS[0]:
Datatables.addTable(xByY);
break;
case Datatables.vars.TBL_OPTIONS[1]:
Datatables.insertTblRow("addA");
break;
case Datatables.vars.TBL_OPTIONS[2]:
Datatables.insertTblRow("addB");
break;
case Datatables.vars.TBL_OPTIONS[3]:
Datatables.insertTblColumn("addL");
break;
case Datatables.vars.TBL_OPTIONS[4]:
Datatables.insertTblColumn("addR");
break;
case Datatables.vars.TBL_OPTIONS[5]:
Datatables.deleteTable();
break;
case Datatables.vars.TBL_OPTIONS[6]:
Datatables.deleteTblRow();
break;
case Datatables.vars.TBL_OPTIONS[7]:
Datatables.deleteTblColumn();
break;
}
}
};
dt.addTable = function (tableObj) {
var rep = this.context.rep;
var start = rep.selStart;
var end = rep.selEnd;
var line = rep.lines.atIndex(rep.selStart[0]);
var hasMoreRows = null;
var isRowAddition = null;
if (tableObj) {
hasMoreRows = tableObj.hasMoreRows;
isRowAddition = tableObj.isRowAddition;
}
if (isRowAddition) {
var table = JSON.parse(tableObj.tblString);
insertTblRowBelow(0, table);
performDocApplyTblAttrToRow(rep.selStart, JSON.stringify(table.tblProperties));
return;
}
//if the carret is within a table, add the new table at the bottom
if (line) {
var currLineText = line.text;
if (currLineText.indexOf("data-tables") != -1) {
do {
rep.selStart[0] = rep.selStart[0] + 1
currLineText = rep.lines.atIndex(rep.selStart[0]).text;
} while (currLineText.indexOf("data-tables") != -1);
rep.selEnd[1] = rep.selStart[1] = currLineText.length;
this.context.editorInfo.ace_doReturnKey();
this.context.editorInfo.ace_doReturnKey();
} else {
rep.selEnd[1] = rep.selStart[1] = currLineText.length;
this.context.editorInfo.ace_doReturnKey();
// this.context.editorInfo.ace_inCallStackIfNecessary ('newline',this.context.editorInfo.ace_doReturnKey);
}
}
//if no col/row specified, create a default 3X3 empty table
if (tableObj == null) {
var authors = {};
this.insertTblRowBelow(3);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(null, true));
this.insertTblRowBelow(3);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(authors));
this.insertTblRowBelow(3);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(authors));
this.context.editorInfo.ace_doReturnKey();
//this.context.editorInfo.ace_inCallStackIfNecessary ('newline',this.context.editorInfo.ace_doReturnKey);
this.updateAuthorAndCaretPos(rep.selStart[0] - 3);
return;
}
//xbyy cols and rows have been specified or an actual payload object is present, for the former, create x rows of magicdom lines that contain a row
//per table.
xByYSelect = typeof (tableObj) == "object" ? null : tableObj.split("X");
if (xByYSelect != null && xByYSelect.length == 3) {
var cols = parseInt(xByYSelect[1]);
var rows = parseInt(xByYSelect[2]);
var jsoStrTblProp = JSON.stringify(this.createDefaultTblProperties());
var authors = {};
for (var i = 0; i < rows; i++) {
this.insertTblRowBelow(cols);
if (i == 0) {
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(null, true));
} else {
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(authors));
}
}
this.context.editorInfo.ace_doReturnKey();
this.updateAuthorAndCaretPos(rep.selStart[0] - rows + 1);
return;
}
return newText;
};
//insert a row
dt.insertTblRow = function (aboveOrBelow) {
var func = 'insertTblRow()';
var rep = this.context.rep;
try {
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
//console.log("INSERT ROW | lineText", currLineText);
var payload = JSON.parse(currLineText).payload;
//console.log("Payload", payload);
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
//console.log("currTdInfo", currTdInfo);
var currRow = currTdInfo.row;
var lastRowOffSet = 0;
var start = [],
end = [];
start[0] = rep.selStart[0], start[1] = rep.selStart[1];
end[0] = rep.selStart[0], end[1] = rep.selStart[1];
//console.log("start", start, "end", end);
if (aboveOrBelow == 'addA') {
// If we add a row above the first row, the new row must have the prop 'isFirstRow' and we must remove 'isFirstRow' from current row
var isFirstRow = false;
var jsoTblProp = this.getLineTableProperty(start[0]);
//console.log("table prop", jsoTblProp);
if (isFirstRow = jsoTblProp.isFirstRow) {
delete jsoTblProp['isFirstRow'];
this.updateTblPropInAPool(-1, -1, jsoTblProp, start);
}
rep.selStart[0] = rep.selEnd[0] = rep.selStart[0] - 1;
this.insertTblRowBelow(payload[0].length);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties(null, isFirstRow));
} else { //below curr row ( aboveOrBelow = 'addB')
this.insertTblRowBelow(payload[0].length);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties());
}
this.updateAuthorAndCaretPos(rep.selStart[0]);
var updateEvenOddBgColor = true;
this.sanitizeTblProperties(rep.selStart, updateEvenOddBgColor);
} catch (error) {
//domline.createErrorState(start,end,'insertTblRow',rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,newText,error);
}
};
//delete a table, also removes table overhead
dt.deleteTable = function () {
var rep = this.context.rep;
var func = 'deleteTable()';
var start = rep.seStart;
var end = rep.seEnd;
try {
var line = rep.selStart[0] - 1;
var numOfLinesAbove = 0;
var numOfLinesBelow = 0;
while (rep.lines.atIndex(line).text.indexOf('data-tables') != -1) { //count num of rows above current pos
numOfLinesAbove++;
line--;
}
line = rep.selEnd[0] + 1;
while (rep.lines.atIndex(line).text.indexOf('data-tables') != -1) { //count num of rows below current pos
numOfLinesBelow++;
line++;
}
rep.selStart[1] = 0;
rep.selStart[0] = rep.selStart[0] - numOfLinesAbove;
rep.selEnd[0] = rep.selEnd[0] + numOfLinesBelow;
rep.selEnd[1] = rep.lines.atIndex(rep.selEnd[0]).text.length;
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
} catch (error) {
//domline.createErrorState(start,end,func,rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,"",error);
}
};
//delete a row
dt.deleteTblRow = function () {
var func = 'deleteTblRow()';
var rep = this.context.rep;
try {
// Check if the deleted row is the first row of the table
var isFirstRow = false;
var jsoTblProp = this.getLineTableProperty(rep.selStart[0]);
if (jsoTblProp.isFirstRow) isFirstRow = true;
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
if (currLineText.indexOf('data-tables') == -1) return;
rep.selEnd[0] = rep.selStart[0] + 1;
rep.selStart[1] = 0;
rep.selEnd[1] = 0;
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
currLineText = rep.lines.atIndex(rep.selStart[0]).text;
if (currLineText.indexOf('data-tables') == -1) return;
this.updateAuthorAndCaretPos(rep.selStart[0], 0, 0);
updateEvenOddBgColor = true;
this.sanitizeTblProperties(rep.selStart, updateEvenOddBgColor);
// If the deleted row was the first row, the new first row becomes the first row and must have the prop 'isFirstRow'
if (isFirstRow) {
var newJsoTblProp = this.getLineTableProperty(rep.selStart[0]);
newJsoTblProp['isFirstRow'] = true;
this.updateTblPropInAPool(-1, -1, newJsoTblProp, rep.selStart);
}
} catch (error) {
//domline.createErrorState(start,end,'deleteTblRow',rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,"",error);
}
};
dt.updateTableProperties = function (props) {
var rep = this.context.rep;
var currTd = null;
if (props.tblColWidth || props.tblSingleColBgColor || props.tblColVAlign) {
var currLine = rep.lines.atIndex(rep.selStart[0]);
var currLineText = currLine.text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
currTd = currTdInfo.td;
}
if (props.tblWidth || props.tblHeight || props.tblBorderWidth || props.tblBorderColor || props.tblColWidth || props.tblSingleColBgColor || props.tblEvenRowBgColor || props.tblOddRowBgColor || props.tblColVAlign) {
var start = [];
start[0] = rep.selStart[0], start[1] = rep.selStart[1];
var numOfLinesAbove = this.getTblAboveRowsFromCurFocus(start);
var tempStart = [];
tempStart[0] = start[0] - numOfLinesAbove;
tempStart[1] = start[1];
while (tempStart[0] < rep.lines.length() && rep.lines.atIndex(tempStart[0]).text.indexOf('data-tables') != -1) { //start from top of a table
if (props.tblEvenRowBgColor && tempStart[0] % 2 != 0) {
tempStart[0] = tempStart[0] + 1;
continue;
} else if (props.tblOddRowBgColor && tempStart[0] % 2 == 0) {
tempStart[0] = tempStart[0] + 1;
continue;
}
this.updateTablePropertiesHelper(props, tempStart, currTd);
tempStart[0] = tempStart[0] + 1;
}
} else {
var start = [];
start[0] = rep.selStart[0];
start[1] = rep.selStart[1];
this.updateTablePropertiesHelper(props, start, currTd);
}
};
dt.addCellAttr = function (start, tblJSONObj, tblProperties, attrName, attrValue) {
var rep = this.context.rep;
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, start[1]);
var currRow = currTdInfo.row;
var currTd = currTdInfo.td;
var cellAttrs = tblProperties.cellAttrs;
var row = cellAttrs[currRow];
if (row == null || typeof (row) == 'undefined') {
row = [];
}
cell = row[currTd];
if (cell == null || typeof (cell) == 'undefined') {
cell = {};
}
//toggle these attributes
if (attrName == 'fontWeight' || attrName == 'fontStyle' || attrName == 'textDecoration') {
if (cell[attrName] == attrValue) {
attrValue = '';
}
} else if (cell[attrName] == attrValue) return false; //other wise no need to update
cell[attrName] = attrValue;
row[currTd] = cell;
cellAttrs[currRow] = row;
tblProperties.cellAttrs = cellAttrs;
return tblProperties;
};
//returns false if no chanage to tblProperties
dt.addRowAttr = function (tblJSONObj, tblProperties, attrName, attrValue) {
var rep = this.context.rep;
var rowAttrs = tblProperties.rowAttrs;
if (attrName == 'bgColor') { //specific single row property
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
var currRow = currTdInfo.row;
var singleRowAttrs = rowAttrs.singleRowAttrs;
if (singleRowAttrs == null || typeof (singleRowAttrs) == 'undefined') {
singleRowAttrs = [];
}
if (singleRowAttrs[currRow] == null || typeof (singleRowAttrs[currRow]) == 'undefined') {
singleRowAttrs[currRow] = {};
} else if (singleRowAttrs[currRow][attrName] == attrValue) {
return false;
}
singleRowAttrs[currRow][attrName] = attrValue;
rowAttrs.singleRowAttrs = singleRowAttrs;
} else { //even-odd rows properties,rowAlign
if (rowAttrs[attrName] == attrValue) return false;
rowAttrs[attrName] = attrValue;
}
tblProperties.rowAttrs = rowAttrs;
return tblProperties;
};
//returns false if no chanage to tblProperties
dt.addColumnAttr = function (start, tblJSONObj, tblProperties, attrName, attrValue, currTd) {
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, start[1]);
var colAttrs = tblProperties.colAttrs;
if (colAttrs == null || typeof (colAttrs) == 'undefined') {
colAttrs = [];
}
if (colAttrs[currTd] == null || typeof (colAttrs[currTd]) == 'undefined') {
colAttrs[currTd] = {};
} else if (colAttrs[currTd][attrName] == attrValue) {
return false;
}
colAttrs[currTd][attrName] = attrValue;
tblProperties.colAttrs = colAttrs;
return tblProperties;
};
dt.updateTablePropertiesHelper = function (props, start, currTd) {
var rep = this.context.rep;
lastTblPropertyUsed = 'updateTableProperties';
start = start || rep.selStart;
if (!(start)) return;
var currLine = rep.lines.atIndex(start[0]);
var currLineText = currLine.text;
if (currLineText.indexOf('data-tables') == -1) return true;
try {
var tblJSONObj = JSON.parse(currLineText);
var tblProperties = this.getLineTableProperty(start[0]);
var update = false;
//table width , height , border width and tbl color
if (props.tblWidth || props.tblHeight || props.tblBorderWidth || props.tblBorderColor) {
var currAttrValue = tblProperties[props.attrName];
if (props.attrValue != null && (typeof (currAttrValue) == 'undefined' || currAttrValue != props.attrValue)) {
tblProperties[props.attrName] = props.attrValue;
update = true;
}
}
//cell background color, height, font, font size, font-weight, italic, text-decoration, v-align, h-align
if (props.tblCellFont || props.tblCellFontSize || props.tblCellBgColor || props.tblCellHeight || props.tblcellVAlign || props.tblCellBold || props.tblCellItalic || props.tblCellDecoration || props.tblCellVAlign || props.tblCellHAlign) {
var tblProps = this.addCellAttr(start, tblJSONObj, tblProperties, props.attrName, props.attrValue);
if (tblProps) {
tblProperties = tblProps;
update = true;
}
}
//even/odd row background color
if (props.tblEvenRowBgColor || props.tblOddRowBgColor) {
var tblProps = this.addRowAttr(tblJSONObj, tblProperties, props.attrName, props.attrValue);
if (tblProps) {
tblProperties = tblProps;
update = true;
}
}
//single row background color, rowVAlign
if (props.tblSingleRowBgColor || props.tblRowVAlign) {
var tblProps = this.addRowAttr(tblJSONObj, tblProperties, props.attrName, props.attrValue);
if (tblProps) {
tblProperties = tblProps;
update = true;
}
}
// col width, col bgColor, colVAlign
if (props.tblColWidth || props.tblSingleColBgColor || props.tblColVAlign) {
var tblProps = this.addColumnAttr(start, tblJSONObj, tblProperties, props.attrName, props.attrValue, currTd);
if (tblProps) {
tblProperties = tblProps;
update = true;
}
}
//only update if there is a change
if (update) {
this.updateTblPropInAPool(-1, -1, tblProperties, start);
}
} catch (error) {
// domline.createErrorState(start,end,'updateTableProperties',rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,"",error);
}
};
dt.updateAuthorAndCaretPos = function (magicDomLineNum, tblRowNum, tblColNum) {
var rep = this.context.rep;
rep.selStart[1] = rep.selEnd[1] = this.vars.OVERHEAD_LEN_PRE;
rep.selStart[0] = rep.selEnd[0] = magicDomLineNum;
var row = typeof (tblRowNum) == 'undefined' || tblRowNum == null ? 0 : tblRowNum;
var col = typeof (tblColNum) == 'undefined' || tblRowNum == null ? 0 : tblColNum;
this.updateTblPropInAPool(row, col, null, rep.selStart);
rep.selStart[1] = rep.selEnd[1] = this.vars.OVERHEAD_LEN_PRE;
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, '');
}
dt.insertTblRowBelow = function (numOfRows, table) {
var rep = this.rep;
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var payload = [
[]
];
if (!numOfRows && numOfRows != 0) {
var tblPayload = JSON.parse(currLineText).payload;
numOfRows = tblPayload[0].length;
}
var tblRows = new Array(numOfRows);
if (numOfRows != 0) {
for (var i = 0; i < tblRows.length; i++) {
tblRows[i] = " ";
}
}
payload = [tblRows];
if (table) {
payload = table.payload;
}
tableObj = {
"payload": payload,
"tblId": 1,
"tblClass": "data-tables",
"trClass": "alst",
"tdClass": "hide-el"
}
rep.selEnd[1] = rep.selStart[1] = currLineText.length;
this.context.editorInfo.ace_doReturnKey();
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, JSON.stringify(tableObj));
};
dt.createDefaultTblProperties = function (authors, isFirstRow) {
var rep = this.context.rep;
var defTblProp = {
borderWidth: "1",
cellAttrs: [],
width: "100",
rowAttrs: {},
colAttrs: [],
authors: {}
};
if (authors) {
defTblProp['authors'] = authors;
}
if (isFirstRow) {
defTblProp['isFirstRow'] = isFirstRow;
}
//from existing Table tableborder,tbl_border_width,table_width and table_height
var prevLine = rep.lines.atIndex(rep.selEnd[0] - 1);
var jsoTblProp = null;
if (prevLine) {
var prevLineText = prevLine.text;
if (prevLineText.indexOf("data-tables") != -1) {
jsoTblProp = this.getLineTableProperty(rep.selStart[0] - 1);
}
}
if (!jsoTblProp) {
var nextLine = rep.lines.atIndex(rep.selEnd[0] +1);
if (nextLine) {
var nextLineText = nextLine.text;
if (nextLineText.indexOf("data-tables") != -1) {
jsoTblProp = this.getLineTableProperty(rep.selStart[0] + 1);
}
}
}
if (jsoTblProp) {
defTblProp.borderWidth = jsoTblProp.borderWidth;
defTblProp.borderColor = jsoTblProp.borderColor;
defTblProp.width = jsoTblProp.width;
defTblProp.height = jsoTblProp.height;
defTblProp.colAttrs = jsoTblProp.colAttrs;
}
var jsoStrTblProp = JSON.stringify(defTblProp);
return jsoStrTblProp;
}
dt.performDocApplyTblAttrToRow = function (start, jsoStrTblProp) {
var tempStart = [],
tempEnd = [];
tempStart[0] = start[0], tempEnd[0] = start[0];
tempStart[1] = 0, tempEnd[1] = this.context.rep.lines.atIndex(start[0]).text.length;
this.context.editorInfo.ace_performDocumentApplyAttributesToRange(tempStart, tempEnd, [
["tblProp", jsoStrTblProp]
]);
}
/* handles tab key within a table */
dt.performDocumentTableTabKey = function () {
try {
var context = this.context;
var rep = context.rep;
var currLine = rep.lines.atIndex(rep.selStart[0]);
var currLineText = currLine.text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
var leftOverTdTxtLen = currTdInfo.leftOverTdTxtLen;
var currRow = currTdInfo.row;
var currTd = currTdInfo.td;
if (typeof (payload[currRow][currTd + 1]) == "undefined") { //next row
currRow += 1;
var nextLine = rep.lines.atIndex(rep.selStart[0] + 1);
var nextLineText = nextLine.text;
var updateEvenOddBgColor = false;
if (nextLineText == null || nextLineText == '' || nextLineText.indexOf('data-tables') == -1) { //create new row and move caret to this new row
this.insertTblRowBelow(null, null);
this.performDocApplyTblAttrToRow(rep.selStart, this.createDefaultTblProperties());
rep.selEnd[1] = rep.selStart[1] = this.vars.OVERHEAD_LEN_PRE;
updateEvenOddBgColor = true;
} else { //just move caret to existing next row
currTd = -1;
rep.selStart[0] = rep.selEnd[0] = rep.selStart[0] + 1;
var tblJSONObj = JSON.parse(nextLineText);
var payload = tblJSONObj.payload;
leftOverTdTxtLen = payload[0][0].length;
rep.selEnd[1] = rep.selStart[1] = this.vars.OVERHEAD_LEN_PRE + leftOverTdTxtLen;
}
context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
var start = [];
start[0] = rep.selStart[0];
start[1] = rep.selStart[1];
dt.updateTblCellAuthor(0, 0, null, start, updateEvenOddBgColor); //this requires removing potential user color in a differnt row(although it should only check the previous row)
} else { //tab to the next col and update cell user color
var nextTdTxtLen = typeof (payload[currRow]) == 'undefined' ? -leftOverTdTxtLen : payload[currRow][currTd + 1].length;
payload = tblJSONObj.payload;
rep.selStart[1] = rep.selEnd[1] = rep.selEnd[1] + nextTdTxtLen + leftOverTdTxtLen;
context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
// debugger;
dt.updateTblPropInAPool(currRow, currTd + 1, null, rep.selStart); //this need not to update entire table of color setting
}
} catch (error) {}
};
dt.getTdInfo = function (payload, tdIndex) {
var rep = this.context.rep;
var startOffset = this.vars.OVERHEAD_LEN_PRE;
var rowStartOffset = startOffset;
var payloadSum = startOffset;
var tds = payload[0];
for (var tIndex = 0, tLen = tds.length; tIndex < tLen; tIndex++) {
var overHeadLen = this.vars.OVERHEAD_LEN_MID;
if (tIndex == tLen - 1) {
overHeadLen = this.vars.OVERHEAD_LEN_ROW_END;
}
payloadSum += tds[tIndex].length + overHeadLen;
if (tIndex >= tdIndex) {
return {
cellStartOffset: payloadSum - tds[tIndex].length - overHeadLen,
cellEndOffset: payloadSum
}
}
}
};
dt.getNextTdInfo = function (payload, currTdInfo) {
var rep = this.context.rep;
var startOffset = currTdInfo.rowEndOffset;
var rowStartOffset = startOffset;
var payloadSum = startOffset;
var tds = payload[currTdInfo.row];
for (var tIndex = 0, tLen = tds.length; tIndex < tLen; tIndex++) {
var overHeadLen = this.vars.OVERHEAD_LEN_MID;
if (tIndex == tLen - 1) {
overHeadLen = this.vars.OVERHEAD_LEN_ROW_END;
}
payloadSum += tds[tIndex].length + overHeadLen;
if (tIndex >= currTdInfo.td) {
var leftOverTdTxtLen = payloadSum - startOffset == 0 ? payload[currTdInfo.row + 1][tIndex].length + this.vars.OVERHEAD_LEN_MID : payloadSum - startOffset;
var rowEndOffset = this._getRowEndOffset(rowStartOffset, tds);
var tdInfo = {
row: currTdInfo.row + 1,
td: tIndex,
leftOverTdTxtLen: leftOverTdTxtLen,
rowStartOffset: rowStartOffset,
rowEndOffset: rowEndOffset,
cellStartOffset: payloadSum - tds[tIndex].length - overHeadLen,
cellEndOffset: payloadSum
};
return tdInfo;
}
}
};
//insert a column.
dt.insertTblColumn = function (leftOrRight, start, end) {
var rep = this.context.rep;
var func = 'insertTblColumn()';
try {
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
var currTd = currTdInfo.td;
start = [], end = [];
start[0] = rep.selStart[0];
start[1] = rep.selStart[1];
end[0] = rep.selEnd[0];
end[1] = rep.selEnd[1];
if (leftOrRight == "addL") {
currTd -= 1;
}
var numOfLinesAbove = this.getTblAboveRowsFromCurFocus(start);
rep.selEnd[0] = rep.selStart[0] = rep.selStart[0] - numOfLinesAbove;
while (rep.selStart[0] < rep.lines.length() && rep.lines.atIndex(rep.selStart[0]).text.indexOf('data-tables') != -1) { //count num of rows above current pos
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
var cellPos = this.getTdInfo(payload, currTd).cellEndOffset;
var newText = '" ",';
if (currTd == payload[0].length - 1) { //add to the most right
rep.selStart[1] = rep.selEnd[1] = cellPos - this.vars.OVERHEAD_LEN_ROW_END + 1;
newText = '," "';
} else if (currTd == -1) { //add to most left
rep.selStart[1] = rep.selEnd[1] = this.vars.OVERHEAD_LEN_PRE - 1;
} else {
rep.selStart[1] = rep.selEnd[1] = cellPos - 1;
}
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, newText);
rep.selEnd[0] = rep.selStart[0] = rep.selStart[0] + 1;
}
rep.selStart = start;
rep.selEnd = end;
if (leftOrRight == "addL") {
rep.selStart[1] = rep.selEnd[1] = this.vars.OVERHEAD_LEN_PRE;
rep.selStart[0] = rep.selEnd[0] = rep.selStart[0];
this.updateTblPropInAPool(0, 0, null, rep.selStart);
rep.selStart[1] = rep.selEnd[1] = this.vars.OVERHEAD_LEN_PRE;
}
currTd++;
var updateEvenOddBgColor = false;
var updateColAttrs = true;
this.sanitizeTblProperties(start, updateEvenOddBgColor, updateColAttrs, currTd, "add")
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
} catch (error) {
//domline.createErrorState(start,end,'insertTblColumn',rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,"",error);
}
};
dt.deleteTblColumn = function () {
var func = 'deleteTblColumn()';
var rep = this.context.rep;
try {
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
if (payload[0].length == 1) {
deleteTable();
}
var currTdInfo = this.getFocusedTdInfo(payload, rep.selStart[1]);
var currTd = currTdInfo.td;
var start = [],
end = [];
start[0] = rep.selStart[0];
start[1] = rep.selStart[1];
end[0] = rep.selEnd[0];
end[1] = rep.selEnd[1];
var numOfLinesAbove = this.getTblAboveRowsFromCurFocus(start);
rep.selEnd[0] = rep.selStart[0] = rep.selStart[0] - numOfLinesAbove;
while (rep.selStart[0] < rep.lines.length() && rep.lines.atIndex(rep.selStart[0]).text.indexOf('data-tables') != -1) { //count num of rows above current pos
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var tblJSONObj = JSON.parse(currLineText);
var payload = tblJSONObj.payload;
var cellTdInfo = this.getTdInfo(payload, currTd);
var newText = '" ",';
if (currTd == payload[0].length - 1) { //remove most right col
rep.selStart[1] = cellTdInfo.cellStartOffset - 2;
rep.selEnd[1] = cellTdInfo.cellEndOffset - 2;
} else if (currTd == 0) { //remove most left col
rep.selStart[1] = this.vars.OVERHEAD_LEN_PRE - 1;
rep.selEnd[1] = cellTdInfo.cellEndOffset - 1;
} else {
rep.selStart[1] = cellTdInfo.cellStartOffset - 1
rep.selEnd[1] = cellTdInfo.cellEndOffset - 1;
}
this.context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, "");
rep.selEnd[0] = rep.selStart[0] = rep.selStart[0] + 1;
}
rep.selStart = start;
rep.selEnd = end;
var updateEvenOddBgColor = false;
var updateColAttrs = true;
this.sanitizeTblProperties(start, updateEvenOddBgColor, updateColAttrs, currTd, "del")
this.updateAuthorAndCaretPos(rep.selStart[0], 0, 0);
} catch (error) {
//domline.createErrorState(start,end,'deleteTblColumn',rep.lines.atIndex(rep.selStart[0]).text,rep.selStart,rep.selEnd,"",error);
}
}
dt.insertTblRowBelow = function (numOfRows, table) {
var context = this.context;
var rep = context.rep;
var currLineText = rep.lines.atIndex(rep.selStart[0]).text;
var payload = [
[]
];
if (!numOfRows && numOfRows != 0) {
var tblPayload = JSON.parse(currLineText).payload;
numOfRows = tblPayload[0].length;
}
var tblRows = new Array(numOfRows);
if (numOfRows != 0) {
for (var i = 0; i < tblRows.length; i++) {
tblRows[i] = " ";
}
}
payload = [tblRows];
if (table) {
payload = table.payload;
}
tableObj = {
"payload": payload,
"tblId": 1,
"tblClass": "data-tables",
"trClass": "alst",
"tdClass": "hide-el"
}
rep.selEnd[1] = rep.selStart[1] = currLineText.length;
this.context.editorInfo.ace_inCallStackIfNecessary('newline', this.context.editorInfo.ace_doReturnKey);
context.editorInfo.ace_performDocumentReplaceRange(rep.selStart, rep.selEnd, JSON.stringify(tableObj));
};
dt.doReturnKey = function () {
var context = this.context;
var rep = context.rep;
var start = rep.seStart;
var end = rep.selEnd;
lastTblPropertyUsed = 'doTableReturnKey';
var currLine = rep.lines.atIndex(rep.selStart[0]);
var currLineText = currLine.text;
if (currLineText.indexOf('data-tables') == -1) return false;
else {
var func = 'doTableReturnKey()';
try {
var currCarretPos = rep.selStart[1];
if (currLineText.substring(currCarretPos - 1, currCarretPos + 2) == '","') return true;
else if (currLineText.substring(currCarretPos - 2, currCarretPos + 1) == '","') return true;
else if (currCarretPos < this.vars.OVERHEAD_LEN_PRE) return true;
else if (currCarretPos > currLineText.length) return true;
var start = rep.selStart,
end = rep.selEnd;
newText = " /r/n ";
start[1] = currCarretPos;
end[1] = currCarretPos;
try {
var jsonObj = JSON.parse(currLineText.substring(0, start[1]) + newText + currLineText.substring(start[1]));
payloadStr = JSON.stringify(jsonObj.payload);
if (currCarretPos > payloadStr.length + this.vars.OVERHEAD_LEN_PRE - 2) {
return true;
}
} catch (error) {
return true;
}
context.editorInfo.ace_performDocumentReplaceRange(start, end, newText);
} catch (error) {}
return true;
}
};
dt.isCellDeleteOk = function (keyCode) {
var context = this.context;
var rep = context.rep;
var start = rep.selStart;
var end = rep.selEnd;
var currLine = rep.lines.atIndex(rep.selStart[0]);
var currLineText = currLine.text;
if (currLineText.indexOf('data-tables') == -1) return true;
var isDeleteAccepted = false;
try {
var tblJSONObj = JSON.parse(currLineText);
var table = tblJSONObj.payload;
var currTdInfo = this.getFocusedTdInfo(table, rep.selStart[1]);
cellEntryLen = table[currTdInfo.row][currTdInfo.td].length;
var currCarretPos = rep.selStart[1];
if (currLineText.substring(currCarretPos - 1, currCarretPos + 2) == '","') return false;
else if (currLineText.substring(currCarretPos - 2, currCarretPos + 1) == '","') return false;
switch (keyCode) {
case this.vars.JS_KEY_CODE_BS:
if (cellEntryLen != 0 && cellEntryLen > (currTdInfo.leftOverTdTxtLen - this.vars.OVERHEAD_LEN_MID)) {
isDeleteAccepted = true;
}
break;
case this.vars.JS_KEY_CODE_DEL:
return false; //disabled for the moment
if (cellEntryLen != 0 && currTdInfo.leftOverTdTxtLen - this.vars.OVERHEAD_LEN_MID > 0) {
isDeleteAccepted = true;
}
break;
default:
// cntrl H
if (cellEntryLen != 0 && cellEntryLen > (currTdInfo.leftOverTdTxtLen - this.vars.OVERHEAD_LEN_MID)) {
isDeleteAccepted = true;
}
break;
}
} catch (error) {
isDeleteAccepted = false;
}
return isDeleteAccepted;
};
dt.nodeTextPlain = function (n) {
return n.innerText || n.textContent || n.nodeValue || '';
}
// Get the text within an element
// Doesn't do any normalising, returns a string
// of text as found.
dt.toString = function () {
return "ep_tables2";
};
dt.getLineAndCharForPoint = function () {
var context = this.context;
var point = context.point;
var root = context.root;
// Turn DOM node selection into [line,char] selection.
// This method has to work when the DOM is not pristine,
// assuming the point is not in a dirty node.
if (point.node == root) {
if (point.index == 0) {
return [0, 0];
} else {
var N = this.context.rep.lines.length();
var ln = this.context.rep.lines.atIndex(N - 1);
return [N - 1, ln.text.length];
}
} else {
var n = point.node;
var col = 0;
// if this part fails, it probably means the selection node
// was dirty, and we didn't see it when collecting dirty nodes.
if (nodeText(n) || point.index > 0) {
col = point.index;
}
var parNode, prevSib;
while ((parNode = n.parentNode) != root) {
if ((prevSib = n.previousSibling)) {
n = prevSib;
var textLen = nodeText(n).length == 0 ? this.nodeTextPlain(n).length : nodeText(n).length;
col += textLen;
} else {
n = parNode;
}
}
// if (n.id == "") console.debug("BAD");
if (n.firstChild && context.editorInfo.ace_isBlockElement(n.firstChild)) {
col += 1; // lineMarker
}
var lineEntry = this.context.rep.lines.atKey(n.id);
var lineNum = this.context.rep.lines.indexOfEntry(lineEntry);
return [lineNum, col];
}
};
dt.doDeleteKey = function () {
var context = this.context;
var evt = context.evt || {};
var handled = false;
var rep = this.context.rep;
var editorInfo = context.editorInfo;
if (rep.selStart) {
//end tbl-mod-by-wlos
if (editorInfo.ace_isCaret()) {
var lineNum = editorInfo.ace_caretLine();
var col = editorInfo.ace_caretColumn();
var lineEntry = rep.lines.atIndex(lineNum);
var lineText = lineEntry.text;
var lineMarker = lineEntry.lineMarker;
if (/^ +$/.exec(lineText.substring(lineMarker, col))) {
var col2 = col - lineMarker;
var tabSize = ''.length; // zero for now, tabs are not supported within tables
var toDelete = ((col2 - 1) % tabSize) + 1;
editorInfo.ace_performDocumentReplaceRange([lineNum, col - toDelete], [lineNum, col], '');
//scrollSelectionIntoView();
handled = true;
}
}
if (!handled) {
if (editorInfo.ace_isCaret()) {
var theLine = editorInfo.ace_caretLine();
var lineEntry = rep.lines.atIndex(theLine);
if (editorInfo.ace_caretColumn() <= lineEntry.lineMarker) {
// delete at beginning of line
var action = 'delete_newline';
var prevLineListType = (theLine > 0 ? editorInfo.ace_getLineListType(theLine - 1) : '');
var thisLineListType = editorInfo.ace_getLineListType(theLine);
var prevLineEntry = (theLine > 0 && rep.lines.atIndex(theLine - 1));
var prevLineBlank = (prevLineEntry && prevLineEntry.text.length == prevLineEntry.lineMarker);
if (thisLineListType) {
// this line is a list
if (prevLineBlank && !prevLineListType) {
// previous line is blank, remove it
editorInfo.ace_performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
} else {
// delistify
editorInfo.ace_performDocumentReplaceRange([theLine, 0], [theLine, lineEntry.lineMarker], '');
}
} else if (theLine > 0) {
// remove newline
editorInfo.ace_performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
}
} else {
var docChar = editorInfo.ace_caretDocChar();
if (docChar >