403 lines
9.4 KiB
Vue
403 lines
9.4 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, HASH } 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'
|
|
|
|
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}`, 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}`, 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>
|