Skip to content

Commit

Permalink
started support for OpenAlex
Browse files Browse the repository at this point in the history
SQUASHED: AUTO-COMMIT-src-client-literature.js,AUTO-COMMIT-src-client-preferences.js,AUTO-COMMIT-src-client-protocols-alex.js,AUTO-COMMIT-src-client-protocols-scholar.js,AUTO-COMMIT-src-components-tools-lively-generic-search.js,AUTO-COMMIT-test-client-literature-alex-example.js,AUTO-COMMIT-test-client-literature-alex-work-example.js,AUTO-COMMIT-test-client-literature-alex-work-example.json,AUTO-COMMIT-test-client-literature-test.js,
  • Loading branch information
JensLincke committed Mar 5, 2025
1 parent 3d6d611 commit 71de294
Show file tree
Hide file tree
Showing 7 changed files with 1,958 additions and 5 deletions.
93 changes: 93 additions & 0 deletions src/client/literature.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {pt} from "src/client/graphics.js"
import toTitleCase from "src/external/title-case.js"
import moment from "src/external/moment.js"

import Preferences from 'src/client/preferences.js'

function specialInspect(target, contentNode, inspector, normal) {
inspector.renderObjectdProperties(contentNode, target)
Expand Down Expand Up @@ -430,8 +431,87 @@ export class Paper {
}


export class AlexAuthor {

constructor(value) {
this.value = value
}

get name() {
return this.value.author.display_name // "Original author name"
}

get id() {
return this.value.author.id
}

livelyInspect(contentNode, inspector, normal) {
specialInspect(this, contentNode, inspector, normal)
}
}

export class AlexPaper extends Paper {


get authors() {
return (this.value.authorships || []).map(ea => new AlexAuthor(ea))
}


get year() {
return this.value.publication_year
}

get doi() {
return this.value.doi
}


get bibtexType() {
// https://docs.openalex.org/api-entities/works/work-object#type
var type = this.value.type
switch(type) {
case "article": return "article"; // TODO distinguish conference article from journal?
case "book-chapter": return "article";
case "dataset": return "misc"
case "preprint": return "misc"
case "dissertation": return "phdthesis";
case "book": return "book";
case "review": return "misc"
case "paratext":return "misc"
case "libguides":return "misc"
case "letter":return "misc"
case "other":return "misc"
case "reference-entry":return "misc"
case "report":return "misc"
case "editorial":return "misc"
case "peer-review":return "misc"
case "erratum":return "misc"
case "standard":return "misc"
case "grant":return "misc"
case "supplementary-materials":return "misc"
}
return "misc"
}

get booktitle() {
return this.value.primary_location.source.display_name
}

get keywords() {
return [] // #TODO
}




}

export default class Literature {

static useOpenAlex() {
return Preferences.get("UseOpenAlex")
}

static async ensureCache() {
if (this.isLoadingCache) {
Expand Down Expand Up @@ -573,6 +653,19 @@ export default class Literature {

return db
}

static get alexdb() {
var db = new Dexie("openalex");

db.version(1).stores({
papers: 'alexid,doi,authors,year,title,key,keywords,booktitle',
}).upgrade(function () {
})


return db
}

}


Expand Down
1 change: 1 addition & 0 deletions src/client/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default class Preferences {
AILukasExperiment: {default: false, short: "AI Lukas Experiment"},
DisableBabelCaching: {default: false, short: "Disable babel transpile caching"},
SemanticScholarAuth: {default: false, short: "use Semantic Scholar API key"},
UseOpenAlex: {default: true, short: "use OpenAlex for Literature"},
}
}

Expand Down
114 changes: 114 additions & 0 deletions src/client/protocols/alex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Scheme } from "src/client/poid.js";
import PolymorphicIdentifier from "src/client/poid.js";
import focalStorage from "src/external/focalStorage.js";

import {Author, Paper} from "src/client/literature.js"

import Preferences from 'src/client/preferences.js';


import _ from 'src/external/lodash/lodash.js';
/*MD
# Alex Scholar API
MD*/``


export default class OpenAlexScheme extends Scheme {

get scheme() {
return "alex";
}

resolve() {
return true;
}

response(content, contentType = "text/html") {
return new Response(content, {
headers: {
"content-type": contentType
},
status: 200
});
}

notfound(content, contentType = "text/html") {
return new Response(content, {
headers: {
"content-type": contentType
},
status: 303
});
}


get baseURL() {
return "https://api.openalex.org/"
}


async GET(options) {
var m = this.url.match(new RegExp(this.scheme + "\:\/\/([^/]*)/(.*)"))
var mode = m[1]
var query = m[2];
if (query.length < 2) return this.response(`{"error": "query to short"}`);

if (mode === "browse") {
if (query.match("work/")) {
let id = query.replace(/paper\//,"")
return this.response(`<literature-paper openalexid="${id}"><literature-paper>`);
} else {
return this.response(`query not supported: ` + query);
}
}

var url = this.baseURL + query

var headers = new Headers({})
var content = await fetch(url, {
method: "GET",
headers: headers
}).then(r => r.text())

return this.response(content);
}

async POST(options) {
// #TODO get rid of duplication with GET
var m = this.url.match(new RegExp(this.scheme + "\:\/\/([^/]*)/(.*)"))
var mode = m[1]
var query = m[2];
if (query.length < 2) return this.response(`{"error": "query to short"}`);

var url = this.baseURL + query


var headers = new Headers({})
var content = await fetch(url, {
method: "POST",
headers: headers,
body: options.body
}).then(r => r.text())

return this.response(content);
}


async OPTIONS(options) {
var content = JSON.stringify({}, undefined, 2);
return new Response(content, {
headers: {
"content-type": "application/json"
},
status: 200
});
}

}

PolymorphicIdentifier.register(OpenAlexScheme);

// import Tracing from "src/client/tracing.js"
// Tracing.traceClass(Paper)
8 changes: 4 additions & 4 deletions src/client/protocols/scholar.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ export default class SemanticScholarScheme extends Scheme {
}

var url = this.baseURL + query


var headers = new Headers({})
if (Preferences.get("SemanticScholarAuth")) {
var key = await SemanticScholarScheme.ensureSubscriptionKey() // maybe only get... ?
var headers = new Headers({})
if (key) {
headers.set("x-api-key", key)
}
Expand Down Expand Up @@ -160,10 +160,10 @@ fetch("scholar://data/paper/batch?fields=referenceCount,citationCount,title", {
if (query.length < 2) return this.response(`{"error": "query to short"}`);

var url = this.baseURL + query


var headers = new Headers({})
if (Preferences.get("SemanticScholarAuth")) {
var key = await SemanticScholarScheme.ensureSubscriptionKey() // maybe only get... ?
var headers = new Headers({})
if (key) {
headers.set("x-api-key", key)
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/tools/lively-generic-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ export default class LivelyGenericSearch extends Morph {
var pattern = this.input.value;
var search = new RegExp(pattern, 'ig');

var root = lively4url + "/"
const filteredFiles = (await this.files).filter(file => {
if (file.url.startsWith(lively4url)) {
if (file.url.startsWith(root)) {
const relativePath = file.url.replace(/.*\//ig, '');
return relativePath.match(search);
} else {
Expand Down
Loading

0 comments on commit 71de294

Please sign in to comment.