add: post image uploads (supporter)

This commit is contained in:
trisua 2025-05-11 14:27:55 -04:00
parent ba1f8ef063
commit 70965298b5
18 changed files with 455 additions and 50 deletions

View file

@ -61,9 +61,13 @@
></textarea>
</div>
<div id="files_list" class="flex gap-2 flex-wrap"></div>
<div class="flex gap-2">
{{ components::emoji_picker(element_id="content",
render_dialog=true) }}
render_dialog=true) }} {% if is_supporter %} {{
components::file_picker(files_list_id="files_list") }}
{% endif %}
<button
class="small square quaternary"
@ -85,17 +89,32 @@
async function create_post_from_form_town_square(e) {
e.preventDefault();
await trigger("atto::debounce", ["posts::create"]);
fetch("/api/v1/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
e.target
.querySelector("button.primary")
.classList.add("hidden");
// create body
const body = new FormData();
for (const file of e.target.file_picker.files) {
body.append(file.name, file);
}
body.append(
"body",
JSON.stringify({
content: e.target.content.value,
community: document.getElementById(
"community_to_post_to",
).selectedOptions[0].value,
}),
);
// ...
fetch("/api/v1/posts", {
method: "POST",
body,
})
.then((res) => res.json())
.then(async (res) => {
@ -130,6 +149,10 @@
setTimeout(() => {
window.location.href = `/post/${res.payload}`;
}, 100);
} else {
e.target
.querySelector("button.primary")
.classList.remove("hidden");
}
});
}

View file

@ -78,15 +78,21 @@
async function create_post_from_form(e) {
e.preventDefault();
await trigger("atto::debounce", ["posts::create"]);
fetch("/api/v1/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
// create body
const body = new FormData();
body.append(
"body",
JSON.stringify({
content: e.target.content.value,
community: "{{ community.id }}",
}),
);
// ...
fetch("/api/v1/posts", {
method: "POST",
body,
})
.then((res) => res.json())
.then((res) => {

View file

@ -238,7 +238,8 @@ and show_community and community.id != config.town_square or question %}
>
{{ post.content|markdown|safe }}
</span>
{% else %}
{{ self::post_media(upload_ids=post.uploads) }} {% else %}
<details>
<summary
class="card flex gap-2 items-center tertiary red w-full"
@ -247,13 +248,17 @@ and show_community and community.id != config.town_square or question %}
<b>{{ post.context.content_warning }}</b>
</summary>
<span
id="post-content:{{ post.id }}"
class="no_p_margin"
hook="long"
>
{{ post.content|markdown|safe }}
</span>
<div class="flex flex-col gap-2">
<span
id="post-content:{{ post.id }}"
class="no_p_margin"
hook="long"
>
{{ post.content|markdown|safe }}
</span>
{{ self::post_media(upload_ids=post.uploads) }}
</div>
</details>
{% endif %}
@ -386,6 +391,17 @@ and show_community and community.id != config.town_square or question %}
{% if community and show_community and community.id != config.town_square or
question %}
</div>
{% endif %} {%- endmacro %} {% macro post_media(upload_ids) -%} {% if
upload_ids|length > 0%}
<div class="media_gallery gap-2">
{% for upload in upload_ids %}
<img
src="/api/v1/uploads/{{ upload }}"
alt="Image upload"
onclick="trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload }}'])"
/>
{% endfor %}
</div>
{% endif %} {%- endmacro %} {% macro notification(notification) -%}
<div class="w-full card-nest">
<div class="card small notif_title flex items-center">
@ -1262,8 +1278,77 @@ show_kick=false, secondary=false) -%}
</div>
</div>
</dialog>
{% endif %} {%- endmacro %} {% macro supporter_ad(body="") -%} {% if
config.stripe and not is_supporter %}
{% endif %} {%- endmacro %} {% macro file_picker(files_list_id) -%}
<button
class="button small square quaternary"
onclick="pick_file()"
title="Images"
type="button"
>
{{ icon "image-up" }}
</button>
<input
type="file"
multiple
accept="image/png,image/jpeg,image/avif,image/webp"
style="display: none"
name="file_picker"
/>
<div style="display: none" id="file_template">
{{ icon "image" }}
<b class="name shorter" style="overflow-wrap: normal">.file_name</b>
</div>
<script>
(() => {
const input = document.querySelector("input[name=file_picker]");
const element = document.getElementById("{{ files_list_id }}");
const template = document.getElementById("file_template");
globalThis.pick_file = () => {
input.click();
};
globalThis.render_file_picker_files = () => {
element.innerHTML = "";
let idx = 0;
for (const file of input.files) {
element.innerHTML += `<div class="card small secondary flex items-center gap-2" onclick="remove_file(${idx})" style="cursor: pointer">${template.innerHTML.replace(
".file_name",
file.name,
)}</div>`;
idx += 1;
}
};
globalThis.remove_file = (idx) => {
const files = Array.from(input.files);
files.splice(idx - 1, 1);
// update files
const list = new DataTransfer();
for (item of files) {
list.items.add(item);
}
input.files = list.files;
// render
render_file_picker_files();
};
input.addEventListener("change", () => {
render_file_picker_files();
});
})();
</script>
{%- endmacro %} {% macro supporter_ad(body="") -%} {% if config.stripe and not
is_supporter %}
<div
class="card w-full supporter_ad"
ui_ident="supporter_ad"

View file

@ -290,16 +290,23 @@
async function create_reply_from_form(e) {
e.preventDefault();
await trigger("atto::debounce", ["posts::create"]);
fetch("/api/v1/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
// create body
const body = new FormData();
body.append(
"body",
JSON.stringify({
content: e.target.content.value,
community: "{{ community.id }}",
replying_to: "{{ post.id }}",
}),
);
// ...
fetch("/api/v1/posts", {
method: "POST",
body,
})
.then((res) => res.json())
.then((res) => {

View file

@ -67,7 +67,7 @@ macros -%}
<body>
<div id="toast_zone"></div>
<div id="page" style="display: contents">
<div id="page">
<!-- prettier-ignore -->
{% if user and user.id == 0 %}
<article>
@ -281,6 +281,17 @@ macros -%}
</div>
</dialog>
<div class="lightbox hidden" id="lightbox">
<button
class="lightbox_exit small square quaternary red"
onclick="trigger('ui::lightbox_close')"
>
{{ icon "x" }}
</button>
<img src="" alt="Image" id="lightbox_img" />
</div>
{% if user %}
<dialog id="tokens_dialog">
<div class="inner flex flex-col gap-2">