Mercurial > libervia-web
comparison browser_side/panels.py @ 282:ae3ec654836d
browser_side: added blog item modification/deletion
author | souliane <souliane@mailoo.org> |
---|---|
date | Tue, 10 Dec 2013 09:07:03 +0100 |
parents | 2d6bd975a72d |
children | 4f0c2fea358a |
comparison
equal
deleted
inserted
replaced
281:36ce989c73a5 | 282:ae3ec654836d |
---|---|
32 from pyjamas.ui.HTML import HTML | 32 from pyjamas.ui.HTML import HTML |
33 from pyjamas.ui.Image import Image | 33 from pyjamas.ui.Image import Image |
34 from pyjamas.ui.PopupPanel import PopupPanel | 34 from pyjamas.ui.PopupPanel import PopupPanel |
35 from pyjamas.ui.StackPanel import StackPanel | 35 from pyjamas.ui.StackPanel import StackPanel |
36 from pyjamas.ui.ClickListener import ClickHandler | 36 from pyjamas.ui.ClickListener import ClickHandler |
37 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN | 37 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler |
38 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT | 38 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT |
39 from pyjamas.ui.MouseListener import MouseHandler | 39 from pyjamas.ui.MouseListener import MouseHandler |
40 from pyjamas.ui.ListBox import ListBox | |
41 from pyjamas.Timer import Timer | 40 from pyjamas.Timer import Timer |
42 from pyjamas import DOM | 41 from pyjamas import DOM |
43 from card_game import CardPanel | 42 from card_game import CardPanel |
44 from radiocol import RadioColPanel | 43 from radiocol import RadioColPanel |
45 from menu import Menu | 44 from menu import Menu |
47 from tools import html_sanitize, addURLToText, inlineRoot, setPresenceStyle | 46 from tools import html_sanitize, addURLToText, inlineRoot, setPresenceStyle |
48 from datetime import datetime | 47 from datetime import datetime |
49 from time import time | 48 from time import time |
50 import dialog | 49 import dialog |
51 import base_widget | 50 import base_widget |
52 from richtext import RichTextEditor | 51 from dialog import ConfirmDialog |
52 import richtext | |
53 from plugin_xep_0085 import ChatStateMachine | 53 from plugin_xep_0085 import ChatStateMachine |
54 from pyjamas import Window | 54 from pyjamas import Window |
55 from __pyjamas__ import doc | 55 from __pyjamas__ import doc |
56 from sat_frontends.tools.games import SYMBOLS | 56 from sat_frontends.tools.games import SYMBOLS |
57 from sat_frontends import constants | 57 from sat_frontends import constants |
58 from pyjamas.ui.ContextMenuPopupPanel import ContextMenuPopupPanel | 58 from pyjamas.ui.FocusListener import FocusHandler |
59 import logging | |
59 | 60 |
60 | 61 |
61 const = constants.Const # to directly import 'const' doesn't work | 62 const = constants.Const # to directly import 'const' doesn't work |
62 | 63 |
63 | 64 |
93 self.setCellWidth(self.unibox, '100%') | 94 self.setCellWidth(self.unibox, '100%') |
94 self.button.setVisible(True) | 95 self.button.setVisible(True) |
95 self.unibox.setVisible(True) | 96 self.unibox.setVisible(True) |
96 self.host.resize() | 97 self.host.resize() |
97 | 98 |
98 RichTextEditor.getOrCreate(self.host, self, onCloseCallback) | 99 richtext.RichTextEditor.getOrCreate(self.host, self, onCloseCallback) |
99 self.host.resize() | 100 self.host.resize() |
100 | 101 |
101 | 102 |
102 class UniBox(TextArea, MouseHandler): #AutoCompleteTextBox): | 103 class UniBox(TextArea, MouseHandler): #AutoCompleteTextBox): |
103 """This text box is used as a main typing point, for message, microblog, etc""" | 104 """This text box is used as a main typing point, for message, microblog, etc""" |
316 self.comments_node = data['comments_node'] | 317 self.comments_node = data['comments_node'] |
317 except KeyError: | 318 except KeyError: |
318 print "Warning: can't manage comment [%s], some keys are missing in microblog data (%s)" % (data["comments"], data.keys()) | 319 print "Warning: can't manage comment [%s], some keys are missing in microblog data (%s)" % (data["comments"], data.keys()) |
319 self.comments = False | 320 self.comments = False |
320 if set(("service", "node")).issubset(data.keys()): | 321 if set(("service", "node")).issubset(data.keys()): |
322 # comment item | |
321 self.service = data["service"] | 323 self.service = data["service"] |
322 self.node = data["node"] | 324 self.node = data["node"] |
323 self.hash = (self.service, self.node) | 325 else: |
324 | 326 # main item |
325 | 327 try: |
326 class MicroblogEntry(SimplePanel, ClickHandler): | 328 self.service = data['comments_service'] |
329 self.node = data['comments_node'] | |
330 except KeyError: | |
331 logging.error("Main item %s is missing its comments information!" % self.id) | |
332 self.hash = (self.service, self.node) | |
333 | |
334 | |
335 class MicroblogEntry(SimplePanel, ClickHandler, FocusHandler, KeyboardHandler): | |
327 | 336 |
328 def __init__(self, blog_panel, mblog_entry): | 337 def __init__(self, blog_panel, mblog_entry): |
329 SimplePanel.__init__(self) | 338 SimplePanel.__init__(self) |
330 self._blog_panel = blog_panel | 339 self._blog_panel = blog_panel |
331 | 340 |
341 self.entry = mblog_entry | |
332 self.author = mblog_entry.author | 342 self.author = mblog_entry.author |
333 self.timestamp = mblog_entry.timestamp | 343 self.timestamp = mblog_entry.timestamp |
334 _datetime = datetime.fromtimestamp(mblog_entry.timestamp) | 344 _datetime = datetime.fromtimestamp(mblog_entry.timestamp) |
335 self.comments = mblog_entry.comments | 345 self.comments = mblog_entry.comments |
336 | 346 self.pub_data = (mblog_entry.hash[0], mblog_entry.hash[1], mblog_entry.id) |
347 | |
348 self.editable_content = (mblog_entry.xhtml, const._SYNTAX_XHTML) if mblog_entry.xhtml else (mblog_entry.content, None) | |
337 self.panel = HTMLPanel(""" | 349 self.panel = HTMLPanel(""" |
338 <div class='mb_entry_header'><span class='mb_entry_author'>%(author)s</span> on <span class='mb_entry_timestamp'>%(timestamp)s</span></div> | 350 <div class='mb_entry_header'><span class='mb_entry_author'>%(author)s</span> on <span class='mb_entry_timestamp'>%(timestamp)s</span></div> |
351 <div class='mb_entry_delete_update'> | |
352 <div id="id_delete"></div> | |
353 <div id="id_update"></div> | |
354 </div> | |
339 <div class="mb_entry_avatar" id='id_avatar'></div> | 355 <div class="mb_entry_avatar" id='id_avatar'></div> |
340 <div class="mb_entry_dialog"> | 356 <div class="mb_entry_dialog" id='id_entry_dialog'></div> |
341 <div class="bubble">%(body)s</div> | |
342 </div> | 357 </div> |
343 """ % {"author": html_sanitize(self.author), | 358 """ % {"author": html_sanitize(self.author), |
344 "timestamp": _datetime, | 359 "timestamp": _datetime |
345 "body": addURLToText(html_sanitize(mblog_entry.content)) if not mblog_entry.xhtml else mblog_entry.xhtml | |
346 }) | 360 }) |
347 self.avatar = Image(blog_panel.host.getAvatar(self.author)) | 361 self.avatar = Image(blog_panel.host.getAvatar(self.author)) |
348 self.panel.add(self.avatar, "id_avatar") | 362 self.panel.add(self.avatar, "id_avatar") |
363 body = addURLToText(html_sanitize(mblog_entry.content)) if not mblog_entry.xhtml else mblog_entry.xhtml | |
364 self.bubble = HTML(body) | |
365 self.bubble.setStyleName("bubble") | |
366 self.panel.add(self.bubble, "id_entry_dialog") | |
349 self.panel.setStyleName('mb_entry') | 367 self.panel.setStyleName('mb_entry') |
368 if self.author == blog_panel.host.whoami.bare: | |
369 self.delete_label = Label(u"✗") | |
370 self.delete_label.setTitle("Delete this message") | |
371 self.panel.add(self.delete_label, "id_delete") | |
372 self.update_label = Label(u"✍") | |
373 self.update_label.setTitle("Edit this message") | |
374 self.panel.add(self.update_label, "id_update") | |
375 self.delete_label.addClickListener(self) | |
376 self.update_label.addClickListener(self) | |
377 else: | |
378 self.modify_label = self.delete_label = None | |
379 self.editbox = None | |
350 self.add(self.panel) | 380 self.add(self.panel) |
351 ClickHandler.__init__(self) | 381 ClickHandler.__init__(self) |
352 self.addClickListener(self) | 382 self.addClickListener(self) |
353 | 383 |
354 def updateAvatar(self, new_avatar): | 384 def updateAvatar(self, new_avatar): |
355 """Change the avatar of the entry | 385 """Change the avatar of the entry |
356 @param new_avatar: path to the new image""" | 386 @param new_avatar: path to the new image""" |
357 self.avatar.setUrl(new_avatar) | 387 self.avatar.setUrl(new_avatar) |
358 | 388 |
359 def onClick(self, sender): | 389 def onClick(self, sender): |
360 print "microblog entry selected (author=%s)" % self.author | 390 if sender == self: |
361 self._blog_panel.setSelectedEntry(self if self.comments else None) | 391 self._blog_panel.setSelectedEntry(self if self.comments else None) |
392 elif sender == self.update_label: | |
393 self._update() | |
394 elif sender == self.delete_label: | |
395 self._delete() | |
396 | |
397 def onKeyUp(self, sender, keycode, modifiers): | |
398 """Update is done when ENTER key is pressed within the raw editbox""" | |
399 if sender != self.editbox or not self.editbox.getVisible(): | |
400 return | |
401 if keycode == KEY_ENTER: | |
402 self.updateContent() | |
403 | |
404 def onLostFocus(self, sender): | |
405 """Update is done when the focus leaves the raw editbox""" | |
406 if sender != self.editbox or not self.editbox.getVisible(): | |
407 return | |
408 self.updateContent() | |
409 | |
410 def updateContent(self, cancel=False): | |
411 """Send the new content to the backend""" | |
412 if not self.editbox or not self.editbox.getVisible(): | |
413 return | |
414 self.panel.remove(self.edit_panel) | |
415 self.panel.add(self.bubble, "id_entry_dialog") | |
416 new_text = self.editbox.getText().strip() | |
417 self.edit_panel = self.editbox = None | |
418 if cancel or new_text == self.editable_content[0] or new_text == "": | |
419 return | |
420 self.editable_content[0] = new_text | |
421 self._blog_panel.host.bridge.call('updateMblog', None, self.pub_data, self.comments, new_text, | |
422 {'rich': new_text} if self.entry.xhtml else {}) | |
423 | |
424 def _update(self): | |
425 """Change the bubble to an editbox""" | |
426 if self.editbox and self.editbox.getVisible(): | |
427 return | |
428 | |
429 def setOriginalText(text, container): | |
430 text = text.strip() | |
431 container.original_text = text | |
432 self.editbox.setWidth('100%') | |
433 self.editbox.setText(text) | |
434 panel = SimplePanel() | |
435 panel.add(container) | |
436 panel.setStyleName("bubble") | |
437 panel.addStyleName('bubble-editbox') | |
438 self.bubble.removeFromParent() | |
439 self.panel.add(panel, "id_entry_dialog") | |
440 self.editbox.setFocus(True) | |
441 self.editbox.setSelectionRange(len(text), 0) | |
442 self.edit_panel = panel | |
443 self.editable_content = (text, container.format if isinstance(container, richtext.RichTextEditor) else None) | |
444 | |
445 if self.entry.xhtml: | |
446 options = ('no_recipient', 'no_sync_unibox', 'no_style', 'update_msg', 'no_close') | |
447 | |
448 def cb(result): | |
449 self.updateContent(result == richtext.CANCEL) | |
450 | |
451 editor = richtext.RichTextEditor(self._blog_panel.host, self.panel, cb, options=options) | |
452 editor.setVisible(True) # needed to build the toolbar | |
453 self.editbox = editor.textarea | |
454 self._blog_panel.host.bridge.call('syntaxConvert', lambda d: setOriginalText(d, editor), | |
455 self.editable_content[0], self.editable_content[1]) | |
456 else: | |
457 self.editbox = TextArea() | |
458 self.editbox.addFocusListener(self) | |
459 self.editbox.addKeyboardListener(self) | |
460 setOriginalText(self.editable_content[0], self.editbox) | |
461 | |
462 def _delete(self): | |
463 """Ask confirmation for deletion""" | |
464 def confirm_cb(answer): | |
465 if answer: | |
466 self._blog_panel.host.bridge.call('deleteMblog', None, self.pub_data, self.comments) | |
467 | |
468 target = 'message and all its comments' if self.comments else 'comment' | |
469 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to delete this %s?" % target) | |
470 _dialog.show() | |
362 | 471 |
363 | 472 |
364 class MicroblogPanel(base_widget.LiberviaWidget): | 473 class MicroblogPanel(base_widget.LiberviaWidget): |
365 warning_msg_public = "This message will be PUBLIC and everybody will be able to see it, even people you don't know" | 474 warning_msg_public = "This message will be PUBLIC and everybody will be able to see it, even people you don't know" |
366 warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" | 475 warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" |
525 if not sub_panel or not isinstance(sub_panel, VerticalPanel): | 634 if not sub_panel or not isinstance(sub_panel, VerticalPanel): |
526 sub_panel = VerticalPanel() | 635 sub_panel = VerticalPanel() |
527 sub_panel.setStyleName('microblogPanel') | 636 sub_panel.setStyleName('microblogPanel') |
528 sub_panel.addStyleName('subPanel') | 637 sub_panel.addStyleName('subPanel') |
529 self.vpanel.insert(sub_panel, parent_idx + 1) | 638 self.vpanel.insert(sub_panel, parent_idx + 1) |
530 | 639 for idx in xrange(0, len(sub_panel.getChildren())): |
640 comment = sub_panel.getIndexedChild(idx) | |
641 if comment.pub_data[2] == mblog_item.id: | |
642 # update an existing comment | |
643 sub_panel.remove(comment) | |
644 sub_panel.insert(_entry, idx) | |
645 return | |
531 # we want comments to be inserted in chronological order | 646 # we want comments to be inserted in chronological order |
532 self._chronoInsert(sub_panel, _entry, reverse=False) | 647 self._chronoInsert(sub_panel, _entry, reverse=False) |
533 return | 648 return |
534 | 649 |
535 if mblog_item.id in self.entries: | 650 update = mblog_item.id in self.entries |
536 return | |
537 _entry = MicroblogEntry(self, mblog_item) | 651 _entry = MicroblogEntry(self, mblog_item) |
538 | 652 if update: |
653 idx = self.vpanel.getWidgetIndex(self.entries[mblog_item.id]) | |
654 self.vpanel.remove(self.entries[mblog_item.id]) | |
655 self.vpanel.insert(_entry, idx) | |
656 else: | |
657 self._chronoInsert(self.vpanel, _entry) | |
539 self.entries[mblog_item.id] = _entry | 658 self.entries[mblog_item.id] = _entry |
540 | |
541 self._chronoInsert(self.vpanel, _entry) | |
542 | 659 |
543 if mblog_item.comments: | 660 if mblog_item.comments: |
544 # entry has comments, we keep the comment node as a reference | 661 # entry has comments, we keep the comment node as a reference |
545 self.comments[mblog_item.comments_hash] = _entry | 662 self.comments[mblog_item.comments_hash] = _entry |
546 self.host.bridge.call('getMblogComments', self.mblogsInsert, mblog_item.comments_service, mblog_item.comments_node) | 663 self.host.bridge.call('getMblogComments', self.mblogsInsert, mblog_item.comments_service, mblog_item.comments_node) |
664 | |
665 def removeEntry(self, type_, id_): | |
666 """Remove an entry from the panel | |
667 @param type_: entry type ('main_item' or 'comment') | |
668 @param id_: entry id | |
669 """ | |
670 for child in self.vpanel.getChildren(): | |
671 if isinstance(child, MicroblogEntry) and type_ == 'main_item': | |
672 print child.pub_data | |
673 if child.pub_data[2] == id_: | |
674 main_idx = self.vpanel.getWidgetIndex(child) | |
675 try: | |
676 sub_panel = self.vpanel.getWidget(main_idx + 1) | |
677 if isinstance(sub_panel, VerticalPanel): | |
678 sub_panel.removeFromParent() | |
679 except IndexError: | |
680 pass | |
681 child.removeFromParent() | |
682 self.selected_entry = None | |
683 break | |
684 elif isinstance(child, VerticalPanel) and type_ == 'comment': | |
685 for comment in child.getChildren(): | |
686 if comment.pub_data[2] == id_: | |
687 comment.removeFromParent() | |
688 self.selected_entry = None | |
689 break | |
547 | 690 |
548 def setSelectedEntry(self, entry): | 691 def setSelectedEntry(self, entry): |
549 if self.selected_entry == entry: | 692 if self.selected_entry == entry: |
550 entry = None | 693 entry = None |
551 if self.selected_entry: | 694 if self.selected_entry: |
552 self.selected_entry.removeStyleName('selected_entry') | 695 self.selected_entry.removeStyleName('selected_entry') |
553 if entry: | 696 if entry: |
697 print "microblog entry selected (author=%s)" % entry.author | |
554 entry.addStyleName('selected_entry') | 698 entry.addStyleName('selected_entry') |
555 self.selected_entry = entry | 699 self.selected_entry = entry |
556 | 700 |
557 def updateValue(self, type_, jid, value): | 701 def updateValue(self, type_, jid, value): |
558 """Update a jid value in entries | 702 """Update a jid value in entries |