<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title or "GitMan"}}</title>
<link rel="icon" type="image/png" sizes="32x32" href="/static/icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/icon.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/icon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<header class="site-header">
<a class="brand" href="/"><img src="/static/logo.png" style="max-height:36px;"></a>
<nav class="nav">
% if user:
<a href="/{{user['username']}}">{{user['username']}}</a>
<a href="/new">New repo</a>
<form action="/logout" method="post">
{{!csrf_field()}}
<button class="link-button" type="submit">Log out</button>
</form>
% else:
<a href="/login">Log in</a>
<a class="button small" href="/signup">Sign up</a>
% end
</nav>
</header>
<main class="page">
% if error:
<div class="alert">{{error}}</div>
% end
% if notice:
<div class="notice">{{notice}}</div>
% end
{{!base}}
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>
if (window.hljs) {
document.querySelectorAll("pre code").forEach((block) => hljs.highlightElement(block));
}
</script>
<script>
(() => {
const pickers = document.querySelectorAll("[data-ref-picker]");
if (!pickers.length) return;
const clearSearchResults = (picker) => {
picker.querySelectorAll("[data-ref-picker-search-result]").forEach((option) => option.remove());
};
const showInitialOptions = (picker) => {
const options = picker.querySelectorAll("[data-ref-picker-option]");
const empty = picker.querySelector("[data-ref-picker-empty]");
let visibleCount = 0;
clearSearchResults(picker);
options.forEach((option) => {
const isVisible = option.dataset.refInitial === "true";
option.hidden = !isVisible;
option.style.display = isVisible ? "" : "none";
if (isVisible) visibleCount += 1;
});
if (empty) empty.hidden = visibleCount > 0;
};
const buildRefUrl = (result) => {
const url = new URL(window.location.href);
["ref", "ref_type", "ref_value"].forEach((key) => url.searchParams.delete(key));
const type = result.type || "tip";
url.searchParams.set("ref_type", type);
if (type !== "tip") url.searchParams.set("ref", result.name || "");
return `${url.pathname}${url.search}${url.hash}`;
};
const renderSearchResults = (picker, results) => {
const empty = picker.querySelector("[data-ref-picker-empty]");
const options = picker.querySelectorAll("[data-ref-picker-option]");
clearSearchResults(picker);
options.forEach((option) => {
option.hidden = true;
option.style.display = "none";
});
results.forEach((result) => {
const option = document.createElement("a");
const label = document.createElement("span");
option.className = "ref-picker-option";
option.href = buildRefUrl(result);
option.setAttribute("role", "menuitem");
option.dataset.refPickerSearchResult = "true";
label.textContent = result.label || result.name || "";
option.append(label);
empty.parentElement.insertBefore(option, empty);
});
if (empty) empty.hidden = results.length > 0;
};
const searchRefs = (picker) => {
const search = picker.querySelector("[data-ref-picker-search]");
const query = search ? search.value.trim() : "";
const token = (picker._refPickerSearchToken || 0) + 1;
picker._refPickerSearchToken = token;
if (!query) {
showInitialOptions(picker);
return;
}
const empty = picker.querySelector("[data-ref-picker-empty]");
picker.querySelectorAll("[data-ref-picker-option]").forEach((option) => {
option.hidden = true;
option.style.display = "none";
});
clearSearchResults(picker);
if (empty) empty.hidden = true;
const url = new URL(picker.dataset.refSearchUrl, window.location.origin);
url.searchParams.set("q", query);
fetch(url.toString(), { headers: { Accept: "application/json" } })
.then((response) => {
if (!response.ok) throw new Error("Unable to search refs.");
return response.json();
})
.then((data) => {
if (picker._refPickerSearchToken !== token) return;
renderSearchResults(picker, data.results || []);
})
.catch(() => {
if (picker._refPickerSearchToken !== token) return;
renderSearchResults(picker, []);
});
};
const resetFilter = (picker) => {
const search = picker.querySelector("[data-ref-picker-search]");
if (search) search.value = "";
searchRefs(picker);
};
const closePicker = (picker) => {
const button = picker.querySelector(".ref-picker-toggle");
const menu = picker.querySelector("[data-ref-picker-menu]");
if (!menu || menu.hidden) return;
menu.hidden = true;
if (button) button.setAttribute("aria-expanded", "false");
};
const openPicker = (picker) => {
pickers.forEach((other) => {
if (other !== picker) closePicker(other);
});
const button = picker.querySelector(".ref-picker-toggle");
const menu = picker.querySelector("[data-ref-picker-menu]");
const search = picker.querySelector("[data-ref-picker-search]");
if (!menu) return;
resetFilter(picker);
menu.hidden = false;
if (button) button.setAttribute("aria-expanded", "true");
if (search) search.focus();
};
pickers.forEach((picker) => {
const button = picker.querySelector(".ref-picker-toggle");
const search = picker.querySelector("[data-ref-picker-search]");
if (button) {
button.addEventListener("click", () => {
const menu = picker.querySelector("[data-ref-picker-menu]");
if (menu && menu.hidden) {
openPicker(picker);
} else {
closePicker(picker);
}
});
}
if (search) {
search.addEventListener("input", () => {
searchRefs(picker);
});
}
});
document.addEventListener("click", (event) => {
pickers.forEach((picker) => {
if (!picker.contains(event.target)) closePicker(picker);
});
});
document.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
pickers.forEach(closePicker);
});
})();
</script>
</body>
</html>