|
| 1 | +{% extends "layouts/base.html" %} |
| 2 | + |
| 3 | +{% block title %} |
| 4 | + Add a new project ~ ATR |
| 5 | +{% endblock title %} |
| 6 | + |
| 7 | +{% block description %} |
| 8 | + Add a new project based on an existing one. |
| 9 | +{% endblock description %} |
| 10 | + |
| 11 | +{% block content %} |
| 12 | + <h1> |
| 13 | + Add a new <strong>{{ project_name.removesuffix(" (Incubating)") }}</strong> sub-project |
| 14 | + </h1> |
| 15 | + <p>New projects can only be derived from existing projects, by adding a suffix.</p> |
| 16 | + |
| 17 | + <form method="post" class="atr-canary py-4"> |
| 18 | + {{ form.hidden_tag() }} |
| 19 | + |
| 20 | + <div class="mb-3 pb-3 row border-bottom"> |
| 21 | + <label for="{{ form.derived_project_name.id }}" |
| 22 | + class="col-sm-3 col-form-label text-sm-end">{{ form.derived_project_name.label.text }}:</label> |
| 23 | + <div class="col-sm-8"> |
| 24 | + {{ form.derived_project_name(class_="form-control") }} |
| 25 | + {% if form.derived_project_name.errors -%} |
| 26 | + <span class="text-danger small">{{ form.derived_project_name.errors[0] }}</span>{%- endif %} |
| 27 | + <p class="text-muted mt-1">The desired suffix for the full project name.</p> |
| 28 | + <p id="capitalisation-warning" class="text-danger small mt-1 d-none"> |
| 29 | + <span class="fa-solid fa-triangle-exclamation"></span> |
| 30 | + Warning: Ensure all words in the derived name start with a capital for proper display. |
| 31 | + </p> |
| 32 | + </div> |
| 33 | + </div> |
| 34 | + |
| 35 | + <div class="mb-3 pb-3 row border-bottom"> |
| 36 | + <label id="new-project-name-label" |
| 37 | + for="new-project-name-display" |
| 38 | + class="col-sm-3 col-form-label text-sm-end">Project name preview:</label> |
| 39 | + <div class="col-sm-8"> |
| 40 | + <code id="new-project-name-display" |
| 41 | + class="form-control-plaintext bg-light p-2 rounded d-block"></code> |
| 42 | + <p class="text-muted small mt-1">This will be the full display name for the derived project.</p> |
| 43 | + </div> |
| 44 | + </div> |
| 45 | + |
| 46 | + <div class="mb-3 pb-3 row border-bottom"> |
| 47 | + <label id="new-project-label-label" |
| 48 | + for="new-project-label-display" |
| 49 | + class="col-sm-3 col-form-label text-sm-end">Project label preview:</label> |
| 50 | + <div class="col-sm-8"> |
| 51 | + <code id="new-project-label-display" |
| 52 | + class="form-control-plaintext bg-light p-2 rounded d-block"></code> |
| 53 | + <p class="text-muted small mt-1">This will be the short label used in URLs and identifiers.</p> |
| 54 | + </div> |
| 55 | + </div> |
| 56 | + |
| 57 | + <div class="row"> |
| 58 | + <div class="col-sm-9 offset-sm-3">{{ form.submit(class_="btn btn-primary mt-3") }}</div> |
| 59 | + </div> |
| 60 | + </form> |
| 61 | + {% endblock content %} |
| 62 | + |
| 63 | + {% block javascripts %} |
| 64 | + {{ super() }} |
| 65 | + <script> |
| 66 | + document.addEventListener("DOMContentLoaded", () => { |
| 67 | + const projectLabel = document.getElementById("{{ form.project_name.id }}"); |
| 68 | + const projectSelect = "{{ project_name }}"; |
| 69 | + const derivedNameInput = document.getElementById("{{ form.derived_project_name.id }}"); |
| 70 | + const newNameDisplay = document.getElementById("new-project-name-display"); |
| 71 | + const newLabelDisplay = document.getElementById("new-project-label-display"); |
| 72 | + const capitalisationWarning = document.getElementById("capitalisation-warning"); |
| 73 | + |
| 74 | + if (!projectSelect || !derivedNameInput || !newNameDisplay || !newLabelDisplay || !capitalisationWarning) return; |
| 75 | + |
| 76 | + function generateSlug(text) { |
| 77 | + return text.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""); |
| 78 | + } |
| 79 | + |
| 80 | + function updatePreview() { |
| 81 | + const selectedOption = projectSelect; |
| 82 | + const baseLabel = projectLabel.value; |
| 83 | + const baseFullName = selectedOption; |
| 84 | + const derivedNameValue = derivedNameInput.value.trim(); |
| 85 | + |
| 86 | + let hasCapitalisationIssue = false; |
| 87 | + if (derivedNameValue) { |
| 88 | + const words = derivedNameValue.split(/\s+/); |
| 89 | + for (const word of words) { |
| 90 | + if (word.length > 0 && !/^[A-Z]/.test(word)) { |
| 91 | + hasCapitalisationIssue = true; |
| 92 | + break; |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + if (hasCapitalisationIssue) { |
| 98 | + capitalisationWarning.classList.remove("d-none"); |
| 99 | + } else { |
| 100 | + capitalisationWarning.classList.add("d-none"); |
| 101 | + } |
| 102 | + |
| 103 | + let newFullName = baseFullName; |
| 104 | + if (derivedNameValue) { |
| 105 | + const match = baseFullName.match(/^(.*?)\s*(\(.*\))?$/); |
| 106 | + let mainPart = baseFullName.trim(); |
| 107 | + let suffixPart = null; |
| 108 | + |
| 109 | + if (match) { |
| 110 | + mainPart = match[1] ? match[1].trim() : mainPart; |
| 111 | + suffixPart = match[2]; |
| 112 | + } |
| 113 | + |
| 114 | + if (suffixPart) { |
| 115 | + newFullName = `${mainPart} ${derivedNameValue} ${suffixPart}`; |
| 116 | + } else { |
| 117 | + newFullName = `${mainPart} ${derivedNameValue}`; |
| 118 | + } |
| 119 | + newFullName = newFullName.replace(/\s{2,}/g, " ").trim(); |
| 120 | + } |
| 121 | + newNameDisplay.textContent = newFullName || "(Select base project)"; |
| 122 | + |
| 123 | + let newLabel = baseLabel; |
| 124 | + if (derivedNameValue) { |
| 125 | + const derivedSlug = generateSlug(derivedNameValue); |
| 126 | + if (derivedSlug) { |
| 127 | + newLabel = `${baseLabel}-${derivedSlug}`; |
| 128 | + } |
| 129 | + } |
| 130 | + newLabelDisplay.textContent = newLabel || "(Enter derived project name)"; |
| 131 | + } |
| 132 | + |
| 133 | + derivedNameInput.addEventListener("input", updatePreview); |
| 134 | + |
| 135 | + updatePreview(); |
| 136 | + }); |
| 137 | + </script> |
| 138 | + {% endblock javascripts %} |
0 commit comments