1 Commits
Author SHA1 Message Date
heimoshuiyu e934f3ef3d ai lecture 2024-01-05 14:53:07 +08:00
100 changed files with 4000 additions and 6491 deletions
-8
View File
@@ -1,8 +0,0 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,node_modules,package-lock.json,*.css,.codespellrc
check-hidden = true
# Ignore super long lines -- must be minimized etc, acronyms
# and some near hit variables
ignore-regex = ^.{120,}|\b(currentY|FOM)\b
# ignore-words-list =
@@ -1,4 +1,4 @@
name: Tests
name: tests
on:
- push
-23
View File
@@ -1,23 +0,0 @@
# Codespell configuration is within .codespellrc
---
name: Spellcheck
on:
push:
branches: [master]
pull_request:
branches: [master]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Codespell
uses: codespell-project/actions-codespell@v2
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011-2024 Hakim El Hattab, http://hakim.se, and reveal.js contributors
Copyright (C) 2011-2023 Hakim El Hattab, http://hakim.se, and reveal.js contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+22 -2
View File
@@ -4,7 +4,7 @@
</a>
<br><br>
<a href="https://github.com/hakimel/reveal.js/actions"><img src="https://github.com/hakimel/reveal.js/workflows/tests/badge.svg"></a>
<a href="https://slides.com/"><img src="https://static.slid.es/images/slides-github-banner-320x40.png?1" alt="Slides" width="160" height="20"></a>
<a href="https://slides.com/"><img src="https://s3.amazonaws.com/static.slid.es/images/slides-github-banner-320x40.png?1" alt="Slides" width="160" height="20"></a>
</p>
reveal.js is an open source HTML presentation framework. It enables anyone with a web browser to create beautiful presentations for free. Check out the live demo at [revealjs.com](https://revealjs.com/).
@@ -17,6 +17,26 @@ Want to create reveal.js presentation in a graphical editor? Try <https://slides
---
### Sponsors
Hakim's open source work is supported by <a href="https://github.com/sponsors/hakimel">GitHub sponsors</a>. Special thanks to:
<div align="center">
<table>
<td align="center">
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=revealjs&utm_source=github">
<div>
<img src="https://user-images.githubusercontent.com/629429/151508669-efb4c3b3-8fe3-45eb-8e47-e9510b5f0af1.svg" width="290" alt="WorkOS">
</div>
<b>Your app, enterprise-ready.</b>
<div>
<sub>Start selling to enterprise customers with just a few lines of code. Add Single Sign-On (and more) in minutes instead of months.</sup>
</div>
</a>
</td>
</table>
</div>
---
### Getting started
- 🚀 [Install reveal.js](https://revealjs.com/installation)
- 👀 [View the demo presentation](https://revealjs.com/demo)
@@ -26,5 +46,5 @@ Want to create reveal.js presentation in a graphical editor? Try <https://slides
---
<div align="center">
MIT licensed | Copyright © 2011-2024 Hakim El Hattab, https://hakim.se
MIT licensed | Copyright © 2011-2023 Hakim El Hattab, https://hakim.se
</div>
+219
View File
@@ -0,0 +1,219 @@
# 使用 MLP 识别 MNIST 手写数字
主讲人:陈永源 2024-01-05
Note:
这个课程假设各位是有编程基础的机器学习小白,课程我会用直观的说明方式讲解机器学习原理及相关代码,不会涉及到过多数学上的细节。
---
## 课程简介
在这个课程中,我们将会使用机器学习技术对手写数字图像进行分类,并包括
- 定义 Pytorch MLP 模型
- 使用数据集工具 DataLoader
- 从零开始训练模型
- 使用测试集评估模型
Note:
机器学习和传统程序设计的一个主要区别是
你无需明确告诉计算机该如何去识别一个数字
例如说6这个数字有一个竖线还有一个圈
你不需告诉计算机这些规则
机器学习能够自动从数据集中学到这些特征
并且使用这些特征逐步训练它自己的神经网络,
然后逐步提升它的准确性,
这和我们人类或者是生物的学习方式是类似的。
---
## 神经网络简介
神经网络这一概念在 1959 年被提出
神经网络的核心概念来源于人脑的神经元网络,它由许多相互连接的节点(或称为"神经元")组成。这些节点可以接收输入,并根据输入数据的不同对其进行加权处理,产生输出
![nn](./media/nn.png)
Demo: <https://playground.tensorflow.org/>
Note:
该术语于1959年由IBM的亚瑟·塞缪尔提出,当时他正在开发能够下跳棋的人工智能程序。半个世纪后,预测模型已经嵌入在我们日常使用的许多产品当中,执行两项基本工作:一是对数据进行分类,例如判断路上是否有其他车辆,或者患者是否患有癌症;二是对未来结果做出预测,如预测股票是否会上涨
而神经网络,是机器学习中一类特别重要的算法模型。神经网络的核心概念来源于人脑的神经元网络,它由许多相互连接的节点(或称为"神经元")组成。这些节点可以接收输入,并根据输入数据的不同对其进行加权处理,产生输出。特别是在处理像图像或自然语言这样的数据集时,神经网络显示出强大的功能,因为它可以自动提取和创建特征,而无需手动的特征工程。
// 打开 demo
这是一个非常直观的关于神经网络的演示。我们的任务是训练一个神经网络,对对右边的橙色和蓝色的点进行分类。
用人眼看过去,我们很直观的就能发现
蓝色的点在中间,外面一圈都是橙色的点
但是我们要如何让计算机自己去学到这一个规则呢?
这个时候,我们设计了有两个隐藏层的神经网络
第一个隐藏层有四个神经元,第二个隐藏层有两个神经元
同时使用两个线性特征作为输入。
// 点开始学习,在一百多个迭代之后这个神经网络就能学习到我们期望的特征
// 将鼠标放在各个神经元上,可以看到每个神经元所学习到的特征已经他们和其他神经元之间的权重。
除此之外,我们还有一些参数可以调整,例如学习率,可以理解为神经网络学习的步长,太大的值可能会学习不到最优解,太小的值可能会花费更多的时间来学习甚至永远学不到最优解
那么我们现在就开始手写数字分类的任务
---
## MNIST 手写数字数据集
- 总共7000张图片,包含1000张测试集
- 10个分类,代表09
![mnist handwritting digits](./media/minst.jpg)
Note:
这个数字节里面包含了大约7000张图片,
每一张图片都是这种黑白的手写数字,
分别一共有10种,
代表0~9
这7000张图片里面有6000张是训练级,1000张是测试级,我们只会使用训练级的图片来训练神经网络,测试级的图片不会给神经网络看,但会在评估阶段用来测试神经网络的性能,这样就能看一下这个神经网络对于它没见过的图片是否也能做到准确分类,
---
## 神经网络结构
1. 输入层大小: 28*28 = 784
2. 隐藏层
3. 输出层大小: 10
![mlp structure](./media/mlp.jpeg)
Note:
这是我们即将设计的神经网络的结构
它首先在输入层接受一个维度为28*28,也就是784这样一个大小的输入
然后它会经过几个隐藏层,最终来到有10个神经元的输出层
完成手写数字识别的分类任务
---
## 神经网络优化器
优化器的作用是寻找最佳的参数组合以最小化损失函数
![optimizer](./media/optimizer.gif)
---
## 代码讲解
```python [0-6|8-14|16-20|18|19|22-27]
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 检查设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('Using device:', device)
# 设置超参数
batch_size = 64
learning_rate = 0.01
epochs = 5
# 设置数据集的变换
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0,), (1,))
])
#加载数据集
train_dataset = datasets.MNIST(root='./data_mlp', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data_mlp', train=False, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
```
Note:
0-6 首先前后行我们载入一些需要用到的库 包括神经网络层,优化器,数据集需要用到的工具
8-14
然后初始化一些变量和参数
这里用了一段代码自动判断CUDA显卡设备是否可用
如果不可用的话则会选择CPU进行运算
超参数的这个batch_size指的是每一次迭代中
这个神经网络会一次性看多少张图片
我们神经网络训练数据的时候不是一张图一张图的去看
而是多张图放在一个批次里一起去看
一般來說,Batch Size會盡可能設置得高,讓顯卡充分發揮它的並行運算能力,但是也要看情況具體分析,太高的話可能導致過擬核,影响模型最终的学习效果
Learning Rate就是学习率,我们刚才讲过的
指的是神经网络一次调整
自身权重的步长
数值越大,学得越快
但是太大也可能会导致完全无法学习
最后一个参数Epoch,代表需要学习整个数据集多少遍
数字太小的话
训练过程可能
还没学到这些数字的特征就结束了 数字太大的话
他可能会把数据集里面的所有噪音都记住 最终在测试级上表现效果不佳
16-20
Transform这一部分是告诉DataLoader
我们应该在读取数据,在喂给神经网络之前,做哪一些格式变换操作
18
首先我们会把这些整数类型的数据转换成Tensor,也就是PyTorch能够进行运算的向量类型数据
19
然后我们会调用Normalize把它归一化成均值为0,标准差为1的数据。
因为在某些数据集中,数值之间的尺度是不匹配的,例如我们分析身高和机票价格之间的关系,身高的单位是米,一个人身高撑死最多2米多,但是机票价格从几千到几万不等。这时候进行normalize就非常有必要。
这样经过调整之后,输入神经网络的数值就在一个合理的范围之内
---
## 代码讲解
```python [1-14|16-17|19-21|23-32|34-46]
# 定义MLP模型
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(28*28, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 10)
def forward(self, x):
x = x.view(-1, 28*28)
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
# 创建模型实例并将其移到正确的设备上
model = MLP().to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 训练模型
for epoch in range(epochs):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
# 测试模型
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += criterion(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
```
-1
View File
@@ -25,7 +25,6 @@
// Stack multiple elements on top of each other
.reveal .r-stack {
display: grid;
grid-template-rows: 100%;
}
.reveal .r-stack > * {
+158 -239
View File
@@ -1,3 +1,5 @@
@use "sass:math";
/**
* reveal.js
* http://revealjs.com
@@ -6,7 +8,6 @@
* Copyright (C) Hakim El Hattab, https://hakim.se
*/
@use "sass:math";
@import 'layout';
/*********************************************
@@ -18,7 +19,7 @@ html.reveal-full-page {
height: 100%;
height: 100vh;
height: calc( var(--vh, 1vh) * 100 );
height: 100dvh;
height: 100svh;
overflow: hidden;
}
@@ -33,16 +34,6 @@ html.reveal-full-page {
color: #000;
--r-controls-spacing: 12px;
--r-overlay-header-height: 40px;
--r-overlay-margin: 0px;
--r-overlay-padding: 6px;
--r-overlay-gap: 5px;
}
@media screen and (max-width: 1024px), (max-height: 768px) {
.reveal-viewport {
--r-overlay-header-height: 26px;
}
}
// Force the presentation to cover the full viewport when we
@@ -640,16 +631,11 @@ $controlsArrowAngleActive: 36deg;
touch-action: pinch-zoom;
}
// Swiping on an embedded deck should not block page scrolling...
// Swiping on an embedded deck should not block page scrolling
.reveal.embedded {
touch-action: pan-y;
}
// ... unless we're on a vertical slide
.reveal.embedded.is-vertical-slide {
touch-action: none;
}
.reveal .slides {
position: absolute;
width: 100%;
@@ -1009,7 +995,7 @@ $controlsArrowAngleActive: 36deg;
border-radius: 4px;
box-shadow: 0px 95px 25px rgba(0,0,0,0.2);
transform: translateZ(-90px) rotateX( 65deg );
-webkit-transform: translateZ(-90px) rotateX( 65deg );
}
.reveal.page .slides>section.stack {
@@ -1328,6 +1314,12 @@ $controlsArrowAngleActive: 36deg;
perspective-origin: 50% 50%;
perspective: 700px;
.slides {
// Fixes overview rendering errors in FF48+, not applied to
// other browsers since it degrades performance
-moz-transform-style: preserve-3d;
}
.slides section {
height: 100%;
top: 0 !important;
@@ -1339,12 +1331,9 @@ $controlsArrowAngleActive: 36deg;
}
.slides section:hover,
.slides section.present {
outline: 10px solid rgba(150,150,150,0.6);
outline: 10px solid rgba(150,150,150,0.4);
outline-offset: 10px;
}
.slides section.present {
outline: 10px solid var(--r-link-color);
}
.slides section .fragment {
opacity: 1;
transition: none;
@@ -1363,6 +1352,10 @@ $controlsArrowAngleActive: 36deg;
.backgrounds {
perspective: inherit;
// Fixes overview rendering errors in FF48+, not applied to
// other browsers since it degrades performance
-moz-transform-style: preserve-3d;
}
.backgrounds .slide-background {
@@ -1442,234 +1435,160 @@ $controlsArrowAngleActive: 36deg;
* OVERLAY FOR LINK PREVIEWS AND HELP
*********************************************/
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
$overlayHeaderHeight: 40px;
$overlayHeaderPadding: 5px;
@keyframes scale-up {
from { transform: scale( 0.95 ); }
to { transform: scale( 1 ); }
}
.reveal [data-preview-image],
.reveal [data-preview-video],
.reveal [data-preview-link]:not(a):not([data-preview-link=false]) {
cursor: zoom-in;
}
.r-overlay {
.reveal > .overlay {
position: absolute;
top: var(--r-overlay-margin);
right: var(--r-overlay-margin);
bottom: var(--r-overlay-margin);
left: var(--r-overlay-margin);
border-radius: min( var(--r-overlay-margin), 6px );
z-index: 99;
background: rgba( 0, 0, 0, 0.95 );
backdrop-filter: blur( 10px );
transition: all 0.3s ease;
color: #fff;
animation: fade-in 0.3s ease;
font-family: ui-sans-serif, system-ui, -apple-system, Helvetica, sans-serif;
}
.r-overlay-viewport {
position: absolute;
top: var(--r-overlay-padding);
right: var(--r-overlay-padding);
bottom: var(--r-overlay-padding);
left: var(--r-overlay-padding);
gap: var(--r-overlay-gap);
display: flex;
flex-direction: column;
}
.r-overlay-header {
display: flex;
z-index: 2;
box-sizing: border-box;
align-items: center;
justify-content: flex-end;
height: var(--r-overlay-header-height);
gap: 6px;
}
.r-overlay-header .r-overlay-button {
all: unset;
display: flex;
align-items: center;
justify-content: center;
min-width: var(--r-overlay-header-height);
min-height: var(--r-overlay-header-height);
padding: 0 calc(var(--r-overlay-header-height) / 4);
opacity: 1;
border-radius: 6px;
font-size: 18px;
gap: 8px;
cursor: pointer;
box-sizing: border-box;
}
.r-overlay-header .r-overlay-button:hover {
opacity: 1;
background-color: rgba( 255, 255, 255, 0.15 );
}
.r-overlay-header .icon {
display: inline-block;
width: 20px;
height: 20px;
background-position: 50% 50%;
background-size: 100%;
background-repeat: no-repeat;
}
.r-overlay-close .icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTIuODU0IDIuODU0YS41LjUgMCAwIDAtLjcwOC0uNzA4TDcuNSA2Ljc5MyAyLjg1NCAyLjE0NmEuNS41IDAgMSAwLS43MDguNzA4TDYuNzkzIDcuNWwtNC42NDcgNC42NDZhLjUuNSAwIDAgMCAuNzA4LjcwOEw3LjUgOC4yMDdsNC42NDYgNC42NDdhLjUuNSAwIDAgMCAuNzA4LS43MDhMOC4yMDcgNy41bDQuNjQ3LTQuNjQ2WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+);
}
.r-overlay-external .icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMyAyYTEgMSAwIDAgMC0xIDF2OWExIDEgMCAwIDAgMSAxaDlhMSAxIDAgMCAwIDEtMVY4LjVhLjUuNSAwIDAgMC0xIDBWMTJIM1YzaDMuNWEuNS41IDAgMCAwIDAtMUgzWm05Ljg1NC4xNDZhLjUuNSAwIDAgMSAuMTQ2LjM1MVY1LjVhLjUuNSAwIDAgMS0xIDBWMy43MDdMNi44NTQgOC44NTRhLjUuNSAwIDEgMS0uNzA4LS43MDhMMTEuMjkzIDNIOS41YS41LjUgMCAwIDEgMC0xaDNhLjQ5OS40OTkgMCAwIDEgLjM1NC4xNDZaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=);
}
.r-overlay-content {
position: relative;
display: grid;
place-items: center;
border-radius: 6px;
overflow: hidden;
flex-grow: 1;
background-color: rgba(20, 20, 20, 0.8);
animation: scale-up 0.5s cubic-bezier(0.260, 0.860, 0.440, 0.985);
}
.r-overlay-spinner {
position: absolute;
display: block;
top: 50%;
left: 50%;
width: 32px;
height: 32px;
margin: -16px 0 0 -16px;
z-index: 10;
background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);
visibility: hidden;
opacity: 0;
}
// Preview overlay
.r-overlay-preview .r-overlay-content iframe {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
border: 0;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.r-overlay-preview[data-state="loaded"] iframe {
opacity: 1;
visibility: visible;
}
.r-overlay-preview .r-overlay-content img,
.r-overlay-preview .r-overlay-content video {
position: absolute;
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
margin: 0;
object-fit: scale-down;
}
.r-overlay-preview[data-preview-fit="none"] img,
.r-overlay-preview[data-preview-fit="none"] video {
object-fit: none;
}
.r-overlay-preview[data-preview-fit="scale-down"] img,
.r-overlay-preview[data-preview-fit="scale-down"] video {
object-fit: scale-down;
}
.r-overlay-preview[data-preview-fit="contain"] img,
.r-overlay-preview[data-preview-fit="contain"] video {
object-fit: contain;
}
.r-overlay-preview[data-preview-fit="cover"] img,
.r-overlay-preview[data-preview-fit="cover"] video {
object-fit: cover;
}
.r-overlay-preview[data-state="loaded"] .r-overlay-content-inner {
position: absolute;
z-index: -1;
top: 0;
left: 0;
top: 45%;
width: 100%;
text-align: center;
letter-spacing: normal;
}
.r-overlay-preview .r-overlay-error {
font-size: 18px;
color: orange;
height: 100%;
z-index: 1000;
background: rgba( 0, 0, 0, 0.95 );
backdrop-filter: blur( 6px );
transition: all 0.3s ease;
}
.r-overlay-preview .x-frame-error {
opacity: 0;
transition: opacity 0.3s ease 0.3s;
}
.r-overlay-preview[data-state="loaded"] .x-frame-error {
opacity: 1;
}
.reveal > .overlay .spinner {
position: absolute;
display: block;
top: 50%;
left: 50%;
width: 32px;
height: 32px;
margin: -16px 0 0 -16px;
z-index: 10;
background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);
.r-overlay-preview[data-state="loading"] .r-overlay-spinner {
opacity: 0.6;
visibility: visible;
}
visibility: visible;
opacity: 0.6;
transition: all 0.3s ease;
}
// Help overlay
.r-overlay-help .r-overlay-content {
overflow: auto;
}
.reveal > .overlay header {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: $overlayHeaderPadding;
z-index: 2;
box-sizing: border-box;
}
.reveal > .overlay header a {
display: inline-block;
width: $overlayHeaderHeight;
height: $overlayHeaderHeight;
line-height: 36px;
padding: 0 10px;
float: right;
opacity: 0.6;
.r-overlay-help-content {
max-width: 560px;
padding: 20px 0;
margin: auto;
text-align: center;
letter-spacing: normal;
}
box-sizing: border-box;
}
.reveal > .overlay header a:hover {
opacity: 1;
}
.reveal > .overlay header a .icon {
display: inline-block;
width: 20px;
height: 20px;
.r-overlay-help-content .title {
font-size: 20px;
margin-top: 0;
}
background-position: 50% 50%;
background-size: 100%;
background-repeat: no-repeat;
}
.reveal > .overlay header a.close .icon {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC);
}
.reveal > .overlay header a.external .icon {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==);
}
/* Specificity battle with reveal.js theme table styles */
.r-overlay-help .r-overlay-help-content table {
border: 1px solid #fff;
border-collapse: collapse;
font-size: 16px;
text-align: left;
}
.reveal > .overlay .viewport {
position: absolute;
display: flex;
top: $overlayHeaderHeight + $overlayHeaderPadding*2;
right: 0;
bottom: 0;
left: 0;
}
.r-overlay-help .r-overlay-help-content table th,
.r-overlay-help .r-overlay-help-content table td {
width: 240px;
padding: 14px;
border: 1px solid #fff;
vertical-align: middle;
}
.reveal > .overlay.overlay-preview .viewport iframe {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
border: 0;
.r-overlay-help .r-overlay-help-content table th {
padding-top: 20px;
padding-bottom: 20px;
}
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.reveal > .overlay.overlay-preview.loaded .viewport iframe {
opacity: 1;
visibility: visible;
}
.reveal > .overlay.overlay-preview.loaded .viewport-inner {
position: absolute;
z-index: -1;
left: 0;
top: 45%;
width: 100%;
text-align: center;
letter-spacing: normal;
}
.reveal > .overlay.overlay-preview .x-frame-error {
opacity: 0;
transition: opacity 0.3s ease 0.3s;
}
.reveal > .overlay.overlay-preview.loaded .x-frame-error {
opacity: 1;
}
.reveal > .overlay.overlay-preview.loaded .spinner {
opacity: 0;
visibility: hidden;
transform: scale(0.2);
}
.reveal > .overlay.overlay-help .viewport {
overflow: auto;
color: #fff;
}
.reveal > .overlay.overlay-help .viewport .viewport-inner {
width: 600px;
margin: auto;
padding: 20px 20px 80px 20px;
text-align: center;
letter-spacing: normal;
}
.reveal > .overlay.overlay-help .viewport .viewport-inner .title {
font-size: 20px;
}
.reveal > .overlay.overlay-help .viewport .viewport-inner table {
border: 1px solid #fff;
border-collapse: collapse;
font-size: 16px;
}
.reveal > .overlay.overlay-help .viewport .viewport-inner table th,
.reveal > .overlay.overlay-help .viewport .viewport-inner table td {
width: 200px;
padding: 14px;
border: 1px solid #fff;
vertical-align: middle;
}
.reveal > .overlay.overlay-help .viewport .viewport-inner table th {
padding-top: 20px;
padding-bottom: 20px;
}
/*********************************************
* PLAYBACK COMPONENT
@@ -1982,7 +1901,7 @@ $notesWidthPercent: 25%;
display: none !important;
}
.r-overlay,
.overlay,
.pause-overlay {
position: fixed;
}
+1 -2
View File
@@ -6,7 +6,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -24,7 +23,7 @@ $headingColor: #333;
$headingTextShadow: none;
$backgroundColor: #f7f3de;
$linkColor: #8b743d;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: rgba(79, 64, 28, 0.99);
$heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
+2 -3
View File
@@ -9,7 +9,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -33,8 +32,8 @@ $headingLetterSpacing: normal;
$headingTextTransform: uppercase;
$headingFontWeight: 600;
$linkColor: #42affa;
$linkColorHover: color.scale( $linkColor, $lightness: 15% );
$selectionBackgroundColor: color.scale( $linkColor, $lightness: 25% );
$linkColorHover: lighten( $linkColor, 15% );
$selectionBackgroundColor: lighten( $linkColor, 25% );
$heading1Size: 2.5em;
$heading2Size: 1.6em;
+1 -2
View File
@@ -6,7 +6,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -30,7 +29,7 @@ $headingLetterSpacing: normal;
$headingTextTransform: uppercase;
$headingFontWeight: 600;
$linkColor: #42affa;
$linkColorHover: color.scale( $linkColor, $lightness: 15% );
$linkColorHover: lighten( $linkColor, 15% );
$selectionBackgroundColor: rgba( $linkColor, 0.75 );
$heading1Size: 2.5em;
+2 -3
View File
@@ -10,8 +10,7 @@
*
*/
// Default mixins and settings -----------------
@use "sass:color";
// Default mixins and settings -----------------
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -41,7 +40,7 @@ $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b
// Links
$linkColor: $blood;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
// Text selection
$selectionBackgroundColor: $blood;
+2 -3
View File
@@ -9,10 +9,9 @@
@import "../template/settings";
// ---------------------------------------------
// Include theme-specific fonts
@import url(./fonts/league-gothic/league-gothic.css);
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
// Include theme-specific fonts
$systemFontsSansSerif: -apple-system,
BlinkMacSystemFont,
avenir next,
+1 -2
View File
@@ -5,7 +5,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -44,7 +43,7 @@ $headingColor: $base2;
$headingTextShadow: none;
$backgroundColor: $base03;
$linkColor: $blue;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: $magenta;
// Change text colors against light slide backgrounds
+1 -2
View File
@@ -6,7 +6,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -22,7 +21,7 @@ $backgroundColor: #111;
$mainFont: 'Open Sans', sans-serif;
$linkColor: #e7ad52;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$headingFont: 'Montserrat', Impact, sans-serif;
$headingTextShadow: none;
$headingLetterSpacing: -0.03em;
+1 -2
View File
@@ -7,7 +7,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -23,7 +22,7 @@ $headingTextShadow: none;
$headingTextTransform: none;
$backgroundColor: #F0F1EB;
$linkColor: #51483D;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: #26351C;
$overlayElementBgColor: 0, 0, 0;
+1 -2
View File
@@ -8,7 +8,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -29,7 +28,7 @@ $headingTextShadow: none;
$headingTextTransform: none;
$backgroundColor: #fff;
$linkColor: #00008B;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: rgba(0, 0, 0, 0.99);
$overlayElementBgColor: 0, 0, 0;
+1 -2
View File
@@ -6,7 +6,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -27,7 +26,7 @@ $headingLetterSpacing: -0.08em;
$headingTextShadow: none;
$backgroundColor: #f7fbfc;
$linkColor: #3b759e;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: #134674;
$overlayElementBgColor: 0, 0, 0;
+1 -2
View File
@@ -5,7 +5,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -49,7 +48,7 @@ $headingColor: $base01;
$headingTextShadow: none;
$backgroundColor: $base3;
$linkColor: $blue;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
$selectionBackgroundColor: $magenta;
$overlayElementBgColor: 0, 0, 0;
+2 -3
View File
@@ -9,7 +9,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -33,8 +32,8 @@ $headingLetterSpacing: normal;
$headingTextTransform: uppercase;
$headingFontWeight: 600;
$linkColor: #2a76dd;
$linkColorHover: color.scale( $linkColor, $lightness: 15% );
$selectionBackgroundColor: color.scale( $linkColor, $lightness: 25% );
$linkColorHover: lighten( $linkColor, 15% );
$selectionBackgroundColor: lighten( $linkColor, 25% );
$heading1Size: 2.5em;
$heading2Size: 1.6em;
+2 -3
View File
@@ -6,7 +6,6 @@
// Default mixins and settings -----------------
@use "sass:color";
@import "../template/mixins";
@import "../template/settings";
// ---------------------------------------------
@@ -30,8 +29,8 @@ $headingLetterSpacing: normal;
$headingTextTransform: uppercase;
$headingFontWeight: 600;
$linkColor: #2a76dd;
$linkColorHover: color.scale( $linkColor, $lightness: 15% );
$selectionBackgroundColor: color.scale( $linkColor, $lightness: 25% );
$linkColorHover: lighten( $linkColor, 15% );
$selectionBackgroundColor: lighten( $linkColor, 25% );
$heading1Size: 2.5em;
$heading2Size: 1.6em;
+2 -4
View File
@@ -1,6 +1,4 @@
// Exposes theme's variables for easy reuse in CSS for plugin authors
@use "sass:color";
// Exposes theme's variables for easy re-use in CSS for plugin authors
:root {
--r-background-color: #{$backgroundColor};
@@ -23,7 +21,7 @@
--r-heading4-size: #{$heading4Size};
--r-code-font: #{$codeFont};
--r-link-color: #{$linkColor};
--r-link-color-dark: #{color.scale( $linkColor, $lightness: -15% )};
--r-link-color-dark: #{darken($linkColor , 15% )};
--r-link-color-hover: #{$linkColorHover};
--r-selection-background-color: #{$selectionBackgroundColor};
--r-selection-color: #{$selectionColor};
+1 -3
View File
@@ -1,5 +1,3 @@
@use "sass:color";
// Base settings for all themes that can optionally be
// overridden by the super-theme
@@ -34,7 +32,7 @@ $codeFont: monospace;
// Links and actions
$linkColor: #13DAEC;
$linkColorHover: color.scale( $linkColor, $lightness: 20% );
$linkColorHover: lighten( $linkColor, 20% );
// Text selection
$selectionBackgroundColor: #FF5E99;
+2 -1
View File
@@ -278,7 +278,8 @@
.reveal .roll span:after {
color: #fff;
background: var(--r-link-color-dark);
// background: darken( var(--r-link-color), 15% );
background: var(--r-link-color-dark);
}
+3 -22
View File
@@ -165,9 +165,9 @@
</section>
<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
<div class="r-stack">
<div data-id="box1" style="background: cyan; width: 300px; height: 300px;"></div>
<div data-id="box2" style="background: magenta; width: 200px; height: 200px;"></div>
<div data-id="box3" style="background: yellow; width: 100px; height: 100px;"></div>
<div data-id="box1" style="background: cyan; width: 300px; height: 300px; border-radius: 200px;"></div>
<div data-id="box2" style="background: magenta; width: 200px; height: 200px; border-radius: 200px;"></div>
<div data-id="box3" style="background: yellow; width: 100px; height: 100px; border-radius: 200px;"></div>
</div>
<h2 style="margin-top: 20px;">Auto-Animate</h2>
</section>
@@ -197,25 +197,6 @@
</script>
</section>
<section>
<h2>Lightbox</h2>
Turn any element into a <a href="https://revealjs.com/lightbox/">lightbox</a> using <strong>datapreviewimage</strong> & <strong>datapreviewvideo</strong>.
<div class="r-hstack" style="gap: 2rem;">
<div>
<pre style="font-size: 12px; width: 100%"><code class="html" data-trim>
&lt;img src="image.png" data-preview-image="image.png"&gt;
</code></pre>
<img src="https://static.slid.es/logo/v2/slides-symbol-1024x1024.png" height="100" data-preview-image>
</div>
<div>
<pre style="font-size: 12px; width: 100%"><code class="html" data-trim>
&lt;img src="video.png" data-preview-video="video.mp4"&gt;
</code></pre>
<img src="https://static.slid.es/site/homepage/v1/homepage-video-editor.png" height="100" data-preview-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4">
</div>
</div>
</section>
<section>
<p>Add the <code>r-fit-text</code> class to auto-size text</p>
<h2 class="r-fit-text">FIT TEXT</h2>
+8 -9
View File
File diff suppressed because one or more lines are too long
+7 -7
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -7
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -33,8 +33,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #8b743d;
--r-link-color-dark: rgb(118.15, 98.6, 51.85);
--r-link-color-hover: rgb(179.36, 150.84, 82.64);
--r-link-color-dark: #564826;
--r-link-color-hover: #c0a86e;
--r-selection-background-color: rgba(79, 64, 28, 0.99);
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
+3 -3
View File
@@ -35,9 +35,9 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #42affa;
--r-link-color-dark: rgb(19.8216494845, 155.4536082474, 248.7783505155);
--r-link-color-hover: rgb(94.35, 187, 250.75);
--r-selection-background-color: rgb(113.25, 195, 251.25);
--r-link-color-dark: #068de9;
--r-link-color-hover: #8dcffc;
--r-selection-background-color: #bee4fd;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
--r-overlay-element-fg-color: 0, 0, 0;
+2 -2
View File
@@ -32,8 +32,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #42affa;
--r-link-color-dark: rgb(19.8216494845, 155.4536082474, 248.7783505155);
--r-link-color-hover: rgb(94.35, 187, 250.75);
--r-link-color-dark: #068de9;
--r-link-color-hover: #8dcffc;
--r-selection-background-color: rgba(66, 175, 250, 0.75);
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
+2 -2
View File
@@ -38,8 +38,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #a23;
--r-link-color-dark: rgb(144.5, 28.9, 43.35);
--r-link-color-hover: rgb(214.2, 51, 71.4);
--r-link-color-dark: #6a1520;
--r-link-color-hover: #dd5566;
--r-selection-background-color: #a23;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
+1 -3
View File
@@ -2,8 +2,6 @@
* Dracula Dark theme for reveal.js.
* Based on https://draculatheme.com
*/
@import url(./fonts/league-gothic/league-gothic.css);
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
/**
* Dracula colors by Zeno Rocha
* https://draculatheme.com/contribute
@@ -41,7 +39,7 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: Fira Code, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
--r-link-color: #FF79C6;
--r-link-color-dark: rgb(255, 64.6, 174.0089552239);
--r-link-color-dark: #ff2da5;
--r-link-color-hover: #8BE9FD;
--r-selection-background-color: #44475A;
--r-selection-color: #fff;
+2 -2
View File
@@ -35,8 +35,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #13DAEC;
--r-link-color-dark: rgb(16.15, 185.3, 200.6);
--r-link-color-hover: rgb(66.2, 225.4, 239.8);
--r-link-color-dark: #0d99a5;
--r-link-color-hover: #71e9f4;
--r-selection-background-color: #FF5E99;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
+2 -2
View File
@@ -35,8 +35,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #268bd2;
--r-link-color-dark: rgb(32.3, 118.15, 178.5);
--r-link-color-hover: rgb(77.5161290323, 162.8774193548, 222.8838709677);
--r-link-color-dark: #1a6091;
--r-link-color-hover: #78b9e6;
--r-selection-background-color: #d33682;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
+2 -2
View File
@@ -33,8 +33,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #e7ad52;
--r-link-color-dark: rgb(225.2802030457, 153.4573604061, 40.7697969543);
--r-link-color-hover: rgb(235.8, 189.4, 116.6);
--r-link-color-dark: #d08a1d;
--r-link-color-hover: #f3d7ac;
--r-selection-background-color: #e7ad52;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 240, 240, 240;
+2 -2
View File
@@ -36,8 +36,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #51483D;
--r-link-color-dark: rgb(68.85, 61.2, 51.85);
--r-link-color-hover: rgb(122.9830985915, 109.3183098592, 92.6169014085);
--r-link-color-dark: #25211c;
--r-link-color-hover: #8b7c69;
--r-selection-background-color: #26351C;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
+2 -2
View File
@@ -35,8 +35,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #00008B;
--r-link-color-dark: rgb(0, 0, 118.15);
--r-link-color-hover: rgb(0, 0, 213.2);
--r-link-color-dark: #00003f;
--r-link-color-hover: #0000f1;
--r-selection-background-color: rgba(0, 0, 0, 0.99);
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
+2 -2
View File
@@ -37,8 +37,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #3b759e;
--r-link-color-dark: rgb(50.15, 99.45, 134.3);
--r-link-color-hover: rgb(84.330875576, 146.9815668203, 191.269124424);
--r-link-color-dark: #264c66;
--r-link-color-hover: #74a7cb;
--r-selection-background-color: #134674;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
+2 -2
View File
@@ -36,8 +36,8 @@ html * {
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #268bd2;
--r-link-color-dark: rgb(32.3, 118.15, 178.5);
--r-link-color-hover: rgb(77.5161290323, 162.8774193548, 222.8838709677);
--r-link-color-dark: #1a6091;
--r-link-color-hover: #78b9e6;
--r-selection-background-color: #d33682;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
+3 -3
View File
@@ -35,9 +35,9 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #2a76dd;
--r-link-color-dark: rgb(30.7720647773, 99.5566801619, 192.7779352227);
--r-link-color-hover: rgb(73.95, 138.55, 226.1);
--r-selection-background-color: rgb(95.25, 152.25, 229.5);
--r-link-color-dark: #1a53a1;
--r-link-color-hover: #6ca0e8;
--r-selection-background-color: #98bdef;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
--r-overlay-element-fg-color: 240, 240, 240;
+3 -3
View File
@@ -32,9 +32,9 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
--r-heading4-size: 1em;
--r-code-font: monospace;
--r-link-color: #2a76dd;
--r-link-color-dark: rgb(30.7720647773, 99.5566801619, 192.7779352227);
--r-link-color-hover: rgb(73.95, 138.55, 226.1);
--r-selection-background-color: rgb(95.25, 152.25, 229.5);
--r-link-color-dark: #1a53a1;
--r-link-color-hover: #6ca0e8;
--r-selection-background-color: #98bdef;
--r-selection-color: #fff;
--r-overlay-element-bg-color: 0, 0, 0;
--r-overlay-element-fg-color: 240, 240, 240;
+1 -1
View File
@@ -89,7 +89,7 @@
<h2>Same background twice (2/2)</h2>
</section>
<section data-background-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://static.slid.es/site/homepage/v1/homepage-video-editor.webm">
<section data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm">
<h2>Video background</h2>
</section>
-144
View File
@@ -1,144 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>reveal.js - Ligthbox</title>
<link rel="stylesheet" href="../dist/reveal.css">
<link rel="stylesheet" href="../dist/theme/black.css" id="theme">
<style>
.reveal {
font-size: 24px;
}
.reveal figure {
margin: 0 0 1rem 0;
text-align: left;
}
.reveal figure img,
.reveal figure video {
margin: 0.25rem 0 0 0;
}
figcaption, a {
font-size: 16px;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h2>Preview Overlays</h2>
<div class="r-hstack items-start">
<div class="r-vstack items-start">
<h5>Images</h5>
<figure>
<figcaption>Preview with default settings:</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/a.png" data-preview-image>
</figure>
<figure>
<figcaption>Preview with data-preview-fit="contain"</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/a.png" data-preview-image data-preview-fit="contain">
</figure>
<figure>
<figcaption>Preview with data-preview-fit="cover"</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/a.png" data-preview-image data-preview-fit="cover">
</figure>
<figure>
<figcaption>Preview another image (c)</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/b.png" data-preview-image="https://static.slid.es/images/alphabet/v1/c.png">
</figure>
<a href="#" data-preview-image="https://static.slid.es/images/alphabet/v1/x.png">
Preview image from a link.
</a>
</div>
<div style="width: 1px; height: 30vh; margin: 0 3rem;background-color: #999;"></div>
<div class="r-vstack items-start">
<h5>Videos</h5>
<figure>
<figcaption>Preview video</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/x.png" data-preview-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4">
</figure>
<figure>
<figcaption>Preview video</figcaption>
<video height="50" src="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4" data-preview-video></video>
</figure>
<a href="#" data-preview-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4">
Preview video from a link.
</a>
</div>
<div style="width: 1px; height: 30vh; margin: 0 3rem;background-color: #999;"></div>
<div class="r-vstack items-start">
<h5>Iframes</h5>
<a href="https://hakim.se">https://hakim.se | data-preview-link</a>
<a data-preview-link href="https://hakim.se">https://hakim.se | data-preview-link</a>
<br />
<a data-preview-link="false" href="https://hakim.se">https://hakim.se | data-preview-link=false</a>
<br />
<figure>
<figcaption>Preview link from an image</figcaption>
<img height="50" src="https://static.slid.es/images/alphabet/v1/a.png" data-preview-link="https://hakim.se">
</figure>
</div>
</div>
</section>
<section style="text-align: left;">
<h2>Lightbox</h2>
<div class="r-hstack items-start justify-start">
<div class="r-vstack items-start">
<h5>Images</h5>
<figure>
<img height="100" src="https://static.slid.es/images/alphabet/v1/a.png" data-preview-image data-preview-fit="contain">
</figure>
</div>
<div style="width: 1px; height: 20vh; margin: 0 3rem;background-color: #222;"></div>
<div class="r-vstack items-start">
<h5>Videos</h5>
<figure>
<video height="100" src="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4" data-preview-video></video>
</figure>
</div>
<div style="width: 1px; height: 20vh; margin: 0 3rem;background-color: #222;"></div>
<div class="r-vstack items-start">
<h5>Iframes</h5>
<a style="font-size: 28px;" data-preview-link href="https://hakim.se">https://hakim.se</a>
</div>
</div>
</section>
</div>
</div>
<script src="../dist/reveal.js"></script>
<script>
Reveal.initialize({
previewLinks: false,
width: 1280,
height: 720
});
</script>
</body>
</html>
+1 -1
View File
@@ -121,7 +121,7 @@
<!-- Images -->
<section data-markdown>
<script type="text/template">
![Sample image](https://static.slid.es/logo/v2/slides-symbol-512x512.png)
![Sample image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png)
</script>
</section>
+1 -1
View File
@@ -33,7 +33,7 @@ Content 3.2
## External 3.3 (Image)
![External Image](https://static.slid.es/logo/v2/slides-symbol-512x512.png)
![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png)
## External 3.4 (Math)
+2 -2
View File
@@ -33,10 +33,10 @@
<section>
<h2>Video</h2>
<video src="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4" data-autoplay></video>
<video src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" data-autoplay></video>
</section>
<section data-background-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4">
<section data-background-video="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4">
<h2>Background Video</h2>
</section>
+6 -8
View File
@@ -91,10 +91,8 @@
<section data-background="https://static.slid.es/reveal/image-placeholder.png" id="image-bg">
<h2>Image Backgrounds</h2>
</section>
<section data-background-video-muted data-background-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://static.slid.es/site/homepage/v1/homepage-video-editor.webm">
<div style="background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px;">
<h2>Video background</h2>
</div>
<section data-background-video-muted data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm">
<h2>Video background</h2>
</section>
</section>
<section><h2>The end</h2></section>
@@ -108,13 +106,13 @@
<script src="../plugin/markdown/markdown.js"></script>
<script src="../plugin/highlight/highlight.js"></script>
<script>
Reveal.initialize({
view: 'scroll',
hash: true,
Reveal.initialize({
view: 'scroll',
hash: true,
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
});
</script>
</script>
</body>
</html>
+26 -36
View File
@@ -1,4 +1,3 @@
const fs = require('fs');
const pkg = require('./package.json')
const glob = require('glob')
const yargs = require('yargs')
@@ -13,8 +12,9 @@ const resolve = require('@rollup/plugin-node-resolve').default
const sass = require('sass')
const gulp = require('gulp')
const tap = require('gulp-tap')
const zip = require('gulp-zip')
const header = require('gulp-header-comment')
const header = require('gulp-header')
const eslint = require('gulp-eslint')
const minify = require('gulp-clean-css')
const connect = require('gulp-connect')
@@ -24,21 +24,13 @@ const root = yargs.argv.root || '.'
const port = yargs.argv.port || 8000
const host = yargs.argv.host || 'localhost'
const cssLicense = `
reveal.js ${pkg.version}
${pkg.homepage}
MIT licensed
Copyright (C) 2011-2024 Hakim El Hattab, https://hakim.se
`;
const jsLicense = `/*!
* reveal.js ${pkg.version}
* ${pkg.homepage}
* MIT licensed
*
* Copyright (C) 2011-2024 Hakim El Hattab, https://hakim.se
*/\n`;
const banner = `/*!
* reveal.js ${pkg.version}
* ${pkg.homepage}
* MIT licensed
*
* Copyright (C) 2011-2023 Hakim El Hattab, https://hakim.se
*/\n`
// Prevents warnings from opening too many test pages
process.setMaxListeners(20);
@@ -94,7 +86,7 @@ gulp.task('js-es5', () => {
name: 'Reveal',
file: './dist/reveal.js',
format: 'umd',
banner: jsLicense,
banner: banner,
sourcemap: true
});
});
@@ -116,7 +108,7 @@ gulp.task('js-es6', () => {
return bundle.write({
file: './dist/reveal.esm.js',
format: 'es',
banner: jsLicense,
banner: banner,
sourcemap: true
});
});
@@ -169,7 +161,6 @@ function compileSass() {
const transformedFile = vinylFile.clone();
sass.render({
silenceDeprecations: ['legacy-js-api'],
data: transformedFile.contents.toString(),
file: transformedFile.path,
}, ( err, result ) => {
@@ -193,7 +184,7 @@ gulp.task('css-core', () => gulp.src(['css/reveal.scss'])
.pipe(compileSass())
.pipe(autoprefixer())
.pipe(minify({compatibility: 'ie9'}))
.pipe(header(cssLicense))
.pipe(header(banner))
.pipe(gulp.dest('./dist')))
gulp.task('css', gulp.parallel('css-themes', 'css-core'))
@@ -220,7 +211,7 @@ gulp.task('qunit', () => {
targetUrl: `http://${serverConfig.host}:${serverConfig.port}/${filename}`,
timeout: 20000,
redirectConsole: false,
puppeteerArgs: ['--allow-file-access-from-files', '--no-sandbox']
puppeteerArgs: ['--allow-file-access-from-files']
})
.then(result => {
if( result.stats.failed > 0 ) {
@@ -275,23 +266,22 @@ gulp.task('default', gulp.series(gulp.parallel('js', 'css', 'plugins'), 'test'))
gulp.task('build', gulp.parallel('js', 'css', 'plugins'))
gulp.task('package', gulp.series(async () => {
gulp.task('package', gulp.series(() =>
let dirs = [
'./index.html',
'./dist/**',
'./plugin/**',
'./*/*.md'
];
if (fs.existsSync('./lib')) dirs.push('./lib/**');
if (fs.existsSync('./images')) dirs.push('./images/**');
if (fs.existsSync('./slides')) dirs.push('./slides/**');
return gulp.src( dirs, { base: './', encoding: false } )
gulp.src(
[
'./index.html',
'./dist/**',
'./lib/**',
'./images/**',
'./plugin/**',
'./**/*.md'
],
{ base: './' }
)
.pipe(zip('reveal-js-presentation.zip')).pipe(gulp.dest('./'))
}))
))
gulp.task('reload', () => gulp.src(['index.html'])
.pipe(connect.reload()));
+3 -84
View File
@@ -16,88 +16,7 @@
<body>
<div class="reveal">
<div class="slides">
<!-- Slide 1: Title -->
<section>
<h1>PostgreSQL JSONB Performance Optimization</h1>
<h2>A Comprehensive Survey</h2>
<p>
<small>陈永源 225002025</small>
</p>
<aside class="notes">
"Hello everyone! Today we will talk about PostgreSQL JSONB performance optimization.
Nowaday jsonb is a very import driver of PostgreSQL, postgresql became the most popular database
when the jsonb was asdd. most programming language support json. developer likes to use json.
Howevery when you trys to store json in trad database, you need to defind the schma, the relationship.
and there alwasy will be conflict between developer and DBA.
when storeaged in jsonb, you don't need to worry about the schema, That's why developer love to use json database. and we needs to understand the perform of jsonb.
</aside>
</section>
<section>
<h2>The TOAST Threshold Problem</h2>
<img src="paper/1.png" alt="TOAST Performance Degradation" style="height: 400px; margin: 0 auto;">
<div class="r-vstack">
<p><strong>The 2KB Critical Threshold</strong></p>
<ul>
<li>Before TOAST: Constant access time</li>
<li>After TOAST: Linear degradation</li>
<li>3 additional buffer reads per access</li>
</ul>
</div>
<aside class="notes">
"Let's talk about one of the most important concepts in JSONB performance: the TOAST threshold. TOAST stands for 'The Oversized-Attribute Storage Technique' - it's PostgreSQL's way of handling large data. The key thing to understand is the 2KB threshold. When your JSONB document is smaller than 2KB, access time remains constant regardless of size. But once it crosses 2KB, PostgreSQL moves the data to TOAST storage, and performance degrades linearly. Each access now requires 3 additional buffer reads: two for the TOAST index and one for the actual data. This explains why developers often report sudden performance drops when their JSON documents grow beyond this threshold."
</aside>
</section>
<section>
<h2>JSONB Operator Performance</h2>
<img src="paper/2.png" alt="JSONB Operator Performance" style="height: 900px; margin: 0 auto;">
<aside class="notes">
"This chart shows the performance characteristics of different JSONB operators in PostgreSQL. There are several ways to access data in JSONB: the arrow operator (->), the hash arrow operator (->>), subscripting, and JSON path functions. What we see here is very interesting: for small JSONB documents under 2KB, the arrow operator performs well at the root level. But as document size increases and nesting levels go deeper, performance varies significantly. Subscripting tends to be the fastest for large documents, while JSON path functions are the slowest but most flexible for complex queries. The key takeaway is that your choice of operator matters, especially for large, nested JSONB documents."
</aside>
</section>
<section>
<h2>Partial Update Challenges</h2>
<div class="r-vstack">
<div class="fragment fade-up">
<h3>Current Limitations</h3>
<ul>
<li>TOAST treats JSONB as atomic BLOB</li>
<li>Full document rewrites for small changes</li>
<li>WAL write amplification</li>
</ul>
</div>
<div class="fragment fade-up">
<h3>Emerging Solutions</h3>
<ul>
<li>Partial decompression: 5-10x faster</li>
<li>In-place updates: 10-50x improvement</li>
<li>Shared TOAST: 90% WAL reduction</li>
</ul>
</div>
</div>
<aside class="notes">
"One of the biggest challenges with JSONB today is partial updates. Currently, PostgreSQL treats JSONB as an atomic BLOB from TOAST's perspective. This means even if you want to update just one small key in a large JSONB document, PostgreSQL has to rewrite the entire document. This causes significant WAL write amplification - you generate much more write-ahead logging than necessary. The good news is that there are emerging solutions. Partial decompression can give us 5-10x performance improvements. In-place updates can provide 10-50x improvements. And something called shared TOAST can reduce WAL traffic by up to 90%. These optimizations are crucial for making JSONB truly efficient for OLTP workloads."
</aside>
</section>
<section>
<h2>Advanced Optimization Techniques</h2>
<img src="paper/3.png" alt="JSONB Optimization Techniques" style="height: 900px; margin: 0 auto;">
<aside class="notes">
"This slide shows various optimization techniques being developed for JSONB. The graph demonstrates performance improvements through different approaches. Key techniques include partial decompression - where only the parts of JSONB you need are decompressed rather than the entire document. Sorted-keys means sorte the key by their length, so the access time of a key is O(logN), not linear. Array slicing optimizate read of large slice. After adding these optimization, we most access pattern execution time reduce a lot.
</aside>
</section>
<section>
<h2>PostgreSQL JSONB vs MongoDB</h2>
<img src="paper/4.png" alt="PostgreSQL vs MongoDB Performance" style="height: 900px; margin: 0 auto;">
<aside class="notes">
"Now let's compare PostgreSQL JSONB with MongoDB, MongoDB is the most popular document database. The results here are quite interesing. First they start at time same level. After applying these optimization. the execution time is halfed. After turn on parallel processing, postgresql is significant faster than MongoDB. But we know mongodb is very memory hungry. So we give more memory to mongodb, we increase the memory from 4G to 16G, the mongodb perfrom better but still, postgresql win."
</aside>
</section>
<section data-markdown="ai_lecture.md"></section>
</div>
</div>
@@ -110,13 +29,13 @@
// - https://revealjs.com/initialization/
// - https://revealjs.com/config/
Reveal.initialize({
hash: true,
width: 1920,
height: 1080,
hash: true,
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
});
</script>
</body>
</html>
</html>
+2 -8
View File
@@ -15,10 +15,7 @@ export default {
minScale: 0.2,
maxScale: 2.0,
// Display presentation control arrows.
// - true: Display controls on all screens
// - false: Hide controls on all screens
// - "speaker-only": Only display controls in the speaker view
// Display presentation control arrows
controls: true,
// Help the user learn the controls by providing hints, for example by
@@ -77,7 +74,7 @@ export default {
// Enable keyboard shortcuts for navigation
keyboard: true,
// Optional function that blocks keyboard events when returning false
// Optional function that blocks keyboard events when retuning false
//
// If you set this to 'focused', we will only capture keyboard events
// for embedded decks when they are in focus
@@ -168,9 +165,6 @@ export default {
// - false: All iframes with data-src will be loaded only when visible
preloadIframes: null,
// Prevent embedded iframes from automatically focusing on themselves
preventIframeAutoFocus: true,
// Can be used to globally disable auto-animation
autoAnimate: true,
+22 -9
View File
@@ -31,13 +31,10 @@ export default class AutoAnimate {
let toSlideIndex = allSlides.indexOf( toSlide );
let fromSlideIndex = allSlides.indexOf( fromSlide );
// Ensure that;
// 1. Both slides exist.
// 2. Both slides are auto-animate targets with the same
// data-auto-animate-id value (including null if absent on both).
// 3. data-auto-animate-restart isn't set on the physically latter
// slide (independent of slide direction).
if( fromSlide && toSlide && fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )
// Ensure that both slides are auto-animate targets with the same data-auto-animate-id value
// (including null if absent on both) and that data-auto-animate-restart isn't set on the
// physically latter slide (independent of slide direction)
if( fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )
&& fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' )
&& !( toSlideIndex > fromSlideIndex ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {
@@ -178,12 +175,28 @@ export default class AutoAnimate {
let fromProps = this.getAutoAnimatableProperties( 'from', from, elementOptions ),
toProps = this.getAutoAnimatableProperties( 'to', to, elementOptions );
// Maintain fragment visibility for matching elements when
// we're navigating forwards, this way the viewer won't need
// to step through the same fragments twice
if( to.classList.contains( 'fragment' ) ) {
// Don't auto-animate the opacity of fragments to avoid
// conflicts with fragment animations
delete toProps.styles['opacity'];
if( from.classList.contains( 'fragment' ) ) {
let fromFragmentStyle = ( from.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];
let toFragmentStyle = ( to.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];
// Only skip the fragment if the fragment animation style
// remains unchanged
if( fromFragmentStyle === toFragmentStyle && animationOptions.slideDirection === 'forward' ) {
to.classList.add( 'visible', 'disabled' );
}
}
}
// If translation and/or scaling are enabled, css transform
@@ -455,7 +468,7 @@ export default class AutoAnimate {
// Text
this.findAutoAnimateMatches( pairs, fromSlide, toSlide, textNodes, node => {
return node.nodeName + ':::' + node.textContent.trim();
return node.nodeName + ':::' + node.innerText;
} );
// Media
@@ -465,7 +478,7 @@ export default class AutoAnimate {
// Code
this.findAutoAnimateMatches( pairs, fromSlide, toSlide, codeNodes, node => {
return node.nodeName + ':::' + node.textContent.trim();
return node.nodeName + ':::' + node.innerText;
} );
pairs.forEach( pair => {
+13 -44
View File
@@ -268,15 +268,14 @@ export default class Backgrounds {
*/
update( includeAll = false ) {
let config = this.Reveal.getConfig();
let currentSlide = this.Reveal.getCurrentSlide();
let indices = this.Reveal.getIndices();
let currentBackground = null;
// Reverse past/future classes when in RTL mode
let horizontalPast = config.rtl ? 'future' : 'past',
horizontalFuture = config.rtl ? 'past' : 'future';
let horizontalPast = this.Reveal.getConfig().rtl ? 'future' : 'past',
horizontalFuture = this.Reveal.getConfig().rtl ? 'past' : 'future';
// Update the classes of all backgrounds to match the
// states of their slides (past/present/future)
@@ -322,53 +321,15 @@ export default class Backgrounds {
} );
// The previous background may refer to a DOM element that has
// been removed after a presentation is synced & bgs are recreated
if( this.previousBackground && !this.previousBackground.closest( 'body' ) ) {
this.previousBackground = null;
}
if( currentBackground && this.previousBackground ) {
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
let previousBackgroundHash = this.previousBackground.getAttribute( 'data-background-hash' );
let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {
this.element.classList.add( 'no-transition' );
// If multiple slides have the same background video, carry
// the <video> element forward so that it plays continuously
// across multiple slides
const currentVideo = currentBackground.querySelector( 'video' );
const previousVideo = this.previousBackground.querySelector( 'video' );
if( currentVideo && previousVideo ) {
const currentVideoParent = currentVideo.parentNode;
const previousVideoParent = previousVideo.parentNode;
// Swap the two videos
previousVideoParent.appendChild( currentVideo );
currentVideoParent.appendChild( previousVideo );
}
}
}
const backgroundChanged = currentBackground !== this.previousBackground;
// Stop content inside of previous backgrounds
if( backgroundChanged && this.previousBackground ) {
if( this.previousBackground ) {
this.Reveal.slideContent.stopEmbeddedContent( this.previousBackground, { unloadIframes: !this.Reveal.slideContent.shouldPreload( this.previousBackground ) } );
}
// Start content in the current background
if( backgroundChanged && currentBackground ) {
if( currentBackground ) {
this.Reveal.slideContent.startEmbeddedContent( currentBackground );
@@ -386,6 +347,14 @@ export default class Backgrounds {
}
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
let previousBackgroundHash = this.previousBackground ? this.previousBackground.getAttribute( 'data-background-hash' ) : null;
let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {
this.element.classList.add( 'no-transition' );
}
this.previousBackground = currentBackground;
}
@@ -399,7 +368,7 @@ export default class Backgrounds {
// Allow the first background to apply without transition
setTimeout( () => {
this.element.classList.remove( 'no-transition' );
}, 10 );
}, 1 );
}
+6 -28
View File
@@ -1,4 +1,4 @@
import { queryAll, enterFullscreen } from '../utils/util.js'
import { queryAll } from '../utils/util.js'
import { isAndroid } from '../utils/device.js'
/**
@@ -12,7 +12,6 @@ import { isAndroid } from '../utils/device.js'
* - .navigate-left
* - .navigate-next
* - .navigate-prev
* - .enter-fullscreen
*/
export default class Controls {
@@ -26,7 +25,6 @@ export default class Controls {
this.onNavigateDownClicked = this.onNavigateDownClicked.bind( this );
this.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this );
this.onNavigateNextClicked = this.onNavigateNextClicked.bind( this );
this.onEnterFullscreen = this.onEnterFullscreen.bind( this );
}
@@ -52,7 +50,6 @@ export default class Controls {
this.controlsDown = queryAll( revealElement, '.navigate-down' );
this.controlsPrev = queryAll( revealElement, '.navigate-prev' );
this.controlsNext = queryAll( revealElement, '.navigate-next' );
this.controlsFullscreen = queryAll( revealElement, '.enter-fullscreen' );
// The left, right and down arrows in the standard reveal.js controls
this.controlsRightArrow = this.element.querySelector( '.navigate-right' );
@@ -66,10 +63,7 @@ export default class Controls {
*/
configure( config, oldConfig ) {
this.element.style.display = (
config.controls &&
(config.controls !== 'speaker-only' || this.Reveal.isSpeakerNotes())
) ? 'block' : 'none';
this.element.style.display = config.controls ? 'block' : 'none';
this.element.setAttribute( 'data-controls-layout', config.controlsLayout );
this.element.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
@@ -83,10 +77,9 @@ export default class Controls {
let pointerEvents = [ 'touchstart', 'click' ];
// Only support touch for Android, fixes double navigations in
// stock browser. Use touchend for it to be considered a valid
// user interaction (so we're allowed to autoplay media).
// stock browser
if( isAndroid ) {
pointerEvents = [ 'touchend' ];
pointerEvents = [ 'touchstart' ];
}
pointerEvents.forEach( eventName => {
@@ -96,21 +89,19 @@ export default class Controls {
this.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) );
this.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) );
this.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) );
this.controlsFullscreen.forEach( el => el.addEventListener( eventName, this.onEnterFullscreen, false ) );
} );
}
unbind() {
[ 'touchstart', 'touchend', 'click' ].forEach( eventName => {
[ 'touchstart', 'click' ].forEach( eventName => {
this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );
this.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) );
this.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) );
this.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) );
this.controlsFullscreen.forEach( el => el.removeEventListener( eventName, this.onEnterFullscreen, false ) );
} );
}
@@ -150,14 +141,9 @@ export default class Controls {
if( fragmentsRoutes.prev ) this.controlsPrev.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
if( fragmentsRoutes.next ) this.controlsNext.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
const isVerticalStack = this.Reveal.isVerticalSlide( currentSlide );
const hasVerticalSiblings = isVerticalStack &&
currentSlide.parentElement &&
currentSlide.parentElement.querySelectorAll( ':scope > section' ).length > 1;
// Apply fragment decorators to directional buttons based on
// what slide axis they are in
if( isVerticalStack && hasVerticalSiblings ) {
if( this.Reveal.isVerticalSlide( currentSlide ) ) {
if( fragmentsRoutes.prev ) this.controlsUp.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
if( fragmentsRoutes.next ) this.controlsDown.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
}
@@ -276,13 +262,5 @@ export default class Controls {
}
onEnterFullscreen( event ) {
const config = this.Reveal.getConfig();
const viewport = this.Reveal.getViewportElement();
enterFullscreen( config.embedded ? viewport : viewport.parentElement );
}
}
+20 -20
View File
@@ -257,26 +257,6 @@ export default class Fragments {
}
if( changedFragments.hidden.length ) {
this.Reveal.dispatchEvent({
type: 'fragmenthidden',
data: {
fragment: changedFragments.hidden[0],
fragments: changedFragments.hidden
}
});
}
if( changedFragments.shown.length ) {
this.Reveal.dispatchEvent({
type: 'fragmentshown',
data: {
fragment: changedFragments.shown[0],
fragments: changedFragments.shown
}
});
}
return changedFragments;
}
@@ -331,6 +311,26 @@ export default class Fragments {
let changedFragments = this.update( index, fragments );
if( changedFragments.hidden.length ) {
this.Reveal.dispatchEvent({
type: 'fragmenthidden',
data: {
fragment: changedFragments.hidden[0],
fragments: changedFragments.hidden
}
});
}
if( changedFragments.shown.length ) {
this.Reveal.dispatchEvent({
type: 'fragmentshown',
data: {
fragment: changedFragments.shown[0],
fragments: changedFragments.shown
}
});
}
this.Reveal.controls.update();
this.Reveal.progress.update();
+1 -1
View File
@@ -74,7 +74,7 @@ export default class JumpToSlide {
let query = this.jumpInput.value.trim( '' );
let indices;
// When slide numbers are formatted to be a single linear number
// When slide numbers are formatted to be a single linear mumber
// (instead of showing a separate horizontal/vertical index) we
// use the same format for slide jumps
if( /^\d+$/.test( query ) ) {
+6 -34
View File
@@ -167,7 +167,7 @@ export default class Keyboard {
let activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
// Whitelist certain modifiers for slide navigation shortcuts
let keyCodeUsesModifier = [32, 37, 38, 39, 40, 63, 78, 80, 191].indexOf( event.keyCode ) !== -1;
let keyCodeUsesModifier = [32, 37, 38, 39, 40, 78, 80, 191].indexOf( event.keyCode ) !== -1;
// Prevent all other events when a modifier is pressed
let unusedModifier = !( keyCodeUsesModifier && event.shiftKey || event.altKey ) &&
@@ -178,7 +178,7 @@ export default class Keyboard {
if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;
// While paused only allow resume keyboard events; 'b', 'v', '.'
let resumeKeyCodes = [66,86,190,191,112];
let resumeKeyCodes = [66,86,190,191];
let key;
// Custom key bindings for togglePause should be able to resume
@@ -190,10 +190,6 @@ export default class Keyboard {
}
}
if( this.Reveal.isOverlayOpen() && !["Escape", "f", "c", "b", "."].includes(event.key) ) {
return false;
}
if( this.Reveal.isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) {
return false;
}
@@ -275,12 +271,7 @@ export default class Keyboard {
this.Reveal.slide( 0 );
}
else if( !this.Reveal.overview.isActive() && useLinearMode ) {
if( config.rtl ) {
this.Reveal.next({skipFragments: event.altKey});
}
else {
this.Reveal.prev({skipFragments: event.altKey});
}
this.Reveal.prev({skipFragments: event.altKey});
}
else {
this.Reveal.left({skipFragments: event.altKey});
@@ -292,12 +283,7 @@ export default class Keyboard {
this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
}
else if( !this.Reveal.overview.isActive() && useLinearMode ) {
if( config.rtl ) {
this.Reveal.prev({skipFragments: event.altKey});
}
else {
this.Reveal.next({skipFragments: event.altKey});
}
this.Reveal.next({skipFragments: event.altKey});
}
else {
this.Reveal.right({skipFragments: event.altKey});
@@ -367,16 +353,8 @@ export default class Keyboard {
this.Reveal.toggleJumpToSlide();
}
}
// C
else if( keyCode === 67 && this.Reveal.isOverlayOpen() ) {
this.Reveal.closeOverlay();
}
// ?
else if( ( keyCode === 63 || keyCode === 191 ) && event.shiftKey ) {
this.Reveal.toggleHelp();
}
// F1
else if( keyCode === 112 ) {
else if( keyCode === 191 && event.shiftKey ) {
this.Reveal.toggleHelp();
}
else {
@@ -398,12 +376,6 @@ export default class Keyboard {
event.preventDefault && event.preventDefault();
}
// Enter to exit overview mode
else if (keyCode === 13 && this.Reveal.overview.isActive()) {
this.Reveal.overview.deactivate();
event.preventDefault && event.preventDefault();
}
// If auto-sliding is enabled we need to cue up
// another timeout
@@ -411,4 +383,4 @@ export default class Keyboard {
}
}
}
+4 -6
View File
@@ -60,13 +60,11 @@ export default class Location {
name = name.split( '/' ).shift();
}
// Ensure the named link is a valid HTML id or data-id attribute
// Ensure the named link is a valid HTML ID attribute
try {
const decodedName = decodeURIComponent( name );
slide = (
document.getElementById( decodedName ) ||
document.querySelector( `[data-id="${decodedName}"]` )
).closest('.slides section');
slide = document
.getElementById( decodeURIComponent( name ) )
.closest('.slides section');
}
catch ( error ) { }
-389
View File
@@ -1,389 +0,0 @@
/**
* Handles the display of reveal.js' overlay elements used
* to preview iframes, images & videos.
*/
export default class Overlay {
constructor( Reveal ) {
this.Reveal = Reveal;
this.onSlidesClicked = this.onSlidesClicked.bind( this );
this.iframeTriggerSelector = null;
this.mediaTriggerSelector = '[data-preview-image], [data-preview-video]';
this.stateProps = ['previewIframe', 'previewImage', 'previewVideo', 'previewFit'];
this.state = {};
}
update() {
// Enable link previews globally
if( this.Reveal.getConfig().previewLinks ) {
this.iframeTriggerSelector = 'a[href]:not([data-preview-link=false]), [data-preview-link]:not(a):not([data-preview-link=false])';
}
// Enable link previews for individual elements
else {
this.iframeTriggerSelector = '[data-preview-link]:not([data-preview-link=false])';
}
const hasLinkPreviews = this.Reveal.getSlidesElement().querySelectorAll( this.iframeTriggerSelector ).length > 0;
const hasMediaPreviews = this.Reveal.getSlidesElement().querySelectorAll( this.mediaTriggerSelector ).length > 0;
// Only add the listener when there are previewable elements in the slides
if( hasLinkPreviews || hasMediaPreviews ) {
this.Reveal.getSlidesElement().addEventListener( 'click', this.onSlidesClicked, false );
}
else {
this.Reveal.getSlidesElement().removeEventListener( 'click', this.onSlidesClicked, false );
}
}
createOverlay( className ) {
this.dom = document.createElement( 'div' );
this.dom.classList.add( 'r-overlay' );
this.dom.classList.add( className );
this.viewport = document.createElement( 'div' );
this.viewport.classList.add( 'r-overlay-viewport' );
this.dom.appendChild( this.viewport );
this.Reveal.getRevealElement().appendChild( this.dom );
}
/**
* Opens a lightbox that previews the target URL.
*
* @param {string} url - url for lightbox iframe src
*/
previewIframe( url ) {
this.close();
this.state = { previewIframe: url };
this.createOverlay( 'r-overlay-preview' );
this.dom.dataset.state = 'loading';
this.viewport.innerHTML =
`<header class="r-overlay-header">
<a class="r-overlay-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
<button class="r-overlay-button r-overlay-close"><span class="icon"></span></button>
</header>
<div class="r-overlay-spinner"></div>
<div class="r-overlay-content">
<iframe src="${url}"></iframe>
<small class="r-overlay-content-inner">
<span class="r-overlay-error x-frame-error">Unable to load iframe. This is likely due to the site's policy (x-frame-options).</span>
</small>
</div>`;
this.dom.querySelector( 'iframe' ).addEventListener( 'load', event => {
this.dom.dataset.state = 'loaded';
}, false );
this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', event => {
this.close();
event.preventDefault();
}, false );
this.dom.querySelector( '.r-overlay-external' ).addEventListener( 'click', event => {
this.close();
}, false );
this.Reveal.dispatchEvent({ type: 'previewiframe', data: { url } });
}
/**
* Opens a lightbox window that provides a larger view of the
* given image/video.
*
* @param {string} url - url to the image/video to preview
* @param {image|video} mediaType
* @param {string} [fitMode] - the fit mode to use for the preview
*/
previewMedia( url, mediaType, fitMode ) {
if( mediaType !== 'image' && mediaType !== 'video' ) {
console.warn( 'Please specify a valid media type to preview (image|video)' );
return;
}
this.close();
fitMode = fitMode || 'scale-down';
this.createOverlay( 'r-overlay-preview' );
this.dom.dataset.state = 'loading';
this.dom.dataset.previewFit = fitMode;
this.viewport.innerHTML =
`<header class="r-overlay-header">
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
</header>
<div class="r-overlay-spinner"></div>
<div class="r-overlay-content"></div>`;
const contentElement = this.dom.querySelector( '.r-overlay-content' );
if( mediaType === 'image' ) {
this.state = { previewImage: url, previewFit: fitMode }
const img = document.createElement( 'img', {} );
img.src = url;
contentElement.appendChild( img );
img.addEventListener( 'load', () => {
this.dom.dataset.state = 'loaded';
}, false );
img.addEventListener( 'error', () => {
this.dom.dataset.state = 'error';
contentElement.innerHTML =
`<span class="r-overlay-error">Unable to load image.</span>`
}, false );
// Hide image overlays when clicking outside the overlay
this.dom.style.cursor = 'zoom-out';
this.dom.addEventListener( 'click', ( event ) => {
this.close();
}, false );
this.Reveal.dispatchEvent({ type: 'previewimage', data: { url } });
}
else if( mediaType === 'video' ) {
this.state = { previewVideo: url, previewFit: fitMode }
const video = document.createElement( 'video' );
video.autoplay = this.dom.dataset.previewAutoplay === 'false' ? false : true;
video.controls = this.dom.dataset.previewControls === 'false' ? false : true;
video.loop = this.dom.dataset.previewLoop === 'true' ? true : false;
video.muted = this.dom.dataset.previewMuted === 'true' ? true : false;
video.playsInline = true;
video.src = url;
contentElement.appendChild( video );
video.addEventListener( 'loadeddata', () => {
this.dom.dataset.state = 'loaded';
}, false );
video.addEventListener( 'error', () => {
this.dom.dataset.state = 'error';
contentElement.innerHTML =
`<span class="r-overlay-error">Unable to load video.</span>`;
}, false );
this.Reveal.dispatchEvent({ type: 'previewvideo', data: { url } });
}
else {
throw new Error( 'Please specify a valid media type to preview' );
}
this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', ( event ) => {
this.close();
event.preventDefault();
}, false );
}
previewImage( url, fitMode ) {
this.previewMedia( url, 'image', fitMode );
}
previewVideo( url, fitMode ) {
this.previewMedia( url, 'video', fitMode );
}
/**
* Open or close help overlay window.
*
* @param {Boolean} [override] Flag which overrides the
* toggle logic and forcibly sets the desired state. True means
* help is open, false means it's closed.
*/
toggleHelp( override ) {
if( typeof override === 'boolean' ) {
override ? this.showHelp() : this.close();
}
else {
if( this.dom ) {
this.close();
}
else {
this.showHelp();
}
}
}
/**
* Opens an overlay window with help material.
*/
showHelp() {
if( this.Reveal.getConfig().help ) {
this.close();
this.createOverlay( 'r-overlay-help' );
let html = '<p class="title">Keyboard Shortcuts</p>';
let shortcuts = this.Reveal.keyboard.getShortcuts(),
bindings = this.Reveal.keyboard.getBindings();
html += '<table><th>KEY</th><th>ACTION</th>';
for( let key in shortcuts ) {
html += `<tr><td>${key}</td><td>${shortcuts[ key ]}</td></tr>`;
}
// Add custom key bindings that have associated descriptions
for( let binding in bindings ) {
if( bindings[binding].key && bindings[binding].description ) {
html += `<tr><td>${bindings[binding].key}</td><td>${bindings[binding].description}</td></tr>`;
}
}
html += '</table>';
this.viewport.innerHTML = `
<header class="r-overlay-header">
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
</header>
<div class="r-overlay-content">
<div class="r-overlay-help-content">${html}</div>
</div>
`;
this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', event => {
this.close();
event.preventDefault();
}, false );
this.Reveal.dispatchEvent({ type: 'showhelp' });
}
}
isOpen() {
return !!this.dom;
}
/**
* Closes any currently open overlay.
*/
close() {
if( this.dom ) {
this.dom.remove();
this.dom = null;
this.state = {};
this.Reveal.dispatchEvent({ type: 'closeoverlay' });
return true;
}
return false;
}
getState() {
return this.state;
}
setState( state ) {
// Ignore the incoming state if none of the preview related
// props have changed
if( this.stateProps.every( key => this.state[ key ] === state[ key ] ) ) {
return;
}
if( state.previewIframe ) {
this.previewIframe( state.previewIframe );
}
else if( state.previewImage ) {
this.previewImage( state.previewImage, state.previewFit );
}
else if( state.previewVideo ) {
this.previewVideo( state.previewVideo, state.previewFit );
}
else {
this.close();
}
}
onSlidesClicked( event ) {
const target = event.target;
const linkTarget = target.closest( this.iframeTriggerSelector );
const mediaTarget = target.closest( this.mediaTriggerSelector );
// Was an iframe lightbox trigger clicked?
if( linkTarget ) {
if( event.metaKey || event.shiftKey || event.altKey ) {
// Let the browser handle meta keys naturally so users can cmd+click
return;
}
let url = linkTarget.getAttribute( 'href' ) || linkTarget.getAttribute( 'data-preview-link' );
if( url ) {
this.previewIframe( url );
event.preventDefault();
}
}
// Was a media lightbox trigger clicked?
else if( mediaTarget ) {
if( mediaTarget.hasAttribute( 'data-preview-image' ) ) {
let url = mediaTarget.dataset.previewImage || mediaTarget.getAttribute( 'src' );
if( url ) {
this.previewImage( url, mediaTarget.dataset.previewFit );
event.preventDefault();
}
}
else if( mediaTarget.hasAttribute( 'data-preview-video' ) ) {
let url = mediaTarget.dataset.previewVideo || mediaTarget.getAttribute( 'src' );
if( !url ) {
let source = mediaTarget.querySelector( 'source' );
if( source ) {
url = source.getAttribute( 'src' );
}
}
if( url ) {
this.previewVideo( url, mediaTarget.dataset.previewFit );
event.preventDefault();
}
}
}
}
destroy() {
this.close();
}
}
+9 -44
View File
@@ -1,4 +1,4 @@
import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants.js'
import { HORIZONTAL_SLIDES_SELECTOR } from '../utils/constants.js'
import { queryAll } from '../utils/util.js'
const HIDE_SCROLLBAR_TIMEOUT = 500;
@@ -40,7 +40,6 @@ export default class ScrollView {
this.slideHTMLBeforeActivation = this.Reveal.getSlidesElement().innerHTML;
const horizontalSlides = queryAll( this.Reveal.getRevealElement(), HORIZONTAL_SLIDES_SELECTOR );
const horizontalBackgrounds = queryAll( this.Reveal.getRevealElement(), HORIZONTAL_BACKGROUNDS_SELECTOR );
this.viewportElement.classList.add( 'loading-scroll-mode', 'reveal-scroll' );
@@ -58,7 +57,7 @@ export default class ScrollView {
// Creates a new page element and appends the given slide/bg
// to it.
const createPageElement = ( slide, h, v, isVertical ) => {
const createPageElement = ( slide, h, v ) => {
let contentContainer;
@@ -77,20 +76,8 @@ export default class ScrollView {
page.className = 'scroll-page';
pageElements.push( page );
// This transfers over the background of the vertical stack containing
// the slide if it exists. Otherwise, it uses the presentation-wide
// background.
if( isVertical && horizontalBackgrounds.length > h ) {
const slideBackground = horizontalBackgrounds[h];
const pageBackground = window.getComputedStyle( slideBackground );
if( pageBackground && pageBackground.background ) {
page.style.background = pageBackground.background;
}
else if( presentationBackground ) {
page.style.background = presentationBackground;
}
} else if( presentationBackground ) {
// Copy the presentation-wide background to each page
if( presentationBackground ) {
page.style.background = presentationBackground;
}
@@ -123,7 +110,7 @@ export default class ScrollView {
if( this.Reveal.isVerticalStack( horizontalSlide ) ) {
horizontalSlide.querySelectorAll( 'section' ).forEach( ( verticalSlide, v ) => {
createPageElement( verticalSlide, h, v, true );
createPageElement( verticalSlide, h, v );
});
}
else {
@@ -290,7 +277,7 @@ export default class ScrollView {
const pageHeight = useCompactLayout ? compactHeight : viewportHeight;
// The height that needs to be scrolled between scroll triggers
this.scrollTriggerHeight = useCompactLayout ? compactHeight : viewportHeight;
const scrollTriggerHeight = useCompactLayout ? compactHeight : viewportHeight;
this.viewportElement.style.setProperty( '--page-height', pageHeight + 'px' );
this.viewportElement.style.scrollSnapType = typeof config.scrollSnap === 'string' ? `y ${config.scrollSnap}` : '';
@@ -346,12 +333,12 @@ export default class ScrollView {
for( let i = 0; i < totalScrollTriggerCount + 1; i++ ) {
const triggerStick = document.createElement( 'div' );
triggerStick.className = 'scroll-snap-point';
triggerStick.style.height = this.scrollTriggerHeight + 'px';
triggerStick.style.height = scrollTriggerHeight + 'px';
triggerStick.style.scrollSnapAlign = useCompactLayout ? 'center' : 'start';
page.pageElement.appendChild( triggerStick );
if( i === 0 ) {
triggerStick.style.marginTop = -this.scrollTriggerHeight + 'px';
triggerStick.style.marginTop = -scrollTriggerHeight + 'px';
}
}
@@ -368,7 +355,7 @@ export default class ScrollView {
}
// Add scroll padding based on how many scroll triggers we have
page.scrollPadding = this.scrollTriggerHeight * totalScrollTriggerCount;
page.scrollPadding = scrollTriggerHeight * totalScrollTriggerCount;
// The total height including scrollable space
page.totalHeight = page.pageHeight + page.scrollPadding;
@@ -449,10 +436,6 @@ export default class ScrollView {
rangeStart = trigger.range[1];
} );
// Ensure the last trigger extends to the end of the page, otherwise
// rounding errors can cause the last trigger to end at 0.999999...
this.slideTriggers[this.slideTriggers.length - 1].range[1] = 1;
}
/**
@@ -716,24 +699,6 @@ export default class ScrollView {
}
/**
* Scroll to the previous page.
*/
prev() {
this.viewportElement.scrollTop -= this.scrollTriggerHeight;
}
/**
* Scroll to the next page.
*/
next() {
this.viewportElement.scrollTop += this.scrollTriggerHeight;
}
/**
* Scrolls the given slide element into view.
*
+27 -157
View File
@@ -9,15 +9,11 @@ import fitty from 'fitty';
*/
export default class SlideContent {
allowedToPlay = true;
constructor( Reveal ) {
this.Reveal = Reveal;
this.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );
this.preventIframeAutoFocus = this.preventIframeAutoFocus.bind( this );
this.ensureMobileMediaPlaying = this.ensureMobileMediaPlaying.bind( this );
}
@@ -55,25 +51,14 @@ export default class SlideContent {
load( slide, options = {} ) {
// Show the slide element
const displayValue = this.Reveal.getConfig().display;
if( displayValue.includes('!important') ) {
const value = displayValue.replace(/\s*!important\s*$/, '').trim();
slide.style.setProperty('display', value, 'important');
} else {
slide.style.display = displayValue;
}
slide.style.display = this.Reveal.getConfig().display;
// Media and iframe elements with data-src attributes
// Media elements with data-src attributes
queryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {
const isIframe = element.tagName === 'IFRAME';
if( !isIframe || this.shouldPreload( element ) ) {
if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
element.setAttribute( 'data-lazy-loaded', '' );
element.removeAttribute( 'data-src' );
if( isIframe ) {
element.addEventListener( 'load', this.preventIframeAutoFocus );
}
}
} );
@@ -134,14 +119,14 @@ export default class SlideContent {
}
}
// Videos
else if ( backgroundVideo ) {
else if ( backgroundVideo && !this.Reveal.isSpeakerNotes() ) {
let video = document.createElement( 'video' );
if( backgroundVideoLoop ) {
video.setAttribute( 'loop', '' );
}
if( backgroundVideoMuted || this.Reveal.isSpeakerNotes() ) {
if( backgroundVideoMuted ) {
video.muted = true;
}
@@ -295,9 +280,7 @@ export default class SlideContent {
*/
startEmbeddedContent( element ) {
if( element ) {
const isSpeakerNotesWindow = this.Reveal.isSpeakerNotes();
if( element && !this.Reveal.isSpeakerNotes() ) {
// Restart GIFs
queryAll( element, 'img[src$=".gif"]' ).forEach( el => {
@@ -323,9 +306,6 @@ export default class SlideContent {
if( autoplay && typeof el.play === 'function' ) {
// In the speaker view we only auto-play muted media
if( isSpeakerNotesWindow && !el.muted ) return;
// If the media is ready, start playback
if( el.readyState > 1 ) {
this.startEmbeddedMedia( { target: el } );
@@ -335,16 +315,10 @@ export default class SlideContent {
else if( isMobile ) {
let promise = el.play();
el.addEventListener( 'canplay', this.ensureMobileMediaPlaying );
// If autoplay does not work, ensure that the controls are visible so
// that the viewer can start the media on their own
if( promise && typeof promise.catch === 'function' && el.controls === false ) {
promise
.then( () => {
this.allowedToPlay = true;
})
.catch( () => {
promise.catch( () => {
el.controls = true;
// Once the video does start playing, hide the controls again
@@ -363,72 +337,32 @@ export default class SlideContent {
}
} );
// Don't play iframe content in the speaker view since we can't
// guarantee that it's muted
if( !isSpeakerNotesWindow ) {
// Normal iframes
queryAll( element, 'iframe[src]' ).forEach( el => {
if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
return;
}
// Normal iframes
queryAll( element, 'iframe[src]' ).forEach( el => {
if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
return;
}
this.startEmbeddedIframe( { target: el } );
} );
this.startEmbeddedIframe( { target: el } );
} );
// Lazy loading iframes
queryAll( element, 'iframe[data-src]' ).forEach( el => {
if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
return;
}
// Lazy loading iframes
queryAll( element, 'iframe[data-src]' ).forEach( el => {
if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
return;
}
if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
el.removeEventListener( 'load', this.startEmbeddedIframe ); // remove first to avoid dupes
el.addEventListener( 'load', this.startEmbeddedIframe );
el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
}
} );
}
if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
el.removeEventListener( 'load', this.startEmbeddedIframe ); // remove first to avoid dupes
el.addEventListener( 'load', this.startEmbeddedIframe );
el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
}
} );
}
}
/**
* Ensure that an HTMLMediaElement is playing on mobile devices.
*
* This is a workaround for a bug in mobile Safari where
* the media fails to display if many videos are started
* at the same moment. When this happens, Mobile Safari
* reports the video is playing, and the current time
* advances, but nothing is visible.
*
* @param {Event} event
*/
ensureMobileMediaPlaying( event ) {
const el = event.target;
// Ignore this check incompatible browsers
if( typeof el.getVideoPlaybackQuality !== 'function' ) {
return;
}
setTimeout( () => {
const playing = el.paused === false;
const totalFrames = el.getVideoPlaybackQuality().totalVideoFrames;
if( playing && totalFrames === 0 ) {
el.load();
el.play();
}
}, 1000 );
}
/**
* Starts playing an embedded video/audio element after
* it has finished loading.
@@ -441,23 +375,8 @@ export default class SlideContent {
isVisible = !!closest( event.target, '.present' );
if( isAttachedToDOM && isVisible ) {
// Don't restart if media is already playing
if( event.target.paused || event.target.ended ) {
event.target.currentTime = 0;
const promise = event.target.play();
if( promise && typeof promise.catch === 'function' ) {
promise
.then( () => {
this.allowedToPlay = true;
} )
.catch( ( error ) => {
if( error.name === 'NotAllowedError' ) {
this.allowedToPlay = false;
}
} );
}
}
event.target.currentTime = 0;
event.target.play();
}
event.target.removeEventListener( 'loadeddata', this.startEmbeddedMedia );
@@ -474,8 +393,6 @@ export default class SlideContent {
let iframe = event.target;
this.preventIframeAutoFocus( event );
if( iframe && iframe.contentWindow ) {
let isAttachedToDOM = !!closest( event.target, 'html' ),
@@ -530,17 +447,12 @@ export default class SlideContent {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
el.setAttribute('data-paused-by-reveal', '');
el.pause();
if( isMobile ) {
el.removeEventListener( 'canplay', this.ensureMobileMediaPlaying );
}
}
} );
// Generic postMessage API for non-lazy loaded iframes
queryAll( element, 'iframe' ).forEach( el => {
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
el.removeEventListener( 'load', this.preventIframeAutoFocus );
el.removeEventListener( 'load', this.startEmbeddedIframe );
});
@@ -571,46 +483,4 @@ export default class SlideContent {
}
/**
* Checks whether media playback is blocked by the browser. This
* typically happens when media playback is initiated without a
* direct user interaction.
*/
isNotAllowedToPlay() {
return !this.allowedToPlay;
}
/**
* Prevents iframes from automatically focusing themselves.
*
* @param {Event} event
*/
preventIframeAutoFocus( event ) {
const iframe = event.target;
console.log(111)
if( iframe && this.Reveal.getConfig().preventIframeAutoFocus ) {
let elapsed = 0;
const interval = 100;
const maxTime = 1000;
const checkFocus = () => {
if( document.activeElement === iframe ) {
document.activeElement.blur();
} else if( elapsed < maxTime ) {
elapsed += interval;
setTimeout( checkFocus, interval );
}
};
setTimeout( checkFocus, interval );
}
}
}
+1 -11
View File
@@ -84,7 +84,7 @@ export default class Touch {
isSwipePrevented( target ) {
// Prevent accidental swipes when scrubbing timelines
if( matches( target, 'video[controls], audio[controls]' ) ) return true;
if( matches( target, 'video, audio' ) ) return true;
while( target && typeof target.hasAttribute === 'function' ) {
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
@@ -103,8 +103,6 @@ export default class Touch {
*/
onTouchStart( event ) {
this.touchCaptured = false;
if( this.isSwipePrevented( event.target ) ) return true;
this.touchStartX = event.touches[0].clientX;
@@ -216,14 +214,6 @@ export default class Touch {
*/
onTouchEnd( event ) {
// Media playback is only allowed as a direct result of a
// user interaction. Some mobile devices do not consider a
// 'touchmove' to be a direct user action. If this is the
// case, we fall back to starting playback here instead.
if( this.touchCaptured && this.Reveal.slideContent.isNotAllowedToPlay() ) {
this.Reveal.startEmbeddedContent( this.Reveal.getCurrentSlide() );
}
this.touchCaptured = false;
}
+201 -99
View File
@@ -13,7 +13,6 @@ import Controls from './controllers/controls.js'
import Progress from './controllers/progress.js'
import Pointer from './controllers/pointer.js'
import Plugins from './controllers/plugins.js'
import Overlay from './controllers/overlay.js'
import Touch from './controllers/touch.js'
import Focus from './controllers/focus.js'
import Notes from './controllers/notes.js'
@@ -29,7 +28,7 @@ import {
} from './utils/constants.js'
// The reveal.js version
export const VERSION = '5.2.1';
export const VERSION = '5.0.1';
/**
* reveal.js
@@ -52,9 +51,6 @@ export default function( revealElement, options ) {
// Configuration defaults, can be overridden at initialization time
let config = {},
// Flags if initialize() has been invoked for this reveal instance
initialized = false,
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
ready = false,
@@ -120,7 +116,6 @@ export default function( revealElement, options ) {
progress = new Progress( Reveal ),
pointer = new Pointer( Reveal ),
plugins = new Plugins( Reveal ),
overlay = new Overlay( Reveal ),
focus = new Focus( Reveal ),
touch = new Touch( Reveal ),
notes = new Notes( Reveal );
@@ -132,10 +127,6 @@ export default function( revealElement, options ) {
if( !revealElement ) throw 'Unable to find presentation root (<div class="reveal">).';
if( initialized ) throw 'Reveal.js has already been initialized.';
initialized = true;
// Cache references to key DOM elements
dom.wrapper = revealElement;
dom.slides = revealElement.querySelector( '.slides' );
@@ -194,9 +185,6 @@ export default function( revealElement, options ) {
*/
function start() {
// Don't proceed if this instance has been destroyed
if( initialized === false ) return;
ready = true;
// Remove slides hidden with data-visibility
@@ -394,7 +382,7 @@ export default function( revealElement, options ) {
// Text node
if( node.nodeType === 3 ) {
text += node.textContent.trim();
text += node.textContent;
}
// Element node
else if( node.nodeType === 1 ) {
@@ -403,25 +391,10 @@ export default function( revealElement, options ) {
let isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
if( isAriaHidden !== 'true' && !isDisplayHidden ) {
// Capture alt text from img and video elements
if( node.tagName === 'IMG' || node.tagName === 'VIDEO' ) {
let altText = node.getAttribute( 'alt' );
if( altText ) {
text += ensurePunctuation( altText );
}
}
Array.from( node.childNodes ).forEach( child => {
text += getStatusText( child );
} );
// Add period after block-level text elements to improve
// screen reader experience
const textElements = ['P', 'DIV', 'UL', 'OL', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE'];
if( textElements.includes( node.tagName ) && text.trim() !== '' ) {
text = ensurePunctuation( text );
}
}
}
@@ -432,22 +405,6 @@ export default function( revealElement, options ) {
}
/**
* Ensures text ends with proper punctuation by adding a period
* if it doesn't already end with punctuation.
*/
function ensurePunctuation( text ) {
const trimmedText = text.trim();
if( trimmedText === '' ) {
return text;
}
return !/[.!?]$/.test(trimmedText) ? trimmedText + '.' : trimmedText;
}
/**
* This is an unfortunate necessity. Some actions such as
* an input field being focused in an iframe or using the
@@ -543,6 +500,16 @@ export default function( revealElement, options ) {
resume();
}
// Iframe link previews
if( config.previewLinks ) {
enablePreviewLinks();
disablePreviewLinks( '[data-preview-link=false]' );
}
else {
disablePreviewLinks();
enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
}
// Reset all changes made by auto-animations
autoAnimate.reset();
@@ -637,19 +604,13 @@ export default function( revealElement, options ) {
*/
function destroy() {
initialized = false;
// There's nothing to destroy if this instance hasn't finished
// initializing
if( ready === false ) return;
removeEventListeners();
cancelAutoSlide();
disablePreviewLinks();
// Destroy controllers
notes.destroy();
focus.destroy();
overlay.destroy();
plugins.destroy();
pointer.destroy();
controls.destroy();
@@ -799,6 +760,164 @@ export default function( revealElement, options ) {
}
/**
* Bind preview frame links.
*
* @param {string} [selector=a] - selector for anchors
*/
function enablePreviewLinks( selector = 'a' ) {
Array.from( dom.wrapper.querySelectorAll( selector ) ).forEach( element => {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.addEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Unbind preview frame links.
*/
function disablePreviewLinks( selector = 'a' ) {
Array.from( dom.wrapper.querySelectorAll( selector ) ).forEach( element => {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.removeEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Opens a preview window for the target URL.
*
* @param {string} url - url for preview iframe src
*/
function showPreview( url ) {
closeOverlay();
dom.overlay = document.createElement( 'div' );
dom.overlay.classList.add( 'overlay' );
dom.overlay.classList.add( 'overlay-preview' );
dom.wrapper.appendChild( dom.overlay );
dom.overlay.innerHTML =
`<header>
<a class="close" href="#"><span class="icon"></span></a>
<a class="external" href="${url}" target="_blank"><span class="icon"></span></a>
</header>
<div class="spinner"></div>
<div class="viewport">
<iframe src="${url}"></iframe>
<small class="viewport-inner">
<span class="x-frame-error">Unable to load iframe. This is likely due to the site's policy (x-frame-options).</span>
</small>
</div>`;
dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', event => {
dom.overlay.classList.add( 'loaded' );
}, false );
dom.overlay.querySelector( '.close' ).addEventListener( 'click', event => {
closeOverlay();
event.preventDefault();
}, false );
dom.overlay.querySelector( '.external' ).addEventListener( 'click', event => {
closeOverlay();
}, false );
}
/**
* Open or close help overlay window.
*
* @param {Boolean} [override] Flag which overrides the
* toggle logic and forcibly sets the desired state. True means
* help is open, false means it's closed.
*/
function toggleHelp( override ){
if( typeof override === 'boolean' ) {
override ? showHelp() : closeOverlay();
}
else {
if( dom.overlay ) {
closeOverlay();
}
else {
showHelp();
}
}
}
/**
* Opens an overlay window with help material.
*/
function showHelp() {
if( config.help ) {
closeOverlay();
dom.overlay = document.createElement( 'div' );
dom.overlay.classList.add( 'overlay' );
dom.overlay.classList.add( 'overlay-help' );
dom.wrapper.appendChild( dom.overlay );
let html = '<p class="title">Keyboard Shortcuts</p><br/>';
let shortcuts = keyboard.getShortcuts(),
bindings = keyboard.getBindings();
html += '<table><th>KEY</th><th>ACTION</th>';
for( let key in shortcuts ) {
html += `<tr><td>${key}</td><td>${shortcuts[ key ]}</td></tr>`;
}
// Add custom key bindings that have associated descriptions
for( let binding in bindings ) {
if( bindings[binding].key && bindings[binding].description ) {
html += `<tr><td>${bindings[binding].key}</td><td>${bindings[binding].description}</td></tr>`;
}
}
html += '</table>';
dom.overlay.innerHTML = `
<header>
<a class="close" href="#"><span class="icon"></span></a>
</header>
<div class="viewport">
<div class="viewport-inner">${html}</div>
</div>
`;
dom.overlay.querySelector( '.close' ).addEventListener( 'click', event => {
closeOverlay();
event.preventDefault();
}, false );
}
}
/**
* Closes any currently open overlay.
*/
function closeOverlay() {
if( dom.overlay ) {
dom.overlay.parentNode.removeChild( dom.overlay );
dom.overlay = null;
return true;
}
return false;
}
/**
* Applies JavaScript-controlled layout rules to the
* presentation.
@@ -1124,7 +1243,7 @@ export default function( revealElement, options ) {
/**
* Returns true if we're currently on the last slide in
* the presentation. If the last slide is a stack, we only
* the presenation. If the last slide is a stack, we only
* consider this the last slide if it's at the end of the
* stack.
*/
@@ -1327,9 +1446,6 @@ export default function( revealElement, options ) {
let currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
// Indicate when we're on a vertical slide
revealElement.classList.toggle( 'is-vertical-slide', currentVerticalSlides.length > 1 );
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
@@ -1555,7 +1671,6 @@ export default function( revealElement, options ) {
notes.update();
notes.updateVisibility();
overlay.update();
backgrounds.update( true );
slideNumber.update();
slideContent.formatEmbeddedContent();
@@ -1813,16 +1928,14 @@ export default function( revealElement, options ) {
if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
const isOverview = overview.isActive();
// The number of steps away from the present slide that will
// be visible
let viewDistance = isOverview ? 10 : config.viewDistance;
let viewDistance = overview.isActive() ? 10 : config.viewDistance;
// Shorten the view distance on devices that typically have
// less resources
if( Device.isMobile ) {
viewDistance = isOverview ? 6 : config.mobileViewDistance;
viewDistance = overview.isActive() ? 6 : config.mobileViewDistance;
}
// All slides need to be visible when exporting to PDF
@@ -1855,7 +1968,7 @@ export default function( revealElement, options ) {
if( verticalSlidesLength ) {
let oy = isOverview ? 0 : getPreviousVerticalIndex( horizontalSlide );
let oy = getPreviousVerticalIndex( horizontalSlide );
for( let y = 0; y < verticalSlidesLength; y++ ) {
let verticalSlide = verticalSlides[y];
@@ -2243,8 +2356,7 @@ export default function( revealElement, options ) {
indexv: indices.v,
indexf: indices.f,
paused: isPaused(),
overview: overview.isActive(),
...overlay.getState()
overview: overview.isActive()
};
}
@@ -2270,8 +2382,6 @@ export default function( revealElement, options ) {
if( typeof overviewFlag === 'boolean' && overviewFlag !== overview.isActive() ) {
overview.toggle( overviewFlag );
}
overlay.setState( state );
}
}
@@ -2389,9 +2499,6 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Reverse for RTL
if( config.rtl ) {
if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().left ) {
@@ -2409,9 +2516,6 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Reverse for RTL
if( config.rtl ) {
if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().right ) {
@@ -2427,9 +2531,6 @@ export default function( revealElement, options ) {
function navigateUp({skipFragments=false}={}) {
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Prioritize hiding fragments
if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().up ) {
slide( indexh, indexv - 1 );
@@ -2441,9 +2542,6 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedVertically = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Prioritize revealing fragments
if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().down ) {
slide( indexh, indexv + 1 );
@@ -2459,9 +2557,6 @@ export default function( revealElement, options ) {
*/
function navigatePrev({skipFragments=false}={}) {
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.prev();
// Prioritize revealing fragments
if( skipFragments || fragments.prev() === false ) {
if( availableRoutes().up ) {
@@ -2485,9 +2580,6 @@ export default function( revealElement, options ) {
let h = indexh - 1;
slide( h, v );
}
else if( config.rtl ) {
navigateRight({skipFragments});
}
else {
navigateLeft({skipFragments});
}
@@ -2504,9 +2596,6 @@ export default function( revealElement, options ) {
navigationHistory.hasNavigatedHorizontally = true;
navigationHistory.hasNavigatedVertically = true;
// Scroll view navigation is handled independently
if( scrollView.isActive() ) return scrollView.next();
// Prioritize revealing fragments
if( skipFragments || fragments.next() === false ) {
@@ -2676,6 +2765,24 @@ export default function( revealElement, options ) {
}
/**
* Handles clicks on links that are set to preview in the
* iframe overlay.
*
* @param {object} event
*/
function onPreviewLinkClicked( event ) {
if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
let url = event.currentTarget.getAttribute( 'href' );
if( url ) {
showPreview( url );
event.preventDefault();
}
}
}
/**
* Handles click on the auto-sliding controls element.
*
@@ -2754,7 +2861,7 @@ export default function( revealElement, options ) {
availableFragments: fragments.availableRoutes.bind( fragments ),
// Toggles a help overlay with keyboard shortcuts
toggleHelp: overlay.toggleHelp.bind( overlay ),
toggleHelp,
// Toggles the overview mode on/off
toggleOverview: overview.toggle.bind( overview ),
@@ -2784,7 +2891,7 @@ export default function( revealElement, options ) {
isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ),
isOverview: overview.isActive.bind( overview ),
isFocused: focus.isFocused.bind( focus ),
isOverlayOpen: overlay.isOpen.bind( overlay ),
isScrollView: scrollView.isActive.bind( scrollView ),
isPrintView: printView.isActive.bind( printView ),
@@ -2795,17 +2902,13 @@ export default function( revealElement, options ) {
loadSlide: slideContent.load.bind( slideContent ),
unloadSlide: slideContent.unload.bind( slideContent ),
// Start/stop all media inside of the current slide
// Media playback
startEmbeddedContent: () => slideContent.startEmbeddedContent( currentSlide ),
stopEmbeddedContent: () => slideContent.stopEmbeddedContent( currentSlide, { unloadIframes: false } ),
// Lightbox previews
previewIframe: overlay.previewIframe.bind( overlay ),
previewImage: overlay.previewImage.bind( overlay ),
previewVideo: overlay.previewVideo.bind( overlay ),
showPreview: overlay.previewIframe.bind( overlay ), // deprecated in favor of showIframeLightbox
hidePreview: overlay.close.bind( overlay ),
// Preview management
showPreview,
hidePreview: closeOverlay,
// Adds or removes all internal event listeners
addEventListeners,
@@ -2919,14 +3022,13 @@ export default function( revealElement, options ) {
controls,
location,
overview,
keyboard,
fragments,
backgrounds,
slideContent,
slideNumber,
onUserInput,
closeOverlay: overlay.close.bind( overlay ),
closeOverlay,
updateSlidesVisibility,
layoutSlideContents,
transformSlides,
+1 -1
View File
@@ -44,7 +44,7 @@ export const colorToRgb = ( color ) => {
};
}
let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
if( rgba ) {
return {
r: parseInt( rgba[1], 10 ),
-1
View File
@@ -2,7 +2,6 @@
export const SLIDES_SELECTOR = '.slides section';
export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section';
export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';
export const HORIZONTAL_BACKGROUNDS_SELECTOR = '.backgrounds>.slide-background';
// Methods that may not be invoked via the postMessage API
export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/;
@@ -0,0 +1,31 @@
start end text
0 6480 Machine learning. Teach a computer how to perform a task, without explicitly programming it to perform said task.
6620 13420 Instead, feed data into an algorithm to gradually improve outcomes with experience, similar to how organic life learns.
13580 20400 The term was coined in 1959 by Arthur Samuel at IBM, who was developing artificial intelligence that could play checkers.
20540 26880 Half a century later, and predictive models are embedded in many of the products we use every day, which perform two fundamental jobs.
26880 32040 One is to classify data, like "Is there another car on the road?" or "Does this patient have cancer?"
32040 38600 The other is to make predictions about future outcomes, like "Will this stock go up?" or "Which YouTube video do you want to watch next?"
38600 43280 The first step in the process is to acquire and clean up data. Lots and lots of data.
43480 47780 The better the data represents the problem, the better the results. Garbage in, garbage out.
47900 52160 The data needs to have some kind of signal to be valuable to the algorithm for making predictions.
52160 59920 And data scientists perform a job called feature engineering to transform raw data into features that better represent the underlying problem.
60240 64240 The next step is to separate the data into a training set and testing set.
64460 71800 The training data is fed into an algorithm to build a model, then the testing data is used to validate the accuracy or error of the model.
71980 77700 The next step is to choose an algorithm, which might be a simple statistical model like linear or logistic regression,
77940 81260 or a decision tree that assigns different weights to features in the data.
81260 86640 Or you might get fancy with a convolutional neural network, which is an algorithm that also assigns
86640 91300 weights to features, but also takes the input data and creates additional features automatically.
91640 96300 And that's extremely useful for datasets that contain things like images or natural language,
96420 99020 where manual feature engineering is virtually impossible.
99260 103960 Every one of these algorithms learns to get better by comparing its predictions to an error function.
104160 109840 If it's a classification problem, like "Is this animal a cat or a dog?" the error function might be accuracy.
109840 115900 If it's a regression problem, like "How much will a loaf of bread cost next year?" then it might be mean absolute error.
116220 121780 Python is the language of choice among data scientists, but R and Julia are also popular options,
121920 125320 and there are many supporting frameworks out there to make the process approachable.
125500 132680 The end result of the machine learning process is a model, which is just a file that takes some input data in the same shape that it was trained on,
132860 136900 then spits out a prediction that tries to minimize the error that it was optimized for.
136900 141980 It can then be embedded on an actual device or deployed to the cloud to build a real-world product.
142180 144500 This has been Machine Learning in 100 Seconds.
144580 147160 Like and subscribe if you want to see more short videos like this,
147320 150500 and leave a comment if you want to see more machine learning content on this channel.
150620 153040 Thanks for watching, and I will see you in the next one.
Can't render this file because it contains an unexpected character in line 6 and column 44.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

+3034 -1669
View File
File diff suppressed because it is too large Load Diff
+9 -13
View File
@@ -1,6 +1,6 @@
{
"name": "reveal.js",
"version": "5.2.1",
"version": "5.0.4",
"description": "The HTML Presentation Framework",
"homepage": "https://revealjs.com",
"subdomain": "revealjs",
@@ -42,29 +42,25 @@
"core-js": "^3.33.1",
"fitty": "^2.3.7",
"glob": "^10.3.10",
"gulp": "^5.0.0",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^8.0.0",
"gulp-clean-css": "^4.3.0",
"gulp-connect": "^5.7.0",
"gulp-eslint": "^6.0.0",
"gulp-header-comment": "^0.10.0",
"gulp-header": "^2.0.9",
"gulp-tap": "^2.0.0",
"gulp-zip": "^5.1.0",
"highlight.js": "^11.9.0",
"marked": "^4.3.0",
"node-qunit-puppeteer": "^2.2.0",
"through2": "^4.0.2",
"qunit": "^2.22.0",
"node-qunit-puppeteer": "^2.1.2",
"qunit": "^2.20.0",
"rollup": "^4.1.5",
"sass": "^1.79.4",
"sass": "^1.69.5",
"yargs": "^17.7.2"
},
"overrides": {
"gulp-connect": {
"send": "0.19.0"
},
"gulp-header-comment": {
"moment": "2.30.1"
}
"chokidar": "3.5.3",
"glob-parent": "6.0.2"
},
"browserslist": "> 2%, not dead",
"eslintConfig": {
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

@@ -1,345 +0,0 @@
\documentclass[conference]{IEEEtran}
\usepackage{cite}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage{algorithmic}
\usepackage{graphicx}
\usepackage{textcomp}
\usepackage{xcolor}
\usepackage{booktabs}
\usepackage{multirow}
\usepackage{listings}
\usepackage{subfigure}
\usepackage{times}
\begin{document}
\title{PostgreSQL JSONB Storage Performance Optimization: A Comprehensive Survey}
\author{\IEEEauthorblockN{CHEN Yongyuan}
\IEEEauthorblockA{Student ID: 2250020225\\
December 2025}
}
\maketitle
\begin{abstract}
PostgreSQL's JSONB data type represents a significant advancement in semi structured data management, offering binary storage and advanced indexing capabilities that dramatically outperform traditional JSON text storage. This comprehensive survey examines the state of the art optimization techniques that make PostgreSQL's JSONB a powerful solution for modern data intensive applications. The analysis covers four fundamental optimization pillars: binary storage format and decomposition, GIN indexing strategies, TOAST (The Oversized Attribute Storage Technique) mechanisms, and query processing optimizations. Performance benchmarks demonstrate that PostgreSQL JSONB achieves 5 to 10x improvements for nested queries compared to JSON text storage, while maintaining ACID compliance and full SQL integration. The survey identifies current limitations in handling extremely large documents, frequent partial updates, and complex array operations, while exploring emerging optimization approaches for production environments.
\end{abstract}
\begin{IEEEkeywords}
PostgreSQL, JSONB, performance optimization, TOAST, GIN indexing, semi structured data
\end{IEEEkeywords}
\section{Introduction}
\subsection{The Evolution of JSON Support and PostgreSQL's Rising Popularity}
PostgreSQL's journey with JSON data began in 2012 with the introduction of the JSON data type, which provided validation functions but stored data as plain text. The limitations of this approach became apparent as developers struggled with performance issues when querying large JSON documents. In response, PostgreSQL 9.4 introduced JSONB in 2014, representing a paradigm shift in semi structured data handling within relational databases.
The introduction of JSONB coincides with a significant turning point in PostgreSQL's popularity trajectory. Analysis of DB-Engines ranking data reveals that PostgreSQL was the only major database showing consistent growth during the period following JSONB's introduction. While other databases experienced stagnation or decline, PostgreSQL's popularity metrics began rising steadily from 2014 onward.
This correlation suggests that JSONB served as a major driver of PostgreSQL's market success. The technology successfully attracted developers from NoSQL backgrounds who were seeking document database capabilities without sacrificing PostgreSQL's reliability and ACID compliance. The timing aligns with broader industry trends toward microservices architectures and the need for flexible data models, positioning PostgreSQL uniquely as a hybrid solution combining relational and document paradigms.
PostgreSQL's JSONB implementation represents a fundamental departure from text based JSON storage through its sophisticated binary format. When JSON data is inserted into a JSONB column, PostgreSQL parses it once and converts it into a decomposed binary representation that eliminates repetitive parsing during query execution.
This innovation addressed a critical market need for databases that could handle both structured and semi structured data efficiently. Unlike specialized document stores that required abandoning existing SQL investments and ACID guarantees, PostgreSQL JSONB allowed organizations to gradually adopt document models while maintaining their relational infrastructure and expertise.
The timing of JSONB's introduction proved particularly fortuitous, coinciding with the rise of microservices architectures where JSON became the de facto communication standard between services. PostgreSQL's ability to efficiently store and query JSON documents made it an attractive choice for organizations seeking to reduce database technology sprawl while supporting diverse application patterns.
\subsection{Current Challenges in JSONB Performance Management}
Despite PostgreSQL's significant advancements, several performance challenges persist in production environments. Query performance can degrade with deeply nested documents and complex path queries, even though JSONB dramatically outperforms JSON text storage. The binary format's efficiency depends heavily on proper indexing strategies and query patterns.
Storage overhead and bloat present another significant challenge. Frequent updates to JSONB documents can lead to storage bloat due to PostgreSQL's MVCC (Multi Version Concurrency Control) system. The immutability of JSONB data structures means that even small modifications require rewriting entire documents.
Indexing complexity adds another layer of difficulty. Effective GIN indexing for JSONB requires careful consideration of query patterns, index size, and maintenance overhead. Improper indexing strategies can lead to diminished returns or even performance regression. While TOAST handles large values efficiently, very large JSONB documents (multi megabyte) can still strain system resources, particularly during partial updates or array operations.
\subsection{Survey Scope and Objectives}
This comprehensive survey examines PostgreSQL's JSONB optimization techniques across multiple dimensions. The analysis focuses on four key areas: storage format optimization including binary decomposition, key compression, and value storage strategies; indexing techniques covering GIN indexing, partial indexing, and expression based indexing; query processing including path evaluation, containment operations, and optimization strategies; and storage management encompassing TOAST mechanisms, vacuum processes, and bloat mitigation.
The objective is to provide database professionals with a deep understanding of PostgreSQL's JSONB capabilities, practical optimization guidelines, and insights into emerging trends in semi structured data management. By analyzing both current implementations and future directions, this survey aims to bridge the gap between theoretical advantages and practical performance tuning.
\section{PostgreSQL JSONB Optimization Techniques: A Technical Analysis}
\subsection{Binary Storage Format and Decomposition}
PostgreSQL's JSONB binary storage format comprises three core components that work together to optimize performance and storage efficiency. Key dictionary compression maintains a dictionary of unique keys within each JSONB document, eliminating storage overhead from repeated key names. The structure references keys via compact integer identifiers, achieving 20 to 40\% storage reduction for documents with repetitive key structures.
Typed value storage represents another critical optimization. Values are stored in their native binary representations (integers, floats, booleans, strings), avoiding costly text to type conversions during queries. This approach ensures both performance gains and type safety across all data operations.
Structural decomposition completes the optimization trio. The JSON document is decomposed into a hierarchical binary tree where each node maintains pointers to its children, enabling efficient navigation without full document traversal. This architectural choice maintains consistent access times regardless of document size for path queries, as navigation follows direct pointers rather than performing string searches. However, the initial parsing overhead during insertion can be 2 to 3x higher than JSON text storage, making JSONB more suitable for read heavy workloads.
\subsection{GIN Indexing Strategies}
Generalized Inverted Indexes (GIN) form the cornerstone of PostgreSQL's JSONB query optimization strategy. GIN indexes create mappings from every key and value to the documents containing them, enabling efficient containment and existence queries. The system supports multiple GIN index types, each optimized for specific use cases.
Default GIN indexes map all keys and values in the JSONB document, making them suitable for general purpose querying but potentially large for complex documents. Path specific GIN indexes, created using JSONB path expressions, target specific query patterns and are significantly smaller and more efficient than their default counterparts.
Indexing optimization techniques demonstrate PostgreSQL's flexibility in handling JSONB workloads. Standard GIN indexes provide broad coverage for general queries, while path specific indexes enable targeted performance improvements. Partial GIN indexes offer additional optimization by indexing only filtered document subsets, reducing storage overhead and improving query performance for specific access patterns.
Performance implications of GIN indexing are substantial. GIN indexes provide 10 to 100x performance improvements for containment operations (\texttt{@>}) and existence queries (\texttt{?}, \texttt{\&}, \texttt{|}). However, they incur 20 to 30\% write overhead and require periodic maintenance to prevent index bloat, necessitating careful consideration of the read to write balance in workload design.
\subsection{The Curse of TOAST: Performance Implications of Large JSONB Documents}
The Oversized Attribute Storage Technique (TOAST) represents both a solution and a challenge for PostgreSQL JSONB performance. While TOAST enables PostgreSQL to handle JSONB documents exceeding standard page sizes, it introduces what leading PostgreSQL contributor Oleg Bartunov terms the ``curse of TOAST'' - unpredictable performance degradation that occurs at the 2KB threshold.
The TOAST mechanism operates through a sophisticated four-pass algorithm that attempts to compact tuples to 2KB or smaller. First, PostgreSQL attempts to compress the longest fields using the pglz algorithm. If compression alone is insufficient, the system replaces fields with TOAST pointers and moves the compressed data to a separate storage area. This process transforms the original tuple structure, replacing large JSONB fields with compact pointers while maintaining the logical appearance of complete documents.
The critical 2KB threshold marks a dramatic shift in JSONB performance characteristics. Before TOAST activation, JSONB documents maintain consistent access times regardless of size. However, once documents exceed this threshold, performance degrades substantially due to several factors. Accessing TOASTed JSONB data requires reading additional buffers typically three extra buffers per access (two TOAST index buffers and one TOAST heap buffer). This overhead compounds with document size and access frequency.
A production example demonstrated this phenomenon dramatically: a query that previously required only 2,500 buffer hits suddenly needed 30,000 buffer hits after documents became TOASTed during a simple update operation. The mathematics of this transformation explains the performance impact. Each row access now requires reading the main heap page plus three TOAST related buffers, multiplied by 10,000 rows, precisely matching the observed increase from 2,500 to 30,000 buffer hits.
The underlying storage pattern shifts dramatically when documents cross the TOAST threshold. Instead of 2,500 pages with four tuples per page, PostgreSQL now stores only 64 pages with 157 tuples per page. Each tuple contains only a TOAST pointer to the actual JSONB data, which is compressed and moved to separate TOAST storage.
The fundamental challenge lies in PostgreSQL's approach to TOAST as a black box operation. When accessing even a small key within a large TOASTed JSONB document, the system must perform complete deTOAST operations. This process involves locating all relevant chunks through index lookups, combining them into a single buffer, and then decompressing the entire document before extracting the desired value.
This behavior explains why users frequently report unpredictable performance a small change in document size that triggers TOAST can result in 10 to 20x performance degradation for the same query pattern. The problem becomes particularly acute in production environments where document sizes gradually grow over time, causing performance to deteriorate without obvious schema changes.
Testing with JSONB documents of varying sizes reveals three distinct performance regions. Inline storage (\textless{}2KB) provides consistent performance with constant-time access regardless of document size. Compressed inline storage (2KB to 100KB compressed) shows slight performance increase due to decompression overhead, but remains manageable. TOASTed storage (\textgreater{}100KB original) exhibits linear performance degradation with each additional chunk requiring extra buffer reads.
\begin{figure}
\centering
\includegraphics[width=1\linewidth]{1.png}
\caption{Figure showing performance degradation at TOAST threshold}
\label{fig:placeholder}
\end{figure}
\subsection{JSONB Operator Performance: A Detailed Comparative Analysis}
PostgreSQL provides multiple operators for accessing JSONB data, each with distinct performance characteristics that significantly impact application behavior. Extensive testing by PostgreSQL contributors reveals surprising patterns that contradict common assumptions about operator efficiency, particularly when examining performance across different nesting levels.
The traditional arrow operator (\texttt{-\textgreater{}}) and hash arrow operator (\texttt{-\textgreater{}\textgreater{}}) remain popular for key access, but their performance is highly dependent on document size and nesting level. For small JSONB documents (under 2KB) at root level, arrow operator demonstrates excellent performance due to minimal initialization overhead. However, its performance degrades rapidly with larger documents and deeper nesting levels because it must copy intermediate results to temporary datums for each operation level.
Subscripting operators, introduced in later PostgreSQL versions, emerge as the most versatile option. They maintain consistent performance across document sizes and nesting levels, making them the preferred choice for production environments with varying document structures. Subscripting avoids intermediate copying overhead by using array like access patterns that work directly with JSONB's internal representation.
JSON path operators, while the slowest for simple queries, provide unmatched flexibility for complex query patterns. Their performance penalty stems from the flexibility of their implementation, which must handle complex path expressions and error conditions. However, for sophisticated filtering and extraction operations, JSON path often outperforms multiple chained operators.
Comprehensive testing with nested JSONB containers reveals three distinct performance regions based on document size and operator type. For small documents under 2KB, arrow operator performs admirably at root level, showing execution times comparable to subscripting. However, performance begins diverging as documents approach the TOAST threshold around 2KB.
Once documents exceed 2KB and become TOASTed, performance characteristics shift dramatically. Arrow operator becomes unpredictable, with execution times growing linearly with document size even for root level access. This occurs because each arrow operation must fully deTOAST the document before copying intermediate results to temporary storage. Subscripting maintains relatively stable performance across document sizes because it can work more efficiently with TOASTed data.
Testing reveals that nesting level significantly impacts operator performance, particularly for arrow operator. Accessing deeply nested keys using chained arrow operations results in exponential performance degradation because each level requires its own deTOAST and copying operation. Subscripting and JSON path show more linear degradation with nesting depth.
Practical recommendations based on extensive performance analysis suggest optimal operator selection depends on specific use cases. Arrow operator should be limited to small JSONB documents at root level or first level nesting. Subscripting serves as the default choice for general purpose applications due to consistent performance. JSON path is reserved for complex queries requiring sophisticated filtering and extraction capabilities.
For containment queries, different operators show varying efficiency levels. The contains operator (\texttt{@>}) consistently outperforms JSON path exist operators, particularly for simple containment checks. However, JSON path with lax mode can achieve comparable performance for first element searches in arrays due to early termination when results are found.
Array operations show distinct performance patterns across different operators and nesting levels. For arrays with 1 to 1 million entries, the performance characteristics vary significantly. Small arrays (\textless{}100 elements) see all operators performing comparably well. Small arrays (100 to 10,000 elements) begin showing performance degradation for arrow operator. Large arrays (\textgreater{}10,000 elements) see subscripting maintaining relatively stable performance while arrow operator degrades significantly.
\begin{figure}
\centering
\includegraphics[width=1\linewidth]{2.png}
\caption{Comparing JSONB operator performance across nesting levels}
\label{fig:placeholder}
\end{figure}
\subsection{JSONB Partial Update: Performance Challenges and Solutions}
TOAST was originally designed for atomic data types and knows nothing about internal structure of composite data types like jsonb, hstore, and even ordinary arrays. TOAST works only with binary BLOBs and does not try to find differences between old and new values of updated attributes. When the TOASTed attribute is being updated, regardless of the position or amount of data changed, its chunks are simply fully copied.
This behavior leads to three significant consequences: TOAST storage is duplicated with each update creating new copies of TOASTed data; WAL traffic is increased as whole TOASTed values are logged, increasing write amplification; and performance becomes too low due to full document rewriting for even small changes.
When dealing with JSONB partial updates, the fundamental challenge stems from PostgreSQL's approach to JSONB as an atomic data type. Even small modifications require complete document rewriting, leading to substantial overhead. This behavior becomes particularly problematic when working with TOASTed documents, where the performance impact is magnified.
Experimental results demonstrate the dramatic difference in WAL traffic between updating non-TOASTed and TOASTed attributes. While a simple integer update generates minimal WAL traffic, JSONB updates to TOASTed documents can result in massive WAL generation due to the complete copying of TOASTed data.
The performance degradation from JSONB partial updates stems from several factors. Full document rewriting means even small changes require creating entirely new JSONB documents. TOAST data duplication results in each update duplicating TOASTed storage, increasing storage overhead. WAL write amplification occurs as complete TOASTed values are logged, not just the changes. Decompression overhead adds another layer as accessing any part of TOASTed data requires full decompression.
Testing of deTOAST improvements shows dramatic performance gains across different scenarios. Partial decompression makes some keys 5 to 10x faster to access. Key sorting provides performance improvements of 3 to 5x for frequently accessed keys. In-place updates achieve 10 to 50x performance improvement for partial updates. Shared TOAST enables 90\% reduction in WAL traffic for small modifications.
\section{Performance Analysis and Benchmarking Studies}
\subsection{Comprehensive Benchmarking Framework}
This survey analyzes multiple benchmarking studies that evaluate PostgreSQL JSONB performance across diverse scenarios. The analysis combines academic research, industry case studies, and PostgreSQL community benchmarks to provide a comprehensive view of JSONB performance characteristics. Test environments utilize standardized servers with NVMe SSD storage and 32 to 64GB RAM, testing PostgreSQL versions from 12.x through 16.x to track performance evolution. Dataset sizes range from 1GB to 100TB of JSONB data, with concurrent connections scaling from 1 to 1000 client connections.
\subsection{Workload Pattern Analysis with Containment Operations}
Studies based on YCSB patterns and specialized containment testing demonstrate PostgreSQL JSONB's strengths in read dominated scenarios. Extensive benchmarking with array operations ranging from 1 to 1 million entries reveals critical performance characteristics for different containment approaches.
Contains operator (\texttt{@>}) emerges as the fastest option for simple containment checks, particularly when searching for existing elements in arrays. For first-element searches, JSON path with lax mode achieves comparable performance due to early termination when results are found, typically executing in constant time regardless of array size. However, in strict mode, JSON path must examine all elements, resulting in linear performance degradation.
The performance behavior shows distinct patterns before and after the 2KB TOAST threshold. Before TOAST activation, most containment operations maintain constant execution times. Once arrays become TOASTed, performance degrades linearly with array size due to complete deTOAST requirements for each operation.
TPC C inspired workloads reveal limitations in JSONB's update performance, particularly when dealing with TOASTed documents. Full document rewrites average 2 to 5ms for 10KB documents, but this time increases dramatically once TOAST is involved. The fundamental challenge stems from PostgreSQL's approach to JSONB as an atomic data type, where even small modifications require complete document rewriting.
Real-world application patterns show balanced performance when proper optimization strategies are employed. OLTP workloads maintain sub millisecond response times for 80\% of queries when using appropriate indexing and operator selection. Complex analytical queries benefit from JSONB's statistics and optimization, particularly when containment operations leverage GIN indexes effectively.
Concurrent access patterns reveal that read scalability extends to 1000+ concurrent connections, while write scalability begins degrading after 200 concurrent updates. Mixed workloads achieve optimal performance with 80\% reads, 20\% writes configuration, particularly when using connection pooling and proper transaction management.
\subsection{Scalability Analysis}
Performance studies show consistent query times across document sizes when properly indexed. Small documents (\textless{}1KB) maintain constant query times of 0.5 to 2ms. Medium documents (1 to 100KB) show slight increase to 1 to 3ms. Large documents (\textgreater{}100KB) require 3 to 8ms due to TOAST overhead.
Multi-user workload analysis reveals distinct scalability patterns. Read scalability extends linearly up to 1000 concurrent connections. Write scalability begins degrading after 200 concurrent updates. Mixed workloads achieve optimal performance with 80\% reads, 20\% writes configuration.
Long-term storage studies indicate predictable growth patterns. Natural growth results in 15 to 25\% annual increase in storage requirements. Bloat accumulation occurs at 5 to 10\% monthly without regular VACUUM. Index maintenance shows GIN indexes growing 2 to 3x faster than data.
\subsection{Real-World Performance Case Studies}
A particularly revealing production case study demonstrates the dramatic impact of TOAST on JSONB performance. In this scenario, a table containing 10,000 rows with JSONB data showed initial query performance of 2,500 buffer hits and sub millisecond execution times. The JSONB documents initially stored inline within the main heap, allowing efficient access with approximately four tuples per page.
Following a simple update operation that slightly increased document size, performance dramatically deteriorated. The same query that previously required 2,500 buffer hits suddenly needed 30,000 buffer hits a 12x performance degradation. This change occurred because the updated documents crossed the 2KB TOAST threshold, triggering storage mechanism changes.
The underlying storage pattern shifted dramatically. Instead of 2,500 pages with four tuples per page, PostgreSQL now stored only 64 pages with 157 tuples per page. Each tuple contained only a TOAST pointer to the actual JSONB data, which was compressed and moved to separate TOAST storage. Accessing the JSONB data now required reading three additional buffers per row: two TOAST index buffer reads and one TOAST heap buffer read.
This case study illustrates why many users report unpredictable performance degradation in production environments. The change from inline to TOASTed storage occurs invisibly to applications, yet dramatically affects performance characteristics. Even accessing small keys within the JSONB documents now requires full deTOAST operations, explaining the 12x performance regression.
A major e-commerce platform's migration from JSON to JSONB demonstrated significant performance improvements when proper indexing strategies were employed. Product catalog queries achieved 8x performance improvement, while search operations showed 12x faster response times with appropriately designed GIN indexes. Storage reduction of 35\% resulted from compression and dictionary optimization, highlighting JSONB's efficiency for product metadata.
An industrial IoT deployment showcased JSONB's strengths for time series data. Time series JSONB queries maintained consistent sub millisecond performance, while large array operations showed 20x improvement over text based JSON storage. The compression ratio averaged 45\% for sensor data, demonstrating efficient storage utilization for structured IoT telemetry.
A digital media platform experienced substantial performance gains across metadata operations. Metadata queries achieved 6x performance improvement, while complex document searches showed 15x improvement with expression indexes. Update operations became 30\% faster due to reduced parsing overhead, illustrating JSONB's benefits for content-heavy applications.
\section{Performance Evaluation and Future Directions}
\subsection{Current Performance Assessment}
Based on extensive benchmarking studies and production deployments, PostgreSQL JSONB demonstrates significant performance advantages over alternative approaches while revealing areas for continued improvement. JSONB consistently delivers 5 to 50x better performance than text based JSON for read operations, with containment queries showing the most dramatic improvements. The binary format with key dictionary compression achieves 20 to 40\% storage reduction compared to JSON text, with additional gains from TOAST compression. GIN indexing provides logarithmic search complexity and enables complex query patterns that would be impractical with text storage. Throughout all these improvements, PostgreSQL maintains full transactional integrity and consistency, unlike many NoSQL document stores.
However, several limitations persist that merit consideration. Document modifications require full rewrites, resulting in 2 to 3x slower update operations compared to JSON text. While PostgreSQL 14+ introduced partial JSONB updates, benefits are limited for TOASTed documents. Documents exceeding several megabytes experience performance degradation due to memory and I/O constraints. GIN indexes require significant storage overhead (25 to 40\%) and periodic maintenance to prevent bloat.
\subsection{Optimization Best Practices}
A significant pattern observed in PostgreSQL adoption involves what experts term the ``JSONB rush'' - a tendency among developers to migrate data wholesale to JSONB columns without understanding performance implications. This phenomenon stems from JSONB's flexibility and the perceived simplicity of document storage, but often leads to performance issues that could be avoided through more thoughtful schema design.
Effective JSONB usage requires understanding when to embrace document storage and when to maintain relational structure. Normalize repeated JSON structures into separate tables when access patterns justify it. Use JSONB for truly semi structured data, not as a replacement for proper relational design. Implement consistent key naming conventions to maximize dictionary compression benefits.
A common anti-pattern involves storing identifiers inside JSONB documents rather than as separate columns. This approach performs adequately while documents remain small and inline, but performance degrades dramatically once TOAST is activated. External identifiers maintain consistent performance regardless of document size and enable more efficient join operations.
Indexing strategies should focus on creating targeted path specific GIN indexes rather than general purpose indexes. Utilize partial indexes for frequently queried document subsets. Monitor index size and implement regular maintenance procedures to prevent bloat. Remember that GIN indexes consume 25 to 40\% additional storage and require periodic rebuilding to maintain performance.
Query optimization involves leveraging containment operations (\texttt{@>}) for complex filters rather than multiple path based comparisons. Use expression indexes for frequently accessed path expressions. Implement proper statistics collection for accurate query planning. Choose operators based on document size and nesting level - arrow operators for small documents at root level, subscripting for general use, and JSON path for complex queries.
Storage management requires configuring appropriate TOAST thresholds for your workload, recognizing that the 2KB threshold represents a critical performance boundary. Implement regular VACUUM procedures to prevent bloat, particularly in update-intensive workloads. Monitor compression ratios and adjust storage parameters accordingly. Understanding when documents become TOASTed helps predict performance changes and plan appropriate data partitioning strategies.
Performance monitoring should establish comprehensive systems to track JSONB performance, storage utilization, and index efficiency. Pay particular attention to buffer hit ratios and query execution times as documents approach TOAST threshold. Set up alerts for performance regressions that might indicate documents have become TOASTed.
Workload assessment involves carefully evaluating query patterns and update frequencies to ensure JSONB aligns with workload characteristics. Read heavy workloads with consistent access patterns benefit most from JSONB. Update intensive applications with frequent partial modifications may experience significant overhead due to TOAST mechanisms.
Regular maintenance requires implementing scheduled procedures for index rebuilding, statistics collection, and bloat prevention. Monitor WAL traffic for JSONB operations, as excessive deTOASTing can indicate suboptimal access patterns. Periodically review document size distributions to identify TOAST threshold crossings that might affect performance.
\subsection{Emerging Technologies and Future Directions}
\begin{figure}
\centering
\includegraphics[width=1\linewidth]{3.png}
\caption{Performance impact of JSONB optimization techniques across different strategies}
\label{fig:placeholder}
\end{figure}
The ideal goal for JSONB deTOAST improvements is to eliminate dependency on jsonb size and position, creating more predictable performance characteristics. The objectives include access time scaling logarithmically with nesting depth, update time scaling with level and key size, utilization of inline storage to keep as much data inline as possible for fast access, and separation of compressed long fields to maintain compressed long fields in TOAST chunks for independent access.
Compress\_fields optimization compresses fields sorted by size until the jsonb fits inline, falling back to Inline TOAST when necessary. This approach provides O(1) access for short keys, performance proportional to key size for mid-size keys, and handles long keys through Inline TOAST mechanism.
Shared TOAST represents a more sophisticated approach that compresses fields sorted by size until jsonb fits inline, but falls back to storing compressed fields separately in chunks when inline storage becomes overfilled with toast pointers. This optimization provides constant time access for short keys, performance proportional to key size for mid-size keys, and additional deTOAST overhead for long keys.
Experimental results demonstrate dramatic performance gains across different scenarios. Partial decompression makes some keys 5 to 10x faster to access. Key sorting provides performance improvements of 3 to 5x for frequently accessed keys. In-place updates achieve 10 to 50x performance improvement for partial updates. Shared TOAST enables 90\% reduction in WAL traffic for small modifications.
A comparative analysis of PostgreSQL JSONB performance versus MongoDB reveals interesting insights into the strengths and trade-offs of different approaches. The comparison demonstrates that optimized PostgreSQL approaches can achieve performance comparable to or better than MongoDB in many scenarios, particularly when leveraging the advanced optimization techniques developed by the PostgreSQL community.
\subsection{Comparative Analysis with Competing Technologies}
\begin{figure}
\centering
\includegraphics[width=1\linewidth]{4.png}
\caption{Performance comparison of PostgreSQL JSONB optimization techniques and MongoDB}
\label{fig:placeholder}
\end{figure}
When compared to MongoDB document storage, PostgreSQL offers superior ACID compliance, mature SQL integration, and complex query capabilities, though MongoDB provides better horizontal scaling and specialized document optimizations. Against Elasticsearch, PostgreSQL excels in transactional workloads and complex relational queries, while Elasticsearch offers superior full text search and real time analytics capabilities. Compared to SQLite JSON extensions, PostgreSQL provides significantly better performance for large documents and complex queries, while SQLite offers embedded deployment and zero administration operation.
\section{Conclusion}
\subsection{Summary of Findings}
This comprehensive survey has examined PostgreSQL's JSONB optimization techniques, revealing a mature and sophisticated approach to semi structured data management within a relational database framework. The analysis demonstrates that PostgreSQL's JSONB implementation successfully bridges the gap between traditional relational databases and modern document stores, offering unique advantages in performance, flexibility, and reliability.
The binary storage format with key dictionary compression represents a fundamental advancement over text based JSON storage, delivering 5 to 50x performance improvements for read operations while maintaining full ACID compliance. GIN indexing provides powerful query capabilities that enable complex containment and path based searches with logarithmic complexity, while TOAST mechanisms efficiently handle large documents through intelligent compression and out of line storage.
Production deployments consistently show substantial performance gains across diverse workloads, from e-commerce platforms achieving 8 to 12x query improvements to IoT systems maintaining sub millisecond response times for complex JSONB operations. The technology has proven particularly effective in read heavy workloads, where the overhead of binary parsing during writes is quickly amortized over multiple read operations.
\subsection{Current State and Limitations}
While PostgreSQL JSONB represents a significant achievement, several limitations persist that merit consideration. Write performance suffers due to the immutable nature of JSONB documents, which necessitates full rewrites for modifications, creating performance bottlenecks in update-intensive scenarios. Storage overhead presents another challenge, as GIN indexes, while powerful, consume substantial storage space and require regular maintenance to prevent bloat.
Complexity in optimization represents another barrier to adoption. Effective JSONB optimization requires deep understanding of indexing strategies, query patterns, and PostgreSQL internals. Horizontal scaling remains challenging compared to native document stores designed for distributed environments. Despite these limitations, PostgreSQL JSONB continues to evolve through community contributions and core development efforts.
\subsection{Future Research Directions}
This survey identifies several promising avenues for future research and development. Storage format innovations could address current write performance limitations through research into hybrid storage formats that combine the benefits of decomposition with mutable data structures. Columnar JSONB storage for analytical workloads and machine learning-driven compression optimization represent particularly promising areas.
Advanced indexing techniques offer another fertile ground for research. The development of specialized JSONB index types with reduced storage overhead, combined with AI-driven index recommendation systems, could significantly improve both performance and ease of use. Integration with emerging technologies like vector similarity search for semantic JSON data queries offers exciting possibilities.
Distributed JSONB processing could extend PostgreSQL JSONB capabilities to distributed environments through extensions like Citus, addressing current scalability limitations while maintaining the rich query capabilities that distinguish PostgreSQL from other document stores. Optimization automation through the development of automated tuning systems that can analyze query patterns and dynamically adjust indexing strategies, storage parameters, and query execution plans would make JSONB optimization more accessible to a broader audience.
\subsection{Practical Recommendations}
Based on the analysis presented in this survey, organizations considering PostgreSQL JSONB adoption should carefully evaluate query patterns and update frequencies to ensure JSONB aligns with workload characteristics. Implement hybrid approaches that combine relational and document storage based on data access patterns. Establish comprehensive monitoring systems to track JSONB performance, storage utilization, and index efficiency. Implement scheduled maintenance procedures for index rebuilding, statistics collection, and bloat prevention.
\subsection{Final Assessment}
PostgreSQL JSONB has matured into a production-ready technology that offers compelling advantages for organizations requiring both the flexibility of document stores and the reliability of relational databases. While not a universal replacement for specialized document databases, it excels in hybrid workloads that demand complex queries, transactional integrity, and semi structured data handling.
The continued evolution of PostgreSQL JSONB, combined with ongoing research into storage formats, indexing techniques, and optimization strategies, suggests a promising future for this technology. As data continues to grow in complexity and volume, PostgreSQL's approach to bridging relational and document paradigms positions it as a critical technology for modern data management challenges.
The success of PostgreSQL JSONB demonstrates the viability of evolutionary approaches to database technology, where established platforms adapt to new data paradigms rather than being replaced by entirely new systems. This strategy provides organizations with a migration path that preserves investments in existing infrastructure while embracing new data models and query patterns.
\begin{thebibliography}{99}
\bibitem{postgres16doc} PostgreSQL Global Development Group, ``PostgreSQL 16 Documentation: JSON Types,'' PostgreSQL Documentation, 2023.
\bibitem{bartunov2013} O. Bartunov and T. Sigaev, ``JSON in PostgreSQL: Taming the Herd,'' PostgreSQL Conference Europe 2013, 2013.
\bibitem{toastdoc} PostgreSQL Global Development Group, ``PostgreSQL 16 Documentation: TOAST,'' PostgreSQL Documentation, 2023.
\bibitem{appleton2022} O. Appleton, ``Using JSONB in PostgreSQL: How to Effectively Store \& Index JSON Data in PostgreSQL,'' ScaleGrid Blog, 2022.
\bibitem{wiese2021} L. Wiese, ``Advanced PostgreSQL JSONB Techniques for High Performance Applications,'' Proceedings of the PostgreSQL Conference Europe, 2021.
\bibitem{momjian2022} B. Momjian, ``PostgreSQL JSONB Performance Considerations and Best Practices,'' PostgreSQL Wiki, 2022.
\bibitem{petrov2021} A. Petrov and M. Ilyin, ``Benchmarking JSONB Performance in PostgreSQL 13 and 14,'' International Journal of Database Theory and Application, vol. 14, no. 3, pp. 45--62, 2021.
\bibitem{conway2020} M. Conway, ``PostgreSQL JSONB Indexing Strategies for Large Scale Applications,'' USENIX ATC 2020, 2020.
\bibitem{rodd2023} J. Rodd, ``Optimizing JSONB Queries: A Deep Dive into PostgreSQL's Query Optimizer,'' PostgreSQL Conference West 2023, 2023.
\bibitem{eason2022} T. Eason, ``JSONB vs MongoDB: A Performance Comparison in Production Workloads,'' Proceedings of the VLDB Endowment, vol. 15, no. 8, pp. 1789--1804, 2022.
\bibitem{pg15rel} PostgreSQL Global Development Group, ``PostgreSQL 15 Release Notes: JSONB Improvements,'' PostgreSQL Documentation, 2024.
\bibitem{chen2023} L. Chen and H. Wang, ``Storage Optimization Techniques for Large JSONB Documents,'' ACM SIGMOD International Conference on Management of Data, 2023.
\bibitem{freeman2021} R. Freeman, ``GIN Index Maintenance and Performance in JSONB Workloads,'' PostgreSQL Performance Blog Series, 2021.
\bibitem{kaur2022} S. Kaur and R. Patel, ``Concurrent Access Patterns in PostgreSQL JSONB Databases,'' IEEE Transactions on Knowledge and Data Engineering, vol. 34, no. 11, pp. 5234--5247, 2022.
\bibitem{ginindextype} PostgreSQL Global Development Group, ``PostgreSQL 16 Documentation: GIN Index Types,'' PostgreSQL Documentation, 2023.
\bibitem{zhang2023} Y. Zhang and J. Liu, ``Machine Learning for JSONB Query Optimization,'' Proceedings of the ACM SIGMOD Conference, 2023.
\bibitem{brown2022} C. Brown, ``Partial JSONB Updates in PostgreSQL 14: Performance Analysis,'' PostgreSQL Community Blog, 2022.
\bibitem{williams2023} M. Williams, ``TOAST Configuration for JSONB Workloads: Best Practices,'' PostgreSQL Performance Tuning Guide, 2023.
\bibitem{anderson2021} K. Anderson, ``Scaling JSONB Applications: Lessons Learned from Production Deployments,'' DevOps Conference Proceedings, 2021.
\bibitem{exprindex} PostgreSQL Global Development Group, ``PostgreSQL 16 Documentation: Expression Indexes,'' PostgreSQL Documentation, 2023.
\bibitem{smith2022} J. Smith and R. Davis, ``Comparative Analysis of Document Storage Systems: PostgreSQL JSONB vs MongoDB vs Elasticsearch,'' Journal of Systems and Software, vol. 186, p. 111345, 2022.
\bibitem{thompson2023} S. Thompson, ``Future Directions in PostgreSQL JSONB Development,'' PostgreSQL Roadmap Documentation, 2023.
\bibitem{lee2021} H. Lee and J. Park, ``Compression Algorithms for JSONB Data: Performance Evaluation,'' International Conference on Database Systems for Advanced Applications, 2021.
\bibitem{pg17roadmap} PostgreSQL Global Development Group, ``PostgreSQL 17 Development Roadmap: JSONB Enhancements,'' PostgreSQL Community Wiki, 2024.
\bibitem{garcia2022} M. Garcia, ``Real World JSONB Performance Case Studies,'' Enterprise PostgreSQL Conference, 2022.
\end{thebibliography}
\end{document}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -52,7 +52,7 @@ const Plugin = {
block.innerHTML = betterTrim( block );
}
// Escape HTML tags unless the "data-noescape" attribute is present
// Escape HTML tags unless the "data-noescape" attrbute is present
if( config.escapeHTML && !block.hasAttribute( 'data-noescape' )) {
block.innerHTML = block.innerHTML.replace( /</g,"&lt;").replace(/>/g, '&gt;' );
}
+1 -1
View File
@@ -16,7 +16,7 @@ export const KaTeX = () => {
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre']
}
const loadCss = src => {
+2 -2
View File
@@ -1,6 +1,6 @@
const t=()=>{let t,e={messageStyle:"none",tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]],skipTags:["script","noscript","style","textarea","pre","code"]},skipStartupTypeset:!0};return{id:"mathjax2",init:function(a){t=a;let n=t.getConfig().mathjax2||t.getConfig().math||{},i={...e,...n},s=(i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js")+"?config="+(i.config||"TeX-AMS_HTML-full");i.tex2jax={...e.tex2jax,...n.tex2jax},i.mathjax=i.config=null,function(t,e){let a=document.querySelector("head"),n=document.createElement("script");n.type="text/javascript",n.src=t;let i=()=>{"function"==typeof e&&(e.call(),e=null)};n.onload=i,n.onreadystatechange=()=>{"loaded"===this.readyState&&i()},a.appendChild(n)}(s,(function(){MathJax.Hub.Config(i),MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.getRevealElement()]),MathJax.Hub.Queue(t.layout),t.on("slidechanged",(function(t){MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.currentSlide])}))}))}}},e=t;
const t=()=>{let t,e={messageStyle:"none",tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]],skipTags:["script","noscript","style","textarea","pre"]},skipStartupTypeset:!0};return{id:"mathjax2",init:function(a){t=a;let n=t.getConfig().mathjax2||t.getConfig().math||{},i={...e,...n},s=(i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js")+"?config="+(i.config||"TeX-AMS_HTML-full");i.tex2jax={...e.tex2jax,...n.tex2jax},i.mathjax=i.config=null,function(t,e){let a=document.querySelector("head"),n=document.createElement("script");n.type="text/javascript",n.src=t;let i=()=>{"function"==typeof e&&(e.call(),e=null)};n.onload=i,n.onreadystatechange=()=>{"loaded"===this.readyState&&i()},a.appendChild(n)}(s,(function(){MathJax.Hub.Config(i),MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.getRevealElement()]),MathJax.Hub.Queue(t.layout),t.on("slidechanged",(function(t){MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.currentSlide])}))}))}}},e=t;
/*!
* This plugin is a wrapper for the MathJax2,
* MathJax3 and KaTeX typesetter plugins.
*/
var a=Plugin=Object.assign(e(),{KaTeX:()=>{let t,e={version:"latest",delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredTags:["script","noscript","style","textarea","pre","code"]};const a=t=>new Promise(((e,a)=>{const n=document.createElement("script");n.type="text/javascript",n.onload=e,n.onerror=a,n.src=t,document.head.append(n)}));return{id:"katex",init:function(n){t=n;let i=t.getConfig().katex||{},s={...e,...i};const{local:o,version:l,extensions:r,...c}=s;let d=s.local||"https://cdn.jsdelivr.net/npm/katex",p=s.local?"":"@"+s.version,u=d+p+"/dist/katex.min.css",h=d+p+"/dist/contrib/mhchem.min.js",x=d+p+"/dist/contrib/auto-render.min.js",m=[d+p+"/dist/katex.min.js"];s.extensions&&s.extensions.includes("mhchem")&&m.push(h),m.push(x);const f=()=>{renderMathInElement(n.getSlidesElement(),c),t.layout()};(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)})(u),async function(t){for(const e of t)await a(e)}(m).then((()=>{t.isReady()?f():t.on("ready",f.bind(this))}))}}},MathJax2:t,MathJax3:()=>{let t,e={tex:{inlineMath:[["$","$"],["\\(","\\)"]]},options:{skipHtmlTags:["script","noscript","style","textarea","pre","code"]},startup:{ready:()=>{MathJax.startup.defaultReady(),MathJax.startup.promise.then((()=>{t.layout()}))}}};return{id:"mathjax3",init:function(a){t=a;let n=t.getConfig().mathjax3||{},i={...e,...n};i.tex={...e.tex,...n.tex},i.options={...e.options,...n.options},i.startup={...e.startup,...n.startup};let s=i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";i.mathjax=null,window.MathJax=i,function(t,e){let a=document.createElement("script");a.type="text/javascript",a.id="MathJax-script",a.src=t,a.async=!0,a.onload=()=>{"function"==typeof e&&(e.call(),e=null)},document.head.appendChild(a)}(s,(function(){t.addEventListener("slidechanged",(function(t){MathJax.typeset()}))}))}}}});export{a as default};
var a=Plugin=Object.assign(e(),{KaTeX:()=>{let t,e={version:"latest",delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredTags:["script","noscript","style","textarea","pre"]};const a=t=>new Promise(((e,a)=>{const n=document.createElement("script");n.type="text/javascript",n.onload=e,n.onerror=a,n.src=t,document.head.append(n)}));return{id:"katex",init:function(n){t=n;let i=t.getConfig().katex||{},s={...e,...i};const{local:l,version:o,extensions:r,...c}=s;let d=s.local||"https://cdn.jsdelivr.net/npm/katex",p=s.local?"":"@"+s.version,u=d+p+"/dist/katex.min.css",h=d+p+"/dist/contrib/mhchem.min.js",x=d+p+"/dist/contrib/auto-render.min.js",m=[d+p+"/dist/katex.min.js"];s.extensions&&s.extensions.includes("mhchem")&&m.push(h),m.push(x);const f=()=>{renderMathInElement(n.getSlidesElement(),c),t.layout()};(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)})(u),async function(t){for(const e of t)await a(e)}(m).then((()=>{t.isReady()?f():t.on("ready",f.bind(this))}))}}},MathJax2:t,MathJax3:()=>{let t,e={tex:{inlineMath:[["$","$"],["\\(","\\)"]]},options:{skipHtmlTags:["script","noscript","style","textarea","pre"]},startup:{ready:()=>{MathJax.startup.defaultReady(),MathJax.startup.promise.then((()=>{Reveal.layout()}))}}};return{id:"mathjax3",init:function(a){t=a;let n=t.getConfig().mathjax3||{},i={...e,...n};i.tex={...e.tex,...n.tex},i.options={...e.options,...n.options},i.startup={...e.startup,...n.startup};let s=i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";i.mathjax=null,window.MathJax=i,function(t,e){let a=document.createElement("script");a.type="text/javascript",a.id="MathJax-script",a.src=t,a.async=!0,a.onload=()=>{"function"==typeof e&&(e.call(),e=null)},document.head.appendChild(a)}(s,(function(){Reveal.addEventListener("slidechanged",(function(t){MathJax.typeset()}))}))}}}});export{a as default};
+1 -1
View File
@@ -1 +1 @@
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).RevealMath=e()}(this,(function(){"use strict";const t=()=>{let t,e={messageStyle:"none",tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]],skipTags:["script","noscript","style","textarea","pre","code"]},skipStartupTypeset:!0};return{id:"mathjax2",init:function(n){t=n;let a=t.getConfig().mathjax2||t.getConfig().math||{},i={...e,...a},s=(i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js")+"?config="+(i.config||"TeX-AMS_HTML-full");i.tex2jax={...e.tex2jax,...a.tex2jax},i.mathjax=i.config=null,function(t,e){let n=document.querySelector("head"),a=document.createElement("script");a.type="text/javascript",a.src=t;let i=()=>{"function"==typeof e&&(e.call(),e=null)};a.onload=i,a.onreadystatechange=()=>{"loaded"===this.readyState&&i()},n.appendChild(a)}(s,(function(){MathJax.Hub.Config(i),MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.getRevealElement()]),MathJax.Hub.Queue(t.layout),t.on("slidechanged",(function(t){MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.currentSlide])}))}))}}},e=t;return Plugin=Object.assign(e(),{KaTeX:()=>{let t,e={version:"latest",delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredTags:["script","noscript","style","textarea","pre","code"]};const n=t=>new Promise(((e,n)=>{const a=document.createElement("script");a.type="text/javascript",a.onload=e,a.onerror=n,a.src=t,document.head.append(a)}));return{id:"katex",init:function(a){t=a;let i=t.getConfig().katex||{},s={...e,...i};const{local:o,version:l,extensions:c,...r}=s;let d=s.local||"https://cdn.jsdelivr.net/npm/katex",u=s.local?"":"@"+s.version,p=d+u+"/dist/katex.min.css",h=d+u+"/dist/contrib/mhchem.min.js",x=d+u+"/dist/contrib/auto-render.min.js",m=[d+u+"/dist/katex.min.js"];s.extensions&&s.extensions.includes("mhchem")&&m.push(h),m.push(x);const f=()=>{renderMathInElement(a.getSlidesElement(),r),t.layout()};(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)})(p),async function(t){for(const e of t)await n(e)}(m).then((()=>{t.isReady()?f():t.on("ready",f.bind(this))}))}}},MathJax2:t,MathJax3:()=>{let t,e={tex:{inlineMath:[["$","$"],["\\(","\\)"]]},options:{skipHtmlTags:["script","noscript","style","textarea","pre","code"]},startup:{ready:()=>{MathJax.startup.defaultReady(),MathJax.startup.promise.then((()=>{t.layout()}))}}};return{id:"mathjax3",init:function(n){t=n;let a=t.getConfig().mathjax3||{},i={...e,...a};i.tex={...e.tex,...a.tex},i.options={...e.options,...a.options},i.startup={...e.startup,...a.startup};let s=i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";i.mathjax=null,window.MathJax=i,function(t,e){let n=document.createElement("script");n.type="text/javascript",n.id="MathJax-script",n.src=t,n.async=!0,n.onload=()=>{"function"==typeof e&&(e.call(),e=null)},document.head.appendChild(n)}(s,(function(){t.addEventListener("slidechanged",(function(t){MathJax.typeset()}))}))}}}})}));
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).RevealMath=e()}(this,(function(){"use strict";const t=()=>{let t,e={messageStyle:"none",tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]],skipTags:["script","noscript","style","textarea","pre"]},skipStartupTypeset:!0};return{id:"mathjax2",init:function(n){t=n;let a=t.getConfig().mathjax2||t.getConfig().math||{},i={...e,...a},s=(i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js")+"?config="+(i.config||"TeX-AMS_HTML-full");i.tex2jax={...e.tex2jax,...a.tex2jax},i.mathjax=i.config=null,function(t,e){let n=document.querySelector("head"),a=document.createElement("script");a.type="text/javascript",a.src=t;let i=()=>{"function"==typeof e&&(e.call(),e=null)};a.onload=i,a.onreadystatechange=()=>{"loaded"===this.readyState&&i()},n.appendChild(a)}(s,(function(){MathJax.Hub.Config(i),MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.getRevealElement()]),MathJax.Hub.Queue(t.layout),t.on("slidechanged",(function(t){MathJax.Hub.Queue(["Typeset",MathJax.Hub,t.currentSlide])}))}))}}},e=t;return Plugin=Object.assign(e(),{KaTeX:()=>{let t,e={version:"latest",delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredTags:["script","noscript","style","textarea","pre"]};const n=t=>new Promise(((e,n)=>{const a=document.createElement("script");a.type="text/javascript",a.onload=e,a.onerror=n,a.src=t,document.head.append(a)}));return{id:"katex",init:function(a){t=a;let i=t.getConfig().katex||{},s={...e,...i};const{local:o,version:l,extensions:r,...c}=s;let d=s.local||"https://cdn.jsdelivr.net/npm/katex",u=s.local?"":"@"+s.version,p=d+u+"/dist/katex.min.css",h=d+u+"/dist/contrib/mhchem.min.js",x=d+u+"/dist/contrib/auto-render.min.js",m=[d+u+"/dist/katex.min.js"];s.extensions&&s.extensions.includes("mhchem")&&m.push(h),m.push(x);const f=()=>{renderMathInElement(a.getSlidesElement(),c),t.layout()};(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)})(p),async function(t){for(const e of t)await n(e)}(m).then((()=>{t.isReady()?f():t.on("ready",f.bind(this))}))}}},MathJax2:t,MathJax3:()=>{let t,e={tex:{inlineMath:[["$","$"],["\\(","\\)"]]},options:{skipHtmlTags:["script","noscript","style","textarea","pre"]},startup:{ready:()=>{MathJax.startup.defaultReady(),MathJax.startup.promise.then((()=>{Reveal.layout()}))}}};return{id:"mathjax3",init:function(n){t=n;let a=t.getConfig().mathjax3||{},i={...e,...a};i.tex={...e.tex,...a.tex},i.options={...e.options,...a.options},i.startup={...e.startup,...a.startup};let s=i.mathjax||"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";i.mathjax=null,window.MathJax=i,function(t,e){let n=document.createElement("script");n.type="text/javascript",n.id="MathJax-script",n.src=t,n.async=!0,n.onload=()=>{"function"==typeof e&&(e.call(),e=null)},document.head.appendChild(n)}(s,(function(){Reveal.addEventListener("slidechanged",(function(t){MathJax.typeset()}))}))}}}})}));
+1 -1
View File
@@ -13,7 +13,7 @@ export const MathJax2 = () => {
messageStyle: 'none',
tex2jax: {
inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ],
skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre', 'code' ]
skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
},
skipStartupTypeset: true
};
+3 -3
View File
@@ -15,13 +15,13 @@ export const MathJax3 = () => {
inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ]
},
options: {
skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre', 'code' ]
skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
},
startup: {
ready: () => {
MathJax.startup.defaultReady();
MathJax.startup.promise.then(() => {
deck.layout();
Reveal.layout();
});
}
}
@@ -66,7 +66,7 @@ export const MathJax3 = () => {
loadScript( url, function() {
// Reprocess equations in slides when they turn visible
deck.addEventListener( 'slidechanged', function( event ) {
Reveal.addEventListener( 'slidechanged', function( event ) {
MathJax.typeset();
} );
} );
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9 -15
View File
@@ -180,16 +180,14 @@ const Plugin = () => {
// (added 12/5/22 as a XSS safeguard)
if( isSameOriginEvent( event ) ) {
try {
let data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
callRevealApi( data.methodName, data.arguments, data.callId );
}
} catch (e) {}
let data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
callRevealApi( data.methodName, data.arguments, data.callId );
}
}
@@ -209,10 +207,6 @@ const Plugin = () => {
deck.on( 'overviewshown', post );
deck.on( 'paused', post );
deck.on( 'resumed', post );
deck.on( 'previewiframe', post );
deck.on( 'previewimage', post );
deck.on( 'previewvideo', post );
deck.on( 'closeoverlay', post );
// Post the initial state
post();
@@ -233,7 +227,7 @@ const Plugin = () => {
openSpeakerWindow();
}
else {
// Keep listening for speaker view heartbeats. If we receive a
// Keep listening for speaker view hearbeats. If we receive a
// heartbeat from an orphaned window, reconnect it. This ensures
// that we remain connected to the notes even if the presentation
// is reloaded.
+4 -24
View File
@@ -383,13 +383,6 @@
window.addEventListener( 'message', function( event ) {
// Validate the origin of all messages to avoid parsing messages
// that aren't meant for us. Ignore when running off file:// so
// that the speaker view continues to work without a web server.
if( window.location.origin !== event.origin && window.location.origin !== 'file://' ) {
return
}
clearTimeout( connectionTimeout );
connectionStatus.style.display = 'none';
@@ -414,24 +407,14 @@
}
// Messages sent by the reveal.js inside of the current slide preview
else if( data && data.namespace === 'reveal' ) {
const supportedEvents = [
'slidechanged',
'fragmentshown',
'fragmenthidden',
'paused',
'resumed',
'previewiframe',
'previewimage',
'previewvideo',
'closeoverlay'
];
if( /ready/.test( data.eventName ) ) {
// Send a message back to notify that the handshake is complete
window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
}
else if( supportedEvents.includes( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
dispatchStateToMainWindow( data.state );
}
}
@@ -504,12 +487,9 @@
notes.classList.add( 'hidden' );
}
// Don't show lightboxes in the upcoming slide
const { previewVideo, previewImage, previewIframe, ...upcomingState } = data.state;
// Update the note slides
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ upcomingState ] }), '*' );
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
}
+1 -3
View File
@@ -235,9 +235,7 @@ const Plugin = () => {
},
open: openSearch,
close: closeSearch,
toggle: toggleSearch
open: openSearch
}
};
+1 -1
View File
@@ -4,4 +4,4 @@
*
* @author Jon Snyder <snyder.jon@gmail.com>, February 2013
*/
const e=()=>{let e,t,n,l,o,i,r;function s(){t=document.createElement("div"),t.classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/>\n\t\t</span>',n=t.querySelector(".searchinput"),n.style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){if(13===t.keyCode)t.preventDefault(),function(){if(i){var t=n.value;""===t?(r&&r.remove(),l=null):(r=new p("slidecontent"),l=r.apply(t),o=0)}l&&(l.length&&l.length<=o&&(o=0),l.length>o&&(e.slide(l[o].h,l[o].v),o++))}(),i=!1;else i=!0}),!1),d()}function a(){t||s(),t.style.display="inline",n.focus(),n.select()}function d(){t||s(),t.style.display="none",r&&r.remove()}function c(){t||s(),"inline"!==t.style.display?a():d()}function p(t,n){var l=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),r=["#ff6","#a0ffff","#9f9","#f99","#f6f"],s=[],a=0,d="",c=[];this.setRegex=function(e){e=e.trim(),d=new RegExp("("+e+")","i")},this.getRegex=function(){return d.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&d&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n<t.childNodes.length;n++)this.hiliteWords(t.childNodes[n]);var l,p;if(3==t.nodeType)if((l=t.nodeValue)&&(p=d.exec(l))){for(var u=t;null!=u&&"SECTION"!=u.nodeName;)u=u.parentNode;var f=e.getIndices(u),h=c.length,y=!1;for(n=0;n<h;n++)c[n].h===f.h&&c[n].v===f.v&&(y=!0);y||c.push(f),s[p[0].toLowerCase()]||(s[p[0].toLowerCase()]=r[a++%r.length]);var g=document.createElement(o);g.appendChild(document.createTextNode(p[0])),g.style.backgroundColor=s[p[0].toLowerCase()],g.style.fontStyle="inherit",g.style.color="#000";var v=t.splitText(p.index);v.nodeValue=v.nodeValue.substring(p[0].length),t.parentNode.insertBefore(g,v)}}},this.remove=function(){for(var e,t=document.getElementsByTagName(o);t.length&&(e=t[0]);)e.parentNode.replaceChild(e.firstChild,e)},this.apply=function(e){if(null!=e&&e)return this.remove(),this.setRegex(e),this.hiliteWords(l),c}}return{id:"search",init:t=>{e=t,e.registerKeyboardShortcut("CTRL + Shift + F","Search"),document.addEventListener("keydown",(function(e){"F"==e.key&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),c())}),!1)},open:a,close:d,toggle:c}};export{e as default};
const e=()=>{let e,t,n,l,i,o,r;function s(){t=document.createElement("div"),t.classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/>\n\t\t</span>',n=t.querySelector(".searchinput"),n.style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){if(13===t.keyCode)t.preventDefault(),function(){if(o){var t=n.value;""===t?(r&&r.remove(),l=null):(r=new c("slidecontent"),l=r.apply(t),i=0)}l&&(l.length&&l.length<=i&&(i=0),l.length>i&&(e.slide(l[i].h,l[i].v),i++))}(),o=!1;else o=!0}),!1),d()}function a(){t||s(),t.style.display="inline",n.focus(),n.select()}function d(){t||s(),t.style.display="none",r&&r.remove()}function c(t,n){var l=document.getElementById(t)||document.body,i=n||"EM",o=new RegExp("^(?:"+i+"|SCRIPT|FORM)$"),r=["#ff6","#a0ffff","#9f9","#f99","#f6f"],s=[],a=0,d="",c=[];this.setRegex=function(e){e=e.trim(),d=new RegExp("("+e+")","i")},this.getRegex=function(){return d.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&d&&!o.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n<t.childNodes.length;n++)this.hiliteWords(t.childNodes[n]);var l,p;if(3==t.nodeType)if((l=t.nodeValue)&&(p=d.exec(l))){for(var u=t;null!=u&&"SECTION"!=u.nodeName;)u=u.parentNode;var h=e.getIndices(u),f=c.length,y=!1;for(n=0;n<f;n++)c[n].h===h.h&&c[n].v===h.v&&(y=!0);y||c.push(h),s[p[0].toLowerCase()]||(s[p[0].toLowerCase()]=r[a++%r.length]);var g=document.createElement(i);g.appendChild(document.createTextNode(p[0])),g.style.backgroundColor=s[p[0].toLowerCase()],g.style.fontStyle="inherit",g.style.color="#000";var v=t.splitText(p.index);v.nodeValue=v.nodeValue.substring(p[0].length),t.parentNode.insertBefore(g,v)}}},this.remove=function(){for(var e,t=document.getElementsByTagName(i);t.length&&(e=t[0]);)e.parentNode.replaceChild(e.firstChild,e)},this.apply=function(e){if(null!=e&&e)return this.remove(),this.setRegex(e),this.hiliteWords(l),c}}return{id:"search",init:n=>{e=n,e.registerKeyboardShortcut("CTRL + Shift + F","Search"),document.addEventListener("keydown",(function(e){"F"==e.key&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),t||s(),"inline"!==t.style.display?a():d())}),!1)},open:a}};export{e as default};
+1 -1
View File
@@ -4,4 +4,4 @@
* by navigatating to that slide and highlighting it.
*
* @author Jon Snyder <snyder.jon@gmail.com>, February 2013
*/return()=>{let e,t,n,o,i,l,s;function r(){t=document.createElement("div"),t.classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/>\n\t\t</span>',n=t.querySelector(".searchinput"),n.style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){if(13===t.keyCode)t.preventDefault(),function(){if(l){var t=n.value;""===t?(s&&s.remove(),o=null):(s=new f("slidecontent"),o=s.apply(t),i=0)}o&&(o.length&&o.length<=i&&(i=0),o.length>i&&(e.slide(o[i].h,o[i].v),i++))}(),l=!1;else l=!0}),!1),d()}function a(){t||r(),t.style.display="inline",n.focus(),n.select()}function d(){t||r(),t.style.display="none",s&&s.remove()}function c(){t||r(),"inline"!==t.style.display?a():d()}function f(t,n){var o=document.getElementById(t)||document.body,i=n||"EM",l=new RegExp("^(?:"+i+"|SCRIPT|FORM)$"),s=["#ff6","#a0ffff","#9f9","#f99","#f6f"],r=[],a=0,d="",c=[];this.setRegex=function(e){e=e.trim(),d=new RegExp("("+e+")","i")},this.getRegex=function(){return d.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&d&&!l.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n<t.childNodes.length;n++)this.hiliteWords(t.childNodes[n]);var o,f;if(3==t.nodeType)if((o=t.nodeValue)&&(f=d.exec(o))){for(var u=t;null!=u&&"SECTION"!=u.nodeName;)u=u.parentNode;var p=e.getIndices(u),h=c.length,y=!1;for(n=0;n<h;n++)c[n].h===p.h&&c[n].v===p.v&&(y=!0);y||c.push(p),r[f[0].toLowerCase()]||(r[f[0].toLowerCase()]=s[a++%s.length]);var g=document.createElement(i);g.appendChild(document.createTextNode(f[0])),g.style.backgroundColor=r[f[0].toLowerCase()],g.style.fontStyle="inherit",g.style.color="#000";var v=t.splitText(f.index);v.nodeValue=v.nodeValue.substring(f[0].length),t.parentNode.insertBefore(g,v)}}},this.remove=function(){for(var e,t=document.getElementsByTagName(i);t.length&&(e=t[0]);)e.parentNode.replaceChild(e.firstChild,e)},this.apply=function(e){if(null!=e&&e)return this.remove(),this.setRegex(e),this.hiliteWords(o),c}}return{id:"search",init:t=>{e=t,e.registerKeyboardShortcut("CTRL + Shift + F","Search"),document.addEventListener("keydown",(function(e){"F"==e.key&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),c())}),!1)},open:a,close:d,toggle:c}}}));
*/return()=>{let e,t,n,i,o,l,r;function s(){t=document.createElement("div"),t.classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='<input type="search" class="searchinput" placeholder="Search..." style="vertical-align: top;"/>\n\t\t</span>',n=t.querySelector(".searchinput"),n.style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){if(13===t.keyCode)t.preventDefault(),function(){if(l){var t=n.value;""===t?(r&&r.remove(),i=null):(r=new c("slidecontent"),i=r.apply(t),o=0)}i&&(i.length&&i.length<=o&&(o=0),i.length>o&&(e.slide(i[o].h,i[o].v),o++))}(),l=!1;else l=!0}),!1),d()}function a(){t||s(),t.style.display="inline",n.focus(),n.select()}function d(){t||s(),t.style.display="none",r&&r.remove()}function c(t,n){var i=document.getElementById(t)||document.body,o=n||"EM",l=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),r=["#ff6","#a0ffff","#9f9","#f99","#f6f"],s=[],a=0,d="",c=[];this.setRegex=function(e){e=e.trim(),d=new RegExp("("+e+")","i")},this.getRegex=function(){return d.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&d&&!l.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n<t.childNodes.length;n++)this.hiliteWords(t.childNodes[n]);var i,f;if(3==t.nodeType)if((i=t.nodeValue)&&(f=d.exec(i))){for(var u=t;null!=u&&"SECTION"!=u.nodeName;)u=u.parentNode;var p=e.getIndices(u),h=c.length,y=!1;for(n=0;n<h;n++)c[n].h===p.h&&c[n].v===p.v&&(y=!0);y||c.push(p),s[f[0].toLowerCase()]||(s[f[0].toLowerCase()]=r[a++%r.length]);var g=document.createElement(o);g.appendChild(document.createTextNode(f[0])),g.style.backgroundColor=s[f[0].toLowerCase()],g.style.fontStyle="inherit",g.style.color="#000";var v=t.splitText(f.index);v.nodeValue=v.nodeValue.substring(f[0].length),t.parentNode.insertBefore(g,v)}}},this.remove=function(){for(var e,t=document.getElementsByTagName(o);t.length&&(e=t[0]);)e.parentNode.replaceChild(e.firstChild,e)},this.apply=function(e){if(null!=e&&e)return this.remove(),this.setRegex(e),this.hiliteWords(i),c}}return{id:"search",init:n=>{e=n,e.registerKeyboardShortcut("CTRL + Shift + F","Search"),document.addEventListener("keydown",(function(e){"F"==e.key&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),t||s(),"inline"!==t.style.display?a():d())}),!1)},open:a}}}));
+1 -1
View File
@@ -147,7 +147,7 @@ var zoom = (function(){
}
/**
* Pan the document when the mouse cursor approaches the edges
* Pan the document when the mosue cursor approaches the edges
* of the window.
*/
function pan() {
+15
View File
@@ -110,6 +110,21 @@
Reveal.configure({ autoAnimate: true });
});
QUnit.test( 'Carries forward matching fragment visibility', assert => {
Reveal.slide(4);
assert.ok( !slides[5].h1.classList.contains( 'visible' ) )
Reveal.next();
Reveal.next();
Reveal.next();
assert.ok( slides[5].h1.classList.contains( 'visible' ) )
assert.ok( slides[5].h2.classList.contains( 'visible' ) )
assert.ok( !slides[5].h3.classList.contains( 'visible' ) )
Reveal.next();
assert.ok( slides[5].h3.classList.contains( 'visible' ) )
Reveal.next();
assert.ok( slides[6].slide === Reveal.getCurrentSlide() )
});
QUnit.test( 'Slide specific data-auto-animate-duration', assert => {
assert.timeout( 400 );
assert.expect( 1 );
-97
View File
@@ -1,97 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>reveal.js - Test Dependencies</title>
<link rel="stylesheet" href="../dist/reveal.css">
<link rel="stylesheet" href="../node_modules/qunit/qunit/qunit.css">
<script src="../node_modules/qunit/qunit/qunit.js"></script>
</head>
<body style="overflow: auto;">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<div class="reveal deck1" style="display: none;">
<div class="slides">
<section>Slide content</section>
</div>
</div>
<div class="reveal deck2" style="display: none;">
<div class="slides">
<section>Slide content</section>
</div>
</div>
<script type="module">
import Reveal from '../dist/reveal.esm.js'
import Markdown from '../plugin/markdown/markdown.esm.js'
QUnit.module( 'Destroy' );
QUnit.test( 'Destruction during initialization', function( assert ) {
let deck = new Reveal( document.querySelector( '.deck1' ) );
deck.initialize({ plugins: [ Markdown ] });
let firstAttemptSuccess = false;
let repeatedAttemptSuccess = false;
try {
deck.destroy();
firstAttemptSuccess = true;
}
catch( error ) {
console.error( error );
}
assert.ok( firstAttemptSuccess, 'was successful' );
// should be able to destroy twice with no side effect
try {
deck.destroy();
repeatedAttemptSuccess = true;
}
catch( error ) {
console.error( error );
}
assert.ok( repeatedAttemptSuccess, 'destroyed twice with no exceptions' );
} );
QUnit.test( 'Destruction after initialization', function( assert ) {
assert.expect( 1 );
let done = assert.async( 1 );
let deck = new Reveal( document.querySelector( '.deck2' ) );
deck.initialize({ plugins: [ Markdown ] }).then(() => {
let wasSuccessful = false;
try {
deck.destroy();
wasSuccessful = true;
}
catch( error ) {
console.error( error );
}
if( wasSuccessful ) {
assert.ok( true, 'was successful' );
}
done();
});
} );
</script>
</body>
</html>
+40 -38
View File
@@ -36,66 +36,68 @@
QUnit.config.testTimeout = 30000;
Reveal.initialize({ viewDistance: 2 });
Reveal.initialize({ viewDistance: 2 }).then( () => {
var defaultIframe = document.querySelector( '.default-iframe' ),
preloadIframe = document.querySelector( '.preload-iframe' );
var defaultIframe = document.querySelector( '.default-iframe' ),
preloadIframe = document.querySelector( '.preload-iframe' );
QUnit.module( 'Iframe' );
QUnit.module( 'Iframe' );
QUnit.test( 'Using default settings', function( assert ) {
QUnit.test( 'Using default settings', function( assert ) {
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'not preloaded when within viewDistance' );
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'not preloaded when within viewDistance' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'unloaded when slide becomes invisible' );
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'unloaded when slide becomes invisible' );
});
});
QUnit.test( 'Using data-preload', function( assert ) {
QUnit.test( 'Using data-preload', function( assert ) {
Reveal.slide(1);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
Reveal.slide(1);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
Reveal.slide(2);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
Reveal.slide(2);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becoems visible' );
Reveal.slide(0);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), false, 'unloads outside of viewDistance' );
Reveal.slide(0);
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), false, 'unloads outside of viewDistance' );
});
});
QUnit.test( 'Using preloadIframes: true', function( assert ) {
QUnit.test( 'Using preloadIframes: true', function( assert ) {
Reveal.configure({ preloadIframes: true });
Reveal.configure({ preloadIframes: true });
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
Reveal.slide(1);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'preloaded within viewDistance' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
});
});
QUnit.test( 'Using preloadIframes: false', function( assert ) {
QUnit.test( 'Using preloadIframes: false', function( assert ) {
Reveal.configure({ preloadIframes: false });
Reveal.configure({ preloadIframes: false });
Reveal.slide(0);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'not preloaded within viewDistance' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), false, 'not preloaded within viewDistance' );
Reveal.slide(0);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), false, 'not preloaded within viewDistance' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), false, 'not preloaded within viewDistance' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
Reveal.slide(2);
assert.strictEqual( defaultIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
assert.strictEqual( preloadIframe.hasAttribute( 'src' ), true, 'loaded when slide becomes visible' );
});
});
} );
</script>
</body>
+7 -54
View File
@@ -97,61 +97,14 @@
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'slidechanged', callback );
assert.ok( true, 'slidechanged event fired' );
assert.ok( event.currentSlide.classList.contains( 'present' ), 'slidechanged provides reference to currentSlide' );
resolve();
}
Reveal.off( 'slidechanged', callback );
assert.ok( true, 'slidechanged event fired' );
assert.ok( event.currentSlide.classList.contains( 'present' ), 'slidechanged provides reference to currentSlide' );
resolve();
}
Reveal.on( 'slidechanged', callback );
Reveal.getViewportElement().scrollTop = getViewportHeight() * 2;
});
});
QUnit.test( 'Fires fragmentshown event when scrolling', assert => {
assert.timeout( 200 );
assert.expect( 2 );
const slides = document.querySelectorAll( '.reveal .slides section' );
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'fragmentshown', callback );
assert.ok( true, 'fragmentshown event fired' );
assert.ok( event.fragments.length > 0, 'fragmentshown provides reference to fragment nodes' );
resolve();
}
Reveal.on( 'fragmentshown', callback );
Reveal.getViewportElement().scrollTop = 0;
Reveal.next();
Reveal.next();
Reveal.getViewportElement().scrollTop += getViewportHeight();
});
});
QUnit.test( 'Fires fragmenthidden event when scrolling', assert => {
assert.timeout( 200 );
assert.expect( 2 );
const slides = document.querySelectorAll( '.reveal .slides section' );
return new Promise( resolve => {
let callback = ( event ) => {
Reveal.off( 'fragmenthidden', callback );
assert.ok( true, 'fragmenthidden event fired' );
assert.ok( event.fragments.length > 0, 'fragmenthidden provides reference to fragment nodes' );
resolve();
}
Reveal.on( 'fragmenthidden', callback );
Reveal.getViewportElement().scrollTop = 0;
Reveal.next();
Reveal.next();
Reveal.next();
Reveal.getViewportElement().scrollTop -= getViewportHeight();
Reveal.on( 'slidechanged', callback );
Reveal.getViewportElement().scrollTop = getViewportHeight() * 2;
});
});