Skip to content
Snippets Groups Projects
Commit d5649c1e authored by Benoit Lavenier's avatar Benoit Lavenier
Browse files

fix(home): Feed: fix feed load, when cache storage enabled + code refactor

parent 661a8de5
No related branches found
No related tags found
No related merge requests found
...@@ -28,9 +28,6 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -28,9 +28,6 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
return null; return null;
}) })
.then(function(feed) { .then(function(feed) {
// Clean title (remove duplicated titles)
$scope.prepareJsonFeed(feed);
$scope.loading = false; $scope.loading = false;
$scope.feed = feed; $scope.feed = feed;
var showFeed = feed && feed.items && feed.items.length > 0 || false; var showFeed = feed && feed.items && feed.items.length > 0 || false;
...@@ -49,15 +46,21 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -49,15 +46,21 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
var minDate = maxAgeInMonths > 0 ? moment().subtract(maxAgeInMonths, 'month').startOf('day').utc() : undefined; var minDate = maxAgeInMonths > 0 ? moment().subtract(maxAgeInMonths, 'month').startOf('day').utc() : undefined;
$scope.search.minTime = minDate && minDate.unix(); $scope.search.minTime = minDate && minDate.unix();
// Reset load counter
$scope.search.loadCount = 0;
var now = Date.now(); var now = Date.now();
console.debug("[feed] Loading from {url: {0}, minTime: '{1}'}".format(feedUrl, minDate && minDate.toISOString() || 'all')); console.debug("[feed] Loading from {url: {0}, minTime: '{1}'}".format(feedUrl, minDate && minDate.toISOString() || 'all'));
return $scope.getJsonFeed(feedUrl) return $scope.loadJsonFeed(feedUrl)
.then(function (feed) { .then(function (feed) {
console.debug('[feed] {0} items loaded in {0}ms'.format(feed && feed.items && feed.items.length || 0, Date.now() - now)); console.debug('[feed] {0} items loaded in {0}ms'.format(feed && feed.items && feed.items.length || 0, Date.now() - now));
if (!feed || !feed.items) return null; // skip if (!feed || !feed.items) return null; // skip
// Clean title (remove duplicated titles)
$scope.cleanDuplicatedTitles(feed);
return feed; return feed;
}) })
.catch(function(err) { .catch(function(err) {
...@@ -66,26 +69,30 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -66,26 +69,30 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
}); });
}; };
$scope.getJsonFeed = function(feedUrl, maxCount) { /**
* Load a JSON file, from the given URL, and convert into the JSON Feed format
* @param feedUrl
* @param maxCount
* @returns {*}
*/
$scope.loadJsonFeed = function(feedUrl, maxItemCount) {
var locale = $translate.use(); var locale = $translate.use();
maxCount = maxCount || $scope.search.maxCount;
var minTime = $scope.search.minTime; var minTime = $scope.search.minTime;
maxItemCount = maxItemCount || $scope.search.maxCount;
// Increment load counter (to avoid infinite loop)
$scope.search.loadCount++; $scope.search.loadCount++;
return $scope.getJson(feedUrl) return $scope.getJson(feedUrl)
.then(function(json) { .then(function(json) {
// Detect Discourse category, then convert // Parse JSON from discourse
if ($scope.isDiscourseCategory(json)) { if ($scope.isJsonDiscourse(json)) {
return $scope.parseDiscourseCategory(feedUrl, json); return $scope.parseJsonDiscourse(feedUrl, json);
} }
// Detect Discourse topic, then convert // Return a copy (to avoid any change in cached data)
else if ($scope.isDiscourseTopic(json)) { return angular.copy(json);
return $scope.parseDiscourseTopic(feedUrl, json);
}
return json;
}) })
.then(function(feed) { .then(function(feed) {
if (!feed || !feed.items && !feed.next_url) return null; // skip if (!feed || !feed.items && !feed.next_url) return null; // skip
...@@ -115,6 +122,7 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -115,6 +122,7 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
return res.concat(item); return res.concat(item);
}, []); }, []);
return feed; return feed;
}) })
.then(function(feed) { .then(function(feed) {
...@@ -122,23 +130,23 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -122,23 +130,23 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
feed.items = feed.items || []; feed.items = feed.items || [];
// Slice to keep last (more recent) items // Slice to keep last (more recent) items
if (feed.items.length > maxCount) { if (feed.items.length > maxItemCount) {
feed.items = feed.items.slice(feed.items.length - maxCount); feed.items = feed.items.slice(feed.items.length - maxItemCount);
return feed; return feed;
} }
// Not enough items: try to fetch more // Not enough items: try to fetch more
var canFetchMore = feed.next_url && feed.next_url !== feedUrl && $scope.search.loadCount < $scope.search.maxLoadCount; var canFetchMore = feed.next_url && feed.next_url !== feedUrl && $scope.search.loadCount < $scope.search.maxLoadCount;
if (canFetchMore && feed.items.length < maxCount) { if (canFetchMore && feed.items.length < maxItemCount) {
console.debug("[feed] Loading from {next_url: '{0}'}".format(feed.next_url)); console.debug("[feed] Loading from {next_url: '{0}'}".format(feed.next_url));
// Fetch more // Fetch more
return $scope.getJsonFeed(feed.next_url, maxCount - feed.items.length) return $scope.loadJsonFeed(feed.next_url, maxItemCount - feed.items.length)
.then(function(moreFeed) { .then(function(moreFeed) {
// Append new items // Append new items
if (moreFeed && moreFeed.items && moreFeed.items.length) { if (moreFeed && moreFeed.items && moreFeed.items.length) {
feed.items = feed.items.concat(moreFeed.items.slice(0, maxCount - feed.items.length)); feed.items = feed.items.concat(moreFeed.items.slice(0, maxItemCount - feed.items.length));
} }
return feed; return feed;
...@@ -147,8 +155,13 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -147,8 +155,13 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
return feed; return feed;
}); });
} };
/**
* Fetch a JSON file, from a URL. Use a cache (of 1 hour) to avoid to many network request
* @param url
* @returns {*}
*/
$scope.getJson = function(url) { $scope.getJson = function(url) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
$http.get(url, { $http.get(url, {
...@@ -165,6 +178,15 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -165,6 +178,15 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
return (!item || (!item.title && !item.content_text && !item.content_html)); return (!item || (!item.title && !item.content_text && !item.content_html));
}; };
/**
* Prepare a feed for the template :
* - set 'time' with a unix timestamp
* - set 'content' with the HTML or text content (truncated if too long)
* - fill authors if not exists, using feed authors
* @param item
* @param feed
* @returns {{content}|*}
*/
$scope.prepareJsonFeedItem = function(item, feed) { $scope.prepareJsonFeedItem = function(item, feed) {
if ($scope.isEmptyFeedItem(item)) throw Error('Empty feed item') if ($scope.isEmptyFeedItem(item)) throw Error('Empty feed item')
...@@ -197,15 +219,14 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -197,15 +219,14 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
item.authors = item.authors || feed.authors; item.authors = item.authors || feed.authors;
return item; return item;
} };
/** /**
* Prepare feed (e.g. clean duplicated title, when feed URL is a discourse topic, all item will have the same title) * Prepare feed (e.g. clean duplicated title, when feed URL is a discourse topic, all item will have the same title)
* @param feed * @param feed
* @returns {*} * @returns {*}
*/ */
$scope.prepareJsonFeed = function(feed) { $scope.cleanDuplicatedTitles = function(feed) {
if (!feed || !feed.items) return feed; if (!feed || !feed.items) return feed;
_.forEach(feed.items, function(item, index) { _.forEach(feed.items, function(item, index) {
...@@ -216,12 +237,39 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -216,12 +237,39 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
return feed; return feed;
}; };
/**
* Detect this the given JSON is from Discourse
* @param json
* @returns {*}
*/
$scope.isJsonDiscourse = function(json) {
return $scope.isDiscourseCategory(json) || $scope.isDiscourseTopic(json);
};
/**
* Transform a JSON from Discourse into JSON feed
* @param feedUrl
* @param json
* @returns {*}
*/
$scope.parseJsonDiscourse = function(feedUrl, json) {
// Detect if category category
if ($scope.isDiscourseCategory(json)) {
// Convert category to feed
return $scope.parseDiscourseCategory(feedUrl, json);
}
// Convert topic to feed
return $scope.parseDiscourseTopic(feedUrl, json);
};
$scope.isDiscourseCategory = function(category) { $scope.isDiscourseCategory = function(category) {
return category && category.topic_list && Array.isArray(category.topic_list.topics); return category && category.topic_list && Array.isArray(category.topic_list.topics) &&
} !!category.topic_list.more_topics_url || false;
};
$scope.parseDiscourseCategory = function(url, category, locale) { $scope.parseDiscourseCategory = function(url, category, locale) {
// Make sure this is a valid topic // Check is a discourse category
if (!$scope.isDiscourseCategory(category)) throw new Error('Not a discourse category'); if (!$scope.isDiscourseCategory(category)) throw new Error('Not a discourse category');
locale = locale || $translate.use(); locale = locale || $translate.use();
...@@ -242,28 +290,38 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -242,28 +290,38 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
// Exclude category description (=tag 'about-category') // Exclude category description (=tag 'about-category')
if (topic.tags && topic.tags.includes('about-category')) return res; if (topic.tags && topic.tags.includes('about-category')) return res;
// Skip if not expected language // Detect language, from the title. Then skip if not compatible with expected locale
var language = $scope.getLanguageFromTitle(topic.title); var topicLanguage = $scope.getLanguageFromTitle(topic.title);
if (!$scope.isCompatibleLanguage(locale, language)) return res; if (!$scope.isCompatibleLanguage(locale, topicLanguage)) return res;
// Compute the URL to load the topic
var topicUrl = [baseUrl, 't', topic.slug, topic.id].join('/') + '.json'; var topicUrl = [baseUrl, 't', topic.slug, topic.id].join('/') + '.json';
return res.concat($scope.getJson(topicUrl))
}, []) // Load topic JSON
).then(function(topics) { return res.concat($scope.getJson(topicUrl)
feed.items = topics.reduce(function(res, topic) { .catch(function(err) {
if (!$scope.isDiscourseTopic(topic)) return res; // Not a topic: skip console.error("[feed] Failed to load discourse topic from '{}'".format(topicUrl), err);
var feedTopic = $scope.parseDiscourseTopic(baseUrl, topic, feed); return null; // continue
})
if (!feedTopic.items || !feedTopic.items.length) return res; // Topic is empty: skip )
return res.concat(feedTopic.items[0]); }, []))
}, []); .then(function(topics) {
feed.items = topics.reduce(function(res, topic) {
if (!$scope.isDiscourseTopic(topic)) return res; // Not a topic: skip
var feedTopic = $scope.parseDiscourseTopic(baseUrl, topic, feed);
if (!feedTopic.items || !feedTopic.items.length) return res; // Topic is empty: skip
return res.concat(feedTopic.items[0]);
}, []);
return feed; return feed;
}); });
} };
$scope.isDiscourseTopic = function(topic) { $scope.isDiscourseTopic = function(topic) {
return topic && topic.title && topic.post_stream && Array.isArray(topic.post_stream.posts); return topic && topic.title && topic.post_stream && Array.isArray(topic.post_stream.posts);
} };
$scope.parseDiscourseTopic = function(url, topic, feed) { $scope.parseDiscourseTopic = function(url, topic, feed) {
// Make sure this is a valid topic // Make sure this is a valid topic
...@@ -273,7 +331,8 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -273,7 +331,8 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
var baseUrl = uri.protocol + '//' + uri.host + (uri.port != 443 && uri.port != 80 ? uri.port : ''); var baseUrl = uri.protocol + '//' + uri.host + (uri.port != 443 && uri.port != 80 ? uri.port : '');
// Clean title (e.g. remove '(fr)' or '(en)' or '(es)') // Clean title (e.g. remove '(fr)' or '(en)' or '(es)')
var title = $scope.cleanTitle(topic.title); // Prefer unicode title (e.g. emoji are replaced)
var title = $scope.cleanTitle(topic.unicode_title || topic.title);
var language = $scope.getLanguageFromTitle(topic.title); var language = $scope.getLanguageFromTitle(topic.title);
// Prepare root feed, if not yet exists // Prepare root feed, if not yet exists
...@@ -335,7 +394,7 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -335,7 +394,7 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
$scope.cleanTitle = function(title) { $scope.cleanTitle = function(title) {
if (!title) return undefined; if (!title) return undefined;
return title.replace(/\s\([a-z]{2}(:?-[A-Z]{2})?\)$/, ''); return title.replace(/\s\([a-z]{2}(:?-[A-Z]{2})?\)$/, '');
} };
/** /**
* Clean a title : remove locale string at the end (e.g. '(fr)' or '(en)' or '(es)') * Clean a title : remove locale string at the end (e.g. '(fr)' or '(en)' or '(es)')
...@@ -345,17 +404,17 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt ...@@ -345,17 +404,17 @@ function FeedController($scope, $timeout, $http, $translate, $q, csConfig, csHtt
if (!title) return undefined; if (!title) return undefined;
var matches = /\s\(([a-z]{2}(:?-[A-Z]{2})?)\)$/.exec(title); var matches = /\s\(([a-z]{2}(:?-[A-Z]{2})?)\)$/.exec(title);
return matches && matches[1]; return matches && matches[1];
} };
$scope.isCompatibleLanguage = function(expectedLocale, language) { $scope.isCompatibleLanguage = function(expectedLocale, language) {
if (!expectedLocale || !language || expectedLocale === language) return true; if (!expectedLocale || !language || expectedLocale === language) return true;
// Extract the language from the locale, then compare // Extract the language from the locale, then compare
// E.g. 'fr-FR' => 'fr' // E.g. 'fr-FR' => 'fr'
const expectedLanguage = expectedLocale.split('-', 2)[0]; var expectedLanguage = expectedLocale.split('-', 2)[0];
return expectedLanguage.toLowerCase() === language.toLowerCase(); return expectedLanguage.toLowerCase() === language.toLowerCase();
} };
csSettings.api.locale.on.changed($scope, function() { csSettings.api.locale.on.changed($scope, function() {
if ($scope.loading) return; if ($scope.loading) return;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment