lint: Run eslint --fix on src/

This commit is contained in:
Richard Hansen 2020-11-23 13:24:19 -05:00 committed by John McLear
parent b8d07a42eb
commit 8e5fd19db2
109 changed files with 9061 additions and 10572 deletions

View file

@ -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;

View file

@ -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

View file

@ -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]);
}
};

View file

@ -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">&nbsp;</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(''));

View file

@ -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

View file

@ -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);
});

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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>&nbsp;</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;
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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

View file

@ -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}`;
},
};
}

View file

@ -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 += '&nbsp;';
}
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, '&nbsp;');
}
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] = '&nbsp;';
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] = '&nbsp;';
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] = '&nbsp;';
}
}

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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,
};
};

View file

@ -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;
}());

View file

@ -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('/'));
}
});
}

View file

@ -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;
}());

View file

@ -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;
}());

View file

@ -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;
}());

View file

@ -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;
}
};

View file

@ -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) + '">&nbsp;</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)}">&nbsp;</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
}
}

View file

@ -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;

View file

@ -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.');
}
}

View file

@ -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,

View file

@ -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];

View file

@ -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],
);
}

View file

@ -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;
}
}

View file

@ -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;
}
};

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}
};

View file

@ -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'));
}

View file

@ -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;
}
},
});
}

View file

@ -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() || '');
});
}

View file

@ -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
})();