-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
base: master
Are you sure you want to change the base?
Add sections plugin #733
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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). | ||
|
||
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) |
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" ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should set these in the |
||
|
||
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; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ); |
There was a problem hiding this comment.
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.