mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
Fixed startup to "Running npm to get a list of installed plugins"
This commit is contained in:
parent
76a6f665a4
commit
aa6323e488
63 changed files with 1536 additions and 1767 deletions
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
9
src/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
*
|
|
@ -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();
|
|
@ -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]) {
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
const msgBlock = document.createElement('blockquote');
|
||||
box.appendChild(msgBlock);
|
||||
msgBlock.style.fontWeight = 'bold';
|
||||
if (typeof msg === "string") {
|
||||
msgBlock.appendChild(document.createTextNode(msg));
|
||||
}
|
||||
const loc = document.createElement('p');
|
||||
box.appendChild(loc);
|
||||
loc.appendChild(document.createTextNode(`in ${url}`));
|
|
@ -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;
|
|
@ -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 };
|
|
@ -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'));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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
|
|
@ -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
33
src/static/js/chat.js → src/static/js/chat.ts
Executable file → Normal 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,
|
|
@ -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) {
|
|
@ -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
113
src/static/js/colorutils.ts
Normal 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 };
|
|
@ -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 };
|
|
@ -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 += ' ';
|
||||
}
|
||||
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, ' ');
|
||||
}
|
||||
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] = ' ';
|
||||
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] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) !== '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const p = parts[i];
|
||||
if (p === ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
||||
export { domline };
|
408
src/static/js/domline.ts
Normal file
408
src/static/js/domline.ts
Normal 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, ' ');
|
||||
}
|
||||
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] = ' ';
|
||||
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] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) !== '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const p = parts[i];
|
||||
if (p === ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
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 += ' ';
|
||||
}
|
||||
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 += ' ';
|
||||
}
|
||||
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 };
|
|
@ -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();
|
||||
});
|
|
@ -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);
|
|
@ -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 = {
|
||||
|
||||
// @ts-ignore
|
||||
const 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) => {
|
||||
},
|
||||
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) => {
|
||||
})}`,
|
||||
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,8 +116,8 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
|
|||
};
|
||||
})();
|
||||
return authorColorFunc;
|
||||
};
|
||||
linestylefilter.getAtSignSplitterFilter = (lineText, textAndClassFunc) => {
|
||||
},
|
||||
getAtSignSplitterFilter: (lineText, textAndClassFunc) => {
|
||||
const at = /@/g;
|
||||
at.lastIndex = 0;
|
||||
let splitPoints = null;
|
||||
|
@ -130,8 +131,8 @@ linestylefilter.getAtSignSplitterFilter = (lineText, textAndClassFunc) => {
|
|||
if (!splitPoints)
|
||||
return textAndClassFunc;
|
||||
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
|
||||
};
|
||||
linestylefilter.getRegexpFilter = (regExp, tag) => (lineText, textAndClassFunc) => {
|
||||
},
|
||||
getRegexpFilter: (regExp, tag) => (lineText, textAndClassFunc) => {
|
||||
regExp.lastIndex = 0;
|
||||
let regExpMatchs = null;
|
||||
let splitPoints = null;
|
||||
|
@ -171,9 +172,11 @@ linestylefilter.getRegexpFilter = (regExp, tag) => (lineText, textAndClassFunc)
|
|||
};
|
||||
})();
|
||||
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
|
||||
};
|
||||
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(padutils.urlRegex, 'url');
|
||||
linestylefilter.textAndClassFuncSplitter = (func, splitPointsOpt) => {
|
||||
},
|
||||
getURLFilter: ()=>{
|
||||
return linestylefilter.getRegexpFilter(padutils.urlRegex, 'url')
|
||||
},
|
||||
textAndClassFuncSplitter: (func, splitPointsOpt) => {
|
||||
let nextPointIndex = 0;
|
||||
let idx = 0;
|
||||
// don't split at 0
|
||||
|
@ -210,9 +213,9 @@ linestylefilter.textAndClassFuncSplitter = (func, splitPointsOpt) => {
|
|||
}
|
||||
};
|
||||
return spanHandler;
|
||||
};
|
||||
linestylefilter.getFilterStack = (lineText, textAndClassFunc, abrowser) => {
|
||||
let func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
|
||||
},
|
||||
getFilterStack: (lineText, textAndClassFunc, abrowser?) => {
|
||||
let func = linestylefilter.getURLFilter();
|
||||
const hookFilters = hooks.callAll('aceGetFilterStack', {
|
||||
linestylefilter,
|
||||
browser: abrowser,
|
||||
|
@ -221,9 +224,8 @@ linestylefilter.getFilterStack = (lineText, textAndClassFunc, abrowser) => {
|
|||
func = hookFilter(lineText, func);
|
||||
});
|
||||
return func;
|
||||
};
|
||||
// domLineObj is like that returned by domline.createDomLine
|
||||
linestylefilter.populateDomLine = (textLine, aline, apool, domLineObj) => {
|
||||
},
|
||||
populateDomLine: (textLine, aline, apool, domLineObj) => {
|
||||
// remove final newline from text if any
|
||||
let text = textLine;
|
||||
if (text.slice(-1) === '\n') {
|
||||
|
@ -235,6 +237,11 @@ linestylefilter.populateDomLine = (textLine, aline, apool, domLineObj) => {
|
|||
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 };
|
|
@ -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 = '';
|
|
@ -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;
|
|
@ -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: () => {
|
|
@ -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) {
|
|
@ -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";
|
|
@ -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';
|
|
@ -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`);
|
|
@ -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,
|
|
@ -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(' ')))
|
||||
.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) {
|
|
@ -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>');
|
||||
|
|
|
@ -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;
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
|
@ -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;
|
||||
};
|
|
@ -1,2 +0,0 @@
|
|||
'use strict';
|
||||
export * from "security";
|
10
src/static/js/security.ts
Normal file
10
src/static/js/security.ts
Normal 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.");
|
||||
}
|
||||
|
|
@ -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) {
|
|
@ -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 };
|
|
@ -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";
|
|
@ -1,2 +0,0 @@
|
|||
'use strict';
|
||||
export { default } from "underscore";
|
|
@ -1,5 +1,5 @@
|
|||
import * as Changeset from "./Changeset.js";
|
||||
import * as _ from "./underscore.js";
|
||||
import _ from "underscore";
|
||||
'use strict';
|
||||
const undoModule = (() => {
|
||||
const stack = (() => {
|
943
src/static/js/vendors/html10n.js
vendored
943
src/static/js/vendors/html10n.js
vendored
|
@ -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
15
src/static/js/vendors/i18next.ts
vendored
Normal 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
|
||||
});
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue