mirror of
https://github.com/caddyserver/website.git
synced 2025-04-24 05:56:15 -04:00
docs: Add SPA page for listing modules by their ID
This commit is contained in:
parent
0834562c9b
commit
ca8197d483
14 changed files with 565 additions and 392 deletions
|
@ -1,11 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>JSON Structure - Caddy Documentation</title>
|
||||
<title>JSON Config Structure - Caddy Documentation</title>
|
||||
{{include "/includes/docs-head.html"}}
|
||||
<link rel="stylesheet" href="/resources/css/docs-json.css">
|
||||
<script src="/resources/js/marked-0.8.0.min.js"></script>
|
||||
<script src="/resources/js/docs-json.js"></script>
|
||||
<script src="/resources/js/docs-api.js"></script>
|
||||
<script src="/resources/js/json-docs.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{include "/includes/docs-header.html"}}
|
||||
|
@ -16,36 +17,16 @@
|
|||
<!--Populated by JS-->
|
||||
</div>
|
||||
|
||||
<pre><code class="json" id="renderbox"><!--Populated by JS--></code></pre>
|
||||
{{include "/includes/docs-renderbox.html"}}
|
||||
|
||||
<article>
|
||||
<p id="top-doc">
|
||||
<!--Populated by JS-->
|
||||
</p>
|
||||
<h2 id="field-list-header">Field List</h2>
|
||||
<dl id="field-list">
|
||||
<!--Populated by JS-->
|
||||
</dl>
|
||||
{{include "/includes/docs-details.html"}}
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar"></div>
|
||||
</main>
|
||||
|
||||
|
||||
<div id="hovercard" class="arrow-box">
|
||||
<div id="hovercard-docs" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
<div id="hovercard-module" class="hovercard-elem">
|
||||
<p id="hovercard-namespace-box">
|
||||
Fulfilled by modules in namespace: <span id="hovercard-namespace"><!--Populated by JS--></span>
|
||||
</p>
|
||||
<div id="hovercard-module-list"><!--Populated by JS--></div>
|
||||
</div>
|
||||
<div id="hovercard-inline-key" class="hovercard-elem">
|
||||
<p class="explain">This property is <b>required</b> because it specifies the module name.</p>
|
||||
</div>
|
||||
<div id="hovercard-breadcrumb-siblings" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
<div id="hovercard-inline-link" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
</div>
|
||||
{{include "/includes/docs-hovercard.html"}}
|
||||
|
||||
{{include "/includes/footer.html"}}
|
||||
</body>
|
||||
|
|
54
src/docs/modules/index.html
Normal file
54
src/docs/modules/index.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Modules - Caddy Documentation</title>
|
||||
{{include "/includes/docs-head.html"}}
|
||||
<link rel="stylesheet" href="/resources/css/docs-json.css">
|
||||
<script src="/resources/js/marked-0.8.0.min.js"></script>
|
||||
<script src="/resources/js/docs-api.js"></script>
|
||||
<script src="/resources/js/module-docs.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{include "/includes/docs-header.html"}}
|
||||
<main>
|
||||
{{include "/includes/docs-nav.html"}}
|
||||
<div class="article-container">
|
||||
|
||||
<div id="module-list-container">
|
||||
<article>
|
||||
<h1>All Modules</h1>
|
||||
<p>
|
||||
This page lists all registered Caddy modules.
|
||||
</p>
|
||||
<p>
|
||||
We recommend using your browser's "Find in page" feature for quick lookups.
|
||||
</p>
|
||||
<table id="module-list">
|
||||
<tr>
|
||||
<th>Module ID</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<!-- Populated by JS-->
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div id="module-docs-container">
|
||||
<article>
|
||||
<h1 id="module-name"><!--Populated by JS--></h1>
|
||||
</article>
|
||||
{{include "/includes/docs-renderbox.html"}}
|
||||
<article>
|
||||
{{include "/includes/docs-details.html"}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="sidebar"></div>
|
||||
</main>
|
||||
|
||||
{{include "/includes/docs-hovercard.html"}}
|
||||
|
||||
{{include "/includes/footer.html"}}
|
||||
</body>
|
||||
</html>
|
7
src/includes/docs-details.html
Normal file
7
src/includes/docs-details.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<p id="top-doc">
|
||||
<!--Populated by JS-->
|
||||
</p>
|
||||
<h2 id="field-list-header">Field List</h2>
|
||||
<dl id="field-list">
|
||||
<!--Populated by JS-->
|
||||
</dl>
|
14
src/includes/docs-hovercard.html
Normal file
14
src/includes/docs-hovercard.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div id="hovercard" class="arrow-box">
|
||||
<div id="hovercard-docs" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
<div id="hovercard-module" class="hovercard-elem">
|
||||
<p id="hovercard-namespace-box">
|
||||
Fulfilled by modules in namespace: <span id="hovercard-namespace"><!--Populated by JS--></span>
|
||||
</p>
|
||||
<div id="hovercard-module-list"><!--Populated by JS--></div>
|
||||
</div>
|
||||
<div id="hovercard-inline-key" class="hovercard-elem">
|
||||
<p class="explain">This property is <b>required</b> because it specifies the module name.</p>
|
||||
</div>
|
||||
<div id="hovercard-breadcrumb-siblings" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
<div id="hovercard-inline-link" class="hovercard-elem"><!--Populated by JS--></div>
|
||||
</div>
|
|
@ -30,6 +30,7 @@
|
|||
<li><a href="/docs/caddyfile/options">Global options</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/docs/modules/">Modules</a></li>
|
||||
<li><a href="/docs/json/">JSON Config Structure</a></li>
|
||||
<li><a href="/docs/automatic-https">Automatic HTTPS</a></li>
|
||||
|
||||
|
|
1
src/includes/docs-renderbox.html
Normal file
1
src/includes/docs-renderbox.html
Normal file
|
@ -0,0 +1 @@
|
|||
<pre><code class="json" id="renderbox"><!--Populated by JS--></code></pre>
|
|
@ -198,7 +198,7 @@ article li {
|
|||
h1 {
|
||||
font-size: 80px;
|
||||
color: #196165;
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -352,12 +352,13 @@ main > .sidebar:not(:last-child) {
|
|||
.json .str { color: #2f8598; }
|
||||
.json .num { color: #038a3f; }
|
||||
.json .bool { color: #9b5e14; }
|
||||
.json .key a:not(:hover) { color: inherit; }
|
||||
.json .key a:not([href]) { color: #222; }
|
||||
.json a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.json .has-popup { border-bottom: 1px dashed #1c82dc; }
|
||||
.json .has-popup { border-bottom: 1px dashed #222; }
|
||||
.json a[href].has-popup { border-bottom-color: #1c82dc; }
|
||||
.json .has-popup.module { border-bottom: none; }
|
||||
|
||||
|
||||
|
@ -567,6 +568,51 @@ td code {
|
|||
font-size: 14px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#module-docs-container,
|
||||
#module-list-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#module-list {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#module-list td:first-child {
|
||||
word-wrap: break-word;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#module-list .module-link {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
article aside {
|
||||
|
|
345
src/resources/js/docs-api.js
Normal file
345
src/resources/js/docs-api.js
Normal file
|
@ -0,0 +1,345 @@
|
|||
// TODO: sanitize all HTML renderings, especially markdown: https://github.com/cure53/DOMPurify
|
||||
|
||||
var pageData = {}, pageDocs = {};
|
||||
|
||||
var $renderbox, $hovercard;
|
||||
|
||||
$(function() {
|
||||
$renderbox = $('#renderbox');
|
||||
$hovercard = $('#hovercard');
|
||||
|
||||
var hoverTimeout;
|
||||
$hovercard.hover(function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
}, function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$hovercard.removeClass('popup');
|
||||
});
|
||||
|
||||
// toggle an object as expanded or collapsed
|
||||
$('#renderbox').on('click', '.toggle-obj', function() {
|
||||
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);
|
||||
|
||||
var pos = $(this).position();
|
||||
|
||||
// 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
|
||||
if ($hovercard.is(':visible') && $hovercard.position().top - 10 < pos.top) {
|
||||
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/>');
|
||||
if (pageData.namespaces && pageData.namespaces[modNamespace]) {
|
||||
for (var i = 0; i < pageData.namespaces[modNamespace].length; i++) {
|
||||
var modInfo = pageData.namespaces[modNamespace][i];
|
||||
var href = canTraverse() ? '.'+elemPath+'/'+modInfo.name+'/' : './'+modNamespace+'.'+modInfo.name;
|
||||
$list.append('<a href="'+href+'" class="module-link">'+modInfo.name+'<span class="module-link-description">'+truncate(modInfo.docs, 115)+'</span></a>');
|
||||
}
|
||||
}
|
||||
$('#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');
|
||||
var bcVal = pageData.breadcrumb[siblingPath];
|
||||
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];
|
||||
bcSiblings.push({name: sf.key, path: siblingPath})
|
||||
}
|
||||
break;
|
||||
|
||||
case "module":
|
||||
case "module_map":
|
||||
for (var j = 0; j < pageData.namespaces[bcVal.module_namespace].length; j++) {
|
||||
var mod = pageData.namespaces[bcVal.module_namespace][j];
|
||||
bcSiblings.push({name: mod.name, path: siblingPath})
|
||||
}
|
||||
}
|
||||
|
||||
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+"/";
|
||||
$siblings.append('<a href="'+jsonDocsPathPrefix+sibPath+'">'+sib.name+'</a>');
|
||||
}
|
||||
$('#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');
|
||||
});
|
||||
|
||||
|
||||
function beginRendering(json) {
|
||||
pageData = json;
|
||||
console.log("DATA:", pageData);
|
||||
|
||||
if (pageData.structure.doc) {
|
||||
// for most types, just render their docs
|
||||
$('#top-doc').html(markdown(pageData.structure.doc));
|
||||
} else if (pageData.structure.elems) {
|
||||
// for maps or arrays, fall through to the underlying type
|
||||
$('#top-doc').html(markdown(pageData.structure.elems.doc));
|
||||
}
|
||||
$('#top-doc').append(makeSubmoduleList("", pageData.structure));
|
||||
|
||||
renderData(pageData.structure, 0, "", $('<div class="group"/>'));
|
||||
console.log("DOCS:", pageDocs);
|
||||
|
||||
if ($('#field-list').html().trim()) {
|
||||
$('#field-list-header').show();
|
||||
}
|
||||
}
|
||||
|
||||
function renderData(data, nesting, path, $group) {
|
||||
switch (data.type) {
|
||||
case "struct":
|
||||
$group.append('{<a href="javascript:" class="toggle-obj expanded" title="Collapse/expand">▾</a>');
|
||||
nesting++;
|
||||
|
||||
var $fieldGroup = $('<div class="group"/>');
|
||||
renderModuleInlineKey(data, nesting, $fieldGroup);
|
||||
$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>';
|
||||
fieldDoc += makeSubmoduleList(fieldPath, field.value);
|
||||
appendToFieldDocs(cleanFieldPath, fieldDoc);
|
||||
|
||||
// render the field to the JSON box
|
||||
var $fieldGroup = $('<div class="group"/>');
|
||||
indent(nesting, $fieldGroup);
|
||||
var keyATag = '<a ';
|
||||
if (canTraverse()) {
|
||||
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>: ');
|
||||
renderData(field.value, nesting, fieldPath, $fieldGroup);
|
||||
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 {
|
||||
renderData(data.elems, nesting, path, $group);
|
||||
}
|
||||
$group.append(']');
|
||||
break;
|
||||
|
||||
case "map":
|
||||
$group.append('{\n')
|
||||
nesting++;
|
||||
renderModuleInlineKey(data, nesting, $group);
|
||||
indent(nesting, $group);
|
||||
renderData(data.map_keys, nesting, path, $group);
|
||||
$group.append(': ');
|
||||
renderData(data.elems, nesting, path, $group);
|
||||
$group.append('\n');
|
||||
nesting--;
|
||||
indent(nesting, $group);
|
||||
$group.append('}');
|
||||
break;
|
||||
|
||||
case "module":
|
||||
case "module_map":
|
||||
var aTag = '<a';
|
||||
if (canTraverse()) {
|
||||
aTag += ' href=".'+path+'/"';
|
||||
}
|
||||
aTag += ' class="module has-popup" data-namespace="'+(data.module_namespace || '')+'" data-path="'+path+'">•••</a>';
|
||||
$group.append('{'+aTag+'}');
|
||||
break;
|
||||
}
|
||||
|
||||
$renderbox.append($group);
|
||||
}
|
||||
|
||||
function renderModuleInlineKey(data, nesting, $group) {
|
||||
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');
|
||||
|
||||
appendToFieldDocs(data.module_inline_key, $('#hovercard-inline-key').html());
|
||||
}
|
||||
|
||||
function appendToFieldDocs(cleanFieldPath, fieldDoc) {
|
||||
var dt = cleanFieldPath;
|
||||
if (canTraverse()) {
|
||||
dt = '<a href="./'+cleanFieldPath+'/">'+dt+'</a>';
|
||||
}
|
||||
$('#field-list').append('<dt class="field-name" id="'+cleanFieldPath+'"><a href="#'+cleanFieldPath+'" class="inline-link">🔗</a>'+dt+'</dt> <dd>'+fieldDoc+'</dd>');
|
||||
}
|
||||
|
||||
function indent(nesting, $group) {
|
||||
var $span = $('<span class="indent"></span>');
|
||||
$span.append('\t'.repeat(nesting));
|
||||
$group.append($span);
|
||||
}
|
||||
|
||||
function truncate(str, len) {
|
||||
if (!str) return "";
|
||||
var startLen = str.length;
|
||||
str = str.substring(0, len);
|
||||
if (startLen > len) {
|
||||
str += "...";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function makeSubmoduleList(path, value) {
|
||||
while (value.elems) {
|
||||
value = value.elems;
|
||||
}
|
||||
if (value.type != "module" && value.type != "module_map") {
|
||||
return '';
|
||||
}
|
||||
var submodList = '<ul>';
|
||||
if (pageData.namespaces && pageData.namespaces[value.module_namespace]) {
|
||||
for (var j = 0; j < pageData.namespaces[value.module_namespace].length; j++) {
|
||||
var submod = pageData.namespaces[value.module_namespace][j];
|
||||
var href = canTraverse() ? '.'+path+'/'+submod.name+'/' : './'+value.module_namespace+'.'+submod.name;
|
||||
submodList += '<li><a href="'+href+'">'+submod.name+'</li>';
|
||||
}
|
||||
}
|
||||
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.
|
||||
function canTraverse() {
|
||||
return pageData.breadcrumb != null;
|
||||
}
|
||||
|
||||
function markdown(input) {
|
||||
if (!input) {
|
||||
return "";
|
||||
}
|
||||
return marked(input);
|
||||
}
|
|
@ -1,359 +0,0 @@
|
|||
// TODO: sanitize all HTML renderings, especially markdown: https://github.com/cure53/DOMPurify
|
||||
|
||||
function markdown(input) {
|
||||
if (!input) {
|
||||
return "";
|
||||
}
|
||||
return marked(input);
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
var $renderbox = $('#renderbox');
|
||||
var $hovercard = $('#hovercard');
|
||||
|
||||
var pageDocs = {}, pageData = {};
|
||||
|
||||
const jsonDocsPathPrefix = "/docs/json/";
|
||||
var configPath = window.location.pathname.substr(jsonDocsPathPrefix.length-1); // keep trailing slash
|
||||
|
||||
// set the page title with something useful
|
||||
var parts = configPath.split("/");
|
||||
if (parts.length > 1) {
|
||||
if (!parts[0]) {
|
||||
parts.shift();
|
||||
}
|
||||
if (!parts[parts.length-1]) {
|
||||
parts.pop();
|
||||
}
|
||||
var titlePrefix = parts.slice(-2).join("/");
|
||||
if (parts.length > 4) {
|
||||
titlePrefix = parts.slice(0, 2).join("/")+"/.../"+titlePrefix;
|
||||
}
|
||||
if (titlePrefix) {
|
||||
document.title = titlePrefix + " - " + document.title;
|
||||
}
|
||||
}
|
||||
|
||||
var hoverTimeout;
|
||||
$hovercard.hover(function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
}, function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$hovercard.removeClass('popup');
|
||||
});
|
||||
|
||||
$('body').on({
|
||||
mouseenter: function() {
|
||||
// don't allow hoverbox to close anymore, we're re-opening it
|
||||
clearTimeout(hoverTimeout);
|
||||
|
||||
var pos = $(this).position();
|
||||
|
||||
// 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
|
||||
if ($hovercard.is(':visible') && $hovercard.position().top - 10 < pos.top) {
|
||||
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/>');
|
||||
if (pageData.namespaces && pageData.namespaces[modNamespace]) {
|
||||
for (var i = 0; i < pageData.namespaces[modNamespace].length; i++) {
|
||||
var modInfo = pageData.namespaces[modNamespace][i];
|
||||
$list.append('<a href=".'+elemPath+'/'+modInfo.name+'/" class="module-link">'+modInfo.name+'<span class="module-link-description">'+truncate(modInfo.docs, 115)+'</span></a>');
|
||||
}
|
||||
}
|
||||
$('#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');
|
||||
var bcVal = pageData.breadcrumb[siblingPath];
|
||||
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];
|
||||
bcSiblings.push({name: sf.key, path: siblingPath})
|
||||
}
|
||||
break;
|
||||
|
||||
case "module":
|
||||
case "module_map":
|
||||
for (var j = 0; j < pageData.namespaces[bcVal.module_namespace].length; j++) {
|
||||
var mod = pageData.namespaces[bcVal.module_namespace][j];
|
||||
bcSiblings.push({name: mod.name, path: siblingPath})
|
||||
}
|
||||
}
|
||||
|
||||
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+"/";
|
||||
$siblings.append('<a href="'+jsonDocsPathPrefix+sibPath+'">'+sib.name+'</a>');
|
||||
}
|
||||
$('#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');
|
||||
|
||||
var pathComponents = configPath.split('/');
|
||||
|
||||
// load the docs for this path
|
||||
$.get("/api/docs/config"+configPath, function(json) {
|
||||
pageData = json;
|
||||
if (pageData.structure.doc) {
|
||||
// for most types, just render their docs
|
||||
$('#top-doc').html(markdown(pageData.structure.doc));
|
||||
} else if (pageData.structure.elems) {
|
||||
// for maps or arrays, fall through to the underlying type
|
||||
$('#top-doc').html(markdown(pageData.structure.elems.doc));
|
||||
}
|
||||
$('#top-doc').append(makeSubmoduleList("", pageData.structure));
|
||||
|
||||
console.log("DATA:", pageData);
|
||||
renderData(pageData.structure, 0, "", $('<div class="group"/>'));
|
||||
console.log("DOCS:", pageDocs);
|
||||
|
||||
if ($('#field-list').html().trim()) {
|
||||
$('#field-list-header').show();
|
||||
}
|
||||
|
||||
// establish the breadcrumb
|
||||
var $bc = $('.breadcrumbs');
|
||||
$('<a href="'+jsonDocsPathPrefix+'" id="top-breadcrumb">JSON Config Structure</a> ›').appendTo($bc);
|
||||
for (var i = 1; i < pathComponents.length-1; i++) {
|
||||
var bcPath = pathComponents.slice(0, i+1).join('/');
|
||||
var bcSiblingPath = pathComponents.slice(1, i).join('/');
|
||||
|
||||
// prefixing with <span/> is a hack so jQuery treats this as a HTML DOM object
|
||||
$('<span/> › <a href="'+jsonDocsPathPrefix+bcPath.substr(1)+'/" class="breadcrumb has-popup" data-sibling-path="'+bcSiblingPath+'">'+pathComponents[i]+'</a>').appendTo($bc);
|
||||
}
|
||||
});
|
||||
|
||||
function renderData(data, nesting, path, $group) {
|
||||
switch (data.type) {
|
||||
case "struct":
|
||||
$group.append('{<a href="javascript:" class="toggle-obj expanded" title="Collapse/expand">▾</a>');
|
||||
nesting++;
|
||||
|
||||
var $fieldGroup = $('<div class="group"/>');
|
||||
renderModuleInlineKey(data, nesting, $fieldGroup);
|
||||
$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>';
|
||||
fieldDoc += makeSubmoduleList(fieldPath, field.value);
|
||||
appendToFieldDocs(cleanFieldPath, fieldDoc);
|
||||
|
||||
// render the field to the JSON box
|
||||
var $fieldGroup = $('<div class="group"/>');
|
||||
indent(nesting, $fieldGroup);
|
||||
$fieldGroup.append('<span class="qu">"</span><span class="key"><a href=".'+fieldPath+'/" data-path="'+fieldPath+'" class="'+linkClass+'">'+field.key+'</a></span><span class="qu">"</span>: ');
|
||||
renderData(field.value, nesting, fieldPath, $fieldGroup);
|
||||
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 {
|
||||
renderData(data.elems, nesting, path, $group);
|
||||
}
|
||||
$group.append(']');
|
||||
break;
|
||||
|
||||
case "map":
|
||||
$group.append('{\n')
|
||||
nesting++;
|
||||
renderModuleInlineKey(data, nesting, $group);
|
||||
indent(nesting, $group);
|
||||
renderData(data.map_keys, nesting, path, $group);
|
||||
$group.append(': ');
|
||||
renderData(data.elems, nesting, path, $group);
|
||||
$group.append('\n');
|
||||
nesting--;
|
||||
indent(nesting, $group);
|
||||
$group.append('}');
|
||||
break;
|
||||
|
||||
case "module":
|
||||
// TODO:
|
||||
$group.append('{<a href=".'+path+'/" class="module has-popup" data-namespace="'+(data.module_namespace || '')+'" data-path="'+path+'">•••</a>}');
|
||||
break;
|
||||
|
||||
case "module_map":
|
||||
// TODO:
|
||||
$group.append('{<a href=".'+path+'/" class="module has-popup" data-namespace="'+(data.module_namespace || '')+'" data-path="'+path+'">•••</a>}');
|
||||
break;
|
||||
}
|
||||
|
||||
$renderbox.append($group);
|
||||
}
|
||||
|
||||
function renderModuleInlineKey(data, nesting, $group) {
|
||||
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');
|
||||
|
||||
appendToFieldDocs(data.module_inline_key, $('#hovercard-inline-key').html());
|
||||
}
|
||||
|
||||
function appendToFieldDocs(cleanFieldPath, fieldDoc) {
|
||||
$('#field-list').append('<dt class="field-name" id="'+cleanFieldPath+'"><a href="#'+cleanFieldPath+'" class="inline-link">🔗</a><a href="./'+cleanFieldPath+'/">'+cleanFieldPath+'</a></dt> <dd>'+fieldDoc+'</dd>');
|
||||
}
|
||||
|
||||
// toggle an object as expanded or collapsed
|
||||
$('#renderbox').on('click', '.toggle-obj', function() {
|
||||
if ($(this).hasClass('expanded')) {
|
||||
// collapse
|
||||
$(this).html('▸');
|
||||
} else {
|
||||
// expand
|
||||
$(this).html('▾');
|
||||
}
|
||||
$(this).nextUntil('.end-obj').toggleClass('collapsed');
|
||||
$(this).toggleClass('expanded');
|
||||
});
|
||||
|
||||
function makeSubmoduleList(path, value) {
|
||||
while (value.elems) {
|
||||
value = value.elems;
|
||||
}
|
||||
if (value.type != "module" && value.type != "module_map") {
|
||||
return '';
|
||||
}
|
||||
var submodList = '<ul>';
|
||||
if (pageData.namespaces && pageData.namespaces[value.module_namespace]) {
|
||||
for (var j = 0; j < pageData.namespaces[value.module_namespace].length; j++) {
|
||||
var submod = pageData.namespaces[value.module_namespace][j];
|
||||
submodList += '<li><a href=".'+path+'/'+submod.name+'/">'+submod.name+'</li>';
|
||||
}
|
||||
}
|
||||
submodList += '</ul>';
|
||||
return '<div><p>Fulfilled by modules in namespace: <b>'+value.module_namespace+'</b></p>'+submodList+'</div>';
|
||||
}
|
||||
|
||||
function indent(nesting, $group) {
|
||||
var $span = $('<span class="indent"></span>');
|
||||
$span.append('\t'.repeat(nesting));
|
||||
$group.append($span);
|
||||
}
|
||||
|
||||
function truncate(str, len) {
|
||||
if (!str) return "";
|
||||
var startLen = str.length;
|
||||
str = str.substring(0, len);
|
||||
if (startLen > len) {
|
||||
str += "...";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
});
|
|
@ -11,6 +11,10 @@ $(function() {
|
|||
// as a special case, highlight the JSON structure link anywhere within it
|
||||
$currentPageLink = $('main nav a[href="/docs/json/"]');
|
||||
}
|
||||
if (hasPrefix(window.location.pathname, "/docs/modules/")) {
|
||||
// as another special case, highlight the modules link anywhere within it
|
||||
$currentPageLink = $('main nav a[href="/docs/modules/"]');
|
||||
}
|
||||
$currentPageLink.addClass('current');
|
||||
|
||||
// add anchor links, inspired by https://github.com/bryanbraun/anchorjs
|
||||
|
|
46
src/resources/js/json-docs.js
Normal file
46
src/resources/js/json-docs.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
const jsonDocsPathPrefix = "/docs/json/";
|
||||
|
||||
var configPath = window.location.pathname.substr(jsonDocsPathPrefix.length-1); // keep trailing slash
|
||||
var pathComponents = configPath.split('/');
|
||||
|
||||
setPageTitle();
|
||||
|
||||
// load the docs for this path
|
||||
$.get("/api/docs/config"+configPath, function(json) {
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
beginRendering(json);
|
||||
|
||||
// establish the breadcrumb
|
||||
var $bc = $('.breadcrumbs');
|
||||
$('<a href="'+jsonDocsPathPrefix+'" id="top-breadcrumb">JSON Config Structure</a> ›').appendTo($bc);
|
||||
for (var i = 1; i < pathComponents.length-1; i++) {
|
||||
var bcPath = pathComponents.slice(0, i+1).join('/');
|
||||
var bcSiblingPath = pathComponents.slice(1, i).join('/');
|
||||
|
||||
// prefixing with <span/> is a hack so jQuery treats this as a HTML DOM object
|
||||
$('<span/> › <a href="'+jsonDocsPathPrefix+bcPath.substr(1)+'/" class="breadcrumb has-popup" data-sibling-path="'+bcSiblingPath+'">'+pathComponents[i]+'</a>').appendTo($bc);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function setPageTitle() {
|
||||
// set the page title with something useful
|
||||
var parts = configPath.split("/");
|
||||
if (parts.length > 1) {
|
||||
if (!parts[0]) {
|
||||
parts.shift();
|
||||
}
|
||||
if (!parts[parts.length-1]) {
|
||||
parts.pop();
|
||||
}
|
||||
var titlePrefix = parts.slice(-2).join("/");
|
||||
if (parts.length > 4) {
|
||||
titlePrefix = parts.slice(0, 2).join("/")+"/.../"+titlePrefix;
|
||||
}
|
||||
if (titlePrefix) {
|
||||
document.title = titlePrefix + " - " + document.title;
|
||||
}
|
||||
}
|
||||
}
|
31
src/resources/js/module-docs.js
Normal file
31
src/resources/js/module-docs.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const moduleDocsPathPrefix = "/docs/modules/";
|
||||
|
||||
var moduleID = window.location.pathname.substr(moduleDocsPathPrefix.length);
|
||||
if (moduleID) {
|
||||
// update page title and load the docs for this module
|
||||
document.title = "Module " + moduleID + " - Caddy Documentation";
|
||||
$.get("/api/docs/module/"+moduleID, function(json) {
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
$('#module-docs-container').show();
|
||||
$('h1').text("Module "+moduleID);
|
||||
beginRendering(json);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// populate the module list
|
||||
$.get("/api/modules", function(moduleList) {
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
$('#module-list-container').show();
|
||||
$table = $('#module-list');
|
||||
for (modID in moduleList) {
|
||||
var doc = moduleList[modID];
|
||||
var $tr = $('<tr/>');
|
||||
$tr.append('<td><a href="./'+modID+'" class="module-link">'+modID+'</a></td>');
|
||||
$tr.append('<td>'+markdown(truncate(doc, 200))+'</td>');
|
||||
$table.append($tr);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue