Synchronize with Etiquette.

This commit is contained in:
voussoir 2020-09-15 15:04:50 -07:00
parent ec19301bcc
commit 76b1504353
3 changed files with 238 additions and 203 deletions

View file

@ -201,17 +201,26 @@ button:active
#message_area #message_area
{ {
display: flex; display: grid;
flex-direction: column; grid-auto-flow: row;
align-items: center; grid-auto-rows: min-content;
grid-gap: 8px;
padding: 8px;
overflow-y: auto; overflow-y: auto;
background-color: var(--color_transparency); background-color: var(--color_transparency);
} }
#message_area > :last-child
{
/*
For some reason, the message_area's 8px padding doesn't apply to the bottom
when the container is scrolled.
*/
margin-bottom: 8px;
}
.message_bubble .message_bubble
{ {
width: 80%; padding: 2px;
margin: 4px;
word-wrap: break-word; word-wrap: break-word;
} }
.message_bubble * .message_bubble *

View file

@ -1,12 +1,12 @@
var common = {}; const common = {};
common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]); common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]);
common._request = common._request =
function _request(method, url, callback) function _request(method, url, callback)
{ {
let request = new XMLHttpRequest(); const request = new XMLHttpRequest();
let response = { const response = {
"completed": false, "completed": false,
}; };
@ -29,7 +29,7 @@ function _request(method, url, callback)
} }
callback(response); callback(response);
}; };
let asynchronous = true; const asynchronous = true;
request.open(method, url, asynchronous); request.open(method, url, asynchronous);
return request; return request;
} }
@ -61,7 +61,7 @@ function bind_box_to_button(box, button, ctrl_enter)
Thanks Yaroslav Yakovlev Thanks Yaroslav Yakovlev
http://stackoverflow.com/a/9343095 http://stackoverflow.com/a/9343095
*/ */
let bound_box_hook = function(event) const bound_box_hook = function(event)
{ {
if (event.key !== "Enter") if (event.key !== "Enter")
{return;} {return;}
@ -83,9 +83,9 @@ function create_message_bubble(message_area, message_positivity, message_text, l
{ {
lifespan = 8000; lifespan = 8000;
} }
let message = document.createElement("div"); const message = document.createElement("div");
message.className = "message_bubble " + message_positivity; message.className = "message_bubble " + message_positivity;
let span = document.createElement("span"); const span = document.createElement("span");
span.innerHTML = message_text; span.innerHTML = message_text;
message.appendChild(span); message.appendChild(span);
message_area.appendChild(message); message_area.appendChild(message);
@ -168,13 +168,13 @@ function entry_with_history_hook(event)
common.html_to_element = common.html_to_element =
function html_to_element(html) function html_to_element(html)
{ {
let template = document.createElement("template"); const template = document.createElement("template");
template.innerHTML = html; template.innerHTML = html.trim();
return template.content.firstChild; return template.content.firstElementChild;
} }
common.init_atag_merge_params = common.init_atag_merge_params =
function init_atag_merge_params() function init_atag_merge_params(a)
{ {
/* /*
To create an <a> tag where the ?parameters written on the href are merged To create an <a> tag where the ?parameters written on the href are merged
@ -195,42 +195,47 @@ function init_atag_merge_params()
href: "?orderby=date" href: "?orderby=date"
Result: "?filter=hello&orderby=date" Result: "?filter=hello&orderby=date"
*/ */
function ingest(params, new_params)
{
for (const [key, value] of params) { new_params.set(key, value); }
}
const page_params = Array.from(new URLSearchParams(window.location.search)); const page_params = Array.from(new URLSearchParams(window.location.search));
let as = Array.from(document.getElementsByClassName("merge_params")); let to_merge;
for (const a of as)
{
const a_params = new URLSearchParams(a.search);
const new_params = new URLSearchParams();
if (a.dataset.mergeParams) if (a.dataset.mergeParams)
{ {
let keep = new Set(a.dataset.mergeParams.split(/[\s,]+/)); const keep = new Set(a.dataset.mergeParams.split(/[\s,]+/));
ingest(page_params.filter(key_value => keep.has(key_value[0])), new_params); to_merge = page_params.filter(key_value => keep.has(key_value[0]));
delete a.dataset.mergeParams; delete a.dataset.mergeParams;
} }
else if (a.dataset.mergeParamsExcept) else if (a.dataset.mergeParamsExcept)
{ {
let remove = new Set(a.dataset.mergeParamsExcept.split(/[\s,]+/)); const remove = new Set(a.dataset.mergeParamsExcept.split(/[\s,]+/));
ingest(page_params.filter(key_value => (! remove.has(key_value[0]))), new_params); to_merge = page_params.filter(key_value => (! remove.has(key_value[0])));
delete a.dataset.mergeParamsExcept; delete a.dataset.mergeParamsExcept;
} }
else else
{ {
ingest(page_params, new_params); to_merge = page_params;
} }
ingest(a_params, new_params); to_merge = to_merge.concat(Array.from(new URLSearchParams(a.search)));
const new_params = new URLSearchParams();
for (const [key, value] of to_merge)
{ new_params.set(key, value); }
a.search = new_params.toString(); a.search = new_params.toString();
a.classList.remove("merge_params"); a.classList.remove("merge_params");
} }
common.init_all_atag_merge_params =
function init_all_atag_merge_params()
{
const page_params = Array.from(new URLSearchParams(window.location.search));
const as = Array.from(document.getElementsByClassName("merge_params"));
for (const a of as)
{
setTimeout(() => common.init_atag_merge_params(a), 0);
}
} }
common.init_button_with_confirm = common.init_button_with_confirm =
function init_button_with_confirm() function init_button_with_confirm(button)
{ {
/* /*
To create a button that requires a second confirmation step, assign it the To create a button that requires a second confirmation step, assign it the
@ -262,25 +267,19 @@ function init_button_with_confirm()
data-holder-class: CSS class for the new span that holds the menu. data-holder-class: CSS class for the new span that holds the menu.
*/ */
let buttons = Array.from(document.getElementsByClassName("button_with_confirm"));
for (const button of buttons)
{
button.classList.remove("button_with_confirm"); button.classList.remove("button_with_confirm");
let holder = document.createElement("span"); const holder = document.createElement("span");
holder.classList.add("confirm_holder"); holder.className = "confirm_holder " + (button.dataset.holderClass || "");
holder.classList.add(button.dataset.holderClass || "confirm_holder");
button.parentElement.insertBefore(holder, button); button.parentElement.insertBefore(holder, button);
button.parentElement.removeChild(button);
let holder_stage1 = document.createElement("span"); const holder_stage1 = document.createElement("span");
holder_stage1.classList.add("confirm_holder_stage1"); holder_stage1.className = "confirm_holder_stage1";
holder_stage1.appendChild(button); holder_stage1.appendChild(button);
holder.appendChild(holder_stage1); holder.appendChild(holder_stage1);
let holder_stage2 = document.createElement("span"); const holder_stage2 = document.createElement("span");
holder_stage2.classList.add("confirm_holder_stage2"); holder_stage2.className = "confirm_holder_stage2 hidden";
holder_stage2.classList.add("hidden");
holder.appendChild(holder_stage2); holder.appendChild(holder_stage2);
let prompt; let prompt;
@ -302,7 +301,7 @@ function init_button_with_confirm()
delete button.dataset.prompt; delete button.dataset.prompt;
delete button.dataset.promptClass; delete button.dataset.promptClass;
let button_confirm = document.createElement("button"); const button_confirm = document.createElement("button");
button_confirm.innerText = (button.dataset.confirm || button.innerText).trim(); button_confirm.innerText = (button.dataset.confirm || button.innerText).trim();
if (button.dataset.confirmClass === undefined) if (button.dataset.confirmClass === undefined)
{ {
@ -324,7 +323,7 @@ function init_button_with_confirm()
delete button.dataset.confirmClass; delete button.dataset.confirmClass;
delete button.dataset.isInput; delete button.dataset.isInput;
let button_cancel = document.createElement("button"); const button_cancel = document.createElement("button");
button_cancel.innerText = button.dataset.cancel || "Cancel"; button_cancel.innerText = button.dataset.cancel || "Cancel";
button_cancel.className = button.dataset.cancelClass || ""; button_cancel.className = button.dataset.cancelClass || "";
holder_stage2.appendChild(button_cancel); holder_stage2.appendChild(button_cancel);
@ -332,20 +331,20 @@ function init_button_with_confirm()
delete button.dataset.cancelClass; delete button.dataset.cancelClass;
// If this is stupid, let me know. // If this is stupid, let me know.
let confirm_onclick = button.dataset.onclick + ` const confirm_onclick = `
;
let holder = event.target.parentElement.parentElement; let holder = event.target.parentElement.parentElement;
holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden"); holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden"); holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
` ` + button.dataset.onclick;
button_confirm.onclick = Function(confirm_onclick); button_confirm.onclick = Function(confirm_onclick);
button.removeAttribute("onclick"); button.removeAttribute("onclick");
button.onclick = function(event) button.onclick = function(event)
{ {
let holder = event.target.parentElement.parentElement; const holder = event.target.parentElement.parentElement;
holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden"); holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden");
holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden"); holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden");
let input = holder.getElementsByTagName("input")[0]; const input = holder.getElementsByTagName("input")[0];
if (input) if (input)
{ {
input.focus(); input.focus();
@ -354,54 +353,108 @@ function init_button_with_confirm()
button_cancel.onclick = function(event) button_cancel.onclick = function(event)
{ {
let holder = event.target.parentElement.parentElement; const holder = event.target.parentElement.parentElement;
holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden"); holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden"); holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
} }
delete button.dataset.onclick; delete button.dataset.onclick;
} }
common.init_all_button_with_confirm =
function init_all_button_with_confirm()
{
const buttons = Array.from(document.getElementsByClassName("button_with_confirm"));
for (const button of buttons)
{
setTimeout(() => common.init_button_with_confirm(button), 0);
}
} }
common.init_enable_on_pageload = common.init_enable_on_pageload =
function init_enable_on_pageload() function init_enable_on_pageload(element)
{ {
/* /*
To create an input element which is disabled at first, and is enabled when To create an input element which is disabled at first, and is enabled when
the DOM has completed loading, give it the disabled attribute and the the DOM has completed loading, give it the disabled attribute and the
class "enable_on_pageload". class "enable_on_pageload".
*/ */
let elements = Array.from(document.getElementsByClassName("enable_on_pageload"));
for (const element of elements)
{
element.disabled = false; element.disabled = false;
element.classList.remove("enable_on_pageload"); element.classList.remove("enable_on_pageload");
} }
common.init_all_enable_on_pageload =
function init_all_enable_on_pageload()
{
const elements = Array.from(document.getElementsByClassName("enable_on_pageload"));
for (const element of elements)
{
setTimeout(() => common.init_enable_on_pageload(element), 0);
}
} }
common.init_entry_with_history = common.init_entry_with_history =
function init_entry_with_history() function init_entry_with_history(input)
{
const inputs = Array.from(document.getElementsByClassName("entry_with_history"));
for (const input of inputs)
{ {
input.addEventListener("keydown", common.entry_with_history_hook); input.addEventListener("keydown", common.entry_with_history_hook);
input.classList.remove("entry_with_history"); input.classList.remove("entry_with_history");
} }
common.init_all_entry_with_history =
function init_all_entry_with_history()
{
const inputs = Array.from(document.getElementsByClassName("entry_with_history"));
for (const input of inputs)
{
setTimeout(() => common.init_entry_with_history(input), 0);
}
} }
common.init_tabbed_container = common.init_tabbed_container =
function init_tabbed_container() function init_tabbed_container(tabbed_container)
{ {
let switch_tab = const button_container = document.createElement("div");
function switch_tab(event) button_container.className = "tab_buttons";
tabbed_container.prepend(button_container);
const tabs = Array.from(tabbed_container.getElementsByClassName("tab"));
for (const tab of tabs)
{ {
let tab_button = event.target; tab.classList.add("hidden");
const tab_id = tab.dataset.tabId || tab.dataset.tabTitle;
tab.dataset.tabId = tab_id;
tab.style.borderTopColor = "transparent";
const button = document.createElement("button");
button.className = "tab_button tab_button_inactive";
button.onclick = common.tabbed_container_switcher;
button.innerText = tab.dataset.tabTitle;
button.dataset.tabId = tab_id;
button_container.append(button);
}
tabs[0].classList.remove("hidden");
button_container.firstElementChild.classList.remove("tab_button_inactive");
button_container.firstElementChild.classList.add("tab_button_active");
}
common.init_all_tabbed_container =
function init_all_tabbed_container()
{
const tabbed_containers = Array.from(document.getElementsByClassName("tabbed_container"));
for (const tabbed_container of tabbed_containers)
{
setTimeout(() => common.init_tabbed_container(tabbed_container), 0);
}
}
common.tabbed_container_switcher =
function tabbed_container_switcher(event)
{
const tab_button = event.target;
if (tab_button.classList.contains("tab_button_active")) if (tab_button.classList.contains("tab_button_active"))
{ return; } { return; }
let tab_id = tab_button.dataset.tabId; const tab_id = tab_button.dataset.tabId;
let tab_buttons = tab_button.parentElement.getElementsByClassName("tab_button"); const tab_buttons = tab_button.parentElement.getElementsByClassName("tab_button");
let tabs = tab_button.parentElement.parentElement.getElementsByClassName("tab"); const tabs = tab_button.parentElement.parentElement.getElementsByClassName("tab");
for (const tab_button of tab_buttons) for (const tab_button of tab_buttons)
{ {
if (tab_button.dataset.tabId === tab_id) if (tab_button.dataset.tabId === tab_id)
@ -424,33 +477,6 @@ function init_tabbed_container()
} }
} }
let tabbed_containers = Array.from(document.getElementsByClassName("tabbed_container"));
for (const tabbed_container of tabbed_containers)
{
let button_container = document.createElement("div");
button_container.className = "tab_buttons";
tabbed_container.prepend(button_container);
let tabs = Array.from(tabbed_container.getElementsByClassName("tab"));
for (const tab of tabs)
{
tab.classList.add("hidden");
let tab_id = tab.dataset.tabId || tab.dataset.tabTitle;
tab.dataset.tabId = tab_id;
tab.style.borderTopColor = "transparent";
let button = document.createElement("button");
button.className = "tab_button tab_button_inactive";
button.onclick = switch_tab;
button.innerText = tab.dataset.tabTitle;
button.dataset.tabId = tab_id;
button_container.append(button);
}
tabs[0].classList.remove("hidden");
button_container.firstElementChild.classList.remove("tab_button_inactive");
button_container.firstElementChild.classList.add("tab_button_active");
}
}
common.refresh = common.refresh =
function refresh() function refresh()
{ {
@ -460,10 +486,10 @@ function refresh()
common.on_pageload = common.on_pageload =
function on_pageload() function on_pageload()
{ {
common.init_atag_merge_params(); common.init_all_atag_merge_params();
common.init_button_with_confirm(); common.init_all_button_with_confirm();
common.init_enable_on_pageload(); common.init_all_enable_on_pageload();
common.init_entry_with_history(); common.init_all_entry_with_history();
common.init_tabbed_container(); common.init_all_tabbed_container();
} }
document.addEventListener("DOMContentLoaded", common.on_pageload); document.addEventListener("DOMContentLoaded", common.on_pageload);

View file

@ -1,4 +1,4 @@
var spinner = {}; const spinner = {};
/* /*
In general, spinners are used for functions that launch a callback, and the In general, spinners are used for functions that launch a callback, and the