Skip to content

Commit

Permalink
Restructure footnotes JS in PDF for EBTv2
Browse files Browse the repository at this point in the history
I missed this when creating EBTv2, so footnotes
were broken when we started merging HTML for PDF.
This fixes that, and improves the logic, too.
  • Loading branch information
arthurattwell committed Apr 18, 2023
1 parent 3590514 commit d105a84
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 117 deletions.
91 changes: 58 additions & 33 deletions _sass/template/partials/_print-notes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,32 +42,54 @@ $print-notes: true !default;
margin: ($line-height-default * 0.75) 0 0 0;
}

// The page-footnotes area
@page {
counter-reset: manual-footnote;

@footnote {
border-top: $rule-thickness solid $color-text-main;
margin-top: $line-height-default / 2;
padding-top: $line-height-default / 2;
font-size: $font-size-default * $font-size-smaller;
}
}

// True footnotes (we call them page-footnotes for clarity)
// For styling see https://www.princexml.com/doc-prince/#footnote-calls

// The numbers in front of footnote text
*::footnote-marker {
float: left;
font-size: $font-size-default * $font-size-smaller;
width: 0;
}

// The page-footnote references in body text
// Note that other <sup> styling is defined in
// _print-base-typography.scss
*::footnote-call {
content: counter(footnote);
font-size: $font-size-default * 0.7;
vertical-align: super;
line-height: none;

// Shift down
top: $font-size-default / 4;
position: relative;
}

.page-footnote {
float: footnote;
font-size: $font-size-default * $font-size-smaller;
footnote-style-position: inside;
footnote-style-position: inside; // a prince-only property
text-indent: -($paragraph-indent);
margin-left: $paragraph-indent;
// Rule above
&:first-of-type {
border-top: $rule-thickness solid $color-text-secondary;
padding-top: $line-height-default;
}

p {
margin-bottom: 0;
text-indent: 0;
&:first-of-type {
}
}
}
// The numbers in front of footnote text
*::footnote-marker {
float: left;
font-size: $font-size-default * $font-size-smaller;
width: 0;
}

// Avoid page-footnotes created by footnotes.js
// from inheriting blockquote indentation by
Expand All @@ -77,26 +99,29 @@ $print-notes: true !default;
margin-left: $paragraph-indent;
}

// The page-footnotes area
@page {
@footnote {
margin-top: $line-height-default / 2;
padding-top: $line-height-default / 2;
font-size: $font-size-default * $font-size-smaller;
// Footnotes created with one-by-one 'move-to-footnote'
// class need a different kind of counter, because
// in a document with some endnotes and some manual footnotes,
// they need to have a different numbering system.
// Otherwise, they'll appear at the bottom of the page
// but be numbered in order with the endnotes.

.page-footnote {
counter-increment: manual-footnote;

&::footnote-call {
content: counter(manual-footnote, asterisks);
font-size: $font-size-default * 0.7;
vertical-align: super;
line-height: none;

// Shift down
top: $font-size-default / 4;
position: relative;
}
}

// The page-footnote references in body text
// Note that other <sup> styling is defined in
// _print-base-typography.scss
*::footnote-call {
content: counter(footnote);
font-size: $font-size-default * 0.7;
vertical-align: super;
line-height: none;
// Shift down
top: $font-size-default / 4;
position: relative;
&::footnote-marker {
content: counter(manual-footnote, asterisks);
}
}

}
224 changes: 140 additions & 84 deletions assets/js/footnotes.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,161 @@
// Move footnotes to bottoms of pages

console.log('Checking for footnotes to move to endnotes...');

function ebFootnotesToMove() {
'use strict';
// If there are any footnotes...
if (document.querySelector('.footnotes')) {
// And if the page-footnotes setting is on,
// or at least on for one of the footnotes
if (document.body.hasAttribute('data-page-footnotes') ||
document.querySelector('.footnotes .page-footnote')) {
return true;
}
/* eslint no-var: off */

// The above comment is necessary because Prince 11
// can't use let and const, as Standard JS would prefer.

// Move footnote text to the bottoms of pages by moving them
// from the end of the document (where kramdown gathers them)
// to a container div beside their in-text references.

// A counter for footnote references created one-by-one,
// rather than for an entire page or books.
var manualFootnoteCounter = 1

function ebFootnotesToMove (wrapper) {
'use strict'

// If there are any footnotes ...
if (wrapper.querySelector('.footnotes')) {
// ... and if the page-footnotes setting is on,
// or at least on for one of the footnotes
if (wrapper.hasAttribute('data-page-footnotes') ||
wrapper.querySelector('.footnotes .move-to-footnote')) {
// ... then return true
return true
}
}
}

function ebMoveEndnoteToFootnote(noteReference) {
'use strict';
function ebMoveEndnoteToFootnote (noteReference) {
'use strict'

// Get the footnote ID
var footnoteReferenceID = noteReference.hash

// NOTE: Prince's .hash behaviour is unusual: it strips the # out.
// So, let's use getElementById instead of querySelector.
// If it starts with a hash, chop it out.
if (footnoteReferenceID.indexOf('#') === 0) {
footnoteReferenceID = footnoteReferenceID.replace('#', '')
}

// Find the li with the ID from the .footnote's href
var endnote = document.getElementById(footnoteReferenceID)

// Check that we should actually process this footnote.
// If the data-page-footnotes setting isn't applied to this doc...
var wrapper = noteReference.closest('.wrapper')
if (!wrapper.hasAttribute('data-page-footnotes')) {
// ...check whether this particular footnote should be moved.
if (!endnote.querySelector('.move-to-footnote')) {
endnote.classList.add('endnote-text')
noteReference.classList.add('endnote-reference')
return
}
}

var footnoteReferenceID, endnote, pageFootnote,
containingElement, footnoteReferenceContainer;
// Make a div.page-footnote
var pageFootnote = document.createElement('div')
// pageFootnote.className += ' page-footnote'
pageFootnote.classList.add('page-footnote')
pageFootnote.id = footnoteReferenceID

// get the footnote ID
footnoteReferenceID = noteReference.hash;
// Add and increment the manual-footnote counter
if (endnote.querySelector('.move-to-footnote')) {
pageFootnote.setAttribute('page-footnote-counter', manualFootnoteCounter)
manualFootnoteCounter += 1
}

// NOTE: Prince's .hash behaviour is unusual: it strips the # out
// So, let's use getElementById instead of querySelector.
// If it starts with a hash, chop it out.
if (footnoteReferenceID.indexOf('#') === 0) {
footnoteReferenceID = footnoteReferenceID.replace('#', '');
}
// Get the sup that contains the footnoteReference a.footnote
var footnoteReferenceContainer = noteReference.parentNode

// Find the li with the ID from the .footnote's href
endnote = document.getElementById(footnoteReferenceID);
// Get the element that contains the footnote reference
var containingElement = noteReference.parentNode.parentNode

// Check that we should actually process this footnote.
// If the data-page-footnotes setting isn't applied to this doc...
if (!document.body.hasAttribute('data-page-footnotes')) {
// ...check whether this particular footnote should be moved.
if (!endnote.querySelector('.move-to-footnote')) {
return;
}
}
// and add a class to it.
containingElement.parentNode.className += ' contains-footnote'

// Make a div.page-footnote
pageFootnote = document.createElement('div');
pageFootnote.className += ' page-footnote';
pageFootnote.id = footnoteReferenceID;
// Move the endnote contents inside the div.page-footnote
pageFootnote.innerHTML = endnote.innerHTML

// Get the sup that contains the footnoteReference a.footnote
footnoteReferenceContainer = noteReference.parentNode;
// Insert the new .page-footnote at the reference.
// Technically, before the <sup> that contains the reference <a>.
// We have to use insertBefore because Prince borks at insertAdjacentElement.
containingElement.insertBefore(pageFootnote, footnoteReferenceContainer)

// Get the element that contains the footnote reference
containingElement = noteReference.parentNode.parentNode;
// Remove the old endnote, and the old reference to it
// (Prince creates new references to page-footnotes)
endnote.parentNode.removeChild(endnote)
footnoteReferenceContainer.parentNode.removeChild(footnoteReferenceContainer)
}

// and add a class to it.
containingElement.parentNode.className += ' contains-footnote';
function ebFootnotesRenumberEndnotes () {
'use strict'

// Get all the endnote lists in the doc
var endNoteLists = document.querySelectorAll('div.footnotes ol')

// For each list, update the endnote numbers
endNoteLists.forEach(function (list) {
var endnoteListItems = list.querySelectorAll('li.endnote-text')

// If we moved any notes to on-page footnotes with .move-to-footnote,
// and we still have a list of endnotes, then we must renumber the endnote
// references, because now the reference numbers will not match the
// auto-numbered list of endnotes.
// Note that the endnote *references* might be repeated (e.g. two places
// in the text that refer to the same endnote).

var i
for (i = 0; i < endnoteListItems.length; i += 1) {
var endnoteNewNumber = i + 1
var endnoteOldID = endnoteListItems[i].id
var endnoteNewID = endnoteOldID.replace(/fn:\d+$/, 'fn:' + endnoteNewNumber)
var endnoteReferences = document.querySelectorAll('[href="#' + endnoteOldID + '"]')

// Update all the endnote references that refer to the old number
endnoteReferences.forEach(function (reference) {
reference.innerHTML = endnoteNewNumber
reference.hash = endnoteNewID
})

// Update the IDs of the endnotes sequentially
endnoteListItems[i].id = endnoteListItems[i].id.replace(/fn:\d+$/, 'fn:' + endnoteNewNumber)
}
})
}

// Move the endnote contents inside the div.page-footnote
pageFootnote.innerHTML = endnote.innerHTML;
function ebEndnotesToFootnotes (wrapper) {
'use strict'

// Insert the new .page-footnote at the reference.
// Technically, before the <sup> that contains the reference <a>.
// We have to use insertBefore because Prince borks at insertAdjacentElement.
containingElement.insertBefore(pageFootnote, footnoteReferenceContainer);
// Get all the a.footnote links we want to move
var footnoteReferences = wrapper.querySelectorAll('.footnote')

// Remove the old endnote, and the old reference to it
// (Prince creates new references to page-footnotes)
endnote.parentNode.removeChild(endnote);
footnoteReferenceContainer.parentNode.removeChild(footnoteReferenceContainer);
// Process all those footnotes
var i
for (i = 0; i < footnoteReferences.length; i += 1) {
ebMoveEndnoteToFootnote(footnoteReferences[i])

if (i === footnoteReferences.length - 1) {
console.log('On-page footnotes moved. Now to renumber endnotes ...')
ebFootnotesRenumberEndnotes()
}
}
}

function ebEndnotesToFootnotes() {
'use strict';

// get all the a.footnote links
var footnoteReferences = document.querySelectorAll('.footnote');

// Process all the footnotes
var i;
for (i = 0; i < footnoteReferences.length; i += 1) {
ebMoveEndnoteToFootnote(footnoteReferences[i]);

// // If page-footnotes are on for the document,
// // move all the endnotes to page footnotes
// if (document.body.getAttribute('data-page-footnotes')) {
// ebMoveEndnoteToFootnote(footnoteReferences[i]);
// } else {
// // If a given endnote contains .move-to-footnote,
// // move that one endnote.
// console.log('footnoteReferences[i].innerHTML: ' + footnoteReferences[i].innerHTML);
// if (footnoteReferences[i].querySelector('.move-to-footnote')) {
// ebMoveEndnoteToFootnote(footnoteReferences[i]);
// }
// }
// If there are footnotes to move, move them.
function ebFootnotesForPDF () {
'use strict'

var wrappers = document.querySelectorAll('.wrapper')

wrappers.forEach(function (wrapper) {
if (ebFootnotesToMove(wrapper)) {
console.log('We have endnotes to move to page footnotes ...')
ebEndnotesToFootnotes(wrapper)
}
})
}

/// If there are footnotes to move, move them.
if (ebFootnotesToMove()) {
console.log('Yes, we have footnotes to move...');
ebEndnotesToFootnotes();
}
// Go
ebFootnotesForPDF()

0 comments on commit d105a84

Please sign in to comment.