Split slides first before rendering them
- add some more config options - cleanup code - remove state machine
This commit is contained in:
parent
33dadaff87
commit
7d3d3a5abf
11 changed files with 213 additions and 221 deletions
27
api/views.go
27
api/views.go
|
@ -1,13 +1,9 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"hash/fnv"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gomarkdown/markdown"
|
|
||||||
"github.com/gomarkdown/markdown/html"
|
|
||||||
"github.com/gomarkdown/markdown/parser"
|
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/baez90/goveal/config"
|
"github.com/baez90/goveal/config"
|
||||||
|
@ -15,13 +11,6 @@ import (
|
||||||
"github.com/baez90/goveal/rendering"
|
"github.com/baez90/goveal/rendering"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
parserExtensions = parser.NoIntraEmphasis | parser.Tables | parser.FencedCode |
|
|
||||||
parser.Autolink | parser.Strikethrough | parser.SpaceHeadings | parser.HeadingIDs |
|
|
||||||
parser.BackslashLineBreak | parser.DefinitionLists | parser.MathJax | parser.Titleblock |
|
|
||||||
parser.OrderedListStart | parser.Attributes
|
|
||||||
)
|
|
||||||
|
|
||||||
type Views struct {
|
type Views struct {
|
||||||
cfg *config.Components
|
cfg *config.Components
|
||||||
wdfs fs.FS
|
wdfs fs.FS
|
||||||
|
@ -54,20 +43,14 @@ func (p *Views) RenderedMarkdown(ctx *fiber.Ctx) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mdParser := parser.NewWithExtensions(parserExtensions)
|
var rendered []byte
|
||||||
rr := &rendering.RevealRenderer{
|
if rendered, err = rendering.ToHTML(string(data), p.cfg.Rendering); err != nil {
|
||||||
StateMachine: rendering.NewStateMachine("***", "---"),
|
return err
|
||||||
Hash: fnv.New32a(),
|
} else if _, err = ctx.Write(rendered); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
renderer := html.NewRenderer(html.RendererOptions{
|
|
||||||
Flags: html.CommonFlags | html.HrefTargetBlank,
|
|
||||||
RenderNodeHook: rr.RenderHook,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.Append(fiber.HeaderContentType, fiber.MIMETextHTML)
|
ctx.Append(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||||
if _, err = ctx.Write(markdown.ToHTML(data, mdParser, renderer)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,10 @@ var defaults = map[string]interface{}{
|
||||||
"slideNumber": true,
|
"slideNumber": true,
|
||||||
"menu.numbers": true,
|
"menu.numbers": true,
|
||||||
"menu.useTextContentForMissingTitles": true,
|
"menu.useTextContentForMissingTitles": true,
|
||||||
|
"menu.transitions": true,
|
||||||
|
"menu.hideMissingTitles": true,
|
||||||
|
"menu.markers": true,
|
||||||
|
"menu.openButton": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -51,7 +55,10 @@ type (
|
||||||
Menu struct {
|
Menu struct {
|
||||||
Numbers bool `json:"numbers"`
|
Numbers bool `json:"numbers"`
|
||||||
UseTextContentForMissingTitles bool `json:"useTextContentForMissingTitles"`
|
UseTextContentForMissingTitles bool `json:"useTextContentForMissingTitles"`
|
||||||
Transitions bool
|
Transitions bool `json:"transitions"`
|
||||||
|
HideMissingTitles bool `json:"hideMissingTitles"`
|
||||||
|
Markers bool `json:"markers"`
|
||||||
|
OpenButton bool `json:"openButton"`
|
||||||
} `json:"menu"`
|
} `json:"menu"`
|
||||||
}
|
}
|
||||||
Components struct {
|
Components struct {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
theme: night
|
theme: night
|
||||||
codeTheme: monokai
|
codeTheme: monokai
|
||||||
horizontalSeparator: ===
|
horizontalSeparator: ---
|
||||||
verticalSeparator: ---
|
verticalSeparator: '\*\*\*'
|
||||||
transition: convex
|
transition: convex
|
||||||
controlsLayout: edges
|
controlsLayout: edges
|
||||||
controls: true
|
controls: true
|
||||||
|
@ -12,6 +12,10 @@ slideNumber: true
|
||||||
menu:
|
menu:
|
||||||
numbers: false
|
numbers: false
|
||||||
useTextContentForMissingTitles: true
|
useTextContentForMissingTitles: true
|
||||||
|
transitions: true
|
||||||
|
hideMissingTitles: false
|
||||||
|
markers: true
|
||||||
|
openButton: true
|
||||||
mermaid:
|
mermaid:
|
||||||
theme: forest
|
theme: forest
|
||||||
stylesheets:
|
stylesheets:
|
||||||
|
|
|
@ -91,6 +91,7 @@ for (var j = 0; j < i; j++) {
|
||||||
### Mermaid
|
### Mermaid
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark'}}%%
|
||||||
flowchart LR
|
flowchart LR
|
||||||
a --> b & c--> d
|
a --> b & c--> d
|
||||||
```
|
```
|
||||||
|
|
90
rendering/html.go
Normal file
90
rendering/html.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package rendering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/gomarkdown/markdown/parser"
|
||||||
|
|
||||||
|
"github.com/baez90/goveal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
parserExtensions = parser.NoIntraEmphasis | parser.Tables | parser.FencedCode |
|
||||||
|
parser.Autolink | parser.Strikethrough | parser.SpaceHeadings | parser.HeadingIDs |
|
||||||
|
parser.BackslashLineBreak | parser.DefinitionLists | parser.MathJax | parser.Titleblock |
|
||||||
|
parser.OrderedListStart | parser.Attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToHTML(markdown string, renderCfg config.Rendering) (rendered []byte, err error) {
|
||||||
|
var slides []rawSlide
|
||||||
|
if slides, err = splitIntoRawSlides(markdown, renderCfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := templateRenderBufferPool.Get().(*bytes.Buffer)
|
||||||
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
templateRenderBufferPool.Put(buf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for idx := range slides {
|
||||||
|
if rendered, err := slides[idx].ToHTML(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
buf.WriteString(string(rendered))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawSlide struct {
|
||||||
|
Content string
|
||||||
|
Children []rawSlide
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s rawSlide) HasNotes() bool {
|
||||||
|
return notesLineRegexp.MatchString(s.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s rawSlide) ToHTML() (template.HTML, error) {
|
||||||
|
if rendered, err := renderTemplate("slide.gohtml", s); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
//nolint:gosec // should not be sanitized
|
||||||
|
return template.HTML(rendered), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitIntoRawSlides(markdown string, renderCfg config.Rendering) ([]rawSlide, error) {
|
||||||
|
var (
|
||||||
|
verticalSplit, horizontalSplit *regexp.Regexp
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if verticalSplit, err = regexp.Compile(fmt.Sprintf(splitFormat, renderCfg.VerticalSeparator)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if horizontalSplit, err = regexp.Compile(fmt.Sprintf(splitFormat, renderCfg.HorizontalSeparator)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontalSlides := horizontalSplit.Split(markdown, -1)
|
||||||
|
slides := make([]rawSlide, 0, len(horizontalSlides))
|
||||||
|
for _, hs := range horizontalSlides {
|
||||||
|
s := rawSlide{
|
||||||
|
Content: hs,
|
||||||
|
}
|
||||||
|
verticalSlides := verticalSplit.Split(hs, -1)
|
||||||
|
s.Children = make([]rawSlide, 0, len(verticalSlides))
|
||||||
|
for _, vs := range verticalSlides {
|
||||||
|
s.Children = append(s.Children, rawSlide{Content: vs})
|
||||||
|
}
|
||||||
|
slides = append(slides, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return slides, nil
|
||||||
|
}
|
19
rendering/patterns.go
Normal file
19
rendering/patterns.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package rendering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// language=regexp
|
||||||
|
notesRegex = `(?i)notes?:`
|
||||||
|
// language=regexp
|
||||||
|
splitFormat = `\r?\n%s\r?\n`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlElementAttributesRegexp = regexp.MustCompile(`(?P<key>[a-z]+(-[a-z]+)*)="(?P<value>.+)"`)
|
||||||
|
notesRegexp = regexp.MustCompile(fmt.Sprintf(`^%s`, notesRegex))
|
||||||
|
notesLineRegexp = regexp.MustCompile(fmt.Sprintf(`\r?\n%s\r?\n`, notesRegex))
|
||||||
|
)
|
|
@ -2,65 +2,26 @@ package rendering
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"hash"
|
"hash"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
|
||||||
"github.com/gomarkdown/markdown/ast"
|
"github.com/gomarkdown/markdown/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
//go:embed templates/*.gohtml
|
|
||||||
templatesFS embed.FS
|
|
||||||
templates *template.Template
|
|
||||||
templateRenderBufferPool = &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
htmlElementAttributesRegexp = regexp.MustCompile(`(?P<key>[a-z]+(-[a-z]+)*)="(?P<value>.+)"`)
|
|
||||||
notesRegexp = regexp.MustCompile(`^(?i)notes?`)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mermaidCodeBlock = "mermaid"
|
mermaidCodeBlock = "mermaid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
templates = template.New("rendering").Funcs(sprig.FuncMap())
|
|
||||||
if templates, err = templates.ParseFS(templatesFS, "templates/*.gohtml"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RevealRenderer struct {
|
type RevealRenderer struct {
|
||||||
StateMachine *StateMachine
|
|
||||||
Hash hash.Hash
|
Hash hash.Hash
|
||||||
hasNotes bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo // under construction
|
|
||||||
func (r *RevealRenderer) RenderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
func (r *RevealRenderer) RenderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
||||||
switch b := node.(type) {
|
switch b := node.(type) {
|
||||||
case *ast.Document:
|
|
||||||
if entering {
|
|
||||||
_, _ = w.Write([]byte("<section>\n"))
|
|
||||||
if next := peekNextRuler(b); next != nil && string(next.Literal) == "***" {
|
|
||||||
_, _ = w.Write([]byte("<section>\n"))
|
|
||||||
r.StateMachine.CurrentState = StateTypeNested
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, _ = w.Write([]byte("</section>\n"))
|
|
||||||
}
|
|
||||||
return ast.GoToNext, false
|
|
||||||
case *ast.ListItem:
|
case *ast.ListItem:
|
||||||
if entering {
|
if entering {
|
||||||
return r.handleListItem(w, b)
|
return r.handleListItem(w, b)
|
||||||
|
@ -72,7 +33,6 @@ func (r *RevealRenderer) RenderHook(w io.Writer, node ast.Node, entering bool) (
|
||||||
}
|
}
|
||||||
if notesRegexp.Match(b.Literal) {
|
if notesRegexp.Match(b.Literal) {
|
||||||
_, err := w.Write([]byte(`<aside class="notes">`))
|
_, err := w.Write([]byte(`<aside class="notes">`))
|
||||||
r.hasNotes = true
|
|
||||||
return ast.SkipChildren, err == nil
|
return ast.SkipChildren, err == nil
|
||||||
}
|
}
|
||||||
return ast.GoToNext, false
|
return ast.GoToNext, false
|
||||||
|
@ -81,18 +41,6 @@ func (r *RevealRenderer) RenderHook(w io.Writer, node ast.Node, entering bool) (
|
||||||
return r.handleCodeBlock(w, b)
|
return r.handleCodeBlock(w, b)
|
||||||
}
|
}
|
||||||
return ast.GoToNext, false
|
return ast.GoToNext, false
|
||||||
case *ast.HorizontalRule:
|
|
||||||
next := peekNextRuler(b)
|
|
||||||
input := string(b.Literal)
|
|
||||||
if next != nil {
|
|
||||||
input += string(next.Literal)
|
|
||||||
}
|
|
||||||
if r.hasNotes {
|
|
||||||
_, _ = w.Write([]byte(`</aside>`))
|
|
||||||
r.hasNotes = false
|
|
||||||
}
|
|
||||||
_, _ = w.Write(r.StateMachine.Accept(input))
|
|
||||||
return ast.GoToNext, true
|
|
||||||
case *ast.Image:
|
case *ast.Image:
|
||||||
if entering {
|
if entering {
|
||||||
return r.handleImage(w, b)
|
return r.handleImage(w, b)
|
||||||
|
@ -215,17 +163,6 @@ func renderCodeTemplate(templateName string, codeBlock *ast.CodeBlock) (output [
|
||||||
return renderTemplate(templateName, data)
|
return renderTemplate(templateName, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(templateName string, data interface{}) (output []byte, err error) {
|
|
||||||
buffer := templateRenderBufferPool.Get().(*bytes.Buffer)
|
|
||||||
defer func() {
|
|
||||||
buffer.Reset()
|
|
||||||
templateRenderBufferPool.Put(buffer)
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = templates.ExecuteTemplate(buffer, templateName, data)
|
|
||||||
return buffer.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineNumbers(attrs *ast.Attribute) string {
|
func lineNumbers(attrs *ast.Attribute) string {
|
||||||
if attrs == nil || attrs.Attrs == nil {
|
if attrs == nil || attrs.Attrs == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
package rendering
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
EventTypeHorizontalSplit EventType = iota
|
|
||||||
EventTypeVerticalSplit
|
|
||||||
EventTypeHorizontalEnd
|
|
||||||
EventTypeVerticalSplitEnd
|
|
||||||
EventTypeVerticalDocumentEnd
|
|
||||||
EventTypeVerticalVerticalSplit
|
|
||||||
|
|
||||||
StateTypeRegular StateType = iota
|
|
||||||
StateTypeNested
|
|
||||||
)
|
|
||||||
|
|
||||||
var EventMapping = map[EventType][]byte{
|
|
||||||
EventTypeHorizontalSplit: []byte(`
|
|
||||||
</section>
|
|
||||||
<section>`),
|
|
||||||
EventTypeVerticalSplit: []byte(`
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<section>
|
|
||||||
`),
|
|
||||||
EventTypeHorizontalEnd: []byte(`
|
|
||||||
</section>
|
|
||||||
`),
|
|
||||||
EventTypeVerticalSplitEnd: []byte(`
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section>`),
|
|
||||||
EventTypeVerticalDocumentEnd: []byte(`
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
`),
|
|
||||||
EventTypeVerticalVerticalSplit: []byte(`
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<section>`),
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStateMachine(verticalSplit, horizontalSplit string) *StateMachine {
|
|
||||||
return &StateMachine{
|
|
||||||
CurrentState: StateTypeRegular,
|
|
||||||
States: map[StateType]State{
|
|
||||||
StateTypeRegular: {
|
|
||||||
Transitions: map[string]TransitionResult{
|
|
||||||
horizontalSplit: {EventTypeHorizontalSplit, StateTypeRegular},
|
|
||||||
fmt.Sprintf("%s%s", horizontalSplit, horizontalSplit): {EventTypeHorizontalSplit, StateTypeRegular},
|
|
||||||
fmt.Sprintf("%s%s", horizontalSplit, verticalSplit): {EventTypeVerticalSplit, StateTypeNested},
|
|
||||||
fmt.Sprintf("%s%s", verticalSplit, horizontalSplit): {EventTypeVerticalSplit, StateTypeNested},
|
|
||||||
"": {EventTypeHorizontalEnd, StateTypeRegular},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
StateTypeNested: {
|
|
||||||
Transitions: map[string]TransitionResult{
|
|
||||||
fmt.Sprintf("%s%s", verticalSplit, verticalSplit): {EventTypeHorizontalSplit, StateTypeNested},
|
|
||||||
verticalSplit: {EventTypeHorizontalSplit, StateTypeNested},
|
|
||||||
fmt.Sprintf("%s%s", verticalSplit, horizontalSplit): {EventTypeVerticalSplitEnd, StateTypeNested},
|
|
||||||
horizontalSplit: {EventTypeVerticalSplitEnd, StateTypeRegular},
|
|
||||||
fmt.Sprintf("%s%s", horizontalSplit, verticalSplit): {EventTypeVerticalVerticalSplit, StateTypeNested},
|
|
||||||
fmt.Sprintf("%s%s", horizontalSplit, horizontalSplit): {EventTypeVerticalSplitEnd, StateTypeRegular},
|
|
||||||
"": {EventTypeVerticalDocumentEnd, StateTypeRegular},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
EventType uint
|
|
||||||
StateType uint
|
|
||||||
TransitionResult struct {
|
|
||||||
EventType EventType
|
|
||||||
StateType StateType
|
|
||||||
}
|
|
||||||
State struct {
|
|
||||||
Transitions map[string]TransitionResult
|
|
||||||
}
|
|
||||||
StateMachine struct {
|
|
||||||
CurrentState StateType
|
|
||||||
States map[StateType]State
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *StateMachine) Accept(input string) []byte {
|
|
||||||
if result, ok := m.States[m.CurrentState].Transitions[input]; ok {
|
|
||||||
m.CurrentState = result.StateType
|
|
||||||
return EventMapping[result.EventType]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func peekNextRuler(node ast.Node) *ast.HorizontalRule {
|
|
||||||
if node.AsContainer() == nil {
|
|
||||||
node = node.GetParent()
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes := node.GetChildren()
|
|
||||||
if nodes == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var selfIdx int
|
|
||||||
for idx := range nodes {
|
|
||||||
if nodes[idx] == node {
|
|
||||||
selfIdx = idx
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx := selfIdx + 1; idx < len(nodes); idx++ {
|
|
||||||
if hr, ok := nodes[idx].(*ast.HorizontalRule); ok {
|
|
||||||
return hr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
61
rendering/templates.go
Normal file
61
rendering/templates.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package rendering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"hash/fnv"
|
||||||
|
"html/template"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig/v3"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
mdhtml "github.com/gomarkdown/markdown/html"
|
||||||
|
"github.com/gomarkdown/markdown/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed templates/*.gohtml
|
||||||
|
templatesFS embed.FS
|
||||||
|
templates *template.Template
|
||||||
|
templateRenderBufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
templates = template.New("rendering").
|
||||||
|
Funcs(sprig.FuncMap()).
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"renderMarkdown": func(md string) template.HTML {
|
||||||
|
rr := &RevealRenderer{
|
||||||
|
Hash: fnv.New32a(),
|
||||||
|
}
|
||||||
|
mdParser := parser.NewWithExtensions(parserExtensions)
|
||||||
|
renderer := mdhtml.NewRenderer(mdhtml.RendererOptions{
|
||||||
|
Flags: mdhtml.CommonFlags | mdhtml.HrefTargetBlank,
|
||||||
|
RenderNodeHook: rr.RenderHook,
|
||||||
|
})
|
||||||
|
|
||||||
|
renderedHTML := markdown.ToHTML([]byte(md), mdParser, renderer)
|
||||||
|
//nolint:gosec // template should be esacped
|
||||||
|
return template.HTML(renderedHTML)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
var err error
|
||||||
|
if templates, err = templates.ParseFS(templatesFS, "templates/*.gohtml"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(templateName string, data interface{}) (output []byte, err error) {
|
||||||
|
buffer := templateRenderBufferPool.Get().(*bytes.Buffer)
|
||||||
|
defer func() {
|
||||||
|
buffer.Reset()
|
||||||
|
templateRenderBufferPool.Put(buffer)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = templates.ExecuteTemplate(buffer, templateName, data)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
12
rendering/templates/slide.gohtml
Normal file
12
rendering/templates/slide.gohtml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<section>
|
||||||
|
{{ if .Children }}
|
||||||
|
{{ range .Children }}
|
||||||
|
{{ .ToHTML }}
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
{{ renderMarkdown .Content }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .HasNotes }}
|
||||||
|
</aside>
|
||||||
|
{{ end }}
|
||||||
|
</section>
|
|
@ -5,7 +5,6 @@ document.addEventListener("DOMContentLoaded", _ => {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
subscribeToEvents()
|
subscribeToEvents()
|
||||||
console.info("finished initializing")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -34,6 +33,7 @@ async function initReveal() {
|
||||||
let cfg = await getRevealConfig()
|
let cfg = await getRevealConfig()
|
||||||
Reveal.initialize({
|
Reveal.initialize({
|
||||||
controls: cfg.controls,
|
controls: cfg.controls,
|
||||||
|
controlsLayout: cfg.controlsLayout,
|
||||||
progress: cfg.progress,
|
progress: cfg.progress,
|
||||||
history: cfg.history,
|
history: cfg.history,
|
||||||
center: cfg.center,
|
center: cfg.center,
|
||||||
|
@ -44,6 +44,10 @@ async function initReveal() {
|
||||||
menu: {
|
menu: {
|
||||||
numbers: cfg.menu.numbers,
|
numbers: cfg.menu.numbers,
|
||||||
useTextContentForMissingTitles: cfg.menu.useTextContentForMissingTitles,
|
useTextContentForMissingTitles: cfg.menu.useTextContentForMissingTitles,
|
||||||
|
transitions: cfg.menu.transitions,
|
||||||
|
hideMissingTitles: cfg.hideMissingTitles,
|
||||||
|
markers: cfg.menu.markers,
|
||||||
|
openButton: cfg.menu.openButton,
|
||||||
custom: [
|
custom: [
|
||||||
{
|
{
|
||||||
title: 'Print',
|
title: 'Print',
|
||||||
|
@ -64,7 +68,6 @@ async function initReveal() {
|
||||||
{name: 'Solarized', theme: '/reveal/dist/theme/solarized.css'},
|
{name: 'Solarized', theme: '/reveal/dist/theme/solarized.css'},
|
||||||
{name: 'White', theme: '/reveal/dist/theme/white.css'}
|
{name: 'White', theme: '/reveal/dist/theme/white.css'}
|
||||||
],
|
],
|
||||||
transitions: true,
|
|
||||||
},
|
},
|
||||||
plugins: [RevealHighlight, RevealNotes, RevealMenu]
|
plugins: [RevealHighlight, RevealNotes, RevealMenu]
|
||||||
})
|
})
|
||||||
|
@ -87,20 +90,19 @@ function subscribeToEvents() {
|
||||||
let source = new EventSource("/api/v1/events");
|
let source = new EventSource("/api/v1/events");
|
||||||
|
|
||||||
source.onopen = (() => {
|
source.onopen = (() => {
|
||||||
console.log("eventsource connection open");
|
console.debug("eventsource connection open");
|
||||||
})
|
})
|
||||||
|
|
||||||
source.onerror = (ev => {
|
source.onerror = (ev => {
|
||||||
if (ev.target.readyState === 0) {
|
if (ev.target.readyState === 0) {
|
||||||
console.log("reconnecting to eventsource");
|
console.debug("reconnecting to eventsource");
|
||||||
} else {
|
} else {
|
||||||
console.log("eventsource error", ev);
|
console.error("eventsource error", ev);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
source.onmessage = (ev => {
|
source.onmessage = (ev => {
|
||||||
let obj = JSON.parse(ev.data);
|
let obj = JSON.parse(ev.data);
|
||||||
console.log(obj);
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case obj.forceReload:
|
case obj.forceReload:
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
|
|
Loading…
Reference in a new issue