add inline edit / fix copy button
This commit is contained in:
parent
db3322c3d8
commit
7666186f83
10 changed files with 304 additions and 45 deletions
|
|
@ -14,6 +14,15 @@ func Unique[T any](slice []T, key func(item T) string) []T {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Find[T any](slice []T, predicate func(item *T) bool) *T {
|
||||||
|
for _, v := range slice {
|
||||||
|
if predicate(&v) {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filter returns a new slice with only items that match the predicate.
|
// Filter returns a new slice with only items that match the predicate.
|
||||||
func Filter[T any](slice []T, predicate func(item T) bool) []T {
|
func Filter[T any](slice []T, predicate func(item T) bool) []T {
|
||||||
var result []T
|
var result []T
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func RenderMarkdown(reader io.Reader) bytes.Buffer {
|
||||||
highlighting.WithFormatOptions(
|
highlighting.WithFormatOptions(
|
||||||
chromahtml.WithLineNumbers(true),
|
chromahtml.WithLineNumbers(true),
|
||||||
chromahtml.WithCustomCSS(map[chroma.TokenType]string{
|
chromahtml.WithCustomCSS(map[chroma.TokenType]string{
|
||||||
chroma.PreWrapper: "padding: 12px; overflow: auto; background-color: rgb(245, 245, 245) !important;",
|
chroma.PreWrapper: "font-size: 14px; padding: 12px; overflow: auto; background-color: rgb(245, 245, 245) !important;",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
highlighting.WithStyle("github"),
|
highlighting.WithStyle("github"),
|
||||||
|
|
|
||||||
10
htmgo-site/pages/examples/click-to-edit.go
Normal file
10
htmgo-site/pages/examples/click-to-edit.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package examples
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClickToEditExample(ctx *h.RequestContext) *h.Page {
|
||||||
|
SetSnippet(ctx, &ClickToEditSnippet)
|
||||||
|
return Index(ctx)
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -29,20 +32,38 @@ var RenderCodeToStringCached = h.CachedPerKeyT(time.Minute*30, func(snippet *Sni
|
||||||
})
|
})
|
||||||
|
|
||||||
func renderCodeToString(snippet *Snippet) *h.Element {
|
func renderCodeToString(snippet *Snippet) *h.Element {
|
||||||
url := GetGithubRawPath(snippet.path)
|
source := ""
|
||||||
slog.Info("getting snippet source code", slog.String("url", url))
|
// in development, use the local file
|
||||||
resp, err := http.Get(url)
|
if h.IsDevelopment() {
|
||||||
if err != nil {
|
ptr := reflect.ValueOf(snippet.partial).Pointer()
|
||||||
return h.Empty()
|
fnInfo := runtime.FuncForPC(ptr)
|
||||||
|
if fnInfo == nil {
|
||||||
|
return h.Empty()
|
||||||
|
}
|
||||||
|
file, _ := fnInfo.FileLine(ptr)
|
||||||
|
b, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return h.Empty()
|
||||||
|
}
|
||||||
|
source = string(b)
|
||||||
|
} else {
|
||||||
|
url := GetGithubRawPath(snippet.path)
|
||||||
|
slog.Info("getting snippet source code", slog.String("url", url))
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return h.Empty()
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return h.Empty()
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return h.Empty()
|
||||||
|
}
|
||||||
|
source = out.String()
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
return ui.CodeSnippet(source, "border-radius: 0.5rem;")
|
||||||
return h.Empty()
|
|
||||||
}
|
|
||||||
out := bytes.NewBuffer(nil)
|
|
||||||
_, err = io.Copy(out, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return h.Empty()
|
|
||||||
}
|
|
||||||
return ui.CodeSnippet(out.String(), "border-radius: 0.5rem;")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import "htmgo-site/partials/snippets"
|
||||||
var FormWithLoadingStateSnippet = Snippet{
|
var FormWithLoadingStateSnippet = Snippet{
|
||||||
name: "Form",
|
name: "Form",
|
||||||
description: "A simple form submission example with a loading state",
|
description: "A simple form submission example with a loading state",
|
||||||
sidebarName: "Form with loading state",
|
sidebarName: "Form With Loading State",
|
||||||
path: "/examples/form",
|
path: "/examples/form",
|
||||||
partial: snippets.FormExample,
|
partial: snippets.FormExample,
|
||||||
}
|
}
|
||||||
|
|
@ -22,16 +22,16 @@ var UserAuthSnippet = Snippet{
|
||||||
var ChatSnippet = Snippet{
|
var ChatSnippet = Snippet{
|
||||||
name: "Chat App",
|
name: "Chat App",
|
||||||
description: "A simple chat application built with htmgo using SSE for real-time updates",
|
description: "A simple chat application built with htmgo using SSE for real-time updates",
|
||||||
sidebarName: "Chat App",
|
sidebarName: "Chat App Using SSE",
|
||||||
path: "/examples/chat",
|
path: "/examples/chat",
|
||||||
externalRoute: "https://chat-example.htmgo.dev",
|
externalRoute: "https://chat-example.htmgo.dev",
|
||||||
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/chat",
|
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/chat",
|
||||||
}
|
}
|
||||||
|
|
||||||
var HackerNewsSnippet = Snippet{
|
var HackerNewsSnippet = Snippet{
|
||||||
name: "Hacker News Clone",
|
name: "HackerNews Clone",
|
||||||
description: "A hacker news reader clone built with htmgo",
|
description: "A hacker news reader clone built with htmgo",
|
||||||
sidebarName: "Hacker News Clone",
|
sidebarName: "HackerNews Clone",
|
||||||
path: "/examples/hackernews",
|
path: "/examples/hackernews",
|
||||||
externalRoute: "https://hn.htmgo.dev",
|
externalRoute: "https://hn.htmgo.dev",
|
||||||
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/hackernews",
|
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/hackernews",
|
||||||
|
|
@ -55,8 +55,17 @@ var TodoListSnippet = Snippet{
|
||||||
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/todo-list",
|
sourceCodePath: "https://github.com/maddalax/htmgo/tree/master/examples/todo-list",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ClickToEditSnippet = Snippet{
|
||||||
|
name: "Inline Click To Edit",
|
||||||
|
description: "List view of items with a click to edit button and persistence",
|
||||||
|
sidebarName: "Inline click to edit",
|
||||||
|
path: "/examples/click-to-edit",
|
||||||
|
partial: snippets.ClickToEdit,
|
||||||
|
}
|
||||||
|
|
||||||
var examples = []Snippet{
|
var examples = []Snippet{
|
||||||
FormWithLoadingStateSnippet,
|
FormWithLoadingStateSnippet,
|
||||||
|
ClickToEditSnippet,
|
||||||
UserAuthSnippet,
|
UserAuthSnippet,
|
||||||
ChatSnippet,
|
ChatSnippet,
|
||||||
HackerNewsSnippet,
|
HackerNewsSnippet,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ func HtmlToGoPage(ctx *h.RequestContext) *h.Page {
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("h-full w-full flex gap-4 p-8"),
|
h.Class("h-full w-full flex gap-4 p-8"),
|
||||||
partials.HtmlInput(),
|
partials.HtmlInput(),
|
||||||
|
partials.HiddenCopyOutput(""),
|
||||||
partials.GoOutput(""),
|
partials.GoOutput(""),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@ package partials
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"github.com/maddalax/htmgo/framework/js"
|
|
||||||
"github.com/maddalax/htmgo/tools/html-to-htmgo/htmltogo"
|
"github.com/maddalax/htmgo/tools/html-to-htmgo/htmltogo"
|
||||||
"htmgo-site/ui"
|
"htmgo-site/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConvertHtmlToGo(ctx *h.RequestContext) *h.Partial {
|
func ConvertHtmlToGo(ctx *h.RequestContext) *h.Partial {
|
||||||
value := ctx.FormValue("html-input")
|
value := ctx.FormValue("html-input")
|
||||||
parsed := htmltogo.Parse([]byte(value))
|
parsed := string(htmltogo.Parse([]byte(value)))
|
||||||
|
|
||||||
formatted := ui.FormatCode(string(parsed), "height: 100%;")
|
formatted := ui.FormatCode(parsed, "height: 100%;")
|
||||||
|
|
||||||
return h.SwapPartial(ctx, GoOutput(formatted))
|
return h.SwapManyPartial(ctx,
|
||||||
|
GoOutput(formatted),
|
||||||
|
HiddenCopyOutput(parsed),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HtmlInput() *h.Element {
|
func HtmlInput() *h.Element {
|
||||||
|
|
@ -30,6 +32,14 @@ func HtmlInput() *h.Element {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HiddenCopyOutput(content string) *h.Element {
|
||||||
|
return h.Div(
|
||||||
|
h.Class("hidden"),
|
||||||
|
h.Id("go-output-raw"),
|
||||||
|
h.UnsafeRaw(content),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func GoOutput(content string) *h.Element {
|
func GoOutput(content string) *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("h-full w-1/2 min-w-1/2"),
|
h.Class("h-full w-1/2 min-w-1/2"),
|
||||||
|
|
@ -43,25 +53,7 @@ func GoOutput(content string) *h.Element {
|
||||||
),
|
),
|
||||||
h.If(
|
h.If(
|
||||||
content != "",
|
content != "",
|
||||||
h.Div(
|
ui.CopyButton("#go-output-raw"),
|
||||||
h.Class("absolute top-0 right-0 p-2 bg-slate-800 text-white rounded-bl-md cursor-pointer"),
|
|
||||||
h.Text("Copy"),
|
|
||||||
h.OnClick(
|
|
||||||
// language=JavaScript
|
|
||||||
js.EvalJs(`
|
|
||||||
if(!navigator.clipboard) {
|
|
||||||
alert("Clipboard API not supported");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let text = self.parentElement.querySelector("#go-output-content").innerText;
|
|
||||||
navigator.clipboard.writeText(text);
|
|
||||||
self.innerText = "Copied!";
|
|
||||||
setTimeout(() => {
|
|
||||||
self.innerText = "Copy";
|
|
||||||
}, 1000);
|
|
||||||
`),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
178
htmgo-site/partials/snippets/click-to-edit.go
Normal file
178
htmgo-site/partials/snippets/click-to-edit.go
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
package snippets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RowClasses defined here for simplicity of the example
|
||||||
|
var RowClasses = "whitespace-nowrap px-4 py-4 font-medium text-gray-900 text-left"
|
||||||
|
var ButtonClasses = "inline-block rounded bg-slate-900 px-4 py-2 text-xs font-medium text-white hover:bg-slate-800"
|
||||||
|
var InputClasses = "-ml-2 max-w-[125px] border p-2 rounded focus:outline-none focus:ring focus:ring-slate-800"
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
Birthday string
|
||||||
|
Role string
|
||||||
|
Salary string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableProps struct {
|
||||||
|
EditingId string
|
||||||
|
}
|
||||||
|
|
||||||
|
var records = []Record{
|
||||||
|
{
|
||||||
|
Id: "1",
|
||||||
|
Name: "John Doe",
|
||||||
|
Birthday: "24/05/1995",
|
||||||
|
Role: "htmgo developer",
|
||||||
|
Salary: "$250,000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "2",
|
||||||
|
Name: "Jake Smith",
|
||||||
|
Birthday: "24/05/1995",
|
||||||
|
Role: "htmx developer",
|
||||||
|
Salary: "$100,000",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClickToEdit(ctx *h.RequestContext) *h.Partial {
|
||||||
|
return h.NewPartial(
|
||||||
|
h.Div(
|
||||||
|
h.Class("flex gap-2 items-center w-full"),
|
||||||
|
Table(TableProps{
|
||||||
|
// no record is being edited initially
|
||||||
|
EditingId: "",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartEditing is a partial that is called when the user clicks on the edit button,
|
||||||
|
// it will swap in the form for editing for the given record
|
||||||
|
func StartEditing(ctx *h.RequestContext) *h.Partial {
|
||||||
|
id := ctx.QueryParam("id")
|
||||||
|
|
||||||
|
record := h.Find(records, func(record *Record) bool {
|
||||||
|
return record.Id == id
|
||||||
|
})
|
||||||
|
|
||||||
|
if record == nil {
|
||||||
|
return h.EmptyPartial()
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.SwapManyPartial(
|
||||||
|
ctx,
|
||||||
|
TableRow(record, true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveEditing is a partial that is called when the user clicks on the save button while editing,
|
||||||
|
// it will update the record with the new values and swap it back out
|
||||||
|
// note: in the example, we are just creating a new record in memory instead of updating the existing one,
|
||||||
|
// normally you would update a persistent record in a database
|
||||||
|
func SaveEditing(ctx *h.RequestContext) *h.Partial {
|
||||||
|
id := ctx.QueryParam("id")
|
||||||
|
|
||||||
|
// just for the example, create a new record so it doesn't affect the global original
|
||||||
|
record := Record{
|
||||||
|
Id: id,
|
||||||
|
Name: ctx.FormValue("name"),
|
||||||
|
Birthday: ctx.FormValue("birthday"),
|
||||||
|
Role: ctx.FormValue("role"),
|
||||||
|
Salary: ctx.FormValue("salary"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.SwapPartial(ctx, TableRow(&record, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Table(props TableProps) *h.Element {
|
||||||
|
return h.Div(
|
||||||
|
h.Class("overflow-x-auto w-full"),
|
||||||
|
h.Table(
|
||||||
|
h.Class("divide-y divide-gray-200 bg-white table-fixed"),
|
||||||
|
h.THead(
|
||||||
|
h.Tr(
|
||||||
|
h.Th(
|
||||||
|
h.Class(RowClasses),
|
||||||
|
h.Text("Name"),
|
||||||
|
),
|
||||||
|
h.Th(
|
||||||
|
h.Class(RowClasses),
|
||||||
|
h.Text("Date of Birth"),
|
||||||
|
),
|
||||||
|
h.Th(
|
||||||
|
h.Class(RowClasses),
|
||||||
|
h.Text("Role"),
|
||||||
|
),
|
||||||
|
h.Th(
|
||||||
|
h.Class(RowClasses),
|
||||||
|
h.Text("Salary"),
|
||||||
|
),
|
||||||
|
h.Th(
|
||||||
|
h.Class("px-4 py-2"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h.TBody(
|
||||||
|
h.Class("divide-y divide-gray-200"),
|
||||||
|
h.List(records, func(record Record, index int) *h.Element {
|
||||||
|
editing := props.EditingId == record.Id
|
||||||
|
return TableRow(&record, editing)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TableRow(record *Record, editing bool) *h.Element {
|
||||||
|
recordId := fmt.Sprintf("record-%s", record.Id)
|
||||||
|
|
||||||
|
var Cell = func(name string, value string) *h.Element {
|
||||||
|
return h.Td(
|
||||||
|
h.Class(RowClasses, "h-[75px]"),
|
||||||
|
h.IfElse(
|
||||||
|
!editing,
|
||||||
|
h.Pf(
|
||||||
|
value,
|
||||||
|
h.Class("w-[125px]"),
|
||||||
|
),
|
||||||
|
h.Input(
|
||||||
|
"text",
|
||||||
|
h.Class(InputClasses),
|
||||||
|
h.Value(value),
|
||||||
|
h.Name(name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Tr(
|
||||||
|
h.If(
|
||||||
|
editing,
|
||||||
|
// this is important to make sure the inputs are included in the form submission
|
||||||
|
h.HxInclude("input"),
|
||||||
|
),
|
||||||
|
h.Id(recordId),
|
||||||
|
Cell("name", record.Name),
|
||||||
|
Cell("birthday", record.Birthday),
|
||||||
|
Cell("role", record.Role),
|
||||||
|
Cell("salary", record.Salary),
|
||||||
|
// Edit button
|
||||||
|
h.Td(
|
||||||
|
h.Button(
|
||||||
|
h.Class(ButtonClasses),
|
||||||
|
h.PostPartialWithQs(
|
||||||
|
h.Ternary(!editing, StartEditing, SaveEditing),
|
||||||
|
h.NewQs("id", record.Id),
|
||||||
|
),
|
||||||
|
h.Text(
|
||||||
|
h.Ternary(!editing, "Edit", "Save"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
28
htmgo-site/ui/copy.go
Normal file
28
htmgo-site/ui/copy.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
"github.com/maddalax/htmgo/framework/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CopyButton(selector string) *h.Element {
|
||||||
|
return h.Div(
|
||||||
|
h.Class("absolute top-0 right-0 p-2 bg-slate-800 text-white rounded-bl-md cursor-pointer"),
|
||||||
|
h.Text("Copy"),
|
||||||
|
h.OnClick(
|
||||||
|
// language=JavaScript
|
||||||
|
js.EvalJs(fmt.Sprintf(`
|
||||||
|
if(!navigator.clipboard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let text = document.querySelector("%s").innerText;
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
self.innerText = "Copied!";
|
||||||
|
setTimeout(() => {
|
||||||
|
self.innerText = "Copy";
|
||||||
|
}, 1000);
|
||||||
|
`, selector)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +20,7 @@ func FormatCode(code string, customStyles ...string) string {
|
||||||
html.WrapLongLines(true),
|
html.WrapLongLines(true),
|
||||||
html.WithLineNumbers(true),
|
html.WithLineNumbers(true),
|
||||||
html.WithCustomCSS(map[chroma.TokenType]string{
|
html.WithCustomCSS(map[chroma.TokenType]string{
|
||||||
chroma.PreWrapper: fmt.Sprintf("padding: 12px; overflow: auto; background-color: rgb(245, 245, 245) !important; %s", strings.Join(customStyles, ";")),
|
chroma.PreWrapper: fmt.Sprintf("font-size: 14px; padding: 12px; overflow: auto; background-color: rgb(245, 245, 245) !important; %s", strings.Join(customStyles, ";")),
|
||||||
}))
|
}))
|
||||||
iterator, err := lexer.Tokenise(nil, code)
|
iterator, err := lexer.Tokenise(nil, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -30,7 +31,17 @@ func FormatCode(code string, customStyles ...string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CodeSnippet(code string, customStyles ...string) *h.Element {
|
func CodeSnippet(code string, customStyles ...string) *h.Element {
|
||||||
|
id := fmt.Sprintf("code-snippet-%s", uuid.NewString())
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.UnsafeRaw(FormatCode(code, customStyles...)),
|
h.Class("relative"),
|
||||||
|
h.Div(
|
||||||
|
h.UnsafeRaw(code),
|
||||||
|
h.Class("hidden"),
|
||||||
|
h.Id(id),
|
||||||
|
),
|
||||||
|
CopyButton("#"+id),
|
||||||
|
h.UnsafeRaw(
|
||||||
|
FormatCode(code, customStyles...),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue