comparison 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
comparison
equal deleted inserted replaced
1517:b8ed9726525b 1518:eb00d593801d
1 """text edition management"""
2
3 from browser import document, window, aio, bind, timer
4 from browser.local_storage import storage
5 from browser.object_storage import ObjectStorage
6 from javascript import JSON
7 from bridge import AsyncBridge as Bridge, BridgeException
8 from template import Template
9 import dialog
10
11 bridge = Bridge()
12 object_storage = ObjectStorage(storage)
13 profile = window.profile
14
15 # how often we save forms, in seconds
16 AUTOSAVE_FREQUENCY = 20
17
18
19 def serialise_form(form_elt):
20 ret = {}
21 for elt in form_elt.elements:
22 if elt.tagName == "INPUT":
23 if elt.type in ("hidden", "submit"):
24 continue
25 elif elt.type == "text":
26 ret[elt.name] = elt.value
27 else:
28 print(f"elt.type not managet yet: {elt.type}")
29 continue
30 elif elt.tagName == "TEXTAREA":
31 ret[elt.name] = elt.value
32 elif elt.tagName in ("BUTTON",):
33 continue
34 else:
35 print(f"tag not managet yet: {elt.tagName}")
36 continue
37 return ret
38
39
40 def restore_form(form_elt, data):
41 for elt in form_elt.elements:
42 if elt.tagName not in ("INPUT", "TEXTAREA"):
43 continue
44 try:
45 value = data[elt.name]
46 except KeyError:
47 continue
48 else:
49 elt.value = value
50
51
52 def set_form_autosave(form_id):
53 """Save locally form data regularly and restore it until it's submitted
54
55 form is saved every AUTOSAVE_FREQUENCY seconds and when visibility is lost.
56 Saved data is restored when the method is called.
57 Saved data is cleared when the form is submitted.
58 """
59 if profile is None:
60 print(f"No session started, won't save and restore form {form_id}")
61 return
62
63 form_elt = document[form_id]
64 submitted = False
65
66 key = {"profile": profile, "type": "form_autosave", "form": form_id}
67 try:
68 form_saved_data = object_storage[key]
69 except KeyError:
70 last_serialised = None
71 else:
72 print(f"restoring content of form {form_id!r}")
73 last_serialised = form_saved_data
74 restore_form(form_elt, form_saved_data)
75
76 def save_form():
77 if not submitted:
78 nonlocal last_serialised
79 serialised = serialise_form(form_elt)
80 if serialised != last_serialised:
81 last_serialised = serialised
82 print(f"saving content of form {form_id!r}")
83 object_storage[key] = serialised
84
85 @bind(form_elt, "submit")
86 def on_submit(evt):
87 nonlocal submitted
88 submitted = True
89 print(f"clearing stored content of form {form_id!r}")
90 try:
91 del object_storage[key]
92 except KeyError:
93 print("key error")
94 pass
95
96 @bind(document, "visibilitychange")
97 def on_visibiliy_change(evt):
98 print("visibility change")
99 if document.visibilityState != "visible":
100 save_form()
101
102 timer.set_interval(save_form, AUTOSAVE_FREQUENCY * 1000)
103
104
105 class TagsEditor:
106
107 def __init__(self, input_selector):
108 print("installing Tags Editor")
109 self.input_elt = document.select_one(input_selector)
110 self.input_elt.style.display = "none"
111 tags_editor_tpl = Template('editor/tags_editor.html')
112 self.tag_tpl = Template('editor/tag.html')
113
114 editor_elt = tags_editor_tpl.get_elt()
115 self.input_elt.parent <= editor_elt
116 self.tag_input_elt = editor_elt.select_one(".tag_input")
117 self.tag_input_elt.bind("keydown", self.on_key_down)
118 self._current_tags = None
119 self.tags_map = {}
120 for tag in self.current_tags:
121 self.add_tag(tag, force=True)
122
123 @property
124 def current_tags(self):
125 if self._current_tags is None:
126 self._current_tags = {
127 t.strip() for t in self.input_elt.value.split(',') if t.strip()
128 }
129 return self._current_tags
130
131 @current_tags.setter
132 def current_tags(self, tags):
133 self._current_tags = tags
134
135 def add_tag(self, tag, force=False):
136 tag = tag.strip()
137 if not force and (not tag or tag in self.current_tags):
138 return
139 self.current_tags = self.current_tags | {tag}
140 self.input_elt.value = ','.join(self.current_tags)
141 tag_elt = self.tag_tpl.get_elt({"label": tag})
142 self.tags_map[tag] = tag_elt
143 self.tag_input_elt.parent.insertBefore(tag_elt, self.tag_input_elt)
144 tag_elt.select_one(".click_to_delete").bind(
145 "click", lambda evt: self.on_tag_click(evt, tag)
146 )
147
148 def remove_tag(self, tag):
149 try:
150 tag_elt = self.tags_map[tag]
151 except KeyError:
152 print(f"trying to remove an inexistant tag: {tag}")
153 else:
154 self.current_tags = self.current_tags - {tag}
155 self.input_elt.value = ','.join(self.current_tags)
156 tag_elt.remove()
157
158 def on_tag_click(self, evt, tag):
159 evt.stopPropagation()
160 self.remove_tag(tag)
161
162 def on_key_down(self, evt):
163 if evt.key in (",", "Enter"):
164 evt.stopPropagation()
165 evt.preventDefault()
166 self.add_tag(self.tag_input_elt.value)
167 self.tag_input_elt.value = ""
168
169
170 class BlogEditor:
171 """Editor class, handling tabs, preview, and submit loading button
172
173 It's using and HTML form as source
174 The form must have:
175 - a "title" text input
176 - a "body" textarea
177 - an optional "tags" text input with comma separated tags (may be using Tags
178 Editor)
179 - a "tab_preview" tab element
180 """
181
182 def __init__(self, form_id="blog_post_edit"):
183 self.tab_select = window.tab_select
184 self.item_tpl = Template('blog/item.html')
185 self.form = document[form_id]
186 for elt in document.select(".click_to_edit"):
187 elt.bind("click", self.on_edit)
188 for elt in document.select('.click_to_preview'):
189 elt.bind("click", lambda evt: aio.run(self.on_preview(evt)))
190 self.form.bind("submit", self.on_submit)
191
192
193 def on_edit(self, evt):
194 self.tab_select(evt.target, "tab_edit", "is-active")
195
196 async def on_preview(self, evt):
197 """Generate a blog preview from HTML form
198
199 """
200 print("on preview OK")
201 elt = evt.target
202 tab_preview = document["tab_preview"]
203 tab_preview.clear()
204 data = {
205 "content_rich": self.form.select_one('textarea[name="body"]').value.strip()
206 }
207 title = self.form.select_one('input[name="title"]').value.strip()
208 if title:
209 data["title_rich"] = title
210 tags_input_elt = self.form.select_one('input[name="tags"]')
211 if tags_input_elt is not None:
212 tags = tags_input_elt.value.strip()
213 if tags:
214 data['tags'] = [t.strip() for t in tags.split(',') if t.strip()]
215 try:
216 preview_data = JSON.parse(
217 await bridge.mb_preview("", "", JSON.stringify(data))
218 )
219 except BridgeException as e:
220 dialog.notification.show(
221 f"Can't generate item preview: {e.message}",
222 level="error"
223 )
224 else:
225 self.tab_select(elt, "tab_preview", "is-active")
226 item_elt = self.item_tpl.get_elt({
227 "item": preview_data,
228 "dates_format": "short",
229 })
230 tab_preview <= item_elt
231
232 def on_submit(self, evt):
233 submit_btn = document.select_one("button[type='submit']")
234 submit_btn.classList.add("is-loading")