mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
search hints progress
This commit is contained in:
parent
ba13b0837d
commit
5005e0d448
9 changed files with 287 additions and 40 deletions
BIN
dashboard-ui/css/images/items/searchhints/film.png
Normal file
BIN
dashboard-ui/css/images/items/searchhints/film.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
dashboard-ui/css/images/items/searchhints/game.png
Normal file
BIN
dashboard-ui/css/images/items/searchhints/game.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
dashboard-ui/css/images/items/searchhints/music.png
Normal file
BIN
dashboard-ui/css/images/items/searchhints/music.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
dashboard-ui/css/images/items/searchhints/person.png
Normal file
BIN
dashboard-ui/css/images/items/searchhints/person.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
dashboard-ui/css/images/items/searchhints/tv.png
Normal file
BIN
dashboard-ui/css/images/items/searchhints/tv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,6 +1,7 @@
|
||||||
.headerSearch {
|
.headerSearch {
|
||||||
display: none;
|
display: none;
|
||||||
margin-right: 3em;
|
margin-right: 3em;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.txtSearch {
|
.txtSearch {
|
||||||
|
@ -11,12 +12,6 @@
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchHints {
|
|
||||||
width: 148px;
|
|
||||||
color: #000;
|
|
||||||
font-family: Arial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btnSearch {
|
.btnSearch {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background-color: #4d90fe;
|
background-color: #4d90fe;
|
||||||
|
@ -50,16 +45,62 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchHints {
|
.searchHints {
|
||||||
|
width: 148px;
|
||||||
|
color: #000;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
margin-top: 1px;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchHintsContent {
|
.searchHintsContent {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchHint {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHint:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintImage {
|
||||||
|
display: inline-block;
|
||||||
|
width: 15%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintContent {
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintName {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintSecondaryText {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintContentInner {
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (min-width: 650px) {
|
@media all and (min-width: 650px) {
|
||||||
|
|
||||||
.headerSearch {
|
.headerSearch {
|
||||||
|
@ -87,3 +128,31 @@
|
||||||
width: 248px;
|
width: 248px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 1200px) {
|
||||||
|
.txtSearch {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHints {
|
||||||
|
width: 348px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 1440px) {
|
||||||
|
.txtSearch {
|
||||||
|
width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHints {
|
||||||
|
width: 448px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintImage {
|
||||||
|
width: 12%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHintContent {
|
||||||
|
width: 88%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -282,17 +282,22 @@
|
||||||
return item.url;
|
return item.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemByNameContext = itemByNameContext || "";
|
||||||
|
|
||||||
|
// Handle search hints
|
||||||
|
var id = item.Id || item.ItemId;
|
||||||
|
|
||||||
if (item.Type == "Series") {
|
if (item.Type == "Series") {
|
||||||
return "itemdetails.html?id=" + item.Id;
|
return "itemdetails.html?id=" + id;
|
||||||
}
|
}
|
||||||
if (item.Type == "Season") {
|
if (item.Type == "Season") {
|
||||||
return "itemdetails.html?id=" + item.Id;
|
return "itemdetails.html?id=" + id;
|
||||||
}
|
}
|
||||||
if (item.Type == "BoxSet") {
|
if (item.Type == "BoxSet") {
|
||||||
return "itemdetails.html?id=" + item.Id;
|
return "itemdetails.html?id=" + id;
|
||||||
}
|
}
|
||||||
if (item.Type == "MusicAlbum") {
|
if (item.Type == "MusicAlbum") {
|
||||||
return "itemdetails.html?id=" + item.Id;
|
return "itemdetails.html?id=" + id;
|
||||||
}
|
}
|
||||||
if (item.Type == "Genre") {
|
if (item.Type == "Genre") {
|
||||||
return "itembynamedetails.html?genre=" + encodeName(item.Name) + "&context=" + itemByNameContext;
|
return "itembynamedetails.html?genre=" + encodeName(item.Name) + "&context=" + itemByNameContext;
|
||||||
|
@ -304,10 +309,10 @@
|
||||||
return "itembynamedetails.html?person=" + encodeName(item.Name) + "&context=" + itemByNameContext;
|
return "itembynamedetails.html?person=" + encodeName(item.Name) + "&context=" + itemByNameContext;
|
||||||
}
|
}
|
||||||
if (item.Type == "Artist") {
|
if (item.Type == "Artist") {
|
||||||
return "itembynamedetails.html?artist=" + encodeName(item.Name) + "&context=" + itemByNameContext;
|
return "itembynamedetails.html?artist=" + encodeName(item.Name) + "&context=" + (itemByNameContext || "music");
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.IsFolder ? (item.Id ? "itemList.html?parentId=" + item.Id : "#") : "itemdetails.html?id=" + item.Id;
|
return item.IsFolder ? (id ? "itemList.html?parentId=" + id : "#") : "itemdetails.html?id=" + id;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -340,7 +345,8 @@
|
||||||
return ApiClient.getArtistImageUrl(item.Name, options);
|
return ApiClient.getArtistImageUrl(item.Name, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiClient.getImageUrl(item.Id, options);
|
// For search hints
|
||||||
|
return ApiClient.getImageUrl(item.Id || item.ItemId, options);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,205 @@
|
||||||
(function ($, document, window) {
|
(function ($, document, window, clearTimeout, setTimeout) {
|
||||||
|
|
||||||
|
var searchHintTimeout;
|
||||||
|
|
||||||
function createSearchHintsElement() {
|
function clearSearchHintTimeout() {
|
||||||
|
|
||||||
$(document.body).append('<div id="searchHints" class="searchHints"><div class="searchHintsContent">Coming soon<div><div>').off("mousedown.hidesearchhints").on("mousedown.hidesearchhints", function (e) {
|
if (searchHintTimeout) {
|
||||||
|
|
||||||
|
clearTimeout(searchHintTimeout);
|
||||||
|
searchHintTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSearchHintsElement(page) {
|
||||||
|
|
||||||
|
$(document.body).off("mousedown.hidesearchhints").on("mousedown.hidesearchhints", function (e) {
|
||||||
|
|
||||||
var elem = $(e.target);
|
var elem = $(e.target);
|
||||||
|
|
||||||
if (!elem.is('#searchHints,#txtSearch,#btnSearch') && !elem.parents('#searchHints,#txtSearch,#btnSearch').length) {
|
if (!elem.is('#searchHints,#txtSearch,#btnSearch') && !elem.parents('#searchHints,#txtSearch,#btnSearch').length) {
|
||||||
|
hideFlyout(page);
|
||||||
$('#searchHints').remove();
|
|
||||||
|
|
||||||
$(document.body).off("mousedown.hidesearchhints");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var txtElem = $('#txtSearch');
|
return $('#searchHints', page).show();
|
||||||
var pos = txtElem.offset();
|
|
||||||
|
|
||||||
var hints = $('#searchHints')[0];
|
|
||||||
|
|
||||||
hints.style.top = txtElem[0].offsetHeight + pos.top + 1 + "px";
|
|
||||||
hints.style.left = pos.left + "px";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSearchHints(searchTerm) {
|
function hideFlyout(page) {
|
||||||
|
$('#searchHints', page).hide();
|
||||||
|
|
||||||
var hints = $('#searchHints');
|
$(document.body).off("mousedown.hidesearchhints");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFlyout(page) {
|
||||||
|
|
||||||
|
var hints = $('#searchHints:visible');
|
||||||
|
|
||||||
if (!hints.length) {
|
if (!hints.length) {
|
||||||
|
|
||||||
hints = createSearchHintsElement();
|
createSearchHintsElement(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFlyout(page, searchTerm) {
|
||||||
|
|
||||||
|
if (!searchTerm) {
|
||||||
|
|
||||||
|
clearSearchHintTimeout();
|
||||||
|
hideFlyout(page);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSearchHintTimeout();
|
||||||
|
|
||||||
|
searchHintTimeout = setTimeout(function () {
|
||||||
|
|
||||||
|
requestSearchHints(page, searchTerm);
|
||||||
|
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestSearchHints(page, searchTerm) {
|
||||||
|
|
||||||
|
var currentTimeout = searchHintTimeout;
|
||||||
|
|
||||||
|
$.getJSON(ApiClient.getUrl("Search/Hints", { userId: Dashboard.getCurrentUserId(), searchTerm: searchTerm, limit: 10 })).done(function (result) {
|
||||||
|
|
||||||
|
if (currentTimeout != searchHintTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.TotalRecordCount) {
|
||||||
|
hideFlyout(page);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSearchHintResult(page, result.SearchHints);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function preg_quote(str, delimiter) {
|
||||||
|
// http://kevin.vanzonneveld.net
|
||||||
|
// + original by: booeyOH
|
||||||
|
// + improved by: Ates Goral (http://magnetiq.com)
|
||||||
|
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||||
|
// + bugfixed by: Onno Marsman
|
||||||
|
// + improved by: Brett Zamir (http://brett-zamir.me)
|
||||||
|
// * example 1: preg_quote("$40");
|
||||||
|
// * returns 1: '\$40'
|
||||||
|
// * example 2: preg_quote("*RRRING* Hello?");
|
||||||
|
// * returns 2: '\*RRRING\* Hello\?'
|
||||||
|
// * example 3: preg_quote("\\.+*?[^]$(){}=!<>|:");
|
||||||
|
// * returns 3: '\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:'
|
||||||
|
return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHintDisplayName(data, term) {
|
||||||
|
|
||||||
|
var regexp = new RegExp("(" + preg_quote(term) + ")", 'gi');
|
||||||
|
|
||||||
|
return data.replace(regexp, "<b>$1</b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchHintHtml(hint) {
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
html += '<a class="searchHint" href="' + LibraryBrowser.getHref(hint) + '">';
|
||||||
|
|
||||||
|
var imgUrl;
|
||||||
|
|
||||||
|
if (hint.PrimaryImageTag) {
|
||||||
|
|
||||||
|
hint.ImageTags = { Primary: hint.PrimaryImageTag };
|
||||||
|
imgUrl = LibraryBrowser.getImageUrl(hint, "Primary", 0, { maxwidth: 150, maxheight: 150 });
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (hint.MediaType == "Game") {
|
||||||
|
|
||||||
|
imgUrl = "css/images/items/searchhints/game.png";
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (hint.Type == "Episode" || hint.Type == "Season" || hint.Type == "Series") {
|
||||||
|
|
||||||
|
imgUrl = "css/images/items/searchhints/tv.png";
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (hint.Type == "Audio" || hint.Type == "MusicAlbum" || hint.Type == "Artist") {
|
||||||
|
|
||||||
|
imgUrl = "css/images/items/searchhints/music.png";
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (hint.Type == "Person") {
|
||||||
|
|
||||||
|
imgUrl = "css/images/items/searchhints/person.png";
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
imgUrl = "css/images/items/searchhints/film.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<img class="searchHintImage" src="' + imgUrl + '" />';
|
||||||
|
|
||||||
|
html += '<div class="searchHintContent"><div class="searchHintContentInner">';
|
||||||
|
|
||||||
|
html += '<div class="searchHintName">' + getHintDisplayName(hint.Name, hint.MatchedTerm) + '</div>';
|
||||||
|
|
||||||
|
if (hint.Type == "Audio") {
|
||||||
|
|
||||||
|
html += '<div class="searchHintSecondaryText">' + [hint.AlbumArtist, hint.Album].join(" - ") + '</div>';
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (hint.Type == "MusicAlbum") {
|
||||||
|
|
||||||
|
html += '<div class="searchHintSecondaryText">' + hint.AlbumArtist + '</div>';
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
html += '<div class="searchHintSecondaryText">' + (hint.DisplayMediaType || hint.Type) + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var text;
|
||||||
|
|
||||||
|
if (hint.EpisodeCount) {
|
||||||
|
|
||||||
|
text = hint.EpisodeCount == 1 ? "1 Episode" : hint.EpisodeCount + " Episodes";
|
||||||
|
|
||||||
|
html += '<div class="searchHintSecondaryText">' + text + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (hint.SongCount) {
|
||||||
|
|
||||||
|
text = hint.SongCount == 1 ? "1 Song" : hint.SongCount + " Songs";
|
||||||
|
|
||||||
|
html += '<div class="searchHintSecondaryText">' + text + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (hint.RunTimeTicks) {
|
||||||
|
html += '<div class="searchHintSecondaryText">' + DashboardPage.getDisplayText(hint.RunTimeTicks) + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div></div>';
|
||||||
|
|
||||||
|
|
||||||
|
html += '</a>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSearchHintResult(page, hints) {
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
for (var i = 0, length = hints.length; i < length; i++) {
|
||||||
|
html += getSearchHintHtml(hints[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#searchHints', page).html(html);
|
||||||
|
}
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -50,14 +214,17 @@
|
||||||
html += '<img src="css/images/searchbutton.png" />';
|
html += '<img src="css/images/searchbutton.png" />';
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
|
|
||||||
html += '</form></div>';
|
html += '</form>';
|
||||||
|
|
||||||
|
html += '<div id="searchHints" class="searchHints" style="display:none;"><div class="searchHintsContent"></div></div>';
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.onSearchRendered = function (parentElem) {
|
self.onSearchRendered = function (parentElem) {
|
||||||
|
|
||||||
|
|
||||||
$('#searchForm', parentElem).on("submit", function () {
|
$('#searchForm', parentElem).on("submit", function () {
|
||||||
|
|
||||||
Dashboard.alert('Coming soon.');
|
Dashboard.alert('Coming soon.');
|
||||||
|
@ -65,16 +232,19 @@
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#txtSearch', parentElem).on("keypress", function () {
|
$('#txtSearch', parentElem).on("keyup", function (e) {
|
||||||
|
|
||||||
renderSearchHints(this.value);
|
var value = this.value;
|
||||||
|
|
||||||
|
showFlyout(parentElem);
|
||||||
|
updateFlyout(parentElem, value);
|
||||||
|
|
||||||
}).on("focus", function () {
|
}).on("focus", function () {
|
||||||
|
|
||||||
var value = this.value;
|
var value = this.value;
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
renderSearchHints(value);
|
showFlyout(parentElem, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -84,4 +254,4 @@
|
||||||
|
|
||||||
window.Search = new search();
|
window.Search = new search();
|
||||||
|
|
||||||
})(jQuery, document, window);
|
})(jQuery, document, window, clearTimeout, setTimeout);
|
|
@ -696,7 +696,9 @@ var Dashboard = {
|
||||||
|
|
||||||
if (user && !page.hasClass('wizardPage')) {
|
if (user && !page.hasClass('wizardPage')) {
|
||||||
|
|
||||||
|
if (isLibraryPage) {
|
||||||
headerHtml += Search.getSearchHtml();
|
headerHtml += Search.getSearchHtml();
|
||||||
|
}
|
||||||
|
|
||||||
headerHtml += '<a class="imageLink btnCurrentUser" href="#" onclick="Dashboard.showUserFlyout();"><span class="currentUsername">' + user.Name + '</span>';
|
headerHtml += '<a class="imageLink btnCurrentUser" href="#" onclick="Dashboard.showUserFlyout();"><span class="currentUsername">' + user.Name + '</span>';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue