add: post image uploads (supporter)
This commit is contained in:
parent
ba1f8ef063
commit
70965298b5
18 changed files with 455 additions and 50 deletions
|
@ -317,6 +317,60 @@ img.emoji {
|
|||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.media_gallery {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr 1fr;
|
||||
grid-auto-flow: column dense;
|
||||
border-radius: var(--radius);
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.media_gallery {
|
||||
grid-auto-flow: row dense;
|
||||
}
|
||||
}
|
||||
|
||||
.media_gallery img {
|
||||
border-radius: var(--radius);
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
|
||||
.media_gallery img:hover {
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
.lightbox {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
background: hsla(0, 0%, 0%, 50%);
|
||||
backdrop-filter: blur(5px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.lightbox_exit {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.lightbox img {
|
||||
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
|
||||
var(--color-shadow);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* avatar/banner */
|
||||
.avatar {
|
||||
--size: 50px;
|
||||
|
@ -802,7 +856,7 @@ nav .button:not(.title):not(.active):hover {
|
|||
top: unset;
|
||||
}
|
||||
|
||||
body {
|
||||
#page {
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -1028,6 +1028,16 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
return get_permissions_html;
|
||||
},
|
||||
);
|
||||
|
||||
// lightbox
|
||||
self.define("lightbox_open", (_, src) => {
|
||||
document.getElementById("lightbox_img").src = src;
|
||||
document.getElementById("lightbox").classList.remove("hidden");
|
||||
});
|
||||
|
||||
self.define("lightbox_close", () => {
|
||||
document.getElementById("lightbox").classList.add("hidden");
|
||||
});
|
||||
})();
|
||||
|
||||
(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue