2020-03-21 21:03:29 -06:00
// TODO: sanitize all HTML renderings, especially markdown: https://github.com/cure53/DOMPurify
2021-06-02 15:16:15 -06:00
var pageDocs = { } ;
var pageData = { } ;
var $hovercard ;
2020-03-21 21:03:29 -06:00
2020-07-16 15:51:46 -06:00
const nonStandardFlag = '<span class="nonstandard-flag" title="This module does not come with official Caddy distributions by default; it needs to be added to custom Caddy builds.">Non-standard</span>' ;
2021-06-02 15:16:15 -06:00
const standardFlag = '<span class="standard-flag" title="This module comes with official Caddy distributions by default.">Standard</span>' ;
2020-07-16 15:51:46 -06:00
2020-03-21 21:03:29 -06:00
$ ( function ( ) {
$hovercard = $ ( '#hovercard' ) ;
var hoverTimeout ;
$hovercard . hover ( function ( ) {
clearTimeout ( hoverTimeout ) ;
} , function ( ) {
clearTimeout ( hoverTimeout ) ;
$hovercard . removeClass ( 'popup' ) ;
} ) ;
// toggle an object as expanded or collapsed
2021-06-02 15:16:15 -06:00
$ ( 'body' ) . on ( 'click' , '.renderbox .toggle-obj' , function ( ) {
2020-03-21 21:03:29 -06:00
if ( $ ( this ) . hasClass ( 'expanded' ) ) {
// collapse
$ ( this ) . html ( '▸' ) ;
} else {
// expand
$ ( this ) . html ( '▾' ) ;
}
$ ( this ) . nextUntil ( '.end-obj' ) . toggleClass ( 'collapsed' ) ;
$ ( this ) . toggleClass ( 'expanded' ) ;
} ) ;
$ ( 'body' ) . on ( {
mouseenter : function ( ) {
// don't allow hoverbox to close anymore, we're re-opening it
clearTimeout ( hoverTimeout ) ;
2020-03-30 15:38:40 -06:00
var pos = $ ( this ) . offset ( ) ;
2021-06-02 15:16:15 -06:00
var moduleID = $ ( this ) . closest ( '.module-repo-container' ) . data ( 'module-id' ) || '' ;
var moduleData = pageData [ moduleID ] ;
2020-03-21 21:03:29 -06:00
// there is a gap between the hoverbox and the link that originated it;
// there may be a different link in this gap; if the hover box is visible,
// then we should ignore the hover on this link to allow cursor to visit
// the hoverbox if that is where it is going; this makes it possible to
// visit the hoverbox while it is over a list of links that are tightly
// stacked vertically; if user wants to visit hoverbox for link in this
// gap, they can just move the cursor slow enough to fire the timeout
2020-03-30 15:38:40 -06:00
if ( $hovercard . is ( ':visible' ) && $hovercard . offset ( ) . top - 10 < pos . top ) {
2020-03-21 21:03:29 -06:00
return ;
}
// fill out hovercard
var elemPath = $ ( this ) . data ( 'path' ) ;
var modNamespace = $ ( this ) . data ( 'namespace' ) ;
$ ( '.hovercard-elem' ) . hide ( ) ;
if ( $ ( this ) . hasClass ( 'module' ) ) {
// module
var $list = $ ( '<div/>' ) ;
2021-06-02 15:16:15 -06:00
if ( moduleData . namespaces && moduleData . namespaces [ modNamespace ] ) {
for ( var i = 0 ; i < moduleData . namespaces [ modNamespace ] . length ; i ++ ) {
var modInfo = moduleData . namespaces [ modNamespace ] [ i ] ;
var href = canTraverse ( moduleData ) ? '.' + elemPath + '/' + modInfo . name + '/' : './' + modNamespace + '.' + modInfo . name ;
2020-07-16 15:51:46 -06:00
var content = '<a href="' + href + '" class="module-link"> ' + modInfo . name ;
if ( ! isStandard ( modInfo . package ) ) {
content += nonStandardFlag ;
}
content += '<span class="module-link-description">' + truncate ( modInfo . docs , 115 ) + '</span></a>' ;
$list . append ( content ) ;
2020-03-21 21:03:29 -06:00
}
}
$ ( '#hovercard-module-list' ) . html ( $list ) ;
$ ( '#hovercard-namespace' ) . text ( modNamespace )
$ ( '#hovercard-module' ) . show ( ) ;
} else if ( $ ( this ) . hasClass ( 'module-inline-key' ) ) {
// inline key
$ ( '#hovercard-inline-key' ) . show ( ) ;
} else if ( $ ( this ) . hasClass ( 'breadcrumb' ) ) {
// breadcrumb siblings
var siblingPath = $ ( this ) . data ( 'sibling-path' ) ;
2021-06-02 15:16:15 -06:00
var bcVal = moduleData . breadcrumb [ siblingPath ] ;
2020-03-21 21:03:29 -06:00
var bcSiblings = [ ] ;
// drill down to the true underlying type
while ( bcVal . elems ) {
bcVal = bcVal . elems ;
}
switch ( bcVal . type ) {
case "struct" :
for ( var j = 0 ; j < bcVal . struct _fields . length ; j ++ ) {
var sf = bcVal . struct _fields [ j ] ;
2020-07-16 15:51:46 -06:00
bcSiblings . push ( { name : sf . key , path : siblingPath , isStandard : isStandard ( bcVal . type _name ) } )
2020-03-21 21:03:29 -06:00
}
break ;
case "module" :
case "module_map" :
2021-06-02 15:16:15 -06:00
for ( var j = 0 ; j < moduleData . namespaces [ bcVal . module _namespace ] . length ; j ++ ) {
var mod = moduleData . namespaces [ bcVal . module _namespace ] [ j ] ;
2020-07-16 15:51:46 -06:00
bcSiblings . push ( { name : mod . name , path : siblingPath , isStandard : isStandard ( mod . package ) } )
2020-03-21 21:03:29 -06:00
}
}
var $siblings = $ ( '<div class="breadcrumb-siblings"/>' ) . append ( '<div class="breadcrumb-siblings-title">Siblings:</div>' ) ;
for ( var j = 0 ; j < bcSiblings . length ; j ++ ) {
var sib = bcSiblings [ j ] ;
var sibPath = sib . path ;
if ( sibPath ) {
sibPath += "/" ;
}
sibPath += sib . name + "/" ;
2020-07-16 15:51:46 -06:00
var aTag = '<a href="' + jsonDocsPathPrefix + sibPath + '"' ;
if ( ! sib . isStandard ) {
aTag += ' class="nonstandard" title="Non-standard module"' ;
}
aTag += '>' + sib . name + '</a>' ;
$siblings . append ( aTag ) ;
2020-03-21 21:03:29 -06:00
}
$ ( '#hovercard-breadcrumb-siblings' ) . html ( $siblings ) . show ( ) ;
} else if ( $ ( this ) . hasClass ( 'documented' ) ) {
// docs
var elemDocs = truncate ( pageDocs [ elemPath ] , 500 ) ;
if ( ! elemDocs ) {
elemDocs = '<p class="explain">There are no docs for this property.</p>' ;
return ;
}
$ ( '#hovercard-docs' ) . html ( markdown ( elemDocs ) ) . show ( ) ;
$ ( '#hovercard-inline-link' ) . html ( '<a href="#' + elemPath . substr ( 1 ) + '">View docs below ↓</a>' ) . show ( ) ;
}
// show hoverbox for this link
var height = $ ( this ) . height ( ) ;
var linkWidth = $ ( this ) . width ( ) ;
var boxWidth = $hovercard . width ( ) ;
$hovercard . css ( {
'top' : pos . top + height * 1.5 + 10 , // '+10' counters 'translateY(-10px)'
'left' : pos . left + ( linkWidth / 2 ) - ( boxWidth / 2 )
} ) . addClass ( 'popup' ) ;
} ,
mouseleave : function ( ) {
// don't hide the hoverbox right away; user might need a
// few milliseconds to get the cursor over the hovercard
hoverTimeout = setTimeout ( function ( ) {
$hovercard . removeClass ( 'popup' ) ;
} , 200 ) ;
}
} , '.has-popup' ) ;
} ) ;
2021-06-02 15:16:15 -06:00
function beginRenderingInto ( $tpl , moduleID , module ) {
console . log ( "RENDERING:" , moduleID , module ) ;
$tpl . data ( 'module-id' , moduleID ) ;
pageData [ moduleID ] = module ;
2020-03-21 21:03:29 -06:00
2020-07-16 15:51:46 -06:00
// show notice if module is non-standard
2021-06-02 15:16:15 -06:00
if ( module . repo ) {
2021-10-12 14:51:36 -06:00
if ( ! isStandard ( module . structure . type _name ) ) {
2021-06-02 15:16:15 -06:00
let { pkg , _ } = splitTypeName ( module . structure . type _name ) ;
$ ( '.nonstandard-project-link' , $tpl ) . attr ( 'href' , module . repo ) . text ( module . repo ) ;
$ ( '.nonstandard-package-path' , $tpl ) . text ( pkg ) ;
2022-03-08 15:05:13 -07:00
$ ( '.nonstandard-notice' , $tpl ) . css ( 'display' , 'block' ) . prepend ( nonStandardFlag ) ; // for some reason show() dosen't work
2021-06-02 15:16:15 -06:00
}
var $repoName = $ ( '<span/>' ) . text ( stripScheme ( module . repo ) ) ;
$ ( '.module-repo-selector' , $tpl ) . html ( '<span class="module-repo-selector-arrow">▸</span>' ) . append ( $repoName ) ;
2020-07-16 15:51:46 -06:00
}
2021-05-25 17:26:35 -06:00
// for most types, just render their docs; but for maps or arrays, fall through to underlying type for docs
2021-06-02 15:16:15 -06:00
let rawDocs = module . structure . doc ? ? module . structure . elems ;
2021-05-25 17:26:35 -06:00
2021-06-02 15:16:15 -06:00
$ ( '.top-doc' , $tpl ) . html ( markdown ( replaceGoTypeNameWithCaddyModuleName ( rawDocs , module , moduleID ) ) ) ;
$ ( '.top-doc' , $tpl ) . append ( makeSubmoduleList ( module , "" , module . structure ) ) ;
2020-03-21 21:03:29 -06:00
2021-06-02 15:16:15 -06:00
let $group = newGroup ( ) ;
renderData ( $tpl , module , module . structure , 0 , "" , $group ) ;
$ ( '.renderbox' , $tpl ) . append ( $group ) ;
2020-03-21 21:03:29 -06:00
2021-06-02 15:16:15 -06:00
if ( $ ( '.field-list-contents' , $tpl ) . text ( ) . trim ( ) ) {
$ ( '.field-list-header' , $tpl ) . show ( ) ;
2020-03-30 15:38:40 -06:00
}
2021-06-02 15:16:15 -06:00
// TODO: see about fixing this for module and JSON docs pages
// // if the browser tried to navigate directly to an element
// // on the page when it loaded, it would have failed since
// // we hadn't rendered it yet; but now we can scroll to it
// // directly since rendering has finished
// if (window.location.hash.length > 1) {
// document.getElementById(window.location.hash.substr(1)).scrollIntoView();
// }
2020-03-21 21:03:29 -06:00
}
2021-06-02 15:16:15 -06:00
function renderData ( $tpl , module , data , nesting , path , $group ) {
2020-03-21 21:03:29 -06:00
switch ( data . type ) {
case "struct" :
$group . append ( '{<a href="javascript:" class="toggle-obj expanded" title="Collapse/expand">▾</a>' ) ;
nesting ++ ;
2021-06-02 15:16:15 -06:00
var $fieldGroup = newGroup ( ) ;
renderModuleInlineKey ( $tpl , module , data , nesting , $fieldGroup ) ;
2020-03-21 21:03:29 -06:00
$group . append ( $fieldGroup ) ;
if ( data . struct _fields ) {
// TODO: Not sure if sorting the struct fields is a good idea...
// data.struct_fields.sort(function(a, b) {
// if (a.key > b.key) return 1;
// if (b.key > a.key) return -1;
// return 0;
// });
for ( var i = 0 ; i < data . struct _fields . length ; i ++ ) {
var field = data . struct _fields [ i ] ;
var fieldPath = path + "/" + field . key ;
var cleanFieldPath = fieldPath . slice ( 1 ) ; // trim leading slash
// store the docs for this path
let linkClass = "documented" ;
if ( field . doc ) {
pageDocs [ fieldPath ] = field . doc ;
linkClass += " has-popup" ;
}
// render the docs to the page
var fieldDoc = markdown ( field . doc ) || '<p class="explain">There are no docs for this property.</p>' ;
2021-06-02 15:16:15 -06:00
fieldDoc += makeSubmoduleList ( module , fieldPath , field . value ) ;
appendToFieldDocs ( $tpl , module , cleanFieldPath , fieldDoc ) ;
2020-03-21 21:03:29 -06:00
// render the field to the JSON box
2021-06-02 15:16:15 -06:00
var $fieldGroup = newGroup ( ) ;
2020-03-21 21:03:29 -06:00
indent ( nesting , $fieldGroup ) ;
var keyATag = '<a ' ;
2021-06-02 15:16:15 -06:00
if ( canTraverse ( module ) ) {
2020-03-21 21:03:29 -06:00
keyATag += 'href=".' + fieldPath + '/" ' ;
}
keyATag += 'data-path="' + fieldPath + '" class="' + linkClass + '">' + field . key + '</a>' ;
$fieldGroup . append ( '<span class="qu">"</span><span class="key">' + keyATag + '</span><span class="qu">"</span>: ' ) ;
2021-06-02 15:16:15 -06:00
renderData ( $tpl , module , field . value , nesting , fieldPath , $fieldGroup ) ;
2020-03-21 21:03:29 -06:00
if ( i < data . struct _fields . length - 1 ) {
$fieldGroup . append ( ',' ) ;
}
$group . append ( $fieldGroup ) ;
}
}
nesting -- ;
indent ( nesting , $group ) ;
$group . append ( '<span class="end-obj">}</span>' ) ;
break ;
case "bool" :
$group . append ( '<span class="bool">false</span>' ) ; // TODO: default value?
break ;
case "int" :
case "uint" :
case "float" :
case "complex" :
$group . append ( '<span class="num">0</span>' ) ; // TODO: default value?
break ;
case "string" :
$group . append ( '<span class="qu">"</span><span class="str"></span><span class="qu">"</span>' ) ; // TODO: default value?
break ;
case "array" :
$group . append ( '[' ) ;
if ( data . elems . type == "module_map" ) {
$group . append ( '{<a href=".' + path + '/" class="module has-popup" data-namespace="' + ( data . elems . module _namespace || '' ) + '" data-path="' + path + '">•••</a>}' ) ;
} else {
2021-06-02 15:16:15 -06:00
renderData ( $tpl , module , data . elems , nesting , path , $group ) ;
2020-03-21 21:03:29 -06:00
}
$group . append ( ']' ) ;
break ;
case "map" :
$group . append ( '{\n' )
nesting ++ ;
2021-06-02 15:16:15 -06:00
renderModuleInlineKey ( $tpl , module , data , nesting , $group ) ;
2020-03-21 21:03:29 -06:00
indent ( nesting , $group ) ;
2021-06-02 15:16:15 -06:00
renderData ( $tpl , module , data . map _keys , nesting , path , $group ) ;
2020-03-21 21:03:29 -06:00
$group . append ( ': ' ) ;
2021-06-02 15:16:15 -06:00
renderData ( $tpl , module , data . elems , nesting , path , $group ) ;
2020-03-21 21:03:29 -06:00
$group . append ( '\n' ) ;
nesting -- ;
indent ( nesting , $group ) ;
$group . append ( '}' ) ;
break ;
case "module" :
case "module_map" :
var aTag = '<a' ;
2021-06-02 15:16:15 -06:00
if ( canTraverse ( module ) ) {
2020-03-21 21:03:29 -06:00
aTag += ' href=".' + path + '/"' ;
}
aTag += ' class="module has-popup" data-namespace="' + ( data . module _namespace || '' ) + '" data-path="' + path + '">•••</a>' ;
$group . append ( '{' + aTag + '}' ) ;
break ;
}
}
2021-06-02 15:16:15 -06:00
function renderModuleInlineKey ( $tpl , module , data , nesting , $group ) {
2020-03-21 21:03:29 -06:00
if ( ! data . module _inline _key ) {
return
}
var moduleName = pathComponents [ pathComponents . length - 2 ] ;
indent ( nesting , $group ) ;
$group . append ( '<span class="qu">"</span><span class="key module-inline-key has-popup">' + data . module _inline _key + '</span><span class="qu">"</span>: <span class="qu">"</span><span class="str"><b>' + moduleName + '</b></span><span class="qu">"</span>' ) ;
if ( data . struct _fields && data . struct _fields . length > 0 ) {
$group . append ( ',' ) ;
}
$group . append ( '\n' ) ;
2021-06-02 15:16:15 -06:00
appendToFieldDocs ( $tpl , module , data . module _inline _key , $ ( '#hovercard-inline-key' ) . html ( ) ) ;
2020-03-21 21:03:29 -06:00
}
2021-06-02 15:16:15 -06:00
function appendToFieldDocs ( $tpl , module , cleanFieldPath , fieldDoc ) {
2020-03-21 21:03:29 -06:00
var dt = cleanFieldPath ;
2021-06-02 15:16:15 -06:00
if ( canTraverse ( module ) ) {
2020-03-21 21:03:29 -06:00
dt = '<a href="./' + cleanFieldPath + '/">' + dt + '</a>' ;
}
2021-06-02 15:16:15 -06:00
$ ( '.field-list-contents' , $tpl ) . append ( '<dt class="field-name" id="' + cleanFieldPath + '"><a href="#' + cleanFieldPath + '" class="inline-link">🔗</a>' + dt + '</dt> <dd>' + fieldDoc + '</dd>' ) ;
2020-03-21 21:03:29 -06:00
}
function indent ( nesting , $group ) {
var $span = $ ( '<span class="indent"></span>' ) ;
$span . append ( '\t' . repeat ( nesting ) ) ;
$group . append ( $span ) ;
}
2021-06-02 15:16:15 -06:00
function makeSubmoduleList ( module , path , value ) {
2020-03-21 21:03:29 -06:00
while ( value . elems ) {
value = value . elems ;
}
if ( value . type != "module" && value . type != "module_map" ) {
return '' ;
}
var submodList = '<ul>' ;
2021-06-02 15:16:15 -06:00
if ( module . namespaces && module . namespaces [ value . module _namespace ] ) {
2025-01-01 19:39:20 +03:00
module . namespaces [ value . module _namespace ] . sort ( function ( a , b ) {
if ( isStandard ( a . package ) && ! isStandard ( b . package ) ) return - 1 ;
if ( ! isStandard ( a . package ) && isStandard ( b . package ) ) return 1 ;
if ( a . name < b . name ) return - 1 ;
if ( a . name > b . name ) return 1 ;
return 0 ;
} ) ;
2021-06-02 15:16:15 -06:00
for ( var j = 0 ; j < module . namespaces [ value . module _namespace ] . length ; j ++ ) {
var submod = module . namespaces [ value . module _namespace ] [ j ] ;
var href = canTraverse ( module ) ? '.' + path + '/' + submod . name + '/' : './' + value . module _namespace + '.' + submod . name ;
2020-07-16 15:51:46 -06:00
var submodLink = '<a href="' + href + '">' + submod . name + '</a>' ;
if ( ! isStandard ( submod . package ) ) {
submodLink += ' ' + nonStandardFlag ;
}
submodList += '<li>' + submodLink + '</li>' ;
2020-03-21 21:03:29 -06:00
}
}
submodList += '</ul>' ;
return '<div><p>Fulfilled by modules in namespace: <b>' + value . module _namespace + '</b></p>' + submodList + '</div>' ;
}
// canTraverse returns true if the page data
// includes breadcrumbs; i.e. we are on a page
// that can traverse the JSON structure, not
// only render part of it in isolation.
2021-06-02 15:16:15 -06:00
function canTraverse ( data ) {
return data . breadcrumb != null ;
}
function newGroup ( ) {
return $ ( '<div class="group"/>' ) ;
2020-03-21 21:03:29 -06:00
}
2021-06-02 15:16:15 -06:00
function replaceGoTypeNameWithCaddyModuleName ( docs , module , moduleID ) {
2021-05-25 17:26:35 -06:00
if ( ! docs || ! moduleID ) return docs ;
// fully qualified type name
2021-06-02 15:16:15 -06:00
let fqtn = module . structure . type _name ;
2021-05-25 17:26:35 -06:00
// extract just the local type name
2021-06-02 15:16:15 -06:00
let { _ , typeName } = splitTypeName ( fqtn ) ;
2021-05-25 17:26:35 -06:00
// replace the type name with the Caddy module ID if it starts the docs.
if ( docs . indexOf ( typeName ) === 0 ) {
docs = moduleID + docs . substr ( typeName . length ) ;
}
return docs ;
}
2020-03-21 21:03:29 -06:00
function markdown ( input ) {
if ( ! input ) {
return "" ;
}
return marked ( input ) ;
}