- -
-Field List
--
-
-
diff --git a/Caddyfile b/Caddyfile index 9fa1df9..58fbec9 100644 --- a/Caddyfile +++ b/Caddyfile @@ -5,8 +5,10 @@ root * src file_server templates -redir /docs/json /docs/json/ -rewrite /docs/json/* /docs/json/index.html -rewrite /docs/* /docs/index.html +redir /docs/json /docs/json/ +redir /docs/modules /docs/modules/ +rewrite /docs/json/* /docs/json/index.html +rewrite /docs/modules/* /docs/modules/index.html +rewrite /docs/* /docs/index.html reverse_proxy /api/* localhost:4444 diff --git a/README.md b/README.md index cc2e741..088718e 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ This is the source of the Caddy website, [caddyserver.com](https://caddyserver.c 2. `cd website` 3. `caddy run` -Your first time, you may be prompted for a password. This is so Caddy can serve the site over local HTTPS. +Your first time, you may be prompted for a password. This is so Caddy can serve the site over local HTTPS. If you can't bind to low ports, change [the address at the top of the Caddyfile](https://github.com/caddyserver/website/blob/master/Caddyfile#L1), for example `localhost:2015`. -You can then load [https://localhost](https://localhost) in your browser. +You can then load [https://localhost](https://localhost) (or whatever address you configured) in your browser. diff --git a/src/docs/json/index.html b/src/docs/json/index.html index 0bdce92..6b3951b 100644 --- a/src/docs/json/index.html +++ b/src/docs/json/index.html @@ -1,11 +1,12 @@
-
+ {{include "/includes/docs-renderbox.html"}}
- -
-- Fulfilled by modules in namespace: -
- -This property is required because it specifies the module name.
-+ This page lists all registered Caddy modules. +
++ We recommend using your browser's "Find in page" feature for quick lookups. +
+Module ID | +Description | +
---|
+ +
++ Fulfilled by modules in namespace: +
+ +This property is required because it specifies the module name.
+
\ No newline at end of file
diff --git a/src/resources/css/docs.css b/src/resources/css/docs.css
index 99701ff..844f7c9 100644
--- a/src/resources/css/docs.css
+++ b/src/resources/css/docs.css
@@ -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 {
diff --git a/src/resources/js/docs-api.js b/src/resources/js/docs-api.js
new file mode 100644
index 0000000..a05b7dd
--- /dev/null
+++ b/src/resources/js/docs-api.js
@@ -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 =$('');
+ 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(''+modInfo.name+''+truncate(modInfo.docs, 115)+'');
+ }
+ }
+ $('#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 = $('').append(' ');
+ for (var j = 0; j < bcSiblings.length; j++) {
+ var sib = bcSiblings[j];
+ var sibPath = sib.path;
+ if (sibPath) {
+ sibPath += "/";
+ }
+ sibPath += sib.name+"/";
+ $siblings.append(''+sib.name+'');
+ }
+ $('#hovercard-breadcrumb-siblings').html($siblings).show();
+
+ } else if ($(this).hasClass('documented')) {
+ // docs
+ var elemDocs = truncate(pageDocs[elemPath], 500);
+ if (!elemDocs) {
+ elemDocs = 'There are no docs for this property.
'; + return; + } + $('#hovercard-docs').html(markdown(elemDocs)).show(); + $('#hovercard-inline-link').html('View docs below ↓').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, "", $('')); + 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('{▾'); + nesting++; + + var $fieldGroup = $(''); + 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) || 'There are no docs for this property.
'; + fieldDoc += makeSubmoduleList(fieldPath, field.value); + appendToFieldDocs(cleanFieldPath, fieldDoc); + + // render the field to the JSON box + var $fieldGroup = $(''); + indent(nesting, $fieldGroup); + var keyATag = ''+field.key+''; + $fieldGroup.append('"'+keyATag+'": '); + renderData(field.value, nesting, fieldPath, $fieldGroup); + if (i < data.struct_fields.length-1) { + $fieldGroup.append(','); + } + $group.append($fieldGroup); + } + } + nesting--; + indent(nesting, $group); + $group.append('}'); + break; + + case "bool": + $group.append('false'); // TODO: default value? + break; + + case "int": + case "uint": + case "float": + case "complex": + $group.append('0'); // TODO: default value? + break; + + case "string": + $group.append('""'); // TODO: default value? + break; + + case "array": + $group.append('['); + if (data.elems.type == "module_map") { + $group.append('{•••}'); + } 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 = '•••'; + $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('"'+data.module_inline_key+'": "'+moduleName+'"'); + 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 = ''+dt+''; + } + $('#field-list').append('Fulfilled by modules in namespace: '+value.module_namespace+'
'+submodList+'There are no docs for this property.
'; - return; - } - $('#hovercard-docs').html(markdown(elemDocs)).show(); - $('#hovercard-inline-link').html('View docs below ↓').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, "", $('')); - console.log("DOCS:", pageDocs); - - if ($('#field-list').html().trim()) { - $('#field-list-header').show(); - } - - // establish the breadcrumb - var $bc = $('.breadcrumbs'); - $('JSON Config Structure ›').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 is a hack so jQuery treats this as a HTML DOM object - $(' › '+pathComponents[i]+'').appendTo($bc); - } - }); - - function renderData(data, nesting, path, $group) { - switch (data.type) { - case "struct": - $group.append('{▾'); - nesting++; - - var $fieldGroup = $(''); - 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) || 'There are no docs for this property.
'; - fieldDoc += makeSubmoduleList(fieldPath, field.value); - appendToFieldDocs(cleanFieldPath, fieldDoc); - - // render the field to the JSON box - var $fieldGroup = $(''); - indent(nesting, $fieldGroup); - $fieldGroup.append('"'+field.key+'": '); - renderData(field.value, nesting, fieldPath, $fieldGroup); - if (i < data.struct_fields.length-1) { - $fieldGroup.append(','); - } - $group.append($fieldGroup); - } - } - nesting--; - indent(nesting, $group); - $group.append('}'); - break; - - case "bool": - $group.append('false'); // TODO: default value? - break; - - case "int": - case "uint": - case "float": - case "complex": - $group.append('0'); // TODO: default value? - break; - - case "string": - $group.append('""'); // TODO: default value? - break; - - case "array": - $group.append('['); - if (data.elems.type == "module_map") { - $group.append('{•••}'); - } 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('{•••}'); - break; - - case "module_map": - // TODO: - $group.append('{•••}'); - 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('"'+data.module_inline_key+'": "'+moduleName+'"'); - 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('Fulfilled by modules in namespace: '+value.module_namespace+'
'+submodList+'