Various feeds changes

- Hide notes, tags and related for feed items in itembox
- Add feed support for <enclosure> elements
- Add feed syncing methods for synced settings (additional work is
  needed on the sync architecture to download synced settings from the
  server)
- Change feed item clear policy to be less aggressive
- Adjust for deasyncification
- Disable translate-on-select
- Close adomasven/zotero#7, Remove context menu items from feeds
This commit is contained in:
Adomas Venčkauskas 2016-02-11 11:02:38 +00:00 committed by Dan Stillman
parent 0d4025e9fb
commit 12fc6cfbe8
42 changed files with 894 additions and 379 deletions

View file

@ -341,7 +341,7 @@
fieldNames.push(Zotero.ItemFields.getName(fields[i]));
}
if (! (this.item instanceof Zotero.FeedItem)) {
if (!(this.item instanceof Zotero.FeedItem)) {
fieldNames.push("dateAdded", "dateModified");
}
}

View file

@ -36,31 +36,28 @@ var Zotero_Feed_Settings = new function() {
urlTainted = false;
let cleanURL = function(url) {
let cleanUrl = Zotero.Utilities.cleanURL(url, true);
let cleanURL = Zotero.Utilities.cleanURL(url, true);
if (cleanUrl) {
if (/^https?:\/\/[^\/\s]+\/\S/.test(cleanUrl)) {
return cleanUrl;
if (cleanURL) {
if (/^https?:\/\/[^\/\s]+\/\S/.test(cleanURL)) {
return cleanURL;
} else {
Zotero.debug(uri.scheme + " is not a supported protocol for feeds.");
Zotero.debug(uri.scheme + " is not a supported protocol for feeds");
}
}
};
this.init = function() {
this.init = Zotero.Promise.coroutine(function* () {
this.toggleAdvancedOptions(false);
data = window.arguments[0];
if (data.url) {
document.getElementById('feed-url').value = data.url;
}
if (!data.url) {
this.invalidateUrl();
} else {
// Do not allow to change URL for existing feed
document.getElementById('feed-url').readOnly = true;
} else {
this.invalidateURL();
}
if (data.title) {
@ -71,20 +68,20 @@ var Zotero_Feed_Settings = new function() {
if (data.ttl !== undefined) {
ttl = Math.floor(data.ttl / 60);
} else {
ttl = 1;
ttl = Zotero.Prefs.get('feeds.defaultTTL');
}
document.getElementById('feed-ttl').value = ttl;
let cleanupAfter = data.cleanupAfter;
if (cleanupAfter === undefined) cleanupAfter = 2;
if (cleanupAfter === undefined) cleanupAfter = Zotero.Prefs.get('feeds.defaultCleanupAfter');
document.getElementById('feed-cleanupAfter').value = cleanupAfter;
if (data.url && !data.urlIsValid) {
this.validateUrl();
yield this.validateURL();
}
};
});
this.invalidateUrl = function() {
this.invalidateURL = function() {
urlTainted = true;
if (feedReader) {
feedReader.terminate();
@ -100,7 +97,7 @@ var Zotero_Feed_Settings = new function() {
document.documentElement.getButton('accept').disabled = true;
};
this.validateUrl = Zotero.Promise.coroutine(function* () {
this.validateURL = Zotero.Promise.coroutine(function* () {
if (feedReader) {
feedReader.terminate();
feedReader = null;
@ -111,11 +108,11 @@ var Zotero_Feed_Settings = new function() {
if (!url) return;
try {
let fr = feedReader = new Zotero.FeedReader(url);
var fr = feedReader = new Zotero.FeedReader(url);
yield fr.process();
let feed = fr.feedProperties;
// Prevent progress if textbox changes triggered another call to
// validateUrl / invalidateUrl (old session)
// validateURL / invalidateURL (old session)
if (feedReader !== fr || urlTainted) return;
let title = document.getElementById('feed-title');

View file

@ -15,36 +15,48 @@
<script src="include.js"/>
<script src="feedSettings.js"/>
<vbox>
<hbox align="center">
<label value="&zotero.feedSettings.url.label;" control="feed-url"/>
<textbox id="feed-url" flex="1" type="search" size="2"
oninput="Zotero_Feed_Settings.invalidateUrl()"
oncommand="Zotero_Feed_Settings.validateUrl()"
focused="true" newlines="replacewithspaces"
style="width: 30em; max-width: 30em"/>
</hbox>
<hbox align="center">
<label value="&zotero.feedSettings.title.label;" control="feed-url"/>
<textbox id="feed-title" flex="1" newlines="replacewithspaces"/>
</hbox>
<vbox id="advanced-options" class="zotero-advanced-options">
<hbox onclick="Zotero_Feed_Settings.toggleAdvancedOptions()" class="zotero-advanced-options-label">
<dropmarker/>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row>
<hbox align="center">
<label value="&zotero.feedSettings.url.label;" control="feed-url"/>
</hbox>
<textbox id="feed-url" flex="1" size="2"
oninput="Zotero_Feed_Settings.invalidateURL();Zotero_Feed_Settings.validateURL()"
focused="true" newlines="stripsurroundingwhitespace"
style="width: 30em; max-width: 30em"/>
</row>
<row>
<hbox align="center">
<label value="&zotero.feedSettings.title.label;" control="feed-url"/>
</hbox>
<textbox id="feed-title" flex="1" newlines="replacewithspaces"/>
</row>
</rows>
</grid>
<vbox id="advanced-options" class="zotero-advanced-options">
<hbox onclick="Zotero_Feed_Settings.toggleAdvancedOptions()" class="zotero-advanced-options-label">
<dropmarker/>
<hbox align="center">
<label value="&zotero.general.advancedOptions.label;"/>
</hbox>
<vbox id="advanced-options-togglable">
<hbox align="center">
<label value="&zotero.feedSettings.refresh.label1;" control="feed-ttl"/>
<textbox id="feed-ttl" type="number" min="0" increment="1" size="3"/>
<label value="&zotero.feedSettings.refresh.label2;" control="feed-ttl"/>
</hbox>
<hbox align="center">
<label value="&zotero.feedSettings.cleanupAfter.label1;" control="feed-cleanupAfter"/>
<textbox id="feed-cleanupAfter" type="number" min="0" increment="1" size="2"/>
<label value="&zotero.feedSettings.cleanupAfter.label2;" control="feed-cleanupAfter"/>
</hbox>
</vbox>
</hbox>
<vbox id="advanced-options-togglable">
<hbox align="center">
<label value="&zotero.feedSettings.refresh.label1;" control="feed-ttl"/>
<textbox id="feed-ttl" type="number" min="0" increment="1" size="3"/>
<label value="&zotero.feedSettings.refresh.label2;" control="feed-ttl"/>
</hbox>
<hbox align="center">
<label value="&zotero.feedSettings.cleanupAfter.label1;" control="feed-cleanupAfter"/>
<textbox id="feed-cleanupAfter" type="number" min="0" increment="1" size="2"/>
<label value="&zotero.feedSettings.cleanupAfter.label2;" control="feed-cleanupAfter"/>
</hbox>
</vbox>
</vbox>
</dialog>

View file

@ -86,6 +86,10 @@ var ZoteroItemPane = new function() {
_lastItem = item;
// Hide for feed items
document.getElementById('zotero-editpane-tabs').setAttribute('hidden', item.isFeedItem);
document.getElementById('zotero-view-item').classList.add('no-tabs');
if (index == 1) {
var editable = ZoteroPane_Local.canEdit();
_notesButton.hidden = !editable;

View file

@ -52,41 +52,41 @@
<!-- Regular item -->
<tabbox id="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
<tabs>
<tab label="&zotero.tabs.info.label;"/>
<tab label="&zotero.tabs.notes.label;"/>
<tab label="&zotero.tabs.tags.label;"/>
<tab label="&zotero.tabs.related.label;"/>
<tabs id="zotero-editpane-tabs">
<tab id="zotero-editpane-info-tab" label="&zotero.tabs.info.label;"/>
<tab id="zotero-editpane-notes-tab" label="&zotero.tabs.notes.label;"/>
<tab id="zotero-editpane-tags-tab" label="&zotero.tabs.tags.label;"/>
<tab id="zotero-editpane-related-tab" label="&zotero.tabs.related.label;"/>
</tabs>
<tabpanels id="zotero-view-item" flex="1">
<tabpanel>
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
</tabpanel>
<tabpanel flex="1" orient="vertical">
<vbox flex="1" class="zotero-box">
<hbox align="center">
<label id="zotero-editpane-notes-label"/>
<button id="zotero-editpane-notes-add" label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote(event.shiftKey);"/>
</hbox>
<grid flex="1">
<columns>
<column flex="1"/>
<column/>
</columns>
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
</grid>
</vbox>
</tabpanel>
<tabpanel>
<tagsbox id="zotero-editpane-tags" flex="1"/>
</tabpanel>
<tabpanel>
<relatedbox id="zotero-editpane-related" flex="1"/>
</tabpanel>
</tabpanels>
<tabpanels id="zotero-view-item" flex="1">
<tabpanel>
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
</tabpanel>
<tabpanel flex="1" orient="vertical">
<vbox flex="1" class="zotero-box">
<hbox align="center">
<label id="zotero-editpane-notes-label"/>
<button id="zotero-editpane-notes-add" label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote(event.shiftKey);"/>
</hbox>
<grid flex="1">
<columns>
<column flex="1"/>
<column/>
</columns>
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
</grid>
</vbox>
</tabpanel>
<tabpanel>
<tagsbox id="zotero-editpane-tags" flex="1"/>
</tabpanel>
<tabpanel>
<relatedbox id="zotero-editpane-related" flex="1"/>
</tabpanel>
</tabpanels>
</tabbox>
<!-- Note item -->

View file

@ -816,7 +816,7 @@ Zotero_Preferences.Keys = {
for (var i=0; i<rows.length; i++) {
// Display the appropriate modifier keys for the platform
let label = rows[i].firstChild.nextSibling;
if (label.className == 'cmd-shift') {
if (label.className == 'modifier') {
label.value = Zotero.isMac ? Zotero.getString('general.keys.cmdShift') : Zotero.getString('general.keys.ctrlShift');
}
}

View file

@ -23,7 +23,12 @@
***** END LICENSE BLOCK *****
-->
<!DOCTYPE prefwindow SYSTEM "chrome://zotero/locale/preferences.dtd">
<!DOCTYPE window [
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
%zoteroDTD;
<!ENTITY % preferencesDTD SYSTEM "chrome://zotero/locale/preferences.dtd">
%preferencesDTD;
]>
<overlay id="zotero-prefpane-advanced-overlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
@ -50,13 +55,18 @@
<preference id="pref-keys-importFromClipboard" name="extensions.zotero.keys.importFromClipboard" type="string"/>
<preference id="pref-keys-copySelectedItemCitationsToClipboard" name="extensions.zotero.keys.copySelectedItemCitationsToClipboard" type="string"/>
<preference id="pref-keys-copySelectedItemsToClipboard" name="extensions.zotero.keys.copySelectedItemsToClipboard" type="string"/>
<preference id="pref-feeds-sortAscending" name="extensions.zotero.feeds.sortAscending" type="bool"/>
<preference id="pref-feeds-defaultTTL" name="extensions.zotero.feeds.defaultTTL" type="int"/>
<preference id="pref-feeds-defaultCleanupAfter" name="extensions.zotero.feeds.defaultCleanupAfter" type="int"/>
</preferences>
<tabbox id="zotero-prefpane-advanced-tabs">
<tabs>
<tab label="&zotero.preferences.prefpane.general;"/>
<tab label="&zotero.preferences.advanced.filesAndFolders;"/>
<tab label="&zotero.preferences.advanced.keys;"/>
<tab label="&zotero.preferences.advanced.keys;"/>
<tab label="&zotero.preferences.feeds;"/>
</tabs>
<tabpanels id="zotero-prefpane-advanced-tabpanels">
@ -218,37 +228,37 @@
<rows id="zotero-keys-rows">
<row id="zotero-keys-new-item">
<label value="&zotero.preferences.keys.newItem;" control="textbox-newItem"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-newItem" maxlength="1" size="1" preference="pref-keys-newItem"/>
</row>
<row>
<label value="&zotero.preferences.keys.newNote;" control="textbox-newNote"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-newNote" maxlength="1" size="1" preference="pref-keys-newNote"/>
</row>
<row>
<label value="&zotero.preferences.keys.importFromClipboard;" control="textbox-importFromClipboard"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-importFromClipboard" maxlength="1" size="1" preference="pref-keys-importFromClipboard"/>
</row>
<row id="zotero-keys-focus-libraries-pane">
<label value="&zotero.preferences.keys.focusLibrariesPane;" control="textbox-library"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-library" maxlength="1" size="1" preference="pref-keys-library"/>
</row>
<row>
<label value="&zotero.preferences.keys.quicksearch;" control="textbox-quicksearch"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-quicksearch" maxlength="1" size="1" preference="pref-keys-quicksearch"/>
</row>
<row>
<label value="&zotero.preferences.keys.copySelectedItemCitationsToClipboard;" control="textbox-copySelectedItemCitationsToClipboard"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-copySelectedItemCitationsToClipboard" maxlength="1" size="1"
preference="pref-keys-copySelectedItemCitationsToClipboard"
onchange="if (Zotero_Preferences.Export) { Zotero_Preferences.Export.updateQuickCopyInstructions(); }"/>
@ -256,7 +266,7 @@
<row>
<label value="&zotero.preferences.keys.copySelectedItemsToClipboard;" control="textbox-copySelectedItemsToClipboard"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-copySelectedItemsToClipboard" maxlength="1" size="1"
preference="pref-keys-copySelectedItemsToClipboard"
onchange="if (Zotero_Preferences.Export) { Zotero_Preferences.Export.updateQuickCopyInstructions(); }"/>
@ -264,7 +274,7 @@
<row>
<label value="&zotero.preferences.keys.toggleTagSelector;" control="textbox-toggleTagSelector"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-toggleTagSelector" maxlength="1" size="1" preference="pref-keys-toggleTagSelector"/>
</row>
@ -280,6 +290,40 @@
<separator/>
</tabpanel>
<tabpanel id="zotero-prefpane-advanced-feeds-tab" orient="vertical">
<groupbox>
<hbox>
<hbox align="center">
<label value="&zotero.preferences.feeds.sorting.label1;"/>
<menulist id="feed-sort" preference="pref-feeds-sortAscending">
<menupopup>
<menuitem label="&zotero.preferences.feeds.sorting.newest;" value="false"/>
<menuitem label="&zotero.preferences.feeds.sorting.oldest;" value="true"/>
</menupopup>
</menulist>
<label value="&zotero.preferences.feeds.sorting.label2;"/>
</hbox>
</hbox>
</groupbox>
<groupbox>
<caption label="&zotero.preferences.feeds.feedDefaults;"/>
<hbox>
<hbox align="center">
<label value="&zotero.feedSettings.refresh.label1;"/>
<textbox maxlength="2" size="2" preference="pref-feeds-defaultTTL"/>
<label value="&zotero.feedSettings.refresh.label2;"/>
</hbox>
</hbox>
<hbox>
<hbox align="center">
<label value="&zotero.feedSettings.cleanupAfter.label1;"/>
<textbox maxlength="3" size="3" preference="pref-feeds-defaultCleanupAfter"/>
<label value="&zotero.feedSettings.cleanupAfter.label2;"/>
</hbox>
</hbox>
</groupbox>
</tabpanel>
</tabpanels>
</tabbox>

View file

@ -51,19 +51,19 @@
<rows id="zotero-keys-rows">
<row insertbefore="zotero-keys-new-item">
<label value="&zotero.preferences.keys.openZotero;" control="key-textbox-openZotero"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-openZotero" maxlength="1" size="1" preference="pref-keys-openZotero"/>
</row>
<row insertbefore="zotero-keys-new-item">
<label value="&zotero.preferences.keys.toggleFullscreen;" control="textbox-toggleFullscreen"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-toggleFullscreen" maxlength="1" size="1" preference="pref-keys-toggleFullscreen"/>
</row>
<row insertbefore="zotero-keys-new-item">
<label value="&zotero.preferences.keys.saveToZotero;" control="key-textbox-saveToZotero"/>
<label class="cmd-shift"/>
<label class="modifier"/>
<textbox id="textbox-saveToZotero" maxlength="1" size="1" preference="pref-keys-saveToZotero"/>
</row>
</rows>

View file

@ -44,7 +44,6 @@
<preference id="pref-groups-copyChildLinks" name="extensions.zotero.groups.copyChildLinks" type="bool"/>
<preference id="pref-groups-copyTags" name="extensions.zotero.groups.copyTags" type="bool"/>
<preference id="pref-feeds-sortAsc" name="extensions.zotero.feeds.sortAsc" type="bool"/>
</preferences>
<groupbox id="zotero-prefpane-general-groupbox">
@ -132,18 +131,6 @@
</vbox>
</groupbox>
<groupbox>
<caption label="&zotero.preferences.feeds;"/>
<label value="&zotero.preferences.feeds.sorting;"/>
<vbox style="margin-left: 2em">
<radiogroup id="feed-sort" orient="horizontal" align="center" preference="pref-feeds-sortAsc">
<radio label="&zotero.preferences.feeds.sorting.newestFirst;" value="false"/>
<radio label="&zotero.preferences.feeds.sorting.oldestFirst;" value="true"/>
</radiogroup>
</vbox>
</groupbox>
<separator/>
<separator/>
</prefpane>

View file

@ -689,6 +689,7 @@ Zotero.Attachments = new function(){
* @deprecated Use Zotero.Utilities.cleanURL instead
*/
this.cleanAttachmentURI = function (uri, tryHttp) {
Zotero.debug("Zotero.Attachments.cleanAttachmentURI() is deprecated -- use Zotero.Utilities.cleanURL");
return Zotero.Utilities.cleanURL(uri, tryHttp);
}

View file

@ -152,13 +152,13 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () {
if (this.isTrash() || this.isShare() || this.isBucket()) {
return false;
}
if (this.isGroup() || this.isFeed()) {
return this.ref.editable;
}
if (!this.isWithinGroup() || this.isPublications()) {
return true;
}
var libraryID = this.ref.libraryID;
if (this.isGroup() || this.isFeed()) {
return this.ref.editable;
}
if (this.isCollection() || this.isSearch() || this.isDuplicates() || this.isUnfiled()) {
var type = Zotero.Libraries.get(libraryID).libraryType;
if (type == 'group') {

View file

@ -724,11 +724,14 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
switch (collectionType) {
case 'library':
case 'feed':
// Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973
/*
if (treeRow.ref.updating) {
collectionType += '-updating';
} else if (treeRow.ref.lastCheckError) {
collectionType += '-error';
}
*/
break;
case 'trash':
@ -1337,7 +1340,7 @@ Zotero.CollectionTreeView.prototype._rememberOpenStates = Zotero.Promise.corouti
var open = this.isContainerOpen(i);
// Collections and feeds default to closed
if (!open && treeRow.isCollection() || treeRow.isFeed()) {
if ((!open && treeRow.isCollection()) || treeRow.isFeed()) {
delete state[treeRow.id];
continue;
}
@ -1938,7 +1941,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
var items = yield Zotero.Items.getAsync(ids);
if (!items) {
if (items.length == 0) {
return;
}

View file

@ -757,11 +757,16 @@ Zotero.DataObject.prototype.isEditable = function () {
Zotero.DataObject.prototype.editCheck = function () {
let library = Zotero.Libraries.get(this.libraryID);
if ((this._objectType == 'collection' || this._objectType == 'search')
&& Zotero.Libraries.get(this.libraryID).libraryType == 'publications') {
&& library.libraryType == 'publications') {
throw new Error(this._ObjectTypePlural + " cannot be added to My Publications");
}
if (library.libraryType == 'feed') {
return;
}
if (!this.isEditable()) {
throw new Error("Cannot edit " + this._objectType + " in read-only library "
+ Zotero.Libraries.getName(this.libraryID));

View file

@ -70,6 +70,8 @@ Zotero.Feed = function(params = {}) {
this._feedUnreadCount = null;
this._updating = false;
this._syncedSettings = null;
this._previousURL = null;
}
Zotero.Feed._colToProp = function(c) {
@ -79,13 +81,13 @@ Zotero.Feed._colToProp = function(c) {
Zotero.extendClass(Zotero.Library, Zotero.Feed);
Zotero.defineProperty(Zotero.Feed, '_unreadCountSQL', {
value: "(SELECT COUNT(*) FROM items I JOIN feedItems FeI USING (itemID)"
+ " WHERE I.libraryID=F.libraryID AND FeI.readTime IS NULL) AS _feedUnreadCount"
value: "(SELECT COUNT(*) FROM items I JOIN feedItems FI USING (itemID)"
+ " WHERE I.libraryID=F.libraryID AND FI.readTime IS NULL) AS _feedUnreadCount"
});
Zotero.defineProperty(Zotero.Feed, '_dbColumns', {
value: Object.freeze(['name', 'url', 'lastUpdate', 'lastCheck',
'lastCheckError', 'lastGUID', 'cleanupAfter', 'refreshInterval'])
'lastCheckError', 'cleanupAfter', 'refreshInterval'])
});
Zotero.defineProperty(Zotero.Feed, '_primaryDataSQLParts');
@ -130,7 +132,7 @@ for (let i=0; i<accessors.length; i++) {
set: function(v) this._set(prop, v)
})
}
let getters = ['lastCheck', 'lastUpdate', 'lastCheckError', 'lastGUID'];
let getters = ['lastCheck', 'lastUpdate', 'lastCheckError'];
for (let i=0; i<getters.length; i++) {
let name = getters[i];
let prop = Zotero.Feed._colToProp(name);
@ -179,6 +181,7 @@ Zotero.Feed.prototype._set = function (prop, val) {
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
throw new Error(invalidUrlError);
}
this._previousURL = this.url;
break;
case '_feedRefreshInterval':
case '_feedCleanupAfter':
@ -212,7 +215,6 @@ Zotero.Feed.prototype._loadDataFromRow = function(row) {
this._feedLastCheckError = row._feedLastCheckError || null;
this._feedLastCheck = row._feedLastCheck || null;
this._feedLastUpdate = row._feedLastUpdate || null;
this._feedLastGUID = row._feedLastGUID || null;
this._feedCleanupAfter = parseInt(row._feedCleanupAfter) || null;
this._feedRefreshInterval = parseInt(row._feedRefreshInterval) || null;
this._feedUnreadCount = parseInt(row._feedUnreadCount);
@ -225,7 +227,7 @@ Zotero.Feed.prototype._reloadFromDB = Zotero.Promise.coroutine(function* () {
});
Zotero.defineProperty(Zotero.Feed.prototype, '_childObjectTypes', {
value: Object.freeze(['feedItem'])
value: Object.freeze(['feedItem', 'item'])
});
Zotero.Feed.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
@ -285,17 +287,78 @@ Zotero.Feed.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
});
Zotero.Feed.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
let changedURL = this._changed._feedUrl;
yield Zotero.Feed._super.prototype._finalizeSave.apply(this, arguments);
if (env.isNew) {
Zotero.Feeds.register(this);
} else if (changedURL) {
if (!env.isNew && this._previousURL) {
// Re-register library if URL changed
Zotero.Feeds.unregister(this.libraryID);
Zotero.Feeds.register(this);
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
delete syncedFeeds[this._previousURL];
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
}
if (env.isNew || this._previousURL) {
Zotero.Feeds.register(this);
yield this.storeSyncedSettings();
}
this._previousURL = null;
});
Zotero.Feed.prototype._finalizeErase = Zotero.Promise.coroutine(function* (){
let notifierData = {};
notifierData[this.libraryID] = {
libraryID: this.libraryID
};
Zotero.Notifier.trigger('delete', 'feed', this.id, notifierData);
Zotero.Feeds.unregister(this.libraryID);
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
delete syncedFeeds[this.url];
if (Object.keys(syncedFeeds).length == 0) {
yield Zotero.SyncedSettings.clear(Zotero.Libraries.userLibraryID, 'feeds');
} else {
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
}
return Zotero.Feed._super.prototype._finalizeErase.apply(this, arguments);
});
Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* (options = {}) {
let childItemIDs = yield Zotero.FeedItems.getAll(this.id, false, false, true);
yield Zotero.FeedItems.erase(childItemIDs);
yield Zotero.Feed._super.prototype.erase.call(this, options);
});
Zotero.Feed.prototype.getSyncedSettings = function () {
if (!this._syncedSettings) {
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
this._syncedSettings = syncedFeeds[this.url];
}
if (!this._syncedSettings) {
this._syncedSettings = {
url: this.url,
name: this.name,
cleanupAfter: this.cleanupAfter,
refreshInterval: this.refreshInterval,
markedAsRead: {}
};
}
return this._syncedSettings;
};
Zotero.Feed.prototype.setSyncedSettings = Zotero.Promise.coroutine(function* (syncedSettings, store=false) {
this._syncedSettings = syncedSettings;
if (store) {
return this.storeSyncedSettings();
}
});
Zotero.Feed.prototype.storeSyncedSettings = Zotero.Promise.coroutine(function* () {
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds') || {};
syncedFeeds[this.url] = this.getSyncedSettings();
return Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', syncedFeeds);
});
Zotero.Feed.prototype.getExpiredFeedItemIDs = Zotero.Promise.coroutine(function* () {
@ -304,32 +367,49 @@ Zotero.Feed.prototype.getExpiredFeedItemIDs = Zotero.Promise.coroutine(function*
+ "WHERE I.libraryID=? "
+ "AND readTime IS NOT NULL "
+ "AND julianday('now', 'utc') - (julianday(readTime, 'utc') + ?) > 0";
let expiredIDs = yield Zotero.DB.queryAsync(sql, [this.id, {int: this.cleanupAfter}]);
return expiredIDs.map(row => row.id);
return Zotero.DB.columnQueryAsync(sql, [this.id, {int: this.cleanupAfter}]);
});
Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* () {
/**
* Clearing conditions for an item:
* - Has been read at least feed.cleanupAfter earlier AND
* - Does not exist in the RSS feed anymore
*
* If we clear items once they've been read, we may potentially end up
* with empty feeds for those that do not update very frequently.
*/
Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* (itemsInFeedIDs) {
itemsInFeedIDs = itemsInFeedIDs || new Set();
try {
// Clear expired items
if (this.cleanupAfter) {
let expiredItems = yield this.getExpiredFeedItemIDs();
Zotero.debug("Cleaning up read feed items...");
if (expiredItems.length) {
Zotero.debug(expiredItems.join(', '));
yield Zotero.FeedItems.forceErase(expiredItems);
let toClear = expiredItems;
if (itemsInFeedIDs.size) {
toClear = [];
for (let id of expiredItems) {
if (!itemsInFeedIDs.has(id)) {
toClear.push(id);
}
}
}
Zotero.debug("Clearing up read feed items...");
if (toClear.length) {
Zotero.debug(toClear.join(', '));
yield Zotero.FeedItems.erase(toClear);
} else {
Zotero.debug("No expired feed items");
}
}
} catch(e) {
Zotero.debug("Error clearing expired feed items.");
Zotero.debug("Error clearing expired feed items");
Zotero.debug(e);
}
return this.storeSyncedSettings();
});
Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
var toAdd = [];
var createNew = true;
var toSave = [], attachmentsToAdd = [], feedItemIDs = new Set();
if (this._updating) {
return this._updating;
}
@ -338,49 +418,59 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
Zotero.Notifier.trigger('statusChanged', 'feed', this.id);
this._set('_feedLastCheckError', null);
yield this.clearExpiredItems();
try {
let fr = new Zotero.FeedReader(this.url);
yield fr.process();
let itemIterator = new fr.ItemIterator();
let item, processedGUIDs = [];
let item, processedGUIDs = new Set();
while (item = yield itemIterator.next().value) {
// Append id at the end to prevent same item collisions from different feeds
// when to terminate item retrieval.
item.guid += ":" + this.id;
if (item.guid == this.lastGUID) {
Zotero.debug("Feed#update: last seen item reached (" + this.lastGUID + ")");
Zotero.debug(item);
// Don't create new items (expired and deleted), but update existing ones
createNew = false;
}
if (processedGUIDs.indexOf(item.guid) != -1) {
Zotero.debug("Feed item " + item.guid + " already processed from feed.");
if (processedGUIDs.has(item.guid)) {
Zotero.debug("Feed item " + item.guid + " already processed from feed");
continue;
}
processedGUIDs.push(item.guid);
processedGUIDs.add(item.guid);
Zotero.debug("New feed item retrieved:", 5);
Zotero.debug("Feed item retrieved:", 5);
Zotero.debug(item, 5);
let feedItem = yield Zotero.FeedItems.getAsyncByGUID(item.guid);
if (!feedItem && createNew) {
if (feedItem) {
feedItemIDs.add(feedItem.id);
}
if (!feedItem) {
Zotero.debug("Creating new feed item " + item.guid);
feedItem = new Zotero.FeedItem();
feedItem.guid = item.guid;
feedItem.libraryID = this.id;
} else if(! feedItem.isTranslated) {
Zotero.debug("Feed item " + item.guid + " already in library.");
} else if (!feedItem.isTranslated) {
// TODO: maybe handle enclosed items on update better
item.enclosedItems = [];
// TODO figure out a better GUID collision resolution system
// that works with sync.
if (feedItem.libraryID != this.libraryID) {
let otherFeed = Zotero.Feeds.get(feedItem.libraryID);
Zotero.debug("Feed item " + feedItem.url + " from " + this.url +
" exists in a different feed " + otherFeed.url + ". Skipping");
continue;
}
Zotero.debug("Feed item " + item.guid + " already in library");
Zotero.debug("Updating metadata");
yield feedItem.loadItemData();
yield feedItem.loadCreators();
} else {
// Either has been translated or beyond lastGUID
// Not new and has been translated
Zotero.debug("Feed item " + item.guid + " is not new and has already been translated. Skipping");
continue;
}
for (let enclosedItem of item.enclosedItems) {
enclosedItem.parentItem = feedItem;
attachmentsToAdd.push(enclosedItem);
}
// Delete invalid data
delete item.guid;
delete item.enclosedItems;
feedItem.fromJSON(item);
if (!feedItem.hasChanged()) {
@ -388,7 +478,7 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
continue
}
feedItem.isRead = false;
toAdd.push(feedItem);
toSave.push(feedItem);
}
}
catch (e) {
@ -398,18 +488,24 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
}
this._set('_feedLastCheckError', e.message || 'Error processing feed');
}
if (toAdd.length) {
if (toSave.length) {
yield Zotero.DB.executeTransaction(function* () {
// Save in reverse order
for (let i=toAdd.length-1; i>=0; i--) {
// Saving currently has to happen sequentially so as not to violate the
// unique constraints in itemDataValues (FIXME)
yield toAdd[i].save({skipEditCheck: true});
for (let i=toSave.length-1; i>=0; i--) {
yield toSave[i].save();
}
});
this._set('_feedLastUpdate', Zotero.Date.dateToSQL(new Date(), true));
this._set('_feedLastGUID', toAdd[0].guid);
}
for (let attachment of attachmentsToAdd) {
if (attachment.url.indexOf('pdf') != -1 || attachment.contentType.indexOf('pdf') != -1) {
attachment.parentItemID = attachment.parentItem.id;
attachment.title = Zotero.getString('fileTypes.pdf');
yield Zotero.Attachments.linkFromURL(attachment);
}
}
yield this.clearExpiredItems(feedItemIDs);
this._set('_feedLastCheck', Zotero.Date.dateToSQL(new Date(), true));
yield this.saveTx();
yield this.updateUnreadCount();
@ -427,23 +523,6 @@ Zotero.Feed.prototype.updateFeed = Zotero.Promise.coroutine(function* () {
}
});
Zotero.Feed.prototype._finalizeErase = Zotero.Promise.coroutine(function* (){
let notifierData = {};
notifierData[this.libraryID] = {
libraryID: this.libraryID
};
Zotero.Notifier.trigger('delete', 'feed', this.id, notifierData);
Zotero.Feeds.unregister(this.libraryID);
return Zotero.Feed._super.prototype._finalizeErase.apply(this, arguments);
});
Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* (options = {}) {
let childItemIDs = yield Zotero.FeedItems.getAll(this.id, false, false, true);
yield Zotero.FeedItems.forceErase(childItemIDs);
yield Zotero.Feed._super.prototype.erase.call(this, options);
});
Zotero.Feed.prototype.updateUnreadCount = Zotero.Promise.coroutine(function* () {
let sql = "SELECT " + Zotero.Feed._unreadCountSQL
+ " FROM feeds F JOIN libraries L USING (libraryID)"
@ -455,3 +534,9 @@ Zotero.Feed.prototype.updateUnreadCount = Zotero.Promise.coroutine(function* ()
Zotero.Notifier.trigger('unreadCountUpdated', 'feed', this.id);
}
});
Zotero.Feed.prototype.updateFromJSON = Zotero.Promise.coroutine(function* (json) {
yield this.updateFeed();
yield Zotero.FeedItems.markAsReadByGUID(Object.keys(json.markedAsRead));
yield this.updateUnreadCount();
});

View file

@ -121,8 +121,8 @@ Zotero.FeedItem.prototype.fromJSON = function(json) {
d = new Date(d.year, d.month, d.day);
Zotero.debug(dateField + " " + JSON.stringify(d), 1);
}
if (!d) {
Zotero.logError("Discarding invalid " + field + " '" + val
if (isNaN(d.getTime())) {
Zotero.logError("Discarding invalid " + dateField + " '" + json[dateField]
+ "' for item " + this.libraryKey);
delete json[dateField];
continue;
@ -160,13 +160,6 @@ Zotero.FeedItem.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
return proceed;
});
Zotero.FeedItem.prototype.forceSaveTx = function(options) {
let newOptions = {};
Object.assign(newOptions, options || {});
newOptions.skipEditCheck = true;
return this.saveTx(newOptions);
}
Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.FeedItem._super.prototype._saveData.apply(this, arguments);
@ -178,25 +171,38 @@ Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
}
});
Zotero.FeedItem.prototype._finalizeErase = Zotero.Promise.coroutine(function* () {
// Set for syncing
let feed = Zotero.Feeds.get(this.libraryID);
let syncedSettings = feed.getSyncedSettings();
delete syncedSettings.markedAsRead[this.guid];
yield feed.setSyncedSettings(syncedSettings);
return Zotero.FeedItem._super.prototype._finalizeErase.apply(this, arguments);
});
Zotero.FeedItem.prototype.toggleRead = Zotero.Promise.coroutine(function* (state) {
state = state !== undefined ? !!state : !this.isRead;
let changed = this.isRead != state;
if (changed) {
this.isRead = state;
yield this.saveTx({skipEditCheck: true, skipDateModifiedUpdate: true});
// Set for syncing
let feed = Zotero.Feeds.get(this.libraryID);
let syncedSettings = feed.getSyncedSettings();
if (state) {
syncedSettings.markedAsRead[this.guid] = true;
} else {
delete syncedSettings.markedAsRead[this.guid];
}
yield feed.setSyncedSettings(syncedSettings, true);
yield this.saveTx();
yield feed.updateUnreadCount();
}
});
Zotero.FeedItem.prototype.forceEraseTx = function(options) {
let newOptions = {};
Object.assign(newOptions, options || {});
newOptions.skipEditCheck = true;
return this.eraseTx(newOptions);
};
/**
* Uses the item url to translate an existing feed item.
* If libraryID empty, overwrites feed item, otherwise saves
@ -233,7 +239,7 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
// Load document
let hiddenBrowser = Zotero.HTTP.processDocuments(
this.getField('url'),
(item) => deferred.resolve(item),
item => deferred.resolve(item),
()=>{}, error, true
);
let doc = yield deferred.promise;
@ -246,9 +252,9 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
translate.setHandler('translators', (me, translators) => deferred.resolve(translators));
translate.getTranslators();
let translators = yield deferred.promise;
if (! translators || !translators.length) {
Zotero.debug("No translators detected for FeedItem " + this.id + " with url " + this.getField('url'), 2);
throw new Zotero.Error("No translators detected for FeedItem " + this.id + " with url " + this.getField('url'))
if (!translators || !translators.length) {
Zotero.debug("No translators detected for feed item " + this.id + " with URL " + this.getField('url'), 2);
throw new Zotero.Error("No translators detected for feed item " + this.id + " with URL " + this.getField('url'))
}
translate.setTranslator(translators[0]);
@ -280,7 +286,7 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
this.fromJSON(itemData);
this.isTranslated = true;
this.forceSaveTx();
yield this.saveTx();
return this;
});

View file

@ -38,13 +38,13 @@ Zotero.FeedItems = new Proxy(function() {
Zotero.defineProperty(Zotero.Items, '_primaryDataSQLParts', {
get: function() {
let obj = zi_primaryDataSQLParts.call(this);
obj.feedItemGUID = "FeI.guid AS feedItemGUID";
obj.feedItemReadTime = "FeI.readTime AS feedItemReadTime";
obj.feedItemTranslatedTime = "FeI.translatedTime AS feedItemTranslatedTime";
obj.feedItemGUID = "FI.guid AS feedItemGUID";
obj.feedItemReadTime = "FI.readTime AS feedItemReadTime";
obj.feedItemTranslatedTime = "FI.translatedTime AS feedItemTranslatedTime";
return obj;
}
}, {lazy: true});
Zotero.Items._primaryDataSQLFrom += " LEFT JOIN feedItems FeI ON (FeI.itemID=O.itemID)";
Zotero.Items._primaryDataSQLFrom += " LEFT JOIN feedItems FI ON (FI.itemID=O.itemID)";
let zi_getObjectForRow = Zotero.Items._getObjectForRow;
Zotero.Items._getObjectForRow = function(row) {
@ -58,7 +58,7 @@ Zotero.FeedItems = new Proxy(function() {
this.getIDFromGUID = Zotero.Promise.coroutine(function* (guid) {
if (_idCache[guid] !== undefined) return _idCache[guid];
id = yield Zotero.DB.valueQueryAsync('SELECT itemID FROM feedItems WHERE guid=?', [guid]);
let id = yield Zotero.DB.valueQueryAsync('SELECT itemID FROM feedItems WHERE guid=?', [guid]);
if (!id) return false;
this._setGUIDMapping(guid, id);
@ -95,6 +95,38 @@ Zotero.FeedItems = new Proxy(function() {
return this.getAsync(id);
});
this.getMarkedAsRead = Zotero.Promise.coroutine(function* (libraryID, onlyGUIDs=false) {
let sql = "SELECT " + (onlyGUIDs ? "guid " : "itemID ") +
"FROM feedItems FI " +
"JOIN items I USING (itemID) " +
"WHERE libraryID=? AND readTime IS NOT NULL";
let ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
if (onlyGUIDs) {
return ids;
}
return Zotero.FeedItems.getAsync(ids);
});
/**
* Used on restore from sync
*/
this.markAsReadByGUID = Zotero.Promise.coroutine(function* (guids) {
if (! Array.isArray(guids)) {
throw new Error('guids must be an array in Zotero.FeedItems.toggleReadByID');
}
let ids = [];
Zotero.debug("Marking items as read");
Zotero.debug(guids);
for (let guid of guids) {
let id = yield this.getIDFromGUID(guid);
if (id) {
ids.push(id);
}
}
return this.toggleReadByID(ids, true);
});
this.toggleReadByID = Zotero.Promise.coroutine(function* (ids, state) {
var feedsToUpdate = new Set();
if (!Array.isArray(ids)) {
@ -115,24 +147,31 @@ Zotero.FeedItems = new Proxy(function() {
}
}
yield Zotero.DB.executeTransaction(function() {
for (let i=0; i<items.length; i++) {
items[i].isRead = state;
// Set for syncing
let feed = Zotero.Feeds.get(items[i].libraryID);
let syncedSettings = feed.getSyncedSettings();
if (state) {
syncedSettings.markedAsRead[items[i].guid] = true;
} else {
delete syncedSettings.markedAsRead[items[i].guid];
}
yield feed.setSyncedSettings(syncedSettings);
yield items[i].save({skipEditCheck: true});
feedsToUpdate.add(items[i].libraryID);
feedsToUpdate.add(feed);
}
});
for (let feedID of feedsToUpdate) {
let feed = Zotero.Feeds.get(feedID);
yield feed.updateUnreadCount();
for (let feed of feedsToUpdate) {
yield Zotero.Promise.all([feed.updateUnreadCount(), feed.storeSyncedSettings()]);
}
});
this.forceErase = function(ids, options = {}) {
options.skipEditCheck = true;
return this.erase(ids, options);
};
return this;
}.call({}),

View file

@ -68,6 +68,51 @@ Zotero.Feeds = new function() {
delete this._cache.urlByLibraryID[libraryID];
delete this._cache.libraryIDByURL[url];
}
this.init = function () {
return this.scheduleNextFeedCheck();
}
this.restoreFromJSON = Zotero.Promise.coroutine(function* (json, merge=false) {
Zotero.debug("Restoring feeds from remote JSON");
Zotero.debug(json);
if (merge) {
let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
for (let url in json) {
if (syncedFeeds[url]) {
syncedFeeds[url].name = json[url].name;
syncedFeeds[url].cleanupAfter = json[url].cleanupAfter;
syncedFeeds[url].refreshInterval = json[url].refreshInterval;
for (let guid in json[url].markedAsRead) {
syncedFeeds[url].markedAsRead[guid] = true;
}
} else {
syncedFeeds[url] = json[url];
}
}
json = syncedFeeds;
}
yield Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, 'feeds', json);
//
let feeds = Zotero.Feeds.getAll();
for (let feed of feeds) {
if (json[feed.url]) {
Zotero.debug("Feed " + feed.url + " is being updated from remote JSON");
yield feed.updateFromJSON(json[feed.url]);
delete json[feed.url];
} else {
Zotero.debug("Feed " + feed.url + " does not exist in remote JSON. Deleting");
yield feed.eraseTx();
}
}
// Because existing json[feed.url] got deleted, `json` now only contains new feeds
for (let url in json) {
Zotero.debug("Feed " + url + " is being created from remote JSON");
let feed = new Zotero.Feed(json[url]);
yield feed.saveTx();
yield feed.updateFromJSON(json[url]);
}
});
this.getByURL = function(urls) {
if (!this._cache) throw new Error("Zotero.Feeds cache is not initialized");
@ -78,17 +123,16 @@ Zotero.Feeds = new function() {
asArray = false;
}
let libraryIDs = Array(urls.length);
let feeds = new Array(urls.length);
for (let i=0; i<urls.length; i++) {
let libraryID = this._cache.libraryIDByURL[urls[i]];
if (!libraryID) {
throw new Error('Feed with url ' + urls[i] + ' not registered in feed cache');
return
}
libraryIDs[i] = libraryID;
feeds[i] = Zotero.Libraries.get(libraryID);
}
let feeds = Zotero.Libraries.get(libraryIDs);
return asArray ? feeds : feeds[0];
}
@ -118,7 +162,7 @@ Zotero.Feeds = new function() {
let globalFeedCheckDelay = Zotero.Promise.resolve();
this.scheduleNextFeedCheck = Zotero.Promise.coroutine(function* () {
Zotero.debug("Scheduling next feed update.");
Zotero.debug("Scheduling next feed update");
let sql = "SELECT ( CASE "
+ "WHEN lastCheck IS NULL THEN 0 "
+ "ELSE strftime('%s', lastCheck) + refreshInterval*3600 - strftime('%s', 'now') "
@ -149,7 +193,7 @@ Zotero.Feeds = new function() {
throw e;
});
} else {
Zotero.debug("No feeds with auto-update.");
Zotero.debug("No feeds with auto-update");
}
});
@ -165,7 +209,7 @@ Zotero.Feeds = new function() {
yield feed._updateFeed();
}
Zotero.debug("All feed updates done.");
Zotero.debug("All feed updates done");
this.scheduleNextFeedCheck();
});
}

View file

@ -133,10 +133,10 @@ Zotero.Items = function() {
* @param {Integer} libraryID
* @param {Boolean} [onlyTopLevel=false] If true, don't include child items
* @param {Boolean} [includeDeleted=false] If true, include deleted items
* @param {Boolean} [onlyIDs=false] If true, resolves only with IDs
* @param {Boolean} [asIDs=false] If true, resolves only with IDs
* @return {Promise<Array<Zotero.Item|Integer>>}
*/
this.getAll = Zotero.Promise.coroutine(function* (libraryID, onlyTopLevel, includeDeleted, onlyIDs=false) {
this.getAll = Zotero.Promise.coroutine(function* (libraryID, onlyTopLevel, includeDeleted, asIDs=false) {
var sql = 'SELECT A.itemID FROM items A';
if (onlyTopLevel) {
sql += ' LEFT JOIN itemNotes B USING (itemID) '
@ -151,7 +151,7 @@ Zotero.Items = function() {
}
sql += " AND libraryID=?";
var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
if (onlyIDs) {
if (asIDs) {
return ids;
}
return this.getAsync(ids);

View file

@ -462,6 +462,8 @@ Zotero.FeedReader._getFeedItem = function(feedEntry, feedInfo) {
Zotero.FeedReader._guessItemType(item);
item.enclosedItems = Zotero.FeedReader._getEnclosedItems(feedEntry);
return item;
}
@ -501,3 +503,19 @@ Zotero.FeedReader._getFeedField = function(feedEntry, field, namespace) {
return;
}
Zotero.FeedReader._getEnclosedItems = function(feedEntry) {
var enclosedItems = [];
if (feedEntry.enclosures) {
for (let i = 0; i < feedEntry.enclosures.length; i++) {
let elem = feedEntry.enclosures.queryElementAt(0, Components.interfaces.nsIPropertyBag2);
if (elem.get('url')) {
let enclosedItem = {url: elem.get('url'), contentType: elem.get('type') || ''};
enclosedItems.push(enclosedItem);
}
}
}
return enclosedItems;
}

View file

@ -98,6 +98,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
}
this._treebox = treebox;
this.setSortColumn();
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
@ -259,6 +260,43 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
});
Zotero.ItemTreeView.prototype.setSortColumn = function() {
var dir, col, currentCol, currentDir;
for (let i=0, len=this._treebox.columns.count; i<len; i++) {
let column = this._treebox.columns.getColumnAt(i);
if (column.element.getAttribute('sortActive')) {
currentCol = column;
currentDir = column.element.getAttribute('sortDirection');
column.element.removeAttribute('sortActive');
column.element.removeAttribute('sortDirection');
break;
}
}
let colID = Zotero.Prefs.get('itemTree.sortColumnID');
// Restore previous sort setting (feed -> non-feed)
if (! this.collectionTreeRow.isFeed() && colID) {
col = this._treebox.columns.getNamedColumn(colID);
dir = Zotero.Prefs.get('itemTree.sortDirection');
Zotero.Prefs.clear('itemTree.sortColumnID');
Zotero.Prefs.clear('itemTree.sortDirection');
// No previous sort setting stored, so store it (non-feed -> feed)
} else if (this.collectionTreeRow.isFeed() && !colID && currentCol) {
Zotero.Prefs.set('itemTree.sortColumnID', currentCol.id);
Zotero.Prefs.set('itemTree.sortDirection', currentDir);
// Retain current sort setting (non-feed -> non-feed)
} else {
col = currentCol;
dir = currentDir;
}
if (col) {
col.element.setAttribute('sortActive', true);
col.element.setAttribute('sortDirection', dir);
}
}
/**
* Reload the rows from the data access methods
* (doesn't call the tree.invalidate methods, etc.)
@ -1228,6 +1266,9 @@ Zotero.ItemTreeView.prototype.isSorted = function()
}
Zotero.ItemTreeView.prototype.cycleHeader = function (column) {
if (this.collectionTreeRow.isFeed()) {
return;
}
for(var i=0, len=this._treebox.columns.count; i<len; i++)
{
col = this._treebox.columns.getColumnAt(i);
@ -2136,8 +2177,8 @@ Zotero.ItemTreeView.prototype.getSortFields = function () {
* Returns 'ascending' or 'descending'
*/
Zotero.ItemTreeView.prototype.getSortDirection = function() {
if (this.collectionTreeRow.isFeed) {
return Zotero.Prefs.get('feeds.sortAsc') ? 'ascending' : 'descending';
if (this.collectionTreeRow.isFeed()) {
return Zotero.Prefs.get('feeds.sortAscending') ? 'ascending' : 'descending';
}
var column = this._treebox.columns.getSortedColumn();
if (!column) {

View file

@ -2216,7 +2216,7 @@ Zotero.Schema = new function(){
// Feeds
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS feeds");
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS feedItems");
yield Zotero.DB.queryAsync("CREATE TABLE feeds (\n libraryID INTEGER PRIMARY KEY,\n name TEXT NOT NULL,\n url TEXT NOT NULL UNIQUE,\n lastUpdate TIMESTAMP,\n lastCheck TIMESTAMP,\n lastCheckError TEXT,\n lastGUID TEXT,\n cleanupAfter INT,\n refreshInterval INT,\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("CREATE TABLE feeds (\n libraryID INTEGER PRIMARY KEY,\n name TEXT NOT NULL,\n url TEXT NOT NULL UNIQUE,\n lastUpdate TIMESTAMP,\n lastCheck TIMESTAMP,\n lastCheckError TEXT,\n cleanupAfter INT,\n refreshInterval INT,\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("CREATE TABLE feedItems (\n itemID INTEGER PRIMARY KEY,\n guid TEXT NOT NULL UNIQUE,\n readTime TIMESTAMP,\n translatedTime TIMESTAMP,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
}
}

View file

@ -21,6 +21,7 @@ Zotero.Sync.Storage.Local = {
switch (libraryType) {
case 'user':
case 'publications':
case 'feed':
return Zotero.Prefs.get("sync.storage.protocol") == 'webdav' ? 'webdav' : 'zfs';
case 'group':

View file

@ -879,6 +879,7 @@ Zotero.Sync.Data.Local = {
Zotero.debug("SAVING " + json.key + " WITH SYNCED");
Zotero.debug(obj.version);
yield obj.save({
skipEditCheck: true,
skipDateModifiedUpdate: true,
skipSelect: true,
errorHandler: function (e) {

View file

@ -242,11 +242,12 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Prompt if library empty and there is no userID stored
this.checkEmptyLibrary = Zotero.Promise.coroutine(function* (keyInfo) {
let library = Zotero.Libraries.userLibrary;
let feeds = Zotero.Feeds.getAll();
let userID = Zotero.Users.getCurrentUserID();
if (!userID) {
let hasItems = yield library.hasItems();
if (!hasItems) {
if (!hasItems && feeds.length <= 0) {
let ps = Services.prompt;
let index = ps.confirmEx(
null,

View file

@ -626,7 +626,8 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
yield Zotero.Searches.init();
yield Zotero.Creators.init();
yield Zotero.Groups.init();
yield Zotero.Relations.init()
yield Zotero.Relations.init();
yield Zotero.Feeds.init();
let libraryIDs = Zotero.Libraries.getAll().map(x => x.libraryID);
for (let libraryID of libraryIDs) {

View file

@ -1381,9 +1381,10 @@ var ZoteroPane = new function()
}
if (item.isFeedItem) {
if (! item.isTranslated) {
item.translate();
}
// Too slow for now
// if (!item.isTranslated) {
// item.translate();
// }
this.startItemReadTimeout(item.id);
}
}
@ -1756,7 +1757,7 @@ var ZoteroPane = new function()
return;
}
if (!this.canEdit()) {
if (!this.canEdit() && !collectionTreeRow.isFeed()) {
this.displayCannotEditLibraryMessage();
return;
}
@ -1874,7 +1875,7 @@ var ZoteroPane = new function()
}
if (this.collectionsView.selection.count > 0) {
var row = this.collectionsView.getRow(this.collectionsView.selection.currentIndex);
var row = this.collectionsView.selectedTreeRow;
if (row.isCollection()) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@ -1924,7 +1925,7 @@ var ZoteroPane = new function()
this.markFeedRead = Zotero.Promise.coroutine(function* () {
if (!this.collectionsView.selection.count) return;
let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
let feed = this.collectionsView.selectedTreeRow.ref;
let feedItemIDs = yield Zotero.FeedItems.getAll(feed.libraryID, true, false, true);
yield Zotero.FeedItems.toggleReadByID(feedItemIDs, true);
});
@ -1933,7 +1934,7 @@ var ZoteroPane = new function()
this.editSelectedFeed = Zotero.Promise.coroutine(function* () {
if (!this.collectionsView.selection.count) return;
let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
let feed = this.collectionsView.selectedTreeRow.ref;
let data = {
url: feed.url,
title: feed.name,
@ -1954,7 +1955,7 @@ var ZoteroPane = new function()
this.refreshFeed = function() {
if (!this.collectionsView.selection.count) return;
let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
let feed = this.collectionsView.selectedTreeRow.ref;
return feed.updateFeed();
}
@ -2297,29 +2298,15 @@ var ZoteroPane = new function()
m.sep1,
m.markReadFeed,
m.editSelectedFeed,
m.deleteCollectionAndItems,
m.sep2,
m.exportCollection,
m.createBibCollection,
m.loadReport
m.deleteCollectionAndItems
];
if (!this.itemsView.rowCount) {
disable = [m.createBibCollection, m.loadReport]
if (this.collectionsView.isContainerEmpty(this.collectionsView.selection.currentIndex)) {
disable.push(m.exportCollection);
}
}
if (collectionTreeRow.ref.unreadCount == 0) {
disable.push(m.markReadFeed);
}
// Adjust labels
menu.childNodes[m.deleteCollectionAndItems].setAttribute('label', Zotero.getString('pane.collections.menu.delete.feedAndItems'));
menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.feed'));
menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.feed'));
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.feed'));
}
else if (collectionTreeRow.isSearch()) {
show = [
@ -2464,11 +2451,13 @@ var ZoteroPane = new function()
else if (collectionTreeRow.isPublications()) {
show.push(m.deleteFromLibrary);
}
else if (! collectionTreeRow.isFeed()) {
else if (!collectionTreeRow.isFeed()) {
show.push(m.moveToTrash);
}
show.push(m.sep3, m.exportItems, m.createBib, m.loadReport);
if(!collectionTreeRow.isFeed()) {
show.push(m.sep3, m.exportItems, m.createBib, m.loadReport);
}
if (this.itemsView.selection.count > 0) {
// Multiple items selected
@ -2523,9 +2512,9 @@ var ZoteroPane = new function()
if (canMarkRead) {
show.push(m.toggleRead);
if (markUnread) {
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markUnread'));
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsUnread'));
} else {
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markRead'));
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsRead'));
}
}
@ -2618,9 +2607,9 @@ var ZoteroPane = new function()
else if (item.isFeedItem) {
show.push(m.toggleRead);
if (item.isRead) {
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markUnread'));
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsUnread'));
} else {
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markRead'));
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsRead'));
}
}
else {
@ -2652,13 +2641,14 @@ var ZoteroPane = new function()
m.moveToTrash, m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport);
}
if (!collectionTreeRow.editable || collectionTreeRow.isPublications()) {
if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeed()) {
for (let i in m) {
// Still show export/bib/report for non-editable views
// Still allow export/bib/report/read for non-editable views
switch (i) {
case 'exportItems':
case 'createBib':
case 'loadReport':
case 'toggleRead':
continue;
}
if (isTrash) {