From 733776ee88df9ae5acf7edc39c6f572b4cdb9a9d Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Mon, 14 Sep 2020 17:21:13 -0700 Subject: [PATCH] Failed experiment: clientside updates of tag actions. I'm committing this so I can reference it later if I decide to try again, but for the time being I'm going to immediately revert it. --- .../etiquette_flask/static/css/etiquette.css | 4 +- frontends/etiquette_flask/templates/tags.html | 463 ++++++++++++++++-- 2 files changed, 432 insertions(+), 35 deletions(-) diff --git a/frontends/etiquette_flask/static/css/etiquette.css b/frontends/etiquette_flask/static/css/etiquette.css index 69b2d72..5abb059 100644 --- a/frontends/etiquette_flask/static/css/etiquette.css +++ b/frontends/etiquette_flask/static/css/etiquette.css @@ -124,8 +124,8 @@ is hovered over. { display: none; } -.tag_object:hover + * .remove_tag_button, -.tag_object:hover + .remove_tag_button, +.tag_object:hover ~ * .remove_tag_button, +.tag_object:hover ~ .remove_tag_button, .remove_tag_button:hover, .remove_tag_button_perm:hover { diff --git a/frontends/etiquette_flask/templates/tags.html b/frontends/etiquette_flask/templates/tags.html index baa5ada..b39c2d1 100644 --- a/frontends/etiquette_flask/templates/tags.html +++ b/frontends/etiquette_flask/templates/tags.html @@ -149,9 +149,7 @@ h2, h3 {% for ancestor in specific_tag.get_parents() %}
  • {{tag_object.tag_object(ancestor, link="search_musts", innertext="(+)")}} - {{tag_object.tag_object(ancestor, link="search_forbids", innertext="(x)")}} - {{tag_object.tag_object(ancestor, link="info", innertext=ancestor.name, with_alt_description=True)}}
  • {% endfor %} @@ -171,53 +169,45 @@ h2, h3 {% for (qualified_name, tag) in tags %}
  • {{tag_object.tag_object(tag, link="search_musts", innertext="(+)")}} - {{tag_object.tag_object(tag, link="search_forbids", innertext="(x)")}} - - {{tag_object.tag_object(tag, link="info", innertext=qualified_name, with_alt_description=True)}} - {%- if "." in qualified_name -%} - - {%- else -%} + {{tag_object.tag_object(tag, link="info", innertext=qualified_name, with_alt_description=True)-}} - {% endif %} + >Delete + {{-''-}} + +
  • {% if include_synonyms %} - {% for synonym in tag.get_synonyms() %} + {% for synonym in tag.get_synonyms()|sort %}
  • - {{tag_object.tag_object(tag, link="search_musts", innertext="(+)")}} - + {{-tag_object.tag_object(tag, link="search_musts", innertext="(+)")}} {{tag_object.tag_object(tag, link="search_forbids", innertext="(x)")}} - {{tag_object.tag_object(tag, link='info', innertext=qualified_name + '+' + synonym)-}} + >Remove
  • {% endfor %} {% endif %} @@ -268,15 +258,144 @@ var add_tag_button = document.getElementById('add_tag_button'); var message_area = document.getElementById('message_area'); common.bind_box_to_button(add_tag_textbox, add_tag_button, false); +TEMPLATE_BUTTON_DELETE_TAG = ``; + +TEMPLATE_BUTTON_REMOVE_CHILD = ``; + +TEMPLATE_BUTTON_REMOVE_SYNONYM = ``; + +TEMPLATE_TAG_LI = `
  • (+) +(x) +{tag_name}
  • `; + +function get_all_tag_objects() +{ return Array.from(document.querySelectorAll(".tag_object[href^='/tag/']")); } + +function get_tag_objects_for_descendant(tag_name, child_name) +{ return get_tag_objects_matching(`(^|\\.)${re_escape(tag_name)}\\.${re_escape(child_name)}($|\\.|\\+)`); } + +function get_tag_objects_for_descendants(tag_name) +{ return get_tag_objects_matching(`(^|\\.)${re_escape(tag_name)}\\.`); } + +function get_tag_objects_for_synonym(synonym) +{ return get_tag_objects_matching(`\\+${re_escape(synonym)}$`); } + +function get_tag_objects_for_non_root_tag(tag_name) +{ return get_tag_objects_matching(`\\.${re_escape(tag_name)}($|\\+)`); } + +function get_tag_objects_for_non_root_tag_and_descendants(tag_name) +{ return get_tag_objects_matching(`\\.${re_escape(tag_name)}($|\\.|\\+)`); } + +function get_tag_objects_for_root_tag(tag_name) +{ return get_tag_objects_matching(`^${re_escape(tag_name)}($|\\+)`); } + +function get_tag_objects_for_root_tag_and_descendants(tag_name) +{ return get_tag_objects_matching(`^${re_escape(tag_name)}($|\\.|\\+)`); } + +function get_tag_objects_for_tag(tag_name) +{ return get_tag_objects_matching(`(^|\\.)${re_escape(tag_name)}$`); } + +function get_tag_objects_for_tag_and_descendants(tag_name) +{ return get_tag_objects_matching(`(^|\\.)${re_escape(tag_name)}($|\\.|\\+)`); } + +function get_tag_objects_for_tag_and_synonyms(tag_name) +{ return get_tag_objects_matching(`(^|\\.)${re_escape(tag_name)}($|\\+)`); } + +function get_tag_objects_matching(regex) +{ + regex = new RegExp(regex); + const ret = get_all_tag_objects().filter(tag_object => tag_object.innerText.match(regex)); + return ret; +} + +function split_exclusive(text, tag_name) +{ + /* + Given text="a.b.c.d.e", tag_name"c", return "d.e". + */ + const regex = `(?:^|\\.)${re_escape(tag_name)}(?:$|\\.)`; + const splitted = text.split(new RegExp(regex)); + if (splitted.length == 1) + { return null; } + const descendantry = splitted.pop(); + return descendantry; +} + +function split_inclusive(text, tag_name) +{ + /* + Given text="a.b.c.d.e", tag_name"c", return "c.d.e". + */ + const descendantry = split_exclusive(text, tag_name); + if (descendantry === null) + { return null; } + if (descendantry === "") + { return tag_name; } + return tag_name + "." + descendantry; +} + function tag_object_from_li(li) { const tag_objects = li.getElementsByClassName("tag_object"); return tag_objects[tag_objects.length - 1]; } +function lift_tag_object(tag_object, from_parent) +{ + const descendantry = split_exclusive(tag_object.innerText, from_parent); + const regex = `(^|\\.)${re_escape(from_parent)}\\.`; + tag_object.innerText = tag_object.innerText.replace(new RegExp(regex), "$1"); + const is_now_root = tag_object.innerText.indexOf(descendantry) == 0; + if (! is_now_root) + { + return; + } + const other_parents = get_tag_objects_matching(`\\.${re_escape(descendantry)}$`) + if (other_parents.length == 0) + { + return; + } + const li = tag_object.closest("li"); + li.parentElement.removeChild(li); +} + +function re_escape(s) +{ + // Thank you Pi Marillion. + // https://stackoverflow.com/a/30851002 + return s.replace(/[^A-Za-z0-9_]/g, '\\$&'); +} + + +// FORM FUNCTIONS ////////////////////////////////////////////////////////////// + function easybake_form() { - let easybake_string = add_tag_textbox.value; + const easybake_string = add_tag_textbox.value; if (easybake_string === "") { add_tag_textbox.focus(); @@ -335,16 +454,79 @@ function remove_synonym_form(event) return api.tags.remove_synonym(tag_name, synonym, tag_action_callback); } +// CALLBACKS /////////////////////////////////////////////////////////////////// + +function easybake_callback(response) +{ + tag_action_callback(response); + if (response.meta.status !== 200) + {return;} + for (const data of response.data) + { + if (!("action" in data)) + {continue;} + const action = data.action; + if (action == "new_tag") + { + const tag_name = data.tagname; + new_tag(tag_name); + } + else if (action == "new_synonym") + { + const [tag_name, synonym] = data.tagname.split("+"); + new_synonym(tag_name, synonym); + } + else if (action == "join_group") + { + const [parent_name, child_name] = data.tagname.split("."); + join_group(parent_name, child_name); + } + else if (action == "rename_tag") + { + const [rename_from, rename_to] = data.tagname.split("="); + rename_tag(rename_from, rename_to); + } + } +} + +function delete_tag_callback(response) +{ + tag_action_callback(response); + if (response.meta.status !== 200) + {return;} + const tag_name = response.data.tagname; + delete_tag(tag_name); +} + +function remove_child_callback(response) +{ + tag_action_callback(response); + if (response.meta.status !== 200) + {return;} + const [parent_name, tag_name] = response.data.tagname.split("."); + remove_child(parent_name, tag_name); +} + +function remove_synonym_callback(response) +{ + tag_action_callback(response); + if (response.meta.status !== 200) + {return;} + + const synonym = response.data.synonym; + remove_synonym(synonym); +} + function tag_action_callback(response) { - datas = response.data; + let datas = response.data; if (!Array.isArray(datas)) { datas = [datas]; } for (const data of datas) { - let tagname = data.tagname; + const tagname = data.tagname; let message_positivity; let message_text; if ("error_type" in data) @@ -354,7 +536,7 @@ function tag_action_callback(response) } else if ("action" in data) { - let action = data.action; + const action = data.action; message_positivity = "message_positive"; if (action == "new_tag") {message_text = `Created tag ${tagname}`;} @@ -379,12 +561,221 @@ function tag_action_callback(response) else if (action == "remove_child") {message_text = `Unlinked tags ${tagname}`;} - } common.create_message_bubble(message_area, message_positivity, message_text, 8000); } } +// UI UPDATERS ///////////////////////////////////////////////////////////////// + +function new_tag(tag_name) +{ + const template = TEMPLATE_TAG_LI.replace(/\{tag_name\}/g, tag_name); + const new_li = common.html_to_element(template); + add_buttons_to_li(new_li); + document.getElementById("tag_list").appendChild(new_li); + sort_tag_objects(); +} + +function new_synonym(tag_name, synonym) +{ + for (const tag_object of get_tag_objects_for_tag(tag_name)) + { + const li = tag_object.closest("li"); + const new_li = li.cloneNode(true); + tag_object_from_li(new_li).innerText += `+${synonym}`; + li.parentElement.appendChild(new_li); + add_buttons_to_li(new_li); + } + sort_tag_objects(); +} + +function join_group(parent_name, child_name) +{ + console.log(parent_name + " " + SPECIFIC_TAG); + if (parent_name === SPECIFIC_TAG) + { + console.log("GO!"); + return new_tag(`${parent_name}.${child_name}`); + } + const parent_tags = get_tag_objects_for_tag(parent_name); + const seen_names = new Set(); + for (child_tag of get_tag_objects_for_tag_and_descendants(child_name)) + { + const descendantry = split_inclusive(child_tag.innerText, child_name); + if (seen_names.has(descendantry)) + { break; } + seen_names.add(descendantry); + const child_li = child_tag.closest("li"); + for (const parent_tag of parent_tags) + { + const parent_li = parent_tag.closest("li"); + const new_li = child_li.cloneNode(true); + tag_object_from_li(new_li).innerText = parent_tag.innerText + "." + descendantry; + parent_li.parentElement.appendChild(new_li); + add_buttons_to_li(new_li); + } + } + + for (const tag_object of get_tag_objects_for_root_tag_and_descendants(child_name)) + { + const li = tag_object.closest("li"); + li.parentElement.removeChild(li); + } + sort_tag_objects(); +} + +function rename_tag(rename_from, rename_to) +{ + if (rename_from == rename_to) + { return; } + for (const tag_object of get_all_tag_objects()) + { + tag_object.innerText = tag_object.innerText.replace(new RegExp(`(^|\\.)${re_escape(rename_from)}($|\\.|\\+)`), `$1${rename_to}$2`); + } + sort_tag_objects(); +} + +function delete_tag(tag_name) +{ + const tag_objects = get_tag_objects_for_tag_and_synonyms(tag_name); + for (const tag_object of tag_objects) + { + const li = tag_object.closest("li"); + li.parentElement.removeChild(li); + } + const child_tags = get_tag_objects_for_descendants(tag_name); + for (const child_tag of child_tags) + { + lift_tag_object(child_tag, tag_name); + } + update_all_li_buttons(); + sort_tag_objects(); +} + +function remove_child(parent_name, child_name) +{ + const tag_objects = get_tag_objects_for_descendant(parent_name, child_name); + const seen_names = new Set(); + for (const tag_object of tag_objects) + { + const descendantry = split_exclusive(tag_object.innerText, parent_name); + if (descendantry === null) + { + continue; + } + const tag_li = tag_object.closest("li"); + if (seen_names.has(descendantry)) + { + tag_li.parentElement.removeChild(tag_li); + continue; + } + tag_object.innerText = descendantry; + update_li_buttons(tag_li); + seen_names.add(descendantry); + } + + const child_as_root = get_tag_objects_for_root_tag_and_descendants(child_name); + const child_as_non_root = get_tag_objects_for_non_root_tag(child_name) + if (child_as_non_root.length > 0) + { + for (const tag_object of child_as_root) + { + const tag_li = tag_object.closest("li"); + tag_li.parentElement.removeChild(tag_li); + } + } + sort_tag_objects(); +} + +function remove_synonym(synonym) +{ + for (const tag_object of get_tag_objects_for_synonym(synonym)) + { + const li = tag_object.closest("li"); + li.parentElement.removeChild(li); + } +} + +function add_buttons_to_li(li) +{ + for (const existing of Array.from(li.getElementsByClassName("confirm_holder"))) + { + li.removeChild(existing); + } + li.appendChild(common.html_to_element(TEMPLATE_BUTTON_DELETE_TAG)); + li.appendChild(common.html_to_element(TEMPLATE_BUTTON_REMOVE_CHILD)); + li.appendChild(common.html_to_element(TEMPLATE_BUTTON_REMOVE_SYNONYM)); + for (const button of Array.from(li.getElementsByClassName("button_with_confirm"))) + { + common.init_button_with_confirm(button); + } + update_li_buttons(li); +} + +function add_buttons_to_all_lis() +{ + const lis = document.getElementById("tag_list").children; + for (const li of lis) + { + add_buttons_to_li(li); + } +} + +function update_li_buttons(li) +{ + const tag_object = tag_object_from_li(li); + const delete_button = li.querySelector(".confirm_holder_delete_tag"); + const unlink_button = li.querySelector(".confirm_holder_remove_child"); + const synonym_button = li.querySelector(".confirm_holder_remove_synonym"); + if (tag_object.innerText.includes("+")) + { + synonym_button.classList.remove("hidden"); + delete_button.classList.add("hidden"); + unlink_button.classList.add("hidden"); + } + else if (tag_object.innerText.includes(".")) + { + unlink_button.classList.remove("hidden"); + delete_button.classList.add("hidden"); + synonym_button.classList.add("hidden"); + } + else + { + delete_button.classList.remove("hidden"); + unlink_button.classList.add("hidden"); + synonym_button.classList.add("hidden"); + } +} + +function update_all_li_buttons() +{ + const lis = document.getElementById("tag_list").children; + for (const li of lis) + { + update_li_buttons(li); + } +} + +function sort_tag_objects() +{ + const start = performance.now(); + function compare(li1, li2) + { + const tag1 = tag_object_from_li(li1).innerText; + const tag2 = tag_object_from_li(li2).innerText; + return tag1 < tag2 ? -1 : 1; + } + const tag_list = document.getElementById("tag_list"); + const lis = Array.from(tag_list.children); + lis.sort(compare); + for (const li of lis) + { + tag_list.appendChild(li); + } + const end = performance.now(); +} + {% if specific_tag is not none %} function on_open(ed, edit_element_map) { @@ -442,5 +833,11 @@ var name_text = document.getElementById("name_text"); var description_text = document.getElementById("description_text"); var ed = new editor.Editor([name_text, description_text], on_open, on_save, on_cancel); {% endif %} + +function on_pageload() +{ + // window.setTimeout(add_buttons_to_all_lis, 0); +} +document.addEventListener("DOMContentLoaded", on_pageload);