mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-21 16:06:16 -04:00
lint: Run eslint --fix
on src/
This commit is contained in:
parent
b8d07a42eb
commit
8e5fd19db2
109 changed files with 9061 additions and 10572 deletions
|
@ -1,18 +1,18 @@
|
|||
var Changeset = require('./Changeset');
|
||||
var ChangesetUtils = require('./ChangesetUtils');
|
||||
var _ = require('./underscore');
|
||||
const Changeset = require('./Changeset');
|
||||
const ChangesetUtils = require('./ChangesetUtils');
|
||||
const _ = require('./underscore');
|
||||
|
||||
var lineMarkerAttribute = 'lmkr';
|
||||
const lineMarkerAttribute = 'lmkr';
|
||||
|
||||
// Some of these attributes are kept for compatibility purposes.
|
||||
// Not sure if we need all of them
|
||||
var DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
|
||||
const DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
|
||||
|
||||
// If one of these attributes are set to the first character of a
|
||||
// line it is considered as a line attribute marker i.e. attributes
|
||||
// set on this marker are applied to the whole line.
|
||||
// The list attribute is only maintained for compatibility reasons
|
||||
var lineAttributes = [lineMarkerAttribute,'list'];
|
||||
const lineAttributes = [lineMarkerAttribute, 'list'];
|
||||
|
||||
/*
|
||||
The Attribute manager builds changesets based on a document
|
||||
|
@ -29,7 +29,7 @@ var lineAttributes = [lineMarkerAttribute,'list'];
|
|||
- a SkipList `lines` containing the text lines of the document.
|
||||
*/
|
||||
|
||||
var AttributeManager = function(rep, applyChangesetCallback) {
|
||||
const AttributeManager = function (rep, applyChangesetCallback) {
|
||||
this.rep = rep;
|
||||
this.applyChangesetCallback = applyChangesetCallback;
|
||||
this.author = '';
|
||||
|
@ -43,12 +43,11 @@ AttributeManager.lineAttributes = lineAttributes;
|
|||
|
||||
AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
||||
|
||||
applyChangeset: function(changeset){
|
||||
if(!this.applyChangesetCallback) return changeset;
|
||||
applyChangeset(changeset) {
|
||||
if (!this.applyChangesetCallback) return changeset;
|
||||
|
||||
var cs = changeset.toString();
|
||||
if (!Changeset.isIdentity(cs))
|
||||
{
|
||||
const cs = changeset.toString();
|
||||
if (!Changeset.isIdentity(cs)) {
|
||||
this.applyChangesetCallback(cs);
|
||||
}
|
||||
|
||||
|
@ -61,17 +60,17 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
@param end [row, col] tuple pointing to the end of the range
|
||||
@param attribs: an array of attributes
|
||||
*/
|
||||
setAttributesOnRange: function(start, end, attribs) {
|
||||
setAttributesOnRange(start, end, attribs) {
|
||||
// instead of applying the attributes to the whole range at once, we need to apply them
|
||||
// line by line, to be able to disregard the "*" used as line marker. For more details,
|
||||
// see https://github.com/ether/etherpad-lite/issues/2772
|
||||
var allChangesets;
|
||||
for(var row = start[0]; row <= end[0]; row++) {
|
||||
var rowRange = this._findRowRange(row, start, end);
|
||||
var startCol = rowRange[0];
|
||||
var endCol = rowRange[1];
|
||||
let allChangesets;
|
||||
for (let row = start[0]; row <= end[0]; row++) {
|
||||
const rowRange = this._findRowRange(row, start, end);
|
||||
const startCol = rowRange[0];
|
||||
const endCol = rowRange[1];
|
||||
|
||||
var rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
|
||||
const rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
|
||||
|
||||
// compose changesets of all rows into a single changeset, as the range might not be continuous
|
||||
// due to the presence of line markers on the rows
|
||||
|
@ -85,12 +84,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
return this.applyChangeset(allChangesets);
|
||||
},
|
||||
|
||||
_findRowRange: function(row, start, end) {
|
||||
var startCol, endCol;
|
||||
_findRowRange(row, start, end) {
|
||||
let startCol, endCol;
|
||||
|
||||
var startLineOffset = this.rep.lines.offsetOfIndex(row);
|
||||
var endLineOffset = this.rep.lines.offsetOfIndex(row+1);
|
||||
var lineLength = endLineOffset - startLineOffset;
|
||||
const startLineOffset = this.rep.lines.offsetOfIndex(row);
|
||||
const endLineOffset = this.rep.lines.offsetOfIndex(row + 1);
|
||||
const lineLength = endLineOffset - startLineOffset;
|
||||
|
||||
// find column where range on this row starts
|
||||
if (row === start[0]) { // are we on the first row of range?
|
||||
|
@ -116,8 +115,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
@param endCol column where range ends
|
||||
@param attribs: an array of attributes
|
||||
*/
|
||||
_setAttributesOnRangeByLine: function(row, startCol, endCol, attribs) {
|
||||
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
_setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
|
||||
return builder;
|
||||
|
@ -127,12 +126,10 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
Returns if the line already has a line marker
|
||||
@param lineNum: the number of the line
|
||||
*/
|
||||
lineHasMarker: function(lineNum){
|
||||
var that = this;
|
||||
lineHasMarker(lineNum) {
|
||||
const that = this;
|
||||
|
||||
return _.find(lineAttributes, function(attribute){
|
||||
return that.getAttributeOnLine(lineNum, attribute) != '';
|
||||
}) !== undefined;
|
||||
return _.find(lineAttributes, (attribute) => that.getAttributeOnLine(lineNum, attribute) != '') !== undefined;
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -140,14 +137,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
@param lineNum: the number of the line to set the attribute for
|
||||
@param attributeKey: the name of the attribute to get, e.g. list
|
||||
*/
|
||||
getAttributeOnLine: function(lineNum, attributeName){
|
||||
getAttributeOnLine(lineNum, attributeName) {
|
||||
// get `attributeName` attribute of first char of line
|
||||
var aline = this.rep.alines[lineNum];
|
||||
if (aline)
|
||||
{
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext())
|
||||
{
|
||||
const aline = this.rep.alines[lineNum];
|
||||
if (aline) {
|
||||
const opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext()) {
|
||||
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
|
||||
}
|
||||
}
|
||||
|
@ -158,96 +153,94 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
Gets all attributes on a line
|
||||
@param lineNum: the number of the line to get the attribute for
|
||||
*/
|
||||
getAttributesOnLine: function(lineNum){
|
||||
getAttributesOnLine(lineNum) {
|
||||
// get attributes of first char of line
|
||||
var aline = this.rep.alines[lineNum];
|
||||
var attributes = []
|
||||
if (aline)
|
||||
{
|
||||
var opIter = Changeset.opIterator(aline)
|
||||
, op
|
||||
if (opIter.hasNext())
|
||||
{
|
||||
op = opIter.next()
|
||||
if(!op.attribs) return []
|
||||
const aline = this.rep.alines[lineNum];
|
||||
const attributes = [];
|
||||
if (aline) {
|
||||
const opIter = Changeset.opIterator(aline);
|
||||
let op;
|
||||
if (opIter.hasNext()) {
|
||||
op = opIter.next();
|
||||
if (!op.attribs) return [];
|
||||
|
||||
Changeset.eachAttribNumber(op.attribs, function(n) {
|
||||
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
|
||||
}.bind(this))
|
||||
Changeset.eachAttribNumber(op.attribs, (n) => {
|
||||
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]);
|
||||
});
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/*
|
||||
/*
|
||||
Gets a given attribute on a selection
|
||||
@param attributeName
|
||||
@param prevChar
|
||||
returns true or false if an attribute is visible in range
|
||||
*/
|
||||
getAttributeOnSelection: function(attributeName, prevChar){
|
||||
var rep = this.rep;
|
||||
if (!(rep.selStart && rep.selEnd)) return
|
||||
getAttributeOnSelection(attributeName, prevChar) {
|
||||
const rep = this.rep;
|
||||
if (!(rep.selStart && rep.selEnd)) return;
|
||||
// If we're looking for the caret attribute not the selection
|
||||
// has the user already got a selection or is this purely a caret location?
|
||||
var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
|
||||
if(isNotSelection){
|
||||
if(prevChar){
|
||||
const isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
|
||||
if (isNotSelection) {
|
||||
if (prevChar) {
|
||||
// If it's not the start of the line
|
||||
if(rep.selStart[1] !== 0){
|
||||
if (rep.selStart[1] !== 0) {
|
||||
rep.selStart[1]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var withIt = Changeset.makeAttribsString('+', [
|
||||
[attributeName, 'true']
|
||||
const withIt = Changeset.makeAttribsString('+', [
|
||||
[attributeName, 'true'],
|
||||
], rep.apool);
|
||||
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)");
|
||||
const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
|
||||
function hasIt(attribs) {
|
||||
return withItRegex.test(attribs);
|
||||
}
|
||||
|
||||
return rangeHasAttrib(rep.selStart, rep.selEnd)
|
||||
return rangeHasAttrib(rep.selStart, rep.selEnd);
|
||||
|
||||
function rangeHasAttrib(selStart, selEnd) {
|
||||
// if range is collapsed -> no attribs in range
|
||||
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false
|
||||
if (selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false;
|
||||
|
||||
if(selStart[0] != selEnd[0]) { // -> More than one line selected
|
||||
var hasAttrib = true
|
||||
if (selStart[0] != selEnd[0]) { // -> More than one line selected
|
||||
var hasAttrib = true;
|
||||
|
||||
// from selStart to the end of the first line
|
||||
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length])
|
||||
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
|
||||
|
||||
// for all lines in between
|
||||
for(var n=selStart[0]+1; n < selEnd[0]; n++) {
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length])
|
||||
for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
|
||||
}
|
||||
|
||||
// for the last, potentially partial, line
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]])
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
|
||||
|
||||
return hasAttrib
|
||||
return hasAttrib;
|
||||
}
|
||||
|
||||
// Logic tells us we now have a range on a single line
|
||||
|
||||
var lineNum = selStart[0]
|
||||
, start = selStart[1]
|
||||
, end = selEnd[1]
|
||||
, hasAttrib = true
|
||||
const lineNum = selStart[0];
|
||||
const start = selStart[1];
|
||||
const end = selEnd[1];
|
||||
var hasAttrib = true;
|
||||
|
||||
// Iterate over attribs on this line
|
||||
|
||||
var opIter = Changeset.opIterator(rep.alines[lineNum])
|
||||
, indexIntoLine = 0
|
||||
const opIter = Changeset.opIterator(rep.alines[lineNum]);
|
||||
let indexIntoLine = 0;
|
||||
|
||||
while (opIter.hasNext()) {
|
||||
var op = opIter.next();
|
||||
var opStartInLine = indexIntoLine;
|
||||
var opEndInLine = opStartInLine + op.chars;
|
||||
const op = opIter.next();
|
||||
const opStartInLine = indexIntoLine;
|
||||
const opEndInLine = opStartInLine + op.chars;
|
||||
if (!hasIt(op.attribs)) {
|
||||
// does op overlap selection?
|
||||
if (!(opEndInLine <= start || opStartInLine >= end)) {
|
||||
|
@ -258,7 +251,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
indexIntoLine = opEndInLine;
|
||||
}
|
||||
|
||||
return hasAttrib
|
||||
return hasAttrib;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -269,40 +262,39 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
returns a list of attributes in the format
|
||||
[ ["key","value"], ["key","value"], ... ]
|
||||
*/
|
||||
getAttributesOnPosition: function(lineNumber, column){
|
||||
getAttributesOnPosition(lineNumber, column) {
|
||||
// get all attributes of the line
|
||||
var aline = this.rep.alines[lineNumber];
|
||||
const aline = this.rep.alines[lineNumber];
|
||||
|
||||
if (!aline) {
|
||||
return [];
|
||||
return [];
|
||||
}
|
||||
// iterate through all operations of a line
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
const opIter = Changeset.opIterator(aline);
|
||||
|
||||
// we need to sum up how much characters each operations take until the wanted position
|
||||
var currentPointer = 0;
|
||||
var attributes = [];
|
||||
var currentOperation;
|
||||
let currentPointer = 0;
|
||||
const attributes = [];
|
||||
let currentOperation;
|
||||
|
||||
while (opIter.hasNext()) {
|
||||
currentOperation = opIter.next();
|
||||
currentPointer = currentPointer + currentOperation.chars;
|
||||
currentPointer += currentOperation.chars;
|
||||
|
||||
if (currentPointer > column) {
|
||||
// we got the operation of the wanted position, now collect all its attributes
|
||||
Changeset.eachAttribNumber(currentOperation.attribs, function (n) {
|
||||
Changeset.eachAttribNumber(currentOperation.attribs, (n) => {
|
||||
attributes.push([
|
||||
this.rep.apool.getAttribKey(n),
|
||||
this.rep.apool.getAttribValue(n)
|
||||
this.rep.apool.getAttribValue(n),
|
||||
]);
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
// skip the loop
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -311,7 +303,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
returns a list of attributes in the format
|
||||
[ ["key","value"], ["key","value"], ... ]
|
||||
*/
|
||||
getAttributesOnCaret: function(){
|
||||
getAttributesOnCaret() {
|
||||
return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
|
||||
},
|
||||
|
||||
|
@ -322,72 +314,72 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
|
||||
|
||||
*/
|
||||
setAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
||||
var loc = [0,0];
|
||||
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
var hasMarker = this.lineHasMarker(lineNum);
|
||||
setAttributeOnLine(lineNum, attributeName, attributeValue) {
|
||||
let loc = [0, 0];
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
const hasMarker = this.lineHasMarker(lineNum);
|
||||
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
|
||||
|
||||
if(hasMarker){
|
||||
if (hasMarker) {
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
|
||||
[attributeName, attributeValue]
|
||||
[attributeName, attributeValue],
|
||||
], this.rep.apool);
|
||||
} else {
|
||||
// add a line marker
|
||||
builder.insert('*', [
|
||||
['author', this.author],
|
||||
['insertorder', 'first'],
|
||||
[lineMarkerAttribute, '1'],
|
||||
[attributeName, attributeValue],
|
||||
], this.rep.apool);
|
||||
}else{
|
||||
// add a line marker
|
||||
builder.insert('*', [
|
||||
['author', this.author],
|
||||
['insertorder', 'first'],
|
||||
[lineMarkerAttribute, '1'],
|
||||
[attributeName, attributeValue]
|
||||
], this.rep.apool);
|
||||
}
|
||||
|
||||
return this.applyChangeset(builder);
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Removes a specified attribute on a line
|
||||
* @param lineNum the number of the affected line
|
||||
* @param attributeName the name of the attribute to remove, e.g. list
|
||||
* @param attributeValue if given only attributes with equal value will be removed
|
||||
*/
|
||||
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
||||
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
var hasMarker = this.lineHasMarker(lineNum);
|
||||
var found = false;
|
||||
removeAttributeOnLine(lineNum, attributeName, attributeValue) {
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
const hasMarker = this.lineHasMarker(lineNum);
|
||||
let found = false;
|
||||
|
||||
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
|
||||
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){
|
||||
found = true;
|
||||
return [attributeName, ''];
|
||||
}else if (attrib[0] === 'author'){
|
||||
// update last author to make changes to line attributes on this line
|
||||
return [attributeName, this.author];
|
||||
}
|
||||
return attrib;
|
||||
});
|
||||
const attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
|
||||
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) {
|
||||
found = true;
|
||||
return [attributeName, ''];
|
||||
} else if (attrib[0] === 'author') {
|
||||
// update last author to make changes to line attributes on this line
|
||||
return [attributeName, this.author];
|
||||
}
|
||||
return attrib;
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
|
||||
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
|
||||
|
||||
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];})
|
||||
.map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
|
||||
const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1])
|
||||
.map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
|
||||
|
||||
//if we have marker and any of attributes don't need to have marker. we need delete it
|
||||
if(hasMarker && !countAttribsWithMarker){
|
||||
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
|
||||
}else{
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
|
||||
}
|
||||
// if we have marker and any of attributes don't need to have marker. we need delete it
|
||||
if (hasMarker && !countAttribsWithMarker) {
|
||||
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
|
||||
} else {
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
|
||||
}
|
||||
|
||||
return this.applyChangeset(builder);
|
||||
},
|
||||
return this.applyChangeset(builder);
|
||||
},
|
||||
|
||||
/*
|
||||
/*
|
||||
Toggles a line attribute for the specified line number
|
||||
If a line attribute with the specified name exists with any value it will be removed
|
||||
Otherwise it will be set to the given value
|
||||
|
@ -395,20 +387,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
@param attributeKey: the name of the attribute to toggle, e.g. list
|
||||
@param attributeValue: the value to pass to the attribute (e.g. indention level)
|
||||
*/
|
||||
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
|
||||
return this.getAttributeOnLine(lineNum, attributeName) ?
|
||||
this.removeAttributeOnLine(lineNum, attributeName) :
|
||||
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
||||
|
||||
toggleAttributeOnLine(lineNum, attributeName, attributeValue) {
|
||||
return this.getAttributeOnLine(lineNum, attributeName)
|
||||
? this.removeAttributeOnLine(lineNum, attributeName)
|
||||
: this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
||||
},
|
||||
|
||||
hasAttributeOnSelectionOrCaretPosition: function(attributeName) {
|
||||
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
|
||||
var hasAttrib;
|
||||
hasAttributeOnSelectionOrCaretPosition(attributeName) {
|
||||
const hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
|
||||
let hasAttrib;
|
||||
if (hasSelection) {
|
||||
hasAttrib = this.getAttributeOnSelection(attributeName);
|
||||
}else {
|
||||
var attributesOnCaretPosition = this.getAttributesOnCaret();
|
||||
} else {
|
||||
const attributesOnCaretPosition = this.getAttributesOnCaret();
|
||||
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
|
||||
}
|
||||
return hasAttrib;
|
||||
|
|
|
@ -28,28 +28,28 @@
|
|||
used to reference Attributes in Changesets.
|
||||
*/
|
||||
|
||||
var AttributePool = function () {
|
||||
const AttributePool = function () {
|
||||
this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
|
||||
this.attribToNum = {}; // e.g. {'foo,bar': 0}
|
||||
this.nextNum = 0;
|
||||
};
|
||||
|
||||
AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
|
||||
var str = String(attrib);
|
||||
const str = String(attrib);
|
||||
if (str in this.attribToNum) {
|
||||
return this.attribToNum[str];
|
||||
}
|
||||
if (dontAddIfAbsent) {
|
||||
return -1;
|
||||
}
|
||||
var num = this.nextNum++;
|
||||
const num = this.nextNum++;
|
||||
this.attribToNum[str] = num;
|
||||
this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
|
||||
return num;
|
||||
};
|
||||
|
||||
AttributePool.prototype.getAttrib = function (num) {
|
||||
var pair = this.numToAttrib[num];
|
||||
const pair = this.numToAttrib[num];
|
||||
if (!pair) {
|
||||
return pair;
|
||||
}
|
||||
|
@ -57,20 +57,20 @@ AttributePool.prototype.getAttrib = function (num) {
|
|||
};
|
||||
|
||||
AttributePool.prototype.getAttribKey = function (num) {
|
||||
var pair = this.numToAttrib[num];
|
||||
const pair = this.numToAttrib[num];
|
||||
if (!pair) return '';
|
||||
return pair[0];
|
||||
};
|
||||
|
||||
AttributePool.prototype.getAttribValue = function (num) {
|
||||
var pair = this.numToAttrib[num];
|
||||
const pair = this.numToAttrib[num];
|
||||
if (!pair) return '';
|
||||
return pair[1];
|
||||
};
|
||||
|
||||
AttributePool.prototype.eachAttrib = function (func) {
|
||||
for (var n in this.numToAttrib) {
|
||||
var pair = this.numToAttrib[n];
|
||||
for (const n in this.numToAttrib) {
|
||||
const pair = this.numToAttrib[n];
|
||||
func(pair[0], pair[1]);
|
||||
}
|
||||
};
|
||||
|
@ -78,7 +78,7 @@ AttributePool.prototype.eachAttrib = function (func) {
|
|||
AttributePool.prototype.toJsonable = function () {
|
||||
return {
|
||||
numToAttrib: this.numToAttrib,
|
||||
nextNum: this.nextNum
|
||||
nextNum: this.nextNum,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,7 @@ AttributePool.prototype.fromJsonable = function (obj) {
|
|||
this.numToAttrib = obj.numToAttrib;
|
||||
this.nextNum = obj.nextNum;
|
||||
this.attribToNum = {};
|
||||
for (var n in this.numToAttrib) {
|
||||
for (const n in this.numToAttrib) {
|
||||
this.attribToNum[String(this.numToAttrib[n])] = Number(n);
|
||||
}
|
||||
return this;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,40 +18,33 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
exports.buildRemoveRange = function(rep, builder, start, end) {
|
||||
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
|
||||
exports.buildRemoveRange = function (rep, builder, start, end) {
|
||||
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
const endLineOffset = rep.lines.offsetOfIndex(end[0]);
|
||||
|
||||
if (end[0] > start[0])
|
||||
{
|
||||
if (end[0] > start[0]) {
|
||||
builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
|
||||
builder.remove(end[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
builder.remove(end[1] - start[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.buildKeepRange = function(rep, builder, start, end, attribs, pool) {
|
||||
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
|
||||
exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) {
|
||||
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
const endLineOffset = rep.lines.offsetOfIndex(end[0]);
|
||||
|
||||
if (end[0] > start[0])
|
||||
{
|
||||
if (end[0] > start[0]) {
|
||||
builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
|
||||
builder.keep(end[1], 0, attribs, pool);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
builder.keep(end[1] - start[1], 0, attribs, pool);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.buildKeepToStartOfRange = function(rep, builder, start) {
|
||||
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
exports.buildKeepToStartOfRange = function (rep, builder, start) {
|
||||
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
|
||||
|
||||
builder.keep(startLineOffset, start[0]);
|
||||
builder.keep(start[1]);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -23,61 +23,57 @@
|
|||
// requires: top
|
||||
// requires: undefined
|
||||
|
||||
var KERNEL_SOURCE = '../static/js/require-kernel.js';
|
||||
const KERNEL_SOURCE = '../static/js/require-kernel.js';
|
||||
|
||||
Ace2Editor.registry = {
|
||||
nextId: 1
|
||||
nextId: 1,
|
||||
};
|
||||
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var pluginUtils = require('./pluginfw/shared');
|
||||
var _ = require('./underscore');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const pluginUtils = require('./pluginfw/shared');
|
||||
const _ = require('./underscore');
|
||||
|
||||
function scriptTag(source) {
|
||||
return (
|
||||
'<script type="text/javascript">\n'
|
||||
+ source.replace(/<\//g, '<\\/') +
|
||||
'</script>'
|
||||
)
|
||||
`<script type="text/javascript">\n${
|
||||
source.replace(/<\//g, '<\\/')
|
||||
}</script>`
|
||||
);
|
||||
}
|
||||
|
||||
function Ace2Editor() {
|
||||
var ace2 = Ace2Editor;
|
||||
const ace2 = Ace2Editor;
|
||||
|
||||
var editor = {};
|
||||
var info = {
|
||||
editor: editor,
|
||||
id: (ace2.registry.nextId++)
|
||||
const editor = {};
|
||||
let info = {
|
||||
editor,
|
||||
id: (ace2.registry.nextId++),
|
||||
};
|
||||
var loaded = false;
|
||||
let loaded = false;
|
||||
|
||||
var actionsPendingInit = [];
|
||||
let actionsPendingInit = [];
|
||||
|
||||
function pendingInit(func, optDoNow) {
|
||||
return function() {
|
||||
var that = this;
|
||||
var args = arguments;
|
||||
var action = function() {
|
||||
return function () {
|
||||
const that = this;
|
||||
const args = arguments;
|
||||
const action = function () {
|
||||
func.apply(that, args);
|
||||
}
|
||||
if (optDoNow)
|
||||
{
|
||||
};
|
||||
if (optDoNow) {
|
||||
optDoNow.apply(that, args);
|
||||
}
|
||||
if (loaded)
|
||||
{
|
||||
if (loaded) {
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
actionsPendingInit.push(action);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function doActionsPendingInit() {
|
||||
_.each(actionsPendingInit, function(fn,i){
|
||||
fn()
|
||||
_.each(actionsPendingInit, (fn, i) => {
|
||||
fn();
|
||||
});
|
||||
actionsPendingInit = [];
|
||||
}
|
||||
|
@ -86,43 +82,56 @@ function Ace2Editor() {
|
|||
|
||||
// The following functions (prefixed by 'ace_') are exposed by editor, but
|
||||
// execution is delayed until init is complete
|
||||
var aceFunctionsPendingInit = ['importText', 'importAText', 'focus',
|
||||
'setEditable', 'getFormattedCode', 'setOnKeyPress', 'setOnKeyDown',
|
||||
'setNotifyDirty', 'setProperty', 'setBaseText', 'setBaseAttributedText',
|
||||
'applyChangesToBase', 'applyPreparedChangesetToBase',
|
||||
'setUserChangeNotificationCallback', 'setAuthorInfo',
|
||||
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange'];
|
||||
const aceFunctionsPendingInit = ['importText',
|
||||
'importAText',
|
||||
'focus',
|
||||
'setEditable',
|
||||
'getFormattedCode',
|
||||
'setOnKeyPress',
|
||||
'setOnKeyDown',
|
||||
'setNotifyDirty',
|
||||
'setProperty',
|
||||
'setBaseText',
|
||||
'setBaseAttributedText',
|
||||
'applyChangesToBase',
|
||||
'applyPreparedChangesetToBase',
|
||||
'setUserChangeNotificationCallback',
|
||||
'setAuthorInfo',
|
||||
'setAuthorSelectionRange',
|
||||
'callWithAce',
|
||||
'execCommand',
|
||||
'replaceRange'];
|
||||
|
||||
_.each(aceFunctionsPendingInit, function(fnName,i){
|
||||
var prefix = 'ace_';
|
||||
var name = prefix + fnName;
|
||||
editor[fnName] = pendingInit(function(){
|
||||
if(fnName === "setAuthorInfo"){
|
||||
if(!arguments[0]){
|
||||
_.each(aceFunctionsPendingInit, (fnName, i) => {
|
||||
const prefix = 'ace_';
|
||||
const name = prefix + fnName;
|
||||
editor[fnName] = pendingInit(function () {
|
||||
if (fnName === 'setAuthorInfo') {
|
||||
if (!arguments[0]) {
|
||||
// setAuthorInfo AuthorId not set for some reason
|
||||
}else{
|
||||
} else {
|
||||
info[prefix + fnName].apply(this, arguments);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
info[prefix + fnName].apply(this, arguments);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
editor.exportText = function() {
|
||||
if (!loaded) return "(awaiting init)\n";
|
||||
editor.exportText = function () {
|
||||
if (!loaded) return '(awaiting init)\n';
|
||||
return info.ace_exportText();
|
||||
};
|
||||
|
||||
editor.getFrame = function() {
|
||||
editor.getFrame = function () {
|
||||
return info.frame || null;
|
||||
};
|
||||
|
||||
editor.getDebugProperty = function(prop) {
|
||||
editor.getDebugProperty = function (prop) {
|
||||
return info.ace_getDebugProperty(prop);
|
||||
};
|
||||
|
||||
editor.getInInternationalComposition = function() {
|
||||
editor.getInInternationalComposition = function () {
|
||||
if (!loaded) return false;
|
||||
return info.ace_getInInternationalComposition();
|
||||
};
|
||||
|
@ -136,26 +145,25 @@ function Ace2Editor() {
|
|||
// to prepareUserChangeset will return an updated changeset that takes into account the
|
||||
// latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase
|
||||
// accordingly.
|
||||
editor.prepareUserChangeset = function() {
|
||||
editor.prepareUserChangeset = function () {
|
||||
if (!loaded) return null;
|
||||
return info.ace_prepareUserChangeset();
|
||||
};
|
||||
|
||||
editor.getUnhandledErrors = function() {
|
||||
editor.getUnhandledErrors = function () {
|
||||
if (!loaded) return [];
|
||||
// returns array of {error: <browser Error object>, time: +new Date()}
|
||||
return info.ace_getUnhandledErrors();
|
||||
};
|
||||
|
||||
|
||||
|
||||
function sortFilesByEmbeded(files) {
|
||||
var embededFiles = [];
|
||||
var remoteFiles = [];
|
||||
const embededFiles = [];
|
||||
let remoteFiles = [];
|
||||
|
||||
if (Ace2Editor.EMBEDED) {
|
||||
for (var i = 0, ii = files.length; i < ii; i++) {
|
||||
var file = files[i];
|
||||
for (let i = 0, ii = files.length; i < ii; i++) {
|
||||
const file = files[i];
|
||||
if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) {
|
||||
embededFiles.push(file);
|
||||
} else {
|
||||
|
@ -169,9 +177,9 @@ function Ace2Editor() {
|
|||
return {embeded: embededFiles, remote: remoteFiles};
|
||||
}
|
||||
function pushStyleTagsFor(buffer, files) {
|
||||
var sorted = sortFilesByEmbeded(files);
|
||||
var embededFiles = sorted.embeded;
|
||||
var remoteFiles = sorted.remote;
|
||||
const sorted = sortFilesByEmbeded(files);
|
||||
const embededFiles = sorted.embeded;
|
||||
const remoteFiles = sorted.remote;
|
||||
|
||||
if (embededFiles.length > 0) {
|
||||
buffer.push('<style type="text/css">');
|
||||
|
@ -183,67 +191,66 @@ function Ace2Editor() {
|
|||
}
|
||||
for (var i = 0, ii = remoteFiles.length; i < ii; i++) {
|
||||
var file = remoteFiles[i];
|
||||
buffer.push('<link rel="stylesheet" type="text/css" href="' + encodeURI(file) + '"\/>');
|
||||
buffer.push(`<link rel="stylesheet" type="text/css" href="${encodeURI(file)}"\/>`);
|
||||
}
|
||||
}
|
||||
|
||||
editor.destroy = pendingInit(function() {
|
||||
editor.destroy = pendingInit(() => {
|
||||
info.ace_dispose();
|
||||
info.frame.parentNode.removeChild(info.frame);
|
||||
delete ace2.registry[info.id];
|
||||
info = null; // prevent IE 6 closure memory leaks
|
||||
});
|
||||
|
||||
editor.init = function(containerId, initialCode, doneFunc) {
|
||||
|
||||
editor.init = function (containerId, initialCode, doneFunc) {
|
||||
editor.importText(initialCode);
|
||||
|
||||
info.onEditorReady = function() {
|
||||
info.onEditorReady = function () {
|
||||
loaded = true;
|
||||
doActionsPendingInit();
|
||||
doneFunc();
|
||||
};
|
||||
|
||||
(function() {
|
||||
var doctype = "<!doctype html>";
|
||||
(function () {
|
||||
const doctype = '<!doctype html>';
|
||||
|
||||
var iframeHTML = [];
|
||||
const iframeHTML = [];
|
||||
|
||||
iframeHTML.push(doctype);
|
||||
iframeHTML.push("<html class='inner-editor " + clientVars.skinVariants + "'><head>");
|
||||
iframeHTML.push(`<html class='inner-editor ${clientVars.skinVariants}'><head>`);
|
||||
|
||||
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed
|
||||
// and compressed, putting the compressed code from the named file directly into the
|
||||
// source here.
|
||||
// these lines must conform to a specific format because they are passed by the build script:
|
||||
var includedCSS = [];
|
||||
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
|
||||
$$INCLUDE_CSS("../static/css/iframe_editor.css");
|
||||
var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
|
||||
$$INCLUDE_CSS('../static/css/iframe_editor.css');
|
||||
|
||||
// disableCustomScriptsAndStyles can be used to disable loading of custom scripts
|
||||
if(!clientVars.disableCustomScriptsAndStyles){
|
||||
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString);
|
||||
if (!clientVars.disableCustomScriptsAndStyles) {
|
||||
$$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
|
||||
}
|
||||
|
||||
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){
|
||||
var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
|
||||
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
|
||||
return path;
|
||||
}
|
||||
return '../static/plugins/' + path;
|
||||
return `../static/plugins/${path}`;
|
||||
});
|
||||
includedCSS = includedCSS.concat(additionalCSS);
|
||||
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString);
|
||||
$$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
|
||||
|
||||
pushStyleTagsFor(iframeHTML, includedCSS);
|
||||
|
||||
if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
|
||||
// Remotely src'd script tag will not work in IE; it must be embedded, so
|
||||
// throw an error if it is not.
|
||||
throw new Error("Require kernel could not be found.");
|
||||
throw new Error('Require kernel could not be found.');
|
||||
}
|
||||
|
||||
iframeHTML.push(scriptTag(
|
||||
Ace2Editor.EMBEDED[KERNEL_SOURCE] + '\n\
|
||||
`${Ace2Editor.EMBEDED[KERNEL_SOURCE]}\n\
|
||||
require.setRootURI("../javascripts/src");\n\
|
||||
require.setLibraryURI("../javascripts/lib");\n\
|
||||
require.setGlobalKeyPath("require");\n\
|
||||
|
@ -257,23 +264,23 @@ var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\
|
|||
plugins.ensure(function () {\n\
|
||||
Ace2Inner.init();\n\
|
||||
});\n\
|
||||
'));
|
||||
`));
|
||||
|
||||
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
|
||||
|
||||
hooks.callAll("aceInitInnerdocbodyHead", {
|
||||
iframeHTML: iframeHTML
|
||||
hooks.callAll('aceInitInnerdocbodyHead', {
|
||||
iframeHTML,
|
||||
});
|
||||
|
||||
iframeHTML.push('</head><body id="innerdocbody" class="innerdocbody" role="application" class="syntax" spellcheck="false"> </body></html>');
|
||||
|
||||
// Expose myself to global for my child frame.
|
||||
var thisFunctionsName = "ChildAccessibleAce2Editor";
|
||||
(function () {return this}())[thisFunctionsName] = Ace2Editor;
|
||||
const thisFunctionsName = 'ChildAccessibleAce2Editor';
|
||||
(function () { return this; }())[thisFunctionsName] = Ace2Editor;
|
||||
|
||||
var outerScript = '\
|
||||
editorId = ' + JSON.stringify(info.id) + ';\n\
|
||||
editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\
|
||||
const outerScript = `\
|
||||
editorId = ${JSON.stringify(info.id)};\n\
|
||||
editorInfo = parent[${JSON.stringify(thisFunctionsName)}].registry[editorId];\n\
|
||||
window.onload = function () {\n\
|
||||
window.onload = null;\n\
|
||||
setTimeout(function () {\n\
|
||||
|
@ -293,34 +300,35 @@ window.onload = function () {\n\
|
|||
};\n\
|
||||
var doc = iframe.contentWindow.document;\n\
|
||||
doc.open();\n\
|
||||
var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\
|
||||
var text = (${JSON.stringify(iframeHTML.join('\n'))});\n\
|
||||
doc.write(text);\n\
|
||||
doc.close();\n\
|
||||
}, 0);\n\
|
||||
}';
|
||||
}`;
|
||||
|
||||
var outerHTML = [doctype, '<html class="inner-editor outerdoc ' + clientVars.skinVariants + '"><head>']
|
||||
const outerHTML = [doctype, `<html class="inner-editor outerdoc ${clientVars.skinVariants}"><head>`];
|
||||
|
||||
var includedCSS = [];
|
||||
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
|
||||
$$INCLUDE_CSS("../static/css/iframe_editor.css");
|
||||
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString);
|
||||
var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
|
||||
$$INCLUDE_CSS('../static/css/iframe_editor.css');
|
||||
$$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
|
||||
|
||||
|
||||
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){
|
||||
var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
|
||||
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
|
||||
return path;
|
||||
}
|
||||
return '../static/plugins/' + path }
|
||||
return `../static/plugins/${path}`;
|
||||
},
|
||||
);
|
||||
includedCSS = includedCSS.concat(additionalCSS);
|
||||
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString);
|
||||
$$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
|
||||
|
||||
pushStyleTagsFor(outerHTML, includedCSS);
|
||||
|
||||
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
|
||||
// (throbs busy while typing)
|
||||
var pluginNames = pluginUtils.clientPluginNames();
|
||||
const pluginNames = pluginUtils.clientPluginNames();
|
||||
outerHTML.push(
|
||||
'<style type="text/css" title="dynamicsyntax"></style>',
|
||||
'<link rel="stylesheet" type="text/css" href="data:text/css,"/>',
|
||||
|
@ -331,14 +339,14 @@ window.onload = function () {\n\
|
|||
'<div id="linemetricsdiv">x</div>',
|
||||
'</body></html>');
|
||||
|
||||
var outerFrame = document.createElement("IFRAME");
|
||||
outerFrame.name = "ace_outer";
|
||||
const outerFrame = document.createElement('IFRAME');
|
||||
outerFrame.name = 'ace_outer';
|
||||
outerFrame.frameBorder = 0; // for IE
|
||||
outerFrame.title = "Ether";
|
||||
outerFrame.title = 'Ether';
|
||||
info.frame = outerFrame;
|
||||
document.getElementById(containerId).appendChild(outerFrame);
|
||||
|
||||
var editorDocument = outerFrame.contentWindow.document;
|
||||
const editorDocument = outerFrame.contentWindow.document;
|
||||
|
||||
editorDocument.open();
|
||||
editorDocument.write(outerHTML.join(''));
|
||||
|
|
|
@ -20,27 +20,27 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Security = require('./security');
|
||||
const Security = require('./security');
|
||||
|
||||
function isNodeText(node) {
|
||||
return (node.nodeType == 3);
|
||||
}
|
||||
|
||||
function object(o) {
|
||||
var f = function(){};
|
||||
const f = function () {};
|
||||
f.prototype = o;
|
||||
return new f();
|
||||
}
|
||||
|
||||
function getAssoc(obj, name) {
|
||||
return obj["_magicdom_" + name];
|
||||
return obj[`_magicdom_${name}`];
|
||||
}
|
||||
|
||||
function setAssoc(obj, name, value) {
|
||||
// note that in IE designMode, properties of a node can get
|
||||
// copied to new nodes that are spawned during editing; also,
|
||||
// properties representable in HTML text can survive copy-and-paste
|
||||
obj["_magicdom_" + name] = value;
|
||||
obj[`_magicdom_${name}`] = value;
|
||||
}
|
||||
|
||||
// "func" is a function over 0..(numItems-1) that is monotonically
|
||||
|
@ -52,11 +52,10 @@ function binarySearch(numItems, func) {
|
|||
if (numItems < 1) return 0;
|
||||
if (func(0)) return 0;
|
||||
if (!func(numItems - 1)) return numItems;
|
||||
var low = 0; // func(low) is always false
|
||||
var high = numItems - 1; // func(high) is always true
|
||||
while ((high - low) > 1)
|
||||
{
|
||||
var x = Math.floor((low + high) / 2); // x != low, x != high
|
||||
let low = 0; // func(low) is always false
|
||||
let high = numItems - 1; // func(high) is always true
|
||||
while ((high - low) > 1) {
|
||||
const x = Math.floor((low + high) / 2); // x != low, x != high
|
||||
if (func(x)) high = x;
|
||||
else low = x;
|
||||
}
|
||||
|
@ -64,7 +63,7 @@ function binarySearch(numItems, func) {
|
|||
}
|
||||
|
||||
function binarySearchInfinite(expectedLength, func) {
|
||||
var i = 0;
|
||||
let i = 0;
|
||||
while (!func(i)) i += expectedLength;
|
||||
return binarySearch(i, func);
|
||||
}
|
||||
|
@ -73,7 +72,7 @@ function htmlPrettyEscape(str) {
|
|||
return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
|
||||
}
|
||||
|
||||
var noop = function(){};
|
||||
const noop = function () {};
|
||||
|
||||
exports.isNodeText = isNodeText;
|
||||
exports.object = object;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,271 +1,262 @@
|
|||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
let socket;
|
||||
const loc = document.location;
|
||||
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
|
||||
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
|
||||
const pathComponents = location.pathname.split('/');
|
||||
// Strip admin/plugins
|
||||
const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
|
||||
const resource = `${baseURL.substring(1)}socket.io`;
|
||||
|
||||
var socket,
|
||||
loc = document.location,
|
||||
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
|
||||
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
|
||||
pathComponents = location.pathname.split('/'),
|
||||
// Strip admin/plugins
|
||||
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
|
||||
resource = baseURL.substring(1) + "socket.io";
|
||||
|
||||
//connect
|
||||
var room = url + "pluginfw/installer";
|
||||
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
|
||||
// connect
|
||||
const room = `${url}pluginfw/installer`;
|
||||
socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
|
||||
|
||||
function search(searchTerm, limit) {
|
||||
if(search.searchTerm != searchTerm) {
|
||||
search.offset = 0
|
||||
search.results = []
|
||||
search.end = false
|
||||
if (search.searchTerm != searchTerm) {
|
||||
search.offset = 0;
|
||||
search.results = [];
|
||||
search.end = false;
|
||||
}
|
||||
limit = limit? limit : search.limit
|
||||
limit = limit ? limit : search.limit;
|
||||
search.searchTerm = searchTerm;
|
||||
socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir});
|
||||
socket.emit('search', {searchTerm, offset: search.offset, limit, sortBy: search.sortBy, sortDir: search.sortDir});
|
||||
search.offset += limit;
|
||||
|
||||
$('#search-progress').show()
|
||||
search.messages.show('fetching')
|
||||
search.searching = true
|
||||
$('#search-progress').show();
|
||||
search.messages.show('fetching');
|
||||
search.searching = true;
|
||||
}
|
||||
search.searching = false;
|
||||
search.offset = 0;
|
||||
search.limit = 999;
|
||||
search.results = [];
|
||||
search.sortBy = 'name';
|
||||
search.sortDir = /*DESC?*/true;
|
||||
search.sortDir = /* DESC?*/true;
|
||||
search.end = true;// have we received all results already?
|
||||
search.messages = {
|
||||
show: function(msg) {
|
||||
//$('.search-results .messages').show()
|
||||
$('.search-results .messages .'+msg+'').show()
|
||||
$('.search-results .messages .'+msg+' *').show()
|
||||
show(msg) {
|
||||
// $('.search-results .messages').show()
|
||||
$(`.search-results .messages .${msg}`).show();
|
||||
$(`.search-results .messages .${msg} *`).show();
|
||||
},
|
||||
hide: function(msg) {
|
||||
$('.search-results .messages').hide()
|
||||
$('.search-results .messages .'+msg+'').hide()
|
||||
$('.search-results .messages .'+msg+' *').hide()
|
||||
}
|
||||
}
|
||||
hide(msg) {
|
||||
$('.search-results .messages').hide();
|
||||
$(`.search-results .messages .${msg}`).hide();
|
||||
$(`.search-results .messages .${msg} *`).hide();
|
||||
},
|
||||
};
|
||||
|
||||
var installed = {
|
||||
const installed = {
|
||||
progress: {
|
||||
show: function(plugin, msg) {
|
||||
$('.installed-results .'+plugin+' .progress').show()
|
||||
$('.installed-results .'+plugin+' .progress .message').text(msg)
|
||||
if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100)
|
||||
show(plugin, msg) {
|
||||
$(`.installed-results .${plugin} .progress`).show();
|
||||
$(`.installed-results .${plugin} .progress .message`).text(msg);
|
||||
if ($(window).scrollTop() > $(`.${plugin}`).offset().top)$(window).scrollTop($(`.${plugin}`).offset().top - 100);
|
||||
},
|
||||
hide(plugin) {
|
||||
$(`.installed-results .${plugin} .progress`).hide();
|
||||
$(`.installed-results .${plugin} .progress .message`).text('');
|
||||
},
|
||||
hide: function(plugin) {
|
||||
$('.installed-results .'+plugin+' .progress').hide()
|
||||
$('.installed-results .'+plugin+' .progress .message').text('')
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
show: function(msg) {
|
||||
$('.installed-results .messages').show()
|
||||
$('.installed-results .messages .'+msg+'').show()
|
||||
show(msg) {
|
||||
$('.installed-results .messages').show();
|
||||
$(`.installed-results .messages .${msg}`).show();
|
||||
},
|
||||
hide(msg) {
|
||||
$('.installed-results .messages').hide();
|
||||
$(`.installed-results .messages .${msg}`).hide();
|
||||
},
|
||||
hide: function(msg) {
|
||||
$('.installed-results .messages').hide()
|
||||
$('.installed-results .messages .'+msg+'').hide()
|
||||
}
|
||||
},
|
||||
list: []
|
||||
}
|
||||
list: [],
|
||||
};
|
||||
|
||||
function displayPluginList(plugins, container, template) {
|
||||
plugins.forEach(function(plugin) {
|
||||
var row = template.clone();
|
||||
plugins.forEach((plugin) => {
|
||||
const row = template.clone();
|
||||
|
||||
for (attr in plugin) {
|
||||
if(attr == "name"){ // Hack to rewrite URLS into name
|
||||
var link = $('<a>');
|
||||
link.attr('href', 'https://npmjs.org/package/'+plugin['name']);
|
||||
if (attr == 'name') { // Hack to rewrite URLS into name
|
||||
const link = $('<a>');
|
||||
link.attr('href', `https://npmjs.org/package/${plugin.name}`);
|
||||
link.attr('plugin', 'Plugin details');
|
||||
link.attr('target', '_blank');
|
||||
link.text(plugin['name'].substr(3));
|
||||
link.text(plugin.name.substr(3));
|
||||
row.find('.name').append(link);
|
||||
} else {
|
||||
row.find("." + attr).text(plugin[attr]);
|
||||
row.find(`.${attr}`).text(plugin[attr]);
|
||||
}
|
||||
}
|
||||
row.find(".version").text(plugin.version);
|
||||
row.addClass(plugin.name)
|
||||
row.data('plugin', plugin.name)
|
||||
row.find('.version').text(plugin.version);
|
||||
row.addClass(plugin.name);
|
||||
row.data('plugin', plugin.name);
|
||||
container.append(row);
|
||||
})
|
||||
});
|
||||
updateHandlers();
|
||||
}
|
||||
|
||||
function sortPluginList(plugins, property, /*ASC?*/dir) {
|
||||
return plugins.sort(function(a, b) {
|
||||
if (a[property] < b[property])
|
||||
return dir? -1 : 1;
|
||||
if (a[property] > b[property])
|
||||
return dir? 1 : -1;
|
||||
function sortPluginList(plugins, property, /* ASC?*/dir) {
|
||||
return plugins.sort((a, b) => {
|
||||
if (a[property] < b[property]) return dir ? -1 : 1;
|
||||
if (a[property] > b[property]) return dir ? 1 : -1;
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function updateHandlers() {
|
||||
// Search
|
||||
$("#search-query").unbind('keyup').keyup(function () {
|
||||
search($("#search-query").val());
|
||||
$('#search-query').unbind('keyup').keyup(() => {
|
||||
search($('#search-query').val());
|
||||
});
|
||||
|
||||
// Prevent form submit
|
||||
$('#search-query').parent().bind('submit', function() {
|
||||
return false;
|
||||
});
|
||||
$('#search-query').parent().bind('submit', () => false);
|
||||
|
||||
// update & install
|
||||
$(".do-install, .do-update").unbind('click').click(function (e) {
|
||||
var $row = $(e.target).closest("tr")
|
||||
, plugin = $row.data('plugin');
|
||||
if($(this).hasClass('do-install')) {
|
||||
$row.remove().appendTo('#installed-plugins')
|
||||
installed.progress.show(plugin, 'Installing')
|
||||
}else{
|
||||
installed.progress.show(plugin, 'Updating')
|
||||
$('.do-install, .do-update').unbind('click').click(function (e) {
|
||||
const $row = $(e.target).closest('tr');
|
||||
const plugin = $row.data('plugin');
|
||||
if ($(this).hasClass('do-install')) {
|
||||
$row.remove().appendTo('#installed-plugins');
|
||||
installed.progress.show(plugin, 'Installing');
|
||||
} else {
|
||||
installed.progress.show(plugin, 'Updating');
|
||||
}
|
||||
socket.emit("install", plugin);
|
||||
installed.messages.hide("nothing-installed")
|
||||
socket.emit('install', plugin);
|
||||
installed.messages.hide('nothing-installed');
|
||||
});
|
||||
|
||||
// uninstall
|
||||
$(".do-uninstall").unbind('click').click(function (e) {
|
||||
var $row = $(e.target).closest("tr")
|
||||
, pluginName = $row.data('plugin');
|
||||
socket.emit("uninstall", pluginName);
|
||||
installed.progress.show(pluginName, 'Uninstalling')
|
||||
installed.list = installed.list.filter(function(plugin) {
|
||||
return plugin.name != pluginName
|
||||
})
|
||||
$('.do-uninstall').unbind('click').click((e) => {
|
||||
const $row = $(e.target).closest('tr');
|
||||
const pluginName = $row.data('plugin');
|
||||
socket.emit('uninstall', pluginName);
|
||||
installed.progress.show(pluginName, 'Uninstalling');
|
||||
installed.list = installed.list.filter((plugin) => plugin.name != pluginName);
|
||||
});
|
||||
|
||||
// Sort
|
||||
$('.sort.up').unbind('click').click(function() {
|
||||
$('.sort.up').unbind('click').click(function () {
|
||||
search.sortBy = $(this).attr('data-label').toLowerCase();
|
||||
search.sortDir = false;
|
||||
search.offset = 0;
|
||||
search(search.searchTerm, search.results.length);
|
||||
search.results = [];
|
||||
})
|
||||
$('.sort.down, .sort.none').unbind('click').click(function() {
|
||||
});
|
||||
$('.sort.down, .sort.none').unbind('click').click(function () {
|
||||
search.sortBy = $(this).attr('data-label').toLowerCase();
|
||||
search.sortDir = true;
|
||||
search.offset = 0;
|
||||
search(search.searchTerm, search.results.length);
|
||||
search.results = [];
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('results:search', function (data) {
|
||||
if(!data.results.length) search.end = true;
|
||||
if(data.query.offset == 0) search.results = [];
|
||||
search.messages.hide('nothing-found')
|
||||
search.messages.hide('fetching')
|
||||
$("#search-query").removeAttr('disabled')
|
||||
socket.on('results:search', (data) => {
|
||||
if (!data.results.length) search.end = true;
|
||||
if (data.query.offset == 0) search.results = [];
|
||||
search.messages.hide('nothing-found');
|
||||
search.messages.hide('fetching');
|
||||
$('#search-query').removeAttr('disabled');
|
||||
|
||||
console.log('got search results', data)
|
||||
console.log('got search results', data);
|
||||
|
||||
// add to results
|
||||
search.results = search.results.concat(data.results);
|
||||
|
||||
// Update sorting head
|
||||
$('.sort')
|
||||
.removeClass('up down')
|
||||
.addClass('none');
|
||||
$('.search-results thead th[data-label='+data.query.sortBy+']')
|
||||
.removeClass('none')
|
||||
.addClass(data.query.sortDir? 'up' : 'down');
|
||||
.removeClass('up down')
|
||||
.addClass('none');
|
||||
$(`.search-results thead th[data-label=${data.query.sortBy}]`)
|
||||
.removeClass('none')
|
||||
.addClass(data.query.sortDir ? 'up' : 'down');
|
||||
|
||||
// re-render search results
|
||||
var searchWidget = $(".search-results");
|
||||
searchWidget.find(".results *").remove();
|
||||
if(search.results.length > 0) {
|
||||
displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr"))
|
||||
}else {
|
||||
search.messages.show('nothing-found')
|
||||
const searchWidget = $('.search-results');
|
||||
searchWidget.find('.results *').remove();
|
||||
if (search.results.length > 0) {
|
||||
displayPluginList(search.results, searchWidget.find('.results'), searchWidget.find('.template tr'));
|
||||
} else {
|
||||
search.messages.show('nothing-found');
|
||||
}
|
||||
search.messages.hide('fetching')
|
||||
$('#search-progress').hide()
|
||||
search.searching = false
|
||||
search.messages.hide('fetching');
|
||||
$('#search-progress').hide();
|
||||
search.searching = false;
|
||||
});
|
||||
|
||||
socket.on('results:installed', function (data) {
|
||||
installed.messages.hide("fetching")
|
||||
installed.messages.hide("nothing-installed")
|
||||
socket.on('results:installed', (data) => {
|
||||
installed.messages.hide('fetching');
|
||||
installed.messages.hide('nothing-installed');
|
||||
|
||||
installed.list = data.installed
|
||||
sortPluginList(installed.list, 'name', /*ASC?*/true);
|
||||
installed.list = data.installed;
|
||||
sortPluginList(installed.list, 'name', /* ASC?*/true);
|
||||
|
||||
// filter out epl
|
||||
installed.list = installed.list.filter(function(plugin) {
|
||||
return plugin.name != 'ep_etherpad-lite'
|
||||
})
|
||||
installed.list = installed.list.filter((plugin) => plugin.name != 'ep_etherpad-lite');
|
||||
|
||||
// remove all installed plugins (leave plugins that are still being installed)
|
||||
installed.list.forEach(function(plugin) {
|
||||
$('#installed-plugins .'+plugin.name).remove()
|
||||
})
|
||||
installed.list.forEach((plugin) => {
|
||||
$(`#installed-plugins .${plugin.name}`).remove();
|
||||
});
|
||||
|
||||
if(installed.list.length > 0) {
|
||||
displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template"));
|
||||
if (installed.list.length > 0) {
|
||||
displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template'));
|
||||
socket.emit('checkUpdates');
|
||||
}else {
|
||||
installed.messages.show("nothing-installed")
|
||||
} else {
|
||||
installed.messages.show('nothing-installed');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('results:updatable', function(data) {
|
||||
data.updatable.forEach(function(pluginName) {
|
||||
var $row = $('#installed-plugins > tr.'+pluginName)
|
||||
, actions = $row.find('.actions')
|
||||
actions.append('<input class="do-update" type="button" value="Update" />')
|
||||
})
|
||||
socket.on('results:updatable', (data) => {
|
||||
data.updatable.forEach((pluginName) => {
|
||||
const $row = $(`#installed-plugins > tr.${pluginName}`);
|
||||
const actions = $row.find('.actions');
|
||||
actions.append('<input class="do-update" type="button" value="Update" />');
|
||||
});
|
||||
updateHandlers();
|
||||
})
|
||||
});
|
||||
|
||||
socket.on('finished:install', function(data) {
|
||||
if(data.error) {
|
||||
if(data.code === "EPEERINVALID"){
|
||||
socket.on('finished:install', (data) => {
|
||||
if (data.error) {
|
||||
if (data.code === 'EPEERINVALID') {
|
||||
alert("This plugin requires that you update Etherpad so it can operate in it's true glory");
|
||||
}
|
||||
alert('An error occurred while installing '+data.plugin+' \n'+data.error)
|
||||
$('#installed-plugins .'+data.plugin).remove()
|
||||
alert(`An error occurred while installing ${data.plugin} \n${data.error}`);
|
||||
$(`#installed-plugins .${data.plugin}`).remove();
|
||||
}
|
||||
|
||||
socket.emit("getInstalled");
|
||||
socket.emit('getInstalled');
|
||||
|
||||
// update search results
|
||||
search.offset = 0;
|
||||
search(search.searchTerm, search.results.length);
|
||||
search.results = [];
|
||||
})
|
||||
});
|
||||
|
||||
socket.on('finished:uninstall', function(data) {
|
||||
if(data.error) alert('An error occurred while uninstalling the '+data.plugin+' \n'+data.error)
|
||||
socket.on('finished:uninstall', (data) => {
|
||||
if (data.error) alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`);
|
||||
|
||||
// remove plugin from installed list
|
||||
$('#installed-plugins .'+data.plugin).remove()
|
||||
$(`#installed-plugins .${data.plugin}`).remove();
|
||||
|
||||
socket.emit("getInstalled");
|
||||
socket.emit('getInstalled');
|
||||
|
||||
// update search results
|
||||
search.offset = 0;
|
||||
search(search.searchTerm, search.results.length);
|
||||
search.results = [];
|
||||
})
|
||||
});
|
||||
|
||||
// init
|
||||
updateHandlers();
|
||||
socket.emit("getInstalled");
|
||||
socket.emit('getInstalled');
|
||||
search('');
|
||||
|
||||
// check for updates every 5mins
|
||||
setInterval(function() {
|
||||
setInterval(() => {
|
||||
socket.emit('checkUpdates');
|
||||
}, 1000*60*5)
|
||||
}, 1000 * 60 * 5);
|
||||
});
|
||||
|
|
|
@ -1,81 +1,75 @@
|
|||
$(document).ready(function () {
|
||||
var socket,
|
||||
loc = document.location,
|
||||
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
|
||||
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
|
||||
pathComponents = location.pathname.split('/'),
|
||||
// Strip admin/plugins
|
||||
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
|
||||
resource = baseURL.substring(1) + "socket.io";
|
||||
$(document).ready(() => {
|
||||
let socket;
|
||||
const loc = document.location;
|
||||
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
|
||||
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
|
||||
const pathComponents = location.pathname.split('/');
|
||||
// Strip admin/plugins
|
||||
const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
|
||||
const resource = `${baseURL.substring(1)}socket.io`;
|
||||
|
||||
//connect
|
||||
var room = url + "settings";
|
||||
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
|
||||
|
||||
socket.on('settings', function (settings) {
|
||||
// connect
|
||||
const room = `${url}settings`;
|
||||
socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
|
||||
|
||||
socket.on('settings', (settings) => {
|
||||
/* Check whether the settings.json is authorized to be viewed */
|
||||
if(settings.results === 'NOT_ALLOWED') {
|
||||
if (settings.results === 'NOT_ALLOWED') {
|
||||
$('.innerwrapper').hide();
|
||||
$('.innerwrapper-err').show();
|
||||
$('.err-message').html("Settings json is not authorized to be viewed in Admin page!!");
|
||||
$('.err-message').html('Settings json is not authorized to be viewed in Admin page!!');
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check to make sure the JSON is clean before proceeding */
|
||||
if(isJSONClean(settings.results))
|
||||
{
|
||||
if (isJSONClean(settings.results)) {
|
||||
$('.settings').append(settings.results);
|
||||
$('.settings').focus();
|
||||
$('.settings').autosize();
|
||||
}
|
||||
else{
|
||||
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD");
|
||||
} else {
|
||||
alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
|
||||
}
|
||||
});
|
||||
|
||||
/* When the admin clicks save Settings check the JSON then send the JSON back to the server */
|
||||
$('#saveSettings').on('click', function(){
|
||||
var editedSettings = $('.settings').val();
|
||||
if(isJSONClean(editedSettings)){
|
||||
$('#saveSettings').on('click', () => {
|
||||
const editedSettings = $('.settings').val();
|
||||
if (isJSONClean(editedSettings)) {
|
||||
// JSON is clean so emit it to the server
|
||||
socket.emit("saveSettings", $('.settings').val());
|
||||
}else{
|
||||
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD")
|
||||
socket.emit('saveSettings', $('.settings').val());
|
||||
} else {
|
||||
alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
|
||||
$('.settings').focus();
|
||||
}
|
||||
});
|
||||
|
||||
/* Tell Etherpad Server to restart */
|
||||
$('#restartEtherpad').on('click', function(){
|
||||
socket.emit("restartServer");
|
||||
$('#restartEtherpad').on('click', () => {
|
||||
socket.emit('restartServer');
|
||||
});
|
||||
|
||||
socket.on('saveprogress', function(progress){
|
||||
socket.on('saveprogress', (progress) => {
|
||||
$('#response').show();
|
||||
$('#response').text(progress);
|
||||
$('#response').fadeOut('slow');
|
||||
});
|
||||
|
||||
socket.emit("load"); // Load the JSON from the server
|
||||
|
||||
socket.emit('load'); // Load the JSON from the server
|
||||
});
|
||||
|
||||
|
||||
function isJSONClean(data){
|
||||
var cleanSettings = JSON.minify(data);
|
||||
function isJSONClean(data) {
|
||||
let cleanSettings = JSON.minify(data);
|
||||
// this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}'
|
||||
cleanSettings = cleanSettings.replace(",]","]").replace(",}","}");
|
||||
try{
|
||||
cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}');
|
||||
try {
|
||||
var response = jQuery.parseJSON(cleanSettings);
|
||||
}
|
||||
catch(e){
|
||||
} catch (e) {
|
||||
return false; // the JSON failed to be parsed
|
||||
}
|
||||
if(typeof response !== 'object'){
|
||||
if (typeof response !== 'object') {
|
||||
return false;
|
||||
}else{
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,32 +20,30 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var makeCSSManager = require('./cssmanager').makeCSSManager;
|
||||
var domline = require('./domline').domline;
|
||||
var AttribPool = require('./AttributePool');
|
||||
var Changeset = require('./Changeset');
|
||||
var linestylefilter = require('./linestylefilter').linestylefilter;
|
||||
var colorutils = require('./colorutils').colorutils;
|
||||
var _ = require('./underscore');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
const makeCSSManager = require('./cssmanager').makeCSSManager;
|
||||
const domline = require('./domline').domline;
|
||||
const AttribPool = require('./AttributePool');
|
||||
const Changeset = require('./Changeset');
|
||||
const linestylefilter = require('./linestylefilter').linestylefilter;
|
||||
const colorutils = require('./colorutils').colorutils;
|
||||
const _ = require('./underscore');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
// These parameters were global, now they are injected. A reference to the
|
||||
// Timeslider controller would probably be more appropriate.
|
||||
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) {
|
||||
var changesetLoader = undefined;
|
||||
let changesetLoader = undefined;
|
||||
|
||||
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm
|
||||
if (!Array.prototype.indexOf)
|
||||
{
|
||||
Array.prototype.indexOf = function(elt /*, from*/ ) {
|
||||
var len = this.length >>> 0;
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function (elt /* , from*/) {
|
||||
const len = this.length >>> 0;
|
||||
|
||||
var from = Number(arguments[1]) || 0;
|
||||
let from = Number(arguments[1]) || 0;
|
||||
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
|
||||
if (from < 0) from += len;
|
||||
|
||||
for (; from < len; from++)
|
||||
{
|
||||
for (; from < len; from++) {
|
||||
if (from in this && this[from] === elt) return from;
|
||||
}
|
||||
return -1;
|
||||
|
@ -53,22 +51,19 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
}
|
||||
|
||||
function debugLog() {
|
||||
try
|
||||
{
|
||||
try {
|
||||
if (window.console) console.log.apply(console, arguments);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (window.console) console.log("error printing: ", e);
|
||||
} catch (e) {
|
||||
if (window.console) console.log('error printing: ', e);
|
||||
}
|
||||
}
|
||||
|
||||
//var socket;
|
||||
var channelState = "DISCONNECTED";
|
||||
// var socket;
|
||||
const channelState = 'DISCONNECTED';
|
||||
|
||||
var appLevelDisconnectReason = null;
|
||||
const appLevelDisconnectReason = null;
|
||||
|
||||
var padContents = {
|
||||
const padContents = {
|
||||
currentRevision: clientVars.collab_client_vars.rev,
|
||||
currentTime: clientVars.collab_client_vars.time,
|
||||
currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text),
|
||||
|
@ -76,13 +71,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
// to be filled in once the dom loads
|
||||
apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool),
|
||||
alines: Changeset.splitAttributionLines(
|
||||
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
|
||||
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
|
||||
|
||||
// generates a jquery element containing HTML for a line
|
||||
lineToElement: function(line, aline) {
|
||||
var element = document.createElement("div");
|
||||
var emptyLine = (line == '\n');
|
||||
var domInfo = domline.createDomLine(!emptyLine, true);
|
||||
lineToElement(line, aline) {
|
||||
const element = document.createElement('div');
|
||||
const emptyLine = (line == '\n');
|
||||
const domInfo = domline.createDomLine(!emptyLine, true);
|
||||
linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
|
||||
domInfo.prepareForAdd();
|
||||
element.className = domInfo.node.className;
|
||||
|
@ -91,35 +86,29 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
return $(element);
|
||||
},
|
||||
|
||||
applySpliceToDivs: function(start, numRemoved, newLines) {
|
||||
applySpliceToDivs(start, numRemoved, newLines) {
|
||||
// remove spliced-out lines from DOM
|
||||
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++)
|
||||
{
|
||||
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) {
|
||||
this.currentDivs[i].remove();
|
||||
}
|
||||
|
||||
// remove spliced-out line divs from currentDivs array
|
||||
this.currentDivs.splice(start, numRemoved);
|
||||
|
||||
var newDivs = [];
|
||||
for (var i = 0; i < newLines.length; i++)
|
||||
{
|
||||
const newDivs = [];
|
||||
for (var i = 0; i < newLines.length; i++) {
|
||||
newDivs.push(this.lineToElement(newLines[i], this.alines[start + i]));
|
||||
}
|
||||
|
||||
// grab the div just before the first one
|
||||
var startDiv = this.currentDivs[start - 1] || null;
|
||||
let startDiv = this.currentDivs[start - 1] || null;
|
||||
|
||||
// insert the div elements into the correct place, in the correct order
|
||||
for (var i = 0; i < newDivs.length; i++)
|
||||
{
|
||||
if (startDiv)
|
||||
{
|
||||
for (var i = 0; i < newDivs.length; i++) {
|
||||
if (startDiv) {
|
||||
startDiv.after(newDivs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#innerdocbody").prepend(newDivs[i]);
|
||||
} else {
|
||||
$('#innerdocbody').prepend(newDivs[i]);
|
||||
}
|
||||
startDiv = newDivs[i];
|
||||
}
|
||||
|
@ -132,10 +121,8 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
},
|
||||
|
||||
// splice the lines
|
||||
splice: function(start, numRemoved, newLinesVA) {
|
||||
var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) {
|
||||
return s;
|
||||
});
|
||||
splice(start, numRemoved, newLinesVA) {
|
||||
const newLines = _.map(Array.prototype.slice.call(arguments, 2), (s) => s);
|
||||
|
||||
// apply this splice to the divs
|
||||
this.applySpliceToDivs(start, numRemoved, newLines);
|
||||
|
@ -146,30 +133,26 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
this.currentLines.splice.apply(this.currentLines, arguments);
|
||||
},
|
||||
// returns the contents of the specified line I
|
||||
get: function(i) {
|
||||
get(i) {
|
||||
return this.currentLines[i];
|
||||
},
|
||||
// returns the number of lines in the document
|
||||
length: function() {
|
||||
length() {
|
||||
return this.currentLines.length;
|
||||
},
|
||||
|
||||
getActiveAuthors: function() {
|
||||
var self = this;
|
||||
var authors = [];
|
||||
var seenNums = {};
|
||||
var alines = self.alines;
|
||||
for (var i = 0; i < alines.length; i++)
|
||||
{
|
||||
Changeset.eachAttribNumber(alines[i], function(n) {
|
||||
if (!seenNums[n])
|
||||
{
|
||||
getActiveAuthors() {
|
||||
const self = this;
|
||||
const authors = [];
|
||||
const seenNums = {};
|
||||
const alines = self.alines;
|
||||
for (let i = 0; i < alines.length; i++) {
|
||||
Changeset.eachAttribNumber(alines[i], (n) => {
|
||||
if (!seenNums[n]) {
|
||||
seenNums[n] = true;
|
||||
if (self.apool.getAttribKey(n) == 'author')
|
||||
{
|
||||
var a = self.apool.getAttribValue(n);
|
||||
if (a)
|
||||
{
|
||||
if (self.apool.getAttribKey(n) == 'author') {
|
||||
const a = self.apool.getAttribValue(n);
|
||||
if (a) {
|
||||
authors.push(a);
|
||||
}
|
||||
}
|
||||
|
@ -178,27 +161,21 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
}
|
||||
authors.sort();
|
||||
return authors;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function callCatchingErrors(catcher, func) {
|
||||
try
|
||||
{
|
||||
try {
|
||||
wrapRecordingErrors(catcher, func)();
|
||||
}
|
||||
catch (e)
|
||||
{ /*absorb*/
|
||||
} catch (e) { /* absorb*/
|
||||
}
|
||||
}
|
||||
|
||||
function wrapRecordingErrors(catcher, func) {
|
||||
return function() {
|
||||
try
|
||||
{
|
||||
return function () {
|
||||
try {
|
||||
return func.apply(this, Array.prototype.slice.call(arguments));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
} catch (e) {
|
||||
// caughtErrors.push(e);
|
||||
// caughtErrorCatchers.push(catcher);
|
||||
// caughtErrorTimes.push(+new Date());
|
||||
|
@ -210,13 +187,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
}
|
||||
|
||||
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) {
|
||||
var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
|
||||
const broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
|
||||
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
|
||||
BroadcastSlider.setSliderLength(revisionInfo.latest);
|
||||
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
At this point, we must be certain that the changeset really does map from
|
||||
the current revision to the specified revision. Any mistakes here will
|
||||
cause the whole slider to get out of sync.
|
||||
|
@ -224,38 +201,34 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
|
||||
function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) {
|
||||
// disable the next 'gotorevision' call handled by a timeslider update
|
||||
if (!preventSliderMovement)
|
||||
{
|
||||
if (!preventSliderMovement) {
|
||||
goToRevisionIfEnabledCount++;
|
||||
BroadcastSlider.setSliderPosition(revision);
|
||||
}
|
||||
|
||||
let oldAlines = padContents.alines.slice();
|
||||
try
|
||||
{
|
||||
const oldAlines = padContents.alines.slice();
|
||||
try {
|
||||
// must mutate attribution lines before text lines
|
||||
Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
|
||||
// scroll to the area that is changed before the lines are mutated
|
||||
if($('#options-followContents').is(":checked") || $('#options-followContents').prop("checked")){
|
||||
if ($('#options-followContents').is(':checked') || $('#options-followContents').prop('checked')) {
|
||||
// get the index of the first line that has mutated attributes
|
||||
// the last line in `oldAlines` should always equal to "|1+1", ie newline without attributes
|
||||
// so it should be safe to assume this line has changed attributes when inserting content at
|
||||
// the bottom of a pad
|
||||
let lineChanged;
|
||||
_.some(oldAlines, function(line, index){
|
||||
if(line !== padContents.alines[index]){
|
||||
_.some(oldAlines, (line, index) => {
|
||||
if (line !== padContents.alines[index]) {
|
||||
lineChanged = index;
|
||||
return true; // break
|
||||
}
|
||||
})
|
||||
});
|
||||
// deal with someone is the author of a line and changes one character, so the alines won't change
|
||||
if(lineChanged === undefined) {
|
||||
if (lineChanged === undefined) {
|
||||
lineChanged = Changeset.opIterator(Changeset.unpack(changeset).ops).next().lines;
|
||||
}
|
||||
|
||||
|
@ -268,106 +241,94 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
|
||||
updateTimer();
|
||||
|
||||
var authors = _.map(padContents.getActiveAuthors(), function(name) {
|
||||
return authorData[name];
|
||||
});
|
||||
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
|
||||
|
||||
BroadcastSlider.setAuthors(authors);
|
||||
}
|
||||
|
||||
function updateTimer() {
|
||||
var zpad = function(str, length) {
|
||||
str = str + "";
|
||||
while (str.length < length)
|
||||
str = '0' + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
var date = new Date(padContents.currentTime);
|
||||
var dateFormat = function() {
|
||||
var month = zpad(date.getMonth() + 1, 2);
|
||||
var day = zpad(date.getDate(), 2);
|
||||
var year = (date.getFullYear());
|
||||
var hours = zpad(date.getHours(), 2);
|
||||
var minutes = zpad(date.getMinutes(), 2);
|
||||
var seconds = zpad(date.getSeconds(), 2);
|
||||
return (html10n.get("timeslider.dateformat", {
|
||||
"day": day,
|
||||
"month": month,
|
||||
"year": year,
|
||||
"hours": hours,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
const zpad = function (str, length) {
|
||||
str = `${str}`;
|
||||
while (str.length < length) str = `0${str}`;
|
||||
return str;
|
||||
};
|
||||
|
||||
const date = new Date(padContents.currentTime);
|
||||
const dateFormat = function () {
|
||||
const month = zpad(date.getMonth() + 1, 2);
|
||||
const day = zpad(date.getDate(), 2);
|
||||
const year = (date.getFullYear());
|
||||
const hours = zpad(date.getHours(), 2);
|
||||
const minutes = zpad(date.getMinutes(), 2);
|
||||
const seconds = zpad(date.getSeconds(), 2);
|
||||
return (html10n.get('timeslider.dateformat', {
|
||||
day,
|
||||
month,
|
||||
year,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
$('#timer').html(dateFormat());
|
||||
var revisionDate = html10n.get("timeslider.saved", {
|
||||
"day": date.getDate(),
|
||||
"month": [
|
||||
html10n.get("timeslider.month.january"),
|
||||
html10n.get("timeslider.month.february"),
|
||||
html10n.get("timeslider.month.march"),
|
||||
html10n.get("timeslider.month.april"),
|
||||
html10n.get("timeslider.month.may"),
|
||||
html10n.get("timeslider.month.june"),
|
||||
html10n.get("timeslider.month.july"),
|
||||
html10n.get("timeslider.month.august"),
|
||||
html10n.get("timeslider.month.september"),
|
||||
html10n.get("timeslider.month.october"),
|
||||
html10n.get("timeslider.month.november"),
|
||||
html10n.get("timeslider.month.december")
|
||||
][date.getMonth()],
|
||||
"year": date.getFullYear()
|
||||
const revisionDate = html10n.get('timeslider.saved', {
|
||||
day: date.getDate(),
|
||||
month: [
|
||||
html10n.get('timeslider.month.january'),
|
||||
html10n.get('timeslider.month.february'),
|
||||
html10n.get('timeslider.month.march'),
|
||||
html10n.get('timeslider.month.april'),
|
||||
html10n.get('timeslider.month.may'),
|
||||
html10n.get('timeslider.month.june'),
|
||||
html10n.get('timeslider.month.july'),
|
||||
html10n.get('timeslider.month.august'),
|
||||
html10n.get('timeslider.month.september'),
|
||||
html10n.get('timeslider.month.october'),
|
||||
html10n.get('timeslider.month.november'),
|
||||
html10n.get('timeslider.month.december'),
|
||||
][date.getMonth()],
|
||||
year: date.getFullYear(),
|
||||
});
|
||||
$('#revision_date').html(revisionDate)
|
||||
|
||||
$('#revision_date').html(revisionDate);
|
||||
}
|
||||
|
||||
updateTimer();
|
||||
|
||||
function goToRevision(newRevision) {
|
||||
padContents.targetRevision = newRevision;
|
||||
var self = this;
|
||||
var path = revisionInfo.getPath(padContents.currentRevision, newRevision);
|
||||
const self = this;
|
||||
const path = revisionInfo.getPath(padContents.currentRevision, newRevision);
|
||||
|
||||
hooks.aCallAll('goToRevisionEvent', {
|
||||
rev: newRevision
|
||||
rev: newRevision,
|
||||
});
|
||||
|
||||
if (path.status == 'complete')
|
||||
{
|
||||
if (path.status == 'complete') {
|
||||
var cs = path.changesets;
|
||||
var changeset = cs[0];
|
||||
var timeDelta = path.times[0];
|
||||
for (var i = 1; i < cs.length; i++)
|
||||
{
|
||||
for (var i = 1; i < cs.length; i++) {
|
||||
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
|
||||
timeDelta += path.times[i];
|
||||
}
|
||||
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
|
||||
}
|
||||
else if (path.status == "partial")
|
||||
{
|
||||
var sliderLocation = padContents.currentRevision;
|
||||
} else if (path.status == 'partial') {
|
||||
const sliderLocation = padContents.currentRevision;
|
||||
// callback is called after changeset information is pulled from server
|
||||
// this may never get called, if the changeset has already been loaded
|
||||
var update = function(start, end) {
|
||||
// if we've called goToRevision in the time since, don't goToRevision
|
||||
goToRevision(padContents.targetRevision);
|
||||
};
|
||||
const update = function (start, end) {
|
||||
// if we've called goToRevision in the time since, don't goToRevision
|
||||
goToRevision(padContents.targetRevision);
|
||||
};
|
||||
|
||||
// do our best with what we have...
|
||||
var cs = path.changesets;
|
||||
|
||||
var changeset = cs[0];
|
||||
var timeDelta = path.times[0];
|
||||
for (var i = 1; i < cs.length; i++)
|
||||
{
|
||||
for (var i = 1; i < cs.length; i++) {
|
||||
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
|
||||
timeDelta += path.times[i];
|
||||
}
|
||||
|
@ -379,21 +340,17 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
loadChangesetsForRevision(padContents.currentRevision - 1);
|
||||
}
|
||||
|
||||
var authors = _.map(padContents.getActiveAuthors(), function(name){
|
||||
return authorData[name];
|
||||
});
|
||||
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
|
||||
BroadcastSlider.setAuthors(authors);
|
||||
}
|
||||
|
||||
function loadChangesetsForRevision(revision, callback) {
|
||||
if (BroadcastSlider.getSliderLength() > 10000)
|
||||
{
|
||||
if (BroadcastSlider.getSliderLength() > 10000) {
|
||||
var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10
|
||||
changesetLoader.queueUp(start, 100);
|
||||
}
|
||||
|
||||
if (BroadcastSlider.getSliderLength() > 1000)
|
||||
{
|
||||
if (BroadcastSlider.getSliderLength() > 1000) {
|
||||
var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1
|
||||
changesetLoader.queueUp(start, 10);
|
||||
}
|
||||
|
@ -410,167 +367,146 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
requestQueue2: [],
|
||||
requestQueue3: [],
|
||||
reqCallbacks: [],
|
||||
queueUp: function(revision, width, callback) {
|
||||
queueUp(revision, width, callback) {
|
||||
if (revision < 0) revision = 0;
|
||||
// if(changesetLoader.requestQueue.indexOf(revision) != -1)
|
||||
// return; // already in the queue.
|
||||
if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server
|
||||
changesetLoader.resolved.push(revision + "_" + width);
|
||||
if (changesetLoader.resolved.indexOf(`${revision}_${width}`) != -1) return; // already loaded from the server
|
||||
changesetLoader.resolved.push(`${revision}_${width}`);
|
||||
|
||||
var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
|
||||
const requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
|
||||
requestQueue.push(
|
||||
{
|
||||
'rev': revision,
|
||||
'res': width,
|
||||
'callback': callback
|
||||
});
|
||||
if (!changesetLoader.running)
|
||||
{
|
||||
{
|
||||
rev: revision,
|
||||
res: width,
|
||||
callback,
|
||||
});
|
||||
if (!changesetLoader.running) {
|
||||
changesetLoader.running = true;
|
||||
setTimeout(changesetLoader.loadFromQueue, 10);
|
||||
}
|
||||
},
|
||||
loadFromQueue: function() {
|
||||
var self = changesetLoader;
|
||||
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
|
||||
loadFromQueue() {
|
||||
const self = changesetLoader;
|
||||
const requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
|
||||
|
||||
if (!requestQueue)
|
||||
{
|
||||
if (!requestQueue) {
|
||||
self.running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var request = requestQueue.pop();
|
||||
var granularity = request.res;
|
||||
var callback = request.callback;
|
||||
var start = request.rev;
|
||||
var requestID = Math.floor(Math.random() * 100000);
|
||||
const request = requestQueue.pop();
|
||||
const granularity = request.res;
|
||||
const callback = request.callback;
|
||||
const start = request.rev;
|
||||
const requestID = Math.floor(Math.random() * 100000);
|
||||
|
||||
sendSocketMsg("CHANGESET_REQ", {
|
||||
"start": start,
|
||||
"granularity": granularity,
|
||||
"requestID": requestID
|
||||
sendSocketMsg('CHANGESET_REQ', {
|
||||
start,
|
||||
granularity,
|
||||
requestID,
|
||||
});
|
||||
|
||||
self.reqCallbacks[requestID] = callback;
|
||||
},
|
||||
handleSocketResponse: function(message) {
|
||||
var self = changesetLoader;
|
||||
handleSocketResponse(message) {
|
||||
const self = changesetLoader;
|
||||
|
||||
var start = message.data.start;
|
||||
var granularity = message.data.granularity;
|
||||
var callback = self.reqCallbacks[message.data.requestID];
|
||||
const start = message.data.start;
|
||||
const granularity = message.data.granularity;
|
||||
const callback = self.reqCallbacks[message.data.requestID];
|
||||
delete self.reqCallbacks[message.data.requestID];
|
||||
|
||||
self.handleResponse(message.data, start, granularity, callback);
|
||||
setTimeout(self.loadFromQueue, 10);
|
||||
},
|
||||
handleResponse: function(data, start, granularity, callback) {
|
||||
var pool = (new AttribPool()).fromJsonable(data.apool);
|
||||
for (var i = 0; i < data.forwardsChangesets.length; i++)
|
||||
{
|
||||
var astart = start + i * granularity - 1; // rev -1 is a blank single line
|
||||
var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
|
||||
handleResponse(data, start, granularity, callback) {
|
||||
const pool = (new AttribPool()).fromJsonable(data.apool);
|
||||
for (let i = 0; i < data.forwardsChangesets.length; i++) {
|
||||
const astart = start + i * granularity - 1; // rev -1 is a blank single line
|
||||
let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
|
||||
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
|
||||
//debugLog("adding changeset:", astart, aend);
|
||||
var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
|
||||
var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
|
||||
// debugLog("adding changeset:", astart, aend);
|
||||
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
|
||||
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
|
||||
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
|
||||
}
|
||||
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
|
||||
},
|
||||
handleMessageFromServer: function (obj) {
|
||||
if (obj.type == "COLLABROOM")
|
||||
{
|
||||
handleMessageFromServer(obj) {
|
||||
if (obj.type == 'COLLABROOM') {
|
||||
obj = obj.data;
|
||||
|
||||
if (obj.type == "NEW_CHANGES")
|
||||
{
|
||||
var changeset = Changeset.moveOpsToNewPool(
|
||||
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
if (obj.type == 'NEW_CHANGES') {
|
||||
const changeset = Changeset.moveOpsToNewPool(
|
||||
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.inverse(
|
||||
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
|
||||
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.moveOpsToNewPool(
|
||||
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
|
||||
}
|
||||
else if (obj.type == "NEW_AUTHORDATA")
|
||||
{
|
||||
var authorMap = {};
|
||||
} else if (obj.type == 'NEW_AUTHORDATA') {
|
||||
const authorMap = {};
|
||||
authorMap[obj.author] = obj.data;
|
||||
receiveAuthorData(authorMap);
|
||||
|
||||
var authors = _.map(padContents.getActiveAuthors(), function(name) {
|
||||
return authorData[name];
|
||||
});
|
||||
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
|
||||
|
||||
BroadcastSlider.setAuthors(authors);
|
||||
}
|
||||
else if (obj.type == "NEW_SAVEDREV")
|
||||
{
|
||||
var savedRev = obj.savedRev;
|
||||
} else if (obj.type == 'NEW_SAVEDREV') {
|
||||
const savedRev = obj.savedRev;
|
||||
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
|
||||
}
|
||||
hooks.callAll('handleClientTimesliderMessage_' + obj.type, {payload: obj});
|
||||
}
|
||||
else if(obj.type == "CHANGESET_REQ")
|
||||
{
|
||||
hooks.callAll(`handleClientTimesliderMessage_${obj.type}`, {payload: obj});
|
||||
} else if (obj.type == 'CHANGESET_REQ') {
|
||||
changesetLoader.handleSocketResponse(obj);
|
||||
} else {
|
||||
debugLog(`Unknown message type: ${obj.type}`);
|
||||
}
|
||||
else
|
||||
{
|
||||
debugLog("Unknown message type: " + obj.type);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// to start upon window load, just push a function onto this array
|
||||
//window['onloadFuncts'].push(setUpSocket);
|
||||
//window['onloadFuncts'].push(function ()
|
||||
fireWhenAllScriptsAreLoaded.push(function() {
|
||||
// window['onloadFuncts'].push(setUpSocket);
|
||||
// window['onloadFuncts'].push(function ()
|
||||
fireWhenAllScriptsAreLoaded.push(() => {
|
||||
// set up the currentDivs and DOM
|
||||
padContents.currentDivs = [];
|
||||
$("#innerdocbody").html("");
|
||||
for (var i = 0; i < padContents.currentLines.length; i++)
|
||||
{
|
||||
var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
|
||||
$('#innerdocbody').html('');
|
||||
for (let i = 0; i < padContents.currentLines.length; i++) {
|
||||
const div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
|
||||
padContents.currentDivs.push(div);
|
||||
$("#innerdocbody").append(div);
|
||||
$('#innerdocbody').append(div);
|
||||
}
|
||||
});
|
||||
|
||||
// this is necessary to keep infinite loops of events firing,
|
||||
// since goToRevision changes the slider position
|
||||
var goToRevisionIfEnabledCount = 0;
|
||||
var goToRevisionIfEnabled = function() {
|
||||
if (goToRevisionIfEnabledCount > 0)
|
||||
{
|
||||
const goToRevisionIfEnabled = function () {
|
||||
if (goToRevisionIfEnabledCount > 0) {
|
||||
goToRevisionIfEnabledCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
goToRevision.apply(goToRevision, arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BroadcastSlider.onSlider(goToRevisionIfEnabled);
|
||||
|
||||
var dynamicCSS = makeCSSManager('dynamicsyntax');
|
||||
const dynamicCSS = makeCSSManager('dynamicsyntax');
|
||||
var authorData = {};
|
||||
|
||||
function receiveAuthorData(newAuthorData) {
|
||||
for (var author in newAuthorData)
|
||||
{
|
||||
var data = newAuthorData[author];
|
||||
var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId;
|
||||
if (bgcolor && dynamicCSS)
|
||||
{
|
||||
var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author));
|
||||
selector.backgroundColor = bgcolor
|
||||
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part
|
||||
for (const author in newAuthorData) {
|
||||
const data = newAuthorData[author];
|
||||
const bgcolor = typeof data.colorId === 'number' ? clientVars.colorPalette[data.colorId] : data.colorId;
|
||||
if (bgcolor && dynamicCSS) {
|
||||
const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`);
|
||||
selector.backgroundColor = bgcolor;
|
||||
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; // see ace2_inner.js for the other part
|
||||
}
|
||||
authorData[author] = data;
|
||||
}
|
||||
|
@ -580,15 +516,15 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
|
|||
|
||||
return changesetLoader;
|
||||
|
||||
function goToLineNumber(lineNumber){
|
||||
function goToLineNumber(lineNumber) {
|
||||
// Sets the Y scrolling of the browser to go to this line
|
||||
var line = $('#innerdocbody').find("div:nth-child("+(lineNumber+1)+")");
|
||||
var newY = $(line)[0].offsetTop;
|
||||
var ecb = document.getElementById('editorcontainerbox');
|
||||
const line = $('#innerdocbody').find(`div:nth-child(${lineNumber + 1})`);
|
||||
const newY = $(line)[0].offsetTop;
|
||||
const ecb = document.getElementById('editorcontainerbox');
|
||||
// Chrome 55 - 59 bugfix
|
||||
if(ecb.scrollTo){
|
||||
if (ecb.scrollTo) {
|
||||
ecb.scrollTo({top: newY, behavior: 'smooth'});
|
||||
}else{
|
||||
} else {
|
||||
$('#editorcontainerbox').scrollTop(newY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,68 +29,60 @@ function loadBroadcastRevisionsJS() {
|
|||
this.changesets = [];
|
||||
}
|
||||
|
||||
Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta) {
|
||||
var changesetWrapper = {
|
||||
Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta) {
|
||||
const changesetWrapper = {
|
||||
deltaRev: destIndex - this.rev,
|
||||
deltaTime: timeDelta,
|
||||
getValue: function() {
|
||||
getValue() {
|
||||
return changeset;
|
||||
}
|
||||
},
|
||||
};
|
||||
this.changesets.push(changesetWrapper);
|
||||
this.changesets.sort(function(a, b) {
|
||||
return (b.deltaRev - a.deltaRev)
|
||||
});
|
||||
}
|
||||
this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
|
||||
};
|
||||
|
||||
revisionInfo = {};
|
||||
revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta) {
|
||||
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
|
||||
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
|
||||
const startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
const endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
|
||||
startRevision.addChangeset(toIndex, changeset, timeDelta);
|
||||
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
|
||||
}
|
||||
};
|
||||
|
||||
revisionInfo.latest = clientVars.collab_client_vars.rev || -1;
|
||||
|
||||
revisionInfo.createNew = function(index) {
|
||||
revisionInfo.createNew = function (index) {
|
||||
revisionInfo[index] = new Revision(index);
|
||||
if (index > revisionInfo.latest)
|
||||
{
|
||||
if (index > revisionInfo.latest) {
|
||||
revisionInfo.latest = index;
|
||||
}
|
||||
|
||||
return revisionInfo[index];
|
||||
}
|
||||
};
|
||||
|
||||
// assuming that there is a path from fromIndex to toIndex, and that the links
|
||||
// are laid out in a skip-list format
|
||||
revisionInfo.getPath = function(fromIndex, toIndex) {
|
||||
var changesets = [];
|
||||
var spans = [];
|
||||
var times = [];
|
||||
var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
if (elem.changesets.length != 0 && fromIndex != toIndex)
|
||||
{
|
||||
var reverse = !(fromIndex < toIndex)
|
||||
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse))
|
||||
{
|
||||
var couldNotContinue = false;
|
||||
var oldRev = elem.rev;
|
||||
revisionInfo.getPath = function (fromIndex, toIndex) {
|
||||
const changesets = [];
|
||||
const spans = [];
|
||||
const times = [];
|
||||
let elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
if (elem.changesets.length != 0 && fromIndex != toIndex) {
|
||||
const reverse = !(fromIndex < toIndex);
|
||||
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) {
|
||||
let couldNotContinue = false;
|
||||
const oldRev = elem.rev;
|
||||
|
||||
for (var i = reverse ? elem.changesets.length - 1 : 0;
|
||||
reverse ? i >= 0 : i < elem.changesets.length;
|
||||
i += reverse ? -1 : 1)
|
||||
{
|
||||
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse))
|
||||
{
|
||||
for (let i = reverse ? elem.changesets.length - 1 : 0;
|
||||
reverse ? i >= 0 : i < elem.changesets.length;
|
||||
i += reverse ? -1 : 1) {
|
||||
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse)) {
|
||||
couldNotContinue = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse))
|
||||
{
|
||||
var topush = elem.changesets[i];
|
||||
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse)) {
|
||||
const topush = elem.changesets[i];
|
||||
changesets.push(topush.getValue());
|
||||
spans.push(elem.changesets[i].deltaRev);
|
||||
times.push(topush.deltaTime);
|
||||
|
@ -103,18 +95,18 @@ function loadBroadcastRevisionsJS() {
|
|||
}
|
||||
}
|
||||
|
||||
var status = 'partial';
|
||||
let status = 'partial';
|
||||
if (elem.rev == toIndex) status = 'complete';
|
||||
|
||||
return {
|
||||
'fromRev': fromIndex,
|
||||
'rev': elem.rev,
|
||||
'status': status,
|
||||
'changesets': changesets,
|
||||
'spans': spans,
|
||||
'times': times
|
||||
fromRev: fromIndex,
|
||||
rev: elem.rev,
|
||||
status,
|
||||
changesets,
|
||||
spans,
|
||||
times,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;
|
||||
|
|
|
@ -20,62 +20,60 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// These parameters were global, now they are injected. A reference to the
|
||||
// Timeslider controller would probably be more appropriate.
|
||||
var _ = require('./underscore');
|
||||
var padmodals = require('./pad_modals').padmodals;
|
||||
var colorutils = require('./colorutils').colorutils;
|
||||
// These parameters were global, now they are injected. A reference to the
|
||||
// Timeslider controller would probably be more appropriate.
|
||||
const _ = require('./underscore');
|
||||
const padmodals = require('./pad_modals').padmodals;
|
||||
const colorutils = require('./colorutils').colorutils;
|
||||
|
||||
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
|
||||
var BroadcastSlider;
|
||||
let BroadcastSlider;
|
||||
|
||||
// Hack to ensure timeslider i18n values are in
|
||||
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get("timeslider.toolbar.returnbutton"));
|
||||
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get('timeslider.toolbar.returnbutton'));
|
||||
|
||||
(function() { // wrap this code in its own namespace
|
||||
var sliderLength = 1000;
|
||||
var sliderPos = 0;
|
||||
var sliderActive = false;
|
||||
var slidercallbacks = [];
|
||||
var savedRevisions = [];
|
||||
var sliderPlaying = false;
|
||||
(function () { // wrap this code in its own namespace
|
||||
let sliderLength = 1000;
|
||||
let sliderPos = 0;
|
||||
let sliderActive = false;
|
||||
const slidercallbacks = [];
|
||||
const savedRevisions = [];
|
||||
let sliderPlaying = false;
|
||||
|
||||
var _callSliderCallbacks = function(newval) {
|
||||
sliderPos = newval;
|
||||
for (var i = 0; i < slidercallbacks.length; i++)
|
||||
{
|
||||
slidercallbacks[i](newval);
|
||||
}
|
||||
const _callSliderCallbacks = function (newval) {
|
||||
sliderPos = newval;
|
||||
for (let i = 0; i < slidercallbacks.length; i++) {
|
||||
slidercallbacks[i](newval);
|
||||
}
|
||||
};
|
||||
|
||||
var updateSliderElements = function() {
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var position = parseInt(savedRevisions[i].attr('pos'));
|
||||
savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
}
|
||||
$("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
|
||||
const updateSliderElements = function () {
|
||||
for (let i = 0; i < savedRevisions.length; i++) {
|
||||
const position = parseInt(savedRevisions[i].attr('pos'));
|
||||
savedRevisions[i].css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
}
|
||||
$('#ui-slider-handle').css('left', sliderPos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
|
||||
};
|
||||
|
||||
var addSavedRevision = function(position, info) {
|
||||
var newSavedRevision = $('<div></div>');
|
||||
newSavedRevision.addClass("star");
|
||||
const addSavedRevision = function (position, info) {
|
||||
const newSavedRevision = $('<div></div>');
|
||||
newSavedRevision.addClass('star');
|
||||
|
||||
newSavedRevision.attr('pos', position);
|
||||
newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
$("#ui-slider-bar").append(newSavedRevision);
|
||||
newSavedRevision.mouseup(function(evt) {
|
||||
BroadcastSlider.setSliderPosition(position);
|
||||
});
|
||||
savedRevisions.push(newSavedRevision);
|
||||
};
|
||||
newSavedRevision.attr('pos', position);
|
||||
newSavedRevision.css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
$('#ui-slider-bar').append(newSavedRevision);
|
||||
newSavedRevision.mouseup((evt) => {
|
||||
BroadcastSlider.setSliderPosition(position);
|
||||
});
|
||||
savedRevisions.push(newSavedRevision);
|
||||
};
|
||||
|
||||
var removeSavedRevision = function(position) {
|
||||
var element = $("div.star [pos=" + position + "]");
|
||||
savedRevisions.remove(element);
|
||||
element.remove();
|
||||
return element;
|
||||
};
|
||||
const removeSavedRevision = function (position) {
|
||||
const element = $(`div.star [pos=${position}]`);
|
||||
savedRevisions.remove(element);
|
||||
element.remove();
|
||||
return element;
|
||||
};
|
||||
|
||||
/* Begin small 'API' */
|
||||
|
||||
|
@ -90,19 +88,19 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
|
|||
function setSliderPosition(newpos) {
|
||||
newpos = Number(newpos);
|
||||
if (newpos < 0 || newpos > sliderLength) return;
|
||||
if(!newpos){
|
||||
if (!newpos) {
|
||||
newpos = 0; // stops it from displaying NaN if newpos isn't set
|
||||
}
|
||||
window.location.hash = "#" + newpos;
|
||||
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
|
||||
$("a.tlink").map(function() {
|
||||
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
|
||||
window.location.hash = `#${newpos}`;
|
||||
$('#ui-slider-handle').css('left', newpos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
|
||||
$('a.tlink').map(function () {
|
||||
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
|
||||
});
|
||||
|
||||
$("#revision_label").html(html10n.get("timeslider.version", { "version": newpos}));
|
||||
$('#revision_label').html(html10n.get('timeslider.version', {version: newpos}));
|
||||
|
||||
$("#leftstar, #leftstep").toggleClass('disabled', newpos == 0);
|
||||
$("#rightstar, #rightstep").toggleClass('disabled', newpos == sliderLength);
|
||||
$('#leftstar, #leftstep').toggleClass('disabled', newpos == 0);
|
||||
$('#rightstar, #rightstep').toggleClass('disabled', newpos == sliderLength);
|
||||
|
||||
sliderPos = newpos;
|
||||
_callSliderCallbacks(newpos);
|
||||
|
@ -120,89 +118,80 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
|
|||
// just take over the whole slider screen with a reconnect message
|
||||
|
||||
function showReconnectUI() {
|
||||
padmodals.showModal("disconnected");
|
||||
padmodals.showModal('disconnected');
|
||||
}
|
||||
|
||||
function setAuthors(authors) {
|
||||
var authorsList = $("#authorsList");
|
||||
const authorsList = $('#authorsList');
|
||||
authorsList.empty();
|
||||
var numAnonymous = 0;
|
||||
var numNamed = 0;
|
||||
var colorsAnonymous = [];
|
||||
_.each(authors, function(author) {
|
||||
if(author)
|
||||
{
|
||||
var authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
|
||||
if (author.name)
|
||||
{
|
||||
let numAnonymous = 0;
|
||||
let numNamed = 0;
|
||||
const colorsAnonymous = [];
|
||||
_.each(authors, (author) => {
|
||||
if (author) {
|
||||
const authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
|
||||
if (author.name) {
|
||||
if (numNamed !== 0) authorsList.append(', ');
|
||||
var textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName)
|
||||
const textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName);
|
||||
$('<span />')
|
||||
.text(author.name || "unnamed")
|
||||
.css('background-color', authorColor)
|
||||
.css('color', textColor)
|
||||
.addClass('author')
|
||||
.appendTo(authorsList);
|
||||
.text(author.name || 'unnamed')
|
||||
.css('background-color', authorColor)
|
||||
.css('color', textColor)
|
||||
.addClass('author')
|
||||
.appendTo(authorsList);
|
||||
|
||||
numNamed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
numAnonymous++;
|
||||
if(authorColor) colorsAnonymous.push(authorColor);
|
||||
if (authorColor) colorsAnonymous.push(authorColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (numAnonymous > 0)
|
||||
{
|
||||
var anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous });
|
||||
if (numAnonymous > 0) {
|
||||
const anonymousAuthorString = html10n.get('timeslider.unnamedauthors', {num: numAnonymous});
|
||||
|
||||
if (numNamed !== 0){
|
||||
authorsList.append(' + ' + anonymousAuthorString);
|
||||
if (numNamed !== 0) {
|
||||
authorsList.append(` + ${anonymousAuthorString}`);
|
||||
} else {
|
||||
authorsList.append(anonymousAuthorString);
|
||||
}
|
||||
|
||||
if(colorsAnonymous.length > 0){
|
||||
if (colorsAnonymous.length > 0) {
|
||||
authorsList.append(' (');
|
||||
_.each(colorsAnonymous, function(color, i){
|
||||
if( i > 0 ) authorsList.append(' ');
|
||||
_.each(colorsAnonymous, (color, i) => {
|
||||
if (i > 0) authorsList.append(' ');
|
||||
$('<span> </span>')
|
||||
.css('background-color', color)
|
||||
.addClass('author author-anonymous')
|
||||
.appendTo(authorsList);
|
||||
.css('background-color', color)
|
||||
.addClass('author author-anonymous')
|
||||
.appendTo(authorsList);
|
||||
});
|
||||
authorsList.append(')');
|
||||
}
|
||||
|
||||
}
|
||||
if (authors.length == 0)
|
||||
{
|
||||
authorsList.append(html10n.get("timeslider.toolbar.authorsList"));
|
||||
if (authors.length == 0) {
|
||||
authorsList.append(html10n.get('timeslider.toolbar.authorsList'));
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastSlider = {
|
||||
onSlider: onSlider,
|
||||
getSliderPosition: getSliderPosition,
|
||||
setSliderPosition: setSliderPosition,
|
||||
getSliderLength: getSliderLength,
|
||||
setSliderLength: setSliderLength,
|
||||
isSliderActive: function() {
|
||||
onSlider,
|
||||
getSliderPosition,
|
||||
setSliderPosition,
|
||||
getSliderLength,
|
||||
setSliderLength,
|
||||
isSliderActive() {
|
||||
return sliderActive;
|
||||
},
|
||||
playpause: playpause,
|
||||
addSavedRevision: addSavedRevision,
|
||||
showReconnectUI: showReconnectUI,
|
||||
setAuthors: setAuthors
|
||||
}
|
||||
playpause,
|
||||
addSavedRevision,
|
||||
showReconnectUI,
|
||||
setAuthors,
|
||||
};
|
||||
|
||||
function playButtonUpdater() {
|
||||
if (sliderPlaying)
|
||||
{
|
||||
if (getSliderPosition() + 1 > sliderLength)
|
||||
{
|
||||
$("#playpause_button_icon").toggleClass('pause');
|
||||
if (sliderPlaying) {
|
||||
if (getSliderPosition() + 1 > sliderLength) {
|
||||
$('#playpause_button_icon').toggleClass('pause');
|
||||
sliderPlaying = false;
|
||||
return;
|
||||
}
|
||||
|
@ -213,156 +202,142 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
|
|||
}
|
||||
|
||||
function playpause() {
|
||||
$("#playpause_button_icon").toggleClass('pause');
|
||||
$('#playpause_button_icon').toggleClass('pause');
|
||||
|
||||
if (!sliderPlaying)
|
||||
{
|
||||
if (!sliderPlaying) {
|
||||
if (getSliderPosition() == sliderLength) setSliderPosition(0);
|
||||
sliderPlaying = true;
|
||||
playButtonUpdater();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
sliderPlaying = false;
|
||||
}
|
||||
}
|
||||
|
||||
// assign event handlers to html UI elements after page load
|
||||
fireWhenAllScriptsAreLoaded.push(function() {
|
||||
$(document).keyup(function(e) {
|
||||
fireWhenAllScriptsAreLoaded.push(() => {
|
||||
$(document).keyup((e) => {
|
||||
if (!e) var e = window.event;
|
||||
var code = e.keyCode || e.which;
|
||||
const code = e.keyCode || e.which;
|
||||
|
||||
if (code == 37)
|
||||
{ // left
|
||||
if (code == 37) { // left
|
||||
if (e.shiftKey) {
|
||||
$('#leftstar').click();
|
||||
} else {
|
||||
$('#leftstep').click();
|
||||
}
|
||||
}
|
||||
else if (code == 39)
|
||||
{ // right
|
||||
} else if (code == 39) { // right
|
||||
if (e.shiftKey) {
|
||||
$('#rightstar').click();
|
||||
} else {
|
||||
$('#rightstep').click();
|
||||
}
|
||||
}
|
||||
else if (code == 32)
|
||||
{ // spacebar
|
||||
$("#playpause_button_icon").trigger('click');
|
||||
} else if (code == 32) { // spacebar
|
||||
$('#playpause_button_icon').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
// Resize
|
||||
$(window).resize(function() {
|
||||
$(window).resize(() => {
|
||||
updateSliderElements();
|
||||
});
|
||||
|
||||
// Slider click
|
||||
$("#ui-slider-bar").mousedown(function(evt) {
|
||||
$("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left));
|
||||
$("#ui-slider-handle").trigger(evt);
|
||||
$('#ui-slider-bar').mousedown((evt) => {
|
||||
$('#ui-slider-handle').css('left', (evt.clientX - $('#ui-slider-bar').offset().left));
|
||||
$('#ui-slider-handle').trigger(evt);
|
||||
});
|
||||
|
||||
// Slider dragging
|
||||
$("#ui-slider-handle").mousedown(function(evt) {
|
||||
$('#ui-slider-handle').mousedown(function (evt) {
|
||||
this.startLoc = evt.clientX;
|
||||
this.currentLoc = parseInt($(this).css('left'));
|
||||
var self = this;
|
||||
const self = this;
|
||||
sliderActive = true;
|
||||
$(document).mousemove(function(evt2) {
|
||||
$(self).css('pointer', 'move')
|
||||
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
$(document).mousemove((evt2) => {
|
||||
$(self).css('pointer', 'move');
|
||||
let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
if (newloc < 0) newloc = 0;
|
||||
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
|
||||
$("#revision_label").html(html10n.get("timeslider.version", { "version": Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))}));
|
||||
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
|
||||
$('#revision_label').html(html10n.get('timeslider.version', {version: Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))}));
|
||||
$(self).css('left', newloc);
|
||||
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
|
||||
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
|
||||
});
|
||||
$(document).mouseup(function(evt2) {
|
||||
$(document).mouseup((evt2) => {
|
||||
$(document).unbind('mousemove');
|
||||
$(document).unbind('mouseup');
|
||||
sliderActive = false;
|
||||
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
if (newloc < 0) newloc = 0;
|
||||
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
|
||||
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
|
||||
$(self).css('left', newloc);
|
||||
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
|
||||
setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
|
||||
if(parseInt($(self).css('left')) < 2){
|
||||
setSliderPosition(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
|
||||
if (parseInt($(self).css('left')) < 2) {
|
||||
$(self).css('left', '2px');
|
||||
}else{
|
||||
} else {
|
||||
self.currentLoc = parseInt($(self).css('left'));
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// play/pause toggling
|
||||
$("#playpause_button_icon").click(function(evt) {
|
||||
$('#playpause_button_icon').click((evt) => {
|
||||
BroadcastSlider.playpause();
|
||||
});
|
||||
|
||||
// next/prev saved revision and changeset
|
||||
$('.stepper').click(function(evt) {
|
||||
switch ($(this).attr("id")) {
|
||||
case "leftstep":
|
||||
$('.stepper').click(function (evt) {
|
||||
switch ($(this).attr('id')) {
|
||||
case 'leftstep':
|
||||
setSliderPosition(getSliderPosition() - 1);
|
||||
break;
|
||||
case "rightstep":
|
||||
case 'rightstep':
|
||||
setSliderPosition(getSliderPosition() + 1);
|
||||
break;
|
||||
case "leftstar":
|
||||
case 'leftstar':
|
||||
var nextStar = 0; // default to first revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
for (var i = 0; i < savedRevisions.length; i++) {
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
break;
|
||||
case "rightstar":
|
||||
case 'rightstar':
|
||||
var nextStar = sliderLength; // default to last revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
for (var i = 0; i < savedRevisions.length; i++) {
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (clientVars)
|
||||
{
|
||||
$("#timeslider-wrapper").show();
|
||||
if (clientVars) {
|
||||
$('#timeslider-wrapper').show();
|
||||
|
||||
var startPos = clientVars.collab_client_vars.rev;
|
||||
if(window.location.hash.length > 1)
|
||||
{
|
||||
var hashRev = Number(window.location.hash.substr(1));
|
||||
if(!isNaN(hashRev))
|
||||
{
|
||||
const startPos = clientVars.collab_client_vars.rev;
|
||||
if (window.location.hash.length > 1) {
|
||||
const hashRev = Number(window.location.hash.substr(1));
|
||||
if (!isNaN(hashRev)) {
|
||||
// this is necessary because of the socket.io-event which loads the changesets
|
||||
setTimeout(function() { setSliderPosition(hashRev); }, 1);
|
||||
setTimeout(() => { setSliderPosition(hashRev); }, 1);
|
||||
}
|
||||
}
|
||||
|
||||
setSliderLength(clientVars.collab_client_vars.rev);
|
||||
setSliderPosition(clientVars.collab_client_vars.rev);
|
||||
|
||||
_.each(clientVars.savedRevisions, function(revision) {
|
||||
_.each(clientVars.savedRevisions, (revision) => {
|
||||
addSavedRevision(revision.revNum, revision);
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
BroadcastSlider.onSlider(function(loc) {
|
||||
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
|
||||
})
|
||||
BroadcastSlider.onSlider((loc) => {
|
||||
$('#viewlatest').html(loc == BroadcastSlider.getSliderLength() ? 'Viewing latest content' : 'View latest content');
|
||||
});
|
||||
|
||||
return BroadcastSlider;
|
||||
}
|
||||
|
|
|
@ -2,39 +2,39 @@
|
|||
// This function is useful to get the caret position of the line as
|
||||
// is represented by the browser
|
||||
exports.getPosition = function () {
|
||||
var rect, line;
|
||||
var editor = $('#innerdocbody')[0];
|
||||
var range = getSelectionRange();
|
||||
var isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
|
||||
let rect, line;
|
||||
const editor = $('#innerdocbody')[0];
|
||||
const range = getSelectionRange();
|
||||
const isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
|
||||
|
||||
if(isSelectionInsideTheEditor){
|
||||
if (isSelectionInsideTheEditor) {
|
||||
// when we have the caret in an empty line, e.g. a line with only a <br>,
|
||||
// getBoundingClientRect() returns all dimensions value as 0
|
||||
var selectionIsInTheBeginningOfLine = range.endOffset > 0;
|
||||
const selectionIsInTheBeginningOfLine = range.endOffset > 0;
|
||||
if (selectionIsInTheBeginningOfLine) {
|
||||
var clonedRange = createSelectionRange(range);
|
||||
line = getPositionOfElementOrSelection(clonedRange);
|
||||
clonedRange.detach()
|
||||
clonedRange.detach();
|
||||
}
|
||||
|
||||
// when there's a <br> or any element that has no height, we can't get
|
||||
// the dimension of the element where the caret is
|
||||
if(!rect || rect.height === 0){
|
||||
if (!rect || rect.height === 0) {
|
||||
var clonedRange = createSelectionRange(range);
|
||||
|
||||
// as we can't get the element height, we create a text node to get the dimensions
|
||||
// on the position
|
||||
var shadowCaret = $(document.createTextNode("|"));
|
||||
const shadowCaret = $(document.createTextNode('|'));
|
||||
clonedRange.insertNode(shadowCaret[0]);
|
||||
clonedRange.selectNode(shadowCaret[0]);
|
||||
|
||||
line = getPositionOfElementOrSelection(clonedRange);
|
||||
clonedRange.detach()
|
||||
clonedRange.detach();
|
||||
shadowCaret.remove();
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
};
|
||||
|
||||
var createSelectionRange = function (range) {
|
||||
clonedRange = range.cloneRange();
|
||||
|
@ -46,9 +46,9 @@ var createSelectionRange = function (range) {
|
|||
clonedRange.setStart(range.endContainer, range.endOffset);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
return clonedRange;
|
||||
}
|
||||
};
|
||||
|
||||
var getPositionOfRepLineAtOffset = function (node, offset) {
|
||||
const getPositionOfRepLineAtOffset = function (node, offset) {
|
||||
// it is not a text node, so we cannot make a selection
|
||||
if (node.tagName === 'BR' || node.tagName === 'EMPTY') {
|
||||
return getPositionOfElementOrSelection(node);
|
||||
|
@ -58,21 +58,21 @@ var getPositionOfRepLineAtOffset = function (node, offset) {
|
|||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
var newRange = new Range();
|
||||
const newRange = new Range();
|
||||
newRange.setStart(node, offset);
|
||||
newRange.setEnd(node, offset);
|
||||
var linePosition = getPositionOfElementOrSelection(newRange);
|
||||
const linePosition = getPositionOfElementOrSelection(newRange);
|
||||
newRange.detach(); // performance sake
|
||||
return linePosition;
|
||||
}
|
||||
};
|
||||
|
||||
function getPositionOfElementOrSelection(element) {
|
||||
var rect = element.getBoundingClientRect();
|
||||
var linePosition = {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const linePosition = {
|
||||
bottom: rect.bottom,
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
}
|
||||
top: rect.top,
|
||||
};
|
||||
return linePosition;
|
||||
}
|
||||
|
||||
|
@ -82,67 +82,66 @@ function getPositionOfElementOrSelection(element) {
|
|||
// of the previous line
|
||||
// [2] the line before is part of another rep line. It's possible this line has different margins
|
||||
// height. So we have to get the exactly position of the line
|
||||
exports.getPositionTopOfPreviousBrowserLine = function(caretLinePosition, rep) {
|
||||
var previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
|
||||
var isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
|
||||
exports.getPositionTopOfPreviousBrowserLine = function (caretLinePosition, rep) {
|
||||
let previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
|
||||
const isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
|
||||
|
||||
// the caret is in the beginning of a rep line, so the previous browser line
|
||||
// is the last line browser line of the a rep line
|
||||
if (isCaretLineFirstBrowserLine) { //[2]
|
||||
var lineBeforeCaretLine = rep.selStart[0] - 1;
|
||||
var firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
|
||||
var linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
|
||||
if (isCaretLineFirstBrowserLine) { // [2]
|
||||
const lineBeforeCaretLine = rep.selStart[0] - 1;
|
||||
const firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
|
||||
const linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
|
||||
previousLineTop = linePosition.top;
|
||||
}
|
||||
return previousLineTop;
|
||||
}
|
||||
};
|
||||
|
||||
function caretLineIsFirstBrowserLine(caretLineTop, rep) {
|
||||
var caretRepLine = rep.selStart[0];
|
||||
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
var firstRootNode = getFirstRootChildNode(lineNode);
|
||||
const caretRepLine = rep.selStart[0];
|
||||
const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
const firstRootNode = getFirstRootChildNode(lineNode);
|
||||
|
||||
// to get the position of the node we get the position of the first char
|
||||
var positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
|
||||
const positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
|
||||
return positionOfFirstRootNode.top === caretLineTop;
|
||||
}
|
||||
|
||||
// find the first root node, usually it is a text node
|
||||
function getFirstRootChildNode(node) {
|
||||
if(!node.firstChild){
|
||||
if (!node.firstChild) {
|
||||
return node;
|
||||
}else{
|
||||
} else {
|
||||
return getFirstRootChildNode(node.firstChild);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getPreviousVisibleLine(line, rep) {
|
||||
if (line < 0) {
|
||||
return 0;
|
||||
}else if (isLineVisible(line, rep)) {
|
||||
} else if (isLineVisible(line, rep)) {
|
||||
return line;
|
||||
}else{
|
||||
} else {
|
||||
return getPreviousVisibleLine(line - 1, rep);
|
||||
}
|
||||
}
|
||||
|
||||
function getDimensionOfLastBrowserLineOfRepLine(line, rep) {
|
||||
var lineNode = rep.lines.atIndex(line).lineNode;
|
||||
var lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
const lineNode = rep.lines.atIndex(line).lineNode;
|
||||
const lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
|
||||
// we get the position of the line in the last char of it
|
||||
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
return lastRootChildNodePosition;
|
||||
}
|
||||
|
||||
function getLastRootChildNode(node) {
|
||||
if(!node.lastChild){
|
||||
if (!node.lastChild) {
|
||||
return {
|
||||
node: node,
|
||||
length: node.length
|
||||
node,
|
||||
length: node.length,
|
||||
};
|
||||
}else{
|
||||
} else {
|
||||
return getLastRootChildNode(node.lastChild);
|
||||
}
|
||||
}
|
||||
|
@ -152,50 +151,50 @@ function getLastRootChildNode(node) {
|
|||
// So, we can use the caret line to calculate the bottom of the line.
|
||||
// [2] the next line is part of another rep line. It's possible this line has different dimensions, so we
|
||||
// have to get the exactly dimension of it
|
||||
exports.getBottomOfNextBrowserLine = function(caretLinePosition, rep) {
|
||||
var nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; //[1]
|
||||
var isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
|
||||
exports.getBottomOfNextBrowserLine = function (caretLinePosition, rep) {
|
||||
let nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; // [1]
|
||||
const isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
|
||||
|
||||
// the caret is at the end of a rep line, so we can get the next browser line dimension
|
||||
// using the position of the first char of the next rep line
|
||||
if(isCaretLineLastBrowserLine){ //[2]
|
||||
var nextLineAfterCaretLine = rep.selStart[0] + 1;
|
||||
var firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
|
||||
var linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
|
||||
if (isCaretLineLastBrowserLine) { // [2]
|
||||
const nextLineAfterCaretLine = rep.selStart[0] + 1;
|
||||
const firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
|
||||
const linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
|
||||
nextLineBottom = linePosition.bottom;
|
||||
}
|
||||
return nextLineBottom;
|
||||
}
|
||||
};
|
||||
|
||||
function caretLineIsLastBrowserLineOfRepLine(caretLineTop, rep) {
|
||||
var caretRepLine = rep.selStart[0];
|
||||
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
var lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
const caretRepLine = rep.selStart[0];
|
||||
const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
|
||||
const lastRootChildNode = getLastRootChildNode(lineNode);
|
||||
|
||||
// we take a rep line and get the position of the last char of it
|
||||
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
|
||||
return lastRootChildNodePosition.top === caretLineTop;
|
||||
}
|
||||
|
||||
function getPreviousVisibleLine(line, rep) {
|
||||
var firstLineOfPad = 0;
|
||||
const firstLineOfPad = 0;
|
||||
if (line <= firstLineOfPad) {
|
||||
return firstLineOfPad;
|
||||
}else if (isLineVisible(line,rep)) {
|
||||
} else if (isLineVisible(line, rep)) {
|
||||
return line;
|
||||
}else{
|
||||
} else {
|
||||
return getPreviousVisibleLine(line - 1, rep);
|
||||
}
|
||||
}
|
||||
exports.getPreviousVisibleLine = getPreviousVisibleLine;
|
||||
|
||||
function getNextVisibleLine(line, rep) {
|
||||
var lastLineOfThePad = rep.lines.length() - 1;
|
||||
const lastLineOfThePad = rep.lines.length() - 1;
|
||||
if (line >= lastLineOfThePad) {
|
||||
return lastLineOfThePad;
|
||||
}else if (isLineVisible(line,rep)) {
|
||||
} else if (isLineVisible(line, rep)) {
|
||||
return line;
|
||||
}else{
|
||||
} else {
|
||||
return getNextVisibleLine(line + 1, rep);
|
||||
}
|
||||
}
|
||||
|
@ -206,23 +205,23 @@ function isLineVisible(line, rep) {
|
|||
}
|
||||
|
||||
function getDimensionOfFirstBrowserLineOfRepLine(line, rep) {
|
||||
var lineNode = rep.lines.atIndex(line).lineNode;
|
||||
var firstRootChildNode = getFirstRootChildNode(lineNode);
|
||||
const lineNode = rep.lines.atIndex(line).lineNode;
|
||||
const firstRootChildNode = getFirstRootChildNode(lineNode);
|
||||
|
||||
// we can get the position of the line, getting the position of the first char of the rep line
|
||||
var firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
|
||||
const firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
|
||||
return firstRootChildNodePosition;
|
||||
}
|
||||
|
||||
function getSelectionRange() {
|
||||
var selection;
|
||||
let selection;
|
||||
if (!window.getSelection) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
selection = window.getSelection();
|
||||
if (selection.rangeCount > 0) {
|
||||
return selection.getRangeAt(0);
|
||||
return selection.getRangeAt(0);
|
||||
} else {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,80 +20,70 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var AttributePool = require('./AttributePool');
|
||||
var Changeset = require('./Changeset');
|
||||
const AttributePool = require('./AttributePool');
|
||||
const Changeset = require('./Changeset');
|
||||
|
||||
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
|
||||
|
||||
// latest official text from server
|
||||
var baseAText = Changeset.makeAText("\n");
|
||||
let baseAText = Changeset.makeAText('\n');
|
||||
// changes applied to baseText that have been submitted
|
||||
var submittedChangeset = null;
|
||||
let submittedChangeset = null;
|
||||
// changes applied to submittedChangeset since it was prepared
|
||||
var userChangeset = Changeset.identity(1);
|
||||
let userChangeset = Changeset.identity(1);
|
||||
// is the changesetTracker enabled
|
||||
var tracking = false;
|
||||
let tracking = false;
|
||||
// stack state flag so that when we change the rep we don't
|
||||
// handle the notification recursively. When setting, always
|
||||
// unset in a "finally" block. When set to true, the setter
|
||||
// takes change of userChangeset.
|
||||
var applyingNonUserChanges = false;
|
||||
let applyingNonUserChanges = false;
|
||||
|
||||
var changeCallback = null;
|
||||
let changeCallback = null;
|
||||
|
||||
var changeCallbackTimeout = null;
|
||||
let changeCallbackTimeout = null;
|
||||
|
||||
function setChangeCallbackTimeout() {
|
||||
// can call this multiple times per call-stack, because
|
||||
// we only schedule a call to changeCallback if it exists
|
||||
// and if there isn't a timeout already scheduled.
|
||||
if (changeCallback && changeCallbackTimeout === null)
|
||||
{
|
||||
changeCallbackTimeout = scheduler.setTimeout(function() {
|
||||
try
|
||||
{
|
||||
if (changeCallback && changeCallbackTimeout === null) {
|
||||
changeCallbackTimeout = scheduler.setTimeout(() => {
|
||||
try {
|
||||
changeCallback();
|
||||
}
|
||||
catch(pseudoError) {}
|
||||
finally
|
||||
{
|
||||
} catch (pseudoError) {} finally {
|
||||
changeCallbackTimeout = null;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
var self;
|
||||
let self;
|
||||
return self = {
|
||||
isTracking: function() {
|
||||
isTracking() {
|
||||
return tracking;
|
||||
},
|
||||
setBaseText: function(text) {
|
||||
setBaseText(text) {
|
||||
self.setBaseAttributedText(Changeset.makeAText(text), null);
|
||||
},
|
||||
setBaseAttributedText: function(atext, apoolJsonObj) {
|
||||
aceCallbacksProvider.withCallbacks("setBaseText", function(callbacks) {
|
||||
setBaseAttributedText(atext, apoolJsonObj) {
|
||||
aceCallbacksProvider.withCallbacks('setBaseText', (callbacks) => {
|
||||
tracking = true;
|
||||
baseAText = Changeset.cloneAText(atext);
|
||||
if (apoolJsonObj)
|
||||
{
|
||||
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
|
||||
if (apoolJsonObj) {
|
||||
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
|
||||
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
|
||||
}
|
||||
submittedChangeset = null;
|
||||
userChangeset = Changeset.identity(atext.text.length);
|
||||
applyingNonUserChanges = true;
|
||||
try
|
||||
{
|
||||
try {
|
||||
callbacks.setDocumentAttributedText(atext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
} finally {
|
||||
applyingNonUserChanges = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
composeUserChangeset: function(c) {
|
||||
composeUserChangeset(c) {
|
||||
if (!tracking) return;
|
||||
if (applyingNonUserChanges) return;
|
||||
if (Changeset.isIdentity(c)) return;
|
||||
|
@ -101,149 +91,132 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
|
|||
|
||||
setChangeCallbackTimeout();
|
||||
},
|
||||
applyChangesToBase: function(c, optAuthor, apoolJsonObj) {
|
||||
applyChangesToBase(c, optAuthor, apoolJsonObj) {
|
||||
if (!tracking) return;
|
||||
|
||||
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks) {
|
||||
|
||||
if (apoolJsonObj)
|
||||
{
|
||||
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
|
||||
aceCallbacksProvider.withCallbacks('applyChangesToBase', (callbacks) => {
|
||||
if (apoolJsonObj) {
|
||||
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
|
||||
c = Changeset.moveOpsToNewPool(c, wireApool, apool);
|
||||
}
|
||||
|
||||
baseAText = Changeset.applyToAText(c, baseAText, apool);
|
||||
|
||||
var c2 = c;
|
||||
if (submittedChangeset)
|
||||
{
|
||||
var oldSubmittedChangeset = submittedChangeset;
|
||||
let c2 = c;
|
||||
if (submittedChangeset) {
|
||||
const oldSubmittedChangeset = submittedChangeset;
|
||||
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
|
||||
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
|
||||
}
|
||||
|
||||
var preferInsertingAfterUserChanges = true;
|
||||
var oldUserChangeset = userChangeset;
|
||||
const preferInsertingAfterUserChanges = true;
|
||||
const oldUserChangeset = userChangeset;
|
||||
userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
|
||||
var postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
|
||||
const postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
|
||||
|
||||
var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor);
|
||||
const preferInsertionAfterCaret = true; // (optAuthor && optAuthor > thisAuthor);
|
||||
applyingNonUserChanges = true;
|
||||
try
|
||||
{
|
||||
try {
|
||||
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
|
||||
}
|
||||
finally
|
||||
{
|
||||
} finally {
|
||||
applyingNonUserChanges = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
prepareUserChangeset: function() {
|
||||
prepareUserChangeset() {
|
||||
// If there are user changes to submit, 'changeset' will be the
|
||||
// changeset, else it will be null.
|
||||
var toSubmit;
|
||||
if (submittedChangeset)
|
||||
{
|
||||
let toSubmit;
|
||||
if (submittedChangeset) {
|
||||
// submission must have been canceled, prepare new changeset
|
||||
// that includes old submittedChangeset
|
||||
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
} else {
|
||||
// add forEach function to Array.prototype for IE8
|
||||
if (!('forEach' in Array.prototype)) {
|
||||
Array.prototype.forEach= function(action, that /*opt*/) {
|
||||
for (var i= 0, n= this.length; i<n; i++)
|
||||
if (i in this)
|
||||
action.call(that, this[i], i, this);
|
||||
Array.prototype.forEach = function (action, that /* opt*/) {
|
||||
for (let i = 0, n = this.length; i < n; i++) if (i in this) action.call(that, this[i], i, this);
|
||||
};
|
||||
}
|
||||
|
||||
// Get my authorID
|
||||
var authorId = parent.parent.pad.myUserInfo.userId;
|
||||
const authorId = parent.parent.pad.myUserInfo.userId;
|
||||
|
||||
// Sanitize authorship
|
||||
// We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes
|
||||
if(apool.numToAttrib){
|
||||
for (var attr in apool.numToAttrib){
|
||||
if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36)
|
||||
if (apool.numToAttrib) {
|
||||
for (const attr in apool.numToAttrib) {
|
||||
if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36);
|
||||
}
|
||||
|
||||
// Replace all added 'author' attribs with the value of the current user
|
||||
var cs = Changeset.unpack(userChangeset)
|
||||
, iterator = Changeset.opIterator(cs.ops)
|
||||
, op
|
||||
, assem = Changeset.mergingOpAssembler();
|
||||
var cs = Changeset.unpack(userChangeset);
|
||||
const iterator = Changeset.opIterator(cs.ops);
|
||||
let op;
|
||||
const assem = Changeset.mergingOpAssembler();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
op = iterator.next()
|
||||
if(op.opcode == '+') {
|
||||
var newAttrs = ''
|
||||
while (iterator.hasNext()) {
|
||||
op = iterator.next();
|
||||
if (op.opcode == '+') {
|
||||
var newAttrs = '';
|
||||
|
||||
op.attribs.split('*').forEach(function(attrNum) {
|
||||
if(!attrNum) return
|
||||
var attr = apool.getAttrib(parseInt(attrNum, 36))
|
||||
if(!attr) return
|
||||
if('author' == attr[0]) {
|
||||
op.attribs.split('*').forEach((attrNum) => {
|
||||
if (!attrNum) return;
|
||||
const attr = apool.getAttrib(parseInt(attrNum, 36));
|
||||
if (!attr) return;
|
||||
if ('author' == attr[0]) {
|
||||
// replace that author with the current one
|
||||
newAttrs += '*'+authorAttr;
|
||||
}
|
||||
else newAttrs += '*'+attrNum // overtake all other attribs as is
|
||||
})
|
||||
op.attribs = newAttrs
|
||||
newAttrs += `*${authorAttr}`;
|
||||
} else { newAttrs += `*${attrNum}`; } // overtake all other attribs as is
|
||||
});
|
||||
op.attribs = newAttrs;
|
||||
}
|
||||
assem.append(op)
|
||||
assem.append(op);
|
||||
}
|
||||
assem.endDocument();
|
||||
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank)
|
||||
Changeset.checkRep(userChangeset)
|
||||
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
|
||||
Changeset.checkRep(userChangeset);
|
||||
}
|
||||
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
|
||||
else toSubmit = userChangeset;
|
||||
}
|
||||
|
||||
var cs = null;
|
||||
if (toSubmit)
|
||||
{
|
||||
if (toSubmit) {
|
||||
submittedChangeset = toSubmit;
|
||||
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
|
||||
|
||||
cs = toSubmit;
|
||||
}
|
||||
var wireApool = null;
|
||||
if (cs)
|
||||
{
|
||||
var forWire = Changeset.prepareForWire(cs, apool);
|
||||
let wireApool = null;
|
||||
if (cs) {
|
||||
const forWire = Changeset.prepareForWire(cs, apool);
|
||||
wireApool = forWire.pool.toJsonable();
|
||||
cs = forWire.translated;
|
||||
}
|
||||
|
||||
var data = {
|
||||
const data = {
|
||||
changeset: cs,
|
||||
apool: wireApool
|
||||
apool: wireApool,
|
||||
};
|
||||
return data;
|
||||
},
|
||||
applyPreparedChangesetToBase: function() {
|
||||
if (!submittedChangeset)
|
||||
{
|
||||
applyPreparedChangesetToBase() {
|
||||
if (!submittedChangeset) {
|
||||
// violation of protocol; use prepareUserChangeset first
|
||||
throw new Error("applySubmittedChangesToBase: no submitted changes to apply");
|
||||
throw new Error('applySubmittedChangesToBase: no submitted changes to apply');
|
||||
}
|
||||
//bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
|
||||
// bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
|
||||
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
|
||||
submittedChangeset = null;
|
||||
},
|
||||
setUserChangeNotificationCallback: function(callback) {
|
||||
setUserChangeNotificationCallback(callback) {
|
||||
changeCallback = callback;
|
||||
},
|
||||
hasUncommittedChanges: function() {
|
||||
hasUncommittedChanges() {
|
||||
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
exports.makeChangesetTracker = makeChangesetTracker;
|
||||
|
|
|
@ -14,174 +14,165 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padutils = require('./pad_utils').padutils;
|
||||
var padcookie = require('./pad_cookie').padcookie;
|
||||
var Tinycon = require('tinycon/tinycon');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var padeditor = require('./pad_editor').padeditor;
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
const padcookie = require('./pad_cookie').padcookie;
|
||||
const Tinycon = require('tinycon/tinycon');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const padeditor = require('./pad_editor').padeditor;
|
||||
|
||||
var chat = (function() {
|
||||
var isStuck = false;
|
||||
var userAndChat = false;
|
||||
var gotInitialMessages = false;
|
||||
var historyPointer = 0;
|
||||
var chatMentions = 0;
|
||||
var chat = (function () {
|
||||
let isStuck = false;
|
||||
let userAndChat = false;
|
||||
const gotInitialMessages = false;
|
||||
const historyPointer = 0;
|
||||
let chatMentions = 0;
|
||||
var self = {
|
||||
show: function () {
|
||||
$("#chaticon").removeClass('visible');
|
||||
$("#chatbox").addClass('visible');
|
||||
show() {
|
||||
$('#chaticon').removeClass('visible');
|
||||
$('#chatbox').addClass('visible');
|
||||
self.scrollDown(true);
|
||||
chatMentions = 0;
|
||||
Tinycon.setBubble(0);
|
||||
$('.chat-gritter-msg').each(function() {
|
||||
$('.chat-gritter-msg').each(function () {
|
||||
$.gritter.remove(this.id);
|
||||
});
|
||||
},
|
||||
focus: function () {
|
||||
setTimeout(function(){
|
||||
$("#chatinput").focus();
|
||||
},100);
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
$('#chatinput').focus();
|
||||
}, 100);
|
||||
},
|
||||
stickToScreen: function(fromInitialCall) { // Make chat stick to right hand side of screen
|
||||
if(pad.settings.hideChat){
|
||||
stickToScreen(fromInitialCall) { // Make chat stick to right hand side of screen
|
||||
if (pad.settings.hideChat) {
|
||||
return;
|
||||
}
|
||||
chat.show();
|
||||
isStuck = (!isStuck || fromInitialCall);
|
||||
$('#chatbox').hide();
|
||||
// Add timeout to disable the chatbox animations
|
||||
setTimeout(function() {
|
||||
$('#chatbox, .sticky-container').toggleClass("stickyChat", isStuck);
|
||||
setTimeout(() => {
|
||||
$('#chatbox, .sticky-container').toggleClass('stickyChat', isStuck);
|
||||
$('#chatbox').css('display', 'flex');
|
||||
}, 0);
|
||||
|
||||
padcookie.setPref("chatAlwaysVisible", isStuck);
|
||||
padcookie.setPref('chatAlwaysVisible', isStuck);
|
||||
$('#options-stickychat').prop('checked', isStuck);
|
||||
},
|
||||
chatAndUsers: function(fromInitialCall) {
|
||||
var toEnable = $('#options-chatandusers').is(":checked");
|
||||
if(toEnable || !userAndChat || fromInitialCall){
|
||||
chatAndUsers(fromInitialCall) {
|
||||
const toEnable = $('#options-chatandusers').is(':checked');
|
||||
if (toEnable || !userAndChat || fromInitialCall) {
|
||||
chat.stickToScreen(true);
|
||||
$('#options-stickychat').prop('checked', true)
|
||||
$('#options-chatandusers').prop('checked', true)
|
||||
$('#options-stickychat').prop("disabled", "disabled");
|
||||
$('#options-stickychat').prop('checked', true);
|
||||
$('#options-chatandusers').prop('checked', true);
|
||||
$('#options-stickychat').prop('disabled', 'disabled');
|
||||
userAndChat = true;
|
||||
}else{
|
||||
$('#options-stickychat').prop("disabled", false);
|
||||
} else {
|
||||
$('#options-stickychat').prop('disabled', false);
|
||||
userAndChat = false;
|
||||
}
|
||||
padcookie.setPref("chatAndUsers", userAndChat);
|
||||
$('#users, .sticky-container').toggleClass("chatAndUsers popup-show stickyUsers", userAndChat);
|
||||
$("#chatbox").toggleClass("chatAndUsersChat", userAndChat);
|
||||
padcookie.setPref('chatAndUsers', userAndChat);
|
||||
$('#users, .sticky-container').toggleClass('chatAndUsers popup-show stickyUsers', userAndChat);
|
||||
$('#chatbox').toggleClass('chatAndUsersChat', userAndChat);
|
||||
},
|
||||
hide: function () {
|
||||
hide() {
|
||||
// decide on hide logic based on chat window being maximized or not
|
||||
if ($('#options-stickychat').prop('checked')) {
|
||||
chat.stickToScreen();
|
||||
$('#options-stickychat').prop('checked', false);
|
||||
}
|
||||
else {
|
||||
$("#chatcounter").text("0");
|
||||
$("#chaticon").addClass('visible');
|
||||
$("#chatbox").removeClass('visible');
|
||||
} else {
|
||||
$('#chatcounter').text('0');
|
||||
$('#chaticon').addClass('visible');
|
||||
$('#chatbox').removeClass('visible');
|
||||
}
|
||||
},
|
||||
scrollDown: function(force) {
|
||||
scrollDown(force) {
|
||||
if ($('#chatbox').hasClass('visible')) {
|
||||
if (force || !self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) {
|
||||
// if we use a slow animate here we can have a race condition when a users focus can not be moved away
|
||||
// from the last message recieved.
|
||||
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false });
|
||||
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, {duration: 400, queue: false});
|
||||
self.lastMessage = $('#chattext > p').eq(-1);
|
||||
}
|
||||
}
|
||||
},
|
||||
send: function() {
|
||||
var text = $("#chatinput").val();
|
||||
if(text.replace(/\s+/,'').length == 0)
|
||||
return;
|
||||
this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text});
|
||||
$("#chatinput").val("");
|
||||
send() {
|
||||
const text = $('#chatinput').val();
|
||||
if (text.replace(/\s+/, '').length == 0) return;
|
||||
this._pad.collabClient.sendMessage({type: 'CHAT_MESSAGE', text});
|
||||
$('#chatinput').val('');
|
||||
},
|
||||
addMessage: function(msg, increment, isHistoryAdd) {
|
||||
//correct the time
|
||||
addMessage(msg, increment, isHistoryAdd) {
|
||||
// correct the time
|
||||
msg.time += this._pad.clientTimeOffset;
|
||||
|
||||
//create the time string
|
||||
var minutes = "" + new Date(msg.time).getMinutes();
|
||||
var hours = "" + new Date(msg.time).getHours();
|
||||
if(minutes.length == 1)
|
||||
minutes = "0" + minutes ;
|
||||
if(hours.length == 1)
|
||||
hours = "0" + hours ;
|
||||
var timeStr = hours + ":" + minutes;
|
||||
// create the time string
|
||||
let minutes = `${new Date(msg.time).getMinutes()}`;
|
||||
let hours = `${new Date(msg.time).getHours()}`;
|
||||
if (minutes.length == 1) minutes = `0${minutes}`;
|
||||
if (hours.length == 1) hours = `0${hours}`;
|
||||
const timeStr = `${hours}:${minutes}`;
|
||||
|
||||
//create the authorclass
|
||||
// create the authorclass
|
||||
if (!msg.userId) {
|
||||
/*
|
||||
* If, for a bug or a database corruption, the message coming from the
|
||||
* server does not contain the userId field (see for example #3731),
|
||||
* let's be defensive and replace it with "unknown".
|
||||
*/
|
||||
msg.userId = "unknown";
|
||||
msg.userId = 'unknown';
|
||||
console.warn('The "userId" field of a chat message coming from the server was not present. Replacing with "unknown". This may be a bug or a database corruption.');
|
||||
}
|
||||
|
||||
var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c) {
|
||||
if (c == ".") return "-";
|
||||
return 'z' + c.charCodeAt(0) + 'z';
|
||||
});
|
||||
const authorClass = `author-${msg.userId.replace(/[^a-y0-9]/g, (c) => {
|
||||
if (c == '.') return '-';
|
||||
return `z${c.charCodeAt(0)}z`;
|
||||
})}`;
|
||||
|
||||
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank");
|
||||
const text = padutils.escapeHtmlWithClickableLinks(msg.text, '_blank');
|
||||
|
||||
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
|
||||
const authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
|
||||
|
||||
// the hook args
|
||||
var ctx = {
|
||||
"authorName" : authorName,
|
||||
"author" : msg.userId,
|
||||
"text" : text,
|
||||
"sticky" : false,
|
||||
"timestamp" : msg.time,
|
||||
"timeStr" : timeStr,
|
||||
"duration" : 4000
|
||||
}
|
||||
const ctx = {
|
||||
authorName,
|
||||
author: msg.userId,
|
||||
text,
|
||||
sticky: false,
|
||||
timestamp: msg.time,
|
||||
timeStr,
|
||||
duration: 4000,
|
||||
};
|
||||
|
||||
// is the users focus already in the chatbox?
|
||||
var alreadyFocused = $("#chatinput").is(":focus");
|
||||
const alreadyFocused = $('#chatinput').is(':focus');
|
||||
|
||||
// does the user already have the chatbox open?
|
||||
var chatOpen = $("#chatbox").hasClass("visible");
|
||||
const chatOpen = $('#chatbox').hasClass('visible');
|
||||
|
||||
// does this message contain this user's name? (is the curretn user mentioned?)
|
||||
var myName = $('#myusernameedit').val();
|
||||
var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined");
|
||||
const myName = $('#myusernameedit').val();
|
||||
const wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != 'undefined');
|
||||
|
||||
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen)
|
||||
{ // If the user was mentioned, make the message sticky
|
||||
if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { // If the user was mentioned, make the message sticky
|
||||
chatMentions++;
|
||||
Tinycon.setBubble(chatMentions);
|
||||
ctx.sticky = true;
|
||||
}
|
||||
|
||||
// Call chat message hook
|
||||
hooks.aCallAll("chatNewMessage", ctx, function() {
|
||||
hooks.aCallAll('chatNewMessage', ctx, () => {
|
||||
const html = `<p data-authorId='${msg.userId}' class='${authorClass}'><b>${authorName}:</b><span class='time ${authorClass}'>${ctx.timeStr}</span> ${ctx.text}</p>`;
|
||||
if (isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton');
|
||||
else $('#chattext').append(html);
|
||||
|
||||
var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + ctx.timeStr + "</span> " + ctx.text + "</p>";
|
||||
if(isHistoryAdd)
|
||||
$(html).insertAfter('#chatloadmessagesbutton');
|
||||
else
|
||||
$("#chattext").append(html);
|
||||
|
||||
//should we increment the counter??
|
||||
if(increment && !isHistoryAdd)
|
||||
{
|
||||
// should we increment the counter??
|
||||
if (increment && !isHistoryAdd) {
|
||||
// Update the counter of unread messages
|
||||
var count = Number($("#chatcounter").text());
|
||||
let count = Number($('#chatcounter').text());
|
||||
count++;
|
||||
$("#chatcounter").text(count);
|
||||
$('#chatcounter').text(count);
|
||||
|
||||
if(!chatOpen && ctx.duration > 0) {
|
||||
if (!chatOpen && ctx.duration > 0) {
|
||||
$.gritter.add({
|
||||
// Note: ctx.authorName and ctx.text are already HTML-escaped.
|
||||
text: $('<p>')
|
||||
|
@ -190,26 +181,25 @@ var chat = (function() {
|
|||
sticky: ctx.sticky,
|
||||
time: 5000,
|
||||
position: 'bottom',
|
||||
class_name: 'chat-gritter-msg'
|
||||
class_name: 'chat-gritter-msg',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the chat mentions when the user clicks on the chat input box
|
||||
$('#chatinput').click(function(){
|
||||
$('#chatinput').click(() => {
|
||||
chatMentions = 0;
|
||||
Tinycon.setBubble(0);
|
||||
});
|
||||
if(!isHistoryAdd)
|
||||
self.scrollDown();
|
||||
if (!isHistoryAdd) self.scrollDown();
|
||||
},
|
||||
init: function(pad) {
|
||||
init(pad) {
|
||||
this._pad = pad;
|
||||
$("#chatinput").on("keydown", function(evt){
|
||||
$('#chatinput').on('keydown', (evt) => {
|
||||
// If the event is Alt C or Escape & we're already in the chat menu
|
||||
// Send the users focus back to the pad
|
||||
if((evt.altKey == true && evt.which === 67) || evt.which === 27){
|
||||
if ((evt.altKey == true && evt.which === 67) || evt.which === 27) {
|
||||
// If we're in chat already..
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
padeditor.ace.focus(); // Sends focus back to pad
|
||||
|
@ -218,20 +208,19 @@ var chat = (function() {
|
|||
}
|
||||
});
|
||||
|
||||
$('body:not(#chatinput)').on("keypress", function(evt){
|
||||
if (evt.altKey && evt.which == 67){
|
||||
$('body:not(#chatinput)').on('keypress', function (evt) {
|
||||
if (evt.altKey && evt.which == 67) {
|
||||
// Alt c focuses on the Chat window
|
||||
$(this).blur();
|
||||
chat.show();
|
||||
$("#chatinput").focus();
|
||||
$('#chatinput').focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$("#chatinput").keypress(function(evt){
|
||||
//if the user typed enter, fire the send
|
||||
if(evt.which == 13 || evt.which == 10)
|
||||
{
|
||||
$('#chatinput').keypress((evt) => {
|
||||
// if the user typed enter, fire the send
|
||||
if (evt.which == 13 || evt.which == 10) {
|
||||
evt.preventDefault();
|
||||
self.send();
|
||||
}
|
||||
|
@ -239,25 +228,24 @@ var chat = (function() {
|
|||
|
||||
// initial messages are loaded in pad.js' _afterHandshake
|
||||
|
||||
$("#chatcounter").text(0);
|
||||
$("#chatloadmessagesbutton").click(function() {
|
||||
var start = Math.max(self.historyPointer - 20, 0);
|
||||
var end = self.historyPointer;
|
||||
$('#chatcounter').text(0);
|
||||
$('#chatloadmessagesbutton').click(() => {
|
||||
const start = Math.max(self.historyPointer - 20, 0);
|
||||
const end = self.historyPointer;
|
||||
|
||||
if(start == end) // nothing to load
|
||||
return;
|
||||
if (start == end) // nothing to load
|
||||
{ return; }
|
||||
|
||||
$("#chatloadmessagesbutton").css("display", "none");
|
||||
$("#chatloadmessagesball").css("display", "block");
|
||||
$('#chatloadmessagesbutton').css('display', 'none');
|
||||
$('#chatloadmessagesball').css('display', 'block');
|
||||
|
||||
pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end});
|
||||
pad.collabClient.sendMessage({type: 'GET_CHAT_MESSAGES', start, end});
|
||||
self.historyPointer = start;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return self;
|
||||
}());
|
||||
|
||||
exports.chat = chat;
|
||||
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var chat = require('./chat').chat;
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
const chat = require('./chat').chat;
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
// Dependency fill on init. This exists for `pad.socket` only.
|
||||
// TODO: bind directly to the socket.
|
||||
var pad = undefined;
|
||||
let pad = undefined;
|
||||
function getSocket() {
|
||||
return pad && pad.socket;
|
||||
}
|
||||
|
@ -34,134 +34,118 @@ function getSocket() {
|
|||
ACE's ready callback does not need to have fired yet.
|
||||
"serverVars" are from calling doc.getCollabClientVars() on the server. */
|
||||
function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) {
|
||||
var editor = ace2editor;
|
||||
const editor = ace2editor;
|
||||
pad = _pad; // Inject pad to avoid a circular dependency.
|
||||
|
||||
var rev = serverVars.rev;
|
||||
var padId = serverVars.padId;
|
||||
let rev = serverVars.rev;
|
||||
const padId = serverVars.padId;
|
||||
|
||||
var state = "IDLE";
|
||||
var stateMessage;
|
||||
var channelState = "CONNECTING";
|
||||
var appLevelDisconnectReason = null;
|
||||
let state = 'IDLE';
|
||||
let stateMessage;
|
||||
let channelState = 'CONNECTING';
|
||||
let appLevelDisconnectReason = null;
|
||||
|
||||
var lastCommitTime = 0;
|
||||
var initialStartConnectTime = 0;
|
||||
let lastCommitTime = 0;
|
||||
let initialStartConnectTime = 0;
|
||||
|
||||
var userId = initialUserInfo.userId;
|
||||
//var socket;
|
||||
var userSet = {}; // userId -> userInfo
|
||||
const userId = initialUserInfo.userId;
|
||||
// var socket;
|
||||
const userSet = {}; // userId -> userInfo
|
||||
userSet[userId] = initialUserInfo;
|
||||
|
||||
var caughtErrors = [];
|
||||
var caughtErrorCatchers = [];
|
||||
var caughtErrorTimes = [];
|
||||
var debugMessages = [];
|
||||
var msgQueue = [];
|
||||
const caughtErrors = [];
|
||||
const caughtErrorCatchers = [];
|
||||
const caughtErrorTimes = [];
|
||||
const debugMessages = [];
|
||||
const msgQueue = [];
|
||||
|
||||
var isPendingRevision = false;
|
||||
let isPendingRevision = false;
|
||||
|
||||
tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
|
||||
tellAceActiveAuthorInfo(initialUserInfo);
|
||||
|
||||
var callbacks = {
|
||||
onUserJoin: function() {},
|
||||
onUserLeave: function() {},
|
||||
onUpdateUserInfo: function() {},
|
||||
onChannelStateChange: function() {},
|
||||
onClientMessage: function() {},
|
||||
onInternalAction: function() {},
|
||||
onConnectionTrouble: function() {},
|
||||
onServerMessage: function() {}
|
||||
const callbacks = {
|
||||
onUserJoin() {},
|
||||
onUserLeave() {},
|
||||
onUpdateUserInfo() {},
|
||||
onChannelStateChange() {},
|
||||
onClientMessage() {},
|
||||
onInternalAction() {},
|
||||
onConnectionTrouble() {},
|
||||
onServerMessage() {},
|
||||
};
|
||||
if (browser.firefox)
|
||||
{
|
||||
if (browser.firefox) {
|
||||
// Prevent "escape" from taking effect and canceling a comet connection;
|
||||
// doesn't work if focus is on an iframe.
|
||||
$(window).bind("keydown", function(evt) {
|
||||
if (evt.which == 27)
|
||||
{
|
||||
evt.preventDefault()
|
||||
$(window).bind('keydown', (evt) => {
|
||||
if (evt.which == 27) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
editor.setProperty("userAuthor", userId);
|
||||
editor.setProperty('userAuthor', userId);
|
||||
editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
|
||||
editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
|
||||
editor.setUserChangeNotificationCallback(wrapRecordingErrors('handleUserChanges', handleUserChanges));
|
||||
|
||||
function dmesg(str) {
|
||||
if (typeof window.ajlog == "string") window.ajlog += str + '\n';
|
||||
if (typeof window.ajlog === 'string') window.ajlog += `${str}\n`;
|
||||
debugMessages.push(str);
|
||||
}
|
||||
|
||||
function handleUserChanges() {
|
||||
if (editor.getInInternationalComposition()) return;
|
||||
if ((!getSocket()) || channelState == "CONNECTING")
|
||||
{
|
||||
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
|
||||
{
|
||||
setChannelState("DISCONNECTED", "initsocketfail");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((!getSocket()) || channelState == 'CONNECTING') {
|
||||
if (channelState == 'CONNECTING' && (((+new Date()) - initialStartConnectTime) > 20000)) {
|
||||
setChannelState('DISCONNECTED', 'initsocketfail');
|
||||
} else {
|
||||
// check again in a bit
|
||||
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000);
|
||||
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 1000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var t = (+new Date());
|
||||
const t = (+new Date());
|
||||
|
||||
if (state != "IDLE")
|
||||
{
|
||||
if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000)
|
||||
{
|
||||
if (state != 'IDLE') {
|
||||
if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 20000) {
|
||||
// a commit is taking too long
|
||||
setChannelState("DISCONNECTED", "slowcommit");
|
||||
}
|
||||
else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000)
|
||||
{
|
||||
callbacks.onConnectionTrouble("SLOW");
|
||||
}
|
||||
else
|
||||
{
|
||||
setChannelState('DISCONNECTED', 'slowcommit');
|
||||
} else if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 5000) {
|
||||
callbacks.onConnectionTrouble('SLOW');
|
||||
} else {
|
||||
// run again in a few seconds, to detect a disconnect
|
||||
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
|
||||
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var earliestCommit = lastCommitTime + 500;
|
||||
if (t < earliestCommit)
|
||||
{
|
||||
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t);
|
||||
const earliestCommit = lastCommitTime + 500;
|
||||
if (t < earliestCommit) {
|
||||
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), earliestCommit - t);
|
||||
return;
|
||||
}
|
||||
|
||||
// apply msgQueue changeset.
|
||||
if (msgQueue.length != 0) {
|
||||
var msg;
|
||||
let msg;
|
||||
while (msg = msgQueue.shift()) {
|
||||
var newRev = msg.newRev;
|
||||
rev=newRev;
|
||||
if (msg.type == "ACCEPT_COMMIT")
|
||||
{
|
||||
const newRev = msg.newRev;
|
||||
rev = newRev;
|
||||
if (msg.type == 'ACCEPT_COMMIT') {
|
||||
editor.applyPreparedChangesetToBase();
|
||||
setStateIdle();
|
||||
callCatchingErrors("onInternalAction", function() {
|
||||
callbacks.onInternalAction("commitAcceptedByServer");
|
||||
callCatchingErrors('onInternalAction', () => {
|
||||
callbacks.onInternalAction('commitAcceptedByServer');
|
||||
});
|
||||
callCatchingErrors("onConnectionTrouble", function() {
|
||||
callbacks.onConnectionTrouble("OK");
|
||||
callCatchingErrors('onConnectionTrouble', () => {
|
||||
callbacks.onConnectionTrouble('OK');
|
||||
});
|
||||
handleUserChanges();
|
||||
}
|
||||
else if (msg.type == "NEW_CHANGES")
|
||||
{
|
||||
var changeset = msg.changeset;
|
||||
var author = (msg.author || '');
|
||||
var apool = msg.apool;
|
||||
} else if (msg.type == 'NEW_CHANGES') {
|
||||
const changeset = msg.changeset;
|
||||
const author = (msg.author || '');
|
||||
const apool = msg.apool;
|
||||
|
||||
editor.applyChangesToBase(changeset, author, apool);
|
||||
}
|
||||
|
@ -171,43 +155,38 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
}
|
||||
}
|
||||
|
||||
var sentMessage = false;
|
||||
let sentMessage = false;
|
||||
// Check if there are any pending revisions to be received from server.
|
||||
// Allow only if there are no pending revisions to be received from server
|
||||
if (!isPendingRevision)
|
||||
{
|
||||
var userChangesData = editor.prepareUserChangeset();
|
||||
if (userChangesData.changeset)
|
||||
{
|
||||
lastCommitTime = t;
|
||||
state = "COMMITTING";
|
||||
stateMessage = {
|
||||
type: "USER_CHANGES",
|
||||
baseRev: rev,
|
||||
changeset: userChangesData.changeset,
|
||||
apool: userChangesData.apool
|
||||
};
|
||||
sendMessage(stateMessage);
|
||||
sentMessage = true;
|
||||
callbacks.onInternalAction("commitPerformed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// run again in a few seconds, to check if there was a reconnection attempt
|
||||
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
|
||||
if (!isPendingRevision) {
|
||||
const userChangesData = editor.prepareUserChangeset();
|
||||
if (userChangesData.changeset) {
|
||||
lastCommitTime = t;
|
||||
state = 'COMMITTING';
|
||||
stateMessage = {
|
||||
type: 'USER_CHANGES',
|
||||
baseRev: rev,
|
||||
changeset: userChangesData.changeset,
|
||||
apool: userChangesData.apool,
|
||||
};
|
||||
sendMessage(stateMessage);
|
||||
sentMessage = true;
|
||||
callbacks.onInternalAction('commitPerformed');
|
||||
}
|
||||
} else {
|
||||
// run again in a few seconds, to check if there was a reconnection attempt
|
||||
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
|
||||
}
|
||||
|
||||
if (sentMessage)
|
||||
{
|
||||
if (sentMessage) {
|
||||
// run again in a few seconds, to detect a disconnect
|
||||
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
|
||||
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function setUpSocket() {
|
||||
hiccupCount = 0;
|
||||
setChannelState("CONNECTED");
|
||||
setChannelState('CONNECTED');
|
||||
doDeferredActions();
|
||||
|
||||
initialStartConnectTime = +new Date();
|
||||
|
@ -217,49 +196,42 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
|
||||
function sendMessage(msg) {
|
||||
getSocket().json.send(
|
||||
{
|
||||
type: "COLLABROOM",
|
||||
component: "pad",
|
||||
data: msg
|
||||
});
|
||||
{
|
||||
type: 'COLLABROOM',
|
||||
component: 'pad',
|
||||
data: msg,
|
||||
});
|
||||
}
|
||||
|
||||
function wrapRecordingErrors(catcher, func) {
|
||||
return function() {
|
||||
try
|
||||
{
|
||||
return function () {
|
||||
try {
|
||||
return func.apply(this, Array.prototype.slice.call(arguments));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
} catch (e) {
|
||||
caughtErrors.push(e);
|
||||
caughtErrorCatchers.push(catcher);
|
||||
caughtErrorTimes.push(+new Date());
|
||||
//console.dir({catcher: catcher, e: e});
|
||||
// console.dir({catcher: catcher, e: e});
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function callCatchingErrors(catcher, func) {
|
||||
try
|
||||
{
|
||||
try {
|
||||
wrapRecordingErrors(catcher, func)();
|
||||
}
|
||||
catch (e)
|
||||
{ /*absorb*/
|
||||
} catch (e) { /* absorb*/
|
||||
}
|
||||
}
|
||||
|
||||
function handleMessageFromServer(evt) {
|
||||
if (!getSocket()) return;
|
||||
if (!evt.data) return;
|
||||
var wrapper = evt;
|
||||
if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return;
|
||||
var msg = wrapper.data;
|
||||
const wrapper = evt;
|
||||
if (wrapper.type != 'COLLABROOM' && wrapper.type != 'CUSTOM') return;
|
||||
const msg = wrapper.data;
|
||||
|
||||
if (msg.type == "NEW_CHANGES")
|
||||
{
|
||||
if (msg.type == 'NEW_CHANGES') {
|
||||
var newRev = msg.newRev;
|
||||
var changeset = msg.changeset;
|
||||
var author = (msg.author || '');
|
||||
|
@ -270,9 +242,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev;
|
||||
else oldRev = rev;
|
||||
|
||||
if (newRev != (oldRev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
|
||||
if (newRev != (oldRev + 1)) {
|
||||
window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${oldRev + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_newchanges");
|
||||
return;
|
||||
}
|
||||
|
@ -280,24 +251,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
return;
|
||||
}
|
||||
|
||||
if (newRev != (rev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
|
||||
if (newRev != (rev + 1)) {
|
||||
window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${rev + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_newchanges");
|
||||
return;
|
||||
}
|
||||
rev = newRev;
|
||||
|
||||
editor.applyChangesToBase(changeset, author, apool);
|
||||
}
|
||||
else if (msg.type == "ACCEPT_COMMIT")
|
||||
{
|
||||
} else if (msg.type == 'ACCEPT_COMMIT') {
|
||||
var newRev = msg.newRev;
|
||||
if (msgQueue.length > 0)
|
||||
{
|
||||
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
|
||||
if (msgQueue.length > 0) {
|
||||
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
|
||||
window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
|
||||
return;
|
||||
}
|
||||
|
@ -305,178 +271,140 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
return;
|
||||
}
|
||||
|
||||
if (newRev != (rev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
|
||||
if (newRev != (rev + 1)) {
|
||||
window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${rev + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
|
||||
return;
|
||||
}
|
||||
rev = newRev;
|
||||
editor.applyPreparedChangesetToBase();
|
||||
setStateIdle();
|
||||
callCatchingErrors("onInternalAction", function() {
|
||||
callbacks.onInternalAction("commitAcceptedByServer");
|
||||
callCatchingErrors('onInternalAction', () => {
|
||||
callbacks.onInternalAction('commitAcceptedByServer');
|
||||
});
|
||||
callCatchingErrors("onConnectionTrouble", function() {
|
||||
callbacks.onConnectionTrouble("OK");
|
||||
callCatchingErrors('onConnectionTrouble', () => {
|
||||
callbacks.onConnectionTrouble('OK');
|
||||
});
|
||||
handleUserChanges();
|
||||
}
|
||||
else if (msg.type == 'CLIENT_RECONNECT')
|
||||
{
|
||||
} else if (msg.type == 'CLIENT_RECONNECT') {
|
||||
// Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns
|
||||
// all pending revisions along with this CLIENT_RECONNECT message
|
||||
if (msg.noChanges)
|
||||
{
|
||||
if (msg.noChanges) {
|
||||
// If no revisions are pending, just make everything normal
|
||||
setIsPendingRevision(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var headRev = msg.headRev;
|
||||
const headRev = msg.headRev;
|
||||
var newRev = msg.newRev;
|
||||
var changeset = msg.changeset;
|
||||
var author = (msg.author || '');
|
||||
var apool = msg.apool;
|
||||
|
||||
if (msgQueue.length > 0)
|
||||
{
|
||||
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
|
||||
if (msgQueue.length > 0) {
|
||||
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
|
||||
window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
|
||||
return;
|
||||
}
|
||||
msg.type = "NEW_CHANGES";
|
||||
msg.type = 'NEW_CHANGES';
|
||||
msgQueue.push(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newRev != (rev + 1))
|
||||
{
|
||||
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (rev + 1));
|
||||
if (newRev != (rev + 1)) {
|
||||
window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${rev + 1}`);
|
||||
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
|
||||
return;
|
||||
}
|
||||
|
||||
rev = newRev;
|
||||
if (author == pad.getUserId())
|
||||
{
|
||||
if (author == pad.getUserId()) {
|
||||
editor.applyPreparedChangesetToBase();
|
||||
setStateIdle();
|
||||
callCatchingErrors("onInternalAction", function() {
|
||||
callbacks.onInternalAction("commitAcceptedByServer");
|
||||
callCatchingErrors('onInternalAction', () => {
|
||||
callbacks.onInternalAction('commitAcceptedByServer');
|
||||
});
|
||||
callCatchingErrors("onConnectionTrouble", function() {
|
||||
callbacks.onConnectionTrouble("OK");
|
||||
callCatchingErrors('onConnectionTrouble', () => {
|
||||
callbacks.onConnectionTrouble('OK');
|
||||
});
|
||||
handleUserChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
editor.applyChangesToBase(changeset, author, apool);
|
||||
}
|
||||
|
||||
if (newRev == headRev)
|
||||
{
|
||||
if (newRev == headRev) {
|
||||
// Once we have applied all pending revisions, make everything normal
|
||||
setIsPendingRevision(false);
|
||||
}
|
||||
}
|
||||
else if (msg.type == "NO_COMMIT_PENDING")
|
||||
{
|
||||
if (state == "COMMITTING")
|
||||
{
|
||||
} else if (msg.type == 'NO_COMMIT_PENDING') {
|
||||
if (state == 'COMMITTING') {
|
||||
// server missed our commit message; abort that commit
|
||||
setStateIdle();
|
||||
handleUserChanges();
|
||||
}
|
||||
}
|
||||
else if (msg.type == "USER_NEWINFO")
|
||||
{
|
||||
} else if (msg.type == 'USER_NEWINFO') {
|
||||
var userInfo = msg.userInfo;
|
||||
var id = userInfo.userId;
|
||||
|
||||
// Avoid a race condition when setting colors. If our color was set by a
|
||||
// query param, ignore our own "new user" message's color value.
|
||||
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor)
|
||||
{
|
||||
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) {
|
||||
msg.userInfo.colorId = initialUserInfo.globalUserColor;
|
||||
}
|
||||
|
||||
|
||||
if (userSet[id])
|
||||
{
|
||||
if (userSet[id]) {
|
||||
userSet[id] = userInfo;
|
||||
callbacks.onUpdateUserInfo(userInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
userSet[id] = userInfo;
|
||||
callbacks.onUserJoin(userInfo);
|
||||
}
|
||||
tellAceActiveAuthorInfo(userInfo);
|
||||
}
|
||||
else if (msg.type == "USER_LEAVE")
|
||||
{
|
||||
} else if (msg.type == 'USER_LEAVE') {
|
||||
var userInfo = msg.userInfo;
|
||||
var id = userInfo.userId;
|
||||
if (userSet[id])
|
||||
{
|
||||
if (userSet[id]) {
|
||||
delete userSet[userInfo.userId];
|
||||
fadeAceAuthorInfo(userInfo);
|
||||
callbacks.onUserLeave(userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
else if (msg.type == "DISCONNECT_REASON")
|
||||
{
|
||||
} else if (msg.type == 'DISCONNECT_REASON') {
|
||||
appLevelDisconnectReason = msg.reason;
|
||||
}
|
||||
else if (msg.type == "CLIENT_MESSAGE")
|
||||
{
|
||||
} else if (msg.type == 'CLIENT_MESSAGE') {
|
||||
callbacks.onClientMessage(msg.payload);
|
||||
}
|
||||
else if (msg.type == "CHAT_MESSAGE")
|
||||
{
|
||||
} else if (msg.type == 'CHAT_MESSAGE') {
|
||||
chat.addMessage(msg, true, false);
|
||||
}
|
||||
else if (msg.type == "CHAT_MESSAGES")
|
||||
{
|
||||
for(var i = msg.messages.length - 1; i >= 0; i--)
|
||||
{
|
||||
} else if (msg.type == 'CHAT_MESSAGES') {
|
||||
for (let i = msg.messages.length - 1; i >= 0; i--) {
|
||||
chat.addMessage(msg.messages[i], true, true);
|
||||
}
|
||||
if(!chat.gotInitalMessages)
|
||||
{
|
||||
if (!chat.gotInitalMessages) {
|
||||
chat.scrollDown();
|
||||
chat.gotInitalMessages = true;
|
||||
chat.historyPointer = clientVars.chatHead - msg.messages.length;
|
||||
}
|
||||
|
||||
// messages are loaded, so hide the loading-ball
|
||||
$("#chatloadmessagesball").css("display", "none");
|
||||
$('#chatloadmessagesball').css('display', 'none');
|
||||
|
||||
// there are less than 100 messages or we reached the top
|
||||
if(chat.historyPointer <= 0)
|
||||
$("#chatloadmessagesbutton").css("display", "none");
|
||||
else // there are still more messages, re-show the load-button
|
||||
$("#chatloadmessagesbutton").css("display", "block");
|
||||
}
|
||||
else if (msg.type == "SERVER_MESSAGE")
|
||||
{
|
||||
if (chat.historyPointer <= 0) { $('#chatloadmessagesbutton').css('display', 'none'); } else // there are still more messages, re-show the load-button
|
||||
{ $('#chatloadmessagesbutton').css('display', 'block'); }
|
||||
} else if (msg.type == 'SERVER_MESSAGE') {
|
||||
callbacks.onServerMessage(msg.payload);
|
||||
}
|
||||
|
||||
//HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload
|
||||
//FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work
|
||||
if(msg.type.indexOf("USER_") > -1) {
|
||||
// HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload
|
||||
// FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work
|
||||
if (msg.type.indexOf('USER_') > -1) {
|
||||
msg.payload = msg.userInfo;
|
||||
}
|
||||
// Similar for NEW_CHANGES
|
||||
if(msg.type === "NEW_CHANGES") msg.payload = msg;
|
||||
if (msg.type === 'NEW_CHANGES') msg.payload = msg;
|
||||
|
||||
hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload});
|
||||
hooks.callAll(`handleClientMessage_${msg.type}`, {payload: msg.payload});
|
||||
}
|
||||
|
||||
function updateUserInfo(userInfo) {
|
||||
|
@ -485,10 +413,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
tellAceActiveAuthorInfo(userInfo);
|
||||
if (!getSocket()) return;
|
||||
sendMessage(
|
||||
{
|
||||
type: "USERINFO_UPDATE",
|
||||
userInfo: userInfo
|
||||
});
|
||||
{
|
||||
type: 'USERINFO_UPDATE',
|
||||
userInfo,
|
||||
});
|
||||
}
|
||||
|
||||
function tellAceActiveAuthorInfo(userInfo) {
|
||||
|
@ -496,23 +424,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
}
|
||||
|
||||
function tellAceAuthorInfo(userId, colorId, inactive) {
|
||||
if(typeof colorId == "number")
|
||||
{
|
||||
if (typeof colorId === 'number') {
|
||||
colorId = clientVars.colorPalette[colorId];
|
||||
}
|
||||
|
||||
var cssColor = colorId;
|
||||
if (inactive)
|
||||
{
|
||||
const cssColor = colorId;
|
||||
if (inactive) {
|
||||
editor.setAuthorInfo(userId, {
|
||||
bgcolor: cssColor,
|
||||
fade: 0.5
|
||||
fade: 0.5,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
editor.setAuthorInfo(userId, {
|
||||
bgcolor: cssColor
|
||||
bgcolor: cssColor,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -526,27 +450,24 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
}
|
||||
|
||||
function tellAceAboutHistoricalAuthors(hadata) {
|
||||
for (var author in hadata)
|
||||
{
|
||||
var data = hadata[author];
|
||||
if (!userSet[author])
|
||||
{
|
||||
for (const author in hadata) {
|
||||
const data = hadata[author];
|
||||
if (!userSet[author]) {
|
||||
tellAceAuthorInfo(author, data.colorId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setChannelState(newChannelState, moreInfo) {
|
||||
if (newChannelState != channelState)
|
||||
{
|
||||
if (newChannelState != channelState) {
|
||||
channelState = newChannelState;
|
||||
callbacks.onChannelStateChange(channelState, moreInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function valuesArray(obj) {
|
||||
var array = [];
|
||||
$.each(obj, function(k, v) {
|
||||
const array = [];
|
||||
$.each(obj, (k, v) => {
|
||||
array.push(v);
|
||||
});
|
||||
return array;
|
||||
|
@ -554,39 +475,32 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
|
||||
// We need to present a working interface even before the socket
|
||||
// is connected for the first time.
|
||||
var deferredActions = [];
|
||||
let deferredActions = [];
|
||||
|
||||
function defer(func, tag) {
|
||||
return function() {
|
||||
var that = this;
|
||||
var args = arguments;
|
||||
return function () {
|
||||
const that = this;
|
||||
const args = arguments;
|
||||
|
||||
function action() {
|
||||
func.apply(that, args);
|
||||
}
|
||||
action.tag = tag;
|
||||
if (channelState == "CONNECTING")
|
||||
{
|
||||
if (channelState == 'CONNECTING') {
|
||||
deferredActions.push(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function doDeferredActions(tag) {
|
||||
var newArray = [];
|
||||
for (var i = 0; i < deferredActions.length; i++)
|
||||
{
|
||||
var a = deferredActions[i];
|
||||
if ((!tag) || (tag == a.tag))
|
||||
{
|
||||
const newArray = [];
|
||||
for (let i = 0; i < deferredActions.length; i++) {
|
||||
const a = deferredActions[i];
|
||||
if ((!tag) || (tag == a.tag)) {
|
||||
a();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
newArray.push(a);
|
||||
}
|
||||
}
|
||||
|
@ -595,10 +509,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
|
||||
function sendClientMessage(msg) {
|
||||
sendMessage(
|
||||
{
|
||||
type: "CLIENT_MESSAGE",
|
||||
payload: msg
|
||||
});
|
||||
{
|
||||
type: 'CLIENT_MESSAGE',
|
||||
payload: msg,
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentRevisionNumber() {
|
||||
|
@ -606,18 +520,16 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
}
|
||||
|
||||
function getMissedChanges() {
|
||||
var obj = {};
|
||||
const obj = {};
|
||||
obj.userInfo = userSet[userId];
|
||||
obj.baseRev = rev;
|
||||
if (state == "COMMITTING" && stateMessage)
|
||||
{
|
||||
if (state == 'COMMITTING' && stateMessage) {
|
||||
obj.committedChangeset = stateMessage.changeset;
|
||||
obj.committedChangesetAPool = stateMessage.apool;
|
||||
editor.applyPreparedChangesetToBase();
|
||||
}
|
||||
var userChangesData = editor.prepareUserChangeset();
|
||||
if (userChangesData.changeset)
|
||||
{
|
||||
const userChangesData = editor.prepareUserChangeset();
|
||||
if (userChangesData.changeset) {
|
||||
obj.furtherChangeset = userChangesData.changeset;
|
||||
obj.furtherChangesetAPool = userChangesData.apool;
|
||||
}
|
||||
|
@ -625,8 +537,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
}
|
||||
|
||||
function setStateIdle() {
|
||||
state = "IDLE";
|
||||
callbacks.onInternalAction("newlyIdle");
|
||||
state = 'IDLE';
|
||||
callbacks.onInternalAction('newlyIdle');
|
||||
schedulePerhapsCallIdleFuncs();
|
||||
}
|
||||
|
||||
|
@ -642,55 +554,53 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
var idleFuncs = [];
|
||||
|
||||
function schedulePerhapsCallIdleFuncs() {
|
||||
setTimeout(function() {
|
||||
if (state == "IDLE")
|
||||
{
|
||||
while (idleFuncs.length > 0)
|
||||
{
|
||||
var f = idleFuncs.shift();
|
||||
setTimeout(() => {
|
||||
if (state == 'IDLE') {
|
||||
while (idleFuncs.length > 0) {
|
||||
const f = idleFuncs.shift();
|
||||
f();
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
var self = {
|
||||
setOnUserJoin: function(cb) {
|
||||
const self = {
|
||||
setOnUserJoin(cb) {
|
||||
callbacks.onUserJoin = cb;
|
||||
},
|
||||
setOnUserLeave: function(cb) {
|
||||
setOnUserLeave(cb) {
|
||||
callbacks.onUserLeave = cb;
|
||||
},
|
||||
setOnUpdateUserInfo: function(cb) {
|
||||
setOnUpdateUserInfo(cb) {
|
||||
callbacks.onUpdateUserInfo = cb;
|
||||
},
|
||||
setOnChannelStateChange: function(cb) {
|
||||
setOnChannelStateChange(cb) {
|
||||
callbacks.onChannelStateChange = cb;
|
||||
},
|
||||
setOnClientMessage: function(cb) {
|
||||
setOnClientMessage(cb) {
|
||||
callbacks.onClientMessage = cb;
|
||||
},
|
||||
setOnInternalAction: function(cb) {
|
||||
setOnInternalAction(cb) {
|
||||
callbacks.onInternalAction = cb;
|
||||
},
|
||||
setOnConnectionTrouble: function(cb) {
|
||||
setOnConnectionTrouble(cb) {
|
||||
callbacks.onConnectionTrouble = cb;
|
||||
},
|
||||
setOnServerMessage: function(cb) {
|
||||
setOnServerMessage(cb) {
|
||||
callbacks.onServerMessage = cb;
|
||||
},
|
||||
updateUserInfo: defer(updateUserInfo),
|
||||
handleMessageFromServer: handleMessageFromServer,
|
||||
getConnectedUsers: getConnectedUsers,
|
||||
sendClientMessage: sendClientMessage,
|
||||
sendMessage: sendMessage,
|
||||
getCurrentRevisionNumber: getCurrentRevisionNumber,
|
||||
getMissedChanges: getMissedChanges,
|
||||
callWhenNotCommitting: callWhenNotCommitting,
|
||||
handleMessageFromServer,
|
||||
getConnectedUsers,
|
||||
sendClientMessage,
|
||||
sendMessage,
|
||||
getCurrentRevisionNumber,
|
||||
getMissedChanges,
|
||||
callWhenNotCommitting,
|
||||
addHistoricalAuthors: tellAceAboutHistoricalAuthors,
|
||||
setChannelState: setChannelState,
|
||||
setStateIdle: setStateIdle,
|
||||
setIsPendingRevision: setIsPendingRevision
|
||||
setChannelState,
|
||||
setStateIdle,
|
||||
setIsPendingRevision,
|
||||
};
|
||||
|
||||
setUpSocket();
|
||||
|
|
|
@ -22,111 +22,110 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var colorutils = {};
|
||||
const colorutils = {};
|
||||
|
||||
// Check that a given value is a css hex color value, e.g.
|
||||
// "#ffffff" or "#fff"
|
||||
colorutils.isCssHex = function(cssColor) {
|
||||
colorutils.isCssHex = function (cssColor) {
|
||||
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor);
|
||||
}
|
||||
};
|
||||
|
||||
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
|
||||
colorutils.css2triple = function(cssColor) {
|
||||
var sixHex = colorutils.css2sixhex(cssColor);
|
||||
colorutils.css2triple = function (cssColor) {
|
||||
const sixHex = colorutils.css2sixhex(cssColor);
|
||||
|
||||
function hexToFloat(hh) {
|
||||
return Number("0x" + hh) / 255;
|
||||
return Number(`0x${hh}`) / 255;
|
||||
}
|
||||
return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))];
|
||||
}
|
||||
};
|
||||
|
||||
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
|
||||
colorutils.css2sixhex = function(cssColor) {
|
||||
var h = /[0-9a-fA-F]+/.exec(cssColor)[0];
|
||||
if (h.length != 6)
|
||||
{
|
||||
var a = h.charAt(0);
|
||||
var b = h.charAt(1);
|
||||
var c = h.charAt(2);
|
||||
colorutils.css2sixhex = function (cssColor) {
|
||||
let h = /[0-9a-fA-F]+/.exec(cssColor)[0];
|
||||
if (h.length != 6) {
|
||||
const a = h.charAt(0);
|
||||
const b = h.charAt(1);
|
||||
const c = h.charAt(2);
|
||||
h = a + a + b + b + c + c;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
// [1.0, 1.0, 1.0] -> "#ffffff"
|
||||
colorutils.triple2css = function(triple) {
|
||||
colorutils.triple2css = function (triple) {
|
||||
function floatToHex(n) {
|
||||
var n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
|
||||
return ("0" + n2.toString(16)).slice(-2);
|
||||
const n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
|
||||
return (`0${n2.toString(16)}`).slice(-2);
|
||||
}
|
||||
return "#" + floatToHex(triple[0]) + floatToHex(triple[1]) + floatToHex(triple[2]);
|
||||
}
|
||||
return `#${floatToHex(triple[0])}${floatToHex(triple[1])}${floatToHex(triple[2])}`;
|
||||
};
|
||||
|
||||
|
||||
colorutils.clamp = function(v, bot, top) {
|
||||
colorutils.clamp = function (v, bot, top) {
|
||||
return v < bot ? bot : (v > top ? top : v);
|
||||
};
|
||||
colorutils.min3 = function(a, b, c) {
|
||||
colorutils.min3 = function (a, b, c) {
|
||||
return (a < b) ? (a < c ? a : c) : (b < c ? b : c);
|
||||
};
|
||||
colorutils.max3 = function(a, b, c) {
|
||||
colorutils.max3 = function (a, b, c) {
|
||||
return (a > b) ? (a > c ? a : c) : (b > c ? b : c);
|
||||
};
|
||||
colorutils.colorMin = function(c) {
|
||||
colorutils.colorMin = function (c) {
|
||||
return colorutils.min3(c[0], c[1], c[2]);
|
||||
};
|
||||
colorutils.colorMax = function(c) {
|
||||
colorutils.colorMax = function (c) {
|
||||
return colorutils.max3(c[0], c[1], c[2]);
|
||||
};
|
||||
colorutils.scale = function(v, bot, top) {
|
||||
colorutils.scale = function (v, bot, top) {
|
||||
return colorutils.clamp(bot + v * (top - bot), 0, 1);
|
||||
};
|
||||
colorutils.unscale = function(v, bot, top) {
|
||||
colorutils.unscale = function (v, bot, top) {
|
||||
return colorutils.clamp((v - bot) / (top - bot), 0, 1);
|
||||
};
|
||||
|
||||
colorutils.scaleColor = function(c, bot, top) {
|
||||
colorutils.scaleColor = function (c, bot, top) {
|
||||
return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)];
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.unscaleColor = function(c, bot, top) {
|
||||
colorutils.unscaleColor = function (c, bot, top) {
|
||||
return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], bot, top), colorutils.unscale(c[2], bot, top)];
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.luminosity = function(c) {
|
||||
colorutils.luminosity = function (c) {
|
||||
// rule of thumb for RGB brightness; 1.0 is white
|
||||
return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.saturate = function(c) {
|
||||
var min = colorutils.colorMin(c);
|
||||
var max = colorutils.colorMax(c);
|
||||
colorutils.saturate = function (c) {
|
||||
const min = colorutils.colorMin(c);
|
||||
const max = colorutils.colorMax(c);
|
||||
if (max - min <= 0) return [1.0, 1.0, 1.0];
|
||||
return colorutils.unscaleColor(c, min, max);
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.blend = function(c1, c2, t) {
|
||||
colorutils.blend = function (c1, c2, t) {
|
||||
return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.invert = function(c) {
|
||||
return [1 - c[0], 1 - c[1], 1- c[2]];
|
||||
}
|
||||
colorutils.invert = function (c) {
|
||||
return [1 - c[0], 1 - c[1], 1 - c[2]];
|
||||
};
|
||||
|
||||
colorutils.complementary = function(c) {
|
||||
var inv = colorutils.invert(c);
|
||||
colorutils.complementary = function (c) {
|
||||
const inv = colorutils.invert(c);
|
||||
return [
|
||||
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
|
||||
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
|
||||
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11)
|
||||
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
colorutils.textColorFromBackgroundColor = function(bgcolor, skinName) {
|
||||
var white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff';
|
||||
var black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222';
|
||||
colorutils.textColorFromBackgroundColor = function (bgcolor, skinName) {
|
||||
const white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff';
|
||||
const black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222';
|
||||
|
||||
return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black;
|
||||
}
|
||||
};
|
||||
|
||||
exports.colorutils = colorutils;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,41 +21,34 @@
|
|||
*/
|
||||
|
||||
function makeCSSManager(emptyStylesheetTitle, doc) {
|
||||
if (doc === true)
|
||||
{
|
||||
if (doc === true) {
|
||||
doc = 'parent';
|
||||
} else if (!doc) {
|
||||
doc = 'inner';
|
||||
}
|
||||
|
||||
function getSheetByTitle(title) {
|
||||
if (doc === 'parent')
|
||||
{
|
||||
if (doc === 'parent') {
|
||||
win = window.parent.parent;
|
||||
}
|
||||
else if (doc === 'inner') {
|
||||
} else if (doc === 'inner') {
|
||||
win = window;
|
||||
}
|
||||
else if (doc === 'outer') {
|
||||
} else if (doc === 'outer') {
|
||||
win = window.parent;
|
||||
} else {
|
||||
throw 'Unknown dynamic style container';
|
||||
}
|
||||
else {
|
||||
throw "Unknown dynamic style container";
|
||||
}
|
||||
var allSheets = win.document.styleSheets;
|
||||
const allSheets = win.document.styleSheets;
|
||||
|
||||
for (var i = 0; i < allSheets.length; i++)
|
||||
{
|
||||
var s = allSheets[i];
|
||||
if (s.title == title)
|
||||
{
|
||||
for (let i = 0; i < allSheets.length; i++) {
|
||||
const s = allSheets[i];
|
||||
if (s.title == title) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var browserSheet = getSheetByTitle(emptyStylesheetTitle);
|
||||
const browserSheet = getSheetByTitle(emptyStylesheetTitle);
|
||||
|
||||
function browserRules() {
|
||||
return (browserSheet.cssRules || browserSheet.rules);
|
||||
|
@ -67,16 +60,14 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
|
|||
}
|
||||
|
||||
function browserInsertRule(i, selector) {
|
||||
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
|
||||
if (browserSheet.insertRule) browserSheet.insertRule(`${selector} {}`, i);
|
||||
else browserSheet.addRule(selector, null, i);
|
||||
}
|
||||
var selectorList = [];
|
||||
const selectorList = [];
|
||||
|
||||
function indexOfSelector(selector) {
|
||||
for (var i = 0; i < selectorList.length; i++)
|
||||
{
|
||||
if (selectorList[i] == selector)
|
||||
{
|
||||
for (let i = 0; i < selectorList.length; i++) {
|
||||
if (selectorList[i] == selector) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -84,9 +75,8 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
|
|||
}
|
||||
|
||||
function selectorStyle(selector) {
|
||||
var i = indexOfSelector(selector);
|
||||
if (i < 0)
|
||||
{
|
||||
let i = indexOfSelector(selector);
|
||||
if (i < 0) {
|
||||
// add selector
|
||||
browserInsertRule(0, selector);
|
||||
selectorList.splice(0, 0, selector);
|
||||
|
@ -96,20 +86,19 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
|
|||
}
|
||||
|
||||
function removeSelectorStyle(selector) {
|
||||
var i = indexOfSelector(selector);
|
||||
if (i >= 0)
|
||||
{
|
||||
const i = indexOfSelector(selector);
|
||||
if (i >= 0) {
|
||||
browserDeleteRule(i);
|
||||
selectorList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectorStyle: selectorStyle,
|
||||
removeSelectorStyle: removeSelectorStyle,
|
||||
info: function() {
|
||||
return selectorList.length + ":" + browserRules().length;
|
||||
}
|
||||
selectorStyle,
|
||||
removeSelectorStyle,
|
||||
info() {
|
||||
return `${selectorList.length}:${browserRules().length}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -26,167 +26,150 @@
|
|||
// requires: plugins
|
||||
// requires: undefined
|
||||
|
||||
var Security = require('./security');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var _ = require('./underscore');
|
||||
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
||||
var noop = function(){};
|
||||
const Security = require('./security');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const _ = require('./underscore');
|
||||
const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
||||
const noop = function () {};
|
||||
|
||||
|
||||
var domline = {};
|
||||
const domline = {};
|
||||
|
||||
domline.addToLineClass = function(lineClass, cls) {
|
||||
domline.addToLineClass = function (lineClass, cls) {
|
||||
// an "empty span" at any point can be used to add classes to
|
||||
// the line, using line:className. otherwise, we ignore
|
||||
// the span.
|
||||
cls.replace(/\S+/g, function(c) {
|
||||
if (c.indexOf("line:") == 0)
|
||||
{
|
||||
cls.replace(/\S+/g, (c) => {
|
||||
if (c.indexOf('line:') == 0) {
|
||||
// add class to line
|
||||
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
|
||||
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
|
||||
}
|
||||
});
|
||||
return lineClass;
|
||||
}
|
||||
};
|
||||
|
||||
// if "document" is falsy we don't create a DOM node, just
|
||||
// an object with innerHTML and className
|
||||
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
|
||||
var result = {
|
||||
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
||||
const result = {
|
||||
node: null,
|
||||
appendSpan: noop,
|
||||
prepareForAdd: noop,
|
||||
notifyAdded: noop,
|
||||
clearSpans: noop,
|
||||
finishUpdate: noop,
|
||||
lineMarker: 0
|
||||
lineMarker: 0,
|
||||
};
|
||||
|
||||
var document = optDocument;
|
||||
const document = optDocument;
|
||||
|
||||
if (document)
|
||||
{
|
||||
result.node = document.createElement("div");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (document) {
|
||||
result.node = document.createElement('div');
|
||||
} else {
|
||||
result.node = {
|
||||
innerHTML: '',
|
||||
className: ''
|
||||
className: '',
|
||||
};
|
||||
}
|
||||
|
||||
var html = [];
|
||||
var preHtml = '',
|
||||
postHtml = '';
|
||||
var curHTML = null;
|
||||
let html = [];
|
||||
let preHtml = '';
|
||||
let postHtml = '';
|
||||
let curHTML = null;
|
||||
|
||||
function processSpaces(s) {
|
||||
return domline.processSpaces(s, doesWrap);
|
||||
}
|
||||
|
||||
var perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
|
||||
var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
|
||||
var lineClass = 'ace-line';
|
||||
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
|
||||
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
|
||||
let lineClass = 'ace-line';
|
||||
|
||||
result.appendSpan = function(txt, cls) {
|
||||
|
||||
var processedMarker = false;
|
||||
result.appendSpan = function (txt, cls) {
|
||||
let processedMarker = false;
|
||||
// Handle lineAttributeMarker, if present
|
||||
if (cls.indexOf(lineAttributeMarker) >= 0)
|
||||
{
|
||||
var listType = /(?:^| )list:(\S+)/.exec(cls);
|
||||
var start = /(?:^| )start:(\S+)/.exec(cls);
|
||||
if (cls.indexOf(lineAttributeMarker) >= 0) {
|
||||
let listType = /(?:^| )list:(\S+)/.exec(cls);
|
||||
const start = /(?:^| )start:(\S+)/.exec(cls);
|
||||
|
||||
_.map(hooks.callAll("aceDomLinePreProcessLineAttributes", {
|
||||
domline: domline,
|
||||
cls: cls
|
||||
}), function(modifier) {
|
||||
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
|
||||
domline,
|
||||
cls,
|
||||
}), (modifier) => {
|
||||
preHtml += modifier.preHtml;
|
||||
postHtml += modifier.postHtml;
|
||||
processedMarker |= modifier.processedMarker;
|
||||
});
|
||||
|
||||
if (listType)
|
||||
{
|
||||
if (listType) {
|
||||
listType = listType[1];
|
||||
if (listType)
|
||||
{
|
||||
if(listType.indexOf("number") < 0)
|
||||
{
|
||||
preHtml += '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
|
||||
postHtml = '</li></ul>' + postHtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(start){ // is it a start of a list with more than one item in?
|
||||
if(start[1] == 1){ // if its the first one at this level?
|
||||
lineClass = lineClass + " " + "list-start-" + listType; // Add start class to DIV node
|
||||
if (listType) {
|
||||
if (listType.indexOf('number') < 0) {
|
||||
preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
|
||||
postHtml = `</li></ul>${postHtml}`;
|
||||
} else {
|
||||
if (start) { // is it a start of a list with more than one item in?
|
||||
if (start[1] == 1) { // if its the first one at this level?
|
||||
lineClass = `${lineClass} ` + `list-start-${listType}`; // Add start class to DIV node
|
||||
}
|
||||
preHtml += '<ol start='+start[1]+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
|
||||
}else{
|
||||
preHtml += '<ol class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; // Handles pasted contents into existing lists
|
||||
preHtml += `<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
|
||||
} else {
|
||||
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`; // Handles pasted contents into existing lists
|
||||
}
|
||||
postHtml += '</li></ol>';
|
||||
}
|
||||
}
|
||||
processedMarker = true;
|
||||
}
|
||||
_.map(hooks.callAll("aceDomLineProcessLineAttributes", {
|
||||
domline: domline,
|
||||
cls: cls
|
||||
}), function(modifier) {
|
||||
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
|
||||
domline,
|
||||
cls,
|
||||
}), (modifier) => {
|
||||
preHtml += modifier.preHtml;
|
||||
postHtml += modifier.postHtml;
|
||||
processedMarker |= modifier.processedMarker;
|
||||
});
|
||||
if( processedMarker ){
|
||||
if (processedMarker) {
|
||||
result.lineMarker += txt.length;
|
||||
return; // don't append any text
|
||||
}
|
||||
}
|
||||
var href = null;
|
||||
var simpleTags = null;
|
||||
if (cls.indexOf('url') >= 0)
|
||||
{
|
||||
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url) {
|
||||
let href = null;
|
||||
let simpleTags = null;
|
||||
if (cls.indexOf('url') >= 0) {
|
||||
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
|
||||
href = url;
|
||||
return space + "url";
|
||||
return `${space}url`;
|
||||
});
|
||||
}
|
||||
if (cls.indexOf('tag') >= 0)
|
||||
{
|
||||
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag) {
|
||||
if (cls.indexOf('tag') >= 0) {
|
||||
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
|
||||
if (!simpleTags) simpleTags = [];
|
||||
simpleTags.push(tag.toLowerCase());
|
||||
return space + tag;
|
||||
});
|
||||
}
|
||||
|
||||
var extraOpenTags = "";
|
||||
var extraCloseTags = "";
|
||||
let extraOpenTags = '';
|
||||
let extraCloseTags = '';
|
||||
|
||||
_.map(hooks.callAll("aceCreateDomLine", {
|
||||
domline: domline,
|
||||
cls: cls
|
||||
}), function(modifier) {
|
||||
_.map(hooks.callAll('aceCreateDomLine', {
|
||||
domline,
|
||||
cls,
|
||||
}), (modifier) => {
|
||||
cls = modifier.cls;
|
||||
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
|
||||
extraOpenTags += modifier.extraOpenTags;
|
||||
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
|
||||
});
|
||||
|
||||
if ((!txt) && cls)
|
||||
{
|
||||
if ((!txt) && cls) {
|
||||
lineClass = domline.addToLineClass(lineClass, cls);
|
||||
}
|
||||
else if (txt)
|
||||
{
|
||||
if (href)
|
||||
{
|
||||
urn_schemes = new RegExp("^(about|geo|mailto|tel):");
|
||||
if(!~href.indexOf("://") && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
|
||||
} else if (txt) {
|
||||
if (href) {
|
||||
urn_schemes = new RegExp('^(about|geo|mailto|tel):');
|
||||
if (!~href.indexOf('://') && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
|
||||
{
|
||||
href = "http://"+href;
|
||||
href = `http://${href}`;
|
||||
}
|
||||
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
|
||||
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
|
||||
|
@ -195,115 +178,94 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '" rel="noreferrer noopener">';
|
||||
extraCloseTags = '</a>' + extraCloseTags;
|
||||
extraOpenTags = `${extraOpenTags}<a href="${Security.escapeHTMLAttribute(href)}" rel="noreferrer noopener">`;
|
||||
extraCloseTags = `</a>${extraCloseTags}`;
|
||||
}
|
||||
if (simpleTags)
|
||||
{
|
||||
if (simpleTags) {
|
||||
simpleTags.sort();
|
||||
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
|
||||
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
|
||||
simpleTags.reverse();
|
||||
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
|
||||
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
|
||||
}
|
||||
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
|
||||
}
|
||||
};
|
||||
result.clearSpans = function() {
|
||||
result.clearSpans = function () {
|
||||
html = [];
|
||||
lineClass = 'ace-line';
|
||||
result.lineMarker = 0;
|
||||
};
|
||||
|
||||
function writeHTML() {
|
||||
var newHTML = perHtmlLineProcess(html.join(''));
|
||||
if (!newHTML)
|
||||
{
|
||||
if ((!document) || (!optBrowser))
|
||||
{
|
||||
let newHTML = perHtmlLineProcess(html.join(''));
|
||||
if (!newHTML) {
|
||||
if ((!document) || (!optBrowser)) {
|
||||
newHTML += ' ';
|
||||
}
|
||||
else if (!optBrowser.msie)
|
||||
{
|
||||
} else if (!optBrowser.msie) {
|
||||
newHTML += '<br/>';
|
||||
}
|
||||
}
|
||||
if (nonEmpty)
|
||||
{
|
||||
if (nonEmpty) {
|
||||
newHTML = (preHtml || '') + newHTML + (postHtml || '');
|
||||
}
|
||||
html = preHtml = postHtml = ''; // free memory
|
||||
if (newHTML !== curHTML)
|
||||
{
|
||||
if (newHTML !== curHTML) {
|
||||
curHTML = newHTML;
|
||||
result.node.innerHTML = curHTML;
|
||||
}
|
||||
if (lineClass !== null) result.node.className = lineClass;
|
||||
|
||||
hooks.callAll("acePostWriteDomLineHTML", {
|
||||
node: result.node
|
||||
hooks.callAll('acePostWriteDomLineHTML', {
|
||||
node: result.node,
|
||||
});
|
||||
}
|
||||
result.prepareForAdd = writeHTML;
|
||||
result.finishUpdate = writeHTML;
|
||||
result.getInnerHTML = function() {
|
||||
result.getInnerHTML = function () {
|
||||
return curHTML || '';
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
domline.processSpaces = function(s, doesWrap) {
|
||||
if (s.indexOf("<") < 0 && !doesWrap)
|
||||
{
|
||||
domline.processSpaces = function (s, doesWrap) {
|
||||
if (s.indexOf('<') < 0 && !doesWrap) {
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) {
|
||||
const parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap)
|
||||
{
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
if (doesWrap) {
|
||||
let endOfLine = true;
|
||||
let beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--)
|
||||
{
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (p == ' ') {
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
} else if (p.charAt(0) != '<') {
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
} else if (p.charAt(0) != '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
} else {
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,25 +33,25 @@ function randomPadName() {
|
|||
for (let i = 0; i < string_length; i++) {
|
||||
// instead of writing "Math.floor(randomarray[i]/256*64)"
|
||||
// we can save some cycles.
|
||||
const rnum = Math.floor(randomarray[i]/4);
|
||||
const rnum = Math.floor(randomarray[i] / 4);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$('#go2Name').submit(function() {
|
||||
$('#go2Name').submit(() => {
|
||||
const padname = $('#padname').val();
|
||||
if (padname.length > 0) {
|
||||
window.location = 'p/' + encodeURIComponent(padname.trim());
|
||||
window.location = `p/${encodeURIComponent(padname.trim())}`;
|
||||
} else {
|
||||
alert('Please enter a name');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#button').click(function() {
|
||||
window.location = 'p/' + randomPadName();
|
||||
$('#button').click(() => {
|
||||
window.location = `p/${randomPadName()}`;
|
||||
});
|
||||
|
||||
// start the custom js
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
(function(document) {
|
||||
(function (document) {
|
||||
// Set language for l10n
|
||||
var language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
|
||||
if(language) language = language[1];
|
||||
let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
|
||||
if (language) language = language[1];
|
||||
|
||||
html10n.bind('indexed', function() {
|
||||
html10n.localize([language, navigator.language, navigator.userLanguage, 'en'])
|
||||
})
|
||||
html10n.bind('indexed', () => {
|
||||
html10n.localize([language, navigator.language, navigator.userLanguage, 'en']);
|
||||
});
|
||||
|
||||
html10n.bind('localized', function() {
|
||||
document.documentElement.lang = html10n.getLanguage()
|
||||
document.documentElement.dir = html10n.getDirection()
|
||||
})
|
||||
})(document)
|
||||
html10n.bind('localized', () => {
|
||||
document.documentElement.lang = html10n.getLanguage();
|
||||
document.documentElement.dir = html10n.getDirection();
|
||||
});
|
||||
})(document);
|
||||
|
|
|
@ -28,33 +28,32 @@
|
|||
// requires: plugins
|
||||
// requires: undefined
|
||||
|
||||
var Changeset = require('./Changeset');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var linestylefilter = {};
|
||||
var _ = require('./underscore');
|
||||
var AttributeManager = require('./AttributeManager');
|
||||
const Changeset = require('./Changeset');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const linestylefilter = {};
|
||||
const _ = require('./underscore');
|
||||
const AttributeManager = require('./AttributeManager');
|
||||
|
||||
linestylefilter.ATTRIB_CLASSES = {
|
||||
'bold': 'tag:b',
|
||||
'italic': 'tag:i',
|
||||
'underline': 'tag:u',
|
||||
'strikethrough': 'tag:s'
|
||||
bold: 'tag:b',
|
||||
italic: 'tag:i',
|
||||
underline: 'tag:u',
|
||||
strikethrough: 'tag:s',
|
||||
};
|
||||
|
||||
var lineAttributeMarker = 'lineAttribMarker';
|
||||
const lineAttributeMarker = 'lineAttribMarker';
|
||||
exports.lineAttributeMarker = lineAttributeMarker;
|
||||
|
||||
linestylefilter.getAuthorClassName = function(author) {
|
||||
return "author-" + author.replace(/[^a-y0-9]/g, function(c) {
|
||||
if (c == ".") return "-";
|
||||
return 'z' + c.charCodeAt(0) + 'z';
|
||||
});
|
||||
linestylefilter.getAuthorClassName = function (author) {
|
||||
return `author-${author.replace(/[^a-y0-9]/g, (c) => {
|
||||
if (c == '.') return '-';
|
||||
return `z${c.charCodeAt(0)}z`;
|
||||
})}`;
|
||||
};
|
||||
|
||||
// lineLength is without newline; aline includes newline,
|
||||
// but may be falsy if lineLength == 0
|
||||
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool) {
|
||||
|
||||
linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFunc, apool) {
|
||||
// Plugin Hook to add more Attrib Classes
|
||||
for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) {
|
||||
Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses);
|
||||
|
@ -62,64 +61,54 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
|
|||
|
||||
if (lineLength == 0) return textAndClassFunc;
|
||||
|
||||
var nextAfterAuthorColors = textAndClassFunc;
|
||||
const nextAfterAuthorColors = textAndClassFunc;
|
||||
|
||||
var authorColorFunc = (function() {
|
||||
var lineEnd = lineLength;
|
||||
var curIndex = 0;
|
||||
var extraClasses;
|
||||
var leftInAuthor;
|
||||
const authorColorFunc = (function () {
|
||||
const lineEnd = lineLength;
|
||||
let curIndex = 0;
|
||||
let extraClasses;
|
||||
let leftInAuthor;
|
||||
|
||||
function attribsToClasses(attribs) {
|
||||
var classes = '';
|
||||
var isLineAttribMarker = false;
|
||||
let classes = '';
|
||||
let isLineAttribMarker = false;
|
||||
|
||||
// For each attribute number
|
||||
Changeset.eachAttribNumber(attribs, function(n) {
|
||||
Changeset.eachAttribNumber(attribs, (n) => {
|
||||
// Give us this attributes key
|
||||
var key = apool.getAttribKey(n);
|
||||
if (key)
|
||||
{
|
||||
var value = apool.getAttribValue(n);
|
||||
if (value)
|
||||
{
|
||||
if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0){
|
||||
const key = apool.getAttribKey(n);
|
||||
if (key) {
|
||||
const value = apool.getAttribValue(n);
|
||||
if (value) {
|
||||
if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0) {
|
||||
isLineAttribMarker = true;
|
||||
}
|
||||
if (key == 'author')
|
||||
{
|
||||
classes += ' ' + linestylefilter.getAuthorClassName(value);
|
||||
}
|
||||
else if (key == 'list')
|
||||
{
|
||||
classes += ' list:' + value;
|
||||
}
|
||||
else if (key == 'start'){
|
||||
if (key == 'author') {
|
||||
classes += ` ${linestylefilter.getAuthorClassName(value)}`;
|
||||
} else if (key == 'list') {
|
||||
classes += ` list:${value}`;
|
||||
} else if (key == 'start') {
|
||||
// Needed to introduce the correct Ordered list item start number on import
|
||||
classes += ' start:' + value;
|
||||
}
|
||||
else if (linestylefilter.ATTRIB_CLASSES[key])
|
||||
{
|
||||
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
classes += hooks.callAllStr("aceAttribsToClasses", {
|
||||
linestylefilter: linestylefilter,
|
||||
key: key,
|
||||
value: value
|
||||
}, " ", " ", "");
|
||||
classes += ` start:${value}`;
|
||||
} else if (linestylefilter.ATTRIB_CLASSES[key]) {
|
||||
classes += ` ${linestylefilter.ATTRIB_CLASSES[key]}`;
|
||||
} else {
|
||||
classes += hooks.callAllStr('aceAttribsToClasses', {
|
||||
linestylefilter,
|
||||
key,
|
||||
value,
|
||||
}, ' ', ' ', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(isLineAttribMarker) classes += ' ' + lineAttributeMarker;
|
||||
if (isLineAttribMarker) classes += ` ${lineAttributeMarker}`;
|
||||
return classes.substring(1);
|
||||
}
|
||||
|
||||
var attributionIter = Changeset.opIterator(aline);
|
||||
var nextOp, nextOpClasses;
|
||||
const attributionIter = Changeset.opIterator(aline);
|
||||
let nextOp, nextOpClasses;
|
||||
|
||||
function goNextOp() {
|
||||
nextOp = attributionIter.next();
|
||||
|
@ -128,13 +117,11 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
|
|||
goNextOp();
|
||||
|
||||
function nextClasses() {
|
||||
if (curIndex < lineEnd)
|
||||
{
|
||||
if (curIndex < lineEnd) {
|
||||
extraClasses = nextOpClasses;
|
||||
leftInAuthor = nextOp.chars;
|
||||
goNextOp();
|
||||
while (nextOp.opcode && nextOpClasses == extraClasses)
|
||||
{
|
||||
while (nextOp.opcode && nextOpClasses == extraClasses) {
|
||||
leftInAuthor += nextOp.chars;
|
||||
goNextOp();
|
||||
}
|
||||
|
@ -142,33 +129,28 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
|
|||
}
|
||||
nextClasses();
|
||||
|
||||
return function(txt, cls) {
|
||||
|
||||
var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", {
|
||||
linestylefilter: linestylefilter,
|
||||
return function (txt, cls) {
|
||||
const disableAuthColorForThisLine = hooks.callAll('disableAuthorColorsForThisLine', {
|
||||
linestylefilter,
|
||||
text: txt,
|
||||
"class": cls
|
||||
}, " ", " ", "");
|
||||
var disableAuthors = (disableAuthColorForThisLine==null||disableAuthColorForThisLine.length==0)?false:disableAuthColorForThisLine[0];
|
||||
while (txt.length > 0)
|
||||
{
|
||||
if (leftInAuthor <= 0 || disableAuthors)
|
||||
{
|
||||
class: cls,
|
||||
}, ' ', ' ', '');
|
||||
const disableAuthors = (disableAuthColorForThisLine == null || disableAuthColorForThisLine.length == 0) ? false : disableAuthColorForThisLine[0];
|
||||
while (txt.length > 0) {
|
||||
if (leftInAuthor <= 0 || disableAuthors) {
|
||||
// prevent infinite loop if something funny's going on
|
||||
return nextAfterAuthorColors(txt, cls);
|
||||
}
|
||||
var spanSize = txt.length;
|
||||
if (spanSize > leftInAuthor)
|
||||
{
|
||||
let spanSize = txt.length;
|
||||
if (spanSize > leftInAuthor) {
|
||||
spanSize = leftInAuthor;
|
||||
}
|
||||
var curTxt = txt.substring(0, spanSize);
|
||||
const curTxt = txt.substring(0, spanSize);
|
||||
txt = txt.substring(spanSize);
|
||||
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
|
||||
nextAfterAuthorColors(curTxt, (cls && `${cls} `) + extraClasses);
|
||||
curIndex += spanSize;
|
||||
leftInAuthor -= spanSize;
|
||||
if (leftInAuthor == 0)
|
||||
{
|
||||
if (leftInAuthor == 0) {
|
||||
nextClasses();
|
||||
}
|
||||
}
|
||||
|
@ -177,15 +159,13 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
|
|||
return authorColorFunc;
|
||||
};
|
||||
|
||||
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) {
|
||||
var at = /@/g;
|
||||
linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc) {
|
||||
const at = /@/g;
|
||||
at.lastIndex = 0;
|
||||
var splitPoints = null;
|
||||
var execResult;
|
||||
while ((execResult = at.exec(lineText)))
|
||||
{
|
||||
if (!splitPoints)
|
||||
{
|
||||
let splitPoints = null;
|
||||
let execResult;
|
||||
while ((execResult = at.exec(lineText))) {
|
||||
if (!splitPoints) {
|
||||
splitPoints = [];
|
||||
}
|
||||
splitPoints.push(execResult.index);
|
||||
|
@ -196,21 +176,19 @@ linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) {
|
|||
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
|
||||
};
|
||||
|
||||
linestylefilter.getRegexpFilter = function(regExp, tag) {
|
||||
return function(lineText, textAndClassFunc) {
|
||||
linestylefilter.getRegexpFilter = function (regExp, tag) {
|
||||
return function (lineText, textAndClassFunc) {
|
||||
regExp.lastIndex = 0;
|
||||
var regExpMatchs = null;
|
||||
var splitPoints = null;
|
||||
var execResult;
|
||||
while ((execResult = regExp.exec(lineText)))
|
||||
{
|
||||
if (!regExpMatchs)
|
||||
{
|
||||
let regExpMatchs = null;
|
||||
let splitPoints = null;
|
||||
let execResult;
|
||||
while ((execResult = regExp.exec(lineText))) {
|
||||
if (!regExpMatchs) {
|
||||
regExpMatchs = [];
|
||||
splitPoints = [];
|
||||
}
|
||||
var startIndex = execResult.index;
|
||||
var regExpMatch = execResult[0];
|
||||
const startIndex = execResult.index;
|
||||
const regExpMatch = execResult[0];
|
||||
regExpMatchs.push([startIndex, regExpMatch]);
|
||||
splitPoints.push(startIndex, startIndex + regExpMatch.length);
|
||||
}
|
||||
|
@ -218,26 +196,23 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
|
|||
if (!regExpMatchs) return textAndClassFunc;
|
||||
|
||||
function regExpMatchForIndex(idx) {
|
||||
for (var k = 0; k < regExpMatchs.length; k++)
|
||||
{
|
||||
var u = regExpMatchs[k];
|
||||
if (idx >= u[0] && idx < u[0] + u[1].length)
|
||||
{
|
||||
for (let k = 0; k < regExpMatchs.length; k++) {
|
||||
const u = regExpMatchs[k];
|
||||
if (idx >= u[0] && idx < u[0] + u[1].length) {
|
||||
return u[1];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var handleRegExpMatchsAfterSplit = (function() {
|
||||
var curIndex = 0;
|
||||
return function(txt, cls) {
|
||||
var txtlen = txt.length;
|
||||
var newCls = cls;
|
||||
var regExpMatch = regExpMatchForIndex(curIndex);
|
||||
if (regExpMatch)
|
||||
{
|
||||
newCls += " " + tag + ":" + regExpMatch;
|
||||
const handleRegExpMatchsAfterSplit = (function () {
|
||||
let curIndex = 0;
|
||||
return function (txt, cls) {
|
||||
const txtlen = txt.length;
|
||||
let newCls = cls;
|
||||
const regExpMatch = regExpMatchForIndex(curIndex);
|
||||
if (regExpMatch) {
|
||||
newCls += ` ${tag}:${regExpMatch}`;
|
||||
}
|
||||
textAndClassFunc(txt, newCls);
|
||||
curIndex += txtlen;
|
||||
|
@ -250,45 +225,36 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
|
|||
|
||||
|
||||
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
|
||||
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
|
||||
linestylefilter.REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source}|${linestylefilter.REGEX_WORDCHAR.source})`);
|
||||
linestylefilter.REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source}*(?![:.,;])${linestylefilter.REGEX_URLCHAR.source}`, 'g');
|
||||
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
|
||||
linestylefilter.REGEX_URL, 'url');
|
||||
linestylefilter.REGEX_URL, 'url');
|
||||
|
||||
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
|
||||
var nextPointIndex = 0;
|
||||
var idx = 0;
|
||||
linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt) {
|
||||
let nextPointIndex = 0;
|
||||
let idx = 0;
|
||||
|
||||
// don't split at 0
|
||||
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
|
||||
{
|
||||
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0) {
|
||||
nextPointIndex++;
|
||||
}
|
||||
|
||||
function spanHandler(txt, cls) {
|
||||
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
|
||||
{
|
||||
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
|
||||
func(txt, cls);
|
||||
idx += txt.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var splitPoints = splitPointsOpt;
|
||||
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
|
||||
var txtlen = txt.length;
|
||||
if (pointLocInSpan >= txtlen)
|
||||
{
|
||||
} else {
|
||||
const splitPoints = splitPointsOpt;
|
||||
const pointLocInSpan = splitPoints[nextPointIndex] - idx;
|
||||
const txtlen = txt.length;
|
||||
if (pointLocInSpan >= txtlen) {
|
||||
func(txt, cls);
|
||||
idx += txt.length;
|
||||
if (pointLocInSpan == txtlen)
|
||||
{
|
||||
if (pointLocInSpan == txtlen) {
|
||||
nextPointIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pointLocInSpan > 0)
|
||||
{
|
||||
} else {
|
||||
if (pointLocInSpan > 0) {
|
||||
func(txt.substring(0, pointLocInSpan), cls);
|
||||
idx += pointLocInSpan;
|
||||
}
|
||||
|
@ -301,34 +267,32 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
|
|||
return spanHandler;
|
||||
};
|
||||
|
||||
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, abrowser) {
|
||||
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
|
||||
linestylefilter.getFilterStack = function (lineText, textAndClassFunc, abrowser) {
|
||||
let func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
|
||||
|
||||
var hookFilters = hooks.callAll("aceGetFilterStack", {
|
||||
linestylefilter: linestylefilter,
|
||||
browser: abrowser
|
||||
const hookFilters = hooks.callAll('aceGetFilterStack', {
|
||||
linestylefilter,
|
||||
browser: abrowser,
|
||||
});
|
||||
_.map(hookFilters ,function(hookFilter) {
|
||||
_.map(hookFilters, (hookFilter) => {
|
||||
func = hookFilter(lineText, func);
|
||||
});
|
||||
|
||||
if (abrowser !== undefined && abrowser.msie)
|
||||
{
|
||||
if (abrowser !== undefined && abrowser.msie) {
|
||||
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
|
||||
// We then normalize it back to text with no angle brackets. It's weird. So always
|
||||
// break spans at an "at" sign.
|
||||
func = linestylefilter.getAtSignSplitterFilter(
|
||||
lineText, func);
|
||||
lineText, func);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
// domLineObj is like that returned by domline.createDomLine
|
||||
linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) {
|
||||
linestylefilter.populateDomLine = function (textLine, aline, apool, domLineObj) {
|
||||
// remove final newline from text if any
|
||||
var text = textLine;
|
||||
if (text.slice(-1) == '\n')
|
||||
{
|
||||
let text = textLine;
|
||||
if (text.slice(-1) == '\n') {
|
||||
text = text.substring(0, text.length - 1);
|
||||
}
|
||||
|
||||
|
@ -336,7 +300,7 @@ linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) {
|
|||
domLineObj.appendSpan(tokenText, tokenClass);
|
||||
}
|
||||
|
||||
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
|
||||
let func = linestylefilter.getFilterStack(text, textAndClassFunc);
|
||||
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
|
||||
func(text, '');
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,24 +1,23 @@
|
|||
|
||||
exports.showCountDownTimerToReconnectOnModal = function($modal, pad) {
|
||||
exports.showCountDownTimerToReconnectOnModal = function ($modal, pad) {
|
||||
if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) {
|
||||
createCountDownElementsIfNecessary($modal);
|
||||
|
||||
var timer = createTimerForModal($modal, pad);
|
||||
const timer = createTimerForModal($modal, pad);
|
||||
|
||||
$modal.find('#cancelreconnect').one('click', function() {
|
||||
$modal.find('#cancelreconnect').one('click', () => {
|
||||
timer.cancel();
|
||||
disableAutomaticReconnection($modal);
|
||||
});
|
||||
|
||||
enableAutomaticReconnection($modal);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var createCountDownElementsIfNecessary = function($modal) {
|
||||
var elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
|
||||
var createCountDownElementsIfNecessary = function ($modal) {
|
||||
const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
|
||||
if (elementsDoNotExist) {
|
||||
var $defaultMessage = $modal.find('#defaulttext');
|
||||
var $reconnectButton = $modal.find('#forcereconnect');
|
||||
const $defaultMessage = $modal.find('#defaulttext');
|
||||
const $reconnectButton = $modal.find('#forcereconnect');
|
||||
|
||||
// create extra DOM elements, if they don't exist
|
||||
const $reconnectTimerMessage =
|
||||
|
@ -44,100 +43,100 @@ var createCountDownElementsIfNecessary = function($modal) {
|
|||
$reconnectTimerMessage.insertAfter($defaultMessage);
|
||||
$cancelReconnect.insertAfter($reconnectButton);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var localize = function($element) {
|
||||
var localize = function ($element) {
|
||||
html10n.translateElement(html10n.translations, $element.get(0));
|
||||
};
|
||||
|
||||
var createTimerForModal = function($modal, pad) {
|
||||
var timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
|
||||
var timer = new CountDownTimer(timeUntilReconnection);
|
||||
var createTimerForModal = function ($modal, pad) {
|
||||
const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
|
||||
const timer = new CountDownTimer(timeUntilReconnection);
|
||||
|
||||
timer.onTick(function(minutes, seconds) {
|
||||
timer.onTick((minutes, seconds) => {
|
||||
updateCountDownTimerMessage($modal, minutes, seconds);
|
||||
}).onExpire(function() {
|
||||
var wasANetworkError = $modal.is('.disconnected');
|
||||
}).onExpire(() => {
|
||||
const wasANetworkError = $modal.is('.disconnected');
|
||||
if (wasANetworkError) {
|
||||
// cannot simply reconnect, client is having issues to establish connection to server
|
||||
waitUntilClientCanConnectToServerAndThen(function() { forceReconnection($modal); }, pad);
|
||||
waitUntilClientCanConnectToServerAndThen(() => { forceReconnection($modal); }, pad);
|
||||
} else {
|
||||
forceReconnection($modal);
|
||||
}
|
||||
}).start();
|
||||
|
||||
return timer;
|
||||
}
|
||||
};
|
||||
|
||||
var disableAutomaticReconnection = function($modal) {
|
||||
var disableAutomaticReconnection = function ($modal) {
|
||||
toggleAutomaticReconnectionOption($modal, true);
|
||||
}
|
||||
var enableAutomaticReconnection = function($modal) {
|
||||
};
|
||||
var enableAutomaticReconnection = function ($modal) {
|
||||
toggleAutomaticReconnectionOption($modal, false);
|
||||
}
|
||||
var toggleAutomaticReconnectionOption = function($modal, disableAutomaticReconnect) {
|
||||
};
|
||||
var toggleAutomaticReconnectionOption = function ($modal, disableAutomaticReconnect) {
|
||||
$modal.find('#cancelreconnect, .reconnecttimer').toggleClass('hidden', disableAutomaticReconnect);
|
||||
$modal.find('#defaulttext').toggleClass('hidden', !disableAutomaticReconnect);
|
||||
}
|
||||
};
|
||||
|
||||
var waitUntilClientCanConnectToServerAndThen = function(callback, pad) {
|
||||
var waitUntilClientCanConnectToServerAndThen = function (callback, pad) {
|
||||
whenConnectionIsRestablishedWithServer(callback, pad);
|
||||
pad.socket.connect();
|
||||
}
|
||||
};
|
||||
|
||||
var whenConnectionIsRestablishedWithServer = function(callback, pad) {
|
||||
var whenConnectionIsRestablishedWithServer = function (callback, pad) {
|
||||
// only add listener for the first try, don't need to add another listener
|
||||
// on every unsuccessful try
|
||||
if (reconnectionTries.counter === 1) {
|
||||
pad.socket.once('connect', callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var forceReconnection = function($modal) {
|
||||
var forceReconnection = function ($modal) {
|
||||
$modal.find('#forcereconnect').click();
|
||||
}
|
||||
};
|
||||
|
||||
var updateCountDownTimerMessage = function($modal, minutes, seconds) {
|
||||
minutes = minutes < 10 ? '0' + minutes : minutes;
|
||||
seconds = seconds < 10 ? '0' + seconds : seconds;
|
||||
var updateCountDownTimerMessage = function ($modal, minutes, seconds) {
|
||||
minutes = minutes < 10 ? `0${minutes}` : minutes;
|
||||
seconds = seconds < 10 ? `0${seconds}` : seconds;
|
||||
|
||||
$modal.find('.timetoexpire').text(minutes + ':' + seconds);
|
||||
}
|
||||
$modal.find('.timetoexpire').text(`${minutes}:${seconds}`);
|
||||
};
|
||||
|
||||
// store number of tries to reconnect to server, in order to increase time to wait
|
||||
// until next try
|
||||
var reconnectionTries = {
|
||||
counter: 0,
|
||||
|
||||
nextTry: function() {
|
||||
nextTry() {
|
||||
// double the time to try to reconnect on every time reconnection fails
|
||||
var nextCounterFactor = Math.pow(2, this.counter);
|
||||
const nextCounterFactor = Math.pow(2, this.counter);
|
||||
this.counter++;
|
||||
|
||||
return nextCounterFactor;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Timer based on http://stackoverflow.com/a/20618517.
|
||||
// duration: how many **seconds** until the timer ends
|
||||
// granularity (optional): how many **milliseconds** between each 'tick' of timer. Default: 1000ms (1s)
|
||||
var CountDownTimer = function(duration, granularity) {
|
||||
this.duration = duration;
|
||||
var CountDownTimer = function (duration, granularity) {
|
||||
this.duration = duration;
|
||||
this.granularity = granularity || 1000;
|
||||
this.running = false;
|
||||
this.running = false;
|
||||
|
||||
this.onTickCallbacks = [];
|
||||
this.onTickCallbacks = [];
|
||||
this.onExpireCallbacks = [];
|
||||
}
|
||||
};
|
||||
|
||||
CountDownTimer.prototype.start = function() {
|
||||
CountDownTimer.prototype.start = function () {
|
||||
if (this.running) {
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
var start = Date.now(),
|
||||
that = this,
|
||||
diff;
|
||||
const start = Date.now();
|
||||
const that = this;
|
||||
let diff;
|
||||
|
||||
(function timer() {
|
||||
diff = that.duration - Math.floor((Date.now() - start) / 1000);
|
||||
|
@ -153,41 +152,41 @@ CountDownTimer.prototype.start = function() {
|
|||
}());
|
||||
};
|
||||
|
||||
CountDownTimer.prototype.tick = function(diff) {
|
||||
var obj = CountDownTimer.parse(diff);
|
||||
this.onTickCallbacks.forEach(function(callback) {
|
||||
CountDownTimer.prototype.tick = function (diff) {
|
||||
const obj = CountDownTimer.parse(diff);
|
||||
this.onTickCallbacks.forEach(function (callback) {
|
||||
callback.call(this, obj.minutes, obj.seconds);
|
||||
}, this);
|
||||
}
|
||||
CountDownTimer.prototype.expire = function() {
|
||||
this.onExpireCallbacks.forEach(function(callback) {
|
||||
};
|
||||
CountDownTimer.prototype.expire = function () {
|
||||
this.onExpireCallbacks.forEach(function (callback) {
|
||||
callback.call(this);
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
CountDownTimer.prototype.onTick = function(callback) {
|
||||
CountDownTimer.prototype.onTick = function (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.onTickCallbacks.push(callback);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
CountDownTimer.prototype.onExpire = function(callback) {
|
||||
CountDownTimer.prototype.onExpire = function (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.onExpireCallbacks.push(callback);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
CountDownTimer.prototype.cancel = function() {
|
||||
CountDownTimer.prototype.cancel = function () {
|
||||
this.running = false;
|
||||
clearTimeout(this.timeoutId);
|
||||
return this;
|
||||
};
|
||||
|
||||
CountDownTimer.parse = function(seconds) {
|
||||
CountDownTimer.parse = function (seconds) {
|
||||
return {
|
||||
'minutes': (seconds / 60) | 0,
|
||||
'seconds': (seconds % 60) | 0
|
||||
minutes: (seconds / 60) | 0,
|
||||
seconds: (seconds % 60) | 0,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,42 +20,40 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padmodals = require('./pad_modals').padmodals;
|
||||
const padmodals = require('./pad_modals').padmodals;
|
||||
|
||||
var padconnectionstatus = (function() {
|
||||
|
||||
var status = {
|
||||
what: 'connecting'
|
||||
const padconnectionstatus = (function () {
|
||||
let status = {
|
||||
what: 'connecting',
|
||||
};
|
||||
|
||||
var self = {
|
||||
init: function() {
|
||||
$('button#forcereconnect').click(function() {
|
||||
const self = {
|
||||
init() {
|
||||
$('button#forcereconnect').click(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
connected: function() {
|
||||
connected() {
|
||||
status = {
|
||||
what: 'connected'
|
||||
what: 'connected',
|
||||
};
|
||||
padmodals.showModal('connected');
|
||||
padmodals.hideOverlay();
|
||||
},
|
||||
reconnecting: function() {
|
||||
reconnecting() {
|
||||
status = {
|
||||
what: 'reconnecting'
|
||||
what: 'reconnecting',
|
||||
};
|
||||
|
||||
padmodals.showModal('reconnecting');
|
||||
padmodals.showOverlay();
|
||||
},
|
||||
disconnected: function(msg) {
|
||||
if(status.what == "disconnected")
|
||||
return;
|
||||
disconnected(msg) {
|
||||
if (status.what == 'disconnected') return;
|
||||
|
||||
status = {
|
||||
what: 'disconnected',
|
||||
why: msg
|
||||
why: msg,
|
||||
};
|
||||
|
||||
// These message IDs correspond to localized strings that are presented to the user. If a new
|
||||
|
@ -83,12 +81,12 @@ var padconnectionstatus = (function() {
|
|||
padmodals.showModal(k);
|
||||
padmodals.showOverlay();
|
||||
},
|
||||
isFullyConnected: function() {
|
||||
isFullyConnected() {
|
||||
return status.what == 'connected';
|
||||
},
|
||||
getStatus: function() {
|
||||
getStatus() {
|
||||
return status;
|
||||
}
|
||||
},
|
||||
};
|
||||
return self;
|
||||
}());
|
||||
|
|
|
@ -20,150 +20,139 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var browser = require('./browser');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var padutils = require('./pad_utils').padutils;
|
||||
var padeditor = require('./pad_editor').padeditor;
|
||||
var padsavedrevs = require('./pad_savedrevs');
|
||||
var _ = require('ep_etherpad-lite/static/js/underscore');
|
||||
const browser = require('./browser');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
const padeditor = require('./pad_editor').padeditor;
|
||||
const padsavedrevs = require('./pad_savedrevs');
|
||||
const _ = require('ep_etherpad-lite/static/js/underscore');
|
||||
require('ep_etherpad-lite/static/js/vendors/nice-select');
|
||||
|
||||
var ToolbarItem = function (element) {
|
||||
const ToolbarItem = function (element) {
|
||||
this.$el = element;
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.getCommand = function () {
|
||||
return this.$el.attr("data-key");
|
||||
return this.$el.attr('data-key');
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.getValue = function () {
|
||||
if (this.isSelect()) {
|
||||
return this.$el.find("select").val();
|
||||
return this.$el.find('select').val();
|
||||
}
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.setValue = function (val) {
|
||||
if (this.isSelect()) {
|
||||
return this.$el.find("select").val(val);
|
||||
return this.$el.find('select').val(val);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ToolbarItem.prototype.getType = function () {
|
||||
return this.$el.attr("data-type");
|
||||
return this.$el.attr('data-type');
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.isSelect = function () {
|
||||
return this.getType() == "select";
|
||||
return this.getType() == 'select';
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.isButton = function () {
|
||||
return this.getType() == "button";
|
||||
return this.getType() == 'button';
|
||||
};
|
||||
|
||||
ToolbarItem.prototype.bind = function (callback) {
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
if (self.isButton()) {
|
||||
self.$el.click(function (event) {
|
||||
self.$el.click((event) => {
|
||||
$(':focus').blur();
|
||||
callback(self.getCommand(), self);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
else if (self.isSelect()) {
|
||||
self.$el.find("select").change(function () {
|
||||
} else if (self.isSelect()) {
|
||||
self.$el.find('select').change(() => {
|
||||
callback(self.getCommand(), self);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var padeditbar = (function() {
|
||||
|
||||
var syncAnimation = (function() {
|
||||
var SYNCING = -100;
|
||||
var DONE = 100;
|
||||
var state = DONE;
|
||||
var fps = 25;
|
||||
var step = 1 / fps;
|
||||
var T_START = -0.5;
|
||||
var T_FADE = 1.0;
|
||||
var T_GONE = 1.5;
|
||||
var animator = padutils.makeAnimationScheduler(function() {
|
||||
if (state == SYNCING || state == DONE)
|
||||
{
|
||||
var padeditbar = (function () {
|
||||
const syncAnimation = (function () {
|
||||
const SYNCING = -100;
|
||||
const DONE = 100;
|
||||
let state = DONE;
|
||||
const fps = 25;
|
||||
const step = 1 / fps;
|
||||
const T_START = -0.5;
|
||||
const T_FADE = 1.0;
|
||||
const T_GONE = 1.5;
|
||||
const animator = padutils.makeAnimationScheduler(() => {
|
||||
if (state == SYNCING || state == DONE) {
|
||||
return false;
|
||||
}
|
||||
else if (state >= T_GONE)
|
||||
{
|
||||
} else if (state >= T_GONE) {
|
||||
state = DONE;
|
||||
$("#syncstatussyncing").css('display', 'none');
|
||||
$("#syncstatusdone").css('display', 'none');
|
||||
$('#syncstatussyncing').css('display', 'none');
|
||||
$('#syncstatusdone').css('display', 'none');
|
||||
return false;
|
||||
}
|
||||
else if (state < 0)
|
||||
{
|
||||
} else if (state < 0) {
|
||||
state += step;
|
||||
if (state >= 0)
|
||||
{
|
||||
$("#syncstatussyncing").css('display', 'none');
|
||||
$("#syncstatusdone").css('display', 'block').css('opacity', 1);
|
||||
if (state >= 0) {
|
||||
$('#syncstatussyncing').css('display', 'none');
|
||||
$('#syncstatusdone').css('display', 'block').css('opacity', 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
state += step;
|
||||
if (state >= T_FADE)
|
||||
{
|
||||
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
|
||||
if (state >= T_FADE) {
|
||||
$('#syncstatusdone').css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, step * 1000);
|
||||
return {
|
||||
syncing: function() {
|
||||
syncing() {
|
||||
state = SYNCING;
|
||||
$("#syncstatussyncing").css('display', 'block');
|
||||
$("#syncstatusdone").css('display', 'none');
|
||||
$('#syncstatussyncing').css('display', 'block');
|
||||
$('#syncstatusdone').css('display', 'none');
|
||||
},
|
||||
done: function() {
|
||||
done() {
|
||||
state = T_START;
|
||||
animator.scheduleAnimation();
|
||||
}
|
||||
},
|
||||
};
|
||||
}());
|
||||
|
||||
var self = {
|
||||
init: function() {
|
||||
var self = this;
|
||||
init() {
|
||||
const self = this;
|
||||
self.dropdowns = [];
|
||||
|
||||
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
|
||||
$('#editbar .editbarbutton').attr('unselectable', 'on'); // for IE
|
||||
this.enable();
|
||||
$("#editbar [data-key]").each(function () {
|
||||
$(this).unbind("click");
|
||||
(new ToolbarItem($(this))).bind(function (command, item) {
|
||||
$('#editbar [data-key]').each(function () {
|
||||
$(this).unbind('click');
|
||||
(new ToolbarItem($(this))).bind((command, item) => {
|
||||
self.triggerCommand(command, item);
|
||||
});
|
||||
});
|
||||
|
||||
$('body:not(#editorcontainerbox)').on("keydown", function(evt){
|
||||
$('body:not(#editorcontainerbox)').on('keydown', (evt) => {
|
||||
bodyKeyEvent(evt);
|
||||
});
|
||||
|
||||
$('.show-more-icon-btn').click(function() {
|
||||
$('.show-more-icon-btn').click(() => {
|
||||
$('.toolbar').toggleClass('full-icons');
|
||||
});
|
||||
self.checkAllIconsAreDisplayedInToolbar();
|
||||
$(window).resize(_.debounce( self.checkAllIconsAreDisplayedInToolbar, 100 ) );
|
||||
$(window).resize(_.debounce(self.checkAllIconsAreDisplayedInToolbar, 100));
|
||||
|
||||
registerDefaultCommands(self);
|
||||
|
||||
hooks.callAll("postToolbarInit", {
|
||||
hooks.callAll('postToolbarInit', {
|
||||
toolbar: self,
|
||||
ace: padeditor.ace
|
||||
ace: padeditor.ace,
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -173,101 +162,91 @@ var padeditbar = (function() {
|
|||
* overflow:hidden on parent
|
||||
*/
|
||||
if (!browser.safari) {
|
||||
$('select').niceSelect();
|
||||
$('select').niceSelect();
|
||||
}
|
||||
|
||||
// When editor is scrolled, we add a class to style the editbar differently
|
||||
$('iframe[name="ace_outer"]').contents().scroll(function() {
|
||||
$('iframe[name="ace_outer"]').contents().scroll(function () {
|
||||
$('#editbar').toggleClass('editor-scrolled', $(this).scrollTop() > 2);
|
||||
})
|
||||
});
|
||||
},
|
||||
isEnabled: function() {
|
||||
isEnabled() {
|
||||
return true;
|
||||
},
|
||||
disable: function() {
|
||||
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
|
||||
disable() {
|
||||
$('#editbar').addClass('disabledtoolbar').removeClass('enabledtoolbar');
|
||||
},
|
||||
enable: function() {
|
||||
enable() {
|
||||
$('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar');
|
||||
},
|
||||
commands: {},
|
||||
registerCommand: function (cmd, callback) {
|
||||
registerCommand(cmd, callback) {
|
||||
this.commands[cmd] = callback;
|
||||
return this;
|
||||
},
|
||||
registerDropdownCommand: function (cmd, dropdown) {
|
||||
registerDropdownCommand(cmd, dropdown) {
|
||||
dropdown = dropdown || cmd;
|
||||
self.dropdowns.push(dropdown)
|
||||
this.registerCommand(cmd, function () {
|
||||
self.dropdowns.push(dropdown);
|
||||
this.registerCommand(cmd, () => {
|
||||
self.toggleDropDown(dropdown);
|
||||
});
|
||||
},
|
||||
registerAceCommand: function (cmd, callback) {
|
||||
this.registerCommand(cmd, function (cmd, ace, item) {
|
||||
ace.callWithAce(function (ace) {
|
||||
registerAceCommand(cmd, callback) {
|
||||
this.registerCommand(cmd, (cmd, ace, item) => {
|
||||
ace.callWithAce((ace) => {
|
||||
callback(cmd, ace, item);
|
||||
}, cmd, true);
|
||||
});
|
||||
},
|
||||
triggerCommand: function (cmd, item) {
|
||||
triggerCommand(cmd, item) {
|
||||
if (self.isEnabled() && this.commands[cmd]) {
|
||||
this.commands[cmd](cmd, padeditor.ace, item);
|
||||
}
|
||||
if(padeditor.ace) padeditor.ace.focus();
|
||||
if (padeditor.ace) padeditor.ace.focus();
|
||||
},
|
||||
toggleDropDown: function(moduleName, cb) {
|
||||
toggleDropDown(moduleName, cb) {
|
||||
// do nothing if users are sticked
|
||||
if (moduleName === "users" && $('#users').hasClass('stickyUsers')) {
|
||||
if (moduleName === 'users' && $('#users').hasClass('stickyUsers')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.nice-select').removeClass('open');
|
||||
$('.toolbar-popup').removeClass("popup-show");
|
||||
$('.toolbar-popup').removeClass('popup-show');
|
||||
|
||||
// hide all modules and remove highlighting of all buttons
|
||||
if(moduleName == "none")
|
||||
{
|
||||
var returned = false;
|
||||
for(var i=0;i<self.dropdowns.length;i++)
|
||||
{
|
||||
if (moduleName == 'none') {
|
||||
const returned = false;
|
||||
for (var i = 0; i < self.dropdowns.length; i++) {
|
||||
var thisModuleName = self.dropdowns[i];
|
||||
|
||||
//skip the userlist
|
||||
if(thisModuleName == "users")
|
||||
continue;
|
||||
// skip the userlist
|
||||
if (thisModuleName == 'users') continue;
|
||||
|
||||
var module = $("#" + thisModuleName);
|
||||
var module = $(`#${thisModuleName}`);
|
||||
|
||||
//skip any "force reconnect" message
|
||||
var isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
|
||||
if(isAForceReconnectMessage)
|
||||
continue;
|
||||
// skip any "force reconnect" message
|
||||
const isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
|
||||
if (isAForceReconnectMessage) continue;
|
||||
if (module.hasClass('popup-show')) {
|
||||
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected");
|
||||
module.removeClass("popup-show");
|
||||
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
||||
module.removeClass('popup-show');
|
||||
}
|
||||
}
|
||||
|
||||
if(!returned && cb) return cb();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!returned && cb) return cb();
|
||||
} else {
|
||||
// hide all modules that are not selected and remove highlighting
|
||||
// respectively add highlighting to the corresponding button
|
||||
for(var i=0;i<self.dropdowns.length;i++)
|
||||
{
|
||||
for (var i = 0; i < self.dropdowns.length; i++) {
|
||||
var thisModuleName = self.dropdowns[i];
|
||||
var module = $("#" + thisModuleName);
|
||||
var module = $(`#${thisModuleName}`);
|
||||
|
||||
if(module.hasClass('popup-show'))
|
||||
{
|
||||
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected");
|
||||
module.removeClass("popup-show");
|
||||
}
|
||||
else if(thisModuleName==moduleName)
|
||||
{
|
||||
$("li[data-key=" + thisModuleName + "] > a").addClass("selected");
|
||||
module.addClass("popup-show");
|
||||
if (module.hasClass('popup-show')) {
|
||||
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
||||
module.removeClass('popup-show');
|
||||
} else if (thisModuleName == moduleName) {
|
||||
$(`li[data-key=${thisModuleName}] > a`).addClass('selected');
|
||||
module.addClass('popup-show');
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
|
@ -275,113 +254,105 @@ var padeditbar = (function() {
|
|||
}
|
||||
}
|
||||
},
|
||||
setSyncStatus: function(status) {
|
||||
if (status == "syncing")
|
||||
{
|
||||
setSyncStatus(status) {
|
||||
if (status == 'syncing') {
|
||||
syncAnimation.syncing();
|
||||
}
|
||||
else if (status == "done")
|
||||
{
|
||||
} else if (status == 'done') {
|
||||
syncAnimation.done();
|
||||
}
|
||||
},
|
||||
setEmbedLinks: function() {
|
||||
var padUrl = window.location.href.split("?")[0];
|
||||
setEmbedLinks() {
|
||||
const padUrl = window.location.href.split('?')[0];
|
||||
|
||||
if ($('#readonlyinput').is(':checked'))
|
||||
{
|
||||
var urlParts = padUrl.split("/");
|
||||
if ($('#readonlyinput').is(':checked')) {
|
||||
const urlParts = padUrl.split('/');
|
||||
urlParts.pop();
|
||||
var readonlyLink = urlParts.join("/") + "/" + clientVars.readOnlyId;
|
||||
$('#embedinput').val('<iframe name="embed_readonly" src="' + readonlyLink + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>');
|
||||
const readonlyLink = `${urlParts.join('/')}/${clientVars.readOnlyId}`;
|
||||
$('#embedinput').val(`<iframe name="embed_readonly" src="${readonlyLink}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
|
||||
$('#linkinput').val(readonlyLink);
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#embedinput').val('<iframe name="embed_readwrite" src="' + padUrl + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>');
|
||||
} else {
|
||||
$('#embedinput').val(`<iframe name="embed_readwrite" src="${padUrl}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
|
||||
$('#linkinput').val(padUrl);
|
||||
}
|
||||
},
|
||||
checkAllIconsAreDisplayedInToolbar: function() {
|
||||
checkAllIconsAreDisplayedInToolbar() {
|
||||
// reset style
|
||||
$('.toolbar').removeClass('cropped')
|
||||
$('.toolbar').removeClass('cropped');
|
||||
$('body').removeClass('mobile-layout');
|
||||
var menu_left = $('.toolbar .menu_left')[0];
|
||||
const menu_left = $('.toolbar .menu_left')[0];
|
||||
|
||||
var menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
|
||||
const menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
|
||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth || $('.toolbar').width() < 1000) {
|
||||
$('body').addClass('mobile-layout');
|
||||
}
|
||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
|
||||
$('.toolbar').addClass('cropped');
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var editbarPosition = 0;
|
||||
|
||||
function bodyKeyEvent(evt){
|
||||
let editbarPosition = 0;
|
||||
|
||||
function bodyKeyEvent(evt) {
|
||||
// If the event is Alt F9 or Escape & we're already in the editbar menu
|
||||
// Send the users focus back to the pad
|
||||
if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){
|
||||
if($(':focus').parents(".toolbar").length === 1){
|
||||
if ((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27) {
|
||||
if ($(':focus').parents('.toolbar').length === 1) {
|
||||
// If we're in the editbar already..
|
||||
// Close any dropdowns we have open..
|
||||
padeditbar.toggleDropDown("none");
|
||||
padeditbar.toggleDropDown('none');
|
||||
// Check we're on a pad and not on the timeslider
|
||||
// Or some other window I haven't thought about!
|
||||
if(typeof pad === 'undefined'){
|
||||
if (typeof pad === 'undefined') {
|
||||
// Timeslider probably..
|
||||
// Shift focus away from any drop downs
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
$('#editorcontainerbox').focus(); // Focus back onto the pad
|
||||
}else{
|
||||
} else {
|
||||
// Shift focus away from any drop downs
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
padeditor.ace.focus(); // Sends focus back to pad
|
||||
// The above focus doesn't always work in FF, you have to hit enter afterwards
|
||||
evt.preventDefault();
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
// Focus on the editbar :)
|
||||
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first();
|
||||
const firstEditbarElement = parent.parent.$('#editbar').children('ul').first().children().first().children().first().children().first();
|
||||
$(this).blur();
|
||||
firstEditbarElement.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
// Are we in the toolbar??
|
||||
if($(':focus').parents(".toolbar").length === 1){
|
||||
if ($(':focus').parents('.toolbar').length === 1) {
|
||||
// On arrow keys go to next/previous button item in editbar
|
||||
if(evt.keyCode !== 39 && evt.keyCode !== 37) return;
|
||||
if (evt.keyCode !== 39 && evt.keyCode !== 37) return;
|
||||
|
||||
// Get all the focusable items in the editbar
|
||||
var focusItems = $('#editbar').find('button, select');
|
||||
const focusItems = $('#editbar').find('button, select');
|
||||
|
||||
// On left arrow move to next button in editbar
|
||||
if(evt.keyCode === 37){
|
||||
if (evt.keyCode === 37) {
|
||||
// If a dropdown is visible or we're in an input don't move to the next button
|
||||
if($('.popup').is(":visible") || evt.target.localName === "input") return;
|
||||
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
||||
|
||||
editbarPosition--;
|
||||
// Allow focus to shift back to end of row and start of row
|
||||
if(editbarPosition === -1) editbarPosition = focusItems.length -1;
|
||||
$(focusItems[editbarPosition]).focus()
|
||||
if (editbarPosition === -1) editbarPosition = focusItems.length - 1;
|
||||
$(focusItems[editbarPosition]).focus();
|
||||
}
|
||||
|
||||
// On right arrow move to next button in editbar
|
||||
if(evt.keyCode === 39){
|
||||
if (evt.keyCode === 39) {
|
||||
// If a dropdown is visible or we're in an input don't move to the next button
|
||||
if($('.popup').is(":visible") || evt.target.localName === "input") return;
|
||||
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
||||
|
||||
editbarPosition++;
|
||||
// Allow focus to shift back to end of row and start of row
|
||||
if(editbarPosition >= focusItems.length) editbarPosition = 0;
|
||||
if (editbarPosition >= focusItems.length) editbarPosition = 0;
|
||||
$(focusItems[editbarPosition]).focus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function aceAttributeCommand(cmd, ace) {
|
||||
|
@ -389,91 +360,91 @@ var padeditbar = (function() {
|
|||
}
|
||||
|
||||
function registerDefaultCommands(toolbar) {
|
||||
toolbar.registerDropdownCommand("showusers", "users");
|
||||
toolbar.registerDropdownCommand("settings");
|
||||
toolbar.registerDropdownCommand("connectivity");
|
||||
toolbar.registerDropdownCommand("import_export");
|
||||
toolbar.registerDropdownCommand("embed");
|
||||
toolbar.registerDropdownCommand('showusers', 'users');
|
||||
toolbar.registerDropdownCommand('settings');
|
||||
toolbar.registerDropdownCommand('connectivity');
|
||||
toolbar.registerDropdownCommand('import_export');
|
||||
toolbar.registerDropdownCommand('embed');
|
||||
|
||||
toolbar.registerCommand("settings", function () {
|
||||
toolbar.toggleDropDown("settings", function(){
|
||||
toolbar.registerCommand('settings', () => {
|
||||
toolbar.toggleDropDown('settings', () => {
|
||||
$('#options-stickychat').focus();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("import_export", function () {
|
||||
toolbar.toggleDropDown("import_export", function(){
|
||||
toolbar.registerCommand('import_export', () => {
|
||||
toolbar.toggleDropDown('import_export', () => {
|
||||
// If Import file input exists then focus on it..
|
||||
if($('#importfileinput').length !== 0){
|
||||
setTimeout(function(){
|
||||
if ($('#importfileinput').length !== 0) {
|
||||
setTimeout(() => {
|
||||
$('#importfileinput').focus();
|
||||
}, 100);
|
||||
}else{
|
||||
} else {
|
||||
$('.exportlink').first().focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("showusers", function () {
|
||||
toolbar.toggleDropDown("users", function(){
|
||||
toolbar.registerCommand('showusers', () => {
|
||||
toolbar.toggleDropDown('users', () => {
|
||||
$('#myusernameedit').focus();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("embed", function () {
|
||||
toolbar.registerCommand('embed', () => {
|
||||
toolbar.setEmbedLinks();
|
||||
toolbar.toggleDropDown("embed", function(){
|
||||
toolbar.toggleDropDown('embed', () => {
|
||||
$('#linkinput').focus().select();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("savedRevision", function () {
|
||||
toolbar.registerCommand('savedRevision', () => {
|
||||
padsavedrevs.saveNow();
|
||||
});
|
||||
|
||||
toolbar.registerCommand("showTimeSlider", function () {
|
||||
document.location = document.location.pathname+ '/timeslider';
|
||||
toolbar.registerCommand('showTimeSlider', () => {
|
||||
document.location = `${document.location.pathname}/timeslider`;
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("bold", aceAttributeCommand);
|
||||
toolbar.registerAceCommand("italic", aceAttributeCommand);
|
||||
toolbar.registerAceCommand("underline", aceAttributeCommand);
|
||||
toolbar.registerAceCommand("strikethrough", aceAttributeCommand);
|
||||
toolbar.registerAceCommand('bold', aceAttributeCommand);
|
||||
toolbar.registerAceCommand('italic', aceAttributeCommand);
|
||||
toolbar.registerAceCommand('underline', aceAttributeCommand);
|
||||
toolbar.registerAceCommand('strikethrough', aceAttributeCommand);
|
||||
|
||||
toolbar.registerAceCommand("undo", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('undo', (cmd, ace) => {
|
||||
ace.ace_doUndoRedo(cmd);
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("redo", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('redo', (cmd, ace) => {
|
||||
ace.ace_doUndoRedo(cmd);
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('insertunorderedlist', (cmd, ace) => {
|
||||
ace.ace_doInsertUnorderedList();
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('insertorderedlist', (cmd, ace) => {
|
||||
ace.ace_doInsertOrderedList();
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("indent", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('indent', (cmd, ace) => {
|
||||
if (!ace.ace_doIndentOutdent(false)) {
|
||||
ace.ace_doInsertUnorderedList();
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("outdent", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('outdent', (cmd, ace) => {
|
||||
ace.ace_doIndentOutdent(true);
|
||||
});
|
||||
|
||||
toolbar.registerAceCommand("clearauthorship", function (cmd, ace) {
|
||||
toolbar.registerAceCommand('clearauthorship', (cmd, ace) => {
|
||||
// If we have the whole document selected IE control A has been hit
|
||||
var rep = ace.ace_getRep();
|
||||
var lastChar = rep.lines.atIndex(rep.lines.length()-1).width-1;
|
||||
var lastLineIndex = rep.lines.length()-1;
|
||||
if(rep.selStart[0] === 0 && rep.selStart[1] === 0){
|
||||
const rep = ace.ace_getRep();
|
||||
const lastChar = rep.lines.atIndex(rep.lines.length() - 1).width - 1;
|
||||
const lastLineIndex = rep.lines.length() - 1;
|
||||
if (rep.selStart[0] === 0 && rep.selStart[1] === 0) {
|
||||
// nesting intentionally here to make things readable
|
||||
if(rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar){
|
||||
if (rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar) {
|
||||
var doPrompt = true;
|
||||
}
|
||||
}
|
||||
|
@ -488,22 +459,21 @@ var padeditbar = (function() {
|
|||
|
||||
// if we don't have any text selected, we have a caret or we have already said to prompt
|
||||
if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) {
|
||||
if (window.confirm(html10n.get("pad.editbar.clearcolors"))) {
|
||||
if (window.confirm(html10n.get('pad.editbar.clearcolors'))) {
|
||||
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
|
||||
['author', '']
|
||||
['author', ''],
|
||||
]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ace.ace_setAttributeOnSelection('author', '');
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.registerCommand('timeslider_returnToPad', function(cmd) {
|
||||
if( document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1, document.referrer.lastIndexOf("/")) === "p") {
|
||||
toolbar.registerCommand('timeslider_returnToPad', (cmd) => {
|
||||
if (document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf('/') - 1, document.referrer.lastIndexOf('/')) === 'p') {
|
||||
document.location = document.referrer;
|
||||
} else {
|
||||
document.location = document.location.href.substring(0,document.location.href.lastIndexOf("/"));
|
||||
document.location = document.location.href.substring(0, document.location.href.lastIndexOf('/'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -21,145 +21,140 @@
|
|||
*/
|
||||
|
||||
const Cookies = require('./pad_utils').Cookies;
|
||||
var padcookie = require('./pad_cookie').padcookie;
|
||||
var padutils = require('./pad_utils').padutils;
|
||||
const padcookie = require('./pad_cookie').padcookie;
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
|
||||
var padeditor = (function() {
|
||||
var Ace2Editor = undefined;
|
||||
var pad = undefined;
|
||||
var settings = undefined;
|
||||
const padeditor = (function () {
|
||||
let Ace2Editor = undefined;
|
||||
let pad = undefined;
|
||||
let settings = undefined;
|
||||
|
||||
var self = {
|
||||
ace: null,
|
||||
// this is accessed directly from other files
|
||||
viewZoom: 100,
|
||||
init: function(readyFunc, initialViewOptions, _pad) {
|
||||
init(readyFunc, initialViewOptions, _pad) {
|
||||
Ace2Editor = require('./ace').Ace2Editor;
|
||||
pad = _pad;
|
||||
settings = pad.settings;
|
||||
|
||||
function aceReady() {
|
||||
$("#editorloadingbox").hide();
|
||||
if (readyFunc)
|
||||
{
|
||||
$('#editorloadingbox').hide();
|
||||
if (readyFunc) {
|
||||
readyFunc();
|
||||
}
|
||||
}
|
||||
|
||||
self.ace = new Ace2Editor();
|
||||
self.ace.init("editorcontainer", "", aceReady);
|
||||
self.ace.setProperty("wraps", true);
|
||||
if (pad.getIsDebugEnabled())
|
||||
{
|
||||
self.ace.setProperty("dmesg", pad.dmesg);
|
||||
self.ace.init('editorcontainer', '', aceReady);
|
||||
self.ace.setProperty('wraps', true);
|
||||
if (pad.getIsDebugEnabled()) {
|
||||
self.ace.setProperty('dmesg', pad.dmesg);
|
||||
}
|
||||
self.initViewOptions();
|
||||
self.setViewOptions(initialViewOptions);
|
||||
|
||||
// view bar
|
||||
$("#viewbarcontents").show();
|
||||
$('#viewbarcontents').show();
|
||||
},
|
||||
initViewOptions: function() {
|
||||
initViewOptions() {
|
||||
// Line numbers
|
||||
padutils.bindCheckboxChange($("#options-linenoscheck"), function() {
|
||||
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck")));
|
||||
padutils.bindCheckboxChange($('#options-linenoscheck'), () => {
|
||||
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
|
||||
});
|
||||
|
||||
// Author colors
|
||||
padutils.bindCheckboxChange($("#options-colorscheck"), function() {
|
||||
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck"));
|
||||
pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck"));
|
||||
padutils.bindCheckboxChange($('#options-colorscheck'), () => {
|
||||
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox('#options-colorscheck'));
|
||||
pad.changeViewOption('showAuthorColors', padutils.getCheckbox('#options-colorscheck'));
|
||||
});
|
||||
|
||||
// Right to left
|
||||
padutils.bindCheckboxChange($("#options-rtlcheck"), function() {
|
||||
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($("#options-rtlcheck")))
|
||||
padutils.bindCheckboxChange($('#options-rtlcheck'), () => {
|
||||
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($('#options-rtlcheck')));
|
||||
});
|
||||
html10n.bind('localized', function() {
|
||||
html10n.bind('localized', () => {
|
||||
pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
|
||||
padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection()));
|
||||
})
|
||||
padutils.setCheckbox($('#options-rtlcheck'), ('rtl' == html10n.getDirection()));
|
||||
});
|
||||
|
||||
// font family change
|
||||
$("#viewfontmenu").change(function() {
|
||||
pad.changeViewOption('padFontFamily', $("#viewfontmenu").val());
|
||||
$('#viewfontmenu').change(() => {
|
||||
pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
|
||||
});
|
||||
|
||||
// Language
|
||||
html10n.bind('localized', function() {
|
||||
$("#languagemenu").val(html10n.getLanguage());
|
||||
html10n.bind('localized', () => {
|
||||
$('#languagemenu').val(html10n.getLanguage());
|
||||
// translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist
|
||||
// this does not interfere with html10n's normal value-setting because html10n just ingores <input>s
|
||||
// also, a value which has been set by the user will be not overwritten since a user-edited <input>
|
||||
// does *not* have the editempty-class
|
||||
$('input[data-l10n-id]').each(function(key, input){
|
||||
$('input[data-l10n-id]').each((key, input) => {
|
||||
input = $(input);
|
||||
if(input.hasClass("editempty")){
|
||||
input.val(html10n.get(input.attr("data-l10n-id")));
|
||||
if (input.hasClass('editempty')) {
|
||||
input.val(html10n.get(input.attr('data-l10n-id')));
|
||||
}
|
||||
});
|
||||
})
|
||||
$("#languagemenu").val(html10n.getLanguage());
|
||||
$("#languagemenu").change(function() {
|
||||
});
|
||||
$('#languagemenu').val(html10n.getLanguage());
|
||||
$('#languagemenu').change(() => {
|
||||
Cookies.set('language', $('#languagemenu').val());
|
||||
window.html10n.localize([$("#languagemenu").val(), 'en']);
|
||||
window.html10n.localize([$('#languagemenu').val(), 'en']);
|
||||
});
|
||||
},
|
||||
setViewOptions: function(newOptions) {
|
||||
setViewOptions(newOptions) {
|
||||
function getOption(key, defaultValue) {
|
||||
var value = String(newOptions[key]);
|
||||
if (value == "true") return true;
|
||||
if (value == "false") return false;
|
||||
const value = String(newOptions[key]);
|
||||
if (value == 'true') return true;
|
||||
if (value == 'false') return false;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var v;
|
||||
let v;
|
||||
|
||||
v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
|
||||
self.ace.setProperty("rtlIsTrue", v);
|
||||
padutils.setCheckbox($("#options-rtlcheck"), v);
|
||||
self.ace.setProperty('rtlIsTrue', v);
|
||||
padutils.setCheckbox($('#options-rtlcheck'), v);
|
||||
|
||||
v = getOption('showLineNumbers', true);
|
||||
self.ace.setProperty("showslinenumbers", v);
|
||||
padutils.setCheckbox($("#options-linenoscheck"), v);
|
||||
self.ace.setProperty('showslinenumbers', v);
|
||||
padutils.setCheckbox($('#options-linenoscheck'), v);
|
||||
|
||||
v = getOption('showAuthorColors', true);
|
||||
self.ace.setProperty("showsauthorcolors", v);
|
||||
self.ace.setProperty('showsauthorcolors', v);
|
||||
$('#chattext').toggleClass('authorColors', v);
|
||||
$('iframe[name="ace_outer"]').contents().find('#sidedivinner').toggleClass('authorColors', v);
|
||||
padutils.setCheckbox($("#options-colorscheck"), v);
|
||||
padutils.setCheckbox($('#options-colorscheck'), v);
|
||||
|
||||
// Override from parameters if true
|
||||
if (settings.noColors !== false){
|
||||
self.ace.setProperty("showsauthorcolors", !settings.noColors);
|
||||
if (settings.noColors !== false) {
|
||||
self.ace.setProperty('showsauthorcolors', !settings.noColors);
|
||||
}
|
||||
|
||||
self.ace.setProperty("textface", newOptions['padFontFamily'] || "");
|
||||
self.ace.setProperty('textface', newOptions.padFontFamily || '');
|
||||
},
|
||||
dispose: function() {
|
||||
if (self.ace)
|
||||
{
|
||||
dispose() {
|
||||
if (self.ace) {
|
||||
self.ace.destroy();
|
||||
self.ace = null;
|
||||
}
|
||||
},
|
||||
enable: function() {
|
||||
if (self.ace)
|
||||
{
|
||||
enable() {
|
||||
if (self.ace) {
|
||||
self.ace.setEditable(true);
|
||||
}
|
||||
},
|
||||
disable: function() {
|
||||
if (self.ace)
|
||||
{
|
||||
self.ace.setProperty("grayedOut", true);
|
||||
disable() {
|
||||
if (self.ace) {
|
||||
self.ace.setProperty('grayedOut', true);
|
||||
self.ace.setEditable(false);
|
||||
}
|
||||
},
|
||||
restoreRevisionText: function(dataFromServer) {
|
||||
restoreRevisionText(dataFromServer) {
|
||||
pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
|
||||
self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
|
||||
}
|
||||
},
|
||||
};
|
||||
return self;
|
||||
}());
|
||||
|
|
|
@ -20,14 +20,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padimpexp = (function() {
|
||||
|
||||
///// import
|
||||
var currentImportTimer = null;
|
||||
const padimpexp = (function () {
|
||||
// /// import
|
||||
let currentImportTimer = null;
|
||||
|
||||
function addImportFrames() {
|
||||
$("#import .importframe").remove();
|
||||
var iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
|
||||
$('#import .importframe').remove();
|
||||
const iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
|
||||
$('#import').append(iframe);
|
||||
}
|
||||
|
||||
|
@ -39,29 +38,27 @@ var padimpexp = (function() {
|
|||
}
|
||||
|
||||
function fileInputSubmit() {
|
||||
$('#importmessagefail').fadeOut("fast");
|
||||
var ret = window.confirm(html10n.get("pad.impexp.confirmimport"));
|
||||
if (ret)
|
||||
{
|
||||
currentImportTimer = window.setTimeout(function() {
|
||||
if (!currentImportTimer)
|
||||
{
|
||||
$('#importmessagefail').fadeOut('fast');
|
||||
const ret = window.confirm(html10n.get('pad.impexp.confirmimport'));
|
||||
if (ret) {
|
||||
currentImportTimer = window.setTimeout(() => {
|
||||
if (!currentImportTimer) {
|
||||
return;
|
||||
}
|
||||
currentImportTimer = null;
|
||||
importFailed("Request timed out.");
|
||||
importFailed('Request timed out.');
|
||||
importDone();
|
||||
}, 25000); // time out after some number of seconds
|
||||
$('#importsubmitinput').attr(
|
||||
{
|
||||
disabled: true
|
||||
}).val(html10n.get("pad.impexp.importing"));
|
||||
{
|
||||
disabled: true,
|
||||
}).val(html10n.get('pad.impexp.importing'));
|
||||
|
||||
window.setTimeout(function() {
|
||||
window.setTimeout(() => {
|
||||
$('#importfileinput').attr(
|
||||
{
|
||||
disabled: true
|
||||
});
|
||||
{
|
||||
disabled: true,
|
||||
});
|
||||
}, 0);
|
||||
$('#importarrow').stop(true, true).hide();
|
||||
$('#importstatusball').show();
|
||||
|
@ -74,8 +71,8 @@ var padimpexp = (function() {
|
|||
}
|
||||
|
||||
function importDone() {
|
||||
$('#importsubmitinput').removeAttr('disabled').val(html10n.get("pad.impexp.importbutton"));
|
||||
window.setTimeout(function() {
|
||||
$('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton'));
|
||||
window.setTimeout(() => {
|
||||
$('#importfileinput').removeAttr('disabled');
|
||||
}, 0);
|
||||
$('#importstatusball').hide();
|
||||
|
@ -84,155 +81,136 @@ var padimpexp = (function() {
|
|||
}
|
||||
|
||||
function importClearTimeout() {
|
||||
if (currentImportTimer)
|
||||
{
|
||||
if (currentImportTimer) {
|
||||
window.clearTimeout(currentImportTimer);
|
||||
currentImportTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function importErrorMessage(status) {
|
||||
var msg="";
|
||||
let msg = '';
|
||||
|
||||
if(status === "convertFailed"){
|
||||
msg = html10n.get("pad.impexp.convertFailed");
|
||||
} else if(status === "uploadFailed"){
|
||||
msg = html10n.get("pad.impexp.uploadFailed");
|
||||
} else if(status === "padHasData"){
|
||||
msg = html10n.get("pad.impexp.padHasData");
|
||||
} else if(status === "maxFileSize"){
|
||||
msg = html10n.get("pad.impexp.maxFileSize");
|
||||
} else if(status === "permission"){
|
||||
msg = html10n.get("pad.impexp.permission");
|
||||
if (status === 'convertFailed') {
|
||||
msg = html10n.get('pad.impexp.convertFailed');
|
||||
} else if (status === 'uploadFailed') {
|
||||
msg = html10n.get('pad.impexp.uploadFailed');
|
||||
} else if (status === 'padHasData') {
|
||||
msg = html10n.get('pad.impexp.padHasData');
|
||||
} else if (status === 'maxFileSize') {
|
||||
msg = html10n.get('pad.impexp.maxFileSize');
|
||||
} else if (status === 'permission') {
|
||||
msg = html10n.get('pad.impexp.permission');
|
||||
}
|
||||
|
||||
function showError(fade) {
|
||||
$('#importmessagefail').html('<strong style="color: red">'+html10n.get('pad.impexp.importfailed')+':</strong> ' + (msg || html10n.get('pad.impexp.copypaste','')))[(fade ? "fadeIn" : "show")]();
|
||||
$('#importmessagefail').html(`<strong style="color: red">${html10n.get('pad.impexp.importfailed')}:</strong> ${msg || html10n.get('pad.impexp.copypaste', '')}`)[(fade ? 'fadeIn' : 'show')]();
|
||||
}
|
||||
|
||||
if ($('#importexport .importmessage').is(':visible'))
|
||||
{
|
||||
$('#importmessagesuccess').fadeOut("fast");
|
||||
$('#importmessagefail').fadeOut("fast", function() {
|
||||
if ($('#importexport .importmessage').is(':visible')) {
|
||||
$('#importmessagesuccess').fadeOut('fast');
|
||||
$('#importmessagefail').fadeOut('fast', () => {
|
||||
showError(true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
showError();
|
||||
}
|
||||
}
|
||||
|
||||
function importSuccessful(token) {
|
||||
$.ajax(
|
||||
{
|
||||
type: 'post',
|
||||
url: '/ep/pad/impexp/import2',
|
||||
data: {
|
||||
token: token,
|
||||
padId: pad.getPadId()
|
||||
},
|
||||
success: importApplicationSuccessful,
|
||||
error: importApplicationFailed,
|
||||
timeout: 25000
|
||||
});
|
||||
{
|
||||
type: 'post',
|
||||
url: '/ep/pad/impexp/import2',
|
||||
data: {
|
||||
token,
|
||||
padId: pad.getPadId(),
|
||||
},
|
||||
success: importApplicationSuccessful,
|
||||
error: importApplicationFailed,
|
||||
timeout: 25000,
|
||||
});
|
||||
addImportFrames();
|
||||
}
|
||||
|
||||
function importApplicationFailed(xhr, textStatus, errorThrown) {
|
||||
importErrorMessage("Error during conversion.");
|
||||
importErrorMessage('Error during conversion.');
|
||||
importDone();
|
||||
}
|
||||
|
||||
///// export
|
||||
// /// export
|
||||
|
||||
function cantExport() {
|
||||
var type = $(this);
|
||||
if (type.hasClass("exporthrefpdf"))
|
||||
{
|
||||
type = "PDF";
|
||||
let type = $(this);
|
||||
if (type.hasClass('exporthrefpdf')) {
|
||||
type = 'PDF';
|
||||
} else if (type.hasClass('exporthrefdoc')) {
|
||||
type = 'Microsoft Word';
|
||||
} else if (type.hasClass('exporthrefodt')) {
|
||||
type = 'OpenDocument';
|
||||
} else {
|
||||
type = 'this file';
|
||||
}
|
||||
else if (type.hasClass("exporthrefdoc"))
|
||||
{
|
||||
type = "Microsoft Word";
|
||||
}
|
||||
else if (type.hasClass("exporthrefodt"))
|
||||
{
|
||||
type = "OpenDocument";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "this file";
|
||||
}
|
||||
alert(html10n.get("pad.impexp.exportdisabled", {type:type}));
|
||||
alert(html10n.get('pad.impexp.exportdisabled', {type}));
|
||||
return false;
|
||||
}
|
||||
|
||||
/////
|
||||
// ///
|
||||
var pad = undefined;
|
||||
var self = {
|
||||
init: function(_pad) {
|
||||
const self = {
|
||||
init(_pad) {
|
||||
pad = _pad;
|
||||
|
||||
//get /p/padname
|
||||
// get /p/padname
|
||||
// if /p/ isn't available due to a rewrite we use the clientVars padId
|
||||
var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId;
|
||||
//get http://example.com/p/padname without Params
|
||||
var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname;
|
||||
const pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId;
|
||||
// get http://example.com/p/padname without Params
|
||||
const pad_root_url = `${document.location.protocol}//${document.location.host}${document.location.pathname}`;
|
||||
|
||||
//i10l buttom import
|
||||
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton"));
|
||||
html10n.bind('localized', function() {
|
||||
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton"));
|
||||
})
|
||||
// i10l buttom import
|
||||
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
|
||||
html10n.bind('localized', () => {
|
||||
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
|
||||
});
|
||||
|
||||
// build the export links
|
||||
$("#exporthtmla").attr("href", pad_root_path + "/export/html");
|
||||
$("#exportetherpada").attr("href", pad_root_path + "/export/etherpad");
|
||||
$("#exportplaina").attr("href", pad_root_path + "/export/txt");
|
||||
$('#exporthtmla').attr('href', `${pad_root_path}/export/html`);
|
||||
$('#exportetherpada').attr('href', `${pad_root_path}/export/etherpad`);
|
||||
$('#exportplaina').attr('href', `${pad_root_path}/export/txt`);
|
||||
|
||||
// activate action to import in the form
|
||||
$("#importform").attr('action', pad_root_url + "/import");
|
||||
$('#importform').attr('action', `${pad_root_url}/import`);
|
||||
|
||||
//hide stuff thats not avaible if abiword/soffice is disabled
|
||||
if(clientVars.exportAvailable == "no")
|
||||
{
|
||||
$("#exportworda").remove();
|
||||
$("#exportpdfa").remove();
|
||||
$("#exportopena").remove();
|
||||
// hide stuff thats not avaible if abiword/soffice is disabled
|
||||
if (clientVars.exportAvailable == 'no') {
|
||||
$('#exportworda').remove();
|
||||
$('#exportpdfa').remove();
|
||||
$('#exportopena').remove();
|
||||
|
||||
$("#importmessageabiword").show();
|
||||
}
|
||||
else if(clientVars.exportAvailable == "withoutPDF")
|
||||
{
|
||||
$("#exportpdfa").remove();
|
||||
$('#importmessageabiword').show();
|
||||
} else if (clientVars.exportAvailable == 'withoutPDF') {
|
||||
$('#exportpdfa').remove();
|
||||
|
||||
$("#exportworda").attr("href", pad_root_path + "/export/doc");
|
||||
$("#exportopena").attr("href", pad_root_path + "/export/odt");
|
||||
$('#exportworda').attr('href', `${pad_root_path}/export/doc`);
|
||||
$('#exportopena').attr('href', `${pad_root_path}/export/odt`);
|
||||
|
||||
$("#importexport").css({"height":"142px"});
|
||||
$("#importexportline").css({"height":"142px"});
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#exportworda").attr("href", pad_root_path + "/export/doc");
|
||||
$("#exportpdfa").attr("href", pad_root_path + "/export/pdf");
|
||||
$("#exportopena").attr("href", pad_root_path + "/export/odt");
|
||||
$('#importexport').css({height: '142px'});
|
||||
$('#importexportline').css({height: '142px'});
|
||||
} else {
|
||||
$('#exportworda').attr('href', `${pad_root_path}/export/doc`);
|
||||
$('#exportpdfa').attr('href', `${pad_root_path}/export/pdf`);
|
||||
$('#exportopena').attr('href', `${pad_root_path}/export/odt`);
|
||||
}
|
||||
|
||||
addImportFrames();
|
||||
$("#importfileinput").change(fileInputUpdated);
|
||||
$('#importform').unbind("submit").submit(fileInputSubmit);
|
||||
$('#importfileinput').change(fileInputUpdated);
|
||||
$('#importform').unbind('submit').submit(fileInputSubmit);
|
||||
$('.disabledexport').click(cantExport);
|
||||
},
|
||||
handleFrameCall: function(directDatabaseAccess, status) {
|
||||
if(directDatabaseAccess === "undefined") directDatabaseAccess = false;
|
||||
if (status !== "ok")
|
||||
{
|
||||
handleFrameCall(directDatabaseAccess, status) {
|
||||
if (directDatabaseAccess === 'undefined') directDatabaseAccess = false;
|
||||
if (status !== 'ok') {
|
||||
importFailed(status);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$('#import_export').removeClass('popup-show');
|
||||
}
|
||||
|
||||
|
@ -244,16 +222,16 @@ var padimpexp = (function() {
|
|||
|
||||
importDone();
|
||||
},
|
||||
disable: function() {
|
||||
$("#impexp-disabled-clickcatcher").show();
|
||||
$("#import").css('opacity', 0.5);
|
||||
$("#impexp-export").css('opacity', 0.5);
|
||||
disable() {
|
||||
$('#impexp-disabled-clickcatcher').show();
|
||||
$('#import').css('opacity', 0.5);
|
||||
$('#impexp-export').css('opacity', 0.5);
|
||||
},
|
||||
enable() {
|
||||
$('#impexp-disabled-clickcatcher').hide();
|
||||
$('#import').css('opacity', 1);
|
||||
$('#impexp-export').css('opacity', 1);
|
||||
},
|
||||
enable: function() {
|
||||
$("#impexp-disabled-clickcatcher").hide();
|
||||
$("#import").css('opacity', 1);
|
||||
$("#impexp-export").css('opacity', 1);
|
||||
}
|
||||
};
|
||||
return self;
|
||||
}());
|
||||
|
|
|
@ -20,33 +20,33 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padeditbar = require('./pad_editbar').padeditbar;
|
||||
var automaticReconnect = require('./pad_automatic_reconnect');
|
||||
const padeditbar = require('./pad_editbar').padeditbar;
|
||||
const automaticReconnect = require('./pad_automatic_reconnect');
|
||||
|
||||
var padmodals = (function() {
|
||||
var pad = undefined;
|
||||
var self = {
|
||||
init: function(_pad) {
|
||||
const padmodals = (function () {
|
||||
let pad = undefined;
|
||||
const self = {
|
||||
init(_pad) {
|
||||
pad = _pad;
|
||||
},
|
||||
showModal: function(messageId) {
|
||||
padeditbar.toggleDropDown("none", function() {
|
||||
$("#connectivity .visible").removeClass('visible');
|
||||
$("#connectivity ."+messageId).addClass('visible');
|
||||
showModal(messageId) {
|
||||
padeditbar.toggleDropDown('none', () => {
|
||||
$('#connectivity .visible').removeClass('visible');
|
||||
$(`#connectivity .${messageId}`).addClass('visible');
|
||||
|
||||
var $modal = $('#connectivity .' + messageId);
|
||||
const $modal = $(`#connectivity .${messageId}`);
|
||||
automaticReconnect.showCountDownTimerToReconnectOnModal($modal, pad);
|
||||
|
||||
padeditbar.toggleDropDown("connectivity");
|
||||
padeditbar.toggleDropDown('connectivity');
|
||||
});
|
||||
},
|
||||
showOverlay: function() {
|
||||
showOverlay() {
|
||||
// Prevent the user to interact with the toolbar. Useful when user is disconnected for example
|
||||
$("#toolbar-overlay").show();
|
||||
$('#toolbar-overlay').show();
|
||||
},
|
||||
hideOverlay() {
|
||||
$('#toolbar-overlay').hide();
|
||||
},
|
||||
hideOverlay: function() {
|
||||
$("#toolbar-overlay").hide();
|
||||
}
|
||||
};
|
||||
return self;
|
||||
}());
|
||||
|
|
|
@ -14,22 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var pad;
|
||||
let pad;
|
||||
|
||||
exports.saveNow = function(){
|
||||
pad.collabClient.sendMessage({"type": "SAVE_REVISION"});
|
||||
exports.saveNow = function () {
|
||||
pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: _("pad.savedrevs.marked"),
|
||||
title: _('pad.savedrevs.marked'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider",
|
||||
text: _('pad.savedrevs.timeslider') || 'You can view saved revisions in the timeslider',
|
||||
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||
sticky: false,
|
||||
time: 3000,
|
||||
class_name: "saved-revision",
|
||||
class_name: 'saved-revision',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = function(_pad){
|
||||
exports.init = function (_pad) {
|
||||
pad = _pad;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,56 +20,55 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padutils = require('./pad_utils').padutils;
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
var myUserInfo = {};
|
||||
let myUserInfo = {};
|
||||
|
||||
var colorPickerOpen = false;
|
||||
var colorPickerSetup = false;
|
||||
var previousColorId = 0;
|
||||
let colorPickerOpen = false;
|
||||
let colorPickerSetup = false;
|
||||
let previousColorId = 0;
|
||||
|
||||
|
||||
var paduserlist = (function() {
|
||||
|
||||
var rowManager = (function() {
|
||||
const paduserlist = (function () {
|
||||
const rowManager = (function () {
|
||||
// The row manager handles rendering rows of the user list and animating
|
||||
// their insertion, removal, and reordering. It manipulates TD height
|
||||
// and TD opacity.
|
||||
|
||||
function nextRowId() {
|
||||
return "usertr" + (nextRowId.counter++);
|
||||
return `usertr${nextRowId.counter++}`;
|
||||
}
|
||||
nextRowId.counter = 1;
|
||||
// objects are shared; fields are "domId","data","animationStep"
|
||||
var rowsFadingOut = []; // unordered set
|
||||
var rowsFadingIn = []; // unordered set
|
||||
var rowsPresent = []; // in order
|
||||
var ANIMATION_START = -12; // just starting to fade in
|
||||
var ANIMATION_END = 12; // just finishing fading out
|
||||
const rowsFadingOut = []; // unordered set
|
||||
const rowsFadingIn = []; // unordered set
|
||||
const rowsPresent = []; // in order
|
||||
const ANIMATION_START = -12; // just starting to fade in
|
||||
const ANIMATION_END = 12; // just finishing fading out
|
||||
|
||||
|
||||
function getAnimationHeight(step, power) {
|
||||
var a = Math.abs(step / 12);
|
||||
if (power == 2) a = a * a;
|
||||
let a = Math.abs(step / 12);
|
||||
if (power == 2) a *= a;
|
||||
else if (power == 3) a = a * a * a;
|
||||
else if (power == 4) a = a * a * a * a;
|
||||
else if (power >= 5) a = a * a * a * a * a;
|
||||
return Math.round(26 * (1 - a));
|
||||
}
|
||||
var OPACITY_STEPS = 6;
|
||||
const OPACITY_STEPS = 6;
|
||||
|
||||
var ANIMATION_STEP_TIME = 20;
|
||||
var LOWER_FRAMERATE_FACTOR = 2;
|
||||
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
|
||||
const ANIMATION_STEP_TIME = 20;
|
||||
const LOWER_FRAMERATE_FACTOR = 2;
|
||||
const scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
|
||||
|
||||
var NUMCOLS = 4;
|
||||
const NUMCOLS = 4;
|
||||
|
||||
// we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
|
||||
// IE's poor handling when manipulating the DOM directly.
|
||||
|
||||
function getEmptyRowHtml(height) {
|
||||
return '<td colspan="' + NUMCOLS + '" style="border:0;height:' + height + 'px"><!-- --></td>';
|
||||
return `<td colspan="${NUMCOLS}" style="border:0;height:${height}px"><!-- --></td>`;
|
||||
}
|
||||
|
||||
function isNameEditable(data) {
|
||||
|
@ -77,84 +76,68 @@ var paduserlist = (function() {
|
|||
}
|
||||
|
||||
function replaceUserRowContents(tr, height, data) {
|
||||
var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
|
||||
if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0)
|
||||
{
|
||||
const tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
|
||||
if (isNameEditable(data) && tr.find('td.usertdname input:enabled').length > 0) {
|
||||
// preserve input field node
|
||||
for (var i = 0; i < tds.length; i++)
|
||||
{
|
||||
var oldTd = $(tr.find("td").get(i));
|
||||
if (!oldTd.hasClass('usertdname'))
|
||||
{
|
||||
for (let i = 0; i < tds.length; i++) {
|
||||
const oldTd = $(tr.find('td').get(i));
|
||||
if (!oldTd.hasClass('usertdname')) {
|
||||
oldTd.replaceWith(tds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
tr.html(tds.join(''));
|
||||
}
|
||||
return tr;
|
||||
}
|
||||
|
||||
function getUserRowHtml(height, data) {
|
||||
var nameHtml;
|
||||
if (data.name)
|
||||
{
|
||||
let nameHtml;
|
||||
if (data.name) {
|
||||
nameHtml = padutils.escapeHtml(data.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
nameHtml = '<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="'+_('pad.userlist.unnamed')+'" ' + (isNameEditable(data) ? '' : 'disabled="disabled" ') + '/>';
|
||||
} else {
|
||||
nameHtml = `<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="${_('pad.userlist.unnamed')}" ${isNameEditable(data) ? '' : 'disabled="disabled" '}/>`;
|
||||
}
|
||||
|
||||
return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + padutils.escapeHtml(data.color) + '"> </div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
|
||||
return ['<td style="height:', height, `px" class="usertdswatch"><div class="swatch" style="background:${padutils.escapeHtml(data.color)}"> </div></td>`, '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
|
||||
}
|
||||
|
||||
function getRowHtml(id, innerHtml, authorId) {
|
||||
return '<tr data-authorId="'+authorId+'" id="' + id + '">' + innerHtml + '</tr>';
|
||||
return `<tr data-authorId="${authorId}" id="${id}">${innerHtml}</tr>`;
|
||||
}
|
||||
|
||||
function rowNode(row) {
|
||||
return $("#" + row.domId);
|
||||
return $(`#${row.domId}`);
|
||||
}
|
||||
|
||||
function handleRowData(row) {
|
||||
if (row.data && row.data.status == 'Disconnected')
|
||||
{
|
||||
if (row.data && row.data.status == 'Disconnected') {
|
||||
row.opacity = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delete row.opacity;
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowNode(tr, data) {
|
||||
if (data.titleText)
|
||||
{
|
||||
var titleText = data.titleText;
|
||||
window.setTimeout(function() {
|
||||
if (data.titleText) {
|
||||
const titleText = data.titleText;
|
||||
window.setTimeout(() => {
|
||||
/* tr.attr('title', titleText)*/
|
||||
}, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
tr.removeAttr('title');
|
||||
}
|
||||
}
|
||||
|
||||
function handleOtherUserInputs() {
|
||||
// handle 'INPUT' elements for naming other unnamed users
|
||||
$("#otheruserstable input.newinput").each(function() {
|
||||
var input = $(this);
|
||||
var tr = input.closest("tr");
|
||||
if (tr.length > 0)
|
||||
{
|
||||
var index = tr.parent().children().index(tr);
|
||||
if (index >= 0)
|
||||
{
|
||||
var userId = rowsPresent[index].data.id;
|
||||
$('#otheruserstable input.newinput').each(function () {
|
||||
const input = $(this);
|
||||
const tr = input.closest('tr');
|
||||
if (tr.length > 0) {
|
||||
const index = tr.parent().children().index(tr);
|
||||
if (index >= 0) {
|
||||
const userId = rowsPresent[index].data.id;
|
||||
rowManagerMakeNameEditor($(this), userId);
|
||||
}
|
||||
}
|
||||
|
@ -168,41 +151,34 @@ var paduserlist = (function() {
|
|||
position = Math.max(0, Math.min(rowsPresent.length, position));
|
||||
animationPower = (animationPower === undefined ? 4 : animationPower);
|
||||
|
||||
var domId = nextRowId();
|
||||
var row = {
|
||||
data: data,
|
||||
const domId = nextRowId();
|
||||
const row = {
|
||||
data,
|
||||
animationStep: ANIMATION_START,
|
||||
domId: domId,
|
||||
animationPower: animationPower
|
||||
domId,
|
||||
animationPower,
|
||||
};
|
||||
var authorId = data.id;
|
||||
const authorId = data.id;
|
||||
|
||||
handleRowData(row);
|
||||
rowsPresent.splice(position, 0, row);
|
||||
var tr;
|
||||
if (animationPower == 0)
|
||||
{
|
||||
let tr;
|
||||
if (animationPower == 0) {
|
||||
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId));
|
||||
row.animationStep = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
rowsFadingIn.push(row);
|
||||
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId));
|
||||
}
|
||||
handleRowNode(tr, data);
|
||||
$("table#otheruserstable").show();
|
||||
if (position == 0)
|
||||
{
|
||||
$("table#otheruserstable").prepend(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
$('table#otheruserstable').show();
|
||||
if (position == 0) {
|
||||
$('table#otheruserstable').prepend(tr);
|
||||
} else {
|
||||
rowNode(rowsPresent[position - 1]).after(tr);
|
||||
}
|
||||
|
||||
if (animationPower != 0)
|
||||
{
|
||||
if (animationPower != 0) {
|
||||
scheduleAnimation();
|
||||
}
|
||||
|
||||
|
@ -212,16 +188,14 @@ var paduserlist = (function() {
|
|||
}
|
||||
|
||||
function updateRow(position, data) {
|
||||
var row = rowsPresent[position];
|
||||
if (row)
|
||||
{
|
||||
const row = rowsPresent[position];
|
||||
if (row) {
|
||||
row.data = data;
|
||||
handleRowData(row);
|
||||
if (row.animationStep == 0)
|
||||
{
|
||||
if (row.animationStep == 0) {
|
||||
// not currently animating
|
||||
var tr = rowNode(row);
|
||||
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
|
||||
const tr = rowNode(row);
|
||||
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find('td').css('opacity', (row.opacity === undefined ? 1 : row.opacity));
|
||||
handleRowNode(tr, data);
|
||||
handleOtherUserInputs();
|
||||
}
|
||||
|
@ -230,16 +204,12 @@ var paduserlist = (function() {
|
|||
|
||||
function removeRow(position, animationPower) {
|
||||
animationPower = (animationPower === undefined ? 4 : animationPower);
|
||||
var row = rowsPresent[position];
|
||||
if (row)
|
||||
{
|
||||
const row = rowsPresent[position];
|
||||
if (row) {
|
||||
rowsPresent.splice(position, 1); // remove
|
||||
if (animationPower == 0)
|
||||
{
|
||||
if (animationPower == 0) {
|
||||
rowNode(row).remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
row.animationStep = -row.animationStep; // use symmetry
|
||||
row.animationPower = animationPower;
|
||||
rowsFadingOut.push(row);
|
||||
|
@ -247,7 +217,7 @@ var paduserlist = (function() {
|
|||
}
|
||||
}
|
||||
if (rowsPresent.length === 0) {
|
||||
$("table#otheruserstable").hide();
|
||||
$('table#otheruserstable').hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,10 +226,9 @@ var paduserlist = (function() {
|
|||
|
||||
function moveRow(oldPosition, newPosition, animationPower) {
|
||||
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
|
||||
var row = rowsPresent[oldPosition];
|
||||
if (row && oldPosition != newPosition)
|
||||
{
|
||||
var rowData = row.data;
|
||||
const row = rowsPresent[oldPosition];
|
||||
if (row && oldPosition != newPosition) {
|
||||
const rowData = row.data;
|
||||
removeRow(oldPosition, animationPower);
|
||||
insertRow(newPosition, rowData, animationPower);
|
||||
}
|
||||
|
@ -267,55 +236,39 @@ var paduserlist = (function() {
|
|||
|
||||
function animateStep() {
|
||||
// animation must be symmetrical
|
||||
for (var i = rowsFadingIn.length - 1; i >= 0; i--)
|
||||
{ // backwards to allow removal
|
||||
for (var i = rowsFadingIn.length - 1; i >= 0; i--) { // backwards to allow removal
|
||||
var row = rowsFadingIn[i];
|
||||
var step = ++row.animationStep;
|
||||
var animHeight = getAnimationHeight(step, row.animationPower);
|
||||
var node = rowNode(row);
|
||||
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
|
||||
if (step <= -OPACITY_STEPS)
|
||||
{
|
||||
node.find("td").height(animHeight);
|
||||
}
|
||||
else if (step == -OPACITY_STEPS + 1)
|
||||
{
|
||||
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS);
|
||||
if (step <= -OPACITY_STEPS) {
|
||||
node.find('td').height(animHeight);
|
||||
} else if (step == -OPACITY_STEPS + 1) {
|
||||
node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1 / OPACITY_STEPS);
|
||||
handleRowNode(node, row.data);
|
||||
}
|
||||
else if (step < 0)
|
||||
{
|
||||
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
|
||||
}
|
||||
else if (step == 0)
|
||||
{
|
||||
} else if (step < 0) {
|
||||
node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
|
||||
} else if (step == 0) {
|
||||
// set HTML in case modified during animation
|
||||
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight);
|
||||
node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1).height(animHeight);
|
||||
handleRowNode(node, row.data);
|
||||
rowsFadingIn.splice(i, 1); // remove from set
|
||||
}
|
||||
}
|
||||
for (var i = rowsFadingOut.length - 1; i >= 0; i--)
|
||||
{ // backwards to allow removal
|
||||
for (var i = rowsFadingOut.length - 1; i >= 0; i--) { // backwards to allow removal
|
||||
var row = rowsFadingOut[i];
|
||||
var step = ++row.animationStep;
|
||||
var node = rowNode(row);
|
||||
var animHeight = getAnimationHeight(step, row.animationPower);
|
||||
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
|
||||
if (step < OPACITY_STEPS)
|
||||
{
|
||||
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
|
||||
}
|
||||
else if (step == OPACITY_STEPS)
|
||||
{
|
||||
if (step < OPACITY_STEPS) {
|
||||
node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
|
||||
} else if (step == OPACITY_STEPS) {
|
||||
node.html(getEmptyRowHtml(animHeight));
|
||||
}
|
||||
else if (step <= ANIMATION_END)
|
||||
{
|
||||
node.find("td").height(animHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else if (step <= ANIMATION_END) {
|
||||
node.find('td').height(animHeight);
|
||||
} else {
|
||||
rowsFadingOut.splice(i, 1); // remove from set
|
||||
node.remove();
|
||||
}
|
||||
|
@ -326,36 +279,30 @@ var paduserlist = (function() {
|
|||
return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
|
||||
}
|
||||
|
||||
var self = {
|
||||
insertRow: insertRow,
|
||||
removeRow: removeRow,
|
||||
moveRow: moveRow,
|
||||
updateRow: updateRow
|
||||
const self = {
|
||||
insertRow,
|
||||
removeRow,
|
||||
moveRow,
|
||||
updateRow,
|
||||
};
|
||||
return self;
|
||||
}()); ////////// rowManager
|
||||
var otherUsersInfo = [];
|
||||
var otherUsersData = [];
|
||||
}()); // //////// rowManager
|
||||
const otherUsersInfo = [];
|
||||
const otherUsersData = [];
|
||||
|
||||
function rowManagerMakeNameEditor(jnode, userId) {
|
||||
setUpEditable(jnode, function() {
|
||||
var existingIndex = findExistingIndex(userId);
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
setUpEditable(jnode, () => {
|
||||
const existingIndex = findExistingIndex(userId);
|
||||
if (existingIndex >= 0) {
|
||||
return otherUsersInfo[existingIndex].name || '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, function(newName) {
|
||||
if (!newName)
|
||||
{
|
||||
jnode.addClass("editempty");
|
||||
}, (newName) => {
|
||||
if (!newName) {
|
||||
jnode.addClass('editempty');
|
||||
jnode.val(_('pad.userlist.unnamed'));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
jnode.attr('disabled', 'disabled');
|
||||
pad.suggestUserName(userId, newName);
|
||||
}
|
||||
|
@ -363,11 +310,9 @@ var paduserlist = (function() {
|
|||
}
|
||||
|
||||
function findExistingIndex(userId) {
|
||||
var existingIndex = -1;
|
||||
for (var i = 0; i < otherUsersInfo.length; i++)
|
||||
{
|
||||
if (otherUsersInfo[i].userId == userId)
|
||||
{
|
||||
let existingIndex = -1;
|
||||
for (let i = 0; i < otherUsersInfo.length; i++) {
|
||||
if (otherUsersInfo[i].userId == userId) {
|
||||
existingIndex = i;
|
||||
break;
|
||||
}
|
||||
|
@ -376,144 +321,134 @@ var paduserlist = (function() {
|
|||
}
|
||||
|
||||
function setUpEditable(jqueryNode, valueGetter, valueSetter) {
|
||||
jqueryNode.bind('focus', function(evt) {
|
||||
var oldValue = valueGetter();
|
||||
if (jqueryNode.val() !== oldValue)
|
||||
{
|
||||
jqueryNode.bind('focus', (evt) => {
|
||||
const oldValue = valueGetter();
|
||||
if (jqueryNode.val() !== oldValue) {
|
||||
jqueryNode.val(oldValue);
|
||||
}
|
||||
jqueryNode.addClass("editactive").removeClass("editempty");
|
||||
jqueryNode.addClass('editactive').removeClass('editempty');
|
||||
});
|
||||
jqueryNode.bind('blur', function(evt) {
|
||||
var newValue = jqueryNode.removeClass("editactive").val();
|
||||
jqueryNode.bind('blur', (evt) => {
|
||||
const newValue = jqueryNode.removeClass('editactive').val();
|
||||
valueSetter(newValue);
|
||||
});
|
||||
padutils.bindEnterAndEscape(jqueryNode, function onEnter() {
|
||||
padutils.bindEnterAndEscape(jqueryNode, () => {
|
||||
jqueryNode.blur();
|
||||
}, function onEscape() {
|
||||
}, () => {
|
||||
jqueryNode.val(valueGetter()).blur();
|
||||
});
|
||||
jqueryNode.removeAttr('disabled').addClass('editable');
|
||||
}
|
||||
|
||||
var knocksToIgnore = {};
|
||||
var guestPromptFlashState = 0;
|
||||
var guestPromptFlash = padutils.makeAnimationScheduler(
|
||||
const knocksToIgnore = {};
|
||||
let guestPromptFlashState = 0;
|
||||
const guestPromptFlash = padutils.makeAnimationScheduler(
|
||||
|
||||
function() {
|
||||
var prompts = $("#guestprompts .guestprompt");
|
||||
if (prompts.length == 0)
|
||||
{
|
||||
return false; // no more to do
|
||||
}
|
||||
() => {
|
||||
const prompts = $('#guestprompts .guestprompt');
|
||||
if (prompts.length == 0) {
|
||||
return false; // no more to do
|
||||
}
|
||||
|
||||
guestPromptFlashState = 1 - guestPromptFlashState;
|
||||
if (guestPromptFlashState)
|
||||
{
|
||||
prompts.css('background', '#ffa');
|
||||
}
|
||||
else
|
||||
{
|
||||
prompts.css('background', '#ffe');
|
||||
}
|
||||
guestPromptFlashState = 1 - guestPromptFlashState;
|
||||
if (guestPromptFlashState) {
|
||||
prompts.css('background', '#ffa');
|
||||
} else {
|
||||
prompts.css('background', '#ffe');
|
||||
}
|
||||
|
||||
return true;
|
||||
}, 1000);
|
||||
return true;
|
||||
}, 1000);
|
||||
|
||||
var pad = undefined;
|
||||
var self = {
|
||||
init: function(myInitialUserInfo, _pad) {
|
||||
init(myInitialUserInfo, _pad) {
|
||||
pad = _pad;
|
||||
|
||||
self.setMyUserInfo(myInitialUserInfo);
|
||||
|
||||
if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>');
|
||||
if ($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>');
|
||||
|
||||
$("#otheruserstable tr").remove();
|
||||
$('#otheruserstable tr').remove();
|
||||
|
||||
if (pad.getUserIsGuest())
|
||||
{
|
||||
$("#myusernameedit").addClass('myusernameedithoverable');
|
||||
setUpEditable($("#myusernameedit"), function() {
|
||||
return myUserInfo.name || '';
|
||||
}, function(newValue) {
|
||||
if (pad.getUserIsGuest()) {
|
||||
$('#myusernameedit').addClass('myusernameedithoverable');
|
||||
setUpEditable($('#myusernameedit'), () => myUserInfo.name || '', (newValue) => {
|
||||
myUserInfo.name = newValue;
|
||||
pad.notifyChangeName(newValue);
|
||||
// wrap with setTimeout to do later because we get
|
||||
// a double "blur" fire in IE...
|
||||
window.setTimeout(function() {
|
||||
window.setTimeout(() => {
|
||||
self.renderMyUserInfo();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// color picker
|
||||
$("#myswatchbox").click(showColorPicker);
|
||||
$("#mycolorpicker .pickerswatchouter").click(function() {
|
||||
$("#mycolorpicker .pickerswatchouter").removeClass('picked');
|
||||
$('#myswatchbox').click(showColorPicker);
|
||||
$('#mycolorpicker .pickerswatchouter').click(function () {
|
||||
$('#mycolorpicker .pickerswatchouter').removeClass('picked');
|
||||
$(this).addClass('picked');
|
||||
});
|
||||
$("#mycolorpickersave").click(function() {
|
||||
$('#mycolorpickersave').click(() => {
|
||||
closeColorPicker(true);
|
||||
});
|
||||
$("#mycolorpickercancel").click(function() {
|
||||
$('#mycolorpickercancel').click(() => {
|
||||
closeColorPicker(false);
|
||||
});
|
||||
//
|
||||
},
|
||||
usersOnline: function() {
|
||||
usersOnline() {
|
||||
// Returns an object of users who are currently online on this pad
|
||||
var userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array
|
||||
const userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array
|
||||
// Now we need to add ourselves..
|
||||
userList.push(myUserInfo);
|
||||
return userList;
|
||||
},
|
||||
users: function(){
|
||||
users() {
|
||||
// Returns an object of users who have been on this pad
|
||||
var userList = self.usersOnline();
|
||||
const userList = self.usersOnline();
|
||||
|
||||
// Now we add historical authors
|
||||
var historical = clientVars.collab_client_vars.historicalAuthorData;
|
||||
for (var key in historical){
|
||||
const historical = clientVars.collab_client_vars.historicalAuthorData;
|
||||
for (const key in historical) {
|
||||
var userId = historical[key].userId;
|
||||
// Check we don't already have this author in our array
|
||||
var exists = false;
|
||||
|
||||
userList.forEach(function(user){
|
||||
if(user.userId === userId) exists = true;
|
||||
userList.forEach((user) => {
|
||||
if (user.userId === userId) exists = true;
|
||||
});
|
||||
|
||||
if(exists === false){
|
||||
if (exists === false) {
|
||||
userList.push(historical[key]);
|
||||
}
|
||||
}
|
||||
return userList;
|
||||
},
|
||||
setMyUserInfo: function(info) {
|
||||
//translate the colorId
|
||||
if(typeof info.colorId == "number")
|
||||
{
|
||||
setMyUserInfo(info) {
|
||||
// translate the colorId
|
||||
if (typeof info.colorId === 'number') {
|
||||
info.colorId = clientVars.colorPalette[info.colorId];
|
||||
}
|
||||
|
||||
myUserInfo = $.extend(
|
||||
{}, info);
|
||||
{}, info);
|
||||
|
||||
self.renderMyUserInfo();
|
||||
},
|
||||
userJoinOrUpdate: function(info) {
|
||||
if ((!info.userId) || (info.userId == myUserInfo.userId))
|
||||
{
|
||||
userJoinOrUpdate(info) {
|
||||
if ((!info.userId) || (info.userId == myUserInfo.userId)) {
|
||||
// not sure how this would happen
|
||||
return;
|
||||
}
|
||||
|
||||
hooks.callAll('userJoinOrUpdate', {
|
||||
userInfo: info
|
||||
userInfo: info,
|
||||
});
|
||||
|
||||
var userData = {};
|
||||
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId;
|
||||
const userData = {};
|
||||
userData.color = typeof info.colorId === 'number' ? clientVars.colorPalette[info.colorId] : info.colorId;
|
||||
userData.name = info.name;
|
||||
userData.status = '';
|
||||
userData.activity = '';
|
||||
|
@ -521,38 +456,32 @@ var paduserlist = (function() {
|
|||
// Firefox ignores \n in title text; Safari does a linebreak
|
||||
userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
|
||||
|
||||
var existingIndex = findExistingIndex(info.userId);
|
||||
const existingIndex = findExistingIndex(info.userId);
|
||||
|
||||
var numUsersBesides = otherUsersInfo.length;
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
let numUsersBesides = otherUsersInfo.length;
|
||||
if (existingIndex >= 0) {
|
||||
numUsersBesides--;
|
||||
}
|
||||
var newIndex = padutils.binarySearch(numUsersBesides, function(n) {
|
||||
if (existingIndex >= 0 && n >= existingIndex)
|
||||
{
|
||||
const newIndex = padutils.binarySearch(numUsersBesides, (n) => {
|
||||
if (existingIndex >= 0 && n >= existingIndex) {
|
||||
// pretend existingIndex isn't there
|
||||
n++;
|
||||
}
|
||||
var infoN = otherUsersInfo[n];
|
||||
var nameN = (infoN.name || '').toLowerCase();
|
||||
var nameThis = (info.name || '').toLowerCase();
|
||||
var idN = infoN.userId;
|
||||
var idThis = info.userId;
|
||||
const infoN = otherUsersInfo[n];
|
||||
const nameN = (infoN.name || '').toLowerCase();
|
||||
const nameThis = (info.name || '').toLowerCase();
|
||||
const idN = infoN.userId;
|
||||
const idThis = info.userId;
|
||||
return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
|
||||
});
|
||||
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
if (existingIndex >= 0) {
|
||||
// update
|
||||
if (existingIndex == newIndex)
|
||||
{
|
||||
if (existingIndex == newIndex) {
|
||||
otherUsersInfo[existingIndex] = info;
|
||||
otherUsersData[existingIndex] = userData;
|
||||
rowManager.updateRow(existingIndex, userData);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
otherUsersInfo.splice(existingIndex, 1);
|
||||
otherUsersData.splice(existingIndex, 1);
|
||||
otherUsersInfo.splice(newIndex, 0, info);
|
||||
|
@ -560,9 +489,7 @@ var paduserlist = (function() {
|
|||
rowManager.updateRow(existingIndex, userData);
|
||||
rowManager.moveRow(existingIndex, newIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
otherUsersInfo.splice(newIndex, 0, info);
|
||||
otherUsersData.splice(newIndex, 0, userData);
|
||||
rowManager.insertRow(newIndex, userData);
|
||||
|
@ -570,12 +497,10 @@ var paduserlist = (function() {
|
|||
|
||||
self.updateNumberOfOnlineUsers();
|
||||
},
|
||||
updateNumberOfOnlineUsers: function() {
|
||||
var online = 1; // you are always online!
|
||||
for (var i = 0; i < otherUsersData.length; i++)
|
||||
{
|
||||
if (otherUsersData[i].status == "")
|
||||
{
|
||||
updateNumberOfOnlineUsers() {
|
||||
let online = 1; // you are always online!
|
||||
for (let i = 0; i < otherUsersData.length; i++) {
|
||||
if (otherUsersData[i].status == '') {
|
||||
online++;
|
||||
}
|
||||
}
|
||||
|
@ -584,33 +509,29 @@ var paduserlist = (function() {
|
|||
|
||||
return online;
|
||||
},
|
||||
userLeave: function(info) {
|
||||
var existingIndex = findExistingIndex(info.userId);
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
var userData = otherUsersData[existingIndex];
|
||||
userLeave(info) {
|
||||
const existingIndex = findExistingIndex(info.userId);
|
||||
if (existingIndex >= 0) {
|
||||
const userData = otherUsersData[existingIndex];
|
||||
userData.status = 'Disconnected';
|
||||
rowManager.updateRow(existingIndex, userData);
|
||||
if (userData.leaveTimer)
|
||||
{
|
||||
if (userData.leaveTimer) {
|
||||
window.clearTimeout(userData.leaveTimer);
|
||||
}
|
||||
// set up a timer that will only fire if no leaves,
|
||||
// joins, or updates happen for this user in the
|
||||
// next N seconds, to remove the user from the list.
|
||||
var thisUserId = info.userId;
|
||||
var thisLeaveTimer = window.setTimeout(function() {
|
||||
var newExistingIndex = findExistingIndex(thisUserId);
|
||||
if (newExistingIndex >= 0)
|
||||
{
|
||||
var newUserData = otherUsersData[newExistingIndex];
|
||||
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
|
||||
{
|
||||
const thisUserId = info.userId;
|
||||
var thisLeaveTimer = window.setTimeout(() => {
|
||||
const newExistingIndex = findExistingIndex(thisUserId);
|
||||
if (newExistingIndex >= 0) {
|
||||
const newUserData = otherUsersData[newExistingIndex];
|
||||
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) {
|
||||
otherUsersInfo.splice(newExistingIndex, 1);
|
||||
otherUsersData.splice(newExistingIndex, 1);
|
||||
rowManager.removeRow(newExistingIndex);
|
||||
hooks.callAll('userLeave', {
|
||||
userInfo: info
|
||||
userInfo: info,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -620,163 +541,143 @@ var paduserlist = (function() {
|
|||
|
||||
self.updateNumberOfOnlineUsers();
|
||||
},
|
||||
showGuestPrompt: function(userId, displayName) {
|
||||
if (knocksToIgnore[userId])
|
||||
{
|
||||
showGuestPrompt(userId, displayName) {
|
||||
if (knocksToIgnore[userId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var encodedUserId = padutils.encodeUserId(userId);
|
||||
const encodedUserId = padutils.encodeUserId(userId);
|
||||
|
||||
var actionName = 'hide-guest-prompt-' + encodedUserId;
|
||||
const actionName = `hide-guest-prompt-${encodedUserId}`;
|
||||
padutils.cancelActions(actionName);
|
||||
|
||||
var box = $("#guestprompt-" + encodedUserId);
|
||||
if (box.length == 0)
|
||||
{
|
||||
let box = $(`#guestprompt-${encodedUserId}`);
|
||||
if (box.length == 0) {
|
||||
// make guest prompt box
|
||||
box = $('<div id="'+padutils.escapeHtml('guestprompt-' + encodedUserId) + '" class="guestprompt"><div class="choices"><a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',false))')+'">'+_('pad.userlist.deny')+'</a> <a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',true))') + '">'+_('pad.userlist.approve')+'</a></div><div class="guestname"><strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName) + '</div></div>');
|
||||
$("#guestprompts").append(box);
|
||||
}
|
||||
else
|
||||
{
|
||||
box = $(`<div id="${padutils.escapeHtml(`guestprompt-${encodedUserId}`)}" class="guestprompt"><div class="choices"><a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},false))`)}">${_('pad.userlist.deny')}</a> <a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},true))`)}">${_('pad.userlist.approve')}</a></div><div class="guestname"><strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}</div></div>`);
|
||||
$('#guestprompts').append(box);
|
||||
} else {
|
||||
// update display name
|
||||
box.find(".guestname").html('<strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName));
|
||||
box.find('.guestname').html(`<strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}`);
|
||||
}
|
||||
var hideLater = padutils.getCancellableAction(actionName, function() {
|
||||
const hideLater = padutils.getCancellableAction(actionName, () => {
|
||||
self.removeGuestPrompt(userId);
|
||||
});
|
||||
window.setTimeout(hideLater, 15000); // time-out with no knock
|
||||
guestPromptFlash.scheduleAnimation();
|
||||
},
|
||||
removeGuestPrompt: function(userId) {
|
||||
var box = $("#guestprompt-" + padutils.encodeUserId(userId));
|
||||
removeGuestPrompt(userId) {
|
||||
const box = $(`#guestprompt-${padutils.encodeUserId(userId)}`);
|
||||
// remove ID now so a new knock by same user gets new, unfaded box
|
||||
box.removeAttr('id').fadeOut("fast", function() {
|
||||
box.removeAttr('id').fadeOut('fast', () => {
|
||||
box.remove();
|
||||
});
|
||||
|
||||
knocksToIgnore[userId] = true;
|
||||
window.setTimeout(function() {
|
||||
window.setTimeout(() => {
|
||||
delete knocksToIgnore[userId];
|
||||
}, 5000);
|
||||
},
|
||||
answerGuestPrompt: function(encodedUserId, approve) {
|
||||
var guestId = padutils.decodeUserId(encodedUserId);
|
||||
answerGuestPrompt(encodedUserId, approve) {
|
||||
const guestId = padutils.decodeUserId(encodedUserId);
|
||||
|
||||
var msg = {
|
||||
const msg = {
|
||||
type: 'guestanswer',
|
||||
authId: pad.getUserId(),
|
||||
guestId: guestId,
|
||||
answer: (approve ? "approved" : "denied")
|
||||
guestId,
|
||||
answer: (approve ? 'approved' : 'denied'),
|
||||
};
|
||||
pad.sendClientMessage(msg);
|
||||
|
||||
self.removeGuestPrompt(guestId);
|
||||
},
|
||||
renderMyUserInfo: function() {
|
||||
if (myUserInfo.name)
|
||||
{
|
||||
$("#myusernameedit").removeClass("editempty").val(myUserInfo.name);
|
||||
renderMyUserInfo() {
|
||||
if (myUserInfo.name) {
|
||||
$('#myusernameedit').removeClass('editempty').val(myUserInfo.name);
|
||||
} else {
|
||||
$('#myusernameedit').attr('placeholder', html10n.get('pad.userlist.entername'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#myusernameedit").attr("placeholder", html10n.get("pad.userlist.entername"));
|
||||
}
|
||||
if (colorPickerOpen)
|
||||
{
|
||||
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
|
||||
if (colorPickerOpen) {
|
||||
$('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
|
||||
} else {
|
||||
$('#myswatchbox').addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
|
||||
}
|
||||
|
||||
$("#myswatch").css({'background-color': myUserInfo.colorId});
|
||||
$('#myswatch').css({'background-color': myUserInfo.colorId});
|
||||
|
||||
if (browser.msie && parseInt(browser.version) <= 8) {
|
||||
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId});
|
||||
$('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`, 'background-color': myUserInfo.colorId});
|
||||
} else {
|
||||
$('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`});
|
||||
}
|
||||
else
|
||||
{
|
||||
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
return self;
|
||||
}());
|
||||
|
||||
function getColorPickerSwatchIndex(jnode) {
|
||||
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
|
||||
return $("#colorpickerswatches li").index(jnode);
|
||||
return $('#colorpickerswatches li').index(jnode);
|
||||
}
|
||||
|
||||
function closeColorPicker(accept) {
|
||||
if (accept)
|
||||
{
|
||||
var newColor = $("#mycolorpickerpreview").css("background-color");
|
||||
var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
if (accept) {
|
||||
var newColor = $('#mycolorpickerpreview').css('background-color');
|
||||
const parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
// parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
|
||||
if (parts) {
|
||||
delete (parts[0]);
|
||||
for (var i = 1; i <= 3; ++i) {
|
||||
parts[i] = parseInt(parts[i]).toString(16);
|
||||
if (parts[i].length == 1) parts[i] = '0' + parts[i];
|
||||
for (let i = 1; i <= 3; ++i) {
|
||||
parts[i] = parseInt(parts[i]).toString(16);
|
||||
if (parts[i].length == 1) parts[i] = `0${parts[i]}`;
|
||||
}
|
||||
var newColor = "#" +parts.join(''); // "0070ff"
|
||||
var newColor = `#${parts.join('')}`; // "0070ff"
|
||||
}
|
||||
myUserInfo.colorId = newColor;
|
||||
pad.notifyChangeColor(newColor);
|
||||
paduserlist.renderMyUserInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
//pad.notifyChangeColor(previousColorId);
|
||||
//paduserlist.renderMyUserInfo();
|
||||
} else {
|
||||
// pad.notifyChangeColor(previousColorId);
|
||||
// paduserlist.renderMyUserInfo();
|
||||
}
|
||||
|
||||
colorPickerOpen = false;
|
||||
$("#mycolorpicker").removeClass('popup-show');
|
||||
$('#mycolorpicker').removeClass('popup-show');
|
||||
}
|
||||
|
||||
function showColorPicker() {
|
||||
previousColorId = myUserInfo.colorId;
|
||||
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId)
|
||||
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId);
|
||||
|
||||
if (!colorPickerOpen)
|
||||
{
|
||||
var palette = pad.getColorPalette();
|
||||
if (!colorPickerOpen) {
|
||||
const palette = pad.getColorPalette();
|
||||
|
||||
if (!colorPickerSetup)
|
||||
{
|
||||
var colorsList = $("#colorpickerswatches")
|
||||
for (var i = 0; i < palette.length; i++)
|
||||
{
|
||||
|
||||
var li = $('<li>', {
|
||||
style: 'background: ' + palette[i] + ';'
|
||||
if (!colorPickerSetup) {
|
||||
const colorsList = $('#colorpickerswatches');
|
||||
for (let i = 0; i < palette.length; i++) {
|
||||
const li = $('<li>', {
|
||||
style: `background: ${palette[i]};`,
|
||||
});
|
||||
|
||||
li.appendTo(colorsList);
|
||||
|
||||
li.bind('click', function(event) {
|
||||
$("#colorpickerswatches li").removeClass('picked');
|
||||
$(event.target).addClass("picked");
|
||||
li.bind('click', (event) => {
|
||||
$('#colorpickerswatches li').removeClass('picked');
|
||||
$(event.target).addClass('picked');
|
||||
|
||||
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked"));
|
||||
const newColorId = getColorPickerSwatchIndex($('#colorpickerswatches .picked'));
|
||||
pad.notifyChangeColor(newColorId);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
colorPickerSetup = true;
|
||||
}
|
||||
|
||||
$("#mycolorpicker").addClass('popup-show')
|
||||
$('#mycolorpicker').addClass('popup-show');
|
||||
colorPickerOpen = true;
|
||||
|
||||
$("#colorpickerswatches li").removeClass('picked');
|
||||
$($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird
|
||||
$('#colorpickerswatches li').removeClass('picked');
|
||||
$($('#colorpickerswatches li')[myUserInfo.colorId]).addClass('picked'); // seems weird
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,129 +20,119 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Security = require('./security');
|
||||
const Security = require('./security');
|
||||
|
||||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
|
||||
*/
|
||||
|
||||
function randomString(len) {
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
len = len || 20
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let randomstring = '';
|
||||
len = len || 20;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
|
||||
var padutils = {
|
||||
escapeHtml: function(x) {
|
||||
escapeHtml(x) {
|
||||
return Security.escapeHTML(String(x));
|
||||
},
|
||||
uniqueId: function() {
|
||||
var pad = require('./pad').pad; // Sidestep circular dependency
|
||||
uniqueId() {
|
||||
const pad = require('./pad').pad; // Sidestep circular dependency
|
||||
function encodeNum(n, width) {
|
||||
// returns string that is exactly 'width' chars, padding with zeros
|
||||
// and taking rightmost digits
|
||||
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
|
||||
}
|
||||
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
|
||||
return [pad.getClientIp(), encodeNum(+new Date(), 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
|
||||
},
|
||||
uaDisplay: function(ua) {
|
||||
var m;
|
||||
uaDisplay(ua) {
|
||||
let m;
|
||||
|
||||
function clean(a) {
|
||||
var maxlen = 16;
|
||||
const maxlen = 16;
|
||||
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
|
||||
if (a.length > maxlen)
|
||||
{
|
||||
if (a.length > maxlen) {
|
||||
a = a.substr(0, maxlen);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function checkver(name) {
|
||||
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
|
||||
if (m && m.length > 1)
|
||||
{
|
||||
const m = ua.match(RegExp(`${name}\\/([\\d\\.]+)`));
|
||||
if (m && m.length > 1) {
|
||||
return clean(name + m[1]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// firefox
|
||||
if (checkver('Firefox'))
|
||||
{
|
||||
if (checkver('Firefox')) {
|
||||
return checkver('Firefox');
|
||||
}
|
||||
|
||||
// misc browsers, including IE
|
||||
m = ua.match(/compatible; ([^;]+);/);
|
||||
if (m && m.length > 1)
|
||||
{
|
||||
if (m && m.length > 1) {
|
||||
return clean(m[1]);
|
||||
}
|
||||
|
||||
// iphone
|
||||
if (ua.match(/\(iPhone;/))
|
||||
{
|
||||
if (ua.match(/\(iPhone;/)) {
|
||||
return 'iPhone';
|
||||
}
|
||||
|
||||
// chrome
|
||||
if (checkver('Chrome'))
|
||||
{
|
||||
if (checkver('Chrome')) {
|
||||
return checkver('Chrome');
|
||||
}
|
||||
|
||||
// safari
|
||||
m = ua.match(/Safari\/[\d\.]+/);
|
||||
if (m)
|
||||
{
|
||||
var v = '?';
|
||||
if (m) {
|
||||
let v = '?';
|
||||
m = ua.match(/Version\/([\d\.]+)/);
|
||||
if (m && m.length > 1)
|
||||
{
|
||||
if (m && m.length > 1) {
|
||||
v = m[1];
|
||||
}
|
||||
return clean('Safari' + v);
|
||||
return clean(`Safari${v}`);
|
||||
}
|
||||
|
||||
// everything else
|
||||
var x = ua.split(' ')[0];
|
||||
const x = ua.split(' ')[0];
|
||||
return clean(x);
|
||||
},
|
||||
// e.g. "Thu Jun 18 2009 13:09"
|
||||
simpleDateTime: function(date) {
|
||||
var d = new Date(+date); // accept either number or date
|
||||
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
|
||||
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
|
||||
var dayOfMonth = d.getDate();
|
||||
var year = d.getFullYear();
|
||||
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
|
||||
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
|
||||
simpleDateTime(date) {
|
||||
const d = new Date(+date); // accept either number or date
|
||||
const dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
|
||||
const month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
|
||||
const dayOfMonth = d.getDate();
|
||||
const year = d.getFullYear();
|
||||
const hourmin = `${d.getHours()}:${(`0${d.getMinutes()}`).slice(-2)}`;
|
||||
return `${dayOfWeek} ${month} ${dayOfMonth} ${year} ${hourmin}`;
|
||||
},
|
||||
findURLs: function(text) {
|
||||
findURLs(text) {
|
||||
// copied from ACE
|
||||
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
|
||||
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
|
||||
const _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
|
||||
const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.source + _REGEX_URLCHAR.source}*(?![:.,;])${_REGEX_URLCHAR.source}`, 'g');
|
||||
|
||||
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
|
||||
|
||||
|
||||
function _findURLs(text) {
|
||||
_REGEX_URL.lastIndex = 0;
|
||||
var urls = null;
|
||||
var execResult;
|
||||
while ((execResult = _REGEX_URL.exec(text)))
|
||||
{
|
||||
let urls = null;
|
||||
let execResult;
|
||||
while ((execResult = _REGEX_URL.exec(text))) {
|
||||
urls = (urls || []);
|
||||
var startIndex = execResult.index;
|
||||
var url = execResult[0];
|
||||
const startIndex = execResult.index;
|
||||
const url = execResult[0];
|
||||
urls.push([startIndex, url]);
|
||||
}
|
||||
|
||||
|
@ -151,24 +141,21 @@ var padutils = {
|
|||
|
||||
return _findURLs(text);
|
||||
},
|
||||
escapeHtmlWithClickableLinks: function(text, target) {
|
||||
var idx = 0;
|
||||
var pieces = [];
|
||||
var urls = padutils.findURLs(text);
|
||||
escapeHtmlWithClickableLinks(text, target) {
|
||||
let idx = 0;
|
||||
const pieces = [];
|
||||
const urls = padutils.findURLs(text);
|
||||
|
||||
function advanceTo(i) {
|
||||
if (i > idx)
|
||||
{
|
||||
if (i > idx) {
|
||||
pieces.push(Security.escapeHTML(text.substring(idx, i)));
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
if (urls)
|
||||
{
|
||||
for (var j = 0; j < urls.length; j++)
|
||||
{
|
||||
var startIndex = urls[j][0];
|
||||
var href = urls[j][1];
|
||||
if (urls) {
|
||||
for (let j = 0; j < urls.length; j++) {
|
||||
const startIndex = urls[j][0];
|
||||
const href = urls[j][1];
|
||||
advanceTo(startIndex);
|
||||
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
|
||||
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
|
||||
|
@ -177,7 +164,7 @@ var padutils = {
|
|||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
|
||||
pieces.push('<a ', (target ? `target="${Security.escapeHTMLAttribute(target)}" ` : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
|
||||
advanceTo(startIndex + href.length);
|
||||
pieces.push('</a>');
|
||||
}
|
||||
|
@ -185,78 +172,66 @@ var padutils = {
|
|||
advanceTo(text.length);
|
||||
return pieces.join('');
|
||||
},
|
||||
bindEnterAndEscape: function(node, onEnter, onEscape) {
|
||||
|
||||
bindEnterAndEscape(node, onEnter, onEscape) {
|
||||
// Use keypress instead of keyup in bindEnterAndEscape
|
||||
// Keyup event is fired on enter in IME (Input Method Editor), But
|
||||
// keypress is not. So, I changed to use keypress instead of keyup.
|
||||
// It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
|
||||
if (onEnter)
|
||||
{
|
||||
node.keypress(function(evt) {
|
||||
if (evt.which == 13)
|
||||
{
|
||||
if (onEnter) {
|
||||
node.keypress((evt) => {
|
||||
if (evt.which == 13) {
|
||||
onEnter(evt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onEscape)
|
||||
{
|
||||
node.keydown(function(evt) {
|
||||
if (evt.which == 27)
|
||||
{
|
||||
if (onEscape) {
|
||||
node.keydown((evt) => {
|
||||
if (evt.which == 27) {
|
||||
onEscape(evt);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
timediff: function(d) {
|
||||
var pad = require('./pad').pad; // Sidestep circular dependency
|
||||
timediff(d) {
|
||||
const pad = require('./pad').pad; // Sidestep circular dependency
|
||||
function format(n, word) {
|
||||
n = Math.round(n);
|
||||
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
|
||||
return (`${n} ${word}${n != 1 ? 's' : ''} ago`);
|
||||
}
|
||||
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
|
||||
if (d < 60)
|
||||
{
|
||||
d = Math.max(0, (+(new Date()) - (+d) - pad.clientTimeOffset) / 1000);
|
||||
if (d < 60) {
|
||||
return format(d, 'second');
|
||||
}
|
||||
d /= 60;
|
||||
if (d < 60)
|
||||
{
|
||||
if (d < 60) {
|
||||
return format(d, 'minute');
|
||||
}
|
||||
d /= 60;
|
||||
if (d < 24)
|
||||
{
|
||||
if (d < 24) {
|
||||
return format(d, 'hour');
|
||||
}
|
||||
d /= 24;
|
||||
return format(d, 'day');
|
||||
},
|
||||
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) {
|
||||
if (stepsAtOnce === undefined)
|
||||
{
|
||||
makeAnimationScheduler(funcToAnimateOneStep, stepTime, stepsAtOnce) {
|
||||
if (stepsAtOnce === undefined) {
|
||||
stepsAtOnce = 1;
|
||||
}
|
||||
|
||||
var animationTimer = null;
|
||||
let animationTimer = null;
|
||||
|
||||
function scheduleAnimation() {
|
||||
if (!animationTimer)
|
||||
{
|
||||
animationTimer = window.setTimeout(function() {
|
||||
if (!animationTimer) {
|
||||
animationTimer = window.setTimeout(() => {
|
||||
animationTimer = null;
|
||||
var n = stepsAtOnce;
|
||||
var moreToDo = true;
|
||||
while (moreToDo && n > 0)
|
||||
{
|
||||
let n = stepsAtOnce;
|
||||
let moreToDo = true;
|
||||
while (moreToDo && n > 0) {
|
||||
moreToDo = funcToAnimateOneStep();
|
||||
n--;
|
||||
}
|
||||
if (moreToDo)
|
||||
{
|
||||
if (moreToDo) {
|
||||
// more to do
|
||||
scheduleAnimation();
|
||||
}
|
||||
|
@ -264,15 +239,15 @@ var padutils = {
|
|||
}
|
||||
}
|
||||
return {
|
||||
scheduleAnimation: scheduleAnimation
|
||||
scheduleAnimation,
|
||||
};
|
||||
},
|
||||
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) {
|
||||
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
|
||||
var animationFrameDelay = 1000 / fps;
|
||||
var animationStep = animationFrameDelay / totalMs;
|
||||
makeShowHideAnimator(funcToArriveAtState, initiallyShown, fps, totalMs) {
|
||||
let animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
|
||||
const animationFrameDelay = 1000 / fps;
|
||||
const animationStep = animationFrameDelay / totalMs;
|
||||
|
||||
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
|
||||
const scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
|
||||
|
||||
function doShow() {
|
||||
animationState = -1;
|
||||
|
@ -281,12 +256,9 @@ var padutils = {
|
|||
}
|
||||
|
||||
function doQuickShow() { // start showing without losing any fade-in progress
|
||||
if (animationState < -1)
|
||||
{
|
||||
if (animationState < -1) {
|
||||
animationState = -1;
|
||||
}
|
||||
else if (animationState > 0)
|
||||
{
|
||||
} else if (animationState > 0) {
|
||||
animationState = Math.max(-1, Math.min(0, -animationState));
|
||||
}
|
||||
funcToArriveAtState(animationState);
|
||||
|
@ -294,47 +266,35 @@ var padutils = {
|
|||
}
|
||||
|
||||
function doHide() {
|
||||
if (animationState >= -1 && animationState <= 0)
|
||||
{
|
||||
if (animationState >= -1 && animationState <= 0) {
|
||||
animationState = 1e-6;
|
||||
scheduleAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
function animateOneStep() {
|
||||
if (animationState < -1 || animationState == 0)
|
||||
{
|
||||
if (animationState < -1 || animationState == 0) {
|
||||
return false;
|
||||
}
|
||||
else if (animationState < 0)
|
||||
{
|
||||
} else if (animationState < 0) {
|
||||
// animate show
|
||||
animationState += animationStep;
|
||||
if (animationState >= 0)
|
||||
{
|
||||
if (animationState >= 0) {
|
||||
animationState = 0;
|
||||
funcToArriveAtState(animationState);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
funcToArriveAtState(animationState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (animationState > 0)
|
||||
{
|
||||
} else if (animationState > 0) {
|
||||
// animate hide
|
||||
animationState += animationStep;
|
||||
if (animationState >= 1)
|
||||
{
|
||||
if (animationState >= 1) {
|
||||
animationState = 1;
|
||||
funcToArriveAtState(animationState);
|
||||
animationState = -2;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
funcToArriveAtState(animationState);
|
||||
return true;
|
||||
}
|
||||
|
@ -344,95 +304,83 @@ var padutils = {
|
|||
return {
|
||||
show: doShow,
|
||||
hide: doHide,
|
||||
quickShow: doQuickShow
|
||||
quickShow: doQuickShow,
|
||||
};
|
||||
},
|
||||
_nextActionId: 1,
|
||||
uncanceledActions: {},
|
||||
getCancellableAction: function(actionType, actionFunc) {
|
||||
var o = padutils.uncanceledActions[actionType];
|
||||
if (!o)
|
||||
{
|
||||
getCancellableAction(actionType, actionFunc) {
|
||||
let o = padutils.uncanceledActions[actionType];
|
||||
if (!o) {
|
||||
o = {};
|
||||
padutils.uncanceledActions[actionType] = o;
|
||||
}
|
||||
var actionId = (padutils._nextActionId++);
|
||||
const actionId = (padutils._nextActionId++);
|
||||
o[actionId] = true;
|
||||
return function() {
|
||||
var p = padutils.uncanceledActions[actionType];
|
||||
if (p && p[actionId])
|
||||
{
|
||||
return function () {
|
||||
const p = padutils.uncanceledActions[actionType];
|
||||
if (p && p[actionId]) {
|
||||
actionFunc();
|
||||
}
|
||||
};
|
||||
},
|
||||
cancelActions: function(actionType) {
|
||||
var o = padutils.uncanceledActions[actionType];
|
||||
if (o)
|
||||
{
|
||||
cancelActions(actionType) {
|
||||
const o = padutils.uncanceledActions[actionType];
|
||||
if (o) {
|
||||
// clear it
|
||||
delete padutils.uncanceledActions[actionType];
|
||||
}
|
||||
},
|
||||
makeFieldLabeledWhenEmpty: function(field, labelText) {
|
||||
makeFieldLabeledWhenEmpty(field, labelText) {
|
||||
field = $(field);
|
||||
|
||||
function clear() {
|
||||
field.addClass('editempty');
|
||||
field.val(labelText);
|
||||
}
|
||||
field.focus(function() {
|
||||
if (field.hasClass('editempty'))
|
||||
{
|
||||
field.focus(() => {
|
||||
if (field.hasClass('editempty')) {
|
||||
field.val('');
|
||||
}
|
||||
field.removeClass('editempty');
|
||||
});
|
||||
field.blur(function() {
|
||||
if (!field.val())
|
||||
{
|
||||
field.blur(() => {
|
||||
if (!field.val()) {
|
||||
clear();
|
||||
}
|
||||
});
|
||||
return {
|
||||
clear: clear
|
||||
clear,
|
||||
};
|
||||
},
|
||||
getCheckbox: function(node) {
|
||||
getCheckbox(node) {
|
||||
return $(node).is(':checked');
|
||||
},
|
||||
setCheckbox: function(node, value) {
|
||||
if (value)
|
||||
{
|
||||
setCheckbox(node, value) {
|
||||
if (value) {
|
||||
$(node).attr('checked', 'checked');
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$(node).removeAttr('checked');
|
||||
}
|
||||
},
|
||||
bindCheckboxChange: function(node, func) {
|
||||
bindCheckboxChange(node, func) {
|
||||
$(node).change(func);
|
||||
},
|
||||
encodeUserId: function(userId) {
|
||||
return userId.replace(/[^a-y0-9]/g, function(c) {
|
||||
if (c == ".") return "-";
|
||||
return 'z' + c.charCodeAt(0) + 'z';
|
||||
encodeUserId(userId) {
|
||||
return userId.replace(/[^a-y0-9]/g, (c) => {
|
||||
if (c == '.') return '-';
|
||||
return `z${c.charCodeAt(0)}z`;
|
||||
});
|
||||
},
|
||||
decodeUserId: function(encodedUserId) {
|
||||
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) {
|
||||
if (cc == '-') return '.';
|
||||
else if (cc.charAt(0) == 'z')
|
||||
{
|
||||
decodeUserId(encodedUserId) {
|
||||
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, (cc) => {
|
||||
if (cc == '-') { return '.'; } else if (cc.charAt(0) == 'z') {
|
||||
return String.fromCharCode(Number(cc.slice(1, -1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return cc;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let globalExceptionHandler = null;
|
||||
|
@ -453,12 +401,12 @@ padutils.setupGlobalExceptionHandler = () => {
|
|||
} else {
|
||||
throw new Error(`unknown event: ${e.toString()}`);
|
||||
}
|
||||
var errorId = randomString(20);
|
||||
const errorId = randomString(20);
|
||||
|
||||
var msgAlreadyVisible = false;
|
||||
$('.gritter-item .error-msg').each(function() {
|
||||
let msgAlreadyVisible = false;
|
||||
$('.gritter-item .error-msg').each(function () {
|
||||
if ($(this).text() === msg) {
|
||||
msgAlreadyVisible = true;
|
||||
msgAlreadyVisible = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -479,9 +427,9 @@ padutils.setupGlobalExceptionHandler = () => {
|
|||
];
|
||||
|
||||
$.gritter.add({
|
||||
title: "An error occurred",
|
||||
title: 'An error occurred',
|
||||
text: errorMsg,
|
||||
class_name: "error",
|
||||
class_name: 'error',
|
||||
position: 'bottom',
|
||||
sticky: true,
|
||||
});
|
||||
|
@ -505,7 +453,7 @@ padutils.setupGlobalExceptionHandler = () => {
|
|||
window.addEventListener('error', globalExceptionHandler);
|
||||
window.addEventListener('unhandledrejection', globalExceptionHandler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
padutils.binarySearch = require('./ace2_common').binarySearch;
|
||||
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
var $, jQuery;
|
||||
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
|
||||
var _ = require("underscore");
|
||||
let $, jQuery;
|
||||
$ = jQuery = require('ep_etherpad-lite/static/js/rjquery').$;
|
||||
const _ = require('underscore');
|
||||
|
||||
var pluginUtils = require('./shared');
|
||||
var defs = require('./plugin_defs');
|
||||
const pluginUtils = require('./shared');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
exports.baseURL = '';
|
||||
|
||||
exports.ensure = function (cb) {
|
||||
if (!defs.loaded)
|
||||
exports.update(cb);
|
||||
else
|
||||
cb();
|
||||
if (!defs.loaded) exports.update(cb);
|
||||
else cb();
|
||||
};
|
||||
|
||||
exports.update = function (cb) {
|
||||
// It appears that this response (see #620) may interrupt the current thread
|
||||
// of execution on Firefox. This schedules the response in the run-loop,
|
||||
// which appears to fix the issue.
|
||||
var callback = function () {setTimeout(cb, 0);};
|
||||
$.ajaxSetup({ cache: false });
|
||||
jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json').done(function(data) {
|
||||
const callback = function () { setTimeout(cb, 0); };
|
||||
$.ajaxSetup({cache: false});
|
||||
jQuery.getJSON(`${exports.baseURL}pluginfw/plugin-definitions.json`).done((data) => {
|
||||
defs.plugins = data.plugins;
|
||||
defs.parts = data.parts;
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, "client_hooks");
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks');
|
||||
defs.loaded = true;
|
||||
callback();
|
||||
}).fail(function(e){
|
||||
console.error("Failed to load plugin-definitions: " + err);
|
||||
}).fail((e) => {
|
||||
console.error(`Failed to load plugin-definitions: ${err}`);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function adoptPluginsFromAncestorsOf(frame) {
|
||||
// Bind plugins with parent;
|
||||
var parentRequire = null;
|
||||
let parentRequire = null;
|
||||
try {
|
||||
while (frame = frame.parent) {
|
||||
if (typeof (frame.require) !== "undefined") {
|
||||
if (typeof (frame.require) !== 'undefined') {
|
||||
parentRequire = frame.require;
|
||||
break;
|
||||
}
|
||||
|
@ -46,17 +44,17 @@ function adoptPluginsFromAncestorsOf(frame) {
|
|||
// Silence (this can only be a XDomain issue).
|
||||
}
|
||||
if (parentRequire) {
|
||||
var ancestorPluginDefs = parentRequire("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
|
||||
const ancestorPluginDefs = parentRequire('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
defs.hooks = ancestorPluginDefs.hooks;
|
||||
defs.loaded = ancestorPluginDefs.loaded;
|
||||
defs.parts = ancestorPluginDefs.parts;
|
||||
defs.plugins = ancestorPluginDefs.plugins;
|
||||
var ancestorPlugins = parentRequire("ep_etherpad-lite/static/js/pluginfw/client_plugins");
|
||||
const ancestorPlugins = parentRequire('ep_etherpad-lite/static/js/pluginfw/client_plugins');
|
||||
exports.baseURL = ancestorPlugins.baseURL;
|
||||
exports.ensure = ancestorPlugins.ensure;
|
||||
exports.update = ancestorPlugins.update;
|
||||
} else {
|
||||
throw new Error("Parent plugins could not be found.")
|
||||
throw new Error('Parent plugins could not be found.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global exports, require */
|
||||
|
||||
var _ = require("underscore");
|
||||
var pluginDefs = require('./plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const pluginDefs = require('./plugin_defs');
|
||||
|
||||
// Maps the name of a server-side hook to a string explaining the deprecation
|
||||
// (e.g., 'use the foo hook instead').
|
||||
|
@ -24,26 +24,24 @@ function checkDeprecation(hook) {
|
|||
deprecationWarned[hook.hook_fn_name] = true;
|
||||
}
|
||||
|
||||
exports.bubbleExceptions = true
|
||||
exports.bubbleExceptions = true;
|
||||
|
||||
var hookCallWrapper = function (hook, hook_name, args, cb) {
|
||||
const hookCallWrapper = function (hook, hook_name, args, cb) {
|
||||
if (cb === undefined) cb = function (x) { return x; };
|
||||
|
||||
checkDeprecation(hook);
|
||||
|
||||
// Normalize output to list for both sync and async cases
|
||||
var normalize = function(x) {
|
||||
const normalize = function (x) {
|
||||
if (x === undefined) return [];
|
||||
return x;
|
||||
}
|
||||
var normalizedhook = function () {
|
||||
return normalize(hook.hook_fn(hook_name, args, function (x) {
|
||||
return cb(normalize(x));
|
||||
}));
|
||||
}
|
||||
};
|
||||
const normalizedhook = function () {
|
||||
return normalize(hook.hook_fn(hook_name, args, (x) => cb(normalize(x))));
|
||||
};
|
||||
|
||||
if (exports.bubbleExceptions) {
|
||||
return normalizedhook();
|
||||
return normalizedhook();
|
||||
} else {
|
||||
try {
|
||||
return normalizedhook();
|
||||
|
@ -51,32 +49,32 @@ var hookCallWrapper = function (hook, hook_name, args, cb) {
|
|||
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.syncMapFirst = function (lst, fn) {
|
||||
var i;
|
||||
var result;
|
||||
let i;
|
||||
let result;
|
||||
for (i = 0; i < lst.length; i++) {
|
||||
result = fn(lst[i])
|
||||
result = fn(lst[i]);
|
||||
if (result.length) return result;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.mapFirst = function (lst, fn, cb, predicate) {
|
||||
if (predicate == null) predicate = (x) => (x != null && x.length > 0);
|
||||
var i = 0;
|
||||
let i = 0;
|
||||
|
||||
var next = function () {
|
||||
if (i >= lst.length) return cb(null, []);
|
||||
fn(lst[i++], function (err, result) {
|
||||
fn(lst[i++], (err, result) => {
|
||||
if (err) return cb(err);
|
||||
if (predicate(result)) return cb(null, result);
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
// Calls the hook function synchronously and returns the value provided by the hook function (via
|
||||
// callback or return value).
|
||||
|
@ -350,11 +348,9 @@ async function callHookFnAsync(hook, context) {
|
|||
exports.aCallAll = async (hookName, context, cb) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
let resultsPromise = Promise.all(hooks.map((hook) => {
|
||||
return callHookFnAsync(hook, context)
|
||||
// `undefined` (but not `null`!) is treated the same as [].
|
||||
.then((result) => (result === undefined) ? [] : result);
|
||||
})).then((results) => _.flatten(results, 1));
|
||||
let resultsPromise = Promise.all(hooks.map((hook) => callHookFnAsync(hook, context)
|
||||
// `undefined` (but not `null`!) is treated the same as [].
|
||||
.then((result) => (result === undefined) ? [] : result))).then((results) => _.flatten(results, 1));
|
||||
if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb);
|
||||
return await resultsPromise;
|
||||
};
|
||||
|
@ -362,49 +358,45 @@ exports.aCallAll = async (hookName, context, cb) => {
|
|||
exports.callFirst = function (hook_name, args) {
|
||||
if (!args) args = {};
|
||||
if (pluginDefs.hooks[hook_name] === undefined) return [];
|
||||
return exports.syncMapFirst(pluginDefs.hooks[hook_name], function(hook) {
|
||||
return hookCallWrapper(hook, hook_name, args);
|
||||
});
|
||||
}
|
||||
return exports.syncMapFirst(pluginDefs.hooks[hook_name], (hook) => hookCallWrapper(hook, hook_name, args));
|
||||
};
|
||||
|
||||
function aCallFirst(hook_name, args, cb, predicate) {
|
||||
if (!args) args = {};
|
||||
if (!cb) cb = function () {};
|
||||
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
|
||||
exports.mapFirst(
|
||||
pluginDefs.hooks[hook_name],
|
||||
function (hook, cb) {
|
||||
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
|
||||
},
|
||||
cb,
|
||||
predicate
|
||||
pluginDefs.hooks[hook_name],
|
||||
(hook, cb) => {
|
||||
hookCallWrapper(hook, hook_name, args, (res) => { cb(null, res); });
|
||||
},
|
||||
cb,
|
||||
predicate,
|
||||
);
|
||||
}
|
||||
|
||||
/* return a Promise if cb is not supplied */
|
||||
exports.aCallFirst = function (hook_name, args, cb, predicate) {
|
||||
if (cb === undefined) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
aCallFirst(hook_name, args, function(err, res) {
|
||||
return err ? reject(err) : resolve(res);
|
||||
}, predicate);
|
||||
return new Promise((resolve, reject) => {
|
||||
aCallFirst(hook_name, args, (err, res) => err ? reject(err) : resolve(res), predicate);
|
||||
});
|
||||
} else {
|
||||
return aCallFirst(hook_name, args, cb, predicate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.callAllStr = function(hook_name, args, sep, pre, post) {
|
||||
exports.callAllStr = function (hook_name, args, sep, pre, post) {
|
||||
if (sep == undefined) sep = '';
|
||||
if (pre == undefined) pre = '';
|
||||
if (post == undefined) post = '';
|
||||
var newCallhooks = [];
|
||||
var callhooks = exports.callAll(hook_name, args);
|
||||
for (var i = 0, ii = callhooks.length; i < ii; i++) {
|
||||
const newCallhooks = [];
|
||||
const callhooks = exports.callAll(hook_name, args);
|
||||
for (let i = 0, ii = callhooks.length; i < ii; i++) {
|
||||
newCallhooks[i] = pre + callhooks[i] + post;
|
||||
}
|
||||
return newCallhooks.join(sep || "");
|
||||
}
|
||||
return newCallhooks.join(sep || '');
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
callHookFnAsync,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const log4js = require('log4js');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var npm = require("npm");
|
||||
var request = require("request");
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const npm = require('npm');
|
||||
const request = require('request');
|
||||
const util = require('util');
|
||||
|
||||
let npmIsLoaded = false;
|
||||
|
@ -13,20 +13,20 @@ const loadNpm = async () => {
|
|||
npm.on('log', log4js.getLogger('npm').log);
|
||||
};
|
||||
|
||||
var tasks = 0
|
||||
let tasks = 0;
|
||||
|
||||
function wrapTaskCb(cb) {
|
||||
tasks++;
|
||||
|
||||
return function() {
|
||||
return function () {
|
||||
cb && cb.apply(this, arguments);
|
||||
tasks--;
|
||||
if (tasks == 0) onAllTasksFinished();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onAllTasksFinished() {
|
||||
hooks.aCallAll("restartServer", {}, function() {});
|
||||
hooks.aCallAll('restartServer', {}, () => {});
|
||||
}
|
||||
|
||||
exports.uninstall = async (pluginName, cb = null) => {
|
||||
|
@ -58,18 +58,18 @@ exports.install = async (pluginName, cb = null) => {
|
|||
};
|
||||
|
||||
exports.availablePlugins = null;
|
||||
var cacheTimestamp = 0;
|
||||
let cacheTimestamp = 0;
|
||||
|
||||
exports.getAvailablePlugins = function(maxCacheAge) {
|
||||
var nowTimestamp = Math.round(Date.now() / 1000);
|
||||
exports.getAvailablePlugins = function (maxCacheAge) {
|
||||
const nowTimestamp = Math.round(Date.now() / 1000);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// check cache age before making any request
|
||||
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
|
||||
return resolve(exports.availablePlugins);
|
||||
}
|
||||
|
||||
request("https://static.etherpad.org/plugins.json", function(er, response, plugins) {
|
||||
request('https://static.etherpad.org/plugins.json', (er, response, plugins) => {
|
||||
if (er) return reject(er);
|
||||
|
||||
try {
|
||||
|
@ -87,26 +87,26 @@ exports.getAvailablePlugins = function(maxCacheAge) {
|
|||
};
|
||||
|
||||
|
||||
exports.search = function(searchTerm, maxCacheAge) {
|
||||
return exports.getAvailablePlugins(maxCacheAge).then(function(results) {
|
||||
var res = {};
|
||||
exports.search = function (searchTerm, maxCacheAge) {
|
||||
return exports.getAvailablePlugins(maxCacheAge).then((results) => {
|
||||
const res = {};
|
||||
|
||||
if (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
}
|
||||
|
||||
for (var pluginName in results) {
|
||||
for (const pluginName in results) {
|
||||
// for every available plugin
|
||||
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
|
||||
|
||||
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm)
|
||||
&& (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) )
|
||||
) {
|
||||
if (typeof results[pluginName].description === "undefined") {
|
||||
console.debug('plugin without Description: %s', results[pluginName].name);
|
||||
}
|
||||
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) &&
|
||||
(typeof results[pluginName].description !== 'undefined' && !~results[pluginName].description.toLowerCase().indexOf(searchTerm))
|
||||
) {
|
||||
if (typeof results[pluginName].description === 'undefined') {
|
||||
console.debug('plugin without Description: %s', results[pluginName].name);
|
||||
}
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
res[pluginName] = results[pluginName];
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
const fs = require('fs').promises;
|
||||
const hooks = require('./hooks');
|
||||
var npm = require("npm/lib/npm.js");
|
||||
var readInstalled = require("./read-installed.js");
|
||||
var path = require("path");
|
||||
var tsort = require("./tsort");
|
||||
var util = require("util");
|
||||
var _ = require("underscore");
|
||||
var settings = require('../../../node/utils/Settings');
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const readInstalled = require('./read-installed.js');
|
||||
const path = require('path');
|
||||
const tsort = require('./tsort');
|
||||
const util = require('util');
|
||||
const _ = require('underscore');
|
||||
const settings = require('../../../node/utils/Settings');
|
||||
|
||||
var pluginUtils = require('./shared');
|
||||
var defs = require('./plugin_defs');
|
||||
const pluginUtils = require('./shared');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
exports.prefix = 'ep_';
|
||||
|
||||
exports.formatPlugins = function () {
|
||||
return _.keys(defs.plugins).join(", ");
|
||||
return _.keys(defs.plugins).join(', ');
|
||||
};
|
||||
|
||||
exports.formatPluginsWithVersion = function () {
|
||||
var plugins = [];
|
||||
_.forEach(defs.plugins, function(plugin) {
|
||||
if(plugin.package.name !== "ep_etherpad-lite"){
|
||||
var pluginStr = plugin.package.name + "@" + plugin.package.version;
|
||||
const plugins = [];
|
||||
_.forEach(defs.plugins, (plugin) => {
|
||||
if (plugin.package.name !== 'ep_etherpad-lite') {
|
||||
const pluginStr = `${plugin.package.name}@${plugin.package.version}`;
|
||||
plugins.push(pluginStr);
|
||||
}
|
||||
});
|
||||
return plugins.join(", ");
|
||||
return plugins.join(', ');
|
||||
};
|
||||
|
||||
exports.formatParts = function () {
|
||||
return _.map(defs.parts, function(part) { return part.full_name; }).join('\n');
|
||||
return _.map(defs.parts, (part) => part.full_name).join('\n');
|
||||
};
|
||||
|
||||
exports.formatHooks = function (hook_set_name) {
|
||||
var res = [];
|
||||
var hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
|
||||
const res = [];
|
||||
const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
|
||||
|
||||
_.chain(hooks).keys().forEach(function (hook_name) {
|
||||
_.forEach(hooks[hook_name], function (hook) {
|
||||
res.push("<dt>" + hook.hook_name + "</dt><dd>" + hook.hook_fn_name + " from " + hook.part.full_name + "</dd>");
|
||||
_.chain(hooks).keys().forEach((hook_name) => {
|
||||
_.forEach(hooks[hook_name], (hook) => {
|
||||
res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} from ${hook.part.full_name}</dd>`);
|
||||
});
|
||||
});
|
||||
return "<dl>" + res.join("\n") + "</dl>";
|
||||
return `<dl>${res.join('\n')}</dl>`;
|
||||
};
|
||||
|
||||
const callInit = async () => {
|
||||
await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => {
|
||||
let plugin = defs.plugins[plugin_name];
|
||||
let ep_init = path.normalize(path.join(plugin.package.path, ".ep_initialized"));
|
||||
const plugin = defs.plugins[plugin_name];
|
||||
const ep_init = path.normalize(path.join(plugin.package.path, '.ep_initialized'));
|
||||
try {
|
||||
await fs.stat(ep_init);
|
||||
} catch (err) {
|
||||
await fs.writeFile(ep_init, 'done');
|
||||
await hooks.aCallAll("init_" + plugin_name, {});
|
||||
await hooks.aCallAll(`init_${plugin_name}`, {});
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
||||
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
|
||||
|
@ -65,32 +65,32 @@ exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
|||
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
|
||||
const fileName = path.normalize(path.join(packageDir, moduleName));
|
||||
return `${fileName}:${functionName}`;
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = async function () {
|
||||
let packages = await exports.getPackages();
|
||||
var parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
var plugins = {};
|
||||
const packages = await exports.getPackages();
|
||||
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
const plugins = {};
|
||||
|
||||
// Load plugin metadata ep.json
|
||||
await Promise.all(Object.keys(packages).map(
|
||||
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
|
||||
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
|
||||
|
||||
defs.plugins = plugins;
|
||||
defs.parts = sortParts(parts);
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
|
||||
defs.loaded = true;
|
||||
await callInit();
|
||||
}
|
||||
};
|
||||
|
||||
exports.getPackages = async function () {
|
||||
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
|
||||
var dir = settings.root;
|
||||
let data = await util.promisify(readInstalled)(dir);
|
||||
const dir = settings.root;
|
||||
const data = await util.promisify(readInstalled)(dir);
|
||||
|
||||
var packages = {};
|
||||
const packages = {};
|
||||
function flatten(deps) {
|
||||
_.chain(deps).keys().each(function (name) {
|
||||
_.chain(deps).keys().each((name) => {
|
||||
if (name.indexOf(exports.prefix) === 0) {
|
||||
packages[name] = _.clone(deps[name]);
|
||||
// Delete anything that creates loops so that the plugin
|
||||
|
@ -100,48 +100,48 @@ exports.getPackages = async function () {
|
|||
}
|
||||
|
||||
// I don't think we need recursion
|
||||
//if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
|
||||
// if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
|
||||
});
|
||||
}
|
||||
|
||||
var tmp = {};
|
||||
const tmp = {};
|
||||
tmp[data.name] = data;
|
||||
flatten(tmp[data.name].dependencies);
|
||||
return packages;
|
||||
};
|
||||
|
||||
async function loadPlugin(packages, plugin_name, plugins, parts) {
|
||||
var plugin_path = path.resolve(packages[plugin_name].path, "ep.json");
|
||||
const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
|
||||
try {
|
||||
let data = await fs.readFile(plugin_path);
|
||||
const data = await fs.readFile(plugin_path);
|
||||
try {
|
||||
var plugin = JSON.parse(data);
|
||||
plugin['package'] = packages[plugin_name];
|
||||
const plugin = JSON.parse(data);
|
||||
plugin.package = packages[plugin_name];
|
||||
plugins[plugin_name] = plugin;
|
||||
_.each(plugin.parts, function (part) {
|
||||
_.each(plugin.parts, (part) => {
|
||||
part.plugin = plugin_name;
|
||||
part.full_name = plugin_name + "/" + part.name;
|
||||
part.full_name = `${plugin_name}/${part.name}`;
|
||||
parts[part.full_name] = part;
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString());
|
||||
console.error(`Unable to parse plugin definition file ${plugin_path}: ${ex.toString()}`);
|
||||
}
|
||||
} catch (er) {
|
||||
console.error("Unable to load plugin definition file " + plugin_path);
|
||||
console.error(`Unable to load plugin definition file ${plugin_path}`);
|
||||
}
|
||||
}
|
||||
|
||||
function partsToParentChildList(parts) {
|
||||
var res = [];
|
||||
_.chain(parts).keys().forEach(function (name) {
|
||||
_.each(parts[name].post || [], function (child_name) {
|
||||
const res = [];
|
||||
_.chain(parts).keys().forEach((name) => {
|
||||
_.each(parts[name].post || [], (child_name) => {
|
||||
res.push([name, child_name]);
|
||||
});
|
||||
_.each(parts[name].pre || [], function (parent_name) {
|
||||
_.each(parts[name].pre || [], (parent_name) => {
|
||||
res.push([parent_name, name]);
|
||||
});
|
||||
if (!parts[name].pre && !parts[name].post) {
|
||||
res.push([name, ":" + name]); // Include apps with no dependency info
|
||||
res.push([name, `:${name}`]); // Include apps with no dependency info
|
||||
}
|
||||
});
|
||||
return res;
|
||||
|
@ -150,10 +150,10 @@ function partsToParentChildList(parts) {
|
|||
// Used only in Node, so no need for _
|
||||
function sortParts(parts) {
|
||||
return tsort(
|
||||
partsToParentChildList(parts)
|
||||
partsToParentChildList(parts),
|
||||
).filter(
|
||||
function (name) { return parts[name] !== undefined; }
|
||||
(name) => parts[name] !== undefined,
|
||||
).map(
|
||||
function (name) { return parts[name]; }
|
||||
(name) => parts[name],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -89,248 +89,251 @@ as far as the left-most node_modules folder.
|
|||
*/
|
||||
|
||||
|
||||
var npm = require("npm/lib/npm.js")
|
||||
, fs = require("graceful-fs")
|
||||
, path = require("path")
|
||||
, asyncMap = require("slide").asyncMap
|
||||
, semver = require("semver")
|
||||
, log = require("log4js").getLogger('pluginfw')
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const fs = require('graceful-fs');
|
||||
const path = require('path');
|
||||
const asyncMap = require('slide').asyncMap;
|
||||
const semver = require('semver');
|
||||
const log = require('log4js').getLogger('pluginfw');
|
||||
|
||||
function readJson(file, callback) {
|
||||
fs.readFile(file, function(er, buf) {
|
||||
if(er) {
|
||||
fs.readFile(file, (er, buf) => {
|
||||
if (er) {
|
||||
callback(er);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback( null, JSON.parse(buf.toString()) )
|
||||
} catch(er) {
|
||||
callback(er)
|
||||
callback(null, JSON.parse(buf.toString()));
|
||||
} catch (er) {
|
||||
callback(er);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = readInstalled
|
||||
module.exports = readInstalled;
|
||||
|
||||
function readInstalled (folder, cb) {
|
||||
function readInstalled(folder, cb) {
|
||||
/* This is where we clear the cache, these three lines are all the
|
||||
* new code there is */
|
||||
rpSeen = {};
|
||||
riSeen = [];
|
||||
var fuSeen = [];
|
||||
const fuSeen = [];
|
||||
|
||||
var d = npm.config.get("depth")
|
||||
readInstalled_(folder, null, null, null, 0, d, function (er, obj) {
|
||||
if (er) return cb(er)
|
||||
const d = npm.config.get('depth');
|
||||
readInstalled_(folder, null, null, null, 0, d, (er, obj) => {
|
||||
if (er) return cb(er);
|
||||
// now obj has all the installed things, where they're installed
|
||||
// figure out the inheritance links, now that the object is built.
|
||||
resolveInheritance(obj)
|
||||
cb(null, obj)
|
||||
})
|
||||
resolveInheritance(obj);
|
||||
cb(null, obj);
|
||||
});
|
||||
}
|
||||
|
||||
var rpSeen = {}
|
||||
function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
|
||||
//console.error(folder, name)
|
||||
var rpSeen = {};
|
||||
function readInstalled_(folder, parent, name, reqver, depth, maxDepth, cb) {
|
||||
// console.error(folder, name)
|
||||
|
||||
var installed
|
||||
, obj
|
||||
, real
|
||||
, link
|
||||
let installed,
|
||||
obj,
|
||||
real,
|
||||
link;
|
||||
|
||||
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
|
||||
fs.readdir(path.resolve(folder, 'node_modules'), (er, i) => {
|
||||
// error indicates that nothing is installed here
|
||||
if (er) i = []
|
||||
installed = i.filter(function (f) { return f.charAt(0) !== "." })
|
||||
next()
|
||||
})
|
||||
if (er) i = [];
|
||||
installed = i.filter((f) => f.charAt(0) !== '.');
|
||||
next();
|
||||
});
|
||||
|
||||
readJson(path.resolve(folder, "package.json"), function (er, data) {
|
||||
obj = copy(data)
|
||||
readJson(path.resolve(folder, 'package.json'), (er, data) => {
|
||||
obj = copy(data);
|
||||
|
||||
if (!parent) {
|
||||
obj = obj || true
|
||||
er = null
|
||||
obj = obj || true;
|
||||
er = null;
|
||||
}
|
||||
return next(er)
|
||||
})
|
||||
return next(er);
|
||||
});
|
||||
|
||||
fs.lstat(folder, function (er, st) {
|
||||
fs.lstat(folder, (er, st) => {
|
||||
if (er) {
|
||||
if (!parent) real = true
|
||||
return next(er)
|
||||
if (!parent) real = true;
|
||||
return next(er);
|
||||
}
|
||||
fs.realpath(folder, function (er, rp) {
|
||||
//console.error("realpath(%j) = %j", folder, rp)
|
||||
real = rp
|
||||
if (st.isSymbolicLink()) link = rp
|
||||
next(er)
|
||||
})
|
||||
})
|
||||
fs.realpath(folder, (er, rp) => {
|
||||
// console.error("realpath(%j) = %j", folder, rp)
|
||||
real = rp;
|
||||
if (st.isSymbolicLink()) link = rp;
|
||||
next(er);
|
||||
});
|
||||
});
|
||||
|
||||
var errState = null
|
||||
, called = false
|
||||
function next (er) {
|
||||
if (errState) return
|
||||
let errState = null;
|
||||
let called = false;
|
||||
function next(er) {
|
||||
if (errState) return;
|
||||
if (er) {
|
||||
errState = er
|
||||
return cb(null, [])
|
||||
errState = er;
|
||||
return cb(null, []);
|
||||
}
|
||||
//console.error('next', installed, obj && typeof obj, name, real)
|
||||
if (!installed || !obj || !real || called) return
|
||||
called = true
|
||||
if (rpSeen[real]) return cb(null, rpSeen[real])
|
||||
// console.error('next', installed, obj && typeof obj, name, real)
|
||||
if (!installed || !obj || !real || called) return;
|
||||
called = true;
|
||||
if (rpSeen[real]) return cb(null, rpSeen[real]);
|
||||
if (obj === true) {
|
||||
obj = {dependencies:{}, path:folder}
|
||||
installed.forEach(function (i) { obj.dependencies[i] = "*" })
|
||||
obj = {dependencies: {}, path: folder};
|
||||
installed.forEach((i) => { obj.dependencies[i] = '*'; });
|
||||
}
|
||||
if (name && obj.name !== name) obj.invalid = true
|
||||
obj.realName = name || obj.name
|
||||
obj.dependencies = obj.dependencies || {}
|
||||
if (name && obj.name !== name) obj.invalid = true;
|
||||
obj.realName = name || obj.name;
|
||||
obj.dependencies = obj.dependencies || {};
|
||||
|
||||
// "foo":"http://blah" is always presumed valid
|
||||
if (reqver
|
||||
&& semver.validRange(reqver)
|
||||
&& !semver.satisfies(obj.version, reqver)) {
|
||||
obj.invalid = true
|
||||
if (reqver &&
|
||||
semver.validRange(reqver) &&
|
||||
!semver.satisfies(obj.version, reqver)) {
|
||||
obj.invalid = true;
|
||||
}
|
||||
|
||||
if (parent
|
||||
&& !(name in parent.dependencies)
|
||||
&& !(name in (parent.devDependencies || {}))) {
|
||||
obj.extraneous = true
|
||||
if (parent &&
|
||||
!(name in parent.dependencies) &&
|
||||
!(name in (parent.devDependencies || {}))) {
|
||||
obj.extraneous = true;
|
||||
}
|
||||
obj.path = obj.path || folder
|
||||
obj.realPath = real
|
||||
obj.link = link
|
||||
if (parent && !obj.link) obj.parent = parent
|
||||
rpSeen[real] = obj
|
||||
obj.depth = depth
|
||||
if (depth >= maxDepth) return cb(null, obj)
|
||||
asyncMap(installed, function (pkg, cb) {
|
||||
var rv = obj.dependencies[pkg]
|
||||
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
|
||||
readInstalled_( path.resolve(folder, "node_modules/"+pkg)
|
||||
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
|
||||
, cb )
|
||||
}, function (er, installedData) {
|
||||
if (er) return cb(er)
|
||||
installedData.forEach(function (dep) {
|
||||
obj.dependencies[dep.realName] = dep
|
||||
})
|
||||
obj.path = obj.path || folder;
|
||||
obj.realPath = real;
|
||||
obj.link = link;
|
||||
if (parent && !obj.link) obj.parent = parent;
|
||||
rpSeen[real] = obj;
|
||||
obj.depth = depth;
|
||||
if (depth >= maxDepth) return cb(null, obj);
|
||||
asyncMap(installed, (pkg, cb) => {
|
||||
let rv = obj.dependencies[pkg];
|
||||
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg];
|
||||
readInstalled_(path.resolve(folder, `node_modules/${pkg}`)
|
||||
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
|
||||
, cb);
|
||||
}, (er, installedData) => {
|
||||
if (er) return cb(er);
|
||||
installedData.forEach((dep) => {
|
||||
obj.dependencies[dep.realName] = dep;
|
||||
});
|
||||
|
||||
// any strings here are unmet things. however, if it's
|
||||
// optional, then that's fine, so just delete it.
|
||||
if (obj.optionalDependencies) {
|
||||
Object.keys(obj.optionalDependencies).forEach(function (dep) {
|
||||
if (typeof obj.dependencies[dep] === "string") {
|
||||
delete obj.dependencies[dep]
|
||||
Object.keys(obj.optionalDependencies).forEach((dep) => {
|
||||
if (typeof obj.dependencies[dep] === 'string') {
|
||||
delete obj.dependencies[dep];
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
return cb(null, obj)
|
||||
})
|
||||
return cb(null, obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// starting from a root object, call findUnmet on each layer of children
|
||||
var riSeen = []
|
||||
function resolveInheritance (obj) {
|
||||
if (typeof obj !== "object") return
|
||||
if (riSeen.indexOf(obj) !== -1) return
|
||||
riSeen.push(obj)
|
||||
if (typeof obj.dependencies !== "object") {
|
||||
obj.dependencies = {}
|
||||
var riSeen = [];
|
||||
function resolveInheritance(obj) {
|
||||
if (typeof obj !== 'object') return;
|
||||
if (riSeen.indexOf(obj) !== -1) return;
|
||||
riSeen.push(obj);
|
||||
if (typeof obj.dependencies !== 'object') {
|
||||
obj.dependencies = {};
|
||||
}
|
||||
Object.keys(obj.dependencies).forEach(function (dep) {
|
||||
findUnmet(obj.dependencies[dep])
|
||||
})
|
||||
Object.keys(obj.dependencies).forEach(function (dep) {
|
||||
resolveInheritance(obj.dependencies[dep])
|
||||
})
|
||||
Object.keys(obj.dependencies).forEach((dep) => {
|
||||
findUnmet(obj.dependencies[dep]);
|
||||
});
|
||||
Object.keys(obj.dependencies).forEach((dep) => {
|
||||
resolveInheritance(obj.dependencies[dep]);
|
||||
});
|
||||
}
|
||||
|
||||
// find unmet deps by walking up the tree object.
|
||||
// No I/O
|
||||
var fuSeen = []
|
||||
function findUnmet (obj) {
|
||||
if (fuSeen.indexOf(obj) !== -1) return
|
||||
fuSeen.push(obj)
|
||||
//console.error("find unmet", obj.name, obj.parent && obj.parent.name)
|
||||
var deps = obj.dependencies = obj.dependencies || {}
|
||||
//console.error(deps)
|
||||
const fuSeen = [];
|
||||
function findUnmet(obj) {
|
||||
if (fuSeen.indexOf(obj) !== -1) return;
|
||||
fuSeen.push(obj);
|
||||
// console.error("find unmet", obj.name, obj.parent && obj.parent.name)
|
||||
const deps = obj.dependencies = obj.dependencies || {};
|
||||
// console.error(deps)
|
||||
Object.keys(deps)
|
||||
.filter(function (d) { return typeof deps[d] === "string" })
|
||||
.forEach(function (d) {
|
||||
//console.error("find unmet", obj.name, d, deps[d])
|
||||
var r = obj.parent
|
||||
, found = null
|
||||
while (r && !found && typeof deps[d] === "string") {
|
||||
.filter((d) => typeof deps[d] === 'string')
|
||||
.forEach((d) => {
|
||||
// console.error("find unmet", obj.name, d, deps[d])
|
||||
let r = obj.parent;
|
||||
let found = null;
|
||||
while (r && !found && typeof deps[d] === 'string') {
|
||||
// if r is a valid choice, then use that.
|
||||
found = r.dependencies[d]
|
||||
if (!found && r.realName === d) found = r
|
||||
found = r.dependencies[d];
|
||||
if (!found && r.realName === d) found = r;
|
||||
|
||||
if (!found) {
|
||||
r = r.link ? null : r.parent
|
||||
continue
|
||||
}
|
||||
if ( typeof deps[d] === "string"
|
||||
&& !semver.satisfies(found.version, deps[d])) {
|
||||
if (!found) {
|
||||
r = r.link ? null : r.parent;
|
||||
continue;
|
||||
}
|
||||
if (typeof deps[d] === 'string' &&
|
||||
!semver.satisfies(found.version, deps[d])) {
|
||||
// the bad thing will happen
|
||||
log.warn(obj.path + " requires "+d+"@'"+deps[d]
|
||||
+"' but will load\n"
|
||||
+found.path+",\nwhich is version "+found.version
|
||||
,"unmet dependency")
|
||||
found.invalid = true
|
||||
log.warn(`${obj.path} requires ${d}@'${deps[d]
|
||||
}' but will load\n${
|
||||
found.path},\nwhich is version ${found.version}`
|
||||
, 'unmet dependency');
|
||||
found.invalid = true;
|
||||
}
|
||||
deps[d] = found;
|
||||
}
|
||||
deps[d] = found
|
||||
}
|
||||
|
||||
})
|
||||
return obj
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
function copy (obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj
|
||||
if (Array.isArray(obj)) return obj.map(copy)
|
||||
function copy(obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
if (Array.isArray(obj)) return obj.map(copy);
|
||||
|
||||
var o = {}
|
||||
for (var i in obj) o[i] = copy(obj[i])
|
||||
return o
|
||||
const o = {};
|
||||
for (const i in obj) o[i] = copy(obj[i]);
|
||||
return o;
|
||||
}
|
||||
|
||||
if (module === require.main) {
|
||||
var util = require("util")
|
||||
console.error("testing")
|
||||
const util = require('util');
|
||||
console.error('testing');
|
||||
|
||||
var called = 0
|
||||
readInstalled(process.cwd(), function (er, map) {
|
||||
console.error(called ++)
|
||||
if (er) return console.error(er.stack || er.message)
|
||||
cleanup(map)
|
||||
console.error(util.inspect(map, true, 10, true))
|
||||
})
|
||||
let called = 0;
|
||||
readInstalled(process.cwd(), (er, map) => {
|
||||
console.error(called++);
|
||||
if (er) return console.error(er.stack || er.message);
|
||||
cleanup(map);
|
||||
console.error(util.inspect(map, true, 10, true));
|
||||
});
|
||||
|
||||
var seen = []
|
||||
function cleanup (map) {
|
||||
if (seen.indexOf(map) !== -1) return
|
||||
seen.push(map)
|
||||
for (var i in map) switch (i) {
|
||||
case "_id":
|
||||
case "path":
|
||||
case "extraneous": case "invalid":
|
||||
case "dependencies": case "name":
|
||||
continue
|
||||
default: delete map[i]
|
||||
}
|
||||
var dep = map.dependencies
|
||||
// delete map.dependencies
|
||||
if (dep) {
|
||||
// map.dependencies = dep
|
||||
for (var i in dep) if (typeof dep[i] === "object") {
|
||||
cleanup(dep[i])
|
||||
const seen = [];
|
||||
function cleanup(map) {
|
||||
if (seen.indexOf(map) !== -1) return;
|
||||
seen.push(map);
|
||||
for (var i in map) {
|
||||
switch (i) {
|
||||
case '_id':
|
||||
case 'path':
|
||||
case 'extraneous': case 'invalid':
|
||||
case 'dependencies': case 'name':
|
||||
continue;
|
||||
default: delete map[i];
|
||||
}
|
||||
}
|
||||
return map
|
||||
const dep = map.dependencies;
|
||||
// delete map.dependencies
|
||||
if (dep) {
|
||||
// map.dependencies = dep
|
||||
for (var i in dep) {
|
||||
if (typeof dep[i] === 'object') {
|
||||
cleanup(dep[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var _ = require("underscore");
|
||||
var defs = require('./plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
const disabledHookReasons = {
|
||||
hooks: {
|
||||
|
@ -9,70 +9,70 @@ const disabledHookReasons = {
|
|||
};
|
||||
|
||||
function loadFn(path, hookName) {
|
||||
var functionName
|
||||
, parts = path.split(":");
|
||||
let functionName;
|
||||
const parts = path.split(':');
|
||||
|
||||
// on windows: C:\foo\bar:xyz
|
||||
if (parts[0].length == 1) {
|
||||
if (parts.length == 3) {
|
||||
functionName = parts.pop();
|
||||
}
|
||||
path = parts.join(":");
|
||||
path = parts.join(':');
|
||||
} else {
|
||||
path = parts[0];
|
||||
functionName = parts[1];
|
||||
}
|
||||
|
||||
var fn = require(path);
|
||||
let fn = require(path);
|
||||
functionName = functionName ? functionName : hookName;
|
||||
|
||||
_.each(functionName.split("."), function (name) {
|
||||
_.each(functionName.split('.'), (name) => {
|
||||
fn = fn[name];
|
||||
});
|
||||
return fn;
|
||||
};
|
||||
}
|
||||
|
||||
function extractHooks(parts, hook_set_name, normalizer) {
|
||||
var hooks = {};
|
||||
_.each(parts,function (part) {
|
||||
const hooks = {};
|
||||
_.each(parts, (part) => {
|
||||
_.chain(part[hook_set_name] || {})
|
||||
.keys()
|
||||
.each(function (hook_name) {
|
||||
var hook_fn_name = part[hook_set_name][hook_name];
|
||||
.keys()
|
||||
.each((hook_name) => {
|
||||
let hook_fn_name = part[hook_set_name][hook_name];
|
||||
|
||||
/* On the server side, you can't just
|
||||
/* On the server side, you can't just
|
||||
* require("pluginname/whatever") if the plugin is installed as
|
||||
* a dependency of another plugin! Bah, pesky little details of
|
||||
* npm... */
|
||||
if (normalizer) {
|
||||
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
|
||||
}
|
||||
if (normalizer) {
|
||||
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
|
||||
}
|
||||
|
||||
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
|
||||
if (disabledReason) {
|
||||
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
|
||||
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
|
||||
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
|
||||
if (disabledReason) {
|
||||
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
|
||||
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
|
||||
'will never be called, which may cause the plugin to fail');
|
||||
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
|
||||
return;
|
||||
}
|
||||
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
if (!hook_fn) {
|
||||
throw "Not a function";
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error("Failed to load '" + hook_fn_name + "' for '" + part.full_name + "/" + hook_set_name + "/" + hook_name + "': " + exc.toString())
|
||||
}
|
||||
if (hook_fn) {
|
||||
if (hooks[hook_name] == null) hooks[hook_name] = [];
|
||||
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part});
|
||||
}
|
||||
});
|
||||
try {
|
||||
var hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
if (!hook_fn) {
|
||||
throw 'Not a function';
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error(`Failed to load '${hook_fn_name}' for '${part.full_name}/${hook_set_name}/${hook_name}': ${exc.toString()}`);
|
||||
}
|
||||
if (hook_fn) {
|
||||
if (hooks[hook_name] == null) hooks[hook_name] = [];
|
||||
hooks[hook_name].push({hook_name, hook_fn, hook_fn_name, part});
|
||||
}
|
||||
});
|
||||
});
|
||||
return hooks;
|
||||
};
|
||||
}
|
||||
|
||||
exports.extractHooks = extractHooks;
|
||||
|
||||
|
@ -88,12 +88,12 @@ exports.extractHooks = extractHooks;
|
|||
* No plugins: []
|
||||
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
|
||||
*/
|
||||
exports.clientPluginNames = function() {
|
||||
var client_plugin_names = _.uniq(
|
||||
defs.parts
|
||||
.filter(function(part) { return part.hasOwnProperty('client_hooks'); })
|
||||
.map(function(part) { return 'plugin-' + part['plugin']; })
|
||||
exports.clientPluginNames = function () {
|
||||
const client_plugin_names = _.uniq(
|
||||
defs.parts
|
||||
.filter((part) => part.hasOwnProperty('client_hooks'))
|
||||
.map((part) => `plugin-${part.plugin}`),
|
||||
);
|
||||
|
||||
return client_plugin_names;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,27 +8,28 @@
|
|||
**/
|
||||
|
||||
function tsort(edges) {
|
||||
var nodes = {}, // hash: stringified id of the node => { id: id, afters: lisf of ids }
|
||||
sorted = [], // sorted list of IDs ( returned value )
|
||||
visited = {}; // hash: id of already visited node => true
|
||||
const nodes = {}; // hash: stringified id of the node => { id: id, afters: lisf of ids }
|
||||
const sorted = []; // sorted list of IDs ( returned value )
|
||||
const visited = {}; // hash: id of already visited node => true
|
||||
|
||||
var Node = function(id) {
|
||||
const Node = function (id) {
|
||||
this.id = id;
|
||||
this.afters = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 1. build data structures
|
||||
edges.forEach(function(v) {
|
||||
var from = v[0], to = v[1];
|
||||
edges.forEach((v) => {
|
||||
const from = v[0]; const
|
||||
to = v[1];
|
||||
if (!nodes[from]) nodes[from] = new Node(from);
|
||||
if (!nodes[to]) nodes[to] = new Node(to);
|
||||
if (!nodes[to]) nodes[to] = new Node(to);
|
||||
nodes[from].afters.push(to);
|
||||
});
|
||||
|
||||
// 2. topological sort
|
||||
Object.keys(nodes).forEach(function visit(idstr, ancestors) {
|
||||
var node = nodes[idstr],
|
||||
id = node.id;
|
||||
const node = nodes[idstr];
|
||||
const id = node.id;
|
||||
|
||||
// if already exists, do nothing
|
||||
if (visited[idstr]) return;
|
||||
|
@ -39,11 +40,11 @@ function tsort(edges) {
|
|||
|
||||
visited[idstr] = true;
|
||||
|
||||
node.afters.forEach(function(afterID) {
|
||||
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
|
||||
throw new Error('closed chain : ' + afterID + ' is in ' + id);
|
||||
node.afters.forEach((afterID) => {
|
||||
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
|
||||
{ throw new Error(`closed chain : ${afterID} is in ${id}`); }
|
||||
|
||||
visit(afterID.toString(), ancestors.map(function(v) { return v })); // recursive call
|
||||
visit(afterID.toString(), ancestors.map((v) => v)); // recursive call
|
||||
});
|
||||
|
||||
sorted.unshift(id);
|
||||
|
@ -56,57 +57,55 @@ function tsort(edges) {
|
|||
* TEST
|
||||
**/
|
||||
function tsortTest() {
|
||||
|
||||
// example 1: success
|
||||
var edges = [
|
||||
let edges = [
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 4],
|
||||
[3, 4]
|
||||
[3, 4],
|
||||
];
|
||||
|
||||
var sorted = tsort(edges);
|
||||
let sorted = tsort(edges);
|
||||
console.log(sorted);
|
||||
|
||||
// example 2: failure ( A > B > C > A )
|
||||
edges = [
|
||||
['A', 'B'],
|
||||
['B', 'C'],
|
||||
['C', 'A']
|
||||
['C', 'A'],
|
||||
];
|
||||
|
||||
try {
|
||||
sorted = tsort(edges);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
|
||||
// example 3: generate random edges
|
||||
var max = 100, iteration = 30;
|
||||
const max = 100; const
|
||||
iteration = 30;
|
||||
function randomInt(max) {
|
||||
return Math.floor(Math.random() * max) + 1;
|
||||
}
|
||||
|
||||
edges = (function() {
|
||||
var ret = [], i = 0;
|
||||
while (i++ < iteration) ret.push( [randomInt(max), randomInt(max)] );
|
||||
edges = (function () {
|
||||
const ret = []; let
|
||||
i = 0;
|
||||
while (i++ < iteration) ret.push([randomInt(max), randomInt(max)]);
|
||||
return ret;
|
||||
})();
|
||||
|
||||
try {
|
||||
sorted = tsort(edges);
|
||||
console.log("succeeded", sorted);
|
||||
console.log('succeeded', sorted);
|
||||
} catch (e) {
|
||||
console.log('failed', e.message);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("failed", e.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for node.js
|
||||
if (typeof exports == 'object' && exports === this) {
|
||||
if (typeof exports === 'object' && exports === this) {
|
||||
module.exports = tsort;
|
||||
if (process.argv[1] === __filename) tsortTest();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
// Proviedes a require'able version of jQuery without leaking $ and jQuery;
|
||||
window.$ = require('./jquery');
|
||||
var jq = window.$.noConflict(true);
|
||||
const jq = window.$.noConflict(true);
|
||||
exports.jQuery = exports.$ = jq;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Browser Line = each vertical line. A <div> can be break into more than one
|
||||
browser line.
|
||||
*/
|
||||
var caretPosition = require('./caretPosition');
|
||||
const caretPosition = require('./caretPosition');
|
||||
|
||||
function Scroll(outerWin) {
|
||||
// scroll settings
|
||||
|
@ -20,317 +20,313 @@ function Scroll(outerWin) {
|
|||
Scroll.prototype.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary = function (rep, isScrollableEvent, innerHeight) {
|
||||
// are we placing the caret on the line at the bottom of viewport?
|
||||
// And if so, do we need to scroll the editor, as defined on the settings.json?
|
||||
var shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
|
||||
const shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
|
||||
if (shouldScrollWhenCaretIsAtBottomOfViewport) {
|
||||
// avoid scrolling when selection includes multiple lines -- user can potentially be selecting more lines
|
||||
// than it fits on viewport
|
||||
var multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
|
||||
const multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
|
||||
|
||||
// avoid scrolling when pad loads
|
||||
if (isScrollableEvent && !multipleLinesSelected && this._isCaretAtTheBottomOfViewport(rep)) {
|
||||
// when scrollWhenFocusLineIsOutOfViewport.percentage is 0, pixelsToScroll is 0
|
||||
var pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
const pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.scrollWhenPressArrowKeys = function(arrowUp, rep, innerHeight) {
|
||||
Scroll.prototype.scrollWhenPressArrowKeys = function (arrowUp, rep, innerHeight) {
|
||||
// if percentageScrollArrowUp is 0, let the scroll to be handled as default, put the previous
|
||||
// rep line on the top of the viewport
|
||||
if(this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)){
|
||||
var pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
|
||||
if (this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)) {
|
||||
const pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
|
||||
|
||||
// by default, the browser scrolls to the middle of the viewport. To avoid the twist made
|
||||
// when we apply a second scroll, we made it immediately (without animation)
|
||||
this._scrollYPageWithoutAnimation(-pixelsToScroll);
|
||||
}else{
|
||||
} else {
|
||||
this.scrollNodeVerticallyIntoView(rep, innerHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Some plugins might set a minimum height to the editor (ex: ep_page_view), so checking
|
||||
// if (caretLine() === rep.lines.length() - 1) is not enough. We need to check if there are
|
||||
// other lines after caretLine(), and all of them are out of viewport.
|
||||
Scroll.prototype._isCaretAtTheBottomOfViewport = function(rep) {
|
||||
Scroll.prototype._isCaretAtTheBottomOfViewport = function (rep) {
|
||||
// computing a line position using getBoundingClientRect() is expensive.
|
||||
// (obs: getBoundingClientRect() is called on caretPosition.getPosition())
|
||||
// To avoid that, we only call this function when it is possible that the
|
||||
// caret is in the bottom of viewport
|
||||
var caretLine = rep.selStart[0];
|
||||
var lineAfterCaretLine = caretLine + 1;
|
||||
var firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
|
||||
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
var lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
|
||||
const caretLine = rep.selStart[0];
|
||||
const lineAfterCaretLine = caretLine + 1;
|
||||
const firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
|
||||
const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
const lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
|
||||
if (caretLineIsPartiallyVisibleOnViewport || lineAfterCaretLineIsPartiallyVisibleOnViewport) {
|
||||
// check if the caret is in the bottom of the viewport
|
||||
var caretLinePosition = caretPosition.getPosition();
|
||||
var viewportBottom = this._getViewPortTopBottom().bottom;
|
||||
var nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
|
||||
var nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
|
||||
const caretLinePosition = caretPosition.getPosition();
|
||||
const viewportBottom = this._getViewPortTopBottom().bottom;
|
||||
const nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
|
||||
const nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
|
||||
return nextLineIsBelowViewportBottom;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._isLinePartiallyVisibleOnViewport = function(lineNumber, rep) {
|
||||
var lineNode = rep.lines.atIndex(lineNumber);
|
||||
var linePosition = this._getLineEntryTopBottom(lineNode);
|
||||
var lineTop = linePosition.top;
|
||||
var lineBottom = linePosition.bottom;
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
var viewportBottom = viewport.bottom;
|
||||
var viewportTop = viewport.top;
|
||||
Scroll.prototype._isLinePartiallyVisibleOnViewport = function (lineNumber, rep) {
|
||||
const lineNode = rep.lines.atIndex(lineNumber);
|
||||
const linePosition = this._getLineEntryTopBottom(lineNode);
|
||||
const lineTop = linePosition.top;
|
||||
const lineBottom = linePosition.bottom;
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
const viewportBottom = viewport.bottom;
|
||||
const viewportTop = viewport.top;
|
||||
|
||||
var topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
|
||||
var bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
|
||||
var topOfLineIsBelowViewportTop = lineTop >= viewportTop;
|
||||
var topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
|
||||
var bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
|
||||
var bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
|
||||
const topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
|
||||
const bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
|
||||
const topOfLineIsBelowViewportTop = lineTop >= viewportTop;
|
||||
const topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
|
||||
const bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
|
||||
const bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
|
||||
|
||||
return (topOfLineIsAboveOfViewportBottom && bottomOfLineIsOnOrBelowOfViewportBottom) ||
|
||||
(topOfLineIsBelowViewportTop && topOfLineIsAboveViewportBottom) ||
|
||||
(bottomOfLineIsAboveViewportBottom && bottomOfLineIsBelowViewportTop);
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._getViewPortTopBottom = function() {
|
||||
var theTop = this.getScrollY();
|
||||
var doc = this.doc;
|
||||
var height = doc.documentElement.clientHeight; // includes padding
|
||||
Scroll.prototype._getViewPortTopBottom = function () {
|
||||
const theTop = this.getScrollY();
|
||||
const doc = this.doc;
|
||||
const height = doc.documentElement.clientHeight; // includes padding
|
||||
|
||||
// we have to get the exactly height of the viewport. So it has to subtract all the values which changes
|
||||
// the viewport height (E.g. padding, position top)
|
||||
var viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
|
||||
const viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
|
||||
return {
|
||||
top: theTop,
|
||||
bottom: (theTop + height - viewportExtraSpacesAndPosition)
|
||||
bottom: (theTop + height - viewportExtraSpacesAndPosition),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._getEditorPositionTop = function() {
|
||||
var editor = parent.document.getElementsByTagName('iframe');
|
||||
var editorPositionTop = editor[0].offsetTop;
|
||||
Scroll.prototype._getEditorPositionTop = function () {
|
||||
const editor = parent.document.getElementsByTagName('iframe');
|
||||
const editorPositionTop = editor[0].offsetTop;
|
||||
return editorPositionTop;
|
||||
}
|
||||
};
|
||||
|
||||
// ep_page_view adds padding-top, which makes the viewport smaller
|
||||
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function() {
|
||||
var aceOuter = this.rootDocument.getElementsByName("ace_outer");
|
||||
var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top"));
|
||||
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function () {
|
||||
const aceOuter = this.rootDocument.getElementsByName('ace_outer');
|
||||
const aceOuterPaddingTop = parseInt($(aceOuter).css('padding-top'));
|
||||
return aceOuterPaddingTop;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._getScrollXY = function() {
|
||||
var win = this.outerWin;
|
||||
var odoc = this.doc;
|
||||
if (typeof(win.pageYOffset) == "number")
|
||||
{
|
||||
Scroll.prototype._getScrollXY = function () {
|
||||
const win = this.outerWin;
|
||||
const odoc = this.doc;
|
||||
if (typeof (win.pageYOffset) === 'number') {
|
||||
return {
|
||||
x: win.pageXOffset,
|
||||
y: win.pageYOffset
|
||||
y: win.pageYOffset,
|
||||
};
|
||||
}
|
||||
var docel = odoc.documentElement;
|
||||
if (docel && typeof(docel.scrollTop) == "number")
|
||||
{
|
||||
const docel = odoc.documentElement;
|
||||
if (docel && typeof (docel.scrollTop) === 'number') {
|
||||
return {
|
||||
x: docel.scrollLeft,
|
||||
y: docel.scrollTop
|
||||
y: docel.scrollTop,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.getScrollX = function() {
|
||||
Scroll.prototype.getScrollX = function () {
|
||||
return this._getScrollXY().x;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.getScrollY = function() {
|
||||
Scroll.prototype.getScrollY = function () {
|
||||
return this._getScrollXY().y;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.setScrollX = function(x) {
|
||||
Scroll.prototype.setScrollX = function (x) {
|
||||
this.outerWin.scrollTo(x, this.getScrollY());
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.setScrollY = function(y) {
|
||||
Scroll.prototype.setScrollY = function (y) {
|
||||
this.outerWin.scrollTo(this.getScrollX(), y);
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.setScrollXY = function(x, y) {
|
||||
Scroll.prototype.setScrollXY = function (x, y) {
|
||||
this.outerWin.scrollTo(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._isCaretAtTheTopOfViewport = function(rep) {
|
||||
var caretLine = rep.selStart[0];
|
||||
var linePrevCaretLine = caretLine - 1;
|
||||
var firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
|
||||
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
var lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
|
||||
Scroll.prototype._isCaretAtTheTopOfViewport = function (rep) {
|
||||
const caretLine = rep.selStart[0];
|
||||
const linePrevCaretLine = caretLine - 1;
|
||||
const firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
|
||||
const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
|
||||
const lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
|
||||
if (caretLineIsPartiallyVisibleOnViewport || lineBeforeCaretLineIsPartiallyVisibleOnViewport) {
|
||||
var caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
|
||||
var viewportPosition = this._getViewPortTopBottom();
|
||||
var viewportTop = viewportPosition.top;
|
||||
var viewportBottom = viewportPosition.bottom;
|
||||
var caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
|
||||
var caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
|
||||
var caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
|
||||
const caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
|
||||
const viewportPosition = this._getViewPortTopBottom();
|
||||
const viewportTop = viewportPosition.top;
|
||||
const viewportBottom = viewportPosition.bottom;
|
||||
const caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
|
||||
const caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
|
||||
const caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
|
||||
if (caretLineIsInsideOfViewport) {
|
||||
var prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
|
||||
var previousLineIsAboveViewportTop = prevLineTop < viewportTop;
|
||||
const prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
|
||||
const previousLineIsAboveViewportTop = prevLineTop < viewportTop;
|
||||
return previousLineIsAboveViewportTop;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// By default, when user makes an edition in a line out of viewport, this line goes
|
||||
// to the edge of viewport. This function gets the extra pixels necessary to get the
|
||||
// caret line in a position X relative to Y% viewport.
|
||||
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function(innerHeight, aboveOfViewport) {
|
||||
var pixels = 0;
|
||||
var scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
|
||||
if(scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1){
|
||||
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function (innerHeight, aboveOfViewport) {
|
||||
let pixels = 0;
|
||||
const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
|
||||
if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) {
|
||||
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
};
|
||||
|
||||
// we use different percentages when change selection. It depends on if it is
|
||||
// either above the top or below the bottom of the page
|
||||
Scroll.prototype._getPercentageToScroll = function(aboveOfViewport) {
|
||||
var percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
|
||||
if(aboveOfViewport){
|
||||
Scroll.prototype._getPercentageToScroll = function (aboveOfViewport) {
|
||||
let percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
|
||||
if (aboveOfViewport) {
|
||||
percentageToScroll = this.scrollSettings.percentage.editionAboveViewport;
|
||||
}
|
||||
return percentageToScroll;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function(innerHeight) {
|
||||
var pixels = 0;
|
||||
var percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
if(percentageToScrollUp > 0 && percentageToScrollUp <= 1){
|
||||
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function (innerHeight) {
|
||||
let pixels = 0;
|
||||
const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) {
|
||||
pixels = parseInt(innerHeight * percentageToScrollUp);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._scrollYPage = function(pixelsToScroll) {
|
||||
var durationOfAnimationToShowFocusline = this.scrollSettings.duration;
|
||||
if(durationOfAnimationToShowFocusline){
|
||||
Scroll.prototype._scrollYPage = function (pixelsToScroll) {
|
||||
const durationOfAnimationToShowFocusline = this.scrollSettings.duration;
|
||||
if (durationOfAnimationToShowFocusline) {
|
||||
this._scrollYPageWithAnimation(pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
}else{
|
||||
} else {
|
||||
this._scrollYPageWithoutAnimation(pixelsToScroll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._scrollYPageWithoutAnimation = function(pixelsToScroll) {
|
||||
Scroll.prototype._scrollYPageWithoutAnimation = function (pixelsToScroll) {
|
||||
this.outerWin.scrollBy(0, pixelsToScroll);
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._scrollYPageWithAnimation = function(pixelsToScroll, durationOfAnimationToShowFocusline) {
|
||||
var outerDocBody = this.doc.getElementById("outerdocbody");
|
||||
Scroll.prototype._scrollYPageWithAnimation = function (pixelsToScroll, durationOfAnimationToShowFocusline) {
|
||||
const outerDocBody = this.doc.getElementById('outerdocbody');
|
||||
|
||||
// it works on later versions of Chrome
|
||||
var $outerDocBody = $(outerDocBody);
|
||||
const $outerDocBody = $(outerDocBody);
|
||||
this._triggerScrollWithAnimation($outerDocBody, pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
|
||||
// it works on Firefox and earlier versions of Chrome
|
||||
var $outerDocBodyParent = $outerDocBody.parent();
|
||||
const $outerDocBodyParent = $outerDocBody.parent();
|
||||
this._triggerScrollWithAnimation($outerDocBodyParent, pixelsToScroll, durationOfAnimationToShowFocusline);
|
||||
}
|
||||
};
|
||||
|
||||
// using a custom queue and clearing it, we avoid creating a queue of scroll animations. So if this function
|
||||
// is called twice quickly, only the last one runs.
|
||||
Scroll.prototype._triggerScrollWithAnimation = function($elem, pixelsToScroll, durationOfAnimationToShowFocusline) {
|
||||
Scroll.prototype._triggerScrollWithAnimation = function ($elem, pixelsToScroll, durationOfAnimationToShowFocusline) {
|
||||
// clear the queue of animation
|
||||
$elem.stop("scrollanimation");
|
||||
$elem.stop('scrollanimation');
|
||||
$elem.animate({
|
||||
scrollTop: '+=' + pixelsToScroll
|
||||
scrollTop: `+=${pixelsToScroll}`,
|
||||
}, {
|
||||
duration: durationOfAnimationToShowFocusline,
|
||||
queue: "scrollanimation"
|
||||
}).dequeue("scrollanimation");
|
||||
}
|
||||
queue: 'scrollanimation',
|
||||
}).dequeue('scrollanimation');
|
||||
};
|
||||
|
||||
// scrollAmountWhenFocusLineIsOutOfViewport is set to 0 (default), scroll it the minimum distance
|
||||
// needed to be completely in view. If the value is greater than 0 and less than or equal to 1,
|
||||
// besides of scrolling the minimum needed to be visible, it scrolls additionally
|
||||
// (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels
|
||||
Scroll.prototype.scrollNodeVerticallyIntoView = function(rep, innerHeight) {
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
var isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
|
||||
Scroll.prototype.scrollNodeVerticallyIntoView = function (rep, innerHeight) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
const isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
|
||||
|
||||
// when the selection changes outside of the viewport the browser automatically scrolls the line
|
||||
// to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now
|
||||
// So, when the line scrolled gets outside of the viewport we let the browser handle it.
|
||||
var linePosition = caretPosition.getPosition();
|
||||
if(linePosition){
|
||||
var distanceOfTopOfViewport = linePosition.top - viewport.top;
|
||||
var distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
|
||||
var caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
|
||||
var caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
|
||||
if(caretIsAboveOfViewport){
|
||||
const linePosition = caretPosition.getPosition();
|
||||
if (linePosition) {
|
||||
const distanceOfTopOfViewport = linePosition.top - viewport.top;
|
||||
const distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
|
||||
const caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
|
||||
const caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
|
||||
if (caretIsAboveOfViewport) {
|
||||
var pixelsToScroll = distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}else if(caretIsBelowOfViewport){
|
||||
} else if (caretIsBelowOfViewport) {
|
||||
var pixelsToScroll = -distanceOfBottomOfViewport + this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
}else{
|
||||
} else {
|
||||
this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._partOfRepLineIsOutOfViewport = function(viewportPosition, rep) {
|
||||
var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
|
||||
var line = rep.lines.atIndex(focusLine);
|
||||
var linePosition = this._getLineEntryTopBottom(line);
|
||||
var lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
|
||||
var lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
|
||||
Scroll.prototype._partOfRepLineIsOutOfViewport = function (viewportPosition, rep) {
|
||||
const focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
|
||||
const line = rep.lines.atIndex(focusLine);
|
||||
const linePosition = this._getLineEntryTopBottom(line);
|
||||
const lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
|
||||
const lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
|
||||
|
||||
return lineIsBelowOfViewport || lineIsAboveOfViewport;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._getLineEntryTopBottom = function(entry, destObj) {
|
||||
var dom = entry.lineNode;
|
||||
var top = dom.offsetTop;
|
||||
var height = dom.offsetHeight;
|
||||
var obj = (destObj || {});
|
||||
Scroll.prototype._getLineEntryTopBottom = function (entry, destObj) {
|
||||
const dom = entry.lineNode;
|
||||
const top = dom.offsetTop;
|
||||
const height = dom.offsetHeight;
|
||||
const obj = (destObj || {});
|
||||
obj.top = top;
|
||||
obj.bottom = (top + height);
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function(arrowUp, rep) {
|
||||
var percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function (arrowUp, rep) {
|
||||
const percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
|
||||
return percentageScrollArrowUp && arrowUp && this._isCaretAtTheTopOfViewport(rep);
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.getVisibleLineRange = function(rep) {
|
||||
var viewport = this._getViewPortTopBottom();
|
||||
//console.log("viewport top/bottom: %o", viewport);
|
||||
var obj = {};
|
||||
var self = this;
|
||||
var start = rep.lines.search(function(e) {
|
||||
return self._getLineEntryTopBottom(e, obj).bottom > viewport.top;
|
||||
});
|
||||
var end = rep.lines.search(function(e) {
|
||||
Scroll.prototype.getVisibleLineRange = function (rep) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
// console.log("viewport top/bottom: %o", viewport);
|
||||
const obj = {};
|
||||
const self = this;
|
||||
const start = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).bottom > viewport.top);
|
||||
let end = rep.lines.search((e) =>
|
||||
// return the first line that the top position is greater or equal than
|
||||
// the viewport. That is the first line that is below the viewport bottom.
|
||||
// So the line that is in the bottom of the viewport is the very previous one.
|
||||
return self._getLineEntryTopBottom(e, obj).top >= viewport.bottom;
|
||||
});
|
||||
self._getLineEntryTopBottom(e, obj).top >= viewport.bottom,
|
||||
);
|
||||
if (end < start) end = start; // unlikely
|
||||
// top.console.log(start+","+(end -1));
|
||||
return [start, end - 1];
|
||||
}
|
||||
};
|
||||
|
||||
Scroll.prototype.getVisibleCharRange = function(rep) {
|
||||
var lineRange = this.getVisibleLineRange(rep);
|
||||
Scroll.prototype.getVisibleCharRange = function (rep) {
|
||||
const lineRange = this.getVisibleLineRange(rep);
|
||||
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = function(outerWin) {
|
||||
exports.init = function (outerWin) {
|
||||
return new Scroll(outerWin);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
// Specific hash to display the skin variants builder popup
|
||||
if (window.location.hash.toLowerCase() == "#skinvariantsbuilder") {
|
||||
if (window.location.hash.toLowerCase() == '#skinvariantsbuilder') {
|
||||
$('#skin-variants').addClass('popup-show');
|
||||
|
||||
$('.skin-variant').change(function() {
|
||||
$('.skin-variant').change(() => {
|
||||
updateSkinVariantsClasses();
|
||||
});
|
||||
|
||||
var containers = ['editor', 'background', 'toolbar'];
|
||||
var colors = ['super-light', 'light', 'dark', 'super-dark'];
|
||||
const containers = ['editor', 'background', 'toolbar'];
|
||||
const colors = ['super-light', 'light', 'dark', 'super-dark'];
|
||||
|
||||
updateCheckboxFromSkinClasses();
|
||||
updateSkinVariantsClasses();
|
||||
|
||||
// add corresponding classes when config change
|
||||
function updateSkinVariantsClasses() {
|
||||
var domsToUpdate = [
|
||||
const domsToUpdate = [
|
||||
$('html'),
|
||||
$('iframe[name=ace_outer]').contents().find('html'),
|
||||
$('iframe[name=ace_outer]').contents().find('iframe[name=ace_inner]').contents().find('html')
|
||||
$('iframe[name=ace_outer]').contents().find('iframe[name=ace_inner]').contents().find('html'),
|
||||
];
|
||||
colors.forEach(function(color) {
|
||||
containers.forEach(function(container) {
|
||||
domsToUpdate.forEach(function(el) { el.removeClass(color + '-' + container); });
|
||||
})
|
||||
})
|
||||
colors.forEach((color) => {
|
||||
containers.forEach((container) => {
|
||||
domsToUpdate.forEach((el) => { el.removeClass(`${color}-${container}`); });
|
||||
});
|
||||
});
|
||||
|
||||
domsToUpdate.forEach(function(el) { el.removeClass('full-width-editor'); });
|
||||
domsToUpdate.forEach((el) => { el.removeClass('full-width-editor'); });
|
||||
|
||||
var new_classes = [];
|
||||
$('select.skin-variant-color').each(function() {
|
||||
new_classes.push($(this).val() + "-" + $(this).data('container'));
|
||||
})
|
||||
if ($('#skin-variant-full-width').is(':checked')) new_classes.push("full-width-editor");
|
||||
const new_classes = [];
|
||||
$('select.skin-variant-color').each(function () {
|
||||
new_classes.push(`${$(this).val()}-${$(this).data('container')}`);
|
||||
});
|
||||
if ($('#skin-variant-full-width').is(':checked')) new_classes.push('full-width-editor');
|
||||
|
||||
domsToUpdate.forEach(function(el) { el.addClass(new_classes.join(" ")); });
|
||||
domsToUpdate.forEach((el) => { el.addClass(new_classes.join(' ')); });
|
||||
|
||||
$('#skin-variants-result').val('"skinVariants": "' + new_classes.join(" ") + '",');
|
||||
$('#skin-variants-result').val(`"skinVariants": "${new_classes.join(' ')}",`);
|
||||
}
|
||||
|
||||
// run on init
|
||||
function updateCheckboxFromSkinClasses() {
|
||||
$('html').attr('class').split(' ').forEach(function(classItem) {
|
||||
$('html').attr('class').split(' ').forEach((classItem) => {
|
||||
var container = classItem.split('-').slice(-1);
|
||||
|
||||
var container = classItem.substring(classItem.lastIndexOf("-") + 1, classItem.length);
|
||||
var container = classItem.substring(classItem.lastIndexOf('-') + 1, classItem.length);
|
||||
if (containers.indexOf(container) > -1) {
|
||||
var color = classItem.substring(0, classItem.lastIndexOf("-"));
|
||||
$('.skin-variant-color[data-container="' + container + '"').val(color);
|
||||
const color = classItem.substring(0, classItem.lastIndexOf('-'));
|
||||
$(`.skin-variant-color[data-container="${container}"`).val(color);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$('#skin-variant-full-width').prop('checked', $('html').hasClass('full-width-editor'));
|
||||
}
|
||||
|
|
|
@ -20,46 +20,45 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Ace2Common = require('./ace2_common'),
|
||||
_ = require('./underscore');
|
||||
const Ace2Common = require('./ace2_common');
|
||||
const _ = require('./underscore');
|
||||
|
||||
var noop = Ace2Common.noop;
|
||||
const noop = Ace2Common.noop;
|
||||
|
||||
function SkipList() {
|
||||
var PROFILER = window.PROFILER;
|
||||
if (!PROFILER)
|
||||
{
|
||||
PROFILER = function() {
|
||||
let PROFILER = window.PROFILER;
|
||||
if (!PROFILER) {
|
||||
PROFILER = function () {
|
||||
return {
|
||||
start: noop,
|
||||
mark: noop,
|
||||
literal: noop,
|
||||
end: noop,
|
||||
cancel: noop
|
||||
cancel: noop,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// if there are N elements in the skiplist, "start" is element -1 and "end" is element N
|
||||
var start = {
|
||||
const start = {
|
||||
key: null,
|
||||
levels: 1,
|
||||
upPtrs: [null],
|
||||
downPtrs: [null],
|
||||
downSkips: [1],
|
||||
downSkipWidths: [0]
|
||||
downSkipWidths: [0],
|
||||
};
|
||||
var end = {
|
||||
const end = {
|
||||
key: null,
|
||||
levels: 1,
|
||||
upPtrs: [null],
|
||||
downPtrs: [null],
|
||||
downSkips: [null],
|
||||
downSkipWidths: [null]
|
||||
downSkipWidths: [null],
|
||||
};
|
||||
var numNodes = 0;
|
||||
var totalWidth = 0;
|
||||
var keyToNodeMap = {};
|
||||
let numNodes = 0;
|
||||
let totalWidth = 0;
|
||||
const keyToNodeMap = {};
|
||||
start.downPtrs[0] = end;
|
||||
end.upPtrs[0] = start;
|
||||
// a "point" object at location x allows modifications immediately after the first
|
||||
|
@ -70,21 +69,19 @@ function SkipList() {
|
|||
|
||||
|
||||
function _getPoint(targetLoc) {
|
||||
var numLevels = start.levels;
|
||||
var lvl = numLevels - 1;
|
||||
var i = -1,
|
||||
ws = 0;
|
||||
var nodes = new Array(numLevels);
|
||||
var idxs = new Array(numLevels);
|
||||
var widthSkips = new Array(numLevels);
|
||||
const numLevels = start.levels;
|
||||
let lvl = numLevels - 1;
|
||||
let i = -1;
|
||||
let ws = 0;
|
||||
const nodes = new Array(numLevels);
|
||||
const idxs = new Array(numLevels);
|
||||
const widthSkips = new Array(numLevels);
|
||||
nodes[lvl] = start;
|
||||
idxs[lvl] = -1;
|
||||
widthSkips[lvl] = 0;
|
||||
while (lvl >= 0)
|
||||
{
|
||||
var n = nodes[lvl];
|
||||
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc))
|
||||
{
|
||||
while (lvl >= 0) {
|
||||
let n = nodes[lvl];
|
||||
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc)) {
|
||||
i += n.downSkips[lvl];
|
||||
ws += n.downSkipWidths[lvl];
|
||||
n = n.downPtrs[lvl];
|
||||
|
@ -93,30 +90,27 @@ function SkipList() {
|
|||
idxs[lvl] = i;
|
||||
widthSkips[lvl] = ws;
|
||||
lvl--;
|
||||
if (lvl >= 0)
|
||||
{
|
||||
if (lvl >= 0) {
|
||||
nodes[lvl] = n;
|
||||
}
|
||||
}
|
||||
return {
|
||||
nodes: nodes,
|
||||
idxs: idxs,
|
||||
nodes,
|
||||
idxs,
|
||||
loc: targetLoc,
|
||||
widthSkips: widthSkips,
|
||||
toString: function() {
|
||||
return "getPoint(" + targetLoc + ")";
|
||||
}
|
||||
widthSkips,
|
||||
toString() {
|
||||
return `getPoint(${targetLoc})`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function _getNodeAtOffset(targetOffset) {
|
||||
var i = 0;
|
||||
var n = start;
|
||||
var lvl = start.levels - 1;
|
||||
while (lvl >= 0 && n.downPtrs[lvl])
|
||||
{
|
||||
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset))
|
||||
{
|
||||
let i = 0;
|
||||
let n = start;
|
||||
let lvl = start.levels - 1;
|
||||
while (lvl >= 0 && n.downPtrs[lvl]) {
|
||||
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset)) {
|
||||
i += n.downSkipWidths[lvl];
|
||||
n = n.downPtrs[lvl];
|
||||
}
|
||||
|
@ -132,31 +126,29 @@ function SkipList() {
|
|||
}
|
||||
|
||||
function _insertKeyAtPoint(point, newKey, entry) {
|
||||
var p = PROFILER("insertKey", false);
|
||||
var newNode = {
|
||||
const p = PROFILER('insertKey', false);
|
||||
const newNode = {
|
||||
key: newKey,
|
||||
levels: 0,
|
||||
upPtrs: [],
|
||||
downPtrs: [],
|
||||
downSkips: [],
|
||||
downSkipWidths: []
|
||||
downSkipWidths: [],
|
||||
};
|
||||
p.mark("donealloc");
|
||||
var pNodes = point.nodes;
|
||||
var pIdxs = point.idxs;
|
||||
var pLoc = point.loc;
|
||||
var widthLoc = point.widthSkips[0] + point.nodes[0].downSkipWidths[0];
|
||||
var newWidth = _entryWidth(entry);
|
||||
p.mark("loop1");
|
||||
p.mark('donealloc');
|
||||
const pNodes = point.nodes;
|
||||
const pIdxs = point.idxs;
|
||||
const pLoc = point.loc;
|
||||
const widthLoc = point.widthSkips[0] + point.nodes[0].downSkipWidths[0];
|
||||
const newWidth = _entryWidth(entry);
|
||||
p.mark('loop1');
|
||||
|
||||
// The new node will have at least level 1
|
||||
// With a proability of 0.01^(n-1) the nodes level will be >= n
|
||||
while (newNode.levels == 0 || Math.random() < 0.01)
|
||||
{
|
||||
while (newNode.levels == 0 || Math.random() < 0.01) {
|
||||
var lvl = newNode.levels;
|
||||
newNode.levels++;
|
||||
if (lvl == pNodes.length)
|
||||
{
|
||||
if (lvl == pNodes.length) {
|
||||
// assume we have just passed the end of point.nodes, and reached one level greater
|
||||
// than the skiplist currently supports
|
||||
pNodes[lvl] = start;
|
||||
|
@ -169,32 +161,31 @@ function SkipList() {
|
|||
start.downSkipWidths[lvl] = totalWidth;
|
||||
point.widthSkips[lvl] = 0;
|
||||
}
|
||||
var me = newNode;
|
||||
const me = newNode;
|
||||
var up = pNodes[lvl];
|
||||
var down = up.downPtrs[lvl];
|
||||
var skip1 = pLoc - pIdxs[lvl];
|
||||
var skip2 = up.downSkips[lvl] + 1 - skip1;
|
||||
const down = up.downPtrs[lvl];
|
||||
const skip1 = pLoc - pIdxs[lvl];
|
||||
const skip2 = up.downSkips[lvl] + 1 - skip1;
|
||||
up.downSkips[lvl] = skip1;
|
||||
up.downPtrs[lvl] = me;
|
||||
me.downSkips[lvl] = skip2;
|
||||
me.upPtrs[lvl] = up;
|
||||
me.downPtrs[lvl] = down;
|
||||
down.upPtrs[lvl] = me;
|
||||
var widthSkip1 = widthLoc - point.widthSkips[lvl];
|
||||
var widthSkip2 = up.downSkipWidths[lvl] + newWidth - widthSkip1;
|
||||
const widthSkip1 = widthLoc - point.widthSkips[lvl];
|
||||
const widthSkip2 = up.downSkipWidths[lvl] + newWidth - widthSkip1;
|
||||
up.downSkipWidths[lvl] = widthSkip1;
|
||||
me.downSkipWidths[lvl] = widthSkip2;
|
||||
}
|
||||
p.mark("loop2");
|
||||
p.literal(pNodes.length, "PNL");
|
||||
for (var lvl = newNode.levels; lvl < pNodes.length; lvl++)
|
||||
{
|
||||
p.mark('loop2');
|
||||
p.literal(pNodes.length, 'PNL');
|
||||
for (var lvl = newNode.levels; lvl < pNodes.length; lvl++) {
|
||||
var up = pNodes[lvl];
|
||||
up.downSkips[lvl]++;
|
||||
up.downSkipWidths[lvl] += newWidth;
|
||||
}
|
||||
p.mark("map");
|
||||
keyToNodeMap['$KEY$' + newKey] = newNode;
|
||||
p.mark('map');
|
||||
keyToNodeMap[`$KEY$${newKey}`] = newNode;
|
||||
numNodes++;
|
||||
totalWidth += newWidth;
|
||||
p.end();
|
||||
|
@ -206,10 +197,8 @@ function SkipList() {
|
|||
|
||||
function _incrementPoint(point) {
|
||||
point.loc++;
|
||||
for (var i = 0; i < point.nodes.length; i++)
|
||||
{
|
||||
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc)
|
||||
{
|
||||
for (let i = 0; i < point.nodes.length; i++) {
|
||||
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc) {
|
||||
point.idxs[i] += point.nodes[i].downSkips[i];
|
||||
point.widthSkips[i] += point.nodes[i].downSkipWidths[i];
|
||||
point.nodes[i] = point.nodes[i].downPtrs[i];
|
||||
|
@ -218,46 +207,40 @@ function SkipList() {
|
|||
}
|
||||
|
||||
function _deleteKeyAtPoint(point) {
|
||||
var elem = point.nodes[0].downPtrs[0];
|
||||
var elemWidth = _entryWidth(elem.entry);
|
||||
for (var i = 0; i < point.nodes.length; i++)
|
||||
{
|
||||
if (i < elem.levels)
|
||||
{
|
||||
const elem = point.nodes[0].downPtrs[0];
|
||||
const elemWidth = _entryWidth(elem.entry);
|
||||
for (let i = 0; i < point.nodes.length; i++) {
|
||||
if (i < elem.levels) {
|
||||
var up = elem.upPtrs[i];
|
||||
var down = elem.downPtrs[i];
|
||||
var totalSkip = up.downSkips[i] + elem.downSkips[i] - 1;
|
||||
const totalSkip = up.downSkips[i] + elem.downSkips[i] - 1;
|
||||
up.downPtrs[i] = down;
|
||||
down.upPtrs[i] = up;
|
||||
up.downSkips[i] = totalSkip;
|
||||
var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
|
||||
const totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
|
||||
up.downSkipWidths[i] = totalWidthSkip;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
var up = point.nodes[i];
|
||||
var down = up.downPtrs[i];
|
||||
up.downSkips[i]--;
|
||||
up.downSkipWidths[i] -= elemWidth;
|
||||
}
|
||||
}
|
||||
delete keyToNodeMap['$KEY$' + elem.key];
|
||||
delete keyToNodeMap[`$KEY$${elem.key}`];
|
||||
numNodes--;
|
||||
totalWidth -= elemWidth;
|
||||
}
|
||||
|
||||
function _propagateWidthChange(node) {
|
||||
var oldWidth = node.downSkipWidths[0];
|
||||
var newWidth = _entryWidth(node.entry);
|
||||
var widthChange = newWidth - oldWidth;
|
||||
var n = node;
|
||||
var lvl = 0;
|
||||
while (lvl < n.levels)
|
||||
{
|
||||
const oldWidth = node.downSkipWidths[0];
|
||||
const newWidth = _entryWidth(node.entry);
|
||||
const widthChange = newWidth - oldWidth;
|
||||
let n = node;
|
||||
let lvl = 0;
|
||||
while (lvl < n.levels) {
|
||||
n.downSkipWidths[lvl] += widthChange;
|
||||
lvl++;
|
||||
while (lvl >= n.levels && n.upPtrs[lvl - 1])
|
||||
{
|
||||
while (lvl >= n.levels && n.upPtrs[lvl - 1]) {
|
||||
n = n.upPtrs[lvl - 1];
|
||||
}
|
||||
}
|
||||
|
@ -265,11 +248,10 @@ function SkipList() {
|
|||
}
|
||||
|
||||
function _getNodeIndex(node, byWidth) {
|
||||
var dist = (byWidth ? 0 : -1);
|
||||
var n = node;
|
||||
while (n !== start)
|
||||
{
|
||||
var lvl = n.levels - 1;
|
||||
let dist = (byWidth ? 0 : -1);
|
||||
let n = node;
|
||||
while (n !== start) {
|
||||
const lvl = n.levels - 1;
|
||||
n = n.upPtrs[lvl];
|
||||
if (byWidth) dist += n.downSkipWidths[lvl];
|
||||
else dist += n.downSkips[lvl];
|
||||
|
@ -278,7 +260,7 @@ function SkipList() {
|
|||
}
|
||||
|
||||
function _getNodeByKey(key) {
|
||||
return keyToNodeMap['$KEY$' + key];
|
||||
return keyToNodeMap[`$KEY$${key}`];
|
||||
}
|
||||
|
||||
// Returns index of first entry such that entryFunc(entry) is truthy,
|
||||
|
@ -287,20 +269,18 @@ function SkipList() {
|
|||
|
||||
|
||||
function _search(entryFunc) {
|
||||
var low = start;
|
||||
var lvl = start.levels - 1;
|
||||
var lowIndex = -1;
|
||||
let low = start;
|
||||
let lvl = start.levels - 1;
|
||||
let lowIndex = -1;
|
||||
|
||||
function f(node) {
|
||||
if (node === start) return false;
|
||||
else if (node === end) return true;
|
||||
else return entryFunc(node.entry);
|
||||
}
|
||||
while (lvl >= 0)
|
||||
{
|
||||
var nextLow = low.downPtrs[lvl];
|
||||
while (!f(nextLow))
|
||||
{
|
||||
while (lvl >= 0) {
|
||||
let nextLow = low.downPtrs[lvl];
|
||||
while (!f(nextLow)) {
|
||||
lowIndex += low.downSkips[lvl];
|
||||
low = nextLow;
|
||||
nextLow = low.downPtrs[lvl];
|
||||
|
@ -310,54 +290,51 @@ function SkipList() {
|
|||
return lowIndex + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
The skip-list contains "entries", JavaScript objects that each must have a unique "key" property
|
||||
that is a string.
|
||||
*/
|
||||
var self = this;
|
||||
const self = this;
|
||||
_.extend(this, {
|
||||
length: function() {
|
||||
length() {
|
||||
return numNodes;
|
||||
},
|
||||
atIndex: function(i) {
|
||||
if (i < 0) console.warn("atIndex(" + i + ")");
|
||||
if (i >= numNodes) console.warn("atIndex(" + i + ">=" + numNodes + ")");
|
||||
atIndex(i) {
|
||||
if (i < 0) console.warn(`atIndex(${i})`);
|
||||
if (i >= numNodes) console.warn(`atIndex(${i}>=${numNodes})`);
|
||||
return _getNodeAtPoint(_getPoint(i)).entry;
|
||||
},
|
||||
// differs from Array.splice() in that new elements are in an array, not varargs
|
||||
splice: function(start, deleteCount, newEntryArray) {
|
||||
if (start < 0) console.warn("splice(" + start + ", ...)");
|
||||
if (start + deleteCount > numNodes)
|
||||
{
|
||||
console.warn("splice(" + start + ", " + deleteCount + ", ...), N=" + numNodes);
|
||||
console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes);
|
||||
splice(start, deleteCount, newEntryArray) {
|
||||
if (start < 0) console.warn(`splice(${start}, ...)`);
|
||||
if (start + deleteCount > numNodes) {
|
||||
console.warn(`splice(${start}, ${deleteCount}, ...), N=${numNodes}`);
|
||||
console.warn('%s %s %s', typeof start, typeof deleteCount, typeof numNodes);
|
||||
console.trace();
|
||||
}
|
||||
|
||||
if (!newEntryArray) newEntryArray = [];
|
||||
var pt = _getPoint(start);
|
||||
for (var i = 0; i < deleteCount; i++)
|
||||
{
|
||||
const pt = _getPoint(start);
|
||||
for (var i = 0; i < deleteCount; i++) {
|
||||
_deleteKeyAtPoint(pt);
|
||||
}
|
||||
for (var i = (newEntryArray.length - 1); i >= 0; i--)
|
||||
{
|
||||
var entry = newEntryArray[i];
|
||||
for (var i = (newEntryArray.length - 1); i >= 0; i--) {
|
||||
const entry = newEntryArray[i];
|
||||
_insertKeyAtPoint(pt, entry.key, entry);
|
||||
var node = _getNodeByKey(entry.key);
|
||||
const node = _getNodeByKey(entry.key);
|
||||
node.entry = entry;
|
||||
}
|
||||
},
|
||||
next: function(entry) {
|
||||
next(entry) {
|
||||
return _getNodeByKey(entry.key).downPtrs[0].entry || null;
|
||||
},
|
||||
prev: function(entry) {
|
||||
prev(entry) {
|
||||
return _getNodeByKey(entry.key).upPtrs[0].entry || null;
|
||||
},
|
||||
push: function(entry) {
|
||||
push(entry) {
|
||||
self.splice(numNodes, 0, [entry]);
|
||||
},
|
||||
slice: function(start, end) {
|
||||
slice(start, end) {
|
||||
// act like Array.slice()
|
||||
if (start === undefined) start = 0;
|
||||
else if (start < 0) start += numNodes;
|
||||
|
@ -371,65 +348,64 @@ that is a string.
|
|||
|
||||
dmesg(String([start, end, numNodes]));
|
||||
if (end <= start) return [];
|
||||
var n = self.atIndex(start);
|
||||
var array = [n];
|
||||
for (var i = 1; i < (end - start); i++)
|
||||
{
|
||||
let n = self.atIndex(start);
|
||||
const array = [n];
|
||||
for (let i = 1; i < (end - start); i++) {
|
||||
n = self.next(n);
|
||||
array.push(n);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
atKey: function(key) {
|
||||
atKey(key) {
|
||||
return _getNodeByKey(key).entry;
|
||||
},
|
||||
indexOfKey: function(key) {
|
||||
indexOfKey(key) {
|
||||
return _getNodeIndex(_getNodeByKey(key));
|
||||
},
|
||||
indexOfEntry: function(entry) {
|
||||
indexOfEntry(entry) {
|
||||
return self.indexOfKey(entry.key);
|
||||
},
|
||||
containsKey: function(key) {
|
||||
containsKey(key) {
|
||||
return !!(_getNodeByKey(key));
|
||||
},
|
||||
// gets the last entry starting at or before the offset
|
||||
atOffset: function(offset) {
|
||||
atOffset(offset) {
|
||||
return _getNodeAtOffset(offset).entry;
|
||||
},
|
||||
keyAtOffset: function(offset) {
|
||||
keyAtOffset(offset) {
|
||||
return self.atOffset(offset).key;
|
||||
},
|
||||
offsetOfKey: function(key) {
|
||||
offsetOfKey(key) {
|
||||
return _getNodeIndex(_getNodeByKey(key), true);
|
||||
},
|
||||
offsetOfEntry: function(entry) {
|
||||
offsetOfEntry(entry) {
|
||||
return self.offsetOfKey(entry.key);
|
||||
},
|
||||
setEntryWidth: function(entry, width) {
|
||||
setEntryWidth(entry, width) {
|
||||
entry.width = width;
|
||||
_propagateWidthChange(_getNodeByKey(entry.key));
|
||||
},
|
||||
totalWidth: function() {
|
||||
totalWidth() {
|
||||
return totalWidth;
|
||||
},
|
||||
offsetOfIndex: function(i) {
|
||||
offsetOfIndex(i) {
|
||||
if (i < 0) return 0;
|
||||
if (i >= numNodes) return totalWidth;
|
||||
return self.offsetOfEntry(self.atIndex(i));
|
||||
},
|
||||
indexOfOffset: function(offset) {
|
||||
indexOfOffset(offset) {
|
||||
if (offset <= 0) return 0;
|
||||
if (offset >= totalWidth) return numNodes;
|
||||
return self.indexOfEntry(self.atOffset(offset));
|
||||
},
|
||||
search: function(entryFunc) {
|
||||
search(entryFunc) {
|
||||
return _search(entryFunc);
|
||||
},
|
||||
//debugToString: _debugToString,
|
||||
// debugToString: _debugToString,
|
||||
debugGetPoint: _getPoint,
|
||||
debugDepth: function() {
|
||||
debugDepth() {
|
||||
return start.levels;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -25,80 +25,74 @@
|
|||
require('./jquery');
|
||||
|
||||
const Cookies = require('./pad_utils').Cookies;
|
||||
var randomString = require('./pad_utils').randomString;
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
const randomString = require('./pad_utils').randomString;
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
var token, padId, export_links;
|
||||
let token, padId, export_links;
|
||||
|
||||
function init() {
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
// start the custom js
|
||||
if (typeof customStart == "function") customStart();
|
||||
if (typeof customStart === 'function') customStart();
|
||||
|
||||
//get the padId out of the url
|
||||
var urlParts= document.location.pathname.split("/");
|
||||
padId = decodeURIComponent(urlParts[urlParts.length-2]);
|
||||
// get the padId out of the url
|
||||
const urlParts = document.location.pathname.split('/');
|
||||
padId = decodeURIComponent(urlParts[urlParts.length - 2]);
|
||||
|
||||
//set the title
|
||||
document.title = padId.replace(/_+/g, ' ') + " | " + document.title;
|
||||
// set the title
|
||||
document.title = `${padId.replace(/_+/g, ' ')} | ${document.title}`;
|
||||
|
||||
//ensure we have a token
|
||||
// ensure we have a token
|
||||
token = Cookies.get('token');
|
||||
if(token == null)
|
||||
{
|
||||
token = "t." + randomString();
|
||||
if (token == null) {
|
||||
token = `t.${randomString()}`;
|
||||
Cookies.set('token', token, {expires: 60});
|
||||
}
|
||||
|
||||
var loc = document.location;
|
||||
//get the correct port
|
||||
var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port;
|
||||
//create the url
|
||||
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
|
||||
//find out in which subfolder we are
|
||||
var resource = exports.baseURL.substring(1) + 'socket.io';
|
||||
const loc = document.location;
|
||||
// get the correct port
|
||||
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
|
||||
// create the url
|
||||
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
|
||||
// find out in which subfolder we are
|
||||
const resource = `${exports.baseURL.substring(1)}socket.io`;
|
||||
|
||||
//build up the socket io connection
|
||||
socket = io.connect(url, {path: exports.baseURL + 'socket.io', resource: resource});
|
||||
// build up the socket io connection
|
||||
socket = io.connect(url, {path: `${exports.baseURL}socket.io`, resource});
|
||||
|
||||
//send the ready message once we're connected
|
||||
socket.on('connect', function() {
|
||||
sendSocketMsg("CLIENT_READY", {});
|
||||
// send the ready message once we're connected
|
||||
socket.on('connect', () => {
|
||||
sendSocketMsg('CLIENT_READY', {});
|
||||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
socket.on('disconnect', () => {
|
||||
BroadcastSlider.showReconnectUI();
|
||||
});
|
||||
|
||||
//route the incoming messages
|
||||
socket.on('message', function(message) {
|
||||
if(message.type == "CLIENT_VARS")
|
||||
{
|
||||
// route the incoming messages
|
||||
socket.on('message', (message) => {
|
||||
if (message.type == 'CLIENT_VARS') {
|
||||
handleClientVars(message);
|
||||
}
|
||||
else if(message.accessStatus)
|
||||
{
|
||||
$("body").html("<h2>You have no permission to access this pad</h2>")
|
||||
} else {
|
||||
if(message.type === 'CHANGESET_REQ') changesetLoader.handleMessageFromServer(message);
|
||||
}
|
||||
} else if (message.accessStatus) {
|
||||
$('body').html('<h2>You have no permission to access this pad</h2>');
|
||||
} else if (message.type === 'CHANGESET_REQ') { changesetLoader.handleMessageFromServer(message); }
|
||||
});
|
||||
|
||||
//get all the export links
|
||||
export_links = $('#export > .exportlink')
|
||||
// get all the export links
|
||||
export_links = $('#export > .exportlink');
|
||||
|
||||
$('button#forcereconnect').click(function() {
|
||||
$('button#forcereconnect').click(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
exports.socket = socket; // make the socket available
|
||||
exports.BroadcastSlider = BroadcastSlider; // Make the slider available
|
||||
|
||||
hooks.aCallAll("postTimesliderInit");
|
||||
hooks.aCallAll('postTimesliderInit');
|
||||
});
|
||||
}
|
||||
|
||||
//sends a message over the socket
|
||||
// sends a message over the socket
|
||||
function sendSocketMsg(type, data) {
|
||||
socket.json.send({
|
||||
component: 'pad', // FIXME: Remove this stupidity!
|
||||
|
@ -111,55 +105,54 @@ function sendSocketMsg(type, data) {
|
|||
});
|
||||
}
|
||||
|
||||
var fireWhenAllScriptsAreLoaded = [];
|
||||
const fireWhenAllScriptsAreLoaded = [];
|
||||
|
||||
var changesetLoader;
|
||||
let changesetLoader;
|
||||
function handleClientVars(message) {
|
||||
//save the client Vars
|
||||
// save the client Vars
|
||||
clientVars = message.data;
|
||||
|
||||
//load all script that doesn't work without the clientVars
|
||||
// load all script that doesn't work without the clientVars
|
||||
BroadcastSlider = require('./broadcast_slider').loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded);
|
||||
require('./broadcast_revisions').loadBroadcastRevisionsJS();
|
||||
changesetLoader = require('./broadcast').loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider);
|
||||
|
||||
//initialize export ui
|
||||
// initialize export ui
|
||||
require('./pad_impexp').padimpexp.init();
|
||||
|
||||
// Create a base URI used for timeslider exports
|
||||
var baseURI = document.location.pathname;
|
||||
const baseURI = document.location.pathname;
|
||||
|
||||
//change export urls when the slider moves
|
||||
BroadcastSlider.onSlider(function(revno) {
|
||||
// change export urls when the slider moves
|
||||
BroadcastSlider.onSlider((revno) => {
|
||||
// export_links is a jQuery Array, so .each is allowed.
|
||||
export_links.each(function() {
|
||||
export_links.each(function () {
|
||||
// Modified from regular expression to fix:
|
||||
// https://github.com/ether/etherpad-lite/issues/4071
|
||||
// Where a padId that was numeric would create the wrong export link
|
||||
if(this.href){
|
||||
var type = this.href.split('export/')[1];
|
||||
var href = baseURI.split('timeslider')[0];
|
||||
href += revno + '/export/' + type;
|
||||
if (this.href) {
|
||||
const type = this.href.split('export/')[1];
|
||||
let href = baseURI.split('timeslider')[0];
|
||||
href += `${revno}/export/${type}`;
|
||||
this.setAttribute('href', href);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//fire all start functions of these scripts, formerly fired with window.load
|
||||
for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++)
|
||||
{
|
||||
// fire all start functions of these scripts, formerly fired with window.load
|
||||
for (let i = 0; i < fireWhenAllScriptsAreLoaded.length; i++) {
|
||||
fireWhenAllScriptsAreLoaded[i]();
|
||||
}
|
||||
$("#ui-slider-handle").css('left', $("#ui-slider-bar").width() - 2);
|
||||
$('#ui-slider-handle').css('left', $('#ui-slider-bar').width() - 2);
|
||||
|
||||
// Translate some strings where we only want to set the title not the actual values
|
||||
$('#playpause_button_icon').attr("title", html10n.get("timeslider.playPause"));
|
||||
$('#leftstep').attr("title", html10n.get("timeslider.backRevision"));
|
||||
$('#rightstep').attr("title", html10n.get("timeslider.forwardRevision"));
|
||||
$('#playpause_button_icon').attr('title', html10n.get('timeslider.playPause'));
|
||||
$('#leftstep').attr('title', html10n.get('timeslider.backRevision'));
|
||||
$('#rightstep').attr('title', html10n.get('timeslider.forwardRevision'));
|
||||
|
||||
// font family change
|
||||
$("#viewfontmenu").change(function(){
|
||||
$('#innerdocbody').css("font-family", $(this).val() || "");
|
||||
$('#viewfontmenu').change(function () {
|
||||
$('#innerdocbody').css('font-family', $(this).val() || '');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -20,96 +20,85 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require('./Changeset');
|
||||
var _ = require('./underscore');
|
||||
const Changeset = require('./Changeset');
|
||||
const _ = require('./underscore');
|
||||
|
||||
var undoModule = (function() {
|
||||
var stack = (function() {
|
||||
var stackElements = [];
|
||||
var undoModule = (function () {
|
||||
const stack = (function () {
|
||||
const stackElements = [];
|
||||
// two types of stackElements:
|
||||
// 1) { elementType: UNDOABLE_EVENT, eventType: "anything", [backset: <changeset>,]
|
||||
// [selStart: <char number>, selEnd: <char number>, selFocusAtStart: <boolean>] }
|
||||
// 2) { elementType: EXTERNAL_CHANGE, changeset: <changeset> }
|
||||
// invariant: no two consecutive EXTERNAL_CHANGEs
|
||||
var numUndoableEvents = 0;
|
||||
let numUndoableEvents = 0;
|
||||
|
||||
var UNDOABLE_EVENT = "undoableEvent";
|
||||
var EXTERNAL_CHANGE = "externalChange";
|
||||
const UNDOABLE_EVENT = 'undoableEvent';
|
||||
const EXTERNAL_CHANGE = 'externalChange';
|
||||
|
||||
function clearStack() {
|
||||
stackElements.length = 0;
|
||||
stackElements.push(
|
||||
{
|
||||
elementType: UNDOABLE_EVENT,
|
||||
eventType: "bottom"
|
||||
});
|
||||
{
|
||||
elementType: UNDOABLE_EVENT,
|
||||
eventType: 'bottom',
|
||||
});
|
||||
numUndoableEvents = 1;
|
||||
}
|
||||
clearStack();
|
||||
|
||||
function pushEvent(event) {
|
||||
var e = _.extend(
|
||||
{}, event);
|
||||
const e = _.extend(
|
||||
{}, event);
|
||||
e.elementType = UNDOABLE_EVENT;
|
||||
stackElements.push(e);
|
||||
numUndoableEvents++;
|
||||
//dmesg("pushEvent backset: "+event.backset);
|
||||
// dmesg("pushEvent backset: "+event.backset);
|
||||
}
|
||||
|
||||
function pushExternalChange(cs) {
|
||||
var idx = stackElements.length - 1;
|
||||
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
|
||||
{
|
||||
const idx = stackElements.length - 1;
|
||||
if (stackElements[idx].elementType == EXTERNAL_CHANGE) {
|
||||
stackElements[idx].changeset = Changeset.compose(stackElements[idx].changeset, cs, getAPool());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
stackElements.push(
|
||||
{
|
||||
elementType: EXTERNAL_CHANGE,
|
||||
changeset: cs
|
||||
});
|
||||
{
|
||||
elementType: EXTERNAL_CHANGE,
|
||||
changeset: cs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _exposeEvent(nthFromTop) {
|
||||
// precond: 0 <= nthFromTop < numUndoableEvents
|
||||
var targetIndex = stackElements.length - 1 - nthFromTop;
|
||||
var idx = stackElements.length - 1;
|
||||
while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE)
|
||||
{
|
||||
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
|
||||
{
|
||||
var ex = stackElements[idx];
|
||||
var un = stackElements[idx - 1];
|
||||
if (un.backset)
|
||||
{
|
||||
var excs = ex.changeset;
|
||||
var unbs = un.backset;
|
||||
const targetIndex = stackElements.length - 1 - nthFromTop;
|
||||
let idx = stackElements.length - 1;
|
||||
while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE) {
|
||||
if (stackElements[idx].elementType == EXTERNAL_CHANGE) {
|
||||
const ex = stackElements[idx];
|
||||
const un = stackElements[idx - 1];
|
||||
if (un.backset) {
|
||||
const excs = ex.changeset;
|
||||
const unbs = un.backset;
|
||||
un.backset = Changeset.follow(excs, un.backset, false, getAPool());
|
||||
ex.changeset = Changeset.follow(unbs, ex.changeset, true, getAPool());
|
||||
if ((typeof un.selStart) == "number")
|
||||
{
|
||||
var newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd);
|
||||
if ((typeof un.selStart) === 'number') {
|
||||
const newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd);
|
||||
un.selStart = newSel[0];
|
||||
un.selEnd = newSel[1];
|
||||
if (un.selStart == un.selEnd)
|
||||
{
|
||||
if (un.selStart == un.selEnd) {
|
||||
un.selFocusAtStart = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
stackElements[idx - 1] = ex;
|
||||
stackElements[idx] = un;
|
||||
if (idx >= 2 && stackElements[idx - 2].elementType == EXTERNAL_CHANGE)
|
||||
{
|
||||
if (idx >= 2 && stackElements[idx - 2].elementType == EXTERNAL_CHANGE) {
|
||||
ex.changeset = Changeset.compose(stackElements[idx - 2].changeset, ex.changeset, getAPool());
|
||||
stackElements.splice(idx - 2, 1);
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
idx--;
|
||||
}
|
||||
}
|
||||
|
@ -133,17 +122,17 @@ var undoModule = (function() {
|
|||
}
|
||||
|
||||
return {
|
||||
numEvents: numEvents,
|
||||
popEvent: popEvent,
|
||||
pushEvent: pushEvent,
|
||||
pushExternalChange: pushExternalChange,
|
||||
clearStack: clearStack,
|
||||
getNthFromTop: getNthFromTop
|
||||
numEvents,
|
||||
popEvent,
|
||||
pushEvent,
|
||||
pushExternalChange,
|
||||
clearStack,
|
||||
getNthFromTop,
|
||||
};
|
||||
})();
|
||||
|
||||
// invariant: stack always has at least one undoable event
|
||||
var undoPtr = 0; // zero-index from top of stack, 0 == top
|
||||
let undoPtr = 0; // zero-index from top of stack, 0 == top
|
||||
|
||||
function clearHistory() {
|
||||
stack.clearStack();
|
||||
|
@ -151,13 +140,11 @@ var undoModule = (function() {
|
|||
}
|
||||
|
||||
function _charOccurrences(str, c) {
|
||||
var i = 0;
|
||||
var count = 0;
|
||||
while (i >= 0 && i < str.length)
|
||||
{
|
||||
let i = 0;
|
||||
let count = 0;
|
||||
while (i >= 0 && i < str.length) {
|
||||
i = str.indexOf(c, i);
|
||||
if (i >= 0)
|
||||
{
|
||||
if (i >= 0) {
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
|
@ -179,27 +166,22 @@ var undoModule = (function() {
|
|||
// A little weird in that it won't merge "make bold" with "insert char"
|
||||
// but will merge "make bold and insert char" with "insert char",
|
||||
// though that isn't expected to come up.
|
||||
var plusCount1 = _opcodeOccurrences(cs1, '+');
|
||||
var plusCount2 = _opcodeOccurrences(cs2, '+');
|
||||
var minusCount1 = _opcodeOccurrences(cs1, '-');
|
||||
var minusCount2 = _opcodeOccurrences(cs2, '-');
|
||||
if (plusCount1 == 1 && plusCount2 == 1 && minusCount1 == 0 && minusCount2 == 0)
|
||||
{
|
||||
const plusCount1 = _opcodeOccurrences(cs1, '+');
|
||||
const plusCount2 = _opcodeOccurrences(cs2, '+');
|
||||
const minusCount1 = _opcodeOccurrences(cs1, '-');
|
||||
const minusCount2 = _opcodeOccurrences(cs2, '-');
|
||||
if (plusCount1 == 1 && plusCount2 == 1 && minusCount1 == 0 && minusCount2 == 0) {
|
||||
var merge = Changeset.compose(cs1, cs2, getAPool());
|
||||
var plusCount3 = _opcodeOccurrences(merge, '+');
|
||||
var minusCount3 = _opcodeOccurrences(merge, '-');
|
||||
if (plusCount3 == 1 && minusCount3 == 0)
|
||||
{
|
||||
if (plusCount3 == 1 && minusCount3 == 0) {
|
||||
return merge;
|
||||
}
|
||||
}
|
||||
else if (plusCount1 == 0 && plusCount2 == 0 && minusCount1 == 1 && minusCount2 == 1)
|
||||
{
|
||||
} else if (plusCount1 == 0 && plusCount2 == 0 && minusCount1 == 1 && minusCount2 == 1) {
|
||||
var merge = Changeset.compose(cs1, cs2, getAPool());
|
||||
var plusCount3 = _opcodeOccurrences(merge, '+');
|
||||
var minusCount3 = _opcodeOccurrences(merge, '-');
|
||||
if (plusCount3 == 0 && minusCount3 == 1)
|
||||
{
|
||||
if (plusCount3 == 0 && minusCount3 == 1) {
|
||||
return merge;
|
||||
}
|
||||
}
|
||||
|
@ -207,70 +189,58 @@ var undoModule = (function() {
|
|||
}
|
||||
|
||||
function reportEvent(event) {
|
||||
var topEvent = stack.getNthFromTop(0);
|
||||
const topEvent = stack.getNthFromTop(0);
|
||||
|
||||
function applySelectionToTop() {
|
||||
if ((typeof event.selStart) == "number")
|
||||
{
|
||||
if ((typeof event.selStart) === 'number') {
|
||||
topEvent.selStart = event.selStart;
|
||||
topEvent.selEnd = event.selEnd;
|
||||
topEvent.selFocusAtStart = event.selFocusAtStart;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!event.backset) || Changeset.isIdentity(event.backset))
|
||||
{
|
||||
if ((!event.backset) || Changeset.isIdentity(event.backset)) {
|
||||
applySelectionToTop();
|
||||
}
|
||||
else
|
||||
{
|
||||
var merged = false;
|
||||
if (topEvent.eventType == event.eventType)
|
||||
{
|
||||
var merge = _mergeChangesets(event.backset, topEvent.backset);
|
||||
if (merge)
|
||||
{
|
||||
} else {
|
||||
let merged = false;
|
||||
if (topEvent.eventType == event.eventType) {
|
||||
const merge = _mergeChangesets(event.backset, topEvent.backset);
|
||||
if (merge) {
|
||||
topEvent.backset = merge;
|
||||
//dmesg("reportEvent merge: "+merge);
|
||||
// dmesg("reportEvent merge: "+merge);
|
||||
applySelectionToTop();
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
if (!merged)
|
||||
{
|
||||
if (!merged) {
|
||||
/*
|
||||
* Push the event on the undo stack only if it exists, and if it's
|
||||
* not a "clearauthorship". This disallows undoing the removal of the
|
||||
* authorship colors, but is a necessary stopgap measure against
|
||||
* https://github.com/ether/etherpad-lite/issues/2802
|
||||
*/
|
||||
if (event && (event.eventType !== "clearauthorship")) {
|
||||
if (event && (event.eventType !== 'clearauthorship')) {
|
||||
stack.pushEvent(event);
|
||||
}
|
||||
}
|
||||
undoPtr = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function reportExternalChange(changeset) {
|
||||
if (changeset && !Changeset.isIdentity(changeset))
|
||||
{
|
||||
if (changeset && !Changeset.isIdentity(changeset)) {
|
||||
stack.pushExternalChange(changeset);
|
||||
}
|
||||
}
|
||||
|
||||
function _getSelectionInfo(event) {
|
||||
if ((typeof event.selStart) != "number")
|
||||
{
|
||||
if ((typeof event.selStart) !== 'number') {
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return {
|
||||
selStart: event.selStart,
|
||||
selEnd: event.selEnd,
|
||||
selFocusAtStart: event.selFocusAtStart
|
||||
selFocusAtStart: event.selFocusAtStart,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -282,27 +252,23 @@ var undoModule = (function() {
|
|||
// "eventFunc" will be called exactly once.
|
||||
|
||||
function performUndo(eventFunc) {
|
||||
if (undoPtr < stack.numEvents() - 1)
|
||||
{
|
||||
var backsetEvent = stack.getNthFromTop(undoPtr);
|
||||
var selectionEvent = stack.getNthFromTop(undoPtr + 1);
|
||||
var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
|
||||
if (undoPtr < stack.numEvents() - 1) {
|
||||
const backsetEvent = stack.getNthFromTop(undoPtr);
|
||||
const selectionEvent = stack.getNthFromTop(undoPtr + 1);
|
||||
const undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
|
||||
stack.pushEvent(undoEvent);
|
||||
undoPtr += 2;
|
||||
}
|
||||
else eventFunc();
|
||||
} else { eventFunc(); }
|
||||
}
|
||||
|
||||
function performRedo(eventFunc) {
|
||||
if (undoPtr >= 2)
|
||||
{
|
||||
var backsetEvent = stack.getNthFromTop(0);
|
||||
var selectionEvent = stack.getNthFromTop(1);
|
||||
if (undoPtr >= 2) {
|
||||
const backsetEvent = stack.getNthFromTop(0);
|
||||
const selectionEvent = stack.getNthFromTop(1);
|
||||
eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
|
||||
stack.popEvent();
|
||||
undoPtr -= 2;
|
||||
}
|
||||
else eventFunc();
|
||||
} else { eventFunc(); }
|
||||
}
|
||||
|
||||
function getAPool() {
|
||||
|
@ -310,13 +276,13 @@ var undoModule = (function() {
|
|||
}
|
||||
|
||||
return {
|
||||
clearHistory: clearHistory,
|
||||
reportEvent: reportEvent,
|
||||
reportExternalChange: reportExternalChange,
|
||||
performUndo: performUndo,
|
||||
performRedo: performRedo,
|
||||
clearHistory,
|
||||
reportEvent,
|
||||
reportExternalChange,
|
||||
performUndo,
|
||||
performRedo,
|
||||
enabled: true,
|
||||
apool: null
|
||||
apool: null,
|
||||
}; // apool is filled in by caller
|
||||
})();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue