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