Mercurial > libervia-web
diff libervia/web/pages/_browser/editor.py @ 1518:eb00d593801d
refactoring: rename `libervia` to `libervia.web` + update imports following backend changes
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 16:49:28 +0200 |
parents | libervia/pages/_browser/editor.py@5ea06e8b06ed |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/web/pages/_browser/editor.py Fri Jun 02 16:49:28 2023 +0200 @@ -0,0 +1,234 @@ +"""text edition management""" + +from browser import document, window, aio, bind, timer +from browser.local_storage import storage +from browser.object_storage import ObjectStorage +from javascript import JSON +from bridge import AsyncBridge as Bridge, BridgeException +from template import Template +import dialog + +bridge = Bridge() +object_storage = ObjectStorage(storage) +profile = window.profile + +# how often we save forms, in seconds +AUTOSAVE_FREQUENCY = 20 + + +def serialise_form(form_elt): + ret = {} + for elt in form_elt.elements: + if elt.tagName == "INPUT": + if elt.type in ("hidden", "submit"): + continue + elif elt.type == "text": + ret[elt.name] = elt.value + else: + print(f"elt.type not managet yet: {elt.type}") + continue + elif elt.tagName == "TEXTAREA": + ret[elt.name] = elt.value + elif elt.tagName in ("BUTTON",): + continue + else: + print(f"tag not managet yet: {elt.tagName}") + continue + return ret + + +def restore_form(form_elt, data): + for elt in form_elt.elements: + if elt.tagName not in ("INPUT", "TEXTAREA"): + continue + try: + value = data[elt.name] + except KeyError: + continue + else: + elt.value = value + + +def set_form_autosave(form_id): + """Save locally form data regularly and restore it until it's submitted + + form is saved every AUTOSAVE_FREQUENCY seconds and when visibility is lost. + Saved data is restored when the method is called. + Saved data is cleared when the form is submitted. + """ + if profile is None: + print(f"No session started, won't save and restore form {form_id}") + return + + form_elt = document[form_id] + submitted = False + + key = {"profile": profile, "type": "form_autosave", "form": form_id} + try: + form_saved_data = object_storage[key] + except KeyError: + last_serialised = None + else: + print(f"restoring content of form {form_id!r}") + last_serialised = form_saved_data + restore_form(form_elt, form_saved_data) + + def save_form(): + if not submitted: + nonlocal last_serialised + serialised = serialise_form(form_elt) + if serialised != last_serialised: + last_serialised = serialised + print(f"saving content of form {form_id!r}") + object_storage[key] = serialised + + @bind(form_elt, "submit") + def on_submit(evt): + nonlocal submitted + submitted = True + print(f"clearing stored content of form {form_id!r}") + try: + del object_storage[key] + except KeyError: + print("key error") + pass + + @bind(document, "visibilitychange") + def on_visibiliy_change(evt): + print("visibility change") + if document.visibilityState != "visible": + save_form() + + timer.set_interval(save_form, AUTOSAVE_FREQUENCY * 1000) + + +class TagsEditor: + + def __init__(self, input_selector): + print("installing Tags Editor") + self.input_elt = document.select_one(input_selector) + self.input_elt.style.display = "none" + tags_editor_tpl = Template('editor/tags_editor.html') + self.tag_tpl = Template('editor/tag.html') + + editor_elt = tags_editor_tpl.get_elt() + self.input_elt.parent <= editor_elt + self.tag_input_elt = editor_elt.select_one(".tag_input") + self.tag_input_elt.bind("keydown", self.on_key_down) + self._current_tags = None + self.tags_map = {} + for tag in self.current_tags: + self.add_tag(tag, force=True) + + @property + def current_tags(self): + if self._current_tags is None: + self._current_tags = { + t.strip() for t in self.input_elt.value.split(',') if t.strip() + } + return self._current_tags + + @current_tags.setter + def current_tags(self, tags): + self._current_tags = tags + + def add_tag(self, tag, force=False): + tag = tag.strip() + if not force and (not tag or tag in self.current_tags): + return + self.current_tags = self.current_tags | {tag} + self.input_elt.value = ','.join(self.current_tags) + tag_elt = self.tag_tpl.get_elt({"label": tag}) + self.tags_map[tag] = tag_elt + self.tag_input_elt.parent.insertBefore(tag_elt, self.tag_input_elt) + tag_elt.select_one(".click_to_delete").bind( + "click", lambda evt: self.on_tag_click(evt, tag) + ) + + def remove_tag(self, tag): + try: + tag_elt = self.tags_map[tag] + except KeyError: + print(f"trying to remove an inexistant tag: {tag}") + else: + self.current_tags = self.current_tags - {tag} + self.input_elt.value = ','.join(self.current_tags) + tag_elt.remove() + + def on_tag_click(self, evt, tag): + evt.stopPropagation() + self.remove_tag(tag) + + def on_key_down(self, evt): + if evt.key in (",", "Enter"): + evt.stopPropagation() + evt.preventDefault() + self.add_tag(self.tag_input_elt.value) + self.tag_input_elt.value = "" + + +class BlogEditor: + """Editor class, handling tabs, preview, and submit loading button + + It's using and HTML form as source + The form must have: + - a "title" text input + - a "body" textarea + - an optional "tags" text input with comma separated tags (may be using Tags + Editor) + - a "tab_preview" tab element + """ + + def __init__(self, form_id="blog_post_edit"): + self.tab_select = window.tab_select + self.item_tpl = Template('blog/item.html') + self.form = document[form_id] + for elt in document.select(".click_to_edit"): + elt.bind("click", self.on_edit) + for elt in document.select('.click_to_preview'): + elt.bind("click", lambda evt: aio.run(self.on_preview(evt))) + self.form.bind("submit", self.on_submit) + + + def on_edit(self, evt): + self.tab_select(evt.target, "tab_edit", "is-active") + + async def on_preview(self, evt): + """Generate a blog preview from HTML form + + """ + print("on preview OK") + elt = evt.target + tab_preview = document["tab_preview"] + tab_preview.clear() + data = { + "content_rich": self.form.select_one('textarea[name="body"]').value.strip() + } + title = self.form.select_one('input[name="title"]').value.strip() + if title: + data["title_rich"] = title + tags_input_elt = self.form.select_one('input[name="tags"]') + if tags_input_elt is not None: + tags = tags_input_elt.value.strip() + if tags: + data['tags'] = [t.strip() for t in tags.split(',') if t.strip()] + try: + preview_data = JSON.parse( + await bridge.mb_preview("", "", JSON.stringify(data)) + ) + except BridgeException as e: + dialog.notification.show( + f"Can't generate item preview: {e.message}", + level="error" + ) + else: + self.tab_select(elt, "tab_preview", "is-active") + item_elt = self.item_tpl.get_elt({ + "item": preview_data, + "dates_format": "short", + }) + tab_preview <= item_elt + + def on_submit(self, evt): + submit_btn = document.select_one("button[type='submit']") + submit_btn.classList.add("is-loading")