Mercurial > libervia-web
comparison src/browser/sat_browser/blog.py @ 667:166f3b624816 frontends_multi_profiles
browser_side (blog): clean the MicroblogPanel:
- remove internal constants TOGGLE_EDITION_USE_ICON and NEW_MESSAGE_USE_BUTTON
- ignore the unibox and, as a consequence, remove the 'selected_entry' attribute
author | souliane <souliane@mailoo.org> |
---|---|
date | Fri, 06 Mar 2015 14:29:37 +0100 |
parents | ebb602d8b3f2 |
children | a90cc8fc9605 |
comparison
equal
deleted
inserted
replaced
666:1bf645e73fe8 | 667:166f3b624816 |
---|---|
47 import libervia_widget | 47 import libervia_widget |
48 from constants import Const as C | 48 from constants import Const as C |
49 from sat_frontends.quick_frontend import quick_widgets | 49 from sat_frontends.quick_frontend import quick_widgets |
50 from sat_frontends.tools import jid | 50 from sat_frontends.tools import jid |
51 | 51 |
52 # TODO: at some point we should decide which behaviors to keep and remove these two constants | |
53 TOGGLE_EDITION_USE_ICON = False # set to True to use an icon inside the "toggle syntax" button | |
54 NEW_MESSAGE_USE_BUTTON = False # set to True to display the "New message" button instead of an empty entry | |
55 | 52 |
56 unicode = str # XXX: pyjamas doesn't manage unicode | 53 unicode = str # XXX: pyjamas doesn't manage unicode |
57 | 54 |
58 | 55 |
59 class MicroblogItem(): | 56 class MicroblogItem(): |
110 assert isinstance(self.author, jid.JID) # FIXME: temporary | 107 assert isinstance(self.author, jid.JID) # FIXME: temporary |
111 self.avatar = Image(self._blog_panel.host.getAvatarURL(self.author)) # FIXME: self.author should be initially a jid.JID | 108 self.avatar = Image(self._blog_panel.host.getAvatarURL(self.author)) # FIXME: self.author should be initially a jid.JID |
112 entry_avatar.add(self.avatar) | 109 entry_avatar.add(self.avatar) |
113 self.panel.add(entry_avatar) | 110 self.panel.add(entry_avatar) |
114 | 111 |
115 if TOGGLE_EDITION_USE_ICON: | 112 self.entry_dialog = VerticalPanel() |
116 self.entry_dialog = HorizontalPanel() | |
117 else: | |
118 self.entry_dialog = VerticalPanel() | |
119 self.entry_dialog.setStyleName('mb_entry_dialog') | 113 self.entry_dialog.setStyleName('mb_entry_dialog') |
120 self.panel.add(self.entry_dialog) | 114 self.panel.add(self.entry_dialog) |
121 | 115 |
122 self.add(self.panel) | 116 self.add(self.panel) |
123 ClickHandler.__init__(self) | 117 ClickHandler.__init__(self) |
210 """Remove the entry if it was an empty one (used for creating a new blog post). | 204 """Remove the entry if it was an empty one (used for creating a new blog post). |
211 Data for the actual new blog post will be received from the bridge""" | 205 Data for the actual new blog post will be received from the bridge""" |
212 if self.empty: | 206 if self.empty: |
213 self._blog_panel.removeEntry(self.type, self.id) | 207 self._blog_panel.removeEntry(self.type, self.id) |
214 if self.type == 'main_item': # restore the "New message" button | 208 if self.type == 'main_item': # restore the "New message" button |
215 self._blog_panel.refresh() | 209 self._blog_panel.addNewMessageEntry() |
216 else: # allow to create a new comment | 210 else: # allow to create a new comment |
217 self._parent_entry._current_comment = None | 211 self._parent_entry._current_comment = None |
218 self.entry_dialog.setWidth('auto') | 212 self.entry_dialog.setWidth('auto') |
219 try: | 213 try: |
220 self.toggle_syntax_button.removeFromParent() | 214 self.toggle_syntax_button.removeFromParent() |
310 title = _('Switch to raw text edition') | 304 title = _('Switch to raw text edition') |
311 else: | 305 else: |
312 image = '<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>' | 306 image = '<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>' |
313 html = '<a style="color: blue;">rich text</a>' | 307 html = '<a style="color: blue;">rich text</a>' |
314 title = _('Switch to rich text edition') | 308 title = _('Switch to rich text edition') |
315 if TOGGLE_EDITION_USE_ICON: | 309 entry.toggle_syntax_button = HTML(html) |
316 entry.entry_dialog.setWidth('80%') | 310 entry.toggle_syntax_button.addClickListener(entry.toggleContentSyntax) |
317 entry.toggle_syntax_button = Button(image, entry.toggleContentSyntax) | 311 entry.toggle_syntax_button.addStyleName('mb_entry_toggle_syntax') |
318 entry.toggle_syntax_button.setTitle(title) | 312 entry.entry_dialog.add(entry.toggle_syntax_button) |
319 entry.entry_dialog.add(entry.toggle_syntax_button) | 313 entry.toggle_syntax_button.setStyleAttribute('top', '-20px') # XXX: need to force CSS |
320 else: | 314 entry.toggle_syntax_button.setStyleAttribute('left', '-20px') |
321 entry.toggle_syntax_button = HTML(html) | |
322 entry.toggle_syntax_button.addClickListener(entry.toggleContentSyntax) | |
323 entry.toggle_syntax_button.addStyleName('mb_entry_toggle_syntax') | |
324 entry.entry_dialog.add(entry.toggle_syntax_button) | |
325 entry.toggle_syntax_button.setStyleAttribute('top', '-20px') # XXX: need to force CSS | |
326 entry.toggle_syntax_button.setStyleAttribute('left', '-20px') | |
327 | 315 |
328 def toggleContentSyntax(self): | 316 def toggleContentSyntax(self): |
329 """Toggle the editor between raw and rich text""" | 317 """Toggle the editor between raw and rich text""" |
330 original_content = self.bubble.getOriginalContent() | 318 original_content = self.bubble.getOriginalContent() |
331 rich = not isinstance(self.bubble, richtext.RichTextEditor) | 319 rich = not isinstance(self.bubble, richtext.RichTextEditor) |
369 # do not mix self.targets (set of tuple of unicode) and self.accepted_groups (set of unicode) | 357 # do not mix self.targets (set of tuple of unicode) and self.accepted_groups (set of unicode) |
370 quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE) | 358 quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE) |
371 libervia_widget.LiberviaWidget.__init__(self, host, ", ".join(self.accepted_groups), selectable=True) | 359 libervia_widget.LiberviaWidget.__init__(self, host, ", ".join(self.accepted_groups), selectable=True) |
372 self.entries = {} | 360 self.entries = {} |
373 self.comments = {} | 361 self.comments = {} |
374 self.selected_entry = None | |
375 self.vpanel = VerticalPanel() | 362 self.vpanel = VerticalPanel() |
376 self.vpanel.setStyleName('microblogPanel') | 363 self.vpanel.setStyleName('microblogPanel') |
377 self.setWidget(self.vpanel) | 364 self.setWidget(self.vpanel) |
365 self.addNewMessageEntry() | |
378 | 366 |
379 # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword) | 367 # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword) |
380 self.avatarListener = self.onAvatarUpdate | 368 self.avatarListener = self.onAvatarUpdate |
381 host.addListener('avatar', self.avatarListener, [C.PROF_KEY_NONE]) | 369 host.addListener('avatar', self.avatarListener, [C.PROF_KEY_NONE]) |
382 | 370 |
396 """ | 384 """ |
397 whoami = self.host.profiles[self.profile].whoami | 385 whoami = self.host.profiles[self.profile].whoami |
398 if self.isJidAccepted(jid_) or jid_.bare == whoami.bare: | 386 if self.isJidAccepted(jid_) or jid_.bare == whoami.bare: |
399 self.updateValue('avatar', jid_, hash_) | 387 self.updateValue('avatar', jid_, hash_) |
400 | 388 |
401 def refresh(self): | 389 def addNewMessageEntry(self): |
402 """Refresh the display of this widget. If the unibox is disabled, | 390 """Add an empty entry for writing a new message if needed.""" |
403 display the 'New message' button or an empty bubble on top of the panel""" | 391 if self.getNewMainEntry(): |
404 if hasattr(self, 'new_button'): | 392 return # there's already one |
405 self.new_button.setVisible(self.host.uni_box is None) | 393 data = {'id': unicode(time()), |
406 return | 394 'new': True, |
407 if self.host.uni_box is None: | 395 'author': unicode(self.host.whoami.bare), |
408 def addBox(): | 396 } |
409 if hasattr(self, 'new_button'): | 397 entry = self.addEntry(data) |
410 self.new_button.setVisible(False) | 398 entry.edit(True) |
411 data = {'id': unicode(time()), | |
412 'new': True, | |
413 'author': unicode(self.host.whoami.bare), | |
414 } | |
415 entry = self.addEntry(data) | |
416 entry.edit(True) | |
417 if NEW_MESSAGE_USE_BUTTON: | |
418 self.new_button = Button("New message", listener=addBox) | |
419 self.new_button.setStyleName("microblogNewButton") | |
420 self.vpanel.insert(self.new_button, 0) | |
421 elif not self.getNewMainEntry(): | |
422 addBox() | |
423 | 399 |
424 def getNewMainEntry(self): | 400 def getNewMainEntry(self): |
425 """Get the new entry being edited, or None if it doesn't exists. | 401 """Get the new entry being edited, or None if it doesn't exists. |
426 | 402 |
427 @return (MicroblogEntry): the new entry being edited. | 403 @return (MicroblogEntry): the new entry being edited. |
444 type_ = 'ALL' if not targets else 'GROUP' | 420 type_ = 'ALL' if not targets else 'GROUP' |
445 # XXX: pyjamas doesn't support use of cls directly | 421 # XXX: pyjamas doesn't support use of cls directly |
446 widget = host.displayWidget(MicroblogPanel, targets, dropped=True) | 422 widget = host.displayWidget(MicroblogPanel, targets, dropped=True) |
447 host.FillMicroblogPanel(widget) | 423 host.FillMicroblogPanel(widget) |
448 host.bridge.getMassiveLastMblogs(type_, targets, 10, profile=C.PROF_KEY_NONE, callback=widget.massiveInsert) | 424 host.bridge.getMassiveLastMblogs(type_, targets, 10, profile=C.PROF_KEY_NONE, callback=widget.massiveInsert) |
449 widget.refresh() # FIXME: needed ? | |
450 return widget | 425 return widget |
451 | 426 |
452 @property | 427 @property |
453 def accepted_groups(self): | 428 def accepted_groups(self): |
454 """Return a set of the accepted groups""" | 429 """Return a set of the accepted groups""" |
455 return set().union(*self.targets) | 430 return set().union(*self.targets) |
456 | 431 |
457 def getWarningData(self, comment=None): | 432 def getWarningData(self, comment): |
458 """ | 433 """ |
459 @param comment: True if the composed message is a comment. If None, consider we are | 434 @param comment: set to True if the composed message is a comment |
460 composing from the unibox and guess the message type from self.selected_entry | |
461 @return: a couple (type, msg) for calling self.host.showWarning""" | 435 @return: a couple (type, msg) for calling self.host.showWarning""" |
462 if comment is None: # composing from the unibox | |
463 if self.selected_entry and not self.selected_entry.comments: | |
464 log.error("an item without comment is selected") | |
465 return ("NONE", None) | |
466 comment = self.selected_entry is not None | |
467 if comment: | 436 if comment: |
468 return ("PUBLIC", "This is a <span class='warningTarget'>comment</span> and keep the initial post visibility, so it is potentialy public") | 437 return ("PUBLIC", "This is a <span class='warningTarget'>comment</span> and keep the initial post visibility, so it is potentialy public") |
469 elif not self.accepted_groups: | 438 elif not self.accepted_groups: |
470 # we have a meta MicroblogPanel, we publish publicly | 439 # we have a meta MicroblogPanel, we publish publicly |
471 return ("PUBLIC", self.warning_msg_public) | 440 return ("PUBLIC", self.warning_msg_public) |
472 else: | 441 else: |
473 # FIXME: manage several groups | 442 # FIXME: manage several groups |
474 return ("GROUP", self.warning_msg_group % ' '.join(self.accepted_groups)) | 443 return ("GROUP", self.warning_msg_group % ' '.join(self.accepted_groups)) |
475 | 444 |
476 def onTextEntered(self, text): | 445 def onTextEntered(self, text): |
477 if self.selected_entry: | 446 if not self.accepted_groups: |
478 # we are entering a comment | |
479 comments_url = self.selected_entry.comments | |
480 if not comments_url: | |
481 raise Exception("ERROR: the comments URL is empty") | |
482 self.bridge.call("sendMblogComment", None, comments_url, text, {}) | |
483 target = ("COMMENT", comments_url) | |
484 elif not self.accepted_groups: | |
485 # we are entering a public microblog | 447 # we are entering a public microblog |
486 self.bridge.call("sendMblog", None, "PUBLIC", (), text, {}) | 448 self.bridge.call("sendMblog", None, "PUBLIC", (), text, {}) |
487 else: | 449 else: |
488 self.bridge.call("sendMblog", None, "GROUP", tuple(self.accepted_groups), text, {}) | 450 self.bridge.call("sendMblog", None, "GROUP", tuple(self.accepted_groups), text, {}) |
489 | 451 |
539 if condition_to_stop != reverse: # != is XOR | 501 if condition_to_stop != reverse: # != is XOR |
540 break | 502 break |
541 idx += 1 | 503 idx += 1 |
542 | 504 |
543 vpanel.insert(entry, idx) | 505 vpanel.insert(entry, idx) |
544 | |
545 | 506 |
546 def addEntryIfAccepted(self, sender, groups, mblog_entry): | 507 def addEntryIfAccepted(self, sender, groups, mblog_entry): |
547 """Check if an entry can go in MicroblogPanel and add to it | 508 """Check if an entry can go in MicroblogPanel and add to it |
548 | 509 |
549 @param sender(jid.JID): jid of the entry sender | 510 @param sender(jid.JID): jid of the entry sender |
622 if isinstance(sub_panel, VerticalPanel): | 583 if isinstance(sub_panel, VerticalPanel): |
623 sub_panel.removeFromParent() | 584 sub_panel.removeFromParent() |
624 except IndexError: | 585 except IndexError: |
625 pass | 586 pass |
626 child.removeFromParent() | 587 child.removeFromParent() |
627 self.selected_entry = None | |
628 break | 588 break |
629 elif isinstance(child, VerticalPanel) and type_ == 'comment': | 589 elif isinstance(child, VerticalPanel) and type_ == 'comment': |
630 for comment in child.getChildren(): | 590 for comment in child.getChildren(): |
631 if comment.id == id_: | 591 if comment.id == id_: |
632 comment.removeFromParent() | 592 comment.removeFromParent() |
633 self.selected_entry = None | |
634 break | 593 break |
635 | 594 |
636 def ensureVisible(self, entry): | 595 def ensureVisible(self, entry): |
637 """Scroll to an entry to ensure its visibility | 596 """Scroll to an entry to ensure its visibility |
638 | 597 |
650 @param ensure_visible (boolean): if True, also scroll to the entry | 609 @param ensure_visible (boolean): if True, also scroll to the entry |
651 """ | 610 """ |
652 if ensure_visible: | 611 if ensure_visible: |
653 self.ensureVisible(entry) | 612 self.ensureVisible(entry) |
654 | 613 |
655 if not self.host.uni_box or not entry.comments: | 614 entry.addStyleName('selected_entry') # blink the clicked entry |
656 entry.addStyleName('selected_entry') # blink the clicked entry | 615 clicked_entry = entry # entry may be None when the timer is done |
657 clicked_entry = entry # entry may be None when the timer is done | 616 Timer(500, lambda timer: clicked_entry.removeStyleName('selected_entry')) |
658 Timer(500, lambda timer: clicked_entry.removeStyleName('selected_entry')) | |
659 if not self.host.uni_box: | |
660 return # unibox is disabled | |
661 | |
662 # from here the previous behavior (toggle main item selection) is conserved | |
663 entry = entry if entry.comments else None | |
664 if self.selected_entry == entry: | |
665 entry = None | |
666 if self.selected_entry: | |
667 self.selected_entry.removeStyleName('selected_entry') | |
668 if entry: | |
669 log.debug("microblog entry selected (author=%s)" % unicode(entry.author)) | |
670 entry.addStyleName('selected_entry') | |
671 self.selected_entry = entry | |
672 | 617 |
673 def updateValue(self, type_, jid_, value): | 618 def updateValue(self, type_, jid_, value): |
674 """Update a jid value in entries | 619 """Update a jid value in entries |
675 | 620 |
676 @param type_: one of 'avatar', 'nick' | 621 @param type_: one of 'avatar', 'nick' |