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.
*/
import Changeset from '../../static/js/Changeset';
import {builder, deserializeOps} from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage';
import {CustomError} from '../utils/customError';
import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager';
@ -518,7 +518,7 @@ export const restoreRevision = async (padID, rev, authorId = '') => {
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
for (const op of Changeset.deserializeOps(attribs)) {
for (const op of deserializeOps(attribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
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
const builder = Changeset.builder(oldText.length);
const builder2 = builder(oldText.length);
// assemble each line into the builder
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');
if (lastNewlinePos < 0) {
builder.remove(oldText.length - 1, 0);
builder2.remove(oldText.length - 1, 0);
} else {
builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
builder.remove(oldText.length - lastNewlinePos - 1, 0);
builder2.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
builder2.remove(oldText.length - lastNewlinePos - 1, 0);
}
const changeset = builder.toString();

View file

@ -21,7 +21,16 @@
import AttributeMap from '../../static/js/AttributeMap';
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 {AttributePool} from '../../static/js/AttributePool';
import AttributeManager from '../../static/js/AttributeManager';
@ -62,6 +71,7 @@ import {ErrorCaused} from "../models/ErrorCaused";
import {Pad} from "../db/Pad";
import {SessionInfo} from "../models/SessionInfo";
import {randomString} from "../utils/randomstring";
import {identity} from "lodash";
const securityManager = require('../db/SecurityManager');
@ -610,10 +620,10 @@ const handleUserChanges = async (socket, message) => {
const pad = await getPad(thisSession.padId, null, thisSession.author);
// 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
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
for (const op of deserializeOps(unpack(changeset).ops)) {
// + can add text with attribs
// = can change or add 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
// 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
let r = baseRev;
@ -645,21 +655,21 @@ const handleUserChanges = async (socket, message) => {
const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r);
if (changeset === c && thisSession.author === authorId) {
// 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
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// 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();
if (Changeset.oldLen(rebasedChangeset) !== prevText.length) {
if (oldLen(rebasedChangeset) !== prevText.length) {
throw new Error(
`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);
@ -674,7 +684,7 @@ const handleUserChanges = async (socket, message) => {
// Make sure the pad always ends with an empty line.
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);
}
@ -729,7 +739,7 @@ export const updatePadClients = async (pad) => {
const revChangeset = revision.changeset;
const currentTime = revision.meta.timestamp;
const forWire = Changeset.prepareForWire(revChangeset, pad.pool);
const forWire = prepareForWire(revChangeset, pad.pool);
const msg = {
type: 'COLLABROOM',
data: {
@ -764,7 +774,7 @@ const _correctMarkersInPad = (atext, apool) => {
// that aren't at the start of a line
const badMarkers = [];
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 hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
if (hasMarker) {
@ -786,15 +796,15 @@ const _correctMarkersInPad = (atext, apool) => {
// create changeset that removes these bad markers
offset = 0;
const builder = Changeset.builder(text.length);
const builder2 = builder(text.length);
badMarkers.forEach((pos) => {
builder.keepText(text.substring(offset, pos));
builder.remove(1);
builder2.keepText(text.substring(offset, pos));
builder2.remove(1);
offset = pos + 1;
});
return builder.toString();
return builder2.toString();
};
export let clientVars:any
@ -918,7 +928,7 @@ const handleClientReady = async (socket, message) => {
// return pending changesets
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',
data: {type: 'CLIENT_RECONNECT',
headRev: pad.getHeadRevisionNumber(),
@ -943,8 +953,8 @@ const handleClientReady = async (socket, message) => {
let apool;
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
try {
atext = Changeset.cloneAText(pad.atext);
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
atext = cloneAText(pad.atext);
const attribsForWire = prepareForWire(atext.attribs, pad.pool);
apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated;
} catch (e) {
@ -1175,13 +1185,13 @@ const getChangesetInfo = async (pad: Pad, startNum: number, endNum: number, gran
if (compositeEnd > endNum || compositeEnd > headRevision + 1) break;
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());
Changeset.mutateTextLines(forwards, lines.textlines);
mutateAttributionLines(forwards, lines.alines, pad.apool());
mutateTextLines(forwards, lines.textlines);
const forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
const backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
const forwards2 = moveOpsToNewPool(forwards, pad.apool(), apool);
const backwards2 = moveOpsToNewPool(backwards, pad.apool(), apool);
const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1];
const t2 = revisionDate[compositeEnd - 1];
@ -1209,12 +1219,12 @@ const getPadLines = async (pad, revNum) => {
if (revNum >= 0) {
atext = await pad.getInternalRevisionAText(revNum);
} else {
atext = Changeset.makeAText('\n');
atext = makeAText('\n');
}
return {
textlines: Changeset.splitTextLines(atext.text),
alines: Changeset.splitAttributionLines(atext.attribs, atext.text),
textlines: splitTextLines(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++) {
const cs = changesets[r];
changeset = Changeset.compose(changeset, cs, pool);
changeset = compose(changeset, cs, pool);
}
return changeset;
} catch (e) {

View file

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

View file

@ -20,12 +20,12 @@
*/
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) => {
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
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 pieces = [];
@ -57,7 +57,7 @@ export const _analyzeLine = (text, aline, apool) => {
let lineMarker = 0;
line.listLevel = 0;
if (aline) {
const [op] = Changeset.deserializeOps(aline);
const [op] = deserializeOps(aline);
if (op != null) {
const attribs = AttributeMap.fromString(op.attribs, apool);
let listType = attribs.get('list');
@ -77,7 +77,7 @@ export const _analyzeLine = (text, aline, apool) => {
}
if (lineMarker) {
line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1);
line.aline = subattribution(aline, 1);
} else {
line.text = text;
line.aline = aline;

View file

@ -15,14 +15,22 @@
* limitations under the License.
*/
import Changeset from '../../static/js/Changeset';
import attributes from "../../static/js/attributes";
import {
deserializeOps,
splitAttributionLines,
stringAssembler,
stringIterator,
subattribution
} from '../../static/js/Changeset';
import {decodeAttribString} from "../../static/js/attributes";
import {getPad} from "../db/PadManager";
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 {required} from '../eejs';
import {_analyzeLine, _encodeWhitespace} from "./ExportHelper";
@ -44,7 +52,7 @@ const getPadHTML = async (pad, revNum) => {
export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
const apool = pad.apool();
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 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>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
const taker = Changeset.stringIterator(text);
const assem = Changeset.stringAssembler();
const taker = stringIterator(text);
const assem = stringAssembler();
const openTags = [];
const getSpanClassFor = (i) => {
@ -200,7 +208,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
return;
}
const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
const ops = deserializeOps(subattribution(attribs, idx, idx + numChars));
idx += numChars;
// 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 = [];
// mark all attribs as used
for (const a of attributes.decodeAttribString(o.attribs)) {
for (const a of decodeAttribString(o.attribs)) {
if (a in anumMap) {
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
s = s.replace(String.fromCharCode(12), '');
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
assem.append(_encodeWhitespace(escapeHTML(s)));
} // end iteration over spans in line
// 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://mathiasbynens.github.io/rel-noopener/
// 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);
assem.append('</a>');
});
@ -477,7 +485,7 @@ export const getPadHTMLDocument = async (padId, revNum, readOnlyId?) => {
return required('ep_etherpad-lite/templates/export_html.html', {
body: html,
padId: Security.escapeHTML(readOnlyId || padId),
padId: escapeHTML(readOnlyId || padId),
extraCSS: stylesForExportCSS,
});
};

View file

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

View file

@ -28,7 +28,7 @@
*/
import exp from "constants";
import packageJSON from '../../../package.json'
import {findEtherpadRoot, makeAbsolute, isSubdir} from './AbsolutePaths';
import deepEqual from 'fast-deep-equal/es6';
import fs from 'fs';
@ -507,7 +507,7 @@ export const getGitCommit = () => {
};
// 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

View file

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

View file

@ -1,9 +1,18 @@
'use strict';
import AttributeMap from '../../static/js/AttributeMap';
import Changeset from '../../static/js/Changeset';
import attributes from '../../static/js/attributes';
import {
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';
// @ts-ignore
import {PadDiffModel} from "ep_etherpad-lite/node/models/PadDiffModel";
export const PadDiff = (pad, fromRev, toRev)=> {
@ -30,7 +39,7 @@ export const PadDiff = (pad, fromRev, toRev)=> {
PadDiff.prototype._isClearAuthorship = function (changeset) {
// unpack
const unpacked = Changeset.unpack(changeset);
const unpacked = unpack(changeset);
// check if there is nothing in the charBank
if (unpacked.charBank !== '') {
@ -42,7 +51,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
return false;
}
const [clearOperator, anotherOp] = Changeset.deserializeOps(unpacked.ops);
const [clearOperator, anotherOp] = deserializeOps(unpacked.ops);
// check if there is only one operator
if (anotherOp != null) return false;
@ -59,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
}
const [appliedAttribute, anotherAttribute] =
attributes.attribsFromString(clearOperator.attribs, this._pad.pool);
attribsFromString(clearOperator.attribs, this._pad.pool);
// Check that the operation has exactly one attribute.
if (appliedAttribute == null || anotherAttribute != null) return false;
@ -76,9 +85,9 @@ PadDiff.prototype._createClearAuthorship = async function (rev) {
const atext = await this._pad.getInternalRevisionAText(rev);
// build clearAuthorship changeset
const builder = Changeset.builder(atext.text.length);
builder.keepText(atext.text, [['author', '']], this._pad.pool);
const changeset = builder.toString();
const builder2 = builder(atext.text.length);
builder2.keepText(atext.text, [['author', '']], this._pad.pool);
const changeset = builder2.toString();
return changeset;
};
@ -91,7 +100,7 @@ PadDiff.prototype._createClearStartAtext = async function (rev) {
const changeset = await this._createClearAuthorship(rev);
// apply the clearAuthorship changeset
const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
const newAText = applyToAText(changeset, atext, this._pad.pool);
return newAText;
};
@ -160,7 +169,7 @@ PadDiff.prototype._createDiffAtext = async function () {
if (superChangeset == null) {
superChangeset = changeset;
} 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);
// 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
atext = Changeset.applyToAText(deletionChangeset, atext, this._pad.pool);
atext = applyToAText(deletionChangeset, atext, this._pad.pool);
}
return atext;
@ -217,22 +226,22 @@ PadDiff.prototype.getAuthors = async function () {
PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
// unpack
const unpacked = Changeset.unpack(changeset);
const unpacked = unpack(changeset);
const assem = Changeset.opAssembler();
const assem = opAssembler();
// create deleted attribs
const authorAttrib = apool.putAttrib(['author', author || '']);
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 === '-') {
// this is a delete operator, extend it with the author
operator.attribs = attribs;
} else if (operator.opcode === '=' && operator.attribs) {
// 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
@ -240,14 +249,14 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
}
// 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,
// it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const lines = Changeset.splitTextLines(startAText.text);
const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
const lines = splitTextLines(startAText.text);
const alines = splitAttributionLines(startAText.attribs, startAText.text);
// lines and alines are what the exports is meant to apply to.
// 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 curLineOpsNext = null;
let curLineOpsLine;
let curLineNextOp = new Changeset.Op('+');
let curLineNextOp = new Op('+');
const unpacked = Changeset.unpack(cs);
const builder = Changeset.builder(unpacked.newLen);
const unpacked = unpack(cs);
const builder2 = builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if (!curLineOps || curLineOpsLine !== curLine) {
curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine;
let indexIntoLine = 0;
@ -302,13 +311,13 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curChar = 0;
curLineOpsLine = curLine;
curLineNextOp.chars = 0;
curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
}
if (!curLineNextOp.chars) {
if (curLineOpsNext.done) {
curLineNextOp = new Changeset.Op();
curLineNextOp = new Op();
} else {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
@ -343,7 +352,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const nextText = (numChars) => {
let len = 0;
const assem = Changeset.stringAssembler();
const assem = stringAssembler();
const firstString = linesGet(curLine).substring(curChar);
len += firstString.length;
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 === '=') {
const textBank = nextText(csOp.chars);
@ -417,7 +426,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
textLeftToProcess = textLeftToProcess.substr(lengthToProcess);
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
consumeAttribRuns(1, () => {});
@ -429,31 +438,31 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
// get the old attributes back
const oldAttribs = undoBackToAttribs(attribs);
builder.insert(processText.substr(textBankIndex, len), oldAttribs);
builder2.insert(processText.substr(textBankIndex, len), oldAttribs);
textBankIndex += len;
});
builder.keep(lengthToProcess, 0);
builder2.keep(lengthToProcess, 0);
}
}
} else {
skip(csOp.chars, csOp.lines);
builder.keep(csOp.chars, csOp.lines);
builder2.keep(csOp.chars, csOp.lines);
}
} else if (csOp.opcode === '+') {
builder.keep(csOp.chars, csOp.lines);
builder2.keep(csOp.chars, csOp.lines);
} else if (csOp.opcode === '-') {
const textBank = nextText(csOp.chars);
let textBankIndex = 0;
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;
});
}
}
return Changeset.checkRep(builder.toString());
return checkRep(builder.toString());
};
// export the constructor

9
src/package-lock.json generated
View file

@ -40,7 +40,7 @@
"rehype-minify-whitespace": "^5.0.1",
"request": "2.88.2",
"resolve": "1.22.2",
"security": "1.0.0",
"security": "^1.0.0",
"semver": "^7.5.2",
"socket.io": "^2.4.1",
"superagent": "^8.0.9",
@ -62,6 +62,7 @@
"@types/jquery": "^3.5.16",
"@types/js-cookie": "^3.0.3",
"@types/node": "^20.3.1",
"@types/underscore": "^1.11.5",
"concurrently": "^8.2.0",
"eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13",
@ -1227,6 +1228,12 @@
"@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": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",

View file

@ -61,7 +61,7 @@
"rehype-minify-whitespace": "^5.0.1",
"request": "2.88.2",
"resolve": "1.22.2",
"security": "1.0.0",
"security": "^1.0.0",
"semver": "^7.5.2",
"socket.io": "^2.4.1",
"superagent": "^8.0.9",
@ -83,6 +83,7 @@
"@types/jquery": "^3.5.16",
"@types/js-cookie": "^3.0.3",
"@types/node": "^20.3.1",
"@types/underscore": "^1.11.5",
"concurrently": "^8.2.0",
"eslint": "^8.14.0",
"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 ChangesetUtils from "./ChangesetUtils.js";
import * as attributes from "./attributes.js";
import * as _ from "./underscore.js";
import _ from "underscore";
export { map, each, identity} from "underscore";
'use strict';
const lineMarkerAttribute = 'lmkr';
// 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 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)
*/
setAttributeOnLine(lineNum, attributeName, attributeValue) {
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.
*/
class AttributeMap extends Map {
pool: any;
/**
* Converts an attribute string into an AttributeMap.
*

View file

@ -1,7 +1,8 @@
import AttributeMap from "./AttributeMap.js";
import AttributePool from "./AttributePool.js";
import {AttributePool} from "./AttributePool";
import * as attributes from "./attributes.js";
import { padutils } from "./pad_utils.js";
import {CustomError} from "../../node/utils/customError";
'use strict';
/**
* 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
*/
const error = (msg) => {
const e = new Error(msg);
const e = new CustomError(msg);
e.easysync = true;
throw e;
};
@ -42,6 +43,10 @@ const assert = (b, msg) => {
* An operation to apply to a shared document.
*/
class Op {
chars: number;
opcode: string;
lines: number;
attribs: string;
/**
* @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
*/
@ -113,6 +118,8 @@ class Op {
* @deprecated Use `deserializeOps` instead.
*/
class OpIter {
private _gen: any;
private _next: any;
/**
* @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.
*/
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).
*/
@ -362,7 +375,7 @@ class TextLinesMutator {
* @param {number} L -
* @param {boolean} includeInSplice - Indicates that attributes are present.
*/
skipLines(L, includeInSplice) {
skipLines(L, includeInSplice?) {
if (!L)
return;
if (includeInSplice) {
@ -906,7 +919,7 @@ export const mergingOpAssembler = () => {
/**
* @param {boolean} [isEndDocument]
*/
const flush = (isEndDocument) => {
const flush = (isEndDocument?: boolean) => {
if (!bufOp.opcode)
return;
if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
@ -1325,7 +1338,7 @@ export const attributeTester = (attribPair, pool) => {
return (attribs) => re.test(attribs);
};
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)
throw new RangeError(`start index must be non-negative (is ${start})`);
if (ndel < 0)
@ -1346,7 +1359,7 @@ export const makeSplice = (orig, start, ndel, ins, attribs, pool) => {
assem.endDocument();
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 newEndChar = endChar;
let lengthChangeSoFar = 0;
@ -1451,7 +1464,7 @@ export const mapAttribNumbers = (cs, func) => {
});
return newUpToDollar + cs.substring(dollarPos);
};
export const makeAText = (text, attribs) => ({
export const makeAText = (text, attribs?) => ({
text,
attribs: (attribs || exports.makeAttribution(text)),
});
@ -1538,7 +1551,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs.
* @returns {Builder} this
*/
keep: (N, L, attribs, pool) => {
keep: (N, L, attribs?, pool?) => {
o.opcode = '=';
o.attribs = typeof attribs === 'string'
? attribs : new AttributeMap(pool).update(attribs || []).toString();
@ -1555,7 +1568,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs.
* @returns {Builder} this
*/
keepText: (text, attribs, pool) => {
keepText: (text, attribs?, pool?) => {
for (const op of opsFromText('=', text, attribs, pool))
assem.append(op);
return self;
@ -1568,7 +1581,7 @@ export const builder = (oldLen) => {
* attribute key, value pairs.
* @returns {Builder} this
*/
insert: (text, attribs, pool) => {
insert: (text, attribs, pool?) => {
for (const op of opsFromText('+', text, attribs, pool))
assem.append(op);
charBank.append(text);
@ -1580,7 +1593,7 @@ export const builder = (oldLen) => {
* character must be a newline.
* @returns {Builder} this
*/
remove: (N, L) => {
remove: (N, L?) => {
o.opcode = '-';
o.attribs = '';
o.chars = N;
@ -1605,7 +1618,7 @@ export const makeAttribsString = (opcode, attribs, pool) => {
return attribs;
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);
let attOpsNext = attOps.next();
const assem = exports.smartOpAssembler();

View file

@ -10,7 +10,7 @@ export const buildRemoveRange = (rep, builder, start, end) => {
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 endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0]) {

View file

@ -8,6 +8,10 @@ const { padutils: { warnDeprecated } } = { padutils };
* Supports serialization to JSON.
*/
class ChatMessage {
private text: any;
authorId: any;
private time: any;
displayName: null;
static fromObject(obj) {
// 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.
@ -83,9 +87,10 @@ class ChatMessage {
// doesn't support authorId and displayName.
toJSON() {
const { authorId, displayName, ...obj } = this;
obj.userId = authorId;
obj.userName = displayName;
return obj;
let objExtendable = obj as any
objExtendable.userId = authorId;
objExtendable.userName = displayName;
return objExtendable;
}
}
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 {Ace2EditorInfo, AceDocType} from "../module/Ace2EditorInfo";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {CustomWindow} from "../module/CustomWindow";
import {CustomElementWithSheet, CustomWindow} from "../module/CustomWindow";
import {required} from "../../node/eejs";
'use strict';
const makeCSSManager = { makeCSSManager: makeCSSManager$0 }.makeCSSManager;
@ -244,17 +245,17 @@ const Ace2Editor = function () {
require.setLibraryURI(absUrl('../javascripts/lib'));
require.setGlobalKeyPath('require');
// intentially moved before requiring client_plugins to save a 307
innerWindow.Ace2Inner = require('ep_etherpad-lite/static/js/ace2_inner');
innerWindow.plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins');
innerWindow.Ace2Inner = required('ep_etherpad-lite/static/js/ace2_inner');
innerWindow.plugins = required('ep_etherpad-lite/static/js/pluginfw/client_plugins');
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');
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()');
await innerWindow.Ace2Inner.init(info, {
inner: makeCSSManager(innerStyle.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');
loaded = true;

View file

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

View file

@ -1,12 +1,15 @@
import { makeCSSManager as makeCSSManager$0 } from "./cssmanager.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 attributes from "./attributes.js";
import { linestylefilter as linestylefilter$0 } from "./linestylefilter.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 {clientVars} from "../../node/handler/PadMessageHandler";
import {Author, AuthorData, CustomElementWithSheet, CustomWindow} from "../module/CustomWindow";
import {i18nextvar} from "./vendors/i18next";
'use strict';
/**
* 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),
currentDivs: null,
// 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),
// generates a jquery element containing HTML for a line
targetRevision: undefined,
lineToElement(line, aline) {
const element = document.createElement('div');
const emptyLine = (line === '\n');
@ -64,7 +68,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
domInfo.prepareForAdd();
element.className = domInfo.node.className;
element.innerHTML = domInfo.node.innerHTML;
element.id = Math.random();
element.id = String(Math.random());
return $(element);
},
// splice the lines
@ -172,11 +176,12 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
padContents.currentRevision = revision;
padContents.currentTime += timeDelta * 1000;
updateTimer();
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
};
const loadedNewChangeset = (changesetForward, changesetBackward, revision, timeDelta) => {
const revisionInfo = window.revisionInfo;
const revisionInfo = (window as unknown as CustomWindow).revisionInfo;
const broadcasting = (BroadcastSlider.getSliderPosition() === revisionInfo.latest);
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
BroadcastSlider.setSliderLength(revisionInfo.latest);
@ -203,7 +208,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const hours = zpad(date.getHours(), 2);
const minutes = zpad(date.getMinutes(), 2);
const seconds = zpad(date.getSeconds(), 2);
return (html10n.get('timeslider.dateformat', {
return (i18nextvar('timeslider.dateformat', {
day,
month,
year,
@ -213,21 +218,21 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}));
};
$('#timer').html(dateFormat());
const revisionDate = html10n.get('timeslider.saved', {
const revisionDate = i18nextvar('timeslider.saved', {
day: date.getDate(),
month: [
html10n.get('timeslider.month.january'),
html10n.get('timeslider.month.february'),
html10n.get('timeslider.month.march'),
html10n.get('timeslider.month.april'),
html10n.get('timeslider.month.may'),
html10n.get('timeslider.month.june'),
html10n.get('timeslider.month.july'),
html10n.get('timeslider.month.august'),
html10n.get('timeslider.month.september'),
html10n.get('timeslider.month.october'),
html10n.get('timeslider.month.november'),
html10n.get('timeslider.month.december'),
i18nextvar('timeslider.month.january'),
i18nextvar('timeslider.month.february'),
i18nextvar('timeslider.month.march'),
i18nextvar('timeslider.month.april'),
i18nextvar('timeslider.month.may'),
i18nextvar('timeslider.month.june'),
i18nextvar('timeslider.month.july'),
i18nextvar('timeslider.month.august'),
i18nextvar('timeslider.month.september'),
i18nextvar('timeslider.month.october'),
i18nextvar('timeslider.month.november'),
i18nextvar('timeslider.month.december'),
][date.getMonth()],
year: date.getFullYear(),
});
@ -236,7 +241,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
updateTimer();
const goToRevision = (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', {
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)
loadChangesetsForRevision(padContents.currentRevision - 1);
}
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
};
const loadChangesetsForRevision = (revision, callback) => {
const loadChangesetsForRevision = (revision, callback?) => {
if (BroadcastSlider.getSliderLength() > 10000) {
const start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10
changesetLoader.queueUp(start, 100);
@ -348,7 +354,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
setTimeout(() => this.loadFromQueue(), 10);
},
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++) {
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
@ -357,7 +363,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// debugLog("adding changeset:", astart, aend);
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
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)
callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
@ -366,15 +372,16 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
if (obj.type === 'COLLABROOM') {
obj = obj.data;
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);
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);
}
else if (obj.type === 'NEW_AUTHORDATA') {
const authorMap = {};
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
// @ts-ignore
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
@ -412,13 +419,15 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
goToRevisionIfEnabledCount--;
}
else {
//FIXME what to do?
// @ts-ignore
goToRevision(...args);
}
};
}
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 receiveAuthorData = (newAuthorData) => {
const receiveAuthorData = (newAuthorData: Author) => {
for (const [author, data] of Object.entries(newAuthorData)) {
const bgcolor = typeof data.colorId === 'number'
? 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.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
import {clientVars} from "../../node/handler/PadMessageHandler";
import {CustomWindow} from "../module/CustomWindow";
/**
* Copyright 2009 Google Inc.
*
@ -36,7 +39,12 @@ const loadBroadcastRevisionsJS = () => {
this.changesets.push(changesetWrapper);
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) {
const startRevision = this[fromIndex] || this.createNew(fromIndex);
const endRevision = this[toIndex] || this.createNew(toIndex);
@ -95,6 +103,6 @@ const loadBroadcastRevisionsJS = () => {
times,
};
};
window.revisionInfo = revisionInfo;
(window as unknown as CustomWindow).revisionInfo = revisionInfo;
};
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 { 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';
const padmodals = { padmodals: padmodals$0 }.padmodals;
const colorutils = { colorutils: colorutils$0 }.colorutils;
const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
let BroadcastSlider;
// Hack to ensure timeslider i18n values are in
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get('timeslider.toolbar.returnbutton'));
$("[data-key='timeslider_returnToPad'] > a > span").html(i18nextvar('timeslider.toolbar.returnbutton'));
(() => {
let sliderLength = 1000;
let sliderPos = 0;
@ -56,7 +60,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$('a.tlink').map(function () {
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
});
$('#revision_label').html(html10n.get('timeslider.version', { version: newpos }));
$('#revision_label').html(i18nextvar('timeslider.version', { version: newpos }));
$('#leftstar, #leftstep').toggleClass('disabled', newpos === 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos === sliderLength);
sliderPos = newpos;
@ -100,7 +104,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
}
});
if (numAnonymous > 0) {
const anonymousAuthorString = html10n.get('timeslider.unnamedauthors', { num: numAnonymous });
const anonymousAuthorString = i18nextvar('timeslider.unnamedauthors', { num: numAnonymous });
if (numNamed !== 0) {
authorsList.append(` + ${anonymousAuthorString}`);
}
@ -121,7 +125,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
}
}
if (authors.length === 0) {
authorsList.append(html10n.get('timeslider.toolbar.authorsList'));
authorsList.append(i18nextvar('timeslider.toolbar.authorsList'));
}
};
const playButtonUpdater = () => {
@ -163,7 +167,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup((e) => {
if (!e)
e = window.event;
e = window.event as any
const code = e.keyCode || e.which;
if (code === 37) { // left
if (e.shiftKey) {
@ -196,19 +200,19 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
});
// Slider dragging
$('#ui-slider-handle').mousedown(function (evt) {
this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left'));
(this as unknown as CustomElementWithSheet).startLoc = evt.clientX;
(this as unknown as CustomElementWithSheet).currentLoc = parseInt($(this).css('left'));
sliderActive = true;
$(document).mousemove((evt2) => {
$(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)
newloc = 0;
const maxPos = $('#ui-slider-bar').width() - 2;
if (newloc > maxPos)
newloc = 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);
if (getSliderPosition() !== version)
_callSliderCallbacks(version);
@ -217,7 +221,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$(document).unbind('mousemove');
$(document).unbind('mouseup');
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)
newloc = 0;
const maxPos = $('#ui-slider-bar').width() - 2;
@ -229,7 +233,7 @@ const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
$(this).css('left', '2px');
}
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';
import {CustomElementWithSheet} from "../module/CustomWindow";
const createSelectionRange = (range) => {
const clonedRange = range.cloneRange();
// 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 = () => {
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;
// 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

View file

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

View file

@ -1,6 +1,8 @@
import { chat as chat$0 } from "./chat.js";
import * as hooks from "./pluginfw/hooks.js";
import browser from "./vendors/browser.js";
import {clientVars} from "../../node/handler/PadMessageHandler";
import {AuthorData} from "../module/CustomWindow";
'use strict';
/**
* 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;
let isPendingRevision = false;
const callbacks = {
onUserJoin: () => { },
onUserLeave: () => { },
onUpdateUserInfo: () => { },
onChannelStateChange: () => { },
onClientMessage: () => { },
onInternalAction: () => { },
onConnectionTrouble: () => { },
onServerMessage: () => { },
onUserJoin: (arg?) => { },
onUserLeave: (arg?) => { },
onUpdateUserInfo: (arg?) => { },
onChannelStateChange: (arg?, addArg?) => { },
onClientMessage: (arg?) => { },
onInternalAction: (arg?) => { },
onConnectionTrouble: (arg?) => { },
onServerMessage: (arg?) => { },
};
if (browser.firefox) {
// 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 {
private _promiseChain: Promise<void>;
constructor() {
this._promiseChain = Promise.resolve();
}
@ -309,7 +312,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
const tellAceActiveAuthorInfo = (userInfo) => {
tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
};
const tellAceAuthorInfo = (userId, colorId, inactive) => {
const tellAceAuthorInfo = (userId, colorId, inactive?) => {
if (typeof colorId === 'number') {
colorId = clientVars.colorPalette[colorId];
}
@ -333,11 +336,11 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
const tellAceAboutHistoricalAuthors = (hadata) => {
for (const [author, data] of Object.entries(hadata)) {
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) {
channelState = newChannelState;
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
// is connected for the first time.
let deferredActions = [];
const defer = (func, tag) => function (...args) {
const defer = (func, tag?) => function (...args) {
const action = () => {
func.call(this, ...args);
};
@ -365,7 +368,7 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
action();
}
};
const doDeferredActions = (tag) => {
const doDeferredActions = (tag?) => {
const newArray = [];
for (let i = 0; i < deferredActions.length; i++) {
const a = deferredActions[i];
@ -386,7 +389,14 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad)
};
const getCurrentRevisionNumber = () => rev;
const getMissedChanges = () => {
const obj = {};
const obj = {
furtherChangeset: undefined,
furtherChangesetAPool: undefined,
committedChangesetAPool: undefined,
committedChangeset: undefined,
userInfo: undefined,
baseRev: undefined
};
obj.userInfo = userSet[userId];
obj.baseRev = rev;
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 UNorm from "unorm";
import * as Changeset from "./Changeset.js";
@ -51,7 +53,7 @@ const supportedElems = new Set([
'u',
'ul',
]);
const makeContentCollector = (collectStyles, abrowser, apool, className2Author) => {
const makeContentCollector = (collectStyles, abrowser, apool?, className2Author?) => {
const _blockElems = {
div: 1,
p: 1,
@ -101,7 +103,380 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
self.startNew();
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) => {
if (!lines.atColumnZero()) {
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).
// can be called at any point
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 { makeContentCollector };
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';
/* eslint-disable-next-line max-len */
// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0
/**
* Copyright 2011 Peter Martischka, Primary Technology.
* Copyright 2020 Richard Hansen
@ -17,6 +18,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FIXME Why should this variable be a rename?
// @ts-ignore
// @ts-ignore
const randomPadName = () => {
// the number of distinct chars (64) is chosen to ensure that the selection will be uniform when
// using the PRNG below
@ -26,7 +31,7 @@ const randomPadName = () => {
// make room for 8-bit integer values that span from 0 to 255.
const randomarray = new Uint8Array(stringLength);
// 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);
let randomstring = '';
for (let i = 0; i < stringLength; i++) {
@ -40,8 +45,8 @@ const randomPadName = () => {
$(() => {
$('#go2Name').submit(() => {
const padname = $('#padname').val();
if (padname.length > 0) {
window.location = `p/${encodeURIComponent(padname.trim())}`;
if (typeof padname === "string" && padname.length > 0) {
window.location.href = `p/${encodeURIComponent(padname.trim())}`;
}
else {
alert('Please enter a name');
@ -49,9 +54,9 @@ $(() => {
return false;
});
$('#button').click(() => {
window.location = `p/${randomPadName()}`;
window.location.href = `p/${randomPadName()}`;
});
// start the custom js
if (typeof window.customStart === 'function')
if ("customStart" in window && typeof window.customStart === 'function')
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 { padutils as padutils$0 } from "./pad_utils.js";
'use strict';
const linestylefilter = {};
const padutils = { padutils: padutils$0 }.padutils;
linestylefilter.ATTRIB_CLASSES = {
bold: 'tag:b',
italic: 'tag:i',
underline: 'tag:u',
strikethrough: 'tag:s',
};
const lineAttributeMarker = 'lineAttribMarker';
linestylefilter.getAuthorClassName = (author) => `author-${author.replace(/[^a-y0-9]/g, (c) => {
if (c === '.')
return '-';
return `z${c.charCodeAt(0)}z`;
})}`;
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool) => {
// @ts-ignore
const linestylefilter = {
ATTRIB_CLASSES: {
bold: 'tag:b',
italic: 'tag:i',
underline: 'tag:u',
strikethrough: 'tag:s',
},
getAuthorClassName: (author) => `author-${author.replace(/[^a-y0-9]/g, (c) => {
if (c === '.')
return '-';
return `z${c.charCodeAt(0)}z`;
})}`,
getLineStyleFilter: (lineLength, aline, textAndClassFunc, apool) => {
// Plugin Hook to add more Attrib Classes
for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) {
Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses);
@ -115,126 +116,132 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
};
})();
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = (lineText, textAndClassFunc) => {
const at = /@/g;
at.lastIndex = 0;
let splitPoints = null;
let execResult;
while ((execResult = at.exec(lineText))) {
if (!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];
},
getAtSignSplitterFilter: (lineText, textAndClassFunc) => {
const at = /@/g;
at.lastIndex = 0;
let splitPoints = null;
let execResult;
while ((execResult = at.exec(lineText))) {
if (!splitPoints) {
splitPoints = [];
}
splitPoints.push(execResult.index);
}
return false;
};
const handleRegExpMatchsAfterSplit = (() => {
let curIndex = 0;
return (txt, cls) => {
const txtlen = txt.length;
let newCls = cls;
const regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) {
newCls += ` ${tag}:${regExpMatch}`;
if (!splitPoints)
return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
},
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 = [];
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
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];
}
}
return false;
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
};
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(padutils.urlRegex, 'url');
linestylefilter.textAndClassFuncSplitter = (func, splitPointsOpt) => {
let nextPointIndex = 0;
let idx = 0;
// don't split at 0
while (splitPointsOpt &&
const handleRegExpMatchsAfterSplit = (() => {
let curIndex = 0;
return (txt, cls) => {
const txtlen = txt.length;
let newCls = cls;
const regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) {
newCls += ` ${tag}:${regExpMatch}`;
}
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 &&
splitPointsOpt[nextPointIndex] === 0) {
nextPointIndex++;
}
const spanHandler = (txt, cls) => {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
func(txt, cls);
idx += txt.length;
nextPointIndex++;
}
else {
const splitPoints = splitPointsOpt;
const pointLocInSpan = splitPoints[nextPointIndex] - idx;
const txtlen = txt.length;
if (pointLocInSpan >= txtlen) {
const spanHandler = (txt, cls) => {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
func(txt, cls);
idx += txt.length;
if (pointLocInSpan === txtlen) {
nextPointIndex++;
}
}
else {
if (pointLocInSpan > 0) {
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
const splitPoints = splitPointsOpt;
const pointLocInSpan = splitPoints[nextPointIndex] - idx;
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);
}
};
return spanHandler;
};
linestylefilter.getFilterStack = (lineText, textAndClassFunc, abrowser) => {
let func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
const hookFilters = hooks.callAll('aceGetFilterStack', {
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 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 { linestylefilter };

View file

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

View file

@ -1,4 +1,7 @@
'use strict';
import {i18nextvar} from "./vendors/i18next";
import {clientVars} from "../../node/handler/PadMessageHandler";
const createCountDownElementsIfNecessary = ($modal) => {
const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
if (elementsDoNotExist) {
@ -24,7 +27,7 @@ const createCountDownElementsIfNecessary = ($modal) => {
}
};
const localize = ($element) => {
html10n.translateElement(html10n.translations, $element.get(0));
i18nextvar($element.get(0));
};
const createTimerForModal = ($modal, pad) => {
const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
@ -87,7 +90,7 @@ const reconnectionTries = {
// duration: how many **seconds** until the timer ends
// granularity (optional): how many **milliseconds**
// between each 'tick' of timer. Default: 1000ms (1s)
const CountDownTimer = function (duration, granularity) {
const CountDownTimer = function (duration, granularity?) {
this.duration = duration;
this.granularity = granularity || 1000;
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
* limitations under the License.
*/
type Pad_connectionStatus = {
what: string,
why?: string
}
const padmodals = { padmodals: padmodals$0 }.padmodals;
const padconnectionstatus = (() => {
let status = {
let status:Pad_connectionStatus = {
what: 'connecting',
why: undefined
};
const self = {
init: () => {

View file

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

View file

@ -1,3 +1,5 @@
// @ts-nocheck
//FIXME lots of errors
import browser from "./vendors/browser.js";
import * as hooks from "./pluginfw/hooks.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 { padcookie as padcookie$0 } from "./pad_cookie.js";
'use strict';

View file

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

View file

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

View file

@ -1,5 +1,9 @@
import { padutils as padutils$0 } from "./pad_utils.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';
/**
* Copyright 2009 Google Inc.
@ -17,7 +21,12 @@ import * as hooks from "./pluginfw/hooks.js";
* limitations under the License.
*/
const padutils = { padutils: padutils$0 }.padutils;
let myUserInfo = {};
let myUserInfo = {
name: undefined, userId: undefined,
colorId: undefined
};
let colorPickerOpen = false;
let colorPickerSetup = false;
const paduserlist = (() => {
@ -83,7 +92,7 @@ const paduserlist = (() => {
handleOtherUserInputs();
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);
if (power === 2)
a **= 2;
@ -138,7 +147,7 @@ const paduserlist = (() => {
.attr('type', 'text')
.addClass('editempty')
.addClass('newinput')
.attr('value', html10n.get('pad.userlist.unnamed'));
.attr('value', i18nextvar('pad.userlist.unnamed'));
if (isNameEditable(data))
name.attr('disabled', 'disabled');
}
@ -148,7 +157,7 @@ const paduserlist = (() => {
.addClass('usertdswatch')
.append($('<div>')
.addClass('swatch')
.css('background', padutils.escapeHtml(data.color))
.css('background', padutils.escapeHtml(data.color) as unknown as string)
.html('&nbsp;')))
.add($('<td>')
.css('height', `${height}px`)
@ -187,7 +196,7 @@ const paduserlist = (() => {
}).removeClass('newinput');
};
// 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));
animationPower = (animationPower === undefined ? 4 : animationPower);
const domId = nextRowId();
@ -237,7 +246,7 @@ const paduserlist = (() => {
}
}
};
const removeRow = (position, animationPower) => {
const removeRow = (position, animationPower?) => {
animationPower = (animationPower === undefined ? 4 : animationPower);
const row = rowsPresent[position];
if (row) {
@ -257,7 +266,7 @@ const paduserlist = (() => {
}
};
// 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
const row = rowsPresent[oldPosition];
if (row && oldPosition !== newPosition) {
@ -288,7 +297,7 @@ const paduserlist = (() => {
}, (newName) => {
if (!newName) {
jnode.addClass('editempty');
jnode.val(html10n.get('pad.userlist.unnamed'));
jnode.val(i18nextvar('pad.userlist.unnamed'));
}
else {
jnode.attr('disabled', 'disabled');
@ -372,11 +381,12 @@ const paduserlist = (() => {
const userList = self.usersOnline();
// Now we add historical authors
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
let exists = false;
userList.forEach((user) => {
if (user.userId === userId)
if (user.userId === resMapped.userId)
exists = true;
});
if (exists === false) {
@ -401,18 +411,20 @@ const paduserlist = (() => {
hooks.callAll('userJoinOrUpdate', {
userInfo: info,
});
const userData = {};
userData.color = typeof info.colorId === 'number'
? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name;
userData.status = '';
userData.activity = '';
userData.id = info.userId;
const userData = {
status: '',
name: info.name,
activity: '',
id: info.userId,
color: typeof info.colorId === 'number'
? clientVars.colorPalette[info.colorId] : info.colorId
};
const existingIndex = findExistingIndex(info.userId);
let numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) {
numUsersBesides--;
}
//@ts-ignore
const newIndex = padutils.binarySearch(numUsersBesides, (n) => {
if (existingIndex >= 0 && n >= existingIndex) {
// pretend existingIndex isn't there
@ -495,7 +507,7 @@ const paduserlist = (() => {
$('#myusernameedit').removeClass('editempty').val(myUserInfo.name);
}
else {
$('#myusernameedit').attr('placeholder', html10n.get('pad.userlist.entername'));
$('#myusernameedit').attr('placeholder', i18nextvar('pad.userlist.entername'));
}
if (colorPickerOpen) {
$('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
@ -536,7 +548,7 @@ const closeColorPicker = (accept) => {
$('#mycolorpicker').removeClass('popup-show');
};
const showColorPicker = () => {
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId);
($ as unknown as JQueryGritter).farbtastic('#colorpicker').setColor(myUserInfo.colorId);
if (!colorPickerOpen) {
const palette = pad.getColorPalette();
if (!colorPickerSetup) {

View file

@ -1,3 +1,6 @@
'use strict';
/**
@ -22,7 +25,9 @@
* 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,
@ -137,7 +142,7 @@ export const padutils = {
}
},
escapeHtml: (x) => Security.escapeHTML(String(x)),
escapeHtml: (x) => escapeHTML(String(x)),
uniqueId: () => {
const pad = require('./pad').pad; // Sidestep circular dependency
// returns string that is exactly 'width' chars, padding with zeros and taking rightmost digits
@ -198,7 +203,7 @@ export const padutils = {
const advanceTo = (i) => {
if (i > idx) {
pieces.push(Security.escapeHTML(text.substring(idx, i)));
pieces.push(escapeHTML(text.substring(idx, i)));
idx = i;
}
};
@ -216,9 +221,9 @@ export const padutils = {
// https://github.com/ether/etherpad-lite/pull/3636
pieces.push(
'<a ',
(target ? `target="${Security.escapeHTMLAttribute(target)}" ` : ''),
(target ? `target="${escapeHTMLAttribute(target)}" ` : ''),
'href="',
Security.escapeHTMLAttribute(href),
escapeHTMLAttribute(href),
'" rel="noreferrer noopener">');
advanceTo(startIndex + href.length);
pieces.push('</a>');

View file

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

View file

@ -8,13 +8,13 @@
// * hook_fn: Plugin-supplied hook function.
// * 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.
export const hooks = {};
export let hooks = {};
// Whether the plugins have been loaded.
export let loaded = false;
// 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
// augmented with additional metadata:

View file

@ -94,7 +94,7 @@ export const pathNormalization = (part, hookFnName, hookName) => {
};
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 plugins = {};
@ -107,7 +107,7 @@ export const update = async () => {
setPlugins(plugins);
setParts(sortParts(parts))
setHooks(extractHooks(parts, 'hooks', exports.pathNormalization));
setHooks(extractHooks(parts, 'hooks', pathNormalization));
setLoaded(true)
await Promise.all(Object.keys(plugins).map(async (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...');
// Notes:
// * 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 {dependencies = {}} = JSON.parse(await exportCMD(cmd, {stdio: [null, 'string']}) as unknown as string);
await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => {
if (!pkg.startsWith(exports.prefix)) {
if (!pkg.startsWith(prefix)) {
delete dependencies[pkg];
return;
}

View file

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

View file

@ -1,8 +1,9 @@
import * as caretPosition from "./caretPosition.js";
import {CustomWindow} from "../module/CustomWindow";
'use strict';
function Scroll(outerWin) {
// scroll settings
this.scrollSettings = parent.parent.clientVars.scrollWhenFocusLineIsOutOfViewport;
this.scrollSettings = (parent.parent as unknown as CustomWindow).clientVars.scrollWhenFocusLineIsOutOfViewport;
// DOM reference
this.outerWin = outerWin;
this.doc = this.outerWin.document;
@ -166,7 +167,7 @@ Scroll.prototype._getPixelsRelativeToPercentageOfViewport =
let pixels = 0;
const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) {
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport);
pixels = parseInt(String(innerHeight * scrollPercentageRelativeToViewport));
}
return pixels;
};
@ -183,7 +184,7 @@ Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function (innerHeigh
let pixels = 0;
const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) {
pixels = parseInt(innerHeight * percentageToScrollUp);
pixels = parseInt(String(innerHeight * percentageToScrollUp));
}
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;
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) {
this.key = entry != null ? entry.key : null;
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
// invalidate this point.
class Point {
private _skipList: any;
private loc: any;
private idxs: any[];
private nodes: any[];
private widthSkips: any[];
constructor(skipList, loc) {
this._skipList = skipList;
this.loc = loc;
@ -172,6 +184,10 @@ class Point {
* property that is a string.
*/
class SkipList {
private _keyToNodeMap: Map<any, any>;
private _start: Node;
private _end: Node;
private _totalWidth: number;
constructor() {
// if there are N elements in the skiplist, "start" is element -1 and "end" is element N
this._start = new Node(null, 1);
@ -199,7 +215,7 @@ class SkipList {
}
return n;
}
_getNodeIndex(node, byWidth) {
_getNodeIndex(node, byWidth?) {
let dist = (byWidth ? 0 : -1);
let n = node;
while (n !== this._start) {

View file

@ -1,4 +1,6 @@
'use strict';
import {CustomWindow} from "../module/CustomWindow";
/**
* Creates a socket.io connection.
* @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
* @return socket.io Socket object
*/
import io from 'socket.io-client';
const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
// 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
@ -15,7 +19,7 @@ const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
// 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
// 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 namespaceUrl = new URL(namespace, new URL('/', baseUrl));
return io(namespaceUrl.href, Object.assign({ path: socketioUrl.pathname }, options));
@ -23,6 +27,6 @@ const connect = (etherpadBaseUrl, namespace = '/', options = {}) => {
if (typeof exports === 'object') {
}
else {
window.socketio = { connect };
(window as unknown as CustomWindow).socketio = { 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 * as padUtils from "./pad_utils.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 _ from "./underscore.js";
import _ from "underscore";
'use strict';
const undoModule = (() => {
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 = {
revisionInfo: any;
$: any;
require: RequireFunction,
Ace2Inner:any,
plugins: any,
jQuery: any,
document: any,
pad: any,
clientVars: any,
socketio: any,
}
@ -12,3 +19,36 @@ export type RequireFunction= {
setLibraryURI: (url: 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": {
"resolveJsonModule": true,
"allowJs": true,
"typeRoots": ["node_modules/@types"],
"types": ["node", "jquery"],
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"target": "ES2015",
"outDir": "dist"
},
"lib": ["es2015"]