updates to site, fixes for windows
This commit is contained in:
parent
2ca816a1e4
commit
c6a0d0e985
18 changed files with 239 additions and 35 deletions
|
|
@ -69,6 +69,11 @@ func downloadTailwindCli() {
|
||||||
distro = "linux-arm64"
|
distro = "linux-arm64"
|
||||||
case os == "linux" && arch == "amd64":
|
case os == "linux" && arch == "amd64":
|
||||||
distro = "linux-x64"
|
distro = "linux-x64"
|
||||||
|
case os == "windows" && arch == "amd64":
|
||||||
|
distro = "windows-x64"
|
||||||
|
case os == "windows" && arch == "arm64":
|
||||||
|
distro = "windows-arm64"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatal(fmt.Sprintf("Unsupported OS/ARCH: %s/%s", os, arch))
|
log.Fatal(fmt.Sprintf("Unsupported OS/ARCH: %s/%s", os, arch))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -178,7 +179,10 @@ func RunMany(commands []string, flags ...RunFlag) error {
|
||||||
func Run(command string, flags ...RunFlag) error {
|
func Run(command string, flags ...RunFlag) error {
|
||||||
parts := strings.Fields(command)
|
parts := strings.Fields(command)
|
||||||
cmd := exec.Command(parts[0], parts[1:]...)
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
}
|
||||||
|
|
||||||
if slices.Contains(flags, Silent) {
|
if slices.Contains(flags, Silent) {
|
||||||
cmd.Stdout = nil
|
cmd.Stdout = nil
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,10 @@ func Raw(text string) *RawContent {
|
||||||
return NewRawContent(text)
|
return NewRawContent(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Style(text string) Ren {
|
||||||
|
return Tag("style", Text(text))
|
||||||
|
}
|
||||||
|
|
||||||
func MultiLineQuotes(text string) string {
|
func MultiLineQuotes(text string) string {
|
||||||
return "`" + text + "`"
|
return "`" + text + "`"
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +144,19 @@ func Input(inputType string, children ...Ren) Ren {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IterMap[T any](m map[string]T, mapper func(key string, value T) *Element) *Element {
|
||||||
|
node := &Element{
|
||||||
|
tag: "",
|
||||||
|
children: make([]Ren, len(m)),
|
||||||
|
}
|
||||||
|
index := 0
|
||||||
|
for key, value := range m {
|
||||||
|
node.children[index] = mapper(key, value)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
func List[T any](items []T, mapper func(item T, index int) *Element) *Element {
|
func List[T any](items []T, mapper func(item T, index int) *Element) *Element {
|
||||||
node := &Element{
|
node := &Element{
|
||||||
tag: "",
|
tag: "",
|
||||||
|
|
|
||||||
78
htmgo-site/internal/datastructures/map.go
Normal file
78
htmgo-site/internal/datastructures/map.go
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package datastructures
|
||||||
|
|
||||||
|
// OrderedMap is a generic data structure that maintains the order of keys.
|
||||||
|
type OrderedMap[K comparable, V any] struct {
|
||||||
|
keys []K
|
||||||
|
values map[K]V
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry[K comparable, V any] struct {
|
||||||
|
Key K
|
||||||
|
Value V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries returns the key-value pairs in the order they were added.
|
||||||
|
func (om *OrderedMap[K, V]) Entries() []Entry[K, V] {
|
||||||
|
entries := make([]Entry[K, V], len(om.keys))
|
||||||
|
for i, key := range om.keys {
|
||||||
|
entries[i] = Entry[K, V]{
|
||||||
|
Key: key,
|
||||||
|
Value: om.values[key],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrderedMap creates a new OrderedMap.
|
||||||
|
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
|
||||||
|
return &OrderedMap[K, V]{
|
||||||
|
keys: []K{},
|
||||||
|
values: make(map[K]V),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds or updates a key-value pair in the OrderedMap.
|
||||||
|
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||||
|
// Check if the key already exists
|
||||||
|
if _, exists := om.values[key]; !exists {
|
||||||
|
om.keys = append(om.keys, key) // Append key to the keys slice if it's a new key
|
||||||
|
}
|
||||||
|
om.values[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a value by key.
|
||||||
|
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
|
||||||
|
value, exists := om.values[key]
|
||||||
|
return value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns the keys in the order they were added.
|
||||||
|
func (om *OrderedMap[K, V]) Keys() []K {
|
||||||
|
return om.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values returns the values in the order of their keys.
|
||||||
|
func (om *OrderedMap[K, V]) Values() []V {
|
||||||
|
values := make([]V, len(om.keys))
|
||||||
|
for i, key := range om.keys {
|
||||||
|
values[i] = om.values[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a key-value pair from the OrderedMap.
|
||||||
|
func (om *OrderedMap[K, V]) Delete(key K) {
|
||||||
|
if _, exists := om.values[key]; exists {
|
||||||
|
// Remove the key from the map
|
||||||
|
delete(om.values, key)
|
||||||
|
|
||||||
|
// Remove the key from the keys slice
|
||||||
|
for i, k := range om.keys {
|
||||||
|
if k == key {
|
||||||
|
om.keys = append(om.keys[:i], om.keys[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -13,8 +15,8 @@ type Page struct {
|
||||||
Parts []string
|
Parts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func WalkPages(dir string, system fs.FS) []Page {
|
func WalkPages(dir string, system fs.FS) []*Page {
|
||||||
pages := make([]Page, 0)
|
pages := make([]*Page, 0)
|
||||||
fs.WalkDir(system, dir, func(path string, d fs.DirEntry, err error) error {
|
fs.WalkDir(system, dir, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -23,7 +25,7 @@ func WalkPages(dir string, system fs.FS) []Page {
|
||||||
if !d.IsDir() && (strings.HasSuffix(name, ".md") || strings.HasSuffix(name, ".go")) {
|
if !d.IsDir() && (strings.HasSuffix(name, ".md") || strings.HasSuffix(name, ".go")) {
|
||||||
fullPath := strings.Replace(path, dir, "", 1)
|
fullPath := strings.Replace(path, dir, "", 1)
|
||||||
fullPath = strings.TrimSuffix(fullPath, ".md")
|
fullPath = strings.TrimSuffix(fullPath, ".md")
|
||||||
pages = append(pages, Page{
|
pages = append(pages, &Page{
|
||||||
RoutePath: fullPath,
|
RoutePath: fullPath,
|
||||||
FilePath: path,
|
FilePath: path,
|
||||||
Parts: h.Filter(strings.Split(fullPath, string(os.PathSeparator)), func(item string) bool {
|
Parts: h.Filter(strings.Split(fullPath, string(os.PathSeparator)), func(item string) bool {
|
||||||
|
|
@ -33,5 +35,23 @@ func WalkPages(dir string, system fs.FS) []Page {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var getRouteOrder = func(page *Page) int {
|
||||||
|
fileName := page.Parts[len(page.Parts)-1]
|
||||||
|
if len(fileName) > 1 && fileName[1] == '_' {
|
||||||
|
num, err := strconv.ParseInt(fileName[0:1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
page.Parts[len(page.Parts)-1] = fileName[2:]
|
||||||
|
return int(num)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(pages, func(a *Page, b *Page) int {
|
||||||
|
return getRouteOrder(a) - getRouteOrder(b)
|
||||||
|
})
|
||||||
|
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func main() {
|
||||||
__htmgo.RegisterPages(e)
|
__htmgo.RegisterPages(e)
|
||||||
|
|
||||||
pages.RegisterMarkdown(e, "md", MarkdownAssets, func(ctx echo.Context, path string) error {
|
pages.RegisterMarkdown(e, "md", MarkdownAssets, func(ctx echo.Context, path string) error {
|
||||||
return pages.MarkdownHandler(ctx.(*h.RequestContext), path)
|
return pages.MarkdownHandler(ctx.(*h.RequestContext), path, "")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
3
htmgo-site/md/docs/1_quick-start/1_introduction.md
Normal file
3
htmgo-site/md/docs/1_quick-start/1_introduction.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
## **Introduction**
|
||||||
|
|
||||||
|
htmgo is a lightweight pure go way to build interactive websites / web applications using go & htmx.
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
## **Getting Started**
|
## **Getting Started**
|
||||||
|
|
||||||
|
|
||||||
|
##### **Prerequisites:**
|
||||||
|
Go: https://go.dev/doc/install
|
||||||
|
<br>
|
||||||
|
|
||||||
##### 1. **Install htmgo**
|
##### 1. **Install htmgo**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GONOPROXY=github.com/maddalax go install github.com/maddalax/htmgo/cli/htmgo@latest
|
go install github.com/maddalax/htmgo/cli/htmgo@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
tip: GONOPROXY helps because the default proxy server for how go resolves modules appears to have fairly long caching on it, so without this env variable, an old version may get installed.
|
tip: GONOPROXY helps because the default proxy server for how go resolves modules appears to have fairly long caching on it, so without this env variable, an old version may get installed.
|
||||||
3
htmgo-site/md/docs/2_core-concepts/pages.md
Normal file
3
htmgo-site/md/docs/2_core-concepts/pages.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Pages ##
|
||||||
|
|
||||||
|
Coming soon
|
||||||
3
htmgo-site/md/docs/2_core-concepts/partials.md
Normal file
3
htmgo-site/md/docs/2_core-concepts/partials.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Partials ##
|
||||||
|
|
||||||
|
Coming soon
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
Coming soon
|
**Todo List MVC**
|
||||||
|
[Github](https://github.com/maddalax/htmgo/tree/master/examples/todo-list)
|
||||||
|
[Live Demo](https://todo-example.htmgo.dev)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ func RootPage(children ...h.Ren) *h.Element {
|
||||||
h.Raw(`
|
h.Raw(`
|
||||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||||
`),
|
`),
|
||||||
|
h.Style(`
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
`),
|
||||||
),
|
),
|
||||||
h.Body(
|
h.Body(
|
||||||
h.Class("bg-neutral-50 min-h-screen overflow-x-hidden"),
|
h.Class("bg-neutral-50 min-h-screen overflow-x-hidden"),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"htmgo-site/internal/dirwalk"
|
"htmgo-site/internal/dirwalk"
|
||||||
"htmgo-site/pages/base"
|
"htmgo-site/pages/base"
|
||||||
|
"htmgo-site/partials"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DocsPage(ctx *h.RequestContext) *h.Page {
|
func DocsPage(ctx *h.RequestContext) *h.Page {
|
||||||
|
|
@ -13,14 +15,17 @@ func DocsPage(ctx *h.RequestContext) *h.Page {
|
||||||
|
|
||||||
return h.NewPage(base.RootPage(
|
return h.NewPage(base.RootPage(
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex flex-col justify-center items-center"),
|
h.Class("flex gap-4 justify-center mb-12"),
|
||||||
h.List(pages, func(page dirwalk.Page, index int) *h.Element {
|
partials.DocSidebar(pages),
|
||||||
return MarkdownContent(ctx, page.FilePath)
|
h.Div(
|
||||||
}),
|
h.Class("flex flex-col justify-center items-center mt-6 gap-12"),
|
||||||
|
h.List(pages, func(page *dirwalk.Page, index int) *h.Element {
|
||||||
|
return MarkdownContent(ctx, page.FilePath, strings.Join(page.Parts, "-"))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
h.Div(
|
||||||
|
h.Class("min-h-12"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
h.Div(
|
))
|
||||||
h.Class("min-h-12"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func IndexPage(ctx *h.RequestContext) *h.Page {
|
func IndexPage(ctx *h.RequestContext) *h.Page {
|
||||||
return h.NewPage(MarkdownPage(ctx, "md/index.md"))
|
return h.NewPage(MarkdownPage(ctx, "md/index.md", ""))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@ import (
|
||||||
"htmgo-site/pages/base"
|
"htmgo-site/pages/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarkdownHandler(ctx *h.RequestContext, path string) error {
|
func MarkdownHandler(ctx *h.RequestContext, path string, id string) error {
|
||||||
return h.HtmlView(ctx, h.NewPage(MarkdownPage(ctx, path)))
|
return h.HtmlView(ctx, h.NewPage(MarkdownPage(ctx, path, id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarkdownPage(ctx *h.RequestContext, path string) *h.Element {
|
func MarkdownPage(ctx *h.RequestContext, path string, id string) *h.Element {
|
||||||
return base.RootPage(
|
return base.RootPage(
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("w-full p-4 flex flex-col justify-center items-center"),
|
h.Class("w-full p-4 flex flex-col justify-center items-center"),
|
||||||
MarkdownContent(ctx, path),
|
MarkdownContent(ctx, path, id),
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("min-h-12"),
|
h.Class("min-h-12"),
|
||||||
),
|
),
|
||||||
|
|
@ -24,10 +24,11 @@ func MarkdownPage(ctx *h.RequestContext, path string) *h.Element {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarkdownContent(ctx *h.RequestContext, path string) *h.Element {
|
func MarkdownContent(ctx *h.RequestContext, path string, id string) *h.Element {
|
||||||
embeddedMd := ctx.Get("embeddedMarkdown").(*embed.FS)
|
embeddedMd := ctx.Get("embeddedMarkdown").(*embed.FS)
|
||||||
renderer := service.Get[markdown.Renderer](ctx.ServiceLocator())
|
renderer := service.Get[markdown.Renderer](ctx.ServiceLocator())
|
||||||
return h.Div(
|
return h.Div(
|
||||||
|
h.If(id != "", h.Id(id)),
|
||||||
h.Article(
|
h.Article(
|
||||||
h.Class("prose max-w-[95vw] md:max-w-2xl px-4 prose-code:text-black"),
|
h.Class("prose max-w-[95vw] md:max-w-2xl px-4 prose-code:text-black"),
|
||||||
h.Raw(renderer.RenderFile(path, embeddedMd)),
|
h.Raw(renderer.RenderFile(path, embeddedMd)),
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ func NavBar(expanded bool) *h.Element {
|
||||||
)
|
)
|
||||||
|
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("mb-4"),
|
|
||||||
h.Id("navbar"),
|
h.Id("navbar"),
|
||||||
prelease,
|
prelease,
|
||||||
MobileNav(expanded),
|
MobileNav(expanded),
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,77 @@
|
||||||
package partials
|
package partials
|
||||||
|
|
||||||
import "github.com/maddalax/htmgo/framework/h"
|
import (
|
||||||
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
"htmgo-site/internal/datastructures"
|
||||||
|
"htmgo-site/internal/dirwalk"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatPart(part string) string {
|
||||||
|
if part[1] == '_' {
|
||||||
|
part = part[2:]
|
||||||
|
}
|
||||||
|
part = strings.ReplaceAll(part, "-", " ")
|
||||||
|
part = strings.ReplaceAll(part, "_", " ")
|
||||||
|
part = strings.Title(part)
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
func partsToName(parts []string) string {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
for i, part := range parts {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
part = formatPart(part)
|
||||||
|
builder.WriteString(part)
|
||||||
|
builder.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupByFirstPart(pages []*dirwalk.Page) *datastructures.OrderedMap[string, []*dirwalk.Page] {
|
||||||
|
grouped := datastructures.NewOrderedMap[string, []*dirwalk.Page]()
|
||||||
|
for _, page := range pages {
|
||||||
|
if len(page.Parts) > 0 {
|
||||||
|
section := page.Parts[0]
|
||||||
|
existing, has := grouped.Get(section)
|
||||||
|
if !has {
|
||||||
|
existing = []*dirwalk.Page{}
|
||||||
|
grouped.Set(section, existing)
|
||||||
|
}
|
||||||
|
grouped.Set(section, append(existing, page))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
|
|
||||||
|
func DocSidebar(pages []*dirwalk.Page) *h.Element {
|
||||||
|
grouped := groupByFirstPart(pages)
|
||||||
|
|
||||||
func SideBar() *h.Element {
|
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("w-40 top-[57px] absolute min-h-screen bg-neutral-50 border border-r-slate-300 p-3"),
|
h.Class("px-3 py-2 pr-6 min-h-[(calc(100%))] min-h-screen bg-neutral-50 border-r border-r-slate-300"),
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("max-w-prose mx-auto"),
|
h.H4(h.Text("Contents"), h.Class("mt-4 text-slate-900 font-bold mb-3")),
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex flex-col gap-4"),
|
h.Class("flex flex-col gap-4"),
|
||||||
h.A(
|
h.List(grouped.Entries(), func(entry datastructures.Entry[string, []*dirwalk.Page], index int) *h.Element {
|
||||||
h.Href("/docs"),
|
return h.Div(
|
||||||
h.Text("Docs"),
|
h.P(h.Text(formatPart(entry.Key)), h.Class("text-slate-800 font-bold")),
|
||||||
),
|
h.Div(
|
||||||
h.A(
|
h.Class("pl-4 flex flex-col"),
|
||||||
h.Href("/examples"),
|
h.List(entry.Value, func(page *dirwalk.Page, index int) *h.Element {
|
||||||
h.Text("Examples"),
|
anchor := strings.Join(page.Parts, "-")
|
||||||
),
|
return h.A(
|
||||||
|
h.Href("#"+anchor),
|
||||||
|
h.Text(partsToName(page.Parts)),
|
||||||
|
h.ClassX("text-slate-900", map[string]bool{}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue