Mercurial > libervia-web
comparison src/browser/sat_browser/panels.py @ 586:3eb3a2c0c011
browser and server side: uses RSM (XEP-0059)
author | souliane <souliane@mailoo.org> |
---|---|
date | Fri, 28 Nov 2014 00:31:27 +0100 |
parents | bade589dbd5a |
children |
comparison
equal
deleted
inserted
replaced
585:bade589dbd5a | 586:3eb3a2c0c011 |
---|---|
21 from sat.core.log import getLogger | 21 from sat.core.log import getLogger |
22 log = getLogger(__name__) | 22 log = getLogger(__name__) |
23 | 23 |
24 from sat_frontends.tools.strings import addURLToText | 24 from sat_frontends.tools.strings import addURLToText |
25 from sat_frontends.tools.games import SYMBOLS | 25 from sat_frontends.tools.games import SYMBOLS |
26 from sat.core.i18n import _ | 26 from sat.core.i18n import _, D_ |
27 | 27 |
28 from pyjamas.ui.SimplePanel import SimplePanel | 28 from pyjamas.ui.SimplePanel import SimplePanel |
29 from pyjamas.ui.AbsolutePanel import AbsolutePanel | 29 from pyjamas.ui.AbsolutePanel import AbsolutePanel |
30 from pyjamas.ui.VerticalPanel import VerticalPanel | 30 from pyjamas.ui.VerticalPanel import VerticalPanel |
31 from pyjamas.ui.HorizontalPanel import HorizontalPanel | 31 from pyjamas.ui.HorizontalPanel import HorizontalPanel |
404 self._blog_panel = blog_panel | 404 self._blog_panel = blog_panel |
405 | 405 |
406 self.panel = FlowPanel() | 406 self.panel = FlowPanel() |
407 self.panel.setStyleName('mb_entry') | 407 self.panel.setStyleName('mb_entry') |
408 | 408 |
409 self.header = HTMLPanel('') | 409 self.header = HorizontalPanel(StyleName='mb_entry_header') |
410 self.panel.add(self.header) | 410 self.panel.add(self.header) |
411 | 411 |
412 self.entry_actions = VerticalPanel() | 412 self.entry_actions = VerticalPanel() |
413 self.entry_actions.setStyleName('mb_entry_actions') | 413 self.entry_actions.setStyleName('mb_entry_actions') |
414 self.panel.add(self.entry_actions) | 414 self.panel.add(self.entry_actions) |
440 self.__setHeader() | 440 self.__setHeader() |
441 self.__setBubble() | 441 self.__setBubble() |
442 self.__setIcons() | 442 self.__setIcons() |
443 | 443 |
444 def __setHeader(self): | 444 def __setHeader(self): |
445 """Set the entry header""" | 445 """Set the entry header.""" |
446 if self.empty: | 446 if self.empty: |
447 return | 447 return |
448 update_text = u" — ✍ " + "<span class='mb_entry_timestamp'>%s</span>" % datetime.fromtimestamp(self.updated) | 448 update_text = u" — ✍ " + "<span class='mb_entry_timestamp'>%s</span>" % datetime.fromtimestamp(self.updated) |
449 self.header.setHTML("""<div class='mb_entry_header'> | 449 self.header.add(HTML("""<span class='mb_entry_header_info'> |
450 <span class='mb_entry_author'>%(author)s</span> on | 450 <span class='mb_entry_author'>%(author)s</span> on |
451 <span class='mb_entry_timestamp'>%(published)s</span>%(updated)s | 451 <span class='mb_entry_timestamp'>%(published)s</span>%(updated)s |
452 </div>""" % {'author': html_tools.html_sanitize(self.author), | 452 </span>""" % {'author': html_tools.html_sanitize(self.author), |
453 'published': datetime.fromtimestamp(self.published), | 453 'published': datetime.fromtimestamp(self.published), |
454 'updated': update_text if self.published != self.updated else '' | 454 'updated': update_text if self.published != self.updated else '' |
455 } | 455 })) |
456 ) | 456 if self.comments: |
457 self.comments_count = self.hidden_count = 0 | |
458 self.show_comments_link = HTML('') | |
459 self.header.add(self.show_comments_link) | |
460 | |
461 def updateHeader(self, comments_count=None, hidden_count=None, inc=None): | |
462 """Update the header. | |
463 | |
464 @param comments_count (int): total number of comments. | |
465 @param hidden_count (int): number of hidden comments. | |
466 @param inc (int): number to increment the total number of comments with. | |
467 """ | |
468 if comments_count is not None: | |
469 self.comments_count = comments_count | |
470 if hidden_count is not None: | |
471 self.hidden_count = hidden_count | |
472 if inc is not None: | |
473 self.comments_count += inc | |
474 | |
475 if self.hidden_count > 0: | |
476 comments = D_('comments') if self.hidden_count > 1 else D_('comment') | |
477 text = D_("<a>show %(count)d previous %(comments)s</a>") % {'count': self.hidden_count, | |
478 'comments': comments} | |
479 if self not in self.show_comments_link._clickListeners: | |
480 self.show_comments_link.addClickListener(self) | |
481 else: | |
482 if self.comments_count > 1: | |
483 text = "%(count)d %(comments)s" % {'count': self.comments_count, | |
484 'comments': D_('comments')} | |
485 elif self.comments_count == 1: | |
486 text = D_('1 comment') | |
487 else: | |
488 text = '' | |
489 try: | |
490 self.show_comments_link.removeClickListener(self) | |
491 except ValueError: | |
492 pass | |
493 | |
494 self.show_comments_link.setHTML("""<span class='mb_entry_comments'>%(text)s</span></div>""" % {'text': text}) | |
457 | 495 |
458 def __setIcons(self): | 496 def __setIcons(self): |
459 """Set the entry icons (delete, update, comment)""" | 497 """Set the entry icons (delete, update, comment)""" |
460 if self.empty: | 498 if self.empty: |
461 return | 499 return |
488 self._delete() | 526 self._delete() |
489 elif sender == self.update_label: | 527 elif sender == self.update_label: |
490 self.edit(True) | 528 self.edit(True) |
491 elif sender == self.comment_label: | 529 elif sender == self.comment_label: |
492 self._comment() | 530 self._comment() |
531 elif sender == self.show_comments_link: | |
532 self._blog_panel.loadAllCommentsForEntry(self) | |
493 | 533 |
494 def __modifiedCb(self, content): | 534 def __modifiedCb(self, content): |
495 """Send the new content to the backend | 535 """Send the new content to the backend |
496 @return: False to restore the original content if a deletion has been cancelled | 536 @return: False to restore the original content if a deletion has been cancelled |
497 """ | 537 """ |
515 | 555 |
516 def __afterEditCb(self, content): | 556 def __afterEditCb(self, content): |
517 """Remove the entry if it was an empty one (used for creating a new blog post). | 557 """Remove the entry if it was an empty one (used for creating a new blog post). |
518 Data for the actual new blog post will be received from the bridge""" | 558 Data for the actual new blog post will be received from the bridge""" |
519 if self.empty: | 559 if self.empty: |
520 self._blog_panel.removeEntry(self.type, self.id) | 560 self._blog_panel.removeEntry(self.type, self.id, update_header=False) |
521 if self.type == 'main_item': # restore the "New message" button | 561 if self.type == 'main_item': # restore the "New message" button |
522 self._blog_panel.refresh() | 562 self._blog_panel.refresh() |
523 else: # allow to create a new comment | 563 else: # allow to create a new comment |
524 self._parent_entry._current_comment = None | 564 self._parent_entry._current_comment = None |
525 self.entry_dialog.setWidth('auto') | 565 self.entry_dialog.setWidth('auto') |
587 'type': 'comment', | 627 'type': 'comment', |
588 'author': self._blog_panel.host.whoami.bare, | 628 'author': self._blog_panel.host.whoami.bare, |
589 'service': self.comments_service, | 629 'service': self.comments_service, |
590 'node': self.comments_node | 630 'node': self.comments_node |
591 } | 631 } |
592 entry = self._blog_panel.addEntry(data) | 632 entry = self._blog_panel.addEntry(data, update_header=False) |
593 if entry is None: | 633 if entry is None: |
594 log.info("The entry of id %s can not be commented" % self.id) | 634 log.info("The entry of id %s can not be commented" % self.id) |
595 return | 635 return |
596 entry._parent_entry = self | 636 entry._parent_entry = self |
597 self._current_comment = entry | 637 self._current_comment = entry |
660 dialog.ConfirmDialog(confirm_cb, text=_("Do you really want to lose the title and text formatting?")).show() | 700 dialog.ConfirmDialog(confirm_cb, text=_("Do you really want to lose the title and text formatting?")).show() |
661 else: | 701 else: |
662 self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_TEXT, C.SYNTAX_XHTML) | 702 self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_TEXT, C.SYNTAX_XHTML) |
663 | 703 |
664 | 704 |
665 class MicroblogPanel(base_widget.LiberviaWidget): | 705 class MicroblogPanel(base_widget.LiberviaWidget, MouseHandler): |
666 warning_msg_public = "This message will be <b>PUBLIC</b> and everybody will be able to see it, even people you don't know" | 706 warning_msg_public = "This message will be <b>PUBLIC</b> and everybody will be able to see it, even people you don't know" |
667 warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" | 707 warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" |
668 | 708 |
669 def __init__(self, host, accepted_groups): | 709 def __init__(self, host, accepted_groups): |
670 """Panel used to show microblog | 710 """Panel used to show microblog |
671 @param accepted_groups: groups displayed in this panel, if empty, show all microblogs from all contacts | 711 @param accepted_groups: groups displayed in this panel, if empty, show all microblogs from all contacts |
672 """ | 712 """ |
673 base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups), selectable=True) | 713 base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups), selectable=True) |
714 MouseHandler.__init__(self) | |
674 self.setAcceptedGroup(accepted_groups) | 715 self.setAcceptedGroup(accepted_groups) |
675 self.host = host | 716 self.host = host |
676 self.entries = {} | 717 self.entries = {} |
677 self.comments = {} | 718 self.comments = {} |
678 self.selected_entry = None | 719 self.selected_entry = None |
679 self.vpanel = VerticalPanel() | 720 self.vpanel = VerticalPanel() |
680 self.vpanel.setStyleName('microblogPanel') | 721 self.vpanel.setStyleName('microblogPanel') |
681 self.setWidget(self.vpanel) | 722 self.setWidget(self.vpanel) |
723 self.footer = HTML('', StyleName='microblogPanel_footer') | |
724 self.footer.waiting = False | |
725 self.footer.addClickListener(self) | |
726 self.footer.addMouseListener(self) | |
727 self.vpanel.add(self.footer) | |
728 self.next_rsm_index = 0 | |
682 | 729 |
683 def refresh(self): | 730 def refresh(self): |
684 """Refresh the display of this widget. If the unibox is disabled, | 731 """Refresh the display of this widget. If the unibox is disabled, |
685 display the 'New message' button or an empty bubble on top of the panel""" | 732 display the 'New message' button or an empty bubble on top of the panel""" |
686 if hasattr(self, 'new_button'): | 733 if hasattr(self, 'new_button'): |
692 self.new_button.setVisible(False) | 739 self.new_button.setVisible(False) |
693 data = {'id': str(time()), | 740 data = {'id': str(time()), |
694 'new': True, | 741 'new': True, |
695 'author': self.host.whoami.bare, | 742 'author': self.host.whoami.bare, |
696 } | 743 } |
697 entry = self.addEntry(data) | 744 entry = self.addEntry(data, update_header=False) |
698 entry.edit(True) | 745 entry.edit(True) |
699 if NEW_MESSAGE_USE_BUTTON: | 746 if NEW_MESSAGE_USE_BUTTON: |
700 self.new_button = Button("New message", listener=addBox) | 747 self.new_button = Button("New message", listener=addBox) |
701 self.new_button.setStyleName("microblogNewButton") | 748 self.new_button.setStyleName("microblogNewButton") |
702 self.vpanel.insert(self.new_button, 0) | 749 self.vpanel.insert(self.new_button, 0) |
706 def getNewMainEntry(self): | 753 def getNewMainEntry(self): |
707 """Get the new entry being edited, or None if it doesn't exists. | 754 """Get the new entry being edited, or None if it doesn't exists. |
708 | 755 |
709 @return (MicroblogEntry): the new entry being edited. | 756 @return (MicroblogEntry): the new entry being edited. |
710 """ | 757 """ |
711 try: | 758 if len(self.vpanel.children) < 2: |
712 first = self.vpanel.children[0] | 759 return None # there's only the footer |
713 except IndexError: | 760 first = self.vpanel.children[0] |
714 return None | |
715 assert(first.type == 'main_item') | 761 assert(first.type == 'main_item') |
716 return first if first.empty else None | 762 return first if first.empty else None |
717 | 763 |
718 @classmethod | 764 @classmethod |
719 def registerClass(cls): | 765 def registerClass(cls): |
727 @param item: single group as a string, list of groups | 773 @param item: single group as a string, list of groups |
728 (as an array) or None (for the meta group = "all groups") | 774 (as an array) or None (for the meta group = "all groups") |
729 @return: the created MicroblogPanel | 775 @return: the created MicroblogPanel |
730 """ | 776 """ |
731 _items = item if isinstance(item, list) else ([] if item is None else [item]) | 777 _items = item if isinstance(item, list) else ([] if item is None else [item]) |
732 _type = 'ALL' if _items == [] else 'GROUP' | |
733 # XXX: pyjamas doesn't support use of cls directly | 778 # XXX: pyjamas doesn't support use of cls directly |
734 _new_panel = MicroblogPanel(host, _items) | 779 _new_panel = MicroblogPanel(host, _items) |
735 host.FillMicroblogPanel(_new_panel) | 780 _new_panel.loadMoreMainEntries() |
736 host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, _type, _items, 10) | |
737 host.setSelected(_new_panel) | 781 host.setSelected(_new_panel) |
738 _new_panel.refresh() | 782 _new_panel.refresh() |
739 return _new_panel | 783 return _new_panel |
740 | 784 |
741 @classmethod | 785 @classmethod |
744 return MicroblogPanel.createPanel(host, None) | 788 return MicroblogPanel.createPanel(host, None) |
745 | 789 |
746 @property | 790 @property |
747 def accepted_groups(self): | 791 def accepted_groups(self): |
748 return self._accepted_groups | 792 return self._accepted_groups |
793 | |
794 def loadAllCommentsForEntry(self, main_entry): | |
795 """Load all the comments for the given main entry. | |
796 | |
797 @param main_entry (MicroblogEntry): main entry having comments. | |
798 """ | |
799 index = str(main_entry.comments_count - main_entry.hidden_count) | |
800 rsm = {'max': str(main_entry.hidden_count), 'index': index} | |
801 self.host.bridge.call('getMblogComments', self.mblogsInsert, main_entry.comments_service, main_entry.comments_node, rsm) | |
802 | |
803 def loadMoreMainEntries(self): | |
804 if self.footer.waiting: | |
805 return | |
806 self.footer.waiting = True | |
807 self.footer.setHTML("loading...") | |
808 | |
809 self.host.loadOurMainEntries(self.next_rsm_index, self) | |
810 | |
811 type_ = 'ALL' if self.accepted_groups == [] else 'GROUP' | |
812 rsm = {'max': str(C.RSM_MAX_ITEMS), 'index': str(self.next_rsm_index)} | |
813 self.host.bridge.call('getMassiveMblogs', self.massiveInsert, type_, self.accepted_groups, rsm) | |
749 | 814 |
750 def matchEntity(self, item): | 815 def matchEntity(self, item): |
751 """ | 816 """ |
752 @param item: single group as a string, list of groups | 817 @param item: single group as a string, list of groups |
753 (as an array) or None (for the meta group = "all groups") | 818 (as an array) or None (for the meta group = "all groups") |
800 """Ask all the entries for the currenly accepted groups, | 865 """Ask all the entries for the currenly accepted groups, |
801 and fill the panel""" | 866 and fill the panel""" |
802 | 867 |
803 def massiveInsert(self, mblogs): | 868 def massiveInsert(self, mblogs): |
804 """Insert several microblogs at once | 869 """Insert several microblogs at once |
805 @param mblogs: dictionary of microblogs, as the result of getMassiveLastGroupBlogs | 870 @param mblogs (dict): dictionary mapping a publisher to microblogs data: |
806 """ | 871 - key: publisher (str) |
807 count = sum([len(value) for value in mblogs.values()]) | 872 - value: couple (list[dict], dict) with: |
808 log.debug("Massive insertion of %d microblogs" % count) | 873 - list of microblogs data |
874 - RSM response data | |
875 """ | |
876 count_pub = len(mblogs) | |
877 count_msg = sum([len(value) for value in mblogs.values()]) | |
878 log.debug("massive insertion of {count_msg} blogs for {count_pub} contacts".format(count_msg=count_msg, count_pub=count_pub)) | |
809 for publisher in mblogs: | 879 for publisher in mblogs: |
810 log.debug("adding blogs for [%s]" % publisher) | 880 log.debug("adding {count} blogs for [{publisher}]".format(count=len(mblogs[publisher]), publisher=publisher)) |
811 for mblog in mblogs[publisher]: | 881 self.mblogsInsert(mblogs[publisher]) |
812 if not "content" in mblog: | 882 self.next_rsm_index += C.RSM_MAX_ITEMS |
813 log.warning("No content found in microblog [%s]" % mblog) | 883 self.footer.waiting = False |
814 continue | 884 self.footer.setHTML('show older messages') |
815 self.addEntry(mblog) | |
816 | 885 |
817 def mblogsInsert(self, mblogs): | 886 def mblogsInsert(self, mblogs): |
818 """ Insert several microblogs at once | 887 """ Insert several microblogs from the same node at once. |
819 @param mblogs: list of microblogs | 888 |
820 """ | 889 @param mblogs (list): couple (list[dict], dict) with: |
890 - list of microblogs data | |
891 - RSM response data | |
892 """ | |
893 mblogs, rsm = mblogs | |
894 | |
821 for mblog in mblogs: | 895 for mblog in mblogs: |
822 if not "content" in mblog: | 896 if "content" not in mblog: |
823 log.warning("No content found in microblog [%s]" % mblog) | 897 log.warning("No content found in microblog [%s]" % mblog) |
824 continue | 898 continue |
825 self.addEntry(mblog) | 899 self.addEntry(mblog, update_header=False) |
900 | |
901 hashes = set([(entry['service'], entry['node']) for entry in mblogs if entry['type'] == 'comment']) | |
902 assert(len(hashes) < 2) # ensure the blogs come from the same node | |
903 if len(hashes) == 1: | |
904 main_entry = self.comments[hashes.pop()] | |
905 count = int(rsm['count']) | |
906 hidden = count - (int(rsm['index']) + len(mblogs)) | |
907 main_entry.updateHeader(count, hidden) | |
826 | 908 |
827 def _chronoInsert(self, vpanel, entry, reverse=True): | 909 def _chronoInsert(self, vpanel, entry, reverse=True): |
828 """ Insert an entry in chronological order | 910 """ Insert an entry in chronological order |
829 @param vpanel: VerticalPanel instance | 911 @param vpanel: VerticalPanel instance |
830 @param entry: MicroblogEntry | 912 @param entry: MicroblogEntry |
831 @param reverse: more recent entry on top if True, chronological order else""" | 913 @param reverse: more recent entry on top if True, chronological order else""" |
914 # XXX: for now we can't use "published" timestamp because the entries | |
915 # are retrieved using the "updated" field. We don't want new items | |
916 # inserted with RSM to be inserted "randomly" in the panel, they | |
917 # should be added at the bottom of the list. | |
832 assert(isinstance(reverse, bool)) | 918 assert(isinstance(reverse, bool)) |
833 if entry.empty: | 919 if entry.empty: |
834 entry.published = time() | 920 entry.updated = time() |
835 # we look for the right index to insert our entry: | 921 # we look for the right index to insert our entry: |
836 # if reversed, we insert the entry above the first entry | 922 # if reversed, we insert the entry above the first entry |
837 # in the past | 923 # in the past |
838 idx = 0 | 924 idx = 0 |
839 | 925 |
840 for child in vpanel.children: | 926 for child in vpanel.children[0:-1]: # ignore the footer |
841 if not isinstance(child, MicroblogEntry): | 927 if not isinstance(child, MicroblogEntry): |
842 idx += 1 | 928 idx += 1 |
843 continue | 929 continue |
844 condition_to_stop = child.empty or (child.published > entry.published) | 930 condition_to_stop = child.empty or (child.updated > entry.updated) |
845 if condition_to_stop != reverse: # != is XOR | 931 if condition_to_stop != reverse: # != is XOR |
846 break | 932 break |
847 idx += 1 | 933 idx += 1 |
848 | 934 |
849 vpanel.insert(entry, idx) | 935 vpanel.insert(entry, idx) |
850 | 936 |
851 def addEntry(self, data): | 937 def addEntry(self, data, update_header=True): |
852 """Add an entry to the panel | 938 """Add an entry to the panel |
853 @param data: dict containing the item data | 939 |
854 @return: the added entry, or None | 940 @param data (dict): dict containing the item data |
941 @param update_header (bool): update or not the main comment header | |
942 @return: the added MicroblogEntry instance, or None | |
855 """ | 943 """ |
856 _entry = MicroblogEntry(self, data) | 944 _entry = MicroblogEntry(self, data) |
857 if _entry.type == "comment": | 945 if _entry.type == "comment": |
858 comments_hash = (_entry.service, _entry.node) | 946 comments_hash = (_entry.service, _entry.node) |
859 if not comments_hash in self.comments: | 947 if comments_hash not in self.comments: |
860 # The comments node is not known in this panel | 948 # The comments node is not known in this panel |
861 return None | 949 return None |
862 parent = self.comments[comments_hash] | 950 parent = self.comments[comments_hash] |
863 parent_idx = self.vpanel.getWidgetIndex(parent) | 951 parent_idx = self.vpanel.getWidgetIndex(parent) |
864 # we find or create the panel where the comment must be inserted | 952 # we find or create the panel where the comment must be inserted |
869 if not sub_panel or not isinstance(sub_panel, VerticalPanel): | 957 if not sub_panel or not isinstance(sub_panel, VerticalPanel): |
870 sub_panel = VerticalPanel() | 958 sub_panel = VerticalPanel() |
871 sub_panel.setStyleName('microblogPanel') | 959 sub_panel.setStyleName('microblogPanel') |
872 sub_panel.addStyleName('subPanel') | 960 sub_panel.addStyleName('subPanel') |
873 self.vpanel.insert(sub_panel, parent_idx + 1) | 961 self.vpanel.insert(sub_panel, parent_idx + 1) |
962 | |
874 for idx in xrange(0, len(sub_panel.getChildren())): | 963 for idx in xrange(0, len(sub_panel.getChildren())): |
875 comment = sub_panel.getIndexedChild(idx) | 964 comment = sub_panel.getIndexedChild(idx) |
876 if comment.id == _entry.id: | 965 if comment.id == _entry.id: |
877 # update an existing comment | 966 # update an existing comment |
878 sub_panel.remove(comment) | 967 sub_panel.remove(comment) |
879 sub_panel.insert(_entry, idx) | 968 sub_panel.insert(_entry, idx) |
880 return _entry | 969 return _entry |
881 # we want comments to be inserted in chronological order | 970 # we want comments to be inserted in chronological order |
882 self._chronoInsert(sub_panel, _entry, reverse=False) | 971 self._chronoInsert(sub_panel, _entry, reverse=False) |
972 if update_header: | |
973 parent.updateHeader(inc=+1) | |
883 return _entry | 974 return _entry |
884 | |
885 if _entry.id in self.entries: # update | |
886 idx = self.vpanel.getWidgetIndex(self.entries[_entry.id]) | |
887 self.vpanel.remove(self.entries[_entry.id]) | |
888 self.vpanel.insert(_entry, idx) | |
889 else: # new entry | |
890 self._chronoInsert(self.vpanel, _entry) | |
891 self.entries[_entry.id] = _entry | |
892 | 975 |
893 if _entry.comments: | 976 if _entry.comments: |
894 # entry has comments, we keep the comments service/node as a reference | 977 # entry has comments, we keep the comments service/node as a reference |
895 comments_hash = (_entry.comments_service, _entry.comments_node) | 978 comments_hash = (_entry.comments_service, _entry.comments_node) |
896 self.comments[comments_hash] = _entry | 979 self.comments[comments_hash] = _entry |
897 self.host.bridge.call('getMblogComments', self.mblogsInsert, _entry.comments_service, _entry.comments_node) | 980 |
981 if _entry.id in self.entries: # update | |
982 old_entry = self.entries[_entry.id] | |
983 idx = self.vpanel.getWidgetIndex(old_entry) | |
984 counts = (old_entry.comments_count, old_entry.hidden_count) | |
985 self.vpanel.remove(old_entry) | |
986 self.vpanel.insert(_entry, idx) | |
987 _entry.updateHeader(*counts) | |
988 else: # new entry | |
989 self._chronoInsert(self.vpanel, _entry) | |
990 if _entry.comments: | |
991 self.host.bridge.call('getMblogComments', self.mblogsInsert, _entry.comments_service, _entry.comments_node) | |
992 | |
993 self.entries[_entry.id] = _entry | |
898 | 994 |
899 return _entry | 995 return _entry |
900 | 996 |
901 def removeEntry(self, type_, id_): | 997 def removeEntry(self, type_, id_, update_header=True): |
902 """Remove an entry from the panel | 998 """Remove an entry from the panel |
903 @param type_: entry type ('main_item' or 'comment') | 999 |
904 @param id_: entry id | 1000 @param type_ (str): entry type ('main_item' or 'comment') |
1001 @param id_ (str): entry id | |
1002 @param update_header (bool): update or not the main comment header | |
905 """ | 1003 """ |
906 for child in self.vpanel.getChildren(): | 1004 for child in self.vpanel.getChildren(): |
907 if isinstance(child, MicroblogEntry) and type_ == 'main_item': | 1005 if isinstance(child, MicroblogEntry) and type_ == 'main_item': |
908 if child.id == id_: | 1006 if child.id == id_: |
909 main_idx = self.vpanel.getWidgetIndex(child) | 1007 main_idx = self.vpanel.getWidgetIndex(child) |
917 self.selected_entry = None | 1015 self.selected_entry = None |
918 break | 1016 break |
919 elif isinstance(child, VerticalPanel) and type_ == 'comment': | 1017 elif isinstance(child, VerticalPanel) and type_ == 'comment': |
920 for comment in child.getChildren(): | 1018 for comment in child.getChildren(): |
921 if comment.id == id_: | 1019 if comment.id == id_: |
1020 if update_header: | |
1021 hash_ = (comment.service, comment.node) | |
1022 self.comments[hash_].updateHeader(inc=-1) | |
922 comment.removeFromParent() | 1023 comment.removeFromParent() |
923 self.selected_entry = None | 1024 self.selected_entry = None |
924 break | 1025 break |
925 | 1026 |
926 def ensureVisible(self, entry): | 1027 def ensureVisible(self, entry): |
995 return True | 1096 return True |
996 for group in self._accepted_groups: | 1097 for group in self._accepted_groups: |
997 if self.host.contact_panel.isContactInGroup(group, jid_s): | 1098 if self.host.contact_panel.isContactInGroup(group, jid_s): |
998 return True | 1099 return True |
999 return False | 1100 return False |
1101 | |
1102 def onClick(self, sender): | |
1103 if sender == self.footer: | |
1104 self.loadMoreMainEntries() | |
1105 | |
1106 def onMouseEnter(self, sender): | |
1107 if sender == self.footer: | |
1108 self.loadMoreMainEntries() | |
1000 | 1109 |
1001 | 1110 |
1002 class StatusPanel(base_panels.HTMLTextEditor): | 1111 class StatusPanel(base_panels.HTMLTextEditor): |
1003 | 1112 |
1004 EMPTY_STATUS = '<click to set a status>' | 1113 EMPTY_STATUS = '<click to set a status>' |