pad.libre-service.eu-etherpad/src/static/js/domline.js

279 lines
8.5 KiB
JavaScript
Raw Normal View History

'use strict';
2011-03-26 14:10:41 +01:00
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
// %APPJET%: import("etherpad.admin.plugins");
/**
* 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.
*/
// requires: top
// requires: plugins
// requires: undefined
2020-11-23 19:24:19 +01:00
const Security = require('./security');
const hooks = require('./pluginfw/hooks');
const _ = require('./underscore');
2020-11-23 19:24:19 +01:00
const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
const noop = () => {};
2012-03-17 13:36:42 +01:00
2020-11-23 19:24:19 +01:00
const domline = {};
2011-03-26 14:10:41 +01:00
domline.addToLineClass = (lineClass, cls) => {
2011-03-26 14:10:41 +01:00
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
2020-11-23 19:24:19 +01:00
cls.replace(/\S+/g, (c) => {
if (c.indexOf('line:') === 0) {
2011-03-26 14:10:41 +01:00
// add class to line
2020-11-23 19:24:19 +01:00
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
2011-03-26 14:10:41 +01:00
}
});
return lineClass;
2020-11-23 19:24:19 +01:00
};
2011-03-26 14:10:41 +01:00
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
2020-11-23 19:24:19 +01:00
const result = {
2011-07-07 19:59:34 +02:00
node: null,
appendSpan: noop,
prepareForAdd: noop,
notifyAdded: noop,
clearSpans: noop,
finishUpdate: noop,
2020-11-23 19:24:19 +01:00
lineMarker: 0,
2011-07-07 19:59:34 +02:00
};
2011-03-26 14:10:41 +01:00
2020-11-23 19:24:19 +01:00
const document = optDocument;
2011-03-26 14:10:41 +01:00
2020-11-23 19:24:19 +01:00
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');
2020-11-23 19:24:19 +01:00
} else {
2011-07-07 19:59:34 +02:00
result.node = {
innerHTML: '',
2020-11-23 19:24:19 +01:00
className: '',
2011-07-07 19:59:34 +02:00
};
2011-03-26 14:10:41 +01:00
}
2020-11-23 19:24:19 +01:00
let html = [];
let preHtml = '';
let postHtml = '';
let curHTML = null;
2011-07-07 19:59:34 +02:00
const processSpaces = (s) => domline.processSpaces(s, doesWrap);
2020-11-23 19:24:19 +01:00
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line';
2015-01-21 15:25:24 +01:00
result.appendSpan = (txt, cls) => {
2020-11-23 19:24:19 +01:00
let processedMarker = false;
// Handle lineAttributeMarker, if present
2020-11-23 19:24:19 +01:00
if (cls.indexOf(lineAttributeMarker) >= 0) {
let listType = /(?:^| )list:(\S+)/.exec(cls);
const start = /(?:^| )start:(\S+)/.exec(cls);
2014-03-05 00:14:15 +01:00
2020-11-23 19:24:19 +01:00
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
2014-03-05 00:14:15 +01:00
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
2020-11-23 19:24:19 +01:00
if (listType) {
2011-03-26 14:10:41 +01:00
listType = listType[1];
2020-11-23 19:24:19 +01:00
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>`;
2020-11-23 19:24:19 +01:00
} else {
// Handles pasted contents into existing lists
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}
2014-03-05 22:44:32 +01:00
postHtml += '</li></ol>';
2012-01-15 18:20:20 +01:00
}
}
processedMarker = true;
}
2020-11-23 19:24:19 +01:00
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
2020-11-23 19:24:19 +01:00
if (processedMarker) {
2011-03-26 14:10:41 +01:00
result.lineMarker += txt.length;
return; // don't append any text
}
2011-03-26 14:10:41 +01:00
}
2020-11-23 19:24:19 +01:00
let href = null;
let simpleTags = null;
if (cls.indexOf('url') >= 0) {
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
2011-07-07 19:59:34 +02:00
href = url;
2020-11-23 19:24:19 +01:00
return `${space}url`;
2011-03-26 14:10:41 +01:00
});
}
2020-11-23 19:24:19 +01:00
if (cls.indexOf('tag') >= 0) {
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
2011-07-07 19:59:34 +02:00
if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
2011-03-26 14:10:41 +01:00
});
}
2020-11-23 19:24:19 +01:00
let extraOpenTags = '';
let extraCloseTags = '';
2011-03-26 14:10:41 +01:00
2020-11-23 19:24:19 +01:00
_.map(hooks.callAll('aceCreateDomLine', {
domline,
cls,
}), (modifier) => {
2011-03-26 14:10:41 +01:00
cls = modifier.cls;
2020-11-23 19:24:19 +01:00
extraOpenTags += modifier.extraOpenTags;
2011-07-07 19:59:34 +02:00
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
2011-03-26 14:10:41 +01:00
});
2020-11-23 19:24:19 +01:00
if ((!txt) && cls) {
2011-03-26 14:10:41 +01:00
lineClass = domline.addToLineClass(lineClass, cls);
2020-11-23 19:24:19 +01:00
} 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)) {
2020-11-23 19:24:19 +01:00
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.
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
const escapedHref = Security.escapeHTMLAttribute(href);
extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
2020-11-23 19:24:19 +01:00
extraCloseTags = `</a>${extraCloseTags}`;
2011-03-26 14:10:41 +01:00
}
2020-11-23 19:24:19 +01:00
if (simpleTags) {
2011-07-07 19:59:34 +02:00
simpleTags.sort();
2020-11-23 19:24:19 +01:00
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
2011-07-07 19:59:34 +02:00
simpleTags.reverse();
2020-11-23 19:24:19 +01:00
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
2011-03-26 14:10:41 +01:00
}
html.push(
'<span class="', Security.escapeHTMLAttribute(cls || ''),
'">',
extraOpenTags,
perTextNodeProcess(Security.escapeHTML(txt)),
extraCloseTags,
'</span>');
2011-03-26 14:10:41 +01:00
}
};
result.clearSpans = () => {
2011-03-26 14:10:41 +01:00
html = [];
lineClass = 'ace-line';
2011-03-26 14:10:41 +01:00
result.lineMarker = 0;
};
2011-07-07 19:59:34 +02:00
const writeHTML = () => {
2020-11-23 19:24:19 +01:00
let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) {
if ((!document) || (!optBrowser)) {
2011-03-26 14:10:41 +01:00
newHTML += '&nbsp;';
2020-12-19 00:13:02 +01:00
} else {
2011-03-26 14:10:41 +01:00
newHTML += '<br/>';
}
}
2020-11-23 19:24:19 +01:00
if (nonEmpty) {
2011-07-07 19:59:34 +02:00
newHTML = (preHtml || '') + newHTML + (postHtml || '');
2011-03-26 14:10:41 +01:00
}
html = preHtml = postHtml = ''; // free memory
2020-11-23 19:24:19 +01:00
if (newHTML !== curHTML) {
2011-03-26 14:10:41 +01:00
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass != null) result.node.className = lineClass;
2020-11-23 19:24:19 +01:00
hooks.callAll('acePostWriteDomLineHTML', {
node: result.node,
2014-03-05 00:36:16 +01:00
});
};
2011-03-26 14:10:41 +01:00
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
return result;
};
domline.processSpaces = (s, doesWrap) => {
2020-11-23 19:24:19 +01:00
if (s.indexOf('<') < 0 && !doesWrap) {
2011-03-26 14:10:41 +01:00
// short-cut
return s.replace(/ /g, '&nbsp;');
}
2020-11-23 19:24:19 +01:00
const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
2011-07-07 19:59:34 +02:00
parts.push(m);
});
2020-11-23 19:24:19 +01:00
if (doesWrap) {
let endOfLine = true;
let beforeSpace = false;
2011-03-26 14:10:41 +01:00
// 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 === ' ') {
2011-07-07 19:59:34 +02:00
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
} else if (p.charAt(0) !== '<') {
2011-07-07 19:59:34 +02:00
endOfLine = false;
beforeSpace = false;
2011-03-26 14:10:41 +01:00
}
}
// beginning of line is nbsp
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
2011-07-07 19:59:34 +02:00
parts[i] = '&nbsp;';
break;
} else if (p.charAt(0) !== '<') {
2011-07-07 19:59:34 +02:00
break;
2011-03-26 14:10:41 +01:00
}
}
2020-11-23 19:24:19 +01:00
} else {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
2011-07-07 19:59:34 +02:00
parts[i] = '&nbsp;';
2011-03-26 14:10:41 +01:00
}
}
}
return parts.join('');
};
exports.domline = domline;