245 lines
8.1 KiB
HTML
245 lines
8.1 KiB
HTML
{{ template "base.html" . }}
|
|
|
|
{{ define "extrahead" }}
|
|
<style>
|
|
.led {
|
|
aspect-ratio: 1;
|
|
border-radius: 50%;
|
|
border-style: solid;
|
|
border-width: 1.5px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.led.yellow {
|
|
background-color: #64501f;
|
|
border-color: #4f3700;
|
|
}
|
|
|
|
.led.yellow.on {
|
|
background-color: #fdf2d8;
|
|
border-color: #f9ad04;
|
|
box-shadow: 0 0 7px #ee9e06;
|
|
}
|
|
|
|
.admin .cover {
|
|
background: repeating-linear-gradient( -45deg, #ff6c00, #ff6c00 12px, #000000 5px, #000000 25px );
|
|
}
|
|
|
|
</style>
|
|
<script src="{{ static "js/templates.js" }}"></script>
|
|
{{ end }}
|
|
|
|
{{ define "content" }}
|
|
<div class="flex flex-column flex-row-l">
|
|
<div class="
|
|
sidebar flex-shrink-0 self-start-l
|
|
flex flex-column flex-row-ns items-start-ns flex-column-l items-stretch-l
|
|
mw5-l mh3 ml0-ns mb3 overflow-hidden
|
|
">
|
|
<div class="w-100 w5-ns flex-shrink-0 flex justify-center">
|
|
<img class="br3" alt="{{ .ProfileUser.Name }}'s Avatar" src="{{ .ProfileUser.AvatarUrl }}">
|
|
</div>
|
|
<div class="mt3 mt0-ns mt3-l ml3-ns ml0-l flex flex-column items-start overflow-hidden">
|
|
{{ with or .ProfileUser.Bio .ProfileUser.Blurb }}
|
|
<div class="mb3">{{ . }}</div>
|
|
{{ end }}
|
|
<div class="w-100 w-auto-ns w-100-l">
|
|
{{ if .ProfileUser.Email }}
|
|
<div class="pair flex">
|
|
<div class="key flex-auto flex-shrink-0 mr2">Email</div>
|
|
<div class="value projectlink truncate">{{ .ProfileUser.Email }}</div>
|
|
</div>
|
|
{{ end }}
|
|
{{ range .ProfileUserLinks }}
|
|
<div class="pair flex">
|
|
<div class="key flex-auto flex-shrink-0 mr2">{{ .Name }}</div>
|
|
<div class="value projectlink truncate"><a class="external" href="{{ .Url }}" ><span class="icon-{{ .Icon }}"></span> {{ .LinkText }}</a></div>
|
|
</div>
|
|
{{ end }}
|
|
|
|
<div class="pair flex">
|
|
<div class="key flex-auto flex-shrink-0 mr2">Member since</div>
|
|
<div class="value projectlink truncate">{{ absoluteshortdate .ProfileUser.DateJoined }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{ if .User }}
|
|
{{ if .User.IsStaff }}
|
|
<div class="mt3 mt0-ns mt3-l ml3-ns ml0-l flex flex-column items-start bg--card pa2 br2 admin">
|
|
<div class="flex flex-row w-100 items-center">
|
|
<b class="flex-grow-1">Admin actions</b>
|
|
<div class="led yellow" style="height: 12px; margin: 3px;"></div>
|
|
<a href="javascript:;" class="unlock">Unlock</a>
|
|
</div>
|
|
<div class="relative w-100">
|
|
<div class="bg--card cover absolute w-100 h-100 br2"></div>
|
|
<div class="mt3">
|
|
<div>User status:</div>
|
|
<form id="admin_set_status_form" method="POST" action="{{ .AdminSetStatusUrl }}">
|
|
{{ csrftoken .Session }}
|
|
<input type="hidden" name="user_id" value="{{ .ProfileUser.ID }}" />
|
|
<input type="hidden" name="username" value="{{ .ProfileUser.Username }}" />
|
|
<select name="status">
|
|
<option value="inactive" {{ if eq .ProfileUser.Status 1 }}selected{{ end }}>Brand new</option>
|
|
<option value="confirmed" {{ if eq .ProfileUser.Status 2 }}selected{{ end }}>Email confirmed</option>
|
|
<option value="approved" {{ if eq .ProfileUser.Status 3 }}selected{{ end }}>Admin approved</option>
|
|
<option value="banned" {{ if eq .ProfileUser.Status 4 }}selected{{ end }}>Banned</option>
|
|
</select>
|
|
<input type="submit" value="Set" />
|
|
<div class="c--dim f7">Only sets status. Doesn't delete anything.</div>
|
|
</form>
|
|
</div>
|
|
<div class="mt3">
|
|
<div>Danger zone:</div>
|
|
<form id="admin_nuke_form" method="POST" action="{{ .AdminNukeUrl }}">
|
|
{{ csrftoken .Session }}
|
|
<input type="hidden" name="user_id" value="{{ .ProfileUser.ID }}" />
|
|
<input type="hidden" name="username" value="{{ .ProfileUser.Username }}" />
|
|
<input type="submit" value="Nuke posts" />
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
let unlockEl = document.querySelector(".admin .unlock");
|
|
let adminUnlockLed = document.querySelector(".admin .led");
|
|
let adminUnlocked = false;
|
|
let panelEl = document.querySelector(".admin .cover");
|
|
unlockEl.addEventListener("click", function() {
|
|
adminUnlocked = true;
|
|
adminUnlockLed.classList.add("on");
|
|
panelEl.style.display = "none";
|
|
});
|
|
|
|
document.querySelector("#admin_set_status_form").addEventListener("submit", function(ev) {
|
|
if (!adminUnlocked) {
|
|
ev.preventDefault();
|
|
}
|
|
});
|
|
|
|
document.querySelector("#admin_nuke_form").addEventListener("submit", function(ev) {
|
|
if (!adminUnlocked) {
|
|
ev.preventDefault();
|
|
}
|
|
});
|
|
</script>
|
|
</div>
|
|
{{ end }}
|
|
{{ end }}
|
|
</div>
|
|
<div class="flex-grow-1 overflow-hidden">
|
|
{{ if or .OwnProfile .ProfileUserProjects }}
|
|
<div class="content-block ph3 ph0-ns">
|
|
<h2>{{ if .OwnProfile }}My {{ end }}Projects</h2>
|
|
{{ range .ProfileUserProjects }}
|
|
<div class="mv3">
|
|
{{ template "project_card.html" projectcarddata . "" }}
|
|
</div>
|
|
{{ end }}
|
|
{{ if .OwnProfile }}
|
|
{{ if .CanAddProject }}
|
|
<a href="{{ .NewProjectUrl }}" class="button submit pv2 ph3">Add New Project</a>
|
|
{{ else }}
|
|
<span class="c--dim i">You have reached the maximum number of personal projects.</span>
|
|
{{ end }}
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
{{ if or .OwnProfile (gt (len .TimelineItems) 0) }}
|
|
<div class="mv3 content-block timeline-container ph3 ph0-ns">
|
|
<div class="flex flex-row items-center">
|
|
<h2>Recent Activity</h2>
|
|
<div class="flex-grow-1"></div>
|
|
{{ if .OwnProfile }}
|
|
<a href="javascript:;" class="create_snippet_link button">Add Snippet</a>
|
|
{{ end }}
|
|
</div>
|
|
<div class="timeline-filters mb2">
|
|
</div>
|
|
<div class="timeline">
|
|
{{ range .TimelineItems }}
|
|
{{ template "timeline_item.html" . }}
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
|
|
{{ if .User }}
|
|
{{ template "snippet_edit.html" . }}
|
|
<script>
|
|
const userName = "{{ .User.Name }}";
|
|
const userAvatar = "{{ .User.AvatarUrl }}";
|
|
const userUrl = "{{ .User.ProfileUrl }}";
|
|
|
|
|
|
{{ if .OwnProfile }}
|
|
document.querySelector(".create_snippet_link")?.addEventListener("click", function() {
|
|
let snippetEdit = makeSnippetEdit(userName, userAvatar, userUrl, new Date(), "", null, [], null, null, null);
|
|
document.querySelector(".timeline").insertBefore(snippetEdit.root, document.querySelector(".timeline").children[0]);
|
|
document.querySelector(".create_snippet_link")?.remove();
|
|
});
|
|
{{ end }}
|
|
|
|
document.querySelector(".timeline").addEventListener("click", function(ev) {
|
|
if (ev.target.classList.contains("edit")) {
|
|
let parent = ev.target.parentElement;
|
|
while (parent && !parent.classList.contains("timeline-item")) {
|
|
parent = parent.parentElement;
|
|
}
|
|
if (parent && parent.classList.contains("timeline-item")) {
|
|
editTimelineSnippet(parent, null);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{{ end }}
|
|
<script>
|
|
const filterTitles = [];
|
|
for (const item of document.querySelectorAll('.timeline-item')) {
|
|
const title = item.getAttribute('data-filter-title');
|
|
if (title && !filterTitles.includes(title)) {
|
|
filterTitles.push(title);
|
|
}
|
|
}
|
|
filterTitles.sort();
|
|
|
|
function itemsForFilterTitle(title) {
|
|
return document.querySelectorAll(`.timeline-item[data-filter-title="${title}"]`);
|
|
}
|
|
|
|
const filters = document.querySelector('.timeline-filters');
|
|
|
|
for (const title of filterTitles) {
|
|
const container = document.createElement("div");
|
|
container.className = "dib filter mr2";
|
|
|
|
const id = `timeline-checkbox-${title.replaceAll(/\s/g, '-')}`;
|
|
|
|
const input = document.createElement("input");
|
|
input.className = "v-mid mr1";
|
|
input.type = "checkbox";
|
|
input.id = id;
|
|
input.checked = true;
|
|
input.addEventListener("change", e => {
|
|
for (const item of itemsForFilterTitle(title)) {
|
|
if (e.target.checked) {
|
|
item.style.removeProperty("display");
|
|
} else {
|
|
item.style.setProperty("display", "none")
|
|
}
|
|
}
|
|
});
|
|
container.appendChild(input);
|
|
|
|
const label = document.createElement("label");
|
|
label.className = "v-mid";
|
|
label.htmlFor = id;
|
|
label.innerText = `${title} (${itemsForFilterTitle(title).length})`;
|
|
container.appendChild(label);
|
|
|
|
filters.appendChild(container);
|
|
}
|
|
</script>
|
|
{{ end }}
|