feature: file uploads and audio embedding
This commit is contained in:
parent
2224e1abe0
commit
4c0a8bb700
7 changed files with 105 additions and 22 deletions
|
@ -7,6 +7,7 @@
|
|||
--border-normal: #888;
|
||||
--border-highlight: #bbb;
|
||||
--icon-scale: 1.25;
|
||||
--border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
|
@ -184,7 +185,7 @@ button {
|
|||
color: inherit;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--text);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
const body = new FormData();
|
||||
body.append("file", avatar, encodeURIComponent(avatar.name));
|
||||
|
||||
const avatar_path = `/files/users/${user.id}/avatars/${avatar.name}`;
|
||||
const avatar_path = `/files/users/${user.id}/avatars/${encodeURIComponent(avatar.name)}`;
|
||||
|
||||
const avatar_upload_response = await api.fetch(avatar_path, {
|
||||
method: "PUT",
|
||||
|
|
|
@ -90,10 +90,24 @@
|
|||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
#talk #room-chat-entry-container form button {
|
||||
#talk #room-chat-entry-container form input[type="file"] {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#talk #room-chat-entry-container form button,
|
||||
#talk #room-chat-entry-container form label {
|
||||
position: relative;
|
||||
top: inherit;
|
||||
font-size: inherit;
|
||||
transition: inherit;
|
||||
width: 50px;
|
||||
padding: inherit;
|
||||
margin: 0 1rem;
|
||||
cursor: pointer;
|
||||
align-content: center;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--text);
|
||||
}
|
||||
|
||||
#talk #room-chat-entry-container form textarea {
|
||||
|
@ -101,6 +115,7 @@
|
|||
flex-grow: 1;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
#talk .message-container {
|
||||
|
@ -109,7 +124,7 @@
|
|||
background: rgba(255, 255, 255, 0.03);
|
||||
margin-top: 0.75rem;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
#talk .message-container.user-tick.time-tick + .message-container.user-tick.time-tick,
|
||||
|
@ -250,12 +265,17 @@
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
#talk .embed-container audio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#talk .embed-container.rounded {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
#talk .embed-container.short {
|
||||
height: 0;
|
||||
min-height: 40px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 7.5%;
|
||||
|
@ -347,7 +367,8 @@
|
|||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
#talk #room-chat-container #room-chat-entry-container form button {
|
||||
#talk #room-chat-container #room-chat-entry-container form button,
|
||||
#talk #room-chat-container #room-chat-entry-container form label {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,9 +89,16 @@
|
|||
<div id="room-chat-content"></div>
|
||||
<div id="room-chat-entry-container">
|
||||
<form id="room-chat-entry" action="" data-smart="true" data-method="POST">
|
||||
<button aria-label="Attach file">
|
||||
<i class="icon attachment"></i>
|
||||
</button>
|
||||
<input
|
||||
id="file-upload-and-share-input"
|
||||
aria-label="Upload and share file"
|
||||
type="file"
|
||||
name="file-upload-and-share"
|
||||
multiple
|
||||
/>
|
||||
<label for="file-upload-and-share-input">
|
||||
<div class="icon attachment"></div>
|
||||
</label>
|
||||
<textarea
|
||||
id="room-chat-input"
|
||||
class="room-chat-input"
|
||||
|
@ -101,10 +108,12 @@
|
|||
<button id="room-chat-send" class="primary" aria-label="Send a message">
|
||||
<i class="icon send"></i>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
{
|
||||
const form = document.currentScript.closest("form");
|
||||
const file_input = document.querySelector(
|
||||
'input[name="file-upload-and-share"]',
|
||||
);
|
||||
const chat_input = document.getElementById("room-chat-input");
|
||||
const room_chat_container =
|
||||
document.getElementById("room-chat-container");
|
||||
|
@ -118,18 +127,48 @@
|
|||
}
|
||||
});
|
||||
|
||||
form.on_submit = (event) => {
|
||||
const message = chat_input.value.trim();
|
||||
if (message.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
form.on_submit = async (event) => {
|
||||
const user = JSON.parse(document.body.dataset.user);
|
||||
const room_id = room_chat_container.dataset.room_id;
|
||||
if (!room_id) {
|
||||
alert("Failed to get room_id!");
|
||||
return false;
|
||||
}
|
||||
|
||||
form.uploaded_urls = [];
|
||||
form.errors = [];
|
||||
for await (const file of file_input.files) {
|
||||
const body = new FormData();
|
||||
body.append("file", file, encodeURIComponent(file.name));
|
||||
|
||||
const file_path = `/files/users/${user.id}/${encodeURIComponent(file.name)}`;
|
||||
|
||||
const file_upload_response = await api.fetch(file_path, {
|
||||
method: "PUT",
|
||||
body,
|
||||
});
|
||||
|
||||
if (!file_upload_response.ok) {
|
||||
const error = await file_upload_response.json();
|
||||
form.errors.push(error?.error?.message ?? "Unknown error.");
|
||||
continue;
|
||||
}
|
||||
|
||||
const file_url = `${window.location.protocol}//${window.location.host}${file_path}`;
|
||||
form.uploaded_urls.push(file_url);
|
||||
}
|
||||
|
||||
if (form.errors.length) {
|
||||
const errors = form.errors.join("\n\n");
|
||||
alert(errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = chat_input.value.trim();
|
||||
if (form.uploaded_urls.length === 0 && message.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
form.action = `/api/rooms/${room_id}/events`;
|
||||
};
|
||||
|
||||
|
@ -147,6 +186,15 @@
|
|||
updated: now,
|
||||
};
|
||||
|
||||
if (form.uploaded_urls.length) {
|
||||
json.data = json.data ?? {};
|
||||
json.data.message =
|
||||
(typeof json.data.message === "string" &&
|
||||
json.data.message.trim().length
|
||||
? json.data.message.trim() + "\n"
|
||||
: "") + form.uploaded_urls.join("\n");
|
||||
}
|
||||
|
||||
const user = JSON.parse(document.body.dataset.user);
|
||||
render_text_event(room_chat_content, json, user);
|
||||
document
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// watch out for places we need to set `lastIndex` ... :frown:
|
||||
|
||||
const URL_MATCHING_REGEX =
|
||||
/(?:(?<protocol>[a-zA-Z]+):\/\/)?(?:(?<auth>(?<username>\S.+)\:(?<password>.+))\@)?(?<host>(?:(?<hostname>[-a-zA-Z0-9\.]+)\.)?(?<domain>[-a-zA-Z0-9]+?\.(?<tld>[-a-zA-Z0-9]{2,64}))(?:\:(?<port>[0-9]{1,6}))?)\b(?<path>[-a-zA-Z0-9@:%_{}\[\]<>\(\)\+.~&\/="]*?(?<extension>\.[^\.?/#"]+)?)(?:\?(?<query>[a-zA-Z0-9!$%&<>()*+,-\.\/\:\;\=\?\@_~"]+))?(?:#(?<hash>[a-zA-Z0-9!$&'()*+,-\.\/\:\;\=\?\@_~"]*?))?(?:$|\s)/gim;
|
||||
/(?:(?<protocol>[a-zA-Z]+):)?(?:\/\/)?(?:(?<auth>(?<username>\S.+)\:(?<password>.+))\@)?(?<host>(?:(?<hostname>[-a-zA-Z0-9\.]+)\.)?(?<domain>(?:[-a-zA-Z0-9]+?\.(?<tld>[-a-zA-Z0-9]{2,64}))|localhost)(?:\:(?<port>[0-9]{1,6}))?)\b(?<path>[-a-zA-Z0-9@:%_{}\[\]<>\(\)\+.~&\/="]*?(?<extension>\.[^\.?/#"\n<>]+)?)(?:\?(?<query>[a-zA-Z0-9!$%&<>()*+,-\.\/\:\;\=\?\@_~"]+))?(?:#(?<hash>[a-zA-Z0-9!$&'()*+,-\.\/\:\;\=\?\@_~"]*?))?(?:$|\s)/gim;
|
||||
|
||||
const VIDEO_ID_EXTRACTOR =
|
||||
/(?<video_domain>vimeo\.com|youtu(?:be\.com|\.be|be\.googleapis\.com))(?:\/(?<action>video|embed|watch|shorts|v))?.*(?:(?:\/|v=)(?<video_id>[A-Za-z0-9._%-]*))\S*/gi;
|
||||
|
@ -159,8 +159,14 @@ const URL_MATCH_HANDLERS = [
|
|||
// const punycode = get_punycode();
|
||||
// const punycoded_url = punycode.encode(match[0]);
|
||||
|
||||
console.dir({
|
||||
link_info,
|
||||
});
|
||||
if (typeof link_info.extension === "string") {
|
||||
const mime_types = get_mime_types(link_info.extension);
|
||||
console.dir({
|
||||
mime_types,
|
||||
});
|
||||
if (mime_types.length) {
|
||||
if (mime_types.includes("image/gif")) {
|
||||
return `<div class="embed-container image gif"><img src="${link_info.url}" alt="A gif from ${link_info.domain}" /></div>`;
|
||||
|
@ -173,6 +179,16 @@ const URL_MATCH_HANDLERS = [
|
|||
if (mime_types[0].indexOf("image") === 0) {
|
||||
return `<div class="embed-container image"><img src="${link_info.url}" alt="An image from ${link_info.domain}" /></div>`;
|
||||
}
|
||||
|
||||
if (mime_types[0].indexOf("audio") === 0) {
|
||||
return `
|
||||
<div class="embed-container short">
|
||||
<audio controls>
|
||||
<source src="${link_info.url}" type="${mime_types[0].indexOf("audio/mpeg") === 0 ? "audio/mpeg" : mime_types[0]}">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +197,7 @@ const URL_MATCH_HANDLERS = [
|
|||
];
|
||||
|
||||
function message_text_to_html(input) {
|
||||
let html_message = input;
|
||||
let html_message = (input ?? "").replace(/\n/g, "<br/>");
|
||||
let match;
|
||||
|
||||
URL_MATCHING_REGEX.lastIndex = 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue