refactor: Update JSON parsing and schema inflation
- Use `json2.decode[json2.Any]` instead of `json2.raw_decode` - Add `@[required]` to procedure function signatures - Improve error handling for missing JSONRPC fields - Update `encode` to use `prettify: true` - Add checks for missing schema and content descriptor references
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
|
||||||
|
|
||||||
import incubaid.herolib.hero.heromodels
|
import incubaid.herolib.hero.heromodels
|
||||||
import incubaid.herolib.hero.db
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub fn json_dict_get_any(r string, clean bool, key string) !json2.Any {
|
|||||||
if r2.trim(' \n') == '' {
|
if r2.trim(' \n') == '' {
|
||||||
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata was empty.')
|
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata was empty.')
|
||||||
}
|
}
|
||||||
data_raw := json2.raw_decode(r2) or {
|
data_raw := json2.decode[json2.Any](r2) or {
|
||||||
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata:\n${r2}\nerror:${err}')
|
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata:\n${r2}\nerror:${err}')
|
||||||
}
|
}
|
||||||
mut res := data_raw.as_map()
|
mut res := data_raw.as_map()
|
||||||
@@ -74,7 +74,7 @@ pub fn json_dict_filter_any(r string, clean bool, include []string, exclude []st
|
|||||||
if r2.trim(' \n') == '' {
|
if r2.trim(' \n') == '' {
|
||||||
return error('Cannot do json2 raw decode in json_dict_filter_any.\ndata was empty.')
|
return error('Cannot do json2 raw decode in json_dict_filter_any.\ndata was empty.')
|
||||||
}
|
}
|
||||||
data_raw := json2.raw_decode(r2) or {
|
data_raw := json2.decode[json2.Any](r2) or {
|
||||||
return error('Cannot do json2 raw decode in json_dict_filter_any.\ndata:\n${r2}\nerror:${err}')
|
return error('Cannot do json2 raw decode in json_dict_filter_any.\ndata:\n${r2}\nerror:${err}')
|
||||||
}
|
}
|
||||||
mut res := data_raw.as_map()
|
mut res := data_raw.as_map()
|
||||||
@@ -111,7 +111,7 @@ pub fn json_list_dict_get_any(r string, clean bool, key string) ![]json2.Any {
|
|||||||
if r2.trim(' \n') == '' {
|
if r2.trim(' \n') == '' {
|
||||||
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata was empty.')
|
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata was empty.')
|
||||||
}
|
}
|
||||||
data_raw := json2.raw_decode(r2) or {
|
data_raw := json2.decode[json2.Any](r2) or {
|
||||||
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata:\n${r2}\nerror:${err}')
|
return error('Cannot do json2 raw decode in json_dict_get_any.\ndata:\n${r2}\nerror:${err}')
|
||||||
}
|
}
|
||||||
mut res_list := data_raw.arr()
|
mut res_list := data_raw.arr()
|
||||||
|
|||||||
@@ -188,6 +188,15 @@ pub fn (mut server HeroServer) doc_handler(mut ctx Context, handler_type string)
|
|||||||
return ctx.server_error('Failed to generate documentation: ${err}')
|
return ctx.server_error('Failed to generate documentation: ${err}')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create server info for navbar
|
||||||
|
server_info := HomePageData{
|
||||||
|
base_url: get_base_url_from_context(ctx)
|
||||||
|
handlers: server.handlers
|
||||||
|
auth_enabled: server.auth_enabled
|
||||||
|
host: server.host
|
||||||
|
port: server.port
|
||||||
|
}
|
||||||
|
|
||||||
// Load and process the HTML template using the literal path
|
// Load and process the HTML template using the literal path
|
||||||
html_content := $tmpl('templates/doc.html')
|
html_content := $tmpl('templates/doc.html')
|
||||||
|
|
||||||
|
|||||||
@@ -1,290 +1,59 @@
|
|||||||
module heroserver
|
module heroserver
|
||||||
|
|
||||||
import x.json2
|
|
||||||
import incubaid.herolib.schemas.jsonschema
|
import incubaid.herolib.schemas.jsonschema
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Constants
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// max_example_depth controls maximum recursion depth for nested schema examples
|
|
||||||
// to prevent infinite loops and keep examples readable
|
|
||||||
const max_example_depth = 4
|
|
||||||
|
|
||||||
// max_props_shallow controls maximum number of properties shown in object examples
|
|
||||||
// at shallow nesting levels (depth <= 2)
|
|
||||||
const max_props_shallow = 10
|
|
||||||
|
|
||||||
// max_props_deep controls maximum number of properties shown in object examples
|
|
||||||
// at deep nesting levels (depth > 2) to keep examples concise
|
|
||||||
const max_props_deep = 3
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// JSON Formatting
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// prettify_json takes a compact JSON string and returns it with pretty formatting.
|
|
||||||
// Uses V's built-in json2 module with 3-space indentation for consistent formatting.
|
|
||||||
// Falls back to the original string if parsing fails.
|
|
||||||
fn prettify_json(compact_json string) string {
|
|
||||||
parsed := json2.decode[json2.Any](compact_json) or { return compact_json }
|
|
||||||
return json2.encode(parsed, prettify: true, indent_string: ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Request Example Generation
|
// Request Example Generation
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// generate_curl_example creates a working curl command for a JSON-RPC method.
|
// generate_curl_example creates a curl command for a JSON-RPC method.
|
||||||
// Takes the method name, example request payload (params only), and endpoint URL.
|
|
||||||
// Returns a properly formatted curl command with JSON-RPC 2.0 wrapper.
|
// Returns a properly formatted curl command with JSON-RPC 2.0 wrapper.
|
||||||
pub fn generate_curl_example(method_name string, params_json string, endpoint_url string) string {
|
pub fn generate_curl_example(method_name string, params_json string, endpoint_url string) string {
|
||||||
// Build the complete JSON-RPC request
|
jsonrpc_request := '{"jsonrpc":"2.0","method":"${method_name}","params":${params_json},"id":1}'
|
||||||
mut jsonrpc_request := '{\n'
|
|
||||||
jsonrpc_request += ' "jsonrpc": "2.0",\n'
|
|
||||||
jsonrpc_request += ' "method": "${method_name}",\n'
|
|
||||||
jsonrpc_request += ' "params": '
|
|
||||||
|
|
||||||
// Add the params (already formatted with proper indentation)
|
|
||||||
// Need to indent each line by 2 spaces to align with the "params" key
|
|
||||||
params_lines := params_json.split('\n')
|
|
||||||
for i, line in params_lines {
|
|
||||||
if i == 0 {
|
|
||||||
jsonrpc_request += line
|
|
||||||
} else {
|
|
||||||
jsonrpc_request += '\n ' + line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add comma after params and then id field
|
|
||||||
jsonrpc_request += ',\n'
|
|
||||||
jsonrpc_request += ' "id": 1\n'
|
|
||||||
jsonrpc_request += '}'
|
|
||||||
|
|
||||||
// Escape single quotes for shell
|
|
||||||
escaped_request := jsonrpc_request.replace("'", "'\\''")
|
escaped_request := jsonrpc_request.replace("'", "'\\''")
|
||||||
|
return "curl -X POST '${endpoint_url}' -H 'Content-Type: application/json' -d '${escaped_request}'"
|
||||||
// Build curl command
|
|
||||||
mut curl := "curl -X POST '${endpoint_url}' \\\n"
|
|
||||||
curl += " -H 'Content-Type: application/json' \\\n"
|
|
||||||
curl += " -d '${escaped_request}'"
|
|
||||||
|
|
||||||
return curl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_request_example[T](model T) !string {
|
// generate_request_example generates a JSON example from DocParam array.
|
||||||
mut field_parts := []string{} // Build JSON manually to avoid type conflicts
|
// Extracts example values from the schema to create proper params.
|
||||||
|
// For single simple parameters, returns just the value.
|
||||||
for param in model {
|
// For multiple or complex parameters, returns a JSON object.
|
||||||
// Use schema-generated examples (already populated by extract_example_from_schema)
|
fn generate_request_example(params []DocParam) !string {
|
||||||
// The example field contains dynamically generated values based on actual schema
|
if params.len == 0 {
|
||||||
if param.example.len == 0 || param.example.trim_space() == '' {
|
|
||||||
return error('Parameter "${param.name}" has no example - schema may be missing')
|
|
||||||
}
|
|
||||||
field_parts << '"${param.name}":${param.example}'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build compact JSON string
|
|
||||||
if field_parts.len == 0 {
|
|
||||||
return '{}'
|
return '{}'
|
||||||
}
|
}
|
||||||
|
|
||||||
compact := '{${field_parts.join(',')}}'
|
// Single parameter with simple type (not object/array) - return just the value
|
||||||
|
if params.len == 1 {
|
||||||
|
example := params[0].example.trim_space()
|
||||||
|
if !example.starts_with('{') && !example.starts_with('[') {
|
||||||
|
return example
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prettify using V's built-in json2 formatter
|
// Multiple parameters or complex type - return JSON object
|
||||||
return prettify_json(compact)
|
mut parts := []string{}
|
||||||
|
for param in params {
|
||||||
|
parts << '"${param.name}":${param.example}'
|
||||||
|
}
|
||||||
|
return '{${parts.join(',')}}'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Schema-based Example Generation
|
// Schema Example Extraction
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// These functions generate examples from JSON Schema objects (used for response examples)
|
|
||||||
|
|
||||||
// extract_example_from_schema extracts or generates an example value from a SchemaRef.
|
// extract_example_from_schema extracts the example value from a schema.
|
||||||
// After schema inflation, all references should be resolved to Schema objects.
|
// If no example is defined, returns an empty placeholder.
|
||||||
// This function intelligently generates examples based on schema type and constraints.
|
|
||||||
// Returns a pretty-formatted JSON string for display in documentation.
|
|
||||||
pub fn extract_example_from_schema(schema_ref jsonschema.SchemaRef) string {
|
pub fn extract_example_from_schema(schema_ref jsonschema.SchemaRef) string {
|
||||||
compact := generate_example_from_schema_with_depth(schema_ref, 0, map[string]bool{})
|
|
||||||
return prettify_json(compact)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate_example_from_schema creates an example value for a parameter or result
|
|
||||||
pub fn generate_example_from_schema(schema_ref jsonschema.SchemaRef, param_name string) string {
|
|
||||||
compact := generate_example_from_schema_with_depth(schema_ref, 0, map[string]bool{})
|
|
||||||
return prettify_json(compact)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate_example_from_schema_with_depth recursively generates example values with depth limiting.
|
|
||||||
// Prevents infinite recursion from circular references by tracking depth and visited schemas.
|
|
||||||
// Max depth is controlled by max_example_depth constant to keep examples readable while showing structure.
|
|
||||||
fn generate_example_from_schema_with_depth(schema_ref jsonschema.SchemaRef, depth int, visited map[string]bool) string {
|
|
||||||
// Depth limit to prevent infinite recursion
|
|
||||||
// Return null for deeply nested structures to keep JSON valid
|
|
||||||
if depth > max_example_depth {
|
|
||||||
return 'null'
|
|
||||||
}
|
|
||||||
|
|
||||||
schema := match schema_ref {
|
schema := match schema_ref {
|
||||||
jsonschema.Schema {
|
jsonschema.Schema { schema_ref }
|
||||||
schema_ref
|
jsonschema.Reference { return '{}' }
|
||||||
}
|
|
||||||
jsonschema.Reference {
|
|
||||||
// After inflation, references should be resolved
|
|
||||||
// If we still encounter a reference, return a placeholder
|
|
||||||
return '"<unresolved_reference>"'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if schema has an explicit example - use it if available
|
|
||||||
// Note: json2.Any.str() returns '[]' or '{}' for empty/null values, so we filter those out
|
|
||||||
// We use json_str() instead of str() to get properly JSON-encoded values (with quotes for strings)
|
|
||||||
example_str := schema.example.json_str()
|
example_str := schema.example.json_str()
|
||||||
if example_str != '' && example_str != '[]' && example_str != '{}' && example_str != 'null' {
|
if example_str != '' && example_str != '[]' && example_str != '{}' && example_str != 'null' {
|
||||||
return example_str
|
return example_str
|
||||||
}
|
}
|
||||||
|
return '{}'
|
||||||
// Track visited schemas by their ID to prevent circular references
|
|
||||||
schema_id := schema.id
|
|
||||||
if schema_id != '' {
|
|
||||||
if schema_id in visited {
|
|
||||||
return 'null'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new visited map for this branch
|
|
||||||
mut new_visited := visited.clone()
|
|
||||||
if schema_id != '' {
|
|
||||||
new_visited[schema_id] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infer type from schema structure if not explicitly set
|
|
||||||
schema_type := if schema.typ != '' {
|
|
||||||
schema.typ
|
|
||||||
} else if schema.properties.len > 0 {
|
|
||||||
'object'
|
|
||||||
} else if schema.items != none {
|
|
||||||
'array'
|
|
||||||
} else {
|
|
||||||
''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate example based on schema type
|
|
||||||
match schema_type {
|
|
||||||
'string' {
|
|
||||||
// Use format hints if available
|
|
||||||
return match schema.format {
|
|
||||||
'date-time' {
|
|
||||||
'"2024-01-15T10:30:00Z"'
|
|
||||||
}
|
|
||||||
'date' {
|
|
||||||
'"2024-01-15"'
|
|
||||||
}
|
|
||||||
'time' {
|
|
||||||
'"10:30:00"'
|
|
||||||
}
|
|
||||||
'email' {
|
|
||||||
'"user@example.com"'
|
|
||||||
}
|
|
||||||
'uri', 'url' {
|
|
||||||
'"https://example.com"'
|
|
||||||
}
|
|
||||||
'uuid' {
|
|
||||||
'"550e8400-e29b-41d4-a716-446655440000"'
|
|
||||||
}
|
|
||||||
'ipv4' {
|
|
||||||
'"192.168.1.1"'
|
|
||||||
}
|
|
||||||
'ipv6' {
|
|
||||||
'"2001:0db8:85a3:0000:0000:8a2e:0370:7334"'
|
|
||||||
}
|
|
||||||
'hostname' {
|
|
||||||
'"example.com"'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Use schema title or description as hint, or generic placeholder
|
|
||||||
if schema.title != '' {
|
|
||||||
'"${schema.title}"'
|
|
||||||
} else if schema.description != '' && schema.description.len < 50 {
|
|
||||||
'"${schema.description}"'
|
|
||||||
} else {
|
|
||||||
'"Sample Text"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'integer', 'number' {
|
|
||||||
// Use minimum/maximum if specified
|
|
||||||
if schema.minimum > 0 {
|
|
||||||
return schema.minimum.str()
|
|
||||||
}
|
|
||||||
if schema.maximum > 0 {
|
|
||||||
return schema.maximum.str()
|
|
||||||
}
|
|
||||||
return '42'
|
|
||||||
}
|
|
||||||
'boolean' {
|
|
||||||
return 'true'
|
|
||||||
}
|
|
||||||
'null' {
|
|
||||||
return 'null'
|
|
||||||
}
|
|
||||||
'array' {
|
|
||||||
// Generate array with one example item (compact format)
|
|
||||||
if items := schema.items {
|
|
||||||
item_example := if items is jsonschema.SchemaRef {
|
|
||||||
generate_example_from_schema_with_depth(items, depth + 1, new_visited)
|
|
||||||
} else if items is []jsonschema.SchemaRef {
|
|
||||||
if items.len > 0 {
|
|
||||||
generate_example_from_schema_with_depth(items[0], depth + 1, new_visited)
|
|
||||||
} else {
|
|
||||||
'null'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
'null'
|
|
||||||
}
|
|
||||||
return '[${item_example}]'
|
|
||||||
}
|
|
||||||
return '[]'
|
|
||||||
}
|
|
||||||
'object' {
|
|
||||||
// Generate object with all properties (compact format)
|
|
||||||
if schema.properties.len > 0 {
|
|
||||||
mut props := []string{}
|
|
||||||
// Limit number of properties shown at deep levels
|
|
||||||
max_props := if depth > 2 { max_props_deep } else { max_props_shallow }
|
|
||||||
mut count := 0
|
|
||||||
|
|
||||||
for prop_name, prop_schema in schema.properties {
|
|
||||||
if count >= max_props {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
prop_example := generate_example_from_schema_with_depth(prop_schema,
|
|
||||||
depth + 1, new_visited)
|
|
||||||
props << '"${prop_name}":${prop_example}'
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
if props.len > 0 {
|
|
||||||
return '{${props.join(',')}}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Handle additionalProperties
|
|
||||||
if additional := schema.additional_properties {
|
|
||||||
value_example := generate_example_from_schema_with_depth(additional, depth + 1,
|
|
||||||
new_visited)
|
|
||||||
return '{"key":${value_example}}'
|
|
||||||
}
|
|
||||||
return '{}'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Handle oneOf - use first option
|
|
||||||
if schema.one_of.len > 0 {
|
|
||||||
return generate_example_from_schema_with_depth(schema.one_of[0], depth + 1,
|
|
||||||
new_visited)
|
|
||||||
}
|
|
||||||
// Unknown type
|
|
||||||
return 'null'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,470 +8,11 @@
|
|||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
@include 'partials/theme_styles'
|
||||||
<style>
|
|
||||||
/* CSS Variables for Light and Dark Themes */
|
|
||||||
:root {
|
|
||||||
/* Light mode colors */
|
|
||||||
--bg-primary: #ffffff;
|
|
||||||
--bg-secondary: #f8f9fa;
|
|
||||||
--bg-tertiary: #e9ecef;
|
|
||||||
--text-primary: #212529;
|
|
||||||
--text-secondary: #6c757d;
|
|
||||||
--text-muted: #6c757d;
|
|
||||||
--border-color: #dee2e6;
|
|
||||||
--border-color-light: #e9ecef;
|
|
||||||
--link-color: #0d6efd;
|
|
||||||
--link-hover-color: #0a58ca;
|
|
||||||
--code-bg: #f8f9fa;
|
|
||||||
--code-text: #212529;
|
|
||||||
--card-bg: #ffffff;
|
|
||||||
--card-header-bg: #f8f9fa;
|
|
||||||
--method-group-bg: #f8f9fa;
|
|
||||||
--method-endpoint-bg: #e3f2fd;
|
|
||||||
--toc-bg: #f8f9fa;
|
|
||||||
--toc-group-bg: #f8f9fa;
|
|
||||||
--alert-info-bg: #cfe2ff;
|
|
||||||
--alert-info-border: #b6d4fe;
|
|
||||||
--alert-info-text: #084298;
|
|
||||||
--badge-bg: #0d6efd;
|
|
||||||
--badge-text: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode colors */
|
|
||||||
body.dark-mode {
|
|
||||||
--bg-primary: #1a1a1a;
|
|
||||||
--bg-secondary: #2d2d2d;
|
|
||||||
--bg-tertiary: #3a3a3a;
|
|
||||||
--text-primary: #e9ecef;
|
|
||||||
--text-secondary: #adb5bd;
|
|
||||||
--text-muted: #adb5bd;
|
|
||||||
--border-color: #495057;
|
|
||||||
--border-color-light: #3a3a3a;
|
|
||||||
--link-color: #6ea8fe;
|
|
||||||
--link-hover-color: #9ec5fe;
|
|
||||||
--code-bg: #2d2d2d;
|
|
||||||
--code-text: #e9ecef;
|
|
||||||
--card-bg: #2d2d2d;
|
|
||||||
--card-header-bg: #3a3a3a;
|
|
||||||
--method-group-bg: #2d2d2d;
|
|
||||||
--method-endpoint-bg: #1e3a5f;
|
|
||||||
--toc-bg: #2d2d2d;
|
|
||||||
--toc-group-bg: #3a3a3a;
|
|
||||||
--alert-info-bg: #052c65;
|
|
||||||
--alert-info-border: #084298;
|
|
||||||
--alert-info-text: #6ea8fe;
|
|
||||||
--badge-bg: #0d6efd;
|
|
||||||
--badge-text: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Smooth transitions for theme changes */
|
|
||||||
body,
|
|
||||||
.card,
|
|
||||||
.card-header,
|
|
||||||
.method-group-section,
|
|
||||||
.toc,
|
|
||||||
.toc-group,
|
|
||||||
.code-block,
|
|
||||||
.alert,
|
|
||||||
pre,
|
|
||||||
.method-endpoint {
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-card {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
border-color: var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
background-color: var(--card-header-bg);
|
|
||||||
border-color: var(--border-color);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.param-table {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.param-table th,
|
|
||||||
.param-table td {
|
|
||||||
border-color: var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block {
|
|
||||||
background-color: var(--code-bg);
|
|
||||||
border: 1px solid var(--border-color-light);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
padding: 1rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
color: var(--code-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block pre {
|
|
||||||
background-color: var(--code-bg);
|
|
||||||
color: var(--code-text);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-section {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-endpoint {
|
|
||||||
background-color: var(--method-endpoint-bg);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc {
|
|
||||||
background-color: var(--toc-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc ul {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc a:hover {
|
|
||||||
color: var(--link-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
background-color: var(--code-bg);
|
|
||||||
color: var(--code-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.curl-section {
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button.copied {
|
|
||||||
background: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curl-command {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin: 0;
|
|
||||||
padding-right: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-header {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-header:hover {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
margin: -0.25rem -0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-toggle-icon {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-toggle-icon.collapsed {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reusable collapsible section styles */
|
|
||||||
.collapsible-header {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsible-header:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsible-toggle {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsible-toggle.collapsed {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-card .card-header {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-card .card-header:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-more-link {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #007bff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-more-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Equal height for example request/response sections */
|
|
||||||
.examples-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.examples-row>[class*='col-'] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.examples-row .code-block {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.examples-row .code-block pre {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Method group section styles */
|
|
||||||
.method-group-section {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
background-color: var(--method-group-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-header {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-header:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-toggle {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-toggle.collapsed {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-group-content .method-card {
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TOC group styles */
|
|
||||||
.toc-group {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
background-color: var(--toc-group-bg);
|
|
||||||
border: 1px solid var(--border-color-light);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-header {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-header:hover {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-toggle {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-toggle.collapsed {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-methods {
|
|
||||||
margin-left: 1.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toc-group-methods li {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme toggle button */
|
|
||||||
.theme-toggle {
|
|
||||||
position: fixed;
|
|
||||||
top: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
z-index: 1000;
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle svg {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
fill: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-muted {
|
|
||||||
color: var(--text-muted) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--link-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
background-color: var(--bg-secondary) !important;
|
|
||||||
color: var(--text-primary);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
background-color: var(--badge-bg) !important;
|
|
||||||
color: var(--badge-text) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Theme Toggle Button -->
|
@include 'partials/navbar'
|
||||||
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode" title="Toggle dark/light mode">
|
|
||||||
<svg id="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;">
|
|
||||||
<path
|
|
||||||
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" />
|
|
||||||
</svg>
|
|
||||||
<svg id="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -531,27 +72,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Authentication Info -->
|
|
||||||
<!-- <div class="row mb-4">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<h5>Authentication Required</h5>
|
|
||||||
<p>All API endpoints require authentication using a session key obtained through the authentication
|
|
||||||
flow:</p>
|
|
||||||
<ol>
|
|
||||||
@for step in spec.auth_info.steps
|
|
||||||
<li>
|
|
||||||
<strong>${step.title}</strong>:
|
|
||||||
<code>${step.method} ${step.endpoint}</code>
|
|
||||||
- ${step.description}
|
|
||||||
<div class="code-block mt-2">${step.example}</div>
|
|
||||||
</li>
|
|
||||||
@end
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- Table of Contents -->
|
<!-- Table of Contents -->
|
||||||
@if spec.methods.len > 0 || spec.objects.len > 0
|
@if spec.methods.len > 0 || spec.objects.len > 0
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
@@ -691,13 +211,21 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Example Request:</h6>
|
<h6>Example Request:</h6>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<pre>${method.example_request}</pre>
|
<button class="copy-button"
|
||||||
|
onclick="copyToClipboard('request-${method.name}', this)">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<pre id="request-${method.name}">${method.example_request}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Example Response:</h6>
|
<h6>Example Response:</h6>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<pre>${method.example_response}</pre>
|
<button class="copy-button"
|
||||||
|
onclick="copyToClipboard('response-${method.name}', this)">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<pre id="response-${method.name}">${method.example_response}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -792,17 +320,25 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
<!-- Examples -->
|
<!-- Examples -->
|
||||||
<div class="row mt-3">
|
<div class="row mt-3 examples-row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Example Request:</h6>
|
<h6>Example Request:</h6>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<pre>${method.example_request}</pre>
|
<button class="copy-button"
|
||||||
|
onclick="copyToClipboard('request-obj-${method.name}', this)">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<pre id="request-obj-${method.name}">${method.example_request}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Example Response:</h6>
|
<h6>Example Response:</h6>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<pre>${method.example_response}</pre>
|
<button class="copy-button"
|
||||||
|
onclick="copyToClipboard('response-obj-${method.name}', this)">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<pre id="response-obj-${method.name}">${method.example_response}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -810,10 +346,10 @@
|
|||||||
<!-- Curl Example -->
|
<!-- Curl Example -->
|
||||||
<div class="curl-section">
|
<div class="curl-section">
|
||||||
<h6>Curl Command:</h6>
|
<h6>Curl Command:</h6>
|
||||||
<button class="copy-button" onclick="copyToClipboard('curl-${method.name}', this)">
|
<button class="copy-button" onclick="copyToClipboard('curl-obj-${method.name}', this)">
|
||||||
📋 Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
<pre class="curl-command" id="curl-${method.name}">${method.curl_example}</pre>
|
<pre class="curl-command" id="curl-obj-${method.name}">${method.curl_example}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -824,66 +360,14 @@
|
|||||||
@end
|
@end
|
||||||
@end
|
@end
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer class="mt-5 py-4 bg-light">
|
|
||||||
<div class="container text-center">
|
|
||||||
<p class="mb-0">Generated from OpenRPC specification • ${spec.info.title}
|
|
||||||
v${spec.info.version}</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include 'partials/footer'
|
||||||
|
|
||||||
<!-- Bootstrap JS Bundle (includes Popper) -->
|
<!-- Bootstrap JS Bundle (includes Popper) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Theme Toggle Functionality -->
|
@include 'partials/theme_script'
|
||||||
<script>
|
|
||||||
// Theme management
|
|
||||||
function toggleTheme() {
|
|
||||||
const body = document.body;
|
|
||||||
const isDark = body.classList.toggle('dark-mode');
|
|
||||||
|
|
||||||
// Save preference to localStorage
|
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
||||||
|
|
||||||
// Update icon visibility
|
|
||||||
updateThemeIcon(isDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThemeIcon(isDark) {
|
|
||||||
const sunIcon = document.getElementById('theme-icon-sun');
|
|
||||||
const moonIcon = document.getElementById('theme-icon-moon');
|
|
||||||
|
|
||||||
if (isDark) {
|
|
||||||
sunIcon.style.display = 'block';
|
|
||||||
moonIcon.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
sunIcon.style.display = 'none';
|
|
||||||
moonIcon.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeTheme() {
|
|
||||||
// Check localStorage for saved preference
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
|
|
||||||
// If no saved preference, check system preference
|
|
||||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
|
|
||||||
// Apply theme
|
|
||||||
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
|
|
||||||
|
|
||||||
if (shouldBeDark) {
|
|
||||||
document.body.classList.add('dark-mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update icon
|
|
||||||
updateThemeIcon(shouldBeDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize theme on page load
|
|
||||||
initializeTheme();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Copy to Clipboard Functionality -->
|
<!-- Copy to Clipboard Functionality -->
|
||||||
<script>
|
<script>
|
||||||
@@ -1023,8 +507,132 @@
|
|||||||
: 'Show less ▲';
|
: 'Show less ▲';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON Prettification
|
||||||
|
function prettifyJSON(jsonString) {
|
||||||
|
try {
|
||||||
|
// Try to parse and prettify the JSON
|
||||||
|
const parsed = JSON.parse(jsonString);
|
||||||
|
return JSON.stringify(parsed, null, 3); // 3 spaces indentation
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, return original string
|
||||||
|
return jsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettifyCurlJSON(curlCommand) {
|
||||||
|
try {
|
||||||
|
// Extract JSON from curl -d '...' part
|
||||||
|
const match = curlCommand.match(/-d\s+'([^']+)'/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const jsonStr = match[1];
|
||||||
|
const parsed = JSON.parse(jsonStr);
|
||||||
|
const prettified = JSON.stringify(parsed, null, 2); // 2 spaces for curl
|
||||||
|
|
||||||
|
// Replace the compact JSON with prettified version
|
||||||
|
// Escape single quotes in the prettified JSON
|
||||||
|
const escapedPrettified = prettified.replace(/'/g, "'\\''");
|
||||||
|
return curlCommand.replace(jsonStr, escapedPrettified);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, return original
|
||||||
|
}
|
||||||
|
return curlCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettifyAllJSON() {
|
||||||
|
// Prettify example requests and responses
|
||||||
|
document.querySelectorAll('.code-block pre').forEach(pre => {
|
||||||
|
const content = pre.textContent.trim();
|
||||||
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
||||||
|
pre.textContent = prettifyJSON(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prettify parameter examples in tables
|
||||||
|
document.querySelectorAll('.param-table code.text-muted').forEach(code => {
|
||||||
|
const content = code.textContent.trim();
|
||||||
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
||||||
|
code.textContent = prettifyJSON(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prettify result examples
|
||||||
|
document.querySelectorAll('.alert-light code.text-muted').forEach(code => {
|
||||||
|
const content = code.textContent.trim();
|
||||||
|
if (content && (content.startsWith('{') || content.startsWith('['))) {
|
||||||
|
code.textContent = prettifyJSON(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prettify JSON in curl commands
|
||||||
|
document.querySelectorAll('.curl-command').forEach(pre => {
|
||||||
|
const content = pre.textContent.trim();
|
||||||
|
if (content.includes('curl') && content.includes('-d')) {
|
||||||
|
pre.textContent = prettifyCurlJSON(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to method and expand it
|
||||||
|
function scrollToMethod(methodName) {
|
||||||
|
// Find the method card
|
||||||
|
const methodCard = document.getElementById('method-' + methodName);
|
||||||
|
if (!methodCard) return;
|
||||||
|
|
||||||
|
// Find which group this method belongs to
|
||||||
|
const methodGroup = methodCard.closest('.method-group-content');
|
||||||
|
if (methodGroup) {
|
||||||
|
const groupId = methodGroup.id;
|
||||||
|
const groupIdx = groupId.replace('group-', '');
|
||||||
|
|
||||||
|
// Expand the group if collapsed
|
||||||
|
const bsGroupCollapse = bootstrap.Collapse.getOrCreateInstance(methodGroup);
|
||||||
|
if (!methodGroup.classList.contains('show')) {
|
||||||
|
bsGroupCollapse.show();
|
||||||
|
const groupToggle = document.getElementById('group-toggle-' + groupIdx);
|
||||||
|
if (groupToggle) {
|
||||||
|
groupToggle.classList.remove('collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the method body
|
||||||
|
const methodBody = methodCard.querySelector('[id^="method-body-"]');
|
||||||
|
if (methodBody) {
|
||||||
|
// Expand the method if collapsed
|
||||||
|
const bsMethodCollapse = bootstrap.Collapse.getOrCreateInstance(methodBody);
|
||||||
|
if (!methodBody.classList.contains('show')) {
|
||||||
|
bsMethodCollapse.show();
|
||||||
|
const bodyId = methodBody.id;
|
||||||
|
const toggleIcon = document.getElementById('toggle-' + bodyId.replace('method-body-', ''));
|
||||||
|
if (toggleIcon) {
|
||||||
|
toggleIcon.classList.remove('collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to the method with smooth animation
|
||||||
|
setTimeout(() => {
|
||||||
|
methodCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}, 300); // Wait for collapse animations to complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle hash navigation on page load and hash change
|
||||||
|
function handleHashNavigation() {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (hash && hash.startsWith('#method-')) {
|
||||||
|
const methodName = hash.replace('#method-', '');
|
||||||
|
scrollToMethod(methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Table of Contents toggle functionality
|
// Table of Contents toggle functionality
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Prettify all JSON on page load
|
||||||
|
prettifyAllJSON();
|
||||||
|
|
||||||
|
// Handle hash changes (when clicking TOC links)
|
||||||
|
window.addEventListener('hashchange', handleHashNavigation);
|
||||||
const tocContent = document.getElementById('tocContent');
|
const tocContent = document.getElementById('tocContent');
|
||||||
const tocHeader = document.querySelector('.toc-header');
|
const tocHeader = document.querySelector('.toc-header');
|
||||||
const tocIcon = document.querySelector('.toc-toggle-icon');
|
const tocIcon = document.querySelector('.toc-toggle-icon');
|
||||||
@@ -1095,6 +703,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initialize method cards - expand only the first method in the first group
|
// Initialize method cards - expand only the first method in the first group
|
||||||
|
// BUT: if there's a hash in the URL, don't expand the first method (hash navigation will handle it)
|
||||||
|
const hasHash = window.location.hash && window.location.hash.startsWith('#method-');
|
||||||
const methodBodies = document.querySelectorAll('[id^="method-body-"]');
|
const methodBodies = document.querySelectorAll('[id^="method-body-"]');
|
||||||
methodBodies.forEach((body) => {
|
methodBodies.forEach((body) => {
|
||||||
const bodyId = body.id;
|
const bodyId = body.id;
|
||||||
@@ -1103,8 +713,8 @@
|
|||||||
const methodIdx = parts[1];
|
const methodIdx = parts[1];
|
||||||
const toggleIcon = document.getElementById('toggle-' + groupIdx + '-' + methodIdx);
|
const toggleIcon = document.getElementById('toggle-' + groupIdx + '-' + methodIdx);
|
||||||
|
|
||||||
if (groupIdx === '0' && methodIdx === '0') {
|
if (!hasHash && groupIdx === '0' && methodIdx === '0') {
|
||||||
// First method in first group - expand it
|
// First method in first group - expand it (only if no hash)
|
||||||
const bsCollapse = new bootstrap.Collapse(body, { toggle: false });
|
const bsCollapse = new bootstrap.Collapse(body, { toggle: false });
|
||||||
bsCollapse.show();
|
bsCollapse.show();
|
||||||
if (toggleIcon) {
|
if (toggleIcon) {
|
||||||
@@ -1117,6 +727,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle initial hash navigation AFTER all initialization is complete
|
||||||
|
// This ensures the hash navigation can properly expand the target method
|
||||||
|
if (hasHash) {
|
||||||
|
// Use setTimeout to ensure all Bootstrap collapse instances are initialized
|
||||||
|
setTimeout(() => {
|
||||||
|
handleHashNavigation();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,192 +8,11 @@
|
|||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
@include 'partials/theme_styles'
|
||||||
<style>
|
|
||||||
/* Smooth transitions for theme changes */
|
|
||||||
body,
|
|
||||||
.card,
|
|
||||||
.alert,
|
|
||||||
.btn,
|
|
||||||
.navbar,
|
|
||||||
.dropdown-menu {
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode overrides */
|
|
||||||
body.dark-mode {
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .navbar {
|
|
||||||
background-color: #2d2d2d !important;
|
|
||||||
border-bottom-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .card {
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
border-color: #495057;
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .bg-light {
|
|
||||||
background-color: #3a3a3a !important;
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode pre,
|
|
||||||
body.dark-mode code {
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
color: #e9ecef;
|
|
||||||
border-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .text-muted {
|
|
||||||
color: #adb5bd !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .border,
|
|
||||||
body.dark-mode .border-top {
|
|
||||||
border-color: #495057 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .dropdown-menu {
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
border-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .dropdown-item {
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .dropdown-item:hover {
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .dropdown-header {
|
|
||||||
color: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .navbar-toggler {
|
|
||||||
border-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .navbar-toggler-icon {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .nav-link {
|
|
||||||
color: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .nav-link:hover {
|
|
||||||
color: #6ea8fe;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .navbar-brand {
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme toggle button in navbar */
|
|
||||||
.theme-toggle-navbar {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .theme-toggle-navbar {
|
|
||||||
border-color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-navbar:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .theme-toggle-navbar:hover {
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-navbar svg {
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive improvements */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.theme-toggle-navbar {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Navigation Bar -->
|
@include 'partials/navbar'
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom sticky-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand fw-semibold" href="/">
|
|
||||||
HeroServer
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
|
||||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto align-items-center">
|
|
||||||
@if server_info.handlers.len > 0
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
API Documentation
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
@for handler_name, _ in server_info.handlers
|
|
||||||
<li>
|
|
||||||
<h6 class="dropdown-header">${handler_name.to_upper()}</h6>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item" href="/doc/${handler_name}">Documentation</a></li>
|
|
||||||
<li><a class="dropdown-item" href="/json/${handler_name}">OpenRPC JSON</a></li>
|
|
||||||
<li><a class="dropdown-item" href="/md/${handler_name}">Markdown Docs</a></li>
|
|
||||||
@end
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
@end
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="#features">Features</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="#endpoints">Endpoints</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button class="theme-toggle-navbar" onclick="toggleTheme()" aria-label="Toggle dark mode"
|
|
||||||
title="Toggle dark/light mode">
|
|
||||||
<svg id="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
|
||||||
style="display: none;">
|
|
||||||
<path
|
|
||||||
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" />
|
|
||||||
</svg>
|
|
||||||
<svg id="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="py-5 text-center">
|
<div class="py-5 text-center">
|
||||||
@@ -328,10 +147,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Available Endpoints Section -->
|
<!-- Available Endpoints Section -->
|
||||||
<div class="row mb-5" id="endpoints">
|
<div class="row mb-4" id="endpoints">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2 class="mb-3">Available API Endpoints</h2>
|
<h2 class="mb-3">Available API Endpoints</h2>
|
||||||
<p class="text-muted mb-4">Explore the available API handlers and their documentation</p>
|
<p class="text-muted">Explore the available API handlers and their documentation</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -362,7 +181,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<a href="/doc/${handler_name}" class="btn btn-primary btn-sm">
|
<a href="/doc/${handler_name}" class="btn btn-outline-secondary btn-sm">
|
||||||
View Documentation
|
View Documentation
|
||||||
</a>
|
</a>
|
||||||
<a href="/json/${handler_name}" class="btn btn-outline-secondary btn-sm">
|
<a href="/json/${handler_name}" class="btn btn-outline-secondary btn-sm">
|
||||||
@@ -447,69 +266,12 @@
|
|||||||
@end
|
@end
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
@include 'partials/footer'
|
||||||
<footer class="border-top py-4 mt-5">
|
|
||||||
<div class="container text-center">
|
|
||||||
<p class="text-muted mb-0">
|
|
||||||
<strong>HeroServer</strong> - Built with V language •
|
|
||||||
<a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none">
|
|
||||||
View on GitHub
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Theme Toggle Functionality -->
|
@include 'partials/theme_script'
|
||||||
<script>
|
|
||||||
// Theme management
|
|
||||||
function toggleTheme() {
|
|
||||||
const body = document.body;
|
|
||||||
const isDark = body.classList.toggle('dark-mode');
|
|
||||||
|
|
||||||
// Save preference to localStorage
|
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
||||||
|
|
||||||
// Update icon visibility
|
|
||||||
updateThemeIcon(isDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThemeIcon(isDark) {
|
|
||||||
const sunIcon = document.getElementById('theme-icon-sun');
|
|
||||||
const moonIcon = document.getElementById('theme-icon-moon');
|
|
||||||
|
|
||||||
if (isDark) {
|
|
||||||
sunIcon.style.display = 'block';
|
|
||||||
moonIcon.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
sunIcon.style.display = 'none';
|
|
||||||
moonIcon.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeTheme() {
|
|
||||||
// Check localStorage for saved preference
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
|
|
||||||
// If no saved preference, check system preference
|
|
||||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
|
|
||||||
// Apply theme
|
|
||||||
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
|
|
||||||
|
|
||||||
if (shouldBeDark) {
|
|
||||||
document.body.classList.add('dark-mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update icon
|
|
||||||
updateThemeIcon(shouldBeDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize theme on page load
|
|
||||||
initializeTheme();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
12
lib/hero/heroserver/templates/partials/footer.html
Normal file
12
lib/hero/heroserver/templates/partials/footer.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!-- Footer -->
|
||||||
|
<footer class="border-top py-4 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
<strong>HeroServer</strong> - Built with V language •
|
||||||
|
<a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none">
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
55
lib/hero/heroserver/templates/partials/navbar.html
Normal file
55
lib/hero/heroserver/templates/partials/navbar.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<!-- Navigation Bar -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom sticky-top">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand fw-semibold" href="/">
|
||||||
|
HeroServer
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav ms-auto align-items-center">
|
||||||
|
@if server_info.handlers.len > 0
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
|
||||||
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
API Documentation
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
|
@for handler_name, _ in server_info.handlers
|
||||||
|
<li>
|
||||||
|
<h6 class="dropdown-header">${handler_name.to_upper()}</h6>
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item" href="/doc/${handler_name}">Documentation</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/json/${handler_name}">OpenRPC JSON</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/md/${handler_name}">Markdown Docs</a></li>
|
||||||
|
@end
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
@end
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/#features">Features</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/#endpoints">Endpoints</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="theme-toggle-navbar" onclick="toggleTheme()" aria-label="Toggle dark mode"
|
||||||
|
title="Toggle dark/light mode">
|
||||||
|
<svg id="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
|
style="display: none;">
|
||||||
|
<path
|
||||||
|
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" />
|
||||||
|
</svg>
|
||||||
|
<svg id="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
40
lib/hero/heroserver/templates/partials/theme_script.html
Normal file
40
lib/hero/heroserver/templates/partials/theme_script.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!-- Theme Toggle Functionality -->
|
||||||
|
<script>
|
||||||
|
function toggleTheme() {
|
||||||
|
const body = document.body;
|
||||||
|
const sunIcon = document.getElementById('theme-icon-sun');
|
||||||
|
const moonIcon = document.getElementById('theme-icon-moon');
|
||||||
|
|
||||||
|
body.classList.toggle('dark-mode');
|
||||||
|
|
||||||
|
if (body.classList.contains('dark-mode')) {
|
||||||
|
// In dark mode, show sun icon (to switch to light)
|
||||||
|
sunIcon.style.display = 'block';
|
||||||
|
moonIcon.style.display = 'none';
|
||||||
|
localStorage.setItem('theme', 'dark');
|
||||||
|
} else {
|
||||||
|
// In light mode, show moon icon (to switch to dark)
|
||||||
|
sunIcon.style.display = 'none';
|
||||||
|
moonIcon.style.display = 'block';
|
||||||
|
localStorage.setItem('theme', 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load saved theme on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
const sunIcon = document.getElementById('theme-icon-sun');
|
||||||
|
const moonIcon = document.getElementById('theme-icon-moon');
|
||||||
|
|
||||||
|
if (savedTheme === 'dark') {
|
||||||
|
document.body.classList.add('dark-mode');
|
||||||
|
// In dark mode, show sun icon (to switch to light)
|
||||||
|
sunIcon.style.display = 'block';
|
||||||
|
moonIcon.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
// In light mode, show moon icon (to switch to dark)
|
||||||
|
sunIcon.style.display = 'none';
|
||||||
|
moonIcon.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
443
lib/hero/heroserver/templates/partials/theme_styles.html
Normal file
443
lib/hero/heroserver/templates/partials/theme_styles.html
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
<!-- Smooth transitions for theme changes -->
|
||||||
|
<style>
|
||||||
|
body,
|
||||||
|
.card,
|
||||||
|
.alert,
|
||||||
|
.btn,
|
||||||
|
.navbar,
|
||||||
|
.dropdown-menu {
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides */
|
||||||
|
body.dark-mode {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .navbar {
|
||||||
|
background-color: #2d2d2d !important;
|
||||||
|
border-bottom-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .card {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .bg-light {
|
||||||
|
background-color: #3a3a3a !important;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode pre,
|
||||||
|
body.dark-mode code {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #e9ecef;
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .text-muted {
|
||||||
|
color: #adb5bd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .border,
|
||||||
|
body.dark-mode .border-top,
|
||||||
|
body.dark-mode hr {
|
||||||
|
border-color: #495057 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .dropdown-menu {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .dropdown-item {
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .dropdown-item:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .dropdown-header {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .navbar-toggler {
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .navbar-toggler-icon {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .nav-link {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .nav-link:hover {
|
||||||
|
color: #6ea8fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .navbar-brand {
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables in dark mode */
|
||||||
|
body.dark-mode .table {
|
||||||
|
color: #e9ecef;
|
||||||
|
border-color: #495057;
|
||||||
|
--bs-table-bg: transparent;
|
||||||
|
--bs-table-striped-bg: #2d2d2d;
|
||||||
|
--bs-table-hover-bg: #3a3a3a;
|
||||||
|
--bs-table-border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .table th,
|
||||||
|
body.dark-mode .table td {
|
||||||
|
border-color: #495057;
|
||||||
|
color: #e9ecef;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .table thead th {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .table tbody tr {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .table tbody tr:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges in dark mode */
|
||||||
|
body.dark-mode .badge {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .badge.bg-secondary {
|
||||||
|
background-color: #6c757d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .badge.bg-danger {
|
||||||
|
background-color: #dc3545 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .badge.bg-success {
|
||||||
|
background-color: #198754 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .badge.bg-primary {
|
||||||
|
background-color: #0d6efd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts in dark mode */
|
||||||
|
body.dark-mode .alert-light {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .alert-info {
|
||||||
|
background-color: #052c65;
|
||||||
|
border-color: #084298;
|
||||||
|
color: #6ea8fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links in dark mode */
|
||||||
|
body.dark-mode a {
|
||||||
|
color: #6ea8fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode a:hover {
|
||||||
|
color: #8bb9fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme toggle button in navbar */
|
||||||
|
.theme-toggle-navbar {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .theme-toggle-navbar {
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle-navbar:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .theme-toggle-navbar:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle-navbar svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sun icon should be light in dark mode */
|
||||||
|
body.dark-mode .theme-toggle-navbar svg {
|
||||||
|
fill: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Moon icon should be dark in light mode */
|
||||||
|
body:not(.dark-mode) .theme-toggle-navbar svg {
|
||||||
|
fill: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive improvements */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.theme-toggle-navbar {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table of Contents */
|
||||||
|
.toc {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .toc {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-header:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .toc-header:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-toggle-icon {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-toggle-icon.collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-header {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-header:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .toc-group-header:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-toggle {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-toggle.collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-methods {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-group-methods li {
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Method Groups */
|
||||||
|
.method-group-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-group-header {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .method-group-header {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-group-header:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .method-group-header:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-group-toggle {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-group-toggle.collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Method Cards */
|
||||||
|
.method-card .card-header {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-card .card-header:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .method-card .card-header:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-toggle {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-toggle.collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-endpoint {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .method-endpoint {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
.code-block {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .code-block {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #495057;
|
||||||
|
color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy button */
|
||||||
|
.copy-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button.copied {
|
||||||
|
background-color: #198754;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Curl section */
|
||||||
|
.curl-section {
|
||||||
|
margin-top: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.curl-command {
|
||||||
|
background-color: #212529;
|
||||||
|
color: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .curl-command {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
border: 1px solid #495057;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -86,13 +86,13 @@ pub fn (mut handler Handler) register_api_handler(groupname string, procedure_gr
|
|||||||
pub struct Procedure[T, U] {
|
pub struct Procedure[T, U] {
|
||||||
pub mut:
|
pub mut:
|
||||||
method string
|
method string
|
||||||
function fn (T) !U
|
function fn (T) !U @[required]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcedureVoid[T] {
|
pub struct ProcedureVoid[T] {
|
||||||
pub mut:
|
pub mut:
|
||||||
method string
|
method string
|
||||||
function fn (T) !
|
function fn (T) ! @[required]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (pw Procedure[T, U]) handle(request Request) !Response {
|
pub fn (pw Procedure[T, U]) handle(request Request) !Response {
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ pub fn new_request(method string, params string) Request {
|
|||||||
pub fn decode_request(data string) !Request {
|
pub fn decode_request(data string) !Request {
|
||||||
mut r2 := json2.decode[json2.Any](data)!
|
mut r2 := json2.decode[json2.Any](data)!
|
||||||
mut r3 := r2.as_map()
|
mut r3 := r2.as_map()
|
||||||
a := r3['jsonrpc'].str()
|
a := (r3['jsonrpc'] or { return error('jsonrpc field not found') }).str()
|
||||||
b := r3['method'].str()
|
b := (r3['method'] or { return error('method field not found') }).str()
|
||||||
c := r3['params'].str()
|
c := (r3['params'] or { return error('params field not found') }).str()
|
||||||
d := r3['id'].int()
|
d := (r3['id'] or { return error('id field not found') }).int()
|
||||||
mut r4 := Request{
|
mut r4 := Request{
|
||||||
jsonrpc: a
|
jsonrpc: a
|
||||||
method: b
|
method: b
|
||||||
@@ -70,7 +70,7 @@ pub fn decode_request(data string) !Request {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - A JSON string representation of the Request
|
// - A JSON string representation of the Request
|
||||||
pub fn (req Request) encode() string {
|
pub fn (req Request) encode() string {
|
||||||
return json2.encode_pretty(req)
|
return json2.encode(req, prettify: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate checks if the Request object contains all required fields
|
// validate checks if the Request object contains all required fields
|
||||||
@@ -133,7 +133,7 @@ pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - The ID as a string, or an error if the ID field is missing
|
// - The ID as a string, or an error if the ID field is missing
|
||||||
pub fn decode_request_id(data string) !int {
|
pub fn decode_request_id(data string) !int {
|
||||||
data_any := json2.raw_decode(data)!
|
data_any := json2.decode[json2.Any](data)!
|
||||||
data_map := data_any.as_map()
|
data_map := data_any.as_map()
|
||||||
id_any := data_map['id'] or { return error('ID field not found') }
|
id_any := data_map['id'] or { return error('ID field not found') }
|
||||||
return id_any.int()
|
return id_any.int()
|
||||||
@@ -148,7 +148,7 @@ pub fn decode_request_id(data string) !int {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - The method name as a string, or an error if the method field is missing
|
// - The method name as a string, or an error if the method field is missing
|
||||||
pub fn decode_request_method(data string) !string {
|
pub fn decode_request_method(data string) !string {
|
||||||
data_any := json2.raw_decode(data)!
|
data_any := json2.decode[json2.Any](data)!
|
||||||
data_map := data_any.as_map()
|
data_map := data_any.as_map()
|
||||||
method_any := data_map['method'] or { return error('Method field not found') }
|
method_any := data_map['method'] or { return error('Method field not found') }
|
||||||
return method_any.str()
|
return method_any.str()
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ pub fn new_error_response(id int, error RPCError) Response {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - A Response object or an error if parsing fails or the response is invalid
|
// - A Response object or an error if parsing fails or the response is invalid
|
||||||
pub fn decode_response(data string) !Response {
|
pub fn decode_response(data string) !Response {
|
||||||
raw := json2.raw_decode(data) or {
|
raw := json2.decode[json2.Any](data) or {
|
||||||
return error('Failed to decode JSONRPC response ${data}\n${err}')
|
return error('Failed to decode JSONRPC response ${data}\n${err}')
|
||||||
}
|
}
|
||||||
raw_map := raw.as_map()
|
raw_map := raw.as_map()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import json
|
|||||||
// Handles complex fields like properties, additionalProperties, items, and examples
|
// Handles complex fields like properties, additionalProperties, items, and examples
|
||||||
// that require custom parsing beyond standard JSON decoding.
|
// that require custom parsing beyond standard JSON decoding.
|
||||||
pub fn decode(data string) !Schema {
|
pub fn decode(data string) !Schema {
|
||||||
schema_map := json2.raw_decode(data)!.as_map()
|
schema_map := json2.decode[Any](data)!.as_map()
|
||||||
mut schema := json.decode(Schema, data)!
|
mut schema := json.decode(Schema, data)!
|
||||||
|
|
||||||
// Process fields that require custom decoding
|
// Process fields that require custom decoding
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub fn decode(data string) !OpenRPC {
|
|||||||
mut object := json.decode(OpenRPC, data) or {
|
mut object := json.decode(OpenRPC, data) or {
|
||||||
return error('Failed to decode json\n=======\n${data}\n===========\n${err}')
|
return error('Failed to decode json\n=======\n${data}\n===========\n${err}')
|
||||||
}
|
}
|
||||||
data_map := json2.raw_decode(data)!.as_map()
|
data_map := json2.decode[Any](data)!.as_map()
|
||||||
if 'components' in data_map {
|
if 'components' in data_map {
|
||||||
object.components = decode_components(data_map) or {
|
object.components = decode_components(data_map) or {
|
||||||
return error('Failed to decode components\n${err}')
|
return error('Failed to decode components\n${err}')
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ pub fn (s OpenRPC) inflate_method(method Method) Method {
|
|||||||
|
|
||||||
pub fn (s OpenRPC) inflate_content_descriptor(cd_ ContentDescriptorRef) ContentDescriptor {
|
pub fn (s OpenRPC) inflate_content_descriptor(cd_ ContentDescriptorRef) ContentDescriptor {
|
||||||
cd := if cd_ is Reference {
|
cd := if cd_ is Reference {
|
||||||
s.components.content_descriptors[cd_.ref] as ContentDescriptor
|
ref_key := cd_.ref
|
||||||
|
descriptor_ref := s.components.content_descriptors[ref_key] or {
|
||||||
|
panic('Content descriptor not found: ${ref_key}')
|
||||||
|
}
|
||||||
|
descriptor_ref as ContentDescriptor
|
||||||
} else {
|
} else {
|
||||||
cd_ as ContentDescriptor
|
cd_ as ContentDescriptor
|
||||||
}
|
}
|
||||||
@@ -35,7 +39,9 @@ pub fn (s OpenRPC) inflate_schema(schema_ref SchemaRef) Schema {
|
|||||||
panic('not implemented')
|
panic('not implemented')
|
||||||
}
|
}
|
||||||
schema_name := schema_ref.ref.trim_string_left('#/components/schemas/')
|
schema_name := schema_ref.ref.trim_string_left('#/components/schemas/')
|
||||||
s.inflate_schema(s.components.schemas[schema_name])
|
s.inflate_schema(s.components.schemas[schema_name] or {
|
||||||
|
panic('Schema not found: ${schema_name}')
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
schema_ref as Schema
|
schema_ref as Schema
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user