forked from andyburke/autonomous.contact
refactor: more refactoring to topics as the top-level organization
This commit is contained in:
parent
11ecd86bb9
commit
4347d20263
18 changed files with 730 additions and 317 deletions
|
|
@ -129,63 +129,244 @@
|
|||
<label id="sidebar-toggle-icon" for="sidebar-toggle">
|
||||
<div class="icon right"></div>
|
||||
</label>
|
||||
<div>
|
||||
<span class="title">topics</span>
|
||||
</div>
|
||||
<ul id="topic-list" class="topic-list"></ul>
|
||||
<div id="topic-creation-container" data-requires-permission="topics.create">
|
||||
<button
|
||||
id="toggle-topic-creation-form-button"
|
||||
onclick="((event) => {
|
||||
event.preventDefault();
|
||||
const topic_create_form = document.getElementById( 'topic-create' );
|
||||
topic_create_form.style[ 'height' ] = topic_create_form.style[ 'height' ] === '5rem' ? '0' : '5rem';
|
||||
})(event)"
|
||||
>
|
||||
<div class="icon plus"></div>
|
||||
</button>
|
||||
<form
|
||||
id="topic-create"
|
||||
data-smart="true"
|
||||
action="/api/topics"
|
||||
method="POST"
|
||||
style="
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s;
|
||||
"
|
||||
>
|
||||
<input
|
||||
id="new-topic-name-input"
|
||||
type="text"
|
||||
name="name"
|
||||
value=""
|
||||
placeholder="new topic"
|
||||
/>
|
||||
|
||||
<input type="submit" hidden />
|
||||
<script>
|
||||
const DEFAULT_AVATAR_URL = `//${window.location.host}/images/default_avatar.gif`;
|
||||
|
||||
new MutationObserver((mutations, observer) => {
|
||||
mutations.forEach((mutation) => {
|
||||
const user_json = document.body.dataset.user;
|
||||
const user = user_json
|
||||
? JSON.parse(user_json)
|
||||
: {
|
||||
username: "",
|
||||
meta: {
|
||||
avatar: DEFAULT_AVATAR_URL,
|
||||
},
|
||||
};
|
||||
|
||||
const ids = document.querySelectorAll("[data-bind__user_id]");
|
||||
for (const id of ids) {
|
||||
const bound_to =
|
||||
typeof id.dataset["bind__user_id"] === "string" &&
|
||||
id.dataset["bind__user_id"].length > 0
|
||||
? id.dataset["bind__user_id"]
|
||||
: "innerHTML";
|
||||
avatar[bound_to] = user.id ?? "<unknown>";
|
||||
}
|
||||
|
||||
const avatars = document.querySelectorAll("[data-bind__user_meta_avatar]");
|
||||
for (const avatar of avatars) {
|
||||
const bound_to =
|
||||
typeof avatar.dataset["bind__user_meta_avatar"] === "string" &&
|
||||
avatar.dataset["bind__user_meta_avatar"].length
|
||||
? avatar.dataset["bind__user_meta_avatar"]
|
||||
: "innerHTML";
|
||||
avatar[bound_to] = user.meta?.avatar ?? DEFAULT_AVATAR_URL;
|
||||
}
|
||||
|
||||
const usernames = document.querySelectorAll("[data-bind__user_username]");
|
||||
for (const username of usernames) {
|
||||
const bound_to =
|
||||
typeof username.dataset["bind__user_username"] === "string" &&
|
||||
username.dataset["bind__user_username"].length > 0
|
||||
? username.dataset["bind__user_username"]
|
||||
: "innerHTML";
|
||||
username[bound_to] = user.username;
|
||||
}
|
||||
});
|
||||
}).observe(document.body, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-user"],
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.profile-container {
|
||||
margin: 1rem auto;
|
||||
max-width: 1024px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.profile-container .avatar-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.profile-container .avatar-container #user-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.profile-container .avatar-container input[type="file"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<div class="profile-container">
|
||||
<div class="avatar-container">
|
||||
<img
|
||||
id="user-avatar"
|
||||
src="/images/default_avatar.gif"
|
||||
alt="User Avatar"
|
||||
data-bind__user_meta_avatar="src"
|
||||
/>
|
||||
<input type="file" accept="image/*" name="avatar" />
|
||||
<script>
|
||||
const avatar_file_input = document.querySelector('input[name="avatar"]');
|
||||
avatar_file_input.addEventListener("change", async (event) => {
|
||||
const user_json = document.body.dataset.user;
|
||||
if (!user_json) {
|
||||
return alert("You must be logged in.");
|
||||
}
|
||||
|
||||
const user = JSON.parse(user_json);
|
||||
const avatar = avatar_file_input.files[0];
|
||||
|
||||
if (!avatar || !avatar.type || !avatar.type.includes("image")) {
|
||||
return alert("You must select a valid image to upload as your avatar.");
|
||||
}
|
||||
|
||||
// TODO: actually enforce this on the upload in serverus somehow
|
||||
if (avatar.size > 512_000) {
|
||||
return alert("512K is the largest allowed avatar size.");
|
||||
}
|
||||
|
||||
const body = new FormData();
|
||||
body.append("file", avatar, encodeURIComponent(avatar.name));
|
||||
|
||||
const avatar_path = `/files/users/${user.id}/avatars/${encodeURIComponent(avatar.name)}`;
|
||||
|
||||
const avatar_upload_response = await api.fetch(avatar_path, {
|
||||
method: "PUT",
|
||||
body,
|
||||
});
|
||||
|
||||
if (!avatar_upload_response.ok) {
|
||||
const error = await avatar_upload_response.json();
|
||||
return alert(error?.error?.message ?? "Unknown error.");
|
||||
}
|
||||
|
||||
const updated_user = { ...user };
|
||||
updated_user.meta = updated_user.meta ?? {};
|
||||
updated_user.meta.avatar = `//${window.location.host}${avatar_path}`;
|
||||
|
||||
const saved_user_response = await api.fetch(`/api/users/${user.id}`, {
|
||||
method: "PUT",
|
||||
json: updated_user,
|
||||
});
|
||||
|
||||
if (!saved_user_response.ok) {
|
||||
const error = await avatar_upload_response.json();
|
||||
return alert(error?.error?.message ?? "Unknown error.");
|
||||
}
|
||||
|
||||
const saved_user = await saved_user_response.json();
|
||||
|
||||
document.body.dataset.user = JSON.stringify(saved_user);
|
||||
document.body.dataset.perms = saved_user.permissions.join(":");
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="username-container">
|
||||
<span class="username" data-bind__user_username></span>
|
||||
</div>
|
||||
|
||||
<div class="notifications-settings-container">
|
||||
<button class="mockup" onclick="NOTIFICATIONS.request_permission()">
|
||||
Enable Notifications
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form data-smart="true" data-method="DELETE" action="/api/auth">
|
||||
<script>
|
||||
{
|
||||
const form = document.currentScript.closest("form");
|
||||
const topic_create_form = document.getElementById("topic-create");
|
||||
const new_topic_name_input = document.getElementById("new-topic-name-input");
|
||||
form.on_reply = (response) => {
|
||||
if (!response.deleted) {
|
||||
alert("error logging out? please reload.");
|
||||
return;
|
||||
}
|
||||
|
||||
form.on_reply = (new_topic) => {
|
||||
const topic_list = document.getElementById("topic-list");
|
||||
topic_list.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`<li id="topic-selector-${new_topic.id}" class="topic"><a href="#/topic/${new_topic.id}">${new_topic.name}</a></li>`,
|
||||
);
|
||||
|
||||
new_topic_name_input.value = "";
|
||||
window.location.hash = `/topic/${new_topic.id}`;
|
||||
topic_create_form.style["height"] = "0";
|
||||
delete document.body.dataset.user;
|
||||
delete document.body.dataset.perms;
|
||||
window.location = "/";
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<button class="primary">Log Out</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="topics-container">
|
||||
<div>
|
||||
<span class="title">topics</span>
|
||||
</div>
|
||||
<ul id="topic-list" class="topic-list"></ul>
|
||||
|
||||
<div id="topic-creation-container" data-requires-permission="topics.create">
|
||||
<button
|
||||
id="toggle-topic-creation-form-button"
|
||||
onclick="((event) => {
|
||||
event.preventDefault();
|
||||
const topic_create_form = document.getElementById( 'topic-create' );
|
||||
topic_create_form.style[ 'height' ] = topic_create_form.style[ 'height' ] === '5rem' ? '0' : '5rem';
|
||||
})(event)"
|
||||
>
|
||||
<div class="icon plus"></div>
|
||||
</button>
|
||||
<form
|
||||
id="topic-create"
|
||||
data-smart="true"
|
||||
action="/api/topics"
|
||||
method="POST"
|
||||
style="
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s;
|
||||
"
|
||||
>
|
||||
<input
|
||||
id="new-topic-name-input"
|
||||
type="text"
|
||||
name="name"
|
||||
value=""
|
||||
placeholder="new topic"
|
||||
/>
|
||||
|
||||
<input type="submit" hidden />
|
||||
<script>
|
||||
{
|
||||
const form = document.currentScript.closest("form");
|
||||
const topic_create_form = document.getElementById("topic-create");
|
||||
const new_topic_name_input =
|
||||
document.getElementById("new-topic-name-input");
|
||||
|
||||
form.on_reply = (new_topic) => {
|
||||
const topic_list = document.getElementById("topic-list");
|
||||
topic_list.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`<li id="topic-selector-${new_topic.id}" class="topic"><a href="#/topic/${new_topic.id}">${new_topic.name}</a></li>`,
|
||||
);
|
||||
|
||||
new_topic_name_input.value = "";
|
||||
window.location.hash = `/topic/${new_topic.id}`;
|
||||
topic_create_form.style["height"] = "0";
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue