sustainableweb-ig

ReSpec Multipage

Note: The Sustainable Web IG has dropped this feature in favor of our filtering system so we won’t be continuing to develop it, that being said, it is fully developed and stable so others can continue to utilize it within ReSpec.

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 { body:not(.full-document) #toc { display: none !important; } }
@media (scripting: enabled) {
	.hide { content-visibility: hidden; display: inherit !important; height: 1px; left: -1000px; overflow: hidden; position: absolute; top: -1px; width: 1px; }
	.show { content-visibility: visible; 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: var(--tocsidebar-bg); 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) {
	.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 considerations = hashSection('#considerations section');
	let glossary = hashSection('#glossary *');
	let credits = hashSection('#acknowledgments section');
	let changelog = hashSection('#changelog section');
	let refs = hashSection('#references *');
	let all = sections.concat(sections, introduction, ux, webdev, infra, biz, considerations, 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(considerations,"considerations");
		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] }