changeset 395:98cd5387d291

browser_side: new microblogs are not using the rich text editor by default: - a button allows to switch from raw to rich text, and vice versa - raw text microblogs are not submitted anymore when the focus is lost, only when <enter> is pressed
author souliane <souliane@mailoo.org>
date Mon, 10 Mar 2014 22:39:26 +0100
parents ee61b0765d6c
children a71fcc27f231
files browser_side/base_panels.py browser_side/panels.py browser_side/richtext.py public/libervia.css
diffstat 4 files changed, 100 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/browser_side/base_panels.py	Thu Mar 06 01:06:19 2014 +0100
+++ b/browser_side/base_panels.py	Mon Mar 10 22:39:26 2014 +0100
@@ -343,6 +343,20 @@
         """
         raise NotImplementedError
 
+    def setOriginalContent(self, content):
+        """Use this method with care! Content initialization should normally be
+        done with self.setContent. This method exists to let you trick the editor,
+        e.g. for self.modified to return True also when nothing has been modified.
+        @param content: dict
+        """
+        self._original_content = content
+
+    def getOriginalContent(self):
+        """
+        @return the original content before modification (dict)
+        """
+        return self._original_content
+
     def modified(self, content=None):
         """Check if the content has been modified.
         Remark: we don't use the direct comparison because we want to ignore empty elements
@@ -424,21 +438,24 @@
 class LightTextEditor(BaseTextEditor, HTML, FocusHandler, KeyboardHandler):
     """Manage a simple text editor whith the HTML 5 "contenteditable" property."""
 
-    def __init__(self, content=None, modifiedCb=None, afterEditCb=None, single_line=False, enhance_display=True):
+    def __init__(self, content=None, modifiedCb=None, afterEditCb=None, single_line=False, enhance_display=True, listen_focus=None):
         """
         @param content
         @param modifiedCb
         @param afterEditCb
-        @param single_line: set to True to manage a single line editor. In that case
-        the edition would be terminated when the focus is lost or <enter> key is pressed.
+        @param single_line: set to True to manage a single line editor. In that
+        case the edition will be terminated when the <enter> key is pressed.
+        @param listen_focus: set to True to terminate the edition when the
+        focus is lost. Leave to None in order to use single_line's value.
         @param enhance_display: if True, the display text will be enhanced with addURLToText
         """
         HTML.__init__(self)
-        if single_line:
+        self.__single_line = single_line
+        self.__enhance_display = enhance_display
+        self.__listen_focus = single_line if listen_focus is None else listen_focus
+        if self.__listen_focus:
             FocusHandler.__init__(self)
         KeyboardHandler.__init__(self)
-        self.__single_line = single_line
-        self.__enhance_display = enhance_display
         strproc = lambda text: html_sanitize(html_strip(text)) if self.__single_line else html_strip(text)
         BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb)
 
@@ -455,12 +472,12 @@
         self.getElement().setAttribute('contenteditable', 'true' if edit else 'false')
         BaseTextEditor.edit(self, edit)
         if edit:
-            if self.__single_line:
+            if self.__listen_focus:
                 self.addFocusListener(self)
             self.addKeyboardListener(self)
         else:
             self.setDisplayContent()
-            if self.__single_line:
+            if self.__listen_focus:
                 if self in self._focusListeners:
                     self.removeFocusListener(self)
             if self in self._keyboardListeners:
@@ -483,8 +500,10 @@
     def onKeyPress(self, sender, keycode, modifiers):
         if not self.__single_line:
             return
-        if keycode == KEY_ENTER:
-            self.setFocus(False)  # finish the edition
+        if keycode == KEY_ENTER:  # finish the edition
+            self.setFocus(False)
+            if not self.__listen_focus:
+                self.edit(False)
 
     def onLostFocus(self, sender):
         """Finish the edition when focus is lost"""
--- a/browser_side/panels.py	Thu Mar 06 01:06:19 2014 +0100
+++ b/browser_side/panels.py	Mon Mar 10 22:39:26 2014 +0100
@@ -388,7 +388,8 @@
                      'author', 'updated', 'published', 'comments', 'service', 'node',
                      'comments_service', 'comments_node']:
             getter = lambda attr: lambda inst: getattr(inst._base_item, attr)
-            setattr(MicroblogEntry, attr, property(getter(attr)))
+            setter = lambda attr: lambda inst, value: setattr(inst._base_item, attr, value)
+            setattr(MicroblogEntry, attr, property(getter(attr), setter(attr)))
 
         SimplePanel.__init__(self)
         self._blog_panel = blog_panel
@@ -409,7 +410,7 @@
         entry_avatar.add(self.avatar)
         self.panel.add(entry_avatar)
 
-        self.entry_dialog = SimplePanel()
+        self.entry_dialog = HorizontalPanel()
         self.entry_dialog.setStyleName('mb_entry_dialog')
         self.panel.add(self.entry_dialog)
 
@@ -473,7 +474,7 @@
         elif sender == self.delete_label:
             self._delete()
         elif sender == self.update_label:
-            self.bubble.edit(True)
+            self.edit(True)
         elif sender == self.comment_label:
             self._comment()
 
@@ -485,7 +486,7 @@
             self._delete(True)
             return False
         extra = {'published': str(self.published)}
-        if self.empty or self.content_xhtml:
+        if isinstance(self.bubble, richtext.RichTextEditor):
             # TODO: if the user change his parameters after the message edition started,
             # the message syntax could be different then the current syntax: pass the
             # message syntax in extra for the frontend to use it instead of current syntax.
@@ -508,12 +509,16 @@
                 self._blog_panel.refresh()
             else:  # allow to create a new comment
                 self._parent_entry._current_comment = None
+        try:
+            self.toggle_syntax_button.removeFromParent()
+        except TypeError:
+            pass
 
-    def __setBubble(self):
+    def __setBubble(self, edit=False):
         """Set the bubble displaying the initial content."""
         content = {'text': self.content_xhtml if self.content_xhtml else self.content,
                    'title': self.title_xhtml if self.title_xhtml else self.title}
-        if self.empty or self.content_xhtml:  # new message and rich text message
+        if self.content_xhtml:
             content.update({'syntax': Const.SYNTAX_XHTML})
             if self.author != self._blog_panel.host.whoami.bare:
                 options = ['read_only']
@@ -521,10 +526,14 @@
                 options = [] if self.empty else ['update_msg']
             self.bubble = richtext.RichTextEditor(self._blog_panel.host, content, self.__modifiedCb, self.__afterEditCb, options)
         else:  # assume raw text message have no title
-            self.bubble = LightTextEditor(content, self.__modifiedCb, self.__afterEditCb, True)
+            self.bubble = LightTextEditor(content, self.__modifiedCb, self.__afterEditCb, single_line=True, listen_focus=False)
         self.bubble.setStyleName("bubble")
+        try:
+            self.toggle_syntax_button.removeFromParent()
+        except TypeError:
+            pass
         self.entry_dialog.add(self.bubble)
-        self.bubble.edit(False)
+        self.edit(edit)
         self.bubble.addEditListener(self.__showWarning)
 
     def __showWarning(self, sender, keycode):
@@ -571,7 +580,55 @@
             return
         entry._parent_entry = self
         self._current_comment = entry
-        entry.bubble.edit(True)
+        self.edit(True, entry)
+
+    def edit(self, edit, entry=None):
+        """Toggle the bubble between display and edit mode
+        @edit: boolean value
+        @entry: MicroblogEntry instance, or None to use self
+        """
+        if entry is None:
+            entry = self
+        try:
+            entry.toggle_syntax_button.removeFromParent()
+        except TypeError:
+            pass
+        entry.bubble.edit(edit)
+        if edit:
+            if isinstance(entry.bubble, richtext.RichTextEditor):
+                image = '<a class="richTextIcon">A</a>'
+                title = _('Switch to raw text edition')
+            else:
+                image = '<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>'
+                title = _('Switch to rich text edition')
+            entry.toggle_syntax_button = Button(image, entry.toggleContentSyntax)
+            entry.toggle_syntax_button.setTitle(title)
+            entry.entry_dialog.add(entry.toggle_syntax_button)
+
+    def toggleContentSyntax(self):
+        """Toggle the editor between raw and rich text"""
+        original_content = self.bubble.getOriginalContent()
+        rich = not isinstance(self.bubble, richtext.RichTextEditor)
+
+        def setBubble(text):
+            self.content = text
+            self.content_xhtml = text if rich else ''
+            self.content_title = self.content_title_xhtml = ''
+            self.bubble.removeFromParent()
+            self.__setBubble(True)
+            self.bubble.setOriginalContent(original_content)
+
+        text = self.bubble.getContent()['text']
+        if not text:
+            setBubble(' ')  # something different than empty string is needed to initialize the rich text editor
+            return
+        if not rich:
+            def confirm_cb(answer):
+                if answer:
+                    self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, Const.SYNTAX_CURRENT, Const.SYNTAX_TEXT)
+            dialog.ConfirmDialog(confirm_cb, text=_("Do you really want to lose the title and text formatting?")).show()
+        else:
+            self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, Const.SYNTAX_TEXT, Const.SYNTAX_XHTML)
 
 
 class MicroblogPanel(base_widget.LiberviaWidget):
@@ -607,7 +664,7 @@
                         'author': self.host.whoami.bare,
                         }
                 entry = self.addEntry(data)
-                entry.bubble.edit(True)
+                entry.edit(True)
             self.new_button = Button("New message", listener=addBox)
             self.new_button.setStyleName("microblogNewButton")
             self.vpanel.insert(self.new_button, 0)
@@ -890,7 +947,7 @@
     def __init__(self, host, status=''):
         self.host = host
         modifiedCb = lambda content: self.host.bridge.call('setStatus', None, self.host.status_panel.presence, content['text']) or True
-        LightTextEditor.__init__(self, {'text': status}, modifiedCb, None, True)
+        LightTextEditor.__init__(self, {'text': status}, modifiedCb, None, single_line=True)
         self.edit(False)
         self.setStyleName('statusPanel')
         ClickHandler.__init__(self)
--- a/browser_side/richtext.py	Thu Mar 06 01:06:19 2014 +0100
+++ b/browser_side/richtext.py	Mon Mar 10 22:39:26 2014 +0100
@@ -285,7 +285,7 @@
             self.refresh(edit)  # not when we are asking for a confirmation
         BaseTextEditor.edit(self, edit, abort, sync)  # after the UI has been refreshed
         if (edit and abort):
-            return  # self.avortEdition is called by BaseTextEditor.edit
+            return  # self.abortEdition is called by BaseTextEditor.edit
         self.setWysiwyg(False, init=True)  # after BaseTextEditor (it affects self.getContent)
         if sync:
             return
--- a/public/libervia.css	Thu Mar 06 01:06:19 2014 +0100
+++ b/public/libervia.css	Mon Mar 10 22:39:26 2014 +0100
@@ -851,6 +851,7 @@
 	float: left;
 	min-height: 54px;
 	padding: 5px 20px 5px 20px;
+    border-collapse: separate;  # for the bubble queue since the entry dialog is now a HorizontalPanel
 }
 
 .bubble {
@@ -866,6 +867,7 @@
     border-style: solid;
     display: block;
     border-collapse: separate;
+    min-height: 15px;  # for the bubble queue to be aligned when the bubble is empty
 }
 
 .bubble:after {