All the code in one batch

pull/1/head
Ozbolt Menegatti 5 years ago
parent 85a10c638f
commit 75755ef3c5

@ -0,0 +1,106 @@
from browser import document
def export_to_xml(model):
xml_document = export_entry(model.entry)
serializer = __new__(XMLSerializer())
return serializer.serializeToString(xml_document);
def export_entry(entry):
parser = __new__(DOMParser())
doc = parser.parseFromString("<entry />", "text/xml")
entry_xml = doc.firstChild
# create head
head = doc.createElement("head")
entry_xml.appendChild(head)
status = doc.createElement("status")
status.textContent = entry.status
head.appendChild(status)
headword = doc.createElement("headword")
headword_lemma = doc.createElement("lemma")
headword_lemma.textContent = entry.headword
headword.appendChild(headword_lemma)
head.appendChild(headword)
grammar = doc.createElement("grammar")
grammar_category = doc.createElement("category")
grammar_category.textContent = entry.grammar
grammar.appendChild(grammar_category)
head.appendChild(grammar)
comment = doc.createElement("comment")
comment.textContent = entry.comment
head.appendChild(comment)
# now lets do body
body = doc.createElement("body")
entry_xml.appendChild(body)
sense_list = doc.createElement("sense_list")
body.appendChild(sense_list)
for sense in entry.senses:
sense_list.appendChild(export_sense(doc, sense))
return doc
def export_sense(doc, sense):
sense_xml = doc.createElement("sense")
label_list = doc.createElement("labelList")
sense_xml.appendChild(label_list)
label = doc.createElement("label")
label.textContent = sense.label
label_list.appendChild(label)
definition_list = doc.createElement("definitionList")
sense_xml.appendChild(definition_list)
definition = doc.createElement("definition")
definition.textContent = sense.definition
definition.setAttribute("type", "indicator")
definition_list.appendChild(definition)
translation_container_list = doc.createElement("translationContainerList")
sense_xml.appendChild(translation_container_list)
for cidx, cluster in enumerate(sense.translations):
for translation in cluster:
translation_container = export_translation(doc, translation)
translation_container.setAttribute("cluster", str(cidx + 1))
translation_container_list.appendChild(translation_container)
return sense_xml
def export_translation(doc, translation):
translation_xml = doc.createElement("translationContainer")
actual_t = doc.createElement("translation")
actual_t.textContent = translation.translation
translation_xml.appendChild(actual_t)
tags = doc.createElement("tagsContainer")
translation_xml.appendChild(tags)
for tagname, tagvalue in translation.tags.items():
name_el = doc.createElement("type")
name_el.textContent = tagname
value_el = doc.createElement("value")
value_el.textContent = tagvalue
tag_el = doc.createElement("tag")
tag_el.appendChild(name_el)
tag_el.appendChild(value_el)
tags.appendChild(tag_el)
return translation_xml

@ -1,10 +1,8 @@
from snabbdom import h, patch
from browser import document from browser import document
from model import Model from model import Model
from view import View from view import View
from update import update from update import update
import modals
model = Model() model = Model()

@ -1,9 +1,7 @@
from message.simple_messages import ListItemClick from message.simple_messages import NoReset, Reset, ModalNotOkClose
from message.translation_edit import EditTranslation, MoveRight, MoveLeft, BinTranslation
from message.show_messages import ShowMenu, ShowEditTranslation, ShowSenseLabelEdit, ShowSenseDefinitionEdit, ShowCommentEdit, ShowAddTranslation
from message.simple_edits import EditSenseLabel, EditSenseDefinition, EditComment
from message.message import msg
def msg(message_class):
from update import update
def callback(arg):
message_instance = message_class(arg)
update.schedule(message_instance)
return callback

@ -1,3 +1,21 @@
from update import update
class Message: class Message:
def update_model(self, model): def update_model(self, model):
raise NotImplementedError("This message does not implement update_model method") raise NotImplementedError("This message does not implement update_model method")
def reset(self):
return True
class ClickMessage(Message):
def __init__(self, event):
event.stopPropagation();
def msg(message_class, params):
def callback(event):
message_instance = message_class(event, params)
update.schedule(message_instance)
return callback

@ -0,0 +1,76 @@
from message.message import Message, ClickMessage
from message.translation_edit import AddTranslation ,EditTranslation
import modals
class GenericShowModal(ClickMessage):
def __init__(self, event, arg):
super().__init__(event)
self.arg = arg
def update_model(self, model):
model.modal_shown = True
class ShowMenu(ClickMessage):
def __init__(self, event, translation):
super().__init__(event)
self.menu_location = (event.pageX, event.pageY)
self.translation = translation
def update_model(self, model):
model.menu_location = self.menu_location
model.menu_shown = True
model.translation = self.translation
class ShowSenseLabelEdit(GenericShowModal):
def update_model(self, model):
super().update_model(model)
model.sense = self.arg
model.modal = modals.edit_sense_label(self.arg)
class ShowSenseDefinitionEdit(GenericShowModal):
def update_model(self, model):
super().update_model(model)
model.sense = self.arg
model.modal = modals.edit_sense_definition(self.arg)
class ShowCommentEdit(ClickMessage):
def update_model(self, model):
model.modal_shown = True
model.modal = modals.edit_comment(model.entry.comment)
class ShowEditTranslation(GenericShowModal):
def update_model(self, model):
model.modal_shown = True
# I need to get number of all clusters and cluster of self.arg
translation = self.arg
for sense in model.entry.senses:
num_clusters = len(sense.translations)
for cidx, cluster in enumerate(sense.translations):
for t in cluster:
if t == translation:
# fount the one!
model.modal = modals.edit_translation(translation, cidx, num_clusters, EditTranslation, (translation, cidx))
return
console.log("Should not be here!")
class ShowAddTranslation(GenericShowModal):
def update_model(self, model):
model.modal_shown = True
chosen_sense = self.arg
for sense in model.entry.senses:
if sense == chosen_sense:
model.modal = modals.edit_translation(sense, -1, len(sense.translations), AddTranslation, sense)
return
console.log("Should not be here!")

@ -0,0 +1,29 @@
from message.message import Message
from browser import document
from model import Sense
class SimpleEditMessage(Message):
def __init__(self, event, prop):
input_element = document.getElementById("modal-input")
self.new_text = input_element.value
self.prop = prop
class EditSenseLabel(SimpleEditMessage):
def update_model(self, model):
sense = self.prop
assert(type(sense) is Sense)
sense.label = self.new_text
class EditSenseDefinition(SimpleEditMessage):
def update_model(self, model):
sense = self.prop
assert(type(sense) is Sense)
sense.definition = self.new_text
class EditComment(SimpleEditMessage):
def update_model(self, model):
model.entry.comment = self.new_text

@ -1,10 +1,29 @@
from message.message import Message from message.message import Message, ClickMessage, msg
from browser import window
import modals
class ListItemClick(Message): class Reset(ClickMessage):
def __init__(self, num): def update_model(self, model):
self.num = num pass
class NoReset(Reset):
def reset(self):
return False
# a "hack" message for reseting modals #
# everytime a modal closes, run 100ms later model.modal = []
# this is done to achieve nice close animation
# after setting model.modal, do view update to actually update the DOM
class _ModalResetDelayed(Message):
def update_model(self, model): def update_model(self, model):
print(self.num) model.modal = []
model.names.splice(self.num, 1)
class ModalNotOkClose(Reset):
def update_model(self, model):
# msg just creates a callback, need to actually run it!
window.setTimeout(lambda: msg(_ModalResetDelayed)(None), 100)

@ -0,0 +1,113 @@
from message.message import Message, ClickMessage
from browser import document, window
from model.translation import TAGS, NewTranslation
class TranslationActionMessage(ClickMessage):
def __init__(self, event, translation):
super().__init__(event)
self.translation = translation
def get_translation_location(entry, translation):
for si, sense in enumerate(entry.senses):
for ci, cluster in enumerate(sense.translations):
for ti, search_translation in enumerate(cluster):
if search_translation == translation:
return (si, ci, ti), (sense, cluster)
class EditTranslation(TranslationActionMessage):
def __init__(self, _, prop):
self.translation, self.old_cluster_idx = prop
def update_model(self, model):
self.translation.translation = document.getElementById("etv").value;
for tag in TAGS.keys():
select = document.getElementById("{}-s".format(tag));
other = document.getElementById("{}-o".format(tag));
if other.value:
self.translation.tags[tag] = other.value
elif select.selectedIndex > 0:
self.translation.tags[tag] = select.options[select.selectedIndex].text
else:
if tag in self.translation.tags:
del self.translation.tags[tag]
new_cluster_idx = int(document.getElementById("cluster-num").value) - 1;
self.handle_cluster_change(new_cluster_idx, model)
def handle_cluster_change(self, new_cluster_idx, model):
if self.old_cluster_idx == new_cluster_idx:
return
# first, find out the correct sense
for sense in model.entry.senses:
for cidx, cluster in enumerate(sense.translations):
for tidx, t in enumerate(cluster):
if t == self.translation:
#found, lets do whatever needs to be done
self.do_cluster_change(sense, cluster, cidx, tidx, new_cluster_idx)
# we are done, lets return
return
def do_cluster_change(self, sense, cluster, cidx, tidx, new_cluster_idx):
# remove the translation from the old cluster
cluster.splice(tidx, 1)
# we maybe are creating a new cluster, handle that
if len(sense.translations) == new_cluster_idx:
sense.translations.append([self.translation])
elif len(sense.translations) > new_cluster_idx:
# lets append the translation to new cluster
sense.translations[new_cluster_idx].append(self.translation)
else:
raise ValueError("Bad new cluster idx :(")
# we still hols cluster reference, check if empty and remove if necessary
# we cant do this earlier since indexes change and yeah, fun stuff
if len(cluster) == 0:
sense.translations.splice(cidx, 1)
class MoveRight(TranslationActionMessage):
def update_model(self, model):
(_, _, idx), (_, cluster) = get_translation_location(model.entry, self.translation)
if idx != len(cluster) - 1:
cluster[idx], cluster[idx + 1] = cluster[idx + 1], cluster[idx]
model.translation = None
class MoveLeft(TranslationActionMessage):
def update_model(self, model):
(_, _, idx), (_, cluster) = get_translation_location(model.entry, self.translation)
if idx != 0 and len(cluster) > 1:
cluster[idx], cluster[idx - 1] = cluster[idx - 1], cluster[idx]
model.translation = None
class BinTranslation(TranslationActionMessage):
def update_model(self, model):
(_, cidx, tidx), (sense, cluster) = get_translation_location(model.entry, self.translation)
if len(cluster) == 1:
# remove empty cluster
sense.translations.splice(cidx, 1)
else:
cluster.splice(tidx, 1)
model.translation = None
class AddTranslation(EditTranslation):
def __init__(self, _, prop):
self.translation = NewTranslation()
self.old_cluster_idx = -1
self.sense = prop
def handle_cluster_change(self, new_cluster_idx, _):
# we need to cheat here
# sense was actually given in constructor
# we make a dummy cluster, cluster_idx and translation_idx
# we give a correct new_cluster_idx
self.do_cluster_change(self.sense, [None, None], None, None, new_cluster_idx)

@ -0,0 +1,78 @@
from lib.snabbdom import h
import message
from browser import document
from model.translation import TAGS
def modal_template(content, title, msg, prop):
reset = message.msg(message.ModalNotOkClose)
return [
h("header", {}, [
h("h3", {}, title),
h("label.close", {"on": {"click": reset}}, "×")]),
h("section.content", {}, content ),
h("footer", {}, [
h("a.button", {"on": {"click": message.msg(msg, prop)}}, "OK"),
h("label.button.dangerous", {"on": {"click": reset}}, "Cancel")])]
def one_question_modal(title, msg, question, current_value, prop):
content = [
h("span", {}, question),
h("label", {}, [
h("input#modal-input", {"props": {"type": "text", "value": current_value}}, "")])]
return modal_template(content, title, msg, prop)
def edit_translation(translation, cluster_idx, num_clusters, cls, prop):
def split_line2(left, right):
return h("div.flex.two", {}, [
h("span.third.span-left-of-input", {}, left), h("span.two-third", {}, right)])
def split_line3(left, center, right):
return h("div.flex.three", {}, [
h("span.third.span-left-of-input", {}, left), h("span.third", {}, center), h("span.third", {}, right)])
def dropdown_right(tag_name):
left = tag_name + ":"
values = TAGS[tag_name]
selected_value = translation.tags[tag_name] if tag_name in translation.tags else None
options = [h("option", {}, [])]
for value in values:
options.append(h("option", {"props": {"selected": selected_value == value}}, value))
center = h("select#{}-s".format(tag_name), {}, options)
right_value = selected_value if selected_value not in values and selected_value is not None else ""
right = h("input#{}-o".format(tag_name),
{"props": {"type": "text", "value": right_value, "placeholder": "drugo"}},
[])
return split_line3(left, center, right)
# first line: transalation itself
content = [split_line2("Prevedek:",
h("input#etv", {"props": {"type": "text", "value": translation.translation}}, ""))]
# cluster number
options = [h("option", {"props": {"selected": idx == cluster_idx}}, str(idx + 1)) for idx in range(num_clusters + 1)]
content.append(split_line2("Stevilka gruce:", h("select#cluster-num", {}, options)))
# tags
content.append(h("h4", {}, "Tags"))
for tag in TAGS.keys():
content.append(dropdown_right(tag))
return modal_template(content, "Translation", cls, prop)
def edit_sense_label(sense):
return one_question_modal("Sense", message.EditSenseLabel, "Edit sense label", sense.label, sense)
def edit_sense_definition(sense):
return one_question_modal("Sense definition", message.EditSenseDefinition, "Edit sense definition", sense.definition, sense)
def edit_comment(comment):
return one_question_modal("Comment", message.EditComment, "Edit comment", comment, None)

@ -1 +1,3 @@
from model.model import Model from model.model import Model
from model.sense import Sense
from model.translation import Translation

@ -13,5 +13,5 @@ class Entry:
self.comment = comment.textContent if comment else "" self.comment = comment.textContent if comment else ""
self.senses = [Sense(sense_xml) for sense_xml in self.senses = [Sense(sense_xml) for sense_xml in
entry_xml.querySelectorAll("body senseList sense")] entry_xml.querySelectorAll("body senseList sense")]

@ -1,25 +1,30 @@
from model.entry import Entry from model.entry import Entry
from browser import window
class Model: class Model:
def __init__(self, names): def __init__(self, names):
# main data stuff
self.entry = None self.entry = None
# report everything that happens!
self.log = [] self.log = []
self.names = ["Ozbolt", "Katarina"]
#runtime info
self.menu_location = (0, 0)
self.menu_shown = False
self.modal = []
self.modal_shown = False
# currently edited stuff
self.translation = None
self.sense = None
def reset(self):
self.menu_shown = False
self.modal_shown = False
def import_xml(self, xml_text): def import_xml(self, xml_text):
parser = __new__(DOMParser()) parser = __new__(DOMParser())
xmlDoc = parser.parseFromString(xml_text, "text/xml") xmlDoc = parser.parseFromString(xml_text, "text/xml")
self.entry = Entry(xmlDoc.querySelector("entry")) self.entry = Entry(xmlDoc.querySelector("entry"))
# type alias Model =
# { shown: Bool
# , editable: Bool
# , entry: Maybe Entry
# , newXml: Bool
# , xml: Maybe String
# , xmlObject: Maybe Xml2.Value
# , log: List LogEntry
# }

@ -1,6 +1,7 @@
from model.example import Example from model.example import Example
from model.translation import Translation from model.translation import Translation
class Sense: class Sense:
def __init__(self, sense_xml): def __init__(self, sense_xml):
label = sense_xml.querySelector("labelList label") label = sense_xml.querySelector("labelList label")

@ -1,3 +1,11 @@
TAGS = {
"podrocje": ["administracija", "antropologija", "arheologija", "arhitektura", "astrologija", "astronomija", "avtomobilizem", "biblično", "bibliotekarstvo", "biologija", "-- anatomija", "-- biokemija", "-- botanika", "-- paleontologija", "-- zoologija", "ekonomija", "-- industrija", "-- ribištvo", "-- rudarstvo", "-- šolstvo", "-- trgovina", "-- turizem", "-- zdravstvo", "-- metalurgija", "-- živilska industrija", "elektrika", "elektronika", "filozofija", "finance", "fizika", "fotografija", "geografija", "-- ekologija", "-- geologija", "-- geodezija", "-- meteorologija", "-- mineralogija", "-- petrografija", "glasba", "gradbeništvo", "heraldika", "jezikoslovje", "kemija", "kmetijstvo", "-- čebelarstvo", "-- gozdarstvo", "-- poljedelstvo", "-- sadjarstvo", "-- vinogradništvo", "-- vrtnarstvo", "konjeništvo", "kulinarika", "letalstvo", "literarna teorija", "-- književnost", "-- pesništvo", "lovstvo", "matematika", "-- geometrija", "-- statistika", "medicina", "mehanika", "mitologija", "moda", "obrtništvo", "-- čevljarstvo", "-- dimnikarstvo", "-- kamnoseštvo", "-- mizarstvo", "-- optika", "oglaševanje", "politika", "pomorstvo", "pošta", "pravo", "psihologija", "računalništvo", "religija", "sociologija", "šport", "tekstil", "telekomunikacije", "-- radiotelevizija", "-- radio", "-- telefonija", "-- televizija", "tiskarstvo", "transport", "-- cestni promet", "-- tovorni promet", "-- zračni promet", "-- železnica", "umetnost", "uprizoritvene umetnosti", "-- film", "-- gledališče", "-- ples", "-- balet", "veterina", "vojska", "zavarovalništvo", "zgodovina"],
"stilne": ["registrske", "-- narečno", "-- sleng", "-- v otroškem govoru", "-- neformalno", "-- formalno", "-- v jeziku mladih", "-- v govoru", "-- v pogovoru", "-- nenevtralno", "konotacijske", "-- šaljivo", "-- ironično", "-- vulgarno", "-- slabšalno", "-- žaljivo", "-- grobo", "-- ljubkovalno", "pragmatične", "-- pregovor", "-- kot pozdrav", "-- kot kletvica", "-- kot žaljivka", "-- kot gesta", "-- kot grožnja", "-- kot nagovor", "-- kot nasvet", "-- kot navodilo", "-- kot opozorilo", "časovne", "-- starinsko"],
"slovnične": ["v pridevniški rabi", "v primerniku", "v presežniku", "v samostalniški rabi", "v ednini", "v dvojini", "v množini", "števno", "neštevno", "v 3. osebi", "v velelniku", "z veliko začetnico"],
"skupne": ["tudi", "predvsem", "običajno", "včasih", "redko"]
}
class Translation: class Translation:
def __init__(self, translation_xml): def __init__(self, translation_xml):
translation = translation_xml.querySelector("translation") translation = translation_xml.querySelector("translation")
@ -8,3 +16,10 @@ class Translation:
t_type = tag_xml.querySelector("type").textContent t_type = tag_xml.querySelector("type").textContent
t_value = tag_xml.querySelector("value").textContent t_value = tag_xml.querySelector("value").textContent
self.tags[t_type] = t_value self.tags[t_type] = t_value
class NewTranslation(Translation):
def __init__(self):
self.translation = ""
self.tags = {}

@ -5,6 +5,12 @@ class Update:
self.view = None self.view = None
def update_model(self): def update_model(self):
# any update resets menu_location, except if it explicitly defines it
for msg in self.message_queue:
if msg.reset():
self.model.reset()
break
for msg in self.message_queue: for msg in self.message_queue:
msg.update_model(self.model) msg.update_model(self.model)

@ -1,5 +1,8 @@
from snabbdom import h, patch from lib.snabbdom import h, patch
from message import ListItemClick, msg from message import *
import random
from export import export_to_xml
class View: class View:
@ -15,17 +18,11 @@ class View:
self.vdom = new_vdom self.vdom = new_vdom
def _view(self): def _view(self):
return View.view_entry(self.model.entry) return h("div", {"on": { "click": msg(Reset) }}, [
View.view_entry(self.model.entry),
def view_list_elements(self): h("button.blk", {"on": { "click": lambda _: console.log(export_to_xml(self.model)) } }, "XML2Console"),
def callback(num): View.view_menu(self.model.menu_location, self.model.menu_shown, self.model.translation),
return msg(lambda: ListItemClick(num)) View.view_modal(self.model.modal_shown, self.model.modal)])
list_elements = [
h('li', {"on": {"click": callback(idx)}}, name)
for idx, name in enumerate(self.model.names)
]
return h('ol', {}, list_elements)
@staticmethod @staticmethod
def view_entry(entry): def view_entry(entry):
@ -34,25 +31,22 @@ class View:
return h("div#entry", {}, [ return h("div#entry", {}, [
h("div#entry-status", {}, entry.status), h("div#entry-status", {}, entry.status),
h("div#entry-header", {}, [ h("div#entry-header", {}, [
h("span#headword", {}, entry.headWord), h("span#headword", {}, entry.headword),
h("span#grammar", {}, entry.grammar), h("span#grammar", {}, entry.grammar),
h("span#comment", {}, entry.comment)]), h("button#comment.warning", {"on": {"click": msg(ShowCommentEdit)}}, entry.comment)]),
h("div#sense-container", {}, view_sense_list)]) h("div#sense-container", {}, view_sense_list)])
@staticmethod @staticmethod
def view_sense(sense, senseNum): def view_sense(sense, senseNum):
examples = [View.view_example(example) for example in sense.examples] examples = [View.view_example(example) for example in sense.examples]
return h("div.elm-div", {}, [ return h("div.elm-div", {}, [
h("div.sense-num", {}, str(senseNum + 1)), h("div.sense-num", {}, str(senseNum + 1)),
h("div.sense", {}, [ h("div.sense", {}, [
h("span.sense-label", {}, sense.label), h("span.sense-label", { "on": { "click": msg(ShowSenseLabelEdit, sense) }}, sense.label),
h("span.sense-definition", {}, sense.definition), h("span.sense-definition", { "on": { "click": msg(ShowSenseDefinitionEdit, sense) }}, sense.definition),
h("div", {}, View.view_translations(sense.translations)), h("div", {}, View.view_translations(sense.translations, sense)),
h("div", {}, examples), h("div", {}, examples)])])
h("input#translation-add",
{"attr": {"type": "button", "value": "+", "title": "Dodaj prevedek / HUN"}},
[])])])
@staticmethod @staticmethod
def view_example(example): def view_example(example):
@ -65,7 +59,7 @@ class View:
h("span", {}, example.translation)])])]) h("span", {}, example.translation)])])])
@staticmethod @staticmethod
def view_translations(translations): def view_translations(translations, sense):
joiner = lambda: h("span.translation-semicolon", {}, ";") joiner = lambda: h("span.translation-semicolon", {}, ";")
result = [] result = []
@ -73,7 +67,8 @@ class View:
result.extend([View.view_one_translation(t) for t in cluster]) result.extend([View.view_one_translation(t) for t in cluster])
result.append(joiner()) result.append(joiner())
result.pop() # remove last ';' and add + button; [-1] does not work in transcrypt
result[len(result) - 1] = h("button", {"on": {"click": msg(ShowAddTranslation, sense)}}, "+")
return result return result
@staticmethod @staticmethod
@ -87,12 +82,30 @@ class View:
elements.append(tags) elements.append(tags)
elements.append(h("span.translation-text", {}, translation.translation)) elements.append(h("span.translation-text", {}, translation.translation))
#elements.append(h("select.translation-select", {}, [ return h("div.translation-div", {"on": {"click": msg(ShowMenu, translation) }}, elements)
# h("option", {"style": {"color": "black"}, {"attr": {"value": "edit", "title": "Spremeni"}}}, "✎"),
# h("option", {"style": {"color": "black"}, {"attr": {"value": "right", "title": "Desno"}}}, "→"),
# h("option", {"style": {"color": "black"}, {"attr": {"value": "left", "title": "Levo"}}}, "←"),
# h("option", {"style": {"color": "black"}, {"attr": {"value": "bin", "title": "Odstrani"}}}, "🗑")]))
return h("div.translation-div", {}, elements)
@staticmethod
def view_menu(location, menu_shown, translation):
style = {
"left": str(location[0]),
"top": str(location[1])
}
if menu_shown:
style["opacity"] = "1"
style["visibility"] = "visible"
return h("span.popup-menu", { "style": style }, [
h("button.shyButton", { "on": {"click": msg(ShowEditTranslation, translation)}}, ""),
h("button.shyButton", { "on": {"click": msg(MoveRight, translation)}}, ""),
h("button.shyButton", { "on": {"click": msg(MoveLeft, translation)}}, ""),
h("button.shyButton", { "on": {"click": msg(BinTranslation, translation)}}, "🗑")])
@staticmethod
def view_modal(modal_shown, modal):
return h("div.modal", {}, [
h("input", { "props": {"type": "checkbox", "checked": modal_shown} }, ""),
h("label.overlay", {}, ""),
h("article", {"on": { "click": NoReset }}, modal)])

Loading…
Cancel
Save