Bugzilla – Attachment 164669 Details for
Bug 26933
Improve handling of multiple covers on catalog search results in the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 26933: Improve handling of multiple covers on catalog search results in the OPAC
Bug-26933-Improve-handling-of-multiple-covers-on-c.patch (text/plain), 27.37 KB, created by
Owen Leonard
on 2024-04-11 10:29:17 UTC
(
hide
)
Description:
Bug 26933: Improve handling of multiple covers on catalog search results in the OPAC
Filename:
MIME Type:
Creator:
Owen Leonard
Created:
2024-04-11 10:29:17 UTC
Size:
27.37 KB
patch
obsolete
>From 855d721b0522584964569f0439d3ba8cb05653c1 Mon Sep 17 00:00:00 2001 >From: Owen Leonard <oleonard@myacpl.org> >Date: Thu, 11 Apr 2024 10:25:21 +0000 >Subject: [PATCH] Bug 26933: Improve handling of multiple covers on catalog > search results in the OPAC > >This patch implements a book cover slider widget for cover images in >search results, matching the way we show covers on the detail page. > >To test, apply the page and rebuild the staff client CSS >(https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client). > >Enable multiple cover image services. The patch was developed with these >services available: > > - Amazon > - Local cover images (including multiple local cover images) > - Coce (serving up Amazon, Google, and OpenLibrary images) > - OpenLibrary > - Baker & Taylor > - Google > - Custom cover images (the CustomCoverImages preference) > >Perform a variet of searches and confirm that cover images are >displaying correctly, whether there be 0, 1, 2, or more covers >available for each. >--- > .../opac-tmpl/bootstrap/css/src/opac.scss | 35 ++- > .../bootstrap/en/includes/cover-images.inc | 90 ++++++ > .../bootstrap/en/modules/opac-results.tt | 257 ++++++++++++------ > 3 files changed, 286 insertions(+), 96 deletions(-) > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/cover-images.inc > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >index 7d0cc68affa..54be1f092f6 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >@@ -734,17 +734,12 @@ th { > } > } > >-#biblio-cover-slider { >- background-color: #FFF; >- border: 1px solid #B9D8D9; >- border-radius: 3px; >- margin: 0 1em .5em 0; >- min-height: 175px; >- padding: 10px 5px 5px 5px; >-} >- > .cover-slider { > background: #FFF url( "../images/spinner-small.gif" ) center center no-repeat; >+ margin: 0 1em .5em 0; >+ min-height: 175px; >+ min-width: 100px; >+ padding: 10px 15px 5px 5px; > > .hint { > font-size: 80%; >@@ -761,13 +756,16 @@ th { > } > } > >+.covercol { >+ .cover-slider { >+ background: transparent none; >+ margin: 0; >+ padding: 5px; >+ } >+} >+ > .cover-image { > display: none; >- >- img { >- height: auto; >- max-width: 100%; >- } > } > > .custom_cover_image { >@@ -2957,6 +2955,15 @@ $star-selected: #EDB867; > .chocolat-bottom { > background: rgba( 0, 0, 0, .7 ); > } >+ >+ .chocolat-description { >+ a:link, >+ a:visited, >+ a:hover, >+ a:active { >+ color: desaturate( lighten( $base-theme-color, 40 ), 30 ) >+ } >+ } > } > > @import "responsive"; >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/cover-images.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/cover-images.inc >new file mode 100644 >index 00000000000..e4f3273b0af >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/cover-images.inc >@@ -0,0 +1,90 @@ >+<div class="bookcover"> >+ >+ <div class="cover-slider cover-slides" data-biblionumber="[% SEARCH_RESULT.biblionumber | html %]" data-title="[% SEARCH_RESULT.title | html %]" data-isbn="[% SEARCH_RESULT.normalized_isbn | html %]"> >+ [% IF ( OPACLocalCoverImages ) %] >+ [% IF localimages.count %] >+ [% FOREACH image IN localimages %] >+ <div class="cover-image local-coverimg"> >+ <a href="/cgi-bin/koha/opac-image.pl?imagenumber=[% image.imagenumber | uri %]" title="Local cover image"> >+ <img src="/cgi-bin/koha/opac-image.pl?thumbnail=1&imagenumber=[% image.imagenumber | uri %]" alt="Local cover image" data-link="/cgi-bin/koha/opac-imageviewer.pl?biblionumber=[% SEARCH_RESULT.biblionumber | uri %]&imagenumber=[% image.imagenumber | uri %]" /> >+ </a> >+ <div class="hint">Local cover image</div> >+ </div> >+ [% END %] >+ [% END %] >+ [% END %] >+ >+ [% IF ( OPACAmazonCoverImages && SEARCH_RESULT.normalized_isbn) %] >+ <div class="cover-image amazon-coverimg"> >+ <a href="https://images-na.ssl-images-amazon.com/images/P/[% SEARCH_RESULT.normalized_isbn | uri %].01.LZZZZZZZ.jpg" title="Amazon cover image"> >+ <img src="https://images-na.ssl-images-amazon.com/images/P/[% SEARCH_RESULT.normalized_isbn | uri %].01.MZZZZZZZ.jpg" alt="Amazon cover image" data-link="http://www.amazon[% AmazonTld | uri %]/gp/reader/[% SEARCH_RESULT.normalized_isbn | uri %][% AmazonAssocTag | uri %]#reader-link"/> >+ </a> >+ <div class="hint">Image from Amazon.com</div> >+ </div> >+ [% END %] >+ >+ [% IF ( SyndeticsEnabled && SyndeticsCoverImages ) %] >+ [% IF ( content_identifier_exists ) %] >+ <div class="cover-image syndetics-coverimg"> >+ <a href="https://secure.syndetics.com/index.aspx?isbn=[% SEARCH_RESULT.normalized_isbn | uri %]/[% SyndeticsCoverImageSize | uri %].GIF&client=[% SyndeticsClientCode | uri %]&type=xw10&upc=[% normalized_upc | uri %]&oclc=[% normalized_oclc | uri %]" title="Syndetics cover image"> >+ <img src="https://secure.syndetics.com/index.aspx?isbn=[% SEARCH_RESULT.normalized_isbn | uri %]/[% SyndeticsCoverImageSize | uri %].GIF&client=[% SyndeticsClientCode | uri %]&type=xw10&upc=[% normalized_upc | uri %]&oclc=[% normalized_oclc | uri %]" alt="Syndetics cover image" /> >+ </a> >+ <div class="hint">Image from Syndetics</div> >+ </div> >+ [% END %] >+ [% END %] >+ >+ [% IF ( GoogleJackets ) %] >+ <div class="cover-image googlejacket-coverimg"> >+ <div title="[% img_title | html %]" class="[% SEARCH_RESULT.normalized_isbn | html %]" id="gbs-thumbnail-preview" data-use-data-link="1"></div> >+ <div class="hint">Image from Google Jackets</div> >+ </div> >+ [% END %] >+ >+ [% IF ( Koha.Preference('OPACCoce') && Koha.Preference('CoceProviders') && SEARCH_RESULT.normalized_isbn ) %] >+ [% coce_id = normalized_ean || SEARCH_RESULT.normalized_isbn %] >+ <div class="cover-image coce-coverimg"> >+ [% IF ( coce_id ) %] >+ <a title="Image from Coce" class="[% coce_id | html %]" id="coce-thumbnail-preview"></a> >+ [% END %] >+ <div class="hint">Image from Coce</div> >+ </div> >+ [% END %] >+ >+ [% IF OpenLibraryCovers %] >+ <div class="cover-image openlibrary-coverimg"> >+ <div title="[% img_title | html %]" class="[% SEARCH_RESULT.normalized_isbn | html %]" id="openlibrary-thumbnail-preview" data-use-data-link="1"></div> >+ <div class="hint">Image from OpenLibrary</div> >+ </div> >+ [% END %] >+ >+ [% bt_id = ( SEARCH_RESULT.normalized_upc || SEARCH_RESULT.normalized_isbn ) %] >+ [% IF ( BakerTaylorEnabled && bt_id ) %] >+ <div class="cover-image bakertaylor-coverimg"> >+ [% IF BakerTaylorBookstoreURL %] >+ <a href="https://[% BakerTaylorBookstoreURL | url %][% bt_id | url %]"> >+ <img alt="See Baker & Taylor" src="[% BakerTaylorImageURL | url %][% bt_id | uri %]" /> >+ </a> >+ [% ELSE %] >+ <a href="[% BakerTaylorImageURL | url %][% bt_id | uri%]"> >+ <img alt="See Baker & Taylor" src="[% BakerTaylorImageURL | url %][% bt_id | uri %]" /> >+ </a> >+ [% END %] >+ <div class="hint">Image from Baker & Taylor</div> >+ </div> >+ [% END %] >+ >+ [% IF Koha.Preference('OPACCustomCoverImages') && Koha.Preference('CustomCoverImagesURL') %] >+ [% SET custom_cover_image_url = SEARCH_RESULT.biblio_object.custom_cover_image_url %] >+ [% IF custom_cover_image_url %] >+ <div class="cover-image custom-coverimg"> >+ <a class="custom_cover_image" href="[% custom_cover_image_url | url %]" title="Custom cover image"> >+ <img id="custom-img" alt="Custom cover image" src="[% custom_cover_image_url | url %]" /> >+ </a> >+ <div class="hint">Custom cover image</div> >+ </div> >+ [% END %] >+ [% END %] >+ </div> <!-- /.cover-slider --> >+ >+</div><!-- / .bookcover --> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >index 5df882aff73..a3efd1e7557 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >@@ -25,9 +25,10 @@ > › > [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog > </title> >-[% INCLUDE 'doc-head-close.inc' %] > > <link rel="alternate" type="application/rss+xml" title="[% LibraryName | html %] Search RSS feed" href="[% OPACBaseURL | url %]/cgi-bin/koha/opac-search.pl?[% query_cgi | $raw %][% limit_cgi | $raw %]&count=[% countrss |uri %]&sort_by=acqdate_dsc&format=rss" /> >+[% Asset.css("lib/Chocolat/css/chocolat.css") | $raw %] >+[% INCLUDE 'doc-head-close.inc' %] > </head> > [% BLOCK cssinclude %][% END %] > [% INCLUDE 'bodytag.inc' bodyid='results' bodyclass='scrollto' %] >@@ -329,7 +330,9 @@ > [% END %] > </td> > [% END %] >- >+ <td class="covercol"> >+ [% INCLUDE 'cover-images.inc' %] >+ </td> > [% # Cell 4: Search result details and controls %] > <td class="bibliocol"> > [% IF ( SEARCH_RESULT.title ) %] >@@ -337,87 +340,7 @@ > [% ELSE %] > [% img_title = SEARCH_RESULT.biblionumber %] > [% END %] >- <div class="coverimages cover-slides itemtype_[% SEARCH_RESULT.itemtype | html %]" data-isbn="[% To.json(SEARCH_RESULT.normalized_isbn) | $raw %]" data-imgtitle="[% img_title | html %]"> >- <a class="p1" href="/cgi-bin/koha/opac-detail.pl?biblionumber=[% SEARCH_RESULT.biblionumber | uri %]"> >- >- [% IF ( OPACLocalCoverImages ) %] >- <span title="[% img_title | html %]" class="[% SEARCH_RESULT.biblionumber | html %]" id="local-thumbnail[% loop.count | html %]"></span> >- [% END %] > >- [% IF ( OPACAmazonCoverImages ) %] >- [% IF ( SEARCH_RESULT.normalized_isbn ) %] >- <span title="[% img_title | html %]" id="amazon-thumbnail[% loop.count | html %]"><img src="https://images-na.ssl-images-amazon.com/images/P/[% SEARCH_RESULT.normalized_isbn | html %].01.TZZZZZZZ.jpg" alt="" class="item-thumbnail" /></span> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF ( SyndeticsEnabled && SyndeticsCoverImages ) %] >- [% IF ( SEARCH_RESULT.normalized_isbn || SEARCH_RESULT.normalized_upc || SEARCH_RESULT.normalized_oclc ) %] >- <span title="[% img_title | html %]" id="syndetics-thumbnail[% loop.count | html %]"><img src="https://secure.syndetics.com/index.aspx?isbn=[% SEARCH_RESULT.normalized_isbn | html %]/[% SyndeticsCoverImageSize | uri %].GIF&client=[% SyndeticsClientCode | html %]&type=xw10&upc=[% SEARCH_RESULT.normalized_upc | html %]&oclc=[% SEARCH_RESULT.normalized_oclc | html %]" alt="" class="item-thumbnail" /></span> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF ( GoogleJackets ) %] >- [% IF ( SEARCH_RESULT.normalized_isbn ) %] >- <span title="[% img_title | html %]" class="[% SEARCH_RESULT.normalized_isbn | html %]" id="gbs-thumbnail[% loop.count | html %]"></span> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF ( Koha.Preference('OpacCoce') && Koha.Preference('CoceProviders') ) %] >- [% coce_id = SEARCH_RESULT.normalized_ean || SEARCH_RESULT.normalized_isbn | html %] >- [% IF ( coce_id ) %] >- <span title="[% img_title | html %]" class="[% coce_id | html %]" id="coce-thumbnail[% loop.count | html %]"></span> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF OpenLibraryCovers %] >- [% IF SEARCH_RESULT.normalized_isbn %] >- <span title="[% img_title | html %]" class="[% SEARCH_RESULT.normalized_isbn | html %]" id="openlibrary-thumbnail[% loop.count | html %]"></span> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF ( BakerTaylorEnabled && !BakerTaylorBookstoreURL ) %] >- [% bt_id = ( SEARCH_RESULT.normalized_upc || SEARCH_RESULT.normalized_isbn ) %] >- [% IF ( bt_id ) %] >- <img alt="See Baker & Taylor" src="[% BakerTaylorImageURL | html %][% bt_id | html %]" /> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- </a> >- >- [% IF ( BakerTaylorEnabled && BakerTaylorBookstoreURL ) %] >- [% bt_id = ( SEARCH_RESULT.normalized_upc || SEARCH_RESULT.normalized_isbn ) %] >- [% IF ( bt_id ) %] >- <a href="https://[% BakerTaylorBookstoreURL | url %][% bt_id | url %]"> >- <img alt="See Baker & Taylor" src="[% BakerTaylorImageURL | html %][% bt_id | html %]" /> >- </a> >- [% ELSE %] >- <span class="no-image">No cover image available</span> >- [% END %] >- [% END %] >- >- [% IF Koha.Preference('OPACCustomCoverImages') AND Koha.Preference('CustomCoverImagesURL') %] >- [% SET custom_cover_image_url = SEARCH_RESULT.biblio_object.custom_cover_image_url %] >- [% IF custom_cover_image_url %] >- [% IF ( OPACURLOpenInNewWindow ) %] >- <a class="custom_cover_image" href="[% custom_cover_image_url | url %]" target="_blank" rel="noreferrer"><img alt="Cover image" src="[% custom_cover_image_url | url %]" /></a> >- [% ELSE %] >- <a class="custom_cover_image" href="[% custom_cover_image_url | url %]"><img alt="Cover image" src="[% custom_cover_image_url | url %]" /></a> >- [% END %] >- [% END %] >- [% END %] >- >- </div> > [% IF ( COinSinOPACResults && SEARCH_RESULT.coins ) %] > <!-- COinS / Openurl --> > <span class="Z3988" title="[% SEARCH_RESULT.coins | html %]"></span> >@@ -578,6 +501,7 @@ > [% IF OpenLibraryCovers || OpenLibrarySearch %] > [% Asset.js("js/openlibrary.js") | $raw %] > [% END %] >+ [% Asset.js("lib/Chocolat/js/chocolat.js") | $raw %] > [% CoverImagePlugins | $raw %] > <script> > [% IF ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( Koha.Preference( 'OPACHoldRequests' ) == 1 ) %] >@@ -688,6 +612,175 @@ > } > [% END # /IF OpacHighlightedWords %] > >+ function verify_cover_images() { >+ // Loop over each container in the template which contains covers >+ $(".cover-slider").each(function( index ){ >+ let biblionumber = $(this).data("biblionumber"); >+ let booktitle = $(this).data("title"); >+ var lightbox_descriptions = []; >+ $(this).find(".cover-image").each( function( index ){ >+ var div = $(this); >+ // Find the image in the container >+ var img = div.find("img")[0]; >+ if( img && $(img).length > 0 ){ >+ // All slides start hidden. If this is the first one, show it. >+ // Check if Amazon image is present >+ if ( div.hasClass("amazon-coverimg") ) { >+ let w = img.width; >+ let h = img.height; >+ if ((w == 1) || (h == 1)) { >+ // Amazon returned single-pixel placeholder >+ // Remove the container >+ div.remove(); >+ } else { >+ lightbox_descriptions.push(_("Amazon cover image (<a href='%s'>see the original image</a>)").format($(img).data('link'))); >+ } >+ } else if( div.hasClass("custom-coverimg") ){ >+ >+ if ( (img.complete != null) && (!img.complete) || img.naturalHeight == 0 ) { >+ // No image was loaded via the CustomCoverImages system preference >+ // Remove the container >+ div.remove(); >+ } else { >+ lightbox_descriptions.push(_("Custom cover image")); >+ } >+ } else if( div.hasClass("syndetics-coverimg") ){ >+ lightbox_descriptions.push(_("Image from Syndetics")) >+ } else if( div.hasClass("googlejacket-coverimg" ) ){ >+ if( img.naturalHeight ){ >+ lightbox_descriptions.push(_("Image from Google Books (<a href='%s'>see the original image</a>)").format($(img).data('link'))); >+ } >+ } else if( div.hasClass("openlibrary-coverimg") ){ >+ lightbox_descriptions.push(_("Image from OpenLibrary (<a href='%s'>see the original image</a>)").format($(img).data('link'))); >+ } else if( div.hasClass("coce-coverimg" ) ){ >+ // Identify which service's image is being loaded by Coce >+ var coce_description; >+ let src = $(img).attr("src"); >+ if( src.indexOf('amazon.com') >= 0 ){ >+ coce_description = _("Coce image from Amazon.com"); >+ } else if( src.indexOf('google.com') >= 0 ){ >+ coce_description = _("Coce image from Google Books"); >+ } else if( src.indexOf('openlibrary.org') >= 0 ){ >+ coce_description = _("Coce image from Open Library"); >+ } >+ div.find(".hint").html(coce_description); >+ lightbox_descriptions.push(coce_description); >+ } else if ( div.hasClass("bakertaylor-coverimg" ) ){ >+ lightbox_descriptions.push(_("Image from Baker & Taylor")); >+ } else if ( div.hasClass("cover-image local-coverimg" ) ){ >+ lightbox_descriptions.push(_("Local cover image")); >+ } else { >+ lightbox_descriptions.push(_("Cover image source unknown")); >+ } >+ } else { >+ div.remove(); >+ } >+ }); >+ >+ // Lightbox for cover images >+ Chocolat(this.querySelectorAll('.cover-image a'), { >+ description: function(){ >+ return lightbox_descriptions[this.settings.currentImageIndex]; >+ } >+ }); >+ >+ }); >+ >+ $(".cover-slider").each(function(){ >+ var coverSlide = this; >+ var coverImages = $(this).find(".cover-image"); >+ if( coverImages.length > 1 ){ >+ coverImages.each(function( index ){ >+ // If more that one image is present, add a navigation link >+ // for activating the slide >+ var covernav = $("<a href=\"#\" data-num=\"" + index + "\" class=\"cover-nav\"></a>"); >+ if( index == 0 ){ >+ // Set the first navigation link as active >+ $(covernav).addClass("nav-active"); >+ } >+ $(covernav).html("<i class=\"fa fa-circle\"></i>"); >+ $(coverSlide).append( covernav ); >+ }); >+ } >+ >+ $(coverSlide).find(".cover-image").eq(0).show(); >+ >+ // if( $(coverSlide).find(".cover-image").length < 1 ){ >+ // $(coverSlide).remove(); >+ // } else { >+ // This is a suboptimal workaround; we should do this via load, but >+ // the image code is scattered all over now. We come here now after >+ // window load and wait_for_images (so load completed). >+ var check_complete = 1; >+ $(coverSlide).find("img").each( function() { >+ if( !this.complete || this.naturalHeight == 0 ) check_complete = 0; >+ }); >+ if( check_complete ) $(coverSlide).removeClass('cover-slides'); >+ // } >+ }); >+ >+ $(".cover-slider").on("click",".cover-nav", function(e){ >+ e.preventDefault(); >+ var cover_slider = $(this).parent(); >+ // Adding click handler for cover image navigation links >+ var num = $(this).data("num"); >+ $(cover_slider).find(".cover-nav").removeClass("nav-active"); >+ $(this).addClass("nav-active"); >+ $(cover_slider).find(".cover-image").hide(); >+ $(cover_slider).find(".cover-image").eq( num ).show(); >+ }); >+ >+ $("#editions img").each(function(i){ >+ if ( this.src.indexOf('amazon.com') >= 0 ) { >+ let w = this.width; >+ let h = this.height; >+ if ((w == 1) || (h == 1)) { >+ this.src = 'https://images-na.ssl-images-amazon.com/images/G/01/x-site/icons/no-img-sm.gif'; >+ } else if ( (this.complete != null) && (!this.complete) || this.naturalHeight == 0 ) { >+ this.src = 'https://images-na.ssl-images-amazon.com/images/G/01/x-site/icons/no-img-sm.gif'; >+ } >+ } >+ }); >+ } /* /verify_images */ >+ >+ >+ let counter_wait = 0; >+ function wait_for_images(cb){ >+ >+ var loaded = 1; >+ counter_wait++; >+ >+ [% IF GoogleJackets %] >+ if ( loaded ) { >+ loaded = KOHA.Google.done; >+ } >+ [% END %] >+ >+ [% IF OpenLibraryCovers %] >+ if ( loaded ) { >+ loaded = KOHA.OpenLibrary.done; >+ } >+ [% END %] >+ >+ [% IF ( Koha.Preference('OpacCoce') && Koha.Preference('CoceProviders') ) %] >+ if ( loaded ) { >+ loaded = KOHA.coce.done; >+ } >+ [% END %] >+ >+ if (!loaded && counter_wait < 50) {// Do not wait more than 5 seconds >+ window.setTimeout(function(){wait_for_images(cb);}, 100); >+ } else { >+ if (counter_wait >= 50 ) { >+ } >+ cb(); >+ } >+ } >+ >+ $(window).load(function() { >+ wait_for_images(verify_cover_images); >+ }); >+ > $(document).ready(function(){ > > $(".moretoggle").click(function(e){ >-- >2.30.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 26933
:
113028
|
164630
|
164669
|
165794
|
165822
|
167794
|
167935
|
169601
|
170656
|
171508