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.
*
@ -14,32 +15,29 @@
* 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 _ = require('underscore');
const Security = require('ep_etherpad-lite/static/js/security');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const eejs = require('ep_etherpad-lite/node/eejs');
const Security = require('../../static/js/security');
const hooks = require('../../static/js/pluginfw/hooks');
const eejs = require('../eejs');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
const padutils = require('../../static/js/pad_utils').padutils;
async function getPadHTML(pad, revNum) {
const getPadHTML = async (pad, revNum) => {
let atext = pad.atext;
// fetch revision atext
if (revNum != undefined) {
if (revNum !== undefined) {
atext = await pad.getInternalRevisionAText(revNum);
}
// convert atext to html
return await getHTMLFromAtext(pad, atext);
}
};
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;
async function getHTMLFromAtext(pad, atext, authorColors) {
const getHTMLFromAtext = async (pad, atext, authorColors) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@ -72,9 +70,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
const anumMap = {};
let css = '';
const stripDotFromAuthorID = function (id) {
return id.replace(/\./g, '_');
};
const stripDotFromAuthorID = (id) => id.replace(/\./g, '_');
if (authorColors) {
css += '<style>\n';
@ -85,15 +81,14 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// skip non author attributes
if (attr[0] === 'author' && attr[1] !== '') {
// add to props array
var propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName);
const propName = `author${stripDotFromAuthorID(attr[1])}`;
const newLength = props.push(propName);
anumMap[a] = newLength - 1;
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if (attr[0] === 'removed') {
var propName = 'removed';
var newLength = props.push(propName);
const propName = 'removed';
const newLength = props.push(propName);
anumMap[a] = newLength - 1;
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
// and decent nesting. For example,
// <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 openTags = [];
function getSpanClassFor(i) {
const getSpanClassFor = (i) => {
// return if author colors are disabled
if (!authorColors) return false;
@ -153,16 +148,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
return false;
}
};
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes
function isSpanWithData(i) {
const isSpanWithData = (i) => {
const property = props[i];
return _.isArray(property);
}
};
function emitOpenTag(i) {
const emitOpenTag = (i) => {
openTags.unshift(i);
const spanClass = getSpanClassFor(i);
@ -175,10 +170,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]);
assem.append('>');
}
}
};
// this closes an open tag and removes its reference from openTags
function emitCloseTag(i) {
const emitCloseTag = (i) => {
openTags.shift();
const spanClass = getSpanClassFor(i);
const spanWithData = isSpanWithData(i);
@ -190,13 +185,13 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]);
assem.append('>');
}
}
};
const urls = padutils.findURLs(text);
let idx = 0;
function processNextChars(numChars) {
const processNextChars = (numChars) => {
if (numChars <= 0) {
return;
}
@ -208,7 +203,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// based on the attribs used
while (iter.hasNext()) {
const o = iter.next();
var usedAttribs = [];
const usedAttribs = [];
// mark all attribs as used
Changeset.eachAttribNumber(o.attribs, (a) => {
@ -218,7 +213,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
});
let outermostTag = -1;
// 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) {
outermostTag = i;
break;
@ -234,7 +229,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
// 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) {
emitOpenTag(usedAttribs[i]);
}
@ -258,14 +253,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
while (openTags.length > 0) {
emitCloseTag(openTags[0]);
}
} // end processNextChars
};
// end processNextChars
if (urls) {
urls.forEach((urlData) => {
const startIndex = urlData[0];
const url = urlData[1];
const urlLength = url.length;
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.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// 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);
return _processSpaces(assem.toString());
} // end getLineHTML
};
// end getLineHTML
const pieces = [css];
// 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
let openLists = [];
for (let i = 0; i < textLines.length; i++) {
var context;
var line = _analyzeLine(textLines[i], attribLines[i], apool);
let context;
const line = _analyzeLine(textLines[i], attribLines[i], apool);
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 = {
line,
lineContent,
@ -315,8 +313,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
await hooks.aCallAll('getLineHTMLForExport', context);
// To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName));
if ((!prevLine || prevLine.listLevel !== line.listLevel) ||
(prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (
item.level === line.listLevel && item.type === line.listTypeName)
);
if (!exists) {
let prevLevel = 0;
if (prevLine && prevLine.listLevel) {
@ -326,23 +327,33 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
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});
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..
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..
<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>
so we consider this HTML,
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>
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>
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
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>
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.
// pieces.push("</li>");
@ -350,14 +361,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// 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 {
pieces.push('<li>');
}
}
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
// 1. hello
// * foo
@ -384,18 +397,24 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
// 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 ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// 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 {
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;
if (nextLine && nextLine.listLevel) {
nextLevel = nextLine.listLevel;
@ -404,10 +423,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
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);
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>');
}
@ -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 = {
line,
lineContent,
@ -435,9 +455,9 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
return pieces.join('');
}
};
exports.getPadHTMLDocument = async function (padId, revNum) {
exports.getPadHTMLDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export
@ -461,7 +481,7 @@ exports.getPadHTMLDocument = async function (padId, revNum) {
};
// copied from ACE
function _processSpaces(s) {
const _processSpaces = (s) => {
const doesWrap = true;
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
@ -476,34 +496,37 @@ function _processSpaces(s) {
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--) {
var p = parts[i];
if (p == ' ') {
for (let i = parts.length - 1; i >= 0; i--) {
const p = parts[i];
if (p === ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
} else if (p.charAt(0) != '<') {
} else if (p.charAt(0) !== '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == ' ') {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
break;
} else if (p.charAt(0) != '<') {
} else if (p.charAt(0) !== '<') {
break;
}
}
} else {
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == ' ') {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
}
};
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;