diff 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
line wrap: on
line diff
--- a/browser_side/panels.py	Mon Dec 09 15:34:03 2013 +0100
+++ b/browser_side/panels.py	Tue Dec 10 09:07:03 2013 +0100
@@ -34,10 +34,9 @@
 from pyjamas.ui.PopupPanel import PopupPanel
 from pyjamas.ui.StackPanel import StackPanel
 from pyjamas.ui.ClickListener import ClickHandler
-from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN
+from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler
 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
 from pyjamas.ui.MouseListener import MouseHandler
-from pyjamas.ui.ListBox import ListBox
 from pyjamas.Timer import Timer
 from pyjamas import DOM
 from card_game import CardPanel
@@ -49,13 +48,15 @@
 from time import time
 import dialog
 import base_widget
-from richtext import RichTextEditor
+from dialog import ConfirmDialog
+import richtext
 from plugin_xep_0085 import ChatStateMachine
 from pyjamas import Window
 from __pyjamas__ import doc
 from sat_frontends.tools.games import SYMBOLS
 from sat_frontends import constants
-from pyjamas.ui.ContextMenuPopupPanel import ContextMenuPopupPanel
+from pyjamas.ui.FocusListener import FocusHandler
+import logging
 
 
 const = constants.Const  # to directly import 'const' doesn't work
@@ -95,7 +96,7 @@
             self.unibox.setVisible(True)
             self.host.resize()
 
-        RichTextEditor.getOrCreate(self.host, self, onCloseCallback)
+        richtext.RichTextEditor.getOrCreate(self.host, self, onCloseCallback)
         self.host.resize()
 
 
@@ -318,35 +319,64 @@
                 print "Warning: can't manage comment [%s], some keys are missing in microblog data (%s)" % (data["comments"], data.keys())
                 self.comments = False
         if set(("service", "node")).issubset(data.keys()):
+            # comment item
             self.service = data["service"]
             self.node = data["node"]
-            self.hash = (self.service, self.node)
+        else:
+            # main item
+            try:
+                self.service = data['comments_service']
+                self.node = data['comments_node']
+            except KeyError:
+                logging.error("Main item %s is missing its comments information!" % self.id)
+        self.hash = (self.service, self.node)
 
 
-class MicroblogEntry(SimplePanel, ClickHandler):
+class MicroblogEntry(SimplePanel, ClickHandler, FocusHandler, KeyboardHandler):
 
     def __init__(self, blog_panel, mblog_entry):
         SimplePanel.__init__(self)
         self._blog_panel = blog_panel
 
+        self.entry = mblog_entry
         self.author = mblog_entry.author
         self.timestamp = mblog_entry.timestamp
         _datetime = datetime.fromtimestamp(mblog_entry.timestamp)
         self.comments = mblog_entry.comments
+        self.pub_data = (mblog_entry.hash[0], mblog_entry.hash[1], mblog_entry.id)
 
+        self.editable_content = (mblog_entry.xhtml, const._SYNTAX_XHTML) if mblog_entry.xhtml else (mblog_entry.content, None)
         self.panel = HTMLPanel("""
             <div class='mb_entry_header'><span class='mb_entry_author'>%(author)s</span> on <span class='mb_entry_timestamp'>%(timestamp)s</span></div>
+            <div class='mb_entry_delete_update'>
+                <div id="id_delete"></div>
+                <div id="id_update"></div>
+            </div>
             <div class="mb_entry_avatar" id='id_avatar'></div>
-            <div class="mb_entry_dialog">
-                <div class="bubble">%(body)s</div>
+            <div class="mb_entry_dialog" id='id_entry_dialog'></div>
             </div>
             """ % {"author": html_sanitize(self.author),
-                   "timestamp": _datetime,
-                   "body": addURLToText(html_sanitize(mblog_entry.content)) if not mblog_entry.xhtml else mblog_entry.xhtml
+                   "timestamp": _datetime
                     })
         self.avatar = Image(blog_panel.host.getAvatar(self.author))
         self.panel.add(self.avatar, "id_avatar")
+        body = addURLToText(html_sanitize(mblog_entry.content)) if not mblog_entry.xhtml else mblog_entry.xhtml
+        self.bubble = HTML(body)
+        self.bubble.setStyleName("bubble")
+        self.panel.add(self.bubble, "id_entry_dialog")
         self.panel.setStyleName('mb_entry')
+        if self.author == blog_panel.host.whoami.bare:
+            self.delete_label = Label(u"✗")
+            self.delete_label.setTitle("Delete this message")
+            self.panel.add(self.delete_label, "id_delete")
+            self.update_label = Label(u"✍")
+            self.update_label.setTitle("Edit this message")
+            self.panel.add(self.update_label, "id_update")
+            self.delete_label.addClickListener(self)
+            self.update_label.addClickListener(self)
+        else:
+            self.modify_label = self.delete_label = None
+        self.editbox = None
         self.add(self.panel)
         ClickHandler.__init__(self)
         self.addClickListener(self)
@@ -357,8 +387,87 @@
         self.avatar.setUrl(new_avatar)
 
     def onClick(self, sender):
-        print "microblog entry selected (author=%s)" % self.author
-        self._blog_panel.setSelectedEntry(self if self.comments else None)
+        if sender == self:
+            self._blog_panel.setSelectedEntry(self if self.comments else None)
+        elif sender == self.update_label:
+            self._update()
+        elif sender == self.delete_label:
+            self._delete()
+
+    def onKeyUp(self, sender, keycode, modifiers):
+        """Update is done when ENTER key is pressed within the raw editbox"""
+        if sender != self.editbox or not self.editbox.getVisible():
+            return
+        if keycode == KEY_ENTER:
+            self.updateContent()
+
+    def onLostFocus(self, sender):
+        """Update is done when the focus leaves the raw editbox"""
+        if sender != self.editbox or not self.editbox.getVisible():
+            return
+        self.updateContent()
+
+    def updateContent(self, cancel=False):
+        """Send the new content to the backend"""
+        if not self.editbox or not self.editbox.getVisible():
+            return
+        self.panel.remove(self.edit_panel)
+        self.panel.add(self.bubble, "id_entry_dialog")
+        new_text = self.editbox.getText().strip()
+        self.edit_panel = self.editbox = None
+        if cancel or new_text == self.editable_content[0] or new_text == "":
+            return
+        self.editable_content[0] = new_text
+        self._blog_panel.host.bridge.call('updateMblog', None, self.pub_data, self.comments, new_text,
+                                          {'rich': new_text} if self.entry.xhtml else {})
+
+    def _update(self):
+        """Change the bubble to an editbox"""
+        if self.editbox and self.editbox.getVisible():
+            return
+
+        def setOriginalText(text, container):
+            text = text.strip()
+            container.original_text = text
+            self.editbox.setWidth('100%')
+            self.editbox.setText(text)
+            panel = SimplePanel()
+            panel.add(container)
+            panel.setStyleName("bubble")
+            panel.addStyleName('bubble-editbox')
+            self.bubble.removeFromParent()
+            self.panel.add(panel, "id_entry_dialog")
+            self.editbox.setFocus(True)
+            self.editbox.setSelectionRange(len(text), 0)
+            self.edit_panel = panel
+            self.editable_content = (text, container.format if isinstance(container, richtext.RichTextEditor) else None)
+
+        if self.entry.xhtml:
+            options = ('no_recipient', 'no_sync_unibox', 'no_style', 'update_msg', 'no_close')
+
+            def cb(result):
+                self.updateContent(result == richtext.CANCEL)
+
+            editor = richtext.RichTextEditor(self._blog_panel.host, self.panel, cb, options=options)
+            editor.setVisible(True)  # needed to build the toolbar
+            self.editbox = editor.textarea
+            self._blog_panel.host.bridge.call('syntaxConvert', lambda d: setOriginalText(d, editor),
+                                              self.editable_content[0], self.editable_content[1])
+        else:
+            self.editbox = TextArea()
+            self.editbox.addFocusListener(self)
+            self.editbox.addKeyboardListener(self)
+            setOriginalText(self.editable_content[0], self.editbox)
+
+    def _delete(self):
+        """Ask confirmation for deletion"""
+        def confirm_cb(answer):
+            if answer:
+                self._blog_panel.host.bridge.call('deleteMblog', None, self.pub_data, self.comments)
+
+        target = 'message and all its comments' if self.comments else 'comment'
+        _dialog = ConfirmDialog(confirm_cb, text="Do you really want to delete this %s?" % target)
+        _dialog.show()
 
 
 class MicroblogPanel(base_widget.LiberviaWidget):
@@ -527,30 +636,65 @@
                 sub_panel.setStyleName('microblogPanel')
                 sub_panel.addStyleName('subPanel')
                 self.vpanel.insert(sub_panel, parent_idx + 1)
-
+            for idx in xrange(0, len(sub_panel.getChildren())):
+                comment = sub_panel.getIndexedChild(idx)
+                if comment.pub_data[2] == mblog_item.id:
+                    # update an existing comment
+                    sub_panel.remove(comment)
+                    sub_panel.insert(_entry, idx)
+                    return
             # we want comments to be inserted in chronological order
             self._chronoInsert(sub_panel, _entry, reverse=False)
             return
 
-        if mblog_item.id in self.entries:
-            return
+        update = mblog_item.id in self.entries
         _entry = MicroblogEntry(self, mblog_item)
-
+        if update:
+            idx = self.vpanel.getWidgetIndex(self.entries[mblog_item.id])
+            self.vpanel.remove(self.entries[mblog_item.id])
+            self.vpanel.insert(_entry, idx)
+        else:
+            self._chronoInsert(self.vpanel, _entry)
         self.entries[mblog_item.id] = _entry
 
-        self._chronoInsert(self.vpanel, _entry)
-
         if mblog_item.comments:
             # entry has comments, we keep the comment node as a reference
             self.comments[mblog_item.comments_hash] = _entry
             self.host.bridge.call('getMblogComments', self.mblogsInsert, mblog_item.comments_service, mblog_item.comments_node)
 
+    def removeEntry(self, type_, id_):
+        """Remove an entry from the panel
+        @param type_: entry type ('main_item' or 'comment')
+        @param id_: entry id
+        """
+        for child in self.vpanel.getChildren():
+            if isinstance(child, MicroblogEntry) and type_ == 'main_item':
+                print child.pub_data
+                if child.pub_data[2] == id_:
+                    main_idx = self.vpanel.getWidgetIndex(child)
+                    try:
+                        sub_panel = self.vpanel.getWidget(main_idx + 1)
+                        if isinstance(sub_panel, VerticalPanel):
+                            sub_panel.removeFromParent()
+                    except IndexError:
+                        pass
+                    child.removeFromParent()
+                    self.selected_entry = None
+                    break
+            elif isinstance(child, VerticalPanel) and type_ == 'comment':
+                for comment in child.getChildren():
+                    if comment.pub_data[2] == id_:
+                        comment.removeFromParent()
+                        self.selected_entry = None
+                        break
+
     def setSelectedEntry(self, entry):
         if self.selected_entry == entry:
             entry = None
         if self.selected_entry:
             self.selected_entry.removeStyleName('selected_entry')
         if entry:
+            print "microblog entry selected (author=%s)" % entry.author
             entry.addStyleName('selected_entry')
         self.selected_entry = entry