Search Overhaul p1

This commit is contained in:
Thomas Hounsell 2014-11-27 19:34:20 +00:00
parent 611089a747
commit b2698e6e42
14 changed files with 1924 additions and 158 deletions

View File

@ -11,6 +11,9 @@ public static void RegisterBundles(BundleCollection bundles)
bundles.Add(new ScriptBundle("~/bundles/jquery").Include( bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js")); "~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jsrender").Include(
"~/Scripts/jsrender*"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*")); "~/Scripts/jquery.validate*"));

View File

@ -162,6 +162,7 @@
<Compile Include="Global.asax.cs"> <Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon> <DependentUpon>Global.asax</DependentUpon>
</Compile> </Compile>
<Compile Include="Models\ApiModel\SearchResult.cs" />
<Compile Include="Models\Build.cs" /> <Compile Include="Models\Build.cs" />
<Compile Include="Models\ViewModel\LoginUser.cs" /> <Compile Include="Models\ViewModel\LoginUser.cs" />
<Compile Include="Models\ViewModel\ChangePassword.cs" /> <Compile Include="Models\ViewModel\ChangePassword.cs" />
@ -184,6 +185,7 @@
<None Include="Scripts\jquery-2.1.1.intellisense.js" /> <None Include="Scripts\jquery-2.1.1.intellisense.js" />
<Content Include="googleacffc6da14c53e15.html" /> <Content Include="googleacffc6da14c53e15.html" />
<Content Include="content\tile\large.png" /> <Content Include="content\tile\large.png" />
<Content Include="Scripts\bfs.js" />
<Content Include="Scripts\jquery-2.1.1.js" /> <Content Include="Scripts\jquery-2.1.1.js" />
<Content Include="Scripts\jquery-2.1.1.min.js" /> <Content Include="Scripts\jquery-2.1.1.min.js" />
<Content Include="Scripts\jquery-2.1.1.min.map" /> <Content Include="Scripts\jquery-2.1.1.min.map" />
@ -224,7 +226,6 @@
<Folder Include="App_Data\" /> <Folder Include="App_Data\" />
<Folder Include="Areas\admin\Models\" /> <Folder Include="Areas\admin\Models\" />
<Folder Include="Areas\admin\Views\Shared\" /> <Folder Include="Areas\admin\Views\Shared\" />
<Folder Include="Models\ApiModel\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="packages.config" /> <Content Include="packages.config" />

View File

@ -5,6 +5,7 @@
using System.Net.Http; using System.Net.Http;
using System.Web.Http; using System.Web.Http;
using BuildFeed.Models; using BuildFeed.Models;
using BuildFeed.Models.ApiModel;
namespace BuildFeed.Controllers namespace BuildFeed.Controllers
{ {
@ -17,7 +18,56 @@ public IEnumerable<Build> GetBuilds()
public IEnumerable<string> GetWin10Labs() public IEnumerable<string> GetWin10Labs()
{ {
return Build.SelectBuildLabs(6, 4); List<string> labs = new List<string>();
labs.AddRange(Build.SelectBuildLabs(6, 4));
labs.AddRange(Build.SelectBuildLabs(10, 0));
return labs.ToArray();
}
public IEnumerable<SearchResult> GetSearchResult(string query)
{
List<SearchResult> results = new List<SearchResult>();
var yearResults = from y in Build.SelectBuildYears()
where y.ToString().Contains(query)
orderby y descending
select new SearchResult()
{
Url = Url.Route("Year Root", new { controller = "build", action = "year", year = y }),
Label = y.ToString().Replace(query, "<strong>" + query + "</strong>"),
Group = "Year"
};
results.AddRange(yearResults);
var labResults = from l in Build.SelectBuildLabs()
where l.Contains(query)
orderby l.IndexOf(query) ascending
select new SearchResult()
{
Url = Url.Route("Lab Root", new { controller = "build", action = "lab", lab = l }),
Label = l.Replace(query, "<strong>" + query + "</strong>"),
Group = "Lab"
};
results.AddRange(labResults);
var buildResults = from b in Build.Select()
where b.FullBuildString.Contains(query)
orderby b.FullBuildString.IndexOf(query) ascending,
b.BuildTime descending
select new SearchResult()
{
Url = Url.Route("Actions", new { controller = "build", action = "info", id = b.Id }),
Label = b.FullBuildString.Replace(query, "<strong>" + query + "</strong>"),
Group = "Build"
};
results.AddRange(buildResults);
return results.Take(6);
} }
} }
} }

View File

@ -70,7 +70,7 @@ public async Task<ActionResult> added()
Title = build.FullBuildString, Title = build.FullBuildString,
Link = new RssUrl(string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Action("info", new { controller = "Build", id = build.Id }))), Link = new RssUrl(string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Action("info", new { controller = "Build", id = build.Id }))),
Guid = new RssGuid() { IsPermaLink = true, Value = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Action("info", new { controller = "Build", id = build.Id })) }, Guid = new RssGuid() { IsPermaLink = true, Value = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Action("info", new { controller = "Build", id = build.Id })) },
//PubDate = build.Added PubDate = build.Added
}).ToList() }).ToList()
} }
}; };

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace BuildFeed.Models.ApiModel
{
public class SearchResult
{
public string Url { get; set; }
public string Label { get; set; }
public string Group { get; set; }
}
}

View File

@ -2,3 +2,5 @@
/// <reference path="jquery-2.1.1.js" /> /// <reference path="jquery-2.1.1.js" />
/// <reference path="jquery.validate.unobtrusive.js" /> /// <reference path="jquery.validate.unobtrusive.js" />
/// <reference path="jquery.validate.js" /> /// <reference path="jquery.validate.js" />
/// <reference path="bfs.js" />
/// <reference path="jsrender.js" />

15
Scripts/bfs.js Normal file
View File

@ -0,0 +1,15 @@
$(function () {
$("#search-input").change(function () {
var search = $(this);
$(this).parent().find(".list-group").remove();
$.ajax("/api/GetSearchResult/?query=" + $(this).val()).done(function (data) {
var template = $.templates("#result-template");
var content = $("<div class='list-group'></div>");
var item = template.render(data);
content.append(item);
search.after(content);
});
});
});

1712
Scripts/jsrender.js Normal file

File diff suppressed because it is too large Load Diff

6
Scripts/jsrender.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -103,137 +103,28 @@
</ul> </ul>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<div class="panel-group" id="filter-list"> <div class="panel panel-default panel-search">
<div class="panel panel-default"> <div class="panel-body">
<div class="panel-heading"> @Html.TextBox("search-input", "", new { @class = "form-control" })
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#filter-clear">
Clear filters
</a>
</h4>
</div>
<div id="filter-clear" class="panel-collapse collapse @(ViewBag.Action == "Index" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
<li>@Html.ActionLink("Clear Filters", "index", new { page = 1 })</li>
</ul>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#filter-version">
Filter by version
</a>
</h4>
</div>
<div id="filter-version" class="panel-collapse collapse @(ViewBag.Action == "Version" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
@foreach (BuildFeed.Models.BuildVersion ver in BuildFeed.Models.Build.SelectBuildVersions())
{
<li><a href="@Url.Action("version", new { minor = ver.Minor, major = ver.Major, page = 1 })">@string.Format("{0}.{1}", ver.Major, ver.Minor)</a></li>
}
</ul>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#filter-lab">
Filter by lab
</a>
</h4>
</div>
<div id="filter-lab" class="panel-collapse collapse @(ViewBag.Action == "Lab" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
@foreach (string lab in BuildFeed.Models.Build.SelectBuildLabs())
{
<li>@Html.ActionLink(lab, "lab", new { lab = lab, page = 1 })</li>
}
</ul>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#filter-year">
Filter by year
</a>
</h4>
</div>
<div id="filter-year" class="panel-collapse collapse @(ViewBag.Action == "Year" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
@foreach (int year in BuildFeed.Models.Build.SelectBuildYears())
{
<li>@Html.ActionLink(year.ToString(), "year", new { year = year, page = 1 })</li>
}
</ul>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#filter-source">
Filter by source
</a>
</h4>
</div>
<div id="filter-source" class="panel-collapse collapse @(ViewBag.Action == "Source" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
@foreach (BuildFeed.Models.TypeOfSource s in Enum.GetValues(typeof(BuildFeed.Models.TypeOfSource)).Cast<BuildFeed.Models.TypeOfSource>().OrderBy(d => d.ToString()))
{
<li><a href="@Url.Action("source", new { source = s })">@Html.DisplayFor(TypeOfSource => s, "Enumeration")</a></li>
}
</ul>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<a data-toggle="collapse" data-parent="#filter-list" href="#admin-actions">
Website Administration
</a>
</h4>
</div>
<div id="admin-actions" class="panel-collapse collapse @(ViewBag.Action == "Source" ? "in" : "")">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
@if (User.Identity.IsAuthenticated)
{
if (User.Identity.Name == "hounsell")
{
<li>@Html.ActionLink("Administration", "index", new { controller = "base", area = "admin" })</li>
<li>&nbsp;</li>
}
<li>@Html.ActionLink("Add a build", "create", "build")</li>
<li>&nbsp;</li>
<li>@Html.ActionLink("Change your password", "password", "support")</li>
<li>@Html.ActionLink("Log out", "logout", "support")</li>
}
else
{
<li>@Html.ActionLink("Log in", "login", "support")</li>
<li>@Html.ActionLink("Register", "register", "support")</li>
}
</ul>
</div>
</div>
</div> </div>
</div> </div>
<div class="list-group">
@if (User.Identity.IsAuthenticated)
{
if (User.Identity.Name == "hounsell")
{
@Html.ActionLink("Administration", "index", new { controller = "base", area = "admin" }, new { @class = "list-group-item" })
}
@Html.ActionLink("Add a build", "create", new { controller = "build" }, new { @class = "list-group-item" })
@Html.ActionLink("Change your password", "password", new { controller = "support" }, new { @class = "list-group-item" })
@Html.ActionLink("Log out", "logout", new { controller = "support" }, new { @class = "list-group-item" })
}
else
{
@Html.ActionLink("Log in", "login", new { controller = "support" }, new { @class = "list-group-item" })
@Html.ActionLink("Register", "register", new { controller = "support" }, new { @class = "list-group-item" })
}
</div>
</div> </div>
</div> </div>
@ -266,3 +157,15 @@
} }
</ul> </ul>
} }
@section scripts
{
@Scripts.Render("~/bundles/jsrender")
<script type="text/javascript" src="~/Scripts/bfs.js"></script>
<script id="result-template" type="text/x-jsrender">
<a href="{{:Url}}" class="list-group-item">
<h4 class="list-group-item-heading">{{:Label}}</h4>
<p class="list-group-item-text">{{:Group}}</p>
</a>
</script>
}

View File

@ -17,7 +17,6 @@
<h2>@Model.FullBuildString</h2> <h2>@Model.FullBuildString</h2>
<div class="form-horizontal form-details"> <div class="form-horizontal form-details">
<div class="form-group"> <div class="form-group">
@Html.LabelFor(model => model.MajorVersion, new { @class = "control-label col-sm-2" }) @Html.LabelFor(model => model.MajorVersion, new { @class = "control-label col-sm-2" })
@ -154,7 +153,15 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<a href="/" class="btn btn-default">Return to Listing</a> <a href="/" class="btn btn-info">Return to Listing</a>
@if (User.Identity.IsAuthenticated)
{
@Html.ActionLink("Edit", "edit", new { id = Model.Id }, new { @class = "btn btn-default" })
}
@if (User.Identity.Name == "hounsell")
{
@Html.ActionLink("Delete", "delete", new { id = Model.Id }, new { @class = "btn btn-danger" })
}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" /> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.0/yeti/bootstrap.min.css" rel="stylesheet" /> <link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.0/yeti/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" /> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" />

View File

@ -1,105 +1,148 @@
body, h1, h2, h3 { body, h1, h2, h3
{
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
} }
h1 { h1
{
font-size: 48px; font-size: 48px;
font-weight: 300; font-weight: 300;
} }
h1 a { h1 a
{
text-decoration: none; text-decoration: none;
color: #000; color: #000;
} }
.social-links { .social-links
{
margin: 30px 0 6px; margin: 30px 0 6px;
} }
.social-links a { .social-links a
{
margin-left: 1em; margin-left: 1em;
} }
.build-head { .build-head
{
margin-bottom: 0.33em; margin-bottom: 0.33em;
} }
.build-head h3 { .build-head h3
{
margin: 0; margin: 0;
display: inline-block; display: inline-block;
} }
.build-head .btn { .build-head .btn
{
display: inline-block; display: inline-block;
margin-right: 0.66em; margin-right: 0.66em;
vertical-align: text-bottom; vertical-align: text-bottom;
padding: 2px 6px; padding: 2px 6px;
} }
.build-foot { .build-foot
{
margin-bottom: 2em; margin-bottom: 2em;
} }
.build-foot .badge { .build-foot .badge
{
border-radius: 4px; border-radius: 4px;
font-weight: normal; font-weight: normal;
color: #666 !important; color: #666 !important;
} }
.build-foot .badge:first-child { .build-foot .badge:first-child
{
min-width: 90px; min-width: 90px;
} }
li:last-child .build-foot { li:last-child .build-foot
{
margin-bottom: 0; margin-bottom: 0;
} }
.fa-sm { .fa-sm
{
font-size: 0.7em; font-size: 0.7em;
vertical-align: 1px; vertical-align: 1px;
margin-right: 0.12em; margin-right: 0.12em;
} }
.field-validation-error { .field-validation-error
{
color: #ff4136; color: #ff4136;
} }
.form-details label { .form-details label
{
font-weight: bold; font-weight: bold;
} }
.panel-heading h4 { .panel-heading h4
{
margin: 0; margin: 0;
} }
.pagination { .panel-search .list-group
{
margin-bottom: 0;
}
.panel-search .list-group-item-heading
{
overflow: hidden;
text-overflow: ellipsis;
}
.panel-search .list-group-item-heading h4
{
font-size: 16px;
}
.panel-search .list-group-item-heading strong
{
font-weight: normal;
}
.pagination
{
font-weight: normal; font-weight: normal;
} }
.pagination > li > a, .pagination > li > a,
.pagination > li > span { .pagination > li > span
{
padding: 2px 7px; padding: 2px 7px;
margin-left: 2px; margin-left: 2px;
} }
#page-footer { #page-footer
{
margin-top: 1em; margin-top: 1em;
} }
.form-horizontal .control-label { .form-horizontal .control-label
{
padding-top: 8px; padding-top: 8px;
} }
label, .control-label, .help-block, .checkbox, .radio { label, .control-label, .help-block, .checkbox, .radio
{
font-size: 14px; font-size: 14px;
} }
.table .btn { .table .btn
{
padding: 4px 9px; padding: 4px 9px;
} }
.table-admin > tbody > tr > th, .table-admin > tbody > tr > th,
.table-admin > tbody > tr > td .table-admin > tbody > tr > td
{ {
vertical-align: middle; vertical-align: middle;
} }