Public
Edited
Dec 1
Paused
Importers
20 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
theme
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Inputs.button("Go to last slide", {
reduce() {
return goToLast();
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function __normalizeDeckInput(slidesInput) {
const flattened = slidesInput.map((s) => {
if (_.isFunction(s)) {
return s();
}
if (_.isString(s)) {
return s.split("\n---\n");
}
return s;
});

return _.flattenDeep(flattened).map((s, i) => {
// We will render this later
let prerendered;
if (__isHtmlImageElement(s) || __isImageAttachment(s)) {
prerendered = __getImageSlideHtmlFromAttachment(s);
// } else if (__isVideoAttachment(s)) {
// prerendered = __getVideoSlideHtmlFromAttachment(s);
} else if (typeof s === "object") {
prerendered = s;
}

// FIXME: this will not work for prerendered items
const markdown = (s.trim ? s : s.toString())
.trim()
.replaceAll("----", "---");

let h1 = 0;
let h2 = 0;
let h3 = 0;
let h4 = 0;
let h5 = 0;
let h6 = 0;
let p = 0;
let ul = 0;
let ol = 0;
let pre = 0;

// FIXME: this is unreliable and hacky. Should parse the markdown properly. Using HTML can easily break this
const lines = _.compact(markdown.split("\n").map((l) => l.trim()));
for (let i = 0; i < lines.length; ++i) {
if (lines[i].indexOf("# ") === 0) {
h1++;
} else if (lines[i].indexOf("## ") === 0) {
h2++;
} else if (lines[i].indexOf("### ") === 0) {
h3++;
} else if (lines[i].indexOf("#### ") === 0) {
h4++;
} else if (lines[i].indexOf("##### ") === 0) {
h5++;
} else if (lines[i].indexOf("###### ") === 0) {
h6++;
} else if (lines[i].indexOf("- ") === 0 || lines[i].indexOf("* ") === 0) {
ul++;

// FIXME: doesn't actually detect ordered numbers and every list item
} else if (lines[i].indexOf("1. ") === 0) {
ol++;
} else if (lines[i].indexOf("```") === 0) {
pre++;
} else {
p++;
}
}

const isCover = !!h1;
const slideshowId = config ? config.id : "slideshow";

// FIXME: this is unreliable
// The purpose is to detect if the slide will be rendered with a parent div or not
const hasWrapper =
lines.length > 1 && (h1 || h2 || h3 || h4 || h5 || h6 || p || pre);

return {
iAmSlide: true,

slideshowId,
id: slideshowId + "-" + i,
i,
prerendered,
markdown,
lines,
hasWrapper,
isCover,

h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol,
pre
};
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
/* async */ function __getImageSlideHtmlFromAttachment(imageAttachment) {
const img = imageAttachment.src
? imageAttachment
: /* await */ imageAttachment.image();
return html`<img src="${img.src}" alt="${
img.alt || img.title || img.name
}" title="${
img.title || img.alt || img.name
}" style="position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%); max-width: 100%; max-height: 100%; object-fit: contain;">`;
}
Insert cell
async function __getVideoSlideHtmlFromAttachment(videoAttachment) {
return html`<video controls style="position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%); max-width: 100%; max-height: 100%; object-fit: contain;">
<source src="${await videoAttachment.url()}" type="${
videoAttachment.mimeType
}" />
<a href="${await videoAttachment.url()}">Download video</a>.
</video>`;
}
Insert cell
Insert cell
function __getSlideHtml({
deckIndex,
slideIndex,
slideCount,
currentIndex,
prevIndex,
nextIndex,
firstIndex,
lastIndex
}) {
const slide = __decks[deckIndex][slideIndex];
const c = __deckConfigs[deckIndex];

const classes = [
"slide",
...c.theme.map((t) => `${t}-slide`),
`slide-${slideIndex}`
];

if (slideIndex < currentIndex) {
classes.push("slide-past");
classes.push("slide-not-current");
} else if (slideIndex > currentIndex) {
classes.push("slide-upcoming");
classes.push("slide-not-current");
} else {
classes.push("slide-current");
}

if (slideIndex === firstIndex) {
classes.push("slide-first");
}

if (slideIndex === lastIndex) {
classes.push("slide-last");
}

if (slideIndex === prevIndex) {
classes.push("slide-prev");
}

if (slideIndex === nextIndex) {
classes.push("slide-next");
}

const tags = ["h1", "h2", "h3", "h4", "h5", "h6", "p", "ul", "ol", "pre"];
for (let i = 0; i < tags.length; ++i) {
if (slide[tags[i]]) {
classes.push(`slide-with-${tags[i]}`);
}
}

if (slide.isCover) {
classes.push("slide-cover");
}

const content = slide.prerendered ? slide.prerendered : md`${slide.markdown}`;

return html`<div
data-slide-i="${slideIndex}"
class="${classes.join(" ")}"
style="font-size: ${c.fontSize}em;"
>${!slide.hasWrapper ? "<div>" : ""}${content}${
!slide.hasWrapper ? "</div>" : ""
}</div>`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more