refactor: move document head includes out of via configuration into their own append funcs; update examples
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
package via
|
package via
|
||||||
|
|
||||||
import "github.com/go-via/via/h"
|
|
||||||
|
|
||||||
type LogLevel int
|
type LogLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -22,15 +20,6 @@ type Options struct {
|
|||||||
// The title of the HTML document.
|
// The title of the HTML document.
|
||||||
DocumentTitle string
|
DocumentTitle string
|
||||||
|
|
||||||
// Elements to include in the head of the base HTML document.
|
|
||||||
// Useful for including css stylesheets and JS scripts.
|
|
||||||
DocumentHeadIncludes []h.H
|
|
||||||
|
|
||||||
// Elements to include in the bottom of the body of the base
|
|
||||||
// HTML document.Useful for including JS scripts or a footer.
|
|
||||||
DocumentBodyIncludes []h.H
|
|
||||||
|
|
||||||
// Plugins to extend the capabilities of the `Via` application.
|
// Plugins to extend the capabilities of the `Via` application.
|
||||||
// Check `https://github.com/go-via/plugins` for a list of available plugins.
|
|
||||||
Plugins []Plugin
|
Plugins []Plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,42 +16,39 @@ func LiveReloadPlugin(v *via.V) {
|
|||||||
|
|
||||||
<-r.Context().Done()
|
<-r.Context().Done()
|
||||||
})
|
})
|
||||||
}
|
v.AppendToFoot(h.Script(h.Raw(`
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
func liveReloadScript() h.H {
|
const evtSource = new EventSource('/dev/reload');
|
||||||
return h.Script(h.Raw(`
|
let overlay = null;
|
||||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
let showTimer = null;
|
||||||
const evtSource = new EventSource('/dev/reload');
|
|
||||||
let overlay = null;
|
evtSource.onerror = () => {
|
||||||
let showTimer = null;
|
evtSource.close();
|
||||||
|
|
||||||
evtSource.onerror = () => {
|
showTimer = setTimeout(() => {
|
||||||
evtSource.close();
|
if (!overlay) {
|
||||||
|
overlay = document.createElement('div');
|
||||||
showTimer = setTimeout(() => {
|
overlay.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(200, 200, 200, 0.95); padding: 20px 40px; border-radius: 8px; color: #333; font-size: 24px; z-index: 999999; font-family: -apple-system, sans-serif;';
|
||||||
if (!overlay) {
|
overlay.textContent = '🔌 Reconnecting...';
|
||||||
overlay = document.createElement('div');
|
document.body.appendChild(overlay);
|
||||||
overlay.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(200, 200, 200, 0.95); padding: 20px 40px; border-radius: 8px; color: #333; font-size: 24px; z-index: 999999; font-family: -apple-system, sans-serif;';
|
}
|
||||||
overlay.textContent = '🔌 Reconnecting...';
|
}, 1000);
|
||||||
document.body.appendChild(overlay);
|
|
||||||
}
|
(async function poll() {
|
||||||
}, 1000);
|
for (let i = 0; i < 100; i++) {
|
||||||
|
try {
|
||||||
(async function poll() {
|
const res = await fetch('/', { method: 'HEAD', signal: AbortSignal.timeout(1000) });
|
||||||
for (let i = 0; i < 100; i++) {
|
if (res.ok) {
|
||||||
try {
|
clearTimeout(showTimer);
|
||||||
const res = await fetch('/', { method: 'HEAD', signal: AbortSignal.timeout(1000) });
|
if (overlay) overlay.remove();
|
||||||
if (res.ok) {
|
location.reload();
|
||||||
clearTimeout(showTimer);
|
return;
|
||||||
if (overlay) overlay.remove();
|
}
|
||||||
location.reload();
|
} catch (e) {}
|
||||||
return;
|
await new Promise(r => setTimeout(r, 50));
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
})();
|
||||||
await new Promise(r => setTimeout(r, 50));
|
};
|
||||||
}
|
}
|
||||||
})();
|
`)))
|
||||||
};
|
|
||||||
}
|
|
||||||
`))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,9 @@ type Counter struct{ Count int }
|
|||||||
func main() {
|
func main() {
|
||||||
v := via.New()
|
v := via.New()
|
||||||
|
|
||||||
LiveReloadPlugin(v)
|
|
||||||
|
|
||||||
v.Config(via.Options{
|
v.Config(via.Options{
|
||||||
DocumentTitle: "Live Reload",
|
DocumentTitle: "Live Reload",
|
||||||
DocumentHeadIncludes: []h.H{
|
Plugins: []via.Plugin{LiveReloadPlugin},
|
||||||
liveReloadScript(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
v.Page("/", func(c *via.Context) {
|
v.Page("/", func(c *via.Context) {
|
||||||
@@ -29,7 +25,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
c.View(func() h.H {
|
c.View(func() h.H {
|
||||||
return h.Div(h.Class("container"),
|
return h.Div(
|
||||||
h.H1(h.Text("Live Reload")),
|
h.H1(h.Text("Live Reload")),
|
||||||
h.P(h.Textf("Count: %d", data.Count)),
|
h.P(h.Textf("Count: %d", data.Count)),
|
||||||
h.P(h.Span(h.Text("Step: ")), h.Span(step.Text())),
|
h.P(h.Span(h.Text("Step: ")), h.Span(step.Text())),
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
v := via.New()
|
v := via.New()
|
||||||
|
|
||||||
v.Config(via.Options{
|
v.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")))
|
||||||
DocumentTitle: "Via",
|
|
||||||
DocumentHeadIncludes: []h.H{
|
|
||||||
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
v.Page("/", func(c *via.Context) {
|
v.Page("/", func(c *via.Context) {
|
||||||
c.View(func() h.H {
|
c.View(func() h.H {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/go-via/via/h"
|
"github.com/go-via/via/h"
|
||||||
)
|
)
|
||||||
|
|
||||||
// In this example we create a Via application with a plugin that adds PicoCSS. The plugin
|
// Example of a Via application with a plugin that adds PicoCSS. The plugin
|
||||||
// is handed to Via in the app Configuration.
|
// is handed to Via in the Configuration.
|
||||||
|
|
||||||
//go:embed pico.yellow.min.css
|
//go:embed pico.yellow.min.css
|
||||||
var picoCSSFile []byte
|
var picoCSSFile []byte
|
||||||
@@ -41,9 +41,5 @@ func PicoCSSPlugin(v *via.V) {
|
|||||||
w.Header().Set("Content-Type", "text/css")
|
w.Header().Set("Content-Type", "text/css")
|
||||||
_, _ = w.Write(picoCSSFile)
|
_, _ = w.Write(picoCSSFile)
|
||||||
})
|
})
|
||||||
v.Config(via.Options{
|
v.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("/_plugins/picocss/assets/style.css")))
|
||||||
DocumentHeadIncludes: []h.H{
|
|
||||||
h.Link(h.Rel("stylesheet"), h.Href("/_plugins/picocss/assets/style.css")),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,10 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
v := via.New()
|
v := via.New()
|
||||||
|
|
||||||
v.Config(via.Options{
|
v.AppendToHead(
|
||||||
DocumentTitle: "Via",
|
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
||||||
DocumentHeadIncludes: []h.H{
|
h.Script(h.Src("https://unpkg.com/echarts@6.0.0/dist/echarts.min.js")),
|
||||||
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
)
|
||||||
h.Script(h.Src("https://unpkg.com/echarts@6.0.0/dist/echarts.min.js")),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
v.Page("/", func(c *via.Context) {
|
v.Page("/", func(c *via.Context) {
|
||||||
chartComp := c.Component(chartCompFn)
|
chartComp := c.Component(chartCompFn)
|
||||||
|
|||||||
42
via.go
42
via.go
@@ -29,7 +29,8 @@ type V struct {
|
|||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
contextRegistry map[string]*Context
|
contextRegistry map[string]*Context
|
||||||
contextRegistryMutex sync.RWMutex
|
contextRegistryMutex sync.RWMutex
|
||||||
baseLayout func(h.HTML5Props) h.H
|
documentHeadIncludes []h.H
|
||||||
|
documentFootIncludes []h.H
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *V) logErr(c *Context, format string, a ...any) {
|
func (v *V) logErr(c *Context, format string, a ...any) {
|
||||||
@@ -78,12 +79,6 @@ func (v *V) Config(cfg Options) {
|
|||||||
if cfg.DocumentTitle != "" {
|
if cfg.DocumentTitle != "" {
|
||||||
v.cfg.DocumentTitle = cfg.DocumentTitle
|
v.cfg.DocumentTitle = cfg.DocumentTitle
|
||||||
}
|
}
|
||||||
if cfg.DocumentHeadIncludes != nil {
|
|
||||||
v.cfg.DocumentHeadIncludes = cfg.DocumentHeadIncludes
|
|
||||||
}
|
|
||||||
if cfg.DocumentBodyIncludes != nil {
|
|
||||||
v.cfg.DocumentBodyIncludes = cfg.DocumentBodyIncludes
|
|
||||||
}
|
|
||||||
if cfg.Plugins != nil {
|
if cfg.Plugins != nil {
|
||||||
for _, plugin := range cfg.Plugins {
|
for _, plugin := range cfg.Plugins {
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
@@ -93,6 +88,27 @@ func (v *V) Config(cfg Options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendToHead appends the given h.H nodes to the head of the base HTML document.
|
||||||
|
// Useful for including css stylesheets and JS scripts.
|
||||||
|
func (v *V) AppendToHead(elements ...h.H) {
|
||||||
|
for _, el := range elements {
|
||||||
|
if el != nil {
|
||||||
|
v.documentHeadIncludes = append(v.documentHeadIncludes, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendToFoot appends the given h.H nodes to the end of the base HTML document body.
|
||||||
|
// Useful for including JS scripts.
|
||||||
|
func (v *V) AppendToFoot(elements ...h.H) {
|
||||||
|
for _, el := range elements {
|
||||||
|
if el != nil {
|
||||||
|
v.documentFootIncludes = append(v.documentFootIncludes, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Page registers a route and its associated page handler.
|
// Page registers a route and its associated page handler.
|
||||||
// The handler receives a *Context to define UI, signals, and actions.
|
// The handler receives a *Context to define UI, signals, and actions.
|
||||||
//
|
//
|
||||||
@@ -113,13 +129,11 @@ func (v *V) Page(route string, composeContext func(c *Context)) {
|
|||||||
v.logDebug(c, "GET %s", route)
|
v.logDebug(c, "GET %s", route)
|
||||||
composeContext(c)
|
composeContext(c)
|
||||||
v.registerCtx(c.id, c)
|
v.registerCtx(c.id, c)
|
||||||
headElements := v.cfg.DocumentHeadIncludes
|
headElements := v.documentHeadIncludes
|
||||||
headElements = append(headElements, h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id))))
|
headElements = append(headElements, h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id))))
|
||||||
headElements = append(headElements, h.Meta(h.Data("init", "@get('/_sse')")))
|
headElements = append(headElements, h.Meta(h.Data("init", "@get('/_sse')")))
|
||||||
bottomBodyElements := []h.H{c.view()}
|
bottomBodyElements := []h.H{c.view()}
|
||||||
for _, el := range v.cfg.DocumentBodyIncludes {
|
bottomBodyElements = append(bottomBodyElements, v.documentFootIncludes...)
|
||||||
bottomBodyElements = append(bottomBodyElements, el)
|
|
||||||
}
|
|
||||||
view := h.HTML5(h.HTML5Props{
|
view := h.HTML5(h.HTML5Props{
|
||||||
Title: v.cfg.DocumentTitle,
|
Title: v.cfg.DocumentTitle,
|
||||||
Head: headElements,
|
Head: headElements,
|
||||||
@@ -172,10 +186,8 @@ func New() *V {
|
|||||||
mux: mux,
|
mux: mux,
|
||||||
contextRegistry: make(map[string]*Context),
|
contextRegistry: make(map[string]*Context),
|
||||||
cfg: Options{
|
cfg: Options{
|
||||||
LogLvl: LogLevelDebug,
|
LogLvl: LogLevelDebug,
|
||||||
DocumentTitle: "Via Application",
|
DocumentTitle: "Via Application",
|
||||||
DocumentHeadIncludes: make([]h.H, 0),
|
|
||||||
DocumentBodyIncludes: make([]h.H, 0),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user