/* global define */ "use strict"; define(["jquery", "underscore", "backbone", "collections/Citations"], function ( $, _, Backbone, Citations ) { /** * @class CitationModel * @classdesc A Citation Model represents a single Citation Object returned by * the metrics-service. A citation model can alternatively be populated with a * SolrResultsModel or a DataONEObjectModel, or an extension of either of * those models. A Citation Model can represent a citation to a local * MetacatUI object, or an external document or publication. * @classcategory Models * @extends Backbone.Model * @see https://app.swaggerhub.com/apis/nenuji/data-metrics */ var Citation = Backbone.Model.extend( /** @lends CitationModel.prototype */ { /** * The name of this type of model * @type {string} */ type: "CitationModel", /** * The default Citation fields * @name CitationModel#defaults * @type {Object} * @property {string} origin - text of authors who published the source * dataset / document / article * @property {string[]} originArray - array of authors who published the * source dataset / document / article. Same as origin, but split on commas * and trimmed. * @property {string} title - Title of the source dataset / document / * article * @property {number} year_of_publishing - Year in which the source dataset * / document / article was published * @property {string} source_url - URL to the source dataset / document / * article. This is usually an external publication that cites one or more * DataONE datasets. * @property {string} source_id - Unique identifier for the source dataset / * document / article that cited the target dataset. This is usually an * external publication that cites one or more DataONE datasets. * @property {string} target_id - Unique identifier to the target DATAONE * dataset. This is the dataset that was cited by the "source" document. * @property {string} publisher - Publisher for the source dataset / * document / article * @property {string} journal - The journal where the the document was * published * @property {number|string} volume - The volume of the journal where the * document was published * @property {number} page - The page of the journal where the document was * published * @property {Citations} citationMetadata - When this Citation Model refers * to an external document, citationMetadata is a collection of DataONE * datasets that the external document cites. This info is retrieved by the * metrics service, then parsed and stored as a collection of Citation * Models. This attribute is used in the Portals view, for example, where we * display a list of external publications that cite the portal data. In * this case, each publication's citationMetadata is the list of local * MetacatUI data packages cited in the publication. * @property {Backbone.Model} sourceModel - The model to use to populate * this citation model. This can be a SolrResultsModel, a * DataONEObjectModel, or an extension of either of those models. Do not set * this attribute directly. Instead, use the setSourceModel() method. * @property {string} pid - The pid or unique identifier of the object being * cited. * @property {string} seriesId - The seriesId of the object being cited * @property {string} view_url - For citations that are in the local * MetacatUI repository, this is the URL to the metadata view page for the * object being cited. * @property {string} pid_url - If the pid is a DOI, then this is the URL to * the DOI landing page for the object being cited. This will automatically * be set when the pid attribute is set. * @property {string} seriesId_url - If the seriesId is a DOI, then this is * the URL to the DOI landing page for the object being cited. This will * automatically be set when the seriesId attribute is set. */ defaults: function () { return { origin: null, originArray: [], title: null, year_of_publishing: null, source_url: null, source_id: null, target_id: null, publisher: null, journal: null, volume: null, page: null, citationMetadata: null, sourceModel: null, pid: null, seriesId: null, view_url: null, pid_url: null, seriesId_url: null, }; }, /** * Get the attribute getters for this model. "Attribute getters" are * functions that return the value of an attribute for this Citation Model * given a source model. The source model can be a SolrResultsModel, a * DataONEObjectModel, or an extension of either of those models. * @returns {Object} - An object that maps the name of the CitationModel * attribute to the function that returns the value for that attribute. */ attrGetters: function () { return { year_of_publishing: this.getYearFromSourceModel, title: this.getTitleFromSourceModel, journal: this.getJournalFromSourceModel, pid: this.getPidFromSourceModel, seriesId: this.getSeriesIdFromSourceModel, originArray: this.getOriginArrayFromSourceModel, view_url: this.getViewUrlFromSourceModel, }; }, /** * Override the default Backbone.Model.parse() method to convert the * citationMetadata object into a nested collection of CitationModels. * @param {Object} response - The response from the metrics-service API * @param {Object} options - Options to pass to the parse() method. * @returns {Object} The parsed response */ parse: function (response) { try { // strings that need formatting when coming from the metrics-service: const toFormat = ["journal", "page", "volume", "publisher"]; toFormat.forEach((attr) => { response[attr] = this.formatMetricsServiceString(response[attr]); }, this); // Turn the author strings into CSL JSON objects if (response.origin) { const or = this.originToArray(response.origin); } let sID = response.source_id; if (this.isDOI(sID)) { if (sID.startsWith("http")) { sID = this.URLtoDOI(sID); } if (!sID.startsWith("doi:")) { sID = "doi:" + sID; } response.source_id = sID; } // Format the citation metadata = DataONE datasets cited by this // citation (external document) const cm = response.citationMetadata; if (cm) { if (cm && !(cm instanceof Citations)) { const citationMetadata = Object.entries(cm).map(([pid, data]) => { // Convert format from {id: {data}} to {data, id} const item = { ...data, pid }; // Origin returned by metrics-service is actually an array, not a // string item.originArray = item.origin; delete item.origin; // Format the authors in the origin array item.originArray = item.originArray.map((author) => this.formatAuthor(author) ); // Get the publish year const date = item.datePublished || item.dateUpdated || item.dateModified; item.year_of_publishing = date ? new Date(date).getUTCFullYear() : null; // Because the citation metadata is always referencing an object // in the local MetacatUI repository, we assume that the view_url // exists for the given PID. // DOIs from the metrics service are not prefixed with "doi:" if (this.isDOI(pid) && !pid.startsWith("doi:")) { pid = "doi:" + pid; } item.pid = pid; item.view_url = MetacatUI.root + "/view/" + encodeURIComponent(pid); return item; }); response.citationMetadata = new Citations(citationMetadata); } } return response; } catch (error) { console.log( "Error parsing a CitationModel. Returning response as-is.", error ); return response; } }, /** * Override the default Backbone.Model.set() method to format the title, * page, and volume attributes before setting them, and ensure that * attributes that are different formats of the same value are in sync, * including: origin and originArray; pid and pid_url; seriesId and * seriesId_url. This method will prevent the sourceModel attribute from * being set here. * * @param {string|Object} key - The attribute name to set, or an object of * attribute names and values to set. * @param {string|number|Object} val - The value to set the attribute to. * @param {Object} options - Options to pass to the set() method. * @see https://backbonejs.org/#Model-set * @since 2.23.0 */ set: function (key, val, options) { try { if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. let attrs = {}; if (typeof key === "object") { attrs = key; options = val; } else { (attrs = {})[key] = val; } // Don't allow setting the sourceModel attribute here. // TODO: how to handle this better? delete attrs.sourceModel; // If the title attribute is being set, then format it first if (Object.keys(attrs).includes("title")) { attrs.title = this.formatTitle(attrs.title); } // Ensure origin and originArray contain the same data, with preference // given to originArray. If originArray has content, then overwrite // origin with the a string created from originArray. If *only* origin // has content, then overwrite originArray with an array created from // origin. if ( Object.keys(attrs).includes("originArray") || Object.keys(attrs).includes("origin") ) { const strToArray = this.originToArray(attrs.origin); const arrayToStr = this.originArrayToString(attrs.originArray); if (!!arrayToStr) { attrs.origin = arrayToStr; } else { attrs.originArray = strToArray; } } // Ensure that the pid_url and seriesId_url attributes match the pid and // seriesId attributes being set, and vice versa. If they don't match, // then set them to the correct values. Prefer the content of the IDs // over the URLs. const idToUrlAttrs = [ { id: "pid", url: "pid_url" }, { id: "seriesId", url: "seriesId_url" }, ]; idToUrlAttrs.forEach(({ id, url }) => { if ( Object.keys(attrs).includes(id) || Object.keys(attrs).includes(url) ) { if (!!attrs[id] && !attrs[url]) { attrs[url] = this.DOItoURL(attrs[id]); } else if (!!attrs[url] && !attrs[id]) { attrs[id] = this.URLtoDOI(attrs[url]); } else if (!!attrs[id] && !!attrs[url]) { attrs[url] = this.DOItoURL(attrs[id]); } } }); // If citationMetadata is being changed, remove old listeners and add // new ones if (Object.keys(attrs).includes("citationMetadata")) { if (this.citationMetadata) { this.stopListening(this.citationMetadata); } if (attrs.citationMetadata && attrs.citationMetadata.length) { this.listenTo( attrs.citationMetadata, "update", this.trigger.bind(this, "change") ); } } // Set modified attributes in the regular Backbone way Backbone.Model.prototype.set.call(this, attrs, options); } catch (error) { console.log( "Error in custom set() method on CitationModel. Will attempt to set" + " using with Backbone set(). Attributes and error stack trace:", { key, val, options }, error ); Backbone.Model.prototype.set.call(this, key, val, options); } }, /** * Sets the sourceModel attribute and calls the method to populate the * Citation Model with the sourceModel attributes. Also removes any existing * listeners on the previous sourceModel and readds them to the new * sourceModel. Use this method to set or change the sourceModel attribute. * @param {Backbone.Model} newSourceModel - The new sourceModel * @since 2.23.0 */ setSourceModel(newSourceModel) { try { newSourceModel = newSourceModel && newSourceModel.type == "Package" ? newSourceModel.getMetadata() : newSourceModel; // Remove any existing listeners on the previous sourceModel const currentSourceModel = this.get("sourceModel"); if (currentSourceModel) { this.stopListening(currentSourceModel); const creators = currentSourceModel.get("creator") || []; creators.forEach((creator) => this.stopListening(creator), this); } // Add listeners to the new sourceModel if (newSourceModel) { const creatorEvents = "change:individualName change:organizationName change:positionName"; const sourceModelEvents = "change:origin change:creator change:pubDate change:dateUploaded change:title change:seriesId change:id change:datasource"; const creators = newSourceModel.get("creator") || []; this.listenTo(newSourceModel, sourceModelEvents, () => { this.setSourceModel(newSourceModel); }); creators.forEach((creator) => { this.listenTo(creator, creatorEvents, () => { this.setSourceModel(newSourceModel); }); }); } Backbone.Model.prototype.set.call( this, "sourceModel", newSourceModel ); this.populateFromModel(newSourceModel); } catch (error) { console.log("Error in CitationModel.setSourceModel(). Error:", error); } }, /** * Do not call this method directly. Instead, call setSourceModel(), which * will update listeners and then call this method. This method will * populate this citation model's attributes from another model, such as a * SolrResult model or a DataONEObject model. This will reset and overwrite * any existing attributes on this model. * @param {Backbone.Model} model - The model to populate from, accepts * SolrResult or a model that is a DataONEObject or an extended * DataONEObject. If no model is passed, then the model will be reset to the * default attributes. * @since 2.23.0 */ populateFromModel: function (newSourceModel) { try { // Populate this model from the new sourceModel const newAttrs = this.defaults(); if (!newSourceModel) { this.set(newAttrs); return; } const attrGetters = this.attrGetters(); Object.entries(attrGetters).forEach(([attrName, getter]) => { const attrValue = getter.call(this, newSourceModel); if (attrValue) newAttrs[attrName] = attrValue; }); this.set(newAttrs); } catch (error) { console.log( "Error populating a CitationModel from the model: ", newSourceModel, " Error: ", error ); } }, /** * Get the year from the sourceModel. First look for pubDate, then * dateUploaded (both in SolrResult & ScienceMetadata/EML models). Lastly * check datePublished (found in ScienceMetadata/EML models only.) * @param {Backbone.Model} sourceModel - The model to get the year from * @returns {Number} - The year * @since 2.23.0 */ getYearFromSourceModel(sourceModel) { try { const year = this.yearFromDate(sourceModel.get("pubDate")) || this.yearFromDate(sourceModel.get("dateUploaded")) || this.yearFromDate(sourceModel.get("datePublished")); return year; } catch (error) { console.log( "Error getting year from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().year_of_publishing; } }, /** * Get the title from the sourceModel * @param {Backbone.Model} sourceModel - The model to get the title from * @returns {String} - The title * @since 2.23.0 */ getTitleFromSourceModel(sourceModel) { try { let title = sourceModel.get("title"); title = Array.isArray(title) ? title[0] : title; // If this is a Data object, there may not be a title, so try to get the // title from the file name if (!title && sourceModel.get("fileName")) { let fn = sourceModel.get("fileName"); const extRegex = /\.[^/.]+$/; // Save the extension let ext = fn ? fn.match(extRegex) : null; // remove the period and make it all uppercase ext = ext ? ext[0].replace(".", "").toUpperCase() : ext; // Remove the extension and replace underscores with spaces fn = fn.replace(extRegex, "").replace(/_+/g, " "); title = fn ? fn : title; title = title && ext ? title + " [" + ext + "]" : title; } return title; } catch (error) { console.log( "Error getting title from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().title; } }, /** * Get the journal (datasource/node) from the sourceModel. If there is a * datasource attribute on the sourceModel, then get the name of the member * node that has that datasource ID. If we can't find a member node that * matches the datasource, then check if the datasource is the current node. * If it is, then use the repository name. If there is no datasource * attribute, then use the current member node's name. * @param {Backbone.Model} sourceModel - The model to get the journal from * @returns {String} - The journal * @since 2.23.0 */ getJournalFromSourceModel(sourceModel) { try { let journal = null; const datasource = sourceModel.get("datasource"); const mn = MetacatUI.nodeModel.getMember(datasource); const currentMN = MetacatUI.nodeModel.get("currentMemberNode"); if (datasource) { if (mn) { journal = mn.name; } else if (datasource == MetacatUI.appModel.get("nodeId")) { journal = MetacatUI.appModel.get("repositoryName"); } } if (!journal && currentMN) { const mnCurrent = MetacatUI.nodeModel.getMember(currentMN); journal = mnCurrent ? mnCurrent.name : null; } return journal; } catch (error) { console.log( "Error getting journal from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().journal; } }, /** * Get the array of authors ("origin") from the sourceModel. First look for * creator (EML), then origin (science metadata & solr results), then * rightsHolder & submitter (base D1 object model). Convert EML parties to * strings & check for incorrectly escaped characters. * @param {Backbone.Model} sourceModel - The model to get the originArray * from * @returns {Array} - The originArray * @since 2.23.0 */ getOriginArrayFromSourceModel(sourceModel) { try { // AUTHORS let authors = // If it's an EML document, there will be a creator field sourceModel.get("creator") || // If it's a science metadata model or solr results, use origin sourceModel.get("origin") || ""; // otherwise, this is probably a base D1 object model. Don't use // rightsHolder or submitter for now, because it might not always be the // author. // sourceModel.get("rightsHolder") || // sourceModel.get("submitter"); // Convert EML parties to strings & check for incorrectly escaped // characters if (authors) { authors = Array.isArray(authors) ? authors : [authors]; authors = authors.map((author) => this.formatAuthor(author)); } return authors; } catch (error) { console.log( "Error getting originArray from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().originArray; } }, /** * Get the pid from the sourceModel. First look for id, then identifier. * @param {Backbone.Model} sourceModel - The model to get the pid from * @returns {String} - The pid * @since 2.23.0 */ getPidFromSourceModel(sourceModel) { try { const pid = sourceModel.get("id") || sourceModel.get("identifier") || null; return pid; } catch (error) { console.log( "Error getting the pid from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().pid; } }, /** * Get the seriesId from the sourceModel. Simply looks for the seriesId * attribute. * @param {Backbone.Model} sourceModel - The model to get the seriesId from * @returns {String} - The seriesId * @since 2.23.0 */ getSeriesIdFromSourceModel(sourceModel) { try { const seriesId = sourceModel.get("seriesId") || null; return seriesId; } catch (error) { console.log( "Error getting the seriesId from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().seriesId; } }, /** * Use the sourceModel's createViewURL() method to get the viewUrl for the * citation. This method is built into DataONEObject models, SolrResult * models, as well as Portal models. If the sourceModel doesn't have a * createViewURL() method, then use the default viewUrl (null) * @param {Backbone.Model} sourceModel - The model to get the viewUrl from * @returns {String} - The viewUrl, or null if the sourceModel doesn't have * a createViewURL() method. * @since 2.23.0 */ getViewUrlFromSourceModel(sourceModel) { try { if (sourceModel && sourceModel.createViewURL) { return sourceModel.createViewURL(); } else { return this.defaults().viewUrl; } } catch (error) { console.log( "Error getting the viewUrl from the sourceModel. Model and error:", sourceModel, error ); return this.defaults().viewUrl; } }, /** * Format an individual author for display within a citation. * @param {string|EMLParty} author The author to format * @returns {string} Returns the author as a string if it was an EMLParty * with any incorrectly escaped characters corrected. */ formatAuthor: function (author) { try { // Update the origin array asynchonously if the author is an ORCID if (this.isOrcid(author)) this.originArrayFromOrcid(author); // If author is an EMLParty model, then convert it to a string with // given name + sur name, or organization name if (typeof author.toCSLJSON === "function") { author = author.toCSLJSON(); } else if (typeof author === "string") { author = this.nameStrToCSLJSON(author); } return author; } catch (error) { console.log( "There was an error formatting an author, returning " + "the author input as is.", error ); return author; } }, /** * Cleans up the title for display within a citation. Removes a period from * the end of the title if it exists and trims whitespace.This method is * called any time a title is set on the Citation model. * @param {string} title The title to format * @returns {string} Returns the title with a period removed from the end if * it exists. * @since 2.23.0 */ formatTitle: function (title) { if (!title) return ""; return title.replace(/\.+$/, "").trim(); }, /** * Cleans up the metrics service string for display within a citation. * Replaces "NULL" with an empty string, removes a period from the end of * the string if it exists, removes curly braces, and trims whitespace. * @param {string} str The metrics service string to format * @returns {string} Returns the metrics service string with "NULL" replaced * with an empty string. * @since 2.23.0 */ formatMetricsServiceString: function (str) { if (!str) return ""; // The metrics service returns "NULL" if there is no data str = str === "NULL" ? "" : str; // Replace period at the end of the string str = str.replace(/\.+$/, ""); // Remove curly braces str = str.replace(/{|}/g, ""); // Remove any leading or trailing whitespace str = str.trim(); // Check for incorrectly escaped characters, like & const doc = new DOMParser().parseFromString(str, "text/html"); str = doc.body.textContent || ""; return str; }, /** * Convert the author string that is returned from the metrics service into * CSL JSON format. Author strings that come from the metrics service take * many formats, which might include full given and last names, middle * initials, first initials, etc. Here are a few example strings: "Chelsea * Wegner Koch", "Lee W. Cooper", "J. Wiktor", "Sei-Ichi Saitoh", "William * K. W. Li", "J.R. Lovvorn". Last name prefixes like "van" or "de" are * stored as a "non-dropping particle". See: * {@link https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#name-variables} * * @param {str} author The author string to convert * @returns {Object} Returns an object with the author's name in CSL JSON * format. * @since 2.23.0 */ nameStrToCSLJSON: function (str) { if (!str) return null; const name = {}; str = this.formatMetricsServiceString(str); // If the string contains one comma, then assume it is in the format "last // name, first name". Move the first name to the front of the string. if (str.split(",").length == 2) { const parts = str.split(","); str = parts[1].trim() + " " + parts[0].trim(); } const parts = str .trim() .split(/\s+|\./) .filter((part) => part !== ""); if (parts.length === 1) { name.literal = str; return name; } // Assume the last word is the family name name.family = parts.pop(); // Any remaining lowercase words are assumed to be non-dropping particles const nonDroppingParticles = parts.filter((part) => part.match(/^[a-z]+$/) ); if (nonDroppingParticles.length > 0) { name["non-dropping-particle"] = nonDroppingParticles.join(" "); } // Any remaining words are assumed to be given names const givenNames = parts.filter((part) => !part.match(/^[a-z]+$/)); if (givenNames.length > 0) { name.given = givenNames.join(" "); } return name; }, /** * Given a date, extract the year as a number. * @param {Date|String|Number} date The date to extract the year from * @returns {Number} Returns the year as a number, or null if the date is * invalid. * @since 2.23.0 */ yearFromDate: function (date) { try { if (!date) return null; // If Date is already a year (Number object with 4 digits), return it if (Number.isInteger(date) && date.toString().length == 4) { return date; } // If it is a string with 4 digits, return it as an integer. Use regex. if (typeof date == "string" && /^\d{4}$/.test(date)) { return parseInt(date); } // Check if the date is a Date object if (!(date instanceof Date)) { date = new Date(date); } const yr = date.getUTCFullYear(); return yr == "NaN" ? null : yr; } catch (error) { console.log( "There was an error getting the year from the date, returning null.", error ); return null; } }, /** * Check if a string is a valid ORCID. * @param {string} orcid The ORCID to check * @returns {boolean} Returns true if the ORCID is valid, false otherwise * @since 2.23.0 */ isOrcid: function (orcid) { try { if (!orcid) return false; const regex = new RegExp( "^https?:\\/\\/orcid.org\\/(\\d{4}-){3}(\\d{3}[0-9X])$" ); return regex.test(orcid); } catch { return false; } }, /** * Use the App Lookup model's get Accounts method to get the name of the * author from their ORCID, then asynchronously set the originArray to * contain that name. * @param {string} orcid The ORCID to get the name for * @since 2.23.0 */ originArrayFromOrcid: function (orcid) { try { const request = { term: orcid }; const model = this; const callback = function (response) { let name = null; if (response) { if (Array.isArray(response)) { const label = response[0].label; if (label) { // Name is the format "Min Liew // (http://orcid.org/0000-0002-5156-4610)" We want to return // "Min Liew". It will always be two spaces and a "(" name = label.split(" (")[0]; } } } if (name) { console.log("Setting originArray to ", [name]); model.set("originArray", [name]); } }; MetacatUI.appLookupModel.getAccountsAutocomplete(request, callback); } catch (error) { console.log( "There was an error getting the name from the orcid.", error ); } }, /** * Checks if the citation is for a DataONE object from a specific node (e.g. * PANGAEA) * @param {string} node - The node id to check, e.g. "urn:node:PANGAEA" * @returns {boolean} - True if the citation is for a DataONE object from * the given node * @since 2.23.0 */ isFromNode: function (node) { try { const sourceModel = this.get("sourceModel"); return ( sourceModel && sourceModel.get && sourceModel.get("datasource") && sourceModel.get("datasource") === node ); } catch (error) { console.log( `There was an error checking if the citation is from node ${node}.` + `Returning false.`, error ); return false; } }, /** * Convert the comma-separated origin string to an array of authors. * @param {string} origin - The origin string to convert to an array. If a * falsy value is passed in, then the default originArray attribute of the * model is returned. * @returns {Array} - An array of authors * @since 2.23.0 */ originToArray: function (origin) { try { if (!origin) { return this.defaults().originArray; } let originArray = origin ? origin.split(", ") : []; return originArray.map((author) => this.formatAuthor(author)); } catch (error) { console.log( "There was an error converting the origin string to an array.", error ); return this.defaults().originArray; } }, /** * Convert the origin array to a string. * @returns {string} - The origin string. If a falsy value is passed in, * then the default origin attribute of the model is returned. * @since 2.23.0 */ originArrayToString: function (originArray) { try { if (!originArray || !originArray.length) { return this.defaults().origin; } // Each author in the array is a CSL JSON object. Map it to a string // with the author's name in the format First Particle Last or Literal. // Separate each author name with a comma and a space. const origin = originArray .map((a) => { if (!a) return null; const ndp = a["non-dropping-particle"]; let name = (a.given ? a.given + " " : "") + (ndp ? ndp + " " : "") + (a.family ? a.family : ""); return (name || a.literal || "").trim(); }) .filter((a) => a) .join(", "); return origin; } catch (error) { console.log( "There was an error converting the origin array to a string.", error ); return this.defaults().origin; } }, /** * Returns true if the citation is for a DataONE object that has been * archived and archived content is not available in the search index. * @returns {boolean} - True if the citation has no content because it is * archived and archived content is not indexed. * @see AppModel#archivedContentIsIndexed * @since 2.23.0 */ isArchivedAndNotIndexed: function () { return this.isArchived() && !this.archivedContentIsIndexed(); }, /** * Checks if the object being cited is archived, according to the `archived` * attribute of the source model. * @returns {boolean} - True if the source model has an `archived` attribute * that is true. * @since 2.23.0 */ isArchived: function () { return ( this.sourceModel && this.sourceModel.get && this.sourceModel.get("archived") ); }, /** * Checks if archived content is available in the search index. * @see AppModel#archivedContentIsIndexed * @returns {boolean} - True if archived content is available in the search * index. * @since 2.23.0 */ archivedContentIsIndexed: function () { return MetacatUI.appModel.get("archivedContentIsIndexed"); }, /** * Remove all DOI prefixes from a DOI string, including https, http, doi.org, * dx.doi.org, and doi:. * @param {string} str - The DOI string to remove prefixes from. * @returns {string} - The DOI string without any prefixes. * @since 2.23.0 */ removeAllDOIPrefixes: function (str) { if (!str) return ""; // Remove https and http prefixes str = str.replace(/^(https?:\/\/)?/, ""); // Remove domain prefixes, like doi.org and dx.doi.org str = str.replace(/^(doi\.org\/|dx\.doi\.org\/)/, ""); // Remove doi: prefix str = str.replace(/^doi:/, ""); return str; }, /** * Check if a string is a valid DOI. * @param {string} doi - The string to check. * @returns {boolean} - True if the string is a valid DOI, false otherwise. * @since 2.23.0 */ isDOI: function (str) { try { if (!str) return false; str = this.removeAllDOIPrefixes(str); const doiRegex = /^10\.[0-9]{4,}(?:[.][0-9]+)*\/[^\s"<>]+$/; return doiRegex.test(str); } catch (e) { console.error("Error checking if string is a DOI", e); return false; } }, /** * Get the URL for the online location of the object being cited when it has * a DOI. If the DOI is not passed to the function, or if the string is not * a DOI, then an empty string is returned. * @param {string} [str] - The DOI string with or without the "doi:" prefix. * It may already be a URL, or it may be a DOI string. It also handles the * case where the DOI is already a URL. * @returns {string} - The DOI URL * @since 2.23.0 */ DOItoURL: function (str) { if (!str) return ""; str = this.removeAllDOIPrefixes(str); if (!this.isDOI(str)) return ""; return "https://doi.org/" + str; }, /** * Get the DOI from a DOI URL. The URL can be http or https, can include the * "doi:" prefix or not, and can use "dx.doi.org" or "doi.org" as the * domain. If a string is not passed to the function, or if the string is * not for a DOI URL, then an empty string is returned. * @param {string} url - The DOI URL * @returns {string} - The DOI string, including the "doi:" prefix * @since 2.23.0 */ URLtoDOI: function (url) { if (!url) return ""; const doiURLRegex = /https?:\/\/(dx\.)?doi\.org\/(doi:)?(10\.[0-9]{4,}(?:[.][0-9]+)*\/[^\s"<>]+)/; const doiURLMatch = url.match(doiURLRegex); if (doiURLMatch) return "doi:" + doiURLMatch[3]; return ""; }, /** * Checks if the citation has a DOI in the seriesId or pid attributes. * @returns {string} - The DOI of the seriesID, if it is a DOI, or the DOI * of the pid, if it is a DOI. Otherwise, returns null. * @since 2.23.0 */ findDOI: function () { try { if (!this.sourceModel || !this.sourceModel.isDOI) return null; const seriesID = this.get("seriesId"); const pid = this.get("pid"); if (this.sourceModel.isDOI(seriesID)) return seriesID; if (this.sourceModel.isDOI(pid)) return pid; return null; } catch (error) { console.log( "There was an error finding the DOI for the citation. Returning null", error ); return null; } }, /** * Checks if the citation has a DOI in the seriesId or pid attributes. * @returns {boolean} - True if the citation has a DOI * @since 2.23.0 */ hasDOI: function () { return this.findDOI() ? true : false; }, /** * If this citation has a source model, and if that source model is a * DataONEObject, then return the results of the DataONEObject's * `getUploadStatus` function * @returns {string} - The upload status of the source model, if it is a * DataONEObject, or null if it is not. * @since 2.23.0 * @see DataONEObject#getUploadStatus */ getUploadStatus: function () { return this.sourceModel ? this.sourceModel.get("uploadStatus") : null; }, /** * Get the URL for the citation. This will check the model for the following * attributes and return the first that is not empty: view_url, source_url, * sid_url, pid_url. * @returns {string} Returns the URL for the citation or an empty string. * @since 2.23.0 */ getURL: function () { const urlSources = ["view_url", "source_url", "sid_url", "pid_url"]; for (let i = 0; i < urlSources.length; i++) { const url = this.get(urlSources[i]); if (url) return url; } return ""; }, /** * Get the main identifier for the citation. This will check the model for * the following attributes and return the first that is not empty: pid, * seriesId, source_url. * @returns {string} Returns the main identifier for the citation or an * empty string. * @since 2.23.0 */ getID: function () { const idSources = ["pid", "seriesId", "source_url"]; for (let i = 0; i < idSources.length; i++) { const id = this.get(idSources[i]); if (id) return id; } return ""; }, } ); return Citation; });