Added POC for browser

This commit is contained in:
SamTV12345 2024-07-13 20:40:56 +02:00 committed by SamTv12345
parent 6d73fed7b6
commit be2616f766
12 changed files with 117 additions and 104 deletions

View file

@ -10,6 +10,9 @@ const settings = require('../../utils/Settings');
const util = require('util'); const util = require('util');
const webaccess = require('./webaccess'); const webaccess = require('./webaccess');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
import {hash, createHash} from 'node:crypto'
import {buildSync} from 'esbuild' import {buildSync} from 'esbuild'
exports.expressPreSession = async (hookName:string, {app}:any) => { exports.expressPreSession = async (hookName:string, {app}:any) => {
// This endpoint is intended to conform to: // This endpoint is intended to conform to:
@ -94,18 +97,30 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
})(), })(),
settings, settings,
})); }));
const hash = createHash('sha256').update(fs.readFileSync(path.join(settings.root, 'var/js/padbootstrap.js'))).digest('hex');
const fileName = `padbootstrap-${hash.substring(0,16)}.min.js`
const result = buildSync({ const result = buildSync({
entryPoints: [settings.root + "/src/templates/padBootstrap.js"], // Entry file(s) entryPoints: [settings.root + "/var/js/padbootstrap.js"], // Entry file(s)
bundle: true, // Bundle the files together bundle: true, // Bundle the files together
minify: true, // Minify the output minify: false, // Minify the output
sourcemap: true, // Generate source maps sourcemap: true, // Generate source maps
sourceRoot: settings.root+"/src/static/js/", sourceRoot: settings.root+"/src/static/js/",
target: ['es2020'], // Target ECMAScript version target: ['es2020'], // Target ECMAScript version
write: false, // Do not write to file system, metafile: true,
write: true, // Do not write to file system,
outfile: settings.root + `/var/js/${fileName}`, // Output file
}) })
const textResult = result.outputFiles[0].text
args.app.get(`/${fileName}`, (req: any, res: any) => {
res.sendFile(settings.root+`/var/js/${fileName}`)
})
args.app.get(`/${fileName}.map`, (req: any, res: any) => {
res.sendFile(settings.root+`/var/js/${fileName}.map`)
})
// serve pad.html under /p // serve pad.html under /p
@ -115,7 +130,7 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
hooks.callAll('padInitToolbar', { hooks.callAll('padInitToolbar', {
toolbar, toolbar,
isReadOnly, isReadOnly
}); });
// can be removed when require-kernel is dropped // can be removed when require-kernel is dropped
@ -124,6 +139,7 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
req, req,
toolbar, toolbar,
isReadOnly, isReadOnly,
entrypoint: "/"+fileName
})); }));
}); });
@ -145,6 +161,4 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
// express-session automatically calls req.session.touch() so we don't need to do it here. // express-session automatically calls req.session.touch() so we don't need to do it here.
res.json({status: 'ok'}); res.json({status: 'ok'});
}); });
return cb();
}; };

View file

@ -4,7 +4,7 @@ const AttributeMap = require('./AttributeMap');
const Changeset = require('./Changeset'); const Changeset = require('./Changeset');
const ChangesetUtils = require('./ChangesetUtils'); const ChangesetUtils = require('./ChangesetUtils');
const attributes = require('./attributes'); const attributes = require('./attributes');
const _ = require('./underscore'); const underscore = require("underscore")
const lineMarkerAttribute = 'lmkr'; const lineMarkerAttribute = 'lmkr';
@ -45,7 +45,7 @@ const AttributeManager = function (rep, applyChangesetCallback) {
AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES; AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES;
AttributeManager.lineAttributes = lineAttributes; AttributeManager.lineAttributes = lineAttributes;
AttributeManager.prototype = _(AttributeManager.prototype).extend({ AttributeManager.prototype = underscore.default(AttributeManager.prototype).extend({
applyChangeset(changeset) { applyChangeset(changeset) {
if (!this.applyChangesetCallback) return changeset; if (!this.applyChangesetCallback) return changeset;
@ -335,7 +335,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1]) const countAttribsWithMarker = underscore.chain(attribs).filter((a) => !!a[1])
.map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value(); .map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
// if we have marker and any of attributes don't need to have marker. we need delete it // if we have marker and any of attributes don't need to have marker. we need delete it

View file

@ -27,9 +27,10 @@
const hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
const makeCSSManager = require('./cssmanager').makeCSSManager; const makeCSSManager = require('./cssmanager').makeCSSManager;
const pluginUtils = require('./pluginfw/shared'); const pluginUtils = require('./pluginfw/shared');
const ace2_inner = require('ep_etherpad-lite/static/js/ace2_inner')
const debugLog = (...args) => {}; const debugLog = (...args) => {};
const cl_plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins')
const rJQuery = require('ep_etherpad-lite/static/js/rjquery')
// The inner and outer iframe's locations are about:blank, so relative URLs are relative to that. // The inner and outer iframe's locations are about:blank, so relative URLs are relative to that.
// Firefox and Chrome seem to do what the developer intends if given a relative URL, but Safari // Firefox and Chrome seem to do what the developer intends if given a relative URL, but Safari
// errors out unless given an absolute URL for a JavaScript-created element. // errors out unless given an absolute URL for a JavaScript-created element.
@ -99,6 +100,7 @@ const Ace2Editor = function () {
}; };
const doActionsPendingInit = () => { const doActionsPendingInit = () => {
console.log('doActionsPendingInit', actionsPendingInit)
for (const fn of actionsPendingInit) fn(); for (const fn of actionsPendingInit) fn();
actionsPendingInit = []; actionsPendingInit = [];
}; };
@ -257,19 +259,19 @@ const Ace2Editor = function () {
// <head> tag // <head> tag
addStyleTagsFor(innerDocument, includedCSS); addStyleTagsFor(innerDocument, includedCSS);
const requireKernel = innerDocument.createElement('script'); //const requireKernel = innerDocument.createElement('script');
requireKernel.type = 'text/javascript'; //requireKernel.type = 'text/javascript';
requireKernel.src = //requireKernel.src =
absUrl(`../static/js/require-kernel.js?v=${clientVars.randomVersionString}`); // absUrl(`../static/js/require-kernel.js?v=${clientVars.randomVersionString}`);
innerDocument.head.appendChild(requireKernel); //innerDocument.head.appendChild(requireKernel);
// Pre-fetch modules to improve load performance. // Pre-fetch modules to improve load performance.
for (const module of ['ace2_inner', 'ace2_common']) { /*for (const module of ['ace2_inner', 'ace2_common']) {
const script = innerDocument.createElement('script'); const script = innerDocument.createElement('script');
script.type = 'text/javascript'; script.type = 'text/javascript';
script.src = absUrl(`../javascripts/lib/ep_etherpad-lite/static/js/${module}.js` + script.src = absUrl(`../javascripts/lib/ep_etherpad-lite/static/js/${module}.js` +
`?callback=require.define&v=${clientVars.randomVersionString}`); `?callback=require.define&v=${clientVars.randomVersionString}`);
innerDocument.head.appendChild(script); innerDocument.head.appendChild(script);
} }*/
const innerStyle = innerDocument.createElement('style'); const innerStyle = innerDocument.createElement('style');
innerStyle.type = 'text/css'; innerStyle.type = 'text/css';
innerStyle.title = 'dynamicsyntax'; innerStyle.title = 'dynamicsyntax';
@ -284,7 +286,7 @@ const Ace2Editor = function () {
innerDocument.body.classList.add('innerdocbody'); innerDocument.body.classList.add('innerdocbody');
innerDocument.body.setAttribute('spellcheck', 'false'); innerDocument.body.setAttribute('spellcheck', 'false');
innerDocument.body.appendChild(innerDocument.createTextNode('\u00A0')); // &nbsp; innerDocument.body.appendChild(innerDocument.createTextNode('\u00A0')); // &nbsp;
/*
debugLog('Ace2Editor.init() waiting for require kernel load'); debugLog('Ace2Editor.init() waiting for require kernel load');
await eventFired(requireKernel, 'load'); await eventFired(requireKernel, 'load');
debugLog('Ace2Editor.init() require kernel loaded'); debugLog('Ace2Editor.init() require kernel loaded');
@ -292,17 +294,16 @@ const Ace2Editor = function () {
require.setRootURI(absUrl('../javascripts/src')); require.setRootURI(absUrl('../javascripts/src'));
require.setLibraryURI(absUrl('../javascripts/lib')); require.setLibraryURI(absUrl('../javascripts/lib'));
require.setGlobalKeyPath('require'); require.setGlobalKeyPath('require');
*/
// intentially moved before requiring client_plugins to save a 307 // intentially moved before requiring client_plugins to save a 307
innerWindow.Ace2Inner = require('ep_etherpad-lite/static/js/ace2_inner'); innerWindow.Ace2Inner = ace2_inner;
innerWindow.plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); innerWindow.plugins = cl_plugins;
innerWindow.plugins.adoptPluginsFromAncestorsOf(innerWindow);
innerWindow.$ = innerWindow.jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; innerWindow.$ = innerWindow.jQuery = rJQuery.jQuery;
debugLog('Ace2Editor.init() waiting for plugins'); debugLog('Ace2Editor.init() waiting for plugins');
await new Promise((resolve, reject) => innerWindow.plugins.ensure( /*await new Promise((resolve, reject) => innerWindow.plugins.ensure(
(err) => err != null ? reject(err) : resolve())); (err) => err != null ? reject(err) : resolve()));*/
debugLog('Ace2Editor.init() waiting for Ace2Inner.init()'); debugLog('Ace2Editor.init() waiting for Ace2Inner.init()');
await innerWindow.Ace2Inner.init(info, { await innerWindow.Ace2Inner.init(info, {
inner: makeCSSManager(innerStyle.sheet), inner: makeCSSManager(innerStyle.sheet),

View file

@ -54,13 +54,16 @@ function Ace2Inner(editorInfo, cssManagers) {
let thisAuthor = ''; let thisAuthor = '';
let disposed = false; let disposed = false;
const outerWin = document.getElementsByName("ace_outer")[0]
const targetDoc = outerWin.contentWindow.document.getElementsByName("ace_inner")[0].contentWindow.document
const targetBody = targetDoc.body
const focus = () => { const focus = () => {
window.focus(); targetBody.focus();
}; };
const outerWin = window.parent; const outerDoc = outerWin.contentWindow.document;
const outerDoc = outerWin.document;
const sideDiv = outerDoc.getElementById('sidediv'); const sideDiv = outerDoc.getElementById('sidediv');
const lineMetricsDiv = outerDoc.getElementById('linemetricsdiv'); const lineMetricsDiv = outerDoc.getElementById('linemetricsdiv');
const sideDivInner = outerDoc.getElementById('sidedivinner'); const sideDivInner = outerDoc.getElementById('sidedivinner');
@ -415,7 +418,7 @@ function Ace2Inner(editorInfo, cssManagers) {
const setWraps = (newVal) => { const setWraps = (newVal) => {
doesWrap = newVal; doesWrap = newVal;
document.body.classList.toggle('doesWrap', doesWrap); targetBody.classList.toggle('doesWrap', doesWrap);
scheduler.setTimeout(() => { scheduler.setTimeout(() => {
inCallStackIfNecessary('setWraps', () => { inCallStackIfNecessary('setWraps', () => {
fastIncorp(7); fastIncorp(7);
@ -445,7 +448,7 @@ function Ace2Inner(editorInfo, cssManagers) {
}; };
const setTextFace = (face) => { const setTextFace = (face) => {
document.body.style.fontFamily = face; targetBody.style.fontFamily = face;
lineMetricsDiv.style.fontFamily = face; lineMetricsDiv.style.fontFamily = face;
}; };
@ -456,8 +459,8 @@ function Ace2Inner(editorInfo, cssManagers) {
const setEditable = (newVal) => { const setEditable = (newVal) => {
isEditable = newVal; isEditable = newVal;
document.body.contentEditable = isEditable ? 'true' : 'false'; targetBody.contentEditable = isEditable ? 'true' : 'false';
document.body.classList.toggle('static', !isEditable); targetBody.classList.toggle('static', !isEditable);
}; };
const enforceEditability = () => setEditable(isEditable); const enforceEditability = () => setEditable(isEditable);
@ -480,6 +483,8 @@ function Ace2Inner(editorInfo, cssManagers) {
newText = `${lines.join('\n')}\n`; newText = `${lines.join('\n')}\n`;
} }
window.console.log('importText', {text, undoable, dontProcess, newText})
inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => { inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => {
setDocText(newText); setDocText(newText);
}); });
@ -520,6 +525,7 @@ function Ace2Inner(editorInfo, cssManagers) {
const oldLen = rep.lines.totalWidth(); const oldLen = rep.lines.totalWidth();
const numLines = rep.lines.length(); const numLines = rep.lines.length();
window.console.log(rep, numLines - 1);
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1); const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length; const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
const assem = Changeset.smartOpAssembler(); const assem = Changeset.smartOpAssembler();
@ -640,8 +646,8 @@ function Ace2Inner(editorInfo, cssManagers) {
// These properties are exposed // These properties are exposed
const setters = { const setters = {
wraps: setWraps, wraps: setWraps,
showsauthorcolors: (val) => document.body.classList.toggle('authorColors', !!val), showsauthorcolors: (val) => targetBody.classList.toggle('authorColors', !!val),
showsuserselections: (val) => document.body.classList.toggle('userSelections', !!val), showsuserselections: (val) => targetBody.classList.toggle('userSelections', !!val),
showslinenumbers: (value) => { showslinenumbers: (value) => {
hasLineNumbers = !!value; hasLineNumbers = !!value;
sideDiv.parentNode.classList.toggle('line-numbers-hidden', !hasLineNumbers); sideDiv.parentNode.classList.toggle('line-numbers-hidden', !hasLineNumbers);
@ -654,8 +660,8 @@ function Ace2Inner(editorInfo, cssManagers) {
styled: setStyled, styled: setStyled,
textface: setTextFace, textface: setTextFace,
rtlistrue: (value) => { rtlistrue: (value) => {
document.body.classList.toggle('rtl', value); targetBody.classList.toggle('rtl', value);
document.body.classList.toggle('ltr', !value); targetBody.classList.toggle('ltr', !value);
document.documentElement.dir = value ? 'rtl' : 'ltr'; document.documentElement.dir = value ? 'rtl' : 'ltr';
}, },
}; };
@ -894,11 +900,11 @@ function Ace2Inner(editorInfo, cssManagers) {
clearObservedChanges(); clearObservedChanges();
const getCleanNodeByKey = (key) => { const getCleanNodeByKey = (key) => {
let n = document.getElementById(key); let n = targetDoc.getElementById(key);
// copying and pasting can lead to duplicate ids // copying and pasting can lead to duplicate ids
while (n && isNodeDirty(n)) { while (n && isNodeDirty(n)) {
n.id = ''; n.id = '';
n = document.getElementById(key); n = targetDoc.getElementById(key);
} }
return n; return n;
}; };
@ -980,11 +986,11 @@ function Ace2Inner(editorInfo, cssManagers) {
const observeSuspiciousNodes = () => { const observeSuspiciousNodes = () => {
// inspired by Firefox bug #473255, where pasting formatted text // inspired by Firefox bug #473255, where pasting formatted text
// causes the cursor to jump away, making the new HTML never found. // causes the cursor to jump away, making the new HTML never found.
if (document.body.getElementsByTagName) { if (targetBody.getElementsByTagName) {
const elts = document.body.getElementsByTagName('style'); const elts = targetBody.getElementsByTagName('style');
for (const elt of elts) { for (const elt of elts) {
const n = topLevel(elt); const n = topLevel(elt);
if (n && n.parentNode === document.body) { if (n && n.parentNode === targetBody) {
observeChangesAroundNode(n); observeChangesAroundNode(n);
} }
} }
@ -999,8 +1005,8 @@ function Ace2Inner(editorInfo, cssManagers) {
if (DEBUG && window.DONT_INCORP || window.DEBUG_DONT_INCORP) return false; if (DEBUG && window.DONT_INCORP || window.DEBUG_DONT_INCORP) return false;
// returns true if dom changes were made // returns true if dom changes were made
if (!document.body.firstChild) { if (!targetBody.firstChild) {
document.body.innerHTML = '<div><!-- --></div>'; targetBody.innerHTML = '<div><!-- --></div>';
} }
observeChangesAroundSelection(); observeChangesAroundSelection();
@ -1022,7 +1028,7 @@ function Ace2Inner(editorInfo, cssManagers) {
j++; j++;
} }
if (!dirtyRangesCheckOut) { if (!dirtyRangesCheckOut) {
for (const bodyNode of document.body.childNodes) { for (const bodyNode of targetBody.childNodes) {
if ((bodyNode.tagName) && ((!bodyNode.id) || (!rep.lines.containsKey(bodyNode.id)))) { if ((bodyNode.tagName) && ((!bodyNode.id) || (!rep.lines.containsKey(bodyNode.id)))) {
observeChangesAroundNode(bodyNode); observeChangesAroundNode(bodyNode);
} }
@ -1044,11 +1050,11 @@ function Ace2Inner(editorInfo, cssManagers) {
const range = dirtyRanges[i]; const range = dirtyRanges[i];
a = range[0]; a = range[0];
b = range[1]; b = range[1];
let firstDirtyNode = (((a === 0) && document.body.firstChild) || let firstDirtyNode = (((a === 0) && targetBody.firstChild) ||
getCleanNodeByKey(rep.lines.atIndex(a - 1).key).nextSibling); getCleanNodeByKey(rep.lines.atIndex(a - 1).key).nextSibling);
firstDirtyNode = (firstDirtyNode && isNodeDirty(firstDirtyNode) && firstDirtyNode); firstDirtyNode = (firstDirtyNode && isNodeDirty(firstDirtyNode) && firstDirtyNode);
let lastDirtyNode = (((b === rep.lines.length()) && document.body.lastChild) || let lastDirtyNode = (((b === rep.lines.length()) && targetBody.lastChild) ||
getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling); getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling);
lastDirtyNode = (lastDirtyNode && isNodeDirty(lastDirtyNode) && lastDirtyNode); lastDirtyNode = (lastDirtyNode && isNodeDirty(lastDirtyNode) && lastDirtyNode);
@ -1135,7 +1141,7 @@ function Ace2Inner(editorInfo, cssManagers) {
callstack: currentCallStack, callstack: currentCallStack,
editorInfo, editorInfo,
rep, rep,
root: document.body, root: targetBody,
point: selection.startPoint, point: selection.startPoint,
documentAttributeManager, documentAttributeManager,
}); });
@ -1147,7 +1153,7 @@ function Ace2Inner(editorInfo, cssManagers) {
callstack: currentCallStack, callstack: currentCallStack,
editorInfo, editorInfo,
rep, rep,
root: document.body, root: targetBody,
point: selection.endPoint, point: selection.endPoint,
documentAttributeManager, documentAttributeManager,
}); });
@ -1227,9 +1233,9 @@ function Ace2Inner(editorInfo, cssManagers) {
info.prepareForAdd(); info.prepareForAdd();
entry.lineMarker = info.lineMarker; entry.lineMarker = info.lineMarker;
if (!nodeToAddAfter) { if (!nodeToAddAfter) {
document.body.insertBefore(node, document.body.firstChild); targetBody.insertBefore(node, targetBody.firstChild);
} else { } else {
document.body.insertBefore(node, nodeToAddAfter.nextSibling); targetBody.insertBefore(node, nodeToAddAfter.nextSibling);
} }
nodeToAddAfter = node; nodeToAddAfter = node;
info.notifyAdded(); info.notifyAdded();
@ -1326,7 +1332,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// Turn DOM node selection into [line,char] selection. // Turn DOM node selection into [line,char] selection.
// This method has to work when the DOM is not pristine, // This method has to work when the DOM is not pristine,
// assuming the point is not in a dirty node. // assuming the point is not in a dirty node.
if (point.node === document.body) { if (point.node === targetBody) {
if (point.index === 0) { if (point.index === 0) {
return [0, 0]; return [0, 0];
} else { } else {
@ -1345,7 +1351,7 @@ function Ace2Inner(editorInfo, cssManagers) {
col = nodeText(n).length; col = nodeText(n).length;
} }
let parNode, prevSib; let parNode, prevSib;
while ((parNode = n.parentNode) !== document.body) { while ((parNode = n.parentNode) !== targetBody) {
if ((prevSib = n.previousSibling)) { if ((prevSib = n.previousSibling)) {
n = prevSib; n = prevSib;
col += nodeText(n).length; col += nodeText(n).length;
@ -1398,7 +1404,7 @@ function Ace2Inner(editorInfo, cssManagers) {
insertDomLines(nodeToAddAfter, lineEntries.map((entry) => entry.domInfo)); insertDomLines(nodeToAddAfter, lineEntries.map((entry) => entry.domInfo));
for (const k of keysToDelete) { for (const k of keysToDelete) {
const n = document.getElementById(k); const n = targetDoc.getElementById(k);
n.parentNode.removeChild(n); n.parentNode.removeChild(n);
} }
@ -2087,7 +2093,7 @@ function Ace2Inner(editorInfo, cssManagers) {
const a = cleanNodeForIndex(i - 1); const a = cleanNodeForIndex(i - 1);
const b = cleanNodeForIndex(i); const b = cleanNodeForIndex(i);
if ((!a) || (!b)) return false; // violates precondition if ((!a) || (!b)) return false; // violates precondition
if ((a === true) && (b === true)) return !document.body.firstChild; if ((a === true) && (b === true)) return !targetBody.firstChild;
if ((a === true) && b.previousSibling) return false; if ((a === true) && b.previousSibling) return false;
if ((b === true) && a.nextSibling) return false; if ((b === true) && a.nextSibling) return false;
if ((a === true) || (b === true)) return true; if ((a === true) || (b === true)) return true;
@ -2232,7 +2238,7 @@ function Ace2Inner(editorInfo, cssManagers) {
}; };
const isNodeDirty = (n) => { const isNodeDirty = (n) => {
if (n.parentNode !== document.body) return true; if (n.parentNode !== targetBody) return true;
const data = getAssoc(n, 'dirtiness'); const data = getAssoc(n, 'dirtiness');
if (!data) return true; if (!data) return true;
if (n.id !== data.nodeId) return true; if (n.id !== data.nodeId) return true;
@ -2856,7 +2862,7 @@ function Ace2Inner(editorInfo, cssManagers) {
updateBrowserSelectionFromRep(); updateBrowserSelectionFromRep();
// get the current caret selection, can't use rep. here because that only gives // get the current caret selection, can't use rep. here because that only gives
// us the start position not the current // us the start position not the current
const myselection = document.getSelection(); const myselection = targetDoc.getSelection();
// get the carets selection offset in px IE 214 // get the carets selection offset in px IE 214
let caretOffsetTop = myselection.focusNode.parentNode.offsetTop || let caretOffsetTop = myselection.focusNode.parentNode.offsetTop ||
myselection.focusNode.offsetTop; myselection.focusNode.offsetTop;
@ -2970,13 +2976,13 @@ function Ace2Inner(editorInfo, cssManagers) {
// with background doesn't seem to show up... // with background doesn't seem to show up...
if (isNodeText(p.node) && p.index === p.maxIndex) { if (isNodeText(p.node) && p.index === p.maxIndex) {
let n = p.node; let n = p.node;
while (!n.nextSibling && n !== document.body && n.parentNode !== document.body) { while (!n.nextSibling && n !== targetBody && n.parentNode !== targetBody) {
n = n.parentNode; n = n.parentNode;
} }
if (n.nextSibling && if (n.nextSibling &&
!(typeof n.nextSibling.tagName === 'string' && !(typeof n.nextSibling.tagName === 'string' &&
n.nextSibling.tagName.toLowerCase() === 'br') && n.nextSibling.tagName.toLowerCase() === 'br') &&
n !== p.node && n !== document.body && n.parentNode !== document.body) { n !== p.node && n !== targetBody && n.parentNode !== targetBody) {
// found a parent, go to next node and dive in // found a parent, go to next node and dive in
p.node = n.nextSibling; p.node = n.nextSibling;
p.maxIndex = nodeMaxIndex(p.node); p.maxIndex = nodeMaxIndex(p.node);
@ -3078,7 +3084,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// each of which has node (a magicdom node), index, and maxIndex. If the node // each of which has node (a magicdom node), index, and maxIndex. If the node
// is a text node, maxIndex is the length of the text; else maxIndex is 1. // is a text node, maxIndex is the length of the text; else maxIndex is 1.
// index is between 0 and maxIndex, inclusive. // index is between 0 and maxIndex, inclusive.
const browserSelection = window.getSelection(); const browserSelection = targetDoc.getSelection();
if (!browserSelection || browserSelection.type === 'None' || if (!browserSelection || browserSelection.type === 'None' ||
browserSelection.rangeCount === 0) { browserSelection.rangeCount === 0) {
return null; return null;
@ -3096,7 +3102,7 @@ function Ace2Inner(editorInfo, cssManagers) {
if (!isInBody(container)) { if (!isInBody(container)) {
// command-click in Firefox selects whole document, HEAD and BODY! // command-click in Firefox selects whole document, HEAD and BODY!
return { return {
node: document.body, node: targetBody,
index: 0, index: 0,
maxIndex: 1, maxIndex: 1,
}; };
@ -3191,7 +3197,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// If non-nullish, pasting on a link should be suppressed. // If non-nullish, pasting on a link should be suppressed.
let suppressPasteOnLink = null; let suppressPasteOnLink = null;
$(document.body).on('auxclick', (e) => { $(targetBody).on('auxclick', (e) => {
if (e.originalEvent.button === 1 && (e.target.a || e.target.localName === 'a')) { if (e.originalEvent.button === 1 && (e.target.a || e.target.localName === 'a')) {
// The user middle-clicked on a link. Usually users do this to open a link in a new tab, but // The user middle-clicked on a link. Usually users do this to open a link in a new tab, but
// in X11 (Linux) this will instead paste the contents of the primary selection at the mouse // in X11 (Linux) this will instead paste the contents of the primary selection at the mouse
@ -3213,7 +3219,7 @@ function Ace2Inner(editorInfo, cssManagers) {
} }
}); });
$(document.body).on('paste', (e) => { $(targetBody).on('paste', (e) => {
if (suppressPasteOnLink != null && (e.target.a || e.target.localName === 'a')) { if (suppressPasteOnLink != null && (e.target.a || e.target.localName === 'a')) {
scheduler.clearTimeout(suppressPasteOnLink); scheduler.clearTimeout(suppressPasteOnLink);
suppressPasteOnLink = null; suppressPasteOnLink = null;
@ -3233,7 +3239,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// We reference document here, this is because if we don't this will expose a bug // We reference document here, this is because if we don't this will expose a bug
// in Google Chrome. This bug will cause the last character on the last line to // in Google Chrome. This bug will cause the last character on the last line to
// not fire an event when dropped into.. // not fire an event when dropped into..
$(document).on('drop', (e) => { $(targetBody).on('drop', (e) => {
if (e.target.a || e.target.localName === 'a') { if (e.target.a || e.target.localName === 'a') {
e.preventDefault(); e.preventDefault();
} }
@ -3251,7 +3257,7 @@ function Ace2Inner(editorInfo, cssManagers) {
const lineAfterSelection = lastLineSelected.nextSibling; const lineAfterSelection = lastLineSelected.nextSibling;
const neighbor = lineBeforeSelection || lineAfterSelection; const neighbor = lineBeforeSelection || lineAfterSelection;
neighbor.appendChild(document.createElement('style')); neighbor.appendChild(targetDoc.createElement('style'));
} }
// Call drop hook // Call drop hook
@ -3263,10 +3269,10 @@ function Ace2Inner(editorInfo, cssManagers) {
}); });
}); });
$(document.documentElement).on('compositionstart', () => { $(targetDoc.documentElement).on('compositionstart', () => {
if (inInternationalComposition) return; if (inInternationalComposition) return;
inInternationalComposition = new Promise((resolve) => { inInternationalComposition = new Promise((resolve) => {
$(document.documentElement).one('compositionend', () => { $(targetDoc.documentElement).one('compositionend', () => {
inInternationalComposition = null; inInternationalComposition = null;
resolve(); resolve();
}); });
@ -3275,8 +3281,8 @@ function Ace2Inner(editorInfo, cssManagers) {
}; };
const topLevel = (n) => { const topLevel = (n) => {
if ((!n) || n === document.body) return null; if ((!n) || n === targetBody) return null;
while (n.parentNode !== document.body) { while (n.parentNode !== targetBody) {
n = n.parentNode; n = n.parentNode;
} }
return n; return n;
@ -3436,10 +3442,10 @@ function Ace2Inner(editorInfo, cssManagers) {
// but as it's non-text type the line-height/margins might not be present and it // but as it's non-text type the line-height/margins might not be present and it
// could be that this breaks a theme that has a different default line height.. // could be that this breaks a theme that has a different default line height..
// So instead of using an integer here we get the value from the Editor CSS. // So instead of using an integer here we get the value from the Editor CSS.
const innerdocbodyStyles = getComputedStyle(document.body); const innerdocbodyStyles = getComputedStyle(targetBody);
const defaultLineHeight = parseInt(innerdocbodyStyles['line-height']); const defaultLineHeight = parseInt(innerdocbodyStyles['line-height']);
for (const docLine of document.body.children) { for (const docLine of targetBody.children) {
let h; let h;
const nextDocLine = docLine.nextElementSibling; const nextDocLine = docLine.nextElementSibling;
if (nextDocLine) { if (nextDocLine) {
@ -3450,7 +3456,7 @@ function Ace2Inner(editorInfo, cssManagers) {
// included on the first line. The default stylesheet doesn't add // included on the first line. The default stylesheet doesn't add
// extra margins/padding, but plugins might. // extra margins/padding, but plugins might.
h = nextDocLine.offsetTop - parseInt( h = nextDocLine.offsetTop - parseInt(
window.getComputedStyle(document.body) window.getComputedStyle(targetBody)
.getPropertyValue('padding-top').split('px')[0]); .getPropertyValue('padding-top').split('px')[0]);
} else { } else {
h = nextDocLine.offsetTop - docLine.offsetTop; h = nextDocLine.offsetTop - docLine.offsetTop;
@ -3496,15 +3502,15 @@ function Ace2Inner(editorInfo, cssManagers) {
this.init = async () => { this.init = async () => {
await $.ready; await $.ready;
inCallStack('setup', () => { inCallStack('setup', () => {
if (browser.firefox) $(document.body).addClass('mozilla'); if (browser.firefox) $(targetBody).addClass('mozilla');
if (browser.safari) $(document.body).addClass('safari'); if (browser.safari) $(targetBody).addClass('safari');
document.body.classList.toggle('authorColors', true); targetBody.classList.toggle('authorColors', true);
document.body.classList.toggle('doesWrap', doesWrap); targetBody.classList.toggle('doesWrap', doesWrap);
enforceEditability(); enforceEditability();
// set up dom and rep // set up dom and rep
while (document.body.firstChild) document.body.removeChild(document.body.firstChild); while (targetBody.firstChild) targetBody.removeChild(targetBody.firstChild);
const oneEntry = createDomLineEntry(''); const oneEntry = createDomLineEntry('');
doRepLineSplice(0, rep.lines.length(), [oneEntry]); doRepLineSplice(0, rep.lines.length(), [oneEntry]);
insertDomLines(null, [oneEntry.domInfo]); insertDomLines(null, [oneEntry.domInfo]);

View file

@ -5,6 +5,7 @@
// is represented by the browser // is represented by the browser
exports.getPosition = () => { exports.getPosition = () => {
const range = getSelectionRange(); const range = getSelectionRange();
console.log("Getting range", range)
if (!range || $(range.endContainer).closest('body')[0].id !== 'innerdocbody') return null; if (!range || $(range.endContainer).closest('body')[0].id !== 'innerdocbody') return null;
// When there's a <br> or any element that has no height, we can't get the dimension of the // When there's a <br> or any element that has no height, we can't get the dimension of the
// element where the caret is. As we can't get the element height, we create a text node to get // element where the caret is. As we can't get the element height, we create a text node to get

View file

@ -24,9 +24,9 @@
const Cookies = require('./pad_utils').Cookies; const Cookies = require('./pad_utils').Cookies;
const padcookie = require('./pad_cookie').padcookie; const padcookie = require('./pad_cookie').padcookie;
const padutils = require('./pad_utils').padutils; const padutils = require('./pad_utils').padutils;
const Ace2Editor = require('./ace').Ace2Editor;
const padeditor = (() => { const padeditor = (() => {
let Ace2Editor = undefined;
let pad = undefined; let pad = undefined;
let settings = undefined; let settings = undefined;
@ -35,7 +35,6 @@ const padeditor = (() => {
// this is accessed directly from other files // this is accessed directly from other files
viewZoom: 100, viewZoom: 100,
init: async (initialViewOptions, _pad) => { init: async (initialViewOptions, _pad) => {
Ace2Editor = require('./ace').Ace2Editor;
pad = _pad; pad = _pad;
settings = pad.settings; settings = pad.settings;
self.ace = new Ace2Editor(); self.ace = new Ace2Editor();

View file

@ -443,7 +443,7 @@ const inThirdPartyIframe = () => {
// This file is included from Node so that it can reuse randomString, but Node doesn't have a global // This file is included from Node so that it can reuse randomString, but Node doesn't have a global
// window object. // window object.
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
exports.Cookies = require('js-cookie/dist/js.cookie').withAttributes({ exports.Cookies = require('js-cookie').withAttributes({
// Use `SameSite=Lax`, unless Etherpad is embedded in an iframe from another site in which case // Use `SameSite=Lax`, unless Etherpad is embedded in an iframe from another site in which case
// use `SameSite=None`. For iframes from another site, only `None` has a chance of working // use `SameSite=None`. For iframes from another site, only `None` has a chance of working
// because the cookies are third-party (not same-site). Many browsers/users block third-party // because the cookies are third-party (not same-site). Many browsers/users block third-party

View file

@ -7,24 +7,13 @@ exports.baseURL = '';
exports.ensure = (cb) => !defs.loaded ? exports.update(cb) : cb(); exports.ensure = (cb) => !defs.loaded ? exports.update(cb) : cb();
exports.update = (cb) => { exports.update = async (modules) => {
// It appears that this response (see #620) may interrupt the current thread const data = await jQuery.getJSON(
// of execution on Firefox. This schedules the response in the run-loop, `${exports.baseURL}pluginfw/plugin-definitions.json?v=${clientVars.randomVersionString}`);
// which appears to fix the issue. defs.plugins = data.plugins;
const callback = () => setTimeout(cb, 0); defs.parts = data.parts;
defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks', null, modules);
jQuery.getJSON( defs.loaded = true;
`${exports.baseURL}pluginfw/plugin-definitions.json?v=${clientVars.randomVersionString}`
).done((data) => {
defs.plugins = data.plugins;
defs.parts = data.parts;
defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks');
defs.loaded = true;
callback();
}).fail((err) => {
console.error(`Failed to load plugin-definitions: ${err}`);
callback();
});
}; };
const adoptPluginsFromAncestorsOf = (frame) => { const adoptPluginsFromAncestorsOf = (frame) => {

View file

@ -15,7 +15,7 @@ function Scroll(outerWin) {
// DOM reference // DOM reference
this.outerWin = outerWin; this.outerWin = outerWin;
this.doc = this.outerWin.document; this.doc = this.outerWin.contentDocument;
this.rootDocument = parent.parent.document; this.rootDocument = parent.parent.document;
} }

View file

@ -172,7 +172,7 @@ $._farbtastic = function (container, options) {
angle2 = d2 * Math.PI * 2, angle2 = d2 * Math.PI * 2,
// Endpoints // Endpoints
x1 = Math.sin(angle1), y1 = -Math.cos(angle1); x1 = Math.sin(angle1), y1 = -Math.cos(angle1);
x2 = Math.sin(angle2), y2 = -Math.cos(angle2), let x2 = Math.sin(angle2), y2 = -Math.cos(angle2),
// Midpoint chosen so that the endpoints are tangent to the circle. // Midpoint chosen so that the endpoints are tangent to the circle.
am = (angle1 + angle2) / 2, am = (angle1 + angle2) / 2,
tan = 1 / Math.cos((angle2 - angle1) / 2), tan = 1 / Math.cos((angle2 - angle1) / 2),
@ -329,8 +329,8 @@ $._farbtastic = function (container, options) {
// Update the overlay canvas. // Update the overlay canvas.
fb.ctxOverlay.clearRect(-fb.mid, -fb.mid, sz, sz); fb.ctxOverlay.clearRect(-fb.mid, -fb.mid, sz, sz);
for (i in circles) { for (let i in circles) {
var c = circles[i]; const c = circles[i];
fb.ctxOverlay.lineWidth = c.lw; fb.ctxOverlay.lineWidth = c.lw;
fb.ctxOverlay.strokeStyle = c.c; fb.ctxOverlay.strokeStyle = c.c;
fb.ctxOverlay.beginPath(); fb.ctxOverlay.beginPath();

View file

@ -1,3 +1,4 @@
(async () => { (async () => {
window.clientVars = { window.clientVars = {
// This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the server // This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the server
@ -6,7 +7,7 @@
}; };
// Allow other frames to access this frame's modules. // Allow other frames to access this frame's modules.
window.require.resolveTmp = require.resolve('ep_etherpad-lite/static/js/pad_cookie'); //window.require.resolveTmp = require.resolve('ep_etherpad-lite/static/js/pad_cookie');
const basePath = new URL('..', window.location.href).pathname; const basePath = new URL('..', window.location.href).pathname;
window.$ = window.jQuery = require('../../src/static/js/rjquery').jQuery; window.$ = window.jQuery = require('../../src/static/js/rjquery').jQuery;

2
var/js/.gitignore vendored
View file

@ -0,0 +1,2 @@
*.js
*.map