Mercurial > libervia-web
annotate libervia/web/pages/_browser/editor.py @ 1577:9ba532041a8e
browser (chat): implement message reactions.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 22 Nov 2023 16:31:36 +0100 |
parents | eb00d593801d |
children |
rev | line source |
---|---|
1415 | 1 """text edition management""" |
2 | |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
3 from browser import document, window, aio, bind, timer |
1415 | 4 from browser.local_storage import storage |
5 from browser.object_storage import ObjectStorage | |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
6 from javascript import JSON |
1510
5ea06e8b06ed
browser: make bridge API closer to the one use with other frontends:
Goffi <goffi@goffi.org>
parents:
1509
diff
changeset
|
7 from bridge import AsyncBridge as Bridge, BridgeException |
1415 | 8 from template import Template |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
9 import dialog |
1415 | 10 |
1510
5ea06e8b06ed
browser: make bridge API closer to the one use with other frontends:
Goffi <goffi@goffi.org>
parents:
1509
diff
changeset
|
11 bridge = Bridge() |
1415 | 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 = "" | |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
168 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
169 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
170 class BlogEditor: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
171 """Editor class, handling tabs, preview, and submit loading button |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
172 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
173 It's using and HTML form as source |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
174 The form must have: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
175 - a "title" text input |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
176 - a "body" textarea |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
177 - an optional "tags" text input with comma separated tags (may be using Tags |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
178 Editor) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
179 - a "tab_preview" tab element |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
180 """ |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
181 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
182 def __init__(self, form_id="blog_post_edit"): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
183 self.tab_select = window.tab_select |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
184 self.item_tpl = Template('blog/item.html') |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
185 self.form = document[form_id] |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
186 for elt in document.select(".click_to_edit"): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
187 elt.bind("click", self.on_edit) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
188 for elt in document.select('.click_to_preview'): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
189 elt.bind("click", lambda evt: aio.run(self.on_preview(evt))) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
190 self.form.bind("submit", self.on_submit) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
191 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
192 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
193 def on_edit(self, evt): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
194 self.tab_select(evt.target, "tab_edit", "is-active") |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
195 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
196 async def on_preview(self, evt): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
197 """Generate a blog preview from HTML form |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
198 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
199 """ |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
200 print("on preview OK") |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
201 elt = evt.target |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
202 tab_preview = document["tab_preview"] |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
203 tab_preview.clear() |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
204 data = { |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
205 "content_rich": self.form.select_one('textarea[name="body"]').value.strip() |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
206 } |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
207 title = self.form.select_one('input[name="title"]').value.strip() |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
208 if title: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
209 data["title_rich"] = title |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
210 tags_input_elt = self.form.select_one('input[name="tags"]') |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
211 if tags_input_elt is not None: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
212 tags = tags_input_elt.value.strip() |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
213 if tags: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
214 data['tags'] = [t.strip() for t in tags.split(',') if t.strip()] |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
215 try: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
216 preview_data = JSON.parse( |
1509
106bae41f5c8
massive refactoring from camelCase -> snake_case. See backend commit log for more details
Goffi <goffi@goffi.org>
parents:
1420
diff
changeset
|
217 await bridge.mb_preview("", "", JSON.stringify(data)) |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
218 ) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
219 except BridgeException as e: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
220 dialog.notification.show( |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
221 f"Can't generate item preview: {e.message}", |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
222 level="error" |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
223 ) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
224 else: |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
225 self.tab_select(elt, "tab_preview", "is-active") |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
226 item_elt = self.item_tpl.get_elt({ |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
227 "item": preview_data, |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
228 "dates_format": "short", |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
229 }) |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
230 tab_preview <= item_elt |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
231 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
232 def on_submit(self, evt): |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
233 submit_btn = document.select_one("button[type='submit']") |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
234 submit_btn.classList.add("is-loading") |