working UGC content upload with multi-upload support

This commit is contained in:
2026-08-03 21:05:12 -05:00
parent 473b3c811b
commit 87b0d46232
18 changed files with 563 additions and 490 deletions
@@ -1,26 +0,0 @@
<div class="form-group">
{{ form.title.errors }}
<label for="{{ form.title.id_for_label }}">Title</label>
{{ form.title }}
</div>
<div class="form-group">
{{ form.summary.errors }}
<label for="{{ form.summary.id_for_label }}">Summary</label>
{{ form.summary }}
</div>
<div class="form-row">
<div class="form-group">
{{ form.category.errors }}
<label for="{{ form.category.id_for_label }}">Category</label>
{{ form.category }}
</div>
</div>
<div class="form-group">
{{ form.description.errors }}
<label for="{{ form.description.id_for_label }}">Description</label>
{{ form.description }}
<p class="bio-help"><i class="fas fa-info-circle"></i> Supports Markdown — headings, lists, links, code, tables and more.</p>
</div>
@@ -1,17 +0,0 @@
<div class="form-group">
{{ form.thumbnail.errors }}
<label for="{{ form.thumbnail.id_for_label }}">Thumbnail</label>
{{ form.thumbnail }}
<p class="bio-help"><i class="fas fa-info-circle"></i> Square image shown on cards and the project header.</p>
</div>
<div class="form-group">
<label>Tags</label>
<div class="tag-editor">
<input type="text" id="tag-input" placeholder="Type a tag and press Enter… (e.g. content:threesome, species:dragon)" autocomplete="off">
<div class="tag-suggestions" id="tag-suggestions" hidden></div>
<div class="tag-chips" id="tag-chips"></div>
{{ form.tags }}
</div>
<p class="bio-help"><i class="fas fa-info-circle"></i> Categories: loader, version, general, creator, content, species, non_version. Unprefixed tags go to General. Creator tags are added automatically.</p>
</div>
@@ -1,18 +0,0 @@
<div class="form-group">
{{ form.version_name.errors }}
<label for="{{ form.version_name.id_for_label }}">{{ form.version_name.label }}</label>
{{ form.version_name }}
</div>
<div class="form-group">
{{ form.file.errors }}
<label for="{{ form.file.id_for_label }}">{{ form.file.label }}</label>
{{ form.file }}
</div>
<div class="form-group">
{{ form.changelog.errors }}
<label for="{{ form.changelog.id_for_label }}">{{ form.changelog.label }}</label>
{{ form.changelog }}
<p class="bio-help"><i class="fas fa-info-circle"></i> Supports Markdown.</p>
</div>
+2 -1
View File
@@ -21,7 +21,7 @@
<input type="file" id="media-input" accept="image/*,video/*" multiple hidden>
<div class="upload-batch" id="media-batch" hidden>
<i class="fas fa-circle-notch fa-spin"></i>
<span id="media-batch-status"></span>
<span class="upload-batch-status" id="media-batch-status"></span>
</div>
<div class="file-preview-grid" id="media-preview"></div>
</div>
@@ -41,5 +41,6 @@
{% endblock %}
{% block extra_js %}
<script src="{% static 'js/uploads.js' %}"></script>
<script src="{% static 'js/asset_upload.js' %}"></script>
{% endblock %}
@@ -96,7 +96,7 @@
<input type="file" id="media-input" accept="image/*,video/*" multiple hidden>
<div class="upload-batch" id="media-batch" hidden>
<i class="fas fa-circle-notch fa-spin"></i>
<span id="media-batch-status"></span>
<span class="upload-batch-status" id="media-batch-status"></span>
</div>
<div class="file-preview-grid" id="media-preview"></div>
</div>
@@ -117,6 +117,8 @@
{% block extra_js %}
{{ draft_uploads|json_script:'packs-draft-uploads' }}
{{ tag_categories|json_script:'packs-tag-categories' }}
<script src="{% static 'js/tags.js' %}"></script>
<script src="{% static 'js/uploads.js' %}"></script>
<script src="{% static 'js/create.js' %}"></script>
{% endblock %}
@@ -135,6 +135,29 @@
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
// Gallery deletion without a page reload, behind a confirmation dialog.
document.querySelectorAll('.gallery-delete').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
if (!confirm('Delete this media? This cannot be undone.')) return;
const xhr = new XMLHttpRequest();
xhr.open('POST', link.getAttribute('href'));
xhr.setRequestHeader('X-CSRFToken', getCookie('csrftoken'));
xhr.onload = function () {
if (xhr.status === 200) {
const figure = link.closest('figure.gallery-item');
if (figure) figure.remove();
} else {
alert('Could not delete media.');
}
};
xhr.onerror = function () {
alert('Network error while deleting media.');
};
xhr.send();
});
});
})();
</script>
{% endblock %}
+54 -8
View File
@@ -7,17 +7,59 @@
<h1><i class="fas fa-pen"></i> Edit {{ project.title }}</h1>
<p><a href="{% url 'library:project_detail' project.slug %}">&larr; Back to project</a></p>
<form method="post" enctype="multipart/form-data" class="card project-form" id="project-form">
<form method="post" class="card project-form create-form" id="project-form">
{% csrf_token %}
<div class="project-form-grid">
<div class="project-form-main">
{% include 'library/_project_form_fields.html' with form=project_form %}
</div>
<div class="create-grid">
<section class="create-panel create-panel-main">
<h2 class="form-section-title"><i class="fas fa-folder-open"></i> Project</h2>
<aside class="project-form-side">
{% include 'library/_project_form_side.html' with form=project_form %}
</aside>
<div class="form-group">
{{ project_form.title.errors }}
<label for="{{ project_form.title.id_for_label }}">Title</label>
{{ project_form.title }}
</div>
<div class="form-group">
{{ project_form.summary.errors }}
<label for="{{ project_form.summary.id_for_label }}">Summary</label>
{{ project_form.summary }}
</div>
<div class="form-group">
{{ project_form.category.errors }}
<label for="{{ project_form.category.id_for_label }}">Category</label>
{{ project_form.category }}
</div>
<div class="form-group">
{{ project_form.description.errors }}
<label for="{{ project_form.description.id_for_label }}">Description</label>
{{ project_form.description }}
<p class="bio-help"><i class="fas fa-info-circle"></i> Supports Markdown — headings, lists, links, code, tables and more.</p>
</div>
</section>
<section class="create-panel">
<h2 class="form-section-title"><i class="fas fa-image"></i> Thumbnail</h2>
<div class="upload-zone" data-upload="thumbnail">
<div class="drop-zone" role="button" tabindex="0">
<div class="drop-zone-inner">
<i class="fas fa-cloud-upload-alt drop-zone-icon"></i>
<p class="drop-zone-text">Drag &amp; drop an image</p>
<p class="drop-zone-sub">or click to browse</p>
</div>
</div>
<input type="file" id="thumb-input" accept="image/*" hidden>
<div class="upload-preview" id="thumb-preview"></div>
</div>
<h2 class="form-section-title" style="margin-top:16px;"><i class="fas fa-tags"></i> Tags</h2>
<div class="tag-editor">
<input type="text" id="tag-input" placeholder="Type a tag and press Enter… (e.g. content:threesome, species:dragon)" autocomplete="off">
<div class="tag-suggestions" id="tag-suggestions" hidden></div>
<div class="tag-chips" id="tag-chips"></div>
{{ project_form.tags }}
</div>
<p class="bio-help"><i class="fas fa-info-circle"></i> Categories: loader, version, general, creator, content, species, non_version. Unprefixed tags go to General.</p>
</section>
</div>
<div class="form-actions">
@@ -27,5 +69,9 @@
{% endblock %}
{% block extra_js %}
{{ pending_uploads|json_script:'packs-pending-uploads' }}
{{ tag_categories|json_script:'packs-tag-categories' }}
<script src="{% static 'js/tags.js' %}"></script>
<script src="{% static 'js/uploads.js' %}"></script>
<script src="{% static 'js/edit.js' %}"></script>
{% endblock %}
+43 -2
View File
@@ -7,11 +7,52 @@
<h1><i class="fas fa-upload"></i> New version — {{ project.title }}</h1>
<p><a href="{% url 'library:project_detail' project.slug %}">&larr; Back to project</a></p>
<form method="post" enctype="multipart/form-data" class="card project-form">
<form method="post" class="card project-form" id="version-form">
{% csrf_token %}
{% include 'library/_version_form_fields.html' with form=version_form %}
{% if version_form.non_field_errors %}
<div class="form-group">
<ul class="messages">
{% for error in version_form.non_field_errors %}<li class="error">{{ error }}</li>{% endfor %}
</ul>
</div>
{% endif %}
<div class="form-group">
{{ version_form.version_name.errors }}
<label for="{{ version_form.version_name.id_for_label }}">{{ version_form.version_name.label }}</label>
{{ version_form.version_name }}
</div>
<div class="form-group">
<label>Version file</label>
<div class="upload-zone" data-upload="version">
<div class="drop-zone" role="button" tabindex="0">
<div class="drop-zone-inner">
<i class="fas fa-cloud-upload-alt drop-zone-icon"></i>
<p class="drop-zone-text">Drag &amp; drop the pack file</p>
<p class="drop-zone-sub">or click to browse</p>
</div>
</div>
<input type="file" id="version-input" hidden>
<div class="upload-preview" id="version-preview"></div>
</div>
</div>
<div class="form-group">
{{ version_form.changelog.errors }}
<label for="{{ version_form.changelog.id_for_label }}">{{ version_form.changelog.label }}</label>
{{ version_form.changelog }}
<p class="bio-help"><i class="fas fa-info-circle"></i> Supports Markdown.</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary"><i class="fas fa-upload"></i> Upload version</button>
</div>
</form>
{% endblock %}
{% block extra_js %}
{{ pending_uploads|json_script:'packs-pending-uploads' }}
<script src="{% static 'js/uploads.js' %}"></script>
<script src="{% static 'js/version_upload.js' %}"></script>
{% endblock %}