Files
bookhoard/templates/api_explorer_templ.go
T
john-okeefe afdcc7b589 docs: add API explorer template component and fix rendering
Phase 3 part 1: Add interactive API explorer
- Create templates/api_explorer.templ with mock and real modes
- Add EndpointInfo and APIExplorerData types to templates/types.go
- Fix markdown rendering with UnsafeHTML.ToComponent()
- Add templ import for Component support
- API explorer supports:
  - Mock data mode for non-authenticated users
  - Real execution mode for logged-in users
  - Request/response display
  - cURL generation
  - Copy to clipboard
2026-02-02 08:56:23 -05:00

186 lines
14 KiB
Go

// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func APIExplorer(explorer APIExplorerData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if !explorer.IsLoggedIn {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " <div class=\"api-explorer\"><div class=\"api-mode-toggle\"><button class=\"mode-btn active\">Mock Data</button> <button class=\"mode-btn\" disabled>Login to Try Real</button></div><div class=\"api-request-editor\"><div class=\"method-selector\"><select disabled><option value=\"GET\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "GET" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ">GET</option> <option value=\"POST\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "POST" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">POST</option> <option value=\"PUT\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "PUT" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ">PUT</option> <option value=\"DELETE\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "DELETE" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ">DELETE</option></select></div><h4>Request Body (Example)</h4><pre><code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.RequestBody)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 21, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</code></pre></div><div class=\"api-response\"><h4>Response (Mock)</h4><pre><code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.Response)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 25, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</code></pre></div><button onclick=\"copyToClipboard(JSON.stringify({ explorer.Endpoint.RequestBody }, null, 2))\">Copy Request</button> <button onclick=\"copyToClipboard(JSON.stringify({ explorer.Endpoint.Response }, null, 2))\">Copy Response</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " <div class=\"api-explorer\"><div class=\"api-mode-toggle\"><button class=\"mode-btn\" id=\"mock-btn\">Mock Data</button> <button class=\"mode-btn active\" id=\"real-btn\">Try It Out</button></div><div class=\"api-request-editor\"><div class=\"method-selector\"><select id=\"http-method\"><option value=\"GET\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "GET" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ">GET</option> <option value=\"POST\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "POST" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ">POST</option> <option value=\"PUT\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "PUT" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, ">PUT</option> <option value=\"DELETE\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if explorer.Endpoint.Method == "DELETE" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, ">DELETE</option></select></div><h4>Request Body</h4><textarea id=\"request-body\" placeholder=\"Edit request body...\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.RequestBody)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 47, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</textarea></div><button id=\"try-it-out\">Try It Out</button><div class=\"api-response\" style=\"display:none\"><div class=\"response-header\"><span id=\"response-status\"></span> <span id=\"response-time\"></span></div><pre><code id=\"response-body\"></code></pre></div><button onclick=\"copyRequest()\">Copy Request</button> <button onclick=\"copyResponse()\">Copy Response</button> <button onclick=\"generateCURL()\">Generate cURL</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<script>\n\t\tconst endpointPath = '{ explorer.Endpoint.Path }';\n\t\tconst exampleRequest = { explorer.Endpoint.RequestBody };\n\t\tconst exampleResponse = { explorer.Endpoint.Response };\n\n\t\t// Mode toggle logic\n\t\tdocument.getElementById('mock-btn')?.addEventListener('click', () => showMode('mock'));\n\t\tdocument.getElementById('real-btn')?.addEventListener('click', () => showMode('real'));\n\n\t\tfunction showMode(mode) {\n\t\t\tif (mode === 'mock') {\n\t\t\t\tdocument.querySelector('.api-response').style.display = 'block';\n\t\t\t\tdocument.getElementById('request-body').readOnly = true;\n\t\t\t\tdocument.getElementById('try-it-out').style.display = 'none';\n\t\t\t\tdocument.getElementById('response-body').textContent = JSON.stringify(exampleResponse, null, 2);\n\t\t\t\tdocument.getElementById('response-status').textContent = '200 OK';\n\t\t\t\tdocument.getElementById('response-time').textContent = 'Mock';\n\t\t\t} else {\n\t\t\t\tdocument.querySelector('.api-response').style.display = 'none';\n\t\t\t\tdocument.getElementById('request-body').readOnly = false;\n\t\t\t\tdocument.getElementById('try-it-out').style.display = 'block';\n\t\t\t}\n\t\t}\n\n\t\t// Try it out logic\n\t\tdocument.getElementById('try-it-out')?.addEventListener('click', async () => {\n\t\t\tconst method = document.getElementById('http-method').value;\n\t\t\tconst body = document.getElementById('request-body').value;\n\n\t\t\tconst startTime = Date.now();\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(endpointPath, {\n\t\t\t\t\tmethod: method,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'Authorization': 'Bearer ' + localStorage.getItem('token')\n\t\t\t\t\t},\n\t\t\t\t\tbody: ['GET', 'DELETE'].includes(method) ? undefined : body\n\t\t\t\t});\n\n\t\t\t\tconst duration = Date.now() - startTime;\n\t\t\t\tconst data = await response.json();\n\n\t\t\t\tdocument.getElementById('response-status').textContent = `${response.status} (${response.statusText})`;\n\t\t\t\tdocument.getElementById('response-time').textContent = `${duration}ms`;\n\t\t\t\tdocument.getElementById('response-body').textContent = JSON.stringify(data, null, 2);\n\t\t\t\tdocument.querySelector('.api-response').style.display = 'block';\n\t\t\t} catch (error) {\n\t\t\t\tdocument.getElementById('response-status').textContent = 'Error';\n\t\t\t\tdocument.getElementById('response-body').textContent = error.message;\n\t\t\t\tdocument.querySelector('.api-response').style.display = 'block';\n\t\t\t}\n\t\t});\n\n\t\tfunction copyToClipboard(text) {\n\t\t\tnavigator.clipboard.writeText(text);\n\t\t}\n\n\t\tfunction copyRequest() {\n\t\t\tconst body = document.getElementById('request-body').value;\n\t\t\tnavigator.clipboard.writeText(body);\n\t\t}\n\n\t\tfunction copyResponse() {\n\t\t\tconst body = document.getElementById('response-body').textContent;\n\t\t\tnavigator.clipboard.writeText(body);\n\t\t}\n\n\t\tfunction generateCURL() {\n\t\t\tconst method = document.getElementById('http-method').value;\n\t\t\tconst body = document.getElementById('request-body').value;\n\t\t\tconst token = localStorage.getItem('token');\n\n\t\t\tlet curl = `curl -X ${method} \\\\n -H \"Content-Type: application/json\" \\\\n -H \"Authorization: Bearer ${token}\"`;\n\t\t\t\n\t\t\tif (!['GET', 'DELETE'].includes(method) && body.trim()) {\n\t\t\t\tcurl += ` \\\\n -d '${body}'`;\n\t\t\t}\n\t\t\t\n\t\t\tcurl += ` \\\\n ${endpointPath}`;\n\t\t\t\n\t\t\tnavigator.clipboard.writeText(curl);\n\t\t}\n\t</script><style>\n\t\t.api-explorer {\n\t\t\tborder: 1px solid var(--border);\n\t\t\tborder-radius: 8px;\n\t\t\tpadding: 1.5rem;\n\t\t\tmargin-top: 2rem;\n\t\t\tbackground: var(--bg-secondary);\n\t\t}\n\n\t\t.api-mode-toggle {\n\t\t\tdisplay: flex;\n\t\t\tgap: 0.5rem;\n\t\t\tmargin-bottom: 1rem;\n\t\t}\n\n\t\t.mode-btn {\n\t\t\tpadding: 0.5rem 1rem;\n\t\t\tborder: 1px solid var(--border);\n\t\t\tborder-radius: 4px;\n\t\t\tbackground: var(--bg-primary);\n\t\t\tcolor: var(--text-primary);\n\t\t\tcursor: pointer;\n\t\t}\n\n\t\t.mode-btn.active {\n\t\t\tbackground: var(--accent);\n\t\t\tcolor: white;\n\t\t}\n\n\t\t.mode-btn:disabled {\n\t\t\topacity: 0.5;\n\t\t\tcursor: not-allowed;\n\t\t}\n\n\t\t.api-request-editor {\n\t\t\tmargin-bottom: 1rem;\n\t\t}\n\n\t\t.method-selector select {\n\t\t\tpadding: 0.5rem;\n\t\t\tborder-radius: 4px;\n\t\t\tbackground: var(--bg-primary);\n\t\t\tcolor: var(--text-primary);\n\t\t\tborder: 1px solid var(--border);\n\t\t\tmargin-bottom: 1rem;\n\t\t}\n\n\t\t.api-request-editor h4 {\n\t\t\tmargin-bottom: 0.5rem;\n\t\t\tcolor: var(--text-secondary);\n\t\t}\n\n\t\ttextarea#request-body {\n\t\t\twidth: 100%;\n\t\t\tmin-height: 150px;\n\t\t\tpadding: 0.75rem;\n\t\t\tfont-family: 'Monaco', 'Consolas', monospace;\n\t\t\tfont-size: 0.875rem;\n\t\t\tbackground: var(--bg-primary);\n\t\t\tcolor: var(--text-primary);\n\t\t\tborder: 1px solid var(--border);\n\t\t\tborder-radius: 4px;\n\t\t}\n\n\t\t.api-response {\n\t\t\tmargin-top: 1rem;\n\t\t}\n\n\t\t.response-header {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: space-between;\n\t\t\tmargin-bottom: 0.5rem;\n\t\t\tfont-size: 0.875rem;\n\t\t}\n\n\t\t#response-status {\n\t\t\tfont-weight: 600;\n\t\t}\n\n\t\t#response-time {\n\t\t\tcolor: var(--text-secondary);\n\t\t}\n\n\t\t.api-response pre {\n\t\t\tbackground: var(--bg-primary);\n\t\t\tpadding: 1rem;\n\t\t\tborder-radius: 4px;\n\t\t\toverflow-x: auto;\n\t\t}\n\n\t\t.api-response code {\n\t\t\tcolor: var(--text-primary);\n\t\t}\n\n\t\t.api-explorer button {\n\t\t\tpadding: 0.5rem 1rem;\n\t\t\tmargin-right: 0.5rem;\n\t\t\tmargin-top: 1rem;\n\t\t\tborder: 1px solid var(--border);\n\t\t\tborder-radius: 4px;\n\t\t\tbackground: var(--bg-primary);\n\t\t\tcolor: var(--text-primary);\n\t\t\tcursor: pointer;\n\t\t}\n\n\t\t#try-it-out {\n\t\t\tbackground: var(--accent);\n\t\t\tcolor: white;\n\t\t\tpadding: 0.75rem 1.5rem;\n\t\t\tborder: none;\n\t\t\tborder-radius: 4px;\n\t\t\tcursor: pointer;\n\t\t\tmargin-top: 1rem;\n\t\t}\n\n\t\t#try-it-out:hover {\n\t\t\topacity: 0.9;\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// APIExplorerData holds data for the API explorer component
type APIExplorerData struct {
Endpoint EndpointInfo
IsLoggedIn bool
}
// EndpointInfo holds information about an API endpoint
type EndpointInfo struct {
Method string
Path string
RequestBody string
Response string
Description string
}
var _ = templruntime.GeneratedTemplate