Files
onlyoffice.github.io/sdkjs-plugins/content/zotero/scripts/csl/styles/styles-manager.js
2025-11-26 18:19:19 +03:00

334 lines
9.7 KiB
JavaScript

// @ts-check
/// <reference path="./storage.js" />
/// <reference path="./style-parser.js" />
function CslStylesManager() {
this._isOnlineAvailable = false;
this._isDesktopAvailable = false;
this._customStylesStorage = new CslStylesStorage();
this._STYLES_JSON_URL = "https://www.zotero.org/styles-files/styles.json";
this._STYLES_JSON_LOCAL = "./resources/csl/styles.json";
this._STYLES_URL = "https://www.zotero.org/styles/";
this._STYLES_LOCAL = "./resources/csl/styles/";
this._lastStyleKey = "zoteroStyleId";
this._lastNotesStyleKey = "zoteroNotesStyleId";
this._lastFormatKey = "zoteroFormatId";
this._lastUsedStyleContainBibliographyKey = "zoteroContainBibliography";
this._defaultStyles = [
"american-medical-association",
"american-political-science-association",
"apa",
"american-sociological-association",
"chicago-author-date-17th-edition",
"harvard-cite-them-right-10th-edition",
"ieee",
"modern-language-association-8th-edition",
"nature",
];
/** @type {Object.<string, string>} */
this._cache = {};
}
/**
* @param {File} file - The file to be added.
* @returns {Promise<StyleInfo>} - A promise that resolves when the file is added.
* @throws {Error} - If the file is not a .csl or .xml file, or if the file size is greater than 1 MB.
*/
CslStylesManager.prototype.addCustomStyle = function (file) {
var self = this;
return new Promise(function (resolve, reject) {
let fileName = file.name.toLowerCase();
if (fileName.slice(-4) === ".csl" || fileName.slice(-4) === ".xml") {
fileName = fileName.substring(0, fileName.length - 4).trim();
} else {
reject("Please select a .csl or .xml file.");
}
if (file.size > 1024 * 1024) {
reject("Maximum file size is 1 MB.");
}
resolve(fileName);
}).then(function (fileName) {
return self._readCSLFile(file).then(function (content) {
if (self._defaultStyles.indexOf(fileName) === -1) {
self._defaultStyles.push(fileName);
}
return self._customStylesStorage.setStyle(fileName, content);
});
});
};
/**
* @returns {StyleFormat}
*/
CslStylesManager.prototype.getLastUsedFormat = function () {
let lastUsedFormat = localStorage.getItem(this._lastFormatKey);
switch (lastUsedFormat) {
case "note":
case "numeric":
case "author":
case "author-date":
case "label":
return lastUsedFormat;
}
return "numeric";
};
/**
* @returns {"endnotes" | "footnotes"}
*/
CslStylesManager.prototype.getLastUsedNotesStyle = function () {
let lastUsedNotesStyle = localStorage.getItem(this._lastNotesStyleKey);
if (
lastUsedNotesStyle === "footnotes" ||
lastUsedNotesStyle === "endnotes"
) {
return lastUsedNotesStyle;
}
return "footnotes";
};
/**
* @returns {string} - style id
*/
CslStylesManager.prototype.getLastUsedStyleId = function () {
let lastUsedStyle = localStorage.getItem(this._lastStyleKey);
if (lastUsedStyle) {
return lastUsedStyle;
}
return "";
};
/**
* @param {string} styleName
* @returns {Promise<string | null>} - csl file content
*/
CslStylesManager.prototype.getStyle = function (styleName) {
const self = this;
return Promise.resolve(styleName)
.then(function (styleName) {
if (self._cache[styleName]) {
return self._cache[styleName];
}
const customStyleNames = self._customStylesStorage.getStyleNames();
if (customStyleNames.indexOf(styleName) !== -1) {
return self._customStylesStorage.getStyle(styleName);
}
let url = self._STYLES_LOCAL + styleName + ".csl";
if (self._isOnlineAvailable) {
url = self._STYLES_URL + styleName;
}
return fetch(url).then(function (resp) {
return resp.text();
});
})
.then(function (content) {
if (
content &&
!self._isValidCSL(content) &&
self._isOnlineAvailable
) {
/** @type {StyleInfo} */
let styleInfo = CslStylesParser.getStyleInfo(
styleName,
content
);
if (styleInfo && styleInfo.dependent > 0 && styleInfo.parent) {
return fetch(styleInfo.parent).then(function (resp) {
return resp.text();
});
}
}
return content;
})
.then(function (content) {
if (content) {
self._saveLastUsedStyle(styleName, content);
}
return content;
});
};
/**
* @returns {Promise<Array<StyleInfo>>}
*/
CslStylesManager.prototype.getStylesInfo = function () {
const self = this;
return Promise.all([
this._getStylesJson(),
this._customStylesStorage.getStylesInfo(),
]).then(function (styles) {
var lastStyle = self.getLastUsedStyleId();
/** @type {Array<StyleInfo>} */
var resultStyles = [];
var resultStyleNames = self._customStylesStorage.getStyleNames();
/** @type {Array<StyleInfo>} */
var loadedStyles = styles[0];
/** @type {Array<StyleInfo>} */
var customStyles = styles[1];
if (self._isDesktopAvailable && !self._isOnlineAvailable) {
loadedStyles = loadedStyles.filter(function (style) {
return (
self._defaultStyles.indexOf(style.name) >= 0 ||
style.name == lastStyle
);
});
}
customStyles.forEach(function (style) {
if (lastStyle === style.name) {
resultStyles.unshift(style);
} else {
resultStyles.push(style);
}
if (self._defaultStyles.indexOf(style.name) === -1) {
self._defaultStyles.push(style.name);
}
});
loadedStyles.forEach(function (style) {
if (resultStyleNames.indexOf(style.name) !== -1) {
// already added
return;
}
if (lastStyle === style.name) {
resultStyles.unshift(style);
} else {
resultStyles.push(style);
}
});
return resultStyles;
});
};
CslStylesManager.prototype._getStylesJson = function () {
let url = this._STYLES_JSON_LOCAL;
if (this._isOnlineAvailable) {
url = this._STYLES_JSON_URL;
}
return fetch(url).then(function (resp) {
return resp.json();
});
};
/**
* @param {string} id
* @returns {string | null}
*/
CslStylesManager.prototype.cached = function (id) {
if (Object.hasOwnProperty.call(this._cache, id)) {
return this._cache[id];
}
return null;
};
/**
* @returns {boolean}
*/
CslStylesManager.prototype.isLastUsedStyleContainBibliography = function () {
let containBibliography = localStorage.getItem(
this._lastUsedStyleContainBibliographyKey
);
return containBibliography !== "false";
};
/**
* @param {string} styleName
* @returns {boolean}
*/
CslStylesManager.prototype.isStyleDefault = function (styleName) {
return this._defaultStyles.indexOf(styleName) >= 0;
};
/**
* @param {string} content
* @returns {boolean}
*/
CslStylesManager.prototype._isValidCSL = function (content) {
return (
content.indexOf("<?xml") > -1 &&
content.indexOf("<style") > -1 &&
content.indexOf("<macro") > -1 &&
content.indexOf("citation") > -1
);
};
/**
* Reads a file and returns its content as a string.
* Rejects the promise if the file is not a valid CSL file.
* @param {File} file - The file to be read.
* @returns {Promise<string>} - A promise that resolves with the file content if the file is a valid CSL file.
* @throws {Error} - If the file is not a valid CSL file.
*/
CslStylesManager.prototype._readCSLFile = function (file) {
const self = this;
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (e) {
var fileContent = e.target ? String(e.target.result) : "";
if (!self._isValidCSL(fileContent)) {
reject("The file is not a valid CSL file");
return;
}
resolve(fileContent);
};
reader.onerror = function () {
reject("Failed to read file");
};
reader.readAsText(file);
});
};
/**
* @param {string} content
* @param {string} id
*/
CslStylesManager.prototype._saveLastUsedStyle = function (id, content) {
this._cache[id] = content;
localStorage.setItem(this._lastStyleKey, id);
const currentStyleFormat = CslStylesParser.getCitationFormat(content);
localStorage.setItem(this._lastFormatKey, currentStyleFormat);
const containBibliography =
CslStylesParser.isStyleContainBibliography(content);
localStorage.setItem(
this._lastUsedStyleContainBibliographyKey,
containBibliography.toString()
);
};
/**
* @param {"footnotes" | "endnotes"} notesStyle
*/
CslStylesManager.prototype.saveLastUsedNotesStyle = function (notesStyle) {
localStorage.setItem(this._lastNotesStyleKey, notesStyle);
};
/**
* @param {boolean} isApiAvailable
*/
CslStylesManager.prototype.setDesktopApiAvailable = function (isApiAvailable) {
this._isDesktopAvailable = isApiAvailable;
};
/**
* @param {boolean} isApiAvailable
*/
CslStylesManager.prototype.setRestApiAvailable = function (isApiAvailable) {
this._isOnlineAvailable = isApiAvailable;
};