mirador_rails 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f5ef99bea15b813081f91b4bae2283f1911641f
4
- data.tar.gz: d4e415082b5ac2e0bbf75c0c33e4465454ca3c44
3
+ metadata.gz: 6e15536d3e44bfc8904735662985d0b0d79c8679
4
+ data.tar.gz: 633ddac8972c2bd82960c67f5d09300b64e3dea2
5
5
  SHA512:
6
- metadata.gz: 50d989244ab2bfeb270f0d7eb01c3537d8a98e9d45ddcc972abc5f28bb3e5b674c655c60adcfd31f8c3b665bf13ad490a1f66a65e9672ee00d28979ff7a8030c
7
- data.tar.gz: b9f59ae584848f3e3f4a765f773461cb186a004a3b249a6e28142283a940c04f24dfcb2a77cb48155b433d8de4268cc7e8b8498965073da92c45f7b90147a762
6
+ metadata.gz: b3f4c8c362d730eb39cab84fe96ebf581fe03ca86fbeeb3c5fbeb47fdde8676b032d030413014b78c08139a0fb32f0dcc6c8dcfd93866b7f6157ca63705fb6ab
7
+ data.tar.gz: 4255dbe0fdc16d503371c99830faa57f2a3508611b3a0a7f10ea52e079124621bceaa5bca21f2b4acaff43274bebed3c1d560f73aa2ef477b5e8d346d2b06b52
@@ -1,3 +1,3 @@
1
1
  module MiradorRails
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -1,5 +1,5 @@
1
- //! mirador 2.3.0
2
- //! Built on 2017-03-24
1
+ //! mirador 2.4.0
2
+ //! Built on 2017-04-10
3
3
  /*! jQuery Migrate v3.0.0 | (c) jQuery Foundation and other contributors | jquery.org/license */
4
4
  "undefined"==typeof jQuery.migrateMute&&(jQuery.migrateMute=!0),function(a,b){"use strict";function c(c){var d=b.console;e[c]||(e[c]=!0,a.migrateWarnings.push(c),d&&d.warn&&!a.migrateMute&&(d.warn("JQMIGRATE: "+c),a.migrateTrace&&d.trace&&d.trace()))}function d(a,b,d,e){Object.defineProperty(a,b,{configurable:!0,enumerable:!0,get:function(){return c(e),d}})}a.migrateVersion="3.0.0",function(){var c=b.console&&b.console.log&&function(){b.console.log.apply(b.console,arguments)},d=/^[12]\./;c&&(a&&!d.test(a.fn.jquery)||c("JQMIGRATE: jQuery 3.0.0+ REQUIRED"),a.migrateWarnings&&c("JQMIGRATE: Migrate plugin loaded multiple times"),c("JQMIGRATE: Migrate is installed"+(a.migrateMute?"":" with logging active")+", version "+a.migrateVersion))}();var e={};a.migrateWarnings=[],void 0===a.migrateTrace&&(a.migrateTrace=!0),a.migrateReset=function(){e={},a.migrateWarnings.length=0},"BackCompat"===document.compatMode&&c("jQuery is not compatible with Quirks Mode");var f=a.fn.init,g=a.isNumeric,h=a.find,i=/\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/,j=/\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g;a.fn.init=function(a){var b=Array.prototype.slice.call(arguments);return"string"==typeof a&&"#"===a&&(c("jQuery( '#' ) is not a valid selector"),b[0]=[]),f.apply(this,b)},a.fn.init.prototype=a.fn,a.find=function(a){var b=Array.prototype.slice.call(arguments);if("string"==typeof a&&i.test(a))try{document.querySelector(a)}catch(d){a=a.replace(j,function(a,b,c,d){return"["+b+c+'"'+d+'"]'});try{document.querySelector(a),c("Attribute selector with '#' must be quoted: "+b[0]),b[0]=a}catch(e){c("Attribute selector with '#' was not fixed: "+b[0])}}return h.apply(this,b)};var k;for(k in h)Object.prototype.hasOwnProperty.call(h,k)&&(a.find[k]=h[k]);a.fn.size=function(){return c("jQuery.fn.size() is deprecated; use the .length property"),this.length},a.parseJSON=function(){return c("jQuery.parseJSON is deprecated; use JSON.parse"),JSON.parse.apply(null,arguments)},a.isNumeric=function(b){function d(b){var c=b&&b.toString();return!a.isArray(b)&&c-parseFloat(c)+1>=0}var e=g(b),f=d(b);return e!==f&&c("jQuery.isNumeric() should not be called on constructed objects"),f},d(a,"unique",a.uniqueSort,"jQuery.unique is deprecated, use jQuery.uniqueSort"),d(a.expr,"filters",a.expr.pseudos,"jQuery.expr.filters is now jQuery.expr.pseudos"),d(a.expr,":",a.expr.pseudos,'jQuery.expr[":"] is now jQuery.expr.pseudos');var l=a.ajax;a.ajax=function(){var a=l.apply(this,arguments);return a.promise&&(d(a,"success",a.done,"jQXHR.success is deprecated and removed"),d(a,"error",a.fail,"jQXHR.error is deprecated and removed"),d(a,"complete",a.always,"jQXHR.complete is deprecated and removed")),a};var m=a.fn.removeAttr,n=a.fn.toggleClass,o=/\S+/g;a.fn.removeAttr=function(b){var d=this;return a.each(b.match(o),function(b,e){a.expr.match.bool.test(e)&&(c("jQuery.fn.removeAttr no longer sets boolean properties: "+e),d.prop(e,!1))}),m.apply(this,arguments)},a.fn.toggleClass=function(b){return void 0!==b&&"boolean"!=typeof b?n.apply(this,arguments):(c("jQuery.fn.toggleClass( boolean ) is deprecated"),this.each(function(){var c=this.getAttribute&&this.getAttribute("class")||"";c&&a.data(this,"__className__",c),this.setAttribute&&this.setAttribute("class",c||b===!1?"":a.data(this,"__className__")||"")}))};var p=!1;a.swap&&a.each(["height","width","reliableMarginRight"],function(b,c){var d=a.cssHooks[c]&&a.cssHooks[c].get;d&&(a.cssHooks[c].get=function(){var a;return p=!0,a=d.apply(this,arguments),p=!1,a})}),a.swap=function(a,b,d,e){var f,g,h={};p||c("jQuery.swap() is undocumented and deprecated");for(g in b)h[g]=a.style[g],a.style[g]=b[g];f=d.apply(a,e||[]);for(g in b)a.style[g]=h[g];return f};var q=a.data;a.data=function(b,d,e){var f;return d&&d!==a.camelCase(d)&&(f=a.hasData(b)&&q.call(this,b),f&&d in f)?(c("jQuery.data() always sets/gets camelCased names: "+d),arguments.length>2&&(f[d]=e),f[d]):q.apply(this,arguments)};var r=a.Tween.prototype.run;a.Tween.prototype.run=function(b){a.easing[this.easing].length>1&&(c('easing function "jQuery.easing.'+this.easing.toString()+'" should use only first argument'),a.easing[this.easing]=a.easing[this.easing].bind(a.easing,b,this.options.duration*b,0,1,this.options.duration)),r.apply(this,arguments)};var s=a.fn.load,t=a.event.fix;a.event.props=[],a.event.fixHooks={},a.event.fix=function(b){var d,e=b.type,f=this.fixHooks[e],g=a.event.props;if(g.length)for(c("jQuery.event.props are deprecated and removed: "+g.join());g.length;)a.event.addProp(g.pop());if(f&&!f._migrated_&&(f._migrated_=!0,c("jQuery.event.fixHooks are deprecated and removed: "+e),(g=f.props)&&g.length))for(;g.length;)a.event.addProp(g.pop());return d=t.call(this,b),f&&f.filter?f.filter(d,b):d},a.each(["load","unload","error"],function(b,d){a.fn[d]=function(){var a=Array.prototype.slice.call(arguments,0);return"load"===d&&"string"==typeof a[0]?s.apply(this,a):(c("jQuery.fn."+d+"() is deprecated"),a.splice(0,0,d),arguments.length?this.on.apply(this,a):(this.triggerHandler.apply(this,a),this))}}),a(function(){a(document).triggerHandler("ready")}),a.event.special.ready={setup:function(){this===document&&c("'ready' event is deprecated")}},a.fn.extend({bind:function(a,b,d){return c("jQuery.fn.bind() is deprecated"),this.on(a,null,b,d)},unbind:function(a,b){return c("jQuery.fn.unbind() is deprecated"),this.off(a,null,b)},delegate:function(a,b,d,e){return c("jQuery.fn.delegate() is deprecated"),this.on(b,a,d,e)},undelegate:function(a,b,d){return c("jQuery.fn.undelegate() is deprecated"),1===arguments.length?this.off(a,"**"):this.off(b,a||"**",d)}});var u=a.fn.offset;a.fn.offset=function(){var b,d=this[0],e={top:0,left:0};return d&&d.nodeType?(b=(d.ownerDocument||document).documentElement,a.contains(b,d)?u.apply(this,arguments):(c("jQuery.fn.offset() requires an element connected to a document"),e)):(c("jQuery.fn.offset() requires a valid DOM element"),e)};var v=a.param;a.param=function(b,d){var e=a.ajaxSettings&&a.ajaxSettings.traditional;return void 0===d&&e&&(c("jQuery.param() no longer uses jQuery.ajaxSettings.traditional"),d=e),v.call(this,b,d)};var w=a.fn.andSelf||a.fn.addBack;a.fn.andSelf=function(){return c("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)};var x=a.Deferred,y=[["resolve","done",a.Callbacks("once memory"),a.Callbacks("once memory"),"resolved"],["reject","fail",a.Callbacks("once memory"),a.Callbacks("once memory"),"rejected"],["notify","progress",a.Callbacks("memory"),a.Callbacks("memory")]];a.Deferred=function(b){var d=x(),e=d.promise();return d.pipe=e.pipe=function(){var b=arguments;return c("deferred.pipe() is deprecated"),a.Deferred(function(c){a.each(y,function(f,g){var h=a.isFunction(b[f])&&b[f];d[g[1]](function(){var b=h&&h.apply(this,arguments);b&&a.isFunction(b.promise)?b.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[g[0]+"With"](this===e?c.promise():this,h?[b]:arguments)})}),b=null}).promise()},b&&b.call(d,d),d}}(jQuery,window);
5
5
  /*! jQuery UI - v1.12.1 - 2016-09-14
@@ -6966,7 +6966,10 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
6966
6966
  //control what is available in the side panel. if "sidePanel" is false, these options won't be applied
6967
6967
  "sidePanelOptions" : {
6968
6968
  "toc" : true,
6969
- "annotations" : false
6969
+ "annotations" : false,
6970
+ "tocTabAvailable": true,
6971
+ // "layersTabAvailable": true,
6972
+ "searchTabAvailable": false,
6970
6973
  },
6971
6974
  "sidePanelVisible" : true, //whether or not to make the side panel visible in this window on load. This setting is dependent on sidePanel being true
6972
6975
  "overlay" : true, //whether or not to make the metadata overlay available/visible in this window
@@ -7226,6 +7229,7 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
7226
7229
  // add main menu
7227
7230
  if (showMainMenu) {
7228
7231
  this.mainMenu = new $.MainMenu({ appendTo: this.element, state: this.state, eventEmitter: this.eventEmitter });
7232
+ this.eventEmitter.publish('mainMenuInitialized');
7229
7233
  }
7230
7234
 
7231
7235
  // add viewer area
@@ -8965,8 +8969,19 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
8965
8969
 
8966
8970
  this.request.done(function(jsonLd) {
8967
8971
  _this.jsonLd = jsonLd;
8972
+ _this.buildCanvasMap();
8968
8973
  });
8969
8974
  },
8975
+ buildCanvasMap: function() {
8976
+ var _this = this;
8977
+ this.canvasMap = {};
8978
+
8979
+ if (this.getCanvases()) {
8980
+ this.getCanvases().forEach(function(canvas) {
8981
+ _this.canvasMap[canvas['@id']] = canvas;
8982
+ });
8983
+ }
8984
+ },
8970
8985
  initFromInfoJson: function(infoJsonUrl) {
8971
8986
  var _this = this;
8972
8987
  this.request = jQuery.ajax({
@@ -9003,10 +9018,10 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
9003
9018
  } else if (canvas.thumbnail.hasOwnProperty('service')) {
9004
9019
  service = canvas.thumbnail.service;
9005
9020
  if(service.hasOwnProperty('profile')) {
9006
- compliance = $.Iiif.getComplianceLevelFromProfile(service.profile);
9021
+ compliance = $.Iiif.getComplianceLevelFromProfile(service.profile);
9007
9022
  }
9008
9023
  if(compliance === 0){
9009
- // don't change existing behaviour unless compliance is explicitly 0
9024
+ // don't change existing behaviour unless compliance is explicitly 0
9010
9025
  thumbnailUrl = canvas.thumbnail['@id'];
9011
9026
  } else {
9012
9027
  // Get the IIIF Image API via the @context
@@ -9040,7 +9055,7 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
9040
9055
  },
9041
9056
  getCanvases : function() {
9042
9057
  var _this = this;
9043
- return _this.jsonLd.sequences[0].canvases;
9058
+ return _this.jsonLd.sequences && _this.jsonLd.sequences[0].canvases;
9044
9059
  },
9045
9060
  getAnnotationsListUrls: function(canvasId) {
9046
9061
  var _this = this;
@@ -9109,6 +9124,46 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
9109
9124
  };
9110
9125
 
9111
9126
  return dummyManifest;
9127
+ },
9128
+ getSearchWithinService: function(){
9129
+ var _this = this;
9130
+ var serviceProperty = _this.jsonLd.service;
9131
+ var service = [];
9132
+ if (serviceProperty === undefined){
9133
+ service = null;
9134
+ }
9135
+ else if (serviceProperty.constructor === Array){
9136
+ for (var i = 0; i < serviceProperty.length; i++){
9137
+ //TODO: should we be filtering search by context
9138
+ if (serviceProperty[i]["@context"] === "http://iiif.io/api/search/0/context.json" ||
9139
+ serviceProperty[i]["@context"] === "http://iiif.io/api/search/1/context.json") {
9140
+ //returns the first service object with the correct context
9141
+ service.push(serviceProperty[i]);
9142
+ }
9143
+ }
9144
+ }
9145
+ else if (_this.jsonLd.service["@context"] === "http://iiif.io/api/search/0/context.json" ||
9146
+ serviceProperty["@context"] === "http://iiif.io/api/search/1/context.json"){
9147
+ service.push(_this.jsonLd.service);
9148
+ }
9149
+ else {
9150
+ //no service object with the right context is found
9151
+ service = null;
9152
+ }
9153
+ return service;
9154
+ },
9155
+
9156
+ /**
9157
+ * Get the label of the a canvas by ID
9158
+ * @param {[type]} canvasId ID of desired canvas
9159
+ * @return {[type]} string
9160
+ */
9161
+ getCanvasLabel: function(canvasId) {
9162
+ console.assert(canvasId && canvasId !== '', "No canvasId was specified.");
9163
+ if (this.canvasMap && canvasId.indexOf('#') >= 0) {
9164
+ var canvas = this.canvasMap[canvasId.split('#')[0]];
9165
+ return canvas ? canvas.label : undefined;
9166
+ }
9112
9167
  }
9113
9168
  };
9114
9169
 
@@ -9675,11 +9730,11 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
9675
9730
  //if it is a manifest annotation, don't allow editing or deletion
9676
9731
  //otherwise, check annotation in endpoint
9677
9732
  var showUpdate = false;
9678
- if (annotation.endpoint !== 'manifest') {
9733
+ if (typeof annotation.endpoint !== 'undefined' && annotation.endpoint !== 'manifest') {
9679
9734
  showUpdate = annotation.endpoint.userAuthorize('update', annotation);
9680
9735
  }
9681
9736
  var showDelete = false;
9682
- if (annotation.endpoint !== 'manifest') {
9737
+ if (typeof annotation.endpoint !== 'undefined' && annotation.endpoint !== 'manifest') {
9683
9738
  showDelete = annotation.endpoint.userAuthorize('delete', annotation);
9684
9739
  }
9685
9740
  htmlAnnotations.push({
@@ -14457,7 +14512,8 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
14457
14512
  "BookView" : "fa fa-columns fa-lg fa-fw",
14458
14513
  "ScrollView" : "fa fa-ellipsis-h fa-lg fa-fw",
14459
14514
  "ThumbnailsView" : "fa fa-th fa-lg fa-rotate-90 fa-fw"
14460
- }
14515
+ },
14516
+ userButtons: null
14461
14517
  }, options);
14462
14518
 
14463
14519
  this.init();
@@ -14556,6 +14612,7 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
14556
14612
  }
14557
14613
  templateData.currentFocusClass = _this.iconClasses[_this.viewType];
14558
14614
  templateData.showFullScreen = _this.fullScreen;
14615
+ templateData.userButtons = _this.userButtons;
14559
14616
  _this.element = jQuery(this.template(templateData)).appendTo(_this.appendTo);
14560
14617
  this.element.find('.manifest-info .mirador-tooltip').each(function() {
14561
14618
  jQuery(this).qtip({
@@ -14713,7 +14770,17 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
14713
14770
  }));
14714
14771
 
14715
14772
  _this.events.push(_this.eventEmitter.subscribe('SET_CURRENT_CANVAS_ID.' + this.id, function(event, canvasID) {
14716
- _this.setCurrentCanvasID(canvasID);
14773
+ if (typeof canvasID === "string") {
14774
+ _this.setCurrentCanvasID(canvasID);
14775
+ } else {
14776
+ if (_this.canvasID !== canvasID.canvasID) {
14777
+ // Order is important
14778
+ _this.setNextCanvasBounds(canvasID.bounds);
14779
+ _this.setCurrentCanvasID(canvasID.canvasID);
14780
+ } else {
14781
+ _this.eventEmitter.publish('fitBounds.' + _this.id, canvasID.bounds);
14782
+ }
14783
+ }
14717
14784
  }));
14718
14785
 
14719
14786
  _this.events.push(_this.eventEmitter.subscribe('REMOVE_CLASS.' + this.id, function(event, className) {
@@ -14911,7 +14978,8 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
14911
14978
  var _this = this,
14912
14979
  tocAvailable = _this.sidePanelOptions.toc,
14913
14980
  annotationsTabAvailable = _this.sidePanelOptions.annotations,
14914
- layersTabAvailable = _this.sidePanelOptions.layers,
14981
+ layersTabAvailable = _this.sidePanelOptions.layersTabAvailable,
14982
+ searchTabAvailable = _this.sidePanelOptions.searchTabAvailable,
14915
14983
  hasStructures = true;
14916
14984
 
14917
14985
  var structures = _this.manifest.getStructures();
@@ -14929,6 +14997,7 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
14929
14997
  canvasID: _this.canvasID,
14930
14998
  layersTabAvailable: layersTabAvailable,
14931
14999
  tocTabAvailable: tocAvailable,
15000
+ searchTabAvailable: searchTabAvailable,
14932
15001
  annotationsTabAvailable: annotationsTabAvailable,
14933
15002
  hasStructures: hasStructures
14934
15003
  });
@@ -15047,7 +15116,7 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15047
15116
  this.updateManifestInfo();
15048
15117
  this.updatePanelsAndOverlay(focusState);
15049
15118
  this.updateSidePanel();
15050
- _this.eventEmitter.publish("focusUpdated");
15119
+ _this.eventEmitter.publish("focusUpdated" + _this.id, focusState);
15051
15120
  _this.eventEmitter.publish("windowUpdated", {
15052
15121
  id: _this.id,
15053
15122
  viewType: _this.viewType,
@@ -15095,10 +15164,13 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15095
15164
  canvasControls: this.canvasControls,
15096
15165
  annotationState : this.canvasControls.annotations.annotationState
15097
15166
  });
15098
- } else {
15099
- var view = this.focusModules.ImageView;
15100
- view.updateImage(canvasID);
15101
- }
15167
+ } else {
15168
+ var view = this.focusModules.ImageView;
15169
+ if (this.boundsToFocusOnNextOpen) {
15170
+ view.boundsToFocusOnNextOpen = this.boundsToFocusOnNextOpen;
15171
+ }
15172
+ view.updateImage(canvasID);
15173
+ }
15102
15174
  this.toggleFocus('ImageView', 'ImageView');
15103
15175
  },
15104
15176
 
@@ -15171,6 +15243,12 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15171
15243
  _this.eventEmitter.publish(('currentCanvasIDUpdated.' + _this.id), canvasID);
15172
15244
  },
15173
15245
 
15246
+ setNextCanvasBounds: function(bounds) {
15247
+ if (bounds) {
15248
+ this.boundsToFocusOnNextOpen = bounds;
15249
+ }
15250
+ },
15251
+
15174
15252
  replaceWindow: function(newSlotAddress, newElement) {
15175
15253
  this.slotAddress = newSlotAddress;
15176
15254
  this.appendTo = newElement;
@@ -15337,6 +15415,9 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15337
15415
  '<div class="window">',
15338
15416
  '<div class="manifest-info">',
15339
15417
  '<div class="window-manifest-navigation">',
15418
+ '{{#if userButtons}}',
15419
+ '{{windowuserbtns userButtons}}',
15420
+ '{{/if}}',
15340
15421
  '<a href="javascript:;" class="mirador-btn mirador-icon-view-type" role="button" title="{{t "viewTypeTooltip"}}" aria-label="{{t "viewTypeTooltip"}}">',
15341
15422
  '<i class="{{currentFocusClass}}"></i>',
15342
15423
  '<i class="fa fa-caret-down"></i>',
@@ -15411,6 +15492,42 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15411
15492
  ].join(''))
15412
15493
  };
15413
15494
 
15495
+ var processUserButtons = function (buttons){
15496
+ return buttons.map(function(button, index){
15497
+ return processUserButton(button);
15498
+ });
15499
+ };
15500
+
15501
+ var processUserButton = function(button){
15502
+ var $a = jQuery('<a>');
15503
+ var $i = jQuery('<i>', {'class': 'fa fa-lg fa-fw'});
15504
+ try {
15505
+ if(!button.iconClass){
15506
+ throw "userButtons must have an iconClass";
15507
+ }
15508
+ // add custom attributes to the button element
15509
+ if(button.attributes){
15510
+ $a.attr(button.attributes);
15511
+ }
15512
+ // add default class to the button element
15513
+ $a.addClass('mirador-btn');
15514
+ // add custom classes to the icon element
15515
+ $i.addClass(button.iconClass);
15516
+ // append the icon element to the button element
15517
+ $a.append($i);
15518
+ return $a.get(0).outerHTML;
15519
+ }catch(error){
15520
+ console && console.error && console.error(error);
15521
+ return '';
15522
+ }
15523
+ };
15524
+
15525
+ $.Handlebars.registerHelper('windowuserbtns', function(userButtons){
15526
+ return new $.Handlebars.SafeString(
15527
+ processUserButtons(userButtons).join('')
15528
+ );
15529
+ });
15530
+
15414
15531
  }(Mirador));
15415
15532
 
15416
15533
  (function($) {
@@ -15439,6 +15556,8 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15439
15556
  init: function() {
15440
15557
  var _this = this;
15441
15558
  _this.eventEmitter.unsubscribe(('modeChange.' + _this.windowId));
15559
+ _this.eventEmitter.unsubscribe(('slotLeave.' + _this.windowId));
15560
+ _this.eventEmitter.unsubscribe(('slotEnter.' + _this.windowId));
15442
15561
 
15443
15562
  this.createStateMachine();
15444
15563
  this.createRenderer();
@@ -15458,6 +15577,20 @@ this._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(thi
15458
15577
  _this.annotationsList = _this.state.getWindowAnnotationsList(_this.windowId);
15459
15578
  _this.updateRenderer();
15460
15579
  });
15580
+
15581
+ _this.eventEmitter.subscribe('slotLeave.' + _this.windowId, function(event, eventData) {
15582
+ if (_this.layerState.current == "display") {
15583
+ _this.layerState.defaultState();
15584
+ _this.modeSwitch();
15585
+ }
15586
+ });
15587
+
15588
+ _this.eventEmitter.subscribe('slotEnter.' + _this.windowId, function(event, eventData) {
15589
+ if (_this.element.showAnno && _this.layerState.current == "display") {
15590
+ _this.layerState.defaultState();
15591
+ _this.modeSwitch();
15592
+ }
15593
+ });
15461
15594
  },
15462
15595
 
15463
15596
  bindEvents: function() {
@@ -16068,6 +16201,11 @@ bindEvents: function() {
16068
16201
  }
16069
16202
  };
16070
16203
 
16204
+ if (_this.boundsToFocusOnNextOpen) {
16205
+ _this.eventEmitter.publish('fitBounds.' + _this.windowId, _this.boundsToFocusOnNextOpen);
16206
+ _this.boundsToFocusOnNextOpen = null;
16207
+ }
16208
+
16071
16209
  _this.osd.world.addHandler( "add-item", addItemHandler );
16072
16210
 
16073
16211
  _this.osd.addHandler('zoom', $.debounce(function(){
@@ -17453,6 +17591,11 @@ bindEvents: function() {
17453
17591
  _this.setBounds();
17454
17592
  }
17455
17593
 
17594
+ if (_this.boundsToFocusOnNextOpen) {
17595
+ _this.eventEmitter.publish('fitBounds.' + _this.windowId, _this.boundsToFocusOnNextOpen);
17596
+ _this.boundsToFocusOnNextOpen = null;
17597
+ }
17598
+
17456
17599
  _this.addAnnotationsLayer(_this.elemAnno);
17457
17600
 
17458
17601
  // get the state before resetting it so we can get back to that state
@@ -17599,11 +17742,17 @@ bindEvents: function() {
17599
17742
  this.localState(localState);
17600
17743
  },
17601
17744
 
17602
- toggle: function() {},
17745
+ imageFocusUpdated: function(focus) {
17746
+ var localState = this.localState();
17747
+ localState.active = (focus === 'ThumbnailsView') ? false : true;
17748
+
17749
+ this.localState(localState);
17750
+ },
17603
17751
 
17604
17752
  listenForActions: function() {
17605
17753
  var _this = this;
17606
17754
 
17755
+ // This event is fired by the component itself anytime its local state is updated.
17607
17756
  _this.eventEmitter.subscribe('layersTabStateUpdated.' + _this.windowId, function(_, data) {
17608
17757
  _this.render(data);
17609
17758
  });
@@ -17615,6 +17764,12 @@ bindEvents: function() {
17615
17764
  _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event, canvasID) {
17616
17765
  //update layers for this canvasID
17617
17766
  });
17767
+
17768
+ _this.eventEmitter.subscribe('focusUpdated' + _this.windowId, function(event, focus) {
17769
+ // update the disabled state of the layersTab
17770
+ // since it cannot be used in overview mode
17771
+ _this.imageFocusUpdated(focus);
17772
+ });
17618
17773
  },
17619
17774
 
17620
17775
  bindEvents: function() {
@@ -17624,15 +17779,16 @@ bindEvents: function() {
17624
17779
 
17625
17780
  render: function(state) {
17626
17781
  var _this = this,
17627
- templateData = {};
17782
+ templateData = {
17783
+ active: state.active ? '' : 'inactive'
17784
+ };
17628
17785
 
17629
17786
  if (this.element) {
17630
17787
  _this.appendTo.find(".layersPanel").remove();
17631
17788
  }
17632
17789
  this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo);
17633
-
17634
- _this.bindEvents();
17635
17790
 
17791
+ _this.bindEvents();
17636
17792
 
17637
17793
  if (state.visible) {
17638
17794
  this.element.show();
@@ -17641,10 +17797,10 @@ bindEvents: function() {
17641
17797
  }
17642
17798
  },
17643
17799
 
17644
- template: $.Handlebars.compile([
17645
- '<div class="layersPanel">',
17800
+ template: Handlebars.compile([
17801
+ '<div class="layersPanel {{active}}">',
17646
17802
  '</div>',
17647
- ].join(''))
17803
+ ].join(''))
17648
17804
  };
17649
17805
 
17650
17806
  }(Mirador));
@@ -17820,7 +17976,7 @@ bindEvents: function() {
17820
17976
  var collectionLabel = within.label || collectionUrl;
17821
17977
  return '<a href="' + collectionUrl + '" target="_blank">' + collectionLabel + '</a>';
17822
17978
  } else if (within instanceof Array) {
17823
- return within.map(this.getWithin).join("<br/>");
17979
+ return within.map(this.getWithin, this).join("<br/>");
17824
17980
  } else {
17825
17981
  return this.stringifyObject(within);
17826
17982
  }
@@ -17952,6 +18108,685 @@ bindEvents: function() {
17952
18108
 
17953
18109
  }(Mirador));
17954
18110
 
18111
+ (function($) {
18112
+
18113
+ $.SearchTab = function(options) {
18114
+ jQuery.extend(true, this, {
18115
+ element: null,
18116
+ appendTo: null,
18117
+ manifest: null,
18118
+ visible: null,
18119
+ canvasID: null,
18120
+ windowId: null,
18121
+ eventEmitter: null,
18122
+ }, options);
18123
+
18124
+ this.init();
18125
+ };
18126
+
18127
+ $.SearchTab.prototype = {
18128
+ init: function() {
18129
+ var _this = this;
18130
+ this.windowId = this.windowId;
18131
+
18132
+ this.localState({
18133
+ id: 'searchTab',
18134
+ visible: this.visible,
18135
+ }, true);
18136
+
18137
+ this.listenForActions();
18138
+ this.render(this.localState());
18139
+ this.loadTabComponents();
18140
+ },
18141
+
18142
+ localState: function(state, initial) {
18143
+ if (!arguments.length) return this.searchTabState;
18144
+ this.searchTabState = state;
18145
+
18146
+ if (!initial) {
18147
+ this.eventEmitter.publish('searchTabStateUpdated.' + this.windowId, this.searchTabState);
18148
+ }
18149
+
18150
+ return this.searchTabState;
18151
+ },
18152
+
18153
+ loadTabComponents: function() {
18154
+ var _this = this;
18155
+ },
18156
+
18157
+ tabStateUpdated: function(data) {
18158
+ if (data.tabs[data.selectedTabIndex].options.id === 'searchTab') {
18159
+ this.element.show();
18160
+ }
18161
+ else {
18162
+ this.element.hide();
18163
+ }
18164
+ },
18165
+
18166
+ toggle: function() {},
18167
+
18168
+ listenForActions: function() {
18169
+ var _this = this;
18170
+
18171
+ //jQuery.subscribe('searchTabStateUpdated.' + _this.windowId, function(_, data) {
18172
+ // _this.render(data);
18173
+ //});
18174
+
18175
+ this.eventEmitter.subscribe('tabStateUpdated.' + _this.windowId, function(_, data) {
18176
+ _this.tabStateUpdated(data);
18177
+ });
18178
+
18179
+ // eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event) {
18180
+ //
18181
+ // });
18182
+ },
18183
+
18184
+ displaySearchWithin: function(query_params, searchUrl){
18185
+ var _this = this;
18186
+ if (query_params !== "") {
18187
+
18188
+ this.searchObject = new $.SearchWithinResults({
18189
+ manifest: _this.manifest,
18190
+ appendTo: _this.element.find(".search-results-list"),
18191
+ panel: true,
18192
+ canvasID: _this.canvasID,
18193
+ windowId: _this.windowId,
18194
+ imagesList: _this.imagesList,
18195
+ thumbInfo: {thumbsHeight: 80, listingCssCls: 'panel-listing-thumbs', thumbnailCls: 'panel-thumbnail-view'},
18196
+ query_params: query_params,
18197
+ searchService: searchUrl,
18198
+ eventEmitter: _this.eventEmitter
18199
+ });
18200
+ }
18201
+ },
18202
+
18203
+ bindEvents: function() {
18204
+ var _this = this;
18205
+
18206
+ this.element.find(".js-perform-query").on('submit', function(event){
18207
+ event.preventDefault();
18208
+
18209
+ var query = _this.element.find(".js-query").val();
18210
+ var motivation = _this.element.find(".js-motivation").val();
18211
+ var date = _this.element.find(".js-date").val();
18212
+ var user = _this.element.find(".js-user").val();
18213
+ var searchUrl = _this.element.find("#search-within-selector").val();
18214
+
18215
+ _this.displaySearchWithin({
18216
+ "q": query,
18217
+ "motivation": motivation,
18218
+ "date": date,
18219
+ "user": user
18220
+ }, searchUrl);
18221
+
18222
+ });
18223
+
18224
+ this.element.find(".js-search-expand").on('click', function(event){
18225
+ event.preventDefault();
18226
+
18227
+ _this.element.find(".js-search-expanded").slideToggle("fast");
18228
+
18229
+ if (jQuery(this).text() === "more"){
18230
+ jQuery(this).html("less");
18231
+ }
18232
+ else if (jQuery(this).text() === "less"){
18233
+ jQuery(this).html("more");
18234
+ }
18235
+ });
18236
+
18237
+ },
18238
+
18239
+ render: function(state) {
18240
+ var _this = this;
18241
+
18242
+ var searchService = this.manifest.getSearchWithinService(),
18243
+ searchServiceIdArray = searchService && searchService.map(function(data){
18244
+ return {
18245
+ "url": data['@id'],
18246
+ "label": data.label
18247
+ };
18248
+ });
18249
+
18250
+ templateData = {
18251
+ searchService: searchServiceIdArray
18252
+ };
18253
+
18254
+ if (!this.element) {
18255
+ this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo);
18256
+ _this.bindEvents();
18257
+ } else {
18258
+ _this.appendTo.find(".search-results").remove();
18259
+ this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo);
18260
+ }
18261
+
18262
+ if (state.visible) {
18263
+ this.element.show();
18264
+ } else {
18265
+ this.element.hide();
18266
+ }
18267
+ },
18268
+
18269
+ template: Handlebars.compile([
18270
+ '<div class="search-results">',
18271
+ '{{#if searchService}}',
18272
+ '<label>Select Search Service',
18273
+ '<select id="search-within-selector" style="width: 100%">',
18274
+ '{{#each searchService}}',
18275
+ '<option value="{{ url }}">{{#if label}}{{ label }}{{ else }} {{ url }}{{/if}}</option>',
18276
+ '{{/each}}',
18277
+ '</select>',
18278
+ '</label>',
18279
+ '<form id="search-within-form" class="js-perform-query">',
18280
+ '<input class="js-query" type="text" placeholder="search text"/>',
18281
+
18282
+ '<input style="margin: 10px 0" type="submit"/>',
18283
+
18284
+ '<a class="js-search-expand" style="display: block; margin: 0 0 5px 0">more</a>',
18285
+ '<div class="js-search-expanded" style="display: none;">',
18286
+ '<input class="js-motivation" type="text" placeholder="motivation"/>',
18287
+ '<input class="js-date" type="text" placeholder="date"/>',
18288
+ '<input class="js-user" type="text" placeholder="user"/>',
18289
+ // '<input class="js-box" type="text" placeholder="box: x, y, w, h"/>',
18290
+ '</div>',
18291
+ '</form>',
18292
+ '<div class="search-results-list"></div>',
18293
+ '{{else}}',
18294
+ 'No search service available',
18295
+ '{{/if}}',
18296
+ '</div>',
18297
+ ].join(''))
18298
+ };
18299
+
18300
+ }(Mirador));
18301
+
18302
+ (function($) {
18303
+
18304
+ /**
18305
+ * UI + logic to get search results for a given search query. On initialization,
18306
+ * the provided search query is given tothe provided IIIF Search service.
18307
+ * The response is displayed as a list.
18308
+ *
18309
+ * Parameter 'query_params': {q: query, motivation: motivation, date: date, user: user}
18310
+ *
18311
+ * Currently follows IIIF Content Search API v1.0
18312
+ * (http://iiif.io/api/search/0.9/)
18313
+ */
18314
+ $.SearchWithinResults = function(options) {
18315
+
18316
+ jQuery.extend(this, {
18317
+ manifest: null,
18318
+ element: null,
18319
+ metadataTypes: null,
18320
+ metadataListingCls: 'metadata-listing',
18321
+ /** Search service URL */
18322
+ searchService: null,
18323
+ windowId: null,
18324
+ /** {q: query, motivation: motivation, date: date, user: user} */
18325
+ query_params: null,
18326
+ /** Used for paging. This assumes that searches start on page 1... */
18327
+ currentPage: 1,
18328
+ eventEmitter: null
18329
+ }, options);
18330
+
18331
+ this.init();
18332
+ };
18333
+
18334
+ $.SearchWithinResults.prototype = {
18335
+
18336
+ init: function() {
18337
+ this.registerHandlebars();
18338
+
18339
+ jQuery(this.appendTo).empty();
18340
+ this.element = jQuery(this.template()).appendTo(this.appendTo);
18341
+ jQuery("<hr/><h3>Search results for: " + this.query_params.q + "</h3><hr/>")
18342
+ .appendTo(this.appendTo.find('.search-results-messages'));
18343
+ this.doSearchFromQuery(this.query_params);
18344
+ },
18345
+
18346
+ doSearchFromQuery: function(query_params) {
18347
+ query_string = "";
18348
+ for (var param in query_params){
18349
+ if (param === "q"){
18350
+ query_string += param + "=" + query_params[param];
18351
+ }
18352
+ else if (query_params[param].length > 0){
18353
+ query_string += "&" + param + "=" + query_params[param];
18354
+ }
18355
+ }
18356
+
18357
+ var url = this.searchService + '?' + query_string;
18358
+ this.doSearchFromUrl(url);
18359
+ },
18360
+
18361
+ /**
18362
+ * AJAX request is made from here!
18363
+ *
18364
+ * TODO Perhaps emit a search event here for the purposes of
18365
+ * state tracking or analytics?
18366
+ *
18367
+ * @param {[type]} url [description]
18368
+ * @return {[type]} [description]
18369
+ */
18370
+ doSearchFromUrl: function(url) {
18371
+ var _this = this;
18372
+
18373
+ this.element.find('.search-results-container').empty();
18374
+
18375
+ jQuery.ajax({
18376
+ url: url,
18377
+ dataType: 'json',
18378
+ async: true
18379
+ })
18380
+ .done(function(searchResults) {
18381
+ if (searchResults.resources) {
18382
+ // display result totals
18383
+ _this.displayResultCounts(searchResults);
18384
+ // show results list
18385
+ _this.processResults(searchResults);
18386
+ } else {
18387
+ jQuery('.search-results-count').html("<hr/><p>No results</p><hr/>");
18388
+ }
18389
+ })
18390
+ .fail(function() {
18391
+ jQuery('.search-results-count').html("<hr/><p>No results</p><hr/>");
18392
+ })
18393
+ .always();
18394
+ },
18395
+
18396
+
18397
+ displayResultCounts: function(searchResults){
18398
+ var total = searchResults.within.total,
18399
+ startResultNumber = searchResults.startIndex + 1,
18400
+ endResultNumber = "";
18401
+
18402
+ //if there is only only resource, it will not be array and therefore we can't
18403
+ //take the length of it; this conditions check first to see if the resources
18404
+ //property contains an array or single object
18405
+
18406
+ // TODO: not that this single object vs. array seems to be problem throughout.
18407
+ // Pages with only one result do not display properly. See for example:
18408
+ // http://exist.scta.info/exist/apps/scta/iiif/pl-zbsSII72/search?q=fides&page=3
18409
+
18410
+ if (searchResults.resources.constructor === Array) {
18411
+ endResultNumber = searchResults.startIndex + searchResults.resources.length;
18412
+ } else {
18413
+ endResultNumber = searchResults.startIndex + 1;
18414
+ }
18415
+
18416
+ jQuery('.search-results-count').html("<hr/><p>Showing " + startResultNumber + " - " + endResultNumber + " out of " + total + "</p><hr/>");
18417
+ },
18418
+
18419
+ processResults: function(searchResults) {
18420
+ //create tplData array
18421
+ if (searchResults.hits) {
18422
+ this.tplData = this.getHits(searchResults);
18423
+ } else {
18424
+ this.tplData = this.getSearchAnnotations(searchResults);
18425
+ }
18426
+ jQuery(Handlebars.compile('{{> resultsList }}')(this.tplData)).appendTo(jQuery(this.element.find('.search-results-container')));
18427
+ this.bindEvents();
18428
+
18429
+ this.setPager(searchResults);
18430
+ },
18431
+
18432
+ /**
18433
+ * Look for necessary properties that point to the need for paging.
18434
+ * Check for total number of results vs number of returned results.
18435
+ *
18436
+ * @param results IIIF Search results
18437
+ * @return TRUE if paging is needed
18438
+ */
18439
+ needsPager: function(results) {
18440
+ // Check for some properties on the search results
18441
+ if (!results || !results.within || !results.resources) {
18442
+ return false;
18443
+ }
18444
+ var total = results.within.total;
18445
+ // Check if 'resources' (list of annotations) is an array, or single value
18446
+ if (Array.isArray(results.resources)) {
18447
+ return results.resources.length < total;
18448
+ } else {
18449
+ return total > 1;
18450
+ }
18451
+ },
18452
+
18453
+ /**
18454
+ * Initialize search results pager. It is assumed that it has already
18455
+ * been determined whether or not the pager needs to be created.
18456
+ * If a pager is created, it will be inserted into the DOM.
18457
+ *
18458
+ * If it is determined that this set of results does not need paging,
18459
+ * then this function will exit early and no paging will be set.
18460
+ * {@link SearchWithinResults#needsPager}
18461
+ *
18462
+ * @param results - IIIF Search results
18463
+ */
18464
+ setPager: function(searchResults) {
18465
+ var _this = this;
18466
+ var pager = this.element.find('.search-results-pager');
18467
+
18468
+ // HACK: pager.pagination will be undefined until canvasID are set properly
18469
+ if (!this.needsPager(searchResults) || !pager.pagination) {
18470
+ return;
18471
+ }
18472
+
18473
+ /*
18474
+ * Hack to get proper page numbers.
18475
+ * TODO probably shouldn't use this library if it requires page numbers,
18476
+ * instead have something with ONLY FIRST/LAST and PREV/NEXT controls.
18477
+ */
18478
+ if (!this.onPageCount) { // This will be set with initial page and not be changed.
18479
+ this.onPageCount = searchResults.resources.length;
18480
+ }
18481
+
18482
+ pager.pagination({
18483
+ items: searchResults.within.total,
18484
+ itemsOnPage: _this.onPageCount,
18485
+ currentPage: _this.currentPage,
18486
+ displayedPages: 1,
18487
+ edges: 1,
18488
+ ellipsePageSet: false,
18489
+ cssStyle: 'compact-theme',
18490
+ hrefTextPrefix: '',
18491
+ prevText: '<i class="fa fa-lg fa-angle-left"></i>',
18492
+ nextText: '<i class="fa fa-lg fa-angle-right"></i>',
18493
+ onPageClick: function(pageNumber, event) {
18494
+ event.preventDefault();
18495
+ pager.pagination('disable');
18496
+
18497
+ if (pageNumber == _this.currentPage - 1) {
18498
+ _this.currentPage--;
18499
+ _this.doSearchFromUrl(searchResults.prev);
18500
+ } else if (pageNumber == _this.currentPage + 1) {
18501
+ _this.currentPage++;
18502
+ _this.doSearchFromUrl(searchResults.next);
18503
+ } else if (pageNumber == 1) {
18504
+ _this.doSearchFromUrl(searchResults.within.first);
18505
+ _this.currentPage = 1;
18506
+ } else {
18507
+ // Assume this is the last page........
18508
+ _this.doSearchFromUrl(searchResults.within.last);
18509
+ /*
18510
+ * NOTE: There is no good way to get the page number from the search URL.
18511
+ * IIIF Content Search v1.0 spec (I do not think) does not define
18512
+ * how to specify a page.
18513
+ * -- For example, the page could be put into the URL
18514
+ * query string, or it could be put into the URL fragment, or somewhere
18515
+ * else.
18516
+ *
18517
+ * This hack pulls the number of the last page on the pager. :/
18518
+ */
18519
+ _this.currentPage = parseInt(
18520
+ _this.element.find('.search-results-pager li:nth-last-child(2)').text()
18521
+ );
18522
+ }
18523
+ }
18524
+ });
18525
+ pager.pagination('enable');
18526
+ },
18527
+
18528
+ /**
18529
+ * Do a Bitwise OR to truncate decimal
18530
+ *
18531
+ * @param num original number, could be integer or decimal
18532
+ * @return integer with any decimal part of input truncated (no rounding)
18533
+ */
18534
+ float2int: function(num) {
18535
+ return num | 0;
18536
+ },
18537
+
18538
+ parseSearchAnnotation: function(annotation){
18539
+ var _this = this;
18540
+ var canvasid = annotation.on;
18541
+ // deals with possibiliy of "on" propert taking an object
18542
+ if (typeof canvasid === 'object') {
18543
+ canvasid = annotation.on['@id'];
18544
+ }
18545
+
18546
+ var canvaslabel = _this.getLabel(annotation);
18547
+
18548
+ // Split ID from Coordinates if necessary
18549
+ var id_parts = _this.splitBaseUrlAndCoordinates(canvasid);
18550
+
18551
+ return {
18552
+ canvasid: id_parts.base,
18553
+ coordinates: id_parts.coords,
18554
+ canvaslabel: canvaslabel,
18555
+ resulttext: annotation.resource.chars
18556
+ };
18557
+ },
18558
+
18559
+ getSearchAnnotations: function(searchResults) {
18560
+ var _this = this;
18561
+ tplData = [];
18562
+ //add condition here to make sure searchResults.resources is not null
18563
+ if (searchResults.resources !== null) {
18564
+ //This conditional handles if the results come back as a single object or as an array
18565
+ //TODO: This possibility is not yet handled in the getHits function
18566
+ if (!Array.isArray(searchResults.resources)){
18567
+ annotation = searchResults.resources;
18568
+ tplData.push(_this.parseSearchAnnotation(annotation));
18569
+ }
18570
+ else {
18571
+ searchResults.resources.forEach(function(annotation){
18572
+ tplData.push(_this.parseSearchAnnotation(annotation));
18573
+ });
18574
+ }
18575
+ return tplData;
18576
+ }
18577
+ },
18578
+
18579
+ getHits: function(searchResults) {
18580
+ var _this = this;
18581
+ tplData = [];
18582
+ searchResults.hits.forEach(function(hit) {
18583
+ //this seems like a really slow way to retrieve on property from hits
18584
+ //note that at present it is only retrieving the first annotation
18585
+ //but a hit annotation property takes an array and could have more than one
18586
+ //annotation -- its not a very common case but a possibility.
18587
+ var resultObject, resultObjects = [];
18588
+ hit.annotations.forEach(function(annotation) {
18589
+ //canvases could come back as an array
18590
+ var resource = _this.getHitResources(searchResults, annotation)[0],
18591
+ canvasLabel = _this.getLabel(resource),
18592
+ canvasID = resource && resource.on;
18593
+ // If you have the full annotation, set ID and label appropriately
18594
+ if (typeof canvasID === 'object') {
18595
+ canvasID = resource.on['@id'];
18596
+ }
18597
+ // Extract coordinates if necessary
18598
+ var canvasIDParts = _this.splitBaseUrlAndCoordinates(canvasID);
18599
+ resultObject = {
18600
+ canvasid: canvasIDParts.base,
18601
+ coordinates: canvasIDParts.coords,
18602
+ canvaslabel: canvasLabel,
18603
+ hit: hit // TODO must handle different results structures, see IIIF search spec for different responses
18604
+ };
18605
+ resultObjects.push(resultObject);
18606
+ });
18607
+ // First result is returned and gets attached an array of all annotations
18608
+ if (resultObjects) {
18609
+ resultObject = resultObjects[0];
18610
+ if (resultObjects.length > 1) {
18611
+ resultObject.annotations = resultObjects;
18612
+ }
18613
+ tplData.push(resultObject);
18614
+ }
18615
+ });
18616
+ return tplData;
18617
+ },
18618
+
18619
+ /**
18620
+ * Get a label describing a search match. This label is set to the
18621
+ * associated annotation label, if available, or to the label of the
18622
+ * parent canvas.
18623
+ *
18624
+ * @param {[type]} resource annotation associated with the search match
18625
+ * @return {[type]} string label
18626
+ */
18627
+ getLabel: function(resource) {
18628
+ var label;
18629
+
18630
+ if (resource && typeof resource === 'object') {
18631
+ if (resource.label) {
18632
+ return resource.label;
18633
+ } else if (resource.resource.label){
18634
+ return resource.resource.label;
18635
+ } else if (resource.on && typeof resource.on === 'string') {
18636
+ label = this.manifest.getCanvasLabel(resource.on);
18637
+ return label ? 'Canvas ' + label : undefined;
18638
+ } else if (resource.on && typeof resource.on === 'object') {
18639
+ label = resource.on.label ? resource.on.label : this.manifest.getCanvasLabel(resource.on['@id']);
18640
+ return label ? 'Canvas ' + label : undefined;
18641
+ }
18642
+ } else {
18643
+ return undefined;
18644
+ }
18645
+ },
18646
+
18647
+ getHitResources: function(searchResults, annotationid) {
18648
+ // Get array of results
18649
+ return searchResults.resources.filter(function(resource){
18650
+ return resource['@id'] === annotationid;
18651
+ });
18652
+ },
18653
+
18654
+ /**
18655
+ * @param url - a resource ID
18656
+ * @return {
18657
+ * base: base URL, or the original url param if no coords are present,
18658
+ * coords: coordinates from ID if present
18659
+ * }
18660
+ */
18661
+ splitBaseUrlAndCoordinates: function(url) {
18662
+ var coordinates;
18663
+ var base = url;
18664
+
18665
+ if (typeof url === 'string') {
18666
+ // Separate base ID from fragment selector
18667
+ var parts = url.split('#');
18668
+
18669
+ base = parts[0];
18670
+ if (parts.length === 2) {
18671
+ coordinates = parts[1];
18672
+ }
18673
+ }
18674
+
18675
+ return {
18676
+ base: base,
18677
+ coords: coordinates
18678
+ };
18679
+ },
18680
+
18681
+ bindEvents: function() {
18682
+ var _this = this;
18683
+
18684
+ // jQuery.subscribe(('currentCanvasIDUpdated.' + _this.windowId), function(event, canvasID) {
18685
+ // //if (!_this.structures) { return; }
18686
+ // //_this.setSelectedElements($.getRangeIDByCanvasID(_this.structures, canvasID));
18687
+ // //_this.render();
18688
+ //
18689
+ // });
18690
+
18691
+ //TODO
18692
+ //This function works to move the user to the specified canvas
18693
+ //but if there are associated coordinates, it does not yet know how to highlight
18694
+ //those coordinates.
18695
+
18696
+ //thie miniAnnotatList attempted to do this, but is no longer working
18697
+ //it needs to be replaced by a new strategy.
18698
+
18699
+ this.element.find('.js-show-canvas').on("click", function(event) {
18700
+ event.stopPropagation();
18701
+
18702
+ var canvasid = jQuery(this).attr('data-canvasid'),
18703
+ coordinates = jQuery(this).attr('data-coordinates'),
18704
+ xywh = coordinates && coordinates.split('=')[1].split(',').map(Number),
18705
+ bounds = xywh && {x: xywh[0], y: xywh[1], width: xywh[2], height: xywh[3]};
18706
+ jQuery(".result-wrapper").css("background-color", "inherit");
18707
+ jQuery(this).parent().css("background-color", "lightyellow");
18708
+ //if there was more than one annotation
18709
+ //(for example if a word crossed a line and needed two coordinates sets)
18710
+ //the miniAnnotationList should have multiple objects
18711
+ miniAnnotationList = [{
18712
+ "@id": "test",
18713
+ "@type": "oa:Annotation",
18714
+ "motivation": "sc:painting",
18715
+ "resource": {
18716
+ "@type": "cnt:ContentAsText",
18717
+ "chars": _this.query
18718
+ },
18719
+ "on": canvasid + (coordinates ? "#" + coordinates : '')
18720
+ }];
18721
+ //_this.parent.annotationsList = miniAnnotationList;
18722
+ var options = {
18723
+ "canvasID": canvasid,
18724
+ "bounds": bounds
18725
+ };
18726
+ _this.eventEmitter.publish('SET_CURRENT_CANVAS_ID.' + _this.windowId, options);
18727
+ });
18728
+ },
18729
+
18730
+ registerHandlebars: function() {
18731
+ Handlebars.registerPartial('resultsList', [
18732
+ '{{#each this}}',
18733
+ '<div class="result-wrapper">',
18734
+ '<a class="search-result search-title js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
18735
+ '{{canvaslabel}}',
18736
+ '</a>',
18737
+ '{{#if annotations}}',
18738
+ '<div>',
18739
+ 'Annotations: ',
18740
+ '{{#each annotations}}',
18741
+ '<a class="search-result search-annotation js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
18742
+ '<i class="fa fa-fw" aria-hidden="true"></i>',
18743
+ '</a>',
18744
+ '{{/each}}',
18745
+ '</div>',
18746
+ '{{/if}}',
18747
+ '<div class="search-result result-paragraph js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
18748
+ '{{#if hit.before}}',
18749
+ '{{hit.before}} ',
18750
+ '{{/if}}',
18751
+ '{{#if hit.match}}',
18752
+ '<span class="highlight">{{hit.match}}</span>',
18753
+ '{{else}}',
18754
+ '{{{resulttext}}}', // If this text must NOT be escaped, use: '{{resulttext}}'
18755
+ '{{/if}}',
18756
+ '{{#if hit.after}}',
18757
+ '{{ hit.after}}',
18758
+ '{{/if}}',
18759
+ '</div>',
18760
+ '</div>',
18761
+ '{{/each}}',
18762
+ ].join(''));
18763
+ },
18764
+
18765
+ /**
18766
+ * Handlebars template. Accepts data and formats appropriately. To use,
18767
+ * just pass in the template data and this will return a String with
18768
+ * the formatted HTML which can then be inserted into the DOM.
18769
+ *
18770
+ * This template expects a IIIF AnnotationList formatted to represent
18771
+ * IIIF Search results.
18772
+ *
18773
+ * EX: assume context:
18774
+ * var templateData = { template data goes here }
18775
+ * var htmlString = template(templateData);
18776
+ */
18777
+ template: Handlebars.compile([
18778
+ '<div>',
18779
+ '<div class="search-results-messages"></div>',
18780
+ '<div class="search-results-count"></div>',
18781
+ '<div class="search-results-pager"></div>',
18782
+ '<div class="search-results-container">',
18783
+ '{{> resultsList }}',
18784
+ '</div>',
18785
+ '</div>'
18786
+ ].join(""))};
18787
+
18788
+ }(Mirador));
18789
+
17955
18790
  (function($) {
17956
18791
 
17957
18792
  $.SidePanel= function(options) {
@@ -17960,10 +18795,11 @@ bindEvents: function() {
17960
18795
  appendTo: null,
17961
18796
  manifest: null,
17962
18797
  panelState: {},
17963
- tocTabAvailable: false,
17964
- annotationsTabAvailable: false,
17965
- layersTabAvailable: false,
17966
- toolsTabAvailable: false,
18798
+ tocTabAvailable: null,
18799
+ // annotationsTabAvailable: false,
18800
+ // layersTabAvailable: null,
18801
+ // toolsTabAvailable: false,
18802
+ searchTabAvailable: null,
17967
18803
  hasStructures: false,
17968
18804
  state: null,
17969
18805
  eventEmitter: null
@@ -17982,7 +18818,7 @@ bindEvents: function() {
17982
18818
  name : 'toc',
17983
18819
  options : {
17984
18820
  available: _this.tocTabAvailable,
17985
- id:'tocTab',
18821
+ id:'tocTab',
17986
18822
  label:'Index'
17987
18823
  }
17988
18824
  },
@@ -17990,26 +18826,34 @@ bindEvents: function() {
17990
18826
  name : 'annotations',
17991
18827
  options : {
17992
18828
  available: _this.annotationsTabAvailable,
17993
- id:'annotationsTab',
18829
+ id:'annotationsTab',
17994
18830
  label:'Annotations'
17995
18831
  }
17996
18832
  },*/
17997
- {
17998
- name : 'layers',
17999
- options : {
18000
- available: _this.layersTabAvailable,
18001
- id:'layersTab',
18002
- label:'Layers'
18003
- }
18004
- },
18833
+ // {
18834
+ // name : 'layers',
18835
+ // options : {
18836
+ // available: _this.layersTabAvailable,
18837
+ // id:'layersTab',
18838
+ // label:'Layers'
18839
+ // }
18840
+ // },
18005
18841
  /*{
18006
18842
  name : 'tools',
18007
18843
  options : {
18008
18844
  available: _this.toolsTabAvailable,
18009
- id:'toolsTab',
18845
+ id:'toolsTab',
18010
18846
  label:'Tools'
18011
18847
  }
18012
18848
  }*/
18849
+ {
18850
+ name : 'search',
18851
+ options : {
18852
+ available: _this.searchTabAvailable,
18853
+ id: 'searchTab',
18854
+ label: 'Search'
18855
+ }
18856
+ }
18013
18857
  ],
18014
18858
  width: 280,
18015
18859
  open: true
@@ -18051,16 +18895,26 @@ bindEvents: function() {
18051
18895
  eventEmitter: _this.eventEmitter
18052
18896
  });
18053
18897
  }
18054
- if (_this.layersTabAvailable) {
18055
- new $.LayersTab({
18898
+ if (_this.searchTabAvailable) {
18899
+ new $.SearchTab({
18056
18900
  manifest: _this.manifest,
18057
18901
  windowId: this.windowId,
18058
18902
  appendTo: _this.element.find('.tabContentArea'),
18059
- canvasID: this.canvasID,
18060
18903
  state: _this.state,
18904
+ manifestVersion: this.manifest.getVersion(),
18061
18905
  eventEmitter: _this.eventEmitter
18062
18906
  });
18063
18907
  }
18908
+ // if (_this.layersTabAvailable) {
18909
+ // new $.LayersTab({
18910
+ // manifest: _this.manifest,
18911
+ // windowId: this.windowId,
18912
+ // appendTo: _this.element.find('.tabContentArea'),
18913
+ // canvasID: this.canvasID,
18914
+ // state: _this.state,
18915
+ // eventEmitter: _this.eventEmitter
18916
+ // });
18917
+ // }
18064
18918
 
18065
18919
  },
18066
18920
 
@@ -18160,11 +19014,11 @@ bindEvents: function() {
18160
19014
  if (!enableSidePanel) {
18161
19015
  jQuery(this.appendTo).hide();
18162
19016
  _this.eventEmitter.publish('ADD_CLASS.'+this.windowId, 'focus-max-width');
18163
- _this.eventEmitter.publish('HIDE_ICON_TOC.'+this.windowId);
19017
+ _this.eventEmitter.publish('HIDE_ICON_TOC.'+this.windowId);
18164
19018
  } else {
18165
19019
  jQuery(this.appendTo).show({effect: "fade", duration: 300, easing: "easeInCubic"});
18166
19020
  _this.eventEmitter.publish('REMOVE_CLASS.'+this.windowId, 'focus-max-width');
18167
- _this.eventEmitter.publish('SHOW_ICON_TOC.'+this.windowId);
19021
+ _this.eventEmitter.publish('SHOW_ICON_TOC.'+this.windowId);
18168
19022
  }
18169
19023
  }
18170
19024
  };
@@ -18209,8 +19063,7 @@ bindEvents: function() {
18209
19063
 
18210
19064
  this.state({
18211
19065
  tabs : this.tabs,
18212
- //tabs: [{id:'tocTab', label:'Indices'}, {id:'annotationsTab', label:'Annotations'}],
18213
- //tabs: [{id:'tocTab', label:'Indices'}],
19066
+ // tabs: [{id:'tocTab', label:'Indices'}, {id:'searchTab', label:'Search'}],
18214
19067
  selectedTabIndex: 0
18215
19068
  }, true);
18216
19069
  this.listenForActions();
@@ -18236,7 +19089,8 @@ bindEvents: function() {
18236
19089
  getTemplateData: function() {
18237
19090
  return {
18238
19091
  annotationsTab: this.state().annotationsTab,
18239
- tocTab: this.state().tocTab
19092
+ tocTab: this.state().tocTab,
19093
+ searchTab: this.state().searchTab
18240
19094
  };
18241
19095
  },
18242
19096
  listenForActions: function() {
@@ -18269,15 +19123,15 @@ bindEvents: function() {
18269
19123
  return value.options.available;
18270
19124
  });
18271
19125
  renderingData.tabs = tabs;
18272
- if(renderingData.tabs.length === 1){
19126
+ if(renderingData.tabs.length === 1){
18273
19127
  // TODO: temporary logic to minimize side panel if only tab is toc and toc is empty
18274
19128
  if (renderingData.tabs[0].name === 'toc' && !_this.hasStructures) {
18275
19129
  _this.eventEmitter.publish("sidePanelVisibilityByTab." + _this.windowId, false);
18276
19130
  }
18277
19131
 
18278
19132
  // don't show button if only one tab
18279
- renderingData.tabs = [];
18280
- }
19133
+ renderingData.tabs = [];
19134
+ }
18281
19135
  //TODO: add text if there is one label or no content within this tab
18282
19136
  this.element = jQuery(_this.template(renderingData)).prependTo(_this.appendTo);
18283
19137
  return;
@@ -18389,11 +19243,12 @@ bindEvents: function() {
18389
19243
  scrollPosition,
18390
19244
  windowObject = this.state.getWindowObjectById(this.windowId);
18391
19245
 
18392
- if (windowObject && windowObject.viewType === 'BookView') {
18393
- scrollPosition = _this.element.scrollLeft() + (target.position().left + (target.next().width() + target.outerWidth())/2) - _this.element.width()/2;
18394
- } else {
18395
-
18396
- scrollPosition = _this.element.scrollLeft() + (target.position().left + target.width()/2) - _this.element.width()/2;
19246
+ if (target.position()) {
19247
+ if (windowObject && windowObject.viewType === 'BookView') {
19248
+ scrollPosition = _this.element.scrollLeft() + (target.position().left + (target.next().width() + target.outerWidth())/2) - _this.element.width()/2;
19249
+ } else {
19250
+ scrollPosition = _this.element.scrollLeft() + (target.position().left + target.width()/2) - _this.element.width()/2;
19251
+ }
18397
19252
  }
18398
19253
  _this.element.scrollTo(scrollPosition, 900);
18399
19254
  },
@@ -18432,8 +19287,8 @@ bindEvents: function() {
18432
19287
  },
18433
19288
 
18434
19289
  toggle: function(stateValue) {
18435
- if (stateValue) {
18436
- this.show();
19290
+ if (stateValue) {
19291
+ this.show();
18437
19292
  } else {
18438
19293
  this.hide();
18439
19294
  }
@@ -20093,7 +20948,7 @@ bindEvents: function() {
20093
20948
 
20094
20949
  $.sanitizeHtml = function(dirty) {
20095
20950
  return sanitizeHtml(dirty, {
20096
- allowedTags: ['a', 'b', 'br', 'i', 'img', 'p', 'span', 'strong', 'em'],
20951
+ allowedTags: ['a', 'b', 'br', 'i', 'img', 'p', 'span', 'strong', 'em', 'ul', 'ol', 'li'],
20097
20952
  allowedAttributes: {
20098
20953
  'a': ['href', 'target'],
20099
20954
  'img': ['src', 'alt'],