diff --git a/build/libZoteroSingle.js b/build/libZoteroSingle.js
index 6ebc3e2..89017e5 100644
--- a/build/libZoteroSingle.js
+++ b/build/libZoteroSingle.js
@@ -803,6 +803,16 @@ Zotero.Feed.prototype.parseXmlFeed = function(data){
this.entries = fel.find('entry');
return this;
};
+/**
+ * A user or group Zotero library. This is generally the top level object
+ * through which interactions should happen. It houses containers for
+ * Zotero API objects (collections, items, etc) and handles making requests
+ * with particular API credentials, as well as storing data locally.
+ * @param {string} type type of library, 'user' or 'group'
+ * @param {int} libraryID ID of the library
+ * @param {string} libraryUrlIdentifier identifier used in urls, could be library id or user/group slug
+ * @param {string} apiKey key to use for API requests
+ */
Zotero.Library = function(type, libraryID, libraryUrlIdentifier, apiKey){
Z.debug("Zotero.Library constructor", 3);
Z.debug("Library Constructor: " + type + " " + libraryID + " ", 3);
@@ -814,7 +824,7 @@ Zotero.Library = function(type, libraryID, libraryUrlIdentifier, apiKey){
earliestVersion: null,
latestVersion: null
};
- library._apiKey = apiKey || false;
+ library._apiKey = apiKey || '';
library.libraryBaseWebsiteUrl = Zotero.config.libraryPathString;
if(type == 'group'){
@@ -901,7 +911,10 @@ Zotero.Library = function(type, libraryID, libraryUrlIdentifier, apiKey){
library.collectionsChanged = function(){};
library.itemsChanged = function(){};
};
-
+/**
+ * Items columns for which sorting is supported
+ * @type {Array}
+ */
Zotero.Library.prototype.sortableColumns = ['title',
'creator',
'itemType',
@@ -920,7 +933,10 @@ Zotero.Library.prototype.sortableColumns = ['title',
/*'numChildren',*/
'addedBy'
/*'modifiedBy'*/];
-
+/**
+ * Columns that can be displayed in an items table UI
+ * @type {Array}
+ */
Zotero.Library.prototype.displayableColumns = ['title',
'creator',
'itemType',
@@ -939,11 +955,21 @@ Zotero.Library.prototype.displayableColumns = ['title',
'numChildren',
'addedBy'
/*'modifiedBy'*/];
-
+/**
+ * Items columns that only apply to group libraries
+ * @type {Array}
+ */
Zotero.Library.prototype.groupOnlyColumns = ['addedBy'
/*'modifiedBy'*/];
-//this does not handle accented characters correctly
+
+/**
+ * Sort function that compares the title attributes of two objects.
+ * This function still does not handle accented characters correctly.
+ * @param {[type]} a [description]
+ * @param {[type]} b [description]
+ * @return {[type]} [description]
+ */
Zotero.Library.prototype.sortByTitleCompare = function(a, b){
//Z.debug("compare by key: " + a + " < " + b + " ?", 4);
if(a.title.toLocaleLowerCase() == b.title.toLocaleLowerCase()){
@@ -955,6 +981,15 @@ Zotero.Library.prototype.sortByTitleCompare = function(a, b){
return 1;
};
+/**
+ * Sort function that converts strings to locale lower case before comparing,
+ * however this is still not particularly effective at getting correct localized
+ * sorting in modern browsers due to browser implementations being poor. What we
+ * really want here is to strip diacritics first.
+ * @param {string} a [description]
+ * @param {string} b [description]
+ * @return {int} [description]
+ */
Zotero.Library.prototype.sortLower = function(a, b){
if(a.toLocaleLowerCase() == b.toLocaleLowerCase()){
return 0;
@@ -1041,6 +1076,14 @@ Zotero.Library.prototype.ajaxRequest = function(url, type, options){
//or do we depend on specified callbacks to update versions if necessary?
//fail on error?
//request object must specify: url, method, body, headers, success callback, fail callback(?)
+
+/**
+ * Take an array of objects that specify Zotero API requests and perform them
+ * in sequence. Return a promise that gets resolved when all requests have
+ * gone through.
+ * @param {[] Objects} requests Array of objects specifying requests to be made
+ * @return {Promise} Promise that resolves/rejects along with requests
+ */
Zotero.Library.prototype.sequentialRequests = function(requests){
Z.debug("Zotero.Library.sequentialRequests", 3);
var library = this;
@@ -1060,9 +1103,16 @@ Zotero.Library.prototype.sequentialRequests = function(requests){
});
}
+/**
+ * Generate a website url based on a dictionary of variables and the configured
+ * libraryBaseWebsiteUrl
+ * @param {Object} urlvars Dictionary of key/value variables
+ * @return {string} website url
+ */
Zotero.Library.prototype.websiteUrl = function(urlvars){
Z.debug("Zotero.library.websiteUrl", 3);
Z.debug(urlvars, 4);
+ var library = this;
var urlVarsArray = [];
J.each(urlvars, function(index, value){
@@ -1073,7 +1123,7 @@ Zotero.Library.prototype.websiteUrl = function(urlvars){
Z.debug(urlVarsArray, 4);
var pathVarsString = urlVarsArray.join('/');
- return this.libraryBaseWebsiteUrl + '/' + pathVarsString;
+ return library.libraryBaseWebsiteUrl + '/' + pathVarsString;
};
@@ -1088,6 +1138,13 @@ Zotero.Library.prototype.synchronize = function(){
//
};
+/**
+ * Make and process API requests to update the local library items based on the
+ * versions we have locally. When the promise is resolved, we should have up to
+ * date items in this library's items container, as well as saved to indexedDB
+ * if configured to use it.
+ * @return {Promise} Promise
+ */
Zotero.Library.prototype.loadUpdatedItems = function(){
var library = this;
//sync from the libraryVersion if it exists, otherwise use the itemsVersion, which is likely
@@ -1217,7 +1274,10 @@ Zotero.Library.prototype.getDeleted = function(version) {
newer:version
};
- if(library.deleted.deletedVersion == version){
+ //don't fetch again if version we'd be requesting is between
+ //deleted.newer and delete.deleted versions, just use that one
+ if(version > library.deleted.newerVersion &&
+ version < library.deleted.deletedVersion){
Z.debug("deletedVersion matches requested: immediately resolving");
return Promise.resolve(library.deleted.deletedData);
}
@@ -2299,7 +2359,7 @@ Zotero.Items.prototype.deleteItems = function(deleteItems, version){
var headers = {'Content-Type': 'application/json'};
- headers['If-Unmodified-Since-Version'] = version;
+ //headers['If-Unmodified-Since-Version'] = version;
deletePromise.then(function(){
return items.owningLibrary.ajaxRequest(config, 'DELETE',
@@ -3744,10 +3804,11 @@ Zotero.Item.prototype.uploadChildAttachment = function(childItem, fileInfo, prog
return childItem.uploadFile(fileInfo, progressCallback)
}, function(response){
//failure during attachmentItem write
- return {
+ throw {
"message":"Failure during attachmentItem write.",
"code": response.jqxhr.status,
- "serverMessage": response.jqxhr.responseText
+ "serverMessage": response.jqxhr.responseText,
+ "response": response
};
});
};
@@ -4977,11 +5038,14 @@ Zotero.url.itemHref = function(item){
Zotero.url.attachmentDownloadLink = function(item){
var retString = '';
+ var downloadUrl = item.attachmentDownloadUrl;
+ var contentType = item.get('contentType');
+
if(item.links && item.links['enclosure']){
- var tail = item.links['enclosure']['href'].substr(-4, 4);
- if(tail == 'view'){
+ var ftype = item.links['enclosure'].type;
+ if(!item.links['enclosure']['length'] && ftype == 'text/html'){
//snapshot: redirect to view
- retString += '' + 'View Snapshot';
+ retString += '' + 'View Snapshot';
}
else{
//file: offer download
@@ -5016,12 +5080,13 @@ Zotero.url.attachmentDownloadUrl = function(item){
var retString = '';
if(item.links && item.links['enclosure']){
if(Zotero.config.directDownloads){
- retString = Zotero.config.baseZoteroWebsiteUrl + Zotero.config.nonparsedBaseUrl + '/' + item.itemKey + '/file';
+ retString = item.links['enclosure']['href'];
+ /*retString = Zotero.config.baseZoteroWebsiteUrl + Zotero.config.nonparsedBaseUrl + '/' + item.itemKey + '/file';
var tail = item.links['enclosure']['href'].substr(-4, 4);
if(tail == 'view'){
//snapshot: redirect to view
retString += '/view';
- }
+ }*/
}
else {
//we have a proxy for downloads at baseDownloadUrl so just pass an itemkey to that
@@ -6415,167 +6480,6 @@ Zotero.Library.prototype.loadAllTags = function(config, checkCached){
});
};
-//download templates for every itemType
-Zotero.Library.prototype.loadItemTemplates = function(){
-
-};
-
-//download possible creatorTypes for every itemType
-Zotero.Library.prototype.loadCreatorTypes = function(){
-
-};
-
-//store a single binary file for offline use using Filestorage shim
-Zotero.Library.prototype.saveFileOffline = function(item){
- try{
- Z.debug("Zotero.Library.saveFileOffline", 3);
- var library = this;
- var deferred = new J.Deferred();
-
- if(library.filestorage === false){
- return false;
- }
- var enclosureUrl;
- var mimetype;
- if(item.links && item.links['enclosure']){
- enclosureUrl = item.links.enclosure.href;
- mimetype = item.links.enclosure.type;
- }
- else{
- return false;
- }
-
- var reqUrl = enclosureUrl + Zotero.ajax.apiQueryString({});
-
- Z.debug("reqUrl:" + reqUrl, 3);
-
- var xhr = new XMLHttpRequest();
- xhr.open('GET', Zotero.ajax.proxyWrapper(reqUrl, 'GET'), true);
- xhr.responseType = 'blob';
-
- xhr.onload = function(e) {
- try{
- if (this.status == 200) {
- Z.debug("Success downloading");
- var blob = this.response;
- //Zotero.temp.fileDataUrl = Util.fileToObjectURL(blob);
- //Zotero.temp.fileUrl = Util.fileToObjectURL(blob);
- library.filestorage.filer.write('/' + item.itemKey, {data:blob, type: mimetype}, J.proxy(function(fileEntry, fileWriter){
- try{
- Z.debug("Success writing file");
- Z.debug("Saved file for item " + item.itemKey + ' for offline use');
- Z.debug("Saving file object somewhere in Zotero namespace:");
- library.filestorage.filer.open(fileEntry, J.proxy(function(file){
- try{
- Z.debug("reading back filesystem stored file into object url");
- //we could return an objectUrl here, but I think that would keep it in memory when we don't necessarily need it
- //Zotero.temp.fileUrlAfter = Util.fileToObjectURL(file);
- deferred.resolve(true);
- }
- catch(e){
- Z.debug("Caught in filer.open");
- Z.debug(e);
- }
- }, this) );
- }
- catch(e){
- Z.debug("Caught in filer.write");
- Z.debug(e);
- }
- }, this) );
- }
- }
- catch(e){
- Z.debug("Caught inside binary xhr onload");
- Z.debug(e);
- }
- };
- xhr.send();
-
- /*
- var downloadDeferred = J.get(Zotero.ajax.proxyWrapper(reqUrl, 'GET'), J.proxy(function(data, textStatus, jqXHR){
- //Z.debug(data);
- Zotero.temp.fileDataUrl = Util.strToDataURL(data, mimetype);
- library.filestorage.filer.write('/' + item.itemKey, {data:data, type: mimetype}, J.proxy(function(fileEntry, fileWriter){
- Z.debug("Success");
- Z.debug("Saved file for item " + item.itemKey + ' for offline use');
- Z.debug("Saving file object somewhere in Zotero namespace:");
- library.filestorage.filer.open(fileEntry, J.proxy(function(file){
- Zotero.temp.fileUrl = Util.fileToObjectURL(file);
- }, this) );
- }, this) );
- }, this) );
- */
- return deferred;
- }
- catch(e){
- Z.debug("Caught in Z.Library.saveFileOffline");
- Z.debug(e);
- }
-};
-
-//save a set of files offline, identified by itemkeys
-Zotero.Library.prototype.saveFileSetOffline = function(itemKeys){
- Z.debug("Zotero.Library.saveFileSetOffline", 3);
- var library = this;
- var ds = [];
- var deferred = new J.Deferred();
- var item;
- var childItemKeys = [];
- var checkedKeys = {};
-
- J.each(itemKeys, function(ind, itemKey){
- if(checkedKeys.hasOwnProperty(itemKey)){
- return;
- }
- else{
- checkedKeys[itemKey] = 1;
- }
- item = library.items.getItem(itemKey);
- if(item && item.links && item.links['enclosure']){
- ds.push(library.saveFileOffline(item));
- }
- if(item.numChildren){
- J.each(item.childItemKeys, function(ind, val){
- childItemKeys.push(val);
- });
- }
- });
-
- J.each(childItemKeys, function(ind, itemKey){
- if(checkedKeys.hasOwnProperty(itemKey)){
- return;
- }
- else{
- checkedKeys[itemKey] = 1;
- }
- item = library.items.getItem(itemKey);
- if(item && item.links && item.links['enclosure']){
- ds.push(library.saveFileOffline(item));
- }
- });
-
- J.when.apply(null, ds).then(J.proxy(function(){
- var d = library.filestorage.listOfflineFiles();
- d.then(J.proxy(function(localItemKeys){
- deferred.resolve();
- }, this) );
- }));
-
- return deferred;
-};
-
-//store all files from a collection for offline use
-//this probably doesn't do anything right now since child items are not part of a collection?
-Zotero.Library.prototype.saveCollectionFilesOffline = function(collectionKey){
- Zotero.debug("Zotero.Library.saveCollectionFilesOffline " + collectionKey, 3);
- var library = this;
- var collection = library.collections.getCollection(collectionKey);
- var itemKeys = collection.itemKeys;
- var d = Zotero.Library.prototype.saveFileSetOffline(itemKeys);
- return d;
-};
-
//load objects from indexedDB
Zotero.Library.prototype.loadIndexedDBCache = function(){
Zotero.debug("Zotero.Library.loadIndexedDBCache", 3);
diff --git a/build/libZoteroSingle.php b/build/libZoteroSingle.php
index 3956c31..14e5081 100644
--- a/build/libZoteroSingle.php
+++ b/build/libZoteroSingle.php
@@ -2747,6 +2747,80 @@ public function dataObject() {
return $jsonItem;
}
+
+ public function role($userID) {
+ if($userID == $this->owner){
+ return 'owner';
+ }
+ if(in_array($userID, $this->adminIDs)){
+ return 'admin';
+ }
+ if(in_array($userID, $this->memberIDs)){
+ return 'member';
+ }
+ return false;
+ }
+
+ public function isOwner($userID){
+ return $this->role($userID) == 'owner';
+ }
+
+ public function isAdmin($userID){
+ $role = $this->role($userID);
+ if($role == 'admin' || $role == 'owner'){
+ return true;
+ }
+ }
+
+ public function isMember($userID){
+ $role = $this->role($userID);
+ if($role === false){
+ return false;
+ }
+ return true;
+ }
+
+ public function userIDs(){
+ //test
+ return array_merge($this->adminIDs, $this->memberIDs);
+ }
+
+ public function groupIdentifier(){
+ if($this->type == 'Private'){
+ return $this->groupID;
+ }
+ $name = $this->name;
+ $slug = trim($name);
+ $slug = strtolower($slug);
+ $slug = preg_replace("/[^a-z0-9 ._-]/", "", $slug);
+ $slug = str_replace(" ", "_", $slug);
+ return $slug;
+ }
+
+ public function userReadable($userID){
+ if($this->libraryReading == 'all'){
+ return true;
+ }
+ if($this->isMember($userID)){
+ return true;
+ }
+ return false;
+ }
+
+ public function userWritable($userID){
+ if($this->libraryEditing == 'members'){
+ if($this->isMember($userID)){
+ return true;
+ }
+ return false;
+ }
+ if($this->libraryEditing == 'admins'){
+ if($this->isAdmin($userID)){
+ return true;
+ }
+ return false;
+ }
+ }
}
/**