From 4ef6c5c5ae221801f1911800431bcf8c0ea6f1d2 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Mon, 30 Jan 2023 19:01:15 +0100 Subject: [PATCH] boxesserver: refactor * move & refactor JS to static * add some typing * move `head` to functions for reusability * fix wrong separator for `hreflang` value * fix various html validator errors --- scripts/boxesserver | 247 ++++++++++++++++++++------------------------ static/self.js | 79 ++++++++++++++ 2 files changed, 193 insertions(+), 133 deletions(-) create mode 100644 static/self.js diff --git a/scripts/boxesserver b/scripts/boxesserver index 4013fe2..d564f8b 100755 --- a/scripts/boxesserver +++ b/scripts/boxesserver @@ -28,7 +28,6 @@ import threading import time import traceback from typing import Any, NoReturn -from urllib.parse import parse_qs from urllib.parse import unquote_plus, quote from wsgiref.simple_server import make_server @@ -180,36 +179,13 @@ class BServer: """""" % (e, ' selected="selected"' if (e == (default or a.default)) or (str(e) == str(default or a.default)) else "", _(e)) for e in a.choices) - input = """\n""".format(name, name, name + "_id", name + "_description", options) + input = """\n""".format(name, name, name + "_id", name + "_description", options) else: - input = """""" % \ + input = """""" % \ (name, name, name + "_id", name + "_description", default or a.default) return row % input - scripts = """ - -""" - def args2html_cached(self, name, box, lang, action="", defaults={}): if defaults == {}: key = (name, lang.info().get('language', None), action) @@ -224,36 +200,30 @@ class BServer: lang_name = lang.info().get('language', None) langparam = "" - lang_attr = "" if lang_name: langparam = "?language=" + lang_name - lang_attr = f" lang=\"{lang_name}\"" - result = [f""" - + result = [f"""{self.genHTMLStart(lang)} {_("%s - Boxes") % _(name)} - - - + {self.genHTMLMeta()} {self.genHTMLMetaLanguageLink()} - - {self.scripts % (len(box.argparser._action_groups) - 3)} - + {self.genHTMLCSS()} + {self.genHTMLJS()} - +

{_("Boxes.py")}

-self-Logo +self-Logo

-

{_(name)}

+

{_(name)}

{_(box.__doc__) if box.__doc__ else ""}

"""] @@ -264,7 +234,7 @@ class BServer: if len(group._group_actions) == 1 and isinstance(group._group_actions[0], argparse._HelpAction): continue prefix = getattr(group, "prefix", None) - result.append(f'''

{_(group.title)}

\n\n''') + result.append(f'''

{_(group.title)}

\n\n''') for a in group._group_actions: if a.dest in ("input", "output"): @@ -293,50 +263,33 @@ class BServer: .replace('src="static/', f'src="{self.static_url}/')) result.append(f'''
- +Picture of box.
-{self.footer(lang)} +{self.genPagePartFooter(lang)} + ''') return (s.encode("utf-8") for s in result) - def menu(self, lang): + def genPageMenu(self, lang): _ = lang.gettext lang_name = lang.info().get('language', None) langparam = "" - lang_attr = "" if lang_name: langparam = "?language=" + lang_name - lang_attr = f" lang=\"{lang_name}\"" - result = [f""" - + result = [f"""{self.genHTMLStart(lang)} {_("Boxes.py")} - - - + {self.genHTMLMeta()} {self.genHTMLMetaLanguageLink()} - -""", -""" {self.scripts % len(self.groups)} - + {self.genHTMLCSS()} + {self.genHTMLJS()} - +

{_("Boxes.py")}

@@ -348,19 +301,26 @@ f""" {self.scripts % len(self.groups)}
-self-Logo +self-Logo

@@ -380,21 +339,44 @@ f""" {self.scripts % len(self.groups)}
-{self.footer(lang)} +{self.genPagePartFooter(lang)} """) return (s.encode("utf-8") for s in result) + def genHTMLStart(self, lang) -> str: + lang_attr = lang.info().get("language", "") + + if lang_attr != "": + return f"""""" + + return "" + + def genHTMLMeta(self) -> str: + return f''' + + + + + + ''' + def genHTMLMetaLanguageLink(self) -> str: """Generates meta language list for search engines.""" languages = self.getLanguages() s = "" for language in languages: - s += f"\n" + s += f'\n' return s + def genHTMLCSS(self) -> str: + return f'' + + def genHTMLJS(self) -> str: + return f'' + def genHTMLLanguageSelection(self, lang) -> str: """Generates a dropdown selection for the language change.""" current_language = lang.info().get('language', '') @@ -415,7 +397,7 @@ f""" {self.scripts % len(self.groups)} """ - def footer(self, lang) -> str: + def genPagePartFooter(self, lang) -> str: _ = lang.gettext return """ @@ -430,19 +412,23 @@ f""" {self.scripts % len(self.groups)} """ - def errorMessage(self, name, e, _): - return [(f""" + def genPageError(self, name, e, lang) -> list[bytes]: + """Generates a error page.""" + _ = lang.gettext + + h = f"""{self.genHTMLStart(lang)} {_("Error generating %s") % _(name)} - + {self.genHTMLMeta()} + -

{_("An error occurred!")}

""" + -"".join("

%s

" % html.escape(s) for s in str(e).split("\n")) + +

{_("An error occurred!")}

""" - - -""").encode("utf-8") ] + for s in str(e).split("\n"): + h += f"

{html.escape(s)}

\n" + h += "" + return [h.encode("utf-8")] def serveStatic(self, environ, start_response): filename = environ["PATH_INFO"][len("/static/"):] @@ -501,10 +487,8 @@ f""" {self.scripts % len(self.groups)} status = '200 OK' headers = [('Content-type', 'text/html; charset=utf-8'), ('X-XSS-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), ('x-frame-options', 'SAMEORIGIN'), ('Referrer-Policy', 'no-referrer')] - d = parse_qs(environ.get('QUERY_STRING', '')) name = environ["PATH_INFO"][1:] - args = [unquote_plus(arg) for arg in - environ.get('QUERY_STRING', '').split("&")] + args = [unquote_plus(arg) for arg in environ.get('QUERY_STRING', '').split("&")] render = "0" for arg in args: if arg.startswith("render="): @@ -519,7 +503,7 @@ f""" {self.scripts % len(self.groups)} lang_name = lang.info().get('language', None) if lang_name not in self._cache: - self._cache[lang_name] = list(self.menu(lang)) + self._cache[lang_name] = list(self.genPageMenu(lang)) return self._cache[lang_name] if name == "TrayLayout2": @@ -536,56 +520,53 @@ f""" {self.scripts % len(self.groups)} defaults[k] = html.escape(v, True) start_response(status, headers) return self.args2html_cached(name, box, lang, "./" + name, defaults=defaults) - else: - args = ["--" + arg for arg in args if not arg.startswith("render=")] - try: - box.parseArgs(args) - except (ArgumentParserError) as e: - start_response(status, headers) - return self.errorMessage(name, e, _) - if name == "TrayLayout": - start_response(status, headers) - box.fillDefault(box.x, box.y) - layout2 = boxes.generators.traylayout.TrayLayout2(self, webargs=True) - layout2.argparser.set_defaults(layout=str(box)) - return self.args2html(name, layout2, lang, action="TrayLayout2") - if name == "TrayLayout2": - try: - box.parse(box.layout.split("\n")) - except Exception as e: - start_response(status, headers) - return self.errorMessage(name, e, _) + args = ["--" + arg for arg in args if not arg.startswith("render=")] + try: + box.parseArgs(args) + except ArgumentParserError as e: + start_response(status, headers) + return self.genPageError(name, e, lang) + if name == "TrayLayout": + start_response(status, headers) + box.fillDefault(box.x, box.y) + layout2 = boxes.generators.traylayout.TrayLayout2(self, webargs=True) + layout2.argparser.set_defaults(layout=str(box)) + return self.args2html(name, layout2, lang, action="TrayLayout2") + if name == "TrayLayout2": try: - fd, box.output = tempfile.mkstemp() - box.metadata["url"] = self.getURL(environ) - box.open() - box.render() - box.close() + box.parse(box.layout.split("\n")) except Exception as e: - if not isinstance(e, ValueError): - print("Exception during rendering:") - traceback.print_exc() - start_response("500 Internal Server Error", - headers) - return self.errorMessage(name, e, _) + start_response(status, headers) + return self.genPageError(name, e, lang) - http_headers = box.formats.http_headers.get( - box.format, - [('Content-type', 'application/unknown; charset=utf-8')])[:] - # Prevent crawlers. - http_headers.append(('X-Robots-Tag', 'noindex,nofollow')) + try: + fd, box.output = tempfile.mkstemp() + box.metadata["url"] = self.getURL(environ) + box.open() + box.render() + box.close() + except Exception as e: + if not isinstance(e, ValueError): + print("Exception during rendering:") + traceback.print_exc() + start_response("500 Internal Server Error", headers) + return self.genPageError(name, e, lang) - if box.format != "svg" or render == "2": - extension = box.format - if extension == "svg_Ponoko": - extension = "svg" - http_headers.append(('Content-Disposition', f'attachment; filename="{box.__class__.__name__}.{extension}"')) - start_response(status, http_headers) - result = open(box.output, 'rb').readlines() - os.close(fd) - os.remove(box.output) - return (l for l in result) + http_headers = box.formats.http_headers.get(box.format, [('Content-type', 'application/unknown; charset=utf-8')])[:] + # Prevent crawlers. + http_headers.append(('X-Robots-Tag', 'noindex,nofollow')) + + if box.format != "svg" or render == "2": + extension = box.format + if extension == "svg_Ponoko": + extension = "svg" + http_headers.append(('Content-Disposition', f'attachment; filename="{box.__class__.__name__}.{extension}"')) + start_response(status, http_headers) + result = open(box.output, 'rb').readlines() + os.close(fd) + os.remove(box.output) + return (l for l in result) if __name__ == "__main__": diff --git a/static/self.js b/static/self.js new file mode 100644 index 0000000..f5421be --- /dev/null +++ b/static/self.js @@ -0,0 +1,79 @@ +function showThumbnail(img_link) { + const img = document.getElementById("sample-preview"); + img.src = img_link; + img.style.height = "auto"; + img.style.display = "block"; +} + +function showThumbnailEvt(evt) { + const url = evt.target.getAttribute("data-thumbnail"); + showThumbnail(url); +} + +function hideThumbnail() { + const img = document.getElementById("sample-preview"); + img.style.display = "none"; +} + + +function toggleId(id) { + const e = document.getElementById(id); + const h = document.getElementById("h-" + id); + if (e.style.display == null || e.style.display === "none") { + e.style.display = "block"; + h.classList.add("open"); + h.setAttribute("aria-expanded", "true"); + } else { + e.style.display = "none"; + h.classList.remove("open"); + h.setAttribute("aria-expanded", "false"); + } +} + +function toggleEl(el) { + const id = el.getAttribute("data-id"); + toggleId(id); +} + +function toggleEvt(evt) { + const id = evt.target.getAttribute("data-id"); + // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role#examples + if (evt instanceof MouseEvent) { + toggleId(id); + } + if (evt instanceof KeyboardEvent && (evt.key === "Enter" || evt.key === " ")) { + evt.preventDefault(); + toggleId(id); + } +} + +function initToggle(el, hide = false) { + // Add event handler. + el.addEventListener("click", toggleEvt); + el.addEventListener("keydown", toggleEvt); + // Hide. + if (hide) { + toggleEl(el); + } +} + +function initThumbnail(el) { + // Add event handler. + el.addEventListener("mouseenter", showThumbnailEvt); + el.addEventListener("mouseleave", hideThumbnail); +} + +function initPage(num_hide = null) { + const h = document.getElementsByClassName("toggle"); + let i = 0; + for (let el of h) { + if (num_hide === null || i < num_hide) { + initToggle(el, true); + } else { + initToggle(el, false); + } + i++; + } + const t = document.getElementsByClassName("thumbnail"); + for (let el of t) initThumbnail(el); +} \ No newline at end of file