New mobile nav. Removed old cruft.
This commit is contained in:
parent
20907bede6
commit
6c9f5f12fe
9 changed files with 106 additions and 253 deletions
62
README.md
62
README.md
|
@ -1,59 +1,15 @@
|
|||
# Yo, Sup?
|
||||
|
||||
Yo Sup? or simply "Yo" for short is a web client for the Nostr protocol. Its
|
||||
aim is to be as good of an experience (if not better than) as Twitter. Note Yo
|
||||
will not be the same as Twitter and will not implement all of it's features.
|
||||
Nor will Yo try to implement all of Nostr's features as there are many.
|
||||
|
||||
The true purpose of Yo is to provide a great experience on any platform for
|
||||
anyone. It should be easy to use and understand making it a great option for
|
||||
people coming from other social networks to engage in their community.
|
||||
|
||||
Yo comes from the legacy Damus Web app an holds all of its history. It has been
|
||||
rewritten to accomodate for the scale issues that we have seen so that it can
|
||||
continue to be used. The main reason for branching off is due to the lack of
|
||||
parity between Damus iOS (and new codebase improvements) and that of what the
|
||||
web version would support.
|
||||
|
||||
New minor features will continue to be added, but nothing substancial without
|
||||
full time maintainers. Security will always be a top concern.
|
||||
# Yo Sup
|
||||
|
||||
[Issue Tracker](https://todo.sr.ht/~tomtom/damus-web-issues)
|
||||
|
||||
## Contribution Guide
|
||||
"Yo Sup" is a minimal Nostr client that grew out of the original Damus Web
|
||||
code. It's goal is to view your feed and access your direct messages very fast.
|
||||
So fast it works over 3G with a fresh page load. It has no goals to fulfill any
|
||||
other NIPs, please use other clients such as Snort, Coracle, or Iris.
|
||||
|
||||
There are rules to contributing to this client. Please ensure you read them
|
||||
before making changes and supplying patch notes.
|
||||
It's written in plain JavaScript, HTML, and CSS for ease of development and
|
||||
building, see the example Dockerfile. Small features and optimizations will be
|
||||
added as needed, but the application is considered "complete".
|
||||
|
||||
- No transpilers. All source code should work out of the box.
|
||||
- Keep source code organised. Refer to the folder structure. If you have a
|
||||
question, ask it.
|
||||
- Do not include your personal tools in the source code. Use your own scripts
|
||||
outside of the project. This does not include build tools such as Make.
|
||||
- Use tabs & write JS with snake_case. End of discussion.
|
||||
- Do not include binary files.
|
||||
- No NPM (and kin) environments. If you need a file from an external resource
|
||||
mark the location in the "sources" file and add it to the repo.
|
||||
- No frameworks. Learn the browser tools and write good code.
|
||||
- No experimental browser APIs.
|
||||
- Do not write animations in JavaScript, CSS only. Keep them short and snappy.
|
||||
Animations should not be a forefront, but an enjoyable addition.
|
||||
- All new & modified code should be properly documented.
|
||||
- Source code should be readable in the browser.
|
||||
- Search for the TODOs.
|
||||
|
||||
These rules are subject to discussion.
|
||||
|
||||
## Terminology
|
||||
|
||||
* Sign Out - Not "log out", "logout", "log off", etc.
|
||||
* Sign In - Not "login", "log in", "signin", "sign-in", etc.
|
||||
* Share - Not "boosted", "retweeted", "repost", etc.
|
||||
* Send - Not "tweet", "toot", "post", etc.
|
||||
* Link - Not "share".
|
||||
|
||||
## Known Issues
|
||||
|
||||
* You cannot send events when running from an IP address that is not secure.
|
||||
Work arounds are not known at this time.
|
||||
Patches are welcomed via email.
|
||||
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
@media (max-width: 800px){
|
||||
:root {
|
||||
/* TODO font size should not be controlled by CSS:
|
||||
* Instead I would prefer user settings. The main reason is the font is
|
||||
* too small on my desktop when I use the app in column mode.
|
||||
*/
|
||||
--fsSmall: 10px;
|
||||
--fsNormal: 14px;
|
||||
--fsReduced: 12px;
|
||||
--fsEnlarged: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 840px){
|
||||
/* Utility */
|
||||
.vertical-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Application Framework */
|
||||
#gnav {
|
||||
display: initial;
|
||||
}
|
||||
#view {
|
||||
flex: 1;
|
||||
width: initial;
|
||||
border-right: none;
|
||||
}
|
||||
.nav.mobile {
|
||||
display: flex;
|
||||
}
|
||||
#content header > label {
|
||||
padding: 12px;
|
||||
}
|
||||
.nav.mobile .new-note {
|
||||
position: fixed;
|
||||
height: initial;
|
||||
bottom: 88px;
|
||||
right: 20px;
|
||||
z-index: var(--zGlobal);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Event */
|
||||
.pfp { /* TODO sync up with userpic */
|
||||
|
|
115
css/styles.css
115
css/styles.css
|
@ -45,13 +45,6 @@ th, td {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
#gsticker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* Welcome */
|
||||
|
||||
#container-busy .loader {
|
||||
|
@ -83,98 +76,53 @@ th, td {
|
|||
}
|
||||
|
||||
/* Navigation */
|
||||
#nav {
|
||||
.nav.full {
|
||||
border-right: 1px solid var(--clrBorder);
|
||||
padding: 10px;
|
||||
}
|
||||
#nav > div {
|
||||
.nav.full > div {
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
#nav > div > * {
|
||||
.nav.full > div > * {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
#nav > div[data-active] img.active {
|
||||
.nav.mobile {
|
||||
display: none;
|
||||
background: var(--clrBg);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: var(--zHeader);
|
||||
flex-direction: row;
|
||||
border-top: var(--clrBorder) 1px solid;
|
||||
}
|
||||
.nav.mobile button {
|
||||
padding: 18px;
|
||||
flex: 1;
|
||||
}
|
||||
.nav [data-view].active img.inactive,
|
||||
.nav [data-view] img.active {
|
||||
display: none;
|
||||
}
|
||||
#nav > div[data-active="home"] [data-view="friends"] img.inactive,
|
||||
#nav > div[data-active="explore"] [data-view="explore"] img.inactive,
|
||||
#nav > div[data-active="notifications"] [data-view="notifications"] img.inactive,
|
||||
#nav > div[data-active="settings"] [data-view="settings"] img.inactive,
|
||||
#nav > div[data-active="messages"] [data-view="dm"] img.inactive {
|
||||
display: none;
|
||||
}
|
||||
#nav > div[data-active="home"] [data-view="friends"] img.active,
|
||||
#nav > div[data-active="explore"] [data-view="explore"] img.active,
|
||||
#nav > div[data-active="notifications"] [data-view="notifications"] img.active,
|
||||
#nav > div[data-active="settings"] [data-view="settings"] img.active,
|
||||
#nav > div[data-active="messages"] [data-view="dm"] img.active {
|
||||
.nav [data-view].active img.active {
|
||||
display: block;
|
||||
}
|
||||
#new-note {
|
||||
background: white;
|
||||
height: 56px;
|
||||
border-radius: 38px;
|
||||
}
|
||||
|
||||
#app-icon-logo > img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
button.nav > img.icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
#gnav {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 55px;
|
||||
right: 55px;
|
||||
z-index: var(--zGlobal);
|
||||
.nav button.new-note {
|
||||
background: white;
|
||||
height: 56px;
|
||||
border-radius: 38px;
|
||||
}
|
||||
#gnav button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--clrText);
|
||||
color: var(--clrBg);
|
||||
padding: 10px;
|
||||
border: transparent 5px solid;
|
||||
transition: top 0.05s linear;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: calc(var(--zGlobal) - 1);
|
||||
}
|
||||
#gnav button > .icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
#gnav button[action="toggle-gnav"] {
|
||||
z-index: var(--zGlobal);
|
||||
padding: 15px;
|
||||
}
|
||||
#gnav.open button[data-view="friends"] {
|
||||
top: -375px;
|
||||
}
|
||||
#gnav.open button[data-view="explore"] {
|
||||
top: -300px;
|
||||
}
|
||||
#gnav.open button[data-view="dm"] {
|
||||
top: -225px;
|
||||
}
|
||||
#gnav.open button[data-view="notifications"] {
|
||||
top: -150px;
|
||||
}
|
||||
#gnav.open button[data-view="notifications"] .new-notifications {
|
||||
right: 9px;
|
||||
}
|
||||
#gnav.open button[data-view="settings"] {
|
||||
top: -75px;
|
||||
#app-icon-logo > img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.new-notifications {
|
||||
|
@ -195,24 +143,29 @@ button.nav > img.icon {
|
|||
flex-flow: row;
|
||||
}
|
||||
#view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid var(--clrBorder);
|
||||
width: 750px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
#view > div > header {
|
||||
#view > header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--zHeader);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
#view > div > header > label {
|
||||
#view > header > label {
|
||||
padding: 15px;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
display: block;
|
||||
}
|
||||
#timeline, #settings, #dms {
|
||||
flex: 1;
|
||||
}
|
||||
#header-tools {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
|
@ -551,7 +504,7 @@ code {
|
|||
}
|
||||
#settings header > label {
|
||||
font-weight: bold;
|
||||
font-size: var(--fsEnlarged);
|
||||
font-size: var(--fsLarge);
|
||||
}
|
||||
|
||||
/* Messaging */
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
--fsReduced: 14px;
|
||||
--fsNormal: 16px;
|
||||
--fsEnlarged: 18px;
|
||||
--fsLarge: 22px;
|
||||
|
||||
/* Font Families */
|
||||
--ffDefault: "Noto Sans", sans-serif;
|
||||
|
|
99
index.html
99
index.html
|
@ -44,39 +44,18 @@
|
|||
Yo, Sup?
|
||||
<img class="icon svg" src="/icon/logo-inverted.svg"/>
|
||||
</h1>
|
||||
<p>The blue bird experience for Nostr.</p>
|
||||
<button class="action" action="sign-in">
|
||||
Sign In with Key
|
||||
<img src="/icon/key.svg" class="icon svg small invert"/>
|
||||
</button>
|
||||
<p>A minimal experience for Nostr.</p>
|
||||
<p>Please access with a nos2x compatible browser.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="container-app" class="hide">
|
||||
<nav id="gnav">
|
||||
<button class="icon" action="toggle-gnav" title="Open Menu">
|
||||
<img class="icon svg invert" src="/icon/logo.svg"/>
|
||||
</button>
|
||||
<button class="icon" action="open-view" data-view="friends" title="Home">
|
||||
<img class="icon svg invert" src="/icon/home.svg"/>
|
||||
</button>
|
||||
<button class="icon" action="open-view" data-view="dm" title="Direct Messages">
|
||||
<img class="icon svg invert" src="/icon/messages.svg"/>
|
||||
<div class="new-notifications hide" role="dm"></div>
|
||||
</button>
|
||||
<button class="icon" action="open-view" data-view="notifications" title="Notifications">
|
||||
<img class="icon svg invert" src="/icon/notifications.svg"/>
|
||||
<div class="new-notifications hide" role="activity"></div>
|
||||
</button>
|
||||
<button class="icon" action="open-view" data-view="settings" title="Settings">
|
||||
<img class="icon svg invert" src="/icon/settings.svg"/>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div id="container">
|
||||
<div class="flex-fill vertical-hide"></div>
|
||||
<div id="nav" class="flex-noshrink vertical-hide">
|
||||
<div data-active="home">
|
||||
<nav id="nav" class="nav full flex-noshrink vertical-hide">
|
||||
<div>
|
||||
<div id="app-icon-logo">
|
||||
<img class="icon svg" title="Damus" src="/icon/logo-inverted.svg"/>
|
||||
</div>
|
||||
|
@ -102,18 +81,17 @@
|
|||
<img class="icon svg inactive" src="/icon/settings.svg"/>
|
||||
<img class="icon svg active" src="/icon/settings-active.svg"/>
|
||||
</button>
|
||||
<button id="new-note" action="new-note" title="New Note" class="nav icon">
|
||||
<button action="new-note" title="New Note" class="nav icon new-note">
|
||||
<img class="icon svg invert" src="/icon/new-note.svg"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="view">
|
||||
<div>
|
||||
<header>
|
||||
<label>Home</label>
|
||||
<div id="header-tools">
|
||||
<button class="action small bordered hide"
|
||||
<button class="action small hide"
|
||||
disabled action="mark-all-read">
|
||||
Mark All Read
|
||||
</button>
|
||||
|
@ -167,11 +145,6 @@
|
|||
<button action="show-timeline-new">
|
||||
Show New (<span role="count">0</span>)</button>
|
||||
</div>
|
||||
<div id="dms-not-available" class="hide">
|
||||
DMs could not be decrypted due to lack of nip04
|
||||
integration. Please use an extension such as nos2x or
|
||||
Alby.
|
||||
</div>
|
||||
<div id="dms" class="hide">
|
||||
</div>
|
||||
<div id="timeline" class="events"></div>
|
||||
|
@ -198,35 +171,41 @@
|
|||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<header><label>About</label></header>
|
||||
<header><label>Info</label></header>
|
||||
<p>
|
||||
Yo, Sup? was originally Damus Web
|
||||
written by <span action="open-profile" class="username clickable"
|
||||
data-pubkey="32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245">
|
||||
jb55</span>. It was rewritten by in order to bring
|
||||
it up to date.
|
||||
<a href="https://git.sr.ht/~tomtom/damus">Source Code</a>
|
||||
<a href="https://todo.sr.ht/~tomtom/damus-web-issues">Bug Tracker</a>
|
||||
<a href="mailto:thomas.c.mathews@gmail.com">Email Me</a>
|
||||
</p>
|
||||
<p>
|
||||
Yo is open source under the AGPL-3 license. You can
|
||||
find the source code <a
|
||||
href="https://git.sr.ht/~tomtom/damus">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
Bugs and feature requests can be emailed to <a
|
||||
href="mailto:thomas.c.mathews@gmail.com">thomas.c.mathews@gmail.com</a>
|
||||
or submitted to the <a
|
||||
href="https://todo.sr.ht/~tomtom/damus-web-issues">
|
||||
tracker</a>.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<button class="action" role="sign-out">
|
||||
Sign Out
|
||||
<img class="icon svg small invert" src="/icon/sign-out.svg"/>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="nav mobile">
|
||||
<button action="open-view" data-view="friends" class="icon"
|
||||
title="Home">
|
||||
<img class="icon svg inactive" src="/icon/home.svg"/>
|
||||
<img class="icon svg active" src="/icon/home-active.svg"/>
|
||||
</button>
|
||||
<button action="open-view" data-view="dm" class="icon"
|
||||
title="Direct Messages">
|
||||
<img class="icon svg inactive" src="/icon/messages.svg"/>
|
||||
<img class="icon svg active" src="/icon/messages-active.svg"/>
|
||||
<div class="new-notifications hide" role="dm"></div>
|
||||
</button>
|
||||
<button action="open-view" data-view="notifications"
|
||||
class="icon" title="Notifications">
|
||||
<img class="icon svg inactive" src="/icon/notifications.svg"/>
|
||||
<img class="icon svg active" src="/icon/notifications-active.svg"/>
|
||||
<div class="new-notifications hide" role="activity"></div>
|
||||
</button>
|
||||
<button action="open-view" data-view="settings"
|
||||
title="Settings" class="icon">
|
||||
<img class="icon svg inactive" src="/icon/settings.svg"/>
|
||||
<img class="icon svg active" src="/icon/settings-active.svg"/>
|
||||
</button>
|
||||
<button action="new-note" title="New Note" class="nav icon new-note">
|
||||
<img class="icon svg invert" src="/icon/new-note.svg"/>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex-fill vertical-hide"></div>
|
||||
</div>
|
||||
|
|
18
js/main.js
18
js/main.js
|
@ -81,10 +81,6 @@ async function webapp_init() {
|
|||
|
||||
// Load data from storage
|
||||
await model_load_settings(model);
|
||||
/*err = await contacts_load(model);
|
||||
if (err) {
|
||||
window.alert("Unable to load contacts.");
|
||||
}*/
|
||||
init_settings(model);
|
||||
|
||||
// Create our pool so that event processing functions can work
|
||||
|
@ -96,14 +92,6 @@ async function webapp_init() {
|
|||
pool.on("eose", on_pool_eose);
|
||||
pool.on("ok", on_pool_ok);
|
||||
|
||||
// Load all events from storage and re-process them so that apply correct
|
||||
// effects.
|
||||
/*await model_load_events(model, (ev)=> {
|
||||
model_process_event(model, undefined, ev);
|
||||
});
|
||||
log_debug("loaded events", Object.keys(model.all_events).length);
|
||||
*/
|
||||
|
||||
var { mode, opts, valid } = parse_url_mode();
|
||||
view_timeline_apply_mode(model, mode, opts, !valid);
|
||||
on_timer_timestamps();
|
||||
|
@ -171,7 +159,7 @@ function on_timer_save() {
|
|||
setTimeout(() => {
|
||||
const model = DAMUS;
|
||||
//model_save_events(model);
|
||||
model_save_settings(model);
|
||||
//model_save_settings(model);
|
||||
on_timer_save();
|
||||
}, 1 * 1000);
|
||||
}
|
||||
|
@ -300,6 +288,7 @@ function fetch_profile(pubkey, pool, relay) {
|
|||
}
|
||||
|
||||
function fetch_thread_history(evid, pool) {
|
||||
// TODO look up referenced relays for thread history
|
||||
const sid = `${SID_THREAD}:${evid}`
|
||||
pool.subscribe(sid, [{
|
||||
kinds: PUBLIC_KINDS,
|
||||
|
@ -309,8 +298,7 @@ function fetch_thread_history(evid, pool) {
|
|||
}
|
||||
|
||||
function fetch_friends_history(friends, pool, relay) {
|
||||
// TODO only fetch friends history from their desired relay instead of
|
||||
// pinging all of the relays
|
||||
// TODO fetch history of each friend by their desired relay
|
||||
pool.subscribe(SID_FRIENDS, [{
|
||||
kinds: PUBLIC_KINDS,
|
||||
authors: friends,
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
function init_settings(model) {
|
||||
const el = find_node("#settings");
|
||||
find_node("#add-relay", el).addEventListener("click", on_click_add_relay);
|
||||
find_node("[role='sign-out']", el).addEventListener("click", on_click_sign_out);
|
||||
const rlist = find_node("#relay-list tbody", el);
|
||||
model.relays.forEach((str) => {
|
||||
rlist.appendChild(new_relay_item(str));
|
||||
});
|
||||
}
|
||||
|
||||
async function on_click_sign_out(ev) {
|
||||
if (confirm("Are you sure you want to sign out?")) {
|
||||
localStorage.clear();
|
||||
await dbclear();
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function new_relay_item(str) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td>${str}</td>
|
||||
|
|
|
@ -115,17 +115,14 @@ function view_timeline_apply_mode(model, mode, opts={}, push_state=true) {
|
|||
|
||||
// Do some visual updates
|
||||
find_node("#show-more").classList.add("hide");
|
||||
find_node("#view header > label").innerText = name;
|
||||
find_node("#nav > div[data-active]").dataset.active = names[mode].toLowerCase();
|
||||
find_node("#view header > label").innerText = name;
|
||||
view_update_navs(mode);
|
||||
find_node("#view [role='profile-info']").classList.toggle("hide", mode != VM_USER);
|
||||
const timeline_el = find_node("#timeline");
|
||||
timeline_el.classList.toggle("reverse", mode == VM_THREAD);
|
||||
timeline_el.classList.toggle("hide", mode == VM_SETTINGS || mode == VM_DM);
|
||||
find_node("#settings").classList.toggle("hide", mode != VM_SETTINGS);
|
||||
find_node("#dms").classList.toggle("hide", mode != VM_DM);
|
||||
find_node("#dms-not-available")
|
||||
.classList.toggle("hide", mode == VM_DM_THREAD || mode == VM_DM ?
|
||||
dms_available() : true);
|
||||
find_node("#header-tools button[action='mark-all-read']")
|
||||
.classList.toggle("hide", mode != VM_DM);
|
||||
|
||||
|
@ -220,6 +217,12 @@ function view_timeline_refresh(model, mode, opts={}) {
|
|||
}
|
||||
}
|
||||
|
||||
function view_update_navs(mode) {
|
||||
find_nodes("nav.nav button[data-view]").forEach((el)=> {
|
||||
el.classList.toggle("active", el.dataset.view == mode)
|
||||
});
|
||||
}
|
||||
|
||||
function view_show_spinner(show=true) {
|
||||
find_node("#view .loading-events").classList.toggle("hide", !show);
|
||||
}
|
||||
|
@ -546,7 +549,6 @@ function get_thread_root_id(damus, id) {
|
|||
|
||||
function switch_view(mode, opts) {
|
||||
view_timeline_apply_mode(DAMUS, mode, opts);
|
||||
close_gnav();
|
||||
}
|
||||
|
||||
function toggle_hide_replys(el) {
|
||||
|
@ -668,9 +670,6 @@ function onclick_any(ev) {
|
|||
const el = ev.target;
|
||||
const action = el.getAttribute("action");
|
||||
switch (action) {
|
||||
case "toggle-gnav":
|
||||
toggle_gnav(el);
|
||||
break;
|
||||
case "sign-in":
|
||||
signin();
|
||||
break;
|
||||
|
|
|
@ -3,17 +3,6 @@
|
|||
* this file grows specific UI area code should be migrated to its own file.
|
||||
*/
|
||||
|
||||
/* toggle_gnav hides or shows the global navigation's additional buttons based
|
||||
* on its opened state.
|
||||
*/
|
||||
function toggle_gnav(el) {
|
||||
el.parentElement.classList.toggle("open");
|
||||
}
|
||||
|
||||
function close_gnav() {
|
||||
find_node("#gnav").classList.remove("open");
|
||||
}
|
||||
|
||||
/* init_message_textareas finds all message textareas and updates their initial
|
||||
* height based on their content (0). This is so there is no jaring affect when
|
||||
* the page loads.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue