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

[Feature] Support markdown with metadata #3527

Open
wants to merge 4 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
33 changes: 33 additions & 0 deletions css/theme/source/metadata.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* metadata.scss */
/* This css is only for example, you can add your own css.*/

section {
width: 100%;
height: 100%;
}

.content-container {
height: 90%;
font-size: 32px;
text-align: left;
}

footer {
height: 10%;
color: #7f7f7f;
display: flex;
justify-content: space-between;
}

.cover .content-container {
align-content: center;
display: grid;
}

.title-image .image-container {
height: 80%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
32 changes: 32 additions & 0 deletions dist/theme/metadata.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* metadata.scss */
/* This css is only for example, you can add your own css.*/
section {
width: 100%;
height: 100%;
}

.content-container {
height: 90%;
font-size: 32px;
text-align: left;
}

footer {
height: 10%;
color: #7f7f7f;
display: flex;
justify-content: space-between;
}

.cover .content-container {
align-content: center;
display: grid;
}

.title-image .image-container {
height: 80%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"colors": "^1.4.0",
"core-js": "^3.33.1",
"fitty": "^2.3.7",
"front-matter": "^4.0.2",
"glob": "^10.3.10",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^8.0.0",
Expand All @@ -51,7 +52,9 @@
"gulp-tap": "^2.0.0",
"gulp-zip": "^5.1.0",
"highlight.js": "^11.9.0",
"js-yaml": "^4.1.0",
"marked": "^4.3.0",
"mustache": "^4.2.0",
"node-qunit-puppeteer": "^2.1.2",
"qunit": "^2.20.0",
"rollup": "^4.1.5",
Expand Down
6 changes: 4 additions & 2 deletions plugin/markdown/markdown.esm.js

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions plugin/markdown/markdown.js

Large diffs are not rendered by default.

106 changes: 103 additions & 3 deletions plugin/markdown/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*/

import { marked } from 'marked';
import yaml from 'js-yaml';
import Mustache from 'mustache';
import fm from 'front-matter';

const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n',
DEFAULT_VERTICAL_SEPARATOR = null,
Expand Down Expand Up @@ -124,6 +127,11 @@ const Plugin = () => {
// with parsing
content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );

// render the template with the content only if there is metadata
if (options.metadata){
content = renderTemplate(content, options)
}

return '<script type="text/template">' + content + '</script>';

}
Expand All @@ -146,6 +154,9 @@ const Plugin = () => {
content,
sectionStack = [];

// separates default metadata from the markdown file
[ markdown, options ] = parseFrontMatter(markdown, options)

// iterate until all blocks between separators are stacked up
while( matches = separatorRegex.exec( markdown ) ) {
const notes = null;
Expand Down Expand Up @@ -181,18 +192,23 @@ const Plugin = () => {

// flatten the hierarchical stack, and insert <section data-markdown> tags
for( let i = 0, len = sectionStack.length; i < len; i++ ) {
// slideOptions is created to avoid mutating the original options object with default metadata
let slideOptions = {...options}

// vertical
if( sectionStack[i] instanceof Array ) {
markdownSections += '<section '+ options.attributes +'>';
markdownSections += '<section ' + slideOptions.attributes + '>';

sectionStack[i].forEach( function( child ) {
markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
[content, slideOptions] = separateInlineMetadataAndMarkdown(child, slideOptions)
markdownSections += '<section ' + slideOptions.attributes + ' data-markdown>' + createMarkdownSlide( content, slideOptions ) + '</section>';
} );

markdownSections += '</section>';
}
else {
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
[content, slideOptions] = separateInlineMetadataAndMarkdown(sectionStack[i], slideOptions)
markdownSections += '<section ' + slideOptions.attributes + ' data-markdown>' + createMarkdownSlide( content, slideOptions ) + '</section>';
}
}

Expand Down Expand Up @@ -415,6 +431,90 @@ const Plugin = () => {

}

/**
* Parse the front matter from the Markdown document
*
* Returns updated options with the default metadata
* and updated content without the front matter
*/
function parseFrontMatter(content, options) {
options = getSlidifyOptions( options)

const parsedFrontMatter = fm(content)

content = parsedFrontMatter.body;
if (parsedFrontMatter.frontmatter){
options.metadata = yaml.load(parsedFrontMatter.frontmatter);
if (!('slideType' in options.metadata)) {
content = `Missing "slideType" in default metadata`
console.error(content)
delete options.metadata
}
}
return [content, options];
}

/**
* Separates the inline metadata and content for each slide
*
* Returns updated options with the inline metadata and
* updated markdown without the inline metadata for each slide
*/
function separateInlineMetadataAndMarkdown(markdown, options) {
const yamlRegex = /```(yaml|yml)\n([\s\S]*?)```(\n[\s\S]*)?/g;
if (yamlRegex.test(markdown)){
yamlRegex.lastIndex = 0;

const markdownParts = yamlRegex.exec(markdown)
const metadata = markdownParts[2] || {}
markdown = markdownParts[3] || ''
if (metadata){
try {
const metadataYAML = yaml.load(metadata);
if (metadataYAML === undefined || metadataYAML.slideType === undefined) {
throw new Error("The inline metadata is not valid.")
}
options.metadata = {...options.metadata, ...metadataYAML}
options.attributes = 'class=' + options.metadata.slideType;
} catch (error) {
markdown = error.message
console.error(error)
}
}
} else if (options.metadata){
options.attributes = 'class=' + options.metadata.slideType;
}

return [markdown, options]
}

/**
* Renders the template for each slide
*
* Returns the rendered template with the content
*/
function renderTemplate(content, options) {
try {
options = getSlidifyOptions(options)
const url = new URL(import.meta.url);
const templatePath = `${url.origin}/templates/${options.metadata.slideType}-template.html`
const xhr = new XMLHttpRequest()
xhr.open('GET', templatePath, false)
xhr.send()
let tempDiv = document.createElement('div');
if (xhr.status === 200) {
tempDiv.innerHTML = Mustache.render(xhr.responseText, { content: content, metadata: options.metadata });
} else {
tempDiv.innerHTML = `Template for slideType "${options.metadata.slideType}" not found.`
console.error(`Failed to fetch template. Status: ${xhr.status}`);
}
return tempDiv.textContent;
} catch (error) {
console.error('Error:', error);
throw error;
}
}

return {
id: 'markdown',

Expand Down
16 changes: 16 additions & 0 deletions templates/cover-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script type="x-tmpl-mustache">
<div class="content-container">
<div class="content-wrapper">
<p>{{ metadata.presenterName }}</p>
{{ content }}
</div>
</div>
<footer>
<div class="footer-content">
<p>{{ metadata.styles }}</p>
</div>
<div class="footer-logo">
<img data-src="{{ metadata.logo }}" alt="Logo">
</div>
</footer>
</script>
18 changes: 18 additions & 0 deletions templates/title-content-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script type="x-tmpl-mustache">
<div class="content-container">
<div class="content-wrapper">

{{ content }}

</div>
</div>

<footer>
<div class="footer-content">
<p>{{ metadata.styles }}</p>
</div>

<div class="footer-logo">
<img data-src="{{ metadata.logo }}" alt="Logo"></div>
</footer>
</script>
37 changes: 37 additions & 0 deletions templates/title-image-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script type="x-tmpl-mustache">
<div class="content-container">
<div class="content-wrapper">

{{ content }}

</div>

<div class="image-container">
{{ #metadata.images }}
<div class="image-wrapper">
<img data-src="{{ url }}" alt="{{ url }}">
{{ #imageDescription }}
<div class="image-description">
<p>{{ . }}</p>
</div>
{{ /imageDescription }}

{{ #imageCredit }}
<div class="image-credit">
<p>Source: {{ . }}</p>
</div>
{{ /imageCredit }}
</div>
{{ /metadata.images }}
</div>
</div>

<footer>
<div class="footer-content">
<p>{{ metadata.styles }}</p>
</div>

<div class="footer-logo">
<img data-src="{{ metadata.logo }}" alt="Logo"></div>
</footer>
</script>
Loading