@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,316 lines (1,291 loc) • 77.5 kB
JavaScript
/*!
*
* 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
*
* http://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.
*
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const index = require('./index-0c94ea0c.js');
class GenericIdentifierType {
constructor(value, settings) {
this._settings = [];
this._items = [];
this._actions = [];
this._value = value;
this._settings = settings;
}
get settings() {
return this._settings;
}
set settings(value) {
this._settings = value;
}
get items() {
return this._items;
}
get actions() {
return this._actions;
}
get value() {
return this._value;
}
get data() {
return undefined;
}
renderBody() {
return undefined;
}
}
class DateType extends GenericIdentifierType {
getSettingsKey() {
return 'DateType';
}
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;
}
isResolvable() {
return false;
}
renderPreview() {
return index.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;
if (renderDynamically)
this._estimatedTypePriority = renderers.length;
else
this._estimatedTypePriority = Parser.getEstimatedPriority(this._value);
}
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.test(this._value);
}
}
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;
}
}
class ORCIDType extends GenericIdentifierType {
constructor() {
super(...arguments);
this.affiliationAt = new Date(Date.now());
this.showAffiliation = true;
}
get data() {
return JSON.stringify(this._orcidInfo.toObject());
}
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'];
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, true));
try {
const familyName = this._orcidInfo.familyName;
if (familyName) {
new FoldableItem(1, 'Family Name', this._orcidInfo.familyName, 'The family name of the person.');
}
}
catch (e) {
console.log('Failed to obtain family name from ORCiD record.', e);
}
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 (index.h("span", { class: 'inline-flex items-center font-mono flex-nowrap align-top' },
index.h("svg", { version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 256 256", class: 'h-5 p-0.5 mr-1 flex-none items-center' },
index.h("style", { type: "text/css" },
`.st0{fill:#A6CE39;}`,
`.st1{fill:#FFFFFF;}`),
index.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" }),
index.h("g", null,
index.h("path", { class: "st1", d: "M86.3,186.2H70.9V79.1h15.4v48.4V186.2z" }),
index.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" }),
index.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" }))),
index.h("span", { class: 'flex-none px-1 items-center' },
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());
}
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 (index.h("span", { class: 'font-mono bg-inherit font-bold rounded-md' }, this._parts.map(element => {
return (index.h("span", { class: 'font-bold font-mono' },
index.h("color-highlight", { text: element.text }),
index.h("span", { class: 'font-mono font-bold text-gray-800 mx-0.5' }, element.nextExists ? '/' : '')));
})));
}
getSettingsKey() {
return 'HandleType';
}
}
class EmailType extends GenericIdentifierType {
getSettingsKey() {
return 'EmailType';
}
hasCorrectFormat() {
const regex = /^(([\w\-.]+@([\w-]+\.)+[\w-]{2,})(\s*,\s*)?)*$/gm;
return regex.test(this.value);
}
init() {
return;
}
isResolvable() {
return false;
}
renderPreview() {
return (index.h("span", { class: 'items-center' }, this.value
.split(new RegExp(/\s*,\s*/))
.filter(email => email.length > 0)
.map(email => {
return (index.h("a", { href: 'mailto:' + email, rel: 'noopener noreferrer', target: "_blank", class: 'items-center inline-flex font-mono text-sm text-blue-400 border border-slate-400 bg-white/60 rounded-md px-1 py-0.5' },
index.h("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", "aria-hidden": "true", viewBox: "0 0 24 24", "stroke-width": "1", stroke: "black", height: "20px", class: 'mr-2' },
index.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" })),
index.h("span", { class: 'ml-2' }, email)));
})));
}
}
class URLType extends GenericIdentifierType {
getSettingsKey() {
return 'URLType';
}
hasCorrectFormat() {
const regex = new RegExp('^http(s)?:(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$');
return regex.test(this.value);
}
init() {
return;
}
isResolvable() {
return false;
}
renderPreview() {
return (index.h("a", { href: this.value, target: "_blank", rel: 'noopener noreferrer', class: 'font-mono text-sm text-blue-400' }, this.value));
}
}
class FallbackType extends GenericIdentifierType {
hasCorrectFormat() {
return true;
}
init() {
return;
}
isResolvable() {
return false;
}
renderPreview() {
return index.h("span", null, this.value);
}
getSettingsKey() {
return 'FallbackType';
}
}
class LocaleType extends GenericIdentifierType {
getSettingsKey() {
return 'LocaleType';
}
hasCorrectFormat() {
const regex = /^([a-zA-Z]{2})(-[A-Z]{2})?$/;
return regex.test(this.value);
}
init() {
return;
}
isResolvable() {
return false;
}
renderPreview() {
return index.h("locale-visualization", { locale: this.value, showFlag: true });
}
}
const renderers = [
{
priority: 0,
key: 'DateType',
constructor: DateType,
},
{
priority: 1,
key: 'ORCIDType',
constructor: ORCIDType,
},
{
priority: 2,
key: 'HandleType',
constructor: HandleType,
},
{
priority: 3,
key: 'EmailType',
constructor: EmailType,
},
{
priority: 4,
key: 'URLType',
constructor: URLType,
},
{
priority: 5,
key: 'LocaleType',
constructor: LocaleType,
},
{
priority: 5,
key: 'FallbackType',
constructor: FallbackType,
},
];
const typeMap = new Map();
const handleMap = new Map();
const unresolvables = new Set();
class Parser {
static getEstimatedPriority(value) {
let priority = 0;
for (let i = 0; i < renderers.length; i++) {
const obj = new renderers[i].constructor(value);
if (obj.hasCorrectFormat()) {
priority = i;
break;
}
}
return priority;
}
static async getBestFit(value, settings) {
var _a;
let bestFit = new renderers[renderers.length - 1].constructor(value);
for (let i = renderers.length - 1; i >= 0; i--) {
const obj = new renderers[i].constructor(value);
if (obj.hasCorrectFormat())
bestFit = obj;
}
try {
const settingsKey = bestFit.getSettingsKey();
const settingsValues = (_a = settings.find(value => value.type === settingsKey)) === null || _a === void 0 ? void 0 : _a.values;
if (settingsValues)
bestFit.settings = settingsValues;
}
catch (e) {
console.warn('Error while adding settings to object:', e);
}
await bestFit.init();
return bestFit;
}
}
const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
let idbProxyableTypes;
let cursorAdvanceMethods;
// This is a function to prevent it throwing up in node environments.
function getIdbProxyableTypes() {
return (idbProxyableTypes ||
(idbProxyableTypes = [
IDBDatabase,
IDBObjectStore,
IDBIndex,
IDBCursor,
IDBTransaction,
]));
}
// This is a function to prevent it throwing up in node environments.
function getCursorAdvanceMethods() {
return (cursorAdvanceMethods ||
(cursorAdvanceMethods = [
IDBCursor.prototype.advance,
IDBCursor.prototype.continue,
IDBCursor.prototype.continuePrimaryKey,
]));
}
const transactionDoneMap = new WeakMap();
const transformCache = new WeakMap();
const reverseTransformCache = new WeakMap();
function promisifyRequest(request) {
const promise = new Promise((resolve, reject) => {
const unlisten = () => {
request.removeEventListener('success', success);
request.removeEventListener('error', error);
};
const success = () => {
resolve(wrap(request.result));
unlisten();
};
const error = () => {
reject(request.error);
unlisten();
};
request.addEventListener('success', success);
request.addEventListener('error', error);
});
// This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
// is because we create many promises from a single IDBRequest.
reverseTransformCache.set(promise, request);
return promise;
}
function cacheDonePromiseForTransaction(tx) {
// Early bail if we've already created a done promise for this transaction.
if (transactionDoneMap.has(tx))
return;
const done = new Promise((resolve, reject) => {
const unlisten = () => {
tx.removeEventListener('complete', complete);
tx.removeEventListener('error', error);
tx.removeEventListener('abort', error);
};
const complete = () => {
resolve();
unlisten();
};
const error = () => {
reject(tx.error || new DOMException('AbortError', 'AbortError'));
unlisten();
};
tx.addEventListener('complete', complete);
tx.addEventListener('error', error);
tx.addEventListener('abort', error);
});
// Cache it for later retrieval.
transactionDoneMap.set(tx, done);
}
let idbProxyTraps = {
get(target, prop, receiver) {
if (target instanceof IDBTransaction) {
// Special handling for transaction.done.
if (prop === 'done')
return transactionDoneMap.get(target);
// Make tx.store return the only store in the transaction, or undefined if there are many.
if (prop === 'store') {
return receiver.objectStoreNames[1]
? undefined
: receiver.objectStore(receiver.objectStoreNames[0]);
}
}
// Else transform whatever we get back.
return wrap(target[prop]);
},
set(target, prop, value) {
target[prop] = value;
return true;
},
has(target, prop) {
if (target instanceof IDBTransaction &&
(prop === 'done' || prop === 'store')) {
return true;
}
return prop in target;
},
};
function replaceTraps(callback) {
idbProxyTraps = callback(idbProxyTraps);
}
function wrapFunction(func) {
// Due to expected object equality (which is enforced by the caching in `wrap`), we
// only create one new func per func.
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
// with real promises, so each advance methods returns a new promise for the cursor object, or
// undefined if the end of the cursor has been reached.
if (getCursorAdvanceMethods().includes(func)) {
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
func.apply(unwrap(this), args);
return wrap(this.request);
};
}
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
return wrap(func.apply(unwrap(this), args));
};
}
function transformCachableValue(value) {
if (typeof value === 'function')
return wrapFunction(value);
// This doesn't return, it just creates a 'done' promise for the transaction,
// which is later returned for transaction.done (see idbObjectHandler).
if (value instanceof IDBTransaction)
cacheDonePromiseForTransaction(value);
if (instanceOfAny(value, getIdbProxyableTypes()))
return new Proxy(value, idbProxyTraps);
// Return the same value back if we're not going to transform it.
return value;
}
function wrap(value) {
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
if (value instanceof IDBRequest)
return promisifyRequest(value);
// If we've already transformed this value before, reuse the transformed value.
// This is faster, but it also provides object equality.
if (transformCache.has(value))
return transformCache.get(value);
const newValue = transformCachableValue(value);
// Not all types are transformed.
// These may be primitive types, so they can't be WeakMap keys.
if (newValue !== value) {
transformCache.set(value, newValue);
reverseTransformCache.set(newValue, value);
}
return newValue;
}
const unwrap = (value) => reverseTransformCache.get(value);
/**
* Open a database.
*
* @param name Name of the database.
* @param version Schema version.
* @param callbacks Additional callbacks.
*/
function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
const request = indexedDB.open(name, version);
const openPromise = wrap(request);
if (upgrade) {
request.addEventListener('upgradeneeded', (event) => {
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
});
}
if (blocked) {
request.addEventListener('blocked', (event) => blocked(
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
event.oldVersion, event.newVersion, event));
}
openPromise
.then((db) => {
if (terminated)
db.addEventListener('close', () => terminated());
if (blocking) {
db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
}
})
.catch(() => { });
return openPromise;
}
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
const writeMethods = ['put', 'add', 'delete', 'clear'];
const cachedMethods = new Map();
function getMethod(target, prop) {
if (!(target instanceof IDBDatabase &&
!(prop in target) &&
typeof prop === 'string')) {
return;
}
if (cachedMethods.get(prop))
return cachedMethods.get(prop);
const targetFuncName = prop.replace(/FromIndex$/, '');
const useIndex = prop !== targetFuncName;
const isWrite = writeMethods.includes(targetFuncName);
if (
// Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
!(isWrite || readMethods.includes(targetFuncName))) {
return;
}
const method = async function (storeName, ...args) {
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
let target = tx.store;
if (useIndex)
target = target.index(args.shift());
// Must reject if op rejects.
// If it's a write operation, must reject if tx.done rejects.
// Must reject with op rejection first.
// Must resolve with op value.
// Must handle both promises (no unhandled rejections)
return (await Promise.all([
target[targetFuncName](...args),
isWrite && tx.done,
]))[0];
};
cachedMethods.set(prop, method);
return method;
}
replaceTraps((oldTraps) => ({
...oldTraps,
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
}));
const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
const methodMap = {};
const advanceResults = new WeakMap();
const ittrProxiedCursorToOriginalProxy = new WeakMap();
const cursorIteratorTraps = {
get(target, prop) {
if (!advanceMethodProps.includes(prop))
return target[prop];
let cachedFunc = methodMap[prop];
if (!cachedFunc) {
cachedFunc = methodMap[prop] = function (...args) {
advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
};
}
return cachedFunc;
},
};
async function* iterate(...args) {
// tslint:disable-next-line:no-this-assignment
let cursor = this;
if (!(cursor instanceof IDBCursor)) {
cursor = await cursor.openCursor(...args);
}
if (!cursor)
return;
cursor = cursor;
const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
// Map this double-proxy back to the original, so other cursor methods work.
reverseTransformCache.set(proxiedCursor, unwrap(cursor));
while (cursor) {
yield proxiedCursor;
// If one of the advancing methods was not called, call continue().
cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
advanceResults.delete(proxiedCursor);
}
}
function isIteratorProp(target, prop) {
return ((prop === Symbol.asyncIterator &&
instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
(prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
}
replaceTraps((oldTraps) => ({
...oldTraps,
get(target, prop, receiver) {
if (isIteratorProp(target, prop))
return iterate;
return oldTraps.get(target, prop, receiver);
},
has(target, prop) {
return isIteratorProp(target, prop) || oldTraps.has(target, prop);
},
}));
const dbName = 'pid-component';
const dbVersion = undefined;
class Database {
constructor() {
this.dbPromise = openDB(dbName, dbVersion, {
upgrade(db) {
const entityStore = db.createObjectStore('entities', {
keyPath: 'value',
});
entityStore.createIndex('by-context', 'context', { unique: false });
const relationStore = db.createObjectStore('relations', {
autoIncrement: true,
});
relationStore.createIndex('by-start', 'start', { unique: false });
relationStore.createIndex('by-description', 'description', { unique: false });
relationStore.createIndex('by-end', 'end', { unique: false });
},
});
}
async addEntity(renderer) {
const context = document.documentURI;
const db = await this.dbPromise;
await db
.add('entities', {
value: renderer.value,
rendererKey: renderer.getSettingsKey(),
context: context,
lastAccess: new Date(),
lastData: renderer.data,
})
.catch(reason => {
if (reason.name === 'ConstraintError') {
console.debug('Entity already exists', reason);
}
else
console.error('Could not add entity', reason);
});
console.debug('added entity', renderer);
const tx = db.transaction('relations', 'readwrite');
const promises = [];
for (const item of renderer.items) {
const relation = {
start: renderer.value,
description: item.keyTitle,
end: item.value,
};
const index = tx.store.index('by-start');
let cursor = await index.openCursor();
while (cursor) {
if (cursor.value.start === relation.start && cursor.value.end === relation.end && cursor.value.description === relation.description) {
return;
}
cursor = await cursor.continue();
}
promises.push(tx.store.add(relation));
}
promises.push(tx.done);
await Promise.all(promises);
console.debug('added relations', promises);
}
async getEntity(value, settings) {
var _a, _b;
try {
const db = await this.dbPromise;
const entity = await db.get('entities', value);
if (entity !== undefined) {
console.debug('Found entity for value in db', entity, value);
const entitySettings = (_a = settings.find(value => value.type === entity.rendererKey)) === null || _a === void 0 ? void 0 : _a.values;
const ttl = entitySettings === null || entitySettings === void 0 ? void 0 : entitySettings.find(value => value.name === 'ttl');
if (ttl != undefined && ttl.value != undefined && (new Date().getTime() - entity.lastAccess.getTime() > ttl.value || ttl.value === 0)) {
console.log('TTL expired! Deleting entry in db', ttl.value, new Date().getTime() - entity.lastAccess.getTime());
await this.deleteEntity(value);
}
else {
console.log('TTL not expired