Make Feeds row into a selectable global view of feed items (#2800)

This commit is contained in:
Abe Jellinek 2022-11-14 16:55:28 -05:00 committed by GitHub
parent 318ec4074e
commit b6591dba5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 300 additions and 106 deletions

View file

@ -75,6 +75,8 @@ var ZoteroAdvancedSearch = new function() {
isPublications: () => false,
isDuplicates: () => false,
isFeed: () => false,
isFeeds: () => false,
isFeedsOrFeed: () => false,
isShare: () => false,
isTrash: () => false
};
@ -109,6 +111,8 @@ var ZoteroAdvancedSearch = new function() {
isPublications: () => false,
isDuplicates: () => false,
isFeed: () => false,
isFeeds: () => false,
isFeedsOrFeed: () => false,
isShare: () => false,
isTrash: () => false
};

View file

@ -236,10 +236,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
// Depth indent
let depth = treeRow.level;
// The arrow on macOS is a full icon's width.
// For non-userLibrary items that are drawn under headers
// For non-userLibrary/feed items that are drawn under headers
// we do not draw the arrow and need to move all items 1 level up
if (Zotero.isMac && !treeRow.isHeader() && treeRow.ref
&& treeRow.ref.libraryID != Zotero.Libraries.userLibraryID) {
if (Zotero.isMac && !treeRow.isHeader() && !treeRow.isFeed()
&& treeRow.ref && treeRow.ref.libraryID != Zotero.Libraries.userLibraryID) {
depth--;
}
div.style.paddingInlineStart = (CHILD_INDENT * depth) + 'px';
@ -425,31 +425,24 @@ var CollectionTree = class CollectionTree extends LibraryTree {
}
// Add feeds
if (this.hideSources.indexOf('feeds') == -1) {
var feeds = Zotero.Feeds.getAll();
// Alphabetize
var collation = Zotero.getLocaleCollation();
feeds.sort(function (a, b) {
return collation.compareString(1, a.name, b.name);
});
if (feeds.length) {
newRows.splice(added++, 0,
new Zotero.CollectionTreeRow(this, 'separator', false),
);
let feedHeader = new Zotero.CollectionTreeRow(this, 'header', {
id: "feed-libraries-header",
label: Zotero.getString('pane.collections.feedLibraries'),
libraryID: -1
});
newRows.splice(added++, 0, feedHeader);
for (let feed of feeds) {
newRows.splice(added++, 0,
new Zotero.CollectionTreeRow(this, 'feed', feed, 1)
);
}
}
if (this.hideSources.indexOf('feeds') == -1 && Zotero.Feeds.haveFeeds()) {
newRows.splice(added++, 0,
new Zotero.CollectionTreeRow(this, 'separator', false),
);
newRows.splice(added++, 0,
new Zotero.CollectionTreeRow(this, 'feeds', {
get unreadCount() {
return Zotero.Feeds.totalUnreadCount();
},
async updateFeed() {
for (let feed of Zotero.Feeds.getAll()) {
await feed.updateFeed();
}
}
})
);
added += await this._expandRow(newRows, added - 1);
}
this.selection.selectEventsSuppressed = true;
@ -552,6 +545,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
selectSearch(id) {
return this.selectByID('S' + id);
}
async selectFeeds() {
return this.selectByID('F1');
}
async selectItem(itemID, inLibraryRoot) {
return !!(await this.selectItems([itemID], inLibraryRoot));
@ -700,10 +697,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
this._removeRow(row);
}
// If a feed was removed and there are no more, remove the 'Feeds' header
// If a feed was removed and there are no more, remove the 'Feeds' row
// (and the splitter before it)
if (feedDeleted && !Zotero.Feeds.haveFeeds()) {
let row = this._rowMap['HF'];
let row = this._rowMap['F1'];
this._removeRow(row);
this._removeRow(row - 1);
}
@ -993,7 +990,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
this.selection.selectEventsSuppressed = true;
var count = 0;
var treeRow = this.getRow(index);
if (treeRow.isLibrary(true) || treeRow.isCollection()) {
if (treeRow.isLibrary(true) || treeRow.isCollection() || treeRow.isFeeds()) {
count = await this._expandRow(this._rows, index, true);
}
if (this.selection.focused > index) {
@ -2039,6 +2036,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
if (treeRow.isCollection()) {
return !treeRow.ref.hasChildCollections();
}
else if (treeRow.isFeeds()) {
// Hidden when empty
return false;
}
return true;
}
@ -2093,14 +2094,15 @@ var CollectionTree = class CollectionTree extends LibraryTree {
collectionType += 'Full';
}
break;
case 'feeds':
collectionType = 'FeedLibrary';
break;
case 'header':
if (treeRow.ref.id == 'group-libraries-header') {
collectionType = 'Groups';
}
else if (treeRow.ref.id == 'feed-libraries-header') {
collectionType = 'FeedLibrary';
}
else if (treeRow.ref.id == 'commons-header') {
collectionType = 'Commons';
}
@ -2186,6 +2188,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
var level = rows[row].level;
var isLibrary = treeRow.isLibrary(true);
var isCollection = treeRow.isCollection();
var isFeeds = treeRow.isFeeds();
var libraryID = treeRow.ref.libraryID;
if (treeRow.isPublications() || treeRow.isFeed()) {
@ -2244,7 +2247,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
let beforeRow = row + 1 + newRows;
rows.splice(beforeRow, 0,
new Zotero.CollectionTreeRow(this, 'collection', collections[i], level + 1));
new Zotero.CollectionTreeRow(this, isFeeds ? 'feed' : 'collection', collections[i], level + 1));
newRows++;
// Recursively expand child collections that should be open
newRows += await this._expandRow(rows, beforeRow);

View file

@ -107,7 +107,12 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
loaded: true
};
if (prevLibraryID != libraryID) {
newState.tagColors = Zotero.Tags.getColors(libraryID);
if (libraryID) {
newState.tagColors = Zotero.Tags.getColors(libraryID);
}
else {
newState.tagColors = new Map();
}
}
var { tags, scope } = await this.getTagsAndScope();
newState.tags = tags;

View file

@ -489,8 +489,14 @@
for (var i=0; i<fields.length; i++) {
fieldNames.push(Zotero.ItemFields.getName(fields[i]));
}
if (!(this.item instanceof Zotero.FeedItem)) {
if (this.item instanceof Zotero.FeedItem) {
let row = ZoteroPane_Local.getCollectionTreeRow();
if (row && row.isFeeds()) {
fieldNames.unshift("feed");
}
}
else {
fieldNames.push("dateAdded", "dateModified");
}
}
@ -516,6 +522,11 @@
if (fieldName == 'itemType') {
val = this.item.itemTypeID;
}
// Fake "field" in the feeds global view that displays the name
// of the containing feed
else if (fieldName == 'feed') {
val = Zotero.Feeds.get(this.item.libraryID)?.name;
}
else {
val = this.item.getField(fieldName);
}

View file

@ -369,7 +369,7 @@ var ItemTree = class ItemTree extends LibraryTree {
const collectionTreeRow = this.collectionTreeRow;
if (collectionTreeRow.isFeed() && action == 'modify') {
if (collectionTreeRow.isFeedsOrFeed() && action == 'modify') {
for (const id of ids) {
this.tree.invalidateRow(this._rowMap[id]);
}
@ -555,7 +555,7 @@ var ItemTree = class ItemTree extends LibraryTree {
sort = true;
}
}
else if (collectionTreeRow.isFeed()) {
else if (collectionTreeRow.isFeedsOrFeed()) {
window.ZoteroPane.updateReadLabel();
}
// If not a search, process modifications manually
@ -962,7 +962,7 @@ var ItemTree = class ItemTree extends LibraryTree {
showHeader: true,
columns: this._getColumns(),
onColumnPickerMenu: this._displayColumnPickerMenu,
onColumnSort: this.collectionTreeRow.isFeed() ? null : this._handleColumnSort,
onColumnSort: this.collectionTreeRow.isFeedsOrFeed() ? null : this._handleColumnSort,
getColumnPrefs: this._getColumnPrefs,
storeColumnPrefs: this._storeColumnPrefs,
getDefaultColumnOrder: this._getDefaultColumnOrder,
@ -1304,6 +1304,9 @@ var ItemTree = class ItemTree extends LibraryTree {
}
}
return val;
case 'feed':
return (row.ref.isFeedItem && Zotero.Feeds.get(row.ref.libraryID).name) || "";
default:
return row.ref.getField(field, false, true);
@ -1814,7 +1817,7 @@ var ItemTree = class ItemTree extends LibraryTree {
*/
getSortDirection(sortFields) {
sortFields = sortFields || this.getSortFields();
if (this.collectionTreeRow.isFeed()) {
if (this.collectionTreeRow.isFeedsOrFeed()) {
return Zotero.Prefs.get('feeds.sortAscending') ? 1 : -1;
}
const columns = this._getColumns();
@ -1828,7 +1831,7 @@ var ItemTree = class ItemTree extends LibraryTree {
}
getSortField() {
if (this.collectionTreeRow.isFeed()) {
if (this.collectionTreeRow.isFeedsOrFeed()) {
return 'id';
}
var column = this._sortedColumn;
@ -3032,6 +3035,7 @@ var ItemTree = class ItemTree extends LibraryTree {
}
}
row.numNotes = treeRow.numNotes() || "";
row.feed = (treeRow.ref.isFeedItem && Zotero.Feeds.get(treeRow.ref.libraryID).name) || "";
row.title = treeRow.ref.getDisplayTitle();
const columns = this.getColumns();
@ -3049,7 +3053,7 @@ var ItemTree = class ItemTree extends LibraryTree {
case 'dateModified':
case 'accessDate':
case 'date':
if (key == 'date' && !this.collectionTreeRow.isFeed()) {
if (key == 'date' && !this.collectionTreeRow.isFeedsOrFeed()) {
break;
}
if (val) {
@ -3653,7 +3657,7 @@ var ItemTree = class ItemTree extends LibraryTree {
//
// Secondary Sort menu
//
if (!this.collectionTreeRow.isFeed()) {
if (!this.collectionTreeRow.isFeedsOrFeed()) {
try {
const id = prefix + 'sort-menu';
const primaryField = this.getSortField();

View file

@ -56,7 +56,7 @@ const COLUMNS = [
{
dataKey: "title",
primary: true,
defaultIn: new Set(["default", "feed"]),
defaultIn: new Set(["default", "feeds", "feed"]),
label: "itemFields.title",
ignoreInColumnPicker: true,
flex: 4,
@ -64,7 +64,7 @@ const COLUMNS = [
},
{
dataKey: "firstCreator",
defaultIn: new Set(["default", "feed"]),
defaultIn: new Set(["default", "feeds", "feed"]),
label: "zotero.items.creator_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
@ -77,7 +77,7 @@ const COLUMNS = [
},
{
dataKey: "date",
defaultIn: new Set(["feed"]),
defaultIn: new Set(["feeds", "feed"]),
defaultSort: -1,
label: "itemFields.date",
flex: 1,
@ -85,7 +85,7 @@ const COLUMNS = [
},
{
dataKey: "year",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
defaultSort: -1,
label: "zotero.items.year_column",
flex: 1,
@ -106,7 +106,7 @@ const COLUMNS = [
},
{
dataKey: "journalAbbreviation",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.journalAbbreviation",
flex: 1,
@ -121,7 +121,7 @@ const COLUMNS = [
},
{
dataKey: "accessDate",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
defaultSort: -1,
submenu: true,
label: "itemFields.accessDate",
@ -130,7 +130,7 @@ const COLUMNS = [
},
{
dataKey: "libraryCatalog",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.libraryCatalog",
flex: 1,
@ -138,7 +138,7 @@ const COLUMNS = [
},
{
dataKey: "callNumber",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.callNumber",
flex: 1,
@ -154,7 +154,7 @@ const COLUMNS = [
{
dataKey: "dateAdded",
defaultSort: -1,
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
label: "itemFields.dateAdded",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
@ -162,14 +162,14 @@ const COLUMNS = [
{
dataKey: "dateModified",
defaultSort: -1,
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
label: "zotero.items.dateModified_column",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "archive",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.archive",
flex: 1,
@ -177,7 +177,7 @@ const COLUMNS = [
},
{
dataKey: "archiveLocation",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.archiveLocation",
flex: 1,
@ -185,7 +185,7 @@ const COLUMNS = [
},
{
dataKey: "place",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.place",
flex: 1,
@ -193,7 +193,7 @@ const COLUMNS = [
},
{
dataKey: "volume",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.volume",
flex: 1,
@ -201,7 +201,7 @@ const COLUMNS = [
},
{
dataKey: "edition",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.edition",
flex: 1,
@ -209,7 +209,7 @@ const COLUMNS = [
},
{
dataKey: "number",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.number",
flex: 1,
@ -217,7 +217,7 @@ const COLUMNS = [
},
{
dataKey: "pages",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.pages",
flex: 1,
@ -225,7 +225,7 @@ const COLUMNS = [
},
{
dataKey: "issue",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.issue",
flex: 1,
@ -233,7 +233,7 @@ const COLUMNS = [
},
{
dataKey: "series",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.series",
flex: 1,
@ -241,7 +241,7 @@ const COLUMNS = [
},
{
dataKey: "seriesTitle",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.seriesTitle",
flex: 1,
@ -249,7 +249,7 @@ const COLUMNS = [
},
{
dataKey: "court",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.court",
flex: 1,
@ -257,7 +257,7 @@ const COLUMNS = [
},
{
dataKey: "medium",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.medium",
flex: 1,
@ -265,7 +265,7 @@ const COLUMNS = [
},
{
dataKey: "genre",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.genre",
flex: 1,
@ -273,7 +273,7 @@ const COLUMNS = [
},
{
dataKey: "system",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.system",
flex: 1,
@ -281,7 +281,7 @@ const COLUMNS = [
},
{
dataKey: "shortTitle",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
submenu: true,
label: "itemFields.shortTitle",
flex: 2,
@ -289,7 +289,7 @@ const COLUMNS = [
},
{
dataKey: "extra",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
label: "itemFields.extra",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
@ -297,7 +297,7 @@ const COLUMNS = [
{
dataKey: "hasAttachment",
defaultIn: new Set(["default"]),
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
label: "zotero.tabs.attachments.label",
iconLabel: <Icons.IconAttachSmall />,
fixedWidth: true,
@ -306,13 +306,20 @@ const COLUMNS = [
},
{
dataKey: "numNotes",
disabledIn: "feed",
disabledIn: ["feeds", "feed"],
label: "zotero.tabs.notes.label",
iconLabel: <Icons.IconTreeitemNoteSmall />,
width: "14",
minWidth: 14,
staticWidth: true,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
},
{
dataKey: "feed",
disabledIn: ["default", "feed"],
label: "itemFields.feed",
flex: 1,
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
}
];

View file

@ -65,12 +65,13 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
case 'trash':
return 'T' + this.ref.libraryID;
case 'feeds':
return 'F1';
case 'header':
switch (this.ref.id) {
case 'group-libraries-header':
return "HG";
case 'feed-libraries-header':
return "HF";
}
break;
}
@ -133,6 +134,14 @@ Zotero.CollectionTreeRow.prototype.isFeed = function() {
return this.type == 'feed';
}
Zotero.CollectionTreeRow.prototype.isFeeds = function() {
return this.type == 'feeds';
}
Zotero.CollectionTreeRow.prototype.isFeedsOrFeed = function() {
return this.isFeeds() || this.isFeed();
}
Zotero.CollectionTreeRow.prototype.isSeparator = function () {
return this.type == 'separator';
}
@ -148,7 +157,7 @@ Zotero.CollectionTreeRow.prototype.isShare = function()
}
Zotero.CollectionTreeRow.prototype.isContainer = function() {
return this.isLibrary(true) || this.isCollection() || this.isPublications() || this.isBucket();
return this.isLibrary(true) || this.isCollection() || this.isPublications() || this.isBucket() || this.isFeeds();
}
@ -171,7 +180,7 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () {
if (this.isTrash() || this.isShare() || this.isBucket()) {
return false;
}
if (this.isGroup() || this.isFeed()) {
if (this.isGroup() || this.isFeedsOrFeed()) {
return this.ref.editable;
}
if (!this.isWithinGroup() || this.isPublications()) {
@ -214,7 +223,7 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('filesEditable', function ()
});
Zotero.CollectionTreeRow.visibilityGroups = {'feed': 'feed'};
Zotero.CollectionTreeRow.visibilityGroups = {'feed': 'feed', 'feeds': 'feeds'};
Zotero.CollectionTreeRow.prototype.__defineGetter__('visibilityGroup', function() {
@ -231,6 +240,9 @@ Zotero.CollectionTreeRow.prototype.getName = function()
case 'publications':
return Zotero.getString('pane.collections.publications');
case 'feeds':
return Zotero.getString('pane.collections.feedLibraries');
case 'trash':
return Zotero.getString('pane.collections.trash');
@ -252,6 +264,9 @@ Zotero.CollectionTreeRow.prototype.getChildren = function () {
else if (this.isCollection()) {
return Zotero.Collections.getByParent(this.ref.id);
}
else if (this.isFeeds()) {
return Zotero.Feeds.getAll().sort((a, b) => Zotero.localeCompare(a.name, b.name));
}
}
Zotero.CollectionTreeRow.prototype.getItems = Zotero.Promise.coroutine(function* ()
@ -350,7 +365,9 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
}
else {
var s = new Zotero.Search();
s.addCondition('libraryID', 'is', this.ref.libraryID);
if (!this.isFeeds()) {
s.addCondition('libraryID', 'is', this.ref.libraryID);
}
// Library root
if (this.isLibrary(true)) {
s.addCondition('noChildren', 'true');
@ -372,6 +389,9 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
else if (this.isTrash()) {
s.addCondition('deleted', 'true');
}
else if (this.isFeeds()) {
s.addCondition('feed', 'true');
}
else {
throw new Error('Invalid search mode ' + this.type);
}
@ -379,7 +399,12 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
// Create the outer (filter) search
var s2 = new Zotero.Search();
s2.addCondition('libraryID', 'is', this.ref.libraryID);
if (this.isFeeds()) {
s2.addCondition('feed', true);
}
else {
s2.addCondition('libraryID', 'is', this.ref.libraryID);
}
if (this.isTrash()) {
s2.addCondition('deleted', 'true');
@ -420,6 +445,9 @@ Zotero.CollectionTreeRow.prototype.getTags = async function (types, tagIDs) {
case 'bucket':
return [];
case 'feeds':
return [];
}
var results = await this.getSearchResults(true);
return Zotero.Tags.getAllWithin({ tmpTable: results, types, tagIDs });

View file

@ -253,6 +253,10 @@ Zotero.Feeds = new function() {
return !!Object.keys(this._cache.urlByLibraryID).length
}
this.totalUnreadCount = function () {
return this.getAll().reduce((prev, feed) => prev + feed.unreadCount, 0);
};
let globalFeedCheckDelay = Zotero.Promise.resolve();
this.scheduleNextFeedCheck = Zotero.Promise.coroutine(function* () {

View file

@ -156,6 +156,8 @@ Zotero.ItemFields = new function() {
case 'dateModified':
case 'itemType':
return Zotero.Schema.globalSchemaLocale.fields[field];
case 'feed':
return Zotero.getString('itemFields.feed');
}
// TODO: different labels for different item types

View file

@ -1034,6 +1034,10 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
case 'publications':
var publications = condition.operator == 'true';
continue;
case 'feed':
var feed = condition.operator == 'true';
continue;
// Search subcollections
case 'recursive':
@ -1134,6 +1138,10 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
if (publications) {
sql += " AND (itemID IN (SELECT itemID FROM publicationsItems))";
}
if (feed) {
sql += " AND (itemID IN (SELECT itemID FROM feedItems))";
}
// Limit to library search belongs to
//

View file

@ -114,6 +114,14 @@ Zotero.SearchConditions = new function(){
false: true
}
},
{
name: 'feed',
operators: {
true: true,
false: true
}
},
{
name: 'includeParentsAndChildren',

View file

@ -921,7 +921,7 @@ var ZoteroPane = new function()
break;
case 'saveToZotero':
var collectionTreeRow = this.getCollectionTreeRow();
if (collectionTreeRow.isFeed()) {
if (collectionTreeRow.isFeedsOrFeed()) {
ZoteroItemPane.translateSelectedItems();
} else {
Zotero.debug(command + ' does not do anything in non-feed views')
@ -936,7 +936,7 @@ var ZoteroPane = new function()
case 'toggleRead':
// Toggle read/unread
let row = this.getCollectionTreeRow();
if (!row || !row.isFeed()) return;
if (!row || !row.isFeedsOrFeed()) return;
this.toggleSelectedItemsRead();
if (itemReadPromise) {
itemReadPromise.cancel();
@ -1308,7 +1308,7 @@ var ZoteroPane = new function()
ZoteroPane_Local.tagSelector.setMode('view');
}
ZoteroPane_Local.tagSelector.onItemViewChanged({
libraryID: collectionTreeRow.ref.libraryID,
libraryID: collectionTreeRow.ref && collectionTreeRow.ref.libraryID,
collectionTreeRow
});
}
@ -1351,11 +1351,23 @@ var ZoteroPane = new function()
// If item data not yet loaded for library, load it now.
// Other data types are loaded at startup
var library = Zotero.Libraries.get(collectionTreeRow.ref.libraryID);
if (!library.getDataLoaded('item')) {
Zotero.debug("Waiting for items to load for library " + library.libraryID);
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
await library.waitForDataLoad('item');
if (collectionTreeRow.isFeeds()) {
var feedsToLoad = Zotero.Feeds.getAll().filter(feed => !feed.getDataLoaded('item'));
if (feedsToLoad.length) {
Zotero.debug("Waiting for items to load for feeds " + feedsToLoad.map(feed => feed.libraryID));
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
for (let feed of feedsToLoad) {
await feed.waitForDataLoad('item');
}
}
}
else {
var library = Zotero.Libraries.get(collectionTreeRow.ref.libraryID);
if (!library.getDataLoaded('item')) {
Zotero.debug("Waiting for items to load for library " + library.libraryID);
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
await library.waitForDataLoad('item');
}
}
this.itemsView.changeCollectionTreeRow(collectionTreeRow);
@ -1521,7 +1533,7 @@ var ZoteroPane = new function()
}
// Zero or multiple items selected
else {
if (collectionTreeRow.isFeed()) {
if (collectionTreeRow.isFeedsOrFeed()) {
this.updateReadLabel();
}
@ -1647,7 +1659,7 @@ var ZoteroPane = new function()
// Feed buttons
document.getElementById('zotero-item-pane-top-buttons-feed').hidden
= !this.getCollectionTreeRow().isFeed()
= !this.getCollectionTreeRow().isFeedsOrFeed()
};
@ -2047,7 +2059,7 @@ var ZoteroPane = new function()
return;
}
if (!this.canEdit() && !collectionTreeRow.isFeed()) {
if (!this.canEdit() && !collectionTreeRow.isFeedsOrFeed()) {
this.displayCannotEditLibraryMessage();
return;
}
@ -2291,9 +2303,11 @@ var ZoteroPane = new function()
var row = this.getCollectionTreeRow();
if (!row) return;
let feed = row.ref;
let feedItemIDs = yield Zotero.FeedItems.getAll(feed.libraryID, true, false, true);
yield Zotero.FeedItems.toggleReadByID(feedItemIDs, true);
let feeds = row.isFeeds() ? Zotero.Feeds.getAll() : [row.ref];
for (let feed of feeds) {
let feedItemIDs = yield Zotero.FeedItems.getAll(feed.libraryID, true, false, true);
yield Zotero.FeedItems.toggleReadByID(feedItemIDs, true);
}
});
@ -2796,7 +2810,7 @@ var ZoteroPane = new function()
// menuitem configuration
//
// This has to be kept in sync with zotero-collectionmenu in zoteroPane.xul. We could do this
// This has to be kept in sync with zotero-collectionmenu in zoteroPane.xhtml. We could do this
// entirely in JS, but various localized strings are only in zotero.dtd, and they're used in
// standalone.xul as well, so for now they have to remain as XML entities.
var _collectionContextMenuOptions = [
@ -2867,6 +2881,9 @@ var ZoteroPane = new function()
id: "editSelectedFeed",
oncommand: () => this.editSelectedFeed()
},
{
id: 'addFeed'
},
{
id: "deleteCollection",
oncommand: () => this.deleteSelectedCollection()
@ -3008,8 +3025,26 @@ var ZoteroPane = new function()
}
// Adjust labels
m.refreshFeed.setAttribute('label', Zotero.getString('pane.collections.menu.refresh.feed'));
m.markReadFeed.setAttribute('label', Zotero.getString('pane.collections.menu.markAsRead.feed'));
m.deleteCollectionAndItems.setAttribute('label', Zotero.getString('pane.collections.menu.delete.feedAndItems'));
}
else if (collectionTreeRow.isFeeds()) {
show = [
'refreshFeed',
'sep2',
'markReadFeed',
'addFeed',
];
if (collectionTreeRow.ref.unreadCount == 0) {
disable = ['markReadFeed'];
}
// Adjust labels
m.refreshFeed.setAttribute('label', Zotero.getString('pane.collections.menu.refresh.allFeeds'));
m.markReadFeed.setAttribute('label', Zotero.getString('pane.collections.menu.markAsRead.allFeeds'));
}
else if (collectionTreeRow.isSearch()) {
show = [
'editSelectedCollection',
@ -3198,11 +3233,11 @@ var ZoteroPane = new function()
disable.add(m.restoreToLibrary);
}
}
else if (!collectionTreeRow.isFeed()) {
else if (!collectionTreeRow.isFeedsOrFeed()) {
show.add(m.moveToTrash);
}
if(!collectionTreeRow.isFeed()) {
if(!collectionTreeRow.isFeedsOrFeed()) {
show.add(m.sep4);
show.add(m.exportItems);
show.add(m.createBib);
@ -3221,7 +3256,7 @@ var ZoteroPane = new function()
canRecognize = true,
canUnrecognize = true,
canRename = true;
var canMarkRead = collectionTreeRow.isFeed();
var canMarkRead = collectionTreeRow.isFeedsOrFeed();
var markUnread = true;
for (let i = 0; i < items.length; i++) {
@ -3280,7 +3315,7 @@ var ZoteroPane = new function()
// "Add/Create Note from Annotations" and "Find Available PDFs"
if (collectionTreeRow.filesEditable
&& !collectionTreeRow.isDuplicates()
&& !collectionTreeRow.isFeed()) {
&& !collectionTreeRow.isFeedsOrFeed()) {
if (items.some(item => attachmentsWithExtractableAnnotations(item).length)
|| items.some(item => isAttachmentWithExtractableAnnotations(item))) {
let menuitem = menu.childNodes[m.createNoteFromAnnotations];
@ -3514,7 +3549,7 @@ var ZoteroPane = new function()
show.delete(m.createBib);
}
if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeed()) {
if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeedsOrFeed()) {
for (let i in m) {
// Still allow some options for non-editable views
switch (i) {
@ -3545,7 +3580,7 @@ var ZoteroPane = new function()
}
// Add to collection
if (!collectionTreeRow.isFeed()
if (!collectionTreeRow.isFeedsOrFeed()
&& collectionTreeRow.editable
&& Zotero.Items.keepParents(items).every(item => item.isTopLevelItem())
) {
@ -3563,6 +3598,14 @@ var ZoteroPane = new function()
show.add(m.removeItems);
}
// Show in library
if (collectionTreeRow.isFeeds()) {
menu.childNodes[m.showInLibrary].setAttribute('label', Zotero.getString('pane.items.menu.showInFeed'));
}
else {
menu.childNodes[m.showInLibrary].setAttribute('label', Zotero.getString('general.showInLibrary'));
}
// Set labels, plural if necessary
menu.childNodes[m.findPDF].setAttribute('label', Zotero.getString('pane.items.menu.findAvailablePDF' + multiple));
menu.childNodes[m.moveToTrash].setAttribute('label', Zotero.getString('pane.items.menu.moveToTrash' + multiple));

View file

@ -911,15 +911,23 @@
<menuitem class="zotero-menuitem-new-collection" label="&zotero.toolbar.newCollection.label;"/>
<menuitem class="zotero-menuitem-new-saved-search" label="&zotero.toolbar.newSavedSearch.label;"/>
<menuitem class="zotero-menuitem-new-collection" label="&zotero.toolbar.newSubcollection.label;"/>
<menuitem class="zotero-menuitem-refresh-feed" label="&zotero.toolbar.feeds.refresh;"/>
<menuitem class="zotero-menuitem-refresh-feed"/>
<menuseparator/>
<menuitem class="zotero-menuitem-show-duplicates" label="&zotero.toolbar.duplicate.label;"/>
<menuitem class="zotero-menuitem-show-unfiled" label="&zotero.collections.showUnfiledItems;"/>
<menuitem class="zotero-menuitem-show-retracted" label="&zotero.collections.showRetractedItems;"/>
<menuitem class="zotero-menuitem-edit-collection"/>
<menuitem class="zotero-menuitem-duplicate-collection"/>
<menuitem class="zotero-menuitem-mark-read-feed" label="&zotero.toolbar.markFeedRead.label;"/>
<menuitem class="zotero-menuitem-mark-read-feed"/>
<menuitem class="zotero-menuitem-edit-feed" label="&zotero.toolbar.feeds.edit;"/>
<menu class="zotero-collectionmenu-feed-add-menu" label="&zotero.toolbar.feeds.new;">
<menupopup>
<menuitem label="&zotero.toolbar.feeds.new.fromURL;"
command="cmd_zotero_newFeed_fromURL"/>
<menuitem label="&zotero.toolbar.feeds.new.fromOPML;"
oncommand="ZoteroPane_Local.importFeedsFromOPML()"/>
</menupopup>
</menu>
<menuitem class="zotero-menuitem-delete-collection"/>
<menuitem class="zotero-menuitem-move-to-trash"/>
<menuseparator/>
@ -932,7 +940,7 @@
</menupopup>
<menupopup id="zotero-itemmenu">
<!-- Keep order in sync with buildItemContextMenu -->
<menuitem class="menuitem-iconic zotero-menuitem-show-in-library" label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(parseInt(this.parentNode.getAttribute('itemID')), true)"/>
<menuitem class="menuitem-iconic zotero-menuitem-show-in-library" oncommand="ZoteroPane.selectItem(parseInt(this.parentNode.getAttribute('itemID')), true)"/>
<menuseparator/>
<!-- with icon: <menuitem class="menuitem-iconic" id="zotero-menuitem-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>-->
<menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemKey'))"/>

View file

@ -83,7 +83,6 @@
<!ENTITY zotero.toolbar.removeItem.label "Remove Item…">
<!ENTITY zotero.toolbar.newLibrary.label "New Library">
<!ENTITY zotero.toolbar.newCollection.label "New Collection…">
<!ENTITY zotero.toolbar.markFeedRead.label "Mark Feed as Read">
<!ENTITY zotero.toolbar.newGroup "New Group…">
<!ENTITY zotero.toolbar.newSubcollection.label "New Subcollection…">
<!ENTITY zotero.toolbar.newSavedSearch.label "New Saved Search…">
@ -105,7 +104,6 @@
<!ENTITY zotero.toolbar.feeds.new "New Feed">
<!ENTITY zotero.toolbar.feeds.new.fromURL "From URL…">
<!ENTITY zotero.toolbar.feeds.new.fromOPML "From OPML…">
<!ENTITY zotero.toolbar.feeds.refresh "Refresh Feed">
<!ENTITY zotero.toolbar.feeds.edit "Edit Feed…">
<!ENTITY zotero.item.add "Add">

View file

@ -290,6 +290,9 @@ pane.collections.menu.generateReport.savedSearch = Generate Report from Saved Se
pane.collections.menu.generateReport.feed = Generate Report from Feed…
pane.collections.menu.refresh.feed = Refresh Feed
pane.collections.menu.refresh.allFeeds = Refresh All Feeds
pane.collections.menu.markAsRead.feed = Mark Feed as Read
pane.collections.menu.markAsRead.allFeeds = Mark All Feeds as Read
pane.tagSelector.rename.title = Rename Tag
pane.tagSelector.rename.message = Please enter a new name for this tag.\n\nThe tag will be changed in all associated items.
@ -363,6 +366,7 @@ pane.items.menu.renameAttachments = Rename File from Parent Metadata
pane.items.menu.renameAttachments.multiple = Rename Files from Parent Metadata
pane.items.menu.duplicateAndConvert.toBookSection = Create Book Section
pane.items.menu.duplicateAndConvert.toBook = Create Book from Book Section
pane.items.menu.showInFeed = Show in Feed
pane.items.showItemInLibrary = Show Item in Library
pane.items.letter.oneParticipant = Letter to %S
@ -607,6 +611,7 @@ itemFields.attachmentPDF = PDF Attachment
itemFields.repository = Repository
itemFields.archiveID = Archive ID
itemFields.citationKey = Citation Key
itemFields.feed = Feed
creatorTypes.author = Author
creatorTypes.contributor = Contributor

View file

@ -1253,4 +1253,60 @@ describe("Zotero.CollectionTree", function() {
})
})
})
describe("Feeds pseudo-library", function () {
beforeEach(async function () {
for (let feed of Zotero.Feeds.getAll()) {
await feed.eraseTx();
}
});
it("should contain feed items from all feeds", async function () {
let feed1 = await createFeed();
let feed2 = await createFeed();
let feedItem1 = await createDataObject('feedItem', { libraryID: feed1.libraryID }, { skipSelect: true });
let feedItem2 = await createDataObject('feedItem', { libraryID: feed2.libraryID }, { skipSelect: true });
await cv.selectFeeds();
await waitForItemsLoad(win);
let itemsView = zp.itemsView;
assert.equal(itemsView.rowCount, 2);
assert.equal(itemsView.getRow(0).ref.id, feedItem2.id);
assert.equal(itemsView.getRow(1).ref.id, feedItem1.id);
});
it("should be filterable", async function () {
let feed1 = await createFeed();
let feed2 = await createFeed();
let feedItem1 = await createDataObject('feedItem', { libraryID: feed1.libraryID, setTitle: true }, { skipSelect: true });
let feedItem2 = await createDataObject('feedItem', { libraryID: feed2.libraryID, setTitle: true }, { skipSelect: true });
await cv.selectFeeds();
await waitForItemsLoad(win);
var quickSearch = win.document.getElementById('zotero-tb-search');
quickSearch.value = feedItem1.getField('title');
quickSearch.doCommand();
let itemsView = zp.itemsView;
await itemsView._refreshPromise;
assert.equal(itemsView.rowCount, 1);
assert.equal(itemsView.getRow(0).ref.id, feedItem1.id);
});
it("should be bold if any feed items are unread", async function () {
let feed1 = await createFeed();
let feed2 = await createFeed();
let feedItem1 = await createDataObject('feedItem', { libraryID: feed1.libraryID, setTitle: true }, { skipSelect: true });
let feedItem2 = await createDataObject('feedItem', { libraryID: feed2.libraryID, setTitle: true }, { skipSelect: true });
await feedItem1.toggleRead(true);
// Unread count is automatically updated on feed refresh, but we need to do it manually here
await feed1.updateUnreadCount();
await feed2.updateUnreadCount();
assert.equal(cv.getRow(cv.getRowIndexByID('F1')).ref.unreadCount, 1);
assert.lengthOf(win.document.querySelectorAll('#zotero-collections-tree .row.unread'), 2);
});
});
})