templates/layout.html
7.1 KB
| 1 | {{define "layout"}}<!DOCTYPE html> |
| 2 | <html lang="ru"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | <meta name="color-scheme" content="light dark"> |
| 7 | <title>{{template "title" .}} · Clancore Repo</title> |
| 8 | <link rel="stylesheet" href="/static/css/main.css"> |
| 9 | </head> |
| 10 | <body> |
| 11 | <a class="skip-link" href="#main">Перейти к содержимому</a> |
| 12 | |
| 13 | <header class="header"> |
| 14 | <div class="container header-inner"> |
| 15 | <a href="/" class="header-logo" aria-label="Clancore Repo — на главную"> |
| 16 | <svg height="32" width="32" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> |
| 17 | <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/> |
| 18 | </svg> |
| 19 | </a> |
| 20 | |
| 21 | <nav class="header-nav" aria-label="Основная навигация"> |
| 22 | <a href="/">Репозитории</a> |
| 23 | {{if .UserID}} |
| 24 | <a href="/dashboard">Панель</a> |
| 25 | <a href="/builds">Сборки</a> |
| 26 | {{end}} |
| 27 | </nav> |
| 28 | |
| 29 | <div class="header-search"> |
| 30 | <form action="/" method="get" role="search"> |
| 31 | <label class="sr-only" for="header-search-input">Поиск репозиториев</label> |
| 32 | <svg class="octicon header-search-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"/></svg> |
| 33 | <input id="header-search-input" type="search" name="q" placeholder="Поиск репозиториев…" value="{{.Query}}" autocomplete="off"> |
| 34 | <kbd class="search-kbd">/</kbd> |
| 35 | </form> |
| 36 | </div> |
| 37 | |
| 38 | <div class="header-user"> |
| 39 | {{if .UserID}} |
| 40 | <a href="/profile" class="avatar avatar-sm header-avatar" title="{{.User.Username}}">{{slice .User.Username 0 1}}</a> |
| 41 | <form method="POST" action="/logout" class="inline-form"> |
| 42 | <input type="hidden" name="csrf_token" value="{{.CSRF}}"> |
| 43 | <button type="submit" class="header-link-button">Выйти</button> |
| 44 | </form> |
| 45 | {{else}} |
| 46 | <a href="/login" class="header-link">Войти</a> |
| 47 | <a href="/register" class="btn btn-sm btn-inverted">Регистрация</a> |
| 48 | {{end}} |
| 49 | </div> |
| 50 | </div> |
| 51 | </header> |
| 52 | |
| 53 | <main class="container" id="main"> |
| 54 | {{template "content" .}} |
| 55 | </main> |
| 56 | |
| 57 | <footer class="footer"> |
| 58 | <div class="container footer-inner"> |
| 59 | <div class="footer-left"> |
| 60 | <a href="/" class="footer-mark" aria-label="Clancore Repo"> |
| 61 | <svg height="24" width="24" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> |
| 62 | <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/> |
| 63 | </svg> |
| 64 | </a> |
| 65 | <span>© Clancore Repo</span> |
| 66 | </div> |
| 67 | <nav class="footer-links" aria-label="Навигация в подвале"> |
| 68 | <a href="/">Репозитории</a> |
| 69 | <a href="/builds">Сборки</a> |
| 70 | {{if not .UserID}}<a href="/register">Регистрация</a>{{end}} |
| 71 | </nav> |
| 72 | </div> |
| 73 | </footer> |
| 74 | |
| 75 | <script> |
| 76 | document.addEventListener("click", function (event) { |
| 77 | var button = event.target.closest("[data-copy]"); |
| 78 | if (!button || !navigator.clipboard) { |
| 79 | return; |
| 80 | } |
| 81 | event.preventDefault(); |
| 82 | var value = button.getAttribute("data-copy"); |
| 83 | navigator.clipboard.writeText(value).then(function () { |
| 84 | var originalHTML = button.innerHTML; |
| 85 | var hasText = button.textContent.trim() !== ""; |
| 86 | if (hasText) { |
| 87 | button.textContent = "Скопировано"; |
| 88 | setTimeout(function () { button.innerHTML = originalHTML; }, 1500); |
| 89 | } else { |
| 90 | button.classList.add("copied"); |
| 91 | button.setAttribute("title", "Скопировано"); |
| 92 | setTimeout(function () { |
| 93 | button.classList.remove("copied"); |
| 94 | button.setAttribute("title", "Скопировать URL"); |
| 95 | }, 1500); |
| 96 | } |
| 97 | }); |
| 98 | }); |
| 99 | |
| 100 | // Выпадающие панели (например, кнопка Code) |
| 101 | document.addEventListener("click", function (event) { |
| 102 | var trigger = event.target.closest("[data-dropdown]"); |
| 103 | if (trigger) { |
| 104 | var panel = document.getElementById(trigger.getAttribute("data-dropdown")); |
| 105 | if (panel) { |
| 106 | var willOpen = !panel.classList.contains("open"); |
| 107 | panel.classList.toggle("open", willOpen); |
| 108 | trigger.setAttribute("aria-expanded", willOpen ? "true" : "false"); |
| 109 | event.preventDefault(); |
| 110 | return; |
| 111 | } |
| 112 | } |
| 113 | if (event.target.closest(".popover")) { |
| 114 | return; |
| 115 | } |
| 116 | document.querySelectorAll(".popover.open").forEach(function (panel) { |
| 117 | panel.classList.remove("open"); |
| 118 | }); |
| 119 | document.querySelectorAll("[data-dropdown]").forEach(function (btn) { |
| 120 | btn.setAttribute("aria-expanded", "false"); |
| 121 | }); |
| 122 | }); |
| 123 | |
| 124 | // Горячие клавиши как в GitHub: "/" — поиск, "t" — фильтр файлов, Esc — закрыть панель |
| 125 | document.addEventListener("keydown", function (event) { |
| 126 | if (event.key === "Escape") { |
| 127 | document.querySelectorAll(".popover.open").forEach(function (panel) { |
| 128 | panel.classList.remove("open"); |
| 129 | }); |
| 130 | document.querySelectorAll("[data-dropdown]").forEach(function (btn) { |
| 131 | btn.setAttribute("aria-expanded", "false"); |
| 132 | }); |
| 133 | return; |
| 134 | } |
| 135 | if (event.key !== "/" && event.key !== "t") { |
| 136 | return; |
| 137 | } |
| 138 | var tag = (document.activeElement && document.activeElement.tagName) || ""; |
| 139 | if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || event.metaKey || event.ctrlKey) { |
| 140 | return; |
| 141 | } |
| 142 | var target = document.getElementById(event.key === "/" ? "header-search-input" : "file-filter"); |
| 143 | if (target) { |
| 144 | event.preventDefault(); |
| 145 | target.focus(); |
| 146 | } |
| 147 | }); |
| 148 | |
| 149 | // "Go to file" — фильтр строк дерева файлов |
| 150 | (function () { |
| 151 | var filter = document.getElementById("file-filter"); |
| 152 | if (!filter) { |
| 153 | return; |
| 154 | } |
| 155 | filter.addEventListener("input", function () { |
| 156 | var query = filter.value.trim().toLowerCase(); |
| 157 | document.querySelectorAll(".file-table tbody tr").forEach(function (row) { |
| 158 | var name = row.querySelector(".file-name"); |
| 159 | var match = query === "" || (name && name.textContent.toLowerCase().indexOf(query) !== -1); |
| 160 | row.hidden = !match; |
| 161 | }); |
| 162 | }); |
| 163 | })(); |
| 164 | </script> |
| 165 | </body> |
| 166 | </html> |
| 167 | {{end}} |