mirror of
https://github.com/caddyserver/website.git
synced 2025-04-22 21:16:15 -04:00
New Website: Phase I (#357)
* Initial commit; starting new design Dropdown menu * Begin docs layout of new design * Get themes under control; button hover splash * Some basic responsiveness * Finish responsive layout; several bug fixes * Avoid flash during color scheme change * Begin building top of homepage * docs: Start building quick-assist feature * Work on homepage a little more * Keep working on homepage * More homepage progress * Some sponsor SVGs * Add sponsor features * Implement basic Sponsor Experience box * Reorganize some styles * WIP sponsors page * Start features page WIP * Minor improvements * Fix headings; work on features page * WIP features page * Continue work on marketing pages * Continue work on features page * More features WIP * Continue features page... * More work on features page * Keeping going :) * Continue home and features pages * More homepage/features content, screenshots, tweaks * Minor fixes to features page * Minor tweaks * Work on testimonials * Work on homepage more * More homepage work * Continue work on homepage * Add some sponsor logos * Some citation screenshots * Add citations * Start making homepage responsive * Re-add cache busting Fix docs * Use markdown syntax highlighting on frontpage * Rework AJQuery to $_ to not interfere with jQuery * Rewrite quick assist with AlpineJS, use markdown for contents * More work on marketing pages * Rebase and fix code displays * Syntax highlight on-demand example, fix rollover * Adjust on-demand demo * Work on responsiveness * Keep working on responsiveness * Mainly finish making design responsive * Thiccer favicon * More work on marketing pages * Keep on going * Fix link * Move new site into src folder * Add open graph image * Add recorded demo for homepage * Tweak caption * Fix Poppins font for now * Minor tweaks * Trim demo ending * Remove unfinished pages Also update Framer logo --------- Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
parent
5bb6d92c63
commit
07c51663ab
191 changed files with 13008 additions and 4970 deletions
63
src/old/resources/js/account/common.js
Normal file
63
src/old/resources/js/account/common.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
if (!loggedIn()
|
||||
&& window.location.pathname != '/account/login'
|
||||
&& window.location.pathname != '/account/create'
|
||||
&& window.location.pathname != '/account/verify'
|
||||
&& window.location.pathname != '/account/reset-password') {
|
||||
window.location = '/account/login?redir='+encodeURIComponent(window.location);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// highlight current page in left nav
|
||||
var $currentPageLink = $('.container > nav a[href="'+window.location.pathname+'"]');
|
||||
$currentPageLink.addClass('current');
|
||||
|
||||
// shortcut any logout links to make the POST directly
|
||||
$('a[href="/account/logout"]').click(function() {
|
||||
logout();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
function loggedIn() {
|
||||
return document.cookie.indexOf('user=') > -1;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
$.post('/api/logout').done(function() {
|
||||
window.location = '/';
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
document.cookie = 'user=; Path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
swal({
|
||||
icon: "error",
|
||||
title: error,
|
||||
content: errorContent(jqxhr)
|
||||
}).then(function() {
|
||||
window.location = '/account/'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function errorContent(jqxhr) {
|
||||
var div = document.createElement('div');
|
||||
var p1 = document.createElement('p');
|
||||
p1.appendChild(document.createTextNode("Sorry, something went wrong:"));
|
||||
div.appendChild(p1);
|
||||
|
||||
var p2 = document.createElement('p');
|
||||
var p2b = document.createElement('b');
|
||||
p2b.appendChild(document.createTextNode(jqxhr.responseJSON ? jqxhr.responseJSON.error.message : jqxhr.status + " " + jqxhr.statusText));
|
||||
p2.appendChild(p2b)
|
||||
div.appendChild(p2);
|
||||
|
||||
if (jqxhr.responseJSON) {
|
||||
var p3 = document.createElement('p');
|
||||
p3.appendChild(document.createTextNode("Please include this error ID if reporting:"));
|
||||
p3.appendChild(document.createElement('br'));
|
||||
p3.appendChild(document.createTextNode(jqxhr.responseJSON.error.id));
|
||||
div.appendChild(p3);
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
28
src/old/resources/js/account/create.js
Normal file
28
src/old/resources/js/account/create.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
if (loggedIn()) window.location = '/account/';
|
||||
|
||||
$(function() {
|
||||
$('form input').first().focus();
|
||||
|
||||
$('form').submit(function(event) {
|
||||
$('#submit').prop('disabled', true);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "Check your email",
|
||||
text: "We've sent you an email with a link that expires in 48 hours. Please verify your account before you can use it."
|
||||
}).then(function() {
|
||||
window.location = '/account/verify';
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
titleText: "Error",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('#submit').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
128
src/old/resources/js/account/dashboard.js
Normal file
128
src/old/resources/js/account/dashboard.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
// download package list as soon as possible
|
||||
$.get("/api/user-packages").done(function(json) {
|
||||
var packageList = json.result;
|
||||
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
// trying out this fancy new syntax:
|
||||
// https://twitter.com/joshmanders/status/1282395540970496001
|
||||
packageList.forEach(pkg => {
|
||||
var $tdPath = $('<td><input type="text" name="path" maxlength="255"></td>');
|
||||
var $tdListed = $('<td class="text-center"><input type="checkbox" name="listed"></td>');
|
||||
var $tdAvail = $('<td class="text-center"><input type="checkbox" name="available"></td>');
|
||||
var $tdDownloads = $('<td>0</td>');
|
||||
var $tdLinks = $('<td><a href="javascript:" class="rescan-package">Rescan</a> <a href="javascript:" class="delete-package">Delete</a></td>');
|
||||
|
||||
if (pkg.listed) {
|
||||
$('input', $tdListed).prop('checked', true);
|
||||
}
|
||||
if (pkg.available) {
|
||||
$('input', $tdAvail).prop('checked', true);
|
||||
}
|
||||
if (pkg.downloads) {
|
||||
$tdDownloads.text(pkg.downloads);
|
||||
}
|
||||
var $pathInput = $('input', $tdPath);
|
||||
$pathInput.val(pkg.path).attr('size', pkg.path.length);
|
||||
|
||||
var $tr = $('<tr data-package-id="'+pkg.id+'"></tr>');
|
||||
$tr.append($tdPath)
|
||||
.append($tdListed)
|
||||
.append($tdAvail)
|
||||
.append($tdDownloads)
|
||||
.append($tdLinks);
|
||||
|
||||
$('#user-packages').append($tr);
|
||||
|
||||
// scroll package paths to the left so if they get
|
||||
// cut off, the leaf package name is still visible
|
||||
$pathInput.scrollLeft($pathInput.width());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
|
||||
// update packages when fields change
|
||||
$('#user-packages').on('change', 'input', function() {
|
||||
$tr = $(this).closest('tr');
|
||||
$('input', $tr).prop('disabled', true);
|
||||
$.post('/api/update-package', {
|
||||
id: $tr.data('package-id'),
|
||||
listed: $('[name=listed]', $tr).prop('checked') ? 1 : 0,
|
||||
available: $('[name=available]', $tr).prop('checked') ? 1 : 0,
|
||||
path: $('[name=path]', $tr).val()
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Could not save changes",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
}).always(function() {
|
||||
$('input', $tr).prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
// rescan package
|
||||
$('#user-packages').on('click', '.rescan-package', function() {
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
$tr = $(this).closest('tr');
|
||||
$('a', $tr).addClass('disabled');
|
||||
|
||||
$.post('/api/rescan-package', {
|
||||
package_id: $tr.data('package-id')
|
||||
}).done(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "Rescan Complete",
|
||||
text: "Package has been re-scanned and its documentation has been updated."
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Rescan failed",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
}).always(function() {
|
||||
$('a', $tr).removeClass('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
// delete package
|
||||
$('#user-packages').on('click', '.delete-package', function() {
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
swal({
|
||||
title: "Delete package?",
|
||||
text: "Deleting the package will remove it from our website.",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then((willDelete) => {
|
||||
// abort if user cancelled
|
||||
if (!willDelete) return;
|
||||
|
||||
$tr = $(this).closest('tr');
|
||||
$('input', $tr).prop('disabled', true);
|
||||
$.post('/api/delete-package', {
|
||||
id: $tr.data('package-id')
|
||||
}).done(function(jqxhr, status, error) {
|
||||
$tr.remove();
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "Package deleted"
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Delete failed",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
}).always(function() {
|
||||
$('input', $tr).prop('disabled', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
24
src/old/resources/js/account/login.js
Normal file
24
src/old/resources/js/account/login.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
if (loggedIn()) window.location = '/account/';
|
||||
|
||||
$(function() {
|
||||
$('form input').first().focus();
|
||||
|
||||
$('form').submit(function(event) {
|
||||
$('#submit').prop('disabled', true);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
var qsParams = new URLSearchParams(window.location.search);
|
||||
var destination = qsParams.get('redir');
|
||||
window.location = destination ? destination : '/account/';
|
||||
}).fail(function(jqxhr, msg, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Bad credentials",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('#submit').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
1
src/old/resources/js/account/logout.js
Normal file
1
src/old/resources/js/account/logout.js
Normal file
|
@ -0,0 +1 @@
|
|||
logout();
|
27
src/old/resources/js/account/register-package.js
Normal file
27
src/old/resources/js/account/register-package.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
$(function() {
|
||||
$('form input').first().focus();
|
||||
|
||||
$('form').submit(function(event) {
|
||||
$('#submit').prop('disabled', true);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "It's yours",
|
||||
text: "Package claimed. Its documentation is now available on our website and you are responsible for maintaining it. Thank you!"
|
||||
}).then(function() {
|
||||
// TODO: ...
|
||||
// window.location = "/account/login";
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: error,
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('#submit').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
80
src/old/resources/js/account/reset-password.js
Normal file
80
src/old/resources/js/account/reset-password.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
if (loggedIn()) window.location = '/account/';
|
||||
|
||||
$(function() {
|
||||
var qsParams = new URLSearchParams(window.location.search);
|
||||
var email = qsParams.get("email");
|
||||
var token = qsParams.get("token");
|
||||
$('input[name=email]').val(email);
|
||||
$('input[name=token]').val(token);
|
||||
if (email && token) showStep2();
|
||||
|
||||
$('form input:visible').first().focus();
|
||||
|
||||
$('#reset-password-step1').submit(function(event) {
|
||||
$('button').prop('disabled', false);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
swal({
|
||||
icon: "info",
|
||||
title: "Check your email",
|
||||
text: "If we have an account with that email address, we just sent you some instructions."
|
||||
}).then(function() {
|
||||
window.location = '/';
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: error,
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('button').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#reset-password-step2').submit(function(event) {
|
||||
$('button').prop('disabled', false);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "Reset completed",
|
||||
text: "You may now log in with your new password."
|
||||
}).then(function() {
|
||||
window.location = '/account/login';
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Error",
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('button').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#goto-step1').click(function(event) {
|
||||
$('#reset-password-step2').hide('fast');
|
||||
$('#reset-password-step1').show('fast', function() {
|
||||
$('input:visible').first().focus();
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#goto-step2').click(function(event) {
|
||||
showStep2();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function showStep2() {
|
||||
$('#reset-password-step1').hide('fast');
|
||||
$('#reset-password-step2').show('fast', function() {
|
||||
if ($('input[name=token]').val() != "")
|
||||
$('input[name=password]').focus();
|
||||
else
|
||||
$('input:visible').first().focus();
|
||||
});
|
||||
}
|
36
src/old/resources/js/account/verify.js
Normal file
36
src/old/resources/js/account/verify.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
if (loggedIn()) window.location = '/account/';
|
||||
|
||||
$(function() {
|
||||
$('form input').first().focus();
|
||||
|
||||
$('form').submit(function(event) {
|
||||
$('#submit').prop('disabled', true);
|
||||
|
||||
$.post($(this).prop("action"), $(this).serialize()).done(function() {
|
||||
swal({
|
||||
icon: "success",
|
||||
title: "Account confirmed",
|
||||
text: "Thank you. You may now log in and use your account!"
|
||||
}).then(function() {
|
||||
window.location = "/account/login";
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: error,
|
||||
content: errorContent(jqxhr)
|
||||
});
|
||||
$('#submit').prop('disabled', false);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// if info is in the query string, fill use and submit it
|
||||
var qsParams = new URLSearchParams(window.location.search);
|
||||
var email = qsParams.get("email");
|
||||
var acct = qsParams.get("code");
|
||||
$('input[name=email]').val(email);
|
||||
$('input[name=account_id]').val(acct);
|
||||
if (email && acct) $('form').submit();
|
||||
});
|
81
src/old/resources/js/common.js
Normal file
81
src/old/resources/js/common.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Algolia search
|
||||
docsearch({
|
||||
appId: "BH4D9OD16A",
|
||||
apiKey: '14275a785f6ebd31d162f9d2d8fc0125',
|
||||
indexName: 'caddyserver',
|
||||
container: '#search',
|
||||
});
|
||||
});
|
||||
|
||||
const caddyImportPath = 'github.com/caddyserver/caddy/v2';
|
||||
|
||||
function isStandard(packagePath) {
|
||||
return packagePath.startsWith(caddyImportPath);
|
||||
}
|
||||
|
||||
function truncate(str, maxLen) {
|
||||
if (!str) return "";
|
||||
str = str.trim();
|
||||
let firstPeriod = str.match(/\.(\s|$)/); // first dot not in the middle of a word, or at end of string
|
||||
let terminate = firstPeriod ? firstPeriod.index+1 : str.length;
|
||||
str = str.substring(0, terminate);
|
||||
if (str.length <= maxLen) {
|
||||
return str;
|
||||
}
|
||||
return str+"...";
|
||||
}
|
||||
|
||||
function moduleDocsPreview(mod, maxLen) {
|
||||
if (!mod || !mod.docs) return "";
|
||||
let short = truncate(mod.docs, maxLen);
|
||||
if (short.indexOf(mod.name) === 0) {
|
||||
short = short.substr(mod.name.length).trim();
|
||||
}
|
||||
return short;
|
||||
}
|
||||
|
||||
function detectPlatform() {
|
||||
// assume 32-bit linux, then change OS and architecture if justified
|
||||
var os = "linux", arch = "amd64";
|
||||
|
||||
// change os
|
||||
if (/Macintosh/i.test(navigator.userAgent)) {
|
||||
os = "darwin";
|
||||
} else if (/Windows/i.test(navigator.userAgent)) {
|
||||
os = "windows";
|
||||
} else if (/FreeBSD/i.test(navigator.userAgent)) {
|
||||
os = "freebsd";
|
||||
} else if (/OpenBSD/i.test(navigator.userAgent)) {
|
||||
os = "openbsd";
|
||||
}
|
||||
|
||||
// change architecture
|
||||
if (os == "darwin" || /amd64|x64|x86_64|Win64|WOW64|i686|64-bit/i.test(navigator.userAgent)) {
|
||||
arch = "amd64";
|
||||
} else if (/arm64/.test(navigator.userAgent)) {
|
||||
arch = "arm64";
|
||||
} else if (/ ARM| armv/.test(navigator.userAgent)) {
|
||||
arch = "arm";
|
||||
}
|
||||
|
||||
// change arm version
|
||||
if (arch == "arm") {
|
||||
var arm = "7"; // assume version 7 by default
|
||||
if (/armv6/.test(navigator.userAgent)) {
|
||||
arm = "6";
|
||||
} else if (/armv5/.test(navigator.userAgent)) {
|
||||
arm = "5";
|
||||
}
|
||||
arch += arm;
|
||||
}
|
||||
|
||||
return [os, arch];
|
||||
}
|
||||
|
||||
// Detect the platform OS, but with an allow-list of values
|
||||
// and if the value is not allowed, return the default.
|
||||
function defaultOS(allowed, def) {
|
||||
var [os] = detectPlatform();
|
||||
return allowed.includes(os) ? os : def;
|
||||
}
|
393
src/old/resources/js/docs-api.js
Normal file
393
src/old/resources/js/docs-api.js
Normal file
|
@ -0,0 +1,393 @@
|
|||
// TODO: sanitize all HTML renderings, especially markdown: https://github.com/cure53/DOMPurify
|
||||
|
||||
var pageDocs = {};
|
||||
var pageData = {};
|
||||
var $hovercard;
|
||||
|
||||
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>';
|
||||
const standardFlag = '<span class="standard-flag" title="This module comes with official Caddy distributions by default.">Standard</span>';
|
||||
|
||||
|
||||
$(function() {
|
||||
$hovercard = $('#hovercard');
|
||||
|
||||
var hoverTimeout;
|
||||
$hovercard.hover(function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
}, function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$hovercard.removeClass('popup');
|
||||
});
|
||||
|
||||
// toggle an object as expanded or collapsed
|
||||
$('body').on('click', '.renderbox .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).offset();
|
||||
var moduleID = $(this).closest('.module-repo-container').data('module-id') || '';
|
||||
var moduleData = pageData[moduleID];
|
||||
|
||||
// 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.offset().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 (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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
$('#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 = moduleData.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, isStandard: isStandard(bcVal.type_name)})
|
||||
}
|
||||
break;
|
||||
|
||||
case "module":
|
||||
case "module_map":
|
||||
for (var j = 0; j < moduleData.namespaces[bcVal.module_namespace].length; j++) {
|
||||
var mod = moduleData.namespaces[bcVal.module_namespace][j];
|
||||
bcSiblings.push({name: mod.name, path: siblingPath, isStandard: isStandard(mod.package)})
|
||||
}
|
||||
}
|
||||
|
||||
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+"/";
|
||||
var aTag = '<a href="'+jsonDocsPathPrefix+sibPath+'"';
|
||||
if (!sib.isStandard) {
|
||||
aTag += ' class="nonstandard" title="Non-standard module"';
|
||||
}
|
||||
aTag += '>'+sib.name+'</a>';
|
||||
$siblings.append(aTag);
|
||||
}
|
||||
$('#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 beginRenderingInto($tpl, moduleID, module) {
|
||||
console.log("RENDERING:", moduleID, module);
|
||||
$tpl.data('module-id', moduleID);
|
||||
pageData[moduleID] = module;
|
||||
|
||||
// show notice if module is non-standard
|
||||
if (module.repo) {
|
||||
if (!isStandard(module.structure.type_name)) {
|
||||
let { pkg, _ } = splitTypeName(module.structure.type_name);
|
||||
$('.nonstandard-project-link', $tpl).attr('href', module.repo).text(module.repo);
|
||||
$('.nonstandard-package-path', $tpl).text(pkg);
|
||||
$('.nonstandard-notice', $tpl).css('display', 'block').prepend(nonStandardFlag); // for some reason show() dosen't work
|
||||
}
|
||||
|
||||
var $repoName = $('<span/>').text(stripScheme(module.repo));
|
||||
$('.module-repo-selector', $tpl).html('<span class="module-repo-selector-arrow">▸</span>').append($repoName);
|
||||
}
|
||||
|
||||
// for most types, just render their docs; but for maps or arrays, fall through to underlying type for docs
|
||||
let rawDocs = module.structure.doc ?? module.structure.elems;
|
||||
|
||||
$('.top-doc', $tpl).html(markdown(replaceGoTypeNameWithCaddyModuleName(rawDocs, module, moduleID)));
|
||||
$('.top-doc', $tpl).append(makeSubmoduleList(module, "", module.structure));
|
||||
|
||||
let $group = newGroup();
|
||||
renderData($tpl, module, module.structure, 0, "", $group);
|
||||
$('.renderbox', $tpl).append($group);
|
||||
|
||||
if ($('.field-list-contents', $tpl).text().trim()) {
|
||||
$('.field-list-header', $tpl).show();
|
||||
}
|
||||
|
||||
// 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();
|
||||
// }
|
||||
}
|
||||
|
||||
function renderData($tpl, module, 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 = newGroup();
|
||||
renderModuleInlineKey($tpl, module, 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(module, fieldPath, field.value);
|
||||
appendToFieldDocs($tpl, module, cleanFieldPath, fieldDoc);
|
||||
|
||||
// render the field to the JSON box
|
||||
var $fieldGroup = newGroup();
|
||||
indent(nesting, $fieldGroup);
|
||||
var keyATag = '<a ';
|
||||
if (canTraverse(module)) {
|
||||
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($tpl, module, 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($tpl, module, data.elems, nesting, path, $group);
|
||||
}
|
||||
$group.append(']');
|
||||
break;
|
||||
|
||||
case "map":
|
||||
$group.append('{\n')
|
||||
nesting++;
|
||||
renderModuleInlineKey($tpl, module, data, nesting, $group);
|
||||
indent(nesting, $group);
|
||||
renderData($tpl, module, data.map_keys, nesting, path, $group);
|
||||
$group.append(': ');
|
||||
renderData($tpl, module, 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(module)) {
|
||||
aTag += ' href=".'+path+'/"';
|
||||
}
|
||||
aTag += ' class="module has-popup" data-namespace="'+(data.module_namespace || '')+'" data-path="'+path+'">•••</a>';
|
||||
$group.append('{'+aTag+'}');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function renderModuleInlineKey($tpl, module, 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($tpl, module, data.module_inline_key, $('#hovercard-inline-key').html());
|
||||
}
|
||||
|
||||
function appendToFieldDocs($tpl, module, cleanFieldPath, fieldDoc) {
|
||||
var dt = cleanFieldPath;
|
||||
if (canTraverse(module)) {
|
||||
dt = '<a href="./'+cleanFieldPath+'/">'+dt+'</a>';
|
||||
}
|
||||
$('.field-list-contents', $tpl).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 makeSubmoduleList(module, path, value) {
|
||||
while (value.elems) {
|
||||
value = value.elems;
|
||||
}
|
||||
if (value.type != "module" && value.type != "module_map") {
|
||||
return '';
|
||||
}
|
||||
var submodList = '<ul>';
|
||||
if (module.namespaces && module.namespaces[value.module_namespace]) {
|
||||
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;
|
||||
var submodLink = '<a href="'+href+'">'+submod.name+'</a>';
|
||||
if (!isStandard(submod.package)) {
|
||||
submodLink += ' '+nonStandardFlag;
|
||||
}
|
||||
submodList += '<li>'+submodLink+'</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(data) {
|
||||
return data.breadcrumb != null;
|
||||
}
|
||||
|
||||
function newGroup() {
|
||||
return $('<div class="group"/>');
|
||||
}
|
||||
|
||||
function replaceGoTypeNameWithCaddyModuleName(docs, module, moduleID) {
|
||||
if (!docs || !moduleID) return docs;
|
||||
|
||||
// fully qualified type name
|
||||
let fqtn = module.structure.type_name;
|
||||
|
||||
// extract just the local type name
|
||||
let {_, typeName} = splitTypeName(fqtn);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
function markdown(input) {
|
||||
if (!input) {
|
||||
return "";
|
||||
}
|
||||
return marked(input);
|
||||
}
|
87
src/old/resources/js/docs.js
Normal file
87
src/old/resources/js/docs.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
$(function() {
|
||||
function hasPrefix(str, prefix) {
|
||||
if (!prefix) return true;
|
||||
if (!str) return false;
|
||||
return str.indexOf(prefix) === 0;
|
||||
}
|
||||
|
||||
// highlight current page in left nav
|
||||
var $currentPageLink = $('main nav a[href="'+window.location.pathname+'"]');
|
||||
if (hasPrefix(window.location.pathname, "/docs/json/")) {
|
||||
// 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
|
||||
$('article > h1[id], article > h2[id], article > h3[id], article > h4[id], article > h5[id], article > h6[id]').each(function() {
|
||||
var $anchor = $('<a href="#'+this.id+'" class="anchor-link" title="Direct link">🔗</a>');
|
||||
$(this).append($anchor);
|
||||
});
|
||||
|
||||
// the server-side markdown renderer annoyingly renders
|
||||
// colored code blocks differently from plain ones, in that
|
||||
// colorized ones do not have the additional <code> inside
|
||||
// the <pre>; this line finds those and adds a .chroma class
|
||||
// to the outer pre element, and our CSS file has a style to
|
||||
// ensure the inner code block does not produce extra padding
|
||||
$('article > pre:not(.chroma) > code:not(.cmd)').parent().addClass('chroma');
|
||||
|
||||
// Add links to Caddyfile directive tokens in code blocks.
|
||||
// See include/docs-head.html for the whitelist bootstrapping logic
|
||||
$('pre.chroma .k')
|
||||
.filter((k, item) =>
|
||||
window.CaddyfileDirectives.includes(item.innerText)
|
||||
|| item.innerText === '<directives...>'
|
||||
)
|
||||
.map(function(k, item) {
|
||||
let text = item.innerText;
|
||||
let url = text === '<directives...>'
|
||||
? '/docs/caddyfile/directives'
|
||||
: '/docs/caddyfile/directives/' + text;
|
||||
text = text.replace(/</g,'<').replace(/>/g,'>');
|
||||
$(item).html('<a href="' + url + '" style="color: inherit;" title="Directive">' + text + '</a>');
|
||||
});
|
||||
|
||||
// Add links to [<matcher>] or named matcher tokens in code blocks.
|
||||
// The matcher text includes <> characters which are parsed as HTML,
|
||||
// so we must use text() to change the link text.
|
||||
$('pre.chroma .nd')
|
||||
.map(function(k, item) {
|
||||
let text = item.innerText.replace(/</g,'<').replace(/>/g,'>');
|
||||
$(item).html('<a href="/docs/caddyfile/matchers#syntax" style="color: inherit;" title="Matcher token">' + text + '</a>');
|
||||
});
|
||||
});
|
||||
|
||||
// addLinkaddLinksToSubdirectivessToAnchors finds all the ID anchors
|
||||
// in the article, and turns any directive or subdirective span into
|
||||
// links that have an ID on the page. This is opt-in for each page,
|
||||
// because it's not necessary to run everywhere.
|
||||
function addLinksToSubdirectives() {
|
||||
let anchors = $('article *[id]').map((i, el) => el.id).toArray();
|
||||
$('pre.chroma .k')
|
||||
.filter((k, item) => anchors.includes(item.innerText))
|
||||
.map(function(k, item) {
|
||||
let text = item.innerText.replace(/</g,'<').replace(/>/g,'>');
|
||||
let url = '#' + item.innerText;
|
||||
$(item).html('<a href="' + url + '" style="color: inherit;" title="' + text + '">' + text + '</a>');
|
||||
});
|
||||
}
|
||||
|
||||
function stripScheme(url) {
|
||||
return url.substring(url.indexOf("://")+3);
|
||||
}
|
||||
|
||||
// splitTypeName splits a fully qualified type name into
|
||||
// its package path and type name components, for example:
|
||||
// "github.com/foo/bar.Type" => "github.com/foo/bar" and "Type".
|
||||
function splitTypeName(fqtn) {
|
||||
let lastDotPos = fqtn.lastIndexOf('.');
|
||||
let pkg = fqtn.substr(0, lastDotPos);
|
||||
let typeName = fqtn.substr(lastDotPos+1);
|
||||
return {pkg: pkg, typeName: typeName};
|
||||
}
|
278
src/old/resources/js/download.js
Normal file
278
src/old/resources/js/download.js
Normal file
|
@ -0,0 +1,278 @@
|
|||
// download package list as soon as possible
|
||||
$.get("/api/packages").done(function(json) {
|
||||
// sort package list by most popular, seems to make sense for convenience
|
||||
var packageList = json.result;
|
||||
packageList.sort((a, b) => {
|
||||
return b.downloads > a.downloads ? 1 : -1;
|
||||
});
|
||||
|
||||
const preselectedPackage = new URL(window.location.href).searchParams.getAll("package");
|
||||
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
const packageTemplate =
|
||||
'<div class="package">\n'+
|
||||
' <div class="package-icon">📦</div>\n'+
|
||||
' <div class="package-data">\n'+
|
||||
' <div class="package-meta">\n'+
|
||||
' <b>downloads:</b> <span class="package-downloads"></span>\n'+
|
||||
' <b>version:</b> <input type="text" class="package-version-input" name="version" placeholder="latest" title="Any version string recognized by `go get` can be used">\n'+
|
||||
' </div>\n'+
|
||||
' <a target="_blank" title="View package repo" class="package-link"></a>\n'+
|
||||
' <div class="package-modules"></div>\n'+
|
||||
' </div>\n'+
|
||||
'</div>\n'
|
||||
|
||||
const moduleTemplate =
|
||||
'<div class="module">\n'+
|
||||
' 🔌 <a target="_blank" title="View module docs" class="module-link"></a>\n'+
|
||||
' <span class="module-desc"></span>\n'+
|
||||
'</div>\n';
|
||||
|
||||
|
||||
for (var i = 0; i < packageList.length; i++) {
|
||||
var pkg = packageList[i];
|
||||
|
||||
var $pkg = $(packageTemplate);
|
||||
|
||||
let { provider, path } = splitVCSProvider(pkg.path);
|
||||
if (provider) {
|
||||
var $pkgHost = $('<span class="package-host"/>').text(provider);
|
||||
$('.package-link', $pkg).html($pkgHost).append('<br/>');
|
||||
}
|
||||
$pkgName = $('<span class="package-name"/>').text(path);
|
||||
|
||||
$('.package-link', $pkg).append($pkgName);
|
||||
$('.package-link', $pkg).prop('href', pkg.repo);
|
||||
$('.package-downloads', $pkg).text(pkg.downloads);
|
||||
if (preselectedPackage.includes(pkg.path)) {
|
||||
$($pkg).addClass("selected");
|
||||
}
|
||||
|
||||
if (pkg.modules && pkg.modules.length > 0) {
|
||||
for (var j = 0; j < pkg.modules.length; j++) {
|
||||
var mod = pkg.modules[j];
|
||||
var $mod = $(moduleTemplate);
|
||||
// TODO: if this module name collides with that from another package, add a #hash to the URL to expand the right module's docs automatically
|
||||
$('.module-link', $mod).attr('href', '/docs/modules/'+mod.name).text(mod.name).attr('title', "View module details");
|
||||
$('.module-desc', $mod).text(moduleDocsPreview(mod, 120));
|
||||
$('.package-modules', $pkg).append($mod);
|
||||
}
|
||||
} else {
|
||||
$('.package-modules', $pkg)
|
||||
.addClass("package-no-modules")
|
||||
.text('This package does not add any modules to the JSON config structure. Either it is another kind of plugin (such as a config adapter) or this listing is in error.');
|
||||
}
|
||||
$('#optional-packages').append($pkg);
|
||||
}
|
||||
updatePage();
|
||||
});
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Unavailable",
|
||||
content: $('<div>Sorry, the build server is down for maintenance right now. You can try again later or <a href="https://github.com/caddyserver/caddy/releases/latest">download pre-built Caddy binaries from GitHub</a> any time.</div>')[0]
|
||||
});
|
||||
$(function() {
|
||||
disableFields(false);
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
autoPlatform();
|
||||
|
||||
downloadButtonHtml = $('#download').html();
|
||||
|
||||
$('#filter').on('search keyup', function(event) {
|
||||
var count = 0;
|
||||
var q = $(this).val().trim().toLowerCase();
|
||||
|
||||
$('.package').each(function() {
|
||||
if (!q) {
|
||||
// filter is cleared; show all
|
||||
this.style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var corpus = $(this).find('.package-link, .module-link, .module-desc').text().trim().toLowerCase();
|
||||
|
||||
if (corpus.indexOf(q) === -1) {
|
||||
this.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
this.style.display = '';
|
||||
count++;
|
||||
});
|
||||
|
||||
// update color of search input based on results
|
||||
if (q) {
|
||||
if (count > 0) {
|
||||
$('#filter').addClass('found').removeClass('not-found');
|
||||
} else {
|
||||
$('#filter').addClass('not-found').removeClass('found');
|
||||
}
|
||||
} else {
|
||||
$('#filter').removeClass('found not-found');
|
||||
}
|
||||
});
|
||||
|
||||
$('#platform').change(function() {
|
||||
updatePage();
|
||||
});
|
||||
|
||||
$('#optional-packages').on('click', '.package', function() {
|
||||
$(this).toggleClass('selected');
|
||||
updatePage();
|
||||
|
||||
let newUrl = new URL(window.location.href);
|
||||
let currentSelected = newUrl.searchParams.getAll("package") ;
|
||||
newUrl.searchParams.delete("package");
|
||||
const pkgPath = $('.package-link', $(this)).text().trim();
|
||||
if ($(this).hasClass('selected')) {
|
||||
if (!currentSelected.includes(pkgPath)) {
|
||||
currentSelected = [...currentSelected, pkgPath];
|
||||
}
|
||||
} else {
|
||||
const position = currentSelected.indexOf(pkgPath);
|
||||
if (position >= 0) {
|
||||
currentSelected.splice(position, 1);
|
||||
}
|
||||
}
|
||||
currentSelected.forEach( (selected) => newUrl.searchParams.append("package", selected));
|
||||
history.replaceState({}, document.title, newUrl.toString());
|
||||
});
|
||||
|
||||
// when a link within a package listing is clicked, only operate the link (don't select the package)
|
||||
$('#optional-packages').on('click', '.package-link, .module-link, .package-version-input', function(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
$('#download').click(function() {
|
||||
if ($(this).hasClass('disabled')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
disableFields(true);
|
||||
|
||||
if (typeof fathom !== 'undefined') {
|
||||
fathom.trackGoal('U9K2UTFV', 0);
|
||||
}
|
||||
|
||||
$.ajax($(this).attr('href'), { method: "HEAD" }).done(function(data, status, jqxhr) {
|
||||
window.onbeforeunload = null; // disable exit confirmation before "redirecting" to download
|
||||
window.location = jqxhr.getResponseHeader("Location");
|
||||
}).fail(function(jqxhr, status, error) {
|
||||
handleBuildError(jqxhr, status, error);
|
||||
}).always(function() {
|
||||
enableFields();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
})
|
||||
|
||||
// autoPlatform chooses the platform in the list that best
|
||||
// matches the user's browser, if it's available.
|
||||
function autoPlatform() {
|
||||
var [os, arch] = detectPlatform();
|
||||
$('#platform').val(os+"-"+arch);
|
||||
updatePage();
|
||||
}
|
||||
|
||||
function getDownloadLink() {
|
||||
// make sure we at least have a default,
|
||||
// in the case that autoPlatform() failed
|
||||
var platformString = $('#platform').val();
|
||||
if (!platformString) {
|
||||
platformString = "linux-amd64"
|
||||
}
|
||||
|
||||
// get platform components
|
||||
var [os, arch, arm = ""] = platformString.split("-");
|
||||
|
||||
var qs = new URLSearchParams();
|
||||
if (os) qs.set("os", os);
|
||||
if (arch) qs.set("arch", arch);
|
||||
if (arm) qs.set("arm", arm);
|
||||
|
||||
// get plugins and their versions
|
||||
$('#optional-packages .selected').each(function() {
|
||||
// get package path
|
||||
var p = $('.package-link', this).text().trim();
|
||||
|
||||
// get package version, if user specified one
|
||||
var ver = $('input[name=version]', this).val().trim();
|
||||
if (ver) {
|
||||
p += "@"+ver;
|
||||
}
|
||||
|
||||
qs.append("p", p);
|
||||
});
|
||||
$("#darwin-warning").toggle(os === "darwin");
|
||||
var idempotencyKey = Math.floor(Math.random() * 99999999999999);
|
||||
qs.append("idempotency", idempotencyKey);
|
||||
|
||||
return "/api/download?"+qs.toString();
|
||||
}
|
||||
|
||||
function handleBuildError(jqxhr, status, error) {
|
||||
var $content = $('<div class="swal-custom-content">');
|
||||
|
||||
if (jqxhr.status == 502) {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Unavailable",
|
||||
content: $content.html('Sorry, the build server is down for maintenance right now. You can try again later or <a href="https://github.com/caddyserver/caddy/releases/latest">download pre-built Caddy binaries from GitHub</a>.')[0]
|
||||
});
|
||||
} else {
|
||||
swal({
|
||||
icon: "error",
|
||||
title: "Build failed",
|
||||
content: $content.html('The two most common reasons are:<ol><li><b>A plugin is not compiling.</b> The developer must release a new version that compiles.</li><li><b>The build configuration is invalid.</b> If you specified any versions, make sure they are correct and <a href="https://golang.org/cmd/go/#hdr-Module_compatibility_and_semantic_versioning" target="_blank">within the same major version</a> as the path of the associated package.</li></ol>In the meantime, you can <a href="https://github.com/caddyserver/caddy/releases/latest">download Caddy from GitHub</a> without any plugins.')[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updatePage() {
|
||||
$('#package-count').text($('.package.selected').length);
|
||||
$('#download').attr('href', getDownloadLink());
|
||||
}
|
||||
|
||||
function disableFields(building) {
|
||||
$('#download, #optional-packages').addClass('disabled');
|
||||
$('.download-bar select, #optional-packages input').prop('disabled', true);
|
||||
if (building) {
|
||||
$('#download').html('<div class="loader"></div> Building');
|
||||
|
||||
// prevent accidentally leaving the page during a build
|
||||
window.onbeforeunload = function() {
|
||||
return "Your custom build is in progress.";
|
||||
};
|
||||
} else {
|
||||
$('#download').html('Builds Unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
function enableFields() {
|
||||
$('#download, #optional-packages').removeClass('disabled');
|
||||
$('.download-bar select, #optional-packages input').prop('disabled', false);
|
||||
$('#download').html(downloadButtonHtml);
|
||||
|
||||
// allow user to leave page easily
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
|
||||
function splitVCSProvider(pkgPath) {
|
||||
var providers = ["github.com/", "bitbucket.org/"];
|
||||
for (var i = 0; i < providers.length; i++) {
|
||||
if (pkgPath.toLowerCase().indexOf(providers[i]) == 0) {
|
||||
return {
|
||||
provider: providers[i],
|
||||
path: pkgPath.slice(providers[i].length)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {provider: "", path: pkgPath};
|
||||
}
|
||||
|
||||
var downloadButtonHtml; // to restore button to its original contents
|
2
src/old/resources/js/jquery-3.4.1.min.js
vendored
Normal file
2
src/old/resources/js/jquery-3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
52
src/old/resources/js/json-docs.js
Normal file
52
src/old/resources/js/json-docs.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
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() {
|
||||
beginRenderingInto($('#json-docs-container'), '', json.result);
|
||||
|
||||
// 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('/');
|
||||
|
||||
// enclosing 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></span>').appendTo($bc);
|
||||
}
|
||||
|
||||
// re-trigger the URL fragment if any, to scroll to the archor
|
||||
var fragment = window.location.hash;
|
||||
if (fragment) {
|
||||
window.location.hash = '';
|
||||
window.location.hash = fragment;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
7
src/old/resources/js/marked-0.8.0.min.js
vendored
Normal file
7
src/old/resources/js/marked-0.8.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
92
src/old/resources/js/module-docs.js
Normal file
92
src/old/resources/js/module-docs.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
const moduleDocsPathPrefix = "/docs/modules/";
|
||||
|
||||
var moduleID = window.location.pathname.substr(moduleDocsPathPrefix.length);
|
||||
if (moduleID) {
|
||||
// update page title and load the docs for these modules (possibly more than 1 with this ID)
|
||||
document.title = "Module " + moduleID + " - Caddy Documentation";
|
||||
$.get("/api/docs/module/"+moduleID, function(json) {
|
||||
var modules = json.result;
|
||||
|
||||
// wait until the DOM has finished loading before rendering the results
|
||||
$(function() {
|
||||
$('#module-docs-container').show();
|
||||
$('.module-name').text("Module "+moduleID);
|
||||
modules.forEach((module) => {
|
||||
$tpl = $('#module-template').clone().attr('id', stripScheme(module.repo));
|
||||
if (modules.length > 1) {
|
||||
$('article', $tpl).hide();
|
||||
}
|
||||
beginRenderingInto($tpl, moduleID, module);
|
||||
$('#module-docs-container').append($tpl);
|
||||
});
|
||||
if (modules.length > 1) {
|
||||
$('#module-multiple-repos .module-name').text(moduleID);
|
||||
$('#module-multiple-repos').show();
|
||||
} else {
|
||||
$('.module-repo-selector').hide();
|
||||
}
|
||||
|
||||
// if a specific repo's module is wanted, expand and scroll to it
|
||||
if (window.location.hash.length > 1) {
|
||||
// TODO: weird bug in jQuery(??) that it can't select IDs with slashes in them, so we use vanilla JS
|
||||
var container = document.getElementById(window.location.hash.substr(1));
|
||||
$('.module-repo-selector', container).click();
|
||||
container.scrollIntoView();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$('body').on('click', '.module-repo-selector', function() {
|
||||
if ($(this).hasClass('expanded')) {
|
||||
// collapse
|
||||
$('.module-repo-selector-arrow', this).html('▸');
|
||||
} else {
|
||||
// expand
|
||||
$('.module-repo-selector-arrow', this).html('▾');
|
||||
}
|
||||
$(this).toggleClass('expanded');
|
||||
$(this).next('article').toggle();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// populate the module list
|
||||
$.get("/api/modules", function(json) {
|
||||
var moduleList = json.result;
|
||||
|
||||
console.log("MODULE LIST:", 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 infos = moduleList[modID];
|
||||
|
||||
infos.forEach((info) => {
|
||||
// refine a short preview of the module's docs
|
||||
let shortDoc = truncate(info.docs, 200);
|
||||
if (shortDoc && shortDoc.indexOf(modID) === 0) {
|
||||
shortDoc = shortDoc.substr(modID.length).trim();
|
||||
}
|
||||
|
||||
let modLink = "./"+modID;
|
||||
if (infos.length > 1) {
|
||||
modLink += "#"+stripScheme(info.repo);
|
||||
}
|
||||
|
||||
var standard = isStandard(info.package);
|
||||
var $tr = $('<tr/>');
|
||||
$tr.append('<td>'+(standard ? standardFlag : nonStandardFlag)+'</td>');
|
||||
var $tdLink = $('<td><a href="'+modLink+'" class="module-link">'+modID+'</a></td>');
|
||||
if (infos.length > 1) {
|
||||
$tdLink.append($('<div class="module-repo-differentiator">').text('('+stripScheme(info.repo)+')'));
|
||||
}
|
||||
$tr.append($tdLink);
|
||||
$tr.append($('<td/>').text(shortDoc));
|
||||
$table.append($tr);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
1
src/old/resources/js/sweetalert.min.js
vendored
Normal file
1
src/old/resources/js/sweetalert.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue