mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-31 15:23:02 +00:00 
			
		
		
		
	Meta data improvements
* Add buttons to show/hide results from Douban & Google * Trigger search with "Enter" key * Use Underscore.js template instead of build HTML strings in JavaScript * Keep click event handler in JavaScript instead of using HTML's `onclick` * Normalize Douban & Google results * Update cover image & add cover URL to the form input
This commit is contained in:
		| @@ -55,6 +55,27 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te | |||||||
| .block-label {display: block;} | .block-label {display: block;} | ||||||
| .fake-input {position: absolute; pointer-events: none; top: 0;} | .fake-input {position: absolute; pointer-events: none; top: 0;} | ||||||
|  |  | ||||||
|  | input.pill { position: absolute; opacity: 0; } | ||||||
|  | input.pill + label { | ||||||
|  |     border: 2px solid #45b29d; | ||||||
|  |     border-radius: 15px; | ||||||
|  |     color: #45b29d; | ||||||
|  |     cursor: pointer; | ||||||
|  |     display: inline-block; | ||||||
|  |     padding: 3px 15px; | ||||||
|  |     user-select: none; | ||||||
|  |     -webkit-font-smoothing: antialiased; | ||||||
|  |     -moz-osx-font-smoothing: grayscale; | ||||||
|  | } | ||||||
|  | input.pill:checked + label { | ||||||
|  |     background-color: #45b29d; | ||||||
|  |     border-color: #fff; | ||||||
|  |     color: #fff; | ||||||
|  | } | ||||||
|  | input.pill:not(:checked) + label .glyphicon { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| .author-bio img {margin: 0 1em 1em 0;} | .author-bio img {margin: 0 1em 1em 0;} | ||||||
| .author-link img {display: inline-block;max-width: 100px;} | .author-link img {display: inline-block;max-width: 100px;} | ||||||
|  |  | ||||||
| @@ -63,4 +84,8 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te | |||||||
|     margin-left: 5px; |     margin-left: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .tags_click, .serie_click, .language_click {margin-right: 5px;} | .tags_click, .serie_click, .language_click {margin-right: 5px;} | ||||||
|  |  | ||||||
|  | #meta-info img { max-height: 150px; max-width: 100px; cursor: pointer; } | ||||||
|  |  | ||||||
|  | .padded-bottom { margin-bottom: 15px; } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
|  * Google Books api document: https://developers.google.com/books/docs/v1/using |  * Google Books api document: https://developers.google.com/books/docs/v1/using | ||||||
|  * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only) |  * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only) | ||||||
|  */ |  */ | ||||||
|  /* global i18nMsg, tinymce */ |  /* global _, i18nMsg, tinymce */ | ||||||
| var dbResults = []; | var dbResults = []; | ||||||
| var ggResults = []; | var ggResults = []; | ||||||
|  |  | ||||||
| @@ -23,64 +23,93 @@ $(function () { | |||||||
|  |  | ||||||
|     var showFlag = 0; |     var showFlag = 0; | ||||||
|  |  | ||||||
|  |     var templates = { | ||||||
|  |         bookResult: _.template( | ||||||
|  |             $("#template-book-result").html() | ||||||
|  |         ) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     function populateForm (book) { | ||||||
|  |         tinymce.get("description").setContent(book.description); | ||||||
|  |         $("#bookAuthor").val(book.authors); | ||||||
|  |         $("#book_title").val(book.title); | ||||||
|  |         $("#tags").val(book.tags.join(",")); | ||||||
|  |         $("#rating").data("rating").setValue(Math.round(book.rating)); | ||||||
|  |         $(".cover img").attr("src", book.cover); | ||||||
|  |         $("#cover_url").val(book.cover); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     function showResult () { |     function showResult () { | ||||||
|         var book; |  | ||||||
|         var i; |  | ||||||
|         var bookHtml; |  | ||||||
|         showFlag++; |         showFlag++; | ||||||
|         if (showFlag === 1) { |         if (showFlag === 1) { | ||||||
|             $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>"); |             $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>"); | ||||||
|         } |         } | ||||||
|         if (ggDone && dbDone) { |         if (ggDone && dbDone) { | ||||||
|             if (!ggResults && !dbResults) { |             if (!ggResults && !dbResults) { | ||||||
|                 $("#meta-info").html("<p class=\"text-danger\">"+ msg.no_result +"</p>"); |                 $("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>"); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (ggDone && ggResults.length > 0) { |         if (ggDone && ggResults.length > 0) { | ||||||
|             for (i = 0; i < ggResults.length; i++) { |             ggResults.forEach(function(result) { | ||||||
|                 book = ggResults[i]; |                 var book = { | ||||||
|                 var bookCover; |                     id: result.id, | ||||||
|                 if (book.volumeInfo.imageLinks) { |                     title: result.volumeInfo.title, | ||||||
|                     bookCover = book.volumeInfo.imageLinks.thumbnail; |                     authors: result.volumeInfo.authors || [], | ||||||
|                 } else { |                     description: result.volumeInfo.description || "", | ||||||
|                     bookCover = "/static/generic_cover.jpg"; |                     publisher: result.volumeInfo.publisher || "", | ||||||
|                 } |                     publishedDate: result.volumeInfo.publishedDate || "", | ||||||
|                 bookHtml = "<li class=\"media\">" + |                     tags: result.volumeInfo.categories || [], | ||||||
|                     "<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" + |                     rating: result.volumeInfo.averageRating || 0, | ||||||
|                     bookCover + "\" alt=\"Cover\" style=\"width:100px;height:150px\" onclick='getMeta(\"google\"," + |                     cover: result.volumeInfo.imageLinks ? | ||||||
|                     i + ")'>" + |                         result.volumeInfo.imageLinks.thumbnail : | ||||||
|                     "<div class=\"media-body\">" + |                         "/static/generic_cover.jpg", | ||||||
|                     "<h4 class=\"media-heading\"><a href=\"https://books.google.com/books?id=" + |                     url: "https://books.google.com/books?id=" + result.id, | ||||||
|                     book.id + "\"  target=\"_blank\">" + book.volumeInfo.title + "</a></h4>" + |                     source: { | ||||||
|                     "<p>"+ msg.author +":" + book.volumeInfo.authors + "</p>" + |                         id: "google", | ||||||
|                     "<p>"+ msg.publisher + ":" + book.volumeInfo.publisher + "</p>" + |                         description: "Google Books", | ||||||
|                     "<p>"+ msg.description + ":" + book.volumeInfo.description + "</p>" + |                         url: "https://books.google.com/" | ||||||
|                     "<p>"+ msg.source + ":<a href=\"https://books.google.com\" target=\"_blank\">Google Books</a></p>" + |                     } | ||||||
|                     "</div>" + |                 }; | ||||||
|                     "</li>"; |  | ||||||
|                 $("#book-list").append(bookHtml); |                 var $book = $(templates.bookResult(book)); | ||||||
|             } |                 $book.find("img").on("click", function () { | ||||||
|  |                     populateForm(book); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 $("#book-list").append($book); | ||||||
|  |             }); | ||||||
|             ggDone = false; |             ggDone = false; | ||||||
|         } |         } | ||||||
|         if (dbDone && dbResults.length > 0) { |         if (dbDone && dbResults.length > 0) { | ||||||
|             for (i = 0; i < dbResults.length; i++) { |             dbResults.forEach(function(result) { | ||||||
|                 book = dbResults[i]; |                 var book = { | ||||||
|                 bookHtml = "<li class=\"media\">" + |                     id: result.id, | ||||||
|                     "<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" + |                     title: result.title, | ||||||
|                     book.image + "\" alt=\"Cover\" style=\"width:100px;height: 150px\" onclick='getMeta(\"douban\"," + |                     authors: result.author || [], | ||||||
|                     i + ")'>" + |                     description: result.summary, | ||||||
|                     "<div class=\"media-body\">" + |                     publisher: result.publisher || "", | ||||||
|                     "<h4 class=\"media-heading\"><a href=\"https://book.douban.com/subject/" + |                     publishedDate: result.pubdate || "", | ||||||
|                     book.id + "\"  target=\"_blank\">" + book.title + "</a></h4>" + |                     tags: result.tags.map(function(tag) { | ||||||
|                     "<p>" + msg.author + ":" + book.author + "</p>" + |                         return tag.title; | ||||||
|                     "<p>" + msg.publisher + ":" + book.publisher + "</p>" + |                     }), | ||||||
|                     "<p>" + msg.description + ":" + book.summary + "</p>" + |                     rating: result.rating.average || 0, | ||||||
|                     "<p>" + msg.source + ":<a href=\"https://book.douban.com\" target=\"_blank\">Douban Books</a></p>" + |                     cover: result.image, | ||||||
|                     "</div>" + |                     url: "https://book.douban.com/subject/" + result.id, | ||||||
|                     "</li>"; |                     source: { | ||||||
|                 $("#book-list").append(bookHtml); |                         id: "douban", | ||||||
|             } |                         description: "Douban Books", | ||||||
|  |                         url: "https://book.douban.com/" | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 var $book = $(templates.bookResult(book)); | ||||||
|  |                 $book.find("img").on("click", function () { | ||||||
|  |                     populateForm(book); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 $("#book-list").append($book); | ||||||
|  |             }); | ||||||
|             dbDone = false; |             dbDone = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -97,6 +126,7 @@ $(function () { | |||||||
|             complete: function complete() { |             complete: function complete() { | ||||||
|                 ggDone = true; |                 ggDone = true; | ||||||
|                 showResult(); |                 showResult(); | ||||||
|  |                 $("#show-google").trigger("change"); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -111,11 +141,12 @@ $(function () { | |||||||
|                 dbResults = data.books; |                 dbResults = data.books; | ||||||
|             }, |             }, | ||||||
|             error: function error() { |             error: function error() { | ||||||
|                 $("#meta-info").html("<p class=\"text-danger\">"+ msg.search_error+"!</p>"); |                 $("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>"); | ||||||
|             }, |             }, | ||||||
|             complete: function complete() { |             complete: function complete() { | ||||||
|                 dbDone = true; |                 dbDone = true; | ||||||
|                 showResult(); |                 showResult(); | ||||||
|  |                 $("#show-douban").trigger("change"); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -130,7 +161,8 @@ $(function () { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $("#do-search").click(function () { |     $("#meta-search").on("submit", function (e) { | ||||||
|  |         e.preventDefault(); | ||||||
|         var keyword = $("#keyword").val(); |         var keyword = $("#keyword").val(); | ||||||
|         if (keyword) { |         if (keyword) { | ||||||
|             doSearch(keyword); |             doSearch(keyword); | ||||||
| @@ -146,35 +178,3 @@ $(function () { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // eslint-disable-next-line no-unused-vars |  | ||||||
| function getMeta (source, id) { |  | ||||||
|     var meta; |  | ||||||
|     var tags; |  | ||||||
|     if (source === "google") { |  | ||||||
|         meta = ggResults[id]; |  | ||||||
|         tinymce.get("description").setContent(meta.volumeInfo.description); |  | ||||||
|         $("#bookAuthor").val(meta.volumeInfo.authors.join(" & ")); |  | ||||||
|         $("#book_title").val(meta.volumeInfo.title); |  | ||||||
|         if (meta.volumeInfo.categories) { |  | ||||||
|             tags = meta.volumeInfo.categories.join(","); |  | ||||||
|             $("#tags").val(tags); |  | ||||||
|         } |  | ||||||
|         if (meta.volumeInfo.averageRating) { |  | ||||||
|             $("#rating").val(Math.round(meta.volumeInfo.averageRating)); |  | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     if (source === "douban") { |  | ||||||
|         meta = dbResults[id]; |  | ||||||
|         tinymce.get("description").setContent(meta.summary); |  | ||||||
|         $("#bookAuthor").val(meta.author.join(" & ")); |  | ||||||
|         $("#book_title").val(meta.title); |  | ||||||
|         tags = ""; |  | ||||||
|         for (var i = 0; i < meta.tags.length; i++) { |  | ||||||
|             tags = tags + meta.tags[i].title + ","; |  | ||||||
|         } |  | ||||||
|         $("#tags").val(tags); |  | ||||||
|         $("#rating").val(Math.round(meta.rating.average / 2)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1 +1,2 @@ | |||||||
| !function(a){"use strict";function b(a){return"[data-value"+(a?"="+a:"")+"]"}function c(a,b,c){var d=c.activeIcon,e=c.inactiveIcon;a.removeClass(b?e:d).addClass(b?d:e)}function d(b,c){var d=a.extend({},i,b.data(),c);return d.inline=""===d.inline||d.inline,d.readonly=""===d.readonly||d.readonly,d.clearable===!1?d.clearableLabel="":d.clearableLabel=d.clearable,d.clearable=""===d.clearable||d.clearable,d}function e(b,c){if(c.inline)var d=a('<span class="rating-input"></span>');else var d=a('<div class="rating-input"></div>');d.addClass(b.attr("class")),d.removeClass("rating");for(var e=c.min;e<=c.max;e++)d.append('<i class="'+c.iconLib+'" data-value="'+e+'"></i>');return c.clearable&&!c.readonly&&d.append(" ").append('<a class="'+f+'"><i class="'+c.iconLib+" "+c.clearableIcon+'"/>'+c.clearableLabel+"</a>"),d}var f="rating-clear",g="."+f,h="hidden",i={min:1,max:5,"empty-value":0,iconLib:"glyphicon",activeIcon:"glyphicon-star",inactiveIcon:"glyphicon-star-empty",clearable:!1,clearableIcon:"glyphicon-remove",clearableRemain:!1,inline:!1,readonly:!1},j=function(a,b){var c=this.$input=a;this.options=d(c,b);var f=this.$el=e(c,this.options);c.addClass(h).before(f),c.attr("type","hidden"),this.highlight(c.val())};j.VERSION="0.4.0",j.DEFAULTS=i,j.prototype={clear:function(){this.setValue(this.options["empty-value"])},setValue:function(a){this.highlight(a),this.updateInput(a)},highlight:function(a,d){var e=this.options,f=this.$el;if(a>=this.options.min&&a<=this.options.max){var i=f.find(b(a));c(i.prevAll("i").andSelf(),!0,e),c(i.nextAll("i"),!1,e)}else c(f.find(b()),!1,e);d||(this.options.clearableRemain?f.find(g).removeClass(h):a&&a!=this.options["empty-value"]?f.find(g).removeClass(h):f.find(g).addClass(h))},updateInput:function(a){var b=this.$input;b.val()!=a&&b.val(a).change()}};var k=a.fn.rating=function(c){return this.filter("input[type=number]").each(function(){var d=a(this),e="object"==typeof c&&c||{},f=new j(d,e);f.options.readonly||f.$el.on("mouseenter",b(),function(){f.highlight(a(this).data("value"),!0)}).on("mouseleave",b(),function(){f.highlight(d.val(),!0)}).on("click",b(),function(){f.setValue(a(this).data("value"))}).on("click",g,function(){f.clear()})})};k.Constructor=j,a(function(){a("input.rating[type=number]").each(function(){a(this).rating()})})}(jQuery); | /** @link https://github.com/javiertoledo/bootstrap-rating-input */ | ||||||
|  | !function(a){"use strict";function n(a){return"[data-value"+(a?"="+a:"")+"]"}function e(a,n,e){var i=e.activeIcon,t=e.inactiveIcon;a.removeClass(n?t:i).addClass(n?i:t)}function i(n,e){var i=a.extend({},s,n.data(),e);return i.inline=""===i.inline||i.inline,i.readonly=""===i.readonly||i.readonly,!1===i.clearable?i.clearableLabel="":i.clearableLabel=i.clearable,i.clearable=""===i.clearable||i.clearable,i}function t(n,e){if(e.inline)i=a('<span class="rating-input"></span>');else var i=a('<div class="rating-input"></div>');i.addClass(n.attr("class")),i.removeClass("rating");for(var t=e.min;t<=e.max;t++)i.append('<i class="'+e.iconLib+'" data-value="'+t+'"></i>');return e.clearable&&!e.readonly&&i.append(" ").append('<a class="'+l+'"><i class="'+e.iconLib+" "+e.clearableIcon+'"/>'+e.clearableLabel+"</a>"),i}var l="rating-clear",o="."+l,s={min:1,max:5,"empty-value":0,iconLib:"glyphicon",activeIcon:"glyphicon-star",inactiveIcon:"glyphicon-star-empty",clearable:!1,clearableIcon:"glyphicon-remove",clearableRemain:!1,inline:!1,readonly:!1},r=function(a,n){var e=this.$input=a;this.options=i(e,n);var l=this.$el=t(e,this.options);e.addClass("hidden").before(l),e.attr("type","hidden"),this.highlight(e.val())};r.VERSION="0.4.0",r.DEFAULTS=s,r.prototype={clear:function(){this.setValue(this.options["empty-value"])},setValue:function(a){this.highlight(a),this.updateInput(a)},highlight:function(a,i){var t=this.options,l=this.$el;if(a>=this.options.min&&a<=this.options.max){var s=l.find(n(a));e(s.prevAll("i").addBack(),!0,t),e(s.nextAll("i"),!1,t)}else e(l.find(n()),!1,t);i||(this.options.clearableRemain?l.find(o).removeClass("hidden"):a&&a!=this.options["empty-value"]?l.find(o).removeClass("hidden"):l.find(o).addClass("hidden"))},updateInput:function(a){var n=this.$input;n.val()!=a&&n.val(a).change()}},(a.fn.rating=function(e){return this.filter("input[type=number]").each(function(){var i=a(this),t=new r(i,"object"==typeof e&&e||{});t.options.readonly||(t.$el.on("mouseenter",n(),function(){t.highlight(a(this).data("value"),!0)}).on("mouseleave",n(),function(){t.highlight(i.val(),!0)}).on("click",n(),function(){t.setValue(a(this).data("value"))}).on("click",o,function(){t.clear()}),i.data("rating",t))})}).Constructor=r,a(function(){a("input.rating[type=number]").each(function(){a(this).rating()})})}(jQuery); | ||||||
|   | |||||||
| @@ -154,22 +154,35 @@ | |||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
| <div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel"> | <div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel"> | ||||||
|   <div class="modal-dialog" role="document"> |   <div class="modal-dialog modal-lg" role="document"> | ||||||
|     <div class="modal-content"> |     <div class="modal-content"> | ||||||
|       <div class="modal-header"> |       <div class="modal-header"> | ||||||
|         <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> |         <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | ||||||
|         <h4 class="modal-title" id="metaModalLabel">{{_('Get metadata')}}</h4> |         <h4 class="modal-title" id="metaModalLabel">{{_('Get metadata')}}</h4> | ||||||
|         <form class="form-inline"> |         <form class="padded-bottom" id="meta-search"> | ||||||
|           <div class="form-group"> |           <div class="input-group"> | ||||||
|             <label class="sr-only" for="keyword">{{_('Keyword')}}</label> |             <label class="sr-only" for="keyword">{{_('Keyword')}}</label> | ||||||
|             <input type="text" class="form-control" id="keyword" placeholder="{{_(" Search keyword ")}}"> |             <input type="text" class="form-control" id="keyword" name="keyword" placeholder="{{_(" Search keyword ")}}"> | ||||||
|  |             <span class="input-group-btn"> | ||||||
|  |               <button type="submit" class="btn btn-primary" id="do-search">{{_("Go!")}}</button> | ||||||
|  |             </span> | ||||||
|           </div> |           </div> | ||||||
|           <button type="button" class="btn btn-default" id="do-search">{{_("Go!")}}</button> |  | ||||||
|           <span>{{_('Click the cover to load metadata to the form')}}</span> |  | ||||||
|         </form> |         </form> | ||||||
|  |         <div>{{_('Click the cover to load metadata to the form')}}</div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="modal-body" id="meta-info"> |       <div class="modal-body"> | ||||||
|         {{_("Loading...")}} |         <div class="text-center padded-bottom"> | ||||||
|  |           <input type="checkbox" id="show-douban" class="pill" data-control="douban" checked> | ||||||
|  |           <label for="show-douban">Douban <span class="glyphicon glyphicon-ok"></span></label> | ||||||
|  |  | ||||||
|  |           <input type="checkbox" id="show-google" class="pill" data-control="google" checked> | ||||||
|  |           <label for="show-google">Google <span class="glyphicon glyphicon-ok"></span></label> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div id="meta-info"> | ||||||
|  |           {{_("Loading...")}} | ||||||
|  |         </div> | ||||||
|  |         <ul id="book-list" class="media-list"></ul> | ||||||
|       </div> |       </div> | ||||||
|       <div class="modal-footer"> |       <div class="modal-footer"> | ||||||
|         <button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button> |         <button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button> | ||||||
| @@ -180,6 +193,31 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block js %} | {% block js %} | ||||||
|  | <script type="text/template" id="template-book-result"> | ||||||
|  |   <li class="media" data-related="<%= source.id %>"> | ||||||
|  |     <img class="pull-left img-responsive" | ||||||
|  |          data-toggle="modal" | ||||||
|  |          data-target="#metaModal" | ||||||
|  |          src="<%= cover %>" | ||||||
|  |          alt="Cover" | ||||||
|  |     > | ||||||
|  |     <div class="media-body"> | ||||||
|  |       <h4 class="media-heading"> | ||||||
|  |         <a href="<%= url %>" target="_blank" rel="noopener"><%= title %></a> | ||||||
|  |       </h4> | ||||||
|  |       <p>{{_('Author')}}:<%= authors.join(" & ") %></p> | ||||||
|  |       <% if (publisher) { %> | ||||||
|  |         <p>{{_('Publisher')}}:<%= publisher %></p> | ||||||
|  |       <% } %> | ||||||
|  |       <% if (description) { %> | ||||||
|  |         <p>{{_('Description')}}: <%= description %></p> | ||||||
|  |       <% } %> | ||||||
|  |       <p>{{_('Source')}}: | ||||||
|  |         <a href="<%= source.url %>" target="_blank" rel="noopener"><%= source.description %></a> | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |   </li> | ||||||
|  | </script> | ||||||
| <script> | <script> | ||||||
|   var i18nMsg = { |   var i18nMsg = { | ||||||
|     'loading': {{_('Loading...')|safe|tojson}}, |     'loading': {{_('Loading...')|safe|tojson}}, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Rehm
					Jonathan Rehm