diff options
-rw-r--r-- | js/items.js | 355 | ||||
-rw-r--r-- | js/main.js | 6 | ||||
-rw-r--r-- | js/menu.js | 35 |
3 files changed, 299 insertions, 97 deletions
diff --git a/js/items.js b/js/items.js index b3bd46ca3..3d86d97e3 100644 --- a/js/items.js +++ b/js/items.js @@ -18,20 +18,62 @@ var t = t || function(app, string){ return string; }; // mock translation for lo (function(){ + /*########################################################################## + * Items + *########################################################################*/ /** * Creates a new item instance and tells it to put the items into * the selected div * @param cssSelector the selector of the div which holds the ul for the feeds */ var Items = function(cssSelector){ + var self = this; this._$articleList = $(cssSelector); this._$articleList.scrollTop(0); + this._itemCache = new ItemCache(); + // this array is used to store ids to prevent sending too // many posts when scrolling. the structure is: feed_id: boolean this._processing = {}; + + // mark items whose title was hid under the top edge as read + // when the bottom is reached, mark all items as read + this._$articleList.scroll(function(){ + var boxHeight = $(this).height(); + var scrollHeight = $(this).prop('scrollHeight'); + var scrolled = $(this).scrollTop() + boxHeight; + $(this).children('ul').children('.feed_item:not(.read)').each(function(){ + var item = this; + var itemOffset = $(this).position().top; + if(itemOffset <= 0 || scrolled >= scrollHeight){ + // wait and check if the item is still under the top edge + setTimeout(function(){ self._markItemAsReadTimeout(item);}, 1000); + } + }) + }); } /** + * Marks an item as read which is called by the timeout + * @param item the dom item + */ + Items.prototype._markItemAsReadTimeout = function(item) { + var itemId = parseInt($(item).data('id')); + var itemOffset = $(item).position().top; + var boxHeight = this._$articleList.height(); + var scrollHeight = this._$articleList.prop('scrollHeight'); + var scrolled = this._$articleList.scrollTop() + boxHeight; + if(itemOffset < 0 || scrolled >= scrollHeight){ + if(this._processing[itemId] === undefined || this._processing[itemId] === false){ + // mark item as processing to prevent unecessary post requests + this._processing[itemId] = true; + var handler = new News.ItemStatusHandler(itemId); + handler.setRead(true); + } + } + }; + + /** * Loads the feeds into the righ view * @param type the type (MenuNodeType) * @param id the id @@ -41,7 +83,8 @@ var t = t || function(app, string){ return string; }; // mock translation for lo var self = this; var data = { feedId: id, - feedType: type + feedType: type, + getMostRecentItemId: this._itemCache.getMostRecentItemId(type, id) }; this._$articleList.addClass('loading'); @@ -49,9 +92,11 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $.post(OC.filePath('news', 'ajax', 'loadfeed.php'), data, function(jsonData) { if(jsonData.status == 'success'){ - // FIXME: caching? split whole html into single articles - self._$articleList.empty().html(jsonData.data.feedItems) - self._bindItemEventListeners(); + self._$articleList.empty() + self._itemCache.populate(jsonData.data.feedItems); + + var $items = self._itemCache.getFeedHtml(type, id); + self._$articleList.append($items); self._$articleList.scrollTop(0); onSuccessCallback(); } else { @@ -62,13 +107,227 @@ var t = t || function(app, string){ return string; }; // mock translation for lo }); }; + /** + * Returns the most recent id of a feed from the cache + * @param type the type (MenuNodeType) + * @param id the id + * @return the most recent id that is loaded on the page or 0 + */ + Items.prototype.getMostRecentItemId = function(type, id) { + return this._itemCache.getMostRecentItemId(type, id); + }; + + /** + * Returns a jquery node by searching for its id + * @param id the id of the node + * @return the jquery node + */ + Items.prototype._findNodeById = function(id) { + id = parseInt(id); + return this._$articleList.find('.feed_item[data-id="' + id + '"]'); + }; + + + Items.prototype._toggleImportant = function(itemId) { + var $currentItem = $() + var $currentItemStar = $currentItem.children('.utils').children('.primary_item_utils').children('.star'); + var important = $currentItemStar.hasClass('important'); + if(_important){ + status = 'unimportant'; + } else { + status = 'important'; + } + + var data = { + itemId: _itemId, + status: status + }; + + $.post(OC.filePath('news', 'ajax', 'setitemstatus.php'), data, function(jsondata){ + if(jsondata.status == 'success'){ + if(_important){ + _$currentItemStar.removeClass('important'); + } else { + _$currentItemStar.addClass('important'); + } + } else{ + OC.dialogs.alert(jsondata.data.message, t('news', 'Error')); + } + }); + }; + + News.Items = Items; + + + /*########################################################################## + * ItemCache + *########################################################################*/ + /** + * A cache which holds the items of all loaded feeds + */ + var ItemCache = function() { + this._items = {}; + this._feeds = {}; + } + + /** + * Adds Html elements to the cache + * @param html the html for a complete list with items + */ + ItemCache.prototype.populate = function(html) { + var self = this; + $html = $(html); + $html.children('.feed_item').each(function(){ + var item = new Item(this); + self._items[item.getId()] = item; + self._feeds[item.getFeedId()] = self._feeds[item.getFeedId()] || {}; + self._feeds[item.getFeedId()][item.getId()] = item; + }); + }; + + /** + * Returns all the ids of feeds for a type sorted by id ascending + * @param type the type (MenuNodeType) + * @param id the id + * @return all the ids of feeds for a type sorted by id ascending + */ + ItemCache.prototype._getSortedItemIds = function(type, id) { + var itemIds = new Array(); + if(Object.keys(this._feeds).length === 0 || Object.keys(this._items).length === 0){ + return itemIds; + } + + switch(type){ + + case MenuNodeType.Feed: + if(this._feeds[id] === undefined){ + return itemIds; + } + $.each(this._feeds[id], function(key, value){ + itemIds.push(value.getId()); + }); + break; + + case MenuNodeType.Folder: + // this is a bit of a hack and not that beautiful^^ + var feedIds = News.Objects.Menu.getFeedIdsOfFolder(id); + for(var i=0; i<feedIds.length; i++){ + itemIds.concat(this._getSortedItemIds(MenuNodeType.Feed, feedIds[i])); + } + break; + + case MenuNodeType.Subscriptions: + $.each(this._items, function(key, value){ + itemIds.push(value.getId()); + }); + break; + + case MenuNodeType.Starred: + $.each(this._items, function(key, value){ + if(value.isStarred()){ + itemIds.push(value.getId()); + } + }); + break; + } + return itemIds.sort(); + }; - // FIXME + /** + * Returns the most recent id of a feed + * @param type the type (MenuNodeType) + * @param id the id + * @return the most recent id that is loaded on the page or 0 + */ + ItemCache.prototype.getMostRecentItemId = function(type, id) { + var itemIds = this._getSortedItemIds(type, id); + if(itemIds.length === 0){ + return 0; + } else { + return itemIds[itemIds.length-1]; + } + }; + + /** + * Returns the html for a specific feed + * @param type the type (MenuNodeType) + * @param id the id + * @return the jquery html element for a complete feed + */ + ItemCache.prototype.getFeedHtml = function(type, id) { + var itemIds = this._getSortedItemIds(type, id); + itemIds.reverse(); // reverse for showing newest item first + var $html = $('<ul>'); + for(var i=0; i<itemIds.length; i++){ + $html.append(this._items[itemIds[i]].getHtml()); + } + return $html; + }; + + /*########################################################################## + * Item + *########################################################################*/ + /** + * An item which binds the appropriate html and event handlers + * @param html the html to populate the item + */ + var Item = function(html){ + this._starred = false; + this._$html = $(html); + this._bindItemEventListeners(); + this._id = parseInt(this._$html.data('id')); + this._feedId = parseInt(this._$html.data('feedid')); + } + + /** + * Returns the html code for the element + * @return the html for the item + */ + Item.prototype.render = function() { + return this._$html[0]; + }; + + /** + * Returns the id of an item + * @return the id of the item + */ + Item.prototype.getId = function() { + return this._id; + }; + + + /** + * Returns the feedid of an item + * @return the feeid of the item + */ + Item.prototype.getFeedId = function() { + return this._feedId; + }; + + /** + * Returns the html of an item + * @return the jquery html of the item + */ + Item.prototype.getHtml = function() { + return this._$html; + }; + + /** + * Returns true if an item is starred + * @return true if starred, otherwise false + */ + Item.prototype.isStarred = function() { + return this._starred; + }; + + // FIXME /** * Binds a listener on the feed item list to detect scrolling and mark previous * items as read */ - Items.prototype._bindItemEventListeners = function() { + Item.prototype._bindItemEventListeners = function() { + var self = this; + // single hover on item should mark it as read too $('#feed_items h1.item_title a').click(function(){ var $item = $(this).parent().parent('.feed_item'); @@ -89,8 +348,7 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $('#feed_items li.star').click(function(){ var $item = $(this).parent().parent().parent('.feed_item'); var itemId = $item.data('id'); - var handler = new News.ItemStatusHandler(itemId); - handler.toggleImportant(); + self._toggleImportant(itemId); }); // toggle logic for the keep unread handler @@ -117,16 +375,6 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $("time.timeago").timeago(); }; - - Items.prototype.length = function(type, id) { - return 0; - }; - - Items.prototype.mostRecentItemId = function(type, id) { - return 0; - }; - - News.Items = Items; })(); @@ -134,35 +382,12 @@ var t = t || function(app, string){ return string; }; // mock translation for lo // TODO: integrate This - - - - /** - * Marks an item as read which is called by the timeout - * @param item the dom item - */ - function markItemAsRead(scrollArea, item){ - var itemId = parseInt($(item).data('id')); - var itemOffset = $(item).position().top; - var boxHeight = $(scrollArea).height(); - var scrollHeight = $(scrollArea).prop('scrollHeight'); - var scrolled = $(scrollArea).scrollTop() + boxHeight; - if(itemOffset < 0 || scrolled >= scrollHeight){ - if(News.Feed.processing[itemId] === undefined || News.Feed.processing[itemId] === false){ - // mark item as processing to prevent unecessary post requests - News.Feed.processing[itemId] = true; - var handler = new News.ItemStatusHandler(itemId); - handler.setRead(true); - } - } - } - /** * This handler handles changes in the ui when the itemstatus changes */ - ItemStatusHandler = function(itemId){ + News.ItemStatusHandler = function(itemId){ var _itemId = parseInt(itemId); - var _$currentItem = $('#feed_items li[data-id="' + itemId + '"]'); + var _feedId = _$currentItem.data('feedid'); var _read = _$currentItem.hasClass('read'); @@ -170,30 +395,7 @@ var t = t || function(app, string){ return string; }; // mock translation for lo * Switches important items to unimportant and vice versa */ var _toggleImportant = function(){ - var _$currentItemStar = _$currentItem.children('.utils').children('.primary_item_utils').children('.star'); - var _important = _$currentItemStar.hasClass('important'); - if(_important){ - status = 'unimportant'; - } else { - status = 'important'; - } - var data = { - itemId: _itemId, - status: status - }; - - $.post(OC.filePath('news', 'ajax', 'setitemstatus.php'), data, function(jsondata){ - if(jsondata.status == 'success'){ - if(_important){ - _$currentItemStar.removeClass('important'); - } else { - _$currentItemStar.addClass('important'); - } - } else{ - OC.dialogs.alert(jsondata.data.message, t('news', 'Error')); - } - }); }; /** @@ -274,26 +476,5 @@ var t = t || function(app, string){ return string; }; // mock translation for lo this.toggleKeepUnread = function(){ _toggleKeepUnread(); }; }; - // mark items whose title was hid under the top edge as read - // when the bottom is reached, mark all items as read - $('#feed_items').scroll(function(){ - var boxHeight = $(this).height(); - var scrollHeight = $(this).prop('scrollHeight'); - var scrolled = $(this).scrollTop() + boxHeight; - var scrollArea = this; - $(this).children('ul').children('.feed_item:not(.read)').each(function(){ - var item = this; - var itemOffset = $(this).position().top; - if(itemOffset <= 0 || scrolled >= scrollHeight){ - // wait and check if the item is still under the top edge - setTimeout(function(){ markItemAsRead(scrollArea, item);}, 1000); - } - }) - }); - $(document).keydown(function(e) { - if ((e.keyCode || e.which) == 74) { // 'j' key shortcut - - } - });
\ No newline at end of file diff --git a/js/main.js b/js/main.js index 23955ae8e..a6e4a9a81 100644 --- a/js/main.js +++ b/js/main.js @@ -82,5 +82,11 @@ $(document).ready(function(){ $('#feedfoldermenu').hide(); }); + $(document).keydown(function(e) { + if ((e.keyCode || e.which) == 74) { // 'j' key shortcut + + } + }); + }); diff --git a/js/menu.js b/js/menu.js index 403daa4bf..3cd42a70c 100644 --- a/js/menu.js +++ b/js/menu.js @@ -88,7 +88,7 @@ var News = News || {}; /*########################################################################## * MenuNodeType - *#########################################################################/ + *########################################################################*/ /** * Enumeration for menu items */ @@ -111,7 +111,7 @@ var News = News || {}; /*########################################################################## * Menu - *#########################################################################/ + *########################################################################*/ /** * This is the basic menu used to construct and maintain the menu * @param updateIntervalMiliseconds how often the menu should refresh @@ -287,6 +287,20 @@ var News = News || {}; }; /** + * Returns the ids of all feeds from a folder + * @param folderId the id of the folder + * @return an array with all the feed ids + */ + Menu.prototype.getFeedIdsOfFolder = function(folderId) { + $folder = this._getNodeFromTypeAndId(MenuNodeType.Folder, folderId); + var ids = new Array(); + $folder.children('ul').children('li').each(function(){ + ids.push(parseInt($(this).data('id'))); + }); + return ids; + }; + + /** * Binds the menu on an existing menu * @param css Selector the selector to get the element with jquery */ @@ -312,7 +326,7 @@ var News = News || {}; // set timeout to avoid racecondition error var self = this; setTimeout(function(){ - self._updateUnreadCountAll(); + //self._updateUnreadCountAll(); }, 1000); this.triggerHideRead(); @@ -518,34 +532,35 @@ var News = News || {}; * @param id the id */ Menu.prototype._markRead = function(type, id){ + var self = this; // make sure only feeds get past switch(type){ case MenuNodeType.Folder: var $folder = this._getNodeFromTypeAndId(type, id); $folder.children('ul').children('li').each(function(){ - var childData = this._getIdAndTypeFromNode($(this)); - this._markRead(childData.type, childData.id); + var childData = self._getIdAndTypeFromNode($(this)); + self._markRead(childData.type, childData.id); }); break; case MenuNodeType.Subscriptions: this._root.children('li').each(function(){ - var childData = this._getIdAndTypeFromNode($(this)); - this._markRead(childData.type, childData.id); + var childData = self._getIdAndTypeFromNode($(this)); + self._markRead(childData.type, childData.id); }); break; case MenuNodeType.Feed: var data = { feedId: id, - mostRecentItemId: this._items.getMostRecentItemId(id) + mostRecentItemId: this._items.getMostRecentItemId(type, id) }; $.post(OC.filePath('news', 'ajax', 'setallitemsread.php'), data, function(jsonData) { if(jsonData.status == 'success'){ - this._items.markAllRead(type, id); - this._updateUnreadCountAll(); + self._items.markAllRead(type, id); + self._updateUnreadCountAll(); } else { OC.dialogs.alert(jsonData.data.message, t('news', 'Error')); } |