Files
zhdiary/packages/web/pages/reveal.vue
T
2019-12-25 00:11:26 +07:00

404 lines
9.5 KiB
Vue

<template lang="pug">
div(style="width: 100vw; height: 100vh")
#global
.reveal
.slides
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import MakeHtml from '@/assets/make-html'
import matter from 'gray-matter'
import { apiHash } from '@/assets/util'
// @ts-ignore
import shortHash from 'short-hash'
import $ from 'cheerio'
// @ts-ignore
import scopeCss from 'scope-css'
import h from 'hyperscript'
import qs from 'querystring'
import stringify from 'fast-json-stable-stringify'
import { globalStore } from '../store'
declare global {
interface Window {
Reveal: RevealStatic
hljs: any
revealMd: RevealMd
}
}
export interface ISlide {
id: string
type: 'hidden' | 'global' | 'regular'
html: string
raw: string
}
const revealCdn = 'https://cdn.jsdelivr.net/npm/reveal.js@3.8.0/'
const makeHtml = new MakeHtml()
@Component({
layout: 'full',
watchQuery: ['path'],
})
export default class RevealMd extends Vue {
trueHeader: any = {}
trueMarkdown = ''
queue: Array<(r?: RevealStatic) => void> = []
raw: ISlide[][] = [[]]
title = ''
defaults = {
header: {},
markdown: '',
}
isReady = false
async asyncData ({ $axios, query }: any) {
const { path } = query
if (path) {
try {
const raw = await apiHash(`/build/slides/${path}`, globalStore.hash.slides[path as string])
const { data, content } = matter(raw)
return {
defaults: {
header: data,
markdown: content,
},
}
} catch (e) {
console.error(e)
}
}
return {}
}
head () {
return {
title: this.title,
link: [
{
rel: 'stylesheet',
href: `${revealCdn}css/reveal.css`,
type: 'text/css',
},
{
rel: 'stylesheet',
href: `${revealCdn}css/theme/white.css`,
type: 'text/css',
id: 'reveal-theme',
},
],
}
}
async mounted () {
let currentSlide = ''
if (process.client) {
window.revealMd = this
const { path } = this.$route.query
if (!this.defaults.markdown && path) {
try {
const raw = await apiHash(`/build/slides/${path}`, globalStore.hash.slides[path as string])
const { data, content } = matter(raw)
this.defaults.markdown = content
this.defaults.header = data
} catch (e) {
console.error(e)
}
}
this.markdown = this.defaults.markdown
this.header = this.defaults.header
this.onReady(() => {
if (currentSlide) {
location.hash = currentSlide
}
})
currentSlide = location.hash.replace(/^#/, '')
const script = document.createElement('script')
script.onload = () => {
window.Reveal.initialize({
slideNumber: true,
// @ts-ignore
hash: true,
dependencies: [
{
src: `${revealCdn}plugin/highlight/highlight.js`,
async: true,
},
],
})
window.Reveal.addEventListener('ready', () => {
this.isReady = true
})
}
script.src = `${revealCdn}js/reveal.min.js`
document.body.appendChild(script)
}
}
get header (): RevealOptions & {
theme?: string
title?: string
} {
return this.trueHeader || {}
}
set header (h) {
const { theme, title, ...subH } = h
this.theme = theme || 'white'
this.title = title || ''
if (stringify(this.trueHeader) === stringify(subH)) {
return
}
this.onReady(() => {
window.Reveal.configure(subH)
})
this.trueHeader = subH
}
get markdown () {
return this.trueMarkdown
}
set markdown (s: string) {
this.trueMarkdown = s
const globalEl = this.$el.querySelector('#global') as HTMLElement
Array.from(globalEl.querySelectorAll('style.ref')).map((el) => el.remove())
let xOffset = 0
const newRaw = s.split(/\r?\n(?:===|---)\r?\n/g).map((el, x) => {
this.raw[x] = this.raw[x] || []
const newRawSs = el.split(/\r?\n--\r?\n/g).map((ss) => this.parseSlide(ss))
if (newRawSs.every((ss) => !ss.html)) {
xOffset++
}
x -= xOffset
let yOffset = 0
return newRawSs.map((thisRaw, y) => {
if (!thisRaw.html) {
yOffset++
return
}
y -= yOffset
let section = this.getSlide(x)
let subSection = this.getSlide(x, y)
if (!this.raw[x][y] || (this.raw[x][y] && this.raw[x][y].raw !== thisRaw.raw)) {
const container = document.createElement('div')
container.className = 'container'
container.innerHTML = thisRaw.html
if (section && subSection) {
const oldContainers = subSection.getElementsByClassName('container')
Array.from(oldContainers).forEach((el) => el.remove())
subSection.appendChild(container)
} else {
subSection = document.createElement('section')
subSection.append(container)
if (section) {
section.appendChild(subSection)
} else {
section = document.createElement('section')
section.appendChild(subSection)
this.$el.querySelector('.reveal .slides')!.appendChild(section)
}
}
Array.from(container.querySelectorAll('pre code:not(.hljs)')).forEach((el) => {
if (window.hljs) {
window.hljs.highlightBlock(el)
}
})
}
return thisRaw
}).filter((el) => el)
}).filter((el) => el && el.length > 0) as ISlide[][]
this.raw.map((el, x) => {
el.map((ss, j) => {
const y = el.length - j - 1
if (!newRaw[x] || !newRaw[x][y]) {
const subSection = this.getSlide(x, y)
if (subSection) {
subSection.remove()
}
}
})
if (!newRaw[x]) {
const section = this.getSlide(x)
if (section) {
section.remove()
}
}
})
this.raw = newRaw
}
get theme () {
const el = document.getElementById('reveal-theme') as HTMLLinkElement
const m = /\/(\S+)\.css$/.exec(el.href)
if (m) {
return m[1]
}
return ''
}
set theme (t) {
const el = document.getElementById('reveal-theme') as HTMLLinkElement
el.href = `${revealCdn}css/theme/${t}.css`
}
update (raw: string) {
const { data, content } = matter(raw)
this.markdown = content
this.header = data
}
onReady (fn?: () => void) {
if (this.isReady) {
if (this.queue.length > 0) {
this.queue.forEach(it => it())
window.Reveal.slide(-1, -1, -1)
window.Reveal.sync()
}
if (fn) {
fn()
}
} else {
if (fn) {
this.queue.push(fn)
}
setTimeout(() => {
this.onReady()
}, 1000)
}
}
parseSlide (text: string): ISlide {
const id = shortHash(text)
const raw = text
let type: 'hidden' | 'global' | 'regular' = 'regular'
let html = text
const [firstLine, ...lines] = html.split('\n')
const newType = firstLine.substr(3)
if (newType === 'hidden') {
type = 'hidden'
return { html: '', raw, id, type }
} else if (newType === 'global') {
type = 'global'
html = lines.join('\n')
}
html = html.replace(/(?:^|\n)\/\/ css=([A-Za-z0-9\-_]+\.css)(?:$|\n)/g, (p0, ref: string) => {
const i = raw.indexOf(p0)
const globalEl = this.$el.querySelector('#global') as HTMLDivElement
const className = `ref${i}`
let el = globalEl.querySelector(`style.ref.${className}`)
if (!el) {
el = document.createElement('style')
el.classList.add('ref', className)
globalEl.appendChild(el)
}
let url = ref
if (!ref.includes('://')) {
url = `/api/data?${qs.stringify({
filename: ref,
})}`
}
fetch(url).then((r) => r.text()).then((content) => {
if (type !== 'global') {
content = scopeCss(content, `#${id}`)
}
el!.innerHTML = content
})
return ''
})
html = html.replace(/(?:^|\n)(\/\/ slide\n)?```(\S+)\n([^]+?)\n```(?:$|\n)/g, (p0, subtype, lang, content) => {
if (type !== 'global' && !subtype) {
return p0
}
if (lang === 'css') {
const globalEl = this.$el.querySelector('#global') as HTMLDivElement
if (type !== 'global') {
content = scopeCss(content, `#${id}`)
}
let el = globalEl.querySelector('style.main')
if (!el) {
el = document.createElement('style')
el.className = 'main'
globalEl.appendChild(el)
}
el.innerHTML = content
return ''
} else if (lang === 'pre') {
return h('pre', content).outerHTML
// } else if (lang === 'pug') {
// return makeHtml.pugConvert(content)
} else if (lang === 'html') {
return content
}
return p0
})
return {
html: h(`#${id}`, {
innerHTML: makeHtml.mdConvert(html),
}).outerHTML,
raw,
id,
type,
}
}
getSlide (x: number, y?: number) {
const s = this.$el.querySelectorAll('.slides > section')
const hSlide = s[x]
if (typeof y === 'number') {
if (hSlide) {
return Array.from(hSlide.children).filter((el) => el.tagName.toLocaleUpperCase() === 'SECTION')[y]
}
return undefined
}
return hSlide
}
}
</script>