diff --git a/scripts/app/staticdb/actions.js b/scripts/app/staticdb/actions.js
index 5f2080f1..35aec1cb 100644
--- a/scripts/app/staticdb/actions.js
+++ b/scripts/app/staticdb/actions.js
@@ -244,15 +244,30 @@ return {
var feedList = require('views/feedList');
var feeds = feedList.getSelectedFeeds();
var ids = _.pluck(feeds, 'id');
+
var special = $('.special.selected').get(0);
if (special) special = special.view.model;
+ if (!special && !ids.length) {
+ special = require('instances/specials').allFeeds;
+ }
+
+ var searches = $('.search-item.selected');
+ if (searches.length) {
+ searches = searches.toArray().map(function(el) {
+ return el.view.model.get('name');
+ });
+ } else {
+ searches = [];
+ }
+
app.trigger('select:' + feedList.el.id, {
action: 'new-select',
feeds: ids,
// _.extend is important, because otherwise it would be sent by reference
filter: special ? _.extend({}, special.get('filter')) : null,
name: special ? special.get('name') : null,
+ searches: searches,
unreadOnly: !!e.altKey || t.className == 'source-counter'
});
@@ -344,21 +359,38 @@ return {
e = e || { currentTarget: $('input[type=search]').get(0) };
var str = e.currentTarget.value || '';
var list = require('views/articleList');
- if (str == '') {
+
+ // hide date groups
+ if (str == '' && !list.currentData.searches.length) {
$('.date-group').css('display', 'block');
} else {
$('.date-group').css('display', 'none');
}
- var searchInContent = false;
- if (str[0] && str[0] == ':') {
- str = str.replace(/^:/, '', str);
- searchInContent = true;
+ function makeSearchList(val) {
+ var o = {
+ searchInContent: val.length && val[0] == ':',
+ str: val.replace(/^:/, '', str)
+ };
+ o.rg = new RegExp(RegExp.escape(o.str), 'i');
+ return o;
}
- var rg = new RegExp(RegExp.escape(str), 'i');
+
+ var searchList = list.currentData.searches.map(makeSearchList);
+
+ searchList.push(makeSearchList(str));
+
list.views.some(function(view) {
if (!view.model) return true;
- if (rg.test(view.model.get('title')) || rg.test(view.model.get('author')) || (searchInContent && rg.test(view.model.get('content')) )) {
+ var allMatch = true;
+ for (var i=0; i
+
<%- name %>
++
\ No newline at end of file
diff --git a/scripts/app/views/SearchView.js b/scripts/app/views/SearchView.js
new file mode 100644
index 00000000..4429f6f9
--- /dev/null
+++ b/scripts/app/views/SearchView.js
@@ -0,0 +1,41 @@
+define([
+ 'jquery', 'underscore', 'views/TopView', 'text!templates/search.html'
+],
+function($, _, TopView, tplSearch) {
+
+ var SearchView = TopView.extend({
+
+ className: 'list-item search-item',
+ template: _.template(tplSearch),
+
+ showContextMenu: function(e) {
+ if (!this.contextMenu) return;
+
+ if (!this.$el.hasClass('selected')) {
+ app.feeds.feedList.select(this, e);
+ }
+ this.contextMenu.currentSource = this.model;
+ this.contextMenu.show(e.clientX, e.clientY);
+ },
+ initialize: function() {
+ this.el.view = this;
+
+ bg.sources.on('clear-events', this.handleClearEvents, this);
+ },
+ handleClearEvents: function(id) {
+ if (window == null || id == tabID) {
+ this.clearEvents();
+ }
+ },
+ clearEvents: function() {
+ bg.sources.off('clear-events', this.handleClearEvents, this);
+ },
+ render: function() {
+ var data = this.model.toJSON();
+ this.$el.html(this.template(data));
+ return this;
+ }
+ });
+
+ return SearchView;
+});
\ No newline at end of file
diff --git a/scripts/app/views/articleList.js b/scripts/app/views/articleList.js
index 20d87e46..b31a939e 100644
--- a/scripts/app/views/articleList.js
+++ b/scripts/app/views/articleList.js
@@ -92,6 +92,7 @@ function (BB, _, $, Groups, Group, GroupView, ItemView, selectable, Locale) {
*/
currentData: {
feeds: [],
+ searches: [],
name: 'all-feeds',
filter: { trashed: false },
unreadOnly: false
@@ -273,7 +274,7 @@ function (BB, _, $, Groups, Group, GroupView, ItemView, selectable, Locale) {
*/
handleRenderScreen: function() {
this.redraw();
- if ($('input[type=search]').val()) {
+ if ($('input[type=search]').val() || this.currentData.searches.length) {
app.actions.execute('articles:search');
}
},
@@ -563,7 +564,7 @@ function (BB, _, $, Groups, Group, GroupView, ItemView, selectable, Locale) {
this.redraw();
- if ($('input[type=search]').val()) {
+ if ($('input[type=search]').val() || this.currentData.searches.length) {
app.actions.execute('articles:search');
}
@@ -579,17 +580,15 @@ function (BB, _, $, Groups, Group, GroupView, ItemView, selectable, Locale) {
// Smart RSS used to reset search on feed select change. It instead keeps the fitler now.
//$('input[type=search]').val('');
- // if prev selected was trash, hide undelete buttons
+ // if trash was previously selected, hide undelete buttons
if (this.currentData.name == 'trash') {
- /*$('[data-action="articles:update"]').css('display', 'block');
- $('[data-action="articles:undelete"]').css('display', 'none');*/
- app.articles.toolbar.showItems('articles:update');
- app.articles.toolbar.hideItems('articles:undelete');
+ app.articles.toolbar.showItems('articles:update').hideItems('articles:undelete');
$('#context-undelete').css('display', 'none');
}
this.currentData = {
feeds: [],
+ searches: [],
name: 'all-feeds',
filter: { trashed: false },
unreadOnly: false
diff --git a/scripts/app/views/feedList.js b/scripts/app/views/feedList.js
index 9233f82d..7f946c60 100644
--- a/scripts/app/views/feedList.js
+++ b/scripts/app/views/feedList.js
@@ -4,9 +4,9 @@
*/
define([
'backbone', 'jquery', 'underscore', 'views/SourceView', 'views/FolderView', 'views/SpecialView', 'models/Special',
- 'instances/contextMenus', 'mixins/selectable', 'instances/specials'
+ 'instances/contextMenus', 'mixins/selectable', 'instances/specials', 'views/SearchView'
],
-function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus, selectable, specials) {
+function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus, selectable, specials, SearchView) {
/**
* List of feeds (in left column)
@@ -66,12 +66,32 @@ function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus,
bg.sources.on('add', this.addSource, this);
bg.sources.on('change:folderID', this.handleChangeFolder, this);
bg.folders.on('add', this.addFolder, this);
+ bg.searches.on('add', this.addSearch, this);
+
bg.sources.on('clear-events', this.handleClearEvents, this);
this.on('pick', this.handlePick);
},
+ /**
+ * Unbinds all listeners to bg process
+ * @method handleClearEvents
+ * @triggered when tab is closed/refershed
+ * @param id {Integer} id of the closed tab
+ */
+ handleClearEvents: function(id) {
+ if (window == null || id == tabID) {
+ bg.sources.off('reset', this.addSources, this);
+ bg.sources.off('add', this.addSource, this);
+ bg.sources.off('change:folderID', this.handleChangeFolder, this);
+ bg.folders.off('add', this.addFolder, this);
+ bg.searches.off('add', this.addSearch, this);
+
+ bg.sources.off('clear-events', this.handleClearEvents, this);
+ }
+ },
+
/**
* Sets comm event listeners and inserts feeds
* @method handleAttached
@@ -114,6 +134,8 @@ function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus,
this.addSources(bg.sources);
+ this.addSearches(bg.searches);
+
return this;
},
@@ -252,21 +274,6 @@ function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus,
this.placeSource(source.view);
},
- /**
- * Unbinds all listeners to bg process
- * @method handleClearEvents
- * @triggered when tab is closed/refershed
- * @param id {Integer} id of the closed tab
- */
- handleClearEvents: function(id) {
- if (window == null || id == tabID) {
- bg.sources.off('reset', this.addSources, this);
- bg.sources.off('add', this.addSource, this);
- bg.sources.off('change:folderID', this.handleChangeFolder, this);
- bg.folders.off('add', this.addFolder, this);
- bg.sources.off('clear-events', this.handleClearEvents, this);
- }
- },
/**
* Adds one special (all feeds, pinned, trash)
@@ -329,6 +336,19 @@ function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus,
this.placeSource(view, noManualSort === true ? true : false);
},
+ /**
+ * Adds one search
+ * @method addSearch
+ * @param search {models/Search} Search model to add
+ * @param noManualSort {Boolean} When false, the rigt place is computed
+ */
+ addSearch: function(search, noManualSort) {
+ var view = new SearchView({ model: search }, this);
+ // this will be still needed to add new searches to right place by title
+ // this.placeSource(view, noManualSort === true ? true : false);
+ this.$el.append(view.render().$el);
+ },
+
/**
* Places source to its right place
* @method placeSource
@@ -422,6 +442,22 @@ function (BB, $, _, SourceView, FolderView, SpecialView, Special, contextMenus,
}, this);
},
+ /**
+ * Add more searches at once
+ * @method addSearches
+ * @param searches {Array} Array of search models to add
+ */
+ addSearches: function(searches) {
+ var that = this;
+ $('.search-item').each(function(i, search) {
+ if (!search.view || !(search instanceof SearchView)) return;
+ that.destroySource(search.view);
+ });
+ searches.forEach(function(source) {
+ this.addSearch(source, true);
+ }, this);
+ },
+
/**
* Destroy feed
* @method removeSource
diff --git a/scripts/bgprocess/bg.js b/scripts/bgprocess/bg.js
index a8bdc92a..5832e2c8 100644
--- a/scripts/bgprocess/bg.js
+++ b/scripts/bgprocess/bg.js
@@ -5,9 +5,9 @@ define([
'jquery',
'modules/Animation', 'models/Settings', 'models/Info', 'models/Source',
'collections/Sources', 'collections/Items', 'collections/Folders', 'models/Loader', 'collections/Logs',
- 'models/Folder', 'models/Item', 'collections/Toolbars'
+ 'models/Folder', 'models/Item', 'collections/Toolbars', 'collections/Searches'
],
-function ($, animation, Settings, Info, Source, Sources, Items, Folders, Loader, Logs, Folder, Item, Toolbars) {
+function ($, animation, Settings, Info, Source, Sources, Items, Folders, Loader, Logs, Folder, Item, Toolbars, Searches) {
/**
* Update animations
@@ -38,6 +38,7 @@ function ($, animation, Settings, Info, Source, Sources, Items, Folders, Loader,
window.sources = new Sources();
window.items = new Items();
window.folders = new Folders();
+ window.searches = new Searches();
/**
* This is used for when new feed is subsribed and smart rss tab is opened to focus the newly added feed
diff --git a/scripts/bgprocess/collections/Searches.js b/scripts/bgprocess/collections/Searches.js
new file mode 100644
index 00000000..c3072a66
--- /dev/null
+++ b/scripts/bgprocess/collections/Searches.js
@@ -0,0 +1,25 @@
+/**
+ * @module BgProcess
+ * @submodule collections/Searches
+ */
+define(['backbone', 'models/Search', 'preps/indexeddb'], function (BB, Search) {
+
+ /**
+ * Collection of feed modules
+ * @class Searches
+ * @constructor
+ * @extends Backbone.Collection
+ */
+ var Searches = BB.Collection.extend({
+ model: Search,
+ /*localStorage: new Backbone.LocalStorage('sources-backbone'),*/
+ comparator: function(a, b) {
+ var t1 = (a.get('title') || '').trim().toLowerCase();
+ var t2 = (b.get('title') || '').trim().toLowerCase();
+ return t1 < t2 ? -1 : 1;
+ }
+ });
+
+ return Searches;
+
+});
\ No newline at end of file
diff --git a/scripts/bgprocess/models/Search.js b/scripts/bgprocess/models/Search.js
new file mode 100644
index 00000000..4ac6939d
--- /dev/null
+++ b/scripts/bgprocess/models/Search.js
@@ -0,0 +1,26 @@
+/**
+ * @module App
+ * @submodule models/Search
+ */
+define(['backbone'], function(BB) {
+ /**
+ * Model for search items in feed list.
+ * @class Search
+ * @constructor
+ * @extends Backbone.Model
+ */
+ var Search = BB.Model.extend({
+ defaults: {
+
+ /**
+ * Name adn title of the search and the value to search by.
+ * @attribute name
+ * @type String
+ * @default ''
+ */
+ name: ''
+ }
+ });
+
+ return Search;
+});
\ No newline at end of file