damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs | README

commit dfdfcf58ca6acb86a795130d7e79be4ea0a5a93a
parent 572122b581e133007cda747426ec12d64230e10f
Author: Thomas Mathews <thomas.c.mathews@gmail.com>
Date:   Fri, 11 Nov 2022 12:23:03 -0800

Updated styling to look like twitter.

Diffstat:
Mweb/damus.css | 324+++----------------------------------------------------------------------------
Mweb/damus.js | 200+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mweb/index.html | 104++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Aweb/styles.css | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 500 insertions(+), 429 deletions(-)

diff --git a/web/damus.css b/web/damus.css @@ -1,29 +1,8 @@ -.header { - display: flex; - margin: 30px 0 30px 0; - flex-direction: column; - align-items: center; -} - -.logo { - margin-bottom: 0; - letter-spacing: -0.05em; -} - - -.logo img { - padding-right: 18px; - width: 60px; -} - -#content-warning-input { - width: 100%; -} - -#content-warning-input-container { - width: 100%; - margin-bottom: 10px; -} +/* + * This code is not maintained it needs to be cleaned up and deleted. The code + * resides here as a working base to migrate to styles.css. Original author is + * JB55. + */ .deleted-comment { border: 2px dashed white; @@ -31,313 +10,32 @@ padding: 10px; } -.clickable { - cursor: pointer; -} - .inline-img { width: 100%; border-radius: 8px; } -.boost { -} - -.boost-text { - padding: 0 10px 10px 10px; - color: #b6ffa8; - margin: auto; - font-size: 0.8em; -} - -.chatroom-name { - color: orange; - font-weight: bold; -} - +/* .line-top { - width: 2px; + width: 3px; height: 5px; - background-color: #eac3ff; + background-color: var(--clrBorder); margin-left: auto; margin-right: auto; -} +}*/ .line-bot { - width: 2px; + width: 3px; height: 100%; margin-top: -7px; - background-color: #eac3ff; + background-color: var(--clrBorder); margin-left: auto; margin-right: auto; } -body { - min-height: 100vh; - font-family: system-ui, sans; -} - -#reply-top { - display: flex; - align-items: center; -} - -#reply-modal { - position: fixed; /* Stay in place */ - z-index: 1; /* Sit on top */ - left: 0; - top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - background: rgba(0,0,0,0.4); -} - -summary { - cursor: pointer; -} - -details { - background-color: rgba(255,255,255,0.2); - padding: 10px; - margin-right: 30px; - border-radius: 20px; -} - -input { - background-color: rgba(255,255,255,0.2); - border: 0; - height: 30px; - color: white; - border-radius: 5px; -} - -textarea { - background-color: rgba(255,255,255,0.2); - border: 0; - width: 100%; - height: 40px; - color: white; - border-radius: 5px; -} - -#newpost { - margin: 0 auto 20px auto; - width: 80% -} - -::placeholder { - color: rgba(255,255,255,0.7); -} - -.post-group { - display: flex; -} - -#post-button { - width: 50px; -} - -.post-group > textarea { - margin-right: 10px; -} - -.small-txt { - font-size: 0.6em; - color: rgba(255,255,255,0.8); -} - -.reaction-group { - display: inline-flex; - background-color: rgba(255,255,255,0.15); - padding: 4px; - border-radius: 5px; -} - -.reaction-group img { - margin-left: -8px; -} - -.reaction-emoji { - margin-right: 14px; -} - -button { - border: 0; - padding: 5px; - color: white; - background-color: #9202e2; - border-radius: 5px; -} - -.close { - margin: 0 8px 0 8px; - font-size: 1.3em; - font-weight: bold; - text-decoration: none; - color: white; -} - -.small-text { - font-size: 12px; -} - -#reply-modal-content { - /*display: none; */ - padding: 10px; - margin: 10% auto; - width: 60%; - overflow: auto; /* Enable scroll if needed */ - background: linear-gradient(0deg, #A74BDB 0%, #C45BFE 100%); - border-radius: 15px; -} - -html { - line-height: 1.5; - font-size: 20px; - font-family: "Georgia", sans-serif; - - color: white; - background: linear-gradient(45deg, rgba(28,85,255,1) 0%, rgba(127,53,171,1) 59%, #C45BFE 100%); -} -.container { - margin: 0 auto; - max-width: 48em; - hyphens: auto; - word-wrap: break-word; - text-rendering: optimizeLegibility; - font-kerning: normal; -} -@media (max-width: 600px) { - .container { - font-size: 0.9em; - } -} -@media print { - .container { - background-color: transparent; - color: black; - font-size: 12pt; - } - p, h2, h3 { - orphans: 3; - widows: 3; - } - h2, h3, h4 { - page-break-after: avoid; - } -} - -.pfp { - border-radius: 50%; -} - -.pfp-small { - width: 28px; - height: 28px; -} - -.pfp-normal { - margin: 0 15px 0 15px; - width: 60px; - height: 60px; - font-size: 2.4em; -} - - -.thread-collapsed { - margin: 0 auto 5px auto; - width: 56.3%; -} - -.comment { - display: flex; - font-family: system-ui, sans; - margin-bottom: 20px; - flex-wrap: wrap; - /*align-items: center;*/ -} - -.comment .comment-body { - background-color: rgba(255.0,255.0,255.0,0.1); - padding: 10px; - border-radius: 8px; - margin: 0; - width: 55%; -} - -.comment-body p { - margin: 0 0 10px 0; -} - -.comment .info { - text-align: right; - width: 18%; - line-height: 0.8em; -} - .quote { - border-left: 2px solid white; margin-left: 10px; padding: 10px; - background-color: rgba(255.0,255.0,255.0,0.1); display: block; } -.comment .timestamp { - font-size: 11px; - color: rgba(255.0,255.0,255.0,0.7); -} - -.invisible { - visibility: hidden; -} - -@media (max-width: 800px){ - /* Reverse the order of elements in the user comments, - so that the avatar and info appear after the text. */ - - .pfp { - margin: 0 0 0 0; - } - - .pfpbox { - order: 1; - margin-right: 10px; - } - - .comment .info { - order: 2; - text-align: left; - width: 70%; - } - - .comment .comment-body { - width: 100%; - order: 3; - margin: 5px 0 0 0; - } - - .line-top { - height: 20px; - } - - .line-bottom { - /*display: none;*/ - } - - .comment { - margin: 0 0 0 0; - align-items: center; - border-radius: 8px; - /*background-color: rgba(255.0,255.0,255.0,0.1);*/ - } - - #newpost { - width: 100%; - } - - .comment p { - order: 3; - width: 100%; - } -} diff --git a/web/damus.js b/web/damus.js @@ -8,21 +8,18 @@ function uuidv4() { } function insert_event_sorted(evs, new_ev) { - for (let i = 0; i < evs.length; i++) { - const ev = evs[i] - - if (new_ev.id === ev.id) { - return false - } - - if (new_ev.created_at > ev.created_at) { - evs.splice(i, 0, new_ev) - return true - } + for (let i = 0; i < evs.length; i++) { + const ev = evs[i] + if (new_ev.id === ev.id) { + return false } - - evs.push(new_ev) - return true + if (new_ev.created_at > ev.created_at) { + evs.splice(i, 0, new_ev) + return true + } + } + evs.push(new_ev) + return true } function init_contacts() { @@ -260,8 +257,6 @@ function process_event(model, ev) process_json_content(ev) else if (ev.kind === 5) process_deletion_event(model, ev) - else if (ev.kind === 0) - process_profile_event(model, ev) const last_notified = get_local_state('last_notified_date') if (notified && (last_notified == null || ((ev.created_at*1000) > last_notified))) { @@ -294,10 +289,6 @@ function should_add_to_home(ev) let rerender_home_timer function handle_home_event(ids, model, relay, sub_id, ev) { - // ignore duplicates - if (model.all_events[ev.id]) - return - model.all_events[ev.id] = ev process_event(model, ev) @@ -320,23 +311,22 @@ function handle_home_event(ids, model, relay, sub_id, ev) { model.done_init = true model.pool.unsubscribe(ids.account, [relay]) break + case 0: + handle_profile_event(model, ev) + break } case ids.profiles: - break + try { + model.profile_events[ev.pubkey] = ev + model.profiles[ev.pubkey] = JSON.parse(ev.content) + } catch { + console.log("failed to parse", ev.content) + } } } -function process_profile_event(model, ev) { - const prev_ev = model.profile_events[ev.pubkey] - if (prev_ev && prev_ev.created_at > ev.created_at) - return - - model.profile_events[ev.pubkey] = ev - try { - model.profiles[ev.pubkey] = JSON.parse(ev.content) - } catch(e) { - log_debug("failed to parse profile contents", ev) - } +function handle_profile_event(model, ev) { + console.log("PROFILE", ev) } function send_initial_filters(account_id, pubkey, relay) { @@ -553,10 +543,18 @@ function setup_home_event_handlers(events_el) function redraw_home_view(model) { model.view_el.innerHTML = render_home_view(model) model.events_el = document.querySelector("#events") - if (model.events.length > 0) + if (model.events.length > 0) { redraw_events(model) - else - model.events_el.innerText = "Loading..." + } else { + model.events_el.innerHTML= ` + <div class="loading-events"> + <span class="loader" title="Loading..."> + <i class="fa-solid fa-fw fa-spin fa-hurricane" + style="--fa-animation-duration: 0.5s;"></i> + </span> + </div> + ` + } } async function send_post() { @@ -567,7 +565,7 @@ async function send_post() { const content = input_el.value const created_at = Math.floor(new Date().getTime() / 1000) const kind = 1 - const tags = cw? [["content-warning", cw]] : [] + const tags = cw ? [["content-warning", cw]] : [] const pubkey = await get_pubkey() const {pool} = DSTATE @@ -600,17 +598,19 @@ async function sign_event(ev) { function render_home_view(model) { return ` <div id="newpost"> - <div id="content-warning-input-container"> - <input id="content-warning-input" type="text" placeholder="Content Warning (nsfw, politics, etc)"></input> + <div><!-- empty to accomodate profile pic --></div> + <div> + <textarea placeholder="What's up?" class="post-input" id="post-input"></textarea> + <div class="post-tools"> + <input id="content-warning-input" class="cw hide" type="text" placeholder="Reason"/> + <button title="Mark this message as sensitive." onclick="toggle_cw(this)" class="cw icon"> + <i class="fa-solid fa-triangle-exclamation"></i> + </button> + <button onclick="send_post(this)" class="action" id="post-button">Send</button> + </div> </div> - - <div class="post-group"> - <textarea placeholder="What's on your mind?" id="post-input"></textarea> - <button onclick="send_post(this)" id="post-button">Post</button> - </div> - </div> - <div id="events"> </div> + <div id="events"></div> ` } @@ -690,7 +690,11 @@ function render_thread_collapsed(model, reply_ev, opts) { if (opts.is_composing) return "" - return `<div onclick="expand_thread('${reply_ev.id}')" class="thread-collapsed clickable">...</div>` + return `<div onclick="expand_thread('${reply_ev.id}')" class="thread-collapsed"> + <div class="thread-summary"> + More messages in thread available. Click to expand. + </div> + </div>` } function* yield_etags(tags) @@ -735,7 +739,7 @@ function render_replying_to_chat(model, ev) { const names = pks.map(pk => render_mentioned_name(pk, model.profiles[pk])).join(", ") const to_users = pks.length === 0 ? "" : ` to ${names}` - return `<div class="replying-to small-txt">replying${to_users} in <span class="chatroom-name">${roomname}</span></div>` + return `<div class="replying-to">replying${to_users} in <span class="chatroom-name">${roomname}</span></div>` } function render_replying_to(model, ev) { @@ -795,14 +799,19 @@ function render_boost(model, ev, opts) { if (!ev.json_content) return render_unknown_event(ev) - const profile = model.profiles[ev.pubkey] + //const profile = model.profiles[ev.pubkey] opts.is_boost_event = true - return ` - <div class="boost"> - <div class="boost-text">Reposted by ${render_name_plain(ev.pubkey, profile)}</div> - ${render_event(model, ev.json_content, opts)} - </div> - ` + opts.boosted = { + pubkey: ev.pubkey, + profile: model.profiles[ev.pubkey] + } + return render_event(model, ev.json_content, opts) + //return ` + //<div class="boost"> + //<div class="boost-text">Reposted by ${render_name_plain(ev.pubkey, profile)}</div> + //${render_event(model, ev.json_content, opts)} + //</div> + //` } function shouldnt_render_event(model, ev, opts) { @@ -818,13 +827,14 @@ function render_deleted_pfp() { } function render_comment_body(model, ev, opts) { - const bar = !can_reply(ev) || opts.nobar? "" : render_action_bar(ev) + const canDelete = !(model.pubkey !== ev.pubkey) + const bar = !can_reply(ev) || opts.nobar? "" : render_action_bar(ev, canDelete) const show_media = !opts.is_composing return ` <div> ${render_replying_to(model, ev)} - ${render_delete_post(model, ev)} + ${render_boosted_by(model, ev, opts)} </div> <p> ${format_content(ev, show_media)} @@ -834,6 +844,19 @@ function render_comment_body(model, ev, opts) { ` } +function render_boosted_by(model, ev, opts) { + const b = opts.boosted + if (!b) { + return "" + } + // TODO encapsulate username as link/button! + return ` + <div class="boosted-by">Shared by + <span class="username" data-pubkey="${b.pubkey}">${render_name_plain(b.pubkey, b.profile)}</span> + </div> + ` +} + function render_deleted_comment_body(ev, deleted) { if (deleted.content) { const show_media = false @@ -844,8 +867,6 @@ function render_deleted_comment_body(ev, deleted) { </div> ` } - - return `<div class="deleted-comment">This comment was deleted</div>` } @@ -868,20 +889,29 @@ function render_event(model, ev, opts={}) { const replied_events = render_replied_events(model, ev, opts) + let name = "???" + if (!deleted) { + name = render_name_plain(ev.pubkey, profile) + } + return ` ${replied_events} - <div id="ev${ev.id}" class="comment"> - <div class="info"> - ${deleted ? render_deleted_name() : render_name(ev.pubkey, profile)} - <span class="timestamp">${delta}</span> - </div> - <div class="pfpbox"> + <div id="ev${ev.id}" class="event"> + <div class="userpic"> ${render_reply_line_top(replied_events === "")} ${deleted ? render_deleted_pfp() : render_pfp(ev.pubkey, profile)} ${reply_line_bot} - </div> - <div class="comment-body"> - ${deleted ? render_deleted_comment_body(ev, deleted) : render_comment_body(model, ev, opts)} + </div> + <div class="event-content"> + <div class="info"> + <span class="username" data-pubkey="${ev.pubkey}" data-name="${name}"> + ${name} + </span> + <span class="timestamp">${delta}</span> + </div> + <div class="comment"> + ${deleted ? render_deleted_comment_body(ev, deleted) : render_comment_body(model, ev, opts)} + </div> </div> </div> ` @@ -889,7 +919,7 @@ function render_event(model, ev, opts={}) { function render_pfp(pk, profile, size="normal") { const name = render_name_plain(pk, profile) - return `<img title="${name}" class="pfp pfp-${size}" onerror="this.onerror=null;this.src='${robohash(pk)}';" src="${get_picture(pk, profile)}">` + return `<img title="${name}" onerror="this.onerror=null;this.src='${robohash(pk)}';" src="${get_picture(pk, profile)}">` } const REACTION_REGEX = /^[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)$/ @@ -990,7 +1020,7 @@ function render_reactions(model, ev) { function close_reply() { const modal = document.querySelector("#reply-modal") - modal.style.display = "none"; + modal.classList.add("closed"); } function gather_reply_tags(pubkey, from) { @@ -1179,7 +1209,7 @@ async function sign_id(privkey, id) function reply_to(evid) { const modal = document.querySelector("#reply-modal") - const replying = modal.style.display === "none"; + modal.classList.remove("closed") const replying_to = modal.querySelector("#replying-to") replying_to.dataset.evid = evid @@ -1189,10 +1219,18 @@ function reply_to(evid) { modal.style.display = replying? "block" : "none"; } -function render_action_bar(ev) { +function render_action_bar(ev, canDelete=false) { + let deleteHTML = "" + if (canDelete) { + deleteHTML = `<button class="icon" title="Delete" onclick="like('${ev.id}')"><i class="fa fa-fw fa-trash"></i></a>` + } return ` <div class="action-bar"> - <a href="javascript:reply_to('${ev.id}')">reply</a> + <button class="icon" title="Reply" onclick="reply_to('${ev.id}')"><i class="fa fa-fw fa-comment"></i></a> + <button class="icon react heart" title="Like" onclick=""><i class="fa fa-fw fa-heart"></i></a> + <button class="icon" title="Share" onclick=""><i class="fa fa-fw fa-link"></i></a> + ${deleteHTML} + <button class="icon" title="View raw Nostr event." onclick=""><i class="fa-solid fa-fw fa-code"></i></a> </div> ` } @@ -1207,7 +1245,7 @@ function is_video_url(path) { return VID_REGEX.test(path) } -const URL_REGEX = /(https?:\/\/[^\s]+)[,:)]?(\w|$)/g; +const URL_REGEX = /(https?:\/\/[^\s\):]+)/g; function linkify(text, show_media) { return text.replace(URL_REGEX, function(url) { const parsed = new URL(url) @@ -1283,11 +1321,16 @@ function format_content(ev, show_media) let cw = get_content_warning(ev.tags) if (cw !== null) { - cw = cw === ""? "Content Warning" : `Content Warning: ${cw}` + let cwHTML = "This content has been marked as sensitive" + if (cw === "") { + cwHTML += "." + } else { + cwHTML += ` due to: "<span>${cw}</span>".` + } const open = !!DSTATE.cw_open[ev.id]? "open" : "" return ` <details class="cw" id="cw_${ev.id}" ${open}> - <summary>${cw}</summary> + <summary>${cwHTML}</summary> ${body} </details> ` @@ -1378,3 +1421,10 @@ function time_delta(current, previous) { return Math.round(elapsed/msPerYear ) + ' years ago'; } } + +function toggle_cw(el) { + el.classList.toggle("active"); + const isOn = el.classList.contains("active"); + const input = el.parentElement.querySelector("input.cw"); + input.classList.toggle("hide", !isOn); +} diff --git a/web/index.html b/web/index.html @@ -3,49 +3,71 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>Damus</title> - - <link rel="stylesheet" href="damus.css?v=21"> + <link rel="stylesheet" href="styles.css?v=17"> + <link rel="stylesheet" href="damus.css?v=20"> + <link rel="stylesheet" + href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"> </head> <body> - <section class="header"> - <span class="logo"> - <img src="img/damus-nobg.svg"/> - </span> - </section> - <div id="view" class="container"> - </div> - - <div style="display: none" id="reply-modal"> - <div id="reply-modal-content"> - <span id="reply-top"> - <a class="close" href="javascript:close_reply()">✕</a> - <span class="small-text"> - Replying to... - </span> - </span> - - <div id="replying-to"> - </div> - - <div> - <textarea id="reply-content"></textarea> - </div> - - <div style="float:right"> - <button onclick="do_send_reply()" id="reply-button">Reply</button> - </div> - </div> - </div> - <script src="noble-secp256k1.js?v=1"></script> - <script src="bech32.js?v=1"></script> - <script src="nostr.js?v=6"></script> - <script src="damus.js?v=59"></script> - <script> - // I have to delay loading to wait for nos2x - const relay = setTimeout(damus_web_init, 100) - </script> + <div id="container"> + <div class="flex-fill"></div> + <div id="nav"> + <div id="app-icon-logo"> + <i class="fa-regular fa-fw fa-hand-peace"></i> + </div> + <div> + <button class="nav icon"> + <i class="fa fa-fw fa-home"></i><span class="hide">Home</span> + </button></div> + <div> + <button class="nav icon"> + <i class="fa fa-fw fa-user"></i><span class="hide">Profile</span> + </button></div> + <div> + <button class="nav icon"> + <i class="fa fa-fw fa-gear"></i><span class="hide">Settings</span> + </button></div> + <div> + <button class="nav icon"> + <i class="fa-regular fa-fw fa-circle-question"></i><span class="hide">Help</span> + </button></div> + </div> + <div id="content"> + <header> + <label>Home</label> + </header> + <div id="view"></div> + </div> + <div class="flex-fill"></div> + </div> + <div class="modal closed" id="reply-modal"> + <div id="reply-modal-content" class="modal-content"> + <header> + <label>Reply To</label> + <button class="icon" onclick="close_reply()"> + <i class="fa fa-xmark"></i> + </button> + </header> + <div id="replying-to"></div> + <div> + <textarea id="reply-content" class="post-input" + placeholder="Write your reply here..."></textarea> + <div class="post-tools"> + <button id="reply-button" class="action" onclick="do_send_reply()"> + Reply + </button> + </div> + </div> + </div> + </div> + <script src="noble-secp256k1.js?v=1"></script> + <script src="bech32.js?v=1"></script> + <script src="nostr.js?v=6"></script> + <script src="damus.js?v=57"></script> + <script> + // I have to delay loading to wait for nos2x + const relay = setTimeout(damus_web_init, 100) + </script> </body> </html> - diff --git a/web/styles.css b/web/styles.css @@ -0,0 +1,301 @@ +/* Hello welcome to my styles. Here are some notes. + * + * - I use Noto Sans because it supports most languages. + * - I will implement light mode first and then attempt dark. + * - All variables should be declared at the top. + * - Use as little as possible, write from scratch, and utilize helper + * classes. + * - No transpilers! + */ + +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;800&display=swap'); + +:root { + --clrBg: #fff; + --clrPanel: #f9f9f9; + --clrBorder: #f2f2f2; + --clrBtn: #202020; + --clrText: #202020; + --clrTextLight: #868686; + --clrTextLighter: #abb4ca; + --clrHeart: #ff5050; + --clrWarn: #fbc560; + + --fsSmall: 12px; + --fsNormal: 16px; + --fsReduced: 14px; + --fsEnlarged: 18px; + + --ffDefault: "Noto Sans", sans-serif; +} +*:focus-visible { + /* Technically this is bad and something else should be done to indicate + * that something is in focus. + */ + outline: none; +} +body { + background: var(--clrBg); + color: var(--clrText); + font-family: "Noto Sans", sans-serif; + font-size: var(--fsNormal); + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +/* Utilities */ +.flex-fill { + flex: 1; +} +.hide { + display: none; +} +.loader { + font-size: 24px; +} +button { + cursor: pointer; +} +button.icon { + border: none; + background: transparent; + padding: 0; + margin: 0; +} +button.action { + border: none; + border-radius: 50px; + background: #171717; + padding: 10px 15px; + font-size: 16px; + color: white; + font-weight: 800; +} +.float-right { + float: right; +} + +/* Application Framework */ +button.nav { + color: var(--clrBtn); + font-size: 24px; + padding: 15px 25px; +} + +#app-icon-logo { + font-size: 28px; + text-align: center; + padding: 20px; +} +#container { + display: flex; + flex-direction: row; + width: 100vw; + height: 100vh; + overflow: hidden; +} +#nav { + flex-shrink: 1; + border-right: 1px solid var(--clrBorder); +} +#content { + display: flex; + flex-direction: column; + border-right: 1px solid var(--clrBorder); +} +#content header > label { + padding: 22px 15px; + font-size: 22px; + font-weight: 800; + display: block; +} +#view { + width: 750px; + overflow-y: scroll; + flex: 1; +} + +/* Events & Content */ +#events > .event { + border-bottom: solid 1px var(--clrBorder); +} +.event { + display: flex; + flex-direction: row; + padding: 15px; + transition: background-color 0.2s linear; +} +.event:hover { + background-color: var(--clrPanel); +} +.loading-events { + text-align: center; + padding: 15px; +} +.userpic { + flex-shrink: 1; +} +.userpic > img { + border-radius: 50%; + width: 64px; + height: 64px; + object-fit: fill; +} +.event-content { + flex: 1; + padding-left: 15px; +} +.event-content > .info { + display: inline-block; +} +.username { + font-weight: 800; + font-size: var(--fsReduced); +} +.chatroom-name { + font-weight: bold; +} +.timestamp, .replying-to, .boosted-by { + font-size: var(--fsSmall); + color: var(--clrTextLight); +} +.comment { + word-break: break-word; +} +.action-bar { +} +.action-bar > button { + margin-right: 25px; + color: var(--clrTextLighter); + font-size: var(--fsNormal); +} +.reactions { + padding-bottom: 15px; +} +.reaction-group img { + width: 18px; + height: 18px; + object-fit: cover; + border-radius: 50%; + margin-left: -8px; + vertical-align: top; +} +.reaction-group img:first-of-type { + margin-left: 0px; +} +.reaction-emoji { +} +.action-bar button.icon { + transition: color 0.3s linear; +} +.action-bar button.icon:hover { + color: var(--clrText); +} +.action-bar button.heart:hover { + color: var(--clrHeart); +} +details.cw summary { + background: #f2f2f2; + padding: 10px; + border-radius: 12px; + color: #444; + cursor: pointer; + margin-bottom: 10px; +} + +/* Thread Expansion */ +.thread-collapsed { + padding: 15px; +} +.thread-summary { + background: #f2f2f2; + padding: 10px; + border-radius: 12px; + color: #444; + cursor: pointer; +} + +/* Modal */ +.modal { + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.4); + opacity: 1; + transition: opacity 0.2s linear; +} +.modal.closed { + opacity: 0; + pointer-events: none; +} +.modal-content { + padding: 20px; + overflow: auto; + border-radius: 15px; + background: var(--clrPanel); + max-width: 700px; + margin: 0 auto; + margin-top: 35px; +} +.modal header { + display: flex; +} +.modal header label { + font-weight: 800; + font-size: var(--fsEnlarged); + flex: 1; +} +.modal header button { + font-size: 24px; +} + +/* Post & Reply */ +#newpost { + padding: 0px 15px 15px; + display: flex; + flex-direction: row; + border-bottom: solid 1px var(--clrBorder); +} +#newpost > :first-child { + width: 64px; +} +#newpost > :last-child { + padding-left: 15px; + flex: 1; +} +textarea.post-input { + display: block; + width: 100%; + border: none; + background: transparent; + color: var(--clrText); + font-size: var(--fsEnlarged); + font-family: var(--ffDefault); + margin: 10px 0; + padding: 0; + box-sizing: border-box; + resize: vertical; +} + +.post-tools { + text-align: right; +} +.post-tools > button.icon { + margin-right: 10px; + font-size: var(--fsEnlarged); + color: var(--clrTextLight); +} +.post-tools > button.icon.cw.active { + color: var(--clrWarn); +} +input[type="text"].cw { + border: none; + border-bottom: solid 2px var(--clrWarn); + font-size: var(--fsReduced); +} +