aboutsummaryrefslogtreecommitdiff
path: root/html/options_main.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/options_main.js')
-rw-r--r--html/options_main.js781
1 files changed, 0 insertions, 781 deletions
diff --git a/html/options_main.js b/html/options_main.js
deleted file mode 100644
index 4b828f0..0000000
--- a/html/options_main.js
+++ /dev/null
@@ -1,781 +0,0 @@
-/**
- * This file is part of Haketilo.
- *
- * Function: Settings page logic.
- *
- * Copyright (C) 2021 Wojtek Kosior
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * As additional permission under GNU GPL version 3 section 7, you
- * may distribute forms of that code without the copy of the GNU
- * GPL normally required by section 4, provided you include this
- * license notice and, in case of non-source distribution, a URL
- * through which recipients can access the Corresponding Source.
- * If you modify file(s) with this exception, you may extend this
- * exception to your version of the file(s), but you are not
- * obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- *
- * As a special exception to the GPL, any HTML file which merely
- * makes function calls to this code, and for that purpose
- * includes it by reference shall be deemed a separate work for
- * copyright law purposes. If you modify this code, you may extend
- * this exception to your version of the code, but you are not
- * obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
- * license. Although I request that you do not make use of this code in a
- * proprietary program, I am not going to enforce this in court.
- */
-
-#FROM common/storage_client.js IMPORT get_remote_storage
-#FROM common/stored_types.js IMPORT TYPE_PREFIX, TYPE_NAME, \
- list_prefixes
-#FROM common/misc.js IMPORT nice_name, matchers
-#FROM common/sanitize_JSON.js IMPORT parse_json_with_schema
-#FROM html/DOM_helpers.js IMPORT get_template, by_id
-#FROM html/import_frame.js IMPORT get_import_frame
-
-var storage;
-
-const item_li_template = get_template("item_li");
-const bag_component_li_template = get_template("bag_component_li");
-const chbx_component_li_template = get_template("chbx_component_li");
-const radio_component_li_template = get_template("radio_component_li");
-/* Make sure they are later cloned without id. */
-item_li_template.removeAttribute("id");
-bag_component_li_template.removeAttribute("id");
-chbx_component_li_template.removeAttribute("id");
-radio_component_li_template.removeAttribute("id");
-
-function list_set_scrollbar(list_elem)
-{
- const op = ((list_elem.children.length === 1 &&
- list_elem.children[0].classList.contains("hide")) ||
- list_elem.children.length < 1) ? "remove" : "add";
- while (!list_elem.classList.contains("table_wrapper"))
- list_elem = list_elem.parentElement;
- list_elem.classList[op]("always_scrollbar");
-}
-
-function item_li_id(prefix, item)
-{
- return `li_${prefix}_${item}`;
-}
-
-/* Insert into list of bags/pages/scripts/repos */
-function add_li(prefix, item, at_the_end=false)
-{
- let ul = ul_by_prefix[prefix];
- let li = item_li_template.cloneNode(true);
- li.id = item_li_id(prefix, item);
-
- let span = li.firstElementChild;
- span.textContent = item;
-
- let edit_button = span.nextElementSibling;
- edit_button.addEventListener("click", () => edit_item(prefix, item));
-
- let remove_button = edit_button.nextElementSibling;
- remove_button.addEventListener("click",
- () => storage.remove(prefix, item));
-
- let export_button = remove_button.nextElementSibling;
- export_button.addEventListener("click",
- () => export_item(prefix, item));
- if (prefix === TYPE_PREFIX.REPO)
- export_button.remove();
-
- if (!at_the_end) {
- for (let element of ul.ul.children) {
- if (element.id < li.id || element.id.startsWith("work_"))
- continue;
-
- ul.ul.insertBefore(li, element);
- break;
- }
- }
- if (!li.parentElement) {
- if (ul.work_li !== ul.ul.lastElementChild)
- ul.ul.appendChild(li);
- else
- ul.work_li.before(li);
- }
-
- list_set_scrollbar(ul.ul);
-}
-
-const chbx_components_ul = by_id("chbx_components_ul");
-const radio_components_ul = by_id("radio_components_ul");
-
-function chbx_li_id(prefix, item)
-{
- return `cli_${prefix}_${item}`;
-}
-
-function radio_li_id(prefix, item)
-{
- return `rli_${prefix}_${item}`;
-}
-
-//TODO: refactor the 2 functions below
-
-function add_chbx_li(prefix, name)
-{
- if (![TYPE_PREFIX.BAG, TYPE_PREFIX.SCRIPT].includes(prefix))
- return;
-
- let li = chbx_component_li_template.cloneNode(true);
- li.id = chbx_li_id(prefix, name);
- li.setAttribute("data-prefix", prefix);
- li.setAttribute("data-name", name);
-
- let chbx = li.firstElementChild.firstElementChild;
- let span = chbx.nextElementSibling;
-
- span.textContent = nice_name(prefix, name);
-
- chbx_components_ul.appendChild(li);
- list_set_scrollbar(chbx_components_ul);
-}
-
-var radio_component_none_li = by_id("radio_component_none_li");
-
-function add_radio_li(prefix, name)
-{
- if (![TYPE_PREFIX.BAG, TYPE_PREFIX.SCRIPT].includes(prefix))
- return;
-
- let li = radio_component_li_template.cloneNode(true);
- li.id = radio_li_id(prefix, name);
- li.setAttribute("data-prefix", prefix);
- li.setAttribute("data-name", name);
-
- let radio = li.firstElementChild.firstElementChild;
- let span = radio.nextElementSibling;
-
- span.textContent = nice_name(prefix, name);
-
- radio_component_none_li.before(li);
- list_set_scrollbar(radio_components_ul);
-}
-
-/* Used to reset edited repo. */
-function reset_work_repo_li(ul, item, _)
-{
- ul.work_name_input.value = maybe_string(item);
-}
-
-/* Used to get repo data for saving */
-function work_repo_li_data(ul)
-{
- return [ul.work_name_input.value, {}];
-}
-
-const allow_native_scripts_container = by_id("allow_native_scripts_container");
-const page_payload_span = by_id("page_payload");
-
-function set_page_components(components)
-{
- if (components === undefined) {
- page_payload_span.setAttribute("data-payload", "no");
- page_payload_span.textContent = "(None)";
- allow_native_scripts_container.classList.remove("form_disabled");
- } else {
- page_payload_span.setAttribute("data-payload", "yes");
- let [prefix, name] = components;
- page_payload_span.setAttribute("data-prefix", prefix);
- page_payload_span.setAttribute("data-name", name);
- page_payload_span.textContent = nice_name(prefix, name);
- allow_native_scripts_container.classList.add("form_disabled");
- }
-}
-
-const page_allow_chbx = by_id("page_allow_chbx");
-
-/* Used to reset edited page. */
-function reset_work_page_li(ul, item, settings)
-{
- ul.work_name_input.value = maybe_string(item);
- settings = settings || {allow: false, components: undefined};
- page_allow_chbx.checked = !!settings.allow;
-
- set_page_components(settings.components);
-}
-
-function work_page_li_components()
-{
- if (page_payload_span.getAttribute("data-payload") === "no")
- return undefined;
-
- let prefix = page_payload_span.getAttribute("data-prefix");
- let name = page_payload_span.getAttribute("data-name");
- return [prefix, name];
-}
-
-/* Used to get edited page data for saving. */
-function work_page_li_data(ul)
-{
- let url = ul.work_name_input.value;
- let settings = {
- components : work_page_li_components(),
- allow : !!page_allow_chbx.checked
- };
-
- return [url, settings];
-}
-
-const empty_bag_component_li = by_id("empty_bag_component_li");
-var bag_components_ul = by_id("bag_components_ul");
-
-function remove_bag_component_entry(entry)
-{
- const list = entry.parentElement;
- entry.remove();
- list_set_scrollbar(list);
-}
-
-/* Used to construct and update components list of edited bag. */
-function add_bag_components(components)
-{
- for (let component of components) {
- let [prefix, name] = component;
- let li = bag_component_li_template.cloneNode(true);
- li.setAttribute("data-prefix", prefix);
- li.setAttribute("data-name", name);
-
- let span = li.firstElementChild;
- span.textContent = nice_name(prefix, name);
- let remove_but = span.nextElementSibling;
- remove_but.addEventListener("click",
- () => remove_bag_component_entry(li));
- bag_components_ul.appendChild(li);
- }
-
- bag_components_ul.appendChild(empty_bag_component_li);
- list_set_scrollbar(bag_components_ul);
-}
-
-/* Used to reset edited bag. */
-function reset_work_bag_li(ul, item, components)
-{
- components = components || [];
-
- ul.work_name_input.value = maybe_string(item);
- let old_components_ul = bag_components_ul;
- bag_components_ul = old_components_ul.cloneNode(false);
-
- old_components_ul.replaceWith(bag_components_ul);
-
- add_bag_components(components);
-}
-
-/* Used to get edited bag data for saving. */
-function work_bag_li_data(ul)
-{
- let component_li = bag_components_ul.firstElementChild;
-
- let components = [];
-
- /* Last list element is empty li with id set. */
- while (component_li.id === '') {
- components.push([component_li.getAttribute("data-prefix"),
- component_li.getAttribute("data-name")]);
- component_li = component_li.nextElementSibling;
- }
-
- return [ul.work_name_input.value, components];
-}
-
-const script_url_input = by_id("script_url_field");
-const script_sha256_input = by_id("script_sha256_field");
-const script_contents_field = by_id("script_contents_field");
-
-function maybe_string(maybe_defined)
-{
- return maybe_defined === undefined ? "" : maybe_defined + "";
-}
-
-/* Used to reset edited script. */
-function reset_work_script_li(ul, name, data)
-{
- ul.work_name_input.value = maybe_string(name);
- if (data === undefined)
- data = {};
- script_url_input.value = maybe_string(data.url);
- script_sha256_input.value = maybe_string(data.hash);
- script_contents_field.value = maybe_string(data.text);
-}
-
-/* Used to get edited script data for saving. */
-function work_script_li_data(ul)
-{
- return [ul.work_name_input.value, {
- url : script_url_input.value,
- hash : script_sha256_input.value,
- text : script_contents_field.value
- }];
-}
-
-function cancel_work(prefix)
-{
- let ul = ul_by_prefix[prefix];
-
- if (ul.state === UL_STATE.IDLE)
- return;
-
- if (ul.state === UL_STATE.EDITING_ENTRY) {
- add_li(prefix, ul.edited_item);
- }
-
- ul.work_li.classList.add("hide");
- ul.ul.append(ul.work_li);
- list_set_scrollbar(ul.ul);
- ul.state = UL_STATE.IDLE;
-}
-
-function save_work(prefix)
-{
- let ul = ul_by_prefix[prefix];
-
- if (ul.state === UL_STATE.IDLE)
- return;
-
- let [item, data] = ul.get_work_li_data(ul);
-
- /* Here we fire promises and return without waiting. */
-
- if (ul.state === UL_STATE.EDITING_ENTRY)
- storage.replace(prefix, ul.edited_item, item, data);
- if (ul.state === UL_STATE.ADDING_ENTRY)
- storage.set(prefix, item, data);
-
- cancel_work(prefix);
-}
-
-function edit_item(prefix, item)
-{
- cancel_work(prefix);
-
- let ul = ul_by_prefix[prefix];
- let li = by_id(item_li_id(prefix, item));
-
- if (li === null) {
- add_new_item(prefix, item);
- return;
- }
-
- ul.reset_work_li(ul, item, storage.get(prefix, item));
- ul.ul.insertBefore(ul.work_li, li);
- ul.ul.removeChild(li);
- ul.work_li.classList.remove("hide");
- list_set_scrollbar(ul.ul);
-
- ul.state = UL_STATE.EDITING_ENTRY;
- ul.edited_item = item;
-}
-
-const file_downloader = by_id("file_downloader");
-
-function recursively_export_item(prefix, name, added_items, items_data)
-{
- let key = prefix + name;
-
- if (added_items.has(key))
- return;
-
- let data = storage.get(prefix, name);
- if (data === undefined) {
- console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`);
- return;
- }
-
- if (prefix !== TYPE_PREFIX.SCRIPT) {
- let components = prefix === TYPE_PREFIX.BAG ?
- data : [data.components];
-
- for (let [comp_prefix, comp_name] of components) {
- recursively_export_item(comp_prefix, comp_name,
- added_items, items_data);
- }
- }
-
- items_data.push({[key]: data});
- added_items.add(key);
-}
-
-function export_item(prefix, name)
-{
- let added_items = new Set();
- let items_data = [];
- recursively_export_item(prefix, name, added_items, items_data);
- let file = new Blob([JSON.stringify(items_data)],
- {type: "application/json"});
- let url = URL.createObjectURL(file);
- file_downloader.setAttribute("href", url);
- file_downloader.setAttribute("download", prefix + name + ".json");
- file_downloader.click();
- file_downloader.removeAttribute("href");
- URL.revokeObjectURL(url);
-}
-
-function add_new_item(prefix, name)
-{
- cancel_work(prefix);
-
- let ul = ul_by_prefix[prefix];
- ul.reset_work_li(ul);
- ul.work_li.classList.remove("hide");
- ul.ul.appendChild(ul.work_li);
- list_set_scrollbar(ul.ul);
-
- if (name !== undefined)
- ul.work_name_input.value = name;
- ul.state = UL_STATE.ADDING_ENTRY;
-}
-
-const chbx_components_window = by_id("chbx_components_window");
-
-function bag_components()
-{
- chbx_components_window.classList.remove("hide");
- radio_components_window.classList.add("hide");
-
- for (let li of chbx_components_ul.children) {
- let chbx = li.firstElementChild.firstElementChild;
- chbx.checked = false;
- }
-}
-
-function commit_bag_components()
-{
- let selected = [];
-
- for (let li of chbx_components_ul.children) {
- let chbx = li.firstElementChild.firstElementChild;
- if (!chbx.checked)
- continue;
-
- selected.push([li.getAttribute("data-prefix"),
- li.getAttribute("data-name")]);
- }
-
- add_bag_components(selected);
- cancel_components();
-}
-
-const radio_components_window = by_id("radio_components_window");
-var radio_component_none_input = by_id("radio_component_none_input");
-
-function page_components()
-{
- radio_components_window.classList.remove("hide");
- chbx_components_window.classList.add("hide");
-
- radio_component_none_input.checked = true;
-
- let components = work_page_li_components();
- if (components === undefined)
- return;
-
- let [prefix, item] = components;
- let li = by_id(radio_li_id(prefix, item));
-
- if (li === null)
- radio_component_none_input.checked = false;
- else
- li.firstElementChild.firstElementChild.checked = true;
-}
-
-function commit_page_components()
-{
- let components = null;
-
- for (let li of radio_components_ul.children) {
- let radio = li.firstElementChild.firstElementChild;
- if (!radio.checked)
- continue;
-
- components = [li.getAttribute("data-prefix"),
- li.getAttribute("data-name")];
-
- if (radio.id === "radio_component_none_input")
- components = undefined;
-
- break;
- }
-
- if (components !== null)
- set_page_components(components);
- cancel_components();
-}
-
-function cancel_components()
-{
- chbx_components_window.classList.add("hide");
- radio_components_window.classList.add("hide");
-}
-
-const UL_STATE = {
- EDITING_ENTRY : 0,
- ADDING_ENTRY : 1,
- IDLE : 2
-};
-
-const ul_by_prefix = {
- [TYPE_PREFIX.REPO] : {
- ul : by_id("repos_ul"),
- work_li : by_id("work_repo_li"),
- work_name_input : by_id("repo_url_field"),
- reset_work_li : reset_work_repo_li,
- get_work_li_data : work_repo_li_data,
- state : UL_STATE.IDLE,
- edited_item : undefined,
- },
- [TYPE_PREFIX.PAGE] : {
- ul : by_id("pages_ul"),
- work_li : by_id("work_page_li"),
- work_name_input : by_id("page_url_field"),
- reset_work_li : reset_work_page_li,
- get_work_li_data : work_page_li_data,
- select_components : page_components,
- commit_components : commit_page_components,
- state : UL_STATE.IDLE,
- edited_item : undefined,
- },
- [TYPE_PREFIX.BAG] : {
- ul : by_id("bags_ul"),
- work_li : by_id("work_bag_li"),
- work_name_input : by_id("bag_name_field"),
- reset_work_li : reset_work_bag_li,
- get_work_li_data : work_bag_li_data,
- select_components : bag_components,
- commit_components : commit_bag_components,
- state : UL_STATE.IDLE,
- edited_item : undefined,
- },
- [TYPE_PREFIX.SCRIPT] : {
- ul : by_id("scripts_ul"),
- work_li : by_id("work_script_li"),
- work_name_input : by_id("script_name_field"),
- reset_work_li : reset_work_script_li,
- get_work_li_data : work_script_li_data,
- state : UL_STATE.IDLE,
- edited_item : undefined,
- }
-}
-
-/*
- * Newer browsers could utilise `text' method of File objects.
- * Older ones require FileReader.
- */
-
-function _read_file(file, resolve, reject)
-{
- let reader = new FileReader();
-
- reader.onload = () => resolve(reader.result);
- reader.onerror = () => reject(reader.error);
- reader.readAsText(file);
-}
-
-function read_file(file)
-{
- return new Promise((resolve, reject) =>
- _read_file(file, resolve, reject));
-}
-
-const url_regex = /^[a-z0-9]+:\/\/[^/]+\.[^/]{2,}(\/[^?#]*)?$/;
-const empty_regex = /^$/;
-
-const settings_schema = [
- [{}, "matchentry", "minentries", 1,
- new RegExp(`^${TYPE_PREFIX.SCRIPT}`), {
- /* script data */
- "url": ["optional", url_regex, "or", empty_regex],
- "sha256": ["optional", matchers.sha256, "or", empty_regex],
- "text": ["optional", "string"]
- },
- new RegExp(`^${TYPE_PREFIX.BAG}`), [
- "optional",
- [matchers.component, "repeat"],
- "default", undefined
- ],
- new RegExp(`^${TYPE_PREFIX.PAGE}`), {
- /* page data */
- "components": ["optional", matchers.component]
- }], "repeat"
-];
-
-const import_window = by_id("import_window");
-let import_frame;
-
-async function import_from_file(event)
-{
- let files = event.target.files;
- if (files.length < 1)
- return;
-
- import_window.classList.remove("hide");
- import_frame.show_loading();
-
- try {
- const file = await read_file(files[0]);
- var result = parse_json_with_schema(settings_schema, file);
- } catch(e) {
- import_frame.show_error("Bad file :(", "" + e);
- return;
- }
-
- import_frame.show_selection(result);
-}
-
-const file_opener_form = by_id("file_opener_form");
-
-function hide_import_window()
-{
- import_window.classList.add("hide");
-
- /*
- * Reset file <input>. Without this, a second attempt to import the same
- * file would result in "change" event not happening on <input> element.
- */
- file_opener_form.reset();
-}
-
-async function initialize_import_facility()
-{
- let import_but = by_id("import_but");
- let file_opener = by_id("file_opener");
-
- import_but.addEventListener("click", () => file_opener.click());
- file_opener.addEventListener("change", import_from_file);
-
- import_frame = await get_import_frame();
- import_frame.onclose = hide_import_window;
- import_frame.style_table("has_bottom_line", "always_scrollbar",
- "has_upper_line", "tight_table");
-}
-
-/*
- * If url has a target appended, e.g.
- * chrome-extension://hnhmbnpohhlmhehionjgongbnfdnabdl/html/options.html#smyhax
- * that target will be split into prefix and item name (e.g. "s" and "myhax")
- * and editing of that respective item will be started.
- *
- * We don't need to worry about the state of the page (e.g. some editing being
- * in progress) in jump_to_item() - this function is called at the beginning,
- * together with callbacks being assigned to buttons, so it is safe to assume
- * lists are initialized with items and page is in its virgin state with regard
- * to everything else.
- */
-function jump_to_item(url_with_item)
-{
- const [dummy1, base_url, dummy2, target] =
- /^([^#]*)(#(.*))?$/i.exec(url_with_item);
- if (target === undefined)
- return;
-
- const prefix = target.substring(0, 1);
-
- if (!list_prefixes.includes(prefix)) {
- history.replaceState(null, "", base_url);
- return;
- }
-
- by_id(`show_${TYPE_NAME[prefix]}s`).checked = true;
- edit_item(prefix, decodeURIComponent(target.substring(1)));
-}
-
-async function main()
-{
- storage = await get_remote_storage();
-
- for (let prefix of list_prefixes) {
- for (let item of storage.get_all_names(prefix).sort()) {
- add_li(prefix, item, true);
- add_chbx_li(prefix, item);
- add_radio_li(prefix, item);
- }
-
- let name = TYPE_NAME[prefix];
-
- let add_but = by_id(`add_${name}_but`);
- let discard_but = by_id(`discard_${name}_but`);
- let save_but = by_id(`save_${name}_but`);
-
- add_but.addEventListener("click", () => add_new_item(prefix));
- discard_but.addEventListener("click", () => cancel_work(prefix));
- save_but.addEventListener("click", () => save_work(prefix));
-
- if ([TYPE_PREFIX.REPO, TYPE_PREFIX.SCRIPT].includes(prefix))
- continue;
-
- let ul = ul_by_prefix[prefix];
-
- let commit_components_but = by_id(`commit_${name}_components_but`);
- let cancel_components_but = by_id(`cancel_${name}_components_but`);
- let select_components_but = by_id(`select_${name}_components_but`);
-
- commit_components_but
- .addEventListener("click", ul.commit_components);
- select_components_but
- .addEventListener("click", ul.select_components);
- cancel_components_but.addEventListener("click", cancel_components);
- }
-
- jump_to_item(document.URL);
-
- storage.add_change_listener(handle_change);
-
- await initialize_import_facility();
-}
-
-function handle_change(change)
-{
- if (change.old_val === undefined) {
- add_li(change.prefix, change.item);
- add_chbx_li(change.prefix, change.item);
- add_radio_li(change.prefix, change.item);
-
- return;
- }
-
- if (change.new_val !== undefined)
- return;
-
- let ul = ul_by_prefix[change.prefix];
- if (ul.state === UL_STATE.EDITING_ENTRY &&
- ul.edited_item === change.item) {
- ul.state = UL_STATE.ADDING_ENTRY;
- return;
- }
-
- let uls_creators = [[ul.ul, item_li_id]];
-
- if ([TYPE_PREFIX.BAG, TYPE_PREFIX.SCRIPT].includes(change.prefix)) {
- uls_creators.push([chbx_components_ul, chbx_li_id]);
- uls_creators.push([radio_components_ul, radio_li_id]);
- }
-
- for (let [components_ul, id_creator] of uls_creators) {
- let li = by_id(id_creator(change.prefix, change.item));
- components_ul.removeChild(li);
- list_set_scrollbar(components_ul);
- }
-}
-
-main();