From 68ecbe2ab87d89ba5e35ff5afc24427d2c3e6a5e Mon Sep 17 00:00:00 2001 From: Dan Scott Date: Mon, 22 May 2017 23:26:28 -0400 Subject: [PATCH] Integrate Wikidata card for music Wikidata music infocard with Wikipedia description Signed-off-by: Dan Scott --- Open-ILS/src/templates/opac/parts/js.tt2 | 3 + .../web/js/ui/default/opac/wikidata_music_card.js | 255 +++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 Open-ILS/web/js/ui/default/opac/wikidata_music_card.js diff --git a/Open-ILS/src/templates/opac/parts/js.tt2 b/Open-ILS/src/templates/opac/parts/js.tt2 index 01fb9f943d..e1fb826f68 100644 --- a/Open-ILS/src/templates/opac/parts/js.tt2 +++ b/Open-ILS/src/templates/opac/parts/js.tt2 @@ -116,6 +116,9 @@ [% END; # use_autosuggest %] [% INCLUDE "opac/parts/acjs.tt2" IF ctx.page == 'record' %] +[% IF ctx.page == 'record' %] + +[% END %] [% IF ctx.page == 'advanced' %] diff --git a/Open-ILS/web/js/ui/default/opac/wikidata_music_card.js b/Open-ILS/web/js/ui/default/opac/wikidata_music_card.js new file mode 100644 index 0000000000..eac375f4da --- /dev/null +++ b/Open-ILS/web/js/ui/default/opac/wikidata_music_card.js @@ -0,0 +1,255 @@ +;(function () { + /** + * Display an infocard with data pulled from Wikidata + * + * Copyright 2017 Dan Scott (dan@coffeecode.net), Laurentian University + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * Based on the primary contributor's name, search Wikidata via SPARQL for + * musicians or bands using an exact string match of the label or alias, + * and retrieve the data of interest. + * + * Current limitations: + * * Only activated for musical recordings, to avoid ambiguous results + * * Hard-coded for English + * + * To use this in your catalogue, you will need to modify a few of the + * CSS queries to find the performer's name and identify appropriate + * locations for inserting the clickable note icon and the infocard. + */ + var icon_node; + var note; + var wd; + + /* Ensure this is a musical recording */ + if (document.getElementById('canvas_main').getAttribute('typeof').indexOf('MusicAlbum') > -1) { + /* Performer's name - used to find a matching item in wikidata */ + var performer_nodes = document.querySelectorAll('span[resource^="#schemacontrib"]'); + for (var node of performer_nodes) { + /* Insert clickable icon here */ + icon_node = node.querySelector('span[property="description"]'); + if (icon_node) { + note = document.createElement('span'); + note.class = 'wikidata'; + note.innerText = ' ♪'; + note.addEventListener('click', perform, { once: true }); + icon_node.insertBefore(note, null); + } + } + } + + function getContribName(node) { + var entity_name = node.querySelector('span[property="name"]').textContent.trim(); + var lastchar = entity_name[entity_name.length - 1]; + if (lastchar === '.' || lastchar === ',') { + entity_name = entity_name.slice(0, -1); + } + var inverse = entity_name.split(','); + if (inverse.length === 2) { + entity_name = inverse[1].trim() + " " + inverse[0].trim(); + } + return entity_name; + } + + function perform(e) { + var entity_name = getContribName(this.parentNode.parentNode); + findPerformer(icon_node, entity_name); + e.preventDefault(); + e.stopPropagation(); + } + + function toggleMode(el) { + wd = document.getElementById('magic_musician'); + wd.style.display = 'inherit'; + el.addEventListener('click', toggle); + } + + function toggle(e) { + wd = document.getElementById('magic_musician'); + + if (wd.style.display === 'inherit') { + wd.style.display = 'none'; + } else { + wd.style.display = 'inherit'; + } + e.preventDefault(); + e.stopPropagation(); + } + + function findPerformer(icon_node, entity_name) { + var url = 'https://query.wikidata.org/sparql'; + var query = 'SELECT DISTINCT ?item ?itemLabel ?itemDescription ?image ?birthPlace ?birthPlaceLabel ' + + '?website ?musicbrainz ?songKick ?twitter ?facebook ?wplink ' + + 'WHERE { ' + + '?item rdfs:label|skos:altLabel|wdt:P1449 "' + entity_name + '"@en . ' + + '{ ?item wdt:P31/wdt:P279* wd:Q215380 . } ' + // band + 'UNION ' + + '{ ?item wdt:P106/wdt:P279* wd:Q639669 . } ' + // musician + 'OPTIONAL { ?item wdt:P3478 ?songKick } . ' + + 'OPTIONAL { ?item wdt:P19 ?birthPlace } . ' + + 'OPTIONAL { ?item wdt:P856 ?website } . ' + + 'OPTIONAL { ?item wdt:P434 ?musicbrainz } . ' + + 'OPTIONAL { ?item wdt:P2002 ?twitter } . ' + + 'OPTIONAL { ?item wdt:P2013 ?facebook } . ' + + 'OPTIONAL { ?item wdt:P18 ?image } . ' + + 'OPTIONAL { ' + + '?wplink schema:about ?item . ' + + '?wplink schema:inLanguage "en" . ' + + '?wplink schema:isPartOf . ' + + '} ' + + 'SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } ' + + '} ' + + 'LIMIT 10'; + var q = '?query=' + encodeURIComponent(query); + + var req = new window.XMLHttpRequest(); + req.open('GET', url + q); + req.setRequestHeader('Accept', 'application/sparql-results+json'); + if (req.responseType && (req.responseType = 'json')) { + req.onload = function (evt) { + var r = req.response.results.bindings[0]; + if (r !== undefined) { + generateCard(icon_node, r); + toggleMode(note); + // console.log(r); + } + } + } else { + // IE 10/11 + req.onload = function (evt) { + var r = JSON.parse(req.responseText).results.bindings[0]; + if (r !== undefined) { + generateCard(icon_node, r); + toggleMode(note); + // console.log(r); + } + } + } + req.send(); + } + + function generateCard(icon_node, r) { + var auth_div = document.querySelector('div[class="rdetail_authors_div"]'); + var musician = document.createElement('div'); + musician.id = 'magic_musician'; + musician.style.padding = '0.5em 1em 1em 1em'; + musician.style.border = 'thin blue solid'; + musician.style.overflow = 'hidden'; + + addWDValue('itemLabel', null, null, false); + + if (r.hasOwnProperty('image')) { + var img = document.createElement('img'); + img.src = r.image.value.replace('http:', 'https:'); + img.style.float = 'left'; + img.style.width = '150px'; + img.style['margin-right'] = '1em'; + musician.appendChild(img); + } + + if (r.hasOwnProperty('itemDescription')) { + var description = r.itemDescription.value; + var wdd = document.createElement('div'); + var wddl = document.createElement('label'); + wddl.innerText = description; + wdd.appendChild(wddl); + musician.appendChild(wdd); + } + + // Get the description from Wikipedia + if (r.hasOwnProperty('wplink')) { + wpapi = 'https://en.wikipedia.org/w/api.php?origin=*&format=json&action=query&prop=extracts&explaintext=true&exintro=true&titles='; + var wplink = r.wplink.value; + // Strip the WP title from the link + wptitle = wplink.substring(wplink.lastIndexOf('/') + 1); + + var req = new window.XMLHttpRequest(); + req.open('GET', wpapi + wptitle); + req.onload = function (evt) { + var r = JSON.parse(req.response); + var k = Object.getOwnPropertyNames(r.query.pages)[0]; + var description = r.query.pages[k].extract; + + // Just the first line will do + var linefeed = description.indexOf('\n'); + var period = description.indexOf('. '); + if (linefeed > 0) { + description = description.substring(0, linefeed); + } else if (period > 0) { + description = description.substring(0, period + 1); + } + var wdd = document.createElement('p'); + var wpa = document.createElement('a'); + wpa.href = wplink; + wpa.innerText = '(Wikipedia)'; + wdd.innerText = description + ' '; + wdd.appendChild(wpa); + musician.appendChild(wdd); + } + req.send(); + } + + function addWDValue(property, propertyLabel, label, isLink, linkFormatter) { + var value = ''; + if (r.hasOwnProperty(property)) { + value = r[property].value; + var valueDiv = document.createElement('div'); + var labelText = value; + if (!propertyLabel && !label && !isLink) { + var strong = document.createElement('strong'); + valueDiv.appendChild(strong); + strong.innerText = labelText; + } + if (propertyLabel) { + var valueLabel = document.createElement('label'); + valueLabel.innerText = propertyLabel; + valueDiv.appendChild(valueLabel); + } + if (label) { + if (r.hasOwnProperty(label)) { + labelText = r[label].value; + } else { + labelText = label; + } + } + if (isLink) { + var valueLink = document.createElement('a'); + if (linkFormatter) { + valueLink.href = linkFormatter(value); + } else { + valueLink.href = value; + } + valueLink.innerText = labelText; + valueDiv.appendChild(valueLink); + } + musician.appendChild(valueDiv); + } + } + + addWDValue('birthPlace', 'Birth place: ', 'birthPlaceLabel', true); + addWDValue('website', 'Web site: ', null, true); + addWDValue('musicbrainz', null, 'Discography (Musicbrainz)', true, function(value) { return 'https://musicbrainz.org/artist/' + value }); + addWDValue('songKick', null, 'Tour dates (Songkick)', true, function(value) { return 'http://www.songkick.com/artists/' + value }); + addWDValue('twitter', 'Twitter: ', null, true, function(value) { return 'https://twitter.com/' + value }); + addWDValue('facebook', 'Facebook: ', null, true, function(value) { return 'https://www.facebook.com/' + value }); + + var uri = r.item.value; + var wd = document.createElement('div'); + var wdl = document.createElement('label'); + wdl.innerText = 'Edit on Wikidata: '; + var wdv = document.createElement('a'); + wdv.href = uri; + wdv.innerText = uri.substr(uri.lastIndexOf('/') + 1); + wd.appendChild(wdl); + wd.appendChild(wdv); + musician.appendChild(wd); + + auth_div.appendChild(musician); + /* Append the Wikidata infocard to the page */ + document.getElementById('rdetail_image_div').style.clear = 'both'; + } +})() -- 2.11.0