mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-19 14:13:34 +01:00
Merge branch 'develop' into mochawesome
This commit is contained in:
commit
7630e71957
75 changed files with 1347 additions and 1020 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,4 +1,8 @@
|
|||
# Changes for the next release
|
||||
|
||||
### Compatibility changes
|
||||
* Node.js 10.17.0 or newer is now required.
|
||||
|
||||
### Notable new features
|
||||
* Database performance is significantly improved.
|
||||
|
||||
|
@ -118,7 +122,7 @@
|
|||
* MINOR: Fix ?showChat URL param issue
|
||||
* MINOR: Issue where timeslider URI fails to be correct if padID is numeric
|
||||
* MINOR: Include prompt for clear authorship when entire document is selected
|
||||
* MINOR: Include full document aText every 100 revisions to make pad restoration on database curruption achievable
|
||||
* MINOR: Include full document aText every 100 revisions to make pad restoration on database corruption achievable
|
||||
* MINOR: Several Colibris CSS fixes
|
||||
* MINOR: Use mime library for mime types instead of hard-coded.
|
||||
* MINOR: Don't show "new pad button" if instance is read only
|
||||
|
@ -368,7 +372,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
# 1.5.3
|
||||
* NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts
|
||||
* NEW: API endpoint for Append Chat Message and Chat Backend Tests
|
||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be supressed)
|
||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be suppressed)
|
||||
* NEW: Content Collector can handle key values
|
||||
* NEW: getAttributesOnPosition Method
|
||||
* FIX: Firefox keeps attributes (bold etc) on cut/copy -> paste
|
||||
|
@ -437,7 +441,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Timeslider UI Fix
|
||||
* Fix: Remove Dokuwiki
|
||||
* Fix: Remove long paths from windows build (stops error during extract)
|
||||
* Fix: Various globals remvoed
|
||||
* Fix: Various globals removed
|
||||
* Fix: Move all scripts into bin/
|
||||
* Fix: Various CSS bugfixes for Mobile devices
|
||||
* Fix: Overflow Toolbar
|
||||
|
@ -513,7 +517,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* FIX: HTML import (don't crash on malformed or blank HTML input; strip title out of html during import)
|
||||
* FIX: check if uploaded file only contains ascii chars when abiword disabled
|
||||
* FIX: Plugin search in /admin/plugins
|
||||
* FIX: Don't create new pad if a non-existant read-only pad is accessed
|
||||
* FIX: Don't create new pad if a non-existent read-only pad is accessed
|
||||
* FIX: Drop messages from unknown connections (would lead to a crash after a restart)
|
||||
* FIX: API: fix createGroupFor endpoint, if mapped group is deleted
|
||||
* FIX: Import form for other locales
|
||||
|
@ -530,7 +534,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* NEW: Bump log4js for improved logging
|
||||
* Fix: Remove URL schemes which don't have RFC standard
|
||||
* Fix: Fix safeRun subsequent restarts issue
|
||||
* Fix: Allow safeRun to pass arguements to run.sh
|
||||
* Fix: Allow safeRun to pass arguments to run.sh
|
||||
* Fix: Include script for more efficient import
|
||||
* Fix: Fix sysv comptibile script
|
||||
* Fix: Fix client side changeset spamming
|
||||
|
@ -569,7 +573,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Support Node 0.10
|
||||
* Fix: Log HTTP on DEBUG log level
|
||||
* Fix: Server wont crash on import fails on 0 file import.
|
||||
* Fix: Import no longer fails consistantly
|
||||
* Fix: Import no longer fails consistently
|
||||
* Fix: Language support for non existing languages
|
||||
* Fix: Mobile support for chat notifications are now usable
|
||||
* Fix: Re-Enable Editbar buttons on reconnect
|
||||
|
@ -601,7 +605,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* NEW: Admin dashboard mobile device support and new hooks for Admin dashboard
|
||||
* NEW: Get current API version from API
|
||||
* NEW: CLI script to delete pads
|
||||
* Fix: Automatic client reconnection on disonnect
|
||||
* Fix: Automatic client reconnection on disconnect
|
||||
* Fix: Text Export indentation now supports multiple indentations
|
||||
* Fix: Bugfix getChatHistory API method
|
||||
* Fix: Stop Chrome losing caret after paste is texted
|
||||
|
@ -621,7 +625,7 @@ finally put them back in their new location, uder `src/static/skins/no-skin`.
|
|||
* Fix: Stop Opera browser inserting two new lines on enter keypress
|
||||
* Fix: Stop timeslider from showing NaN on pads with only one revision
|
||||
* Other: Allow timeslider tests to run and provide & fix various other frontend-tests
|
||||
* Other: Begin dropping referene to Lite. Etherpad Lite is now named "Etherpad"
|
||||
* Other: Begin dropping reference to Lite. Etherpad Lite is now named "Etherpad"
|
||||
* Other: Update to latest jQuery
|
||||
* Other: Change loading message asking user to please wait on first build
|
||||
* Other: Allow etherpad to use global npm installation (Safe since node 6.3)
|
||||
|
|
|
@ -13,7 +13,7 @@ Etherpad is a real-time collaborative editor [scalable to thousands of simultane
|
|||
# Installation
|
||||
|
||||
## Requirements
|
||||
- `nodejs` >= **10.13.0**.
|
||||
- `nodejs` >= **10.17.0**.
|
||||
|
||||
## GNU/Linux and other UNIX-like systems
|
||||
|
||||
|
@ -25,7 +25,7 @@ git clone --branch master https://github.com/ether/etherpad-lite.git && cd ether
|
|||
```
|
||||
|
||||
### Manual install
|
||||
You'll need git and [node.js](https://nodejs.org) installed (minimum required Node version: **10.13.0**).
|
||||
You'll need git and [node.js](https://nodejs.org) installed (minimum required Node version: **10.17.0**).
|
||||
|
||||
**As any user (we recommend creating a separate user called etherpad):**
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ const util = require('util');
|
|||
|
||||
let atext = Changeset.makeAText('\n');
|
||||
|
||||
// run trough all revisions
|
||||
// run through all revisions
|
||||
for (const revNum of revisions) {
|
||||
// console.log('Fetching', revNum)
|
||||
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
# Source constants and usefull functions
|
||||
# Source constants and useful functions
|
||||
. bin/functions.sh
|
||||
|
||||
#Was this script started in the bin folder? if yes move out
|
||||
|
|
391
bin/convert.js
391
bin/convert.js
|
@ -1,391 +0,0 @@
|
|||
const startTime = Date.now();
|
||||
const fs = require('fs');
|
||||
const ueberDB = require('../src/node_modules/ueberdb2');
|
||||
const mysql = require('../src/node_modules/ueberdb2/node_modules/mysql');
|
||||
const async = require('../src/node_modules/async');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
|
||||
|
||||
const settingsFile = process.argv[2];
|
||||
const sqlOutputFile = process.argv[3];
|
||||
|
||||
// stop if the settings file is not set
|
||||
if (!settingsFile || !sqlOutputFile) {
|
||||
console.error('Use: node convert.js $SETTINGSFILE $SQLOUTPUT');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('read settings file...');
|
||||
// read the settings file and parse the json
|
||||
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
||||
log('done');
|
||||
|
||||
log('open output file...');
|
||||
const sqlOutput = fs.openSync(sqlOutputFile, 'w');
|
||||
const sql = 'SET CHARACTER SET UTF8;\n' +
|
||||
'CREATE TABLE IF NOT EXISTS `store` ( \n' +
|
||||
'`key` VARCHAR( 100 ) NOT NULL , \n' +
|
||||
'`value` LONGTEXT NOT NULL , \n' +
|
||||
'PRIMARY KEY ( `key` ) \n' +
|
||||
') ENGINE = INNODB;\n' +
|
||||
'START TRANSACTION;\n\n';
|
||||
fs.writeSync(sqlOutput, sql);
|
||||
log('done');
|
||||
|
||||
const etherpadDB = mysql.createConnection({
|
||||
host: settings.etherpadDB.host,
|
||||
user: settings.etherpadDB.user,
|
||||
password: settings.etherpadDB.password,
|
||||
database: settings.etherpadDB.database,
|
||||
port: settings.etherpadDB.port,
|
||||
});
|
||||
|
||||
// get the timestamp once
|
||||
const timestamp = Date.now();
|
||||
|
||||
let padIDs;
|
||||
|
||||
async.series([
|
||||
// get all padids out of the database...
|
||||
function (callback) {
|
||||
log('get all padIds out of the database...');
|
||||
|
||||
etherpadDB.query('SELECT ID FROM PAD_META', [], (err, _padIDs) => {
|
||||
padIDs = _padIDs;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function (callback) {
|
||||
log('done');
|
||||
|
||||
// create a queue with a concurrency 100
|
||||
const queue = async.queue((padId, callback) => {
|
||||
convertPad(padId, (err) => {
|
||||
incrementPadStats();
|
||||
callback(err);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// set the step callback as the queue callback
|
||||
queue.drain = callback;
|
||||
|
||||
// add the padids to the worker queue
|
||||
for (let i = 0, length = padIDs.length; i < length; i++) {
|
||||
queue.push(padIDs[i].ID);
|
||||
}
|
||||
},
|
||||
], (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
// write the groups
|
||||
let sql = '';
|
||||
for (const proID in proID2groupID) {
|
||||
const groupID = proID2groupID[proID];
|
||||
const subdomain = proID2subdomain[proID];
|
||||
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(`group:${groupID}`)}, ${etherpadDB.escape(JSON.stringify(groups[groupID]))});\n`;
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(`mapper2group:subdomain:${subdomain}`)}, ${etherpadDB.escape(groupID)});\n`;
|
||||
}
|
||||
|
||||
// close transaction
|
||||
sql += 'COMMIT;';
|
||||
|
||||
// end the sql file
|
||||
fs.writeSync(sqlOutput, sql, undefined, 'utf-8');
|
||||
fs.closeSync(sqlOutput);
|
||||
|
||||
log('finished.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
function log(str) {
|
||||
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
|
||||
}
|
||||
|
||||
let padsDone = 0;
|
||||
|
||||
function incrementPadStats() {
|
||||
padsDone++;
|
||||
|
||||
if (padsDone % 100 == 0) {
|
||||
const averageTime = Math.round(padsDone / ((Date.now() - startTime) / 1000));
|
||||
log(`${padsDone}/${padIDs.length}\t${averageTime} pad/s`);
|
||||
}
|
||||
}
|
||||
|
||||
var proID2groupID = {};
|
||||
var proID2subdomain = {};
|
||||
var groups = {};
|
||||
|
||||
function convertPad(padId, callback) {
|
||||
const changesets = [];
|
||||
const changesetsMeta = [];
|
||||
const chatMessages = [];
|
||||
const authors = [];
|
||||
let apool;
|
||||
let subdomain;
|
||||
let padmeta;
|
||||
|
||||
async.series([
|
||||
// get all needed db values
|
||||
function (callback) {
|
||||
async.parallel([
|
||||
// get the pad revisions
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_CHAT_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_CHAT_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(chatMessages, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the chat entries
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_REVS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVS_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(changesets, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, false);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the pad revisions meta data
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_REVMETA_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVMETA_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(changesetsMeta, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the attribute pool of this pad
|
||||
function (callback) {
|
||||
const sql = 'SELECT `JSON` FROM `PAD_APOOL` WHERE `ID` = ?';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
apool = JSON.parse(results[0].JSON).x;
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the authors informations
|
||||
function (callback) {
|
||||
const sql = 'SELECT * FROM `PAD_AUTHORS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_AUTHORS_META` WHERE ID=?)';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
// parse the pages
|
||||
for (let i = 0, length = results.length; i < length; i++) {
|
||||
parsePage(authors, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
||||
}
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the pad information
|
||||
function (callback) {
|
||||
const sql = 'SELECT JSON FROM `PAD_META` WHERE ID=?';
|
||||
|
||||
etherpadDB.query(sql, [padId], (err, results) => {
|
||||
if (!err) {
|
||||
try {
|
||||
padmeta = JSON.parse(results[0].JSON).x;
|
||||
} catch (e) { err = e; }
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
// get the subdomain
|
||||
function (callback) {
|
||||
// skip if this is no proPad
|
||||
if (padId.indexOf('$') == -1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// get the proID out of this padID
|
||||
const proID = padId.split('$')[0];
|
||||
|
||||
const sql = 'SELECT subDomain FROM pro_domains WHERE ID = ?';
|
||||
|
||||
etherpadDB.query(sql, [proID], (err, results) => {
|
||||
if (!err) {
|
||||
subdomain = results[0].subDomain;
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
},
|
||||
function (callback) {
|
||||
// saves all values that should be written to the database
|
||||
const values = {};
|
||||
|
||||
// this is a pro pad, let's convert it to a group pad
|
||||
if (padId.indexOf('$') != -1) {
|
||||
const padIdParts = padId.split('$');
|
||||
const proID = padIdParts[0];
|
||||
const padName = padIdParts[1];
|
||||
|
||||
let groupID;
|
||||
|
||||
// this proID is not converted so far, do it
|
||||
if (proID2groupID[proID] == null) {
|
||||
groupID = `g.${randomString(16)}`;
|
||||
|
||||
// create the mappers for this new group
|
||||
proID2groupID[proID] = groupID;
|
||||
proID2subdomain[proID] = subdomain;
|
||||
groups[groupID] = {pads: {}};
|
||||
}
|
||||
|
||||
// use the generated groupID;
|
||||
groupID = proID2groupID[proID];
|
||||
|
||||
// rename the pad
|
||||
padId = `${groupID}$${padName}`;
|
||||
|
||||
// set the value for this pad in the group
|
||||
groups[groupID].pads[padId] = 1;
|
||||
}
|
||||
|
||||
try {
|
||||
const newAuthorIDs = {};
|
||||
const oldName2newName = {};
|
||||
|
||||
// replace the authors with generated authors
|
||||
// we need to do that cause where the original etherpad saves pad local authors, the new (lite) etherpad uses them global
|
||||
for (var i in apool.numToAttrib) {
|
||||
var key = apool.numToAttrib[i][0];
|
||||
const value = apool.numToAttrib[i][1];
|
||||
|
||||
// skip non authors and anonymous authors
|
||||
if (key != 'author' || value == '') continue;
|
||||
|
||||
// generate new author values
|
||||
const authorID = `a.${randomString(16)}`;
|
||||
const authorColorID = authors[i].colorId || Math.floor(Math.random() * (exports.getColorPalette().length));
|
||||
const authorName = authors[i].name || null;
|
||||
|
||||
// overwrite the authorID of the attribute pool
|
||||
apool.numToAttrib[i][1] = authorID;
|
||||
|
||||
// write the author to the database
|
||||
values[`globalAuthor:${authorID}`] = {colorId: authorColorID, name: authorName, timestamp};
|
||||
|
||||
// save in mappers
|
||||
newAuthorIDs[i] = authorID;
|
||||
oldName2newName[value] = authorID;
|
||||
}
|
||||
|
||||
// save all revisions
|
||||
for (var i = 0; i < changesets.length; i++) {
|
||||
values[`pad:${padId}:revs:${i}`] = {changeset: changesets[i],
|
||||
meta: {
|
||||
author: newAuthorIDs[changesetsMeta[i].a],
|
||||
timestamp: changesetsMeta[i].t,
|
||||
atext: changesetsMeta[i].atext || undefined,
|
||||
}};
|
||||
}
|
||||
|
||||
// save all chat messages
|
||||
for (var i = 0; i < chatMessages.length; i++) {
|
||||
values[`pad:${padId}:chat:${i}`] = {text: chatMessages[i].lineText,
|
||||
userId: oldName2newName[chatMessages[i].userId],
|
||||
time: chatMessages[i].time};
|
||||
}
|
||||
|
||||
// generate the latest atext
|
||||
const fullAPool = (new AttributePool()).fromJsonable(apool);
|
||||
const keyRev = Math.floor(padmeta.head / padmeta.keyRevInterval) * padmeta.keyRevInterval;
|
||||
let atext = changesetsMeta[keyRev].atext;
|
||||
let curRev = keyRev;
|
||||
while (curRev < padmeta.head) {
|
||||
curRev++;
|
||||
const changeset = changesets[curRev];
|
||||
atext = Changeset.applyToAText(changeset, atext, fullAPool);
|
||||
}
|
||||
|
||||
values[`pad:${padId}`] = {atext,
|
||||
pool: apool,
|
||||
head: padmeta.head,
|
||||
chatHead: padmeta.numChatMessages};
|
||||
} catch (e) {
|
||||
console.error(`Error while converting pad ${padId}, pad skipped`);
|
||||
console.error(e.stack ? e.stack : JSON.stringify(e));
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
let sql = '';
|
||||
for (var key in values) {
|
||||
sql += `REPLACE INTO store VALUES (${etherpadDB.escape(key)}, ${etherpadDB.escape(JSON.stringify(values[key]))});\n`;
|
||||
}
|
||||
|
||||
fs.writeSync(sqlOutput, sql, undefined, 'utf-8');
|
||||
callback();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* This parses a Page like Etherpad uses them in the databases
|
||||
* The offsets describes the length of a unit in the page, the data are
|
||||
* all values behind each other
|
||||
*/
|
||||
function parsePage(array, pageStart, offsets, data, json) {
|
||||
let start = 0;
|
||||
const lengths = offsets.split(',');
|
||||
|
||||
for (let i = 0; i < lengths.length; i++) {
|
||||
let unitLength = lengths[i];
|
||||
|
||||
// skip empty units
|
||||
if (unitLength == '') continue;
|
||||
|
||||
// parse the number
|
||||
unitLength = Number(unitLength);
|
||||
|
||||
// cut the unit out of data
|
||||
const unit = data.substr(start, unitLength);
|
||||
|
||||
// put it into the array
|
||||
array[pageStart + i] = json ? JSON.parse(unit) : unit;
|
||||
|
||||
// update start
|
||||
start += unitLength;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
# Source constants and usefull functions
|
||||
# Source constants and useful functions
|
||||
. bin/functions.sh
|
||||
|
||||
# Prepare the environment
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
|
@ -20,7 +23,6 @@
|
|||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
const marked = require('marked');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -33,12 +35,12 @@ let template = null;
|
|||
let inputFile = null;
|
||||
|
||||
args.forEach((arg) => {
|
||||
if (!arg.match(/^\-\-/)) {
|
||||
if (!arg.match(/^--/)) {
|
||||
inputFile = arg;
|
||||
} else if (arg.match(/^\-\-format=/)) {
|
||||
format = arg.replace(/^\-\-format=/, '');
|
||||
} else if (arg.match(/^\-\-template=/)) {
|
||||
template = arg.replace(/^\-\-template=/, '');
|
||||
} else if (arg.match(/^--format=/)) {
|
||||
format = arg.replace(/^--format=/, '');
|
||||
} else if (arg.match(/^--template=/)) {
|
||||
template = arg.replace(/^--template=/, '');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -56,11 +58,11 @@ fs.readFile(inputFile, 'utf8', (er, input) => {
|
|||
});
|
||||
|
||||
|
||||
const includeExpr = /^@include\s+([A-Za-z0-9-_\/]+)(?:\.)?([a-zA-Z]*)$/gmi;
|
||||
const includeExpr = /^@include\s+([A-Za-z0-9-_/]+)(?:\.)?([a-zA-Z]*)$/gmi;
|
||||
const includeData = {};
|
||||
function processIncludes(inputFile, input, cb) {
|
||||
const processIncludes = (inputFile, input, cb) => {
|
||||
const includes = input.match(includeExpr);
|
||||
if (includes === null) return cb(null, input);
|
||||
if (includes == null) return cb(null, input);
|
||||
let errState = null;
|
||||
console.error(includes);
|
||||
let incCount = includes.length;
|
||||
|
@ -70,7 +72,7 @@ function processIncludes(inputFile, input, cb) {
|
|||
let fname = include.replace(/^@include\s+/, '');
|
||||
if (!fname.match(/\.md$/)) fname += '.md';
|
||||
|
||||
if (includeData.hasOwnProperty(fname)) {
|
||||
if (Object.prototype.hasOwnProperty.call(includeData, fname)) {
|
||||
input = input.split(include).join(includeData[fname]);
|
||||
incCount--;
|
||||
if (incCount === 0) {
|
||||
|
@ -94,10 +96,10 @@ function processIncludes(inputFile, input, cb) {
|
|||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function next(er, input) {
|
||||
const next = (er, input) => {
|
||||
if (er) throw er;
|
||||
switch (format) {
|
||||
case 'json':
|
||||
|
@ -117,4 +119,4 @@ function next(er, input) {
|
|||
default:
|
||||
throw new Error(`Invalid format: ${format}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
|
@ -23,17 +25,17 @@ const fs = require('fs');
|
|||
const marked = require('marked');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = toHTML;
|
||||
|
||||
function toHTML(input, filename, template, cb) {
|
||||
const toHTML = (input, filename, template, cb) => {
|
||||
const lexed = marked.lexer(input);
|
||||
fs.readFile(template, 'utf8', (er, template) => {
|
||||
if (er) return cb(er);
|
||||
render(lexed, filename, template, cb);
|
||||
});
|
||||
}
|
||||
};
|
||||
module.exports = toHTML;
|
||||
|
||||
function render(lexed, filename, template, cb) {
|
||||
const render = (lexed, filename, template, cb) => {
|
||||
// get the section
|
||||
const section = getSection(lexed);
|
||||
|
||||
|
@ -52,23 +54,23 @@ function render(lexed, filename, template, cb) {
|
|||
|
||||
// content has to be the last thing we do with
|
||||
// the lexed tokens, because it's destructive.
|
||||
content = marked.parser(lexed);
|
||||
const content = marked.parser(lexed);
|
||||
template = template.replace(/__CONTENT__/g, content);
|
||||
|
||||
cb(null, template);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// just update the list item text in-place.
|
||||
// lists that come right after a heading are what we're after.
|
||||
function parseLists(input) {
|
||||
const parseLists = (input) => {
|
||||
let state = null;
|
||||
let depth = 0;
|
||||
const output = [];
|
||||
output.links = input.links;
|
||||
input.forEach((tok) => {
|
||||
if (state === null) {
|
||||
if (state == null) {
|
||||
if (tok.type === 'heading') {
|
||||
state = 'AFTERHEADING';
|
||||
}
|
||||
|
@ -112,29 +114,27 @@ function parseLists(input) {
|
|||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function parseListItem(text) {
|
||||
text = text.replace(/\{([^\}]+)\}/, '<span class="type">$1</span>');
|
||||
const parseListItem = (text) => {
|
||||
text = text.replace(/\{([^}]+)\}/, '<span class="type">$1</span>');
|
||||
// XXX maybe put more stuff here?
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// section is just the first heading
|
||||
function getSection(lexed) {
|
||||
const section = '';
|
||||
const getSection = (lexed) => {
|
||||
for (let i = 0, l = lexed.length; i < l; i++) {
|
||||
const tok = lexed[i];
|
||||
if (tok.type === 'heading') return tok.text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function buildToc(lexed, filename, cb) {
|
||||
const indent = 0;
|
||||
const buildToc = (lexed, filename, cb) => {
|
||||
let toc = [];
|
||||
let depth = 0;
|
||||
lexed.forEach((tok) => {
|
||||
|
@ -155,18 +155,18 @@ function buildToc(lexed, filename, cb) {
|
|||
|
||||
toc = marked.parse(toc.join('\n'));
|
||||
cb(null, toc);
|
||||
}
|
||||
};
|
||||
|
||||
const idCounters = {};
|
||||
function getId(text) {
|
||||
const getId = (text) => {
|
||||
text = text.toLowerCase();
|
||||
text = text.replace(/[^a-z0-9]+/g, '_');
|
||||
text = text.replace(/^_+|_+$/, '');
|
||||
text = text.replace(/^([^a-z])/, '_$1');
|
||||
if (idCounters.hasOwnProperty(text)) {
|
||||
if (Object.prototype.hasOwnProperty.call(idCounters, text)) {
|
||||
text += `_${++idCounters[text]}`;
|
||||
} else {
|
||||
idCounters[text] = 0;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
'use strict';
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
|
@ -26,7 +27,7 @@ module.exports = doJSON;
|
|||
|
||||
const marked = require('marked');
|
||||
|
||||
function doJSON(input, filename, cb) {
|
||||
const doJSON = (input, filename, cb) => {
|
||||
const root = {source: filename};
|
||||
const stack = [root];
|
||||
let depth = 0;
|
||||
|
@ -40,7 +41,7 @@ function doJSON(input, filename, cb) {
|
|||
// <!-- type = module -->
|
||||
// This is for cases where the markdown semantic structure is lacking.
|
||||
if (type === 'paragraph' || type === 'html') {
|
||||
const metaExpr = /<!--([^=]+)=([^\-]+)-->\n*/g;
|
||||
const metaExpr = /<!--([^=]+)=([^-]+)-->\n*/g;
|
||||
text = text.replace(metaExpr, (_0, k, v) => {
|
||||
current[k.trim()] = v.trim();
|
||||
return '';
|
||||
|
@ -146,7 +147,7 @@ function doJSON(input, filename, cb) {
|
|||
}
|
||||
|
||||
return cb(null, root);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// go from something like this:
|
||||
|
@ -191,7 +192,7 @@ function doJSON(input, filename, cb) {
|
|||
// desc: 'whether or not to send output to parent\'s stdio.',
|
||||
// default: 'false' } ] } ]
|
||||
|
||||
function processList(section) {
|
||||
const processList = (section) => {
|
||||
const list = section.list;
|
||||
const values = [];
|
||||
let current;
|
||||
|
@ -203,13 +204,13 @@ function processList(section) {
|
|||
if (type === 'space') return;
|
||||
if (type === 'list_item_start') {
|
||||
if (!current) {
|
||||
var n = {};
|
||||
const n = {};
|
||||
values.push(n);
|
||||
current = n;
|
||||
} else {
|
||||
current.options = current.options || [];
|
||||
stack.push(current);
|
||||
var n = {};
|
||||
const n = {};
|
||||
current.options.push(n);
|
||||
current = n;
|
||||
}
|
||||
|
@ -247,11 +248,11 @@ function processList(section) {
|
|||
switch (section.type) {
|
||||
case 'ctor':
|
||||
case 'classMethod':
|
||||
case 'method':
|
||||
case 'method': {
|
||||
// each item is an argument, unless the name is 'return',
|
||||
// in which case it's the return value.
|
||||
section.signatures = section.signatures || [];
|
||||
var sig = {};
|
||||
const sig = {};
|
||||
section.signatures.push(sig);
|
||||
sig.params = values.filter((v) => {
|
||||
if (v.name === 'return') {
|
||||
|
@ -262,11 +263,11 @@ function processList(section) {
|
|||
});
|
||||
parseSignature(section.textRaw, sig);
|
||||
break;
|
||||
|
||||
case 'property':
|
||||
}
|
||||
case 'property': {
|
||||
// there should be only one item, which is the value.
|
||||
// copy the data up to the section.
|
||||
var value = values[0] || {};
|
||||
const value = values[0] || {};
|
||||
delete value.name;
|
||||
section.typeof = value.type;
|
||||
delete value.type;
|
||||
|
@ -274,20 +275,21 @@ function processList(section) {
|
|||
section[k] = value[k];
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'event':
|
||||
case 'event': {
|
||||
// event: each item is an argument.
|
||||
section.params = values;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// section.listParsed = values;
|
||||
delete section.list;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// textRaw = "someobject.someMethod(a, [b=100], [c])"
|
||||
function parseSignature(text, sig) {
|
||||
const parseSignature = (text, sig) => {
|
||||
let params = text.match(paramExpr);
|
||||
if (!params) return;
|
||||
params = params[1];
|
||||
|
@ -322,10 +324,10 @@ function parseSignature(text, sig) {
|
|||
if (optional) param.optional = true;
|
||||
if (def !== undefined) param.default = def;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function parseListItem(item) {
|
||||
const parseListItem = (item) => {
|
||||
if (item.options) item.options.forEach(parseListItem);
|
||||
if (!item.textRaw) return;
|
||||
|
||||
|
@ -341,7 +343,7 @@ function parseListItem(item) {
|
|||
item.name = 'return';
|
||||
text = text.replace(retExpr, '');
|
||||
} else {
|
||||
const nameExpr = /^['`"]?([^'`": \{]+)['`"]?\s*:?\s*/;
|
||||
const nameExpr = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
|
||||
const name = text.match(nameExpr);
|
||||
if (name) {
|
||||
item.name = name[1];
|
||||
|
@ -358,7 +360,7 @@ function parseListItem(item) {
|
|||
}
|
||||
|
||||
text = text.trim();
|
||||
const typeExpr = /^\{([^\}]+)\}/;
|
||||
const typeExpr = /^\{([^}]+)\}/;
|
||||
const type = text.match(typeExpr);
|
||||
if (type) {
|
||||
item.type = type[1];
|
||||
|
@ -376,10 +378,10 @@ function parseListItem(item) {
|
|||
text = text.replace(/^\s*-\s*/, '');
|
||||
text = text.trim();
|
||||
if (text) item.desc = text;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function finishSection(section, parent) {
|
||||
const finishSection = (section, parent) => {
|
||||
if (!section || !parent) {
|
||||
throw new Error(`Invalid finishSection call\n${
|
||||
JSON.stringify(section)}\n${
|
||||
|
@ -416,7 +418,7 @@ function finishSection(section, parent) {
|
|||
ctor.signatures.forEach((sig) => {
|
||||
sig.desc = ctor.desc;
|
||||
});
|
||||
sigs.push.apply(sigs, ctor.signatures);
|
||||
sigs.push(...ctor.signatures);
|
||||
});
|
||||
delete section.ctors;
|
||||
}
|
||||
|
@ -479,50 +481,50 @@ function finishSection(section, parent) {
|
|||
|
||||
parent[plur] = parent[plur] || [];
|
||||
parent[plur].push(section);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Not a general purpose deep copy.
|
||||
// But sufficient for these basic things.
|
||||
function deepCopy(src, dest) {
|
||||
Object.keys(src).filter((k) => !dest.hasOwnProperty(k)).forEach((k) => {
|
||||
const deepCopy = (src, dest) => {
|
||||
Object.keys(src).filter((k) => !Object.prototype.hasOwnProperty.call(dest, k)).forEach((k) => {
|
||||
dest[k] = deepCopy_(src[k]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function deepCopy_(src) {
|
||||
const deepCopy_ = (src) => {
|
||||
if (!src) return src;
|
||||
if (Array.isArray(src)) {
|
||||
var c = new Array(src.length);
|
||||
const c = new Array(src.length);
|
||||
src.forEach((v, i) => {
|
||||
c[i] = deepCopy_(v);
|
||||
});
|
||||
return c;
|
||||
}
|
||||
if (typeof src === 'object') {
|
||||
var c = {};
|
||||
const c = {};
|
||||
Object.keys(src).forEach((k) => {
|
||||
c[k] = deepCopy_(src[k]);
|
||||
});
|
||||
return c;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// these parse out the contents of an H# tag
|
||||
const eventExpr = /^Event(?::|\s)+['"]?([^"']+).*$/i;
|
||||
const classExpr = /^Class:\s*([^ ]+).*?$/i;
|
||||
const propExpr = /^(?:property:?\s*)?[^\.]+\.([^ \.\(\)]+)\s*?$/i;
|
||||
const braceExpr = /^(?:property:?\s*)?[^\.\[]+(\[[^\]]+\])\s*?$/i;
|
||||
const propExpr = /^(?:property:?\s*)?[^.]+\.([^ .()]+)\s*?$/i;
|
||||
const braceExpr = /^(?:property:?\s*)?[^.[]+(\[[^\]]+\])\s*?$/i;
|
||||
const classMethExpr =
|
||||
/^class\s*method\s*:?[^\.]+\.([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
||||
/^class\s*method\s*:?[^.]+\.([^ .()]+)\([^)]*\)\s*?$/i;
|
||||
const methExpr =
|
||||
/^(?:method:?\s*)?(?:[^\.]+\.)?([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
||||
const newExpr = /^new ([A-Z][a-z]+)\([^\)]*\)\s*?$/;
|
||||
var paramExpr = /\((.*)\);?$/;
|
||||
/^(?:method:?\s*)?(?:[^.]+\.)?([^ .()]+)\([^)]*\)\s*?$/i;
|
||||
const newExpr = /^new ([A-Z][a-z]+)\([^)]*\)\s*?$/;
|
||||
const paramExpr = /\((.*)\);?$/;
|
||||
|
||||
function newSection(tok) {
|
||||
const newSection = (tok) => {
|
||||
const section = {};
|
||||
// infer the type from the text.
|
||||
const text = section.textRaw = tok.text;
|
||||
|
@ -551,4 +553,4 @@ function newSection(tok) {
|
|||
section.name = text;
|
||||
}
|
||||
return section;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Internal tool for generating Node.js API docs",
|
||||
"version": "0.0.0",
|
||||
"engines": {
|
||||
"node": ">=0.6.10"
|
||||
"node": ">=10.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": "0.8.2"
|
||||
|
|
|
@ -12,7 +12,7 @@ set -eu
|
|||
# source: https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
|
||||
# Source constants and usefull functions
|
||||
# Source constants and useful functions
|
||||
. ${DIR}/../bin/functions.sh
|
||||
|
||||
echo "Running directly, without checking/installing dependencies"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
# Source constants and usefull functions
|
||||
# Source constants and useful functions
|
||||
. bin/functions.sh
|
||||
|
||||
# Is node installed?
|
||||
|
|
|
@ -263,7 +263,7 @@ fs.readdir(pluginPath, (err, rootFiles) => {
|
|||
console.warn('No engines or node engine in package.json');
|
||||
if (autoFix) {
|
||||
const engines = {
|
||||
node: '>=10.13.0',
|
||||
node: '^10.17.0 || >=11.14.0',
|
||||
};
|
||||
parsedPackageJSON.engines = engines;
|
||||
writePackageJson(parsedPackageJSON);
|
||||
|
|
|
@ -23,7 +23,7 @@ const util = require('util');
|
|||
(async () => {
|
||||
await util.promisify(npm.load)({});
|
||||
|
||||
// intialize database
|
||||
// initialize database
|
||||
require('ep_etherpad-lite/node/utils/Settings');
|
||||
const db = require('ep_etherpad-lite/node/db/DB');
|
||||
await db.init();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Move to the folder where ep-lite is installed
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
# Source constants and usefull functions
|
||||
# Source constants and useful functions
|
||||
. bin/functions.sh
|
||||
|
||||
ignoreRoot=0
|
||||
|
|
|
@ -263,7 +263,7 @@ deletes a session
|
|||
#### getSessionInfo(sessionID)
|
||||
* API >= 1
|
||||
|
||||
returns informations about a session
|
||||
returns information about a session
|
||||
|
||||
*Example returns:*
|
||||
* `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}}`
|
||||
|
|
|
@ -95,7 +95,7 @@ For example, if you want to replace `Chat` with `Notes`, simply add...
|
|||
|
||||
## Customization for Administrators
|
||||
|
||||
As an Etherpad administrator, it is possible to overwrite core mesages as well as messages in plugins. These include error messages, labels, and user instructions. Whereas the localization in the source code is in separate files separated by locale, an administrator's custom localizations are in `settings.json` under the `customLocaleStrings` key, with each locale separated by a sub-key underneath.
|
||||
As an Etherpad administrator, it is possible to overwrite core messages as well as messages in plugins. These include error messages, labels, and user instructions. Whereas the localization in the source code is in separate files separated by locale, an administrator's custom localizations are in `settings.json` under the `customLocaleStrings` key, with each locale separated by a sub-key underneath.
|
||||
|
||||
For example, let's say you want to change the text on the "New Pad" button on Etherpad's home page. If you look in `locales/en.json` (or `locales/en-gb.json`) you'll see the key for this text is `"index.newPad"`. You could add the following to `settings.json`:
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ publish your plugin.
|
|||
"author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
|
||||
"contributors": [],
|
||||
"dependencies": {"MODULE": "0.3.20"},
|
||||
"engines": { "node": ">= 10.13.0"}
|
||||
"engines": { "node": "^10.17.0 || >=11.14.0"}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
15
package.json
15
package.json
|
@ -39,7 +39,9 @@
|
|||
"tests/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js"
|
||||
"**/.eslintrc.js",
|
||||
"tests/frontend/travis/**/*",
|
||||
"tests/ratelimit/**/*"
|
||||
],
|
||||
"extends": "etherpad/tests",
|
||||
"rules": {
|
||||
|
@ -75,7 +77,8 @@
|
|||
"tests/frontend/**/*"
|
||||
],
|
||||
"excludedFiles": [
|
||||
"**/.eslintrc.js"
|
||||
"**/.eslintrc.js",
|
||||
"tests/frontend/travis/**/*"
|
||||
],
|
||||
"extends": "etherpad/tests/frontend",
|
||||
"overrides": [
|
||||
|
@ -92,6 +95,12 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"tests/frontend/travis/**/*"
|
||||
],
|
||||
"extends": "etherpad/node"
|
||||
}
|
||||
],
|
||||
"root": true
|
||||
|
@ -100,6 +109,6 @@
|
|||
"lint": "eslint ."
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
"node": "^10.17.0 || >=11.14.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
*
|
||||
*
|
||||
* Database specific settings are dependent on dbType, and go in dbSettings.
|
||||
* Remember that since Etherpad 1.6.0 you can also store these informations in
|
||||
* Remember that since Etherpad 1.6.0 you can also store this information in
|
||||
* credentials.json.
|
||||
*
|
||||
* For a complete list of the supported drivers, please refer to:
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
*
|
||||
*
|
||||
* Database specific settings are dependent on dbType, and go in dbSettings.
|
||||
* Remember that since Etherpad 1.6.0 you can also store these informations in
|
||||
* Remember that since Etherpad 1.6.0 you can also store this information in
|
||||
* credentials.json.
|
||||
*
|
||||
* For a complete list of the supported drivers, please refer to:
|
||||
|
|
|
@ -2,12 +2,47 @@
|
|||
"@metadata": {
|
||||
"authors": [
|
||||
"Elisardojm",
|
||||
"Ghose",
|
||||
"Toliño"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Panel de administración - Etherpad",
|
||||
"admin_plugins": "Xestor de complementos",
|
||||
"admin_plugins.available": "Complementos dispoñibles",
|
||||
"admin_plugins.available_not-found": "Non se atopan complementos.",
|
||||
"admin_plugins.available_fetching": "Obtendo...",
|
||||
"admin_plugins.available_install.value": "Instalar",
|
||||
"admin_plugins.available_search.placeholder": "Buscar complementos para instalar",
|
||||
"admin_plugins.description": "Descrición",
|
||||
"admin_plugins.installed": "Complementos instalados",
|
||||
"admin_plugins.installed_fetching": "Obtendo os complementos instalados...",
|
||||
"admin_plugins.installed_nothing": "Aínda non instalaches ningún complemento.",
|
||||
"admin_plugins.installed_uninstall.value": "Desinstalar",
|
||||
"admin_plugins.last-update": "Última actualización",
|
||||
"admin_plugins.name": "Nome",
|
||||
"admin_plugins.page-title": "Xestos de complementos - Etherpad",
|
||||
"admin_plugins.version": "Versión",
|
||||
"admin_plugins_info": "Información para resolver problemas",
|
||||
"admin_plugins_info.hooks": "Ganchos instalados",
|
||||
"admin_plugins_info.hooks_client": "Ganchos do lado do cliente",
|
||||
"admin_plugins_info.hooks_server": "Ganchos do lado do servidor",
|
||||
"admin_plugins_info.parts": "Partes instaladas",
|
||||
"admin_plugins_info.plugins": "Complementos instalados",
|
||||
"admin_plugins_info.page-title": "Información do complemento - Etherpad",
|
||||
"admin_plugins_info.version": "Versión de Etherpad",
|
||||
"admin_plugins_info.version_latest": "Última versión dispoñible",
|
||||
"admin_plugins_info.version_number": "Número da versión",
|
||||
"admin_settings": "Axustes",
|
||||
"admin_settings.current": "Configuración actual",
|
||||
"admin_settings.current_example-devel": "Modelo de exemplo dos axustes de desenvolvemento",
|
||||
"admin_settings.current_example-prod": "Modelo de exemplo dos axustes en produción",
|
||||
"admin_settings.current_restart.value": "Reiniciar Etherpad",
|
||||
"admin_settings.current_save.value": "Gardar axustes",
|
||||
"admin_settings.page-title": "Axustes - Etherpad",
|
||||
"index.newPad": "Novo documento",
|
||||
"index.createOpenPad": "ou cree/abra un documento co nome:",
|
||||
"pad.toolbar.bold.title": "Negra (Ctrl-B)",
|
||||
"index.createOpenPad": "ou crea/abre un documento co nome:",
|
||||
"index.openPad": "abrir un Pad existente co nome:",
|
||||
"pad.toolbar.bold.title": "Resaltado (Ctrl-B)",
|
||||
"pad.toolbar.italic.title": "Cursiva (Ctrl-I)",
|
||||
"pad.toolbar.underline.title": "Subliñar (Ctrl-U)",
|
||||
"pad.toolbar.strikethrough.title": "Riscar (Ctrl+5)",
|
||||
|
@ -17,28 +52,30 @@
|
|||
"pad.toolbar.unindent.title": "Sen sangría (Maiús.+TAB)",
|
||||
"pad.toolbar.undo.title": "Desfacer (Ctrl-Z)",
|
||||
"pad.toolbar.redo.title": "Refacer (Ctrl-Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Limpar as cores de identificación dos autores (Ctrl+Shift+C)",
|
||||
"pad.toolbar.clearAuthorship.title": "Eliminar as cores que identifican ás autoras (Ctrl+Shift+C)",
|
||||
"pad.toolbar.import_export.title": "Importar/Exportar desde/a diferentes formatos de ficheiro",
|
||||
"pad.toolbar.timeslider.title": "Liña do tempo",
|
||||
"pad.toolbar.savedRevision.title": "Gardar a revisión",
|
||||
"pad.toolbar.settings.title": "Configuracións",
|
||||
"pad.toolbar.settings.title": "Axustes",
|
||||
"pad.toolbar.embed.title": "Compartir e incorporar este documento",
|
||||
"pad.toolbar.showusers.title": "Mostrar os usuarios deste documento",
|
||||
"pad.toolbar.showusers.title": "Mostrar as usuarias deste documento",
|
||||
"pad.colorpicker.save": "Gardar",
|
||||
"pad.colorpicker.cancel": "Cancelar",
|
||||
"pad.loading": "Cargando...",
|
||||
"pad.noCookie": "Non se puido atopar a cookie. Por favor, habilite as cookies no seu navegador!",
|
||||
"pad.permissionDenied": "Non ten permiso para acceder a este documento",
|
||||
"pad.noCookie": "Non se puido atopar a cookie. Por favor, habilita as cookies no teu navegador! A túa sesión e axustes non se gardarán entre visitas. Esto podería deberse a que Etherpad está incluído nalgún iFrame nalgúns navegadores. Asegúrate de que Etherpad está no mesmo subdominio/dominio que o iFrame pai",
|
||||
"pad.permissionDenied": "Non tes permiso para acceder a este documento",
|
||||
"pad.settings.padSettings": "Configuracións do documento",
|
||||
"pad.settings.myView": "A miña vista",
|
||||
"pad.settings.stickychat": "Chat sempre visible",
|
||||
"pad.settings.chatandusers": "Mostrar o chat e os usuarios",
|
||||
"pad.settings.colorcheck": "Cores de identificación",
|
||||
"pad.settings.linenocheck": "Números de liña",
|
||||
"pad.settings.rtlcheck": "Quere ler o contido da dereita á esquerda?",
|
||||
"pad.settings.rtlcheck": "Queres ler o contido da dereita á esquerda?",
|
||||
"pad.settings.fontType": "Tipo de letra:",
|
||||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.language": "Lingua:",
|
||||
"pad.settings.about": "Acerca de",
|
||||
"pad.settings.poweredBy": "Grazas a",
|
||||
"pad.importExport.import_export": "Importar/Exportar",
|
||||
"pad.importExport.import": "Cargar un ficheiro de texto ou documento",
|
||||
"pad.importExport.importSuccessful": "Correcto!",
|
||||
|
@ -49,9 +86,9 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Só pode importar texto simple ou formatos HTML. Para obter máis información sobre as características de importación avanzadas <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instale AbiWord</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Só podes importar texto simple ou formatos HTML. Para obter máis información sobre as características de importación avanzadas <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instala AbiWord</a>.",
|
||||
"pad.modals.connected": "Conectado.",
|
||||
"pad.modals.reconnecting": "Reconectando co seu documento...",
|
||||
"pad.modals.reconnecting": "Reconectando co teu documento...",
|
||||
"pad.modals.forcereconnect": "Forzar a reconexión",
|
||||
"pad.modals.reconnecttimer": "Intentarase reconectar en",
|
||||
"pad.modals.cancel": "Cancelar",
|
||||
|
@ -73,6 +110,10 @@
|
|||
"pad.modals.corruptPad.cause": "Isto pode deberse a unha cofiguración errónea do servidor ou algún outro comportamento inesperado. Póñase en contacto co administrador do servizo.",
|
||||
"pad.modals.deleted": "Borrado.",
|
||||
"pad.modals.deleted.explanation": "Este documento foi eliminado.",
|
||||
"pad.modals.rateLimited": "Taxa limitada.",
|
||||
"pad.modals.rateLimited.explanation": "Enviaches demasiadas mensaxes a este documento polo que te desconectamos.",
|
||||
"pad.modals.rejected.explanation": "O servidor rexeitou unha mensaxe que o teu navegador enviou.",
|
||||
"pad.modals.rejected.cause": "O servidor podería ter sido actualizado mentras ollabas o documento, ou pode que sexa un fallo de Etherpad. Intenta recargar a páxina.",
|
||||
"pad.modals.disconnected": "Foi desconectado.",
|
||||
"pad.modals.disconnected.explanation": "Perdeuse a conexión co servidor",
|
||||
"pad.modals.disconnected.cause": "O servidor non está dispoñible. Póñase en contacto co administrador do servizo se o problema continúa.",
|
||||
|
@ -83,6 +124,9 @@
|
|||
"pad.chat": "Chat",
|
||||
"pad.chat.title": "Abrir o chat deste documento.",
|
||||
"pad.chat.loadmessages": "Cargar máis mensaxes",
|
||||
"pad.chat.stick.title": "Pegar a conversa á pantalla",
|
||||
"pad.chat.writeMessage.placeholder": "Escribe aquí a túa mensaxe",
|
||||
"timeslider.followContents": "Segue as actualizacións do contido",
|
||||
"timeslider.pageTitle": "Liña do tempo de {{appTitle}}",
|
||||
"timeslider.toolbar.returnbutton": "Volver ao documento",
|
||||
"timeslider.toolbar.authors": "Autores:",
|
||||
|
@ -112,7 +156,7 @@
|
|||
"pad.savedrevs.timeslider": "Pode consultar as revisións gardadas visitando a liña do tempo",
|
||||
"pad.userlist.entername": "Insira o seu nome",
|
||||
"pad.userlist.unnamed": "anónimo",
|
||||
"pad.editbar.clearcolors": "Quere limpar as cores de identificación dos autores en todo o documento?",
|
||||
"pad.editbar.clearcolors": "Eliminar as cores relativas aos autores en todo o documento? Non se poderán recuperar",
|
||||
"pad.impexp.importbutton": "Importar agora",
|
||||
"pad.impexp.importing": "Importando...",
|
||||
"pad.impexp.confirmimport": "A importación dun ficheiro ha sobrescribir o texto actual do documento. Está seguro de querer continuar?",
|
||||
|
@ -121,5 +165,6 @@
|
|||
"pad.impexp.uploadFailed": "Houbo un erro ao cargar o ficheiro; inténteo de novo",
|
||||
"pad.impexp.importfailed": "Fallou a importación",
|
||||
"pad.impexp.copypaste": "Copie e pegue",
|
||||
"pad.impexp.exportdisabled": "A exportación en formato {{type}} está desactivada. Póñase en contacto co administrador do sistema se quere máis detalles."
|
||||
"pad.impexp.exportdisabled": "A exportación en formato {{type}} está desactivada. Póñase en contacto co administrador do sistema se quere máis detalles.",
|
||||
"pad.impexp.maxFileSize": "Ficheiro demasiado granda. Contacta coa administración para aumentar o tamaño permitido para importacións"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"Athena in Wonderland",
|
||||
"Cainamarques",
|
||||
"GoEThe",
|
||||
"Guilha",
|
||||
"Hamilton Abreu",
|
||||
"Imperadeiro98",
|
||||
"Luckas",
|
||||
|
@ -16,9 +17,42 @@
|
|||
"Waldyrious"
|
||||
]
|
||||
},
|
||||
"admin.page-title": "Painel do administrador - Etherpad",
|
||||
"admin_plugins": "Gestor de plugins",
|
||||
"admin_plugins.available": "Plugins disponíveis",
|
||||
"admin_plugins.available_not-found": "Não foram encontrados plugins.",
|
||||
"admin_plugins.available_fetching": "A obter...",
|
||||
"admin_plugins.available_install.value": "Instalar",
|
||||
"admin_plugins.available_search.placeholder": "Procura plugins para instalar",
|
||||
"admin_plugins.description": "Descrição",
|
||||
"admin_plugins.installed": "Plugins instalados",
|
||||
"admin_plugins.installed_fetching": "A obter plugins instalados...",
|
||||
"admin_plugins.installed_nothing": "Não instalas-te nenhum plugin ainda.",
|
||||
"admin_plugins.installed_uninstall.value": "Desinstalar",
|
||||
"admin_plugins.last-update": "Ultima atualização",
|
||||
"admin_plugins.name": "Nome",
|
||||
"admin_plugins.page-title": "Gestor de plugins - Etherpad",
|
||||
"admin_plugins.version": "Versão",
|
||||
"admin_plugins_info": "Informação de resolução de problemas",
|
||||
"admin_plugins_info.hooks": "Hooks instalados",
|
||||
"admin_plugins_info.hooks_client": "Hooks do lado-do-cliente",
|
||||
"admin_plugins_info.hooks_server": "Hooks do lado-do-servidor",
|
||||
"admin_plugins_info.parts": "Partes instaladas",
|
||||
"admin_plugins_info.plugins": "Plugins instalados",
|
||||
"admin_plugins_info.page-title": "Informação do plugin - Etherpad",
|
||||
"admin_plugins_info.version": "Versão do Etherpad",
|
||||
"admin_plugins_info.version_latest": "Última versão disponível",
|
||||
"admin_plugins_info.version_number": "Número de versão",
|
||||
"admin_settings": "Definições",
|
||||
"admin_settings.current": "Configuração atual",
|
||||
"admin_settings.current_example-devel": "Exemplo do modo de Desenvolvedor",
|
||||
"admin_settings.current_example-prod": "Exemplo do modo de Produção",
|
||||
"admin_settings.current_restart.value": "Reiniciar Etherpad",
|
||||
"admin_settings.current_save.value": "Guardar Definições",
|
||||
"admin_settings.page-title": "Definições - Etherpad",
|
||||
"index.newPad": "Nova Nota",
|
||||
"index.createOpenPad": "ou crie/abra uma nota com o nome:",
|
||||
"index.openPad": "abrir uma «Nota» existente com o nome:",
|
||||
"index.createOpenPad": "ou cria/abre uma nota com o nome:",
|
||||
"index.openPad": "abrir uma Nota existente com o nome:",
|
||||
"pad.toolbar.bold.title": "Negrito (Ctrl+B)",
|
||||
"pad.toolbar.italic.title": "Itálico (Ctrl+I)",
|
||||
"pad.toolbar.underline.title": "Sublinhado (Ctrl+U)",
|
||||
|
@ -26,7 +60,7 @@
|
|||
"pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)",
|
||||
"pad.toolbar.ul.title": "Lista desordenada (Ctrl+Shift+L)",
|
||||
"pad.toolbar.indent.title": "Indentar (TAB)",
|
||||
"pad.toolbar.unindent.title": "Remover indentação (Shift+TAB)",
|
||||
"pad.toolbar.unindent.title": "Indentação (Shift+TAB)",
|
||||
"pad.toolbar.undo.title": "Desfazer (Ctrl+Z)",
|
||||
"pad.toolbar.redo.title": "Refazer (Ctrl+Y)",
|
||||
"pad.toolbar.clearAuthorship.title": "Limpar cores de autoria (Ctrl+Shift+C)",
|
||||
|
@ -65,7 +99,7 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Só pode fazer importações de texto não formatado ou com formato HTML. Para funcionalidades de importação de texto mais avançadas, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">instale AbiWord ou LibreOffice</a>, por favor.",
|
||||
"pad.modals.connected": "Ligado.",
|
||||
"pad.modals.reconnecting": "A restabelecer ligação ao seu bloco…",
|
||||
"pad.modals.reconnecting": "A restabelecer ligação à nota…",
|
||||
"pad.modals.forcereconnect": "Forçar restabelecimento de ligação",
|
||||
"pad.modals.reconnecttimer": "A tentar restabelecer ligação",
|
||||
"pad.modals.cancel": "Cancelar",
|
||||
|
@ -89,6 +123,8 @@
|
|||
"pad.modals.deleted.explanation": "Esta nota foi removida.",
|
||||
"pad.modals.rateLimited": "Limitado.",
|
||||
"pad.modals.rateLimited.explanation": "Enviou demasiadas mensagens para este pad, por isso foi desligado.",
|
||||
"pad.modals.rejected.explanation": "O servidor rejeitou a mensagem que foi enviada pelo teu navegador.",
|
||||
"pad.modals.rejected.cause": "O server foi atualizado enquanto estávas a ver esta nota, ou talvez seja apenas um bug do Etherpad. Tenta recarregar a página.",
|
||||
"pad.modals.disconnected": "Você foi desligado.",
|
||||
"pad.modals.disconnected.explanation": "A ligação ao servidor foi perdida",
|
||||
"pad.modals.disconnected.cause": "O servidor pode estar indisponível. Por favor, notifique o administrador de serviço se isto continuar a acontecer.",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* The DB Module provides a database initalized with the settings
|
||||
* The DB Module provides a database initialized with the settings
|
||||
* provided by the settings module
|
||||
*/
|
||||
|
||||
|
@ -36,7 +36,7 @@ const db =
|
|||
exports.db = null;
|
||||
|
||||
/**
|
||||
* Initalizes the database with the settings provided by the settings module
|
||||
* Initializes the database with the settings provided by the settings module
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.init = async () => await new Promise((resolve, reject) => {
|
||||
|
|
|
@ -139,7 +139,7 @@ exports.getPad = async (id, text) => {
|
|||
// try to load pad
|
||||
pad = new Pad(id);
|
||||
|
||||
// initalize the pad
|
||||
// initialize the pad
|
||||
await pad.init(text);
|
||||
globalPads.set(id, pad);
|
||||
padList.addPad(id);
|
||||
|
|
|
@ -46,7 +46,7 @@ const rateLimiter = new RateLimiterMemory({
|
|||
});
|
||||
|
||||
/**
|
||||
* A associative array that saves informations about a session
|
||||
* A associative array that saves information about a session
|
||||
* key = sessionId
|
||||
* values = padId, readonlyPadId, readonly, author, rev
|
||||
* padId = the real padId of the pad
|
||||
|
@ -88,7 +88,7 @@ exports.setSocketIO = (socket_io) => {
|
|||
exports.handleConnect = (socket) => {
|
||||
stats.meter('connects').mark();
|
||||
|
||||
// Initalize sessioninfos for this new session
|
||||
// Initialize sessioninfos for this new session
|
||||
sessioninfos[socket.id] = {};
|
||||
};
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ const resources = {
|
|||
// We need an operation that returns a SessionInfo so it can be picked up by the codegen :(
|
||||
info: {
|
||||
operationId: 'getSessionInfo',
|
||||
summary: 'returns informations about a session',
|
||||
summary: 'returns information about a session',
|
||||
responseSchema: {info: {$ref: '#/components/schemas/SessionInfo'}},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -88,7 +88,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
|
||||
// if(settings.minify) io.enable('browser client minification');
|
||||
|
||||
// Initalize the Socket.IO Router
|
||||
// Initialize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent('pad', padMessageHandler);
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ const wtfnode = require('wtfnode');
|
|||
* any modules that require newer versions of NodeJS
|
||||
*/
|
||||
const NodeVersion = require('./utils/NodeVersion');
|
||||
NodeVersion.enforceMinNodeVersion('10.13.0');
|
||||
NodeVersion.checkDeprecationStatus('10.13.0', '1.8.3');
|
||||
NodeVersion.enforceMinNodeVersion('10.17.0');
|
||||
NodeVersion.checkDeprecationStatus('10.17.0', '1.8.8');
|
||||
|
||||
const UpdateCheck = require('./utils/UpdateCheck');
|
||||
const db = require('./db/DB');
|
||||
|
|
|
@ -311,7 +311,7 @@ const statFile = (filename, callback, dirStatLimit) => {
|
|||
const lastModifiedDateOfEverything = (callback) => {
|
||||
const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`];
|
||||
let latestModification = 0;
|
||||
// go trough this two folders
|
||||
// go through this two folders
|
||||
async.forEach(folders2check, (path, callback) => {
|
||||
// read the files in the folder
|
||||
fs.readdir(path, (err, files) => {
|
||||
|
@ -320,7 +320,7 @@ const lastModifiedDateOfEverything = (callback) => {
|
|||
// we wanna check the directory itself for changes too
|
||||
files.push('.');
|
||||
|
||||
// go trough all files in this folder
|
||||
// go through all files in this folder
|
||||
async.forEach(files, (filename, callback) => {
|
||||
// get the stat data of this file
|
||||
fs.stat(`${path}/${filename}`, (err, stats) => {
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
"root": true
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0",
|
||||
"node": "^10.17.0 || >=11.14.0",
|
||||
"npm": ">=5.5.1"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -93,11 +93,8 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
|
|||
} else if (linestylefilter.ATTRIB_CLASSES[key]) {
|
||||
classes += ` ${linestylefilter.ATTRIB_CLASSES[key]}`;
|
||||
} else {
|
||||
classes += hooks.callAllStr('aceAttribsToClasses', {
|
||||
linestylefilter,
|
||||
key,
|
||||
value,
|
||||
}, ' ', ' ', '');
|
||||
const results = hooks.callAll('aceAttribsToClasses', {linestylefilter, key, value});
|
||||
classes += ` ${results.join(' ')}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,7 +294,7 @@ const handshake = () => {
|
|||
// set some client vars
|
||||
window.clientVars = obj.data;
|
||||
|
||||
// initalize the pad
|
||||
// initialize the pad
|
||||
pad._afterHandshake();
|
||||
|
||||
if (clientVars.readonly) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* global exports, require */
|
||||
'use strict';
|
||||
|
||||
const _ = require('underscore');
|
||||
const pluginDefs = require('./plugin_defs');
|
||||
|
||||
// Maps the name of a server-side hook to a string explaining the deprecation
|
||||
|
@ -15,66 +14,37 @@ exports.deprecationNotices = {};
|
|||
|
||||
const deprecationWarned = {};
|
||||
|
||||
function checkDeprecation(hook) {
|
||||
const checkDeprecation = (hook) => {
|
||||
const notice = exports.deprecationNotices[hook.hook_name];
|
||||
if (notice == null) return;
|
||||
if (deprecationWarned[hook.hook_fn_name]) return;
|
||||
console.warn(`${hook.hook_name} hook used by the ${hook.part.plugin} plugin ` +
|
||||
`(${hook.hook_fn_name}) is deprecated: ${notice}`);
|
||||
deprecationWarned[hook.hook_fn_name] = true;
|
||||
}
|
||||
|
||||
exports.bubbleExceptions = true;
|
||||
|
||||
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
|
||||
const normalize = function (x) {
|
||||
if (x === undefined) return [];
|
||||
return x;
|
||||
};
|
||||
const normalizedhook = function () {
|
||||
return normalize(hook.hook_fn(hook_name, args, (x) => cb(normalize(x))));
|
||||
};
|
||||
|
||||
if (exports.bubbleExceptions) {
|
||||
return normalizedhook();
|
||||
} else {
|
||||
try {
|
||||
return normalizedhook();
|
||||
} catch (ex) {
|
||||
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.syncMapFirst = function (lst, fn) {
|
||||
let i;
|
||||
let result;
|
||||
for (i = 0; i < lst.length; i++) {
|
||||
result = fn(lst[i]);
|
||||
if (result.length) return result;
|
||||
}
|
||||
return [];
|
||||
// Calls the node-style callback when the Promise settles. Unlike util.callbackify, this takes a
|
||||
// Promise (rather than a function that returns a Promise), and it returns a Promise (rather than a
|
||||
// function that returns undefined).
|
||||
const attachCallback = (p, cb) => p.then(
|
||||
(val) => cb(null, val),
|
||||
// Callbacks often only check the truthiness, not the nullness, of the first parameter. To avoid
|
||||
// problems, always pass a truthy value as the first argument if the Promise is rejected.
|
||||
(err) => cb(err || new Error(err)));
|
||||
|
||||
// Normalizes the value provided by hook functions so that it is always an array. `undefined` (but
|
||||
// not `null`!) becomes an empty array, array values are returned unmodified, and non-array values
|
||||
// are wrapped in an array (so `null` becomes `[null]`).
|
||||
const normalizeValue = (val) => {
|
||||
// `undefined` is treated the same as `[]`. IMPORTANT: `null` is *not* treated the same as `[]`
|
||||
// because some hooks use `null` as a special value.
|
||||
if (val === undefined) return [];
|
||||
if (Array.isArray(val)) return val;
|
||||
return [val];
|
||||
};
|
||||
|
||||
exports.mapFirst = function (lst, fn, cb, predicate) {
|
||||
if (predicate == null) predicate = (x) => (x != null && x.length > 0);
|
||||
let i = 0;
|
||||
|
||||
var next = function () {
|
||||
if (i >= lst.length) return cb(null, []);
|
||||
fn(lst[i++], (err, result) => {
|
||||
if (err) return cb(err);
|
||||
if (predicate(result)) return cb(null, result);
|
||||
next();
|
||||
});
|
||||
};
|
||||
next();
|
||||
};
|
||||
// Flattens the array one level.
|
||||
const flatten1 = (array) => array.reduce((a, b) => a.concat(b), []);
|
||||
|
||||
// Calls the hook function synchronously and returns the value provided by the hook function (via
|
||||
// callback or return value).
|
||||
|
@ -104,7 +74,7 @@ exports.mapFirst = function (lst, fn, cb, predicate) {
|
|||
//
|
||||
// See the tests in tests/backend/specs/hooks.js for examples of supported and prohibited behaviors.
|
||||
//
|
||||
function callHookFnSync(hook, context) {
|
||||
const callHookFnSync = (hook, context) => {
|
||||
checkDeprecation(hook);
|
||||
|
||||
// This var is used to keep track of whether the hook function already settled.
|
||||
|
@ -177,21 +147,36 @@ function callHookFnSync(hook, context) {
|
|||
// The hook function is assumed to not have a callback parameter, so fall through and accept
|
||||
// `undefined` as the resolved value.
|
||||
//
|
||||
// IMPORTANT: "Rest" parameters and default parameters are not counted in`Function.length`, so
|
||||
// the assumption does not hold for wrappers like `(...args) => { real(...args); }`. Such
|
||||
// functions will still work properly without any logged warnings or errors for now, but:
|
||||
// IMPORTANT: "Rest" parameters and default parameters are not included in `Function.length`,
|
||||
// so the assumption does not hold for wrappers such as:
|
||||
//
|
||||
// const wrapper = (...args) => real(...args);
|
||||
//
|
||||
// ECMAScript does not provide a way to determine whether a function has default or rest
|
||||
// parameters, so there is no way to be certain that a hook function with `length` < 3 will
|
||||
// not call the callback. Synchronous hook functions that call the callback even though
|
||||
// `length` < 3 will still work properly without any logged warnings or errors, but:
|
||||
//
|
||||
// * Once the hook is upgraded to support asynchronous hook functions, calling the callback
|
||||
// will (eventually) cause a double settle error, and the function might prematurely
|
||||
// asynchronously will cause a double settle error, and the hook function will prematurely
|
||||
// resolve to `undefined` instead of the desired value.
|
||||
//
|
||||
// * The above "unsettled function" warning is not logged if the function fails to call the
|
||||
// callback like it is supposed to.
|
||||
//
|
||||
// Wrapper functions can avoid problems by setting the wrapper's `length` property to match
|
||||
// the real function's `length` property:
|
||||
//
|
||||
// Object.defineProperty(wrapper, 'length', {value: real.length});
|
||||
}
|
||||
}
|
||||
|
||||
settle(null, val, 'returned value');
|
||||
return outcome.val;
|
||||
}
|
||||
};
|
||||
|
||||
// DEPRECATED: Use `callAllSerial()` or `aCallAll()` instead.
|
||||
//
|
||||
// Invokes all registered hook functions synchronously.
|
||||
//
|
||||
// Arguments:
|
||||
|
@ -203,15 +188,10 @@ function callHookFnSync(hook, context) {
|
|||
// 1. Collect all values returned by the hook functions into an array.
|
||||
// 2. Convert each `undefined` entry into `[]`.
|
||||
// 3. Flatten one level.
|
||||
exports.callAll = function (hookName, context) {
|
||||
exports.callAll = (hookName, context) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
return _.flatten(hooks.map((hook) => {
|
||||
const ret = callHookFnSync(hook, context);
|
||||
// `undefined` (but not `null`!) is treated the same as [].
|
||||
if (ret === undefined) return [];
|
||||
return ret;
|
||||
}), 1);
|
||||
return flatten1(hooks.map((hook) => normalizeValue(callHookFnSync(hook, context))));
|
||||
};
|
||||
|
||||
// Calls the hook function asynchronously and returns a Promise that either resolves to the hook
|
||||
|
@ -248,7 +228,7 @@ exports.callAll = function (hookName, context) {
|
|||
//
|
||||
// See the tests in tests/backend/specs/hooks.js for examples of supported and prohibited behaviors.
|
||||
//
|
||||
async function callHookFnAsync(hook, context) {
|
||||
const callHookFnAsync = async (hook, context) => {
|
||||
checkDeprecation(hook);
|
||||
return await new Promise((resolve, reject) => {
|
||||
// This var is used to keep track of whether the hook function already settled.
|
||||
|
@ -312,10 +292,21 @@ async function callHookFnAsync(hook, context) {
|
|||
// The hook function is assumed to not have a callback parameter, so fall through and accept
|
||||
// `undefined` as the resolved value.
|
||||
//
|
||||
// IMPORTANT: "Rest" parameters and default parameters are not counted in `Function.length`,
|
||||
// so the assumption does not hold for wrappers like `(...args) => { real(...args); }`. For
|
||||
// such functions, calling the callback will (eventually) cause a double settle error, and
|
||||
// the function might prematurely resolve to `undefined` instead of the desired value.
|
||||
// IMPORTANT: "Rest" parameters and default parameters are not included in
|
||||
// `Function.length`, so the assumption does not hold for wrappers such as:
|
||||
//
|
||||
// const wrapper = (...args) => real(...args);
|
||||
//
|
||||
// ECMAScript does not provide a way to determine whether a function has default or rest
|
||||
// parameters, so there is no way to be certain that a hook function with `length` < 3 will
|
||||
// not call the callback. Hook functions with `length` < 3 that call the callback
|
||||
// asynchronously will cause a double settle error, and the hook function will prematurely
|
||||
// resolve to `undefined` instead of the desired value.
|
||||
//
|
||||
// Wrapper functions can avoid problems by setting the wrapper's `length` property to match
|
||||
// the real function's `length` property:
|
||||
//
|
||||
// Object.defineProperty(wrapper, 'length', {value: real.length});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,17 +317,21 @@ async function callHookFnAsync(hook, context) {
|
|||
(val) => settle(null, val, 'returned value'),
|
||||
(err) => settle(err, null, 'Promise rejection'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Invokes all registered hook functions asynchronously.
|
||||
// Invokes all registered hook functions asynchronously and concurrently. This is NOT the async
|
||||
// equivalent of `callAll()`: `callAll()` calls the hook functions serially (one at a time) but this
|
||||
// function calls them concurrently. Use `callAllSerial()` if the hook functions must be called one
|
||||
// at a time.
|
||||
//
|
||||
// Arguments:
|
||||
// * hookName: Name of the hook to invoke.
|
||||
// * context: Passed unmodified to the hook functions, except nullish becomes {}.
|
||||
// * cb: Deprecated callback. The following:
|
||||
// * cb: Deprecated. Optional node-style callback. The following:
|
||||
// const p1 = hooks.aCallAll('myHook', context, cb);
|
||||
// is equivalent to:
|
||||
// const p2 = hooks.aCallAll('myHook', context).then((val) => cb(null, val), cb);
|
||||
// const p2 = hooks.aCallAll('myHook', context).then(
|
||||
// (val) => cb(null, val), (err) => cb(err || new Error(err)));
|
||||
//
|
||||
// Return value:
|
||||
// If cb is nullish, this function resolves to a flattened array of hook results. Specifically, it
|
||||
|
@ -345,57 +340,75 @@ async function callHookFnAsync(hook, context) {
|
|||
// 2. Convert each `undefined` entry into `[]`.
|
||||
// 3. Flatten one level.
|
||||
// If cb is non-null, this function resolves to the value returned by cb.
|
||||
exports.aCallAll = async (hookName, context, cb) => {
|
||||
exports.aCallAll = async (hookName, context, cb = null) => {
|
||||
if (cb != null) return await attachCallback(exports.aCallAll(hookName, context), cb);
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
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;
|
||||
const results = await Promise.all(
|
||||
hooks.map(async (hook) => normalizeValue(await callHookFnAsync(hook, context))));
|
||||
return flatten1(results);
|
||||
};
|
||||
|
||||
exports.callFirst = function (hook_name, args) {
|
||||
if (!args) args = {};
|
||||
if (pluginDefs.hooks[hook_name] === undefined) return [];
|
||||
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],
|
||||
(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((resolve, reject) => {
|
||||
aCallFirst(hook_name, args, (err, res) => err ? reject(err) : resolve(res), predicate);
|
||||
});
|
||||
} else {
|
||||
return aCallFirst(hook_name, args, cb, predicate);
|
||||
// Like `aCallAll()` except the hook functions are called one at a time instead of concurrently.
|
||||
// Only use this function if the hook functions must be called one at a time, otherwise use
|
||||
// `aCallAll()`.
|
||||
exports.callAllSerial = async (hookName, context) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
const results = [];
|
||||
for (const hook of hooks) {
|
||||
results.push(normalizeValue(await callHookFnAsync(hook, context)));
|
||||
}
|
||||
return flatten1(results);
|
||||
};
|
||||
|
||||
exports.callAllStr = function (hook_name, args, sep, pre, post) {
|
||||
if (sep == undefined) sep = '';
|
||||
if (pre == undefined) pre = '';
|
||||
if (post == undefined) post = '';
|
||||
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;
|
||||
// DEPRECATED: Use `aCallFirst()` instead.
|
||||
//
|
||||
// Like `aCallFirst()`, but synchronous. Hook functions must provide their values synchronously.
|
||||
exports.callFirst = (hookName, context) => {
|
||||
if (context == null) context = {};
|
||||
const predicate = (val) => val.length;
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
for (const hook of hooks) {
|
||||
const val = normalizeValue(callHookFnSync(hook, context));
|
||||
if (predicate(val)) return val;
|
||||
}
|
||||
return newCallhooks.join(sep || '');
|
||||
return [];
|
||||
};
|
||||
|
||||
// Invokes the registered hook functions one at a time until one provides a value that meets a
|
||||
// customizable condition.
|
||||
//
|
||||
// Arguments:
|
||||
// * hookName: Name of the hook to invoke.
|
||||
// * context: Passed unmodified to the hook functions, except nullish becomes {}.
|
||||
// * cb: Deprecated callback. The following:
|
||||
// const p1 = hooks.aCallFirst('myHook', context, cb);
|
||||
// is equivalent to:
|
||||
// const p2 = hooks.aCallFirst('myHook', context).then(
|
||||
// (val) => cb(null, val), (err) => cb(err || new Error(err)));
|
||||
// * predicate: Optional predicate function that returns true if the hook function provided a
|
||||
// value that satisfies a desired condition. If nullish, the predicate defaults to a non-empty
|
||||
// array check. The predicate is invoked each time a hook function returns. It takes one
|
||||
// argument: the normalized value provided by the hook function. If the predicate returns
|
||||
// truthy, iteration over the hook functions stops (no more hook functions will be called).
|
||||
//
|
||||
// Return value:
|
||||
// If cb is nullish, resolves to an array that is either the normalized value that satisfied the
|
||||
// predicate or empty if the predicate was never satisfied. If cb is non-nullish, resolves to the
|
||||
// value returned from cb().
|
||||
exports.aCallFirst = async (hookName, context, cb = null, predicate = null) => {
|
||||
if (cb != null) {
|
||||
return await attachCallback(exports.aCallFirst(hookName, context, null, predicate), cb);
|
||||
}
|
||||
if (context == null) context = {};
|
||||
if (predicate == null) predicate = (val) => val.length;
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
for (const hook of hooks) {
|
||||
const val = normalizeValue(await callHookFnAsync(hook, context));
|
||||
if (predicate(val)) return val;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const log4js = require('log4js');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const plugins = require('./plugins');
|
||||
const hooks = require('./hooks');
|
||||
const npm = require('npm');
|
||||
const request = require('request');
|
||||
const util = require('util');
|
||||
|
@ -13,22 +15,22 @@ const loadNpm = async () => {
|
|||
npm.on('log', log4js.getLogger('npm').log);
|
||||
};
|
||||
|
||||
const onAllTasksFinished = () => {
|
||||
hooks.aCallAll('restartServer', {}, () => {});
|
||||
};
|
||||
|
||||
let tasks = 0;
|
||||
|
||||
function wrapTaskCb(cb) {
|
||||
tasks++;
|
||||
|
||||
return function () {
|
||||
cb && cb.apply(this, arguments);
|
||||
return function (...args) {
|
||||
cb && cb.apply(this, args);
|
||||
tasks--;
|
||||
if (tasks == 0) onAllTasksFinished();
|
||||
if (tasks === 0) onAllTasksFinished();
|
||||
};
|
||||
}
|
||||
|
||||
function onAllTasksFinished() {
|
||||
hooks.aCallAll('restartServer', {}, () => {});
|
||||
}
|
||||
|
||||
exports.uninstall = async (pluginName, cb = null) => {
|
||||
cb = wrapTaskCb(cb);
|
||||
try {
|
||||
|
@ -60,7 +62,7 @@ exports.install = async (pluginName, cb = null) => {
|
|||
exports.availablePlugins = null;
|
||||
let cacheTimestamp = 0;
|
||||
|
||||
exports.getAvailablePlugins = function (maxCacheAge) {
|
||||
exports.getAvailablePlugins = (maxCacheAge) => {
|
||||
const nowTimestamp = Math.round(Date.now() / 1000);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -87,31 +89,33 @@ exports.getAvailablePlugins = function (maxCacheAge) {
|
|||
};
|
||||
|
||||
|
||||
exports.search = function (searchTerm, maxCacheAge) {
|
||||
return exports.getAvailablePlugins(maxCacheAge).then((results) => {
|
||||
const res = {};
|
||||
exports.search = (searchTerm, maxCacheAge) => exports.getAvailablePlugins(maxCacheAge).then(
|
||||
(results) => {
|
||||
const res = {};
|
||||
|
||||
if (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
continue;
|
||||
if (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
}
|
||||
|
||||
res[pluginName] = results[pluginName];
|
||||
}
|
||||
for (const pluginName in results) {
|
||||
// for every available plugin
|
||||
// TODO: Also search in keywords here!
|
||||
if (pluginName.indexOf(plugins.prefix) !== 0) continue;
|
||||
|
||||
return res;
|
||||
});
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
res[pluginName] = results[pluginName];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
// This module contains processed plugin definitions. The data structures in this file are set by
|
||||
// plugins.js (server) or client_plugins.js (client).
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const hooks = require('./hooks');
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const readInstalled = require('./read-installed.js');
|
||||
const path = require('path');
|
||||
const tsort = require('./tsort');
|
||||
|
@ -13,11 +14,9 @@ const defs = require('./plugin_defs');
|
|||
|
||||
exports.prefix = 'ep_';
|
||||
|
||||
exports.formatPlugins = function () {
|
||||
return _.keys(defs.plugins).join(', ');
|
||||
};
|
||||
exports.formatPlugins = () => Object.keys(defs.plugins).join(', ');
|
||||
|
||||
exports.formatPluginsWithVersion = function () {
|
||||
exports.formatPluginsWithVersion = () => {
|
||||
const plugins = [];
|
||||
_.forEach(defs.plugins, (plugin) => {
|
||||
if (plugin.package.name !== 'ep_etherpad-lite') {
|
||||
|
@ -28,17 +27,16 @@ exports.formatPluginsWithVersion = function () {
|
|||
return plugins.join(', ');
|
||||
};
|
||||
|
||||
exports.formatParts = function () {
|
||||
return _.map(defs.parts, (part) => part.full_name).join('\n');
|
||||
};
|
||||
exports.formatParts = () => _.map(defs.parts, (part) => part.full_name).join('\n');
|
||||
|
||||
exports.formatHooks = function (hook_set_name) {
|
||||
exports.formatHooks = (hook_set_name) => {
|
||||
const res = [];
|
||||
const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
|
||||
|
||||
_.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>`);
|
||||
res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} ` +
|
||||
`from ${hook.part.full_name}</dd>`);
|
||||
});
|
||||
});
|
||||
return `<dl>${res.join('\n')}</dl>`;
|
||||
|
@ -57,7 +55,7 @@ const callInit = async () => {
|
|||
}));
|
||||
};
|
||||
|
||||
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
||||
exports.pathNormalization = (part, hook_fn_name, hook_name) => {
|
||||
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
|
||||
// If there is a single colon assume it's 'filename:funcname' not 'C:\\filename'.
|
||||
const functionName = (tmp.length > 1 ? tmp.pop() : null) || hook_name;
|
||||
|
@ -67,7 +65,7 @@ exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
|||
return `${fileName}:${functionName}`;
|
||||
};
|
||||
|
||||
exports.update = async function () {
|
||||
exports.update = async () => {
|
||||
const packages = await exports.getPackages();
|
||||
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
const plugins = {};
|
||||
|
@ -83,13 +81,14 @@ exports.update = async function () {
|
|||
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
|
||||
exports.getPackages = async () => {
|
||||
// Load list of installed NPM packages, flatten it to a list,
|
||||
// and filter out only packages with names that
|
||||
const dir = settings.root;
|
||||
const data = await util.promisify(readInstalled)(dir);
|
||||
|
||||
const packages = {};
|
||||
function flatten(deps) {
|
||||
const flatten = (deps) => {
|
||||
_.chain(deps).keys().each((name) => {
|
||||
if (name.indexOf(exports.prefix) === 0) {
|
||||
packages[name] = _.clone(deps[name]);
|
||||
|
@ -102,7 +101,7 @@ exports.getPackages = async function () {
|
|||
// I don't think we need recursion
|
||||
// if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const tmp = {};
|
||||
tmp[data.name] = data;
|
||||
|
@ -110,7 +109,7 @@ exports.getPackages = async function () {
|
|||
return packages;
|
||||
};
|
||||
|
||||
async function loadPlugin(packages, plugin_name, plugins, parts) {
|
||||
const loadPlugin = async (packages, plugin_name, plugins, parts) => {
|
||||
const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
|
||||
try {
|
||||
const data = await fs.readFile(plugin_path);
|
||||
|
@ -129,9 +128,9 @@ async function loadPlugin(packages, plugin_name, plugins, parts) {
|
|||
} catch (er) {
|
||||
console.error(`Unable to load plugin definition file ${plugin_path}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function partsToParentChildList(parts) {
|
||||
const partsToParentChildList = (parts) => {
|
||||
const res = [];
|
||||
_.chain(parts).keys().forEach((name) => {
|
||||
_.each(parts[name].post || [], (child_name) => {
|
||||
|
@ -145,15 +144,9 @@ function partsToParentChildList(parts) {
|
|||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// Used only in Node, so no need for _
|
||||
function sortParts(parts) {
|
||||
return tsort(
|
||||
partsToParentChildList(parts)
|
||||
).filter(
|
||||
(name) => parts[name] !== undefined
|
||||
).map(
|
||||
(name) => parts[name]
|
||||
);
|
||||
}
|
||||
const sortParts = (parts) => tsort(partsToParentChildList(parts))
|
||||
.filter((name) => parts[name] !== undefined)
|
||||
.map((name) => parts[name]);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
'use strict';
|
||||
const _ = require('underscore');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
|
@ -8,13 +9,13 @@ const disabledHookReasons = {
|
|||
},
|
||||
};
|
||||
|
||||
function loadFn(path, hookName) {
|
||||
const loadFn = (path, hookName) => {
|
||||
let functionName;
|
||||
const parts = path.split(':');
|
||||
|
||||
// on windows: C:\foo\bar:xyz
|
||||
if (parts[0].length == 1) {
|
||||
if (parts.length == 3) {
|
||||
if (parts[0].length === 1) {
|
||||
if (parts.length === 3) {
|
||||
functionName = parts.pop();
|
||||
}
|
||||
path = parts.join(':');
|
||||
|
@ -30,9 +31,9 @@ function loadFn(path, hookName) {
|
|||
fn = fn[name];
|
||||
});
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
|
||||
function extractHooks(parts, hook_set_name, normalizer) {
|
||||
const extractHooks = (parts, hook_set_name, normalizer) => {
|
||||
const hooks = {};
|
||||
_.each(parts, (part) => {
|
||||
_.chain(part[hook_set_name] || {})
|
||||
|
@ -50,20 +51,23 @@ function extractHooks(parts, hook_set_name, normalizer) {
|
|||
|
||||
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
|
||||
if (disabledReason) {
|
||||
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${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`);
|
||||
'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;
|
||||
}
|
||||
|
||||
let hook_fn;
|
||||
try {
|
||||
var hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
if (!hook_fn) {
|
||||
throw 'Not a function';
|
||||
throw new Error('Not a function');
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error(`Failed to load '${hook_fn_name}' for '${part.full_name}/${hook_set_name}/${hook_name}': ${exc.toString()}`);
|
||||
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] = [];
|
||||
|
@ -72,7 +76,7 @@ function extractHooks(parts, hook_set_name, normalizer) {
|
|||
});
|
||||
});
|
||||
return hooks;
|
||||
}
|
||||
};
|
||||
|
||||
exports.extractHooks = extractHooks;
|
||||
|
||||
|
@ -88,10 +92,10 @@ exports.extractHooks = extractHooks;
|
|||
* No plugins: []
|
||||
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
|
||||
*/
|
||||
exports.clientPluginNames = function () {
|
||||
exports.clientPluginNames = () => {
|
||||
const client_plugin_names = _.uniq(
|
||||
defs.parts
|
||||
.filter((part) => part.hasOwnProperty('client_hooks'))
|
||||
.filter((part) => Object.prototype.hasOwnProperty.call(part, 'client_hooks'))
|
||||
.map((part) => `plugin-${part.plugin}`)
|
||||
);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ function runTest(number) {
|
|||
let fN = '/test.txt';
|
||||
let cT = 'text/plain';
|
||||
|
||||
// To be more agressive every other test we mess with Etherpad
|
||||
// To be more aggressive every other test we mess with Etherpad
|
||||
// We provide a weird file name and also set a weird contentType
|
||||
if (number % 2 == 0) {
|
||||
fN = froth().toString();
|
||||
|
|
|
@ -94,7 +94,7 @@ const testImports = {
|
|||
wantText: ' word1 word2 word3\n\n',
|
||||
},
|
||||
'nonBreakingSpacePreceededBySpaceBetweenWords': {
|
||||
description: 'A non-breaking space preceeded by a normal space',
|
||||
description: 'A non-breaking space preceded by a normal space',
|
||||
input: '<html><body> word1 word2 word3<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body> word1 word2 word3<br><br></body></html>',
|
||||
wantText: ' word1 word2 word3\n\n',
|
||||
|
@ -191,7 +191,7 @@ const testImports = {
|
|||
wantText: 'а б в г ґ д е є ж з и і ї й к л м н о\nmultiple\n lines\n in\n pre\n\nп р с т у ф х ц ч ш щ ю я ь\n\n',
|
||||
},
|
||||
'preIntroducesASpace': {
|
||||
description: 'pre should be on a new line not preceeded by a space',
|
||||
description: 'pre should be on a new line not preceded by a space',
|
||||
input: `<html><body><p>
|
||||
1
|
||||
<pre>preline
|
||||
|
|
|
@ -107,9 +107,9 @@ describe(__filename, function () {
|
|||
-> setText(padId, "hello world")
|
||||
-> getLastEdited(padID) -- Should be when pad was made
|
||||
-> getText(padId) -- Should be "hello world"
|
||||
-> movePad(padID, newPadId) -- Should provide consistant pad data
|
||||
-> movePad(padID, newPadId) -- Should provide consistent pad data
|
||||
-> getText(newPadId) -- Should be "hello world"
|
||||
-> movePad(newPadID, originalPadId) -- Should provide consistant pad data
|
||||
-> movePad(newPadID, originalPadId) -- Should provide consistent pad data
|
||||
-> getText(originalPadId) -- Should be "hello world"
|
||||
-> getLastEdited(padID) -- Should not be 0
|
||||
-> appendText(padID, "hello")
|
||||
|
|
|
@ -100,6 +100,83 @@ describe(__filename, function () {
|
|||
assert(res.body.data.groupID);
|
||||
});
|
||||
});
|
||||
|
||||
// Test coverage for https://github.com/ether/etherpad-lite/issues/4227
|
||||
// Creates a group, creates 2 sessions, 2 pads and then deletes the group.
|
||||
it('createGroup', async function () {
|
||||
await api.get(endPoint('createGroup'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
groupID = res.body.data.groupID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createAuthor', async function () {
|
||||
await api.get(endPoint('createAuthor'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.authorID);
|
||||
authorID = res.body.data.authorID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createSession', async function () {
|
||||
await api.get(`${endPoint('createSession')
|
||||
}&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.sessionID);
|
||||
sessionID = res.body.data.sessionID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createSession', async function () {
|
||||
await api.get(`${endPoint('createSession')
|
||||
}&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.sessionID);
|
||||
sessionID = res.body.data.sessionID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupPad', async function () {
|
||||
await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x1234567`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupPad', async function () {
|
||||
await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x12345678`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('deleteGroup', async function () {
|
||||
await api.get(`${endPoint('deleteGroup')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
// End of coverage for https://github.com/ether/etherpad-lite/issues/4227
|
||||
|
||||
});
|
||||
|
||||
describe('API: Author creation', function () {
|
||||
|
|
|
@ -140,7 +140,7 @@ const tests = {
|
|||
wantText: [' word1 word2 word3'],
|
||||
},
|
||||
nonBreakingSpacePreceededBySpaceBetweenWords: {
|
||||
description: 'A non-breaking space preceeded by a normal space',
|
||||
description: 'A non-breaking space preceded by a normal space',
|
||||
html: '<html><body> word1 word2 word3<br></body></html>',
|
||||
wantLineAttribs: ['+l'],
|
||||
wantText: [' word1 word2 word3'],
|
||||
|
@ -240,7 +240,7 @@ pre
|
|||
],
|
||||
},
|
||||
preIntroducesASpace: {
|
||||
description: 'pre should be on a new line not preceeded by a space',
|
||||
description: 'pre should be on a new line not preceded by a space',
|
||||
html: `<html><body><p>
|
||||
1
|
||||
</p><pre>preline
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
function m(mod) { return `${__dirname}/../../../src/${mod}`; }
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const hooks = require(m('static/js/pluginfw/hooks'));
|
||||
const plugins = require(m('static/js/pluginfw/plugin_defs'));
|
||||
const sinon = require(m('node_modules/sinon'));
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const sinon = require('ep_etherpad-lite/node_modules/sinon');
|
||||
|
||||
describe(__filename, function () {
|
||||
const hookName = 'testHook';
|
||||
|
@ -209,7 +206,7 @@ describe(__filename, function () {
|
|||
// Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
|
||||
// time, or call the callback and then return a value.)
|
||||
describe('bad hook function behavior (double settle)', function () {
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
sinon.stub(console, 'error');
|
||||
});
|
||||
|
||||
|
@ -413,6 +410,90 @@ describe(__filename, function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('hooks.callFirst', function () {
|
||||
it('no registered hooks (undefined) -> []', async function () {
|
||||
delete plugins.hooks.testHook;
|
||||
assert.deepEqual(hooks.callFirst(hookName), []);
|
||||
});
|
||||
|
||||
it('no registered hooks (empty list) -> []', async function () {
|
||||
testHooks.length = 0;
|
||||
assert.deepEqual(hooks.callFirst(hookName), []);
|
||||
});
|
||||
|
||||
it('passes hook name => {}', async function () {
|
||||
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
|
||||
hooks.callFirst(hookName);
|
||||
});
|
||||
|
||||
it('undefined context => {}', async function () {
|
||||
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
hooks.callFirst(hookName);
|
||||
});
|
||||
|
||||
it('null context => {}', async function () {
|
||||
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
hooks.callFirst(hookName, null);
|
||||
});
|
||||
|
||||
it('context unmodified', async function () {
|
||||
const wantContext = {};
|
||||
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
|
||||
hooks.callFirst(hookName, wantContext);
|
||||
});
|
||||
|
||||
it('predicate never satisfied -> calls all in order', async function () {
|
||||
const gotCalls = [];
|
||||
testHooks.length = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const hook = makeHook();
|
||||
hook.hook_fn = () => { gotCalls.push(i); };
|
||||
testHooks.push(hook);
|
||||
}
|
||||
assert.deepEqual(hooks.callFirst(hookName), []);
|
||||
assert.deepEqual(gotCalls, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it('stops when predicate is satisfied', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
|
||||
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('skips values that do not satisfy predicate (undefined)', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook('val1'));
|
||||
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('skips values that do not satisfy predicate (empty list)', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook([]), makeHook('val1'));
|
||||
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('null satisifes the predicate', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(null), makeHook('val1'));
|
||||
assert.deepEqual(hooks.callFirst(hookName), [null]);
|
||||
});
|
||||
|
||||
it('non-empty arrays are returned unmodified', async function () {
|
||||
const want = ['val1'];
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(want), makeHook(['val2']));
|
||||
assert.equal(hooks.callFirst(hookName), want); // Note: *NOT* deepEqual!
|
||||
});
|
||||
|
||||
it('value can be passed via callback', async function () {
|
||||
const want = {};
|
||||
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
|
||||
const got = hooks.callFirst(hookName);
|
||||
assert.deepEqual(got, [want]);
|
||||
assert.equal(got[0], want); // Note: *NOT* deepEqual!
|
||||
});
|
||||
});
|
||||
|
||||
describe('callHookFnAsync', function () {
|
||||
const callHookFnAsync = hooks.exportedForTestingOnly.callHookFnAsync; // Convenience shorthand.
|
||||
|
||||
|
@ -587,7 +668,7 @@ describe(__filename, function () {
|
|||
// Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
|
||||
// time, or call the callback and then return a value.)
|
||||
describe('bad hook function behavior (double settle)', function () {
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
sinon.stub(console, 'error');
|
||||
});
|
||||
|
||||
|
@ -935,4 +1016,243 @@ describe(__filename, function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hooks.callAllSerial', function () {
|
||||
describe('basic behavior', function () {
|
||||
it('calls all asynchronously, serially, in order', async function () {
|
||||
const gotCalls = [];
|
||||
testHooks.length = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const hook = makeHook();
|
||||
hook.hook_fn = async () => {
|
||||
gotCalls.push(i);
|
||||
// Check gotCalls asynchronously to ensure that the next hook function does not start
|
||||
// executing before this hook function has resolved.
|
||||
return await new Promise((resolve) => {
|
||||
setImmediate(() => {
|
||||
assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
|
||||
resolve(i);
|
||||
});
|
||||
});
|
||||
};
|
||||
testHooks.push(hook);
|
||||
}
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), [0, 1, 2]);
|
||||
assert.deepEqual(gotCalls, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it('passes hook name', async function () {
|
||||
hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
|
||||
await hooks.callAllSerial(hookName);
|
||||
});
|
||||
|
||||
it('undefined context -> {}', async function () {
|
||||
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
await hooks.callAllSerial(hookName);
|
||||
});
|
||||
|
||||
it('null context -> {}', async function () {
|
||||
hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
await hooks.callAllSerial(hookName, null);
|
||||
});
|
||||
|
||||
it('context unmodified', async function () {
|
||||
const wantContext = {};
|
||||
hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
|
||||
await hooks.callAllSerial(hookName, wantContext);
|
||||
});
|
||||
});
|
||||
|
||||
describe('result processing', function () {
|
||||
it('no registered hooks (undefined) -> []', async function () {
|
||||
delete plugins.hooks[hookName];
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), []);
|
||||
});
|
||||
|
||||
it('no registered hooks (empty list) -> []', async function () {
|
||||
testHooks.length = 0;
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), []);
|
||||
});
|
||||
|
||||
it('flattens one level', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), [1, 2, [3]]);
|
||||
});
|
||||
|
||||
it('filters out undefined', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]), makeHook(Promise.resolve()));
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), [2, [3]]);
|
||||
});
|
||||
|
||||
it('preserves null', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(null), makeHook([2]), makeHook(Promise.resolve(null)));
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), [null, 2, null]);
|
||||
});
|
||||
|
||||
it('all undefined -> []', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook(Promise.resolve()));
|
||||
assert.deepEqual(await hooks.callAllSerial(hookName), []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hooks.aCallFirst', function () {
|
||||
it('no registered hooks (undefined) -> []', async function () {
|
||||
delete plugins.hooks.testHook;
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), []);
|
||||
});
|
||||
|
||||
it('no registered hooks (empty list) -> []', async function () {
|
||||
testHooks.length = 0;
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), []);
|
||||
});
|
||||
|
||||
it('passes hook name => {}', async function () {
|
||||
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
|
||||
await hooks.aCallFirst(hookName);
|
||||
});
|
||||
|
||||
it('undefined context => {}', async function () {
|
||||
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
await hooks.aCallFirst(hookName);
|
||||
});
|
||||
|
||||
it('null context => {}', async function () {
|
||||
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
|
||||
await hooks.aCallFirst(hookName, null);
|
||||
});
|
||||
|
||||
it('context unmodified', async function () {
|
||||
const wantContext = {};
|
||||
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
|
||||
await hooks.aCallFirst(hookName, wantContext);
|
||||
});
|
||||
|
||||
it('default predicate: predicate never satisfied -> calls all in order', async function () {
|
||||
const gotCalls = [];
|
||||
testHooks.length = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const hook = makeHook();
|
||||
hook.hook_fn = () => { gotCalls.push(i); };
|
||||
testHooks.push(hook);
|
||||
}
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), []);
|
||||
assert.deepEqual(gotCalls, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it('calls hook functions serially', async function () {
|
||||
const gotCalls = [];
|
||||
testHooks.length = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const hook = makeHook();
|
||||
hook.hook_fn = async () => {
|
||||
gotCalls.push(i);
|
||||
// Check gotCalls asynchronously to ensure that the next hook function does not start
|
||||
// executing before this hook function has resolved.
|
||||
return await new Promise((resolve) => {
|
||||
setImmediate(() => {
|
||||
assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
testHooks.push(hook);
|
||||
}
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), []);
|
||||
assert.deepEqual(gotCalls, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it('default predicate: stops when satisfied', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('default predicate: skips values that do not satisfy (undefined)', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(), makeHook('val1'));
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('default predicate: skips values that do not satisfy (empty list)', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook([]), makeHook('val1'));
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
|
||||
});
|
||||
|
||||
it('default predicate: null satisifes', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(null), makeHook('val1'));
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName), [null]);
|
||||
});
|
||||
|
||||
it('custom predicate: called for each hook function', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(0), makeHook(1), makeHook(2));
|
||||
let got = 0;
|
||||
await hooks.aCallFirst(hookName, null, null, (val) => { ++got; return false; });
|
||||
assert.equal(got, 3);
|
||||
});
|
||||
|
||||
it('custom predicate: boolean false/true continues/stops iteration', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
|
||||
let nCall = 0;
|
||||
const predicate = (val) => {
|
||||
assert.deepEqual(val, [++nCall]);
|
||||
return nCall === 2;
|
||||
};
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
|
||||
assert.equal(nCall, 2);
|
||||
});
|
||||
|
||||
it('custom predicate: non-boolean falsy/truthy continues/stops iteration', async function () {
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
|
||||
let nCall = 0;
|
||||
const predicate = (val) => {
|
||||
assert.deepEqual(val, [++nCall]);
|
||||
return nCall === 2 ? {} : null;
|
||||
};
|
||||
assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
|
||||
assert.equal(nCall, 2);
|
||||
});
|
||||
|
||||
it('custom predicate: array value passed unmodified to predicate', async function () {
|
||||
const want = [0];
|
||||
hook.hook_fn = () => want;
|
||||
const predicate = (got) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
|
||||
await hooks.aCallFirst(hookName, null, null, predicate);
|
||||
});
|
||||
|
||||
it('custom predicate: normalized value passed to predicate (undefined)', async function () {
|
||||
const predicate = (got) => { assert.deepEqual(got, []); };
|
||||
await hooks.aCallFirst(hookName, null, null, predicate);
|
||||
});
|
||||
|
||||
it('custom predicate: normalized value passed to predicate (null)', async function () {
|
||||
hook.hook_fn = () => null;
|
||||
const predicate = (got) => { assert.deepEqual(got, [null]); };
|
||||
await hooks.aCallFirst(hookName, null, null, predicate);
|
||||
});
|
||||
|
||||
it('non-empty arrays are returned unmodified', async function () {
|
||||
const want = ['val1'];
|
||||
testHooks.length = 0;
|
||||
testHooks.push(makeHook(want), makeHook(['val2']));
|
||||
assert.equal(await hooks.aCallFirst(hookName), want); // Note: *NOT* deepEqual!
|
||||
});
|
||||
|
||||
it('value can be passed via callback', async function () {
|
||||
const want = {};
|
||||
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
|
||||
const got = await hooks.aCallFirst(hookName);
|
||||
assert.deepEqual(got, [want]);
|
||||
assert.equal(got[0], want); // Note: *NOT* deepEqual!
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
function m(mod) { return `${__dirname}/../../../src/${mod}`; }
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const common = require('../common');
|
||||
const plugins = require(m('static/js/pluginfw/plugin_defs'));
|
||||
const settings = require(m('node/utils/Settings'));
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
|
||||
describe(__filename, function () {
|
||||
this.timeout(30000);
|
||||
|
@ -13,6 +11,13 @@ describe(__filename, function () {
|
|||
const backups = {};
|
||||
const authHookNames = ['preAuthorize', 'authenticate', 'authorize'];
|
||||
const failHookNames = ['preAuthzFailure', 'authnFailure', 'authzFailure', 'authFailure'];
|
||||
const makeHook = (hookName, hookFn) => ({
|
||||
hook_fn: hookFn,
|
||||
hook_fn_name: `fake_plugin/${hookName}`,
|
||||
hook_name: hookName,
|
||||
part: {plugin: 'fake_plugin'},
|
||||
});
|
||||
|
||||
before(async function () { agent = await common.init(); });
|
||||
beforeEach(async function () {
|
||||
backups.hooks = {};
|
||||
|
@ -154,7 +159,10 @@ describe(__filename, function () {
|
|||
const h0 = new Handler(hookName, '_0');
|
||||
const h1 = new Handler(hookName, '_1');
|
||||
handlers[hookName] = [h0, h1];
|
||||
plugins.hooks[hookName] = [{hook_fn: h0.handle.bind(h0)}, {hook_fn: h1.handle.bind(h1)}];
|
||||
plugins.hooks[hookName] = [
|
||||
makeHook(hookName, h0.handle.bind(h0)),
|
||||
makeHook(hookName, h1.handle.bind(h1)),
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -217,7 +225,7 @@ describe(__filename, function () {
|
|||
this.timeout(100);
|
||||
handlers.preAuthorize[0].innerHandle = () => [false];
|
||||
let called = false;
|
||||
plugins.hooks.preAuthzFailure = [{hook_fn: (hookName, {req, res}, cb) => {
|
||||
plugins.hooks.preAuthzFailure = [makeHook('preAuthzFailure', (hookName, {req, res}, cb) => {
|
||||
assert.equal(hookName, 'preAuthzFailure');
|
||||
assert(req != null);
|
||||
assert(res != null);
|
||||
|
@ -225,7 +233,7 @@ describe(__filename, function () {
|
|||
called = true;
|
||||
res.status(200).send('injected');
|
||||
return cb([true]);
|
||||
}}];
|
||||
})];
|
||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
|
||||
assert(called);
|
||||
});
|
||||
|
@ -441,11 +449,11 @@ describe(__filename, function () {
|
|||
};
|
||||
const handlers = {};
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
failHookNames.forEach((hookName) => {
|
||||
const handler = new Handler(hookName);
|
||||
handlers[hookName] = handler;
|
||||
plugins.hooks[hookName] = [{hook_fn: handler.handle.bind(handler)}];
|
||||
plugins.hooks[hookName] = [makeHook(hookName, handler.handle.bind(handler))];
|
||||
});
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
'use strict';
|
||||
const helper = {}; // eslint-disable-line
|
||||
const helper = {}; // eslint-disable-line no-redeclare
|
||||
|
||||
(function () {
|
||||
let $iframe; const
|
||||
|
@ -181,7 +181,7 @@ const helper = {}; // eslint-disable-line
|
|||
};
|
||||
|
||||
helper.waitFor = function (conditionFunc, timeoutTime = 1900, intervalTime = 10) {
|
||||
const deferred = $.Deferred(); // eslint-disable-line
|
||||
const deferred = new $.Deferred();
|
||||
|
||||
const _fail = deferred.fail.bind(deferred);
|
||||
let listenForFail = false;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
helper.spyOnSocketIO = function () {
|
||||
helper.contentWindow().pad.socket.on('message', (msg) => {
|
||||
if (msg.type == 'COLLABROOM') {
|
||||
if (msg.data.type == 'ACCEPT_COMMIT') {
|
||||
if (msg.type === 'COLLABROOM') {
|
||||
if (msg.data.type === 'ACCEPT_COMMIT') {
|
||||
helper.commits.push(msg);
|
||||
} else if (msg.data.type == 'USER_NEWINFO') {
|
||||
} else if (msg.data.type === 'USER_NEWINFO') {
|
||||
helper.userInfos.push(msg);
|
||||
} else if (msg.data.type == 'CHAT_MESSAGE') {
|
||||
} else if (msg.data.type === 'CHAT_MESSAGE') {
|
||||
helper.chatMessages.push(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* the contentWindow is either the normal pad or timeslider
|
||||
*
|
||||
|
|
|
@ -170,7 +170,7 @@ $(() => {
|
|||
}
|
||||
});
|
||||
|
||||
// initalize the test helper
|
||||
// initialize the test helper
|
||||
helper.init(() => {
|
||||
// configure and start the test framework
|
||||
const grep = getURLParameter('grep');
|
||||
|
|
|
@ -7,10 +7,11 @@ describe('author of pad edition', function () {
|
|||
|
||||
// author 1 creates a new pad with some content (regular lines and lists)
|
||||
before(function (done) {
|
||||
var padId = helper.newPad(() => {
|
||||
const padId = helper.newPad(() => {
|
||||
// make sure pad has at least 3 lines
|
||||
const $firstLine = helper.padInner$('div').first();
|
||||
const threeLines = ['regular line', 'line with ordered list', 'line with unordered list'].join('<br>');
|
||||
const threeLines = ['regular line', 'line with ordered list', 'line with unordered list']
|
||||
.join('<br>');
|
||||
$firstLine.html(threeLines);
|
||||
|
||||
// wait for lines to be processed by Etherpad
|
||||
|
@ -45,7 +46,8 @@ describe('author of pad edition', function () {
|
|||
setTimeout(() => {
|
||||
// Expire cookie, so author is changed after reloading the pad.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie
|
||||
helper.padChrome$.document.cookie = 'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
helper.padChrome$.document.cookie =
|
||||
'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
|
||||
helper.newPad(done, padId);
|
||||
}, 10000);
|
||||
|
@ -62,26 +64,25 @@ describe('author of pad edition', function () {
|
|||
changeLineAndCheckOnlyThatChangeIsFromThisAuthor(REGULAR_LINE, 'x', done);
|
||||
});
|
||||
|
||||
it('marks only the new content as changes of the second user on a line with ordered list', function (done) {
|
||||
|
||||
it('marks only the new content as changes of the second user on a ' +
|
||||
'line with ordered list', function (done) {
|
||||
this.timeout(1000);
|
||||
changeLineAndCheckOnlyThatChangeIsFromThisAuthor(LINE_WITH_ORDERED_LIST, 'y', done);
|
||||
});
|
||||
|
||||
it('marks only the new content as changes of the second user on a line with unordered list', function (done) {
|
||||
it('marks only the new content as changes of the second user on ' +
|
||||
'a line with unordered list', function (done) {
|
||||
this.timeout(1000);
|
||||
changeLineAndCheckOnlyThatChangeIsFromThisAuthor(LINE_WITH_UNORDERED_LIST, 'z', done);
|
||||
});
|
||||
|
||||
/* ********************** Helper functions ************************ */
|
||||
var getLine = function (lineNumber) {
|
||||
return helper.padInner$('div').eq(lineNumber);
|
||||
};
|
||||
const getLine = (lineNumber) => helper.padInner$('div').eq(lineNumber);
|
||||
|
||||
const getAuthorFromClassList = function (classes) {
|
||||
return classes.find((cls) => cls.startsWith('author'));
|
||||
};
|
||||
const getAuthorFromClassList = (classes) => classes.find((cls) => cls.startsWith('author'));
|
||||
|
||||
var changeLineAndCheckOnlyThatChangeIsFromThisAuthor = function (lineNumber, textChange, done) {
|
||||
const changeLineAndCheckOnlyThatChangeIsFromThisAuthor = (lineNumber, textChange, done) => {
|
||||
// get original author class
|
||||
const classes = getLine(lineNumber).find('span').first().attr('class').split(' ');
|
||||
const originalAuthor = getAuthorFromClassList(classes);
|
||||
|
|
|
@ -22,8 +22,6 @@ describe('bold button', function () {
|
|||
const $boldButton = chrome$('.buttonicon-bold');
|
||||
$boldButton.click();
|
||||
|
||||
// ace creates a new dom element when you press a button
|
||||
// so just get the first text element again
|
||||
const $newFirstTextElement = inner$('div').first();
|
||||
|
||||
// is there a <b> element now?
|
||||
|
@ -48,13 +46,11 @@ describe('bold button', function () {
|
|||
// select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 66; // b
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
||||
// ace creates a new dom element when you press a button
|
||||
// so just get the first text element again
|
||||
const $newFirstTextElement = inner$('div').first();
|
||||
|
||||
// is there a <b> element now?
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
describe('As the caret is moved is the UI properly updated?', function () {
|
||||
/*
|
||||
let padName;
|
||||
const numberOfRows = 50;
|
||||
/*
|
||||
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
|
@ -16,7 +18,8 @@ describe('As the caret is moved is the UI properly updated?', function () {
|
|||
*/
|
||||
|
||||
/* Tests to do
|
||||
* Keystroke up (38), down (40), left (37), right (39) with and without special keys IE control / shift
|
||||
* Keystroke up (38), down (40), left (37), right (39)
|
||||
* with and without special keys IE control / shift
|
||||
* Page up (33) / down (34) with and without special keys
|
||||
* Page up on the first line shouldn't move the viewport
|
||||
* Down down on the last line shouldn't move the viewport
|
||||
|
@ -25,7 +28,9 @@ describe('As the caret is moved is the UI properly updated?', function () {
|
|||
*/
|
||||
|
||||
/* Challenges
|
||||
* How do we keep the authors focus on a line if the lines above the author are modified? We should only redraw the user to a location if they are typing and make sure shift and arrow keys aren't redrawing the UI else highlight - copy/paste would get broken
|
||||
* How do we keep the authors focus on a line if the lines above the author are modified?
|
||||
* We should only redraw the user to a location if they are typing and make sure shift
|
||||
* and arrow keys aren't redrawing the UI else highlight - copy/paste would get broken
|
||||
* How can we simulate an edit event in the test framework?
|
||||
*/
|
||||
/*
|
||||
|
@ -200,7 +205,8 @@ console.log(inner$);
|
|||
var chrome$ = helper.padChrome$;
|
||||
var numberOfRows = 50;
|
||||
|
||||
//ace creates a new dom element when you press a keystroke, so just get the first text element again
|
||||
// ace creates a new dom element when you press a keystroke,
|
||||
// so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
var originalDivHeight = inner$("div").first().css("height");
|
||||
prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target
|
||||
|
@ -208,28 +214,33 @@ console.log(inner$);
|
|||
helper.waitFor(function(){ // Wait for the DOM to register the new items
|
||||
return inner$("div").first().text().length == 6;
|
||||
}).done(function(){ // Once the DOM has registered the items
|
||||
inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc)
|
||||
// Randomize the item heights (replicates images / headings etc)
|
||||
inner$("div").each(function(index){
|
||||
var random = Math.floor(Math.random() * (50)) + 20;
|
||||
$(this).css("height", random+"px");
|
||||
});
|
||||
|
||||
console.log(caretPosition(inner$));
|
||||
var newDivHeight = inner$("div").first().css("height");
|
||||
var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height
|
||||
// has the new div height changed from the original div height
|
||||
var heightHasChanged = originalDivHeight != newDivHeight;
|
||||
expect(heightHasChanged).to.be(true); // expect the first line to be blank
|
||||
});
|
||||
|
||||
// Is this Element now visible to the pad user?
|
||||
helper.waitFor(function(){ // Wait for the DOM to register the new items
|
||||
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place
|
||||
// Wait for the DOM to scroll into place
|
||||
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$);
|
||||
}).done(function(){ // Once the DOM has registered the items
|
||||
inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc)
|
||||
// Randomize the item heights (replicates images / headings etc)
|
||||
inner$("div").each(function(index){
|
||||
var random = Math.floor(Math.random() * (80 - 20 + 1)) + 20;
|
||||
$(this).css("height", random+"px");
|
||||
});
|
||||
|
||||
var newDivHeight = inner$("div").first().css("height");
|
||||
var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height
|
||||
// has the new div height changed from the original div height
|
||||
var heightHasChanged = originalDivHeight != newDivHeight;
|
||||
expect(heightHasChanged).to.be(true); // expect the first line to be blank
|
||||
});
|
||||
var i = 0;
|
||||
|
@ -241,7 +252,8 @@ console.log(inner$);
|
|||
// Does scrolling back up the pad with the up arrow show the correct contents?
|
||||
helper.waitFor(function(){ // Wait for the new position to be in place
|
||||
try{
|
||||
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place
|
||||
// Wait for the DOM to scroll into place
|
||||
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$);
|
||||
}catch(e){
|
||||
return false;
|
||||
}
|
||||
|
@ -256,7 +268,8 @@ console.log(inner$);
|
|||
// Does scrolling back up the pad with the up arrow show the correct contents?
|
||||
helper.waitFor(function(){ // Wait for the new position to be in place
|
||||
try{
|
||||
return isScrolledIntoView(inner$("div:nth-child(0)"), inner$); // Wait for the DOM to scroll into place
|
||||
// Wait for the DOM to scroll into place
|
||||
return isScrolledIntoView(inner$("div:nth-child(0)"), inner$);
|
||||
}catch(e){
|
||||
return false;
|
||||
}
|
||||
|
@ -276,7 +289,8 @@ console.log(inner$);
|
|||
|
||||
// Does scrolling back up the pad with the up arrow show the correct contents?
|
||||
helper.waitFor(function(){ // Wait for the new position to be in place
|
||||
return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place
|
||||
// Wait for the DOM to scroll into place
|
||||
return isScrolledIntoView(inner$("div:nth-child(1)"), inner$);
|
||||
}).done(function(){ // Once the DOM has registered the items
|
||||
expect(true).to.be(true);
|
||||
done();
|
||||
|
@ -284,17 +298,19 @@ console.log(inner$);
|
|||
*/
|
||||
});
|
||||
|
||||
function prepareDocument(n, target) { // generates a random document with random content on n lines
|
||||
// generates a random document with random content on n lines
|
||||
const prepareDocument = (n, target) => {
|
||||
let i = 0;
|
||||
while (i < n) { // for each line
|
||||
target.sendkeys(makeStr()); // generate a random string and send that to the editor
|
||||
target.sendkeys('{enter}'); // generator an enter keypress
|
||||
i++; // rinse n times
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function keyEvent(target, charCode, ctrl, shift) { // sends a charCode to the window
|
||||
const e = target.Event(helper.evtType);
|
||||
// sends a charCode to the window
|
||||
const keyEvent = (target, charCode, ctrl, shift) => {
|
||||
const e = new target.Event(helper.evtType);
|
||||
if (ctrl) {
|
||||
e.ctrlKey = true; // Control key
|
||||
}
|
||||
|
@ -304,30 +320,33 @@ function keyEvent(target, charCode, ctrl, shift) { // sends a charCode to the wi
|
|||
e.which = charCode;
|
||||
e.keyCode = charCode;
|
||||
target('#innerdocbody').trigger(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function makeStr() { // from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
|
||||
// from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
|
||||
const makeStr = () => {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
function isScrolledIntoView(elem, $) { // from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling
|
||||
// from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling
|
||||
const isScrolledIntoView = (elem, $) => {
|
||||
const docViewTop = $(window).scrollTop();
|
||||
const docViewBottom = docViewTop + $(window).height();
|
||||
const elemTop = $(elem).offset().top; // how far the element is from the top of it's container
|
||||
let elemBottom = elemTop + $(elem).height(); // how far plus the height of the elem.. IE is it all in?
|
||||
// how far plus the height of the elem.. IE is it all in?
|
||||
let elemBottom = elemTop + $(elem).height();
|
||||
elemBottom -= 16; // don't ask, sorry but this is needed..
|
||||
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
|
||||
}
|
||||
};
|
||||
|
||||
function caretPosition($) {
|
||||
const caretPosition = ($) => {
|
||||
const doc = $.window.document;
|
||||
const pos = doc.getSelection();
|
||||
pos.y = pos.anchorNode.parentElement.offsetTop;
|
||||
pos.x = pos.anchorNode.parentElement.offsetLeft;
|
||||
return pos;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,7 +7,8 @@ describe('change user color', function () {
|
|||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it('Color picker remembers the user color after a refresh', function (done) {
|
||||
it('Color picker matches original color and remembers the user color' +
|
||||
' after a refresh', function (done) {
|
||||
this.timeout(10000);
|
||||
const chrome$ = helper.padChrome$;
|
||||
|
||||
|
@ -95,7 +96,6 @@ describe('change user color', function () {
|
|||
// simulate a keypress of enter actually does evt.which = 10 not 13
|
||||
$chatInput.sendkeys('{enter}');
|
||||
|
||||
// check if chat shows up
|
||||
// wait until the chat message shows up
|
||||
helper.waitFor(() => chrome$('#chattext').children('p').length !== 0
|
||||
).done(() => {
|
||||
|
|
|
@ -6,8 +6,10 @@ describe('Chat messages and UI', function () {
|
|||
helper.newPad(cb);
|
||||
});
|
||||
|
||||
it('opens chat, sends a message, makes sure it exists and hides chat', async function () {
|
||||
it('opens chat, sends a message, makes sure it exists ' +
|
||||
'on the page and hides chat', async function () {
|
||||
this.timeout(3000);
|
||||
|
||||
const chatValue = 'JohnMcLear';
|
||||
|
||||
await helper.showChat();
|
||||
|
@ -49,7 +51,8 @@ describe('Chat messages and UI', function () {
|
|||
expect(chat.text()).to.be(`${username}${time} ${chatValue}`);
|
||||
});
|
||||
|
||||
it('makes chat stick to right side of the screen via settings, remove sticky via settings, close it', async function () {
|
||||
it('makes chat stick to right side of the screen via settings, ' +
|
||||
'remove sticky via settings, close it', async function () {
|
||||
this.timeout(5000);
|
||||
await helper.showSettings();
|
||||
|
||||
|
@ -66,8 +69,10 @@ describe('Chat messages and UI', function () {
|
|||
expect(helper.isChatboxShown()).to.be(false);
|
||||
});
|
||||
|
||||
it('makes chat stick to right side of the screen via icon on the top right, remove sticky via icon, close it', async function () {
|
||||
it('makes chat stick to right side of the screen via icon on the top' +
|
||||
' right, remove sticky via icon, close it', async function () {
|
||||
this.timeout(5000);
|
||||
|
||||
await helper.showChat();
|
||||
|
||||
await helper.enableStickyChatviaIcon();
|
||||
|
@ -83,7 +88,8 @@ describe('Chat messages and UI', function () {
|
|||
expect(helper.isChatboxShown()).to.be(false);
|
||||
});
|
||||
|
||||
xit('Checks showChat=false URL Parameter shows/hides chat', function (done) {
|
||||
xit('Checks showChat=false URL Parameter hides chat then' +
|
||||
' when removed it shows chat', function (done) {
|
||||
this.timeout(60000);
|
||||
|
||||
setTimeout(() => { // give it a second to save the username on the server side
|
||||
|
|
|
@ -92,7 +92,7 @@ describe('clear authorship colors button', function () {
|
|||
let hasAuthorClass = inner$('div').first().attr('class').indexOf('author') !== -1;
|
||||
expect(hasAuthorClass).to.be(false);
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 90; // z
|
||||
inner$('#innerdocbody').trigger(e); // shouldn't od anything
|
||||
|
|
|
@ -22,8 +22,6 @@ describe('delete keystroke', function () {
|
|||
$firstTextElement.sendkeys('{leftarrow}'); // simulate a keypress of the left arrow key
|
||||
$firstTextElement.sendkeys('{del}'); // simulate a keypress of delete
|
||||
|
||||
// ace creates a new dom element when you press a keystroke
|
||||
// so just get the first text element again
|
||||
const $newFirstTextElement = inner$('div').first();
|
||||
|
||||
// get the new length of this element
|
||||
|
|
|
@ -86,16 +86,16 @@ describe('drag and drop', function () {
|
|||
const TARGET_LINE = 2;
|
||||
const FIRST_SOURCE_LINE = 5;
|
||||
|
||||
const getLine = function (lineNumber) {
|
||||
const getLine = (lineNumber) => {
|
||||
const $lines = helper.padInner$('div');
|
||||
return $lines.slice(lineNumber, lineNumber + 1);
|
||||
};
|
||||
|
||||
const createScriptWithSeveralLines = function (done) {
|
||||
const createScriptWithSeveralLines = (done) => {
|
||||
// create some lines to be used on the tests
|
||||
const $firstLine = helper.padInner$('div').first();
|
||||
$firstLine.html(
|
||||
'...<br>...<br>Target line []<br>...<br>...<br>Source line 1.<br>Source line 2.<br>');
|
||||
$firstLine.html('...<br>...<br>Target line []<br>...<br>...<br>' +
|
||||
'Source line 1.<br>Source line 2.<br>');
|
||||
|
||||
// wait for lines to be split
|
||||
helper.waitFor(() => {
|
||||
|
@ -104,7 +104,7 @@ describe('drag and drop', function () {
|
|||
}).done(done);
|
||||
};
|
||||
|
||||
const selectPartOfSourceLine = function () {
|
||||
const selectPartOfSourceLine = () => {
|
||||
const $sourceLine = getLine(FIRST_SOURCE_LINE);
|
||||
|
||||
// select 'line 1' from 'Source line 1.'
|
||||
|
@ -112,14 +112,14 @@ describe('drag and drop', function () {
|
|||
const end = start + 'line 1'.length;
|
||||
helper.selectLines($sourceLine, $sourceLine, start, end);
|
||||
};
|
||||
const selectMultipleSourceLines = function () {
|
||||
const selectMultipleSourceLines = () => {
|
||||
const $firstSourceLine = getLine(FIRST_SOURCE_LINE);
|
||||
const $lastSourceLine = getLine(FIRST_SOURCE_LINE + 1);
|
||||
|
||||
helper.selectLines($firstSourceLine, $lastSourceLine);
|
||||
};
|
||||
|
||||
const dragSelectedTextAndDropItIntoMiddleOfLine = function (targetLineNumber) {
|
||||
const dragSelectedTextAndDropItIntoMiddleOfLine = (targetLineNumber) => {
|
||||
// dragstart: start dragging content
|
||||
triggerEvent('dragstart');
|
||||
|
||||
|
@ -132,7 +132,7 @@ describe('drag and drop', function () {
|
|||
triggerEvent('dragend');
|
||||
};
|
||||
|
||||
const getHtmlFromSelectedText = function () {
|
||||
const getHtmlFromSelectedText = () => {
|
||||
const innerDocument = helper.padInner$.document;
|
||||
|
||||
const range = innerDocument.getSelection().getRangeAt(0);
|
||||
|
@ -145,12 +145,12 @@ describe('drag and drop', function () {
|
|||
return draggedHtml;
|
||||
};
|
||||
|
||||
const triggerEvent = function (eventName) {
|
||||
const event = helper.padInner$.Event(eventName);
|
||||
const triggerEvent = (eventName) => {
|
||||
const event = new helper.padInner$.Event(eventName);
|
||||
helper.padInner$('#innerdocbody').trigger(event);
|
||||
};
|
||||
|
||||
const moveSelectionIntoTarget = function (draggedHtml, targetLineNumber) {
|
||||
const moveSelectionIntoTarget = (draggedHtml, targetLineNumber) => {
|
||||
const innerDocument = helper.padInner$.document;
|
||||
|
||||
// delete original content
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('the test helper', function () {
|
|||
|
||||
let times = 10;
|
||||
|
||||
const loadPad = function () {
|
||||
const loadPad = () => {
|
||||
helper.newPad(() => {
|
||||
times--;
|
||||
if (times > 0) {
|
||||
|
@ -77,13 +77,14 @@ describe('the test helper', function () {
|
|||
// Before refreshing, make sure the name is there
|
||||
expect($usernameInput.val()).to.be('John McLear');
|
||||
|
||||
// Now that we have a chrome, we can set a pad cookie, so we can confirm it gets wiped as well
|
||||
// Now that we have a chrome, we can set a pad cookie
|
||||
// so we can confirm it gets wiped as well
|
||||
chrome$.document.cookie = 'prefsHtml=baz;expires=Thu, 01 Jan 3030 00:00:00 GMT';
|
||||
expect(chrome$.document.cookie).to.contain('prefsHtml=baz');
|
||||
|
||||
// Cookies are weird. Because it's attached to chrome$ (as helper.setPadCookies does), AND we
|
||||
// didn't put path=/, we shouldn't expect it to be visible on window.document.cookie. Let's just
|
||||
// be sure.
|
||||
// Cookies are weird. Because it's attached to chrome$ (as helper.setPadCookies does)
|
||||
// AND we didn't put path=/, we shouldn't expect it to be visible on
|
||||
// window.document.cookie. Let's just be sure.
|
||||
expect(window.document.cookie).to.not.contain('prefsHtml=baz');
|
||||
|
||||
setTimeout(() => { // give it a second to save the username on the server side
|
||||
|
@ -268,7 +269,8 @@ describe('the test helper', function () {
|
|||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it('changes editor selection to be between startOffset of $startLine and endOffset of $endLine', function (done) {
|
||||
it('changes editor selection to be between startOffset of $startLine ' +
|
||||
'and endOffset of $endLine', function (done) {
|
||||
const inner$ = helper.padInner$;
|
||||
|
||||
const startOffset = 2;
|
||||
|
@ -315,7 +317,8 @@ describe('the test helper', function () {
|
|||
* is not consistent between browsers but that's the situation so that's
|
||||
* how I'm covering it in this test.
|
||||
*/
|
||||
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('ort lines to test');
|
||||
expect(cleanText(
|
||||
selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('ort lines to test');
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -367,12 +370,14 @@ describe('the test helper', function () {
|
|||
* is not consistent between browsers but that's the situation so that's
|
||||
* how I'm covering it in this test.
|
||||
*/
|
||||
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('ort lines to test');
|
||||
expect(cleanText(
|
||||
selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('ort lines to test');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('selects all text between beginning of $startLine and end of $endLine when no offset is provided', function (done) {
|
||||
it('selects all text between beginning of $startLine and end of $endLine ' +
|
||||
'when no offset is provided', function (done) {
|
||||
const inner$ = helper.padInner$;
|
||||
|
||||
const $lines = inner$('div');
|
||||
|
@ -390,7 +395,8 @@ describe('the test helper', function () {
|
|||
* is not consistent between browsers but that's the situation so that's
|
||||
* how I'm covering it in this test.
|
||||
*/
|
||||
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('short lines to test');
|
||||
expect(cleanText(
|
||||
selection.toString().replace(/(\r\n|\n|\r)/gm, ''))).to.be('short lines to test');
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
describe('import functionality', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad(cb); // creates a new pad
|
||||
|
@ -16,7 +18,6 @@ describe('import functionality', function () {
|
|||
return newtext;
|
||||
}
|
||||
function importrequest(data, importurl, type) {
|
||||
let success;
|
||||
let error;
|
||||
const result = $.ajax({
|
||||
url: importurl,
|
||||
|
@ -27,7 +28,17 @@ describe('import functionality', function () {
|
|||
accepts: {
|
||||
text: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
data: `Content-Type: multipart/form-data; boundary=--boundary\r\n\r\n--boundary\r\nContent-Disposition: form-data; name="file"; filename="import.${type}"\r\nContent-Type: text/plain\r\n\r\n${data}\r\n\r\n--boundary`,
|
||||
data: [
|
||||
'Content-Type: multipart/form-data; boundary=--boundary',
|
||||
'',
|
||||
'--boundary',
|
||||
`Content-Disposition: form-data; name="file"; filename="import.${type}"`,
|
||||
'Content-Type: text/plain',
|
||||
'',
|
||||
data,
|
||||
'',
|
||||
'--boundary',
|
||||
].join('\r\n'),
|
||||
error(res) {
|
||||
error = res;
|
||||
},
|
||||
|
@ -56,7 +67,8 @@ describe('import functionality', function () {
|
|||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const textWithNewLines = 'imported text\nnewline';
|
||||
importrequest(textWithNewLines, importurl, 'txt');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('<span class="">imported text</span>\n<span class="">newline</span>\n<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext())
|
||||
.to.be('<span class="">imported text</span>\n<span class="">newline</span>\n<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('imported text<br>newline<br><br>');
|
||||
expect(results[1][1]).to.be('imported text\nnewline\n\n');
|
||||
|
@ -66,7 +78,8 @@ describe('import functionality', function () {
|
|||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithNewLines = '<html><body>htmltext<br/>newline</body></html>';
|
||||
importrequest(htmlWithNewLines, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('<span class="">htmltext</span>\n<span class="">newline</span>\n<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext())
|
||||
.to.be('<span class="">htmltext</span>\n<span class="">newline</span>\n<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('htmltext<br>newline<br><br>');
|
||||
expect(results[1][1]).to.be('htmltext\nnewline\n\n');
|
||||
|
@ -74,69 +87,109 @@ describe('import functionality', function () {
|
|||
});
|
||||
xit('import a pad with attributes from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithNewLines = '<html><body>htmltext<br/><span class="b s i u"><b><i><s><u>newline</u></s></i></b></body></html>';
|
||||
const htmlWithNewLines = '<html><body>htmltext<br/><span class="b s i u">' +
|
||||
'<b><i><s><u>newline</u></s></i></b></body></html>';
|
||||
importrequest(htmlWithNewLines, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('<span class="">htmltext</span>\n<span class="b i s u"><b><i><s><u>newline</u></s></i></b></span>\n<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext())
|
||||
.to.be('<span class="">htmltext</span>\n<span class="b i s u">' +
|
||||
'<b><i><s><u>newline</u></s></i></b></span>\n<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('htmltext<br><strong><em><s><u>newline</u></s></em></strong><br><br>');
|
||||
expect(results[0][1])
|
||||
.to.be('htmltext<br><strong><em><s><u>newline</u></s></em></strong><br><br>');
|
||||
expect(results[1][1]).to.be('htmltext\nnewline\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with bullets from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li><li>bullet2 line 2</li></ul></ul></body></html>';
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li>' +
|
||||
'<li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li>' +
|
||||
'<li>bullet2 line 2</li></ul></ul></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n' +
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><li>bullet2 line 2</li></ul></ul><br>');
|
||||
expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t* bullet2 line 2\n\n');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li>' +
|
||||
'<ul class="bullet"><li>bullet2 line 1</li><li>bullet2 line 2</li></ul></ul><br>');
|
||||
expect(results[1][1])
|
||||
.to.be('\t* bullet line 1\n\t* bullet line 2\n' +
|
||||
'\t\t* bullet2 line 1\n\t\t* bullet2 line 2\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with bullets and newlines from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><li>bullet2 line 2</li></ul></ul></body></html>';
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li>' +
|
||||
'</ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2">' +
|
||||
'<li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1">' +
|
||||
'<ul class="list-bullet2"><li>bullet2 line 2</li></ul></ul></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\
|
||||
<br>\n\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n\
|
||||
<br>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n' +
|
||||
'<br>\n' +
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n' +
|
||||
'<br>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul class="bullet"><li>bullet2 line 2</li></ul></ul><br>');
|
||||
expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t* bullet2 line 2\n\n');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet">' +
|
||||
'<li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul>' +
|
||||
'</ul><br><ul><ul class="bullet"><li>bullet2 line 2</li></ul></ul><br>');
|
||||
expect(results[1][1]).to.be(
|
||||
'\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t* bullet2 line 2\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with bullets and newlines and attributes from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li><span class="b s i u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>bullet4 line 2 bs</s></b></span></li><li><span class="u"><u>bullet4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li></ul></ul></ul></ul></body></html>';
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li>' +
|
||||
'</ul><br/><ul class="list-bullet1"><li>bullet line 2</li>' +
|
||||
'<ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul>' +
|
||||
'<br/><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3">' +
|
||||
'<ul class="list-bullet4"><li><span class="b s i u"><b><i>' +
|
||||
'<s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li>' +
|
||||
'<span class="b s "><b><s>bullet4 line 2 bs</s></b></span></li>' +
|
||||
'<li><span class="u"><u>bullet4 line 2 u</u></span><span class="u i s">' +
|
||||
'<i><s><u>uis</u></s></i></span></li></ul></ul></ul></ul></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\<br>\n\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n\
|
||||
<ul class="list-bullet4"><li><span class="b i s u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li></ul>\n\
|
||||
<ul class="list-bullet4"><li><span class="b s"><b><s>bullet4 line 2 bs</s></b></span></li></ul>\n\
|
||||
<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="b i s u">' +
|
||||
'<b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li></ul>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="b s">' +
|
||||
'<b><s>bullet4 line 2 bs</s></b></span></li></ul>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u>' +
|
||||
'</span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li></ul></ul></ul></ul><br>');
|
||||
expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\n');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ul class="bullet"><li>bullet line 1</li></ul>' +
|
||||
'<br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li>' +
|
||||
'</ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu' +
|
||||
'</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong>' +
|
||||
'</li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li></ul></ul></ul></ul><br>');
|
||||
expect(results[1][1]).to.be(
|
||||
'\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2' +
|
||||
' bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with nested bullets from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul></body></html>';
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li>' +
|
||||
'</ul><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2">' +
|
||||
'<li>bullet2 line 1</li></ul></ul><ul class="list-bullet1"><ul class="list-bullet2">' +
|
||||
'<ul class="list-bullet3"><ul class="list-bullet4"><li>bullet4 line 2</li>' +
|
||||
'<li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul>' +
|
||||
'</ul><li>bullet2 line 1</li></ul></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
const oldtext = getinnertext();
|
||||
helper.waitFor(() => oldtext != getinnertext()
|
||||
helper.waitFor(() => oldtext !== getinnertext()
|
||||
// return expect(getinnertext()).to.be('\
|
||||
// <ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\
|
||||
// <ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\
|
||||
|
@ -148,73 +201,127 @@ describe('import functionality', function () {
|
|||
);
|
||||
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><ul><ul class="bullet"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul><br>');
|
||||
expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li>' +
|
||||
'<ul class="bullet"><li>bullet2 line 1</li><ul><ul class="bullet"><li>bullet4 line 2</li>' +
|
||||
'<li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul>' +
|
||||
'<li>bullet2 line 1</li></ul><br>');
|
||||
expect(results[1][1]).to.be(
|
||||
'\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2' +
|
||||
'\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1' +
|
||||
'\n\t* bullet2 line 1\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with 8 levels of bullets and newlines and attributes from html', function (done) {
|
||||
xit('import with 8 levels of bullets and newlines and attributes from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li><span class="b s i u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>bullet4 line 2 bs</s></b></span></li><li><span class="u"><u>bullet4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li><ul class="list-bullet5"><ul class="list-bullet6"><ul class="list-bullet7"><ul class="list-bullet8"><li><span class="">foo</span></li><li><span class="b s"><b><s>foobar bs</b></s></span></li></ul></ul></ul></ul><ul class="list-bullet5"><li>foobar</li></ul></ul></ul></ul></body></html>';
|
||||
const htmlWithBullets =
|
||||
'<html><body><ul class="list-bullet1"><li>bullet line 1</li>' +
|
||||
'</ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>' +
|
||||
'bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2">' +
|
||||
'<ul class="list-bullet3"><ul class="list-bullet4"><li><span class="b s i u"><b><i>' +
|
||||
'<s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>' +
|
||||
'bullet4 line 2 bs</s></b></span></li><li><span class="u"><u>bullet4 line 2 u' +
|
||||
'</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li>' +
|
||||
'<ul class="list-bullet5"><ul class="list-bullet6"><ul class="list-bullet7">' +
|
||||
'<ul class="list-bullet8"><li><span class="">foo</span></li><li><span class="b s">' +
|
||||
'<b><s>foobar bs</b></s></span></li></ul></ul></ul></ul><ul class="list-bullet5">' +
|
||||
'<li>foobar</li></ul></ul></ul></ul></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\<br>\n\
|
||||
<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\
|
||||
<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n\
|
||||
<ul class="list-bullet4"><li><span class="b i s u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li></ul>\n\
|
||||
<ul class="list-bullet4"><li><span class="b s"><b><s>bullet4 line 2 bs</s></b></span></li></ul>\n\
|
||||
<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\
|
||||
<ul class="list-bullet8"><li><span class="">foo</span></li></ul>\n\
|
||||
<ul class="list-bullet8"><li><span class="b s"><b><s>foobar bs</s></b></span></li></ul>\n\
|
||||
<ul class="list-bullet5"><li><span class="">foobar</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n' +
|
||||
'<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="b i s u"><b><i><s><u>bullet4 line 2 bisu</u>' +
|
||||
'</s></i></b></span></li></ul>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="b s"><b><s>bullet4 line 2 bs</s></b>' +
|
||||
'</span></li></ul>\n' +
|
||||
'<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u></span>' +
|
||||
'<span class="i s u"><i><s><u>uis</u></s>' +
|
||||
'</i></span></li></ul>\n' +
|
||||
'<ul class="list-bullet8"><li><span class="">foo</span></li></ul>\n' +
|
||||
'<ul class="list-bullet8"><li><span class="b s"><b><s>foobar bs</s></b>' +
|
||||
'</span></li></ul>\n' +
|
||||
'<ul class="list-bullet5"><li><span class="">foobar</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="bullet"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>');
|
||||
expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* foobar bs\n\t\t\t\t\t* foobar\n\n');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet">' +
|
||||
'<li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul>' +
|
||||
'<br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>' +
|
||||
'bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>' +
|
||||
'bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em>' +
|
||||
'<s>uis</s></em></u></li><ul><ul><ul><ul class="bullet"><li>foo</li>' +
|
||||
'<li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li>' +
|
||||
'</ul></ul></ul></ul></ul><br>');
|
||||
expect(results[1][1]).to.be(
|
||||
'\t* bullet line 1\n\n\t* bullet line 2\n\t\t* ' +
|
||||
'bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 ' +
|
||||
'bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* ' +
|
||||
'foobar bs\n\t\t\t\t\t* foobar\n\n');
|
||||
done();
|
||||
});
|
||||
|
||||
xit('import a pad with ordered lists from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol></body></html>';
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1">' +
|
||||
'<li>number 1 line 1</li></ol><ol class="list-number1" start="2">' +
|
||||
'<li>number 2 line 2</li></ol></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
console.error(getinnertext());
|
||||
expect(getinnertext()).to.be('\
|
||||
<ol class="list-number1" start="1"><li><span class="">number 1 line 1</span></li></ol>\n\
|
||||
<ol class="list-number1" start="2"><li><span class="">number 2 line 2</span></li></ol>\n\
|
||||
<br>\n');
|
||||
expect(getinnertext()).to.be(
|
||||
'<ol class="list-number1" start="1"><li><span class="">number 1 line 1</span></li></ol>\n' +
|
||||
'<ol class="list-number1" start="2"><li><span class="">number 2 line 2</span></li></ol>\n' +
|
||||
'<br>\n');
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
expect(results[0][1]).to.be('<ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol>');
|
||||
expect(results[0][1]).to.be(
|
||||
'<ol class="list-number1" start="1"><li>number 1 line 1</li>' +
|
||||
'</ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol>');
|
||||
expect(results[1][1]).to.be('');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with ordered lists and newlines from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 9 line 1</li></ol><br/><ol class="list-number1" start="2"><li>number 10 line 2</li><ol class="list-number2"><li>number 2 times line 1</li></ol></ol><br/><ol class="list-bullet1"><ol class="list-number2"><li>number 2 times line 2</li></ol></ol></body></html>';
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1">' +
|
||||
'<li>number 9 line 1</li></ol><br/><ol class="list-number1" start="2">' +
|
||||
'<li>number 10 line 2</li><ol class="list-number2">' +
|
||||
'<li>number 2 times line 1</li></ol></ol><br/><ol class="list-bullet1">' +
|
||||
'<ol class="list-number2"><li>number 2 times line 2</li></ol></ol></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
expect(getinnertext()).to.be('\
|
||||
<ol class="list-number1" start="1"><li><span class="">number 9 line 1</span></li></ol>\n\
|
||||
<br>\n\
|
||||
<ol class="list-number1" start="2"><li><span class="">number 10 line 2</span></li></ol>\n\
|
||||
<ol class="list-number2"><li><span class="">number 2 times line 1</span></li></ol>\n\
|
||||
<br>\n\
|
||||
<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n\
|
||||
<br>\n');
|
||||
expect(getinnertext()).to.be(
|
||||
'<ol class="list-number1" start="1"><li><span class="">number 9 line 1</span></li></ol>\n' +
|
||||
'<br>\n' +
|
||||
'<ol class="list-number1" start="2"><li><span class="">number 10 line 2</span></li>' +
|
||||
'</ol>\n' +
|
||||
'<ol class="list-number2"><li><span class="">number 2 times line 1</span></li></ol>\n' +
|
||||
'<br>\n' +
|
||||
'<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n' +
|
||||
'<br>\n');
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
console.error(results);
|
||||
done();
|
||||
});
|
||||
xit('import a pad with nested ordered lists and attributes and newlines from html', function (done) {
|
||||
xit('import with nested ordered lists and attributes and newlines from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li><span class="b s i u"><b><i><s><u>bold strikethrough italics underline</u></s><i/></b></span> line <span class="b"><b>1bold</b></span></li></ol><br/><span class="i"><i><ol class="list-number1" start="2"><li>number 10 line 2</li><ol class="list-number2"><li>number 2 times line 1</li></ol></ol></i></span><br/><ol class="list-bullet1"><ol class="list-number2"><li>number 2 times line 2</li></ol></ol></body></html>';
|
||||
const htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>' +
|
||||
'<span class="b s i u"><b><i><s><u>bold strikethrough italics underline</u>' +
|
||||
'</s><i/></b></span> line <span class="b"><b>1bold</b></span></li>' +
|
||||
'</ol><br/><span class="i"><i><ol class="list-number1" start="2">' +
|
||||
'<li>number 10 line 2</li><ol class="list-number2">' +
|
||||
'<li>number 2 times line 1</li></ol></ol></i></span><br/>' +
|
||||
'<ol class="list-bullet1"><ol class="list-number2">' +
|
||||
'<li>number 2 times line 2</li></ol></ol></body></html>';
|
||||
importrequest(htmlWithBullets, importurl, 'html');
|
||||
expect(getinnertext()).to.be('\
|
||||
<ol class="list-number1"><li><span class="b i s u"><b><i><s><u>bold strikethrough italics underline</u></s></i></b></span><span class=""> line </span><span class="b"><b>1bold</b></span></li></ol>\n\
|
||||
<br>\n\
|
||||
<ol class="list-number1"><li><span class="i"><i>number 10 line 2</i></span></li></ol>\n\
|
||||
<ol class="list-number2"><li><span class="i"><i>number 2 times line 1</i></span></li></ol>\n\
|
||||
<br>\n\
|
||||
<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n\
|
||||
<br>\n');
|
||||
expect(getinnertext()).to.be(
|
||||
'<ol class="list-number1"><li><span class="b i s u"><b><i><s><u>' +
|
||||
'bold strikethrough italics underline</u></s></i></b></span><span class="">' +
|
||||
' line </span><span class="b"><b>1bold</b></span></li></ol>\n' +
|
||||
'<br>\n' +
|
||||
'<ol class="list-number1"><li><span class="i"><i>number 10 line 2</i></span></li></ol>\n' +
|
||||
'<ol class="list-number2"><li><span class="i">' +
|
||||
'<i>number 2 times line 1</i></span></li></ol>\n' +
|
||||
'<br>\n' +
|
||||
'<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n' +
|
||||
'<br>\n');
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
console.error(results);
|
||||
done();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
describe('import indents functionality', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad(cb); // creates a new pad
|
||||
|
@ -13,7 +15,6 @@ describe('import indents functionality', function () {
|
|||
return newtext;
|
||||
}
|
||||
function importrequest(data, importurl, type) {
|
||||
let success;
|
||||
let error;
|
||||
const result = $.ajax({
|
||||
url: importurl,
|
||||
|
@ -24,7 +25,17 @@ describe('import indents functionality', function () {
|
|||
accepts: {
|
||||
text: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
data: `Content-Type: multipart/form-data; boundary=--boundary\r\n\r\n--boundary\r\nContent-Disposition: form-data; name="file"; filename="import.${type}"\r\nContent-Type: text/plain\r\n\r\n${data}\r\n\r\n--boundary`,
|
||||
data: [
|
||||
'Content-Type: multipart/form-data; boundary=--boundary',
|
||||
'',
|
||||
'--boundary',
|
||||
`Content-Disposition: form-data; name="file"; filename="import.${type}"`,
|
||||
'Content-Type: text/plain',
|
||||
'',
|
||||
data,
|
||||
'',
|
||||
'--boundary',
|
||||
].join('\r\n'),
|
||||
error(res) {
|
||||
error = res;
|
||||
},
|
||||
|
@ -51,54 +62,67 @@ describe('import indents functionality', function () {
|
|||
|
||||
xit('import a pad with indents from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
/* eslint-disable-next-line max-len */
|
||||
const htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li><li>indent line 2</li><ul class="list-indent2"><li>indent2 line 1</li><li>indent2 line 2</li></ul></ul></body></html>';
|
||||
importrequest(htmlWithIndents, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\
|
||||
<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n\
|
||||
<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n\
|
||||
<ul class="list-indent2"><li><span class="">indent2 line 2</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n' +
|
||||
'<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n' +
|
||||
'<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n' +
|
||||
'<ul class="list-indent2"><li><span class="">indent2 line 2</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
/* eslint-disable-next-line max-len */
|
||||
expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li><li>indent2 line 2</li></ul></ul><br>');
|
||||
expect(results[1][1]).to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n');
|
||||
expect(results[1][1])
|
||||
.to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n');
|
||||
done();
|
||||
});
|
||||
|
||||
xit('import a pad with indented lists and newlines from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
/* eslint-disable-next-line max-len */
|
||||
const htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent 1 line 2</li><ul class="list-indent2"><li>indent 2 times line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><li>indent 2 times line 2</li></ul></ul></body></html>';
|
||||
importrequest(htmlWithIndents, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\
|
||||
<br>\n\
|
||||
<ul class="list-indent1"><li><span class="">indent 1 line 2</span></li></ul>\n\
|
||||
<ul class="list-indent2"><li><span class="">indent 2 times line 1</span></li></ul>\n\
|
||||
<br>\n\
|
||||
<ul class="list-indent2"><li><span class="">indent 2 times line 2</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n' +
|
||||
'<br>\n' +
|
||||
'<ul class="list-indent1"><li><span class="">indent 1 line 2</span></li></ul>\n' +
|
||||
'<ul class="list-indent2"><li><span class="">indent 2 times line 1</span></li></ul>\n' +
|
||||
'<br>\n' +
|
||||
'<ul class="list-indent2"><li><span class="">indent 2 times line 2</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
/* eslint-disable-next-line max-len */
|
||||
expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent 1 line 2</li><ul class="indent"><li>indent 2 times line 1</li></ul></ul><br><ul><ul class="indent"><li>indent 2 times line 2</li></ul></ul><br>');
|
||||
/* eslint-disable-next-line max-len */
|
||||
expect(results[1][1]).to.be('\tindent line 1\n\n\tindent 1 line 2\n\t\tindent 2 times line 1\n\n\t\tindent 2 times line 2\n\n');
|
||||
done();
|
||||
});
|
||||
xit('import a pad with 8 levels of indents and newlines and attributes from html', function (done) {
|
||||
xit('import with 8 levels of indents and newlines and attributes from html', function (done) {
|
||||
const importurl = `${helper.padChrome$.window.location.href}/import`;
|
||||
/* eslint-disable-next-line max-len */
|
||||
const htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent line 2</li><ul class="list-indent2"><li>indent2 line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><ul class="list-indent3"><ul class="list-indent4"><li><span class="b s i u"><b><i><s><u>indent4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>indent4 line 2 bs</s></b></span></li><li><span class="u"><u>indent4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li><ul class="list-indent5"><ul class="list-indent6"><ul class="list-indent7"><ul class="list-indent8"><li><span class="">foo</span></li><li><span class="b s"><b><s>foobar bs</b></s></span></li></ul></ul></ul></ul><ul class="list-indent5"><li>foobar</li></ul></ul></ul></ul></body></html>';
|
||||
importrequest(htmlWithIndents, importurl, 'html');
|
||||
helper.waitFor(() => expect(getinnertext()).to.be('\
|
||||
<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\<br>\n\
|
||||
<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n\
|
||||
<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n<br>\n\
|
||||
<ul class="list-indent4"><li><span class="b i s u"><b><i><s><u>indent4 line 2 bisu</u></s></i></b></span></li></ul>\n\
|
||||
<ul class="list-indent4"><li><span class="b s"><b><s>indent4 line 2 bs</s></b></span></li></ul>\n\
|
||||
<ul class="list-indent4"><li><span class="u"><u>indent4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\
|
||||
<ul class="list-indent8"><li><span class="">foo</span></li></ul>\n\
|
||||
<ul class="list-indent8"><li><span class="b s"><b><s>foobar bs</s></b></span></li></ul>\n\
|
||||
<ul class="list-indent5"><li><span class="">foobar</span></li></ul>\n\
|
||||
<br>\n'));
|
||||
helper.waitFor(() => expect(getinnertext()).to.be(
|
||||
'<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n' +
|
||||
'<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n<br>\n' +
|
||||
'<ul class="list-indent4"><li><span class="b i s u"><b><i><s><u>indent4 ' +
|
||||
'line 2 bisu</u></s></i></b></span></li></ul>\n' +
|
||||
'<ul class="list-indent4"><li><span class="b s"><b><s>' +
|
||||
'indent4 line 2 bs</s></b></span></li></ul>\n' +
|
||||
'<ul class="list-indent4"><li><span class="u"><u>indent4 line 2 u</u>' +
|
||||
'</span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n' +
|
||||
'<ul class="list-indent8"><li><span class="">foo</span></li></ul>\n' +
|
||||
'<ul class="list-indent8"><li><span class="b s"><b><s>foobar bs</s></b>' +
|
||||
'</span></li></ul>\n' +
|
||||
'<ul class="list-indent5"><li><span class="">foobar</span></li></ul>\n' +
|
||||
'<br>\n'));
|
||||
const results = exportfunc(helper.padChrome$.window.location.href);
|
||||
/* eslint-disable-next-line max-len */
|
||||
expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li></ul></ul><br><ul><ul><ul><ul class="indent"><li><strong><em><s><u>indent4 line 2 bisu</u></s></em></strong></li><li><strong><s>indent4 line 2 bs</s></strong></li><li><u>indent4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="indent"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>');
|
||||
/* eslint-disable-next-line max-len */
|
||||
expect(results[1][1]).to.be('\tindent line 1\n\n\tindent line 2\n\t\tindent2 line 1\n\n\t\t\t\tindent4 line 2 bisu\n\t\t\t\tindent4 line 2 bs\n\t\t\t\tindent4 line 2 uuis\n\t\t\t\t\t\t\t\tfoo\n\t\t\t\t\t\t\t\tfoobar bs\n\t\t\t\t\tfoobar\n\n');
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('indentation button', function () {
|
|||
// select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.keyCode = 9; // tab :|
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
||||
|
@ -60,7 +60,8 @@ describe('indentation button', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it("indents text with spaces on enter if previous line ends with ':', '[', '(', or '{'", function (done) {
|
||||
it('indents text with spaces on enter if previous line ends ' +
|
||||
"with ':', '[', '(', or '{'", function (done) {
|
||||
this.timeout(1200);
|
||||
const inner$ = helper.padInner$;
|
||||
|
||||
|
@ -111,7 +112,8 @@ describe('indentation button', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it("appends indentation to the indent of previous line if previous line ends with ':', '[', '(', or '{'", function (done) {
|
||||
it('appends indentation to the indent of previous line if previous line ends ' +
|
||||
"with ':', '[', '(', or '{'", function (done) {
|
||||
this.timeout(1200);
|
||||
const inner$ = helper.padInner$;
|
||||
|
||||
|
@ -136,7 +138,8 @@ describe('indentation button', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it("issue #2772 shows '*' when multiple indented lines receive a style and are outdented", async function () {
|
||||
it("issue #2772 shows '*' when multiple indented lines " +
|
||||
' receive a style and are outdented', async function () {
|
||||
this.timeout(1200);
|
||||
const inner$ = helper.padInner$;
|
||||
const chrome$ = helper.padChrome$;
|
||||
|
@ -189,7 +192,6 @@ describe('indentation button', function () {
|
|||
var $indentButton = testHelper.$getPadChrome().find(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
|
@ -227,7 +229,6 @@ describe('indentation button', function () {
|
|||
$outdentButton.click();
|
||||
$outdentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
|
@ -274,7 +275,9 @@ describe('indentation button', function () {
|
|||
|
||||
//get the second text element out of the inner iframe
|
||||
setTimeout(function(){ // THIS IS REALLY BAD
|
||||
var secondTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(1); // THIS IS UGLY
|
||||
var secondTextElement = $('iframe').contents()
|
||||
.find('iframe').contents()
|
||||
.find('iframe').contents().find('body > div').get(1); // THIS IS UGLY
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = secondTextElement.children(":first");
|
||||
|
@ -289,7 +292,10 @@ describe('indentation button', function () {
|
|||
expect(isLI).to.be(true);
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var thirdTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(2); // THIS IS UGLY TOO
|
||||
var thirdTextElement = $('iframe').contents()
|
||||
.find('iframe').contents()
|
||||
.find('iframe').contents()
|
||||
.find('body > div').get(2); // THIS IS UGLY TOO
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = thirdTextElement.children(":first");
|
||||
|
@ -309,7 +315,7 @@ describe('indentation button', function () {
|
|||
|
||||
const pressEnter = () => {
|
||||
const inner$ = helper.padInner$;
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.keyCode = 13; // enter :|
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
};
|
||||
|
|
|
@ -46,7 +46,7 @@ describe('italic some text', function () {
|
|||
// select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 105; // i
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
|
|
@ -15,8 +15,8 @@ describe('author of pad edition', function () {
|
|||
setTimeout(() => {
|
||||
// Expire cookie, so author is changed after reloading the pad.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie
|
||||
const cookieVal = 'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
helper.padChrome$.document.cookie = cookieVal;
|
||||
helper.padChrome$.document.cookie =
|
||||
'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
|
||||
helper.newPad(done, padId);
|
||||
}, 1000);
|
||||
|
@ -25,7 +25,12 @@ describe('author of pad edition', function () {
|
|||
this.timeout(60000);
|
||||
});
|
||||
|
||||
const clearAuthorship = function (done) {
|
||||
// author 2 makes some changes on the pad
|
||||
it('Clears Authorship by second user', function (done) {
|
||||
clearAuthorship(done);
|
||||
});
|
||||
|
||||
const clearAuthorship = (done) => {
|
||||
const inner$ = helper.padInner$;
|
||||
const chrome$ = helper.padChrome$;
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ describe('assign ordered list', function () {
|
|||
|
||||
it('does not insert unordered list', function (done) {
|
||||
this.timeout(3000);
|
||||
helper.waitFor(() => helper.padInner$('div').first().find('ol li').length === 1).done(() => {
|
||||
helper.waitFor(
|
||||
() => helper.padInner$('div').first().find('ol li').length === 1).done(() => {
|
||||
expect().fail(() => 'Unordered list inserted, should ignore shortcut');
|
||||
}).fail(() => {
|
||||
done();
|
||||
|
@ -69,7 +70,8 @@ describe('assign ordered list', function () {
|
|||
|
||||
it('does not insert unordered list', function (done) {
|
||||
this.timeout(3000);
|
||||
helper.waitFor(() => helper.padInner$('div').first().find('ol li').length === 1).done(() => {
|
||||
helper.waitFor(
|
||||
() => helper.padInner$('div').first().find('ol li').length === 1).done(() => {
|
||||
expect().fail(() => 'Unordered list inserted, should ignore shortcut');
|
||||
}).fail(() => {
|
||||
done();
|
||||
|
@ -78,7 +80,8 @@ describe('assign ordered list', function () {
|
|||
});
|
||||
});
|
||||
|
||||
xit('issue #1125 keeps the numbered list on enter for the new line - EMULATES PASTING INTO A PAD', function (done) {
|
||||
xit('issue #1125 keeps the numbered list on enter for the new line', function (done) {
|
||||
// EMULATES PASTING INTO A PAD
|
||||
const inner$ = helper.padInner$;
|
||||
const chrome$ = helper.padChrome$;
|
||||
|
||||
|
@ -104,19 +107,19 @@ describe('assign ordered list', function () {
|
|||
});
|
||||
});
|
||||
|
||||
const triggerCtrlShiftShortcut = function (shortcutChar) {
|
||||
const triggerCtrlShiftShortcut = (shortcutChar) => {
|
||||
const inner$ = helper.padInner$;
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true;
|
||||
e.shiftKey = true;
|
||||
e.which = shortcutChar.toString().charCodeAt(0);
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
};
|
||||
|
||||
const makeSureShortcutIsDisabled = function (shortcut) {
|
||||
const makeSureShortcutIsDisabled = (shortcut) => {
|
||||
helper.padChrome$.window.clientVars.padShortcutEnabled[shortcut] = false;
|
||||
};
|
||||
const makeSureShortcutIsEnabled = function (shortcut) {
|
||||
const makeSureShortcutIsEnabled = (shortcut) => {
|
||||
helper.padChrome$.window.clientVars.padShortcutEnabled[shortcut] = true;
|
||||
};
|
||||
});
|
||||
|
@ -142,7 +145,7 @@ describe('Pressing Tab in an OL increases and decreases indentation', function (
|
|||
const $insertorderedlistButton = chrome$('.buttonicon-insertorderedlist');
|
||||
$insertorderedlistButton.click();
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.keyCode = 9; // tab
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
||||
|
@ -156,7 +159,8 @@ describe('Pressing Tab in an OL increases and decreases indentation', function (
|
|||
});
|
||||
|
||||
|
||||
describe('Pressing indent/outdent button in an OL increases and decreases indentation and bullet / ol formatting', function () {
|
||||
describe('Pressing indent/outdent button in an OL increases and ' +
|
||||
'decreases indentation and bullet / ol formatting', function () {
|
||||
// create a new pad before each test run
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad(cb);
|
||||
|
|
|
@ -100,17 +100,17 @@ describe('Pad modal', function () {
|
|||
});
|
||||
});
|
||||
|
||||
const clickOnPadInner = function () {
|
||||
const clickOnPadInner = () => {
|
||||
const $editor = helper.padInner$('#innerdocbody');
|
||||
$editor.click();
|
||||
};
|
||||
|
||||
const clickOnPadOuter = function () {
|
||||
const clickOnPadOuter = () => {
|
||||
const $lineNumbersColumn = helper.padOuter$('#sidedivinner');
|
||||
$lineNumbersColumn.click();
|
||||
};
|
||||
|
||||
const openSettingsAndWaitForModalToBeVisible = function (done) {
|
||||
const openSettingsAndWaitForModalToBeVisible = (done) => {
|
||||
helper.padChrome$('.buttonicon-settings').click();
|
||||
|
||||
// wait for modal to be displayed
|
||||
|
@ -118,7 +118,7 @@ describe('Pad modal', function () {
|
|||
helper.waitFor(() => isModalOpened(modalSelector), 10000).done(done);
|
||||
};
|
||||
|
||||
const isEditorDisabled = function () {
|
||||
const isEditorDisabled = () => {
|
||||
const editorDocument = helper.padOuter$("iframe[name='ace_inner']").get(0).contentDocument;
|
||||
const editorBody = editorDocument.getElementById('innerdocbody');
|
||||
|
||||
|
@ -128,7 +128,7 @@ describe('Pad modal', function () {
|
|||
return editorIsDisabled;
|
||||
};
|
||||
|
||||
const isModalOpened = function (modalSelector) {
|
||||
const isModalOpened = (modalSelector) => {
|
||||
const $modal = helper.padChrome$(modalSelector);
|
||||
|
||||
return $modal.hasClass('popup-show');
|
||||
|
|
|
@ -3,14 +3,17 @@
|
|||
// Test for https://github.com/ether/etherpad-lite/issues/1763
|
||||
|
||||
// This test fails in Opera, IE and Safari
|
||||
// Opera fails due to a weird way of handling the order of execution, yet actual performance seems fine
|
||||
// Opera fails due to a weird way of handling the order of execution,
|
||||
// yet actual performance seems fine
|
||||
// Safari fails due the delay being too great yet the actual performance seems fine
|
||||
// Firefox might panic that the script is taking too long so will fail
|
||||
// IE will fail due to running out of memory as it can't fit 2M chars in memory.
|
||||
|
||||
// Just FYI Google Docs crashes on large docs whilst trying to Save, it's likely the limitations we are
|
||||
// Just FYI Google Docs crashes on large docs whilst trying to Save,
|
||||
// it's likely the limitations we are
|
||||
// experiencing are more to do with browser limitations than improper implementation.
|
||||
// A ueber fix for this would be to have a separate lower cpu priority thread that handles operations that aren't
|
||||
// A ueber fix for this would be to have a separate lower cpu priority
|
||||
// thread that handles operations that aren't
|
||||
// visible to the user.
|
||||
|
||||
// Adapted from John McLear's original test case.
|
||||
|
@ -22,16 +25,18 @@ xdescribe('Responsiveness of Editor', function () {
|
|||
this.timeout(6000);
|
||||
});
|
||||
// JM commented out on 8th Sep 2020 for a release, after release this needs uncommenting
|
||||
// And the test needs to be fixed to work in Firefox 52 on Windows 7. I am not sure why it fails on this specific platform
|
||||
// The errors show this.timeout... then crash the browser but I am sure something is actually causing the stack trace and
|
||||
// And the test needs to be fixed to work in Firefox 52 on Windows 7.
|
||||
// I am not sure why it fails on this specific platform
|
||||
// The errors show this.timeout... then crash the browser but
|
||||
// I am sure something is actually causing the stack trace and
|
||||
// I just need to narrow down what, offers to help accepted.
|
||||
it('Fast response to keypress in pad with large amount of contents', function (done) {
|
||||
// skip on Windows Firefox 52.0
|
||||
if (window.bowser && window.bowser.windows && window.bowser.firefox && window.bowser.version == '52.0') {
|
||||
if (window.bowser &&
|
||||
window.bowser.windows && window.bowser.firefox && window.bowser.version === '52.0') {
|
||||
this.skip();
|
||||
}
|
||||
const inner$ = helper.padInner$;
|
||||
const chrome$ = helper.padChrome$;
|
||||
const chars = '0000000000'; // row of placeholder chars
|
||||
const amount = 200000; // number of blocks of chars we will insert
|
||||
const length = (amount * (chars.length) + 1); // include a counter for each space
|
||||
|
@ -41,7 +46,7 @@ xdescribe('Responsiveness of Editor', function () {
|
|||
// get keys to send
|
||||
const keyMultiplier = 10; // multiplier * 10 == total number of key events
|
||||
let keysToSend = '';
|
||||
for (var i = 0; i <= keyMultiplier; i++) {
|
||||
for (let i = 0; i <= keyMultiplier; i++) {
|
||||
keysToSend += chars;
|
||||
}
|
||||
|
||||
|
@ -49,23 +54,23 @@ xdescribe('Responsiveness of Editor', function () {
|
|||
textElement.sendkeys('{selectall}'); // select all
|
||||
textElement.sendkeys('{del}'); // clear the pad text
|
||||
|
||||
for (var i = 0; i <= amount; i++) {
|
||||
for (let i = 0; i <= amount; i++) {
|
||||
text = `${text + chars} `; // add the chars and space to the text contents
|
||||
}
|
||||
inner$('div').first().text(text); // Put the text contents into the pad
|
||||
|
||||
helper.waitFor(() => // Wait for the new contents to be on the pad
|
||||
inner$('div').text().length > length
|
||||
).done(() => {
|
||||
expect(inner$('div').text().length).to.be.greaterThan(length); // has the text changed?
|
||||
// Wait for the new contents to be on the pad
|
||||
helper.waitFor(() => inner$('div').text().length > length).done(() => {
|
||||
// has the text changed?
|
||||
expect(inner$('div').text().length).to.be.greaterThan(length);
|
||||
const start = Date.now(); // get the start time
|
||||
|
||||
// send some new text to the screen (ensure all 3 key events are sent)
|
||||
const el = inner$('div').first();
|
||||
for (let i = 0; i < keysToSend.length; ++i) {
|
||||
var x = keysToSend.charCodeAt(i);
|
||||
const x = keysToSend.charCodeAt(i);
|
||||
['keyup', 'keypress', 'keydown'].forEach((type) => {
|
||||
const e = $.Event(type);
|
||||
const e = new $.Event(type);
|
||||
e.keyCode = x;
|
||||
el.trigger(e);
|
||||
});
|
||||
|
|
|
@ -91,7 +91,7 @@ describe('select formatting buttons when selection has style applied', function
|
|||
// select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = key.charCodeAt(0); // I, U, B, 5
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
|
|
@ -22,8 +22,6 @@ describe('strikethrough button', function () {
|
|||
const $strikethroughButton = chrome$('.buttonicon-strikethrough');
|
||||
$strikethroughButton.click();
|
||||
|
||||
// ace creates a new dom element when you press a button
|
||||
// so just get the first text element again
|
||||
const $newFirstTextElement = inner$('div').first();
|
||||
|
||||
// is there a <i> element now?
|
||||
|
|
|
@ -14,7 +14,6 @@ xdescribe('timeslider button takes you to the timeslider of a pad', function ()
|
|||
// get the first text element inside the editable space
|
||||
const $firstTextElement = inner$('div span').first();
|
||||
const originalValue = $firstTextElement.text(); // get the original value
|
||||
const newValue = `Testing${originalValue}`;
|
||||
$firstTextElement.sendkeys('Testing'); // send line 1 to the pad
|
||||
|
||||
const modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
|
|
|
@ -9,8 +9,10 @@ describe('timeslider', function () {
|
|||
/**
|
||||
* @todo test authorsList
|
||||
*/
|
||||
|
||||
it('Shows a correctly formatted date and time', async function () {
|
||||
this.timeout(12000);
|
||||
|
||||
// make some changes to produce 3 revisions
|
||||
const revs = 3;
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ describe('timeslider', function () {
|
|||
|
||||
setTimeout(() => {
|
||||
// go to timeslider
|
||||
$('#iframe-container iframe')
|
||||
.attr('src', `${$('#iframe-container iframe').attr('src')}/timeslider`);
|
||||
$('#iframe-container iframe').attr('src',
|
||||
`${$('#iframe-container iframe').attr('src')}/timeslider`);
|
||||
|
||||
setTimeout(() => {
|
||||
const timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
|
@ -84,8 +84,8 @@ describe('timeslider', function () {
|
|||
|
||||
setTimeout(() => {
|
||||
// go to timeslider
|
||||
$('#iframe-container iframe')
|
||||
.attr('src', `${$('#iframe-container iframe').attr('src')}/timeslider`);
|
||||
$('#iframe-container iframe').attr('src',
|
||||
`${$('#iframe-container iframe').attr('src')}/timeslider`);
|
||||
|
||||
setTimeout(() => {
|
||||
const timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
|
@ -102,15 +102,16 @@ describe('timeslider', function () {
|
|||
helper.waitFor(
|
||||
() => $('#iframe-container iframe')[0].contentWindow.location.hash !== oldUrl, 6000)
|
||||
.always(() => {
|
||||
expect($('#iframe-container iframe')[0].contentWindow.location.hash)
|
||||
.not.to.eql(oldUrl);
|
||||
expect(
|
||||
$('#iframe-container iframe')[0].contentWindow.location.hash
|
||||
).not.to.eql(oldUrl);
|
||||
done();
|
||||
});
|
||||
}, 6000);
|
||||
}, revs * timePerRev);
|
||||
});
|
||||
it('jumps to a revision given in the url', function (done) {
|
||||
this.timeout(6000);
|
||||
this.timeout(40000);
|
||||
const inner$ = helper.padInner$;
|
||||
|
||||
// wait for the text to be loaded
|
||||
|
@ -132,15 +133,16 @@ describe('timeslider', function () {
|
|||
return lenOkay && colorOkay;
|
||||
}, 10000).always(() => {
|
||||
// go to timeslider with a specific revision set
|
||||
$('#iframe-container iframe')
|
||||
.attr('src', `${$('#iframe-container iframe').attr('src')}/timeslider#0`);
|
||||
|
||||
$('#iframe-container iframe').attr('src',
|
||||
`${$('#iframe-container iframe').attr('src')}/timeslider#0`);
|
||||
|
||||
// wait for the timeslider to be loaded
|
||||
helper.waitFor(() => {
|
||||
try {
|
||||
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
} catch (e) {
|
||||
// silently fart. Deadly.
|
||||
// Empty catch block <3
|
||||
}
|
||||
if (timeslider$) {
|
||||
return timeslider$('#innerdocbody').text().length === oldLength;
|
||||
|
@ -161,8 +163,8 @@ describe('timeslider', function () {
|
|||
|
||||
setTimeout(() => {
|
||||
// go to timeslider
|
||||
$('#iframe-container iframe')
|
||||
.attr('src', `${$('#iframe-container iframe').attr('src')}/timeslider#0`);
|
||||
$('#iframe-container iframe').attr('src',
|
||||
`${$('#iframe-container iframe').attr('src')}/timeslider#0`);
|
||||
let timeslider$;
|
||||
let exportLink;
|
||||
|
||||
|
@ -170,7 +172,7 @@ describe('timeslider', function () {
|
|||
try {
|
||||
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
} catch (e) {
|
||||
// silently give up on life.
|
||||
// Empty catch block <3
|
||||
}
|
||||
if (!timeslider$) return false;
|
||||
exportLink = timeslider$('#exportplaina').attr('href');
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('undo button', function () {
|
|||
const modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
const e = inner$.Event(helper.evtType);
|
||||
const e = new inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 90; // z
|
||||
inner$('#innerdocbody').trigger(e);
|
||||
|
|
|
@ -138,7 +138,8 @@ describe('Pressing Tab in an UL increases and decreases indentation', function (
|
|||
});
|
||||
});
|
||||
|
||||
describe('Pressing indent/outdent button in an UL increases and decreases indentation and bullet / ol formatting', function () {
|
||||
describe('Pressing indent/outdent button in an UL increases and decreases indentation ' +
|
||||
'and bullet / ol formatting', function () {
|
||||
// create a new pad before each test run
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad(cb);
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
var srcFolder = '../../../src/node_modules/';
|
||||
var wd = require(`${srcFolder}wd`);
|
||||
var async = require(`${srcFolder}async`);
|
||||
'use strict';
|
||||
|
||||
var config = {
|
||||
const wd = require('ep_etherpad-lite/node_modules/wd');
|
||||
const async = require('ep_etherpad-lite/node_modules/async');
|
||||
|
||||
const config = {
|
||||
host: 'ondemand.saucelabs.com',
|
||||
port: 80,
|
||||
username: process.env.SAUCE_USER,
|
||||
accessKey: process.env.SAUCE_ACCESS_KEY,
|
||||
};
|
||||
|
||||
var allTestsPassed = true;
|
||||
let allTestsPassed = true;
|
||||
// overwrite the default exit code
|
||||
// in case not all worker can be run (due to saucelabs limits), `queue.drain` below will not be called
|
||||
// in case not all worker can be run (due to saucelabs limits),
|
||||
// `queue.drain` below will not be called
|
||||
// and the script would silently exit with error code 0
|
||||
process.exitCode = 2;
|
||||
process.on('exit', (code) => {
|
||||
|
@ -20,13 +22,18 @@ process.on('exit', (code) => {
|
|||
}
|
||||
});
|
||||
|
||||
var sauceTestWorker = async.queue((testSettings, callback) => {
|
||||
const browser = wd.promiseChainRemote(config.host, config.port, config.username, config.accessKey);
|
||||
const name = `${process.env.GIT_HASH} - ${testSettings.browserName} ${testSettings.version}, ${testSettings.platform}`;
|
||||
const sauceTestWorker = async.queue((testSettings, callback) => {
|
||||
const browser = wd.promiseChainRemote(
|
||||
config.host, config.port, config.username, config.accessKey);
|
||||
const name =
|
||||
`${process.env.GIT_HASH} - ${testSettings.browserName} ` +
|
||||
`${testSettings.version}, ${testSettings.platform}`;
|
||||
testSettings.name = name;
|
||||
testSettings.public = true;
|
||||
testSettings.build = process.env.GIT_HASH;
|
||||
testSettings.extendedDebugging = true; // console.json can be downloaded via saucelabs, don't know how to print them into output of the tests
|
||||
// console.json can be downloaded via saucelabs,
|
||||
// don't know how to print them into output of the tests
|
||||
testSettings.extendedDebugging = true;
|
||||
testSettings.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
browser.init(testSettings).get('http://localhost:9001/tests/frontend/', () => {
|
||||
|
@ -34,7 +41,7 @@ var sauceTestWorker = async.queue((testSettings, callback) => {
|
|||
console.log(`Remote sauce test '${name}' started! ${url}`);
|
||||
|
||||
// tear down the test excecution
|
||||
const stopSauce = function (success, timesup) {
|
||||
const stopSauce = (success, timesup) => {
|
||||
clearInterval(getStatusInterval);
|
||||
clearTimeout(timeout);
|
||||
|
||||
|
@ -43,12 +50,15 @@ var sauceTestWorker = async.queue((testSettings, callback) => {
|
|||
allTestsPassed = false;
|
||||
}
|
||||
|
||||
// if stopSauce is called via timeout (in contrast to via getStatusInterval) than the log of up to the last
|
||||
// if stopSauce is called via timeout
|
||||
// (in contrast to via getStatusInterval) than the log of up to the last
|
||||
// five seconds may not be available here. It's an error anyway, so don't care about it.
|
||||
printLog(logIndex);
|
||||
|
||||
if (timesup) {
|
||||
console.log(`[${testSettings.browserName} ${testSettings.platform}${testSettings.version === '' ? '' : (` ${testSettings.version}`)}] \x1B[31mFAILED\x1B[39m allowed test duration exceeded`);
|
||||
console.log(`[${testSettings.browserName} ${testSettings.platform}` +
|
||||
`${testSettings.version === '' ? '' : (` ${testSettings.version}`)}]` +
|
||||
' \x1B[31mFAILED\x1B[39m allowed test duration exceeded');
|
||||
}
|
||||
console.log(`Remote sauce test '${name}' finished! ${url}`);
|
||||
|
||||
|
@ -58,17 +68,19 @@ var sauceTestWorker = async.queue((testSettings, callback) => {
|
|||
|
||||
/**
|
||||
* timeout if a test hangs or the job exceeds 14.5 minutes
|
||||
* It's necessary because if travis kills the saucelabs session due to inactivity, we don't get any output
|
||||
* @todo this should be configured in testSettings, see https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts
|
||||
* It's necessary because if travis kills the saucelabs session due to inactivity,
|
||||
* we don't get any output
|
||||
* @todo this should be configured in testSettings, see
|
||||
* https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts
|
||||
*/
|
||||
var timeout = setTimeout(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
stopSauce(false, true);
|
||||
}, 870000); // travis timeout is 15 minutes, set this to a slightly lower value
|
||||
|
||||
let knownConsoleText = '';
|
||||
// how many characters of the log have been sent to travis
|
||||
let logIndex = 0;
|
||||
var getStatusInterval = setInterval(() => {
|
||||
const getStatusInterval = setInterval(() => {
|
||||
browser.eval("$('#console').text()", (err, consoleText) => {
|
||||
if (!consoleText || err) {
|
||||
return;
|
||||
|
@ -76,9 +88,10 @@ var sauceTestWorker = async.queue((testSettings, callback) => {
|
|||
knownConsoleText = consoleText;
|
||||
|
||||
if (knownConsoleText.indexOf('FINISHED') > 0) {
|
||||
const match = knownConsoleText.match(/FINISHED.*([0-9]+) tests passed, ([0-9]+) tests failed/);
|
||||
const match = knownConsoleText.match(
|
||||
/FINISHED.*([0-9]+) tests passed, ([0-9]+) tests failed/);
|
||||
// finished without failures
|
||||
if (match[2] && match[2] == '0') {
|
||||
if (match[2] && match[2] === '0') {
|
||||
stopSauce(true);
|
||||
|
||||
// finished but some tests did not return or some tests failed
|
||||
|
@ -99,13 +112,17 @@ var sauceTestWorker = async.queue((testSettings, callback) => {
|
|||
*
|
||||
* @param {number} index offset from where to start
|
||||
*/
|
||||
function printLog(index) {
|
||||
let testResult = knownConsoleText.substring(index).replace(/\[red\]/g, '\x1B[31m').replace(/\[yellow\]/g, '\x1B[33m')
|
||||
const printLog = (index) => {
|
||||
let testResult = knownConsoleText.substring(index)
|
||||
.replace(/\[red\]/g, '\x1B[31m').replace(/\[yellow\]/g, '\x1B[33m')
|
||||
.replace(/\[green\]/g, '\x1B[32m').replace(/\[clear\]/g, '\x1B[39m');
|
||||
testResult = testResult.split('\\n').map((line) => `[${testSettings.browserName} ${testSettings.platform}${testSettings.version === '' ? '' : (` ${testSettings.version}`)}] ${line}`).join('\n');
|
||||
testResult = testResult.split('\\n').map((line) => `[${testSettings.browserName} ` +
|
||||
`${testSettings.platform}` +
|
||||
`${testSettings.version === '' ? '' : (` ${testSettings.version}`)}]` +
|
||||
`${line}`).join('\n');
|
||||
|
||||
console.log(testResult);
|
||||
}
|
||||
};
|
||||
});
|
||||
}, 6); // run 6 tests in parrallel
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
let etherpad;
|
||||
try {
|
||||
var etherpad = require('../../src/node_modules/etherpad-cli-client');
|
||||
etherpad = require('ep_etherpad-lite/node_modules/etherpad-cli-client');
|
||||
// ugly
|
||||
} catch {
|
||||
var etherpad = require('etherpad-cli-client');
|
||||
/* eslint-disable-next-line node/no-missing-require */
|
||||
etherpad = require('etherpad-cli-client'); // uses global
|
||||
}
|
||||
const pad = etherpad.connect(process.argv[2]);
|
||||
pad.on('connected', () => {
|
||||
|
@ -18,7 +22,7 @@ pad.on('connected', () => {
|
|||
});
|
||||
// in case of disconnect exit code 1
|
||||
pad.on('message', (message) => {
|
||||
if (message.disconnect == 'rateLimited') {
|
||||
if (message.disconnect === 'rateLimited') {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue