variable typing

This commit is contained in:
Artur
2025-11-19 16:05:50 +03:00
parent 59d68cb72c
commit c355d5d150
10 changed files with 769 additions and 344 deletions

View File

@ -31,7 +31,7 @@
<script src="scripts/citeproc/citeproc_commonjs.js"></script>
<script src="scripts/csl/citation/citation-item-data.js"></script>
<script src="scripts/csl/citation/citation-tem.js"></script>
<script src="scripts/csl/citation/citation-item.js"></script>
<script src="scripts/csl/citation/citation.js"></script>
<script src="scripts/csl/citation/storage.js"></script>
@ -74,7 +74,7 @@
<div class="selectHolder">
<input id="library" readonly class="control select" type="text">
<div id="searchLibrary" class="selectList display-none">
<span data-value="all" class="i18n">All groups</span>
<span data-value="all" class="i18n" selected>All groups</span>
</div>
</div>
<label id="searchLabel">

View File

@ -1,5 +1,9 @@
// @ts-check
/// <reference path="./types-global.js" />
/// <reference path="./csl/citation/citation.js" />
/// <reference path="./csl/styles/style-parser.js" />
/**
* @typedef {Object} CustomField
* @property {string} Value
@ -7,15 +11,6 @@
* @property {string} [FieldId]
*/
/**
* @typedef {Object} SupSubPositions
* @property {'sup'|'sub'} type
* @property {number} start
* @property {number} end
* @property {string} content
* @property {string} originalMatch
*/
/**
* @param {string} citPrefix
* @param {string} citSuffix

View File

@ -15,6 +15,10 @@
* limitations under the License.
*
*/
/// <reference path="./zotero.js" />
/// <reference path="./csl/citation/citation.js" />
/// <reference path="./csl/styles/styles-manager.js" />
(function () {
var counter = 0; // счетчик отправленных запросов (используется чтобы знать показывать "not found" или нет)
var displayNoneClass = "display-none";
@ -64,6 +68,7 @@
var selectedLocale;
var selectedStyle;
/** @type {ZoteroSdk} */
var sdk = null;
var cslStylesManager = null;
var citationDocService = null;
@ -126,7 +131,7 @@
showLoader(true);
setTimeout(function () { searchField.focus(); },100);
sdk = ZoteroSdk();
sdk = new ZoteroSdk();
cslStylesManager = new CslStylesManager();
addEventListeners();
@ -826,6 +831,15 @@
docsScroller.onscroll();
};
/**
* @param {Promise} promise
* @param {boolean} append
* @param {boolean} showLoader
* @param {boolean} hideLoader
* @param {boolean} isGroup
* @param {boolean} bCount
* @returns {Promise}
*/
function loadLibrary(promise, append, showLoader, hideLoader, isGroup, bCount) {
if (showLoader) showLibLoader(true);
if (bCount) counter++;

View File

@ -127,11 +127,18 @@ CitationItemData.prototype._addCustomProperty = function (key, value) {
return this;
};
/**
* @param {string} key
* @returns
*/
CitationItemData.prototype.getCustomProperty = function (key) {
if (Object.hasOwnProperty.call(this._custom, key)) return this._custom[key];
return null;
};
/**
* @param {any} itemDataObject
*/
CitationItemData.prototype.fillFromObject = function (itemDataObject) {
if (Object.hasOwnProperty.call(itemDataObject, "type")) {
this._type = itemDataObject.type;
@ -448,7 +455,10 @@ CitationItemData.prototype.fillFromObject = function (itemDataObject) {
}
if (Object.hasOwnProperty.call(itemDataObject, "creators")) {
itemDataObject.creators.forEach(function (creator) {
const self = this;
itemDataObject.creators.forEach(function (
/** @type {{firstName: string, lastName: string}}} */ creator
) {
let author = {};
if (creator.firstName) {
author.given = creator.firstName;
@ -456,8 +466,9 @@ CitationItemData.prototype.fillFromObject = function (itemDataObject) {
if (creator.lastName) {
author.family = creator.lastName;
}
this._author.push(author);
}, this);
self._author.push(author);
},
this);
}
if (Object.hasOwnProperty.call(itemDataObject, "libraryCatalog")) {

View File

@ -1,3 +1,16 @@
// @ts-check
/// <reference path="./citation-item-data.js" />
/**
* @typedef {Object} OldCitationItem - before version 1.0.5
* @property {string|number} id
* @property {string} [type]
* @property {string} [title]
* @property {string} [userID]
* @property {string} [groupID]
* @property {number} index
* @property {boolean} [`suppress-author`]
*/
/**
* @param {string|number} id
*/
@ -17,7 +30,11 @@ function CitationItem(id) {
this._uris = new Array();
}
/**
* @param {any} itemObject
*/
CitationItem.prototype.fillFromObject = function (itemObject) {
const self = this;
if (
Object.hasOwnProperty.call(itemObject, "version") &&
Object.hasOwnProperty.call(itemObject, "library")
@ -50,12 +67,15 @@ CitationItem.prototype.fillFromObject = function (itemObject) {
if (Object.hasOwnProperty.call(itemObject, "author-only"))
this._authorOnly = itemObject["author-only"];
if (Object.hasOwnProperty.call(itemObject, "uris")) {
itemObject.uris.forEach(function (uri) {
this.addUri(uri);
itemObject.uris.forEach(function (/** @type {string} */ uri) {
self.addUri(uri);
}, this);
}
};
/**
* @returns {SuppressAuthor}
*/
CitationItem.prototype.getSuppressAuthor = function () {
return {
id: this.id,
@ -209,7 +229,12 @@ CitationItem.prototype.toJSON = function () {
return result;
};
/**
* @param {number} index
* @returns
*/
CitationItem.prototype.toFlatJSON = function (index) {
/** @type {OldCitationItem} */
var oldItem = {
id: this.id,
index: index,
@ -221,11 +246,17 @@ CitationItem.prototype.toFlatJSON = function (index) {
let itemDataObject = this._itemData.toJSON();
Object.assign(oldItem, itemDataObject);
if (this._itemData.getCustomProperty("userID") !== null) {
oldItem.userID = this._itemData.getCustomProperty("userID");
if (
typeof this._itemData.getCustomProperty("userID") !== "undefined" &&
this._itemData.getCustomProperty("userID") !== null
) {
oldItem.userID = String(this._itemData.getCustomProperty("userID"));
}
if (this._itemData.getCustomProperty("groupID") !== null) {
oldItem.groupID = this._itemData.getCustomProperty("groupID");
if (
typeof this._itemData.getCustomProperty("groupID") !== "undefined" &&
this._itemData.getCustomProperty("groupID") !== null
) {
oldItem.groupID = String(this._itemData.getCustomProperty("groupID"));
}
return oldItem;

View File

@ -1,7 +1,10 @@
// @ts-check
/// <reference path="./citation-item.js" />
/**
* @typedef {Object} SuppressAuthor
* @property {string} id
* @property {boolean} "suppress-author"
* @property {string|number} id
* @property {boolean} `suppress-author`
*/
/**
@ -40,6 +43,7 @@ function CSLCitation(itemsStartIndex, citationID) {
/** @type {string} */
this.citationID = citationID;
this._itemsStartIndex = itemsStartIndex;
/** @type {Array<CitationItem>} */
this._citationItems = new Array();
this._properties = new Object();
@ -48,7 +52,7 @@ function CSLCitation(itemsStartIndex, citationID) {
}
/**
* @param {*} citationObject
* @param {any} citationObject
* @returns
*/
CSLCitation.prototype.fillFromObject = function (citationObject) {
@ -69,7 +73,12 @@ CSLCitation.prototype.fillFromObject = function (citationObject) {
return this._fillFromCslJson(citationObject);
};
/**
* @param {any} citationObject
* @returns
*/
CSLCitation.prototype._fillFromCitationObject = function (citationObject) {
const self = this;
if (Object.hasOwnProperty.call(citationObject, "schema")) {
// this._setSchema(citationObject.schema);
}
@ -86,11 +95,13 @@ CSLCitation.prototype._fillFromCitationObject = function (citationObject) {
return item.id;
});
citationObject.citationItems.forEach(function (item) {
citationObject.citationItems.forEach(function (
/** @type {CitationItem} */ item
) {
let id = item.id;
let citationItem;
if (existingIds.indexOf(id) >= 0) {
citationItem = this._citationItems[existingIds.indexOf(id)];
citationItem = self._citationItems[existingIds.indexOf(id)];
} else {
citationItem = new CitationItem(id);
existingIds.push(id);
@ -98,21 +109,23 @@ CSLCitation.prototype._fillFromCitationObject = function (citationObject) {
if (typeof id === "number") {
// Word 365 or wps
id = this._extractIdFromWord365Citation(item);
id = self._extractIdFromWord365Citation(item);
}
citationItem.fillFromObject(item);
this._addCitationItem(citationItem);
}, this);
self._addCitationItem(citationItem);
},
this);
return existingIds.length;
};
/**
* @param {{citationObject: Array<{id: string, index: number, "suppress-author": boolean, title: string, type: string, userID: string, groupID: string}>}} citationObject
* @param {{citationItems: OldCitationItem[]}} citationObject
* @returns
*/
CSLCitation.prototype._fillFromFlatCitationObject = function (citationObject) {
const self = this;
if (citationObject.citationItems.length === 0) {
console.error("CSLCitation.citationItems: citationItems is empty");
return 0;
@ -123,14 +136,14 @@ CSLCitation.prototype._fillFromFlatCitationObject = function (citationObject) {
}
citationObject.citationItems.forEach(function (itemObject) {
this._fillFromCslJson(itemObject);
self._fillFromCslJson(itemObject);
}, this);
return 1;
};
/**
* @param {Object} citationObject
* @param {any} itemObject
* @returns
*/
CSLCitation.prototype._fillFromCslJson = function (itemObject) {
@ -155,7 +168,7 @@ CSLCitation.prototype._fillFromCslJson = function (itemObject) {
};
/**
* @param {{key: string, version: number, library: Object, links: Object, meta: Object, data: CitationJsonData}} citationObject
* @param {{key: string, version: number, library: Object, links: Object, meta: Object, data: CitationJsonData}} itemObject
* @returns
*/
CSLCitation.prototype._fillFromJson = function (itemObject) {
@ -195,6 +208,10 @@ CSLCitation.prototype.getSuppressAuthors = function () {
}, this);
};
/**
* @param {string} key
* @returns
*/
CSLCitation.prototype.getProperty = function (key) {
let items = this._citationItems;
for (var i = 0; i < items.length; i++) {
@ -207,6 +224,10 @@ CSLCitation.prototype.getProperty = function (key) {
return null;
};
/**
* @param {CitationItem} item
* @returns
*/
CSLCitation.prototype._addCitationItem = function (item) {
const existingIds = this._citationItems.map(function (item) {
return item.id;
@ -219,17 +240,26 @@ CSLCitation.prototype._addCitationItem = function (item) {
return this;
};
/**
* @param {object} properties
* @returns
*/
CSLCitation.prototype._setProperties = function (properties) {
this._properties = properties;
return this;
};
/**
* @param {string} schema
* @returns
*/
CSLCitation.prototype._setSchema = function (schema) {
this._schema = schema;
return this;
};
/**
* @param {any} item
* @returns {string}
*/
CSLCitation.prototype._extractIdFromWord365Citation = function (item) {
@ -265,9 +295,10 @@ CSLCitation.prototype.validate = function () {
};
CSLCitation.prototype.toJSON = function () {
var result = {
var result = /** @type {any} */ ({
citationID: this.citationID,
};
schema: this._schema,
});
if (this._properties && Object.keys(this._properties).length > 0) {
result.properties = this._properties;
@ -275,11 +306,9 @@ CSLCitation.prototype.toJSON = function () {
if (this._citationItems && this._citationItems.length > 0) {
result.citationItems = this._citationItems.map(function (item) {
return item.toJSON ? item.toJSON() : item;
return item.toJSON();
});
}
result.schema = this._schema;
return result;
};

View File

@ -1,4 +1,6 @@
// @ts-check
/// <reference path="./storage.js" />
/// <reference path="./style-parser.js" />
function CslStylesManager() {
this._isOnlineAvailable = false;

View File

@ -0,0 +1,127 @@
// @ts-check
/**
* @typedef {Object} AscSimpleRequestParams
* @property {string} url
* @property {"GET"|"POST"} method
* @property {object} headers
* @property {string} [body]
* @property {function(AscSimpleResponse, string): void} complete
* @property {function(AscSimpleResponse, string, Error): void} error
*/
/**
* @typedef {Object} AscSimpleRequest
* @property {function(AscSimpleRequestParams): void} createRequest
*/
/**
* @typedef {Object} AscSimpleResponse
* @property {number} responseStatus
* @property {string} responseText
* @property {string} [message]
* @property {string} status
* @property {number} statusCode
*/
/** @type {AscSimpleRequest} */
var AscSimpleRequest = window.AscSimpleRequest;
/** ------------------------------------------------ */
/**
* @typedef {Object} SupSubPositions
* @property {'sup'|'sub'} type
* @property {number} start
* @property {number} end
* @property {string} content
* @property {string} originalMatch
*/
/**
* @typedef {Object} AscPluginTheme
* @property {string} `text-normal`
* @property {string} `background-normal`
* @property {string} `highlight-button-hover`
* @property {string} `highlight-button-pressed`
* @property {string} `border-regular-control`
* @property {string} `border-toolbar`
* @property {string} `border-divider`
* @property {string} `background-toolbar`
* @property {string} RulerLight
* @property {string} Color - text color
* @property {string} type - light/dark
*/
/**
* @callback ExecuteCommandCallback
* @param {string} command
* @param {any} [value]
* @param {function} [callback]
* @returns {void}
*/
/**
* @callback CallCommandCallback
* @param {function} command
* @param {boolean} [isClose]
* @param {boolean} [isCalc]
* @param {function} [callback]
* @returns {void}
*/
/**
* @typedef {Object} AscPlugin
* @property {function(string, Array<any>|null, function(any): void): void} executeMethod
* @property {ExecuteCommandCallback} executeCommand
* @property {CallCommandCallback} callCommand
* @property {function(): void} init
* @property {object} info
* @property {function(string): void} sendToPlugin
* @property {function} onTranslate
* @property {function(string, function): void} attachEvent
* @property {string} onThemeChanged
* @property {function(string): void} onThemeChangedBase
* @property {AscPluginTheme} theme
* @property {function(string): string} tr
*/
/**
* @typedef {Object} Asc
* @property {AscPlugin} plugin
* @property {{positions: Array<SupSubPositions>}} scope
*/
/** @type {Asc} */
var Asc = window.Asc;
/** ------------------------------------------------ */
/**
* @typedef {Object} Api
* @property {function(): any} GetDocument
*/
/** @type {Api} */
var Api = window.Api;
/** ------------------------------------------------ */
/**
* @typedef {Object} FetchResponse
* @property {function(): Promise<ArrayBuffer>} arrayBuffer
* @property {function(): Promise<Blob>} blob
* @property {function(): Promise<Object>} json
* @property {function(): Promise<string>} text
* @property {boolean} ok
* @property {number} status
* @property {string} statusText
* @property {Headers} headers
* @property {string} type
* @property {string} url
* @property {boolean} redirected
*/
/**
* @typedef {Promise<FetchResponse>} FetchPromise
*/

View File

@ -1,3 +1,8 @@
// @ts-check
/// <reference path="./types-global.js" />
/// <reference path="./zotero.js" />
/// <reference path="./zotero-environment.js" />
/**
* @typedef {Object} AvailableApis
* @property {boolean} desktop
@ -14,7 +19,8 @@ var ZoteroApiChecker = {
_online: false,
_hasKey: false,
_timeout: 1000, // 1 second
_callback: function () {},
/** @type {function(AvailableApis): void} */
_callback: function (e) {},
_desktopVersion: (function () {
if (
window.navigator &&
@ -24,15 +30,17 @@ var ZoteroApiChecker = {
)
return false;
if (window.location && window.location.protocol == "file:") return true;
if (
window.document &&
window.document.currentScript &&
0 == window.document.currentScript.src.indexOf("file:///")
)
return true;
const src = window.document.currentScript
? window.document.currentScript.getAttribute("src")
: "";
if (src && 0 == src.indexOf("file:///")) return true;
return false;
})(),
/**
* @param {ZoteroSdk} sdk
* @returns
*/
runApisChecker: function (sdk) {
const self = this;
@ -55,15 +63,21 @@ var ZoteroApiChecker = {
attemptCheck();
return {
subscribe: function (callbackFn) {
subscribe: function (
/** @type {function(AvailableApis): void} */ callbackFn
) {
self._callback = callbackFn;
},
unsubscribe: function () {
done = true;
callback = function () {};
self._done = true;
self._callback = function () {};
},
};
},
/**
* @param {ZoteroSdk} sdk
* @returns
*/
checkStatus: function (sdk) {
return this._checkApiAvailable(sdk);
},
@ -77,7 +91,10 @@ var ZoteroApiChecker = {
desktopVersion: this._desktopVersion,
});
},
/**
* @param {ZoteroSdk} sdk
* @returns
*/
_checkApiAvailable: function (sdk) {
const self = this;
return new Promise(function (resolve) {
@ -115,6 +132,10 @@ var ZoteroApiChecker = {
});
});
},
/**
* @param {string} url
* @returns
*/
_sendDesktopRequest: function (url) {
const self = this;
return new Promise(function (resolve, reject) {
@ -132,7 +153,8 @@ var ZoteroApiChecker = {
"Zotero-API-Version": "3",
"User-Agent": "AscDesktopEditor",
},
complete: function (e) {
complete: function (/** @type {AscSimpleResponse} */ e) {
console.warn(e);
let hasPermission = false;
let isZoteroRunning = false;
if (e.responseStatus == 403) {
@ -147,7 +169,7 @@ var ZoteroApiChecker = {
isZoteroRunning: isZoteroRunning,
});
},
error: function (e) {
error: function (/** @type {AscSimpleResponse} */ e) {
if (e.statusCode == -102) e.statusCode = 404;
reject(e);
},

View File

@ -16,315 +16,509 @@
*
*/
// @ts-check
/// <reference path="./types-global.js" />
/// <reference path="./zotero-environment.js" />
/**
* @typedef {Object} ZoteroGroupInfo
* @property {number} id
* @property {number} version
* @property {{alternate: {href: string, type: string}, self: {href: string, type: string}}} meta
* @property {{created: string, lastModified: string, numItems: number}} links
* @property {{name: string, description: string, id: number, owner: number, type: string}} data
*/
/**
* @typedef {Object} UserGroupInfo
* @property {number} id
* @property {string} name
*/
const ZoteroSdk = function () {
var apiKey;
var userId = 0;
var userGroups = [];
var isOnlineAvailable = true;
this._apiKey = null;
this._userId = 0;
/** @type {Array<UserGroupInfo>}} */
this._userGroups = [];
this._isOnlineAvailable = true;
};
function getRequestWithOfflineSupport(url) {
if (isOnlineAvailable) {
return getRequest(url);
} else {
return getDesktopRequest(url.href);
}
}
// Constants
ZoteroSdk.prototype.ZOTERO_API_VERSION = "3";
ZoteroSdk.prototype.USER_AGENT = "AscDesktopEditor";
ZoteroSdk.prototype.DEFAULT_FORMAT = "csljson";
ZoteroSdk.prototype.STORAGE_KEYS = {
USER_ID: "zoteroUserId",
API_KEY: "zoteroApiKey",
};
ZoteroSdk.prototype.API_PATHS = {
USERS: "users",
GROUPS: "groups",
ITEMS: "items",
KEYS: "keys",
};
/**
* Make a GET request to the local Zotero API.
* @param {string} url - URL of the request.
* @returns {Promise<{responseStatus: number, responseText: string, status: string, statusCode: number}>}
*/
function getDesktopRequest(url) {
return new Promise(function (resolve, reject) {
window.AscSimpleRequest.createRequest({
url: url,
method: "GET",
headers: {
"Zotero-API-Version": "3",
"User-Agent": "AscDesktopEditor",
},
complete: function(e) {
resolve(e);
},
error: function(e) {
if ( e.statusCode == -102 ) {
e.statusCode = 404;
e.message = "Connection to Zotero failed. Make sure Zotero is running";
}
reject(e);
}
});
});
}
/**
* Get appropriate base URL based on online/offline mode
*/
ZoteroSdk.prototype._getBaseUrl = function () {
return this._isOnlineAvailable
? zoteroEnvironment.restApiUrl
: zoteroEnvironment.desktopApiUrl;
};
/**
* Make a GET request to the online Zotero API.
* @param {string} url - URL of the request.
* @returns {Promise<{body: ReadableStream, bodyUsed: boolean, headers: Headers, ok: boolean, redirected: boolean, status: string, statusText: string, type: string, url: string}>}
*/
function getRequest(url) {
return new Promise(function (resolve, reject) {
var headers = {
"Zotero-API-Version": "3"
};
if (apiKey) headers["Zotero-API-Key"] = apiKey;
fetch(url, {
headers: headers
}).then(function (res) {
if (!res.ok) {
reject(new Error(res.status + " " + res.statusText));
return;
};
resolve(res);
}).catch(function (err) {
if (typeof err === "object") {
console.error(err.message);
err.message = "Connection to Zotero failed";
}
reject(err);
});
});
}
/**
* Get locales URL based on online/offline mode
*/
ZoteroSdk.prototype._getLocalesUrl = function () {
return this._isOnlineAvailable
? zoteroEnvironment.localesUrl
: zoteroEnvironment.localesPath;
};
function buildGetRequest(path, query) {
var url = new URL(path, zoteroEnvironment.restApiUrl);
if (!isOnlineAvailable) {
url = new URL(path, zoteroEnvironment.desktopApiUrl);
}
for (var key in query) url.searchParams.append(key, query[key]);
return getRequestWithOfflineSupport(url);
}
/**
* Retrieves items from the Zotero API.
* @param {string} [search] - Search query.
* @param {string[]} [itemsID] - IDs of items to retrieve.
* @param {string} [format]
* @returns {Promise<{body: ReadableStream, bodyUsed: boolean, headers: Headers, ok: boolean, redirected: boolean, status: string, statusText: string, type: string, url: string}>}
*/
function getItems(search, itemsID, format) {
return new Promise(function (resolve, reject) {
format = format || "csljson";
var props = {
format: format
};
if (search) {
props.q = search;
} else if (itemsID) {
props.itemKey = itemsID.join(',');
}
if (isOnlineAvailable) {
parseItemsResponse(buildGetRequest("users/" + userId + "/items", props), resolve, reject, userId);
} else {
parseDesktopItemsResponse(buildGetRequest("users/" + userId + "/items", props), resolve, reject, userId);
/**
* Make a GET request to the local Zotero API (offline mode)
* @param {string} url
* @returns {Promise<AscSimpleResponse>}
*/
ZoteroSdk.prototype._getDesktopRequest = function (url) {
var self = this;
return new Promise(function (resolve, reject) {
window.AscSimpleRequest.createRequest({
url: url,
method: "GET",
headers: {
"Zotero-API-Version": self.ZOTERO_API_VERSION,
"User-Agent": self.USER_AGENT,
},
complete: resolve,
error: function (/** @type {AscSimpleResponse} */ error) {
if (error.statusCode === -102) {
error.statusCode = 404;
error.message =
"Connection to Zotero failed. Make sure Zotero is running";
}
});
}
reject(error);
},
});
});
};
function getGroupItems(search, groupId, itemsID, format) {
return new Promise(function (resolve, reject) {
format = format || "csljson";
var props = {
format: format
};
if (search) {
props.q = search;
} else if (itemsID) {
props.itemKey = itemsID.join(',');
}
if (isOnlineAvailable) {
parseItemsResponse(buildGetRequest("groups/" + groupId + "/items", props), resolve, reject, groupId);
} else {
parseDesktopItemsResponse(buildGetRequest("groups/" + groupId + "/items", props), resolve, reject, groupId);
}
});
}
/**
* Make a GET request to the online Zotero API
* @param {URL} url
* @returns {Promise<FetchResponse>}
*/
ZoteroSdk.prototype._getOnlineRequest = function (url) {
var self = this;
var headers = {
"Zotero-API-Version": self.ZOTERO_API_VERSION,
"Zotero-API-Key": self._apiKey || "",
};
function getLocale(langTag) {
let url = zoteroEnvironment.localesPath;
if (isOnlineAvailable) {
url = zoteroEnvironment.localesUrl;
return fetch(url, { headers: headers })
.then(function (response) {
if (!response.ok) {
throw new Error(response.status + " " + response.statusText);
}
return fetch(url + "locales-" + langTag + ".xml")
.then(function (resp) { return resp.text(); });
}
return response;
})
.catch(function (error) {
console.error("Zotero API request failed:", error.message);
if (typeof error === "object") {
error.message = "Connection to Zotero failed";
}
throw error;
});
};
function getUserGroups() {
return new Promise(function (resolve, reject) {
if (userGroups.length > 0) {
resolve(userGroups);
} else {
buildGetRequest("users/" + userId + "/groups").then(function (res) {
if (isOnlineAvailable) {
if (!res.ok)
throw new Error(res.status + " " + res.statusText);
return res.json();
}
return JSON.parse(res.responseText);
}).then(function (res) {
res.forEach(function(el) {
userGroups.push({
id: el.id,
name: el.data.name
});
});
resolve(userGroups);
}).catch(function (err) {
reject(err);
/**
* Universal request handler with offline support
* @param {URL} url
* @returns {Promise<AscSimpleResponse | FetchResponse>}
*/
ZoteroSdk.prototype._getRequestWithOfflineSupport = function (url) {
return this._isOnlineAvailable
? this._getOnlineRequest(url)
: this._getDesktopRequest(url.href);
};
/**
* Build URL and make GET request
* @param {string} path
* @param {*} [queryParams]
* @returns {Promise<AscSimpleResponse | FetchResponse>}
*/
ZoteroSdk.prototype._buildGetRequest = function (path, queryParams) {
queryParams = queryParams || {};
var url = new URL(path, this._getBaseUrl());
Object.keys(queryParams).forEach(function (key) {
if (queryParams[key] !== undefined && queryParams[key] !== null) {
url.searchParams.append(key, queryParams[key]);
}
});
return this._getRequestWithOfflineSupport(url);
};
/**
* Parse link header for pagination
* @param {string} headerValue
* @returns {{[key: string]: string}}
*/
ZoteroSdk.prototype._parseLinkHeader = function (headerValue) {
/** @type {{[key: string]: string}} */
var links = {};
var linkHeaderRegex = /<(.*?)>; rel="(.*?)"/g;
if (!headerValue) return links;
var match;
while ((match = linkHeaderRegex.exec(headerValue.trim())) !== null) {
links[match[2]] = match[1];
}
return links;
};
/**
* Parse response for desktop (offline) mode
* @param {Promise<AscSimpleResponse>} promise
* @param {function(any): void} resolve
* @param {function(any): void} reject
* @param {number} id
* @returns {Promise<void>}
*/
ZoteroSdk.prototype._parseDesktopItemsResponse = function (
promise,
resolve,
reject,
id
) {
var self = this;
return promise
.then(function (response) {
return {
items: { items: JSON.parse(response.responseText) },
id: id,
};
})
.then(resolve)
.catch(reject);
};
/**
* Parse response for online mode with pagination support
* @param {Promise<FetchResponse>} promise
* @param {function(any): void} resolve
* @param {function(any): void} reject
* @param {number} id
* @returns {Promise<void>}
*/
ZoteroSdk.prototype._parseItemsResponse = function (
promise,
resolve,
reject,
id
) {
var self = this;
return promise
.then(function (response) {
return Promise.all([response.json(), response]);
})
.then(function (results) {
var json = results[0];
var response = results[1];
var links = self._parseLinkHeader(
response.headers.get("Link") || ""
);
/** @type {{items: any, id: number, next?: function(): Promise<void>}} */
var result = {
items: json,
id: id,
};
if (links.next) {
result.next = function () {
return new Promise(function (rs, rj) {
self._parseItemsResponse(
self._getOnlineRequest(new URL(links.next)),
rs,
rj,
id
);
});
}
});
}
function getUserId() {
return userId;
}
function format(ids, key, style, locale) {
return new Promise(function (resolve, reject) {
var request;
if (key) {
request = buildGetRequest("groups/" + key + "/items", {
format: "bib",
style: style,
locale: locale,
itemKey: ids.join(",")
})
} else {
request = buildGetRequest("users/" + userId + "/items", {
format: "bib",
style: style,
locale: locale,
itemKey: ids.join(",")
})
}
request.then(function (res) {
resolve(res.text());
}).catch(function (err) {
reject(err);
});
});
}
function setApiKey(key) {
return new Promise(function (resolve, reject) {
buildGetRequest("keys/" + key)
.then(function (res) {
if (!res.ok) throw new Error(res.status + " " + res.statusText);
return res.json();
}).then(function (res) {
saveSettings(res.userID, key);
resolve(true);
}).catch(function (err) {
reject(err);
});
});
}
function applySettings(id, key) {
userId = id;
apiKey = key;
}
function saveSettings(id, key) {
applySettings(id, key);
localStorage.setItem("zoteroUserId", id);
localStorage.setItem("zoteroApiKey", key);
}
function getSettings() {
var uid = localStorage.getItem("zoteroUserId");
var key = localStorage.getItem("zoteroApiKey");
var configured = !(!uid || !key);
if (configured) applySettings(uid, key);
return configured;
}
function clearSettings() {
localStorage.removeItem("zoteroUserId");
localStorage.removeItem("zoteroApiKey");
userGroups = [];
}
/**
* @param {Promise} promise - promise from items request
* @param {function} resolve - resolve function for returned promise
* @param {function} reject - reject function for returned promise
* @param {string} id - id of request
* @returns {Promise<{items: {items: Array<Object>}, id: string}}>} promise with items and optional next function
*/
function parseDesktopItemsResponse(promise, resolve, reject, id) {
promise.then(function (res) {
var obj = {
items: {items: JSON.parse(res.responseText)},
id: id
};
resolve(obj);
}).catch(function (err) {
reject(err);
});
}
/**
* @param {Promise} promise - promise from items request
* @param {function} resolve - resolve function for returned promise
* @param {function} reject - reject function for returned promise
* @param {string} id - id of request
* @returns {Promise<{items: {items: Array<Object>}, id: string}}>} promise with items and optional next function
*/
function parseItemsResponse(promise, resolve, reject, id) {
promise.then(function (res) {
return res.json().then(function (json) {
var links = parseLinkHeader(res.headers.get("Link"));
var obj = {
items: json,
id: id
};
if (links.next) {
obj.next = function () {
return new Promise(function (rs, rj) {
parseItemsResponse(getRequest(links.next), rs, rj, id);
});
}
}
resolve(obj);
});
}).catch(function (err) {
reject(err);
});
}
var linkHeaderRegex = /<(.*?)>; rel="(.*?)"/g;
function parseLinkHeader(headerValue) {
var links = {};
if (!headerValue) return links;
headerValue = headerValue.trim();
if (!headerValue) return links;
var match;
while ((match = linkHeaderRegex.exec(headerValue)) !== null) {
links[match[2]] = match[1];
}
return links;
resolve(result);
})
.catch(reject);
};
/**
* Universal items response parser
* @param {Promise<AscSimpleResponse | FetchResponse>} promise
* @param {function(any): void} resolve
* @param {function(any): void} reject
* @param {number} id
*/
ZoteroSdk.prototype._parseResponse = function (promise, resolve, reject, id) {
if (this._isOnlineAvailable) {
const fetchPromise = /** @type {Promise<FetchResponse>} */ (promise);
this._parseItemsResponse(fetchPromise, resolve, reject, id);
} else {
const ascSimplePromise = /** @type {Promise<AscSimpleResponse>} */ (
promise
);
this._parseDesktopItemsResponse(
/** @type {Promise<AscSimpleResponse>} */ ascSimplePromise,
resolve,
reject,
id
);
}
};
/**
* Get items from user library
* @param {string} search
* @param {string[]} itemsID
* @param {"csljson"|"json"} format
*/
ZoteroSdk.prototype.getItems = function (search, itemsID, format) {
var self = this;
format = format || self.DEFAULT_FORMAT;
return new Promise(function (resolve, reject) {
var queryParams =
/** @type {{format: string, q?: string, itemKey?: string}} */ ({
format: format,
});
if (search) {
queryParams.q = search;
} else if (itemsID) {
queryParams.itemKey = itemsID.join(",");
}
function setIsOnlineAvailable(isOnline) {
isOnlineAvailable = isOnline;
var path =
self.API_PATHS.USERS +
"/" +
self._userId +
"/" +
self.API_PATHS.ITEMS;
var request = self._buildGetRequest(path, queryParams);
self._parseResponse(request, resolve, reject, self._userId);
});
};
/**
* Get items from group library
* @param {string} search
* @param {number} groupId
* @param {string[]} itemsID
* @param {"csljson"|"json"} format
*
*/
ZoteroSdk.prototype.getGroupItems = function (
search,
groupId,
itemsID,
format
) {
var self = this;
format = format || self.DEFAULT_FORMAT;
return new Promise(function (resolve, reject) {
var queryParams =
/** @type {{format: string, q?: string, itemKey?: string}} */ ({
format: format,
});
if (search) {
queryParams.q = search;
} else if (itemsID) {
queryParams.itemKey = itemsID.join(",");
}
return {
getItems: getItems,
getGroupItems: getGroupItems,
getUserGroups: getUserGroups,
format: format,
hasSettings: getSettings,
clearSettings: clearSettings,
setApiKey: setApiKey,
getUserId: getUserId,
getLocale: getLocale,
setIsOnlineAvailable: setIsOnlineAvailable
var path =
self.API_PATHS.GROUPS + "/" + groupId + "/" + self.API_PATHS.ITEMS;
var request = self._buildGetRequest(path, queryParams);
self._parseResponse(request, resolve, reject, groupId);
});
};
/**
* Get user groups
* @returns {Promise<Array<UserGroupInfo>>}
*/
ZoteroSdk.prototype.getUserGroups = function () {
var self = this;
return new Promise(function (resolve, reject) {
if (self._userGroups.length > 0) {
resolve(self._userGroups);
return;
}
}
var path = self.API_PATHS.USERS + "/" + self._userId + "/groups";
self._buildGetRequest(path)
.then(function (
/** @type {FetchResponse | AscSimpleResponse} */ response
) {
if (self._isOnlineAvailable) {
var fetchResponse = /** @type {FetchResponse} */ (response);
if (!fetchResponse.ok) {
throw new Error(
fetchResponse.status +
" " +
fetchResponse.statusText
);
}
return fetchResponse.json();
}
var ascSimpleResponse = /** @type {AscSimpleResponse} */ (
response
);
return JSON.parse(ascSimpleResponse.responseText);
})
.then(function (groups) {
self._userGroups = groups.map(function (
/** @type {ZoteroGroupInfo} */ group
) {
return {
id: group.id,
name: group.data.name,
};
});
resolve(self._userGroups);
})
.catch(reject);
});
};
/**
* Format citations
*/
/*ZoteroSdk.prototype.format = function (ids, groupKey, style, locale) {
var queryParams = {
format: "bib",
style: style,
locale: locale,
itemKey: ids.join(","),
};
var path = groupKey
? this.API_PATHS.GROUPS + "/" + groupKey + "/" + this.API_PATHS.ITEMS
: this.API_PATHS.USERS +
"/" +
this._userId +
"/" +
this.API_PATHS.ITEMS;
return this._buildGetRequest(path, queryParams).then(function (response) {
return response.text();
});
};*/
/**
* Get locale data
* @param {string} langTag
*/
ZoteroSdk.prototype.getLocale = function (langTag) {
var url = this._getLocalesUrl() + "locales-" + langTag + ".xml";
return fetch(url).then(function (response) {
return response.text();
});
};
/**
* Set API key and validate it
* @param {string} key
*/
ZoteroSdk.prototype.setApiKey = function (key) {
var self = this;
var path = this.API_PATHS.KEYS + "/" + key;
return this._buildGetRequest(path)
.then(function (response) {
var fetchResponse = /** @type {FetchResponse} */ (response);
if (!fetchResponse.ok) {
throw new Error(
fetchResponse.status + " " + fetchResponse.statusText
);
}
return fetchResponse.json();
})
.then(function (/** @type {any} */ keyData) {
self._saveSettings(keyData.userID, key);
return true;
});
};
/**
* Apply settings to current session
* @param {number} userId
* @param {string} apiKey
*/
ZoteroSdk.prototype._applySettings = function (userId, apiKey) {
this._userId = userId;
this._apiKey = apiKey;
};
/**
* Save settings to localStorage
* @param {number} userId
* @param {string} apiKey
*/
ZoteroSdk.prototype._saveSettings = function (userId, apiKey) {
this._applySettings(userId, apiKey);
localStorage.setItem(this.STORAGE_KEYS.USER_ID, String(userId));
localStorage.setItem(this.STORAGE_KEYS.API_KEY, apiKey);
};
/**
* Load settings from localStorage
*/
ZoteroSdk.prototype.hasSettings = function () {
var userId = localStorage.getItem(this.STORAGE_KEYS.USER_ID);
var apiKey = localStorage.getItem(this.STORAGE_KEYS.API_KEY);
if (userId && apiKey) {
this._applySettings(Number(userId), apiKey);
return true;
}
return false;
};
/**
* Clear stored settings
*/
ZoteroSdk.prototype.clearSettings = function () {
localStorage.removeItem(this.STORAGE_KEYS.USER_ID);
localStorage.removeItem(this.STORAGE_KEYS.API_KEY);
this._userGroups = [];
this._userId = 0;
this._apiKey = null;
};
/**
* Get user ID
*/
ZoteroSdk.prototype.getUserId = function () {
return this._userId;
};
/**
* Set online availability
* @param {boolean} isOnline
*/
ZoteroSdk.prototype.setIsOnlineAvailable = function (isOnline) {
this._isOnlineAvailable = isOnline;
};