340 lines
11 KiB
Go
340 lines
11 KiB
Go
package site
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/a-h/templ"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-sanitize/sanitize"
|
|
"github.com/gorilla/sessions"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
var (
|
|
sanitizer *sanitize.Sanitizer
|
|
)
|
|
|
|
func setupExamples(router chi.Router) (err error) {
|
|
mdElementRenderers, _, err := markdownRenders("examples")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sanitizer, err = sanitize.New()
|
|
if err != nil {
|
|
return fmt.Errorf("error creating sanitizer: %w", err)
|
|
}
|
|
|
|
sidebarGroups := []*SidebarGroup{
|
|
{
|
|
Label: "Ported HTMX Examples",
|
|
Links: []*SidebarLink{
|
|
{ID: "click_to_edit"},
|
|
{ID: "bulk_update"},
|
|
{ID: "click_to_load"},
|
|
{ID: "delete_row"},
|
|
{ID: "edit_row"},
|
|
{ID: "lazy_load"},
|
|
{ID: "fetch_indicator"},
|
|
{ID: "inline_validation"},
|
|
{ID: "infinite_scroll"},
|
|
{ID: "active_search"},
|
|
{ID: "progress_bar"},
|
|
{ID: "value_select"},
|
|
{ID: "animations"},
|
|
{ID: "file_upload"},
|
|
{ID: "dialogs_browser"},
|
|
{ID: "lazy_tabs"},
|
|
},
|
|
},
|
|
{
|
|
Label: "Web Components Examples",
|
|
Links: []*SidebarLink{
|
|
{ID: "shoelace_kitchensink"},
|
|
},
|
|
},
|
|
{
|
|
Label: "Reactive Examples",
|
|
Links: []*SidebarLink{
|
|
{ID: "multiline_fragments"},
|
|
{ID: "scroll_into_view"},
|
|
{ID: "on_load"},
|
|
{ID: "model_binding"},
|
|
{ID: "disable_button"},
|
|
{ID: "merge_options"},
|
|
{ID: "redirects"},
|
|
{ID: "view_transition_api"},
|
|
{ID: "title_update_backend"},
|
|
{ID: "store_changed"},
|
|
{ID: "raf_update"},
|
|
{ID: "update_store"},
|
|
},
|
|
},
|
|
{
|
|
Label: "Backend Examples",
|
|
Links: []*SidebarLink{
|
|
{ID: "node"},
|
|
{ID: "python"},
|
|
{ID: "quick_primer_go"},
|
|
{ID: "templ_counter"},
|
|
},
|
|
},
|
|
}
|
|
lo.ForEach(sidebarGroups, func(group *SidebarGroup, grpIdx int) {
|
|
lo.ForEach(group.Links, func(link *SidebarLink, linkIdx int) {
|
|
link.URL = templ.SafeURL("/examples/" + link.ID)
|
|
link.Label = strings.ToUpper(strings.ReplaceAll(link.ID, "_", " "))
|
|
|
|
if linkIdx > 0 {
|
|
link.Prev = group.Links[linkIdx-1]
|
|
} else if grpIdx > 0 {
|
|
prvGrp := sidebarGroups[grpIdx-1]
|
|
link.Prev = prvGrp.Links[len(prvGrp.Links)-1]
|
|
}
|
|
|
|
if linkIdx < len(group.Links)-1 {
|
|
link.Next = group.Links[linkIdx+1]
|
|
} else if grpIdx < len(sidebarGroups)-1 {
|
|
nxtGrp := sidebarGroups[grpIdx+1]
|
|
link.Next = nxtGrp.Links[0]
|
|
}
|
|
})
|
|
})
|
|
|
|
router.Route("/examples", func(examplesRouter chi.Router) {
|
|
examplesRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, string(sidebarGroups[0].Links[0].URL), http.StatusFound)
|
|
})
|
|
|
|
examplesRouter.Get("/{name}", func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
contents, ok := mdElementRenderers[name]
|
|
if !ok {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
var currentLink *SidebarLink
|
|
for _, group := range sidebarGroups {
|
|
for _, link := range group.Links {
|
|
if link.ID == name {
|
|
currentLink = link
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
SidebarPage(r, sidebarGroups, currentLink, contents).Render(r.Context(), w)
|
|
})
|
|
|
|
// mdElementRenderers, _, err := markdownRenders("examples")
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
|
|
// type Example struct {
|
|
// URL string
|
|
// Label string
|
|
// Description string
|
|
// Prev, Next *Example
|
|
// }
|
|
// type ExampleGroup struct {
|
|
// Label string
|
|
// Examples []*Example
|
|
// }
|
|
// var (
|
|
// prevExample *Example
|
|
// examplesByURL = map[string]*Example{}
|
|
// )
|
|
// examples := lo.Map([]ExampleGroup{
|
|
// {
|
|
// Label: "Ported HTMX Examples",
|
|
// Examples: []*Example{
|
|
// {Label: "Click to Edit", Description: "inline editing of a data object"},
|
|
// {Label: "Bulk Update", Description: "bulk updating of multiple rows of data"},
|
|
// {Label: "Click to Load", Description: "loading data on demand"},
|
|
// {Label: "Delete Row", Description: "row deletion in a table"},
|
|
// {Label: "Edit Row", Description: "how to edit rows in a table"},
|
|
// {Label: "Lazy Load", Description: "how to lazy load content"},
|
|
// {Label: "Fetch Indicator", Description: "show a loading indicator when fetching data"},
|
|
// {Label: "Inline Validation", Description: "how to do inline field validation"},
|
|
// {Label: "Infinite Scroll", Description: "infinite scrolling of a page"},
|
|
// {Label: "Active Search", Description: "the active search box pattern"},
|
|
// {Label: "Progress Bar", Description: "a job-runner like progress bar"},
|
|
// {Label: "Value Select", Description: "making the values of a select dependent on another select"},
|
|
// {Label: "Animations", Description: "various animation techniques"},
|
|
// {Label: "File Upload", Description: "how to upload a file via ajax with a progress bar"},
|
|
// {Label: "Dialogs Browser", Description: "the prompt and confirm dialogs"},
|
|
// {Label: "Lazy Tabs", Description: "how to lazy load tabs"},
|
|
// },
|
|
// },
|
|
// {
|
|
// Label: "Web Components Examples",
|
|
// Examples: []*Example{
|
|
// {Label: "Shoelace Kitchensink", Description: "the Shoelace Web Components library"},
|
|
// },
|
|
// },
|
|
// {
|
|
// Label: "Reactive Examples",
|
|
// Examples: []*Example{
|
|
// {Label: "Multiline Fragments", Description: "multiline fragments"},
|
|
// {Label: "Scroll Into View", Description: "scrolling an element into view"},
|
|
// {Label: "On Load", Description: "how to load data on page load"},
|
|
// {Label: "Model Binding", Description: "two-way data binding to signals"},
|
|
// {Label: "Disable Button", Description: "how to disable a button while processing"},
|
|
// {Label: "Merge Options", Description: "how to merge options in a select"},
|
|
// {Label: "Redirects", Description: "how to redirect to another page"},
|
|
// {Label: "View Transition API", Description: "using the view transition API"},
|
|
// {Label: "Title Update Backend", Description: "target a specific element for updates"},
|
|
// {Label: "Store Changed", Description: "detect when a store has changed"},
|
|
// {Label: "RAF Update", Description: "update a signal on requestAnimationFrame"},
|
|
// {Label: "Update Store", Description: "update a store from an SSE event"},
|
|
// },
|
|
// },
|
|
// {
|
|
// Label: "Backend Examples",
|
|
// Examples: []*Example{
|
|
// {Label: "Node", Description: "example backend in node"},
|
|
// {Label: "Python", Description: "example backend in python"},
|
|
// {Label: "Quick Primer Go", Description: "The getting started guide in Go"},
|
|
// {Label: "Templ Counter", Description: "a simple counter example for Templ"},
|
|
// },
|
|
// },
|
|
// }, func(g ExampleGroup, i int) ExampleGroup {
|
|
// for j, example := range g.Examples {
|
|
// g.Examples[j].URL = "/examples/" + toolbelt.Cased(g.Examples[j].Label, toolbelt.Snake, toolbelt.Lower)
|
|
// if prevExample != nil {
|
|
// example.Prev = prevExample
|
|
// prevExample.Next = example
|
|
// }
|
|
// prevExample = example
|
|
// examplesByURL[example.URL] = example
|
|
// }
|
|
// return g
|
|
// })
|
|
|
|
// router.Route("/examples", func(examplesRouter chi.Router) {
|
|
|
|
// examplesRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
// http.Redirect(w, r, examples[0].Examples[0].URL, http.StatusFound)
|
|
// })
|
|
|
|
// examplesRouter.Get("/{exampleName}", func(w http.ResponseWriter, r *http.Request) {
|
|
// exampleName := chi.URLParam(r, "exampleName")
|
|
// contents, ok := mdElementRenderers[exampleName]
|
|
// if !ok {
|
|
// http.Error(w, "not found", http.StatusNotFound)
|
|
// return
|
|
// }
|
|
|
|
// example, ok := examplesByURL[r.URL.Path]
|
|
// if !ok {
|
|
// http.Error(w, "not found", http.StatusNotFound)
|
|
// }
|
|
|
|
// contentGroup := []ElementRenderer{}
|
|
// if example.Prev != nil {
|
|
// contentGroup = append(contentGroup,
|
|
// buttonLink().
|
|
// CLASS("w-full").
|
|
// HREF(example.Prev.URL).
|
|
// Text("Back to "+example.Prev.Label).
|
|
// CLASS("flex flex-col justify-center items-center no-underline"))
|
|
// }
|
|
// contentGroup = append(contentGroup, contents)
|
|
|
|
// nextHREF := "/reference"
|
|
// nextLabel := "Dive deeper"
|
|
|
|
// if example.Next != nil {
|
|
// nextHREF = example.Next.URL
|
|
// nextLabel = "Next " + example.Next.Label
|
|
// }
|
|
// contentGroup = append(contentGroup,
|
|
// buttonLink().
|
|
// CLASS("w-full").
|
|
// HREF(nextHREF).
|
|
// Text(nextLabel).
|
|
// CLASS("flex flex-col justify-center items-center no-underline"))
|
|
|
|
// sidebarContents := Group(
|
|
// Range(examples, func(g ExampleGroup) ElementRenderer {
|
|
// return DIV(
|
|
// DIV(
|
|
// DIV().CLASS("text-2xl font-bold text-primary").Text(g.Label),
|
|
// HR().CLASS("divider border-primary"),
|
|
// ),
|
|
// TABLE().
|
|
// CLASS("table w-full").
|
|
// Children(
|
|
// THEAD(
|
|
// TR(
|
|
// TH().Text("Pattern"),
|
|
// TH().Text("Description"),
|
|
// ),
|
|
// ),
|
|
// TBODY(
|
|
// Range(g.Examples, func(e *Example) ElementRenderer {
|
|
// return TR().
|
|
// CLASS("hover").
|
|
// Children(
|
|
// TD(link(e.URL, e.Label, e.URL == r.URL.Path)),
|
|
// TD().CLASS("text-xs").Text(e.Description),
|
|
// )
|
|
// }),
|
|
// ),
|
|
// ),
|
|
// )
|
|
// }),
|
|
// )
|
|
|
|
// pp := prosePage(r, sidebarContents, Group(contentGroup...), nil)
|
|
// pp.Render(w)
|
|
// })
|
|
|
|
examplesSessionStore := sessions.NewCookieStore([]byte("ExampleSession"))
|
|
|
|
if err := errors.Join(
|
|
setupExamplesClickToEdit(examplesRouter),
|
|
setupExamplesBulkUpdate(examplesRouter),
|
|
setupExamplesClickToLoad(examplesRouter),
|
|
setupExamplesEditRow(examplesRouter),
|
|
setupExamplesDeleteRow(examplesRouter),
|
|
setupExamplesLazyLoad(examplesRouter),
|
|
setupExamplesFetchIndicator(examplesRouter),
|
|
setupExamplesOnLoad(examplesRouter, examplesSessionStore),
|
|
setupExamplesDisableButton(examplesRouter),
|
|
setupExampleInlineValidation(examplesRouter),
|
|
setupExamplesInfiniteScroll(examplesRouter),
|
|
setupExamplesActiveSearch(examplesRouter),
|
|
setupExamplesProgressBar(examplesRouter),
|
|
setupExamplesValueSelect(examplesRouter),
|
|
setupExamplesAnimations(examplesRouter),
|
|
setupExamplesFileUpload(examplesRouter),
|
|
setupExamplesDialogsBrowser(examplesRouter),
|
|
setupExamplesLazyTabs(examplesRouter),
|
|
setupExamplesMergeOptions(examplesRouter),
|
|
setupExamplesRedirects(examplesRouter),
|
|
setupExamplesMultilineFragments(examplesRouter),
|
|
setupExamplesUpdateStore(examplesRouter),
|
|
//
|
|
setupExamplesShoelaceKitchensink(examplesRouter),
|
|
//
|
|
setupExamplesViewTransitionAPI(examplesRouter),
|
|
setupExamplesModelBinding(examplesRouter),
|
|
setupExamplesTitleUpdateBackend(examplesRouter),
|
|
setupExamplesStoreChanged(examplesRouter, examplesSessionStore),
|
|
setupExamplesScrollIntoView(examplesRouter),
|
|
setupExamplesQuickPrimerGo(examplesRouter),
|
|
setupExamplesTemplCounter(examplesRouter, examplesSessionStore),
|
|
); err != nil {
|
|
panic(fmt.Sprintf("error setting up examples routes: %s", err))
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|