refactor: switch how tabs work in an effort to make downstream stuff

easier
This commit is contained in:
Andy Burke 2026-02-23 11:49:23 -08:00
parent 7c4649924e
commit 873773bc91
14 changed files with 846 additions and 947 deletions

View file

@ -5,7 +5,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: absolute; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
@ -18,13 +18,11 @@
#auth-container { #auth-container {
width: 95%; width: 95%;
min-height: 25rem;
position: relative; position: relative;
background: hsl(from var(--bg) h s calc(l/1.1) / 0.5); background: hsl(from var(--bg) h s calc(l/1.1) / 0.5);
max-width: 40em; max-width: 40em;
margin: 0 auto; margin: 0 auto;
border-radius: calc( var(--border-radius) * 2); border-radius: calc( var(--border-radius) * 2);
overflow: hidden;
transition: all 0.33s ease; transition: all 0.33s ease;
animation: zoomsettle 0.4s ease; animation: zoomsettle 0.4s ease;
@ -41,15 +39,6 @@
} }
} }
#login-tab .tab-content {
min-height: 17rem;
overflow: hidden;
}
#signup-tab .tab-content {
min-height: 21rem;
}
body[data-user] #signup-login-wall { body[data-user] #signup-login-wall {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
@ -58,7 +47,7 @@
#signup-login-wall form { #signup-login-wall form {
width: 100%; width: 100%;
padding: 1.5rem 1.5rem 0 1.5rem; padding: 0.5rem 1.5rem 0;
} }
</style> </style>
@ -66,96 +55,93 @@
<!-- #include "./files/settings/signup_pitch.html" or "./files/settings/signup_pitch.md" or "./signup_pitch.default.md" --> <!-- #include "./files/settings/signup_pitch.html" or "./files/settings/signup_pitch.md" or "./signup_pitch.default.md" -->
<div class="tabs"> <div class="tabs">
<div id="signup-tab" class="tab"> <input
<input type="radio"
type="radio" name="signup-login-tabs"
name="signup-login-tabs" id="signup-tab-input"
id="signup-tab-input" class="tabswitch"
class="tab-switch" checked="checked"
checked="checked" />
/> <label for="signup-tab-input" class="tablabel">
<label for="signup-tab-input" class="tab-label"> <div class="label">Sign Up</div>
<div class="label">Sign Up</div> </label>
</label> <div id="signup-tab" class="panel">
<div class="tab-content"> <form data-smart="true" data-method="POST" id="signup-form" action="/api/users">
<form data-smart="true" data-method="POST" id="signup-form" action="/api/users"> <script>
{
const form = document.currentScript.closest("form");
form.on_reply = (response) => {
const user = response.user;
APP.login( user );
};
}
</script>
<div>
<input id="signup-username" type="text" name="username" required />
<label class="placeholder" for="signup-username">username</label>
</div>
<div>
<input id="signup-password" type="password" name="password" required />
<label class="placeholder" for="signup-password">password</label>
</div>
<div>
<script> <script>
{ APP.on( 'load', () => {
const form = document.currentScript.closest("form"); const query = new URL(document.location.toString())
form.on_reply = (response) => { .searchParams;
const user = response.user; const invite_code = query.get("invite_code");
APP.login( user ); if (typeof invite_code === "string" && invite_code.length) {
}; document.getElementById("signup-invite-code").value =
} decodeURIComponent(invite_code);
</script>
<div>
<input id="signup-username" type="text" name="username" required />
<label class="placeholder" for="signup-username">username</label>
</div>
<div>
<input id="signup-password" type="password" name="password" required />
<label class="placeholder" for="signup-password">password</label>
</div>
<div>
<script>
APP.on( 'load', () => {
const query = new URL(document.location.toString())
.searchParams;
const invite_code = query.get("invite_code");
if (typeof invite_code === "string" && invite_code.length) {
document.getElementById("signup-invite-code").value =
decodeURIComponent(invite_code);
document.getElementById("signup-tab-input").checked = true; document.getElementById("signup-tab-input").checked = true;
} }
}); });
</script>
<input
id="signup-invite-code"
type="text"
name="invite_code"
required
/>
<label class="placeholder" for="signup-invite-code">invite code</label>
</div>
<button id="signup-submit" type="submit" class="primary">Sign Up</button>
</form>
</div>
</div>
<div id="login-tab" class="tab">
<input
type="radio"
name="signup-login-tabs"
id="login-tab-input"
class="tab-switch"
/>
<label for="login-tab-input" class="tab-label">
<div class="label">Log In</div>
</label>
<div class="tab-content">
<form data-smart="true" data-method="POST" id="login-form" action="/api/auth">
<script>
{
const form = document.currentScript.closest("form");
form.on_reply = (response) => {
const user = response.user;
APP.login( user );
};
}
</script> </script>
<div> <input
<input id="login-username" type="text" name="username" required /> id="signup-invite-code"
<label class="placeholder" for="login-username">username</label> type="text"
</div> name="invite_code"
<div> required
<input id="login-password" type="password" name="password" required /> />
<label class="placeholder" for="login-password">password</label> <label class="placeholder" for="signup-invite-code">invite code</label>
</div> </div>
<div> <button id="signup-submit" type="submit" class="primary">Sign Up</button>
<button id="login-submit" type="submit" class="primary">Log In</button> </form>
</div> </div>
</form>
</div> <input
type="radio"
name="signup-login-tabs"
id="login-tab-input"
class="tabswitch"
/>
<label for="login-tab-input" class="tablabel">
<div class="label">Log In</div>
</label>
<div id="login-tab" class="panel">
<form data-smart="true" data-method="POST" id="login-form" action="/api/auth">
<script>
{
const form = document.currentScript.closest("form");
form.on_reply = (response) => {
const user = response.user;
APP.login( user );
};
}
</script>
<div>
<input id="login-username" type="text" name="username" required />
<label class="placeholder" for="login-username">username</label>
</div>
<div>
<input id="login-password" type="password" name="password" required />
<label class="placeholder" for="login-password">password</label>
</div>
<div>
<button id="login-submit" type="submit" class="primary">Log In</button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>

View file

@ -127,122 +127,120 @@
} }
</style> </style>
<div id="blurbs" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="blurb-tab-input"
id="blurb-tab-input" class="tabswitch"
class="tab-switch" data-view="blurbs"
data-view="blurbs" />
/> <label for="blurb-tab-input" class="tablabel"
<label for="blurb-tab-input" class="tab-label" ><div class="icon blurb"></div>
><div class="icon blurb"></div> <div class="label">Blurbs</div>
<div class="label">Blurbs</div> </label>
</label> <div class="panel">
<div class="tab-content"> <div id="blurbs-container" class="container">
<div id="blurbs-container" class="container"> <!-- #include "./README.md" -->
<!-- #include "./README.md" -->
<!-- #include "./new_blurb.html" --> <!-- #include "./new_blurb.html" -->
<div <div
id="blurbs-list" id="blurbs-list"
data-feed data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.blurb' ) !== -1" data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.blurb' ) !== -1"
data-source="/api/events?type=blurb,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }" data-source="/api/events?type=blurb,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true" data-longpolling="true"
data-reverse="true" data-reverse="true"
data-insert="prepend" data-insert="prepend"
data-autoscroll="true" data-autoscroll="true"
> >
<script> <script>
{ {
const feed = document.currentScript.closest("[data-feed]"); const feed = document.currentScript.closest("[data-feed]");
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); }); APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
feed.__target_element = (item) => { feed.__target_element = (item) => {
return ( return (
document.querySelector( document.querySelector(
`.blurb-container[data-blurb_id='${item.parent_id}'] > .replies-container`, `.blurb-container[data-blurb_id='${item.parent_id}'] > .replies-container`,
) ?? feed ) ?? feed
); );
};
feed.__target_element = (item) => {
let target = feed;
switch (item.type) {
case "reaction":
target = document.querySelector(
`.blurb-container[data-event_id='${item.parent_id}'] > .reactions-container`,
);
break;
case "blurb":
default:
target =
document.querySelector(
`.blurb-container[data-event_id='${item.parent_id}'] > .replies-container`,
) ?? feed;
break;
}
return target;
};
feed.__context = async (item) => {
const blurb_datetime = datetime_to_local(item.timestamps.created);
return {
event: item,
blurb: item,
creator: await APP.USERS.get(item.creator_id),
blurb_datetime
}; };
feed.__target_element = (item) => { };
let target = feed; }
switch (item.type) { </script>
case "reaction": <template data-for_type="blurb">
target = document.querySelector( <div
`.blurb-container[data-event_id='${item.parent_id}'] > .reactions-container`, id="${ context.blurb.id }"
); class="blurb-container"
break; data-event_id="${context.blurb.id}"
case "blurb": data-creator_id="${context.creator.id}"
default: data-blurb_id="${context.blurb.id}"
target = data-temp_id="${context.blurb.meta?.temp_id ?? ""}">
document.querySelector( <div class="media-preview-container">
`.blurb-container[data-event_id='${item.parent_id}'] > .replies-container`, ${context.blurb.data?.media?.length ? context.blurb.data.media.map(function(url) { return url ? `<img src='${url}' loading="lazy"/>` : ''; }).join('\n') : ''}
) ?? feed;
break;
}
return target;
};
feed.__context = async (item) => {
const blurb_datetime = datetime_to_local(item.timestamps.created);
return {
event: item,
blurb: item,
creator: await APP.USERS.get(item.creator_id),
blurb_datetime
};
};
}
</script>
<template data-for_type="blurb">
<div
id="${ context.blurb.id }"
class="blurb-container"
data-event_id="${context.blurb.id}"
data-creator_id="${context.creator.id}"
data-blurb_id="${context.blurb.id}"
data-temp_id="${context.blurb.meta?.temp_id ?? ""}">
<div class="media-preview-container">
${context.blurb.data?.media?.length ? context.blurb.data.media.map(function(url) { return url ? `<img src='${url}' loading="lazy"/>` : ''; }).join('\n') : ''}
</div>
<div class="info-container">
<div class="avatar-container inline">
<img src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}" alt="user avatar" loading="lazy" />
</div>
<div class="username-container">
<span class="username">${context.creator.username}</span>
</div>
<div class="datetime-container">
<span class="long">${context.blurb_datetime.long}</span>
<span class="short">${context.blurb_datetime.short}</span>
</div>
</div>
<div class="content-container">${htmlify(md_to_html(context.blurb.data.blurb))}</div>
<div class="reactions-container"></div>
<button class="icon more" commandfor="eventactionspopover"></button>
<!-- #include "./new_blurb.html" -->
<div class="replies-container"></div>
</div> </div>
</template>
<template data-for_type="reaction"> <div class="info-container">
<div <div class="avatar-container inline">
id="${context.event.id}" <img src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}" alt="user avatar" loading="lazy" />
class="reaction-container" </div>
data-event_id="${context.event.id}" <div class="username-container">
data-creator_id="${context.creator.id}" <span class="username">${context.creator.username}</span>
data-temp_id="${context.event.meta?.temp_id ?? ''}" </div>
> <div class="datetime-container">
<span class="reaction">${ context.event.data.reaction }</span> <span class="long">${context.blurb_datetime.long}</span>
<span class="short">${context.blurb_datetime.short}</span>
</div>
</div> </div>
</template> <div class="content-container">${htmlify(md_to_html(context.blurb.data.blurb))}</div>
</div> <div class="reactions-container"></div>
<button class="icon more" commandfor="eventactionspopover"></button>
<!-- #include "./new_blurb.html" -->
<div class="replies-container"></div>
</div>
</template>
<template data-for_type="reaction">
<div
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div>
</template>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,16 +1,14 @@
<div id="calendar" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="calendar-tab-input"
id="calendar-tab-input" class="tabswitch"
class="tab-switch" data-view="calendar"
data-view="calendar" />
/> <label for="calendar-tab-input" class="tablabel mockup"
<label for="calendar-tab-input" class="tab-label mockup" ><div class="icon calendar"></div>
><div class="icon calendar"></div> <div class="label">Calendar</div></label
<div class="label">Calendar</div></label >
> <div class="panel">
<div class="tab-content"> <!-- #include "./README.md" -->
<!-- #include "./README.md" -->
</div>
</div> </div>

View file

@ -1,10 +1,10 @@
#chat #chat-container { #chat-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
#chat #chat-content { #chat-container #chat-content {
overflow-y: scroll; overflow-y: scroll;
scroll-behavior: smooth; scroll-behavior: smooth;
position: absolute; position: absolute;
@ -14,13 +14,14 @@
bottom: 5rem; bottom: 5rem;
padding: 1.5rem 1.5rem 0.75rem 1.5rem; padding: 1.5rem 1.5rem 0.75rem 1.5rem;
} }
@media screen and (max-width: 1200px) { @media screen and (max-width: 1200px) {
#chat #chat-content { #chat-container #chat-content {
padding: 0.75rem; padding: 0.75rem;
} }
} }
#chat #chat-entry-container { #chat-container #chat-entry-container {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -29,18 +30,18 @@
padding: 1rem; padding: 1rem;
} }
#chat #chat-entry-container form { #chat-container #chat-entry-container form {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#chat #chat-entry-container form input[type="file"] { #chat-container #chat-entry-container form input[type="file"] {
opacity: 0; opacity: 0;
display: none; display: none;
} }
#chat #chat-entry-container form button, #chat-container #chat-entry-container form button,
#chat #chat-entry-container form label { #chat-container #chat-entry-container form label {
position: relative; position: relative;
top: inherit; top: inherit;
font-size: inherit; font-size: inherit;
@ -54,12 +55,12 @@
border: 1px solid rgba(128, 128, 128, 0.2); border: 1px solid rgba(128, 128, 128, 0.2);
} }
#chat #chat-entry-container form textarea { #chat-container #chat-entry-container form textarea {
flex-grow: 1; flex-grow: 1;
resize: none; resize: none;
} }
#chat .message-container { #chat-container .message-container {
position: relative; position: relative;
transition: all 0.33s; transition: all 0.33s;
background: var(--bg-lighter); background: var(--bg-lighter);
@ -68,7 +69,7 @@
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
#chat .message-container .html-from-markdown { #chat-container .message-container .html-from-markdown {
padding: 0; padding: 0;
} }
@ -100,45 +101,33 @@
padding-top: 0 !important; padding-top: 0 !important;
} }
.user-tock.time-tock + .user-tock.time-tick:has(+ .user-tock.time-tick), .user-tock.time-tock+.user-tock.time-tick:has(+ .user-tock.time-tick),
.user-tock.time-tick + .user-tock.time-tock:has(+ .user-tock.time-tock), .user-tock.time-tick+.user-tock.time-tock:has(+ .user-tock.time-tock),
.user-tick.time-tock + .user-tick.time-tick:has(+ .user-tick.time-tick), .user-tick.time-tock+.user-tick.time-tick:has(+ .user-tick.time-tick),
.user-tick.time-tick + .user-tick.time-tock:has(+ .user-tick.time-tock), .user-tick.time-tick+.user-tick.time-tock:has(+ .user-tick.time-tock),
.user-tock.time-tock + .user-tick.time-tick:has(+ .user-tick.time-tick), .user-tock.time-tock+.user-tick.time-tick:has(+ .user-tick.time-tick),
.user-tock.time-tick + .user-tick.time-tock:has(+ .user-tick.time-tock), .user-tock.time-tick+.user-tick.time-tock:has(+ .user-tick.time-tock),
.user-tick.time-tock + .user-tock.time-tick:has(+ .user-tock.time-tick), .user-tick.time-tock+.user-tock.time-tick:has(+ .user-tock.time-tick),
.user-tick.time-tick + .user-tock.time-tock:has(+ .user-tock.time-tock) { .user-tick.time-tick+.user-tock.time-tock:has(+ .user-tock.time-tock) {
/* border: 1px dotted green; */ /* border: 1px dotted green; */
margin-bottom: -0.75rem !important; margin-bottom: -0.75rem !important;
} }
#chat #chat .message-container.user-tick.time-tick+.message-container.user-tick.time-tick .info-container,
.message-container.user-tick.time-tick #chat .message-container.user-tick.time-tock+.message-container.user-tick.time-tock .info-container,
+ .message-container.user-tick.time-tick #chat .message-container.user-tock.time-tick+.message-container.user-tock.time-tick .info-container,
.info-container, #chat .message-container.user-tock.time-tock+.message-container.user-tock.time-tock .info-container {
#chat
.message-container.user-tick.time-tock
+ .message-container.user-tick.time-tock
.info-container,
#chat
.message-container.user-tock.time-tick
+ .message-container.user-tock.time-tick
.info-container,
#chat
.message-container.user-tock.time-tock
+ .message-container.user-tock.time-tock
.info-container {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
height: 0; height: 0;
margin: 0; margin: 0;
} }
#chat .message-container.sending { #chat-container .message-container.sending {
opacity: 0.75; opacity: 0.75;
} }
#chat .message-container button[commandfor] { #chat-container .message-container button[commandfor] {
position: absolute; position: absolute;
top: 0.1rem; top: 0.1rem;
right: 0.1rem; right: 0.1rem;
@ -147,13 +136,13 @@
z-index: 10; z-index: 10;
} }
#chat .message-container .message-actions-container:has(input[type="checkbox"]:checked) { #chat-container .message-container .message-actions-container:has(input[type="checkbox"]:checked) {
background: rgb(from var(--bg) r g b / 0.9); background: rgb(from var(--bg) r g b / 0.9);
border-color: var(--border-subtle); border-color: var(--border-subtle);
z-index: 11; z-index: 11;
} }
#chat .message-container .message-actions-container .message-action { #chat-container .message-container .message-actions-container .message-action {
border: none; border: none;
opacity: 0; opacity: 0;
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
@ -169,79 +158,75 @@
background: none; background: none;
} }
#chat .message-container .message-actions-container label { #chat-container .message-container .message-actions-container label {
cursor: pointer; cursor: pointer;
width: 2rem; width: 2rem;
text-align: right; text-align: right;
padding-top: 0.5rem; padding-top: 0.5rem;
} }
#chat .message-container .message-actions-container input[type="checkbox"] { #chat-container .message-container .message-actions-container input[type="checkbox"] {
opacity: 0; opacity: 0;
display: none; display: none;
} }
#chat #chat .message-container .message-actions-container input[type="checkbox"]:checked~.message-action {
.message-container
.message-actions-container
input[type="checkbox"]:checked
~ .message-action {
opacity: 1; opacity: 1;
width: 3.25rem; width: 3.25rem;
margin-right: 1.25rem; margin-right: 1.25rem;
} }
#chat .message-container .message-actions-container .message-action .action-name { #chat-container .message-container .message-actions-container .message-action .action-name {
font-size: x-small; font-size: x-small;
} }
#chat .message-container .info-container { #chat-container .message-container .info-container {
display: flex; display: flex;
margin-bottom: -1.75rem; margin-bottom: -1.75rem;
height: 3.75rem; height: 3.75rem;
} }
#chat .message-container .info-container .username-container { #chat-container .message-container .info-container .username-container {
margin: 0 4px; margin: 0 4px;
font-weight: bold; font-weight: bold;
padding-top: 0.5rem; padding-top: 0.5rem;
} }
#chat .message-container .info-container .datetime-container { #chat-container .message-container .info-container .datetime-container {
margin: 0 4px; margin: 0 4px;
padding-top: 0.4rem; padding-top: 0.4rem;
} }
#chat .message-container .info-container .datetime-container .long { #chat-container .message-container .info-container .datetime-container .long {
font-size: x-small; font-size: x-small;
text-transform: uppercase; text-transform: uppercase;
} }
#chat .message-container .info-container .datetime-container .short { #chat-container .message-container .info-container .datetime-container .short {
font-size: xx-small; font-size: xx-small;
visibility: hidden; visibility: hidden;
display: none; display: none;
} }
#chat .message-container .message-content-container, #chat-container .message-container .message-content-container,
#chat .message-container .message-media-container, #chat-container .message-container .message-media-container,
#chat .message-container .reactions-container { #chat-container .message-container .reactions-container {
padding-left: 8rem; padding-left: 8rem;
overflow-x: auto; overflow-x: auto;
} }
#chat .message-container .reactions-container:has(> .reaction-container) { #chat-container .message-container .reactions-container:has(> .reaction-container) {
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
#chat .embed-container { #chat-container .embed-container {
position: relative; position: relative;
width: 100%; width: 100%;
max-width: 640px; max-width: 640px;
} }
#chat .embed-container .embed-actions-container { #chat-container .embed-container .embed-actions-container {
position: absolute; position: absolute;
z-index: 100; z-index: 100;
top: 0.25rem; top: 0.25rem;
@ -253,31 +238,31 @@
opacity: 0; opacity: 0;
} }
#chat .embed-container audio { #chat-container .embed-container audio {
width: 100%; width: 100%;
} }
#chat .embed-container.rounded { #chat-container .embed-container.rounded {
border-radius: 6px; border-radius: 6px;
} }
#chat .embed-container.short { #chat-container .embed-container.short {
height: 120px; height: 120px;
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
} }
#chat .embed-container.tidal { #chat-container .embed-container.tidal {
border-radius: 12px; border-radius: 12px;
} }
#chat .embed-container.vertical { #chat-container .embed-container.vertical {
max-width: 320px; max-width: 320px;
overflow: hidden; overflow: hidden;
aspect-ratio: 9 / 16 !important; aspect-ratio: 9 / 16 !important;
} }
#chat .embed-container.letterbox { #chat-container .embed-container.letterbox {
/* height: 0; */ /* height: 0; */
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
@ -285,14 +270,14 @@
aspect-ratio: 16 / 9 !important; aspect-ratio: 16 / 9 !important;
} }
#chat .embed-container.square { #chat-container .embed-container.square {
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
aspect-ratio: 1 / 1 !important; aspect-ratio: 1 / 1 !important;
} }
#chat .embed-container iframe, #chat-container .embed-container iframe,
#chat .embed-container video { #chat-container .embed-container video {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -305,7 +290,7 @@
} }
@media screen and (max-width: 1200px) { @media screen and (max-width: 1200px) {
#chat #chat-container { #chat-container #chat-container {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -313,28 +298,28 @@
bottom: 0; bottom: 0;
} }
#chat #chat-container #chat-entry-container, #chat-container #chat-container #chat-entry-container,
#chat #chat-container #chat-entry-container form { #chat-container #chat-container #chat-entry-container form {
padding: 0.25rem; padding: 0.25rem;
} }
#chat #chat-container #chat-entry-container form button, #chat-container #chat-container #chat-entry-container form button,
#chat #chat-container #chat-entry-container form label { #chat-container #chat-container #chat-entry-container form label {
margin: 0 0.5rem; margin: 0 0.5rem;
} }
#chat .message-container .message-content-container, #chat-container .message-container .message-content-container,
#chat .message-container .message-media-container, #chat-container .message-container .message-media-container,
#chat .message-container .reactions-container { #chat-container .message-container .reactions-container {
padding-left: 4rem; padding-left: 4rem;
} }
#chat .message-container .info-container .datetime-container .long { #chat-container .message-container .info-container .datetime-container .long {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
#chat .message-container .info-container .datetime-container .short { #chat-container .message-container .info-container .datetime-container .short {
display: inline-block; display: inline-block;
visibility: visible; visibility: visible;
} }

View file

@ -1,234 +1,232 @@
<div id="chat" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="chat-tab-input"
id="chat-tab-input" class="tabswitch"
class="tab-switch" data-view="chat"
data-view="chat" />
/> <label for="chat-tab-input" class="tablabel"
<label for="chat-tab-input" class="tab-label" ><div class="icon chat"></div>
><div class="icon chat"></div> <div class="label">Chat</div>
<div class="label">Chat</div> </label>
</label> <div class="panel">
<div class="tab-content"> <style>
<style> <!-- #include "./chat.css" -->
<!-- #include "./chat.css" --> </style>
</style> <script src="/js/external/mimetypes.js" type="text/javascript"></script>
<script src="/js/external/mimetypes.js" type="text/javascript"></script> <script src="/js/external/punycode.js" type="text/javascript"></script>
<script src="/js/external/punycode.js" type="text/javascript"></script>
<div id="chat-container"> <div id="chat-container">
<div <div
id="chat-content" id="chat-content"
data-feed data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.chat' ) !== -1 && document.body.dataset.channel" data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.chat' ) !== -1 && document.body.dataset.channel"
data-source="/api/channels/${ document.body.dataset.channel }/events?type=chat,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }" data-source="/api/channels/${ document.body.dataset.channel }/events?type=chat,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true" data-longpolling="true"
data-reverse="true" data-reverse="true"
data-insert="append" data-insert="append"
data-autoscroll="true" data-autoscroll="true"
> >
<script> <script>
{ {
const feed = document.currentScript.closest("[data-feed]"); const feed = document.currentScript.closest("[data-feed]");
APP.on("channel_changed", () => { feed.__reset && feed.__reset(); }); APP.on("channel_changed", () => { feed.__reset && feed.__reset(); });
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); }); APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
const time_tick_tock_timeout = 60_000; const time_tick_tock_timeout = 60_000;
let last_event_datetime_value = 0; let last_event_datetime_value = 0;
let time_tick_tock_class = "time-tock"; let time_tick_tock_class = "time-tock";
let last_creator_id = ""; let last_creator_id = "";
let user_tick_tock_class = "user-tock"; let user_tick_tock_class = "user-tock";
feed.__context = async (item) => { feed.__context = async (item) => {
const event_datetime = datetime_to_local(item.timestamps.created); const event_datetime = datetime_to_local(item.timestamps.created);
if ( if (
event_datetime.value - last_event_datetime_value > event_datetime.value - last_event_datetime_value >
time_tick_tock_timeout time_tick_tock_timeout
) { ) {
time_tick_tock_class = time_tick_tock_class =
time_tick_tock_class === "time-tick" time_tick_tock_class === "time-tick"
? "time-tock" ? "time-tock"
: "time-tick"; : "time-tick";
} }
last_event_datetime_value = event_datetime.value; last_event_datetime_value = event_datetime.value;
if (last_creator_id !== item.creator_id) { if (last_creator_id !== item.creator_id) {
user_tick_tock_class = user_tick_tock_class =
user_tick_tock_class === "user-tick" user_tick_tock_class === "user-tick"
? "user-tock" ? "user-tock"
: "user-tick"; : "user-tick";
last_creator_id = item.creator_id; last_creator_id = item.creator_id;
} }
return { return {
event: item, event: item,
creator: await APP.USERS.get(item.creator_id), creator: await APP.USERS.get(item.creator_id),
event_datetime, event_datetime,
time_tick_tock_class, time_tick_tock_class,
user_tick_tock_class, user_tick_tock_class,
};
}; };
};
feed.__target_element = (item) => { feed.__target_element = (item) => {
let target = feed; let target = feed;
switch (item.type) { switch (item.type) {
case "reaction": case "reaction":
target = document.querySelector( target = document.querySelector(
`.message-container[data-event_id='${item.parent_id}'] > .reactions-container`, `.message-container[data-event_id='${item.parent_id}'] > .reactions-container`,
); );
break; break;
case "chat": case "chat":
default: default:
target = target =
document.querySelector( document.querySelector(
`.message-container[data-event_id='${item.parent_id}'] > .replies-container`, `.message-container[data-event_id='${item.parent_id}'] > .replies-container`,
) ?? feed; ) ?? feed;
break; break;
} }
return target; return target;
}; };
} }
</script> </script>
<template data-for_type="chat"> <template data-for_type="chat">
<div <div
id="${context.event.id}" id="${context.event.id}"
class="message-container ${context.user_tick_tock_class} ${context.time_tick_tock_class}" class="message-container ${context.user_tick_tock_class} ${context.time_tick_tock_class}"
data-event_id="${context.event.id}" data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}" data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}" data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<button class="icon more" commandfor="eventactionspopover"></button>
<div class="info-container">
<div class="avatar-container inline">
<img
src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
alt="user avatar"
loading="lazy"
/>
</div>
<div class="username-container">
<span class="username"
>${context.creator.username ?? 'unknown'}</span
>
</div>
<div class="datetime-container">
<span class="long">${context.event_datetime.long}</span>
<span class="short">${context.event_datetime.short}</span>
</div>
</div>
<div class="message-content-container">
${htmlify(md_to_html(context.event.data.content))}
</div>
<div class="message-media-container">
${htmlify(context.event.data.media?.join("\n") ?? "")}
</div>
<div class="reactions-container"></div>
<div class="replies-container"></div>
</div>
</template>
<template data-for_type="reaction">
<div
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div>
</template>
</div>
<div id="chat-entry-container">
<form
id="chat-entry"
data-smart="true"
action="/api/events"
data-requires-permission="events.write.chat"
method="POST"
class="post-creation-form collapsible"
style="
margin-top: 1rem
width: 100%;
transition: all 0.5s;
"
on_reply="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.remove('sending'); }"
on_parsed="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.add('sending'); }"
> >
<input type="hidden" name="type" value="chat" /> <button class="icon more" commandfor="eventactionspopover"></button>
<div class="info-container">
<div class="avatar-container inline">
<img
src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
alt="user avatar"
loading="lazy"
/>
</div>
<div class="username-container">
<span class="username"
>${context.creator.username ?? 'unknown'}</span
>
</div>
<div class="datetime-container">
<span class="long">${context.event_datetime.long}</span>
<span class="short">${context.event_datetime.short}</span>
</div>
</div>
<div class="message-content-container">
${htmlify(md_to_html(context.event.data.content))}
</div>
<div class="message-media-container">
${htmlify(context.event.data.media?.join("\n") ?? "")}
</div>
<div class="reactions-container"></div>
<div class="replies-container"></div>
</div>
</template>
<template data-for_type="reaction">
<div
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div>
</template>
</div>
<div id="chat-entry-container">
<form
id="chat-entry"
data-smart="true"
action="/api/events"
data-requires-permission="events.write.chat"
method="POST"
class="post-creation-form collapsible"
style="
margin-top: 1rem
width: 100%;
transition: all 0.5s;
"
on_reply="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.remove('sending'); }"
on_parsed="async (event) => { await document.getElementById( 'chat-content' ).__render(event); document.getElementById(event.id)?.classList.add('sending'); }"
>
<input type="hidden" name="type" value="chat" />
<input
type="hidden"
name="id"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="meta.temp_id"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="creator_id"
generator="() => { return APP.user?.id; }"
/>
<input
type="hidden"
name="channel"
generator="() => { return document.body.dataset.channel; }"
/>
<input
type="hidden"
name="timestamps.created"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="timestamps.updated"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input type="hidden" name="parent_id" reset-on-submit />
<label>
<input <input
type="hidden" aria-label="Upload and share file"
name="id" type="file"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()" multiple
reset-on-submit data-smartforms-save-to-home="true"
/> name="data.media"
<input
type="hidden"
name="meta.temp_id"
generator="(_input, form) => 'TEMP-' + form.__submitted_at.toISOString()"
reset-on-submit reset-on-submit
/> />
<div class="icon attachment"></div>
</label>
<input <textarea
type="hidden" id="chat-input"
name="creator_id" class="chat-input"
generator="() => { return APP.user?.id; }" rows="1"
/> name="data.content"
reset-on-submit
focus-on-submit
enter-key-submits
></textarea>
<input <button id="chat-send" class="primary" aria-label="Send a message">
type="hidden" <i class="icon send"></i>
name="channel" </button>
generator="() => { return document.body.dataset.channel; }" </form>
/>
<input
type="hidden"
name="timestamps.created"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input
type="hidden"
name="timestamps.updated"
generator="(_input, form) => form.__submitted_at.toISOString()"
reset-on-submit
/>
<input type="hidden" name="parent_id" reset-on-submit />
<label>
<input
aria-label="Upload and share file"
type="file"
multiple
data-smartforms-save-to-home="true"
name="data.media"
reset-on-submit
/>
<div class="icon attachment"></div>
</label>
<textarea
id="chat-input"
class="chat-input"
rows="1"
name="data.content"
reset-on-submit
focus-on-submit
enter-key-submits
></textarea>
<button id="chat-send" class="primary" aria-label="Send a message">
<i class="icon send"></i>
</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -95,121 +95,119 @@
} }
</style> </style>
<div id="essays" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="essay-tab-input"
id="essay-tab-input" class="tabswitch"
class="tab-switch" data-view="essays"
data-view="essays" />
/> <label for="essay-tab-input" class="tablabel"
<label for="essay-tab-input" class="tab-label" ><div class="icon essay"></div>
><div class="icon essay"></div> <div class="label">Essays</div>
<div class="label">Essays</div> </label>
</label> <div class="panel">
<div class="tab-content"> <div id="essays-container" class="container">
<div id="essays-container" class="container"> <!-- #include "./README.md" -->
<!-- #include "./README.md" --> <!-- #include "./new_essay.html" -->
<!-- #include "./new_essay.html" -->
<div <div
id="essays-list" id="essays-list"
data-feed data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.essay' ) !== -1" data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.essay' ) !== -1"
data-source="/api/events?type=essay,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }" data-source="/api/events?type=essay,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true" data-longpolling="true"
data-reverse="true" data-reverse="true"
data-insert="prepend" data-insert="prepend"
data-autoscroll="true" data-autoscroll="true"
> >
<script> <script>
{ {
const feed = document.currentScript.closest("[data-feed]"); const feed = document.currentScript.closest("[data-feed]");
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); }); APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
feed.__target_element = (item) => { feed.__target_element = (item) => {
let target = feed; let target = feed;
switch (item.type) { switch (item.type) {
case "reaction": case "reaction":
target = document.querySelector( target = document.querySelector(
`.essay-container[data-event_id='${item.parent_id}'] > .reactions-container`, `.essay-container[data-event_id='${item.parent_id}'] > .reactions-container`,
); );
break; break;
case "essay": case "essay":
default: default:
target = feed; target = feed;
break; break;
} }
return target; return target;
};
feed.__context = async (item) => {
const essay_datetime = datetime_to_local(item.timestamps.created);
return {
event: item,
essay: item,
creator: await APP.USERS.get(item.creator_id),
essay_datetime,
}; };
};
}
</script>
<template data-for_type="essay">
<div
id="${context.essay.id}"
class="essay-container"
data-event_id="${context.essay.id}"
data-creator_id="${context.creator.id}"
data-essay_id="${context.essay.id}"
data-temp_id="${context.essay.meta?.temp_id ?? ''}"
>
<div class="media-preview-container">
${context.essay.data?.media?.length ?
context.essay.data.media.map(function(url) { return `<img
src="${url}"
loading="lazy"
/>` }).join('\n') : ''}
</div>
feed.__context = async (item) => { <div class="info-container">
const essay_datetime = datetime_to_local(item.timestamps.created); <div class="avatar-container inline medium">
<img
return { src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
event: item, alt="user avatar"
essay: item,
creator: await APP.USERS.get(item.creator_id),
essay_datetime,
};
};
}
</script>
<template data-for_type="essay">
<div
id="${context.essay.id}"
class="essay-container"
data-event_id="${context.essay.id}"
data-creator_id="${context.creator.id}"
data-essay_id="${context.essay.id}"
data-temp_id="${context.essay.meta?.temp_id ?? ''}"
>
<div class="media-preview-container">
${context.essay.data?.media?.length ?
context.essay.data.media.map(function(url) { return `<img
src="${url}"
loading="lazy" loading="lazy"
/>` }).join('\n') : ''} />
</div> </div>
<div class="username-container">
<div class="info-container"> <span class="username">${context.creator.username}</span>
<div class="avatar-container inline medium">
<img
src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
alt="user avatar"
loading="lazy"
/>
</div>
<div class="username-container">
<span class="username">${context.creator.username}</span>
</div>
<div class="datetime-container">
<span class="long">${context.essay_datetime.long}</span>
<span class="short">${context.essay_datetime.short}</span>
</div>
</div> </div>
<div class="title-container">${context.essay.data.title}</div> <div class="datetime-container">
<div class="content-container"> <span class="long">${context.essay_datetime.long}</span>
${htmlify(md_to_html(context.essay.data.essay))} <span class="short">${context.essay_datetime.short}</span>
</div> </div>
<div class="reactions-container"></div>
<button class="icon more" commandfor="eventactionspopover"></button>
</div> </div>
</template> <div class="title-container">${context.essay.data.title}</div>
<template data-for_type="reaction"> <div class="content-container">
<div ${htmlify(md_to_html(context.essay.data.essay))}
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div> </div>
</template> <div class="reactions-container"></div>
</div> <button class="icon more" commandfor="eventactionspopover"></button>
</div>
</template>
<template data-for_type="reaction">
<div
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div>
</template>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,16 +1,14 @@
<div id="exchange" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="exchange-tab-input"
id="exchange-tab-input" class="tabswitch"
class="tab-switch" data-view="exchange"
data-view="exchange" />
/> <label for="exchange-tab-input" class="tablabel"
<label for="exchange-tab-input" class="tab-label" ><div class="icon exchange"></div>
><div class="icon exchange"></div> <div class="label">Exchange</div></label
<div class="label">Exchange</div></label >
> <div class="panel">
<div class="tab-content"> <!-- #include "./README.md" -->
<!-- #include "./README.md" -->
</div>
</div> </div>

View file

@ -127,130 +127,128 @@
} }
</style> </style>
<div id="forum" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="forum-tab-input"
id="forum-tab-input" class="tabswitch"
class="tab-switch" data-view="forum"
data-view="forum" />
/> <label for="forum-tab-input" class="tablabel"
<label for="forum-tab-input" class="tab-label" ><div class="icon forum"></div>
><div class="icon forum"></div> <div class="label">Forum</div></label
<div class="label">Forum</div></label >
<div class="panel forum-container">
<!-- #include "./README.md" -->
<div
id="posts-list"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.post' ) !== -1"
data-source="/api/events?type=post,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="prepend"
data-autoscroll="true"
> >
<div class="tab-content forum-container"> <script>
<!-- #include "./README.md" --> {
const feed = document.currentScript.closest("[data-feed]");
<div APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); });
id="posts-list"
data-feed
data-precheck="!!document.body.dataset.user && document.body.dataset.user.indexOf( 'events.read.post' ) !== -1"
data-source="/api/events?type=post,reaction&limit=100&sort=newest&wait=true&after_id=${ feed.__newest_id ?? 'able-able-able-able-able-able-able-able-able-able' }"
data-longpolling="true"
data-reverse="true"
data-insert="prepend"
data-autoscroll="true"
>
<script>
{
const feed = document.currentScript.closest("[data-feed]");
APP.on("user_logged_in", () => { feed.__reset && feed.__reset(); }); feed.__target_element = (item) => {
let target = feed;
switch (item.type) {
case "reaction":
target = document.querySelector(
`.post-container[data-event_id='${item.parent_id}'] > .reactions-container`,
);
break;
case "chat":
default:
target =
document.querySelector(
`.post-container[data-event_id='${item.parent_id}'] > .replies-container`,
) ?? feed;
break;
}
feed.__target_element = (item) => { return target;
let target = feed; };
switch (item.type) {
case "reaction":
target = document.querySelector(
`.post-container[data-event_id='${item.parent_id}'] > .reactions-container`,
);
break;
case "chat":
default:
target =
document.querySelector(
`.post-container[data-event_id='${item.parent_id}'] > .replies-container`,
) ?? feed;
break;
}
return target; feed.__context = async (item) => {
const post_datetime = datetime_to_local(item.timestamps.created);
return {
event: item,
post: item,
creator: await APP.USERS.get(item.creator_id),
post_datetime,
}; };
};
}
</script>
<template data-for_type="post">
<div
id="${context.post.id}"
class="post-container"
data-event_id="${context.post.id}"
data-creator_id="${context.creator.id}"
data-post_id="${context.post.id}"
>
<div class="expand-toggle-container">
<label>
<input type="checkbox" name="expanded" /><i class="icon plus"></i
><i class="icon minus"></i>
</label>
</div>
feed.__context = async (item) => { <div class="media-preview-container">
const post_datetime = datetime_to_local(item.timestamps.created); <img
src="/images/placeholders/${String((context.post_datetime.ms % 9) + 1).padStart(2, '0')}.svg"
loading="lazy"
/>
</div>
return { <div class="info-container">
event: item, <div class="avatar-container inline">
post: item,
creator: await APP.USERS.get(item.creator_id),
post_datetime,
};
};
}
</script>
<template data-for_type="post">
<div
id="${context.post.id}"
class="post-container"
data-event_id="${context.post.id}"
data-creator_id="${context.creator.id}"
data-post_id="${context.post.id}"
>
<div class="expand-toggle-container">
<label>
<input type="checkbox" name="expanded" /><i class="icon plus"></i
><i class="icon minus"></i>
</label>
</div>
<div class="media-preview-container">
<img <img
src="/images/placeholders/${String((context.post_datetime.ms % 9) + 1).padStart(2, '0')}.svg" src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
alt="user avatar"
loading="lazy" loading="lazy"
/> />
</div> </div>
<div class="username-container">
<div class="info-container"> <span class="username">${context.creator.username}</span>
<div class="avatar-container inline">
<img
src="${context.creator.meta?.avatar ?? '/images/default_avatar.gif'}"
alt="user avatar"
loading="lazy"
/>
</div>
<div class="username-container">
<span class="username">${context.creator.username}</span>
</div>
<div class="datetime-container">
<span class="long">${context.post_datetime.long}</span>
<span class="short">${context.post_datetime.short}</span>
</div>
</div> </div>
<div class="subject-container">${context.post.data.subject}</div> <div class="datetime-container">
<div class="content-container"> <span class="long">${context.post_datetime.long}</span>
${htmlify(md_to_html(context.post.data.content))} <span class="short">${context.post_datetime.short}</span>
</div> </div>
<div class="reactions-container"></div>
<button class="icon more" commandfor="eventactionspopover"></button>
<!-- #include "./new_post.html" -->
<div class="replies-container"></div>
</div> </div>
</template> <div class="subject-container">${context.post.data.subject}</div>
<template data-for_type="reaction"> <div class="content-container">
<div ${htmlify(md_to_html(context.post.data.content))}
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div> </div>
</template> <div class="reactions-container"></div>
</div> <button class="icon more" commandfor="eventactionspopover"></button>
<!-- #include "./new_post.html" -->
<!-- #include "./new_post.html" --> <div class="replies-container"></div>
</div>
</template>
<template data-for_type="reaction">
<div
id="${context.event.id}"
class="reaction-container"
data-event_id="${context.event.id}"
data-creator_id="${context.creator.id}"
data-temp_id="${context.event.meta?.temp_id ?? ''}"
>
<span class="reaction">${ context.event.data.reaction }</span>
</div>
</template>
</div> </div>
<!-- #include "./new_post.html" -->
</div> </div>

View file

@ -1,17 +1,15 @@
<div id="home" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="home-tab-input"
id="home-tab-input" checked="checked"
checked="checked" class="tabswitch"
class="tab-switch" data-view="home"
data-view="home" />
/> <label for="home-tab-input" class="tablabel">
<label for="home-tab-input" class="tab-label"> <div class="icon home"></div>
<div class="icon home"></div> <div class="label">Home</div>
<div class="label">Home</div> </label>
</label> <div class="panel">
<div class="tab-content"> <!-- #include "./README.md" -->
<!-- #include "./README.md" -->
</div>
</div> </div>

View file

@ -1,73 +1,71 @@
<script></script> <script></script>
<style></style> <style></style>
<div id="live-tab" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="live-tab-input"
id="live-tab-input" class="tabswitch"
class="tab-switch" data-view="profile"
data-view="profile" />
/> <label for="live-tab-input" class="tablabel mockup"
<label for="live-tab-input" class="tab-label mockup" ><div class="icon live"></div>
><div class="icon live"></div> <div class="label">Live</div></label
<div class="label">Live</div></label >
<div class="panel">
<div
class="live-container"
style="
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-content: center;
justify-content: center;
flex-flow: wrap;
"
> >
<div class="tab-content">
<div <div
class="live-container" class="mockup"
style=" style="
position: relative; width: 95%;
width: 100%; max-width: 1024px;
height: 100%; border: 1px solid rgba(128, 128, 128, 0.5);
height: 66vh;
overflow: hidden; overflow: hidden;
"
></div>
<div
class="live-actions-container"
style="
width: 80%;
margin: 0 auto;
display: flex; display: flex;
align-content: center; align-items: center;
justify-content: center; justify-content: space-evenly;
flex-flow: wrap; height: 4rem;
" "
> >
<div <input
class="mockup" id="toggle-live-audio"
style=" type="checkbox"
width: 95%; style="display: none; visibility: hidden"
max-width: 1024px; />
border: 1px solid rgba(128, 128, 128, 0.5); <label for="toggle-live-audio" class="mockup">
height: 66vh; <i class="icon phone"></i>
overflow: hidden; <span>Audio</span>
" </label>
></div>
<div <input
class="live-actions-container" id="toggle-live-video"
style=" type="checkbox"
width: 80%; style="display: none; visibility: hidden"
margin: 0 auto; />
display: flex; <label for="toggle-live-video" class="mockup">
align-items: center; <i class="icon camera"></i>
justify-content: space-evenly; <span>Video</span>
height: 4rem; </label>
"
>
<input
id="toggle-live-audio"
type="checkbox"
style="display: none; visibility: hidden"
/>
<label for="toggle-live-audio" class="mockup">
<i class="icon phone"></i>
<span>Audio</span>
</label>
<input
id="toggle-live-video"
type="checkbox"
style="display: none; visibility: hidden"
/>
<label for="toggle-live-video" class="mockup">
<i class="icon camera"></i>
<span>Video</span>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -16,7 +16,7 @@
</script> </script>
<style> <style>
#map-tab .tab-content { #map-tab .panel {
overflow: hidden; overflow: hidden;
} }
@ -37,19 +37,17 @@
} }
} }
</style> </style>
<div id="map-tab" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="map-tab-tab-input"
id="map-tab-tab-input" class="tabswitch"
class="tab-switch" data-view="map"
data-view="map" />
/> <label for="map-tab-tab-input" class="tablabel"
<label for="map-tab-tab-input" class="tab-label" ><div class="icon map-pin"></div>
><div class="icon map-pin"></div> <div class="label">Map</div>
<div class="label">Map</div> </label>
</label> <div class="panel">
<div class="tab-content"> <div id="map"></div>
<div id="map"></div>
</div>
</div> </div>

View file

@ -1,14 +1,12 @@
<div id="resources" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="resources-tab-input"
id="resources-tab-input" class="tabswitch"
class="tab-switch" data-view="resources"
data-view="resources" />
/> <label for="resources-tab-input" class="tablabel mockup"
<label for="resources-tab-input" class="tab-label mockup" ><div class="icon resources"></div>
><div class="icon resources"></div> <div class="label">Resources</div></label
<div class="label">Resources</div></label >
> <div class="panel"><!-- #include "./README.md" --></div>
<div class="tab-content"><!-- #include "./README.md" --></div>
</div>

View file

@ -1,6 +1,6 @@
<script> <script>
APP.on( "view_changed", ({ view }) => { APP.on( "view_changed", ({ view }) => {
const target_tab = document.querySelector(`.tab-switch[data-view="${view}"]`); const target_tab = document.querySelector(`.tabswitch[data-view="${view}"]`);
if (target_tab) { if (target_tab) {
target_tab.click(); target_tab.click();
@ -8,7 +8,7 @@
}); });
APP.on( 'load', () => { APP.on( 'load', () => {
const tab_switchers = document.querySelectorAll(".tab-switch"); const tab_switchers = document.querySelectorAll(".tabswitch");
for (const tab_switch of tab_switchers) { for (const tab_switch of tab_switchers) {
tab_switch.addEventListener("input", (event) => { tab_switch.addEventListener("input", (event) => {
const tab_selector = event.target; const tab_selector = event.target;
@ -22,98 +22,46 @@
}); });
</script> </script>
<style> <style>
.tabs-container { :root {
position: relative; --tab-label-height: 4em;
height: 100%; }
}
.tabs { .tabs {
position: relative; display: flex;
height: 100%; flex-wrap: wrap;
width: 100%; justify-content: space-evenly;
display: flex; }
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
min-height: inherit;
background: inherit;
}
.tabs::before, .tabs .tabswitch {
.tabs::after { position: absolute;
content: ""; opacity: 0;
display: table; }
}
.tabs::after { .tabs .tablabel {
clear: both; cursor: pointer;
} border: none;
text-align: center;
padding: 1em;
height: var(--tab-label-height);
line-height: 2em;
}
.tab { .tabs .tabswitch:checked + .tablabel {
background: inherit; border-bottom: 1px solid var(--accent);
} }
.tab-switch { .tabs .panel {
display: none; display: none;
} position: relative;
padding: 1em;
width: 100%;
height: calc( 100vh - var(--tab-label-height) );
order: 99;
}
.tab-label { .tabs .tabswitch:checked + .tablabel + .panel {
position: relative; display: block;
width: 6rem; }
height: 5rem;
cursor: pointer;
transition: all 0.25s;
display: flex;
align-items: center;
justify-content: end;
flex-direction: column;
}
.tab-label .label {
margin: 0.75rem 0;
}
.tab-content {
position: absolute;
z-index: 1;
top: 5rem;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transition: all 0.35s;
border-top: 1px solid var(--border-subtle);
margin-top: 1px;
overflow-y: scroll;
visibility: hidden;
display: none;
background: inherit;
}
.tab-switch,
.tab-switch + .tab-label {
transition: none;
}
.tab-switch:checked + .tab-label {
margin-top: 1px;
border-bottom: 1px solid var(--accent);
z-index: 1;
}
.tab-switch:checked + label + .tab-content {
z-index: 2;
opacity: 1;
visibility: visible;
display: block;
}
@media screen and (max-width: 800px) {
.tab-label {
width: 4rem;
}
}
</style> </style>
<div class="tabs"> <div class="tabs">

View file

@ -1,14 +1,14 @@
<div id="work" class="tab"> <input
<input type="radio"
type="radio" name="top-level-tabs"
name="top-level-tabs" id="work-tab-input"
id="work-tab-input" class="tabswitch"
class="tab-switch" data-view="work"
data-view="work" />
/> <label for="work-tab-input" class="tablabel"
<label for="work-tab-input" class="tab-label" ><div class="icon work"></div>
><div class="icon work"></div> <div class="label">Work</div>
<div class="label">Work</div> </label>
</label> <div class="panel">
<div class="tab-content"><!-- #include "./README.md" --></div> <!-- #include "./README.md" -->
</div> </div>