home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 February / maximum-cd-2011-02.iso / DiscContents / digsby_setup85.exe / lib / plugins / twitter / res / feed.js < prev    next >
Encoding:
JavaScript  |  2009-11-04  |  17.9 KB  |  589 lines

  1.  
  2. var CONTAINER_TAG = 'div';
  3. var CONTAINER_ID = 'Chat';
  4.  
  5. function Timeline(container, skin) {
  6.     var self = this;
  7.  
  8.     this.container = container;
  9.     this.skin = skin;
  10.  
  11.     $(window).keypress(function(e) { guard(function () {
  12.         var key = String.fromCharCode(e.which);
  13.         var delta;
  14.  
  15.         if (key.toLowerCase() === "j")
  16.             delta = 1;
  17.         else if (key.toLowerCase() === "k")
  18.             delta = -1;
  19.         else if (key === ' ')
  20.             delta = e.shiftKey ? -1 : 1;
  21.         else if (key === 'p') // show timeline tweets in a popup
  22.             window.opener.account.showTimelinePopup();
  23.  
  24.         if (delta)
  25.             self.scrollItems(delta);
  26.     }); });
  27.  
  28.     this.feedView = new FeedView(container,
  29.         function(e) { return self.elemToNode(e); },
  30.         function(e) { return e.id; },
  31.         function(n) { return n.created_at_ms; },
  32.         function(e) { return e.created_at_ms; });
  33. }
  34.  
  35. // called manually by TwitterFrame.OnActivate
  36. // TODO: make wxWebKit document.onblur and onfocus work correctly and use
  37. // those instead.
  38. function onFrameActivate(active) {
  39.     guard(function() { 
  40.         window.focused = active;
  41.         window.timeline.setActive(active);
  42.     });
  43. }
  44.  
  45. Timeline.prototype = {
  46.     toString: function() {
  47.         var count = 0;
  48.         for (var key in this.dataMap) ++count;
  49.         return '[Timeline with ' + count + ' items]';
  50.     },
  51.  
  52.     // twitter specific stuff
  53.     elemKey: function(e) {
  54.         var ms = e.created_at_ms;
  55.         if (ms === undefined)
  56.             console.error('missing created_at_ms: ' + e);
  57.         return ms;
  58.     },
  59.  
  60.     elemToNode: function(e) {
  61.         var id = e.id;
  62.         var node = document.getElementById(e.id);
  63.         if (node) return node;
  64.  
  65.         var screen_name = tweetScreenName(this.account, e);
  66.  
  67.         var username;
  68.         if (get(settings, 'show_real_names', false))
  69.             username = tweetUserName(this.account, e);
  70.         else
  71.             username = screen_name;
  72.  
  73.  
  74.         var classnames = 'timeline';
  75.         var visual_classnames = '';
  76.  
  77.         if (e.sender_id)
  78.             classnames = 'direct';
  79.         else if (this.account.isSelfTweet(e))
  80.             classnames = 'sent';
  81.         else if (e.mention)
  82.             classnames = 'mention';
  83.         else if (e.search)
  84.             classnames = 'search';
  85.  
  86.         if (e.read && !this.displayAllAsUnread)
  87.             visual_classnames += ' context';
  88.  
  89.         var time;
  90.         if (e.created_at)
  91.             time = prettyDate(e.created_at);
  92.  
  93.         //this.dataMap[id] = e;
  94.  
  95.         username = '<a onclick="javascript:return tweetActions.openUser(this);" href="http://twitter.com/' + screen_name + '">' + username + '</a>';
  96.  
  97.         var timeToolTip = longDateFormat(new Date(e.created_at));
  98.  
  99.         var directTarget;
  100.         if (e.recipient_id)
  101.             directTarget = ' to ' + directTargetScreenName(this.account, e);
  102.  
  103.         node = this.skin.createItem(this.container.ownerDocument,
  104.             {id: id,
  105.              type: classnames,
  106.              sender: username,
  107.              message: twitterLinkify(e.text),
  108.              classNames: classnames + visual_classnames,
  109.              time: time,
  110.              timeToolTip: timeToolTip,
  111.              reply: e.in_reply_to_status_id,
  112.              directTarget: directTarget,
  113.              source: e.source,
  114.              lazy_image: tweetUserImage(this.account, e),
  115.              favorited: e.favorited}
  116.         );
  117.  
  118.         node.created_at_ms = e.created_at_ms;
  119.  
  120.         return node;
  121.     },
  122.  
  123.     nodeToElem: function(n) {
  124.         var id = n.getAttribute('id');
  125.         var elem = this.dataMap[id];
  126.         assert(elem !== undefined, 'could not find tweet for id ' + id);
  127.         return elem;
  128.     },
  129.  
  130.     insertAsParent: function(elem, node) {
  131.         var self = this;
  132.         var newNode = this.elemToNode(elem);
  133.         node.appendChild(this.skin.makeReplyArrow());
  134.         this.skin.setHidden(newNode, true);
  135.  
  136.         this.maintainNodePosition(node, function() {
  137.             self.container.insertBefore(newNode, node);
  138.             self.skin.setHidden(newNode, false);
  139.         });
  140.  
  141.         this.skin.loadAllLazy(newNode);
  142.     },
  143.  
  144.     maintainNodePosition: function (node, func) {
  145.         this.pinnedItem = node;
  146.         this.pinnedItemOffset = node.offsetTop;
  147.         this.pinToBottom = false;
  148.  
  149.         guard(func);
  150.  
  151.         this.restoreItemPosition();
  152.     },
  153.  
  154.     /**
  155.      * called when the webpage is activated or deactivated
  156.      */
  157.     setActive: function(active) {
  158.         var self = this;
  159.         if (!active && settings.frame_focus_unread && !this.displayAllAsUnread) {
  160.             // mark all read elements as read visually when the top level frame
  161.             // is deactivated.
  162.             $.each(this.container.childNodes, function (i, elem) {
  163.                 var id = elem.getAttribute('id');
  164.                 var item = self.dataMap[id];
  165.                 if (item && item.read && !self.skin.isMarkedAsRead(elem))
  166.                     self.skin.markAsRead(elem, true);
  167.             });
  168.         }
  169.     },
  170.  
  171.     onWindowResized: function() {
  172.         this.restoreItemPosition();
  173.     },
  174.  
  175.     restoreItemPosition: function() {
  176.         if (this.pinnedItem !== undefined) {
  177.             // during window resizing, maintains the vertical position of the first
  178.             // visible element in container 
  179.             //
  180.             // TODO: figure out why scrollBy from within window.onresize
  181.             // doesn't work in chrome/safari...
  182.             var y;
  183.             if (settings.autoscroll_when_at_bottom && this.pinToBottom)
  184.                 y = document.height;
  185.             else
  186.                 y = this.pinnedItem.offsetTop - this.pinnedItemOffset;
  187.  
  188.             //console.warn('*** restoreItemPosition: delta ' + y);
  189.             window.scrollBy(0, y);
  190.  
  191.             // hack to fix scrolling to almost bottom
  192.             if (this.nearBottom())
  193.                 window.scrollTo(0, document.height);
  194.  
  195.             this.onWindowScrolled();
  196.         }
  197.     },
  198.  
  199.     loadImage: function(item) {
  200.         if (this.skin.isEmptyImage(item))
  201.             this.skin.loadAllLazy(item);
  202.     },
  203.  
  204.     onStoppedScrolling: function() {
  205.         this.stopScrollingTimer = undefined;
  206.  
  207.         if (this.topItem && this.bottomItem) {
  208.             var item = this.topItem;
  209.             while (item != this.bottomItem) {
  210.                 this.loadImage(item);
  211.                 item = item.nextSibling;
  212.             }
  213.             this.loadImage(item);
  214.             
  215.             item = item.nextSibling;
  216.             this.loadImage(item);
  217.         }
  218.     },
  219.  
  220.     onWindowScrolled: function() {
  221.         this.saveItemPosition();
  222.     },
  223.  
  224.     saveItemPosition: function(markAsRead) {
  225.         if (markAsRead === undefined)
  226.             markAsRead = true;
  227.  
  228.         var self = this;
  229.         var container = this.container;
  230.         var children = container.childNodes;
  231.         var containerOffset = container.offsetTop;
  232.         var length = children.length;
  233.  
  234.         var pageY = window.pageYOffset;
  235.         var pageBottom = pageY + window.innerHeight;
  236.  
  237.         // reset the scrolling timer
  238.         if (this.stopScrollingTimer)
  239.             clearTimeout(this.stopScrollingTimer);
  240.         this.stopScrollingTimer = setTimeout(function() { self.onStoppedScrolling(); }, 300);
  241.         
  242.         // TODO: does zooming make these calculations inaccurate?
  243.         this.topItem = undefined;
  244.         this.bottomItem = undefined;
  245.  
  246.         var markElemAsRead;
  247.         if (markAsRead && !this._pauseMarkAsRead)
  248.             markElemAsRead = function (elem) {
  249.                 var id = elem.getAttribute('id');
  250.                 if (id) {
  251.                     var item = self.dataMap[id];
  252.                     self.account.markAsRead(item);
  253.                     if (!self.displayAllAsUnread && !settings.frame_focus_unread)
  254.                         self.skin.markAsRead(elem, true);
  255.                 }
  256.             };
  257.         else
  258.             markElemAsRead = function(elem) {};
  259.  
  260.         this.pinToBottom = pageBottom >= document.height;
  261.  
  262.         var i, elem, offset;
  263.  
  264.         if (pageY > document.height/2) {
  265.             // start from the bottom
  266.             for (i = length-1; i >= 0; --i) {
  267.                 elem = children[i];
  268.                 offset = elem.offsetTop;
  269.                 if (this.bottomItem === undefined && offset + containerOffset + elem.offsetHeight < pageBottom) 
  270.                     this.bottomItem = elem;
  271.  
  272.                 if (this.bottomItem !== undefined) 
  273.                     markElemAsRead(elem);
  274.  
  275.                 if (offset + containerOffset < pageY) {
  276.                     this.topItem = elem;
  277.                     var item = i < children.length-1 ? children[i+1] : children[i];
  278.                     this.pinnedItem = item;
  279.                     this.pinnedItemOffset = item.offsetTop;
  280.                     break;
  281.                 }
  282.             }
  283.         } else {
  284.             // start from the top
  285.             var foundPinned = false;
  286.             for (i = 0; i < length; ++i) {
  287.                 elem = children[i];
  288.                 offset = elem.offsetTop;
  289.                 if (!foundPinned && offset + containerOffset >= pageY) {
  290.                     foundPinned = true;
  291.                     this.pinnedItem = elem;
  292.                     this.pinnedItemOffset = offset;
  293.                     this.topItem = i > 0 ? children[i-1] : children[i];
  294.                     markElemAsRead(this.topItem);
  295.                 }
  296.  
  297.                 var shouldBreak = false;
  298.                 if (i === length - 1)
  299.                     this.bottomItem = elem;
  300.                 else if (offset + containerOffset + elem.offsetHeight >= pageBottom) {
  301.                     var visualBottom = i > 0 ? children[i-1] : children[i];
  302.                     this.bottomItem = children[i];
  303.                     markElemAsRead(visualBottom);
  304.                     shouldBreak = true;
  305.                 }
  306.  
  307.                 if (shouldBreak) break;
  308.                 else if (foundPinned) markElemAsRead(elem);
  309.             }
  310.         }
  311.     },
  312.  
  313.     atBottom: function() {
  314.         return document.body.scrollTop >= 
  315.             document.body.offsetHeight - window.innerHeight;
  316.     },
  317.  
  318.     nearBottom: function(fuzz) {
  319.         if (fuzz === undefined) fuzz = 12;
  320.         var val = Math.abs(document.body.scrollTop - 
  321.             (document.body.offsetHeight - window.innerHeight));
  322.         return val < fuzz;
  323.     },
  324.  
  325.     scrollToNew: function() {
  326.         console.warn('scrollToNew');
  327.         //printStackTrace();
  328.  
  329.         var self = this;
  330.         var y = document.height;
  331.         var found = false;
  332.  
  333.         $.each(this.container.childNodes, function (i, node) {
  334.             var id = node.getAttribute('id');
  335.             var elem = self.dataMap[id];
  336.             if (elem && !elem.read) {
  337.                 found = true;
  338.                 y = node.offsetTop - 30;
  339.                 return false;
  340.             }
  341.         });
  342.  
  343.         document.body.offsetHeight; // force a layout
  344.         // console.warn('*** scrollToNew: ' + y);
  345.         if (!found) {
  346.             window.scrollTo(window.pageOffsetX, y);
  347.             this.onWindowScrolled();
  348.         } else {
  349.             window.scrollTo(window.pageOffsetX, y - window.innerHeight);
  350.  
  351.             var initialDelay = 0;
  352.             var timeToScroll = 200;
  353.             var easeEffect   = undefined;//'easeOutExpo';
  354.             
  355.             var animateFunc = function() {
  356.                 $('body').animate({scrollTop: y}, timeToScroll, easeEffect);
  357.             };
  358.  
  359.             if (initialDelay) setTimeout(animateFunc, initialDelay);
  360.             else animateFunc();
  361.         }
  362.     },
  363.  
  364.     viewChanged: function(feed) {
  365.         var self = this;
  366.         if (typeof feed === 'string')
  367.             feed = this.account.feeds[feed];
  368.  
  369.         if (!feed || this.feed === feed)
  370.             return;
  371.  
  372.         if (this.feed !== undefined)
  373.             this.feed.removeFeedListener(this);
  374.         this.feed = feed;
  375.         this.feed.addFeedListener(this);
  376.  
  377.         console.log('viewChanged: ' + feed);
  378.  
  379.         this.displayAllAsUnread = feed.displayAllAsUnread;
  380.         
  381.         var footer;
  382.         console.log('viewChanged - feed.type: ' + feed.type);
  383.         if (feed.query) {
  384.             if (!feed.save) {
  385.                 var save = function() {
  386.                     self.account.notifyClients('editFeed', feed.serialize()); 
  387.                 }, hide = function() {
  388.                     self.skin.setFooter();
  389.                     self.scrollToBottom();
  390.                 };
  391.  
  392.                 feed.footer = [ [save, 'save this search for later'],
  393.                                      [hide, 'hide'] ];
  394.             } else {
  395.                 feed.footer = undefined;
  396.             }
  397.         }
  398.  
  399.         this.sync();
  400.  
  401.         if (feed.shouldUpdateOnView()) {
  402.             console.log('updating lazy source now: ' + feed.source);
  403.  
  404.             var onDone = function() { self.finish(true, {scrollToBottom: feed.scrollToBottom}); };
  405.             var obj = {success: onDone, error: onDone};
  406.             feed.source.update(obj);
  407.         }
  408.     },
  409.  
  410.     sync: function(footer) {
  411.         var items = this.feed.items;
  412.         this.feedView.sync(items);
  413.         this.skin.setFooter(items.length ? this.feed.footer : undefined);
  414.     },
  415.  
  416.     finish: function (scrollToNew, opts) {
  417.         if (scrollToNew === undefined)
  418.             scrollToNew = true;
  419.         if (opts === undefined)
  420.             opts = {};
  421.  
  422.         this.pauseMarkAsRead(true);
  423.  
  424.         console.warn('in finish');
  425.         console.warn('  scrollToNew is ' + scrollToNew);
  426.         console.warn('  opts are ' + JSON.stringify(opts));
  427.         console.warn('  window.focused is ' + window.focused);
  428.  
  429.         if (!this.toDelete) this.toDelete = [];
  430.         if (!this.toInsert) {
  431.             this.toInsert = [];
  432.             this.toInsertMap = {};
  433.         }
  434.         var self = this;
  435.  
  436.         if (!opts.scrollToBottom && !scrollToNew)
  437.             this.saveItemPosition();
  438.  
  439.         this.sync();
  440.  
  441.         if (this.delayedFooter) {
  442.             this.skin.setFooter(this.delayedFooter);
  443.             this.delayedFooter = undefined;
  444.         }
  445.  
  446.         this.pauseMarkAsRead(false, function() {
  447.             if (opts.scrollToBottom)
  448.                 self.scrollToBottom();
  449.             else if (scrollToNew)
  450.                 self.scrollToNew();
  451.             else
  452.                 self.restoreItemPosition();
  453.         });
  454.     },
  455.  
  456.     pauseMarkAsRead: function(pause, func) {
  457.         console.warn('pauseMarkAsRead: ' + pause);
  458.         if (pause)
  459.             this._pauseMarkAsRead = pause;
  460.         else {
  461.             func();
  462.             this._pauseMarkAsRead = pause;
  463.             this.saveItemPosition();
  464.         }
  465.     },
  466.  
  467.     scrollToBottom: function() {
  468.         document.body.offsetHeight; // force a layout
  469.         console.warn('scrollToBottom: ' + document.height);
  470.         window.scrollTo(window.pageOffsetX, document.height);
  471.     },
  472.  
  473.     scrollToBottomIfAtBottom: function(func) {
  474.         console.warn('scrollToBottomIfAtBottom');
  475.         var atBottom = this.atBottom();
  476.         func();
  477.  
  478.         if (atBottom)
  479.             this.scrollToBottom();
  480.         else
  481.             this.restoreItemPosition();
  482.     },
  483.  
  484.     scrollItems: function(i) {
  485.         var node = this.pinnedItem;
  486.         if (i > 0) {
  487.             while (node && i--) node = node.nextSibling;
  488.         } else if (i < 0) {
  489.             while (node && i++) node = node.previousSibling;
  490.         }
  491.  
  492.         this.scrollToItem(node);
  493.     },
  494.  
  495.     scrollToItem: function(node) {
  496.         // console.warn('*** scrollToItem: ' + node.offsetTop);
  497.         window.scrollTo(window.pageOffsetX, node.offsetTop);
  498.     },
  499.  
  500.     registerEventHandlers: function(window) {
  501.         var self = this;
  502.  
  503.         $(window).resize(function() {
  504.             guard(function() { self.onWindowResized(); });
  505.         });
  506.  
  507.         $(window).scroll(function() {
  508.             guard(function() { self.onWindowScrolled(); });
  509.         });
  510.     },
  511.  
  512.     onClosing: function() {
  513.         this.account.timelineClosing();
  514.         this.account.timeline = undefined;
  515.         this.feed.removeFeedListener(this);
  516.     },
  517.  
  518.     updateTimestamps: function () {
  519.         var self = this;
  520.         $(".timeDescription").each(function (i, e) {
  521.             var tweet = self.nodeToElem($(e).parents(".container")[0]);
  522.             if (tweet) {
  523.                 var time = prettyDate(tweet.created_at);
  524.                 var timeToolTip = longDateFormat(new Date(tweet.created_at));
  525.                 if (e.innerHTML !== time)
  526.                     e.innerHTML = time;
  527.             }
  528.         });
  529.     }
  530. };
  531.  
  532. CONTAINER_TAG = 'div';
  533. CONTAINER_ID = 'Chat';
  534.  
  535. function feed_onunload() {
  536.     window.timeline.onClosing();
  537.     window.opener.onChildWindowUnloaded(window);
  538. }
  539.  
  540. function feed_onload() {
  541.     guard(function() {
  542.     console.log('feed_onload');
  543.     var skin = new SmoothOperator();
  544.  
  545.     var container = document.createElement(CONTAINER_TAG);
  546.     container.setAttribute('id', CONTAINER_ID);
  547.     document.body.appendChild(container);
  548.  
  549.     window.timeline = new Timeline(container, skin);
  550.     window.timeline.window = window;
  551.     window.timeline.registerEventHandlers(window);
  552.     window.opener.onChildWindowLoaded(window);
  553.  
  554.     // init history stuff
  555.     var pageload = function(hash) {
  556.         guard(function() {
  557.             window.opener.account.changeView(hash);
  558.         });
  559.     };
  560.  
  561.     $.historyInit(pageload, "feed.html");
  562.     });
  563. }
  564.  
  565. function historyOnClick() {
  566.     $.historyLoad(this.href.replace(/^.*#/, ''));
  567.     return false;
  568. }
  569.  
  570. (function() {
  571.     function pad(n) { return n.toString().length == 1 ? '0' + n : n; }
  572.  
  573.     function dateFormat(d) {
  574.         var a, h = d.getHours(), m = d.getMinutes();
  575.         if (h > 12) {
  576.             h -= 12;
  577.             a = ' PM';
  578.         } else {
  579.             if (h === 0) h = 12;
  580.             a = ' AM';
  581.         }
  582.  
  583.         return pad(h) + ':' + pad(m) + a;
  584.     }
  585.     
  586.     window.dateFormat = dateFormat;
  587. })();
  588.  
  589.