2021-11-25 03:59:51 +00:00
{{ template "base.html" . }}
{{ define "extrahead" }}
2021-12-07 05:20:12 +00:00
{{ template "markdown_previews.html" . }}
2021-11-25 03:59:51 +00:00
< script src = "{{ static " js / tabs . js " } } " > < / script >
< script src = "{{ static " js / image_selector . js " } } " > < / script >
< script src = "{{ static " js / templates . js " } } " > < / script >
2021-12-07 05:20:12 +00:00
< style >
#desc-preview:empty::after {
content: 'A preview of your description will appear here.';
color: var(--dimmer-color);
font-style: italic;
}
< / style >
2021-11-25 03:59:51 +00:00
{{ end }}
{{ define "content" }}
2021-12-07 05:20:12 +00:00
< div class = "ph3 ph0-ns" >
2021-11-25 03:59:51 +00:00
{{ if .Editing }}
< h1 > Edit {{ .ProjectSettings.Name }}< / h1 >
{{ else }}
< h1 > Create a new project< / h1 >
{{ end }}
< form id = "project_form" class = "tabbed edit-form" method = "POST" enctype = "multipart/form-data" >
{{ csrftoken .Session }}
< div class = "tab" data-name = "General" data-slug = "general" >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Project name:< / div >
< div >
< input required type = "text" name = "project_name" maxlength = "255" class = "textbox" value = "{{ .ProjectSettings.Name }}" >
< span class = "note" > * Required< / span >
< / div >
< / div >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Status:< / div >
< div >
< select name = "lifecycle" >
< option value = "active" { { if eq . ProjectSettings . Lifecycle " active " } } selected { { end } } > Active< / option >
2021-12-07 05:20:12 +00:00
< option value = "hiatus" { { if eq . ProjectSettings . Lifecycle " hiatus " } } selected { { end } } > On Hiatus< / option >
2021-11-25 03:59:51 +00:00
< option value = "done" { { if eq . ProjectSettings . Lifecycle " done " } } selected { { end } } > Completed< / option >
< option value = "dead" { { if eq . ProjectSettings . Lifecycle " dead " } } selected { { end } } > Abandoned< / option >
< / select >
< / div >
< / div >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Owners:< / div >
< div >
< input id = "owner_name" form = "" type = "text" placeholder = "Enter another user's username" / >
< a href = "javascript:;" id = "owner_add" class = "" > Add< / a >
< span id = "owners_error" class = "note" > < / span >
< div id = "owner_list" class = "pt1" >
< template id = "owner_row" >
< div class = "owner_row flex flex-row bg--card w5 pv1 ph2" data-tmpl = "root" >
< input type = "hidden" name = "owners" data-tmpl = "input" / >
< span class = "flex-grow-1" data-tmpl = "name" > < / span >
< a class = "remove_owner" href = "javascript:;" > X< / a >
< / div >
< / template >
{{ range .ProjectSettings.Owners }}
< div class = "owner_row flex flex-row bg--card w5 pv1 ph2" >
< input type = "hidden" name = "owners" value = "{{ .Username }}" / >
< span class = "flex-grow-1" > {{ .Username }}< / span >
2021-12-04 14:55:45 +00:00
{{ if (or $.User.IsStaff (ne .ID $.User.ID)) }}
2021-11-25 03:59:51 +00:00
< a class = "remove_owner" href = "javascript:;" > X< / a >
{{ end }}
< / div >
{{ end }}
< / div >
< / div >
< / div >
2021-12-08 03:37:52 +00:00
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Tag:< / div >
< div >
< input
id="tag" name="tag" type="text"
pattern="^[a-z0-9]+(-[a-z0-9]+)*$" maxlength="20"
value="{{ .ProjectSettings.Tag }}"
/>
< div class = "c--dim f7 mt1" > e.g. "imgui" or "text-editor". Tags must be all lowercase, and can use hyphens to separate words.< / div >
2021-12-09 04:05:17 +00:00
< div class = "c--dim f7" id = "tag-discord-info" > If you have linked your Discord account, any #project-showcase messages with the tag "& < span id = "tag-preview" > < / span > " will automatically be associated with this project.< / div >
2021-12-08 03:37:52 +00:00
< / div >
< / div >
2021-12-02 10:53:36 +00:00
{{ if and .Editing .User.IsStaff }}
2021-11-25 03:59:51 +00:00
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Admin settings< / div >
< / div >
< div class = "edit-form-row" >
2021-12-07 05:20:12 +00:00
< div > Official:< / div >
2021-11-25 03:59:51 +00:00
< div >
2021-12-07 05:20:12 +00:00
< input id = "official" type = "checkbox" name = "official" { { if not . ProjectSettings . Personal } } checked { { end } } / >
< label for = "official" > Official HMN project< / label >
2021-11-25 03:59:51 +00:00
< / div >
< / div >
< div class = "edit-form-row" >
2021-12-07 05:20:12 +00:00
< div > Hidden:< / div >
2021-11-25 03:59:51 +00:00
< div >
2021-12-07 05:20:12 +00:00
< input id = "hidden" type = "checkbox" name = "hidden" { { if . ProjectSettings . Hidden } } checked { { end } } / >
< label for = "hidden" > Hide< / label >
< / div >
< / div >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Slug:< / div >
< div >
< input type = "text" name = "slug" maxlength = "255" class = "textbox" value = "{{ .ProjectSettings.Slug }}" >
2021-12-08 03:37:52 +00:00
< div class = "c--dim f7 mt1" > Has no effect for personal projects. Personal projects have a slug derived from the title.< / div >
2021-12-07 05:20:12 +00:00
< div class = "c--dim f7" > If you change this, make sure to change DNS too!< / div >
2021-11-25 03:59:51 +00:00
< / div >
< / div >
< div class = "edit-form-row" >
< div > Featured:< / div >
< div >
< input id = "featured" type = "checkbox" name = "featured" { { if . ProjectSettings . Featured } } checked { { end } } / >
< label for = "featured" > Featured< / label >
2021-12-07 05:20:12 +00:00
< div class = "c--dim f7" > Bump to the top of the project index and show in the carousel. Has no effect for personal projects.< / div >
2021-11-25 03:59:51 +00:00
< / div >
< / div >
{{ end }}
< div class = "edit-form-row" >
< div > < / div >
< div >
{{ if .Editing }}
< input type = "submit" value = "Save" / >
{{ else }}
< a class = "button" href = "javascript:;" onclick = "gotoTab('description');" > Next< / a >
{{ end }}
< / div >
< / div >
< / div >
< div class = "tab" data-name = "Description" data-slug = "description" >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Short description:< / div >
< div >
2021-12-02 10:53:36 +00:00
< textarea required maxlength = "140" name = "shortdesc" >
2021-11-25 03:59:51 +00:00
{{- .ProjectSettings.Blurb -}}
< / textarea >
< div class = "c--dim f7" > Plaintext only. No links or markdown.< / div >
< / div >
< / div >
< div class = "edit-form-row" >
< div class = "pt-input-ns" > Full description:< / div >
< div >
2021-12-07 05:20:12 +00:00
< textarea id = "description" class = "w-100 h5 minh-5 mono lh-copy" name = "description" >
2021-11-25 03:59:51 +00:00
{{- .ProjectSettings.Description -}}
< / textarea >
2021-12-07 05:20:12 +00:00
< div class = "b mt3 mb2" > Preview:< / div >
< div id = "desc-preview" class = "w-100" > < / div >
2021-11-25 03:59:51 +00:00
< / div >
< / div >
< div class = "edit-form-row" >
< div > < / div >
< div >
{{ if .Editing }}
< input type = "submit" value = "Save" / >
{{ else }}
< a class = "button" href = "javascript:;" onclick = "gotoTab('assets');" > Next< / a >
{{ end }}
< / div >
< / div >
< / div >
< div class = "tab" data-name = "Assets" data-slug = "assets" >
< div class = "edit-form-row" >
< div > Light theme logo:< / div >
< div class = "light_logo" >
{{ template "image_selector.html" imageselectordata "light_logo" .ProjectSettings.LightLogo false }}
< / div >
< / div >
< div class = "edit-form-row" >
< div > Dark theme logo:< / div >
< div class = "dark_logo" >
{{ template "image_selector.html" imageselectordata "dark_logo" .ProjectSettings.DarkLogo false }}
< / div >
< / div >
< div class = "edit-form-row" >
< div > < / div >
< div >
{{ if .Editing }}
< input type = "submit" value = "Save" / >
{{ else }}
< input type = "submit" value = "Create project" / >
{{ end }}
< / div >
< / div >
< / div >
< / form >
< / div >
< script >
let csrf = JSON.parse({{ csrftokenjs .Session }});
2021-12-07 05:20:12 +00:00
let projectForm = document.querySelector("#project_form");
2021-11-25 03:59:51 +00:00
projectForm.addEventListener("invalid", function(ev) {
switchToTabOfElement(document.body, ev.target);
}, true);
function gotoTab(tabName) {
switchTab(document.body, tabName);
}
2021-12-08 03:37:52 +00:00
//////////
// Tags //
//////////
const tag = document.querySelector('#tag');
const tagPreview = document.querySelector('#tag-preview');
function updateTagPreview() {
tagPreview.innerText = tag.value;
document.querySelector('#tag-discord-info').classList.toggle('dn', tag.value.length === 0);
}
updateTagPreview();
tag.addEventListener('input', () => updateTagPreview());
2021-12-07 05:20:12 +00:00
////////////////////////////
// Description management //
////////////////////////////
{{ if .Editing }}
const projectName = "new-project";
{{ else }}
const projectName = "{{ .Project.Name }}";
{{ end }}
const description = document.querySelector('#description');
const descPreview = document.querySelector('#desc-preview');
const { clear: clearDescription } = autosaveContent({
inputEl: description,
storageKey: `project-description/${projectName}`,
});
projectForm.addEventListener('submit', () => clearDescription());
initLiveMarkdown({ inputEl: description, previewEl: descPreview });
2021-11-25 03:59:51 +00:00
//////////////////////
// Owner management //
//////////////////////
const OWNER_QUERY_STATE_IDLE = 0;
const OWNER_QUERY_STATE_QUERYING = 1;
2021-12-09 04:23:20 +00:00
const MAX_OWNERS = {{ .MaxOwners }};
2021-11-25 03:59:51 +00:00
let ownerCheckUrl = "{{ .APICheckUsernameUrl }}";
let ownerQueryState = OWNER_QUERY_STATE_IDLE;
let addOwnerInput = document.querySelector("#owner_name");
let addOwnerButton = document.querySelector("#owner_add");
let ownersError = document.querySelector("#owners_error");
let ownerList = document.querySelector("#owner_list");
let ownerTemplate = makeTemplateCloner("owner_row");
addOwnerInput.addEventListener("keypress", function(ev) {
if (ev.which == 13) {
startAddOwner();
ev.preventDefault();
ev.stopPropagation();
}
});
addOwnerButton.addEventListener("click", function(ev) {
startAddOwner();
});
2021-12-09 04:23:20 +00:00
function updateAddOwnerStyles() {
const numOwnerRows = document.querySelectorAll('.owner_row').length;
addOwnerInput.disabled = numOwnerRows >= MAX_OWNERS;
}
updateAddOwnerStyles();
2021-11-25 03:59:51 +00:00
function startAddOwner() {
if (ownerQueryState == OWNER_QUERY_STATE_QUERYING) {
return;
}
let newOwner = addOwnerInput.value.trim().toLowerCase();
if (newOwner.length == 0) {
return;
}
let ownerEls = ownerList.querySelectorAll(".owner_row input[name='owners']");
for (let i = 0; i < ownerEls.length ; + + i ) {
let existingOwner = ownerEls[i].value.toLowerCase();
if (newOwner == existingOwner) {
return;
}
}
ownersError.textContent = "";
let xhr = new XMLHttpRequest();
2021-12-04 14:55:45 +00:00
xhr.withCredentials = true;
2021-11-25 03:59:51 +00:00
xhr.open("POST", ownerCheckUrl);
xhr.responseType = "json";
xhr.addEventListener("load", function(ev) {
let result = xhr.response;
2021-12-04 14:55:45 +00:00
if (result) {
if (result.found) {
addOwner(result.canonical);
addOwnerInput.value = "";
} else {
ownersError.textContent = "Username not found";
}
2021-11-25 03:59:51 +00:00
} else {
2021-12-04 14:55:45 +00:00
ownersError.textContent = "There was an issue validating this username";
2021-11-25 03:59:51 +00:00
}
setOwnerQueryState(OWNER_QUERY_STATE_IDLE);
if (document.activeElement == addOwnerButton) {
addOwnerInput.focus();
}
});
xhr.addEventListener("error", function(ev) {
ownersError.textContent = "There was an issue validating this username";
setOwnerQueryState(OWNER_QUERY_STATE_IDLE);
});
let data = new FormData();
data.append(csrf.field, csrf.token);
data.append("username", newOwner);
xhr.send(data);
setOwnerQueryState(OWNER_QUERY_STATE_QUERYING);
}
function setOwnerQueryState(state) {
ownerQueryState = state;
querying = (ownerQueryState == OWNER_QUERY_STATE_QUERYING);
addOwnerInput.disabled = querying;
addOwnerButton.disabled = querying;
2021-12-09 04:23:20 +00:00
updateAddOwnerStyles();
2021-11-25 03:59:51 +00:00
}
function addOwner(username) {
let ownerEl = ownerTemplate();
ownerEl.input.value = username;
ownerEl.name.textContent = username;
ownerList.appendChild(ownerEl.root);
2021-12-09 04:23:20 +00:00
updateAddOwnerStyles();
2021-11-25 03:59:51 +00:00
}
ownerList.addEventListener("click", function(ev) {
if (ev.target.classList.contains("remove_owner")) {
ev.target.parentElement.remove();
}
2021-12-09 04:23:20 +00:00
updateAddOwnerStyles();
2021-11-25 03:59:51 +00:00
});
/////////////////////
// Logo management //
/////////////////////
const logoMaxFileSize = {{ .LogoMaxFileSize }};
let lightLogoSelector = new ImageSelector(
document.querySelector("#project_form"),
logoMaxFileSize,
document.querySelector(".light_logo")
);
let darkLogoSelector = new ImageSelector(
document.querySelector("#project_form"),
logoMaxFileSize,
document.querySelector(".dark_logo")
);
< / script >
{{ end }}