Fixed startup to "Running npm to get a list of installed plugins"

This commit is contained in:
SamTV12345 2023-06-25 13:22:01 +02:00
parent 76a6f665a4
commit aa6323e488
No known key found for this signature in database
GPG key ID: E63EEC7466038043
63 changed files with 1536 additions and 1767 deletions

View file

@ -19,7 +19,7 @@
* limitations under the License. * limitations under the License.
*/ */
import Changeset from '../../static/js/Changeset'; import {builder, deserializeOps} from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage'; import ChatMessage from '../../static/js/ChatMessage';
import {CustomError} from '../utils/customError'; import {CustomError} from '../utils/customError';
import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager'; import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager';
@ -518,7 +518,7 @@ export const restoreRevision = async (padID, rev, authorId = '') => {
let textIndex = 0; let textIndex = 0;
const newTextStart = 0; const newTextStart = 0;
const newTextEnd = atext.text.length; const newTextEnd = atext.text.length;
for (const op of Changeset.deserializeOps(attribs)) { for (const op of deserializeOps(attribs)) {
const nextIndex = textIndex + op.chars; const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
@ -528,19 +528,19 @@ export const restoreRevision = async (padID, rev, authorId = '') => {
}; };
// create a new changeset with a helper builder object // create a new changeset with a helper builder object
const builder = Changeset.builder(oldText.length); const builder2 = builder(oldText.length);
// assemble each line into the builder // assemble each line into the builder
eachAttribRun(atext.attribs, (start, end, attribs) => { eachAttribRun(atext.attribs, (start, end, attribs) => {
builder.insert(atext.text.substring(start, end), attribs); builder2.insert(atext.text.substring(start, end), attribs);
}); });
const lastNewlinePos = oldText.lastIndexOf('\n'); const lastNewlinePos = oldText.lastIndexOf('\n');
if (lastNewlinePos < 0) { if (lastNewlinePos < 0) {
builder.remove(oldText.length - 1, 0); builder2.remove(oldText.length - 1, 0);
} else { } else {
builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1); builder2.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
builder.remove(oldText.length - lastNewlinePos - 1, 0); builder2.remove(oldText.length - lastNewlinePos - 1, 0);
} }
const changeset = builder.toString(); const changeset = builder.toString();

View file

@ -21,7 +21,16 @@
import AttributeMap from '../../static/js/AttributeMap'; import AttributeMap from '../../static/js/AttributeMap';
import {getPad} from '../db/PadManager'; import {getPad} from '../db/PadManager';
import {} from '../../static/js/Changeset'; import {
builder,
checkRep, cloneAText, compose,
deserializeOps,
follow, inverse, makeAText,
makeSplice,
moveOpsToNewPool, mutateAttributionLines, mutateTextLines,
oldLen, prepareForWire, splitAttributionLines, splitTextLines,
unpack
} from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage'; import ChatMessage from '../../static/js/ChatMessage';
import {AttributePool} from '../../static/js/AttributePool'; import {AttributePool} from '../../static/js/AttributePool';
import AttributeManager from '../../static/js/AttributeManager'; import AttributeManager from '../../static/js/AttributeManager';
@ -62,6 +71,7 @@ import {ErrorCaused} from "../models/ErrorCaused";
import {Pad} from "../db/Pad"; import {Pad} from "../db/Pad";
import {SessionInfo} from "../models/SessionInfo"; import {SessionInfo} from "../models/SessionInfo";
import {randomString} from "../utils/randomstring"; import {randomString} from "../utils/randomstring";
import {identity} from "lodash";
const securityManager = require('../db/SecurityManager'); const securityManager = require('../db/SecurityManager');
@ -610,10 +620,10 @@ const handleUserChanges = async (socket, message) => {
const pad = await getPad(thisSession.padId, null, thisSession.author); const pad = await getPad(thisSession.padId, null, thisSession.author);
// Verify that the changeset has valid syntax and is in canonical form // Verify that the changeset has valid syntax and is in canonical form
Changeset.checkRep(changeset); checkRep(changeset);
// Validate all added 'author' attribs to be the same value as the current user // Validate all added 'author' attribs to be the same value as the current user
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) { for (const op of deserializeOps(unpack(changeset).ops)) {
// + can add text with attribs // + can add text with attribs
// = can change or add attribs // = can change or add attribs
// - can have attribs, but they are discarded and don't show up in the attribs - // - can have attribs, but they are discarded and don't show up in the attribs -
@ -632,7 +642,7 @@ const handleUserChanges = async (socket, message) => {
// ex. adoptChangesetAttribs // ex. adoptChangesetAttribs
// Afaik, it copies the new attributes from the changeset, to the global Attribute Pool // Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
let rebasedChangeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); let rebasedChangeset = moveOpsToNewPool(changeset, wireApool, pad.pool);
// ex. applyUserChanges // ex. applyUserChanges
let r = baseRev; let r = baseRev;
@ -645,21 +655,21 @@ const handleUserChanges = async (socket, message) => {
const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r); const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r);
if (changeset === c && thisSession.author === authorId) { if (changeset === c && thisSession.author === authorId) {
// Assume this is a retransmission of an already applied changeset. // Assume this is a retransmission of an already applied changeset.
rebasedChangeset = Changeset.identity(Changeset.unpack(changeset).oldLen); rebasedChangeset = identity(unpack(changeset).oldLen);
} }
// At this point, both "c" (from the pad) and "changeset" (from the // At this point, both "c" (from the pad) and "changeset" (from the
// client) are relative to revision r - 1. The follow function // client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r // rebases "changeset" so that it is relative to revision r
// and can be applied after "c". // and can be applied after "c".
rebasedChangeset = Changeset.follow(c, rebasedChangeset, false, pad.pool); rebasedChangeset = follow(c, rebasedChangeset, false, pad.pool);
} }
const prevText = pad.text(); const prevText = pad.text();
if (Changeset.oldLen(rebasedChangeset) !== prevText.length) { if (oldLen(rebasedChangeset) !== prevText.length) {
throw new Error( throw new Error(
`Can't apply changeset ${rebasedChangeset} with oldLen ` + `Can't apply changeset ${rebasedChangeset} with oldLen ` +
`${Changeset.oldLen(rebasedChangeset)} to document of length ${prevText.length}`); `${oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
} }
const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author); const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);
@ -674,7 +684,7 @@ const handleUserChanges = async (socket, message) => {
// Make sure the pad always ends with an empty line. // Make sure the pad always ends with an empty line.
if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) { if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) {
const nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, '\n'); const nlChangeset = makeSplice(pad.text(), pad.text().length - 1, 0, '\n');
await pad.appendRevision(nlChangeset, thisSession.author); await pad.appendRevision(nlChangeset, thisSession.author);
} }
@ -729,7 +739,7 @@ export const updatePadClients = async (pad) => {
const revChangeset = revision.changeset; const revChangeset = revision.changeset;
const currentTime = revision.meta.timestamp; const currentTime = revision.meta.timestamp;
const forWire = Changeset.prepareForWire(revChangeset, pad.pool); const forWire = prepareForWire(revChangeset, pad.pool);
const msg = { const msg = {
type: 'COLLABROOM', type: 'COLLABROOM',
data: { data: {
@ -764,7 +774,7 @@ const _correctMarkersInPad = (atext, apool) => {
// that aren't at the start of a line // that aren't at the start of a line
const badMarkers = []; const badMarkers = [];
let offset = 0; let offset = 0;
for (const op of Changeset.deserializeOps(atext.attribs)) { for (const op of deserializeOps(atext.attribs)) {
const attribs = AttributeMap.fromString(op.attribs, apool); const attribs = AttributeMap.fromString(op.attribs, apool);
const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a)); const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
if (hasMarker) { if (hasMarker) {
@ -786,15 +796,15 @@ const _correctMarkersInPad = (atext, apool) => {
// create changeset that removes these bad markers // create changeset that removes these bad markers
offset = 0; offset = 0;
const builder = Changeset.builder(text.length); const builder2 = builder(text.length);
badMarkers.forEach((pos) => { badMarkers.forEach((pos) => {
builder.keepText(text.substring(offset, pos)); builder2.keepText(text.substring(offset, pos));
builder.remove(1); builder2.remove(1);
offset = pos + 1; offset = pos + 1;
}); });
return builder.toString(); return builder2.toString();
}; };
export let clientVars:any export let clientVars:any
@ -918,7 +928,7 @@ const handleClientReady = async (socket, message) => {
// return pending changesets // return pending changesets
for (const r of revisionsNeeded) { for (const r of revisionsNeeded) {
const forWire = Changeset.prepareForWire(changesets[r].changeset, pad.pool); const forWire = prepareForWire(changesets[r].changeset, pad.pool);
const wireMsg = {type: 'COLLABROOM', const wireMsg = {type: 'COLLABROOM',
data: {type: 'CLIENT_RECONNECT', data: {type: 'CLIENT_RECONNECT',
headRev: pad.getHeadRevisionNumber(), headRev: pad.getHeadRevisionNumber(),
@ -943,8 +953,8 @@ const handleClientReady = async (socket, message) => {
let apool; let apool;
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted // prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
try { try {
atext = Changeset.cloneAText(pad.atext); atext = cloneAText(pad.atext);
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); const attribsForWire = prepareForWire(atext.attribs, pad.pool);
apool = attribsForWire.pool.toJsonable(); apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated; atext.attribs = attribsForWire.translated;
} catch (e) { } catch (e) {
@ -1175,13 +1185,13 @@ const getChangesetInfo = async (pad: Pad, startNum: number, endNum: number, gran
if (compositeEnd > endNum || compositeEnd > headRevision + 1) break; if (compositeEnd > endNum || compositeEnd > headRevision + 1) break;
const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`]; const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`];
const backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool()); const backwards = inverse(forwards, lines.textlines, lines.alines, pad.apool());
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool()); mutateAttributionLines(forwards, lines.alines, pad.apool());
Changeset.mutateTextLines(forwards, lines.textlines); mutateTextLines(forwards, lines.textlines);
const forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool); const forwards2 = moveOpsToNewPool(forwards, pad.apool(), apool);
const backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool); const backwards2 = moveOpsToNewPool(backwards, pad.apool(), apool);
const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1]; const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1];
const t2 = revisionDate[compositeEnd - 1]; const t2 = revisionDate[compositeEnd - 1];
@ -1209,12 +1219,12 @@ const getPadLines = async (pad, revNum) => {
if (revNum >= 0) { if (revNum >= 0) {
atext = await pad.getInternalRevisionAText(revNum); atext = await pad.getInternalRevisionAText(revNum);
} else { } else {
atext = Changeset.makeAText('\n'); atext = makeAText('\n');
} }
return { return {
textlines: Changeset.splitTextLines(atext.text), textlines: splitTextLines(atext.text),
alines: Changeset.splitAttributionLines(atext.attribs, atext.text), alines: splitAttributionLines(atext.attribs, atext.text),
}; };
}; };
@ -1249,7 +1259,7 @@ const composePadChangesets = async (pad, startNum, endNum) => {
for (r = startNum + 1; r < endNum; r++) { for (r = startNum + 1; r < endNum; r++) {
const cs = changesets[r]; const cs = changesets[r];
changeset = Changeset.compose(changeset, cs, pool); changeset = compose(changeset, cs, pool);
} }
return changeset; return changeset;
} catch (e) { } catch (e) {

View file

@ -50,7 +50,7 @@ const getAllLocales = () => {
// Build a locale index (merge all locale data other than user-supplied overrides) // Build a locale index (merge all locale data other than user-supplied overrides)
const locales = {}; const locales = {};
_.each(locales2paths, (files, langcode) => { _.each(locales2paths, (files:[], langcode) => {
locales[langcode] = {}; locales[langcode] = {};
files.forEach((file) => { files.forEach((file) => {

View file

@ -20,12 +20,12 @@
*/ */
import AttributeMap from '../../static/js/AttributeMap'; import AttributeMap from '../../static/js/AttributeMap';
import Changeset from '../../static/js/Changeset'; import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
export const getPadPlainText = (pad, revNum) => { export const getPadPlainText = (pad, revNum) => {
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext); const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = splitAttributionLines(atext.attribs, atext.text);
const apool = pad.pool; const apool = pad.pool;
const pieces = []; const pieces = [];
@ -57,7 +57,7 @@ export const _analyzeLine = (text, aline, apool) => {
let lineMarker = 0; let lineMarker = 0;
line.listLevel = 0; line.listLevel = 0;
if (aline) { if (aline) {
const [op] = Changeset.deserializeOps(aline); const [op] = deserializeOps(aline);
if (op != null) { if (op != null) {
const attribs = AttributeMap.fromString(op.attribs, apool); const attribs = AttributeMap.fromString(op.attribs, apool);
let listType = attribs.get('list'); let listType = attribs.get('list');
@ -77,7 +77,7 @@ export const _analyzeLine = (text, aline, apool) => {
} }
if (lineMarker) { if (lineMarker) {
line.text = text.substring(1); line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1); line.aline = subattribution(aline, 1);
} else { } else {
line.text = text; line.text = text;
line.aline = aline; line.aline = aline;

View file

@ -15,14 +15,22 @@
* limitations under the License. * limitations under the License.
*/ */
import Changeset from '../../static/js/Changeset'; import {
import attributes from "../../static/js/attributes"; deserializeOps,
splitAttributionLines,
stringAssembler,
stringIterator,
subattribution
} from '../../static/js/Changeset';
import {decodeAttribString} from "../../static/js/attributes";
import {getPad} from "../db/PadManager"; import {getPad} from "../db/PadManager";
import _ from "underscore"; import _ from "underscore";
import Security from '../../static/js/security'; // FIXME this is a hack to get around the fact that we don't have a good way
// @ts-ignore
import {escapeHTML,escapeHTMLAttribute} from '../../static/js/security';
import {aCallAll} from '../../static/js/pluginfw/hooks'; import {aCallAll} from '../../static/js/pluginfw/hooks';
import {required} from '../eejs'; import {required} from '../eejs';
import {_analyzeLine, _encodeWhitespace} from "./ExportHelper"; import {_analyzeLine, _encodeWhitespace} from "./ExportHelper";
@ -44,7 +52,7 @@ const getPadHTML = async (pad, revNum) => {
export const getHTMLFromAtext = async (pad, atext, authorColors?) => { export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
const apool = pad.apool(); const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = splitAttributionLines(atext.attribs, atext.text);
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
@ -127,8 +135,8 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes // becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
const taker = Changeset.stringIterator(text); const taker = stringIterator(text);
const assem = Changeset.stringAssembler(); const assem = stringAssembler();
const openTags = []; const openTags = [];
const getSpanClassFor = (i) => { const getSpanClassFor = (i) => {
@ -200,7 +208,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
return; return;
} }
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars)); const ops = deserializeOps(subattribution(attribs, idx, idx + numChars));
idx += numChars; idx += numChars;
// this iterates over every op string and decides which tags to open or to close // this iterates over every op string and decides which tags to open or to close
@ -209,7 +217,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
const usedAttribs = []; const usedAttribs = [];
// mark all attribs as used // mark all attribs as used
for (const a of attributes.decodeAttribString(o.attribs)) { for (const a of decodeAttribString(o.attribs)) {
if (a in anumMap) { if (a in anumMap) {
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc. usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
} }
@ -249,7 +257,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
// from but they break the abiword parser and are completly useless // from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), ''); s = s.replace(String.fromCharCode(12), '');
assem.append(_encodeWhitespace(Security.escapeHTML(s))); assem.append(_encodeWhitespace(escapeHTML(s)));
} // end iteration over spans in line } // end iteration over spans in line
// close all the tags that are open after the last op // close all the tags that are open after the last op
@ -272,7 +280,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/ // https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`); assem.append(`<a href="${escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
processNextChars(urlLength); processNextChars(urlLength);
assem.append('</a>'); assem.append('</a>');
}); });
@ -477,7 +485,7 @@ export const getPadHTMLDocument = async (padId, revNum, readOnlyId?) => {
return required('ep_etherpad-lite/templates/export_html.html', { return required('ep_etherpad-lite/templates/export_html.html', {
body: html, body: html,
padId: Security.escapeHTML(readOnlyId || padId), padId: escapeHTML(readOnlyId || padId),
extraCSS: stylesForExportCSS, extraCSS: stylesForExportCSS,
}); });
}; };

View file

@ -16,8 +16,8 @@
*/ */
import log4js from 'log4js'; import log4js from 'log4js';
import Changeset from '../../static/js/Changeset'; import {builder, deserializeOps} from '../../static/js/Changeset';
import contentcollector from '../../static/js/contentcollector'; import {makeContentCollector} from '../../static/js/contentcollector';
import jsdom from 'jsdom'; import jsdom from 'jsdom';
const apiLogger = log4js.getLogger('ImportHtml'); const apiLogger = log4js.getLogger('ImportHtml');
@ -42,7 +42,7 @@ export const setPadHTML = async (pad, html, authorId = '') => {
// Convert a dom tree into a list of lines and attribute liens // Convert a dom tree into a list of lines and attribute liens
// using the content collector object // using the content collector object
const cc = contentcollector.makeContentCollector(true, null, pad.pool); const cc = makeContentCollector(true, null, pad.pool);
try { try {
// we use a try here because if the HTML is bad it will blow up // we use a try here because if the HTML is bad it will blow up
cc.collectContent(document.body); cc.collectContent(document.body);
@ -68,24 +68,24 @@ export const setPadHTML = async (pad, html, authorId = '') => {
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`; const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
// create a new changeset with a helper builder object // create a new changeset with a helper builder object
const builder = Changeset.builder(1); const builder2 = builder(1);
// assemble each line into the builder // assemble each line into the builder
let textIndex = 0; let textIndex = 0;
const newTextStart = 0; const newTextStart = 0;
const newTextEnd = newText.length; const newTextEnd = newText.length;
for (const op of Changeset.deserializeOps(newAttribs)) { for (const op of deserializeOps(newAttribs)) {
const nextIndex = textIndex + op.chars; const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
const start = Math.max(newTextStart, textIndex); const start = Math.max(newTextStart, textIndex);
const end = Math.min(newTextEnd, nextIndex); const end = Math.min(newTextEnd, nextIndex);
builder.insert(newText.substring(start, end), op.attribs); builder2.insert(newText.substring(start, end), op.attribs);
} }
textIndex = nextIndex; textIndex = nextIndex;
} }
// the changeset is ready! // the changeset is ready!
const theChangeset = builder.toString(); const theChangeset = builder2.toString();
apiLogger.debug(`The changeset: ${theChangeset}`); apiLogger.debug(`The changeset: ${theChangeset}`);
await pad.setText('\n', authorId); await pad.setText('\n', authorId);

View file

@ -28,7 +28,7 @@
*/ */
import exp from "constants"; import exp from "constants";
import packageJSON from '../../../package.json'
import {findEtherpadRoot, makeAbsolute, isSubdir} from './AbsolutePaths'; import {findEtherpadRoot, makeAbsolute, isSubdir} from './AbsolutePaths';
import deepEqual from 'fast-deep-equal/es6'; import deepEqual from 'fast-deep-equal/es6';
import fs from 'fs'; import fs from 'fs';
@ -507,7 +507,7 @@ export const getGitCommit = () => {
}; };
// Return etherpad version from package.json // Return etherpad version from package.json
export const getEpVersion = () => require('../../package.json').version; export const getEpVersion = () => packageJSON.version;
/** /**
* Receives a settingsObj and, if the property name is a valid configuration * Receives a settingsObj and, if the property name is a valid configuration

View file

@ -48,12 +48,11 @@ export const needsUpdate = (cb?:(arg0: boolean)=>void) => {
} }
const check = () => { const check = () => {
const needsUpdate = ((needsUpdate: boolean) => { needsUpdate((needsUpdate)=>{
if (needsUpdate && infos) { if (needsUpdate) {
console.warn(`Update available: Download the latest version ${infos.latestVersion}`); console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
} }
}) })
needsUpdate(infos.latestVersion > getEpVersion());
} }
export default {check, getLatestVersion} export default {check, getLatestVersion}

View file

@ -1,9 +1,18 @@
'use strict'; 'use strict';
import AttributeMap from '../../static/js/AttributeMap'; import AttributeMap from '../../static/js/AttributeMap';
import Changeset from '../../static/js/Changeset'; import {
import attributes from '../../static/js/attributes'; applyToAText,
builder, checkRep,
compose,
deserializeOps,
numToString, Op,
opAssembler, pack, splitAttributionLines, splitTextLines, stringAssembler,
unpack
} from '../../static/js/Changeset';
import {attribsFromString} from '../../static/js/attributes';
import {getHTMLFromAtext} from './ExportHtml'; import {getHTMLFromAtext} from './ExportHtml';
// @ts-ignore
import {PadDiffModel} from "ep_etherpad-lite/node/models/PadDiffModel"; import {PadDiffModel} from "ep_etherpad-lite/node/models/PadDiffModel";
export const PadDiff = (pad, fromRev, toRev)=> { export const PadDiff = (pad, fromRev, toRev)=> {
@ -30,7 +39,7 @@ export const PadDiff = (pad, fromRev, toRev)=> {
PadDiff.prototype._isClearAuthorship = function (changeset) { PadDiff.prototype._isClearAuthorship = function (changeset) {
// unpack // unpack
const unpacked = Changeset.unpack(changeset); const unpacked = unpack(changeset);
// check if there is nothing in the charBank // check if there is nothing in the charBank
if (unpacked.charBank !== '') { if (unpacked.charBank !== '') {
@ -42,7 +51,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
return false; return false;
} }
const [clearOperator, anotherOp] = Changeset.deserializeOps(unpacked.ops); const [clearOperator, anotherOp] = deserializeOps(unpacked.ops);
// check if there is only one operator // check if there is only one operator
if (anotherOp != null) return false; if (anotherOp != null) return false;
@ -59,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
} }
const [appliedAttribute, anotherAttribute] = const [appliedAttribute, anotherAttribute] =
attributes.attribsFromString(clearOperator.attribs, this._pad.pool); attribsFromString(clearOperator.attribs, this._pad.pool);
// Check that the operation has exactly one attribute. // Check that the operation has exactly one attribute.
if (appliedAttribute == null || anotherAttribute != null) return false; if (appliedAttribute == null || anotherAttribute != null) return false;
@ -76,9 +85,9 @@ PadDiff.prototype._createClearAuthorship = async function (rev) {
const atext = await this._pad.getInternalRevisionAText(rev); const atext = await this._pad.getInternalRevisionAText(rev);
// build clearAuthorship changeset // build clearAuthorship changeset
const builder = Changeset.builder(atext.text.length); const builder2 = builder(atext.text.length);
builder.keepText(atext.text, [['author', '']], this._pad.pool); builder2.keepText(atext.text, [['author', '']], this._pad.pool);
const changeset = builder.toString(); const changeset = builder2.toString();
return changeset; return changeset;
}; };
@ -91,7 +100,7 @@ PadDiff.prototype._createClearStartAtext = async function (rev) {
const changeset = await this._createClearAuthorship(rev); const changeset = await this._createClearAuthorship(rev);
// apply the clearAuthorship changeset // apply the clearAuthorship changeset
const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool); const newAText = applyToAText(changeset, atext, this._pad.pool);
return newAText; return newAText;
}; };
@ -160,7 +169,7 @@ PadDiff.prototype._createDiffAtext = async function () {
if (superChangeset == null) { if (superChangeset == null) {
superChangeset = changeset; superChangeset = changeset;
} else { } else {
superChangeset = Changeset.compose(superChangeset, changeset, this._pad.pool); superChangeset = compose(superChangeset, changeset, this._pad.pool);
} }
} }
@ -174,10 +183,10 @@ PadDiff.prototype._createDiffAtext = async function () {
const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool); const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
// apply the superChangeset, which includes all addings // apply the superChangeset, which includes all addings
atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool); atext = applyToAText(superChangeset, atext, this._pad.pool);
// apply the deletionChangeset, which adds a deletions // apply the deletionChangeset, which adds a deletions
atext = Changeset.applyToAText(deletionChangeset, atext, this._pad.pool); atext = applyToAText(deletionChangeset, atext, this._pad.pool);
} }
return atext; return atext;
@ -217,22 +226,22 @@ PadDiff.prototype.getAuthors = async function () {
PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => { PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
// unpack // unpack
const unpacked = Changeset.unpack(changeset); const unpacked = unpack(changeset);
const assem = Changeset.opAssembler(); const assem = opAssembler();
// create deleted attribs // create deleted attribs
const authorAttrib = apool.putAttrib(['author', author || '']); const authorAttrib = apool.putAttrib(['author', author || '']);
const deletedAttrib = apool.putAttrib(['removed', true]); const deletedAttrib = apool.putAttrib(['removed', true]);
const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`; const attribs = `*${numToString(authorAttrib)}*${numToString(deletedAttrib)}`;
for (const operator of Changeset.deserializeOps(unpacked.ops)) { for (const operator of deserializeOps(unpacked.ops)) {
if (operator.opcode === '-') { if (operator.opcode === '-') {
// this is a delete operator, extend it with the author // this is a delete operator, extend it with the author
operator.attribs = attribs; operator.attribs = attribs;
} else if (operator.opcode === '=' && operator.attribs) { } else if (operator.opcode === '=' && operator.attribs) {
// this is operator changes only attributes, let's mark which author did that // this is operator changes only attributes, let's mark which author did that
operator.attribs += `*${Changeset.numToString(authorAttrib)}`; operator.attribs += `*${numToString(authorAttrib)}`;
} }
// append the new operator to our assembler // append the new operator to our assembler
@ -240,14 +249,14 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
} }
// return the modified changeset // return the modified changeset
return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank); return pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
}; };
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, // this method is 80% like Changeset.inverse. I just changed so instead of reverting,
// it adds deletions and attribute changes to to the atext. // it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) { PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const lines = Changeset.splitTextLines(startAText.text); const lines = splitTextLines(startAText.text);
const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text); const alines = splitAttributionLines(startAText.attribs, startAText.text);
// lines and alines are what the exports is meant to apply to. // lines and alines are what the exports is meant to apply to.
// They may be arrays or objects with .get(i) and .length methods. // They may be arrays or objects with .get(i) and .length methods.
@ -274,14 +283,14 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
let curLineOps = null; let curLineOps = null;
let curLineOpsNext = null; let curLineOpsNext = null;
let curLineOpsLine; let curLineOpsLine;
let curLineNextOp = new Changeset.Op('+'); let curLineNextOp = new Op('+');
const unpacked = Changeset.unpack(cs); const unpacked = unpack(cs);
const builder = Changeset.builder(unpacked.newLen); const builder2 = builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => { const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if (!curLineOps || curLineOpsLine !== curLine) { if (!curLineOps || curLineOpsLine !== curLine) {
curLineOps = Changeset.deserializeOps(aLinesGet(curLine)); curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next(); curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine; curLineOpsLine = curLine;
let indexIntoLine = 0; let indexIntoLine = 0;
@ -302,13 +311,13 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curChar = 0; curChar = 0;
curLineOpsLine = curLine; curLineOpsLine = curLine;
curLineNextOp.chars = 0; curLineNextOp.chars = 0;
curLineOps = Changeset.deserializeOps(aLinesGet(curLine)); curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next(); curLineOpsNext = curLineOps.next();
} }
if (!curLineNextOp.chars) { if (!curLineNextOp.chars) {
if (curLineOpsNext.done) { if (curLineOpsNext.done) {
curLineNextOp = new Changeset.Op(); curLineNextOp = new Op();
} else { } else {
curLineNextOp = curLineOpsNext.value; curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next(); curLineOpsNext = curLineOps.next();
@ -343,7 +352,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const nextText = (numChars) => { const nextText = (numChars) => {
let len = 0; let len = 0;
const assem = Changeset.stringAssembler(); const assem = stringAssembler();
const firstString = linesGet(curLine).substring(curChar); const firstString = linesGet(curLine).substring(curChar);
len += firstString.length; len += firstString.length;
assem.append(firstString); assem.append(firstString);
@ -371,7 +380,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
}; };
}; };
for (const csOp of Changeset.deserializeOps(unpacked.ops)) { for (const csOp of deserializeOps(unpacked.ops)) {
if (csOp.opcode === '=') { if (csOp.opcode === '=') {
const textBank = nextText(csOp.chars); const textBank = nextText(csOp.chars);
@ -417,7 +426,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
textLeftToProcess = textLeftToProcess.substr(lengthToProcess); textLeftToProcess = textLeftToProcess.substr(lengthToProcess);
if (lineBreak) { if (lineBreak) {
builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak builder2.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
// consume the attributes of this linebreak // consume the attributes of this linebreak
consumeAttribRuns(1, () => {}); consumeAttribRuns(1, () => {});
@ -429,31 +438,31 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
// get the old attributes back // get the old attributes back
const oldAttribs = undoBackToAttribs(attribs); const oldAttribs = undoBackToAttribs(attribs);
builder.insert(processText.substr(textBankIndex, len), oldAttribs); builder2.insert(processText.substr(textBankIndex, len), oldAttribs);
textBankIndex += len; textBankIndex += len;
}); });
builder.keep(lengthToProcess, 0); builder2.keep(lengthToProcess, 0);
} }
} }
} else { } else {
skip(csOp.chars, csOp.lines); skip(csOp.chars, csOp.lines);
builder.keep(csOp.chars, csOp.lines); builder2.keep(csOp.chars, csOp.lines);
} }
} else if (csOp.opcode === '+') { } else if (csOp.opcode === '+') {
builder.keep(csOp.chars, csOp.lines); builder2.keep(csOp.chars, csOp.lines);
} else if (csOp.opcode === '-') { } else if (csOp.opcode === '-') {
const textBank = nextText(csOp.chars); const textBank = nextText(csOp.chars);
let textBankIndex = 0; let textBankIndex = 0;
consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => { consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); builder2.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
textBankIndex += len; textBankIndex += len;
}); });
} }
} }
return Changeset.checkRep(builder.toString()); return checkRep(builder.toString());
}; };
// export the constructor // export the constructor

9
src/package-lock.json generated
View file

@ -40,7 +40,7 @@
"rehype-minify-whitespace": "^5.0.1", "rehype-minify-whitespace": "^5.0.1",
"request": "2.88.2", "request": "2.88.2",
"resolve": "1.22.2", "resolve": "1.22.2",
"security": "1.0.0", "security": "^1.0.0",
"semver": "^7.5.2", "semver": "^7.5.2",
"socket.io": "^2.4.1", "socket.io": "^2.4.1",
"superagent": "^8.0.9", "superagent": "^8.0.9",
@ -62,6 +62,7 @@
"@types/jquery": "^3.5.16", "@types/jquery": "^3.5.16",
"@types/js-cookie": "^3.0.3", "@types/js-cookie": "^3.0.3",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/underscore": "^1.11.5",
"concurrently": "^8.2.0", "concurrently": "^8.2.0",
"eslint": "^8.14.0", "eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13", "eslint-config-etherpad": "^3.0.13",
@ -1227,6 +1228,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/underscore": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.5.tgz",
"integrity": "sha512-b8e//LrIlhoXaaBcMC0J/s2/lIF9y5VJYKqbW4nA+tW/nqqDk1Dacd1ULLT7zgGsKs7PGbSnqCPzqEniZ0RxYg==",
"dev": true
},
"node_modules/@types/unist": { "node_modules/@types/unist": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",

View file

@ -61,7 +61,7 @@
"rehype-minify-whitespace": "^5.0.1", "rehype-minify-whitespace": "^5.0.1",
"request": "2.88.2", "request": "2.88.2",
"resolve": "1.22.2", "resolve": "1.22.2",
"security": "1.0.0", "security": "^1.0.0",
"semver": "^7.5.2", "semver": "^7.5.2",
"socket.io": "^2.4.1", "socket.io": "^2.4.1",
"superagent": "^8.0.9", "superagent": "^8.0.9",
@ -83,6 +83,7 @@
"@types/jquery": "^3.5.16", "@types/jquery": "^3.5.16",
"@types/js-cookie": "^3.0.3", "@types/js-cookie": "^3.0.3",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/underscore": "^1.11.5",
"concurrently": "^8.2.0", "concurrently": "^8.2.0",
"eslint": "^8.14.0", "eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13", "eslint-config-etherpad": "^3.0.13",

View file

@ -2,7 +2,9 @@ import AttributeMap from "./AttributeMap.js";
import * as Changeset from "./Changeset.js"; import * as Changeset from "./Changeset.js";
import * as ChangesetUtils from "./ChangesetUtils.js"; import * as ChangesetUtils from "./ChangesetUtils.js";
import * as attributes from "./attributes.js"; import * as attributes from "./attributes.js";
import * as _ from "./underscore.js"; import _ from "underscore";
export { map, each, identity} from "underscore";
'use strict'; 'use strict';
const lineMarkerAttribute = 'lmkr'; const lineMarkerAttribute = 'lmkr';
// Some of these attributes are kept for compatibility purposes. // Some of these attributes are kept for compatibility purposes.
@ -255,7 +257,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param lineNum: the number of the line to set the attribute for @param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to set, e.g. list @param attributeKey: the name of the attribute to set, e.g. list
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level) @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
*/ */
setAttributeOnLine(lineNum, attributeName, attributeValue) { setAttributeOnLine(lineNum, attributeName, attributeValue) {
let loc = [0, 0]; let loc = [0, 0];

View file

@ -17,6 +17,7 @@ import * as attributes from "./attributes.js";
* Convenience class to convert an Op's attribute string to/from a Map of key, value pairs. * Convenience class to convert an Op's attribute string to/from a Map of key, value pairs.
*/ */
class AttributeMap extends Map { class AttributeMap extends Map {
pool: any;
/** /**
* Converts an attribute string into an AttributeMap. * Converts an attribute string into an AttributeMap.
* *

View file

@ -1,7 +1,8 @@
import AttributeMap from "./AttributeMap.js"; import AttributeMap from "./AttributeMap.js";
import AttributePool from "./AttributePool.js"; import {AttributePool} from "./AttributePool";
import * as attributes from "./attributes.js"; import * as attributes from "./attributes.js";
import { padutils } from "./pad_utils.js"; import { padutils } from "./pad_utils.js";
import {CustomError} from "../../node/utils/customError";
'use strict'; 'use strict';
/** /**
* A `[key, value]` pair of strings describing a text attribute. * A `[key, value]` pair of strings describing a text attribute.
@ -22,7 +23,7 @@ import { padutils } from "./pad_utils.js";
* @param {string} msg - Just some message * @param {string} msg - Just some message
*/ */
const error = (msg) => { const error = (msg) => {
const e = new Error(msg); const e = new CustomError(msg);
e.easysync = true; e.easysync = true;
throw e; throw e;
}; };
@ -42,6 +43,10 @@ const assert = (b, msg) => {
* An operation to apply to a shared document. * An operation to apply to a shared document.
*/ */
class Op { class Op {
chars: number;
opcode: string;
lines: number;
attribs: string;
/** /**
* @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property. * @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
*/ */
@ -113,6 +118,8 @@ class Op {
* @deprecated Use `deserializeOps` instead. * @deprecated Use `deserializeOps` instead.
*/ */
class OpIter { class OpIter {
private _gen: any;
private _next: any;
/** /**
* @param {string} ops - String encoding the change operations to iterate over. * @param {string} ops - String encoding the change operations to iterate over.
*/ */
@ -240,6 +247,12 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
* with no newlines. * with no newlines.
*/ */
class TextLinesMutator { class TextLinesMutator {
_lines: any;
// FIXME What is this for a type?
_curSplice: any[];
_inSplice: boolean;
_curLine: number;
_curCol: number;
/** /**
* @param {(string[]|StringArrayLike)} lines - Lines to mutate (in place). * @param {(string[]|StringArrayLike)} lines - Lines to mutate (in place).
*/ */
@ -362,7 +375,7 @@ class TextLinesMutator {
* @param {number} L - * @param {number} L -
* @param {boolean} includeInSplice - Indicates that attributes are present. * @param {boolean} includeInSplice - Indicates that attributes are present.
*/ */
skipLines(L, includeInSplice) { skipLines(L, includeInSplice?) {
if (!L) if (!L)
return; return;
if (includeInSplice) { if (includeInSplice) {
@ -906,7 +919,7 @@ export const mergingOpAssembler = () => {
/** /**
* @param {boolean} [isEndDocument] * @param {boolean} [isEndDocument]
*/ */
const flush = (isEndDocument) => { const flush = (isEndDocument?: boolean) => {
if (!bufOp.opcode) if (!bufOp.opcode)
return; return;
if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) { if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
@ -1325,7 +1338,7 @@ export const attributeTester = (attribPair, pool) => {
return (attribs) => re.test(attribs); return (attribs) => re.test(attribs);
}; };
export const identity = (N) => exports.pack(N, N, '', ''); export const identity = (N) => exports.pack(N, N, '', '');
export const makeSplice = (orig, start, ndel, ins, attribs, pool) => { export const makeSplice = (orig, start, ndel, ins, attribs?, pool?) => {
if (start < 0) if (start < 0)
throw new RangeError(`start index must be non-negative (is ${start})`); throw new RangeError(`start index must be non-negative (is ${start})`);
if (ndel < 0) if (ndel < 0)
@ -1346,7 +1359,7 @@ export const makeSplice = (orig, start, ndel, ins, attribs, pool) => {
assem.endDocument(); assem.endDocument();
return exports.pack(orig.length, orig.length + ins.length - ndel, assem.toString(), ins); return exports.pack(orig.length, orig.length + ins.length - ndel, assem.toString(), ins);
}; };
export const characterRangeFollow = (cs, startChar, endChar, insertionsAfter) => { export const characterRangeFollow = (cs, startChar, endChar, insertionsAfter?) => {
let newStartChar = startChar; let newStartChar = startChar;
let newEndChar = endChar; let newEndChar = endChar;
let lengthChangeSoFar = 0; let lengthChangeSoFar = 0;
@ -1451,7 +1464,7 @@ export const mapAttribNumbers = (cs, func) => {
}); });
return newUpToDollar + cs.substring(dollarPos); return newUpToDollar + cs.substring(dollarPos);
}; };
export const makeAText = (text, attribs) => ({ export const makeAText = (text, attribs?) => ({
text, text,
attribs: (attribs || exports.makeAttribution(text)), attribs: (attribs || exports.makeAttribution(text)),
}); });
@ -1538,7 +1551,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs. * attribute key, value pairs.
* @returns {Builder} this * @returns {Builder} this
*/ */
keep: (N, L, attribs, pool) => { keep: (N, L, attribs?, pool?) => {
o.opcode = '='; o.opcode = '=';
o.attribs = typeof attribs === 'string' o.attribs = typeof attribs === 'string'
? attribs : new AttributeMap(pool).update(attribs || []).toString(); ? attribs : new AttributeMap(pool).update(attribs || []).toString();
@ -1555,7 +1568,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs. * attribute key, value pairs.
* @returns {Builder} this * @returns {Builder} this
*/ */
keepText: (text, attribs, pool) => { keepText: (text, attribs?, pool?) => {
for (const op of opsFromText('=', text, attribs, pool)) for (const op of opsFromText('=', text, attribs, pool))
assem.append(op); assem.append(op);
return self; return self;
@ -1568,7 +1581,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs. * attribute key, value pairs.
* @returns {Builder} this * @returns {Builder} this
*/ */
insert: (text, attribs, pool) => { insert: (text, attribs, pool?) => {
for (const op of opsFromText('+', text, attribs, pool)) for (const op of opsFromText('+', text, attribs, pool))
assem.append(op); assem.append(op);
charBank.append(text); charBank.append(text);
@ -1580,7 +1593,7 @@ export const builder = (oldLen) => {
* character must be a newline. * character must be a newline.
* @returns {Builder} this * @returns {Builder} this
*/ */
remove: (N, L) => { remove: (N, L?) => {
o.opcode = '-'; o.opcode = '-';
o.attribs = ''; o.attribs = '';
o.chars = N; o.chars = N;
@ -1605,7 +1618,7 @@ export const makeAttribsString = (opcode, attribs, pool) => {
return attribs; return attribs;
return new AttributeMap(pool).update(attribs, opcode === '+').toString(); return new AttributeMap(pool).update(attribs, opcode === '+').toString();
}; };
export const subattribution = (astr, start, optEnd) => { export const subattribution = (astr, start, optEnd?) => {
const attOps = exports.deserializeOps(astr); const attOps = exports.deserializeOps(astr);
let attOpsNext = attOps.next(); let attOpsNext = attOps.next();
const assem = exports.smartOpAssembler(); const assem = exports.smartOpAssembler();

View file

@ -10,7 +10,7 @@ export const buildRemoveRange = (rep, builder, start, end) => {
builder.remove(end[1] - start[1]); builder.remove(end[1] - start[1]);
} }
}; };
export const buildKeepRange = (rep, builder, start, end, attribs, pool) => { export const buildKeepRange = (rep, builder, start, end, attribs?, pool?) => {
const startLineOffset = rep.lines.offsetOfIndex(start[0]); const startLineOffset = rep.lines.offsetOfIndex(start[0]);
const endLineOffset = rep.lines.offsetOfIndex(end[0]); const endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0]) { if (end[0] > start[0]) {

View file

@ -8,6 +8,10 @@ const { padutils: { warnDeprecated } } = { padutils };
* Supports serialization to JSON. * Supports serialization to JSON.
*/ */
class ChatMessage { class ChatMessage {
private text: any;
authorId: any;
private time: any;
displayName: null;
static fromObject(obj) { static fromObject(obj) {
// The userId property was renamed to authorId, and userName was renamed to displayName. Accept // The userId property was renamed to authorId, and userName was renamed to displayName. Accept
// the old names in case the db record was written by an older version of Etherpad. // the old names in case the db record was written by an older version of Etherpad.
@ -83,9 +87,10 @@ class ChatMessage {
// doesn't support authorId and displayName. // doesn't support authorId and displayName.
toJSON() { toJSON() {
const { authorId, displayName, ...obj } = this; const { authorId, displayName, ...obj } = this;
obj.userId = authorId; let objExtendable = obj as any
obj.userName = displayName; objExtendable.userId = authorId;
return obj; objExtendable.userName = displayName;
return objExtendable;
} }
} }
export default ChatMessage; export default ChatMessage;

View file

@ -3,7 +3,8 @@ import { makeCSSManager as makeCSSManager$0 } from "./cssmanager.js";
import * as pluginUtils from "./pluginfw/shared.js"; import * as pluginUtils from "./pluginfw/shared.js";
import {Ace2EditorInfo, AceDocType} from "../module/Ace2EditorInfo"; import {Ace2EditorInfo, AceDocType} from "../module/Ace2EditorInfo";
import {clientVars} from "../../node/handler/PadMessageHandler"; import {clientVars} from "../../node/handler/PadMessageHandler";
import {CustomWindow} from "../module/CustomWindow"; import {CustomElementWithSheet, CustomWindow} from "../module/CustomWindow";
import {required} from "../../node/eejs";
'use strict'; 'use strict';
const makeCSSManager = { makeCSSManager: makeCSSManager$0 }.makeCSSManager; const makeCSSManager = { makeCSSManager: makeCSSManager$0 }.makeCSSManager;
@ -244,17 +245,17 @@ const Ace2Editor = function () {
require.setLibraryURI(absUrl('../javascripts/lib')); require.setLibraryURI(absUrl('../javascripts/lib'));
require.setGlobalKeyPath('require'); require.setGlobalKeyPath('require');
// intentially moved before requiring client_plugins to save a 307 // intentially moved before requiring client_plugins to save a 307
innerWindow.Ace2Inner = require('ep_etherpad-lite/static/js/ace2_inner'); innerWindow.Ace2Inner = required('ep_etherpad-lite/static/js/ace2_inner');
innerWindow.plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); innerWindow.plugins = required('ep_etherpad-lite/static/js/pluginfw/client_plugins');
innerWindow.plugins.adoptPluginsFromAncestorsOf(innerWindow); innerWindow.plugins.adoptPluginsFromAncestorsOf(innerWindow);
innerWindow.$ = innerWindow.jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; innerWindow.$ = innerWindow.jQuery = required('ep_etherpad-lite/static/js/rjquery').jQuery;
debugLog('Ace2Editor.init() waiting for plugins'); debugLog('Ace2Editor.init() waiting for plugins');
await new Promise((resolve, reject) => innerWindow.plugins.ensure((err) => err != null ? reject(err) : resolve())); await new Promise<void>((resolve, reject) => innerWindow.plugins.ensure((err) => err != null ? reject(err) : resolve()));
debugLog('Ace2Editor.init() waiting for Ace2Inner.init()'); debugLog('Ace2Editor.init() waiting for Ace2Inner.init()');
await innerWindow.Ace2Inner.init(info, { await innerWindow.Ace2Inner.init(info, {
inner: makeCSSManager(innerStyle.sheet), inner: makeCSSManager(innerStyle.sheet),
outer: makeCSSManager(outerStyle.sheet), outer: makeCSSManager(outerStyle.sheet),
parent: makeCSSManager(document.querySelector('style[title="dynamicsyntax"]').sheet), parent: makeCSSManager((document.querySelector('style[title="dynamicsyntax"]') as unknown as CustomElementWithSheet).sheet),
}); });
debugLog('Ace2Editor.init() Ace2Inner.init() returned'); debugLog('Ace2Editor.init() Ace2Inner.init() returned');
loaded = true; loaded = true;

View file

@ -20,7 +20,9 @@
const msgBlock = document.createElement('blockquote'); const msgBlock = document.createElement('blockquote');
box.appendChild(msgBlock); box.appendChild(msgBlock);
msgBlock.style.fontWeight = 'bold'; msgBlock.style.fontWeight = 'bold';
msgBlock.appendChild(document.createTextNode(msg)); if (typeof msg === "string") {
msgBlock.appendChild(document.createTextNode(msg));
}
const loc = document.createElement('p'); const loc = document.createElement('p');
box.appendChild(loc); box.appendChild(loc);
loc.appendChild(document.createTextNode(`in ${url}`)); loc.appendChild(document.createTextNode(`in ${url}`));

View file

@ -1,12 +1,15 @@
import { makeCSSManager as makeCSSManager$0 } from "./cssmanager.js"; import { makeCSSManager as makeCSSManager$0 } from "./cssmanager.js";
import { domline as domline$0 } from "./domline.js"; import { domline as domline$0 } from "./domline.js";
import AttribPool from "./AttributePool.js"; import {AttributePool} from "./AttributePool.js";
import * as Changeset from "./Changeset.js"; import * as Changeset from "./Changeset.js";
import * as attributes from "./attributes.js"; import * as attributes from "./attributes.js";
import { linestylefilter as linestylefilter$0 } from "./linestylefilter.js"; import { linestylefilter as linestylefilter$0 } from "./linestylefilter.js";
import { colorutils as colorutils$0 } from "./colorutils.js"; import { colorutils as colorutils$0 } from "./colorutils.js";
import * as _ from "./underscore.js"; import * as _ from "underscore";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {Author, AuthorData, CustomElementWithSheet, CustomWindow} from "../module/CustomWindow";
import {i18nextvar} from "./vendors/i18next";
'use strict'; 'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
@ -53,9 +56,10 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text), currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text),
currentDivs: null, currentDivs: null,
// to be filled in once the dom loads // to be filled in once the dom loads
apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool), apool: (new AttributePool()).fromJsonable(clientVars.collab_client_vars.apool),
alines: Changeset.splitAttributionLines(clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text), alines: Changeset.splitAttributionLines(clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
// generates a jquery element containing HTML for a line // generates a jquery element containing HTML for a line
targetRevision: undefined,
lineToElement(line, aline) { lineToElement(line, aline) {
const element = document.createElement('div'); const element = document.createElement('div');
const emptyLine = (line === '\n'); const emptyLine = (line === '\n');
@ -64,7 +68,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
domInfo.prepareForAdd(); domInfo.prepareForAdd();
element.className = domInfo.node.className; element.className = domInfo.node.className;
element.innerHTML = domInfo.node.innerHTML; element.innerHTML = domInfo.node.innerHTML;
element.id = Math.random(); element.id = String(Math.random());
return $(element); return $(element);
}, },
// splice the lines // splice the lines
@ -172,11 +176,12 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
padContents.currentRevision = revision; padContents.currentRevision = revision;
padContents.currentTime += timeDelta * 1000; padContents.currentTime += timeDelta * 1000;
updateTimer(); updateTimer();
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
}; };
const loadedNewChangeset = (changesetForward, changesetBackward, revision, timeDelta) => { const loadedNewChangeset = (changesetForward, changesetBackward, revision, timeDelta) => {
const revisionInfo = window.revisionInfo; const revisionInfo = (window as unknown as CustomWindow).revisionInfo;
const broadcasting = (BroadcastSlider.getSliderPosition() === revisionInfo.latest); const broadcasting = (BroadcastSlider.getSliderPosition() === revisionInfo.latest);
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta); revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
BroadcastSlider.setSliderLength(revisionInfo.latest); BroadcastSlider.setSliderLength(revisionInfo.latest);
@ -203,7 +208,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const hours = zpad(date.getHours(), 2); const hours = zpad(date.getHours(), 2);
const minutes = zpad(date.getMinutes(), 2); const minutes = zpad(date.getMinutes(), 2);
const seconds = zpad(date.getSeconds(), 2); const seconds = zpad(date.getSeconds(), 2);
return (html10n.get('timeslider.dateformat', { return (i18nextvar('timeslider.dateformat', {
day, day,
month, month,
year, year,
@ -213,21 +218,21 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
})); }));
}; };
$('#timer').html(dateFormat()); $('#timer').html(dateFormat());
const revisionDate = html10n.get('timeslider.saved', { const revisionDate = i18nextvar('timeslider.saved', {
day: date.getDate(), day: date.getDate(),
month: [ month: [
html10n.get('timeslider.month.january'), i18nextvar('timeslider.month.january'),
html10n.get('timeslider.month.february'), i18nextvar('timeslider.month.february'),
html10n.get('timeslider.month.march'), i18nextvar('timeslider.month.march'),
html10n.get('timeslider.month.april'), i18nextvar('timeslider.month.april'),
html10n.get('timeslider.month.may'), i18nextvar('timeslider.month.may'),
html10n.get('timeslider.month.june'), i18nextvar('timeslider.month.june'),
html10n.get('timeslider.month.july'), i18nextvar('timeslider.month.july'),
html10n.get('timeslider.month.august'), i18nextvar('timeslider.month.august'),
html10n.get('timeslider.month.september'), i18nextvar('timeslider.month.september'),
html10n.get('timeslider.month.october'), i18nextvar('timeslider.month.october'),
html10n.get('timeslider.month.november'), i18nextvar('timeslider.month.november'),
html10n.get('timeslider.month.december'), i18nextvar('timeslider.month.december'),
][date.getMonth()], ][date.getMonth()],
year: date.getFullYear(), year: date.getFullYear(),
}); });
@ -236,7 +241,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
updateTimer(); updateTimer();
const goToRevision = (newRevision) => { const goToRevision = (newRevision) => {
padContents.targetRevision = newRevision; padContents.targetRevision = newRevision;
const path = window.revisionInfo.getPath(padContents.currentRevision, newRevision); const path = (window as unknown as CustomWindow).revisionInfo.getPath(padContents.currentRevision, newRevision);
hooks.aCallAll('goToRevisionEvent', { hooks.aCallAll('goToRevisionEvent', {
rev: newRevision, rev: newRevision,
}); });
@ -273,10 +278,11 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// Loading changeset history for old revision (to make diff between old and new revision) // Loading changeset history for old revision (to make diff between old and new revision)
loadChangesetsForRevision(padContents.currentRevision - 1); loadChangesetsForRevision(padContents.currentRevision - 1);
} }
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
}; };
const loadChangesetsForRevision = (revision, callback) => { const loadChangesetsForRevision = (revision, callback?) => {
if (BroadcastSlider.getSliderLength() > 10000) { if (BroadcastSlider.getSliderLength() > 10000) {
const start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10 const start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10
changesetLoader.queueUp(start, 100); changesetLoader.queueUp(start, 100);
@ -348,7 +354,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
setTimeout(() => this.loadFromQueue(), 10); setTimeout(() => this.loadFromQueue(), 10);
}, },
handleResponse: (data, start, granularity, callback) => { handleResponse: (data, start, granularity, callback) => {
const pool = (new AttribPool()).fromJsonable(data.apool); const pool = (new AttributePool()).fromJsonable(data.apool);
for (let i = 0; i < data.forwardsChangesets.length; i++) { for (let i = 0; i < data.forwardsChangesets.length; i++) {
const astart = start + i * granularity - 1; // rev -1 is a blank single line const astart = start + i * granularity - 1; // rev -1 is a blank single line
let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
@ -357,7 +363,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// debugLog("adding changeset:", astart, aend); // debugLog("adding changeset:", astart, aend);
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
window.revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); (window as unknown as CustomWindow).revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
} }
if (callback) if (callback)
callback(start - 1, start + data.forwardsChangesets.length * granularity - 1); callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
@ -366,15 +372,16 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
if (obj.type === 'COLLABROOM') { if (obj.type === 'COLLABROOM') {
obj = obj.data; obj = obj.data;
if (obj.type === 'NEW_CHANGES') { if (obj.type === 'NEW_CHANGES') {
const changeset = Changeset.moveOpsToNewPool(obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); const changeset = Changeset.moveOpsToNewPool(obj.changeset, (new AttributePool()).fromJsonable(obj.apool), padContents.apool);
let changesetBack = Changeset.inverse(obj.changeset, padContents.currentLines, padContents.alines, padContents.apool); let changesetBack = Changeset.inverse(obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
changesetBack = Changeset.moveOpsToNewPool(changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); changesetBack = Changeset.moveOpsToNewPool(changesetBack, (new AttributePool()).fromJsonable(obj.apool), padContents.apool);
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta); loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
} }
else if (obj.type === 'NEW_AUTHORDATA') { else if (obj.type === 'NEW_AUTHORDATA') {
const authorMap = {}; const authorMap = {};
authorMap[obj.author] = obj.data; authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap); receiveAuthorData(authorMap);
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} }
@ -412,13 +419,15 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
goToRevisionIfEnabledCount--; goToRevisionIfEnabledCount--;
} }
else { else {
//FIXME what to do?
// @ts-ignore
goToRevision(...args); goToRevision(...args);
} }
}; }
BroadcastSlider.onSlider(goToRevisionIfEnabled); BroadcastSlider.onSlider(goToRevisionIfEnabled);
const dynamicCSS = makeCSSManager(document.querySelector('style[title="dynamicsyntax"]').sheet); const dynamicCSS = makeCSSManager((document.querySelector('style[title="dynamicsyntax"]') as unknown as CustomElementWithSheet).sheet);
const authorData = {}; const authorData = {};
const receiveAuthorData = (newAuthorData) => { const receiveAuthorData = (newAuthorData: Author) => {
for (const [author, data] of Object.entries(newAuthorData)) { for (const [author, data] of Object.entries(newAuthorData)) {
const bgcolor = typeof data.colorId === 'number' const bgcolor = typeof data.colorId === 'number'
? clientVars.colorPalette[data.colorId] : data.colorId; ? clientVars.colorPalette[data.colorId] : data.colorId;

View file

@ -4,6 +4,9 @@
* This helps other people to understand this code better and helps them to improve it. * This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/ */
import {clientVars} from "../../node/handler/PadMessageHandler";
import {CustomWindow} from "../module/CustomWindow";
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -36,7 +39,12 @@ const loadBroadcastRevisionsJS = () => {
this.changesets.push(changesetWrapper); this.changesets.push(changesetWrapper);
this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev)); this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
}; };
const revisionInfo = {}; const revisionInfo = {
latest: undefined,
addChangeset: undefined,
createNew: undefined,
getPath: undefined
};
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) { revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
const startRevision = this[fromIndex] || this.createNew(fromIndex); const startRevision = this[fromIndex] || this.createNew(fromIndex);
const endRevision = this[toIndex] || this.createNew(toIndex); const endRevision = this[toIndex] || this.createNew(toIndex);
@ -95,6 +103,6 @@ const loadBroadcastRevisionsJS = () => {
times, times,
}; };
}; };
window.revisionInfo = revisionInfo; (window as unknown as CustomWindow).revisionInfo = revisionInfo;
}; };
export { loadBroadcastRevisionsJS }; export { loadBroadcastRevisionsJS };

View file

@ -1,13 +1,17 @@
import * as _ from "./underscore.js"; import _ from "underscore";
import { padmodals as padmodals$0 } from "./pad_modals.js"; import { padmodals as padmodals$0 } from "./pad_modals.js";
import { colorutils as colorutils$0 } from "./colorutils.js"; import { colorutils as colorutils$0 } from "./colorutils.js";
import {i18nextvar} from "./vendors/i18next";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {CustomElementWithSheet} from "../module/CustomWindow";
'use strict'; 'use strict';
const padmodals = { padmodals: padmodals$0 }.padmodals; const padmodals = { padmodals: padmodals$0 }.padmodals;
const colorutils = { colorutils: colorutils$0 }.colorutils; const colorutils = { colorutils: colorutils$0 }.colorutils;
const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => { const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
let BroadcastSlider; let BroadcastSlider;
// Hack to ensure timeslider i18n values are in // Hack to ensure timeslider i18n values are in
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get('timeslider.toolbar.returnbutton')); $("[data-key='timeslider_returnToPad'] > a > span").html(i18nextvar('timeslider.toolbar.returnbutton'));
(() => { (() => {
let sliderLength = 1000; let sliderLength = 1000;
let sliderPos = 0; let sliderPos = 0;
@ -56,7 +60,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$('a.tlink').map(function () { $('a.tlink').map(function () {
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos)); $(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
}); });
$('#revision_label').html(html10n.get('timeslider.version', { version: newpos })); $('#revision_label').html(i18nextvar('timeslider.version', { version: newpos }));
$('#leftstar, #leftstep').toggleClass('disabled', newpos === 0); $('#leftstar, #leftstep').toggleClass('disabled', newpos === 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos === sliderLength); $('#rightstar, #rightstep').toggleClass('disabled', newpos === sliderLength);
sliderPos = newpos; sliderPos = newpos;
@ -100,7 +104,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
} }
}); });
if (numAnonymous > 0) { if (numAnonymous > 0) {
const anonymousAuthorString = html10n.get('timeslider.unnamedauthors', { num: numAnonymous }); const anonymousAuthorString = i18nextvar('timeslider.unnamedauthors', { num: numAnonymous });
if (numNamed !== 0) { if (numNamed !== 0) {
authorsList.append(` + ${anonymousAuthorString}`); authorsList.append(` + ${anonymousAuthorString}`);
} }
@ -121,7 +125,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
} }
} }
if (authors.length === 0) { if (authors.length === 0) {
authorsList.append(html10n.get('timeslider.toolbar.authorsList')); authorsList.append(i18nextvar('timeslider.toolbar.authorsList'));
} }
}; };
const playButtonUpdater = () => { const playButtonUpdater = () => {
@ -163,7 +167,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
fireWhenAllScriptsAreLoaded.push(() => { fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup((e) => { $(document).keyup((e) => {
if (!e) if (!e)
e = window.event; e = window.event as any
const code = e.keyCode || e.which; const code = e.keyCode || e.which;
if (code === 37) { // left if (code === 37) { // left
if (e.shiftKey) { if (e.shiftKey) {
@ -196,19 +200,19 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
}); });
// Slider dragging // Slider dragging
$('#ui-slider-handle').mousedown(function (evt) { $('#ui-slider-handle').mousedown(function (evt) {
this.startLoc = evt.clientX; (this as unknown as CustomElementWithSheet).startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left')); (this as unknown as CustomElementWithSheet).currentLoc = parseInt($(this).css('left'));
sliderActive = true; sliderActive = true;
$(document).mousemove((evt2) => { $(document).mousemove((evt2) => {
$(this).css('pointer', 'move'); $(this).css('pointer', 'move');
let newloc = this.currentLoc + (evt2.clientX - this.startLoc); let newloc = (this as unknown as CustomElementWithSheet).currentLoc + (evt2.clientX - (this as unknown as CustomElementWithSheet).startLoc);
if (newloc < 0) if (newloc < 0)
newloc = 0; newloc = 0;
const maxPos = $('#ui-slider-bar').width() - 2; const maxPos = $('#ui-slider-bar').width() - 2;
if (newloc > maxPos) if (newloc > maxPos)
newloc = maxPos; newloc = maxPos;
const version = Math.floor(newloc * sliderLength / maxPos); const version = Math.floor(newloc * sliderLength / maxPos);
$('#revision_label').html(html10n.get('timeslider.version', { version })); $('#revision_label').html(i18nextvar('timeslider.version', { version }));
$(this).css('left', newloc); $(this).css('left', newloc);
if (getSliderPosition() !== version) if (getSliderPosition() !== version)
_callSliderCallbacks(version); _callSliderCallbacks(version);
@ -217,7 +221,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$(document).unbind('mousemove'); $(document).unbind('mousemove');
$(document).unbind('mouseup'); $(document).unbind('mouseup');
sliderActive = false; sliderActive = false;
let newloc = this.currentLoc + (evt2.clientX - this.startLoc); let newloc = (this as unknown as CustomElementWithSheet).currentLoc + (evt2.clientX - (this as unknown as CustomElementWithSheet).startLoc);
if (newloc < 0) if (newloc < 0)
newloc = 0; newloc = 0;
const maxPos = $('#ui-slider-bar').width() - 2; const maxPos = $('#ui-slider-bar').width() - 2;
@ -229,7 +233,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$(this).css('left', '2px'); $(this).css('left', '2px');
} }
else { else {
this.currentLoc = parseInt($(this).css('left')); (this as unknown as CustomElementWithSheet).currentLoc = parseInt($(this).css('left'));
} }
}); });
}); });

View file

@ -1,4 +1,6 @@
'use strict'; 'use strict';
import {CustomElementWithSheet} from "../module/CustomWindow";
const createSelectionRange = (range) => { const createSelectionRange = (range) => {
const clonedRange = range.cloneRange(); const clonedRange = range.cloneRange();
// we set the selection start and end to avoid error when user selects a text bigger than // we set the selection start and end to avoid error when user selects a text bigger than
@ -122,7 +124,7 @@ const getSelectionRange = () => {
}; };
export const getPosition = () => { export const getPosition = () => {
const range = getSelectionRange(); const range = getSelectionRange();
if (!range || $(range.endContainer).closest('body')[0].id !== 'innerdocbody') if (!range || ($(range.endContainer).closest('body')[0] as unknown as CustomElementWithSheet).id !== 'innerdocbody')
return null; return null;
// When there's a <br> or any element that has no height, we can't get the dimension of the // When there's a <br> or any element that has no height, we can't get the dimension of the
// element where the caret is. As we can't get the element height, we create a text node to get // element where the caret is. As we can't get the element height, we create a text node to get

View file

@ -1,6 +1,7 @@
import AttributeMap from "./AttributeMap.js"; import AttributeMap from "./AttributeMap.js";
import AttributePool from "./AttributePool.js"; import {AttributePool} from "./AttributePool.js";
import * as Changeset from "./Changeset.js"; import * as Changeset from "./Changeset.js";
import {CustomWindow} from "../module/CustomWindow";
'use strict'; 'use strict';
const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
// latest official text from server // latest official text from server
@ -111,7 +112,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
} }
else { else {
// Get my authorID // Get my authorID
const authorId = parent.parent.pad.myUserInfo.userId; const authorId = (parent.parent as unknown as CustomWindow).pad.myUserInfo.userId;
// Sanitize authorship: Replace all author attributes with this user's author ID in case the // Sanitize authorship: Replace all author attributes with this user's author ID in case the
// text was copied from another author. // text was copied from another author.
const cs = Changeset.unpack(userChangeset); const cs = Changeset.unpack(userChangeset);

33
src/static/js/chat.js → src/static/js/chat.ts Executable file → Normal file
View file

@ -4,6 +4,9 @@ import { padcookie as padcookie$0 } from "./pad_cookie.js";
import Tinycon from "tinycon/tinycon"; import Tinycon from "tinycon/tinycon";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import { padeditor as padeditor$0 } from "./pad_editor.js"; import { padeditor as padeditor$0 } from "./pad_editor.js";
import {CustomWindow, JQueryGritter} from "../module/CustomWindow";
import {pad} from "./pad";
import {i18nextvar} from "./vendors/i18next";
'use strict'; 'use strict';
const padutils = { padutils: padutils$0 }.padutils; const padutils = { padutils: padutils$0 }.padutils;
const padcookie = { padcookie: padcookie$0 }.padcookie; const padcookie = { padcookie: padcookie$0 }.padcookie;
@ -15,6 +18,8 @@ export const chat = (() => {
let userAndChat = false; let userAndChat = false;
let chatMentions = 0; let chatMentions = 0;
return { return {
historyPointer: undefined, gotInitalMessages: false,
show() { show() {
$('#chaticon').removeClass('visible'); $('#chaticon').removeClass('visible');
$('#chatbox').addClass('visible'); $('#chatbox').addClass('visible');
@ -22,7 +27,7 @@ export const chat = (() => {
chatMentions = 0; chatMentions = 0;
Tinycon.setBubble(0); Tinycon.setBubble(0);
$('.chat-gritter-msg').each(function () { $('.chat-gritter-msg').each(function () {
$.gritter.remove(this.id); ($ as unknown as JQueryGritter).gritter.remove(this.id);
}); });
}, },
focus: () => { focus: () => {
@ -31,7 +36,7 @@ export const chat = (() => {
}, 100); }, 100);
}, },
// Make chat stick to right hand side of screen // Make chat stick to right hand side of screen
stickToScreen(fromInitialCall) { stickToScreen(fromInitialCall?) {
if (pad.settings.hideChat) { if (pad.settings.hideChat) {
return; return;
} }
@ -46,7 +51,7 @@ export const chat = (() => {
padcookie.setPref('chatAlwaysVisible', isStuck); padcookie.setPref('chatAlwaysVisible', isStuck);
$('#options-stickychat').prop('checked', isStuck); $('#options-stickychat').prop('checked', isStuck);
}, },
chatAndUsers(fromInitialCall) { chatAndUsers(fromInitialCall?) {
const toEnable = $('#options-chatandusers').is(':checked'); const toEnable = $('#options-chatandusers').is(':checked');
if (toEnable || !userAndChat || fromInitialCall) { if (toEnable || !userAndChat || fromInitialCall) {
this.stickToScreen(true); this.stickToScreen(true);
@ -76,7 +81,7 @@ export const chat = (() => {
$('#chatbox').removeClass('visible'); $('#chatbox').removeClass('visible');
} }
}, },
scrollDown(force) { scrollDown(force?) {
if ($('#chatbox').hasClass('visible')) { if ($('#chatbox').hasClass('visible')) {
if (force || !this.lastMessage || !this.lastMessage.position() || if (force || !this.lastMessage || !this.lastMessage.position() ||
this.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) { this.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) {
@ -89,7 +94,7 @@ export const chat = (() => {
}, },
async send() { async send() {
const text = $('#chatinput').val(); const text = $('#chatinput').val();
if (text.replace(/\s+/, '').length === 0) if (typeof text !== "string" || text?.replace(/\s+/, '').length === 0)
return; return;
const message = new ChatMessage(text); const message = new ChatMessage(text);
await hooks.aCallAll('chatSendMessage', Object.freeze({ message })); await hooks.aCallAll('chatSendMessage', Object.freeze({ message }));
@ -117,7 +122,7 @@ export const chat = (() => {
})}`; })}`;
// the hook args // the hook args
const ctx = { const ctx = {
authorName: msg.displayName != null ? msg.displayName : html10n.get('pad.userlist.unnamed'), authorName: msg.displayName != null ? msg.displayName : i18nextvar('pad.userlist.unnamed'),
author: msg.authorId, author: msg.authorId,
text: padutils.escapeHtmlWithClickableLinks(msg.text, '_blank'), text: padutils.escapeHtmlWithClickableLinks(msg.text, '_blank'),
message: msg, message: msg,
@ -140,8 +145,8 @@ export const chat = (() => {
// does the user already have the chatbox open? // does the user already have the chatbox open?
const chatOpen = $('#chatbox').hasClass('visible'); const chatOpen = $('#chatbox').hasClass('visible');
// does this message contain this user's name? (is the current user mentioned?) // does this message contain this user's name? (is the current user mentioned?)
const wasMentioned = msg.authorId !== window.clientVars.userId && const wasMentioned = msg.authorId !== (window as unknown as CustomWindow).clientVars.userId &&
ctx.authorName !== html10n.get('pad.userlist.unnamed') && ctx.authorName !== i18nextvar('pad.userlist.unnamed') &&
normalize(ctx.text).includes(normalize(ctx.authorName)); normalize(ctx.text).includes(normalize(ctx.authorName));
// If the user was mentioned, make the message sticky // If the user was mentioned, make the message sticky
if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) {
@ -169,7 +174,10 @@ export const chat = (() => {
chatMsg.insertAfter('#chatloadmessagesbutton'); chatMsg.insertAfter('#chatloadmessagesbutton');
else else
$('#chattext').append(chatMsg); $('#chattext').append(chatMsg);
chatMsg.each((i, e) => html10n.translateElement(html10n.translations, e)); chatMsg.each((i, e) => {
//TODO Fix this and return the component
i18nextvar(e)
});
// should we increment the counter?? // should we increment the counter??
if (increment && !isHistoryAdd) { if (increment && !isHistoryAdd) {
// Update the counter of unread messages // Update the counter of unread messages
@ -182,8 +190,11 @@ export const chat = (() => {
// ctx.text was HTML-escaped before calling the hook. Hook functions are trusted // ctx.text was HTML-escaped before calling the hook. Hook functions are trusted
// to not introduce an XSS vulnerability by adding unescaped user input. // to not introduce an XSS vulnerability by adding unescaped user input.
.append($('<div>').html(ctx.text).contents()); .append($('<div>').html(ctx.text).contents());
text.each((i, e) => html10n.translateElement(html10n.translations, e)); text.each((i, e) => {
$.gritter.add({ //TODO Fix this and return the component
i18nextvar(e.getAttribute('data-l10n-id'))
});;
($ as unknown as JQueryGritter).gritter.add({
text, text,
sticky: ctx.sticky, sticky: ctx.sticky,
time: ctx.duration, time: ctx.duration,

View file

@ -1,6 +1,8 @@
import { chat as chat$0 } from "./chat.js"; import { chat as chat$0 } from "./chat.js";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import browser from "./vendors/browser.js"; import browser from "./vendors/browser.js";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {AuthorData} from "../module/CustomWindow";
'use strict'; 'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
@ -46,14 +48,14 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
userSet[userId] = initialUserInfo; userSet[userId] = initialUserInfo;
let isPendingRevision = false; let isPendingRevision = false;
const callbacks = { const callbacks = {
onUserJoin: () => { }, onUserJoin: (arg?) => { },
onUserLeave: () => { }, onUserLeave: (arg?) => { },
onUpdateUserInfo: () => { }, onUpdateUserInfo: (arg?) => { },
onChannelStateChange: () => { }, onChannelStateChange: (arg?, addArg?) => { },
onClientMessage: () => { }, onClientMessage: (arg?) => { },
onInternalAction: () => { }, onInternalAction: (arg?) => { },
onConnectionTrouble: () => { }, onConnectionTrouble: (arg?) => { },
onServerMessage: () => { }, onServerMessage: (arg?) => { },
}; };
if (browser.firefox) { if (browser.firefox) {
// Prevent "escape" from taking effect and canceling a comet connection; // Prevent "escape" from taking effect and canceling a comet connection;
@ -151,6 +153,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
}); });
}; };
const serverMessageTaskQueue = new class { const serverMessageTaskQueue = new class {
private _promiseChain: Promise<void>;
constructor() { constructor() {
this._promiseChain = Promise.resolve(); this._promiseChain = Promise.resolve();
} }
@ -309,7 +312,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
const tellAceActiveAuthorInfo = (userInfo) => { const tellAceActiveAuthorInfo = (userInfo) => {
tellAceAuthorInfo(userInfo.userId, userInfo.colorId); tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
}; };
const tellAceAuthorInfo = (userId, colorId, inactive) => { const tellAceAuthorInfo = (userId, colorId, inactive?) => {
if (typeof colorId === 'number') { if (typeof colorId === 'number') {
colorId = clientVars.colorPalette[colorId]; colorId = clientVars.colorPalette[colorId];
} }
@ -333,11 +336,11 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
const tellAceAboutHistoricalAuthors = (hadata) => { const tellAceAboutHistoricalAuthors = (hadata) => {
for (const [author, data] of Object.entries(hadata)) { for (const [author, data] of Object.entries(hadata)) {
if (!userSet[author]) { if (!userSet[author]) {
tellAceAuthorInfo(author, data.colorId, true); tellAceAuthorInfo(author, (data as AuthorData).colorId, true);
} }
} }
}; };
const setChannelState = (newChannelState, moreInfo) => { const setChannelState = (newChannelState, moreInfo?) => {
if (newChannelState !== channelState) { if (newChannelState !== channelState) {
channelState = newChannelState; channelState = newChannelState;
callbacks.onChannelStateChange(channelState, moreInfo); callbacks.onChannelStateChange(channelState, moreInfo);
@ -353,7 +356,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
// We need to present a working interface even before the socket // We need to present a working interface even before the socket
// is connected for the first time. // is connected for the first time.
let deferredActions = []; let deferredActions = [];
const defer = (func, tag) => function (...args) { const defer = (func, tag?) => function (...args) {
const action = () => { const action = () => {
func.call(this, ...args); func.call(this, ...args);
}; };
@ -365,7 +368,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
action(); action();
} }
}; };
const doDeferredActions = (tag) => { const doDeferredActions = (tag?) => {
const newArray = []; const newArray = [];
for (let i = 0; i < deferredActions.length; i++) { for (let i = 0; i < deferredActions.length; i++) {
const a = deferredActions[i]; const a = deferredActions[i];
@ -386,7 +389,14 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
}; };
const getCurrentRevisionNumber = () => rev; const getCurrentRevisionNumber = () => rev;
const getMissedChanges = () => { const getMissedChanges = () => {
const obj = {}; const obj = {
furtherChangeset: undefined,
furtherChangesetAPool: undefined,
committedChangesetAPool: undefined,
committedChangeset: undefined,
userInfo: undefined,
baseRev: undefined
};
obj.userInfo = userSet[userId]; obj.userInfo = userSet[userId];
obj.baseRev = rev; obj.baseRev = rev;
if (committing && stateMessage) { if (committing && stateMessage) {

View file

@ -1,102 +0,0 @@
'use strict';
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js
// THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const colorutils = {};
// Check that a given value is a css hex color value, e.g.
// "#ffffff" or "#fff"
colorutils.isCssHex = (cssColor) => /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor);
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = (cssColor) => {
const sixHex = colorutils.css2sixhex(cssColor);
const hexToFloat = (hh) => Number(`0x${hh}`) / 255;
return [
hexToFloat(sixHex.substr(0, 2)),
hexToFloat(sixHex.substr(2, 2)),
hexToFloat(sixHex.substr(4, 2)),
];
};
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
colorutils.css2sixhex = (cssColor) => {
let h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length !== 6) {
const a = h.charAt(0);
const b = h.charAt(1);
const c = h.charAt(2);
h = a + a + b + b + c + c;
}
return h;
};
// [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = (triple) => {
const floatToHex = (n) => {
const n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return (`0${n2.toString(16)}`).slice(-2);
};
return `#${floatToHex(triple[0])}${floatToHex(triple[1])}${floatToHex(triple[2])}`;
};
colorutils.clamp = (v, bot, top) => v < bot ? bot : (v > top ? top : v);
colorutils.min3 = (a, b, c) => (a < b) ? (a < c ? a : c) : (b < c ? b : c);
colorutils.max3 = (a, b, c) => (a > b) ? (a > c ? a : c) : (b > c ? b : c);
colorutils.colorMin = (c) => colorutils.min3(c[0], c[1], c[2]);
colorutils.colorMax = (c) => colorutils.max3(c[0], c[1], c[2]);
colorutils.scale = (v, bot, top) => colorutils.clamp(bot + v * (top - bot), 0, 1);
colorutils.unscale = (v, bot, top) => colorutils.clamp((v - bot) / (top - bot), 0, 1);
colorutils.scaleColor = (c, bot, top) => [
colorutils.scale(c[0], bot, top),
colorutils.scale(c[1], bot, top),
colorutils.scale(c[2], bot, top),
];
colorutils.unscaleColor = (c, bot, top) => [
colorutils.unscale(c[0], bot, top),
colorutils.unscale(c[1], bot, top),
colorutils.unscale(c[2], bot, top),
];
// rule of thumb for RGB brightness; 1.0 is white
colorutils.luminosity = (c) => c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
colorutils.saturate = (c) => {
const min = colorutils.colorMin(c);
const max = colorutils.colorMax(c);
if (max - min <= 0)
return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max);
};
colorutils.blend = (c1, c2, t) => [
colorutils.scale(t, c1[0], c2[0]),
colorutils.scale(t, c1[1], c2[1]),
colorutils.scale(t, c1[2], c2[2]),
];
colorutils.invert = (c) => [1 - c[0], 1 - c[1], 1 - c[2]];
colorutils.complementary = (c) => {
const inv = colorutils.invert(c);
return [
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11),
];
};
colorutils.textColorFromBackgroundColor = (bgcolor, skinName) => {
const white = skinName === 'colibris' ? 'var(--super-light-color)' : '#fff';
const black = skinName === 'colibris' ? 'var(--super-dark-color)' : '#222';
return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black;
};
export { colorutils };

113
src/static/js/colorutils.ts Normal file
View file

@ -0,0 +1,113 @@
'use strict';
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js
// THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const colorutils = {
css2triple: (cssColor) => {
const sixHex = colorutils.css2sixhex(cssColor);
const hexToFloat = (hh) => Number(`0x${hh}`) / 255;
return [
hexToFloat(sixHex.substr(0, 2)),
hexToFloat(sixHex.substr(2, 2)),
hexToFloat(sixHex.substr(4, 2)),
];
},
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
css2sixhex: (cssColor) => {
let h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length !== 6) {
const a = h.charAt(0);
const b = h.charAt(1);
const c = h.charAt(2);
h = a + a + b + b + c + c;
}
return h;
},
// [1.0, 1.0, 1.0] -> "#ffffff"
triple2css: (triple) => {
const floatToHex = (n) => {
const n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return (`0${n2.toString(16)}`).slice(-2);
};
return `#${floatToHex(triple[0])}${floatToHex(triple[1])}${floatToHex(triple[2])}`;
},
isCssHex: (cssColor) => /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor),
clamp: (v, bot, top) => v < bot ? bot : (v > top ? top : v),
min3: (a, b, c) => (a < b) ? (a < c ? a : c) : (b < c ? b : c),
max3: (a, b, c) => (a > b) ? (a > c ? a : c) : (b > c ? b : c),
colorMin: (c) => colorutils.min3(c[0], c[1], c[2]),
colorMax: (c) => colorutils.max3(c[0], c[1], c[2]),
scale: (v, bot, top) => colorutils.clamp(bot + v * (top - bot), 0, 1),
unscale: (v, bot, top) => colorutils.clamp((v - bot) / (top - bot), 0, 1),
scaleColor:(c, bot, top) => [
colorutils.scale(c[0], bot, top),
colorutils.scale(c[1], bot, top),
colorutils.scale(c[2], bot, top),
],
unscaleColor: (c, bot, top) => [
colorutils.unscale(c[0], bot, top),
colorutils.unscale(c[1], bot, top),
colorutils.unscale(c[2], bot, top),
],
// rule of thumb for RGB brightness; 1.0 is white
luminosity: (c) => c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11,
saturate: (c) => {
const min = colorutils.colorMin(c);
const max = colorutils.colorMax(c);
if (max - min <= 0)
return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max);
},
blend: (c1, c2, t) => [
colorutils.scale(t, c1[0], c2[0]),
colorutils.scale(t, c1[1], c2[1]),
colorutils.scale(t, c1[2], c2[2]),
],
invert: (c) => [1 - c[0], 1 - c[1], 1 - c[2]],
complementary: (c) => {
const inv = colorutils.invert(c);
return [
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11),
];
},
textColorFromBackgroundColor: (bgcolor, skinName) => {
const white = skinName === 'colibris' ? 'var(--super-light-color)' : '#fff';
const black = skinName === 'colibris' ? 'var(--super-dark-color)' : '#222';
return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black;
},
};
// Check that a given value is a css hex color value, e.g.
// "#ffffff" or "#fff"
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = (cssColor) => {
const sixHex = colorutils.css2sixhex(cssColor);
const hexToFloat = (hh) => Number(`0x${hh}`) / 255;
return [
hexToFloat(sixHex.substr(0, 2)),
hexToFloat(sixHex.substr(2, 2)),
hexToFloat(sixHex.substr(4, 2)),
];
};
export { colorutils };

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//TODO make this file compatible with Typescript
import AttributeMap from "./AttributeMap.js"; import AttributeMap from "./AttributeMap.js";
import UNorm from "unorm"; import UNorm from "unorm";
import * as Changeset from "./Changeset.js"; import * as Changeset from "./Changeset.js";
@ -51,7 +53,7 @@ const supportedElems = new Set([
'u', 'u',
'ul', 'ul',
]); ]);
const makeContentCollector = (collectStyles, abrowser, apool, className2Author) => { const makeContentCollector = (collectStyles, abrowser, apool?, className2Author?) => {
const _blockElems = { const _blockElems = {
div: 1, div: 1,
p: 1, p: 1,
@ -101,7 +103,380 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
self.startNew(); self.startNew();
return self; return self;
})(); })();
const cc = {}; const cc = {
collectContent(body, state?) {
let unsupportedElements = null;
if (!state) {
state = {
flags: { /* name -> nesting counter*/},
localAttribs: null,
attribs: { /* name -> nesting counter*/},
attribString: '',
// lineAttributes maintain a map from attributes to attribute values set on a line
lineAttributes: {
/*
example:
'list': 'bullet1',
*/
},
unsupportedElements: new Set(),
};
unsupportedElements = state.unsupportedElements;
}
const localAttribs = state.localAttribs;
state.localAttribs = null;
const isBlock = isBlockElement(node);
if (!isBlock && node.name && (node.name !== 'body')) {
if (!supportedElems.has(node.name))
state.unsupportedElements.add(node.name);
}
const isEmpty = _isEmpty(node, state);
if (isBlock)
_ensureColumnZero(state);
const startLine = lines.length() - 1;
_reachBlockPoint(node, 0, state);
if (node.nodeType === node.TEXT_NODE) {
const tname = node.parentNode.getAttribute('name');
const context = { cc: this, state, tname, node, text: node.nodeValue };
// Hook functions may either return a string (deprecated) or modify context.text. If any hook
// function modifies context.text then all returned strings are ignored. If no hook functions
// modify context.text, the first hook function to return a string wins.
const [hookTxt] = hooks.callAll('collectContentLineText', context).filter((s) => typeof s === 'string');
let txt = context.text === node.nodeValue && hookTxt != null ? hookTxt : context.text;
let rest = '';
let x = 0; // offset into original text
if (txt.length === 0) {
if (startPoint && node === startPoint.node) {
selStart = _pointHere(0, state);
}
if (endPoint && node === endPoint.node) {
selEnd = _pointHere(0, state);
}
}
while (txt.length > 0) {
let consumed = 0;
if (state.flags.preMode) {
const firstLine = txt.split('\n', 1)[0];
consumed = firstLine.length + 1;
rest = txt.substring(consumed);
txt = firstLine;
}
else { /* will only run this loop body once */
}
if (startPoint && node === startPoint.node && startPoint.index - x <= txt.length) {
selStart = _pointHere(startPoint.index - x, state);
}
if (endPoint && node === endPoint.node && endPoint.index - x <= txt.length) {
selEnd = _pointHere(endPoint.index - x, state);
}
let txt2 = txt;
if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt)) {
// prevents textnodes containing just "\n" from being significant
// in safari when pasting text, now that we convert them to
// spaces instead of removing them, because in other cases
// removing "\n" from pasted HTML will collapse words together.
txt2 = '';
}
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
if (atBeginningOfLine) {
// newlines in the source mustn't become spaces at beginning of line box
txt2 = txt2.replace(/^\n*/, '');
}
if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
_produceLineAttributesMarker(state);
}
lines.appendText(textify(txt2), state.attribString);
x += consumed;
txt = rest;
if (txt.length > 0) {
cc.startNewLine(state);
}
}
}
else if (node.nodeType === node.ELEMENT_NODE) {
const tname = tagName(node) || '';
if (tname === 'img') {
hooks.callAll('collectContentImage', {
cc,
state,
tname,
styl: null,
cls: null,
node,
});
}
else {
// THIS SEEMS VERY HACKY! -- Please submit a better fix!
delete state.lineAttributes.img;
}
if (tname === 'br') {
this.breakLine = true;
const tvalue = node.getAttribute('value');
const [startNewLine = true] = hooks.callAll('collectContentLineBreak', {
cc: this,
state,
tname,
tvalue,
styl: null,
cls: null,
});
if (startNewLine) {
cc.startNewLine(state);
}
}
else if (tname === 'script' || tname === 'style') {
// ignore
}
else if (!isEmpty) {
let styl = node.getAttribute('style');
let cls = node.getAttribute('class');
let isPre = (tname === 'pre');
if ((!isPre) && abrowser && abrowser.safari) {
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
}
if (isPre)
cc.incrementFlag(state, 'preMode');
let oldListTypeOrNull = null;
let oldAuthorOrNull = null;
// LibreOffice Writer puts in weird items during import or copy/paste, we should drop them.
if (cls === 'Numbering_20_Symbols' || cls === 'Bullet_20_Symbols') {
styl = null;
cls = null;
// We have to return here but this could break things in the future,
// for now it shows how to fix the problem
return;
}
if (collectStyles) {
hooks.callAll('collectContentPre', {
cc,
state,
tname,
styl,
cls,
});
if (tname === 'b' ||
(styl && /\bfont-weight:\s*bold\b/i.exec(styl)) ||
tname === 'strong') {
cc.doAttrib(state, 'bold');
}
if (tname === 'i' ||
(styl && /\bfont-style:\s*italic\b/i.exec(styl)) ||
tname === 'em') {
cc.doAttrib(state, 'italic');
}
if (tname === 'u' ||
(styl && /\btext-decoration:\s*underline\b/i.exec(styl)) ||
tname === 'ins') {
cc.doAttrib(state, 'underline');
}
if (tname === 's' ||
(styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) ||
tname === 'del') {
cc.doAttrib(state, 'strikethrough');
}
if (tname === 'ul' || tname === 'ol') {
let type = node.getAttribute('class');
const rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls);
// lists do not need to have a type, so before we make a wrong guess
// check if we find a better hint within the node's children
if (!rr && !type) {
for (const child of node.childNodes) {
if (tagName(child) !== 'ul')
continue;
type = child.getAttribute('class');
if (type)
break;
}
}
if (rr && rr[1]) {
type = rr[1];
}
else {
if (tname === 'ul') {
const cls = node.getAttribute('class');
if ((type && type.match('indent')) || (cls && cls.match('indent'))) {
type = 'indent';
}
else {
type = 'bullet';
}
}
else {
type = 'number';
}
type += String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
}
oldListTypeOrNull = (_enterList(state, type) || 'none');
}
else if ((tname === 'div' || tname === 'p') && cls && cls.match(/(?:^| )ace-line\b/)) {
// This has undesirable behavior in Chrome but is right in other browsers.
// See https://github.com/ether/etherpad-lite/issues/2412 for reasoning
if (!abrowser.chrome)
oldListTypeOrNull = (_enterList(state, undefined) || 'none');
}
else if (tname === 'li') {
state.lineAttributes.start = state.start || 0;
_recalcAttribString(state);
if (state.lineAttributes.list.indexOf('number') !== -1) {
/*
Nested OLs are not --> <ol><li>1</li><ol>nested</ol></ol>
They are --> <ol><li>1</li><li><ol><li>nested</li></ol></li></ol>
Note how the <ol> item has to be inside a <li>
Because of this we don't increment the start number
*/
if (node.parentNode && tagName(node.parentNode) !== 'ol') {
/*
TODO: start number has to increment based on indentLevel(numberX)
This means we have to build an object IE
{
1: 4
2: 3
3: 5
}
But the browser seems to handle it fine using CSS.. Why can't we do the same
with exports? We can.. But let's leave this comment in because it might be useful
in the future..
*/
state.start++; // not if it's parent is an OL or UL.
}
}
// UL list items never modify the start value.
if (node.parentNode && tagName(node.parentNode) === 'ul') {
state.start++;
// TODO, this is hacky.
// Because if the first item is an UL it will increment a list no?
// A much more graceful way would be to say, ul increases if it's within an OL
// But I don't know a way to do that because we're only aware of the previous Line
// As the concept of parent's doesn't exist when processing each domline...
}
}
else {
// Below needs more testin if it's neccesary as _exitList should take care of this.
// delete state.start;
// delete state.listNesting;
// _recalcAttribString(state);
}
if (className2Author && cls) {
const classes = cls.match(/\S+/g);
if (classes && classes.length > 0) {
for (let i = 0; i < classes.length; i++) {
const c = classes[i];
const a = className2Author(c);
if (a) {
oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
break;
}
}
}
}
}
for (const c of node.childNodes) {
cc.collectContent(c, state);
}
if (collectStyles) {
hooks.callAll('collectContentPost', {
cc,
state,
tname,
styl,
cls,
});
}
if (isPre)
cc.decrementFlag(state, 'preMode');
if (state.localAttribs) {
for (let i = 0; i < state.localAttribs.length; i++) {
cc.decrementAttrib(state, state.localAttribs[i]);
}
}
if (oldListTypeOrNull) {
_exitList(state, oldListTypeOrNull);
}
if (oldAuthorOrNull) {
_exitAuthor(state, oldAuthorOrNull);
}
}
}
},
finish() {
lines.flush();
const lineAttribs = lines.attribLines();
const lineStrings = cc.getLines();
lineStrings.length--;
lineAttribs.length--;
const ss = getSelectionStart();
const se = getSelectionEnd();
const fixLongLines = () => {
// design mode does not deal with with really long lines!
const lineLimit = 2000; // chars
const buffer = 10; // chars allowed over before wrapping
let linesWrapped = 0;
let numLinesAfter = 0;
for (let i = lineStrings.length - 1; i >= 0; i--) {
let oldString = lineStrings[i];
let oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit + buffer) {
const newStrings = [];
const newAttribStrings = [];
while (oldString.length > lineLimit) {
// var semiloc = oldString.lastIndexOf(';', lineLimit-1);
// var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
const lengthToTake = lineLimit;
newStrings.push(oldString.substring(0, lengthToTake));
oldString = oldString.substring(lengthToTake);
newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
}
if (oldString.length > 0) {
newStrings.push(oldString);
newAttribStrings.push(oldAttribString);
}
const fixLineNumber = (lineChar) => {
if (lineChar[0] < 0)
return;
let n = lineChar[0];
let c = lineChar[1];
if (n > i) {
n += (newStrings.length - 1);
}
else if (n === i) {
let a = 0;
while (c > newStrings[a].length) {
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
};
fixLineNumber(ss);
fixLineNumber(se);
linesWrapped++;
numLinesAfter += newStrings.length;
lineStrings.splice(i, 1, ...newStrings);
lineAttribs.splice(i, 1, ...newAttribStrings);
}
}
return {
linesWrapped,
numLinesAfter,
};
};
const wrapData = fixLongLines();
return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings,
lineAttribs,
};
}
}
return cc;
}
const _ensureColumnZero = (state) => { const _ensureColumnZero = (state) => {
if (!lines.atColumnZero()) { if (!lines.atColumnZero()) {
cc.startNewLine(state); cc.startNewLine(state);
@ -615,83 +990,6 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
// last line is complete (i.e. if a following span should be on a new line). // last line is complete (i.e. if a following span should be on a new line).
// can be called at any point // can be called at any point
cc.getLines = () => lines.textLines(); cc.getLines = () => lines.textLines();
cc.finish = () => {
lines.flush();
const lineAttribs = lines.attribLines();
const lineStrings = cc.getLines();
lineStrings.length--;
lineAttribs.length--;
const ss = getSelectionStart();
const se = getSelectionEnd();
const fixLongLines = () => {
// design mode does not deal with with really long lines!
const lineLimit = 2000; // chars
const buffer = 10; // chars allowed over before wrapping
let linesWrapped = 0;
let numLinesAfter = 0;
for (let i = lineStrings.length - 1; i >= 0; i--) {
let oldString = lineStrings[i];
let oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit + buffer) {
const newStrings = [];
const newAttribStrings = [];
while (oldString.length > lineLimit) {
// var semiloc = oldString.lastIndexOf(';', lineLimit-1);
// var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
const lengthToTake = lineLimit;
newStrings.push(oldString.substring(0, lengthToTake));
oldString = oldString.substring(lengthToTake);
newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
}
if (oldString.length > 0) {
newStrings.push(oldString);
newAttribStrings.push(oldAttribString);
}
const fixLineNumber = (lineChar) => {
if (lineChar[0] < 0)
return;
let n = lineChar[0];
let c = lineChar[1];
if (n > i) {
n += (newStrings.length - 1);
}
else if (n === i) {
let a = 0;
while (c > newStrings[a].length) {
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
};
fixLineNumber(ss);
fixLineNumber(se);
linesWrapped++;
numLinesAfter += newStrings.length;
lineStrings.splice(i, 1, ...newStrings);
lineAttribs.splice(i, 1, ...newAttribStrings);
}
}
return {
linesWrapped,
numLinesAfter,
};
};
const wrapData = fixLongLines();
return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings,
lineAttribs,
};
};
return cc;
};
export { sanitizeUnicode }; export { sanitizeUnicode };
export { makeContentCollector }; export { makeContentCollector };
export { supportedElems }; export { supportedElems };

View file

@ -1,244 +0,0 @@
import * as Security from "./security.js";
import * as hooks from "./pluginfw/hooks.js";
import * as _ from "./underscore.js";
import { lineAttributeMarker as lineAttributeMarker$0 } from "./linestylefilter.js";
'use strict';
const lineAttributeMarker = { lineAttributeMarker: lineAttributeMarker$0 }.lineAttributeMarker;
const noop = () => { };
const domline = {};
domline.addToLineClass = (lineClass, cls) => {
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
cls.replace(/\S+/g, (c) => {
if (c.indexOf('line:') === 0) {
// add class to line
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
}
});
return lineClass;
};
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
const result = {
node: null,
appendSpan: noop,
prepareForAdd: noop,
notifyAdded: noop,
clearSpans: noop,
finishUpdate: noop,
lineMarker: 0,
};
const document = optDocument;
if (document) {
result.node = document.createElement('div');
// JAWS and NVDA screen reader compatibility. Only needed if in a real browser.
result.node.setAttribute('aria-live', 'assertive');
}
else {
result.node = {
innerHTML: '',
className: '',
};
}
let html = [];
let preHtml = '';
let postHtml = '';
let curHTML = null;
const processSpaces = (s) => domline.processSpaces(s, doesWrap);
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line';
result.appendSpan = (txt, cls) => {
let processedMarker = false;
// Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0) {
let listType = /(?:^| )list:(\S+)/.exec(cls);
const start = /(?:^| )start:(\S+)/.exec(cls);
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (listType) {
listType = listType[1];
if (listType) {
if (listType.indexOf('number') < 0) {
preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
postHtml = `</li></ul>${postHtml}`;
}
else {
if (start) { // is it a start of a list with more than one item in?
if (Number.parseInt(start[1]) === 1) { // if its the first one at this level?
// Add start class to DIV node
lineClass = `${lineClass} ` + `list-start-${listType}`;
}
preHtml +=
`<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
else {
// Handles pasted contents into existing lists
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
postHtml += '</li></ol>';
}
}
processedMarker = true;
}
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (processedMarker) {
result.lineMarker += txt.length;
return; // don't append any text
}
}
let href = null;
let simpleTags = null;
if (cls.indexOf('url') >= 0) {
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
href = url;
return `${space}url`;
});
}
if (cls.indexOf('tag') >= 0) {
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
if (!simpleTags)
simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
let extraOpenTags = '';
let extraCloseTags = '';
_.map(hooks.callAll('aceCreateDomLine', {
domline,
cls,
}), (modifier) => {
cls = modifier.cls;
extraOpenTags += modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls) {
lineClass = domline.addToLineClass(lineClass, cls);
}
else if (txt) {
if (href) {
const urn_schemes = new RegExp('^(about|geo|mailto|tel):');
// if the url doesn't include a protocol prefix, assume http
if (!~href.indexOf('://') && !urn_schemes.test(href)) {
href = `http://${href}`;
}
// Using rel="noreferrer" stops leaking the URL/location of the pad when
// clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636
const escapedHref = Security.escapeHTMLAttribute(href);
extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
extraCloseTags = `</a>${extraCloseTags}`;
}
if (simpleTags) {
simpleTags.sort();
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
simpleTags.reverse();
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
}
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
};
result.clearSpans = () => {
html = [];
lineClass = 'ace-line';
result.lineMarker = 0;
};
const writeHTML = () => {
let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) {
if ((!document) || (!optBrowser)) {
newHTML += '&nbsp;';
}
else {
newHTML += '<br/>';
}
}
if (nonEmpty) {
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = ''; // free memory
if (newHTML !== curHTML) {
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass != null)
result.node.className = lineClass;
hooks.callAll('acePostWriteDomLineHTML', {
node: result.node,
});
};
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
return result;
};
domline.processSpaces = (s, doesWrap) => {
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
return s.replace(/ /g, '&nbsp;');
}
const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m);
});
if (doesWrap) {
let endOfLine = true;
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (let i = parts.length - 1; i >= 0; i--) {
const p = parts[i];
if (p === ' ') {
if (endOfLine || beforeSpace)
parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) !== '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) !== '<') {
break;
}
}
}
else {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
};
export { domline };

408
src/static/js/domline.ts Normal file
View file

@ -0,0 +1,408 @@
import * as Security from "./security.js";
import * as hooks from "./pluginfw/hooks.js";
import * as _ from "underscore";
import { lineAttributeMarker as lineAttributeMarker$0 } from "./linestylefilter.js";
'use strict';
const lineAttributeMarker = { lineAttributeMarker: lineAttributeMarker$0 }.lineAttributeMarker;
const noop = () => { };
const domline = {
processSpaces: (s, doesWrap) => {
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
return s.replace(/ /g, '&nbsp;');
}
const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m);
});
if (doesWrap) {
let endOfLine = true;
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (let i = parts.length - 1; i >= 0; i--) {
const p = parts[i];
if (p === ' ') {
if (endOfLine || beforeSpace)
parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) !== '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) !== '<') {
break;
}
}
}
else {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
},
addToLineClass: (lineClass, cls) => {
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
cls.replace(/\S+/g, (c) => {
if (c.indexOf('line:') === 0) {
// add class to line
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
}
});
return lineClass;
},
createDomLine: (nonEmpty, doesWrap, optBrowser?, optDocument?) => {
const result = {
node: null,
prepareForAdd: noop,
notifyAdded: noop,
finishUpdate: noop,
lineMarker: 0,
clearSpans: () => {
html = [];
lineClass = 'ace-line';
result.lineMarker = 0;
},
appendSpan: (txt, cls) => {
let processedMarker:any = false;
// Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0) {
let listType:any = /(?:^| )list:(\S+)/.exec(cls);
const start = /(?:^| )start:(\S+)/.exec(cls);
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (listType) {
listType = listType[1];
if (listType) {
if (listType.indexOf('number') < 0) {
preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
postHtml = `</li></ul>${postHtml}`;
} else {
if (start) { // is it a start of a list with more than one item in?
if (Number.parseInt(start[1]) === 1) { // if its the first one at this level?
// Add start class to DIV node
lineClass = `${lineClass} ` + `list-start-${listType}`;
}
preHtml +=
`<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
} else {
// Handles pasted contents into existing lists
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
postHtml += '</li></ol>';
}
}
processedMarker = true;
}
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (processedMarker) {
result.lineMarker += txt.length;
return; // don't append any text
}
}
let href = null;
let simpleTags = null;
if (cls.indexOf('url') >= 0) {
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
href = url;
return `${space}url`;
});
}
if (cls.indexOf('tag') >= 0) {
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
if (!simpleTags)
simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
let extraOpenTags = '';
let extraCloseTags = '';
_.map(hooks.callAll('aceCreateDomLine', {
domline,
cls,
}), (modifier) => {
cls = modifier.cls;
extraOpenTags += modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls) {
lineClass = domline.addToLineClass(lineClass, cls);
} else if (txt) {
if (href) {
const urn_schemes = new RegExp('^(about|geo|mailto|tel):');
// if the url doesn't include a protocol prefix, assume http
if (!~href.indexOf('://') && !urn_schemes.test(href)) {
href = `http://${href}`;
}
// Using rel="noreferrer" stops leaking the URL/location of the pad when
// clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636
const escapedHref = Security.escapeHTMLAttribute(href);
extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
extraCloseTags = `</a>${extraCloseTags}`;
}
if (simpleTags) {
simpleTags.sort();
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
simpleTags.reverse();
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
}
(html as any[]).push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags,
perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
}
}
const document = optDocument;
if (document) {
result.node = document.createElement('div');
// JAWS and NVDA screen reader compatibility. Only needed if in a real browser.
result.node.setAttribute('aria-live', 'assertive');
}
else {
result.node = {
innerHTML: '',
className: '',
};
}
let html:any[]|string = [];
let preHtml = '';
let postHtml = '';
let curHTML = null;
const processSpaces = (s) => domline.processSpaces(s, doesWrap);
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line';
const writeHTML = () => {
let newHTML = perHtmlLineProcess((html as any[]).join(''));
if (!newHTML) {
if ((!document) || (!optBrowser)) {
newHTML += '&nbsp;';
}
else {
newHTML += '<br/>';
}
}
if (nonEmpty) {
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = ''; // free memory
if (newHTML !== curHTML) {
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass != null)
result.node.className = lineClass;
hooks.callAll('acePostWriteDomLineHTML', {
node: result.node,
});
};
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
return result;
}
}
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
const result = {
node: null,
prepareForAdd: noop,
notifyAdded: noop,
clearSpans: noop,
finishUpdate: noop,
lineMarker: 0,
appendSpan: (txt, cls) => {
let processedMarker:any = false;
// Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0) {
let listType:any = /(?:^| )list:(\S+)/.exec(cls);
const start = /(?:^| )start:(\S+)/.exec(cls);
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (listType) {
listType = listType[1];
if (listType) {
if (listType.indexOf('number') < 0) {
preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
postHtml = `</li></ul>${postHtml}`;
}
else {
if (start) { // is it a start of a list with more than one item in?
if (Number.parseInt(start[1]) === 1) { // if its the first one at this level?
// Add start class to DIV node
lineClass = `${lineClass} ` + `list-start-${listType}`;
}
preHtml +=
`<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
else {
// Handles pasted contents into existing lists
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
postHtml += '</li></ol>';
}
}
processedMarker = true;
}
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (processedMarker) {
result.lineMarker += txt.length;
return; // don't append any text
}
}
let href = null;
let simpleTags = null;
if (cls.indexOf('url') >= 0) {
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
href = url;
return `${space}url`;
});
}
if (cls.indexOf('tag') >= 0) {
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
if (!simpleTags)
simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
let extraOpenTags = '';
let extraCloseTags = '';
_.map(hooks.callAll('aceCreateDomLine', {
domline,
cls,
}), (modifier) => {
cls = modifier.cls;
extraOpenTags += modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls) {
lineClass = domline.addToLineClass(lineClass, cls);
}
else if (txt) {
if (href) {
const urn_schemes = new RegExp('^(about|geo|mailto|tel):');
// if the url doesn't include a protocol prefix, assume http
if (!~href.indexOf('://') && !urn_schemes.test(href)) {
href = `http://${href}`;
}
// Using rel="noreferrer" stops leaking the URL/location of the pad when
// clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636
const escapedHref = Security.escapeHTMLAttribute(href);
extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
extraCloseTags = `</a>${extraCloseTags}`;
}
if (simpleTags) {
simpleTags.sort();
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
simpleTags.reverse();
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
}
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
}
};
const document = optDocument;
if (document) {
result.node = document.createElement('div');
// JAWS and NVDA screen reader compatibility. Only needed if in a real browser.
result.node.setAttribute('aria-live', 'assertive');
}
else {
result.node = {
innerHTML: '',
className: '',
};
}
let html:any|any[] = [];
let preHtml = '';
let postHtml = '';
let curHTML = null;
const processSpaces = (s) => domline.processSpaces(s, doesWrap);
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line';
const writeHTML = () => {
let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) {
if ((!document) || (!optBrowser)) {
newHTML += '&nbsp;';
}
else {
newHTML += '<br/>';
}
}
if (nonEmpty) {
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = ''; // free memory
if (newHTML !== curHTML) {
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass != null)
result.node.className = lineClass;
hooks.callAll('acePostWriteDomLineHTML', {
node: result.node,
});
};
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
return result;
};
export { domline };

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
/* eslint-disable-next-line max-len */ /* eslint-disable-next-line max-len */
// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0 // @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0
/** /**
* Copyright 2011 Peter Martischka, Primary Technology. * Copyright 2011 Peter Martischka, Primary Technology.
* Copyright 2020 Richard Hansen * Copyright 2020 Richard Hansen
@ -17,6 +18,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
// FIXME Why should this variable be a rename?
// @ts-ignore
// @ts-ignore
const randomPadName = () => { const randomPadName = () => {
// the number of distinct chars (64) is chosen to ensure that the selection will be uniform when // the number of distinct chars (64) is chosen to ensure that the selection will be uniform when
// using the PRNG below // using the PRNG below
@ -26,7 +31,7 @@ const randomPadName = () => {
// make room for 8-bit integer values that span from 0 to 255. // make room for 8-bit integer values that span from 0 to 255.
const randomarray = new Uint8Array(stringLength); const randomarray = new Uint8Array(stringLength);
// use browser's PRNG to generate a "unique" sequence // use browser's PRNG to generate a "unique" sequence
const cryptoObj = window.crypto || window.msCrypto; // for IE 11 const cryptoObj = window.crypto; // for IE 11
cryptoObj.getRandomValues(randomarray); cryptoObj.getRandomValues(randomarray);
let randomstring = ''; let randomstring = '';
for (let i = 0; i < stringLength; i++) { for (let i = 0; i < stringLength; i++) {
@ -40,8 +45,8 @@ const randomPadName = () => {
$(() => { $(() => {
$('#go2Name').submit(() => { $('#go2Name').submit(() => {
const padname = $('#padname').val(); const padname = $('#padname').val();
if (padname.length > 0) { if (typeof padname === "string" && padname.length > 0) {
window.location = `p/${encodeURIComponent(padname.trim())}`; window.location.href = `p/${encodeURIComponent(padname.trim())}`;
} }
else { else {
alert('Please enter a name'); alert('Please enter a name');
@ -49,9 +54,9 @@ $(() => {
return false; return false;
}); });
$('#button').click(() => { $('#button').click(() => {
window.location = `p/${randomPadName()}`; window.location.href = `p/${randomPadName()}`;
}); });
// start the custom js // start the custom js
if (typeof window.customStart === 'function') if ("customStart" in window && typeof window.customStart === 'function')
window.customStart(); window.customStart();
}); });

View file

@ -1,14 +0,0 @@
'use strict';
((document) => {
// Set language for l10n
let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
if (language)
language = language[1];
html10n.bind('indexed', () => {
html10n.localize([language, navigator.language, navigator.userLanguage, 'en']);
});
html10n.bind('localized', () => {
document.documentElement.lang = html10n.getLanguage();
document.documentElement.dir = html10n.getDirection();
});
})(document);

View file

@ -4,23 +4,24 @@ import * as hooks from "./pluginfw/hooks.js";
import AttributeManager from "./AttributeManager.js"; import AttributeManager from "./AttributeManager.js";
import { padutils as padutils$0 } from "./pad_utils.js"; import { padutils as padutils$0 } from "./pad_utils.js";
'use strict'; 'use strict';
const linestylefilter = {};
const padutils = { padutils: padutils$0 }.padutils; const padutils = { padutils: padutils$0 }.padutils;
linestylefilter.ATTRIB_CLASSES = {
bold: 'tag:b', // @ts-ignore
italic: 'tag:i', const linestylefilter = {
underline: 'tag:u', ATTRIB_CLASSES: {
strikethrough: 'tag:s', bold: 'tag:b',
}; italic: 'tag:i',
const lineAttributeMarker = 'lineAttribMarker'; underline: 'tag:u',
linestylefilter.getAuthorClassName = (author) => `author-${author.replace(/[^a-y0-9]/g, (c) => { strikethrough: 'tag:s',
if (c === '.') },
return '-'; getAuthorClassName: (author) => `author-${author.replace(/[^a-y0-9]/g, (c) => {
return `z${c.charCodeAt(0)}z`; if (c === '.')
})}`; return '-';
// lineLength is without newline; aline includes newline, return `z${c.charCodeAt(0)}z`;
// but may be falsy if lineLength == 0 })}`,
linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool) => { getLineStyleFilter: (lineLength, aline, textAndClassFunc, apool) => {
// Plugin Hook to add more Attrib Classes // Plugin Hook to add more Attrib Classes
for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) { for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) {
Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses); Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses);
@ -115,126 +116,132 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
}; };
})(); })();
return authorColorFunc; return authorColorFunc;
}; },
linestylefilter.getAtSignSplitterFilter = (lineText, textAndClassFunc) => { getAtSignSplitterFilter: (lineText, textAndClassFunc) => {
const at = /@/g; const at = /@/g;
at.lastIndex = 0; at.lastIndex = 0;
let splitPoints = null; let splitPoints = null;
let execResult; let execResult;
while ((execResult = at.exec(lineText))) { while ((execResult = at.exec(lineText))) {
if (!splitPoints) { if (!splitPoints) {
splitPoints = []; splitPoints = [];
}
splitPoints.push(execResult.index);
}
if (!splitPoints)
return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = (regExp, tag) => (lineText, textAndClassFunc) => {
regExp.lastIndex = 0;
let regExpMatchs = null;
let splitPoints = null;
let execResult;
while ((execResult = regExp.exec(lineText))) {
if (!regExpMatchs) {
regExpMatchs = [];
splitPoints = [];
}
const startIndex = execResult.index;
const regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
if (!regExpMatchs)
return textAndClassFunc;
const regExpMatchForIndex = (idx) => {
for (let k = 0; k < regExpMatchs.length; k++) {
const u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length) {
return u[1];
} }
splitPoints.push(execResult.index);
} }
return false; if (!splitPoints)
}; return textAndClassFunc;
const handleRegExpMatchsAfterSplit = (() => { return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
let curIndex = 0; },
return (txt, cls) => { getRegexpFilter: (regExp, tag) => (lineText, textAndClassFunc) => {
const txtlen = txt.length; regExp.lastIndex = 0;
let newCls = cls; let regExpMatchs = null;
const regExpMatch = regExpMatchForIndex(curIndex); let splitPoints = null;
if (regExpMatch) { let execResult;
newCls += ` ${tag}:${regExpMatch}`; while ((execResult = regExp.exec(lineText))) {
if (!regExpMatchs) {
regExpMatchs = [];
splitPoints = [];
} }
textAndClassFunc(txt, newCls); const startIndex = execResult.index;
curIndex += txtlen; const regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
if (!regExpMatchs)
return textAndClassFunc;
const regExpMatchForIndex = (idx) => {
for (let k = 0; k < regExpMatchs.length; k++) {
const u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length) {
return u[1];
}
}
return false;
}; };
})(); const handleRegExpMatchsAfterSplit = (() => {
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints); let curIndex = 0;
}; return (txt, cls) => {
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(padutils.urlRegex, 'url'); const txtlen = txt.length;
linestylefilter.textAndClassFuncSplitter = (func, splitPointsOpt) => { let newCls = cls;
let nextPointIndex = 0; const regExpMatch = regExpMatchForIndex(curIndex);
let idx = 0; if (regExpMatch) {
// don't split at 0 newCls += ` ${tag}:${regExpMatch}`;
while (splitPointsOpt && }
textAndClassFunc(txt, newCls);
curIndex += txtlen;
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
},
getURLFilter: ()=>{
return linestylefilter.getRegexpFilter(padutils.urlRegex, 'url')
},
textAndClassFuncSplitter: (func, splitPointsOpt) => {
let nextPointIndex = 0;
let idx = 0;
// don't split at 0
while (splitPointsOpt &&
nextPointIndex < splitPointsOpt.length && nextPointIndex < splitPointsOpt.length &&
splitPointsOpt[nextPointIndex] === 0) { splitPointsOpt[nextPointIndex] === 0) {
nextPointIndex++; nextPointIndex++;
}
const spanHandler = (txt, cls) => {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
func(txt, cls);
idx += txt.length;
} }
else { const spanHandler = (txt, cls) => {
const splitPoints = splitPointsOpt; if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
const pointLocInSpan = splitPoints[nextPointIndex] - idx;
const txtlen = txt.length;
if (pointLocInSpan >= txtlen) {
func(txt, cls); func(txt, cls);
idx += txt.length; idx += txt.length;
if (pointLocInSpan === txtlen) {
nextPointIndex++;
}
} }
else { else {
if (pointLocInSpan > 0) { const splitPoints = splitPointsOpt;
func(txt.substring(0, pointLocInSpan), cls); const pointLocInSpan = splitPoints[nextPointIndex] - idx;
idx += pointLocInSpan; const txtlen = txt.length;
if (pointLocInSpan >= txtlen) {
func(txt, cls);
idx += txt.length;
if (pointLocInSpan === txtlen) {
nextPointIndex++;
}
}
else {
if (pointLocInSpan > 0) {
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
} }
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
} }
};
return spanHandler;
},
getFilterStack: (lineText, textAndClassFunc, abrowser?) => {
let func = linestylefilter.getURLFilter();
const hookFilters = hooks.callAll('aceGetFilterStack', {
linestylefilter,
browser: abrowser,
});
hookFilters.map((hookFilter) => {
func = hookFilter(lineText, func);
});
return func;
},
populateDomLine: (textLine, aline, apool, domLineObj) => {
// remove final newline from text if any
let text = textLine;
if (text.slice(-1) === '\n') {
text = text.substring(0, text.length - 1);
} }
}; const textAndClassFunc = (tokenText, tokenClass) => {
return spanHandler; domLineObj.appendSpan(tokenText, tokenClass);
}; };
linestylefilter.getFilterStack = (lineText, textAndClassFunc, abrowser) => { let func = linestylefilter.getFilterStack(text, textAndClassFunc);
let func = linestylefilter.getURLFilter(lineText, textAndClassFunc); func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
const hookFilters = hooks.callAll('aceGetFilterStack', { func(text, '');
linestylefilter,
browser: abrowser,
});
hookFilters.map((hookFilter) => {
func = hookFilter(lineText, func);
});
return func;
};
// domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = (textLine, aline, apool, domLineObj) => {
// remove final newline from text if any
let text = textLine;
if (text.slice(-1) === '\n') {
text = text.substring(0, text.length - 1);
} }
const textAndClassFunc = (tokenText, tokenClass) => {
domLineObj.appendSpan(tokenText, tokenClass);
};
let func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
}; };
const lineAttributeMarker = 'lineAttribMarker';
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
// domLineObj is like that returned by domline.createDomLine
export { lineAttributeMarker }; export { lineAttributeMarker };
export { linestylefilter }; export { linestylefilter };

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//FIXME this file needs some insights
import "./vendors/jquery.js"; import "./vendors/jquery.js";
import "./vendors/farbtastic.js"; import "./vendors/farbtastic.js";
import "./vendors/gritter.js"; import "./vendors/gritter.js";
@ -15,6 +17,8 @@ import { paduserlist as paduserlist$0 } from "./pad_userlist.js";
import { colorutils as colorutils$0 } from "./colorutils.js"; import { colorutils as colorutils$0 } from "./colorutils.js";
import * as socketio from "./socketio.js"; import * as socketio from "./socketio.js";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {i18nextvar} from "./vendors/i18next";
'use strict'; 'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
@ -139,7 +143,7 @@ const getParameters = [
name: 'lang', name: 'lang',
checkVal: null, checkVal: null,
callback: (val) => { callback: (val) => {
window.html10n.localize([val, 'en']); i18nextvar([val, 'en']);
Cookies.set('language', val); Cookies.set('language', val);
}, },
}, },
@ -347,6 +351,7 @@ const pad = {
padOptions: {}, padOptions: {},
_messageQ: new MessageQueue(), _messageQ: new MessageQueue(),
// these don't require init; clientVars should all go through here // these don't require init; clientVars should all go through here
settings: undefined,
getPadId: () => clientVars.padId, getPadId: () => clientVars.padId,
getClientIp: () => clientVars.clientIp, getClientIp: () => clientVars.clientIp,
getColorPalette: () => clientVars.colorPalette, getColorPalette: () => clientVars.colorPalette,
@ -705,7 +710,8 @@ const settings = {
useMonospaceFontGlobal: false, useMonospaceFontGlobal: false,
globalUserName: false, globalUserName: false,
globalUserColor: false, globalUserColor: false,
rtlIsTrue: false, rtlIsTrue: false, hideChat: false
}; };
pad.settings = settings; pad.settings = settings;
export const baseURL = ''; export const baseURL = '';

View file

@ -1,4 +1,7 @@
'use strict'; 'use strict';
import {i18nextvar} from "./vendors/i18next";
import {clientVars} from "../../node/handler/PadMessageHandler";
const createCountDownElementsIfNecessary = ($modal) => { const createCountDownElementsIfNecessary = ($modal) => {
const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0; const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
if (elementsDoNotExist) { if (elementsDoNotExist) {
@ -24,7 +27,7 @@ const createCountDownElementsIfNecessary = ($modal) => {
} }
}; };
const localize = ($element) => { const localize = ($element) => {
html10n.translateElement(html10n.translations, $element.get(0)); i18nextvar($element.get(0));
}; };
const createTimerForModal = ($modal, pad) => { const createTimerForModal = ($modal, pad) => {
const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry(); const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
@ -87,7 +90,7 @@ const reconnectionTries = {
// duration: how many **seconds** until the timer ends // duration: how many **seconds** until the timer ends
// granularity (optional): how many **milliseconds** // granularity (optional): how many **milliseconds**
// between each 'tick' of timer. Default: 1000ms (1s) // between each 'tick' of timer. Default: 1000ms (1s)
const CountDownTimer = function (duration, granularity) { const CountDownTimer = function (duration, granularity?) {
this.duration = duration; this.duration = duration;
this.granularity = granularity || 1000; this.granularity = granularity || 1000;
this.running = false; this.running = false;

View file

@ -20,10 +20,17 @@ import { padmodals as padmodals$0 } from "./pad_modals.js";
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
type Pad_connectionStatus = {
what: string,
why?: string
}
const padmodals = { padmodals: padmodals$0 }.padmodals; const padmodals = { padmodals: padmodals$0 }.padmodals;
const padconnectionstatus = (() => { const padconnectionstatus = (() => {
let status = { let status:Pad_connectionStatus = {
what: 'connecting', what: 'connecting',
why: undefined
}; };
const self = { const self = {
init: () => { init: () => {

View file

@ -1,4 +1,6 @@
import * as padUtils from "./pad_utils.js"; import * as padUtils from "./pad_utils.js";
import {JQueryGritter} from "../module/CustomWindow";
import {i18nextvar} from "./vendors/i18next";
'use strict'; 'use strict';
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
@ -17,6 +19,7 @@ import * as padUtils from "./pad_utils.js";
*/ */
const Cookies = { Cookies: padUtils }.Cookies; const Cookies = { Cookies: padUtils }.Cookies;
export const padcookie = new class { export const padcookie = new class {
cookieName_: string;
constructor() { constructor() {
this.cookieName_ = window.location.protocol === 'https:' ? 'prefs' : 'prefsHttp'; this.cookieName_ = window.location.protocol === 'https:' ? 'prefs' : 'prefsHttp';
} }
@ -28,9 +31,9 @@ export const padcookie = new class {
this.writePrefs_(prefs); this.writePrefs_(prefs);
// Re-read the saved cookie to test if cookies are enabled. // Re-read the saved cookie to test if cookies are enabled.
if (this.readPrefs_() == null) { if (this.readPrefs_() == null) {
$.gritter.add({ ($ as unknown as JQueryGritter).gritter.add({
title: 'Error', title: 'Error',
text: html10n.get('pad.noCookie'), text: i18nextvar('pad.noCookie'),
sticky: true, sticky: true,
class_name: 'error', class_name: 'error',
}); });
@ -38,6 +41,7 @@ export const padcookie = new class {
} }
readPrefs_() { readPrefs_() {
try { try {
// @ts-ignore
const json = Cookies.get(this.cookieName_); const json = Cookies.get(this.cookieName_);
if (json == null) if (json == null)
return null; return null;
@ -48,6 +52,7 @@ export const padcookie = new class {
} }
} }
writePrefs_(prefs) { writePrefs_(prefs) {
// @ts-ignore
Cookies.set(this.cookieName_, JSON.stringify(prefs), { expires: 365 * 100 }); Cookies.set(this.cookieName_, JSON.stringify(prefs), { expires: 365 * 100 });
} }
getPref(prefName) { getPref(prefName) {

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//FIXME lots of errors
import browser from "./vendors/browser.js"; import browser from "./vendors/browser.js";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import { padutils as padutils$0 } from "./pad_utils.js"; import { padutils as padutils$0 } from "./pad_utils.js";

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//Lots of methods are set onto padutils
import * as padUtils from "./pad_utils.js"; import * as padUtils from "./pad_utils.js";
import { padcookie as padcookie$0 } from "./pad_cookie.js"; import { padcookie as padcookie$0 } from "./pad_cookie.js";
'use strict'; 'use strict';

View file

@ -4,6 +4,10 @@
* This helps other people to understand this code better and helps them to improve it. * This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/ */
import {i18nextvar} from "./vendors/i18next";
import {AjaxDirectDatabaseAccess} from "../module/CustomWindow";
import {clientVars} from "../../node/handler/PadMessageHandler";
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -39,14 +43,14 @@ const padimpexp = (() => {
const fileInputSubmit = function (e) { const fileInputSubmit = function (e) {
e.preventDefault(); e.preventDefault();
$('#importmessagefail').fadeOut('fast'); $('#importmessagefail').fadeOut('fast');
if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) if (!window.confirm(i18nextvar('pad.impexp.confirmimport')))
return; return;
$('#importsubmitinput').attr({ disabled: true }).val(html10n.get('pad.impexp.importing')); $('#importsubmitinput').attr({ disabled: true }).val(i18nextvar('pad.impexp.importing'));
window.setTimeout(() => $('#importfileinput').attr({ disabled: true }), 0); window.setTimeout(() => $('#importfileinput').attr({ disabled: true }), 0);
$('#importarrow').stop(true, true).hide(); $('#importarrow').stop(true, true).hide();
$('#importstatusball').show(); $('#importstatusball').show();
(async () => { (async () => {
const { code, message, data: { directDatabaseAccess } = {} } = await $.ajax({ const { code, message, data: { directDatabaseAccess } = {} }:AjaxDirectDatabaseAccess = await $.ajax({
url: `${window.location.href.split('?')[0].split('#')[0]}/import`, url: `${window.location.href.split('?')[0].split('#')[0]}/import`,
method: 'POST', method: 'POST',
data: new FormData(this), data: new FormData(this),
@ -67,7 +71,7 @@ const padimpexp = (() => {
if (directDatabaseAccess) if (directDatabaseAccess)
window.location.reload(); window.location.reload();
} }
$('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton')); $('#importsubmitinput').removeAttr('disabled').val(i18nextvar('pad.impexp.importbutton'));
window.setTimeout(() => $('#importfileinput').removeAttr('disabled'), 0); window.setTimeout(() => $('#importfileinput').removeAttr('disabled'), 0);
$('#importstatusball').hide(); $('#importstatusball').hide();
addImportFrames(); addImportFrames();
@ -81,12 +85,12 @@ const padimpexp = (() => {
'maxFileSize', 'maxFileSize',
'permission', 'permission',
]; ];
const msg = html10n.get(`pad.impexp.${known.indexOf(status) !== -1 ? status : 'copypaste'}`); const msg = i18nextvar(`pad.impexp.${known.indexOf(status) !== -1 ? status : 'copypaste'}`);
const showError = (fade) => { const showError = (fade?) => {
const popup = $('#importmessagefail').empty() const popup = $('#importmessagefail').empty()
.append($('<strong>') .append($('<strong>')
.css('color', 'red') .css('color', 'red')
.text(`${html10n.get('pad.impexp.importfailed')}: `)) .text(`${i18nextvar('pad.impexp.importfailed')}: `))
.append(document.createTextNode(msg)); .append(document.createTextNode(msg));
popup[(fade ? 'fadeIn' : 'show')](); popup[(fade ? 'fadeIn' : 'show')]();
}; };
@ -100,7 +104,7 @@ const padimpexp = (() => {
}; };
// /// export // /// export
function cantExport() { function cantExport() {
let type = $(this); let type: JQuery<string>|string = $(this);
if (type.hasClass('exporthrefpdf')) { if (type.hasClass('exporthrefpdf')) {
type = 'PDF'; type = 'PDF';
} }
@ -113,7 +117,7 @@ const padimpexp = (() => {
else { else {
type = 'this file'; type = 'this file';
} }
alert(html10n.get('pad.impexp.exportdisabled', { type })); alert(i18nextvar('pad.impexp.exportdisabled', { type }));
return false; return false;
} }
// /// // ///
@ -124,9 +128,9 @@ const padimpexp = (() => {
// if /p/ isn't available due to a rewrite we use the clientVars padId // if /p/ isn't available due to a rewrite we use the clientVars padId
const padRootPath = /.*\/p\/[^/]+/.exec(document.location.pathname) || clientVars.padId; const padRootPath = /.*\/p\/[^/]+/.exec(document.location.pathname) || clientVars.padId;
// i10l buttom import // i10l buttom import
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton')); $('#importsubmitinput').val(i18nextvar('pad.impexp.importbutton'));
html10n.bind('localized', () => { i18nextvar.bind('localized', () => {
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton')); $('#importsubmitinput').val(i18nextvar('pad.impexp.importbutton'));
}); });
// build the export links // build the export links
$('#exporthtmla').attr('href', `${padRootPath}/export/html`); $('#exporthtmla').attr('href', `${padRootPath}/export/html`);

View file

@ -1,4 +1,7 @@
'use strict'; 'use strict';
import {JQueryGritter} from "../module/CustomWindow";
import {i18nextvar} from "./vendors/i18next";
/** /**
* Copyright 2012 Peter 'Pita' Martischka * Copyright 2012 Peter 'Pita' Martischka
* *
@ -17,11 +20,11 @@
let pad; let pad;
export const saveNow = () => { export const saveNow = () => {
pad.collabClient.sendMessage({ type: 'SAVE_REVISION' }); pad.collabClient.sendMessage({ type: 'SAVE_REVISION' });
$.gritter.add({ ($ as unknown as JQueryGritter).gritter.add({
// (string | mandatory) the heading of the notification // (string | mandatory) the heading of the notification
title: html10n.get('pad.savedrevs.marked'), title: i18nextvar('pad.savedrevs.marked'),
// (string | mandatory) the text inside the notification // (string | mandatory) the text inside the notification
text: html10n.get('pad.savedrevs.timeslider') || text: i18nextvar('pad.savedrevs.timeslider') ||
'You can view saved revisions in the timeslider', 'You can view saved revisions in the timeslider',
// (bool | optional) if you want it to fade out on its own or just sit there // (bool | optional) if you want it to fade out on its own or just sit there
sticky: false, sticky: false,

View file

@ -1,5 +1,9 @@
import { padutils as padutils$0 } from "./pad_utils.js"; import { padutils as padutils$0 } from "./pad_utils.js";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";
import {i18nextvar} from "./vendors/i18next";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {HistoricalAuthorData, JQueryGritter} from "../module/CustomWindow";
import {pad} from "./pad";
'use strict'; 'use strict';
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
@ -17,7 +21,12 @@ import * as hooks from "./pluginfw/hooks.js";
* limitations under the License. * limitations under the License.
*/ */
const padutils = { padutils: padutils$0 }.padutils; const padutils = { padutils: padutils$0 }.padutils;
let myUserInfo = {}; let myUserInfo = {
name: undefined, userId: undefined,
colorId: undefined
};
let colorPickerOpen = false; let colorPickerOpen = false;
let colorPickerSetup = false; let colorPickerSetup = false;
const paduserlist = (() => { const paduserlist = (() => {
@ -83,7 +92,7 @@ const paduserlist = (() => {
handleOtherUserInputs(); handleOtherUserInputs();
return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
}; };
const getAnimationHeight = (step, power) => { const getAnimationHeight = (step, power?) => {
let a = Math.abs(step / 12); let a = Math.abs(step / 12);
if (power === 2) if (power === 2)
a **= 2; a **= 2;
@ -138,7 +147,7 @@ const paduserlist = (() => {
.attr('type', 'text') .attr('type', 'text')
.addClass('editempty') .addClass('editempty')
.addClass('newinput') .addClass('newinput')
.attr('value', html10n.get('pad.userlist.unnamed')); .attr('value', i18nextvar('pad.userlist.unnamed'));
if (isNameEditable(data)) if (isNameEditable(data))
name.attr('disabled', 'disabled'); name.attr('disabled', 'disabled');
} }
@ -148,7 +157,7 @@ const paduserlist = (() => {
.addClass('usertdswatch') .addClass('usertdswatch')
.append($('<div>') .append($('<div>')
.addClass('swatch') .addClass('swatch')
.css('background', padutils.escapeHtml(data.color)) .css('background', padutils.escapeHtml(data.color) as unknown as string)
.html('&nbsp;'))) .html('&nbsp;')))
.add($('<td>') .add($('<td>')
.css('height', `${height}px`) .css('height', `${height}px`)
@ -187,7 +196,7 @@ const paduserlist = (() => {
}).removeClass('newinput'); }).removeClass('newinput');
}; };
// animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc. // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc.
const insertRow = (position, data, animationPower) => { const insertRow = (position, data, animationPower?) => {
position = Math.max(0, Math.min(rowsPresent.length, position)); position = Math.max(0, Math.min(rowsPresent.length, position));
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
const domId = nextRowId(); const domId = nextRowId();
@ -237,7 +246,7 @@ const paduserlist = (() => {
} }
} }
}; };
const removeRow = (position, animationPower) => { const removeRow = (position, animationPower?) => {
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
const row = rowsPresent[position]; const row = rowsPresent[position];
if (row) { if (row) {
@ -257,7 +266,7 @@ const paduserlist = (() => {
} }
}; };
// newPosition is position after the row has been removed // newPosition is position after the row has been removed
const moveRow = (oldPosition, newPosition, animationPower) => { const moveRow = (oldPosition, newPosition, animationPower?) => {
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
const row = rowsPresent[oldPosition]; const row = rowsPresent[oldPosition];
if (row && oldPosition !== newPosition) { if (row && oldPosition !== newPosition) {
@ -288,7 +297,7 @@ const paduserlist = (() => {
}, (newName) => { }, (newName) => {
if (!newName) { if (!newName) {
jnode.addClass('editempty'); jnode.addClass('editempty');
jnode.val(html10n.get('pad.userlist.unnamed')); jnode.val(i18nextvar('pad.userlist.unnamed'));
} }
else { else {
jnode.attr('disabled', 'disabled'); jnode.attr('disabled', 'disabled');
@ -372,11 +381,12 @@ const paduserlist = (() => {
const userList = self.usersOnline(); const userList = self.usersOnline();
// Now we add historical authors // Now we add historical authors
const historical = clientVars.collab_client_vars.historicalAuthorData; const historical = clientVars.collab_client_vars.historicalAuthorData;
for (const [key, { userId }] of Object.entries(historical)) { for (const [key, res] of Object.entries(historical)) {
const resMapped = res as HistoricalAuthorData
// Check we don't already have this author in our array // Check we don't already have this author in our array
let exists = false; let exists = false;
userList.forEach((user) => { userList.forEach((user) => {
if (user.userId === userId) if (user.userId === resMapped.userId)
exists = true; exists = true;
}); });
if (exists === false) { if (exists === false) {
@ -401,18 +411,20 @@ const paduserlist = (() => {
hooks.callAll('userJoinOrUpdate', { hooks.callAll('userJoinOrUpdate', {
userInfo: info, userInfo: info,
}); });
const userData = {}; const userData = {
userData.color = typeof info.colorId === 'number' status: '',
? clientVars.colorPalette[info.colorId] : info.colorId; name: info.name,
userData.name = info.name; activity: '',
userData.status = ''; id: info.userId,
userData.activity = ''; color: typeof info.colorId === 'number'
userData.id = info.userId; ? clientVars.colorPalette[info.colorId] : info.colorId
};
const existingIndex = findExistingIndex(info.userId); const existingIndex = findExistingIndex(info.userId);
let numUsersBesides = otherUsersInfo.length; let numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) { if (existingIndex >= 0) {
numUsersBesides--; numUsersBesides--;
} }
//@ts-ignore
const newIndex = padutils.binarySearch(numUsersBesides, (n) => { const newIndex = padutils.binarySearch(numUsersBesides, (n) => {
if (existingIndex >= 0 && n >= existingIndex) { if (existingIndex >= 0 && n >= existingIndex) {
// pretend existingIndex isn't there // pretend existingIndex isn't there
@ -495,7 +507,7 @@ const paduserlist = (() => {
$('#myusernameedit').removeClass('editempty').val(myUserInfo.name); $('#myusernameedit').removeClass('editempty').val(myUserInfo.name);
} }
else { else {
$('#myusernameedit').attr('placeholder', html10n.get('pad.userlist.entername')); $('#myusernameedit').attr('placeholder', i18nextvar('pad.userlist.entername'));
} }
if (colorPickerOpen) { if (colorPickerOpen) {
$('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable'); $('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
@ -536,7 +548,7 @@ const closeColorPicker = (accept) => {
$('#mycolorpicker').removeClass('popup-show'); $('#mycolorpicker').removeClass('popup-show');
}; };
const showColorPicker = () => { const showColorPicker = () => {
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId); ($ as unknown as JQueryGritter).farbtastic('#colorpicker').setColor(myUserInfo.colorId);
if (!colorPickerOpen) { if (!colorPickerOpen) {
const palette = pad.getColorPalette(); const palette = pad.getColorPalette();
if (!colorPickerSetup) { if (!colorPickerSetup) {

View file

@ -1,3 +1,6 @@
'use strict'; 'use strict';
/** /**
@ -22,7 +25,9 @@
* limitations under the License. * limitations under the License.
*/ */
import Security from './security'; //FIXME Security does not have a typescript file
// @ts-ignore
import {escapeHTML,escapeHTMLAttribute} from './security';
/** /**
* Generates a random String with the given length. Is needed to generate the Author, Group, * Generates a random String with the given length. Is needed to generate the Author, Group,
@ -137,7 +142,7 @@ export const padutils = {
} }
}, },
escapeHtml: (x) => Security.escapeHTML(String(x)), escapeHtml: (x) => escapeHTML(String(x)),
uniqueId: () => { uniqueId: () => {
const pad = require('./pad').pad; // Sidestep circular dependency const pad = require('./pad').pad; // Sidestep circular dependency
// returns string that is exactly 'width' chars, padding with zeros and taking rightmost digits // returns string that is exactly 'width' chars, padding with zeros and taking rightmost digits
@ -198,7 +203,7 @@ export const padutils = {
const advanceTo = (i) => { const advanceTo = (i) => {
if (i > idx) { if (i > idx) {
pieces.push(Security.escapeHTML(text.substring(idx, i))); pieces.push(escapeHTML(text.substring(idx, i)));
idx = i; idx = i;
} }
}; };
@ -216,9 +221,9 @@ export const padutils = {
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
pieces.push( pieces.push(
'<a ', '<a ',
(target ? `target="${Security.escapeHTMLAttribute(target)}" ` : ''), (target ? `target="${escapeHTMLAttribute(target)}" ` : ''),
'href="', 'href="',
Security.escapeHTMLAttribute(href), escapeHTMLAttribute(href),
'" rel="noreferrer noopener">'); '" rel="noreferrer noopener">');
advanceTo(startIndex + href.length); advanceTo(startIndex + href.length);
pieces.push('</a>'); pieces.push('</a>');

View file

@ -1,6 +1,8 @@
// @ts-nocheck
import * as pluginUtils from "./shared.js"; import * as pluginUtils from "./shared.js";
import * as defs from "./plugin_defs.js"; import * as defs from "./plugin_defs.js";
'use strict'; 'use strict';
const adoptPluginsFromAncestorsOf = (frame) => { const adoptPluginsFromAncestorsOf = (frame) => {
// Bind plugins with parent; // Bind plugins with parent;
let parentRequire = null; let parentRequire = null;

View file

@ -8,13 +8,13 @@
// * hook_fn: Plugin-supplied hook function. // * hook_fn: Plugin-supplied hook function.
// * hook_fn_name: Name of the hook function, with the form <filename>:<functionName>. // * hook_fn_name: Name of the hook function, with the form <filename>:<functionName>.
// * part: The ep.json part object that declared the hook. See exports.plugins. // * part: The ep.json part object that declared the hook. See exports.plugins.
export const hooks = {}; export let hooks = {};
// Whether the plugins have been loaded. // Whether the plugins have been loaded.
export let loaded = false; export let loaded = false;
// Topologically sorted list of parts from exports.plugins. // Topologically sorted list of parts from exports.plugins.
export const parts = []; export let parts = [];
// Maps the name of a plugin to the plugin's definition provided in ep.json. The ep.json object is // Maps the name of a plugin to the plugin's definition provided in ep.json. The ep.json object is
// augmented with additional metadata: // augmented with additional metadata:

View file

@ -94,7 +94,7 @@ export const pathNormalization = (part, hookFnName, hookName) => {
}; };
export const update = async () => { export const update = async () => {
const packages = await exports.getPackages(); const packages = await getPackages();
let parts:{[keys: string]:any} = {}; // Key is full name. sortParts converts this into a topologically sorted array. let parts:{[keys: string]:any} = {}; // Key is full name. sortParts converts this into a topologically sorted array.
let plugins = {}; let plugins = {};
@ -107,7 +107,7 @@ export const update = async () => {
setPlugins(plugins); setPlugins(plugins);
setParts(sortParts(parts)) setParts(sortParts(parts))
setHooks(extractHooks(parts, 'hooks', exports.pathNormalization)); setHooks(extractHooks(parts, 'hooks', pathNormalization));
setLoaded(true) setLoaded(true)
await Promise.all(Object.keys(plugins).map(async (p) => { await Promise.all(Object.keys(plugins).map(async (p) => {
const logger = log4js.getLogger(`plugin:${p}`); const logger = log4js.getLogger(`plugin:${p}`);
@ -115,7 +115,7 @@ export const update = async () => {
})); }));
}; };
exports.getPackages = async () => { const getPackages = async () => {
logger.info('Running npm to get a list of installed plugins...'); logger.info('Running npm to get a list of installed plugins...');
// Notes: // Notes:
// * Do not pass `--prod` otherwise `npm ls` will fail if there is no `package.json`. // * Do not pass `--prod` otherwise `npm ls` will fail if there is no `package.json`.
@ -125,7 +125,7 @@ exports.getPackages = async () => {
const cmd = ['npm', 'ls', '--long', '--json', '--depth=0', '--no-production']; const cmd = ['npm', 'ls', '--long', '--json', '--depth=0', '--no-production'];
const {dependencies = {}} = JSON.parse(await exportCMD(cmd, {stdio: [null, 'string']}) as unknown as string); const {dependencies = {}} = JSON.parse(await exportCMD(cmd, {stdio: [null, 'string']}) as unknown as string);
await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => { await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => {
if (!pkg.startsWith(exports.prefix)) { if (!pkg.startsWith(prefix)) {
delete dependencies[pkg]; delete dependencies[pkg];
return; return;
} }

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//FIXME Could be interesting to have a look at this
'use strict'; 'use strict';
// Provides a require'able version of jQuery without leaking $ and jQuery; // Provides a require'able version of jQuery without leaking $ and jQuery;
window.$ = require('./vendors/jquery'); window.$ = require('./vendors/jquery');

View file

@ -1,8 +1,9 @@
import * as caretPosition from "./caretPosition.js"; import * as caretPosition from "./caretPosition.js";
import {CustomWindow} from "../module/CustomWindow";
'use strict'; 'use strict';
function Scroll(outerWin) { function Scroll(outerWin) {
// scroll settings // scroll settings
this.scrollSettings = parent.parent.clientVars.scrollWhenFocusLineIsOutOfViewport; this.scrollSettings = (parent.parent as unknown as CustomWindow).clientVars.scrollWhenFocusLineIsOutOfViewport;
// DOM reference // DOM reference
this.outerWin = outerWin; this.outerWin = outerWin;
this.doc = this.outerWin.document; this.doc = this.outerWin.document;
@ -166,7 +167,7 @@ Scroll.prototype._getPixelsRelativeToPercentageOfViewport =
let pixels = 0; let pixels = 0;
const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport); const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) { if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) {
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport); pixels = parseInt(String(innerHeight * scrollPercentageRelativeToViewport));
} }
return pixels; return pixels;
}; };
@ -183,7 +184,7 @@ Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function (innerHeigh
let pixels = 0; let pixels = 0;
const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp; const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) { if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) {
pixels = parseInt(innerHeight * percentageToScrollUp); pixels = parseInt(String(innerHeight * percentageToScrollUp));
} }
return pixels; return pixels;
}; };

View file

@ -1,2 +0,0 @@
'use strict';
export * from "security";

10
src/static/js/security.ts Normal file
View file

@ -0,0 +1,10 @@
'use strict';
export * from "security";
export function escapeHTMLAttribute(href) {
throw new Error("Function not implemented.");
}
export function escapeHTML(txt) {
throw new Error("Function not implemented.");
}

View file

@ -21,6 +21,13 @@
*/ */
const _entryWidth = (e) => (e && e.width) || 0; const _entryWidth = (e) => (e && e.width) || 0;
class Node { class Node {
key: any;
private entry: any;
levels: number;
upPtrs: any[];
downPtrs: any[];
downSkips: any[];
downSkipWidths: any[];
constructor(entry, levels = 0, downSkips = 1, downSkipWidths = 0) { constructor(entry, levels = 0, downSkips = 1, downSkipWidths = 0) {
this.key = entry != null ? entry.key : null; this.key = entry != null ? entry.key : null;
this.entry = entry; this.entry = entry;
@ -51,6 +58,11 @@ class Node {
// is still valid and points to the same index in the skiplist. Other operations with other points // is still valid and points to the same index in the skiplist. Other operations with other points
// invalidate this point. // invalidate this point.
class Point { class Point {
private _skipList: any;
private loc: any;
private idxs: any[];
private nodes: any[];
private widthSkips: any[];
constructor(skipList, loc) { constructor(skipList, loc) {
this._skipList = skipList; this._skipList = skipList;
this.loc = loc; this.loc = loc;
@ -172,6 +184,10 @@ class Point {
* property that is a string. * property that is a string.
*/ */
class SkipList { class SkipList {
private _keyToNodeMap: Map<any, any>;
private _start: Node;
private _end: Node;
private _totalWidth: number;
constructor() { constructor() {
// if there are N elements in the skiplist, "start" is element -1 and "end" is element N // if there are N elements in the skiplist, "start" is element -1 and "end" is element N
this._start = new Node(null, 1); this._start = new Node(null, 1);
@ -199,7 +215,7 @@ class SkipList {
} }
return n; return n;
} }
_getNodeIndex(node, byWidth) { _getNodeIndex(node, byWidth?) {
let dist = (byWidth ? 0 : -1); let dist = (byWidth ? 0 : -1);
let n = node; let n = node;
while (n !== this._start) { while (n !== this._start) {

View file

@ -1,4 +1,6 @@
'use strict'; 'use strict';
import {CustomWindow} from "../module/CustomWindow";
/** /**
* Creates a socket.io connection. * Creates a socket.io connection.
* @param etherpadBaseUrl - Etherpad URL. If relative, it is assumed to be relative to * @param etherpadBaseUrl - Etherpad URL. If relative, it is assumed to be relative to
@ -8,6 +10,8 @@
* https://socket.io/docs/v2/client-api/#new-Manager-url-options * https://socket.io/docs/v2/client-api/#new-Manager-url-options
* @return socket.io Socket object * @return socket.io Socket object
*/ */
import io from 'socket.io-client';
const connect = (etherpadBaseUrl, namespace = '/', options = {}) => { const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
// The API for socket.io's io() function is awkward. The documentation says that the first // The API for socket.io's io() function is awkward. The documentation says that the first
// argument is a URL, but it is not the URL of the socket.io endpoint. The URL's path part is used // argument is a URL, but it is not the URL of the socket.io endpoint. The URL's path part is used
@ -15,7 +19,7 @@ const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
// parameters, if present) is combined with the `path` option (which defaults to '/socket.io', but // parameters, if present) is combined with the `path` option (which defaults to '/socket.io', but
// is overridden here to allow users to host Etherpad at something like '/etherpad') to get the // is overridden here to allow users to host Etherpad at something like '/etherpad') to get the
// URL of the socket.io endpoint. // URL of the socket.io endpoint.
const baseUrl = new URL(etherpadBaseUrl, window.location); const baseUrl = new URL(etherpadBaseUrl, window.location.href);
const socketioUrl = new URL('socket.io', baseUrl); const socketioUrl = new URL('socket.io', baseUrl);
const namespaceUrl = new URL(namespace, new URL('/', baseUrl)); const namespaceUrl = new URL(namespace, new URL('/', baseUrl));
return io(namespaceUrl.href, Object.assign({ path: socketioUrl.pathname }, options)); return io(namespaceUrl.href, Object.assign({ path: socketioUrl.pathname }, options));
@ -23,6 +27,6 @@ const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
if (typeof exports === 'object') { if (typeof exports === 'object') {
} }
else { else {
window.socketio = { connect }; (window as unknown as CustomWindow).socketio = { connect };
} }
export { connect }; export { connect };

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//FIXME Could be interesting to have a look at this
import "./vendors/jquery.js"; import "./vendors/jquery.js";
import * as padUtils from "./pad_utils.js"; import * as padUtils from "./pad_utils.js";
import * as hooks from "./pluginfw/hooks.js"; import * as hooks from "./pluginfw/hooks.js";

View file

@ -1,2 +0,0 @@
'use strict';
export { default } from "underscore";

View file

@ -1,5 +1,5 @@
import * as Changeset from "./Changeset.js"; import * as Changeset from "./Changeset.js";
import * as _ from "./underscore.js"; import _ from "underscore";
'use strict'; 'use strict';
const undoModule = (() => { const undoModule = (() => {
const stack = (() => { const stack = (() => {

View file

@ -1,943 +0,0 @@
// WARNING: This file has been modified from the Original
/**
* Copyright (c) 2012 Marcel Klehr
* Copyright (c) 2011-2012 Fabien Cazenave, Mozilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
window.html10n = (function (window, document, undefined) {
// fix console
(function () {
var noop = function () { };
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
var console = (window.console = window.console || {});
for (var i = 0; i < names.length; ++i) {
if (!console[names[i]]) {
console[names[i]] = noop;
}
}
}());
// fix Array#forEach in IE
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fn, scope) {
for (var i = 0, len = this.length; i < len; ++i) {
if (i in this) {
fn.call(scope, this[i], i, this);
}
}
};
}
// fix Array#indexOf in, guess what, IE! <3
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
}
else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
/**
* MicroEvent - to make any js object an event emitter (server or browser)
*/
var MicroEvent = function () { };
MicroEvent.prototype = {
bind: function (event, fct) {
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind: function (event, fct) {
this._events = this._events || {};
if (event in this._events === false)
return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger: function (event /* , args... */) {
this._events = this._events || {};
if (event in this._events === false)
return;
for (var i = 0; i < this._events[event].length; i++) {
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
/**
* mixin will delegate all MicroEvent.js function in the destination object
* @param {Object} the object which will support MicroEvent
*/
MicroEvent.mixin = function (destObject) {
var props = ['bind', 'unbind', 'trigger'];
if (!destObject)
return;
for (var i = 0; i < props.length; i++) {
destObject[props[i]] = MicroEvent.prototype[props[i]];
}
};
/**
* Loader
* The loader is responsible for loading
* and caching all necessary resources
*/
function Loader(resources) {
this.resources = resources;
this.cache = {}; // file => contents
this.langs = {}; // lang => strings
}
Loader.prototype.load = function (lang, cb) {
if (this.langs[lang])
return cb();
if (this.resources.length > 0) {
var reqs = 0;
for (var i = 0, n = this.resources.length; i < n; i++) {
this.fetch(this.resources[i], lang, function (e) {
reqs++;
if (e)
console.warn(e);
if (reqs < n)
return; // Call back once all reqs are completed
cb && cb();
});
}
}
};
Loader.prototype.fetch = function (href, lang, cb) {
var that = this;
if (this.cache[href]) {
this.parse(lang, href, this.cache[href], cb);
return;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', href, /*async: */ true);
if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json; charset=utf-8');
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) {
var data = JSON.parse(xhr.responseText);
that.cache[href] = data;
// Pass on the contents for parsing
that.parse(lang, href, data, cb);
}
else {
cb(new Error('Failed to load ' + href));
}
}
};
xhr.send(null);
};
Loader.prototype.parse = function (lang, currHref, data, cb) {
if ('object' != typeof data) {
cb(new Error('A file couldn\'t be parsed as json.'));
return;
}
// Check if lang exists
if (!data[lang]) {
// lang not found
// This may be due to formatting (expected 'ru' but browser sent 'ru-RU')
// Set err msg before mutating lang (we may need this later)
var msg = 'Couldn\'t find translations for ' + lang;
// Check for '-' ('ROOT-VARIANT')
if (lang.indexOf('-') > -1) {
// ROOT-VARIANT formatting detected
lang = lang.split('-')[0]; // set lang to ROOT lang
}
// Check if ROOT lang exists (e.g 'ru')
if (!data[lang]) {
// ROOT lang not found. (e.g 'zh')
// Loop through langs data. Maybe we have a variant? e.g (zh-hans)
var l; // langs item. Declare outside of loop
for (l in data) {
// Is not ROOT?
// And index of ROOT equals 0?
// And is known lang?
if (lang != l && l.indexOf(lang) === 0 && data[l]) {
lang = l; // set lang to ROOT-VARIANT (e.g 'zh-hans')
break;
}
}
// Did we find a variant? If not, return err.
if (lang != l) {
return cb(new Error(msg));
}
}
}
if ('string' == typeof data[lang]) {
// Import rule
// absolute path
var importUrl = data[lang];
// relative path
if (data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
importUrl = currHref + "/../" + data[lang];
}
this.fetch(importUrl, lang, cb);
return;
}
if ('object' != typeof data[lang]) {
cb(new Error('Translations should be specified as JSON objects!'));
return;
}
this.langs[lang] = data[lang];
// TODO: Also store accompanying langs
cb();
};
/**
* The html10n object
*/
var html10n = { language: null
};
MicroEvent.mixin(html10n);
html10n.macros = {};
html10n.rtl = ["ar", "dv", "fa", "ha", "he", "ks", "ku", "ps", "ur", "yi"];
/**
* Get rules for plural forms (shared with JetPack), see:
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
*
* @param {string} lang
* locale (language) used.
*
* @return {Function}
* returns a function that gives the plural form name for a given integer:
* var fun = getPluralRules('en');
* fun(1) -> 'one'
* fun(0) -> 'other'
* fun(1000) -> 'other'.
*/
function getPluralRules(lang) {
var locales2rules = {
'af': 3,
'ak': 4,
'am': 4,
'ar': 1,
'asa': 3,
'az': 0,
'be': 11,
'bem': 3,
'bez': 3,
'bg': 3,
'bh': 4,
'bm': 0,
'bn': 3,
'bo': 0,
'br': 20,
'brx': 3,
'bs': 11,
'ca': 3,
'cgg': 3,
'chr': 3,
'cs': 12,
'cy': 17,
'da': 3,
'de': 3,
'dv': 3,
'dz': 0,
'ee': 3,
'el': 3,
'en': 3,
'eo': 3,
'es': 3,
'et': 3,
'eu': 3,
'fa': 0,
'ff': 5,
'fi': 3,
'fil': 4,
'fo': 3,
'fr': 5,
'fur': 3,
'fy': 3,
'ga': 8,
'gd': 24,
'gl': 3,
'gsw': 3,
'gu': 3,
'guw': 4,
'gv': 23,
'ha': 3,
'haw': 3,
'he': 2,
'hi': 4,
'hr': 11,
'hu': 0,
'id': 0,
'ig': 0,
'ii': 0,
'is': 3,
'it': 3,
'iu': 7,
'ja': 0,
'jmc': 3,
'jv': 0,
'ka': 0,
'kab': 5,
'kaj': 3,
'kcg': 3,
'kde': 0,
'kea': 0,
'kk': 3,
'kl': 3,
'km': 0,
'kn': 0,
'ko': 0,
'ksb': 3,
'ksh': 21,
'ku': 3,
'kw': 7,
'lag': 18,
'lb': 3,
'lg': 3,
'ln': 4,
'lo': 0,
'lt': 10,
'lv': 6,
'mas': 3,
'mg': 4,
'mk': 16,
'ml': 3,
'mn': 3,
'mo': 9,
'mr': 3,
'ms': 0,
'mt': 15,
'my': 0,
'nah': 3,
'naq': 7,
'nb': 3,
'nd': 3,
'ne': 3,
'nl': 3,
'nn': 3,
'no': 3,
'nr': 3,
'nso': 4,
'ny': 3,
'nyn': 3,
'om': 3,
'or': 3,
'pa': 3,
'pap': 3,
'pl': 13,
'ps': 3,
'pt': 3,
'rm': 3,
'ro': 9,
'rof': 3,
'ru': 11,
'rwk': 3,
'sah': 0,
'saq': 3,
'se': 7,
'seh': 3,
'ses': 0,
'sg': 0,
'sh': 11,
'shi': 19,
'sk': 12,
'sl': 14,
'sma': 7,
'smi': 7,
'smj': 7,
'smn': 7,
'sms': 7,
'sn': 3,
'so': 3,
'sq': 3,
'sr': 11,
'ss': 3,
'ssy': 3,
'st': 3,
'sv': 3,
'sw': 3,
'syr': 3,
'ta': 3,
'te': 3,
'teo': 3,
'th': 0,
'ti': 4,
'tig': 3,
'tk': 3,
'tl': 4,
'tn': 3,
'to': 0,
'tr': 0,
'ts': 3,
'tzm': 22,
'uk': 11,
'ur': 3,
've': 3,
'vi': 0,
'vun': 3,
'wa': 4,
'wae': 3,
'wo': 0,
'xh': 3,
'xog': 3,
'yo': 0,
'zh': 0,
'zu': 3
};
// utility functions for plural rules methods
function isIn(n, list) {
return list.indexOf(n) !== -1;
}
function isBetween(n, start, end) {
return start <= n && n <= end;
}
// list of all plural rules methods:
// map an integer to the plural form name to use
var pluralRules = {
'0': function (n) {
return 'other';
},
'1': function (n) {
if ((isBetween((n % 100), 3, 10)))
return 'few';
if (n === 0)
return 'zero';
if ((isBetween((n % 100), 11, 99)))
return 'many';
if (n == 2)
return 'two';
if (n == 1)
return 'one';
return 'other';
},
'2': function (n) {
if (n !== 0 && (n % 10) === 0)
return 'many';
if (n == 2)
return 'two';
if (n == 1)
return 'one';
return 'other';
},
'3': function (n) {
if (n == 1)
return 'one';
return 'other';
},
'4': function (n) {
if ((isBetween(n, 0, 1)))
return 'one';
return 'other';
},
'5': function (n) {
if ((isBetween(n, 0, 2)) && n != 2)
return 'one';
return 'other';
},
'6': function (n) {
if (n === 0)
return 'zero';
if ((n % 10) == 1 && (n % 100) != 11)
return 'one';
return 'other';
},
'7': function (n) {
if (n == 2)
return 'two';
if (n == 1)
return 'one';
return 'other';
},
'8': function (n) {
if ((isBetween(n, 3, 6)))
return 'few';
if ((isBetween(n, 7, 10)))
return 'many';
if (n == 2)
return 'two';
if (n == 1)
return 'one';
return 'other';
},
'9': function (n) {
if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
return 'few';
if (n == 1)
return 'one';
return 'other';
},
'10': function (n) {
if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
return 'few';
if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
return 'one';
return 'other';
},
'11': function (n) {
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
return 'few';
if ((n % 10) === 0 ||
(isBetween((n % 10), 5, 9)) ||
(isBetween((n % 100), 11, 14)))
return 'many';
if ((n % 10) == 1 && (n % 100) != 11)
return 'one';
return 'other';
},
'12': function (n) {
if ((isBetween(n, 2, 4)))
return 'few';
if (n == 1)
return 'one';
return 'other';
},
'13': function (n) {
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
return 'few';
if (n != 1 && (isBetween((n % 10), 0, 1)) ||
(isBetween((n % 10), 5, 9)) ||
(isBetween((n % 100), 12, 14)))
return 'many';
if (n == 1)
return 'one';
return 'other';
},
'14': function (n) {
if ((isBetween((n % 100), 3, 4)))
return 'few';
if ((n % 100) == 2)
return 'two';
if ((n % 100) == 1)
return 'one';
return 'other';
},
'15': function (n) {
if (n === 0 || (isBetween((n % 100), 2, 10)))
return 'few';
if ((isBetween((n % 100), 11, 19)))
return 'many';
if (n == 1)
return 'one';
return 'other';
},
'16': function (n) {
if ((n % 10) == 1 && n != 11)
return 'one';
return 'other';
},
'17': function (n) {
if (n == 3)
return 'few';
if (n === 0)
return 'zero';
if (n == 6)
return 'many';
if (n == 2)
return 'two';
if (n == 1)
return 'one';
return 'other';
},
'18': function (n) {
if (n === 0)
return 'zero';
if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
return 'one';
return 'other';
},
'19': function (n) {
if ((isBetween(n, 2, 10)))
return 'few';
if ((isBetween(n, 0, 1)))
return 'one';
return 'other';
},
'20': function (n) {
if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) ||
isBetween((n % 100), 70, 79) ||
isBetween((n % 100), 90, 99)))
return 'few';
if ((n % 1000000) === 0 && n !== 0)
return 'many';
if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
return 'two';
if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
return 'one';
return 'other';
},
'21': function (n) {
if (n === 0)
return 'zero';
if (n == 1)
return 'one';
return 'other';
},
'22': function (n) {
if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
return 'one';
return 'other';
},
'23': function (n) {
if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
return 'one';
return 'other';
},
'24': function (n) {
if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
return 'few';
if (isIn(n, [2, 12]))
return 'two';
if (isIn(n, [1, 11]))
return 'one';
return 'other';
}
};
// return a function that gives the plural form name for a given integer
var index = locales2rules[lang.replace(/-.*$/, '')];
if (!(index in pluralRules)) {
console.warn('plural form unknown for [' + lang + ']');
return function () { return 'other'; };
}
return pluralRules[index];
}
/**
* pre-defined 'plural' macro
*/
html10n.macros.plural = function (key, param, opts) {
var str, n = parseFloat(param);
if (isNaN(n))
return;
// initialize _pluralRules
if (!this._pluralRules)
this._pluralRules = getPluralRules(html10n.language);
var index = this._pluralRules(n);
// try to find a [zero|one|two] key if it's defined
if (n === 0 && ('zero') in opts) {
str = opts['zero'];
}
else if (n == 1 && ('one') in opts) {
str = opts['one'];
}
else if (n == 2 && ('two') in opts) {
str = opts['two'];
}
else if (index in opts) {
str = opts[index];
}
return str;
};
/**
* Localize a document
* @param langs An array of lang codes defining fallbacks
*/
html10n.localize = function (langs) {
var that = this;
// if only one string => create an array
if ('string' == typeof langs)
langs = [langs];
// Expand two-part locale specs
var i = 0;
langs.forEach(function (lang) {
if (!lang)
return;
langs[i++] = lang;
if (~lang.indexOf('-'))
langs[i++] = lang.substr(0, lang.indexOf('-'));
});
this.build(langs, function (er, translations) {
html10n.translations = translations;
html10n.translateElement(translations);
that.trigger('localized');
});
};
/**
* Triggers the translation process
* for an element
* @param translations A hash of all translation strings
* @param element A DOM element, if omitted, the document element will be used
*/
html10n.translateElement = function (translations, element) {
element = element || document.documentElement;
var children = element ? getTranslatableChildren(element) : document.childNodes;
for (var i = 0, n = children.length; i < n; i++) {
this.translateNode(translations, children[i]);
}
// translate element itself if necessary
this.translateNode(translations, element);
};
function asyncForEach(list, iterator, cb) {
var i = 0, n = list.length;
iterator(list[i], i, function each(err) {
if (err)
console.error(err);
i++;
if (i < n)
return iterator(list[i], i, each);
cb();
});
}
function getTranslatableChildren(element) {
if (!document.querySelectorAll) {
if (!element)
return [];
var nodes = element.getElementsByTagName('*'), l10nElements = [];
for (var i = 0, n = nodes.length; i < n; i++) {
if (nodes[i].getAttribute('data-l10n-id'))
l10nElements.push(nodes[i]);
}
return l10nElements;
}
return element.querySelectorAll('*[data-l10n-id]');
}
html10n.get = function (id, args) {
var translations = html10n.translations;
if (!translations)
return console.warn('No translations available (yet)');
if (!translations[id])
return console.warn('Could not find string ' + id);
// apply macros
var str = translations[id];
str = substMacros(id, str, args);
// apply args
str = substArguments(str, args);
return str;
};
// replace {{arguments}} with their values or the
// associated translation string (based on its key)
function substArguments(str, args) {
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/, match;
var translations = html10n.translations;
while (match = reArgs.exec(str)) {
if (!match || match.length < 2)
return str; // argument key not found
var arg = match[1], sub = '';
if (args && arg in args) {
sub = args[arg];
}
else if (translations && arg in translations) {
sub = translations[arg];
}
else {
console.warn('Could not find argument {{' + arg + '}}');
return str;
}
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length);
}
return str;
}
// replace {[macros]} with their values
function substMacros(key, str, args) {
var regex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ //.exec('{[ plural(n) other: are {{n}}, one: is ]}')
, match;
while (match = regex.exec(str)) {
// a macro has been found
// Note: at the moment, only one parameter is supported
var macroName = match[1], paramName = match[2], optv = match[3], opts = {};
if (!(macroName in html10n.macros))
continue;
if (optv) {
optv.match(/(?=\s*)([a-zA-Z]+)\: ?([ a-zA-Z{}]+)(?=,?)/g).forEach(function (arg) {
var parts = arg.split(':'), name = parts[0], value = parts[1].trim();
opts[name] = value;
});
}
var param;
if (args && paramName in args) {
param = args[paramName];
}
else if (paramName in html10n.translations) {
param = translations[paramName];
}
// there's no macro parser: it has to be defined in html10n.macros
var macro = html10n.macros[macroName];
str = str.substr(0, match.index) + macro(key, param, opts) + str.substr(match.index + match[0].length);
}
return str;
}
/**
* Applies translations to a DOM node (recursive)
*/
html10n.translateNode = function (translations, node) {
var str = {};
// get id
str.id = node.getAttribute('data-l10n-id');
if (!str.id)
return;
if (!translations[str.id])
return console.warn('Couldn\'t find translation key ' + str.id);
// get args
if (window.JSON) {
str.args = JSON.parse(node.getAttribute('data-l10n-args'));
}
else {
try {
str.args = eval(node.getAttribute('data-l10n-args'));
}
catch (e) {
console.warn('Couldn\'t parse args for ' + str.id);
}
}
str.str = html10n.get(str.id, str.args);
// get attribute name to apply str to
var prop, index = str.id.lastIndexOf('.'), attrList = // allowed attributes
{ "title": 1,
"innerHTML": 1,
"alt": 1,
"textContent": 1,
"value": 1,
"placeholder": 1
};
if (index > 0 && str.id.substr(index + 1) in attrList) {
// an attribute has been specified (example: "my_translation_key.placeholder")
prop = str.id.substr(index + 1);
}
else { // no attribute: assuming text content by default
prop = document.body.textContent ? 'textContent' : 'innerText';
}
// Apply translation
if (node.children.length === 0 || prop != 'textContent') {
node[prop] = str.str;
node.setAttribute("aria-label", str.str); // Sets the aria-label
// The idea of the above is that we always have an aria value
// This might be a bit of an abrupt solution but let's see how it goes
}
else {
var children = node.childNodes, found = false;
for (var i = 0, n = children.length; i < n; i++) {
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
if (!found) {
children[i].nodeValue = str.str;
found = true;
}
else {
children[i].nodeValue = '';
}
}
}
if (!found) {
console.warn('Unexpected error: could not translate element content for key ' + str.id, node);
}
}
};
/**
* Builds a translation object from a list of langs (loads the necessary translations)
* @param langs Array - a list of langs sorted by priority (default langs should go last)
*/
html10n.build = function (langs, cb) {
var that = this, build = {};
asyncForEach(langs, function (lang, i, next) {
if (!lang)
return next();
that.loader.load(lang, next);
}, function () {
var lang;
langs.reverse();
// loop through the priority array...
for (var i = 0, n = langs.length; i < n; i++) {
lang = langs[i];
if (!lang)
continue;
if (!(lang in that.loader.langs)) { // uh, we don't have this lang availbable..
// then check for related langs
if (~lang.indexOf('-'))
lang = lang.split('-')[0];
for (var l in that.loader.langs) {
if (lang != l && l.indexOf(lang) === 0) {
lang = l;
break;
}
}
if (lang != l)
continue;
}
// ... and apply all strings of the current lang in the list
// to our build object
for (var string in that.loader.langs[lang]) {
build[string] = that.loader.langs[lang][string];
}
// the last applied lang will be exposed as the
// lang the page was translated to
that.language = lang;
}
cb(null, build);
});
};
/**
* Returns the language that was last applied to the translations hash
* thus overriding most of the formerly applied langs
*/
html10n.getLanguage = function () {
return this.language;
};
/**
* Returns the direction of the language returned be html10n#getLanguage
*/
html10n.getDirection = function () {
if (!this.language)
return;
var langCode = this.language.indexOf('-') == -1 ? this.language : this.language.substr(0, this.language.indexOf('-'));
return html10n.rtl.indexOf(langCode) == -1 ? 'ltr' : 'rtl';
};
/**
* Index all <link>s
*/
html10n.index = function () {
// Find all <link>s
var links = document.getElementsByTagName('link'), resources = [];
for (var i = 0, n = links.length; i < n; i++) {
if (links[i].type != 'application/l10n+json')
continue;
resources.push(links[i].href);
}
this.loader = new Loader(resources);
this.trigger('indexed');
};
if (document.addEventListener) // modern browsers and IE9+
document.addEventListener('DOMContentLoaded', function () {
html10n.index();
}, false);
else if (window.attachEvent)
window.attachEvent('onload', function () {
html10n.index();
}, false);
// gettext-like shortcut
if (window._ === undefined)
window._ = html10n.get;
return html10n;
})(window, document);

15
src/static/js/vendors/i18next.ts vendored Normal file
View file

@ -0,0 +1,15 @@
import i18next, {TFunction} from 'i18next'
import Backend from 'i18next-fs-backend';
export let i18nextvar: TFunction
const i18next2 = i18next
.use(Backend)
.init({
fallbackLng: 'en', // Default language fallback
backend: {
loadPath: 'locales/{{lng}}.json', // Path pattern to load locale files
},
}).then(t=>{
i18nextvar = t
});

View file

@ -1,9 +1,16 @@
import {extend} from "jquery";
export type CustomWindow = { export type CustomWindow = {
revisionInfo: any;
$: any;
require: RequireFunction, require: RequireFunction,
Ace2Inner:any, Ace2Inner:any,
plugins: any, plugins: any,
jQuery: any, jQuery: any,
document: any, document: any,
pad: any,
clientVars: any,
socketio: any,
} }
@ -12,3 +19,36 @@ export type RequireFunction= {
setLibraryURI: (url: string)=>void, setLibraryURI: (url: string)=>void,
setGlobalKeyPath: (path: string)=>void, setGlobalKeyPath: (path: string)=>void,
} }
export type CustomElementWithSheet = {
sheet: CSSStyleSheet,
startLoc: number
currentLoc: number,
id
}
export type AuthorData = {
colorId: number|string
}
export type Author = {
[author:string]:AuthorData
}
export type JQueryGritter = {
gritter: any;
farbtastic: any;
}
export type AjaxDirectDatabaseAccess = {
code : number
message: string
data: {
directDatabaseAccess?: boolean
}
}
export type HistoricalAuthorData = {
userId: string,
}

View file

@ -1,13 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"resolveJsonModule": true,
"allowJs": true, "allowJs": true,
"typeRoots": ["node_modules/@types"], "typeRoots": ["node_modules/@types"],
"types": ["node", "jquery"], "types": ["node", "jquery"],
"module": "commonjs", "module": "commonjs",
"esModuleInterop": true, "esModuleInterop": true,
"target": "es6",
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,
"target": "ES2015",
"outDir": "dist" "outDir": "dist"
}, },
"lib": ["es2015"] "lib": ["es2015"]