// ==UserScript==
// @name Feedly filtering and sorting
// @namespace https://github.com/soufianesakhi/feedly-filtering-and-sorting
// @description Enhance the feedly website with advanced filtering, sorting and more
// @author Soufiane Sakhi
// @license MIT licensed, Copyright (c) 2016 Soufiane Sakhi (https://opensource.org/licenses/MIT)
// @homepage https://github.com/soufianesakhi/feedly-filtering-and-sorting
// @supportURL https://github.com/soufianesakhi/feedly-filtering-and-sorting/issues
// @icon https://raw.githubusercontent.com/soufianesakhi/feedly-filtering-and-sorting/master/web-ext/icons/128.png
// @require http://code.jquery.com/jquery.min.js
// @resource jquery.min.js http://code.jquery.com/jquery.min.js
// @require https://greasyfork.org/scripts/19857-node-creation-observer/code/node-creation-observer.js?version=126895
// @resource node-creation-observer.js https://greasyfork.org/scripts/19857-node-creation-observer/code/node-creation-observer.js?version=126895
// @include *://feedly.com/*
// @version 2.2.0
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_getResourceText
// @downloadURL none
// ==/UserScript==
var ext = {
"plusIconLink": "https://cdn0.iconfinder.com/data/icons/social-messaging-ui-color-shapes/128/add-circle-blue-128.png",
"eraseIconLink": "https://cdn2.iconfinder.com/data/icons/large-glossy-svg-icons/512/erase_delete_remove_wipe_out-128.png",
"closeIconLink": "https://cdn2.iconfinder.com/data/icons/social-productivity-line-art-1/128/close-cancel-128.png",
"urlPrefixPattern": "https?:\/\/[^\/]+\/i\/",
"settingsBtnPredecessorSelector": ".button-refresh",
"articleSelector": ".list-entries > .entry",
"sectionSelector": "#timeline > .section",
"publishAgeSpanSelector": ".ago",
"publishAgeTimestampAttr": "title",
"readArticleClass": "read",
"articleSourceSelector": ".source",
"subscriptionChangeSelector": "header .heading",
"articleTitleAttribute": "data-title",
"articleEntryIdAttribute": "data-entryid",
"popularitySelector": ".engagement",
"hidingInfoSibling": "header > h1 > .button-dropdown",
"fullyLoadedArticlesSelector": ".giant-mark-as-read",
"notFollowedPageSelector": "button.follow",
"lastReadEntryId": "lastReadEntry",
"keepNewArticlesUnreadId": "keepNewArticlesUnread",
"articlesToMarkAsReadId": "articlesToMarkAsRead",
"sortedVisibleArticlesId": "sortedVisibleArticles",
"isOpenAndMarkAsReadId": "isOpenAndMarkAsRead",
"openAndMarkAsReadClass": "open-in-new-tab-button"
};
var exported = {};
function $id(id) {
return $('#' + id);
}
function bindMarkup(html, bindings) {
bindings.forEach(function (binding) {
html = html.replace(new RegExp("\{\{" + binding.name + "\}\}", "g"), "" + binding.value);
});
return html;
}
function callbackBindedTo(thisArg) {
return (function (callback) {
return callback.bind(this);
}).bind(thisArg);
}
function capitalizeFirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function isRadioChecked(input) {
return input.is(':checked');
}
function setRadioChecked(htmlId, checked) {
$id(htmlId).prop('checked', checked);
}
function registerAccessors(srcObject, srcFieldName, targetPrototype, setterCallback, setterCallbackThisArg, fieldObjectName) {
for (var field in srcObject) {
var type = typeof (srcObject[field]);
if (type === "object") {
if (!$.isArray(srcObject[field])) {
registerAccessors(srcObject[field], srcFieldName, targetPrototype, setterCallback, setterCallbackThisArg, field);
}
}
else if (type !== "function") {
var accessorName = capitalizeFirst(field);
if (fieldObjectName != null) {
accessorName += "_" + capitalizeFirst(fieldObjectName);
}
var getterName = (type === "boolean" ? "is" : "get") + accessorName;
var setterName = "set" + accessorName;
(function () {
var callbackField = field;
var getFinalObj = function (callbackSrcObj) {
return fieldObjectName == null ? callbackSrcObj : callbackSrcObj[fieldObjectName];
};
if (targetPrototype[getterName] == null) {
targetPrototype[getterName] = function () {
var finalObj = getFinalObj(this[srcFieldName]);
return finalObj[callbackField];
};
}
if (targetPrototype[setterName] == null) {
targetPrototype[setterName] = function (value) {
var callbackSrcObj = this[srcFieldName];
var finalObj = getFinalObj(callbackSrcObj);
finalObj[callbackField] = value;
setterCallback.call(setterCallbackThisArg, callbackSrcObj);
};
}
})();
}
}
}
function getOrDefault(a, b) {
return a != null ? a : b;
}
function deepClone(toClone, clone, alternativeToCloneByField) {
if (!toClone) {
return clone;
}
var typedClone = clone;
if (!clone) {
clone = {};
typedClone = toClone;
}
for (var field in typedClone) {
var type = typeof (typedClone[field]);
if (toClone[field] == null) {
continue;
}
switch (type) {
case "object":
if (!$.isArray(typedClone[field])) {
clone[field] = deepClone(toClone[field], alternativeToCloneByField[field], alternativeToCloneByField);
}
else {
var array = toClone[field];
clone[field] = array.slice(0);
}
break;
case "number":
case "string":
clone[field] = toClone[field] || clone[field];
break;
case "boolean":
clone[field] = getOrDefault(toClone[field], clone[field]);
break;
}
}
return clone;
}
function executeWindow(sourceName) {
var functions = [];
for (var _i = 1; _i < arguments.length; _i++) {
functions[_i - 1] = arguments[_i];
}
var srcTxt = "";
for (var i = 0; i < functions.length; i++) {
srcTxt += "(" + functions[i].toString() + ")();\n";
}
srcTxt += "//# sourceURL=" + sourceName;
injectScriptText(srcTxt);
}
function injectToWindow(functionNames) {
var functions = [];
for (var _i = 1; _i < arguments.length; _i++) {
functions[_i - 1] = arguments[_i];
}
var srcTxt = "";
for (var i = 0; i < functions.length; i++) {
srcTxt += functions[i].toString().replace(/^function/, "function " + functionNames[i]) + "\n";
}
injectScriptText(srcTxt);
}
function injectScriptText(srcTxt) {
var script = document.createElement("script");
script.type = 'text/javascript';
script.text = srcTxt;
document.body.appendChild(script);
}
(function (SortingType) {
SortingType[SortingType["PopularityDesc"] = 0] = "PopularityDesc";
SortingType[SortingType["PopularityAsc"] = 1] = "PopularityAsc";
SortingType[SortingType["TitleDesc"] = 2] = "TitleDesc";
SortingType[SortingType["TitleAsc"] = 3] = "TitleAsc";
SortingType[SortingType["PublishDateNewFirst"] = 4] = "PublishDateNewFirst";
SortingType[SortingType["PublishDateOldFirst"] = 5] = "PublishDateOldFirst";
SortingType[SortingType["SourceAsc"] = 6] = "SourceAsc";
SortingType[SortingType["SourceDesc"] = 7] = "SourceDesc";
})(exported.SortingType || (exported.SortingType = {}));
var SortingType = exported.SortingType;
(function (FilteringType) {
FilteringType[FilteringType["RestrictedOn"] = 0] = "RestrictedOn";
FilteringType[FilteringType["FilteredOut"] = 1] = "FilteredOut";
})(exported.FilteringType || (exported.FilteringType = {}));
var FilteringType = exported.FilteringType;
(function (HTMLElementType) {
HTMLElementType[HTMLElementType["SelectBox"] = 0] = "SelectBox";
HTMLElementType[HTMLElementType["CheckBox"] = 1] = "CheckBox";
HTMLElementType[HTMLElementType["NumberInput"] = 2] = "NumberInput";
})(exported.HTMLElementType || (exported.HTMLElementType = {}));
var HTMLElementType = exported.HTMLElementType;
function getFilteringTypes() {
return [FilteringType.FilteredOut, FilteringType.RestrictedOn];
}
function getFilteringTypeId(type) {
return FilteringType[type];
}
var AsyncResult = (function () {
function AsyncResult(task, taskThisArg) {
this.task = task;
this.taskThisArg = taskThisArg;
}
AsyncResult.prototype.then = function (callback, thisArg) {
try {
this.resultCallback = callback;
this.resultThisArg = thisArg;
this.task.call(this.taskThisArg, this);
}
catch (e) {
console.log(e);
}
};
;
AsyncResult.prototype.result = function (result) {
try {
this.resultCallback.call(this.resultThisArg, result);
}
catch (e) {
console.log(e);
}
};
;
AsyncResult.prototype.chain = function (asyncResult) {
this.then(function () {
asyncResult.done();
}, this);
};
AsyncResult.prototype.done = function () {
try {
this.resultCallback.apply(this.resultThisArg);
}
catch (e) {
console.log(e);
}
};
return AsyncResult;
}());
var UserScriptStorage = (function () {
function UserScriptStorage() {
}
UserScriptStorage.prototype.getAsync = function (id, defaultValue) {
return new AsyncResult(function (p) {
p.result(JSON.parse(GM_getValue(id, JSON.stringify(defaultValue))));
}, this);
};
UserScriptStorage.prototype.put = function (id, value) {
GM_setValue(id, JSON.stringify(value));
};
UserScriptStorage.prototype.delete = function (id) {
GM_deleteValue(id);
};
UserScriptStorage.prototype.listKeys = function () {
return GM_listValues();
};
UserScriptStorage.prototype.init = function () {
return new AsyncResult(function (p) {
p.done();
}, this);
};
UserScriptStorage.prototype.loadScript = function (name) {
injectScriptText(GM_getResourceText(name));
};
return UserScriptStorage;
}());
var LocalPersistence = new UserScriptStorage();
var SubscriptionDTO = (function () {
function SubscriptionDTO(url) {
var _this = this;
this.filteringEnabled = false;
this.restrictingEnabled = false;
this.sortingEnabled = true;
this.openAndMarkAsRead = true;
this.sortingType = SortingType.PopularityDesc;
this.advancedControlsReceivedPeriod = new AdvancedControlsReceivedPeriod();
this.pinHotToTop = false;
this.additionalSortingTypes = [];
this.filteringListsByType = {};
this.url = url;
getFilteringTypes().forEach(function (type) {
_this.filteringListsByType[type] = [];
});
}
return SubscriptionDTO;
}());
var AdvancedControlsReceivedPeriod = (function () {
function AdvancedControlsReceivedPeriod() {
this.maxHours = 6;
this.keepUnread = false;
this.hide = false;
this.showIfHot = false;
this.minPopularity = 200;
this.markAsReadVisible = false;
}
return AdvancedControlsReceivedPeriod;
}());
var Subscription = (function () {
function Subscription(dao, dto) {
this.dao = dao;
if (dto) {
this.dto = dto;
}
}
Subscription.prototype.getURL = function () {
return this.dto.url;
};
Subscription.prototype.isFilteringEnabled = function () {
return this.dto.filteringEnabled;
};
Subscription.prototype.isRestrictingEnabled = function () {
return this.dto.restrictingEnabled;
};
Subscription.prototype.isSortingEnabled = function () {
return this.dto.sortingEnabled;
};
Subscription.prototype.isPinHotToTop = function () {
return this.dto.pinHotToTop;
};
Subscription.prototype.isOpenAndMarkAsRead = function () {
return this.dto.openAndMarkAsRead;
};
Subscription.prototype.getAdvancedControlsReceivedPeriod = function () {
return this.dto.advancedControlsReceivedPeriod;
};
Subscription.prototype.getSortingType = function () {
return this.dto.sortingType;
};
Subscription.prototype.getFilteringList = function (type) {
return this.dto.filteringListsByType[type];
};
Subscription.prototype.setHours_AdvancedControlsReceivedPeriod = function (hours) {
if (hours > 23) {
return;
}
var advancedPeriodDays = Math.floor(this.getAdvancedControlsReceivedPeriod().maxHours / 24);
this.setMaxHours_AdvancedControlsReceivedPeriod(hours, advancedPeriodDays);
};
Subscription.prototype.setDays_AdvancedControlsReceivedPeriod = function (days) {
var advancedPeriodHours = this.getAdvancedControlsReceivedPeriod().maxHours % 24;
this.setMaxHours_AdvancedControlsReceivedPeriod(advancedPeriodHours, days);
};
Subscription.prototype.setMaxHours_AdvancedControlsReceivedPeriod = function (hours, days) {
var maxHours = hours + 24 * days;
this.getAdvancedControlsReceivedPeriod().maxHours = maxHours;
this.save();
};
Subscription.prototype.getAdditionalSortingTypes = function () {
return this.dto.additionalSortingTypes;
};
Subscription.prototype.setAdditionalSortingTypes = function (additionalSortingTypes) {
this.dto.additionalSortingTypes = additionalSortingTypes;
this.save();
};
Subscription.prototype.addAdditionalSortingType = function (additionalSortingType) {
this.dto.additionalSortingTypes.push(additionalSortingType);
this.save();
};
Subscription.prototype.addKeyword = function (keyword, type) {
this.getFilteringList(type).push(keyword);
this.save();
};
Subscription.prototype.removeKeyword = function (keyword, type) {
var keywordList = this.getFilteringList(type);
var index = keywordList.indexOf(keyword);
if (index > -1) {
keywordList.splice(index, 1);
}
this.save();
};
Subscription.prototype.resetFilteringList = function (type) {
this.getFilteringList(type).length = 0;
};
Subscription.prototype.save = function () {
this.dao.save(this.dto);
};
return Subscription;
}());
var SubscriptionDAO = (function () {
function SubscriptionDAO() {
this.SUBSCRIPTION_ID_PREFIX = "subscription_";
this.GLOBAL_SETTINGS_SUBSCRIPTION_URL = "---global settings---";
registerAccessors(new SubscriptionDTO(""), "dto", Subscription.prototype, this.save, this);
}
SubscriptionDAO.prototype.init = function () {
var _this = this;
return new AsyncResult(function (p) {
LocalPersistence.init().then(function () {
var t = _this;
var onLoad = function (sub) {
t.defaultSubscription = sub;
p.done();
};
if (LocalPersistence.listKeys().indexOf(_this.getSubscriptionId(_this.GLOBAL_SETTINGS_SUBSCRIPTION_URL)) > -1) {
_this.loadSubscription(_this.GLOBAL_SETTINGS_SUBSCRIPTION_URL).then(onLoad, _this);
}
else {
var dto = new SubscriptionDTO(_this.GLOBAL_SETTINGS_SUBSCRIPTION_URL);
_this.save(dto);
onLoad.call(_this, new Subscription(_this, dto));
}
}, _this);
}, this);
};
SubscriptionDAO.prototype.loadSubscription = function (url) {
var _this = this;
return new AsyncResult(function (p) {
var sub = new Subscription(_this);
_this.load(url).then(function (dto) {
sub.dto = dto;
p.result(sub);
}, _this);
}, this);
};
;
SubscriptionDAO.prototype.save = function (dto) {
var url = dto.url;
var id = this.getSubscriptionId(url);
LocalPersistence.put(id, dto);
console.log("Subscription saved: " + JSON.stringify(dto));
};
SubscriptionDAO.prototype.load = function (url) {
var _this = this;
return new AsyncResult(function (p) {
LocalPersistence.getAsync(_this.getSubscriptionId(url), null).then(function (dto) {
var cloneURL;
if (dto) {
var linkedURL = dto.linkedUrl;
if (linkedURL != null) {
console.log("Loading linked subscription: " + linkedURL);
_this.load(linkedURL).then(function (dto) {
p.result(dto);
}, _this);
return;
}
else {
cloneURL = dto.url;
console.log("Loaded saved subscription: " + JSON.stringify(dto));
}
}
else {
dto = _this.defaultSubscription ? _this.defaultSubscription.dto : new SubscriptionDTO(url);
cloneURL = url;
}
dto = _this.clone(dto, cloneURL);
p.result(dto);
}, _this);
}, this);
};
SubscriptionDAO.prototype.delete = function (url) {
LocalPersistence.delete(this.getSubscriptionId(url));
console.log("Deleted: " + url);
};
SubscriptionDAO.prototype.clone = function (dtoToClone, cloneUrl) {
var clone = deepClone(dtoToClone, new SubscriptionDTO(cloneUrl), {
"advancedControlsReceivedPeriod": new AdvancedControlsReceivedPeriod()
});
clone.url = cloneUrl;
return clone;
};
SubscriptionDAO.prototype.importSettings = function (urlToImport, actualUrl) {
var _this = this;
return new AsyncResult(function (p) {
_this.load(urlToImport).then(function (dto) {
dto.url = actualUrl;
if (_this.isURLGlobal(actualUrl)) {
_this.defaultSubscription.dto = dto;
}
_this.save(dto);
p.done();
}, _this);
}, this);
};
SubscriptionDAO.prototype.getGlobalSettings = function () {
return this.defaultSubscription;
};
SubscriptionDAO.prototype.getAllSubscriptionURLs = function () {
var _this = this;
var urls = LocalPersistence.listKeys().filter(function (value) {
return value.indexOf(_this.SUBSCRIPTION_ID_PREFIX) == 0;
});
urls = urls.map(function (value) {
return value.substring(_this.SUBSCRIPTION_ID_PREFIX.length);
});
return urls;
};
SubscriptionDAO.prototype.getSubscriptionId = function (url) {
return this.SUBSCRIPTION_ID_PREFIX + url;
};
SubscriptionDAO.prototype.linkSubscriptions = function (url, linkedURL) {
var id = this.getSubscriptionId(url);
var linkedSub = new LinkedSubscriptionDTO(linkedURL);
var t = this;
LocalPersistence.put(id, linkedSub);
console.log("Subscription linked: " + JSON.stringify(linkedSub));
};
SubscriptionDAO.prototype.isURLGlobal = function (url) {
return url === this.GLOBAL_SETTINGS_SUBSCRIPTION_URL;
};
return SubscriptionDAO;
}());
var LinkedSubscriptionDTO = (function () {
function LinkedSubscriptionDTO(linkedUrl) {
this.linkedUrl = linkedUrl;
}
return LinkedSubscriptionDTO;
}());
var SubscriptionManager = (function () {
function SubscriptionManager() {
this.urlPrefixPattern = new RegExp(ext.urlPrefixPattern, "i");
this.dao = new SubscriptionDAO();
}
SubscriptionManager.prototype.init = function () {
var _this = this;
return new AsyncResult(function (p) {
_this.dao.init().chain(p);
}, this);
};
SubscriptionManager.prototype.loadSubscription = function (globalSettingsEnabled) {
var _this = this;
return new AsyncResult(function (p) {
var onLoad = function (sub) {
_this.currentSubscription = sub;
p.result(sub);
};
if (globalSettingsEnabled) {
onLoad.call(_this, _this.dao.getGlobalSettings());
}
else {
_this.dao.loadSubscription(_this.getActualSubscriptionURL()).then(onLoad, _this);
}
}, this);
};
SubscriptionManager.prototype.linkToSubscription = function (url) {
var currentURL = this.currentSubscription.getURL();
if (url === currentURL) {
alert("Linking to the same subscription URL is impossible");
}
else if (this.isGlobalMode()) {
alert("Global settings can't be linked to any other subscription");
}
else {
this.dao.linkSubscriptions(currentURL, url);
}
};
SubscriptionManager.prototype.deleteSubscription = function (url) {
this.dao.delete(url);
};
SubscriptionManager.prototype.importSettings = function (url) {
var _this = this;
return new AsyncResult(function (p) {
var currentURL = _this.currentSubscription.getURL();
_this.dao.importSettings(url, currentURL).chain(p);
}, this);
};
SubscriptionManager.prototype.getAllSubscriptionURLs = function () {
return this.dao.getAllSubscriptionURLs();
};
SubscriptionManager.prototype.getActualSubscriptionURL = function () {
return document.URL.replace(this.urlPrefixPattern, "");
};
SubscriptionManager.prototype.isGlobalMode = function () {
return this.dao.isURLGlobal(this.currentSubscription.getURL());
};
SubscriptionManager.prototype.getCurrentSubscription = function () {
return this.currentSubscription;
};
return SubscriptionManager;
}());
var ArticleManager = (function () {
function ArticleManager(subscriptionManager, page) {
this.articlesCount = 0;
this.lastReadArticleAge = -1;
this.subscriptionManager = subscriptionManager;
this.articleSorterFactory = new ArticleSorterFactory();
this.page = page;
}
ArticleManager.prototype.refreshArticles = function () {
this.resetArticles();
$(ext.articleSelector).toArray().forEach(this.addArticle, this);
};
ArticleManager.prototype.resetArticles = function () {
this.articlesCount = 0;
this.lastReadArticleAge = -1;
this.lastReadArticleGroup = [];
this.articlesToMarkAsRead = [];
};
ArticleManager.prototype.getCurrentSub = function () {
return this.subscriptionManager.getCurrentSubscription();
};
ArticleManager.prototype.getCurrentUnreadCount = function () {
return $(ext.articleSelector).length;
};
ArticleManager.prototype.addArticle = function (a) {
this.articlesCount++;
var article = new Article(a);
this.filterAndRestrict(article);
this.advancedControls(article);
this.checkLastAddedArticle();
};
ArticleManager.prototype.filterAndRestrict = function (article) {
var sub = this.getCurrentSub();
var title = article.getTitle();
if (sub.isFilteringEnabled() || sub.isRestrictingEnabled()) {
var restrictedOnKeywords = sub.getFilteringList(FilteringType.RestrictedOn);
var filteredOutKeywords = sub.getFilteringList(FilteringType.FilteredOut);
var hide = false;
var restrictedCount = restrictedOnKeywords.length;
if (sub.isRestrictingEnabled() && restrictedCount > 0) {
hide = true;
for (var i = 0; i < restrictedCount && hide; i++) {
if (title.indexOf(restrictedOnKeywords[i].toLowerCase()) != -1) {
hide = false;
}
}
}
if (sub.isFilteringEnabled()) {
for (var i = 0; i < filteredOutKeywords.length && !hide; i++) {
if (title.indexOf(filteredOutKeywords[i].toLowerCase()) != -1) {
hide = true;
}
}
}
if (hide) {
article.setVisible(false);
}
else {
article.setVisible();
}
}
else {
article.setVisible();
}
};
ArticleManager.prototype.advancedControls = function (article) {
var sub = this.getCurrentSub();
var advControls = sub.getAdvancedControlsReceivedPeriod();
if (advControls.keepUnread || advControls.hide) {
try {
var threshold = Date.now() - advControls.maxHours * 3600 * 1000;
var publishAge = article.getPublishAge();
if (publishAge <= threshold) {
if (advControls.keepUnread && (this.lastReadArticleAge == -1 ||
publishAge >= this.lastReadArticleAge)) {
if (publishAge != this.lastReadArticleAge) {
this.lastReadArticleGroup = [article];
}
else {
this.lastReadArticleGroup.push(article);
}
this.lastReadArticleAge = publishAge;
}
}
else {
if (advControls.showIfHot && (article.isHot() ||
article.getPopularity() >= advControls.minPopularity)) {
if (advControls.keepUnread && advControls.markAsReadVisible) {
this.articlesToMarkAsRead.push(article);
}
}
else if (advControls.hide) {
article.setVisible(false);
}
}
}
catch (err) {
console.log(err);
}
}
};
ArticleManager.prototype.checkLastAddedArticle = function () {
var sub = this.getCurrentSub();
if (this.articlesCount == this.getCurrentUnreadCount()) {
this.prepareMarkAsRead();
this.sortArticles();
this.page.showHiddingInfo();
}
};
ArticleManager.prototype.sortArticles = function () {
var sub = this.getCurrentSub();
var visibleArticles = [], hiddenArticles = [];
$(ext.articleSelector).toArray().map((function (a) {
return new Article(a);
})).forEach(function (a) {
if (a.isVisible()) {
visibleArticles.push(a);
}
else {
hiddenArticles.push(a);
}
});
if (sub.isPinHotToTop()) {
var hotArticles = [];
var normalArticles = [];
visibleArticles.forEach(function (article) {
if (article.isHot()) {
hotArticles.push(article);
}
else {
normalArticles.push(article);
}
});
this.sortArticleArray(hotArticles);
this.sortArticleArray(normalArticles);
visibleArticles = hotArticles.concat(normalArticles);
}
else {
this.sortArticleArray(visibleArticles);
}
if (sub.isSortingEnabled() || sub.isPinHotToTop()) {
var articlesContainer = $(ext.articleSelector).first().parent();
articlesContainer.empty();
visibleArticles.forEach(function (article) {
articlesContainer.append(article.get());
});
hiddenArticles.forEach(function (article) {
articlesContainer.append(article.get());
});
}
var sortedVisibleArticles = visibleArticles.map(function (a) { return a.getEntryId(); });
this.page.put(ext.sortedVisibleArticlesId, sortedVisibleArticles);
};
ArticleManager.prototype.prepareMarkAsRead = function () {
if (this.lastReadArticleGroup.length > 0) {
var lastReadArticle;
if (this.isOldestFirst()) {
lastReadArticle = this.lastReadArticleGroup[this.lastReadArticleGroup.length - 1];
}
else {
lastReadArticle = this.lastReadArticleGroup[0];
}
if (lastReadArticle != null) {
this.page.put(ext.lastReadEntryId, lastReadArticle.getEntryId());
}
}
if (this.articlesToMarkAsRead.length > 0) {
var ids = this.articlesToMarkAsRead.map(function (article) {
return article.getEntryId();
});
this.page.put(ext.articlesToMarkAsReadId, ids);
}
};
ArticleManager.prototype.sortArticleArray = function (articles) {
var sub = this.getCurrentSub();
if (!sub.isSortingEnabled()) {
return;
}
var sortingTypes = [sub.getSortingType()].concat(sub.getAdditionalSortingTypes());
articles.sort(this.articleSorterFactory.getSorter(sortingTypes));
};
ArticleManager.prototype.isOldestFirst = function () {
try {
var firstPublishAge = new Article($(ext.articleSelector).first().get(0)).getPublishAge();
var lastPublishAge = new Article($(ext.articleSelector).last().get(0)).getPublishAge();
return firstPublishAge < lastPublishAge;
}
catch (err) {
console.log(err);
return false;
}
};
return ArticleManager;
}());
var ArticleSorterFactory = (function () {
function ArticleSorterFactory() {
this.sorterByType = {};
function titleSorter(isAscending) {
var multiplier = isAscending ? 1 : -1;
return function (a, b) {
return a.getTitle().localeCompare(b.getTitle()) * multiplier;
};
}
function popularitySorter(isAscending) {
var multiplier = isAscending ? 1 : -1;
return function (a, b) {
return (a.getPopularity() - b.getPopularity()) * multiplier;
};
}
function publishDateSorter(isNewFirst) {
var multiplier = isNewFirst ? -1 : 1;
return function (a, b) {
return (a.getPublishAge() - b.getPublishAge()) * multiplier;
};
}
function sourceSorter(isAscending) {
var multiplier = isAscending ? 1 : -1;
return function (a, b) {
return a.getSource().localeCompare(b.getSource()) * multiplier;
};
}
this.sorterByType[SortingType.TitleDesc] = titleSorter(false);
this.sorterByType[SortingType.TitleAsc] = titleSorter(true);
this.sorterByType[SortingType.PopularityDesc] = popularitySorter(false);
this.sorterByType[SortingType.PopularityAsc] = popularitySorter(true);
this.sorterByType[SortingType.PublishDateNewFirst] = publishDateSorter(true);
this.sorterByType[SortingType.PublishDateOldFirst] = publishDateSorter(false);
this.sorterByType[SortingType.SourceAsc] = sourceSorter(true);
this.sorterByType[SortingType.SourceDesc] = sourceSorter(false);
}
ArticleSorterFactory.prototype.getSorter = function (sortingTypes) {
var _this = this;
if (sortingTypes.length == 1) {
return this.sorterByType[sortingTypes[0]];
}
return function (a, b) {
var res;
for (var i = 0; i < sortingTypes.length; i++) {
res = _this.sorterByType[sortingTypes[i]](a, b);
if (res != 0) {
return res;
}
}
return res;
};
};
return ArticleSorterFactory;
}());
var Article = (function () {
function Article(article) {
this.article = $(article);
// Title
this.title = this.article.attr(ext.articleTitleAttribute).trim().toLowerCase();
// Popularity
var popularityStr = this.article.find(ext.popularitySelector).text().trim();
popularityStr = popularityStr.replace("+", "");
if (popularityStr.indexOf("K") > -1) {
popularityStr = popularityStr.replace("K", "");
popularityStr += "000";
}
this.popularity = Number(popularityStr);
// Publish age
var ageStr = this.article.find(ext.publishAgeSpanSelector).attr(ext.publishAgeTimestampAttr);
if (ageStr != null) {
var publishDate = ageStr.split("--")[1].replace(/[^:]*:/, "").trim();
this.publishAge = Date.parse(publishDate);
}
// Source
var source = this.article.find(ext.articleSourceSelector);
if (source != null) {
this.source = source.text();
}
}
Article.prototype.get = function () {
return this.article;
};
Article.prototype.getTitle = function () {
return this.title;
};
Article.prototype.getSource = function () {
return this.source;
};
Article.prototype.getPopularity = function () {
return this.popularity;
};
Article.prototype.getPublishAge = function () {
return this.publishAge;
};
Article.prototype.isHot = function () {
var span = this.article.find(ext.popularitySelector);
return span.hasClass("hot") || span.hasClass("onfire");
};
Article.prototype.getEntryId = function () {
return this.article.attr(ext.articleEntryIdAttribute);
};
Article.prototype.setVisible = function (visibile) {
this.article.css("display", visibile == null ? "" : (visibile ? "" : "none"));
};
Article.prototype.isVisible = function () {
return !(this.article.css("display") === "none");
};
return Article;
}());
var templates = {
"settingsHTML": "
",
"filteringListHTML": "",
"filteringKeywordHTML": "",
"sortingSelectHTML": "",
"optionHTML": "",
"styleCSS": "#FFnS_settingsDivContainer { display: none; background: rgba(0,0,0,0.9); width: 100%; height: 100%; z-index: 500; top: 0; left: 0; position: fixed; } #FFnS_settingsDiv { max-height: 500px; margin-top: 1%; margin-left: 15%; margin-right: 1%; border-radius: 25px; border: 2px solid #336699; background: #E0F5FF; padding: 2%; opacity: 1; } .FFnS_input { font-size:12px; } #FFnS_tabs_menu { height: 30px; clear: both; margin-top: 1%; margin-bottom: 0%; padding: 0px; text-align: center; } #FFnS_tabs_menu li { height: 30px; line-height: 30px; display: inline-block; border: 1px solid #d4d4d1; } #FFnS_tabs_menu li.current { background-color: #B9E0ED; } #FFnS_tabs_menu li a { padding: 10px; color: #2A687D; } #FFnS_tabs_content { padding: 1%; } .FFnS_Tab_Menu { display: none; width: 100%; max-height: 300px; overflow-y: auto; overflow-x: hidden; } .FFnS_icon { vertical-align: middle; height: 20px; width: 20px; cursor: pointer; } .FFnS_keyword { vertical-align: middle; background-color: #35A5E2; border-radius: 20px; color: #FFF; cursor: pointer; } .tooltip { position: relative; display: inline-block; border-bottom: 1px dotted black; } .tooltip .tooltiptext { visibility: hidden; width: 120px; background-color: black; color: #fff; text-align: center; padding: 5px; border-radius: 6px; position: absolute; z-index: 1; white-space: normal; } .tooltip:hover .tooltiptext { visibility: visible; } #FFnS_CloseSettingsBtn { float:right; width: 24px; height: 24px; } #FFnS_Tab_SettingsControls button { margin-top: 1%; font-size: 12px; display: block; } #FFnS_Tab_SettingsControls #FFnS_SettingsControls_UnlinkFromSub { display: inline; } #FFnS_MaxPeriod_Infos > input[type=number]{ width: 30px; margin-left: 1%; margin-right: 1%; } #FFnS_MinPopularity_AdvancedControlsReceivedPeriod { width: 45px; } #FFnS_MaxPeriod_Infos { margin: 1% 0 2% 0; } .setting_group { white-space: nowrap; margin-right: 2%; } fieldset { border-color: #333690; border-style: bold; } legend { color: #333690; font-weight: bold; } fieldset + fieldset, #FFnS_Tab_SettingsControls fieldset { margin-top: 1%; } fieldset select { margin-left: 2% } input { vertical-align: middle; } .ShowSettingsBtn { background-image: url('http://megaicons.net/static/img/icons_sizes/8/178/512/objects-empty-filter-icon.png'); background-size: 20px 20px; background-position: center center; background-repeat: no-repeat; color: #757575; background-color: transparent; font-weight: normal; min-width: 0; height: 40px; width: 40px; margin-right: 0px; } .ShowSettingsBtn:hover { color: #636363; background-color: rgba(0,0,0,0.05); } .header + div > div:first-child > div h4 { display: none; } .fx header h1 .detail.FFnS_Hiding_Info::before { content: ''; } .fx .open-in-new-tab-button.mark-as-read { background:url(http://s3.feedly.com/production/head/images/condensed-visit-black.png); background-size: 32px 32px; background-repeat: no-repeat; margin-right: 0px; } .fx .entry.u5 .open-in-new-tab-button { filter: brightness(0) invert(1); margin-right: 4px; margin-top: 4px; } .fx .entry.u0 .open-in-new-tab-button.condensed-toolbar-icon { background-size: 32px 32px; } .ShowSettingsBtn:hover { color: #636363; background-color: rgba(0,0,0,0.05); } "
};
var FeedlyPage = (function () {
function FeedlyPage() {
this.hiddingInfoClass = "FFnS_Hiding_Info";
this.put("ext", ext);
injectToWindow(["getFFnS"], this.getFFnS);
executeWindow("Feedly-Page-FFnS.js", this.initWindow, this.onNewArticle, this.overrideMarkAsRead, this.overrideNavigation);
}
FeedlyPage.prototype.update = function (sub) {
if (sub.isOpenAndMarkAsRead()) {
this.put(ext.isOpenAndMarkAsReadId, true);
$("." + ext.openAndMarkAsReadClass).css("display", "");
}
else {
$("." + ext.openAndMarkAsReadClass).css("display", "none");
}
if (sub.getAdvancedControlsReceivedPeriod().keepUnread) {
this.put(ext.keepNewArticlesUnreadId, true);
}
};
FeedlyPage.prototype.initWindow = function () {
window["ext"] = getFFnS("ext");
};
FeedlyPage.prototype.onNewArticle = function () {
NodeCreationObserver.onCreation(ext.articleSelector + " .content", function (element) {
var a = $(element).closest(ext.articleSelector);
var style = "display: none";
if (getFFnS(ext.isOpenAndMarkAsReadId)) {
style = "";
}
var attributes = {
class: ext.openAndMarkAsReadClass + " mark-as-read",
title: "Open in a new window/tab and mark as read",
type: "button",
style: style
};
if (a.hasClass("u0")) {
attributes.class += " condensed-toolbar-icon icon";
}
var e = $("