lint: src/node/utils/ExportHtml.js

This commit is contained in:
John McLear 2021-01-21 21:06:52 +00:00 committed by Richard Hansen
parent c44c4edc10
commit bfabe7c297

View file

@ -1,3 +1,4 @@
'use strict';
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -14,32 +15,29 @@
* limitations under the License. * limitations under the License.
*/ */
const Changeset = require('ep_etherpad-lite/static/js/Changeset'); const Changeset = require('../../static/js/Changeset');
const padManager = require('../db/PadManager'); const padManager = require('../db/PadManager');
const _ = require('underscore'); const _ = require('underscore');
const Security = require('ep_etherpad-lite/static/js/security'); const Security = require('../../static/js/security');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
const eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('../eejs');
const _analyzeLine = require('./ExportHelper')._analyzeLine; const _analyzeLine = require('./ExportHelper')._analyzeLine;
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
const padutils = require('../../static/js/pad_utils').padutils; const padutils = require('../../static/js/pad_utils').padutils;
async function getPadHTML(pad, revNum) { const getPadHTML = async (pad, revNum) => {
let atext = pad.atext; let atext = pad.atext;
// fetch revision atext // fetch revision atext
if (revNum != undefined) { if (revNum !== undefined) {
atext = await pad.getInternalRevisionAText(revNum); atext = await pad.getInternalRevisionAText(revNum);
} }
// convert atext to html // convert atext to html
return await getHTMLFromAtext(pad, atext); return await getHTMLFromAtext(pad, atext);
} };
exports.getPadHTML = getPadHTML; const getHTMLFromAtext = async (pad, atext, authorColors) => {
exports.getHTMLFromAtext = getHTMLFromAtext;
async function getHTMLFromAtext(pad, atext, authorColors) {
const apool = pad.apool(); const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@ -72,9 +70,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
const anumMap = {}; const anumMap = {};
let css = ''; let css = '';
const stripDotFromAuthorID = function (id) { const stripDotFromAuthorID = (id) => id.replace(/\./g, '_');
return id.replace(/\./g, '_');
};
if (authorColors) { if (authorColors) {
css += '<style>\n'; css += '<style>\n';
@ -85,15 +81,14 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// skip non author attributes // skip non author attributes
if (attr[0] === 'author' && attr[1] !== '') { if (attr[0] === 'author' && attr[1] !== '') {
// add to props array // add to props array
var propName = `author${stripDotFromAuthorID(attr[1])}`; const propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName); const newLength = props.push(propName);
anumMap[a] = newLength - 1; anumMap[a] = newLength - 1;
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`; css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if (attr[0] === 'removed') { } else if (attr[0] === 'removed') {
var propName = 'removed'; const propName = 'removed';
const newLength = props.push(propName);
var newLength = props.push(propName);
anumMap[a] = newLength - 1; anumMap[a] = newLength - 1;
css += '.removed {text-decoration: line-through; ' + css += '.removed {text-decoration: line-through; ' +
@ -122,7 +117,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
}); });
function getLineHTML(text, attribs) { const getLineHTML = (text, attribs) => {
// Use order of tags (b/i/u) as order of nesting, for simplicity // Use order of tags (b/i/u) as order of nesting, for simplicity
// and decent nesting. For example, // and decent nesting. For example,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
@ -132,7 +127,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
const assem = Changeset.stringAssembler(); const assem = Changeset.stringAssembler();
const openTags = []; const openTags = [];
function getSpanClassFor(i) { const getSpanClassFor = (i) => {
// return if author colors are disabled // return if author colors are disabled
if (!authorColors) return false; if (!authorColors) return false;
@ -153,16 +148,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
return false; return false;
} };
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with // tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes // data attributes
function isSpanWithData(i) { const isSpanWithData = (i) => {
const property = props[i]; const property = props[i];
return _.isArray(property); return _.isArray(property);
} };
function emitOpenTag(i) { const emitOpenTag = (i) => {
openTags.unshift(i); openTags.unshift(i);
const spanClass = getSpanClassFor(i); const spanClass = getSpanClassFor(i);
@ -175,10 +170,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]); assem.append(tags[i]);
assem.append('>'); assem.append('>');
} }
} };
// this closes an open tag and removes its reference from openTags // this closes an open tag and removes its reference from openTags
function emitCloseTag(i) { const emitCloseTag = (i) => {
openTags.shift(); openTags.shift();
const spanClass = getSpanClassFor(i); const spanClass = getSpanClassFor(i);
const spanWithData = isSpanWithData(i); const spanWithData = isSpanWithData(i);
@ -190,13 +185,13 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]); assem.append(tags[i]);
assem.append('>'); assem.append('>');
} }
} };
const urls = padutils.findURLs(text); const urls = padutils.findURLs(text);
let idx = 0; let idx = 0;
function processNextChars(numChars) { const processNextChars = (numChars) => {
if (numChars <= 0) { if (numChars <= 0) {
return; return;
} }
@ -208,7 +203,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// based on the attribs used // based on the attribs used
while (iter.hasNext()) { while (iter.hasNext()) {
const o = iter.next(); const o = iter.next();
var usedAttribs = []; const usedAttribs = [];
// mark all attribs as used // mark all attribs as used
Changeset.eachAttribNumber(o.attribs, (a) => { Changeset.eachAttribNumber(o.attribs, (a) => {
@ -218,7 +213,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}); });
let outermostTag = -1; let outermostTag = -1;
// find the outer most open tag that is no longer used // find the outer most open tag that is no longer used
for (var i = openTags.length - 1; i >= 0; i--) { for (let i = openTags.length - 1; i >= 0; i--) {
if (usedAttribs.indexOf(openTags[i]) === -1) { if (usedAttribs.indexOf(openTags[i]) === -1) {
outermostTag = i; outermostTag = i;
break; break;
@ -234,7 +229,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
// open all tags that are used but not open // open all tags that are used but not open
for (i = 0; i < usedAttribs.length; i++) { for (let i = 0; i < usedAttribs.length; i++) {
if (openTags.indexOf(usedAttribs[i]) === -1) { if (openTags.indexOf(usedAttribs[i]) === -1) {
emitOpenTag(usedAttribs[i]); emitOpenTag(usedAttribs[i]);
} }
@ -258,14 +253,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
while (openTags.length > 0) { while (openTags.length > 0) {
emitCloseTag(openTags[0]); emitCloseTag(openTags[0]);
} }
} // end processNextChars };
// end processNextChars
if (urls) { if (urls) {
urls.forEach((urlData) => { urls.forEach((urlData) => {
const startIndex = urlData[0]; const startIndex = urlData[0];
const url = urlData[1]; const url = urlData[1];
const urlLength = url.length; const urlLength = url.length;
processNextChars(startIndex - idx); processNextChars(startIndex - idx);
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document. // Using rel="noreferrer" stops leaking the URL/location of the exported HTML
// when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard. // 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 // https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security. // Additionally, we do rel="noopener" to ensure a higher level of referrer security.
@ -280,7 +277,8 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
processNextChars(text.length - idx); processNextChars(text.length - idx);
return _processSpaces(assem.toString()); return _processSpaces(assem.toString());
} // end getLineHTML };
// end getLineHTML
const pieces = [css]; const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can // Need to deal with constraints imposed on HTML lists; can
@ -292,11 +290,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// => keeps track of the parents level of indentation // => keeps track of the parents level of indentation
let openLists = []; let openLists = [];
for (let i = 0; i < textLines.length; i++) { for (let i = 0; i < textLines.length; i++) {
var context; let context;
var line = _analyzeLine(textLines[i], attribLines[i], apool); const line = _analyzeLine(textLines[i], attribLines[i], apool);
const lineContent = getLineHTML(line.text, line.aline); const lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel)// If we are inside a list // If we are inside a list
{ if (line.listLevel) {
context = { context = {
line, line,
lineContent, lineContent,
@ -315,8 +313,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
await hooks.aCallAll('getLineHTMLForExport', context); await hooks.aCallAll('getLineHTMLForExport', context);
// To create list parent elements // To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) { if ((!prevLine || prevLine.listLevel !== line.listLevel) ||
const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName)); (prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (
item.level === line.listLevel && item.type === line.listTypeName)
);
if (!exists) { if (!exists) {
let prevLevel = 0; let prevLevel = 0;
if (prevLine && prevLine.listLevel) { if (prevLine && prevLine.listLevel) {
@ -326,23 +327,33 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
prevLevel = 0; prevLevel = 0;
} }
for (var diff = prevLevel; diff < line.listLevel; diff++) { for (let diff = prevLevel; diff < line.listLevel; diff++) {
openLists.push({level: diff, type: line.listTypeName}); openLists.push({level: diff, type: line.listTypeName});
const prevPiece = pieces[pieces.length - 1]; const prevPiece = pieces[pieces.length - 1];
if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) { if (prevPiece.indexOf('<ul') === 0 ||
prevPiece.indexOf('<ol') === 0 ||
prevPiece.indexOf('</li>') === 0) {
/* /*
uncommenting this breaks nested ols.. uncommenting this breaks nested ols..
if the previous item is NOT a ul, NOT an ol OR closing li then close the list if the previous item is NOT a ul, NOT an ol OR closing li then close the list
so we consider this HTML, I inserted ** where it throws a problem in Example Wrong.. so we consider this HTML,
<ol><li>one</li><li><ol><li>1.1</li><li><ol><li>1.1.1</li></ol></li></ol></li><li>two</li></ol> I inserted ** where it throws a problem in Example Wrong..
<ol><li>one</li><li><ol><li>1.1</li><li><ol><li>1.1.1</li></ol></li></ol>
</li><li>two</li></ol>
Note that closing the li then re-opening for another li item here is wrong. The correct markup is Note that closing the li then re-opening for another li item here is wrong.
The correct markup is
<ol><li>one<ol><li>1.1<ol><li>1.1.1</li></ol></li></ol></li><li>two</li></ol> <ol><li>one<ol><li>1.1<ol><li>1.1.1</li></ol></li></ol></li><li>two</li></ol>
Exmaple Right: <ol class="number"><li>one</li><ol start="2" class="number"><li>1.1</li><ol start="3" class="number"><li>1.1.1</li></ol></li></ol><li>two</li></ol> Exmaple Right: <ol class="number"><li>one</li><ol start="2" class="number">
Example Wrong: <ol class="number"><li>one</li>**</li>**<ol start="2" class="number"><li>1.1</li>**</li>**<ol start="3" class="number"><li>1.1.1</li></ol></li></ol><li>two</li></ol> <li>1.1</li><ol start="3" class="number"><li>1.1.1</li></ol></li></ol>
So it's firing wrong where the current piece is an li and the previous piece is an ol and next piece is an ol <li>two</li></ol>
Example Wrong: <ol class="number"><li>one</li>**</li>**
<ol start="2" class="number"><li>1.1</li>**</li>**<ol start="3" class="number">
<li>1.1.1</li></ol></li></ol><li>two</li></ol>
So it's firing wrong where the current piece is an li and the previous piece is
an ol and next piece is an ol
So to remedy this we can say if next piece is NOT an OL or UL. So to remedy this we can say if next piece is NOT an OL or UL.
// pieces.push("</li>"); // pieces.push("</li>");
@ -350,14 +361,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) { if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine! // is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls // TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open // don't do anything because the next item is a nested ol openener so
// we need to keep the li open
} else { } else {
pieces.push('<li>'); pieces.push('<li>');
} }
} }
if (line.listTypeName === 'number') { if (line.listTypeName === 'number') {
// We introduce line.start here, this is useful for continuing Ordered list line numbers // We introduce line.start here, this is useful for continuing
// Ordered list line numbers
// in case you have a bullet in a list IE you Want // in case you have a bullet in a list IE you Want
// 1. hello // 1. hello
// * foo // * foo
@ -384,18 +397,24 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
// To close list elements // To close list elements
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) { if (nextLine &&
nextLine.listLevel === line.listLevel &&
line.listTypeName === nextLine.listTypeName) {
if (context.lineContent) { if (context.lineContent) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) { if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine! // is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls // TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open // don't do anything because the next item is a nested ol openener so we need to
// keep the li open
} else { } else {
pieces.push('</li>'); pieces.push('</li>');
} }
} }
} }
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) { if ((!nextLine ||
!nextLine.listLevel ||
nextLine.listLevel < line.listLevel) ||
(nextLine && line.listTypeName !== nextLine.listTypeName)) {
let nextLevel = 0; let nextLevel = 0;
if (nextLine && nextLine.listLevel) { if (nextLine && nextLine.listLevel) {
nextLevel = nextLine.listLevel; nextLevel = nextLine.listLevel;
@ -404,10 +423,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
nextLevel = 0; nextLevel = 0;
} }
for (var diff = nextLevel; diff < line.listLevel; diff++) { for (let diff = nextLevel; diff < line.listLevel; diff++) {
openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName); openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) { if (pieces[pieces.length - 1].indexOf('</ul') === 0 ||
pieces[pieces.length - 1].indexOf('</ol') === 0) {
pieces.push('</li>'); pieces.push('</li>');
} }
@ -418,8 +438,8 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
} }
} }
} else// outside any list, need to close line.listLevel of lists } else {
{ // outside any list, need to close line.listLevel of lists
context = { context = {
line, line,
lineContent, lineContent,
@ -435,9 +455,9 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
return pieces.join(''); return pieces.join('');
} };
exports.getPadHTMLDocument = async function (padId, revNum) { exports.getPadHTMLDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId); const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export // Include some Styles into the Head for Export
@ -461,7 +481,7 @@ exports.getPadHTMLDocument = async function (padId, revNum) {
}; };
// copied from ACE // copied from ACE
function _processSpaces(s) { const _processSpaces = (s) => {
const doesWrap = true; const doesWrap = true;
if (s.indexOf('<') < 0 && !doesWrap) { if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut // short-cut
@ -476,34 +496,37 @@ function _processSpaces(s) {
let beforeSpace = false; let beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--) { for (let i = parts.length - 1; i >= 0; i--) {
var p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} else if (p.charAt(0) != '<') { } else if (p.charAt(0) !== '<') {
endOfLine = false; endOfLine = false;
beforeSpace = false; beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for (i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
break; break;
} else if (p.charAt(0) != '<') { } else if (p.charAt(0) !== '<') {
break; break;
} }
} }
} else { } else {
for (i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
} }
} }
} }
return parts.join(''); return parts.join('');
} };
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;