UNPKG

@kit-data-manager/pid-component

Version:

The PID-Component is a web component that can be used to evaluate and display FAIR Digital Objects, PIDs, ORCiDs, and possibly other identifiers in a user-friendly way. It is easily extensible to support other identifier types.

1,155 lines (1,137 loc) 125 kB
/*! * * Copyright 2024 Karlsruhe Institute of Technology. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import { h, r as registerInstance, H as Host, a as getElement } from './index-DzUQJMIf.js'; import './json-viewer.entry.js'; class GenericIdentifierType { constructor(value, settings) { this._isDarkMode = false; this._settings = []; this._items = []; this._actions = []; this._value = value; this._settings = settings; this.updateDarkMode(); } get settings() { return this._settings; } set settings(value) { this._settings = value; this.updateDarkMode(); } get items() { return this._items; } get actions() { return this._actions; } get value() { return this._value; } get data() { return undefined; } renderBody() { return undefined; } get isDarkMode() { return this._isDarkMode; } updateDarkMode() { var _a; const darkModeSetting = (_a = this._settings) === null || _a === void 0 ? void 0 : _a.find(setting => setting.name === 'darkMode'); if (darkModeSetting) { const darkMode = darkModeSetting.value; if (darkMode === 'dark') { this._isDarkMode = true; } else if (darkMode === 'light') { this._isDarkMode = false; } else if (darkMode === 'system' && typeof window !== 'undefined' && window.matchMedia) { this._isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; } } else { this._isDarkMode = false; } } } class DateType extends GenericIdentifierType { getSettingsKey() { return 'DateType'; } async hasCorrectFormat() { const regex = new RegExp('^([0-9]{4})-([0]?[1-9]|1[0-2])-([0-2][0-9]|3[0-1])(T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9](.[0-9]*)?(Z|([+|-]([0-1][0-9]|2[0-3]):[0-5][0-9])){1}))$'); return regex.test(this.value); } init() { this._date = new Date(this.value); return; } renderPreview() { return h("span", null, this._date.toLocaleString()); } } let cacheInstance; async function open() { if ('caches' in self) { cacheInstance = await caches.open('pid-component'); } } async function cachedFetch(url, init) { await open(); if (cacheInstance) { const response = await cacheInstance.match(url); if (response) { return response.json(); } else { let response; const parts = url.split('://'); if (parts[0] !== 'https') { response = await fetch(`https://${parts[1]}`, init); if (!response) { console.log(`404 for https://${parts[1]} - trying http://${parts[1]}`); response = await fetch(`http://${parts[1]}`, init); } } else { response = await fetch(url, init); } await cacheInstance.put(url, response.clone()); return response.json(); } } else { const response = await fetch(url, init); return response.json(); } } async function clearCache() { if (cacheInstance) { await cacheInstance.delete('pid-component'); } } class ORCIDInfo { constructor(orcid, ORCiDJSON, familyName, givenNames, employments, preferredLocale, biography, emails, keywords, researcherUrls, country) { this._orcid = orcid; this._familyName = familyName; this._givenNames = givenNames; this._employments = employments; this._preferredLocale = preferredLocale; this._biography = biography; this._emails = emails; this._keywords = keywords; this._researcherUrls = researcherUrls; this._country = country; this._ORCiDJSON = ORCiDJSON; } get orcid() { return this._orcid; } get familyName() { return this._familyName; } get givenNames() { return this._givenNames; } get ORCiDJSON() { return this._ORCiDJSON; } get employments() { return this._employments; } get preferredLocale() { return this._preferredLocale; } get biography() { return this._biography; } get emails() { return this._emails; } get keywords() { return this._keywords; } get researcherUrls() { return this._researcherUrls; } get country() { return this._country; } static isORCiD(text) { const regex = new RegExp('^(https://orcid.org/)?[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$'); return regex.test(text); } static async getORCiDInfo(orcid) { if (!ORCIDInfo.isORCiD(orcid)) throw new Error('Invalid input'); if (orcid.match('^(https://orcid.org/)?[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$') !== null) orcid = orcid.replace('https://orcid.org/', ''); const rawOrcidJSON = await cachedFetch(`https://pub.orcid.org/v3.0/${orcid}`, { headers: { Accept: 'application/json', }, }); let familyName = ''; let givenNames = []; try { familyName = rawOrcidJSON['person']['name']['family-name']['value']; } catch (e) { console.debug(e); } try { givenNames = rawOrcidJSON['person']['name']['given-names']['value']; } catch (e) { console.debug(e); } const affiliations = rawOrcidJSON['activities-summary']['employments']['affiliation-group']; const employments = []; try { for (let i = 0; i < affiliations.length; i++) { const employmentSummary = affiliations[i]['summaries'][0]['employment-summary']; const employment = new Employment(new Date(), null, '', ''); if (employmentSummary['start-date'] !== null) employment.startDate = new Date(employmentSummary['start-date']['year']['value'], employmentSummary['start-date']['month']['value'], employmentSummary['start-date']['day']['value']); if (employmentSummary['end-date'] !== null) employment.endDate = new Date(employmentSummary['end-date']['year']['value'], employmentSummary['end-date']['month']['value'], employmentSummary['end-date']['day']['value']); employment.organization = employmentSummary['organization']['name']; employment.department = employmentSummary['department-name']; employments.push(employment); } } catch (e) { console.debug(e); } const preferredLocale = rawOrcidJSON['preferences']['locale'] !== null ? rawOrcidJSON['preferences']['locale'] : undefined; const biography = rawOrcidJSON['person']['biography'] !== null ? rawOrcidJSON['person']['biography']['content'] : undefined; const emails = []; if (rawOrcidJSON['person']['emails']['email'] !== null) { for (const email of rawOrcidJSON['person']['emails']['email']) { emails.push({ email: email['email'], primary: email['primary'], verified: email['verified'], }); } } const keywords = []; if (rawOrcidJSON['person']['keywords']['keyword'] !== null) { for (const keyword of rawOrcidJSON['person']['keywords']['keyword']) { keywords.push({ content: keyword['content'], index: keyword['display-index'], }); } keywords.sort((a, b) => a.index - b.index); } const researcherUrls = []; if (rawOrcidJSON['person']['researcher-urls']['researcher-url'] !== null) { for (const researcherUrl of rawOrcidJSON['person']['researcher-urls']['researcher-url']) { researcherUrls.push({ url: researcherUrl['url']['value'], name: researcherUrl['url-name'], index: researcherUrl['display-index'], }); } researcherUrls.sort((a, b) => a.index - b.index); } const country = rawOrcidJSON['person']['addresses']['address'].length > 0 ? rawOrcidJSON['person']['addresses']['address'][0]['country']['value'] : undefined; return new ORCIDInfo(orcid, rawOrcidJSON, familyName, givenNames, employments, preferredLocale, biography, emails, keywords, researcherUrls, country); } static fromJSON(serialized) { const data = JSON.parse(serialized); const employments = data.employments.map(employment => Employment.fromJSON(employment)); return new ORCIDInfo(data.orcid, data.ORCiDJSON, data.familyName, data.givenNames, employments, data.preferredLocale, data.biography, data.emails, data.keywords, data.researcherUrls, data.country); } getAffiliationsAt(date) { const affiliations = []; for (const employment of this._employments) { if (employment.startDate <= date && employment.endDate === null) affiliations.push(employment); if (employment.startDate <= date && employment.endDate !== null && employment.endDate >= date) affiliations.push(employment); } return affiliations; } getAffiliationAsString(affiliation, showDepartment = true) { if (affiliation === undefined || affiliation.organization === null) return undefined; else { if (showDepartment && affiliation.department !== null) return `${affiliation.organization} [${affiliation.department}]`; else return affiliation.organization; } } toObject() { return { orcid: this._orcid, ORCiDJSON: this._ORCiDJSON, familyName: this._familyName, givenNames: this._givenNames, employments: this._employments.map(employment => JSON.stringify(employment.toObject())), preferredLocale: this._preferredLocale, biography: this._biography, emails: this._emails, keywords: this._keywords, researcherUrls: this._researcherUrls, country: this._country, }; } } class Employment { constructor(startDate, endDate, organization, department) { this.startDate = startDate; this.endDate = endDate; this.organization = organization; this.department = department; } static fromJSON(serialized) { const data = JSON.parse(serialized); const startDate = new Date(data.startDate); const endDate = data.endDate === null ? null : new Date(data.endDate); return new Employment(startDate, endDate, data.organization, data.department); } toObject() { return { startDate: this.startDate, endDate: this.endDate, organization: this.organization, department: this.department, }; } } class FoldableItem { constructor(priority, keyTitle, value, keyTooltip, keyLink, valueRegex, renderDynamically) { this._estimatedTypePriority = 0; this._priority = priority; this._keyTitle = keyTitle; this._value = value; this._keyTooltip = keyTooltip; this._keyLink = keyLink; this._valueRegex = valueRegex; this._renderDynamically = renderDynamically !== undefined ? renderDynamically : true; this._estimatedTypePriority = renderDynamically ? 0 : 0; } get priority() { return this._priority; } get keyTitle() { return this._keyTitle; } get value() { return this._value; } get keyTooltip() { return this._keyTooltip; } get keyLink() { return this._keyLink; } get valueRegex() { return this._valueRegex; } get renderDynamically() { return this._renderDynamically; } get estimatedTypePriority() { return this._estimatedTypePriority; } isValidValue() { return this._valueRegex ? this._valueRegex.test(this._value) : true; } equals(other) { return (this._keyTitle === other._keyTitle && this._value === other._value && this._keyTooltip === other._keyTooltip && this._keyLink === other._keyLink && this._renderDynamically === other._renderDynamically); } } class FoldableAction { constructor(priority, title, link, style) { this._priority = priority; this._title = title; this._link = link; this._style = style; } get priority() { return this._priority; } get title() { return this._title; } get link() { return this._link; } get style() { return this._style; } equals(other) { return this._priority === other._priority && this._title === other._title && this._link === other._link && this._style === other._style; } } class ORCIDType extends GenericIdentifierType { constructor() { super(...arguments); this.affiliationAt = new Date(Date.now()); this.showAffiliation = true; } get data() { return JSON.stringify(this._orcidInfo.toObject()); } async hasCorrectFormat() { return ORCIDInfo.isORCiD(this.value); } async init(data) { if (data !== undefined) { this._orcidInfo = ORCIDInfo.fromJSON(data); console.debug('reload ORCIDInfo from data', this._orcidInfo); } else { this._orcidInfo = await ORCIDInfo.getORCiDInfo(this.value); console.debug('load ORCIDInfo from API', this._orcidInfo); } if (this.settings) { for (const i of this.settings) { switch (i['name']) { case 'affiliationAt': this.affiliationAt = new Date(i['value']); break; case 'showAffiliation': this.showAffiliation = i['value'] === true || i['value'] === 'true' || i['value'] === '1'; break; } } } this.items.push(new FoldableItem(0, 'ORCiD', this._orcidInfo.orcid, 'ORCiD is a free service for researchers to distinguish themselves by creating a unique personal identifier.', 'https://orcid.org', undefined, false)); try { const givenNames = this._orcidInfo.givenNames; if (givenNames) { new FoldableItem(2, 'Given Names', this._orcidInfo.givenNames.toString(), 'The given names of the person.'); } } catch (e) { console.log('Failed to obtain given names from ORCiD record.', e); } this.actions.push(new FoldableAction(0, 'Open ORCiD profile', `https://orcid.org/${this._orcidInfo.orcid}`, 'primary')); try { const affiliations = this._orcidInfo.getAffiliationsAt(new Date(Date.now())); for (const data of affiliations) { const affiliation = this._orcidInfo.getAffiliationAsString(data); if (affiliation !== undefined && affiliation.length > 2) this.items.push(new FoldableItem(50, 'Current Affiliation', affiliation, 'The current affiliation of the person.', undefined, undefined, false)); } } catch (e) { console.log('Failed to obtain affiliations from ORCiD record.', e); } if (this._orcidInfo.getAffiliationsAt(this.affiliationAt) !== this._orcidInfo.getAffiliationsAt(new Date()) && this.affiliationAt.toLocaleDateString('en-US') !== new Date().toLocaleDateString('en-US')) { const affiliationsThen = this._orcidInfo.getAffiliationsAt(this.affiliationAt); for (const data of affiliationsThen) { const affiliation = this._orcidInfo.getAffiliationAsString(data); this.items.push(new FoldableItem(49, 'Affiliation at ' + this.affiliationAt.toLocaleDateString('en-US', { year: 'numeric', month: 'numeric', day: 'numeric', }), affiliation, 'The affiliation of the person at the given date.', undefined, undefined, false)); } } if (this._orcidInfo.emails) { const primary = this._orcidInfo.emails.filter(email => email.primary)[0]; const other = this._orcidInfo.emails.filter(email => !email.primary); if (primary) { this.items.push(new FoldableItem(20, 'Primary E-Mail address', primary.email, 'The primary e-mail address of the person.')); this.actions.push(new FoldableAction(0, 'Send E-Mail', `mailto:${primary.email}`, 'secondary')); } if (other.length > 0) this.items.push(new FoldableItem(70, 'Other E-Mail addresses', other.map(email => email.email).join(', '), 'All other e-mail addresses of the person.')); if (this._orcidInfo.preferredLocale) this.items.push(new FoldableItem(25, 'Preferred Language', this._orcidInfo.preferredLocale, 'The preferred locale/language of the person.')); for (const url of this._orcidInfo.researcherUrls) { this.items.push(new FoldableItem(100, url.name, url.url, 'A link to a website specified by the person.')); } if (this._orcidInfo.keywords.length > 50) this.items.push(new FoldableItem(60, 'Keywords', this._orcidInfo.keywords.map(keyword => keyword.content).join(', '), 'Keywords specified by the person.', undefined, undefined, false)); if (this._orcidInfo.biography) this.items.push(new FoldableItem(200, 'Biography', this._orcidInfo.biography, 'The biography of the person.', undefined, undefined, false)); if (this._orcidInfo.country) this.items.push(new FoldableItem(30, 'Country', this._orcidInfo.country, 'The country of the person.')); } } isResolvable() { return this._orcidInfo.ORCiDJSON !== undefined; } renderPreview() { return (h("span", { class: `inline-flex flex-nowrap items-center align-top font-mono ${this.isDarkMode ? 'text-gray-200' : ''}` }, h("svg", { version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 256 256", class: 'mr-1 h-5 flex-none items-center p-0.5' }, h("style", { type: "text/css" }, `.st0{fill:#A6CE39;}`, `.st1{fill:#FFFFFF;}`), h("path", { class: "st0", d: "M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z" }), h("g", null, h("path", { class: "st1", d: "M86.3,186.2H70.9V79.1h15.4v48.4V186.2z" }), h("path", { class: "st1", d: "M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5\n c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z" }), h("path", { class: "st1", d: "M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1\n C84.2,46.7,88.7,51.3,88.7,56.8z" }))), h("span", { class: `flex-none items-center px-1 ${this.isDarkMode ? 'text-gray-200' : ''}` }, this._orcidInfo.familyName, ", ", this._orcidInfo.givenNames, ' ', this.showAffiliation && this._orcidInfo.getAffiliationsAt(new Date()).length > 0 ? `(${this._orcidInfo.getAffiliationAsString(this._orcidInfo.getAffiliationsAt(new Date())[0], false)}${this._orcidInfo.getAffiliationsAt(this.affiliationAt).length > 0 && this.affiliationAt.toLocaleDateString() !== new Date().toLocaleDateString() && this._orcidInfo.getAffiliationsAt(this.affiliationAt)[0].organization !== this._orcidInfo.getAffiliationsAt(new Date())[0].organization ? `, then: ${this._orcidInfo.getAffiliationsAt(this.affiliationAt)[0].organization}` : ''})` : ''))); } getSettingsKey() { return 'ORCIDType'; } } class PID { constructor(prefix, suffix) { this._prefix = prefix; this._suffix = suffix; } get prefix() { return this._prefix; } get suffix() { return this._suffix; } static isPID(text) { return new RegExp('^([0-9A-Za-z])+.([0-9A-Za-z])+/([!-~])+$').test(text); } static getPIDFromString(pid) { if (!PID.isPID(pid)) throw new Error('Invalid input'); const pidSplit = pid.split('/'); return new PID(pidSplit[0], pidSplit[1]); } static fromJSON(serialized) { const data = JSON.parse(serialized); return new PID(data.prefix, data.suffix); } toString() { return `${this.prefix}/${this.suffix}`; } isResolvable() { return !unresolvables.has(this) && !this.prefix.toUpperCase().match('^(0$|0\\.|HS_|10320$)'); } async resolve() { if (unresolvables.has(this)) return undefined; else if (handleMap.has(this)) return handleMap.get(this); else { const rawJson = (await cachedFetch(`https://hdl.handle.net/api/handles/${this.prefix}/${this.suffix}#resolve`)); console.log(rawJson); const valuePromises = rawJson.values.map(async (value) => { const type = (async () => { if (PID.isPID(value.type)) { const pid = PID.getPIDFromString(value.type); const dataType = await PIDDataType.resolveDataType(pid); return dataType instanceof PIDDataType ? dataType : pid; } return value.type; })(); return { index: value.index, type: await type, data: value.data, ttl: value.ttl, timestamp: Date.parse(value.timestamp), }; }); const values = await Promise.all(valuePromises); const record = new PIDRecord(this, values); handleMap.set(this, record); return record; } } toObject() { return { prefix: this.prefix, suffix: this.suffix, }; } } const locationType = new PID('10320', 'loc'); class PIDDataType { constructor(pid, name, description, redirectURL, regex) { this._pid = pid; this._name = name; this._description = description; this._regex = regex; this._redirectURL = redirectURL; } get pid() { return this._pid; } get name() { return this._name; } get description() { return this._description; } get redirectURL() { return this._redirectURL; } get regex() { return this._regex; } static async resolveDataType(pid) { if (typeMap.has(pid)) return typeMap.get(pid); if (!pid.isResolvable()) { console.debug(`PID ${pid.toString()} has been marked as unresolvable`); return undefined; } const pidRecord = await pid.resolve(); if (pidRecord === undefined) { console.debug(`PID ${pid.toString()} could not be resolved via the API`); unresolvables.add(pid); return undefined; } const tempDataType = { name: '', description: '', redirectURL: '', ePICJSON: {} }; for (let i = 0; i < pidRecord.values.length; i++) { const currentValue = pidRecord.values[i]; if (currentValue.type === locationType || currentValue.type.toString() === locationType.toString()) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(currentValue.data.value, 'text/xml'); const xmlLocations = xmlDoc.getElementsByTagName('location'); for (let j = 0; j < xmlLocations.length; j++) { const newLocation = { href: xmlLocations[j].getAttribute('href'), weight: undefined, view: undefined, resolvedData: undefined, }; try { newLocation.weight = parseInt(xmlLocations[j].getAttribute('weight')); } catch (_ignored) { } try { newLocation.view = xmlLocations[j].getAttribute('view'); } catch (ignored) { } try { if (newLocation.view === 'json') { newLocation.resolvedData = await cachedFetch(newLocation.href); tempDataType.ePICJSON = newLocation.resolvedData; tempDataType.name = newLocation.resolvedData['name']; tempDataType.description = newLocation.resolvedData['description']; } else { tempDataType.redirectURL = newLocation.href; } } catch (ignored) { } } } } try { const type = new PIDDataType(pid, tempDataType.name, tempDataType.description, tempDataType.redirectURL, tempDataType.regex); typeMap.set(pid, type); return type; } catch (e) { console.error(e); return undefined; } } static fromJSON(serialized) { const data = JSON.parse(serialized); return new PIDDataType(PID.fromJSON(data.pid), data.name, data.description, data.redirectURL, data.regex); } toObject() { return { pid: JSON.stringify(this._pid.toObject()), name: this._name, description: this._description, redirectURL: this._redirectURL, regex: this._regex, }; } } class PIDRecord { constructor(pid, values) { this._values = []; this._pid = pid; this._values = values; } get pid() { return this._pid; } get values() { return this._values; } static fromJSON(serialized) { const data = JSON.parse(serialized); const values = data.values.map(value => { const parsed = JSON.parse(value); const parsedType = JSON.parse(parsed.type); let type; if (parsedType.pidDataType !== undefined) { type = PIDDataType.fromJSON(parsedType.pidDataType); } else if (parsedType.pid !== undefined) { type = PID.fromJSON(parsedType.pid); } else { type = parsedType.string; } const data = JSON.parse(parsed.data); return { index: parsed.index, type: type, data: data, ttl: parsed.ttl, timestamp: parsed.timestamp, }; }); return new PIDRecord(PID.fromJSON(data.pid), values); } toObject() { return { pid: JSON.stringify(this._pid.toObject()), values: this._values.map(value => JSON.stringify({ index: value.index, type: JSON.stringify({ pid: value.type instanceof PID ? JSON.stringify(value.type.toObject()) : undefined, pidDataType: value.type instanceof PIDDataType ? JSON.stringify(value.type.toObject()) : undefined, string: typeof value.type == 'string' ? value.type : undefined, }), data: JSON.stringify(value.data), ttl: value.ttl, timestamp: value.timestamp, })), }; } } class HandleType extends GenericIdentifierType { constructor() { super(...arguments); this._parts = []; } get data() { return JSON.stringify(this._pidRecord.toObject()); } async hasCorrectFormat() { return PID.isPID(this.value); } async init(data) { if (data !== undefined) { this._pidRecord = PIDRecord.fromJSON(data); this._parts = await Promise.all([ { text: this._pidRecord.pid.prefix, nextExists: true, }, { text: this._pidRecord.pid.suffix, nextExists: false, }, ]); console.debug('reload PIDRecord from data', this._pidRecord); } else { const pid = PID.getPIDFromString(this.value); this._parts = [ { text: pid.prefix, nextExists: true, }, { text: pid.suffix, nextExists: false, }, ]; this._pidRecord = await pid.resolve(); console.debug('load PIDRecord from API', this._pidRecord); } for (const value of this._pidRecord.values) { if (value.type instanceof PIDDataType) { this.items.push(new FoldableItem(0, value.type.name, value.data.value, value.type.description, value.type.redirectURL, value.type.regex)); } } this.actions.push(new FoldableAction(0, 'Open in FAIR-DOscope', `https://kit-data-manager.github.io/fairdoscope/?pid=${this._pidRecord.pid.toString()}`, 'primary')); this.actions.push(new FoldableAction(0, 'View in Handle.net registry', `https://hdl.handle.net/${this._pidRecord.pid.toString()}`, 'secondary')); return; } isResolvable() { return this._pidRecord.values.length > 0; } renderPreview() { return (h("span", { class: 'rounded-md bg-inherit font-mono font-bold' }, this._parts.map(element => { return (h("span", { class: 'font-mono font-bold' }, h("color-highlight", { text: element.text }), h("span", { class: `mx-0.5 font-mono font-bold` }, element.nextExists ? '/' : ''))); }))); } getSettingsKey() { return 'HandleType'; } } class EmailType extends GenericIdentifierType { getSettingsKey() { return 'EmailType'; } async hasCorrectFormat() { const regex = /^(([\w\-.]+@([\w-]+\.)+[\w-]{2,})(\s*,\s*)?)*$/gm; return regex.test(this.value); } init() { return; } renderPreview() { return (h("span", { class: `inline-flex items-center gap-2 font-mono text-sm text-blue-500` }, this.value .split(new RegExp(/\s*,\s*/)) .filter(email => email.length > 0) .map(email => { return (h("a", { href: 'mailto:' + email, rel: 'noopener noreferrer', target: "_blank", class: `inline-flex items-center rounded-md border border-slate-500 px-1 py-0.5 font-mono text-sm text-blue-500` }, h("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", "aria-hidden": "true", viewBox: "0 0 24 24", stroke: "currentColor", "stroke-width": "1", height: "20px", class: 'mr-2' }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" })), h("span", { class: `ml-2 text-blue-400 hover:text-blue-500` }, email))); }))); } } class URLType extends GenericIdentifierType { getSettingsKey() { return 'URLType'; } async hasCorrectFormat() { const regex = new RegExp('^http(s)?:(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$'); return regex.test(this.value); } init() { return; } renderPreview() { return (h("a", { href: this.value, target: "_blank", rel: 'noopener noreferrer', class: `font-mono text-sm text-blue-400` }, this.value)); } } class FallbackType extends GenericIdentifierType { async hasCorrectFormat() { return true; } init() { return; } renderPreview() { return h("span", null, this.value); } getSettingsKey() { return 'FallbackType'; } } class LocaleType extends GenericIdentifierType { getSettingsKey() { return 'LocaleType'; } async hasCorrectFormat() { const regex = /^([a-zA-Z]{2})(-[A-Z]{2})?$/; return regex.test(this.value); } init() { return; } renderPreview() { return h("locale-visualization", { locale: this.value, showFlag: true }); } } class JSONType extends GenericIdentifierType { constructor() { super(...arguments); this._parsedJsonResult = undefined; } getParsedJson() { if (this._parsedJsonResult === undefined) { try { if (typeof this.value === 'object' && this.value !== null) { this._parsedJsonResult = { data: this.value }; return this._parsedJsonResult; } if (typeof this.value !== 'string') { throw new Error('Input value is not a string or object'); } const trimmedValue = this.value.trim(); if (trimmedValue === '') { this._parsedJsonResult = { error: new Error('Empty JSON string') }; return this._parsedJsonResult; } const parsed = JSON.parse(trimmedValue); this._parsedJsonResult = { data: parsed }; } catch (e) { this._parsedJsonResult = { error: e instanceof Error ? e : new Error(String(e)) }; } } return this._parsedJsonResult; } getSettingsKey() { return 'JSONType'; } async hasCorrectFormat() { const { data, error } = this.getParsedJson(); if (error) { console.warn('JSONType has incorrect format:', error.message, 'for value:', this.value); return false; } return typeof data === 'object' && data !== null; } init() { this._parsedJsonResult = undefined; return Promise.resolve(); } isResolvable() { return false; } renderPreview() { const { data: jsonObj, error } = this.getParsedJson(); if (error) { return h("span", { class: 'text-red-500' }, "Invalid JSON"); } const isComplexObjectOrArray = typeof jsonObj === 'object' && jsonObj !== null; const isArray = Array.isArray(jsonObj); if (isComplexObjectOrArray) { const entryCount = Object.keys(jsonObj).length; return (h("div", { class: "w-full" }, h("div", { class: `flex items-center rounded-md font-mono text-xs` }, h("span", { class: `mr-1 font-medium` }, isArray ? 'Array' : 'Object'), h("span", { class: 'text-gray-500' }, isArray ? '[' : '{'), h("span", { class: `text-xs text-gray-500` }, entryCount, " ", entryCount === 1 ? 'item' : 'items'), h("span", { class: 'text-gray-500' }, isArray ? ']' : '}')))); } return (h("div", { class: "w-full" }, h("pre", { class: `max-w-full overflow-x-auto rounded-md font-mono text-xs whitespace-pre-wrap` }, JSON.stringify(jsonObj, null, 2)))); } renderBody() { var _a, _b; const { data: parsedData, error } = this.getParsedJson(); const darkModeValue = ((_b = (_a = this.settings) === null || _a === void 0 ? void 0 : _a.find(setting => setting.name === 'darkMode')) === null || _b === void 0 ? void 0 : _b.value) || 'system'; if (error) { return (h("div", { class: "w-full overflow-y-auto" }, h("span", { class: 'text-red-500' }, "Invalid JSON data: ", error.message))); } return (h("div", { class: "w-full overflow-y-auto" }, h("json-viewer", { data: parsedData, "expand-all": false, "show-line-numbers": true, theme: darkModeValue }, h("span", { class: 'text-red-500' }, "Could not display JSON data.")))); } } class RORType extends GenericIdentifierType { constructor() { super(...arguments); this.relationshipTypes = { parent: { title: 'Parent Organization', tooltip: 'Organization that this organization is part of', }, child: { title: 'Child Organization', tooltip: 'Organization that is part of this organization', }, related: { title: 'Related Organization', tooltip: 'Organization that is related to this organization', }, predecessor: { title: 'Predecessor Organization', tooltip: 'Organization that preceded this organization', }, successor: { title: 'Successor Organization', tooltip: 'Organization that succeeded this organization', }, }; this.contentMappings = { active: '🟢 Active', inactive: '⚪️ Inactive', withdrawn: '⚠️ Withdrawn', education: '🏫 Education', funder: '💰 Funder', healthcare: '🏥 Healthcare', company: '🏢 Company', archive: '📚 Archive', nonprofit: '🎗️ Nonprofit', government: '🏛️ Government', facility: '🔬 Facility', other: 'Other', unknown: '❓ Unknown', }; } getSettingsKey() { return 'RORType'; } async hasCorrectFormat() { const regex = new RegExp('^https?://ror.org/[0-9a-z]{9}$', 'i'); return regex.test(this.value); } getRorId() { return this.value.split('/').pop(); } getOptimizedContent(content) { if (this.contentMappings[content.toLowerCase()]) { return this.contentMappings[content.toLowerCase()]; } return content; } async init() { try { const rorId = this.getRorId(); const response = await fetch(`https://api.ror.org/v2/organizations/${rorId}`); if (!response.ok) { throw new Error(`Failed to fetch ROR data: ${response.status}`); } this.rorData = await response.json(); if (!this.rorData) return; if (!this.rorData.names || this.rorData.names.length === 0) { this.label = 'Unknown'; this.items.push(new FoldableItem(0, 'Name', 'Unknown', 'No names available for this organization')); return; } else { for (const name of this.rorData.names) { const types = name.types || []; if (types.includes('acronym')) { this.acronym = name.value; this.items.push(new FoldableItem(20, 'Acronym', name.value, 'Short form of the organization name')); } else if (types.includes('ror_display')) { this.label = name.value; this.items.push(new FoldableItem(1, 'Display Name', name.value, 'Name used for display purposes')); } else if (types.includes('alias')) { this.items.push(new FoldableItem(5, 'Alias', name.value, 'Alternative name for the organization')); } else if (types.includes('label')) { this.items.push(new FoldableItem(15, 'Label', name.value, 'Name in another language or script')); } } } this.items.push(new FoldableItem(20, 'ROR ID', this.rorData.id, 'Unique identifier for the organization in the ROR registry', null, null, false)); this.actions.push(new FoldableAction(10, 'View on ROR', this.rorData.id, 'primary')); this.items.push(new FoldableItem(30, 'Status', this.getOptimizedContent(this.rorData.status || 'unknown'), 'Current status of the organization in the ROR registry')); if (!this.rorData.types || this.rorData.types.length === 0) { this.items.push(new FoldableItem(25, 'Type', this.getOptimizedContent('unknown'), 'Type of organization')); } else { for (const type of this.rorData.types) { this.items.push(new FoldableItem(25, 'Type', this.getOptimizedContent(type), 'Type of organization')); } } if (this.rorData.links && this.rorData.links.length > 0) { for (const link of this.rorData.links) { if (link.type) { this.items.push(new FoldableItem(35, `Link to ${link.type}`, link.value, 'External link related to the organization')); } else { this.items.push(new FoldableItem(35, `Link`, link.value, 'External link related to the organization')); } } } if (this.rorData.external_ids && this.rorData.external_ids.length > 0) { for (const external of this.rorData.external_ids) { const type = external.type; const value = external.preferred || external.all[0]; this.items.push(new FoldableItem(40, `External ID: ${type}`, value, `Identifier from another system: ${type}`)); } } if (this.rorData.relationships && this.rorData.relationships.length > 0) { for (const rel of this.rorData.relationships) { const relationType = this.relationshipTypes[rel.type] || { title: rel.type, tooltip: `${rel.type} organization` }; this.items.push(new FoldableItem(90, relationType.title, rel.id, relationType.tooltip)); } } if (this.rorData.locations && this.rorData.locations.length > 0) { for (const location of this.rorData.locations) { const details = location.geonames_details; if (details.country_code) { this.items.push(new FoldableItem(50, 'Country', details.country_code, 'Country where the organization is located')); } if (details.lat && details.lng) { this.items.push(new FoldableItem(55, 'Coordinates', `${details.lat}, ${details.lng}`, 'Geographic coordinates of the organization')); const osmUrl = `https://www.openstreetmap.org/?mlat=${details.lat}&mlon=${details.lng}&zoom=15`; this.actions.push(new FoldableAction(20, 'View on OpenStreetMap', osmUrl, 'secondary')); } } } } catch (error) { console.error('Error fetching ROR data:', error); this.items.push(new FoldableItem(0, 'Error', `Failed to fetch data from ROR API: ${error.message}`)); } } renderPreview() { if (!this.rorData) { return h("span", { class: `font-mono text-sm` }, "Loading ROR: ", this.value, "..."); } return (h("span", { class: `inline-flex flex-nowrap items-center align-top font-mono` }, h("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 164 118", version: "1.1", class: 'mr-1 h-5 flex-none items-center p-0.5', style: { fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: '2' } }, h("g", { transform: "matrix(0.994301,0,0,0.989352,0,0)" }, h("rect", { x: "0", y: "0", width: "164.94", height: "119.27", style: { fill: 'white' } })), h("g", { transform: "matrix(1,0,0,1,-0.945,-0.815)" }, h("path", { d: "M68.65,4.16L56.52,22.74L44.38,4.16L68.65,4.16Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }), h("path", { d: "M119.41,4.16L107.28,22.74L95.14,4.16L119.41,4.16Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }), h("path", { d: "M44.38,115.47L56.52,96.88L68.65,115.47L44.38,115.47Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }), h("path", { d: "M95.14,115.47L107.28,96.88L119.41,115.47L95.14,115.47Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }), h("path", { d: "M145.53,63.71C149.83,62.91 153.1,61 155.33,57.99C157.57,54.98 158.68,51.32 158.68,47.03C158.68,43.47 158.06,40.51 156.83,38.13C155.6,35.75 153.93,33.86 151.84,32.45C149.75,31.05 147.31,30.04 144.53,29.44C141.75,28.84 138.81,28.54 135.72,28.54L112.16,28.54L112.16,47.37C111.97,46.82 111.77,46.28 111.55,45.74C109.92,41.79 107.64,38.42 104.71,35.64C101.78,32.86 98.32,30.72 94.3,29.23C90.29,27.74 85.9,26.99 81.14,26.99C76.38,26.99 72,27.74 67.98,29.23C63.97,30.72 60.5,32.86 57.57,35.64C54.95,38.13 52.85,41.1 51.27,44.54C51.04,42.07 50.46,39.93 49.53,38.13C48.3,35.75 46.63,33.86 44.54,32.45C42.45,31.05 40.01,30.04 37.23,29.44C34.45,28.84 31.51,28.54 28.42,28.54L4.87,28.54L4.87,89.42L18.28,89.42L18.28,65.08L24.9,65.08L37.63,89.42L53.71,89.42L38.24,63.71C42.54,62.91 45.81,61 48.04,57.99C48.14,57.85 48.23,57.7 48.33,57.56C48.31,58.03 48.3,58.5 48.3,58.98C48.3,63.85 49.12,68.27 50.75,72.22C52.38,76.17 54.66,79.54 57.59,82.32C60.51,85.1 63.98,87.24 68,88.73C72.01,90.22 76.4,90.97 81.16,90.97C85.92,90.97 90.3,90.22 94.32,88.73C98.33,87.24 101.8,85.1 104.73,82.32C107.65,79.54 109.93,76.17 111.57,72.22C111.79,71.69 111.99,71.14 112.18,70.59L112.18,89.42L125.59,89.42L125.59,65.08L132.21,65.08L144.94,89.42L161.02,89.42L145.53,63.71ZM36.39,50.81C35.67,51.73 34.77,52.4 33.68,52.83C32.59,53.26 31.37,53.52 30.03,53.6C28.68,53.69 27.41,53.73 26.2,53.73L18.29,53.73L18.29,39.89L27.06,39.89C28.26,39.89 29.5,39.98 30.76,40.15C32.02,40.32 33.14,40.65 34.11,41.14C35.08,41.63 35.89,42.33 36.52,43.25C37.15,44.17 37.47,45.4 37.47,46.95C37.47,48.6 37.11,49.89 36.39,50.81ZM98.74,66.85C97.85,69.23 96.58,71.29 94.91,73.04C93.25,74.79 91.26,76.15 88.93,77.13C86.61,78.11 84.01,78.59 81.15,78.59C78.28,78.59 75.69,78.1 73.37,77.13C71.05,76.16 69.06,74.79 67.39,73.04C65.73,71.29 64.45,69.23 63.56,66.85C62.67,64.47 62.23,61.85 62.23,58.98C62.23,56.17 62.67,53.56 63.56,51.15C64.45,48.74 65.72,46.67 67.39,44.92C69.05,43.17 71.04,41.81 73.37,40.83C75.69,39.86 78.28,39.37 81.15,39.37C84.02,39.37 86.61,39.86 88.93,40.83C91.25,41.8 93.24,43.17 94.91,44.92C96.57,46.67 97.85,48.75 98.74,51.15C99.63,53.56 100.07,56.17 100.07,58.98C100.07,61.85 99.63,64.47 98.74,66.85ZM143.68,50.81C142.96,51.73 142.06,52.4 140.97,52.83C139.88,53.26 138.66,53.52 137.32,53.6C135.97,53.69 134.7,53.73 133.49,53.73L125.58,53.73L125.58,39.89L134.35,39.89C135.55,39.89 136.79,39.98 138.05,40.15C139.31,40.32 140.43,40.65 141.4,41.14C142.37,41.63 143.18,42.33 143.81,43.25C144.44,44.17 144.76,45.4 144.76,46.95C144.76,48.6 144.4,49.89 143.68,50.81Z", style: { fill: 'rgb(32,40,38)', fillRule: 'nonzero' } }))), h("span", { class: `flex-none items-center px-1` }, this.label, this.acronym ? ' (' + this.acronym + ')' : ''))); } get data() { return this.rorData; } } const urlRegex = new RegExp('^https?://spdx.org/licenses/[\\w.\\-+]+/?$', 'i'); class SPDXType extends GenericIdentifierType { constructor() { super(...arguments); this.licenseData = null; this.licenseId = ''; this.corsFallback = true; this.corsProxy = 'https://corsproxy.io/?'; this.spdxBaseUrl = 'https://spdx.org/licenses'; this.fileFormat = 'json'; this.requestTimeout = 10000; } getSettingsKey() { return 'SPDXType'; } async