Mercurial > libervia-web
annotate libervia/pages/_browser/editor.py @ 1466:cff720e26089
pages (blog/view): activate pagination when a single item is shown:
`previous_page_url` and `next_page_url` are set when `item_id` is used. For now, they are
both activated even if there is no item before or after, as it would request to make extra
request to check it. This may be improved in 0.9 by using internal cache.
fix 399
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 30 Sep 2021 17:04:22 +0200 |
parents | 925a7c498cda |
children | 106bae41f5c8 |
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 |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
7 from aio_bridge import 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 |
1420
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
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( |
925a7c498cda
pages (blog/edit): move preview code to new `BlogEditor` class in `editor` module
Goffi <goffi@goffi.org>
parents:
1415
diff
changeset
|
217 await bridge.mbPreview("", "", JSON.stringify(data)) |
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") |