sustainableweb-ig

ReSpec Multipage

Features

HTML

The below is required to be included, and customized for every page so the buttons work:

<ol class="pageButtons">
	<li><a class="previousPage" href="">Previous page<br>None</a></li>
	<li><a class="fullDocument" href="#full-document">Full document</a></li>
	<li><a class="nextPage" href="">Next page<br>None</a></li>
</ol>

CSS

The below must be included within the <head> element:

@media print { #toc { display: none !important; } }
@media (scripting: enabled) {
	.hide { display: inherit !important; height: 1px; left: -1000px; overflow: hidden; position: absolute; top: -1px; width: 1px; }
	.show { height: auto; left: auto; overflow: unset; position: static; top: auto; width: auto; } }
.pageButtons { margin-top: 2em; padding-left: 0; }
.pageButtons, .pageButtons li, .pageButtons tbody { display: flex; flex: 1; gap: 10px; }
.pageButtons a { align-items: center; border: medium solid #d9d9d9; background-color: #F3F3F3; display: flex; font-weight: bold; flex: 1; padding: 0.5em 1em; }
.previousPage { justify-content: left; } .fullDocument { justify-content: center; } .nextPage { justify-content: right; text-align: right; }
@media (scripting: none) {
	@media print { #toc { display: block !important; } }
	.pageButtons { display: none;}
	.hide { display: block !important; height: auto; left: auto; overflow: unset; position: static; top: auto; width: auto; } }

JavaScript

The below must be included within the <head> element:

<script>
function addMultipage() {
	window.addEventListener('hashchange', onHashChange);
	onHashChange(); }
window.addEventListener("load", (event) => {
	addMultipage(); });
function hashSection(hash) {
	// Grabs the ID's from every element requested
	let arrayGrab = Array.from(document.querySelectorAll(hash));
	let mapID = arrayGrab.map(el => el.getAttribute('id'));
	let filtered = mapID.filter(function (el) { return el != null; });
	return filtered; }
function onHashChange() {
	// Gets the current anchor reference & main locations
	let current = window.location.hash.substring(1);
	let sections = hashSection('body > section');
	// Assign your document sections here (to search for IDs)
	let introduction = hashSection('#introduction *');
	let ux = hashSection('#user-experience-design *');
	let webdev = hashSection('#web-development *');
	let infra = hashSection('#hosting-infrastructure-and-systems *');
	let biz = hashSection('#business-strategy-and-product-management *');
	let glossary = hashSection('#glossary *');
	let credits = hashSection('#acknowledgments section');
	let changelog = hashSection('#changelog section');
	let refs = hashSection('#references section');
	let all = sections.concat(sections, introduction, ux, webdev, infra, biz, glossary, credits, changelog, refs);
	// Ensures the TOC is only shown to printers when the initial page is loaded
	if (document.body.classList.contains('full-document')) {
		document.body.classList.remove('full-document'); }
	// If current hash or full-document matches, visibility is assured & buttons appear
	// Otherwise content and buttons disappear until requested for that section
	if (current) {
		for (const value of sections) {
			for (const value of sections) {
				// This makes the content visible
				if ( value != current ) {
					document.getElementById(value).classList.remove('show');
					document.getElementById(value).classList.add('hide'); } else {
					document.getElementById(value).classList.remove('hide');
					document.getElementById(value).classList.add('show'); }}
			if (current == "abstract" || current == "sotd") { header(); }
			// Adds buttons to references as ReSpec auto-generated this section
			for (const value of refs) {
				if ( value != current && document.getElementById('references').querySelector('.pageButtons') == null ) {
					document.getElementById('references').innerHTML = document.getElementById('references').innerHTML + `<ol class="pageButtons">
						<li><a class="previousPage" href="#changelog">Previous page<br>Changelog</a></li>
						<li><a class="fullDocument" href="#full-document">Full document</a></li>
						<li></li>
					</ol>`; } }
			// This ensures the buttons don't appear for full-document mode
			// It also shows the TOC to printers on the initial page
			if (current == "full-document") {
				document.body.classList.add("full-document");
				for (const value of sections) {
					document.getElementById(value).classList.remove('hide');
					document.getElementById(value).classList.add('show'); }
					document.querySelectorAll('.pageButtons').forEach(e => e.classList.remove('show'));
					document.querySelectorAll('.pageButtons').forEach(e => e.classList.add('hide'));
					window.scrollTo(0, 0); } else {
					document.querySelectorAll('.pageButtons').forEach(e => e.classList.remove('hide'));
					document.querySelectorAll('.pageButtons').forEach(e => e.classList.add('show')); }
			// If no hash is visible, show at least something on the screen
			let check = false;
			for (const value of all) {
				if (current == value && check == false ) { check = true; } }
			if (check == false) { header(); } }
		// Assign your headings here to do the above for more than just the main sections
		heading(introduction,"introduction");
		heading(ux,"user-experience-design");
		heading(webdev,"web-development");
		heading(infra,"hosting-infrastructure-and-systems");
		heading(biz,"business-strategy-and-product-management");
		heading(glossary,"glossary");
		heading(credits,"acknowledgments");
		heading(changelog,"changelog");
		heading(refs,"references"); } else {
		for (const value of sections) {
			document.getElementById(value).classList.remove('show');
			document.getElementById(value).classList.add('hide'); }
		header(); }
		// Scrolls to the correct section of the page once its rendered it
		for (const value of all) {
			if (window.location.hash && window.location.hash !="#full-document" && value == current){
				document.getElementById(window.location.hash.substring(1)).scrollIntoView(); } } }
function heading(hash, string) {
	// This function examines headings for things needing to be hidden or made visible
	// It also ensures jump-to-location works as expected with content being shuffled
	let current = window.location.hash.substring(1);
	let sections = hashSection('body > section');
	if (hash.includes(current)) {
		for (const value of sections) {
			if (value == string) {
				document.getElementById(value).classList.remove('hide');
				document.getElementById(value).classList.add('show'); } else {
				document.getElementById(value).classList.remove('show');
				document.getElementById(value).classList.add('hide'); } } }
	location.hash = "#" + current; }
	// This ensures the abstract and sotd content exists if no fragment is visible
function header() {
	document.getElementById("abstract").classList.remove('hide');
	document.getElementById("sotd").classList.remove('hide');
	document.getElementById("abstract").classList.add('show');
	document.getElementById("sotd").classList.add('show');
	buttons("sotd"); window.scrollTo(0, 0);
	if (window.location.hash != "#full-document") {
		document.querySelectorAll('.pageButtons').forEach(e => e.classList.remove('hide'));
		document.querySelectorAll('.pageButtons').forEach(e => e.classList.add('show')); } }
function buttons(id) {
	// Adds the buttons where they are required
	var d = document.querySelector('#' + id + ' ol');
	d.parentNode.appendChild(d); }
</script>

Note: Code comments can be removed and section names should be configured to match your specification TOC.

The below must be included in the respecConfig section:

var respecConfig = {
	lint: { "local-refs-exist": false, },
	postProcess: [addMultipage] }