-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
78b3cfe
commit c584089
Showing
6 changed files
with
515 additions
and
13 deletions.
There are no files selected for viewing
293 changes: 293 additions & 0 deletions
293
fiduswriter/book/static/js/modules/books/exporter/dom_export.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
import {DOMSerializer} from "prosemirror-model" | ||
import {RenderCitations} from "../../citations/render" | ||
import {get} from "../../common" | ||
import {BIBLIOGRAPHY_HEADERS, CATS} from "../../schema/i18n" | ||
|
||
/* | ||
WARNING: DEPRECATED! | ||
Base exporter class for dom-based exports. This is the deprecated way of creating exports. | ||
The epub and html book export filters go over a DOM of a document which they change little | ||
by little, and they are all based on the BaseDOMExporter class. | ||
New exporters should instead by walking the doc.content tree. | ||
This is how all document exporters work, including the new HTML/EPUB exporter. | ||
*/ | ||
|
||
export class DOMExporter { | ||
constructor(schema, csl, documentStyles) { | ||
this.schema = schema | ||
this.csl = csl | ||
this.documentStyles = documentStyles | ||
|
||
this.fontFiles = [] | ||
this.binaryFiles = [] | ||
this.styleSheets = [{url: staticUrl("css/document.css")}] | ||
} | ||
|
||
addDocStyle(doc) { | ||
const docStyle = this.documentStyles.find( | ||
docStyle => docStyle.slug === doc.settings.documentstyle | ||
) | ||
|
||
// The files will be in the base directory. The filenames of | ||
// DocumentStyleFiles will therefore not need to replaced with their URLs. | ||
if (!docStyle) { | ||
return | ||
} | ||
let contents = docStyle.contents | ||
docStyle.documentstylefile_set.forEach( | ||
([_url, filename]) => | ||
(contents = contents.replace( | ||
new RegExp(filename, "g"), | ||
`media/${filename}` | ||
)) | ||
) | ||
this.styleSheets.push({contents, filename: `css/${docStyle.slug}.css`}) | ||
this.fontFiles = this.fontFiles.concat( | ||
docStyle.documentstylefile_set.map(([url, filename]) => ({ | ||
filename: `css/media/${filename}`, | ||
url | ||
})) | ||
) | ||
} | ||
|
||
loadStyles() { | ||
const p = [] | ||
this.styleSheets.forEach(sheet => { | ||
if (sheet.url) { | ||
p.push( | ||
get(sheet.url) | ||
.then(response => response.text()) | ||
.then(response => { | ||
sheet.contents = response | ||
sheet.filename = `css/${sheet.url.split("/").pop().split("?")[0]}` | ||
delete sheet.url | ||
}) | ||
) | ||
} | ||
}) | ||
return Promise.all(p) | ||
} | ||
|
||
joinDocumentParts() { | ||
this.schema.cached.imageDB = this.imageDB | ||
const serializer = DOMSerializer.fromSchema(this.schema) | ||
this.content = serializer.serializeNode( | ||
this.schema.nodeFromJSON(this.docContent) | ||
) | ||
|
||
this.addFootnotes() | ||
const bibliographyHeader = | ||
this.doc.settings.bibliography_header[this.doc.settings.language] || | ||
BIBLIOGRAPHY_HEADERS[this.doc.settings.language] | ||
const citRenderer = new RenderCitations( | ||
this.content, | ||
this.doc.settings.citationstyle, | ||
bibliographyHeader, | ||
this.bibDB, | ||
this.csl | ||
) | ||
return citRenderer.init().then(() => { | ||
this.addBibliographyHTML(citRenderer.fm.bibHTML) | ||
this.cleanHTML(citRenderer.fm) | ||
this.addCategoryLabels(this.doc.settings.language) | ||
return Promise.resolve() | ||
}) | ||
} | ||
|
||
addCategoryLabels(language) { | ||
this.content | ||
.querySelectorAll("figcaption span.label,caption span.label") | ||
.forEach(el => { | ||
const category = el.parentElement.parentElement.dataset.category | ||
el.innerHTML = | ||
category === "none" ? "" : CATS[category][language] | ||
}) | ||
} | ||
|
||
addBibliographyHTML(bibliographyHTML) { | ||
if (bibliographyHTML.length > 0) { | ||
const tempNode = document.createElement("div") | ||
tempNode.innerHTML = bibliographyHTML | ||
while (tempNode.firstChild) { | ||
const footnotesContainer = | ||
this.content.querySelector("section.fnlist") | ||
this.content.insertBefore( | ||
tempNode.firstChild, | ||
footnotesContainer | ||
) | ||
} | ||
} | ||
} | ||
|
||
replaceImgSrc(htmlString) { | ||
htmlString = htmlString.replace( | ||
/<(img|IMG) data-src([^>]+)>/gm, | ||
"<$1 src$2>" | ||
) | ||
return htmlString | ||
} | ||
// Replace all instances of the before string in all descendant textnodes of | ||
// node. | ||
replaceText(node, before, after) { | ||
if (node.nodeType === 1) { | ||
;[].forEach.call(node.childNodes, child => | ||
this.replaceText(child, before, after) | ||
) | ||
} else if (node.nodeType === 3) { | ||
node.textContent = node.textContent.replace( | ||
new window.RegExp(before, "g"), | ||
after | ||
) | ||
} | ||
} | ||
|
||
cleanNode(node) { | ||
if (node.contentEditable === "true") { | ||
node.removeAttribute("contentEditable") | ||
} | ||
if (node.children) { | ||
Array.from(node.children).forEach(childNode => | ||
this.cleanNode(childNode) | ||
) | ||
} | ||
} | ||
|
||
getFootnoteAnchor(counter) { | ||
const footnoteAnchor = document.createElement("a") | ||
footnoteAnchor.setAttribute("href", "#fn" + counter) | ||
// RASH 0.5 doesn't mark the footnote anchors, so we add this class | ||
footnoteAnchor.classList.add("fn") | ||
return footnoteAnchor | ||
} | ||
|
||
addFootnotes() { | ||
// Replace the footnote markers with anchors and put footnotes with contents | ||
// at the back of the document. | ||
// Also, link the footnote anchor with the footnote according to | ||
// https://rawgit.com/essepuntato/rash/master/documentation/index.html#footnotes. | ||
const footnotes = this.content.querySelectorAll(".footnote-marker") | ||
const footnotesContainer = document.createElement("section") | ||
footnotesContainer.classList.add("fnlist") | ||
footnotesContainer.setAttribute("role", "doc-footnotes") | ||
|
||
footnotes.forEach((footnote, index) => { | ||
const counter = index + 1 | ||
const footnoteAnchor = this.getFootnoteAnchor(counter) | ||
footnote.parentNode.replaceChild(footnoteAnchor, footnote) | ||
const newFootnote = document.createElement("section") | ||
newFootnote.id = "fn" + counter | ||
newFootnote.setAttribute("role", "doc-footnote") | ||
newFootnote.innerHTML = footnote.dataset.footnote | ||
footnotesContainer.appendChild(newFootnote) | ||
}) | ||
this.content.appendChild(footnotesContainer) | ||
} | ||
|
||
moveCitationFootnotes(citationFormatter) { | ||
const footnotes = this.content.querySelectorAll("a.fn, .citation") | ||
const footnotesContainer = this.content.querySelector("section.fnlist") | ||
const fnCountOffset = this.content.querySelectorAll("a.fn").length | ||
|
||
if (footnotes.length === fnCountOffset) { | ||
// There are no citations to move | ||
return | ||
} | ||
|
||
let counter = 0 | ||
|
||
footnotes.forEach((footnote, index) => { | ||
if (footnote.matches("a.fn")) { | ||
// Regular footnote - skip | ||
return | ||
} | ||
if (footnote.matches("section.fnlist .citation")) { | ||
// The citation is already in a footnote. Do not add a second footnote. | ||
footnote.innerHTML = | ||
citationFormatter.citationTexts[counter] || " " | ||
counter += 1 | ||
return | ||
} | ||
const id = fnCountOffset + counter + 1 | ||
const footnoteAnchor = this.getFootnoteAnchor(id) | ||
footnote.parentNode.replaceChild(footnoteAnchor, footnote) | ||
const newFootnote = document.createElement("section") | ||
newFootnote.id = "fn" + id | ||
newFootnote.setAttribute("role", "doc-footnote") | ||
newFootnote.innerHTML = `<p><span class="citation">${ | ||
citationFormatter.citationTexts[counter] || " " | ||
}</span></p>` | ||
footnotesContainer.insertBefore( | ||
newFootnote, | ||
footnotesContainer.childNodes[index] | ||
) | ||
counter += 1 | ||
}) | ||
} | ||
|
||
cleanHTML(citationFormatter) { | ||
if (citationFormatter.citationType === "note") { | ||
this.moveCitationFootnotes(citationFormatter) | ||
} | ||
|
||
this.cleanNode(this.content) | ||
|
||
// Replace nbsp spaces with normal ones | ||
this.replaceText(this.content, " ", " ") | ||
|
||
this.content.querySelectorAll(".comment").forEach(el => { | ||
el.insertAdjacentHTML("afterend", el.innerHTML) | ||
el.parentElement.removeChild(el) | ||
}) | ||
|
||
this.content.querySelectorAll(".citation").forEach(el => { | ||
delete el.dataset.references | ||
delete el.dataset.bibs | ||
delete el.dataset.format | ||
}) | ||
this.content.querySelectorAll("img").forEach(el => { | ||
delete el.dataset.image | ||
delete el.dataset.imageSrc | ||
}) | ||
|
||
this.content.querySelectorAll("table").forEach(el => { | ||
delete el.dataset.captionHidden | ||
}) | ||
|
||
this.content | ||
.querySelectorAll( | ||
"figcaption span.text:empty,caption span.text:empty" | ||
) | ||
.forEach(el => { | ||
el.parentElement.removeChild(el) | ||
}) | ||
|
||
this.content.querySelectorAll(".cross-reference").forEach(el => { | ||
el.innerHTML = `<a href="#${el.dataset.id}">${el.innerHTML}</a>` | ||
}) | ||
} | ||
|
||
// Fill the contents of table of contents. | ||
fillToc() { | ||
const headlines = Array.from( | ||
this.content.querySelectorAll("h1,h2,h3,h4,h5,h6") | ||
) | ||
const tocs = Array.from( | ||
this.content.querySelectorAll("div.table-of-contents") | ||
) | ||
tocs.forEach(toc => { | ||
toc.innerHTML += headlines | ||
.map(headline => { | ||
if (!headline.id || !headline.textContent.length) { | ||
// ignore the tocs own headlines | ||
return "" | ||
} | ||
const tagName = headline.tagName.toLowerCase() | ||
return `<${tagName}><a href="#${headline.id}">${headline.innerHTML}</a></${tagName}>` | ||
}) | ||
.join("") | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.