Integrate Wikidata card for music
authorDan Scott <dscott@laurentian.ca>
Tue, 23 May 2017 03:26:28 +0000 (23:26 -0400)
committerDan Scott <dscott@laurentian.ca>
Fri, 25 May 2018 21:25:46 +0000 (17:25 -0400)
Wikidata music infocard with Wikipedia description

Signed-off-by: Dan Scott <dscott@laurentian.ca>
Open-ILS/src/templates/opac/parts/js.tt2
Open-ILS/web/js/ui/default/opac/wikidata_music_card.js [new file with mode: 0644]

index 01fb9f9..e1fb826 100644 (file)
 [% END; # use_autosuggest %]
 
 [% INCLUDE "opac/parts/acjs.tt2" IF ctx.page == 'record' %]
+[% IF ctx.page == 'record' %]
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/opac/wikidata_music_card.js"></script>
+[% END %]
 [% IF ctx.page == 'advanced' %]
 <script type="text/javascript" 
     src="[% ctx.media_prefix %]/js/ui/default/opac/copyloc.js[% ctx.cache_key %]"></script>
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 (file)
index 0000000..eac375f
--- /dev/null
@@ -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 <https://en.wikipedia.org/> . ' +
+        '} ' +
+        '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';
+  }
+})()