2011-12-04 16:33:56 +01:00
|
|
|
/**
|
2019-04-16 00:34:29 +02:00
|
|
|
* This code is mostly from the old Etherpad. Please help us to comment this code.
|
2011-12-04 16:33:56 +01:00
|
|
|
* This helps other people to understand this code better and helps them to improve it.
|
|
|
|
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
|
|
|
|
*/
|
|
|
|
|
2011-03-26 14:10:41 +01:00
|
|
|
/**
|
|
|
|
* Copyright 2009 Google Inc.
|
2011-07-07 19:59:34 +02:00
|
|
|
*
|
2011-03-26 14:10:41 +01:00
|
|
|
* 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
|
2011-07-07 19:59:34 +02:00
|
|
|
*
|
2011-03-26 14:10:41 +01:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2011-07-07 19:59:34 +02:00
|
|
|
*
|
2011-03-26 14:10:41 +01:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2012-03-07 02:27:03 +01:00
|
|
|
var Security = require('./security');
|
2012-01-18 12:58:13 +01:00
|
|
|
|
2012-01-29 02:38:23 +01:00
|
|
|
/**
|
|
|
|
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
|
|
|
|
*/
|
|
|
|
|
|
|
|
function randomString(len)
|
|
|
|
{
|
|
|
|
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
|
|
var randomstring = '';
|
|
|
|
len = len || 20
|
|
|
|
for (var i = 0; i < len; i++)
|
|
|
|
{
|
|
|
|
var rnum = Math.floor(Math.random() * chars.length);
|
|
|
|
randomstring += chars.substring(rnum, rnum + 1);
|
|
|
|
}
|
|
|
|
return randomstring;
|
|
|
|
}
|
|
|
|
|
2011-03-26 14:10:41 +01:00
|
|
|
var padutils = {
|
2011-07-07 19:59:34 +02:00
|
|
|
escapeHtml: function(x)
|
|
|
|
{
|
2012-01-18 12:58:13 +01:00
|
|
|
return Security.escapeHTML(String(x));
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
uniqueId: function()
|
|
|
|
{
|
2012-03-07 02:27:03 +01:00
|
|
|
var pad = require('./pad').pad; // Sidestep circular dependency
|
2011-07-07 19:59:34 +02:00
|
|
|
function encodeNum(n, width)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// returns string that is exactly 'width' chars, padding with zeros
|
|
|
|
// and taking rightmost digits
|
2011-07-07 19:59:34 +02:00
|
|
|
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
uaDisplay: function(ua)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var m;
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function clean(a)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var maxlen = 16;
|
|
|
|
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
|
2011-07-07 19:59:34 +02:00
|
|
|
if (a.length > maxlen)
|
|
|
|
{
|
|
|
|
a = a.substr(0, maxlen);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function checkver(name)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
|
2011-07-07 19:59:34 +02:00
|
|
|
if (m && m.length > 1)
|
|
|
|
{
|
|
|
|
return clean(name + m[1]);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// firefox
|
2011-07-07 19:59:34 +02:00
|
|
|
if (checkver('Firefox'))
|
|
|
|
{
|
|
|
|
return checkver('Firefox');
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
// misc browsers, including IE
|
|
|
|
m = ua.match(/compatible; ([^;]+);/);
|
2011-07-07 19:59:34 +02:00
|
|
|
if (m && m.length > 1)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return clean(m[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// iphone
|
2011-07-07 19:59:34 +02:00
|
|
|
if (ua.match(/\(iPhone;/))
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return 'iPhone';
|
|
|
|
}
|
|
|
|
|
|
|
|
// chrome
|
2011-07-07 19:59:34 +02:00
|
|
|
if (checkver('Chrome'))
|
|
|
|
{
|
|
|
|
return checkver('Chrome');
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
// safari
|
|
|
|
m = ua.match(/Safari\/[\d\.]+/);
|
2011-07-07 19:59:34 +02:00
|
|
|
if (m)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var v = '?';
|
|
|
|
m = ua.match(/Version\/([\d\.]+)/);
|
2011-07-07 19:59:34 +02:00
|
|
|
if (m && m.length > 1)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
v = m[1];
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
return clean('Safari' + v);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// everything else
|
|
|
|
var x = ua.split(' ')[0];
|
|
|
|
return clean(x);
|
|
|
|
},
|
|
|
|
// e.g. "Thu Jun 18 2009 13:09"
|
2011-07-07 19:59:34 +02:00
|
|
|
simpleDateTime: function(date)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var d = new Date(+date); // accept either number or date
|
2011-07-07 19:59:34 +02:00
|
|
|
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
|
|
|
|
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
|
2011-03-26 14:10:41 +01:00
|
|
|
var dayOfMonth = d.getDate();
|
|
|
|
var year = d.getFullYear();
|
2011-07-07 19:59:34 +02:00
|
|
|
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
|
|
|
|
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
findURLs: function(text)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// copied from ACE
|
|
|
|
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
2011-07-07 19:59:34 +02:00
|
|
|
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
|
2016-01-30 19:03:42 +01:00
|
|
|
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
|
2011-07-07 19:59:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
function _findURLs(text)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
_REGEX_URL.lastIndex = 0;
|
|
|
|
var urls = null;
|
|
|
|
var execResult;
|
2011-07-07 19:59:34 +02:00
|
|
|
while ((execResult = _REGEX_URL.exec(text)))
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
urls = (urls || []);
|
|
|
|
var startIndex = execResult.index;
|
|
|
|
var url = execResult[0];
|
|
|
|
urls.push([startIndex, url]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return urls;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _findURLs(text);
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
escapeHtmlWithClickableLinks: function(text, target)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var idx = 0;
|
|
|
|
var pieces = [];
|
|
|
|
var urls = padutils.findURLs(text);
|
2011-07-07 19:59:34 +02:00
|
|
|
|
|
|
|
function advanceTo(i)
|
|
|
|
{
|
|
|
|
if (i > idx)
|
|
|
|
{
|
2012-01-18 12:58:13 +01:00
|
|
|
pieces.push(Security.escapeHTML(text.substring(idx, i)));
|
2011-03-26 14:10:41 +01:00
|
|
|
idx = i;
|
|
|
|
}
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
if (urls)
|
|
|
|
{
|
|
|
|
for (var j = 0; j < urls.length; j++)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var startIndex = urls[j][0];
|
|
|
|
var href = urls[j][1];
|
|
|
|
advanceTo(startIndex);
|
2015-01-27 12:11:07 +01:00
|
|
|
// 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.
|
referer: change referrer policy. Stop sending referers as much as possible
Pull request with discussion: https://github.com/ether/etherpad-lite/pull/3636
What's already there:
* `meta name=referrer`: already done in 1.6.1:
https://github.com/ether/etherpad-lite/pull/3044
https://caniuse.com/#feat=referrer-policy
https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta
(Chrome>=78, Firefox>=70, Safari>=13, Opera>=64, ~IE[1], ~Edge[1])
The previous two commits (by @joelpurra) I backported in this batch:
* `<a rel=noreferrer>`: a pull request denied before:
https://github.com/ether/etherpad-lite/pull/2498
https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
(Firefox>=37, I can't find more info about support)
This commit adds the following:
* `<a rel="noopener">`: fixing a not-so-well-known way to extract referer
https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
(Chrome>=49, Firefox>=52, Safari>=10.1, Opera>=36, !IE, !Edge)
* `Referrer-Policy: same-origin`: the last bastion of referrer security
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
(Chrome>=61, Firefox>=52, Safari>=11.1, Opera>=48, !IE, !Edge)
meta name=referrer wasn't enough. I happened to leak a few referrers with my
Firefox browser, though for some browsers it could have been enough.
[1] IE>=11, Edge>=18 use a different syntax for meta name=referrer, making it
most probably incompatible (but I may be wrong on that, they may support
both, but I have no way to test it currently). The next Edge release will be
based on Chromium, so for that the Chrome version applies.
2019-11-23 08:18:07 +01:00
|
|
|
// 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
|
|
|
|
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
|
2011-03-26 14:10:41 +01:00
|
|
|
advanceTo(startIndex + href.length);
|
|
|
|
pieces.push('</a>');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
advanceTo(text.length);
|
|
|
|
return pieces.join('');
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
bindEnterAndEscape: function(node, onEnter, onEscape)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
// Use keypress instead of keyup in bindEnterAndEscape
|
|
|
|
// Keyup event is fired on enter in IME (Input Method Editor), But
|
|
|
|
// keypress is not. So, I changed to use keypress instead of keyup.
|
|
|
|
// It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
|
2011-07-07 19:59:34 +02:00
|
|
|
if (onEnter)
|
|
|
|
{
|
|
|
|
node.keypress(function(evt)
|
|
|
|
{
|
|
|
|
if (evt.which == 13)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
onEnter(evt);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
if (onEscape)
|
|
|
|
{
|
|
|
|
node.keydown(function(evt)
|
|
|
|
{
|
|
|
|
if (evt.which == 27)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
onEscape(evt);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
timediff: function(d)
|
|
|
|
{
|
2012-03-07 02:27:03 +01:00
|
|
|
var pad = require('./pad').pad; // Sidestep circular dependency
|
2011-07-07 19:59:34 +02:00
|
|
|
function format(n, word)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
n = Math.round(n);
|
|
|
|
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
|
|
|
|
}
|
|
|
|
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
|
2011-07-07 19:59:34 +02:00
|
|
|
if (d < 60)
|
|
|
|
{
|
|
|
|
return format(d, 'second');
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
d /= 60;
|
2011-07-07 19:59:34 +02:00
|
|
|
if (d < 60)
|
|
|
|
{
|
|
|
|
return format(d, 'minute');
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
d /= 60;
|
2011-07-07 19:59:34 +02:00
|
|
|
if (d < 24)
|
|
|
|
{
|
|
|
|
return format(d, 'hour');
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
d /= 24;
|
|
|
|
return format(d, 'day');
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
|
|
|
|
{
|
|
|
|
if (stepsAtOnce === undefined)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
stepsAtOnce = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
var animationTimer = null;
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function scheduleAnimation()
|
|
|
|
{
|
|
|
|
if (!animationTimer)
|
|
|
|
{
|
|
|
|
animationTimer = window.setTimeout(function()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationTimer = null;
|
|
|
|
var n = stepsAtOnce;
|
|
|
|
var moreToDo = true;
|
2011-07-07 19:59:34 +02:00
|
|
|
while (moreToDo && n > 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
moreToDo = funcToAnimateOneStep();
|
|
|
|
n--;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
if (moreToDo)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// more to do
|
|
|
|
scheduleAnimation();
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
}, stepTime * stepsAtOnce);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
return {
|
|
|
|
scheduleAnimation: scheduleAnimation
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
|
|
|
|
var animationFrameDelay = 1000 / fps;
|
|
|
|
var animationStep = animationFrameDelay / totalMs;
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function doShow()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = -1;
|
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
scheduleAnimation();
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function doQuickShow()
|
|
|
|
{ // start showing without losing any fade-in progress
|
|
|
|
if (animationState < -1)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = -1;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (animationState <= 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = animationState;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
animationState = Math.max(-1, Math.min(0, -animationState));
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
scheduleAnimation();
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function doHide()
|
|
|
|
{
|
|
|
|
if (animationState >= -1 && animationState <= 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = 1e-6;
|
|
|
|
scheduleAnimation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
function animateOneStep()
|
|
|
|
{
|
|
|
|
if (animationState < -1 || animationState == 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return false;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (animationState < 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// animate show
|
|
|
|
animationState += animationStep;
|
2011-07-07 19:59:34 +02:00
|
|
|
if (animationState >= 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = 0;
|
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
return false;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (animationState > 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// animate hide
|
|
|
|
animationState += animationStep;
|
2011-07-07 19:59:34 +02:00
|
|
|
if (animationState >= 1)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
animationState = 1;
|
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
animationState = -2;
|
|
|
|
return false;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
funcToArriveAtState(animationState);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
return {
|
|
|
|
show: doShow,
|
|
|
|
hide: doHide,
|
|
|
|
quickShow: doQuickShow
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
|
|
|
_nextActionId: 1,
|
|
|
|
uncanceledActions: {},
|
2011-07-07 19:59:34 +02:00
|
|
|
getCancellableAction: function(actionType, actionFunc)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var o = padutils.uncanceledActions[actionType];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (!o)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
o = {};
|
|
|
|
padutils.uncanceledActions[actionType] = o;
|
|
|
|
}
|
|
|
|
var actionId = (padutils._nextActionId++);
|
|
|
|
o[actionId] = true;
|
2011-07-07 19:59:34 +02:00
|
|
|
return function()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var p = padutils.uncanceledActions[actionType];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (p && p[actionId])
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
actionFunc();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
cancelActions: function(actionType)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var o = padutils.uncanceledActions[actionType];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (o)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// clear it
|
|
|
|
delete padutils.uncanceledActions[actionType];
|
|
|
|
}
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
makeFieldLabeledWhenEmpty: function(field, labelText)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
field = $(field);
|
2011-07-07 19:59:34 +02:00
|
|
|
|
|
|
|
function clear()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
field.addClass('editempty');
|
|
|
|
field.val(labelText);
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
field.focus(function()
|
|
|
|
{
|
|
|
|
if (field.hasClass('editempty'))
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
field.val('');
|
|
|
|
}
|
|
|
|
field.removeClass('editempty');
|
|
|
|
});
|
2011-07-07 19:59:34 +02:00
|
|
|
field.blur(function()
|
|
|
|
{
|
|
|
|
if (!field.val())
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
clear();
|
|
|
|
}
|
|
|
|
});
|
2011-07-07 19:59:34 +02:00
|
|
|
return {
|
|
|
|
clear: clear
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
getCheckbox: function(node)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return $(node).is(':checked');
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
setCheckbox: function(node, value)
|
|
|
|
{
|
|
|
|
if (value)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
$(node).attr('checked', 'checked');
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
$(node).removeAttr('checked');
|
|
|
|
}
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
bindCheckboxChange: function(node, func)
|
|
|
|
{
|
2014-11-01 19:18:25 +01:00
|
|
|
$(node).change(func);
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
encodeUserId: function(userId)
|
|
|
|
{
|
|
|
|
return userId.replace(/[^a-y0-9]/g, function(c)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
if (c == ".") return "-";
|
2011-07-07 19:59:34 +02:00
|
|
|
return 'z' + c.charCodeAt(0) + 'z';
|
2011-03-26 14:10:41 +01:00
|
|
|
});
|
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
decodeUserId: function(encodedUserId)
|
|
|
|
{
|
|
|
|
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
if (cc == '-') return '.';
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (cc.charAt(0) == 'z')
|
|
|
|
{
|
|
|
|
return String.fromCharCode(Number(cc.slice(1, -1)));
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return cc;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2011-08-17 20:24:44 +02:00
|
|
|
|
2012-01-29 03:12:01 +01:00
|
|
|
var globalExceptionHandler = undefined;
|
|
|
|
function setupGlobalExceptionHandler() {
|
|
|
|
if (!globalExceptionHandler) {
|
|
|
|
globalExceptionHandler = function test (msg, url, linenumber)
|
|
|
|
{
|
2012-02-28 14:38:29 +01:00
|
|
|
var errorId = randomString(20);
|
2014-10-06 15:23:13 +02:00
|
|
|
var userAgent = padutils.escapeHtml(navigator.userAgent);
|
2020-05-06 14:09:39 +02:00
|
|
|
|
|
|
|
var msgAlreadyVisible = false;
|
|
|
|
$('.gritter-item .error-msg').each(function() {
|
|
|
|
if ($(this).text() === msg) {
|
|
|
|
msgAlreadyVisible = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!msgAlreadyVisible) {
|
|
|
|
errorMsg = "<b>Please press and hold Ctrl and press F5 to reload this page</b></br> \
|
|
|
|
If the problem persists please send this error message to your webmaster: </br></br>\
|
|
|
|
<div style='text-align:left; font-size: .8em'>\
|
|
|
|
ErrorId: " + errorId + "<br>\
|
|
|
|
URL: " + padutils.escapeHtml(window.location.href) + "<br>\
|
|
|
|
UserAgent: " + userAgent + "<br>\
|
|
|
|
<span class='error-msg'>"+ msg + "</span> in " + url + " at line " + linenumber + '</div>';
|
|
|
|
|
|
|
|
$.gritter.add({
|
|
|
|
title: "An error occurred",
|
|
|
|
text: errorMsg,
|
|
|
|
class_name: "error",
|
|
|
|
position: 'bottom',
|
|
|
|
sticky: true,
|
|
|
|
});
|
2012-02-28 14:38:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//send javascript errors to the server
|
2013-02-26 14:24:24 +01:00
|
|
|
var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: window.location.href, linenumber: linenumber, userAgent: navigator.userAgent})};
|
|
|
|
var loc = document.location;
|
2012-02-28 14:38:29 +01:00
|
|
|
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
|
2019-04-16 00:34:29 +02:00
|
|
|
|
2012-02-28 14:38:29 +01:00
|
|
|
$.post(url, errObj);
|
2019-04-16 00:34:29 +02:00
|
|
|
|
2012-02-28 14:38:29 +01:00
|
|
|
return false;
|
2012-01-29 03:12:01 +01:00
|
|
|
};
|
|
|
|
window.onerror = globalExceptionHandler;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
|
2012-01-16 02:23:48 +01:00
|
|
|
|
2012-03-07 02:27:03 +01:00
|
|
|
padutils.binarySearch = require('./ace2_common').binarySearch;
|
2012-01-26 17:22:44 +01:00
|
|
|
|
2020-10-03 05:53:05 +02:00
|
|
|
// https://stackoverflow.com/a/42660748
|
|
|
|
function inThirdPartyIframe() {
|
|
|
|
try {
|
|
|
|
return (!window.top.location.hostname);
|
|
|
|
} catch (e) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-03 00:43:12 +02:00
|
|
|
// This file is included from Node so that it can reuse randomString, but Node doesn't have a global
|
|
|
|
// window object.
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
exports.Cookies = require('js-cookie/src/js.cookie');
|
2020-10-03 05:53:05 +02:00
|
|
|
// 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
|
|
|
|
// because the cookies are third-party (not same-site). Many browsers/users block third-party
|
|
|
|
// cookies, but maybe blocked is better than definitely blocked (which would happen with `Lax`
|
|
|
|
// or `Strict`). Note: `None` will not work unless secure is true.
|
|
|
|
//
|
2020-10-03 04:32:44 +02:00
|
|
|
// `Strict` is not used because it has few security benefits but significant usability drawbacks
|
|
|
|
// vs. `Lax`. See https://stackoverflow.com/q/41841880 for discussion.
|
2020-10-03 05:53:05 +02:00
|
|
|
exports.Cookies.defaults.sameSite = inThirdPartyIframe() ? 'None' : 'Lax';
|
2020-10-03 00:43:12 +02:00
|
|
|
exports.Cookies.defaults.secure = window.location.protocol === 'https:';
|
|
|
|
}
|
2012-01-29 02:38:23 +01:00
|
|
|
exports.randomString = randomString;
|
2012-01-16 02:23:48 +01:00
|
|
|
exports.padutils = padutils;
|