/**
* The presentation mode enum
*/
enum PresentationMode {
/**
* Presenting
*/
Slides = 0,
/**
* Not presenting
*/
Web = 1,
}
/**
* The object that will handle all of the
* presentation mode activities
*/
class PresentationModeHider {
/**
* The current mode
*/
private mode: PresentationMode;
/**
* A constant value for the localStorage
* Key
*/
private readonly queryKey = 'presentation_mode';
/**
* A constant value for web-only elements class
*/
private readonly webClass = 'article-content';
/**
* A constant value for slides-only elements class
*/
private readonly preClass = 'presentation-only';
constructor() {
this.mode = this.getMode();
this.setMode(); // we do this here to make sure there is a value
this.assignClassesViaComments();
this.tryMoveToFirstChapter();
window.addEventListener('keyup', ev => {
if (!ev.altKey) {
return;
}
if (ev.key == 'p' || ev.key == 'P' || ev.code == 'KeyP') {
this.toggle();
}
});
}
/**
* If in presentation mode and at the root, left and right
* buttons do not page through the presentation. This will
* check for presentation mode and root, if both are true
* it will attempt to move to the first chapter (which is always the same)
*/
tryMoveToFirstChapter() {
if (this.mode === PresentationMode.Slides
&& (location.pathname === ''
|| location.pathname === '/')) {
let chList = document.querySelector('.sidebar .chapter') as HTMLUListElement;
if (!chList) {
return console.error('unable to find chapter 1 link for paging. Please manually click into chapter 1');;
}
let firstLi = chList.firstChild as HTMLLIElement;
let firstLink = firstLi.firstChild as HTMLAnchorElement;
firstLink.click();
}
}
/**
* This loops though the DOM and finds any comments with the value
* 'web-only' or 'slides-only'. It then applies the correct class
* to all elements between this comment and the corresponding end
* comment
* ```html
*
* ```
*
* becomes
*
* ```html
*
* ```
*/
assignClassesViaComments() {
let iter = document.createNodeIterator(document.body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT, null);
let node;
let modeClass = this.mode === PresentationMode.Web ? 'not-presenting' : 'presenting';
let cls = null;
while (node = iter.nextNode()) {
if (node.nodeType === 8) { // comment
let value = node.nodeValue.trim();
if (value === "web-only") {
cls = this.webClass;
} else if (value === "slides-only") {
cls = this.preClass;
} else if (value.startsWith('notes')) {
this.processNotes(value);
} else if (value === "web-only-end"
|| value === "slides-only-end"
|| value === "notes-end") {
cls = null;
}
} else if (node.nodeType === 1 && cls !== null) { // element
node.classList.add(cls, modeClass);
}
}
}
/**
* Get the current presentation mode from storage
* @returns The last known presentation mode or the default (Web)
*/
private getMode(): PresentationMode {
let mode = localStorage.getItem(this.queryKey);
if (mode === null) {
return PresentationMode.Web
}
try {
let ret = parseInt(mode);
if (ret > 1 || ret < 0) {
console.error('presentation_mode was out of range', ret);
return PresentationMode.Web;
}
return ret;
} catch (e) {
console.error('presentation_mode present in localStorage but value is not an integer', mode, e);
return PresentationMode.Web;
}
}
/**
* Update the storage to have the same value as `this.mode`
*/
private setMode() {
localStorage.setItem(this.queryKey, this.mode.toString());
}
/**
* Find all of the `.presentation-only` and `.article-content` items
* and update them to have either a `presenting` or `not-presenting` class
*/
private updatePage() {
this.updateElements(document.querySelectorAll('.presentation-only'));
this.updateElements(document.querySelectorAll('.article-content'))
}
/**
* Update a list of `HTMLDivElement`s to have either the `presenting`
* or `not-presenting` class
* @param elements A list of `HTMLDivElement`s to be updated
*/
private updateElements(elements: NodeListOf) {
for (var i = 0; i < elements.length; i++) {
let el = elements[i];
if (this.mode === PresentationMode.Slides) {
el.classList.replace('not-presenting', 'presenting');
} else {
el.classList.replace('presenting', 'not-presenting');
}
}
}
/**
* Toggle between`Web` and `Slides` presentation mode
* @remarks
* This will update localStorage and the view
*/
private toggle() {
switch (this.mode) {
case PresentationMode.Slides:
this.mode = PresentationMode.Web;
break;
case PresentationMode.Web:
this.mode = PresentationMode.Slides;
break;
}
this.setMode();
this.updatePage();
}
processNotes(text: string) {
let startIdx = text.indexOf('\n');
console.log(`%c${text.substr(startIdx+1)}`, 'font-size: 14pt;');
}
}
const ___presentationModeHider = new PresentationModeHider();