Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sections plugin #733

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/plugins/section/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Section plugin
==============

Sections for impress.js presentations

Usage
-----

Add `<section>` and `<h1>` tags to group your steps as you can see here:

```html
<div id="title" class="step">
<h1>Title of Presentation</h1>

<p>Our agenda for today:</p>
<ul id="impress-section-agenda"></ul>
</div>

<section>
<h1>Section Title</h1>
<div id="first-slide" class="step">

</div>

<!-- Nested sections are supported as well -->
<section>
<h1>Sub Section Title</h1>
<div id="second-slide" class="step">

</div>
</section>
</section>
```

The section name and the current index of the section will be displayed in your presentation.
Therefore, add a div for displaying the current section and/or progress as you can see it here:


```html
<div id="impress-section-overview">
<div class="impress-section-numbers"></div>
<div class="impress-current-section"></div>
</div>
```

```css
#impress-section-overview {
display: flex;
align-items: flex-end;
justify-content: flex-end;
}

#impress-section-overview .impress-section-numbers {
display: inline-block;
margin-top: .25em;
padding: .1em .25em;
color: white;
background: #aaa;
}

#impress-section-overview .impress-current-section {
padding-left: 5px;
}

#impress-section-overview .impress-current-section .section-spacer {
padding-left: 0.3rem;
padding-right: 0.3rem;
}
```

Feel free to change the style of your section overview as you like by editing the CSS file.

Additionally, the plugin will generate an agenda outline for you if you want it to. Therefore, add a `ul` tag with the
id `impress-section-agenda` to any of your divs of the presentation (as shown in the aforementioned HTML snippet).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automatic table of contents is cool! Thanks.

Furthermore, this plugin adds the class `active-section` to all steps of the action section and `hidden-section` to all
steps that do not belong to this section. You can use this classes, e.g. to hide the steps of another section:

```css
.step.hidden-section {
opacity: 0;
}
```

Author
------

Copyright 2020: Marc Schreiber (@schrieveslaach)
210 changes: 210 additions & 0 deletions src/plugins/section/section.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
* Sections Plugin
*
* Copyright 2019 Marc Schreiber (@schrieveslaach)
* Released under the MIT license.
*/
/* global document */

( function( document ) {
"use strict";
let root, api, gc;

let indexedSteps = null;

const agenda = document.querySelector( "#impress-section-agenda" );
const currentSection = document.querySelector(
"#impress-section-overview .impress-current-section" );
const sectionNumbers = document.querySelector(
"#impress-section-overview .impress-section-numbers" );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should set these in the impress:init event. Some other plugin/js might generate html dynamically before this plugin.


function findSection( element ) {
if ( element.parentNode == null ) {
return null;
}
if ( element.parentNode.tagName === "SECTION" ) {
const sectionElement = element.parentNode;

let title = "";
const headingElements = sectionElement.getElementsByTagName( "H1" );
if ( headingElements.length > 0 ) {
title = headingElements[ 0 ].textContent;
}

return {
element: element,
sectionElement: sectionElement,
sectionTitle: title
};
}
return findSection( element.parentNode );
}

function indexSteps() {
return Array.prototype.slice.call( root.querySelectorAll( ".step" ) )
.map( function( step ) {
return findSection( step );
} )
.filter( function( section ) {
return section != null;
} )
.map( function( step, index ) {
step.index = index + 1;
return step;
} );
}

document.addEventListener( "impress:init", function( event ) {
root = event.target;
api = event.detail.api;
gc = api.lib.gc;

if ( agenda !== null ) {
indexedSteps = indexSteps();

function depth( heading, n = 0 ) {
const parent = heading.parentElement;
if ( parent !== null ) {
if ( parent.tagName === "SECTION" ) {
return depth( parent, n + 1 );
}
return depth( parent, n );
}
return n;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code from here to the end of the function would benefit from comments.

const headings = [].slice.call( document.querySelectorAll( "section h1" ) ).map( function ( heading ) {
return { text: heading.innerText, depth: depth( heading ) };
} );

headings.reduce( function ( dom, heading, index ) {
if ( index === 0 ) {
const li = document.createElement( "li" );
li.innerText = heading.text;

dom.appendChild( li );

return {
ul: dom,
depth: heading.depth
};
}

if ( dom.depth === heading.depth ) {
const li = document.createElement( "li" );
li.innerText = heading.text;

dom.ul.appendChild( li );
} else if ( dom.depth < heading.depth ) {
const ul = document.createElement( "ul" );
const li = document.createElement( "li" );
li.innerText = heading.text;
ul.appendChild( li );

const parentLi = dom.ul.lastChild;
parentLi.appendChild( ul );

return {
ul,
depth: heading.depth
};
} else {
const ul = dom.ul.parentElement.parentElement;

const li = document.createElement( "li" );
li.innerText = heading.text;
ul.appendChild( li );

return {
ul,
depth: heading.depth
};
}

return dom;
}, agenda );
}
} );

document.addEventListener( "impress:steprefresh", function( event ) {
updateSectionOverview( event.target );
} );

function updateSectionOverview( targetElement ) {
if ( indexedSteps === null ) {
return;
}

const indexedStep = indexedSteps.find( function( step ) {
return step.element.isSameNode( targetElement );
} );

if ( sectionNumbers !== null ) {
if ( indexedStep === undefined ) {
sectionNumbers.innerText = "";
} else {
sectionNumbers.innerText = indexedStep.index + "/" + indexedSteps.length;
}
}

function findSectionTitles( sectionElement, titles = [] ) {
if ( sectionElement.tagName === "SECTION" ) {
const headingElements = sectionElement.getElementsByTagName( "H1" );
if ( headingElements.length > 0 ) {
titles = titles.concat( headingElements[ 0 ].textContent );
}
}

if ( sectionElement.parentElement !== null ) {
return findSectionTitles( sectionElement.parentElement, titles );
}

return titles;
}
const titles = indexedStep !== undefined ? findSectionTitles( indexedStep.sectionElement ).reverse() : [];

if ( currentSection !== null ) {
currentSection.innerHTML = "";

if ( indexedStep !== undefined ) {
titles.forEach( function( title, index ) {
if ( index > 0 ) {
const span = document.createElement( "span" );
span.classList.add( "section-spacer" );
span.innerText = "❯";
currentSection.appendChild( span );
}

const span = document.createElement( "span" );
span.innerText = title;
currentSection.appendChild( span );
} );
}
}

const currentSectionElement = indexedStep !== undefined ? indexedStep.sectionElement : null;
Array.prototype.slice.call( root.querySelectorAll( ".step" ) ).forEach( function( step ) {
const sectionOfStep = findSection( step );

let activeSection = false;
if ( currentSectionElement === null && sectionOfStep === null ) {
activeSection = true;
}
if ( sectionOfStep !== null && titles.length > 0 ) {
const titlesOfStep = findSectionTitles( sectionOfStep.sectionElement ).reverse();
if ( titlesOfStep.length > 0 && titlesOfStep[ 0 ] === titles[ 0 ] ) {
activeSection = true;
}
}

if ( activeSection ) {
step.classList.add( "active-section" );
step.classList.remove( "hidden-section" );
} else {
step.classList.remove( "active-section" );
step.classList.add( "hidden-section" );
}
} );
}

} )( document );