diff --git a/cmd/goveal/serve.go b/cmd/goveal/serve.go index 2cba01c..c4fd3af 100644 --- a/cmd/goveal/serve.go +++ b/cmd/goveal/serve.go @@ -51,8 +51,8 @@ var ( hub := events.NewEventHub( wdfs, fnv.New32a(), - events.FileNameTrigger(args[0]), - events.FileNameTrigger(filepath.Base(cfg.ConfigFileInUse)), + events.MutationReloadForFile(args[0]), + events.MutationConfigReloadForFile(filepath.Base(cfg.ConfigFileInUse)), ) api.NoCache(app) diff --git a/config/components.go b/config/components.go index 64fdceb..a650f09 100644 --- a/config/components.go +++ b/config/components.go @@ -3,9 +3,10 @@ package config var ( defaults = map[string]interface{}{ "mermaid.theme": "forest", - "reveal.theme": "beige", + "theme": "beige", "codeTheme": "monokai", "transition": TransitionNone, + "controlsLayout": ControlsLayoutEdges, "controls": true, "progress": true, "history": true, @@ -23,11 +24,15 @@ const ( TransitionConvex Transition = "convex" TransitionConcave Transition = "concave" TransitionZoom Transition = "zoom" + + ControlsLayoutBottomRight ControlsLayout = "bottom-right" + ControlsLayoutEdges ControlsLayout = "edges" ) type ( - Transition string - Mermaid struct { + Transition string + ControlsLayout string + Mermaid struct { Theme string `json:"theme"` } Rendering struct { @@ -36,15 +41,16 @@ type ( Stylesheets []string } Reveal struct { - Theme string `json:"theme"` - CodeTheme string `json:"codeTheme"` - Transition Transition `json:"transition"` - Controls bool `json:"controls"` - Progress bool `json:"progress"` - History bool `json:"history"` - Center bool `json:"center"` - SlideNumber bool `json:"slideNumber"` - Menu struct { + Theme string `json:"theme"` + CodeTheme string `json:"codeTheme"` + Transition Transition `json:"transition"` + Controls bool `json:"controls"` + ControlsLayout ControlsLayout `json:"controlsLayout"` + Progress bool `json:"progress"` + History bool `json:"history"` + Center bool `json:"center"` + SlideNumber bool `json:"slideNumber"` + Menu struct { Numbers bool `json:"numbers"` UseTextContentForMissingTitles bool `json:"useTextContentForMissingTitles"` Transitions bool diff --git a/events/event_hub.go b/events/event_hub.go index 2bf9ee1..9458eee 100644 --- a/events/event_hub.go +++ b/events/event_hub.go @@ -21,14 +21,15 @@ const ( ) type ( - ReloadTrigger interface { - Triggers(ev fs.Event) bool + EventMutation interface { + OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent) } ContentEvent struct { File string `json:"file"` FileNameHash string `json:"fileNameHash"` Timestamp string `json:"ts"` ForceReload bool `json:"forceReload"` + ReloadConfig bool `json:"reloadConfig"` } EventSource interface { io.Closer @@ -43,30 +44,35 @@ type ( EventHandler OnError chan error } - FileNameTrigger string - FileSuffixTrigger string + MutationReloadForFile string + MutationConfigReloadForFile string ) -func (t FileNameTrigger) Triggers(ev fs.Event) bool { - fileBase := filepath.Base(ev.File) - return strings.EqualFold(fileBase, string(t)) +func (t MutationReloadForFile) OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent) { + if strings.EqualFold(filepath.Base(ev.File), string(t)) { + in.ForceReload = true + } + return in } -func (t FileSuffixTrigger) Triggers(ev fs.Event) bool { - return strings.HasSuffix(strings.ToLower(filepath.Base(ev.File)), strings.ToLower(string(t))) +func (t MutationConfigReloadForFile) OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent) { + if strings.EqualFold(filepath.Base(ev.File), string(t)) { + in.ReloadConfig = true + } + return in } func (f EventHandlerFunc) OnEvent(ev ContentEvent) error { return f(ev) } -func NewEventHub(eventSource EventSource, fileNameHash hash.Hash, triggers ...ReloadTrigger) *EventHub { +func NewEventHub(eventSource EventSource, fileNameHash hash.Hash, mutations ...EventMutation) *EventHub { hub := &EventHub{ - FileNameHash: fileNameHash, - reloadTriggers: triggers, - source: eventSource, - subscriptions: make(map[uuid.UUID]*subscription), - done: make(chan struct{}), + FileNameHash: fileNameHash, + mutations: mutations, + source: eventSource, + subscriptions: make(map[uuid.UUID]*subscription), + done: make(chan struct{}), } go hub.processEvents() @@ -75,12 +81,12 @@ func NewEventHub(eventSource EventSource, fileNameHash hash.Hash, triggers ...Re } type EventHub struct { - FileNameHash hash.Hash - reloadTriggers []ReloadTrigger - lock sync.RWMutex - done chan struct{} - source EventSource - subscriptions map[uuid.UUID]*subscription + FileNameHash hash.Hash + mutations []EventMutation + lock sync.RWMutex + done chan struct{} + source EventSource + subscriptions map[uuid.UUID]*subscription } func (h *EventHub) Subscribe(handler EventHandler) (id uuid.UUID, onError <-chan error) { @@ -129,20 +135,16 @@ func (h *EventHub) notifySubscribers(ev fs.Event) { h.lock.RLock() defer h.lock.RUnlock() - var triggerReload bool - for idx := range h.reloadTriggers { - if triggerReload = h.reloadTriggers[idx].Triggers(ev); triggerReload { - break - } - } - ce := ContentEvent{ File: fmt.Sprintf("/%s", ev.File), Timestamp: strconv.FormatInt(ev.Timestamp.Unix(), baseDecimal), - ForceReload: triggerReload, FileNameHash: hex.EncodeToString(h.FileNameHash.Sum([]byte(path.Base(ev.File)))), } + for idx := range h.mutations { + ce = h.mutations[idx].OnEvent(ce, ev) + } + for _, handler := range h.subscriptions { if err := handler.OnEvent(ce); err != nil { handler.OnError <- err diff --git a/examples/goveal.yaml b/examples/goveal.yaml index d1aef4f..ba725c0 100644 --- a/examples/goveal.yaml +++ b/examples/goveal.yaml @@ -3,6 +3,16 @@ codeTheme: monokai horizontalSeparator: === verticalSeparator: --- transition: convex -menu.numbers: false +controlsLayout: edges +controls: true +progress: true +history: true +center: true +slideNumber: true +menu: + numbers: false + useTextContentForMissingTitles: true +mermaid: + theme: forest stylesheets: - custom.css \ No newline at end of file diff --git a/web/js/app.js b/web/js/app.js index 1614abe..f529970 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -25,9 +25,13 @@ async function setSlidesContent() { document.getElementById("content-root").innerHTML = contentDocument.documentElement.innerHTML } -async function initReveal() { +async function getRevealConfig() { let resp = await fetch('/api/v1/config/reveal') - let cfg = await resp.json() + return await resp.json() +} + +async function initReveal() { + let cfg = await getRevealConfig() Reveal.initialize({ controls: cfg.controls, progress: cfg.progress, @@ -97,20 +101,27 @@ function subscribeToEvents() { source.onmessage = (ev => { let obj = JSON.parse(ev.data); console.log(obj); - if (obj.forceReload) { - window.location.reload() - } else { - switch (true) { - case obj.file.endsWith(".css"): - let cssLink = document.querySelector(`link[rel=stylesheet][id="${obj.fileNameHash}"]`); - cssLink.href = `${obj.file}?ts=${obj.ts}` - break - default: - let elem = document.getElementById(obj.fileNameHash); - if (elem !== null) { - elem.src = `${obj.file}?ts=${obj.ts}` - } - } + switch (true) { + case obj.forceReload: + window.location.reload() + break + case obj.reloadConfig: + getRevealConfig().then(cfg => { + Reveal.configure(cfg) + }) + break + default: + switch (true) { + case obj.file.endsWith(".css"): + let cssLink = document.querySelector(`link[rel=stylesheet][id="${obj.fileNameHash}"]`); + cssLink.href = `${obj.file}?ts=${obj.ts}` + break + default: + let elem = document.getElementById(obj.fileNameHash); + if (elem !== null) { + elem.src = `${obj.file}?ts=${obj.ts}` + } + } } }) } \ No newline at end of file