diff --git a/_examples/multi_ui/api.go b/_examples/multi_ui/api.go index f240f97..a724be7 100644 --- a/_examples/multi_ui/api.go +++ b/_examples/multi_ui/api.go @@ -11,22 +11,60 @@ func (a *api) SetupRoutes(r chi.Router) error { var apiDef = chioas.Definition{ DocOptions: chioas.DocOptions{ - ServeDocs: true, // makes docs served as interactive UI on /docs/index.htm - UIStyle: chioas.Redoc, + ServeDocs: true, // makes docs served as interactive UI on /docs/index.htm + UIStyle: chioas.Redoc, + StylesOverride: styling, + RedocOptions: chioas.RedocOptions{ + HeaderHtml: `
+ Swagger + Rapidoc +
`, + }, AlternateUIDocs: chioas.AlternateUIDocs{ "/swagger": { - UIStyle: chioas.Swagger, + UIStyle: chioas.Swagger, + StylesOverride: styling, SwaggerOptions: chioas.SwaggerOptions{ DeepLinking: true, + HeaderHtml: `
+ Redoc + Rapidoc +
`, }, }, "/rapidoc": { - UIStyle: chioas.Rapidoc, + UIStyle: chioas.Rapidoc, + StylesOverride: styling, RapidocOptions: &chioas.RapidocOptions{ ShowHeader: true, HeadingText: "Petstore", Theme: "dark", - ShowMethodInNavBar: "false", + ShowMethodInNavBar: "as-colored-block", + UsePathInNavBar: true, + SchemaStyle: "table", + HeadScript: ` +function getRapiDoc(){ + return document.getElementById("thedoc"); +} +function toggleView() { + let currRender = getRapiDoc().getAttribute('render-style'); + let newRender = currRender === "read" ? "view" : "read"; + getRapiDoc().setAttribute('render-style', newRender ); +} +function toggleTheme(){ + if (getRapiDoc().getAttribute('theme') === 'dark'){ + getRapiDoc().setAttribute('theme',"light"); + } + else{ + getRapiDoc().setAttribute('theme',"dark"); + } +}`, + InnerHtml: `
+ + + Redoc + Swagger +
`, }, }, }, @@ -80,6 +118,26 @@ Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/reso Components: &components, } +const styling = `.chioasbtn{ + min-width: 100px; + background-color: #47AFE8; + color: #fff; + font-size: 12px; + display: block; + border: none; + margin: 2px; + border-radius: 2px; + cursor: pointer; + outline: none; + text-align: center; + padding: 4px; + text-decoration: none; + font-family: sans-serif; +} +.chioasbtn:visited{ + color: #fff; +}` + var components = chioas.Components{ Schemas: chioas.Schemas{ { diff --git a/_examples/petstore/api.go b/_examples/petstore/api.go index 911b249..7829b66 100644 --- a/_examples/petstore/api.go +++ b/_examples/petstore/api.go @@ -17,19 +17,24 @@ import ( //go:embed status_schema.yaml petstore-logo.png var supportFilesFS embed.FS -// we're using our own customized docs html template -// -//go:embed index_template.html -var customizedSwaggerTemplate string - var apiDef = chioas.Definition{ DocOptions: chioas.DocOptions{ - ServeDocs: true, // makes docs served as interactive UI on /docs/index.htm - UIStyle: chioas.Swagger, + ServeDocs: true, // makes docs served as interactive UI on /docs/index.htm + UIStyle: chioas.Swagger, + StylesOverride: `.logo img { + padding: inherit; + margin: auto; + width: 200px; + display: block; +}`, + SwaggerOptions: chioas.SwaggerOptions{ + HeaderHtml: ``, + }, SupportFiles: http.FileServer(http.FS(supportFilesFS)), SupportFilesStripPrefix: true, - DocTemplate: customizedSwaggerTemplate, // customized template to show logo - CheckRefs: true, // make sure that any $ref's are valid! + CheckRefs: true, // make sure that any $ref's are valid! }, Info: chioas.Info{ Title: "Swagger Petstore - OpenAPI 3.0", @@ -126,44 +131,3 @@ func commenter(handlerMethod string, comments ...string) []string { func (a *api) SetupRoutes(r chi.Router) error { return a.Definition.SetupRoutes(r, a) } - -/* -// customizing the html template (to show a logo) - copied from chioas/doc_options.go defaultSwaggerTemplate -const customizedSwaggerTemplate = ` - - - {{.title}} - - - - - - - - - - -
- - - - -` -*/ diff --git a/_examples/petstore/index_template.html b/_examples/petstore/index_template.html deleted file mode 100644 index c791d19..0000000 --- a/_examples/petstore/index_template.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - {{.title}} - - - - - - - - - - -
- - - - - \ No newline at end of file diff --git a/doc_options.go b/doc_options.go index 1881f35..582041c 100644 --- a/doc_options.go +++ b/doc_options.go @@ -194,6 +194,7 @@ func (d *DocOptions) getTemplateData() (specName string, data map[string]any) { htmlTagSwaggerPlugins: plugins, htmlTagFavIcons: optionsFavIcons(d.SwaggerOptions), } + addHeaderAndScripts(data, swaggerOpts) case Rapidoc: data = optionsToMap(d.RapidocOptions) data[htmlTagTitle] = defValue(d.Title, defaultTitle) @@ -201,19 +202,36 @@ func (d *DocOptions) getTemplateData() (specName string, data map[string]any) { data[htmlTagSpecName] = specName data[htmlTagFavIcons] = optionsFavIcons(d.RapidocOptions) default: + redocOpts := optionsToMap(d.RedocOptions) data = map[string]any{ htmlTagTitle: defValue(d.Title, defaultTitle), htmlTagStylesOverride: template.CSS(defValue(d.StylesOverride, defaultRedocStylesOverride)), htmlTagSpecName: specName, - htmlTagRedocOpts: optionsToMap(d.RedocOptions), + htmlTagRedocOpts: redocOpts, htmlTagRedocUrl: defValue(d.RedocJsUrl, defaultRedocJsUrl), htmlTagTryUrl: defValue(d.TryJsUrl, defaultTryJsUrl), htmlTagFavIcons: optionsFavIcons(d.RedocOptions), } + addHeaderAndScripts(data, redocOpts) } return } +func addHeaderAndScripts(data, opts map[string]any) { + if v, ok := opts[htmlTagHeaderHtml]; ok { + data[htmlTagHeaderHtml] = v + delete(opts, htmlTagHeaderHtml) + } + if v, ok := opts[htmlTagHeadScript]; ok { + data[htmlTagHeadScript] = v + delete(opts, htmlTagHeadScript) + } + if v, ok := opts[htmlTagBodyScript]; ok { + data[htmlTagBodyScript] = v + delete(opts, htmlTagBodyScript) + } +} + func setupCachedRoutes(def *Definition, asJson bool, specData []byte, docsRoute *chi.Mux, tmp *template.Template, inData map[string]any, indexPage, specName string) (err error) { indexData, err := buildIndexData(tmp, inData) if err != nil { @@ -385,6 +403,9 @@ const ( htmlTagSwaggerPresets = "swaggerpresets" htmlTagSwaggerPlugins = "swaggerplugins" htmlTagFavIcons = "favIcons" + htmlTagHeadScript = "headScript" + htmlTagBodyScript = "bodyScript" + htmlTagHeaderHtml = "headerHtml" ) func defValue(v, def string) string { diff --git a/doc_options_test.go b/doc_options_test.go index 09f603d..ace0b39 100644 --- a/doc_options_test.go +++ b/doc_options_test.go @@ -497,6 +497,9 @@ func TestDocOptions_SwaggerOptions(t *testing.T) { DeepLinking: true, Plugins: []SwaggerPlugin{"MyPlugin1", "MyPlugin2"}, Presets: []SwaggerPreset{"MyPreset1", "MyPreset2"}, + HeaderHtml: `
HEADER
`, + HeadScript: `head();`, + BodyScript: `body();`, }, }, } @@ -516,6 +519,9 @@ func TestDocOptions_SwaggerOptions(t *testing.T) { assert.Contains(t, data, `"url":"spec.yaml"`) assert.Contains(t, data, `cfg.presets = [MyPreset1,MyPreset2]`) assert.Contains(t, data, `cfg.plugins = [MyPlugin1,MyPlugin2]`) + assert.Contains(t, data, `
HEADER
`) + assert.Contains(t, data, ``) + assert.Contains(t, data, ``) expectedSupportFiles := map[string]string{ "favicon-16x16.png": "image/png", diff --git a/docs_alt_ui.go b/docs_alt_ui.go index ea2b481..ebc934f 100644 --- a/docs_alt_ui.go +++ b/docs_alt_ui.go @@ -108,6 +108,7 @@ func (d *AlternateUIDoc) getTemplateData() (specName string, data map[string]any htmlTagSwaggerPlugins: plugins, htmlTagFavIcons: optionsFavIcons(d.SwaggerOptions), } + addHeaderAndScripts(data, swaggerOpts) case Rapidoc: data = optionsToMap(d.RapidocOptions) data[htmlTagTitle] = defValue(d.Title, defaultTitle) @@ -115,15 +116,17 @@ func (d *AlternateUIDoc) getTemplateData() (specName string, data map[string]any data[htmlTagSpecName] = specName data[htmlTagFavIcons] = optionsFavIcons(d.RapidocOptions) default: + redocOpts := optionsToMap(d.RedocOptions) data = map[string]any{ htmlTagTitle: defValue(d.Title, defaultTitle), htmlTagStylesOverride: template.CSS(defValue(d.StylesOverride, defaultRedocStylesOverride)), htmlTagSpecName: specName, - htmlTagRedocOpts: optionsToMap(d.RedocOptions), + htmlTagRedocOpts: redocOpts, htmlTagRedocUrl: defValue(d.RedocJsUrl, defaultRedocJsUrl), htmlTagTryUrl: defValue(d.TryJsUrl, defaultTryJsUrl), htmlTagFavIcons: optionsFavIcons(d.RedocOptions), } + addHeaderAndScripts(data, redocOpts) } return } diff --git a/ui_rapidoc_options.go b/ui_rapidoc_options.go index acabd76..fca1db6 100644 --- a/ui_rapidoc_options.go +++ b/ui_rapidoc_options.go @@ -17,12 +17,14 @@ const defaultRapidocTemplate = ` {{.favIcons}} + {{.headScript}} {{.logo}} {{.innerHtml}} + {{.bodyScript}} ` @@ -58,8 +60,10 @@ type RapidocOptions struct { // LogoSrc is the src for Rapidoc logo LogoSrc string // InnerHtml is any inner HTML for the element - InnerHtml template.HTML - FavIcons FavIcons + InnerHtml template.HTML + HeadScript template.JS + BodyScript template.JS + FavIcons FavIcons // AdditionalAttributes is used to add any rapidoc attributes // not covered by other options // @@ -166,6 +170,12 @@ func (o RapidocOptions) ToMap() map[string]any { if o.InnerHtml != "" { result["innerHtml"] = o.InnerHtml } + if o.HeadScript != "" { + result[htmlTagHeadScript] = template.HTML(``) + } + if o.BodyScript != "" { + result[htmlTagBodyScript] = template.HTML(``) + } return result } diff --git a/ui_rapidoc_options_test.go b/ui_rapidoc_options_test.go index a77da73..dd63bb7 100644 --- a/ui_rapidoc_options_test.go +++ b/ui_rapidoc_options_test.go @@ -63,12 +63,16 @@ func TestRapidocOptions_ToMap(t *testing.T) { nav-logo slot ` o = &RapidocOptions{ - InnerHtml: testInner, + InnerHtml: testInner, + HeadScript: `head();`, + BodyScript: `body();`, } m = o.ToMap() - assert.Equal(t, 2, len(m)) + assert.Equal(t, 4, len(m)) inner := m["innerHtml"].(template.HTML) assert.Equal(t, testInner, string(inner)) + assert.Equal(t, template.HTML(``), m[htmlTagHeadScript]) + assert.Equal(t, template.HTML(``), m[htmlTagBodyScript]) } func TestRapidocOptions_OverrideFavIcons(t *testing.T) { diff --git a/ui_redoc_options.go b/ui_redoc_options.go index 9b1fec3..193153b 100644 --- a/ui_redoc_options.go +++ b/ui_redoc_options.go @@ -1,5 +1,7 @@ package chioas +import "html/template" + const defaultRedocTemplate = ` {{.title}} @@ -8,8 +10,10 @@ const defaultRedocTemplate = ` {{.favIcons}} + {{.headScript}} + {{.headerHtml}}
@@ -19,6 +23,7 @@ const defaultRedocTemplate = ` redocOptions: {{.redocopts}} }) + {{.bodyScript}} ` @@ -56,44 +61,47 @@ body #redoc-container { // RedocOptions for use in DocOptions.RedocOptions (from https://github.com/Redocly/redoc#redoc-options-object) type RedocOptions struct { - DisableSearch bool `json:"disableSearch,omitempty"` // disable search indexing and search box. - MinCharacterLengthToInitSearch int `json:"minCharacterLengthToInitSearch,omitempty"` // set minimal characters length to init search, default 3, minimal 1. - ExpandDefaultServerVariables bool `json:"expandDefaultServerVariables,omitempty"` // enable expanding default server variables, default false. - ExpandResponses string `json:"expandResponses,omitempty"` // specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. expandResponses="200,201". Special value "all" expands all responses by default. Be careful: this option can slow-down documentation rendering time. - GeneratedPayloadSamplesMaxDepth int `json:"generatedPayloadSamplesMaxDepth,omitempty"` // set the maximum render depth for JSON payload samples (responses and request body). The default value is 10. - MaxDisplayedEnumValues int `json:"maxDisplayedEnumValues,omitempty"` // display only specified number of enum values. hide rest values under spoiler. - HideDownloadButton bool `json:"hideDownloadButton,omitempty"` // do not show "Download" spec button. THIS DOESN'T MAKE YOUR SPEC PRIVATE, it just hides the button. - DownloadFileName string `json:"downloadFileName,omitempty"` // set a custom file name for the downloaded API definition file. - DownloadDefinitionUrl string `json:"downloadDefinitionUrl,omitempty"` // If the 'Download' button is visible in the API reference documentation (hideDownloadButton=false), the URL configured here opens when that button is selected. Provide it as an absolute URL with the full URI scheme. - HideHostname bool `json:"hideHostname,omitempty"` // if set, the protocol and hostname is not shown in the operation definition. - HideLoading bool `json:"hideLoading,omitempty"` // do not show loading animation. Useful for small docs. - HideFab bool `json:"hideFab,omitempty"` // do not show FAB in mobile view. Useful for implementing a custom floating action button. - HideSchemaPattern bool `json:"hideSchemaPattern,omitempty"` // if set, the pattern is not shown in the schema. - HideSingleRequestSampleTab bool `json:"hideSingleRequestSampleTab,omitempty"` // do not show the request sample tab for requests with only one sample. - ShowObjectSchemaExamples bool `json:"showObjectSchemaExamples,omitempty"` // show object schema example in the properties, default false. - ExpandSingleSchemaField bool `json:"expandSingleSchemaField,omitempty"` // automatically expand single field in a schema - SchemaExpansionLevel any `json:"schemaExpansionLevel,omitempty"` // specifies whether to automatically expand schemas. Special value "all" expands all levels. The default value is 0. - JsonSampleExpandLevel any `json:"jsonSampleExpandLevel,omitempty"` // set the default expand level for JSON payload samples (responses and request body). Special value "all" expands all levels. The default value is 2. - HideSchemaTitles bool `json:"hideSchemaTitles,omitempty"` // do not display schema title next to the type - SimpleOneOfTypeLabel bool `json:"simpleOneOfTypeLabel,omitempty"` // show only unique oneOf types in the label without titles - SortEnumValuesAlphabetically bool `json:"sortEnumValuesAlphabetically,omitempty"` // set to true, sorts all enum values in all schemas alphabetically - SortOperationsAlphabetically bool `json:"sortOperationsAlphabetically,omitempty"` // set to true, sorts operations in the navigation sidebar and in the middle panel alphabetically - SortTagsAlphabetically bool `json:"sortTagsAlphabetically,omitempty"` // set to true, sorts tags in the navigation sidebar and in the middle panel alphabetically - MenuToggle bool `json:"menuToggle,omitempty"` // if true, clicking second time on expanded menu item collapses it, default true. - NativeScrollbars bool `json:"nativeScrollbars,omitempty"` // use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs). - OnlyRequiredInSamples bool `json:"onlyRequiredInSamples,omitempty"` // shows only required fields in request samples. - PathInMiddlePanel bool `json:"pathInMiddlePanel,omitempty"` // show path link and HTTP verb in the middle panel instead of the right one. - RequiredPropsFirst bool `json:"requiredPropsFirst,omitempty"` // show required properties first ordered in the same order as in required array. - ScrollYOffset any `json:"scrollYOffset,omitempty"` // If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; scrollYOffset can be specified in various ways: - ShowExtensions bool `json:"showExtensions,omitempty"` // show vendor extensions ("x-" fields). Extensions used by Redoc are ignored. Can be boolean or an array of string with names of extensions to display. - SortPropsAlphabetically bool `json:"sortPropsAlphabetically,omitempty"` // sort properties alphabetically. - PayloadSampleIndex int `json:"payloadSampleIdx,omitempty"` // if set, payload sample is inserted at this index or last. Indexes start from 0. - Theme *RedocTheme `json:"theme,omitempty"` // Redoc theme - UntrustedSpec bool `json:"untrustedSpec,omitempty"` // if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. Disabled by default for performance reasons. Enable this option if you work with untrusted user data! - Nonce any `json:"nonce,omitempty"` // if set, the provided value is injected in every injected HTML element in the nonce attribute. Useful when using CSP, see https://webpack.js.org/guides/csp/. - SideNavStyle string `json:"sideNavStyle,omitempty"` // can be specified in various ways: "summary-only" (default), "path-only" or id-only" - ShowWebhookVerb bool `json:"showWebhookVerb,omitempty"` // when set to true, shows the HTTP request method for webhooks in operations and in the sidebar. - FavIcons FavIcons + DisableSearch bool `json:"disableSearch,omitempty"` // disable search indexing and search box. + MinCharacterLengthToInitSearch int `json:"minCharacterLengthToInitSearch,omitempty"` // set minimal characters length to init search, default 3, minimal 1. + ExpandDefaultServerVariables bool `json:"expandDefaultServerVariables,omitempty"` // enable expanding default server variables, default false. + ExpandResponses string `json:"expandResponses,omitempty"` // specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. expandResponses="200,201". Special value "all" expands all responses by default. Be careful: this option can slow-down documentation rendering time. + GeneratedPayloadSamplesMaxDepth int `json:"generatedPayloadSamplesMaxDepth,omitempty"` // set the maximum render depth for JSON payload samples (responses and request body). The default value is 10. + MaxDisplayedEnumValues int `json:"maxDisplayedEnumValues,omitempty"` // display only specified number of enum values. hide rest values under spoiler. + HideDownloadButton bool `json:"hideDownloadButton,omitempty"` // do not show "Download" spec button. THIS DOESN'T MAKE YOUR SPEC PRIVATE, it just hides the button. + DownloadFileName string `json:"downloadFileName,omitempty"` // set a custom file name for the downloaded API definition file. + DownloadDefinitionUrl string `json:"downloadDefinitionUrl,omitempty"` // If the 'Download' button is visible in the API reference documentation (hideDownloadButton=false), the URL configured here opens when that button is selected. Provide it as an absolute URL with the full URI scheme. + HideHostname bool `json:"hideHostname,omitempty"` // if set, the protocol and hostname is not shown in the operation definition. + HideLoading bool `json:"hideLoading,omitempty"` // do not show loading animation. Useful for small docs. + HideFab bool `json:"hideFab,omitempty"` // do not show FAB in mobile view. Useful for implementing a custom floating action button. + HideSchemaPattern bool `json:"hideSchemaPattern,omitempty"` // if set, the pattern is not shown in the schema. + HideSingleRequestSampleTab bool `json:"hideSingleRequestSampleTab,omitempty"` // do not show the request sample tab for requests with only one sample. + ShowObjectSchemaExamples bool `json:"showObjectSchemaExamples,omitempty"` // show object schema example in the properties, default false. + ExpandSingleSchemaField bool `json:"expandSingleSchemaField,omitempty"` // automatically expand single field in a schema + SchemaExpansionLevel any `json:"schemaExpansionLevel,omitempty"` // specifies whether to automatically expand schemas. Special value "all" expands all levels. The default value is 0. + JsonSampleExpandLevel any `json:"jsonSampleExpandLevel,omitempty"` // set the default expand level for JSON payload samples (responses and request body). Special value "all" expands all levels. The default value is 2. + HideSchemaTitles bool `json:"hideSchemaTitles,omitempty"` // do not display schema title next to the type + SimpleOneOfTypeLabel bool `json:"simpleOneOfTypeLabel,omitempty"` // show only unique oneOf types in the label without titles + SortEnumValuesAlphabetically bool `json:"sortEnumValuesAlphabetically,omitempty"` // set to true, sorts all enum values in all schemas alphabetically + SortOperationsAlphabetically bool `json:"sortOperationsAlphabetically,omitempty"` // set to true, sorts operations in the navigation sidebar and in the middle panel alphabetically + SortTagsAlphabetically bool `json:"sortTagsAlphabetically,omitempty"` // set to true, sorts tags in the navigation sidebar and in the middle panel alphabetically + MenuToggle bool `json:"menuToggle,omitempty"` // if true, clicking second time on expanded menu item collapses it, default true. + NativeScrollbars bool `json:"nativeScrollbars,omitempty"` // use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs). + OnlyRequiredInSamples bool `json:"onlyRequiredInSamples,omitempty"` // shows only required fields in request samples. + PathInMiddlePanel bool `json:"pathInMiddlePanel,omitempty"` // show path link and HTTP verb in the middle panel instead of the right one. + RequiredPropsFirst bool `json:"requiredPropsFirst,omitempty"` // show required properties first ordered in the same order as in required array. + ScrollYOffset any `json:"scrollYOffset,omitempty"` // If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; scrollYOffset can be specified in various ways: + ShowExtensions bool `json:"showExtensions,omitempty"` // show vendor extensions ("x-" fields). Extensions used by Redoc are ignored. Can be boolean or an array of string with names of extensions to display. + SortPropsAlphabetically bool `json:"sortPropsAlphabetically,omitempty"` // sort properties alphabetically. + PayloadSampleIndex int `json:"payloadSampleIdx,omitempty"` // if set, payload sample is inserted at this index or last. Indexes start from 0. + Theme *RedocTheme `json:"theme,omitempty"` // Redoc theme + UntrustedSpec bool `json:"untrustedSpec,omitempty"` // if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. Disabled by default for performance reasons. Enable this option if you work with untrusted user data! + Nonce any `json:"nonce,omitempty"` // if set, the provided value is injected in every injected HTML element in the nonce attribute. Useful when using CSP, see https://webpack.js.org/guides/csp/. + SideNavStyle string `json:"sideNavStyle,omitempty"` // can be specified in various ways: "summary-only" (default), "path-only" or id-only" + ShowWebhookVerb bool `json:"showWebhookVerb,omitempty"` // when set to true, shows the HTTP request method for webhooks in operations and in the sidebar. + FavIcons FavIcons `json:"-"` + HeaderHtml template.HTML `json:"-"` + HeadScript template.JS `json:"-"` + BodyScript template.JS `json:"-"` } func (o RedocOptions) GetFavIcons() FavIcons { @@ -139,6 +147,15 @@ func (o RedocOptions) ToMap() map[string]any { addMapNonNil(m, o.Nonce, "nonce") addMap(m, o.SideNavStyle, "sideNavStyle") addMap(m, o.ShowWebhookVerb, "showWebhookVerb") + if o.HeaderHtml != "" { + m[htmlTagHeaderHtml] = o.HeaderHtml + } + if o.HeadScript != "" { + m[htmlTagHeadScript] = template.HTML(``) + } + if o.BodyScript != "" { + m[htmlTagBodyScript] = template.HTML(``) + } return m } diff --git a/ui_redoc_options_test.go b/ui_redoc_options_test.go index 7705cc7..436f915 100644 --- a/ui_redoc_options_test.go +++ b/ui_redoc_options_test.go @@ -2,6 +2,7 @@ package chioas import ( "github.com/stretchr/testify/assert" + "html/template" "testing" ) @@ -63,3 +64,15 @@ func TestRedocOptions_ToMap_Theme_Empty(t *testing.T) { m = m["theme"].(map[string]any) assert.Empty(t, m) } + +func TestRedocOptions_ToMap_HeaderAndScripts(t *testing.T) { + o := &RedocOptions{ + HeaderHtml: `
HEADER
`, + HeadScript: `head();`, + BodyScript: `body();`, + } + m := o.ToMap() + assert.Equal(t, template.HTML(`
HEADER
`), m[htmlTagHeaderHtml]) + assert.Equal(t, template.HTML(``), m[htmlTagHeadScript]) + assert.Equal(t, template.HTML(``), m[htmlTagBodyScript]) +} diff --git a/ui_swagger_options.go b/ui_swagger_options.go index d71d1a1..59669e9 100644 --- a/ui_swagger_options.go +++ b/ui_swagger_options.go @@ -14,8 +14,10 @@ const defaultSwaggerTemplate = ` {{.favIcons}} + {{.headScript}} + {{.headerHtml}}
@@ -28,6 +30,7 @@ const defaultSwaggerTemplate = ` window.ui = ui } + {{.bodyScript}} ` @@ -57,7 +60,10 @@ type SwaggerOptions struct { PersistAuthorization bool `json:"persistAuthorization"` Plugins []SwaggerPlugin `json:"-"` Presets []SwaggerPreset `json:"-"` - FavIcons FavIcons + FavIcons FavIcons `json:"-"` + HeaderHtml template.HTML `json:"-"` + HeadScript template.JS `json:"-"` + BodyScript template.JS `json:"-"` } var defaultSwaggerFavIcons = FavIcons{ @@ -94,6 +100,15 @@ func (o SwaggerOptions) ToMap() map[string]any { addMap(m, o.ValidatorUrl, "validatorUrl") addMap(m, o.WithCredentials, "withCredentials") addMap(m, o.PersistAuthorization, "persistAuthorization") + if o.HeaderHtml != "" { + m[htmlTagHeaderHtml] = o.HeaderHtml + } + if o.HeadScript != "" { + m[htmlTagHeadScript] = template.HTML(``) + } + if o.BodyScript != "" { + m[htmlTagBodyScript] = template.HTML(``) + } return m } diff --git a/ui_swagger_options_test.go b/ui_swagger_options_test.go index 730c259..986d40f 100644 --- a/ui_swagger_options_test.go +++ b/ui_swagger_options_test.go @@ -34,9 +34,12 @@ func TestSwaggerOptions_ToMap_NonEmpty(t *testing.T) { ValidatorUrl: "foo", WithCredentials: true, PersistAuthorization: true, + HeaderHtml: `
HEADER
`, + HeadScript: `head();`, + BodyScript: `body();`, } m := o.ToMap() - assert.Equal(t, 20, len(m)) + assert.Equal(t, 23, len(m)) assert.Contains(t, m, "dom_id") assert.Contains(t, m, "layout") assert.Contains(t, m, "deepLinking") @@ -57,6 +60,9 @@ func TestSwaggerOptions_ToMap_NonEmpty(t *testing.T) { assert.Contains(t, m, "validatorUrl") assert.Contains(t, m, "withCredentials") assert.Contains(t, m, "persistAuthorization") + assert.Equal(t, template.HTML(`
HEADER
`), m[htmlTagHeaderHtml]) + assert.Equal(t, template.HTML(``), m[htmlTagHeadScript]) + assert.Equal(t, template.HTML(``), m[htmlTagBodyScript]) } func TestSwaggerOptions_OverrideFavIcons(t *testing.T) {