aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/packs/application.js8
-rw-r--r--web/packs/home.js3
-rw-r--r--web/packs/message.js2
-rw-r--r--web/packs/src/javascript/index/query_generator.js99
-rw-r--r--web/packs/src/javascript/index/typeahead.js26
-rw-r--r--web/packs/src/javascript/message/git.js23
-rw-r--r--web/packs/src/javascript/message/quotes.js31
-rw-r--r--web/packs/src/stylesheets/application.scss8
-rw-r--r--web/packs/src/stylesheets/home.scss38
-rw-r--r--web/packs/src/stylesheets/message.scss15
-rw-r--r--web/packs/src/stylesheets/misc.scss108
-rw-r--r--web/packs/src/vendor/javascripts/jquery.typeahead.min.js10
-rw-r--r--web/packs/src/vendor/stylesheets/jquery.typeahead.min.css1
-rw-r--r--web/packs/stylesheets.js1
-rw-r--r--web/templates/home/home.tmpl117
-rw-r--r--web/templates/layout/footer.tmpl36
-rw-r--r--web/templates/layout/head.tmpl14
-rw-r--r--web/templates/layout/header.tmpl6
-rw-r--r--web/templates/layout/sitetitle.tmpl37
-rw-r--r--web/templates/layout/tyriannav.tmpl39
-rw-r--r--web/templates/list/browse.tmpl74
-rw-r--r--web/templates/list/components/pagination.tmpl44
-rw-r--r--web/templates/list/messages.tmpl50
-rw-r--r--web/templates/list/show.tmpl35
-rw-r--r--web/templates/list/threads.tmpl50
-rw-r--r--web/templates/message/show.tmpl110
-rw-r--r--web/templates/popular/threads.tmpl35
-rw-r--r--web/templates/search/components/pagination.tmpl44
-rw-r--r--web/templates/search/searchresults.tmpl57
29 files changed, 1121 insertions, 0 deletions
diff --git a/web/packs/application.js b/web/packs/application.js
new file mode 100644
index 0000000..8ac7b88
--- /dev/null
+++ b/web/packs/application.js
@@ -0,0 +1,8 @@
+import "core-js/stable";
+import "regenerator-runtime/runtime";
+
+require("turbolinks").start();
+import {} from 'jquery-ujs';
+//import './src/vendor/javascripts/moment.min';
+
+import 'bootstrap';
diff --git a/web/packs/home.js b/web/packs/home.js
new file mode 100644
index 0000000..3790f16
--- /dev/null
+++ b/web/packs/home.js
@@ -0,0 +1,3 @@
+import './src/vendor/javascripts/jquery.typeahead.min'
+import './src/javascript/index/typeahead'
+import './src/javascript/index/query_generator'
diff --git a/web/packs/message.js b/web/packs/message.js
new file mode 100644
index 0000000..8595541
--- /dev/null
+++ b/web/packs/message.js
@@ -0,0 +1,2 @@
+import './src/javascript/message/quotes'
+import './src/javascript/message/git' \ No newline at end of file
diff --git a/web/packs/src/javascript/index/query_generator.js b/web/packs/src/javascript/index/query_generator.js
new file mode 100644
index 0000000..6a1128b
--- /dev/null
+++ b/web/packs/src/javascript/index/query_generator.js
@@ -0,0 +1,99 @@
+function buildAdvancedQuery(){
+ var query = ""
+ document.querySelectorAll('#search-container > .row').forEach(function(element) {
+ var term = element.querySelector('.form-control').value;
+
+ if(!term.replace(/\s/g, '').length){
+ return;
+ }else{
+ term = parseSearchTerm(term);
+ }
+
+ var operator = parseOperator(element.querySelector(".pgo-query-operator").value);
+ var field = element.querySelector('.pgo-query-field').value;
+
+ query += operator + field + ":" + term + " ";
+ });
+ document.getElementById('q').value = query;
+}
+
+function parseOperator(operator){
+ switch(operator) {
+ case "should match":
+ return "";
+ case "must match":
+ return "+";
+ case "must not match":
+ return "-";
+ default:
+ return "";
+ }
+}
+
+function parseSearchTerm(term){
+ if (/\s/.test(term) && !/^\".*\"$/.test(term)) {
+ return "\"" + term + "\""
+ }else{
+ return term
+ }
+}
+
+function addInput(self){
+ var new_input = document.querySelector('#search-container > .row').cloneNode(true);
+ setEventListener(new_input);
+ resetInput(new_input);
+ document.querySelector('#search-container').append(new_input);
+ checkDeleteButtons();
+ checkAddButtons();
+}
+
+function resetInput(input) {
+ input.querySelector('.form-control').value = '';
+ input.querySelector('.pgo-query-operator').value = 'should match';
+ input.querySelector('.pgo-query-field').value = 'name';
+}
+
+function deleteInput(self){
+ getThirdParent(self).removeChild(getSecondParent(self));
+ checkDeleteButtons();
+ checkAddButtons();
+}
+
+function checkDeleteButtons(){
+ if(document.querySelectorAll('#search-container > .row').length == 1){
+ document.querySelectorAll('.pgo-query-delete-btn').forEach(function(element) {
+ element.style.display = 'none';
+ });
+ }else{
+ document.querySelectorAll('.pgo-query-delete-btn').forEach(function(element) {
+ element.style.display = 'block';
+ });
+ }
+}
+
+function checkAddButtons(){
+ document.querySelectorAll('.pgo-query-add-btn').forEach(function(element) {
+ element.style.display = 'none';
+ });
+
+ document.querySelectorAll('.pgo-query-add-btn')[document.querySelectorAll('.pgo-query-add-btn').length - 1].style.display = 'block';
+}
+
+function getThirdParent(self) {
+ return self.parentElement.parentElement.parentElement;
+}
+
+function getSecondParent(self) {
+ return self.parentElement.parentElement;
+}
+
+function setEventListener(element){
+ element.querySelector(".pgo-query-add-btn").addEventListener("click", addInput);
+ element.querySelector(".pgo-query-delete-btn").addEventListener("click", function(){ deleteInput(this); });
+}
+
+checkDeleteButtons();
+
+setEventListener(document);
+
+document.getElementById("buildAdvancedQuery").addEventListener("click", function(){ buildAdvancedQuery(); }); \ No newline at end of file
diff --git a/web/packs/src/javascript/index/typeahead.js b/web/packs/src/javascript/index/typeahead.js
new file mode 100644
index 0000000..3232fe8
--- /dev/null
+++ b/web/packs/src/javascript/index/typeahead.js
@@ -0,0 +1,26 @@
+$(function() {
+ $('#q').typeahead({
+ order: 'asc',
+ dynamic: true,
+ delay: 500,
+ source: {
+ packages: {
+ display: 'name',
+ href: function(item) { return '/packages/' + item.category + '/' + item.name; },
+ url: [{
+ type: 'GET',
+ url: "/packages/suggest.json",
+ data: {
+ q: "{{query}}"
+ }
+ }, 'results'],
+ template: '<span class="kk-suggest-cat">{{category}}</span>/<span class="kk-suggest-pkg">{{name}}</span> <span class="kk-suggest-detail">{{description}}</span>'
+ }
+ },
+ callback: {
+ onClick: function(node, a, item, event) {
+ window.location = item.href;
+ }
+ }
+ });
+});
diff --git a/web/packs/src/javascript/message/git.js b/web/packs/src/javascript/message/git.js
new file mode 100644
index 0000000..3e2d35c
--- /dev/null
+++ b/web/packs/src/javascript/message/git.js
@@ -0,0 +1,23 @@
+import hljs from 'highlight.js/lib/core';
+import diff from 'highlight.js/lib/languages/diff';
+
+$(function() {
+
+ console.log("Format git!");
+
+ let message = document.querySelector("pre.ag-message-content").innerHTML;
+ let gitDiffRegex = new RegExp('diff --git a\\/(.*)\\sb\\/(.*)\\nindex\\s([a-zA-Z0-9]*)\\.\\.([a-zA-Z0-9]*)\\s([a-zA-Z0-9]*)\\n---\\sa\\/(.*)\\n\\+\\+\\+\\sb\\/(.*)\\n');
+ let isGitDiff = (message.search(gitDiffRegex) != -1);
+
+ if(isGitDiff){
+ hljs.registerLanguage('patch', diff);
+ hljs.registerLanguage('diff', diff);
+
+ document.querySelector("pre.ag-message-content").innerHTML = '<code class="language-patch">' + message + '</code>';
+ document.querySelectorAll('pre.ag-message-content code').forEach((block) => {
+ hljs.highlightBlock(block);
+ });
+ }
+})
+
+
diff --git a/web/packs/src/javascript/message/quotes.js b/web/packs/src/javascript/message/quotes.js
new file mode 100644
index 0000000..cd1071f
--- /dev/null
+++ b/web/packs/src/javascript/message/quotes.js
@@ -0,0 +1,31 @@
+$(function() {
+
+ var message = document.querySelector("pre.ag-message-content").innerHTML;
+ var lines = message.split("\n");
+ var convertedLines = [];
+ var inQuote = false;
+
+ lines.forEach(function (line) {
+ if(line.trimLeft().startsWith("&gt;") || line.trimLeft().startsWith(">")){
+ if(inQuote){
+ convertedLines.push(line);
+ }else{
+ convertedLines.push('<div class="ag-quote">');
+ convertedLines.push(line);
+ inQuote = true;
+ }
+ }else{
+ if(inQuote){
+ convertedLines.push('</div>');
+ convertedLines.push(line);
+ inQuote = false;
+ }else{
+ convertedLines.push(line);
+ }
+ }
+ })
+
+ document.querySelector("pre.ag-message-content").innerHTML = convertedLines.join('\n');
+
+ console.log('Done');
+}) \ No newline at end of file
diff --git a/web/packs/src/stylesheets/application.scss b/web/packs/src/stylesheets/application.scss
new file mode 100644
index 0000000..8de908c
--- /dev/null
+++ b/web/packs/src/stylesheets/application.scss
@@ -0,0 +1,8 @@
+@import "~@gentoo/tyrian/dist/tyrian";
+@import "~@gentoo/tyrian/dist/components/searchbars";
+
+@import "home";
+@import "message";
+@import "misc";
+
+@import "../vendor/stylesheets/jquery.typeahead.min";
diff --git a/web/packs/src/stylesheets/home.scss b/web/packs/src/stylesheets/home.scss
new file mode 100644
index 0000000..cf60b23
--- /dev/null
+++ b/web/packs/src/stylesheets/home.scss
@@ -0,0 +1,38 @@
+.site-welcome {
+ font-size: 3.5em;
+ text-align: center;
+ margin-bottom: 0.75em;
+
+ @media screen and (max-width: 767px) {
+ font-size: 1.75em;
+ }
+}
+
+#landing-page-search-area {
+ height: calc(100vh - 100px);
+}
+
+#landing-page-search-area > .jumbotron {
+ margin-top: calc(15vh - 75px);
+}
+
+#landing-page-popular-threads {
+ margin-top: 15vh;
+}
+
+#scroll-down-section {
+ /*margin-top: calc(35vh - 100px);*/
+ position: absolute;
+ left: calc(50vw - 18px);
+ bottom: 25px;
+
+}
+
+#scroll-down-section > i {
+ font-size: 400%;
+ cursor: pointer;
+}
+
+#scroll-down-section > i:hover {
+ color: #54487A!important;
+}
diff --git a/web/packs/src/stylesheets/message.scss b/web/packs/src/stylesheets/message.scss
new file mode 100644
index 0000000..ae97a36
--- /dev/null
+++ b/web/packs/src/stylesheets/message.scss
@@ -0,0 +1,15 @@
+@import "~highlight.js/styles/default";
+
+.hljs {
+ background: #F5F5F5!important;
+ padding: 0px!important;
+ color: #212529!important;
+}
+
+.hljs-addition, .highlight-git-add {
+ color: green!important;
+}
+
+.hljs-deletion, .highlight-git-del {
+ color: red!important;
+}
diff --git a/web/packs/src/stylesheets/misc.scss b/web/packs/src/stylesheets/misc.scss
new file mode 100644
index 0000000..863249b
--- /dev/null
+++ b/web/packs/src/stylesheets/misc.scss
@@ -0,0 +1,108 @@
+html {
+ scroll-behavior: smooth;
+}
+
+html.turbolinks-progress-bar::before {
+ background-color: #54487A!important;
+}
+
+.ag-text-content {
+ width: 100%;
+ overflow-x: scroll;
+ overflow-y: hidden;
+ display: block;
+}
+
+@media (min-width: 768px) {
+ .ag-message-table,
+ .ag-mostrecent-table {
+ width: 100%;
+ table-layout: fixed;
+ }
+
+ .ag-mostrecent-table td,
+ .ag-message-table td {
+ text-overflow: ellipsis;
+ max-height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .ag-message-table-date {
+ width: 18%;
+ }
+
+ .ag-message-table-from {
+ width: 22%;
+ }
+
+ .ag-mostrecent-table-author {
+ width: 30%;
+ }
+
+ .ag-mostrecent-header {
+ margin-top: 5px;
+ }
+
+ .ag-index-actions {
+ margin-top: 2em;
+ }
+}
+
+.ag-quote {
+ color: #999;
+ cursor: row-resize;
+ margin-bottom: -1em;
+ overflow: hidden;
+}
+
+.ag-quote-hidden {
+ height: 2.4em;
+ overflow: hidden;
+ fdisplay: block;
+ -webkit-mask-image: linear-gradient(to bottom, white, transparent);
+ mask-image: linear-gradient(to bottom, white, transparent);
+}
+
+.ag-toggle-quotes {
+ float: right;
+ margin-bottom: .5em;
+}
+
+.ag-message-content {
+ clear: both;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ padding: 10px;
+}
+
+.ag-message-content pre {
+ border: none;
+}
+
+.ag-message-content blockquote {
+ font-size: inherit;
+}
+
+.ag-header-name-col {
+ width: 10em;
+}
+
+.ag-message-actions {
+ line-height: 2em;
+}
+
+.ag-pager {
+ margin: 0;
+}
+
+.ag-html-content {
+ white-space: normal;
+}
+
+.ag-view-selection {
+ margin-bottom: .5em;
+}
+
+.ag-date {
+}
diff --git a/web/packs/src/vendor/javascripts/jquery.typeahead.min.js b/web/packs/src/vendor/javascripts/jquery.typeahead.min.js
new file mode 100644
index 0000000..402bdd6
--- /dev/null
+++ b/web/packs/src/vendor/javascripts/jquery.typeahead.min.js
@@ -0,0 +1,10 @@
+/*!
+ * jQuery Typeahead
+ * Copyright (C) 2015 RunningCoder.org
+ * Licensed under the MIT license
+ *
+ * @author Tom Bertrand
+ * @version 2.1.2 (2015-09-28)
+ * @link http://www.runningcoder.org/jquerytypeahead/
+*/
+!function(a,b,c,d){a.Typeahead={version:"2.1.2"};var e={input:null,minLength:2,maxItem:8,dynamic:!1,delay:300,order:null,offset:!1,hint:!1,accent:!1,highlight:!0,group:!1,maxItemPerGroup:null,dropdownFilter:!1,dynamicFilter:null,backdrop:!1,cache:!1,ttl:36e5,compression:!1,suggestion:!1,searchOnFocus:!1,resultContainer:null,generateOnLoad:null,mustSelectItem:!1,href:null,display:["display"],template:null,correlativeTemplate:!1,emptyTemplate:!1,source:null,callback:{onInit:null,onReady:null,onSearch:null,onResult:null,onLayoutBuiltBefore:null,onLayoutBuiltAfter:null,onNavigate:null,onMouseEnter:null,onMouseLeave:null,onClickBefore:null,onClickAfter:null,onSendRequest:null,onReceiveRequest:null,onSubmit:null},selector:{container:"typeahead-container",group:"typeahead-group",result:"typeahead-result",list:"typeahead-list",display:"typeahead-display",query:"typeahead-query",filter:"typeahead-filter",filterButton:"typeahead-filter-button",filterValue:"typeahead-filter-value",dropdown:"typeahead-dropdown",dropdownCarret:"typeahead-caret",button:"typeahead-button",backdrop:"typeahead-backdrop",hint:"typeahead-hint"},debug:!1},f=".typeahead",g={from:"ãàáäâẽèéëêìíïîõòóöôùúüûñç",to:"aaaaaeeeeeiiiiooooouuuunc"},h=~navigator.appVersion.indexOf("MSIE 9."),i=function(a,b){this.rawQuery="",this.query="",this.source={},this.isGenerated=null,this.generatedGroupCount=0,this.groupCount=0,this.groupBy="group",this.result=[],this.resultCount=0,this.options=b,this.node=a,this.container=null,this.resultContainer=null,this.item=null,this.xhr={},this.hintIndex=null,this.filters={dropdown:{},dynamic:{}},this.requests={},this.backdrop={},this.hint={},this.__construct()};i.prototype={extendOptions:function(){this.options.dynamic&&(this.options.cache=!1,this.options.compression=!1),this.options.cache&&(this.options.cache=function(){var b="undefined"!=typeof a.localStorage;if(b)try{a.localStorage.setItem("typeahead","typeahead"),a.localStorage.removeItem("typeahead")}catch(c){b=!1}return b}()),this.options.compression&&("object"==typeof LZString&&this.options.cache||(this.options.compression=!1)),"undefined"==typeof this.options.maxItem||/^\d+$/.test(this.options.maxItem)&&0!==this.options.maxItem||(this.options.maxItem=1/0),this.options.maxItemPerGroup&&!/^\d+$/.test(this.options.maxItemPerGroup)&&(this.options.maxItemPerGroup=null),!this.options.display||this.options.display instanceof Array||(this.options.display=[this.options.display]),!this.options.group||this.options.group instanceof Array||(this.options.group=[this.options.group]),!this.options.dynamicFilter||this.options.dynamicFilter instanceof Array||(this.options.dynamicFilter=[this.options.dynamicFilter]),this.options.resultContainer&&("string"==typeof this.options.resultContainer&&(this.options.resultContainer=c(this.options.resultContainer)),this.options.resultContainer instanceof jQuery&&this.options.resultContainer[0]&&(this.resultContainer=this.options.resultContainer)),this.options.group&&"string"==typeof this.options.group[0]&&this.options.maxItemPerGroup&&(this.groupBy=this.options.group[0]),this.options.callback&&this.options.callback.onClick&&(this.options.callback.onClickBefore=this.options.callback.onClick,delete this.options.callback.onClick),this.options=c.extend(!0,{},e,this.options)},unifySourceFormat:function(){if(this.options.source instanceof Array)return this.options.source={group:{data:this.options.source}},this.groupCount+=1,!0;("undefined"!=typeof this.options.source.data||"undefined"!=typeof this.options.source.url)&&(this.options.source={group:this.options.source});for(var a in this.options.source)if(this.options.source.hasOwnProperty(a)){if(("string"==typeof this.options.source[a]||this.options.source[a]instanceof Array)&&(this.options.source[a]={url:this.options.source[a]}),!this.options.source[a].data&&!this.options.source[a].url)return!1;!this.options.source[a].display||this.options.source[a].display instanceof Array||(this.options.source[a].display=[this.options.source[a].display]),this.options.source[a].ignore&&(this.options.source[a].ignore instanceof RegExp||delete this.options.source[a].ignore),this.groupCount+=1}return!0},init:function(){this.helper.executeCallback(this.options.callback.onInit,[this.node]),this.container=this.node.closest("."+this.options.selector.container)},delegateEvents:function(){var a=this,b=["focus"+f,"input"+f,"propertychange"+f,"keydown"+f,"keyup"+f,"dynamic"+f,"generateOnLoad"+f];this.container.off(f).on("click"+f+" touchstart"+f,function(b){b.stopPropagation(),a.options.dropdownFilter&&a.container.find("."+a.options.selector.dropdown.replace(" ",".")).hide()}),this.node.closest("form").on("submit",function(b){return a.options.mustSelectItem&&a.helper.isEmpty(a.item)?void b.preventDefault():(a.hideLayout(),a.rawQuery="",a.query="",a.helper.executeCallback(a.options.callback.onSubmit,[a.node,this,a.item,b])?!1:void 0)});var c=!1;this.node.off(f).on(b.join(" "),function(b){switch(b.type){case"generateOnLoad":case"focus":a.isGenerated&&a.options.searchOnFocus&&a.query.length>=a.options.minLength&&a.showLayout(),null!==a.isGenerated||a.options.dynamic||a.generateSource();break;case"keydown":a.isGenerated&&a.result.length&&b.keyCode&&~[13,27,38,39,40].indexOf(b.keyCode)&&(c=!0,a.navigate(b));break;case"keyup":h&&a.node[0].value.replace(/^\s+/,"").toString().length<a.query.length&&a.node.trigger("input"+f);break;case"propertychange":if(c){c=!1;break}case"input":if(a.rawQuery=a.node[0].value.toString(),a.query=a.node[0].value.replace(/^\s+/,"").toString(),a.options.hint&&a.hint.container&&""!==a.hint.container.val()&&0!==a.hint.container.val().indexOf(a.rawQuery)&&a.hint.container.val(""),a.options.dynamic)return a.isGenerated=null,void a.helper.typeWatch(function(){a.query.length>=a.options.minLength?a.generateSource():a.hideLayout()},a.options.delay);case"dynamic":if(!a.isGenerated)break;if(a.query.length<a.options.minLength){a.hideLayout();break}a.searchResult(),a.buildLayout(),a.result.length>0||a.options.emptyTemplate?a.showLayout():a.hideLayout()}}),this.options.generateOnLoad&&this.node.trigger("generateOnLoad"+f)},generateSource:function(){if(!this.isGenerated||this.options.dynamic){if(this.generatedGroupCount=0,this.isGenerated=!1,!this.helper.isEmpty(this.xhr)){for(var b in this.xhr)this.xhr.hasOwnProperty(b)&&this.xhr[b].abort();this.xhr={}}var c,d,e;for(c in this.options.source)if(this.options.source.hasOwnProperty(c)){if(this.options.cache&&(d=a.localStorage.getItem(this.node.selector+":"+c))){this.options.compression&&(d=LZString.decompressFromUTF16(d)),e=!1;try{d=JSON.parse(d+""),d.data&&d.ttl>(new Date).getTime()?(this.populateSource(d.data,c),e=!0):a.localStorage.removeItem(this.node.selector+":"+c)}catch(f){}if(e)continue}!this.options.source[c].data||this.options.source[c].url?this.options.source[c].url&&(this.requests[c]||(this.requests[c]=this.generateRequestObject(c))):this.populateSource("function"==typeof this.options.source[c].data&&this.options.source[c].data()||this.options.source[c].data,c)}this.handleRequests()}},generateRequestObject:function(a){var b={request:{url:null,dataType:"json"},extra:{path:null,group:a,callback:{done:null,fail:null,always:null,always:null}},validForGroup:[a]};!(this.options.source[a].url instanceof Array)&&this.options.source[a].url instanceof Object&&(this.options.source[a].url=[this.options.source[a].url]),this.options.source[a].url instanceof Array?(this.options.source[a].url[0]instanceof Object?(this.options.source[a].url[0].callback&&(b.extra.callback=this.options.source[a].url[0].callback,delete this.options.source[a].url[0].callback),b.request=c.extend(!0,b.request,this.options.source[a].url[0])):"string"==typeof this.options.source[a].url[0]&&(b.request.url=this.options.source[a].url[0]),this.options.source[a].url[1]&&"string"==typeof this.options.source[a].url[1]&&(b.extra.path=this.options.source[a].url[1])):"string"==typeof this.options.source[a].url&&(b.request.url=this.options.source[a].url),"jsonp"===b.request.dataType.toLowerCase()&&(b.request.jsonpCallback="callback_"+a);var d;for(var e in this.requests)if(this.requests.hasOwnProperty(e)&&(d=JSON.stringify(this.requests[e].request),d===JSON.stringify(b.request))){this.requests[e].validForGroup.push(a),b.isDuplicated=!0,delete b.validForGroup;break}return b},handleRequests:function(){var a=this,b=Object.keys(this.requests).length;b&&this.helper.executeCallback(this.options.callback.onSendRequest,[this.node,this.query]);for(var d in this.requests)this.requests.hasOwnProperty(d)&&(this.requests[d].isDuplicated||!function(d,e){var f;if(~e.request.url.indexOf("{{query}}")&&(e.request.url=e.request.url.replace("{{query}}",a.query)),e.request.data)for(var g in e.request.data)if(e.request.data.hasOwnProperty(g)&&~String(e.request.data[g]).indexOf("{{query}}")){e=c.extend(!0,{},e),e.request.data[g]=e.request.data[g].replace("{{query}}",a.query);break}a.xhr[d]=c.ajax(e.request).done(function(c,d,g){for(var h,i=0;i<e.validForGroup.length;i++)f=a.requests[e.validForGroup[i]],f.extra.callback.done instanceof Function&&(h=f.extra.callback.done(c,d,g),c=h instanceof Array&&h||c),a.populateSource(c,f.extra.group,f.extra.path),b-=1,0===b&&a.helper.executeCallback(a.options.callback.onReceiveRequest,[a.node,a.query])}).fail(function(b,c,d){for(var g=0;g<e.validForGroup.length;g++)f=a.requests[e.validForGroup[g]],f.extra.callback.fail instanceof Function&&f.extra.callback.fail(b,c,d)}).always(function(b,c){for(var d=0;d<e.validForGroup.length;d++)f=a.requests[e.validForGroup[d]],f.extra.callback.always instanceof Function&&f.extra.callback.always(b,c)}).always(function(b,c,d){for(var g=0;g<e.validForGroup.length;g++)f=a.requests[e.validForGroup[g]],f.extra.callback.always instanceof Function&&f.extra.callback.always(b,c,d)})}(d,this.requests[d]))},populateSource:function(a,b,c){var d=this,e=this.options.source[b].url&&this.options.source[b].data;a="string"==typeof c?this.helper.namespace(c,a):a,a instanceof Array||(a=[]),e&&("function"==typeof e&&(e=e()),e instanceof Array&&(a=a.concat(e)));for(var f,g=this.options.source[b].display?"compiled"===this.options.source[b].display[0]?this.options.source[b].display[1]:this.options.source[b].display[0]:"compiled"===this.options.display[0]?this.options.display[1]:this.options.display[0],h=0;h<a.length;h++)"string"==typeof a[h]&&(f={},f[g]=a[h],a[h]=f),a[h].group=b;if(this.options.correlativeTemplate){var i=this.options.source[b].template||this.options.template;if(i){i=i.replace(/<.+?>/g,"");for(var h=0;h<a.length;h++)a[h].compiled=i.replace(/\{\{([\w\-\.]+)(?:\|(\w+))?}}/g,function(b,c){return d.helper.namespace(c,a[h],"get","")}).trim();this.options.source[b].display?~this.options.source[b].display.indexOf("compiled")||this.options.source[b].display.unshift("compiled"):~this.options.display.indexOf("compiled")||this.options.display.unshift("compiled")}else;}if(this.source[b]=a,this.options.cache&&!localStorage.getItem(this.node.selector+":"+b)){var j=JSON.stringify({data:a,ttl:(new Date).getTime()+this.options.ttl});this.options.compression&&(j=LZString.compressToUTF16(j)),localStorage.setItem(this.node.selector+":"+b,j)}this.incrementGeneratedGroup()},incrementGeneratedGroup:function(){this.generatedGroupCount+=1,this.groupCount===this.generatedGroupCount&&(this.isGenerated=!0,this.node.trigger("dynamic"+f))},navigate:function(a){this.helper.executeCallback(this.options.callback.onNavigate,[this.node,this.query,a]);var b=this.resultContainer.find("> ul > li:not([data-search-group])"),c=b.filter(".active"),d=c[0]&&b.index(c)||null;if(27===a.keyCode)return void(this.container.hasClass("result")&&(a.preventDefault(),this.hideLayout()));if(13===a.keyCode){if(c.length>0)return a.preventDefault(),a.stopPropagation(),void c.find("a:first").trigger("click");if(this.options.mustSelectItem&&this.helper.isEmpty(this.item))return;return void this.hideLayout()}if(39===a.keyCode)return void(d?b.eq(d).find("a:first").trigger("click"):this.options.hint&&""!==this.hint.container.val()&&this.helper.getCaret(this.node[0])>=this.query.length&&b.find('a[data-index="'+this.hintIndex+'"]').trigger("click"));if(b.length>0&&c.removeClass("active"),38===a.keyCode?(a.preventDefault(),c.length>0?d-1>=0&&b.eq(d-1).addClass("active"):b.last().addClass("active")):40===a.keyCode&&(a.preventDefault(),c.length>0?d+1<b.length&&b.eq(d+1).addClass("active"):b.first().addClass("active")),c=b.filter(".active"),this.options.hint&&this.hint.container&&(c.length>0?this.hint.container.css("color",this.hint.container.css("background-color")||"fff"):this.hint.container.css("color",this.hint.css.color)),c.length>0){var e=c.find("a:first").attr("data-index");e&&this.node.val(this.result[e][this.result[e].matchedKey])}else this.node.val(this.rawQuery)},searchResult:function(a){a||(this.item={}),this.helper.executeCallback(this.options.callback.onSearch,[this.node,this.query]),this.result=[],this.resultCount=0;var b,c,d,e,f,g,h,i,j,k=this,l=this.query.toLowerCase(),m={},n=this.filters.dropdown&&this.filters.dropdown.key||this.groupBy,o=this.filters.dynamic&&!this.helper.isEmpty(this.filters.dynamic);this.options.accent&&(l=this.helper.removeAccent(l));for(b in this.source)if(this.source.hasOwnProperty(b)&&(!this.filters.dropdown||"group"!==this.filters.dropdown.key||this.filters.dropdown.value===b)){if(this.options.maxItemPerGroup&&"group"===n)if(m[b]){if(m[b]>=this.options.maxItemPerGroup&&!this.options.callback.onResult)break}else m[b]=0;g="undefined"==typeof this.options.source[b].filter||this.options.source[b].filter===!0;for(var p=0;p<this.source[b].length&&(!(this.result.length>=this.options.maxItem)||this.options.callback.onResult);p++)if(!o||this.dynamicFilter.validate.apply(this,[this.source[b][p]])){if(c=this.source[b][p],this.options.maxItemPerGroup&&"group"!==n)if(m[c[n]]){if(m[c[n]]>=this.options.maxItemPerGroup&&!this.options.callback.onResult)continue}else m[c[n]]=0;f=this.options.source[b].display||this.options.display;for(var q=0;q<f.length;q++){if(g){if(e=c[f[q]],!e)continue;if(e=e.toString().toLowerCase(),this.options.accent&&(e=this.helper.removeAccent(e)),d=e.indexOf(l),this.options.correlativeTemplate&&"compiled"===f[q]&&0>d&&/\s/.test(l)){h=!0,i=l.split(" "),j=e;for(var r=0;r<i.length;r++)if(""!==i[r]){if(!~j.indexOf(i[r])){h=!1;break}j=j.replace(i[r],"")}}if(0>d&&!h)continue;if(this.options.offset&&0!==d)continue;if(this.options.source[b].ignore&&this.options.source[b].ignore.test(e))continue}if(!this.filters.dropdown||this.filters.dropdown.value==c[this.filters.dropdown.key]){if(this.resultCount+=1,this.options.callback.onResult&&this.result.length>=this.options.maxItem||this.options.maxItemPerGroup&&m[c[n]]>=this.options.maxItemPerGroup)break;c.matchedKey=f[q],this.result.push(c),this.options.maxItemPerGroup&&(m[c[n]]+=1);break}}}}if(this.options.order){for(var s,f=[],q=0;q<this.result.length;q++)s=this.options.source[this.result[q].group].display||this.options.display,~f.indexOf(s[0])||f.push(s[0]);this.result.sort(k.helper.sort(f,"asc"===k.options.order,function(a){return a.toString().toUpperCase()}))}this.helper.executeCallback(this.options.callback.onResult,[this.node,this.query,this.result,this.resultCount])},buildLayout:function(){this.resultContainer||(this.resultContainer=c("<div/>",{"class":this.options.selector.result}),this.container.append(this.resultContainer));var a=this.query.toLowerCase();this.options.accent&&(a=this.helper.removeAccent(a));var b=this,d=c("<ul/>",{"class":this.options.selector.list+(b.helper.isEmpty(b.result)?" empty":""),html:function(){if(b.options.emptyTemplate&&b.helper.isEmpty(b.result))return c("<li/>",{html:c("<a/>",{href:"javascript:;",html:"function"==typeof b.options.emptyTemplate&&b.options.emptyTemplate(b.query)||b.options.emptyTemplate.replace(/\{\{query}}/gi,b.query)})});for(var d in b.result)b.result.hasOwnProperty(d)&&!function(d,e,f){var g,h,i,j,k,l=e.group,m={},n=b.options.source[e.group].display||b.options.display,o=b.options.source[e.group].href||b.options.href;b.options.group&&("boolean"!=typeof b.options.group[0]&&e[b.options.group[0]]&&(l=e[b.options.group[0]]),c(f).find('li[data-search-group="'+l+'"]')[0]||c(f).append(c("<li/>",{"class":b.options.selector.group,html:c("<a/>",{href:"javascript:;",html:b.options.group[1]&&b.options.group[1].replace(/(\{\{group}})/gi,e[b.options.group[0]]||l)||l}),"data-search-group":l})));for(var p=0;p<n.length;p++)i=n[p],m[i]=e[i];g=c("<li/>",{html:c("<a/>",{href:function(){return o&&("string"==typeof o?o=o.replace(/\{\{([\w\-\.]+)(?:\|(\w+))?}}/g,function(a,c,d){var f=b.helper.namespace(c,e,"get","");return d&&"raw"===d?f:b.helper.slugify(f)}):"function"==typeof o&&(o=o(e)),e.href=o),o||"javascript:;"},"data-group":l,"data-index":d,html:function(){k=e.group&&b.options.source[e.group].template||b.options.template,h=k?k.replace(/\{\{([\w\-\.]+)(?:\|(\w+))?}}/g,function(a,c,d){var f=b.helper.namespace(c,e,"get","");return d&&"raw"===d?f:b.helper.namespace(c,m,"get","")||f}):'<span class="'+b.options.selector.display+'">'+b.helper.joinObject(m," ")+"</span>",b.options.highlight&&(h=b.helper.highlight(h,a.split(" "),b.options.accent)),c(this).append(h)},click:function(a){return b.options.mustSelectItem&&b.helper.isEmpty(e)?void a.preventDefault():(b.item=e,b.helper.executeCallback(b.options.callback.onClickBefore,[b.node,this,e,a]),void(a.isDefaultPrevented()||(a.preventDefault(),b.query=b.rawQuery=e[e.matchedKey].toString(),b.node.val(b.query).focus(),b.searchResult(!0),b.buildLayout(),b.hideLayout(),b.helper.executeCallback(b.options.callback.onClickAfter,[b.node,this,e,a]))))},mouseenter:function(a){c(this).closest("ul").find("li.active").removeClass("active"),c(this).closest("li").addClass("active"),b.helper.executeCallback(b.options.callback.onMouseEnter,[b.node,this,e,a])},mouseleave:function(a){c(this).closest("li").removeClass("active"),b.helper.executeCallback(b.options.callback.onMouseLeave,[b.node,this,e,a])}})}),b.options.group?(j=c(f).find('a[data-group="'+l+'"]:last').closest("li"),j[0]||(j=c(f).find('li[data-search-group="'+l+'"]')),c(g).insertAfter(j)):c(f).append(g)}(d,b.result[d],this)}});if(this.options.callback.onLayoutBuiltBefore){var f=this.helper.executeCallback(this.options.callback.onLayoutBuiltBefore,[this.node,this.query,this.result,d]);f instanceof jQuery&&(d=f)}if(this.container.addClass("result"),this.resultContainer.html(d),this.options.callback.onLayoutBuiltAfter&&this.helper.executeCallback(this.options.callback.onLayoutBuiltAfter,[this.node,this.query,this.result]),this.options.backdrop&&(this.backdrop.container?this.backdrop.container.show():(this.backdrop.css=c.extend({opacity:.6,filter:"alpha(opacity=60)",position:"fixed",top:0,right:0,bottom:0,left:0,"z-index":1040,"background-color":"#000"},this.options.backdrop),this.backdrop.container=c("<div/>",{"class":this.options.selector.backdrop,css:this.backdrop.css,click:function(){b.hideLayout()}}).insertAfter(this.container)),this.container.addClass("backdrop").css({"z-index":this.backdrop.css["z-index"]+1,position:"relative"})),this.options.hint){var g="";if(this.result.length>0&&this.query.length>0){this.hint.container||(this.hint.css=c.extend({"border-color":"transparent",position:"absolute",top:0,display:"inline","z-index":-1,"float":"none",color:"silver","box-shadow":"none",cursor:"default","-webkit-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none"},this.options.hint),this.hint.container=c("<input/>",{type:this.node.attr("type"),"class":this.node.attr("class"),readonly:!0,unselectable:"on",tabindex:-1,click:function(){b.node.focus()}}).addClass(e.selector.hint).css(this.hint.css).insertAfter(this.node),this.node.parent().css({position:"relative"})),this.hint.container.css("color",this.hint.css.color);var h,i,j;this.hintIndex=null;for(var k=0;k<this.result.length;k++){i=this.result[k].group,h=b.options.source[i].display||b.options.display;for(var l=0;l<h.length;l++)if(j=String(this.result[k][h[l]]).toLowerCase(),this.options.accent&&(j=this.helper.removeAccent(j)),0===j.indexOf(a)){g=String(this.result[k][h[l]]),this.hintIndex=k;break}if(null!==this.hintIndex)break}}this.hint.container&&this.hint.container.val(g.length>0&&this.rawQuery+g.substring(this.query.length)||"").show()}},buildDropdownLayout:function(){function a(a){"*"===a.value?delete this.filters.dropdown:this.filters.dropdown=a,this.container.removeClass("filter").find("."+this.options.selector.filterValue).html(a.display||a.value),this.node.trigger("dynamic"+f),this.node.focus()}if(this.options.dropdownFilter){var b,d=this;if("boolean"==typeof this.options.dropdownFilter)b="all";else if("string"==typeof this.options.dropdownFilter)b=this.options.dropdownFilter;else if(this.options.dropdownFilter instanceof Array)for(var e=0;e<this.options.dropdownFilter.length;e++)if("*"===this.options.dropdownFilter[e].value&&this.options.dropdownFilter[e].display){b=this.options.dropdownFilter[e].display;break}c("<span/>",{"class":this.options.selector.filter,html:function(){c(this).append(c("<button/>",{type:"button","class":d.options.selector.filterButton,html:"<span class='"+d.options.selector.filterValue+"'>"+b+"</span> <span class='"+d.options.selector.dropdownCarret+"'></span>",click:function(a){a.stopPropagation();var b=d.container.find("."+d.options.selector.dropdown.replace(" ","."));b.is(":visible")?(d.container.removeClass("filter"),b.hide(),c("html").off(f+".dropdownFilter")):(d.container.addClass("filter"),b.show(),c("html").off(f+".dropdownFilter").on("click"+f+".dropdownFilter touchstart"+f+".dropdownFilter",function(){d.container.removeClass("filter"),b.hide(),c(this).off(f+".dropdownFilter")}))}})),c(this).append(c("<ul/>",{"class":d.options.selector.dropdown,html:function(){var b=d.options.dropdownFilter;if(~["string","boolean"].indexOf(typeof d.options.dropdownFilter)){b=[];for(var e in d.options.source)d.options.source.hasOwnProperty(e)&&b.push({key:"group",value:e});b.push({key:"group",value:"*",display:"string"==typeof d.options.dropdownFilter&&d.options.dropdownFilter||"All"})}for(var f=0;f<b.length;f++)!function(b,e,f){(e.key||"*"===e.value)&&e.value&&("*"===e.value&&c(f).append(c("<li/>",{"class":"divider"})),c(f).append(c("<li/>",{html:c("<a/>",{href:"javascript:;",html:e.display||e.value,click:function(b){b.preventDefault(),a.apply(d,[e])}})})))}(f,b[f],this)}}))}}).insertAfter(d.container.find("."+d.options.selector.query))}},dynamicFilter:{validate:function(a){var b,c,d=null,e=null;for(var f in this.filters.dynamic)if(this.filters.dynamic.hasOwnProperty(f)&&(c=~f.indexOf(".")?this.helper.namespace(f,a,"get"):a[f],"|"!==this.filters.dynamic[f].modifier||d||(d=c==this.filters.dynamic[f].value||!1),"&"===this.filters.dynamic[f].modifier)){if(c!=this.filters.dynamic[f].value){e=!1;break}e=!0}return b=d,null!==e&&(b=e,e===!0&&null!==d&&(b=d)),!!b},set:function(a,b){var c=a.match(/^([|&])?(.+)/);b?this.filters.dynamic[c[2]]={modifier:c[1]||"|",value:b}:delete this.filters.dynamic[c[2]],this.searchResult(),this.buildLayout()},bind:function(){if(this.options.dynamicFilter)for(var a,b=this,d=0;d<this.options.dynamicFilter.length;d++)a=this.options.dynamicFilter[d],"string"==typeof a.selector&&(a.selector=c(a.selector)),a.selector instanceof jQuery&&a.selector[0]&&a.key&&!function(a){a.selector.off(f).on("change"+f,function(){b.dynamicFilter.set.apply(b,[a.key,b.dynamicFilter.getValue(this)])}).trigger("change"+f)}(a)},getValue:function(a){var b;return"SELECT"===a.tagName?b=a.value:"INPUT"===a.tagName&&("checkbox"===a.type?b=a.checked||null:"radio"===a.type&&a.checked&&(b=a.value)),b}},showLayout:function(){var a=this;c("html").off(f).on("click"+f+" touchstart"+f,function(){a.hideLayout(),c(this).off(f)}),(this.result.length||this.options.emptyTemplate)&&this.container.addClass("result hint backdrop")},hideLayout:function(){this.container.removeClass("result hint backdrop filter")},__construct:function(){this.extendOptions(),this.unifySourceFormat()&&(this.init(),this.delegateEvents(),this.buildDropdownLayout(),this.dynamicFilter.bind.apply(this),this.helper.executeCallback(this.options.callback.onReady,[this.node]))},helper:{isEmpty:function(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0},removeAccent:function(a){return"string"==typeof a?a=a.toLowerCase().replace(new RegExp("["+g.from+"]","g"),function(a){return g.to[g.from.indexOf(a)]}):void 0},slugify:function(a){return a=String(a),""!==a&&(a=this.removeAccent(a),a=a.replace(/[^-a-z0-9]+/g,"-").replace(/-+/g,"-").trim("-")),a},sort:function(a,b,c){var d=function(b){for(var d=0;d<a.length;d++)if("undefined"!=typeof b[a[d]])return c(b[a[d]])};return b=[-1,1][+!!b],function(a,c){return a=d(a),c=d(c),b*((a>c)-(c>a))}},replaceAt:function(a,b,c,d){return a.substring(0,b)+d+a.substring(b+c)},highlight:function(a,b,c){a=String(a);var d=c&&this.removeAccent(a)||a,e=[];b instanceof Array||(b=[b]),b.sort(function(a,b){return b.length-a.length});for(var f=b.length-1;f>=0;f--)""!==b[f].trim()?b[f]=b[f].replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"):b.splice(f,1);d.replace(new RegExp("(?:"+b.join("|")+")(?!([^<]+)?>)","gi"),function(a,b,c){e.push({offset:c,length:a.length})});for(var f=e.length-1;f>=0;f--)a=this.replaceAt(a,e[f].offset,e[f].length,"<strong>"+a.substr(e[f].offset,e[f].length)+"</strong>");return a},joinObject:function(a,b){var c="",d=0;for(var e in a)a.hasOwnProperty(e)&&(0!==d&&(c+=b),c+=a[e],d++);return c},getCaret:function(a){if(a.selectionStart)return a.selectionStart;if(b.selection){a.focus();var c=b.selection.createRange();if(null==c)return 0;var d=a.createTextRange(),e=d.duplicate();return d.moveToBookmark(c.getBookmark()),e.setEndPoint("EndToStart",d),e.text.length}return 0},executeCallback:function(b,d){if(!b)return!1;var e;d[0];if("function"==typeof b)e=b;else if(("string"==typeof b||b instanceof Array)&&("string"==typeof b&&(b=[b,[]]),e=this.helper.namespace(b[0],a),"function"!=typeof e))return!1;return e.apply(this,c.merge(b[1]||[],d?d:[]))||!0},namespace:function(b,c,e,f){if("string"!=typeof b||""===b)return!1;for(var g=b.split("."),h=c||a,e=e||"get",i=f||{},j="",k=0,l=g.length;l>k;k++){if(j=g[k],"undefined"==typeof h[j]){if(~["get","delete"].indexOf(e))return"undefined"!=typeof f?f:d;h[j]={}}if(~["set","create","delete"].indexOf(e)&&k===l-1){if("set"!==e&&"create"!==e)return delete h[j],!0;h[j]=i}h=h[j]}return h},typeWatch:function(){var a=0;return function(b,c){clearTimeout(a),a=setTimeout(b,c)}}()}},c.fn.typeahead=c.typeahead=function(a){return j.typeahead(this,a)};var j={typeahead:function(b,d){if(d&&d.source&&"object"==typeof d.source){if("function"==typeof b){if(!d.input)return;b=c(d.input)}if(b.length)for(var e,f=0;f<b.length;f++)e=1===b.length?b:c(b.selector.split(",")[f].trim()),a.Typeahead[e.selector]=new i(e,d)}}};a.console=a.console||{log:function(){}},"trim"in String.prototype||(String.prototype.trim=function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")}),"indexOf"in Array.prototype||(Array.prototype.indexOf=function(a,b){b===d&&(b=0),0>b&&(b+=this.length),0>b&&(b=0);for(var c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1}),Object.keys||(Object.keys=function(a){var b,c=[];for(b in a)Object.prototype.hasOwnProperty.call(a,b)&&c.push(b);return c})}(window,document,window.jQuery);
diff --git a/web/packs/src/vendor/stylesheets/jquery.typeahead.min.css b/web/packs/src/vendor/stylesheets/jquery.typeahead.min.css
new file mode 100644
index 0000000..c0720b9
--- /dev/null
+++ b/web/packs/src/vendor/stylesheets/jquery.typeahead.min.css
@@ -0,0 +1 @@
+.typeahead-field,.typeahead-query{position:relative;width:100%}.typeahead-button,.typeahead-container,.typeahead-field,.typeahead-filter,.typeahead-query{position:relative}.typeahead-container button,.typeahead-field input,.typeahead-select{border:1px solid #ccc;line-height:1.42857143;padding:6px 12px;height:32px}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}.typeahead-container,.typeahead-result.detached .typeahead-list{font-family:"Open Sans",Arial,Helvetica,Sans-Serif}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}.typeahead-container *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.typeahead-query{z-index:2}.typeahead-filter button{min-width:66px}.typeahead-field{display:table;border-collapse:separate}.typeahead-button{font-size:0;white-space:nowrap;width:1%;vertical-align:middle}.typeahead-field>span{display:table-cell;vertical-align:top}.typeahead-button button{border-top-right-radius:2px;border-bottom-right-radius:2px}.typeahead-field input,.typeahead-select{display:block;width:100%;font-size:13px;color:#555;background:0 0;border-radius:2px 0 0 2px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.typeahead-field input{-webkit-appearance:none;background:0 0}.typeahead-field input:last-child,.typeahead-hint{background:#fff}.typeahead-container button{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-color:#fff;white-space:nowrap;font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#333;box-shadow:inset 0 -2px 0 rgba(0,0,0,.05);-moz-box-shadow:inset 0 -2px 0 rgba(0,0,0,.05);-webkit-box-shadow:inset 0 -2px 0 rgba(0,0,0,.05)}.typeahead-container button:active,.typeahead-container button:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.typeahead-container button:focus,.typeahead-container button:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.typeahead-container button.active,.typeahead-container button:active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.typeahead-container button.disabled,.typeahead-container button[disabled],.typeahead-field input.disabled,.typeahead-field input[disabled]{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;background-color:#fff;border-color:#ccc}.typeahead-button button,.typeahead-filter button{margin-left:-1px;border-bottom-left-radius:0;border-top-left-radius:0}.typeahead-button,.typeahead-filter{z-index:1}.typeahead-button:active,.typeahead-button:active button:active,.typeahead-button:focus,.typeahead-button:focus button:focus,.typeahead-button:hover,.typeahead-container.filter .typeahead-filter,.typeahead-filter:active,.typeahead-filter:focus,.typeahead-filter:hover{z-index:1001}.typeahead-dropdown,.typeahead-list{position:absolute;top:100%;left:0;z-index:1000;width:100%;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:13px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:2px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.typeahead-result.detached .typeahead-list{position:relative;z-index:1041;top:auto;left:auto}.typeahead-dropdown{right:0;left:auto;z-index:1001}.typeahead-list>li:first-child{border-top:none}.typeahead-list>li{position:relative;border-top:solid 1px rgba(0,0,0,.15)}.typeahead-dropdown>li>a,.typeahead-list>li>a{display:block;padding:6px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap;text-decoration:none}.typeahead-dropdown>li.active>a,.typeahead-dropdown>li>a:focus,.typeahead-dropdown>li>a:hover,.typeahead-list>li.active>a,.typeahead-list>li>a:focus,.typeahead-list>li>a:hover{background-color:#ebebeb;color:#333}.typeahead-list.empty>li.active>a,.typeahead-list.empty>li>a:focus,.typeahead-list.empty>li>a:hover{background-color:transparent}.typeahead-list.empty>li>a{cursor:default}.typeahead-list>li.typeahead-group.active>a,.typeahead-list>li.typeahead-group>a,.typeahead-list>li.typeahead-group>a:focus,.typeahead-list>li.typeahead-group>a:hover{border-color:#9cb4c5;color:#305d8c;background-color:#d6dde7;cursor:default}.typeahead-container.backdrop+.typeahead-backdrop,.typeahead-container.filter .typeahead-dropdown,.typeahead-container.hint .typeahead-hint,.typeahead-container.result .typeahead-list{display:block!important}.typeahead-container .typeahead-dropdown,.typeahead-container .typeahead-hint,.typeahead-container .typeahead-list,.typeahead-container+.typeahead-backdrop{display:none!important}.typeahead-dropdown .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#e5e5e5}.typeahead-caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.typeahead-search-icon{min-width:40px;height:18px;font-size:13px;display:block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABH0lEQVR4nJ3SvyvFYRTH8deVkkJ3UUZJIbJ8bzJjMtyMym6w2Njs/gCDP0AGCyWjxYDF5GdJYpS6xaIUw/d8771dT7qc+vZ8vs95zvuc5zmnlGWZsG6sYBGjsXeNHWzjQ8JKARjCEUZSh3CJeTy3OjoicxF8hwX0oi/0HSZwiK4UYKUpeBoHeMdb6OnwTWI5BVgMvYZaovwa1kMvpQBjoY8TwVp84ylAO/YV62cKcBt65hfAbKwPKcBu6E2UE8Hl8MF+CrCFG/nwnKKKnviqONOYj6NWQDFIg/I+/3ikFnuUX6d+lY4mR4ZVnMvnoIYLbKCCp0h0otG5egXt2HAED+BFPmAP7bYR7jGHV/RjCjr/AICryFzB3n8ARSX3xc83qRk4q9rDNWcAAAAASUVORK5CYII=) center center no-repeat}
diff --git a/web/packs/stylesheets.js b/web/packs/stylesheets.js
new file mode 100644
index 0000000..07a767b
--- /dev/null
+++ b/web/packs/stylesheets.js
@@ -0,0 +1 @@
+import './src/stylesheets/application.scss';
diff --git a/web/templates/home/home.tmpl b/web/templates/home/home.tmpl
new file mode 100644
index 0000000..b51e2ee
--- /dev/null
+++ b/web/templates/home/home.tmpl
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" "home"}}
+
+<div id="scroll-down-section" class="text-muted" style="z-index: 9999;">
+ <br/><br/>
+ <i onclick="document.getElementById('lists-section').scrollIntoView(true);" class="fa fa-angle-down" aria-hidden="true"></i>
+</div>
+
+<div class="container mb-5">
+ <div class="row">
+ <div id="landing-page-search-area" class="col-12 px-5">
+
+ <div class="jumbotron w-100 px-5" style="background-color: #FAFAFA;">
+ <h2 class="site-welcome stick-top">Welcome to the Home<br/> of <span class="text-primary"> 1,111,111 </span> Gentoo Related Mails</h2>
+
+ <form action="/search" method="get">
+ <div class="typeahead-container">
+ <div class="typeahead-field">
+ <span class="typeahead-query">
+ <input id="q" name="q" type="search" autocomplete="off" placeholder="Find Mails" aria-label="Find Mails" autofocus="">
+ </span>
+
+ <span class="typeahead-button">
+ <button type="button" onclick="$('#searchHelp').modal('show')" title="Search for Threads only" aria-label="Search for Threads only">
+ <span class="fa fa-comments-o" style="font-size: 15px;"></span><span class="sr-only">Search for Threads only</span>
+ </button>
+ </span>
+ <span class="typeahead-button">
+ <button type="submit" title="Find" aria-label="Find">
+ <span class="typeahead-search-icon"></span><span class="sr-only">Find</span>
+ </button>
+ </span>
+ </div>
+ </div>
+ </form>
+ <br>
+ <small class="mt-4 text-muted" style="font-size: 12px;">This is the new archives.gentoo.org site. If anything isn't working as expected, <a href="mailto:infra@gentoo.org">contact us</a>.<br>
+ You can search by <a href="/search?q=gentoo-dev">mailing list</a>, <a href="/search?q=Last+rites">author</a>, <a href="/search?q=Last+rites">subject</a> or <a href="/search?q=File+transfer+program+to+keep+remote+files+into+sync">message body</a>. Results similar to your query will be found as well.</small>
+ </div>
+
+ <div id="landing-page-popular-threads" class="mx-5 text-muted">
+ <div class="mx-auto text-center">
+ <p class="mb-1"><a class="text-muted" href="/popular"><b>Recent Popular Threads</b></a></p>
+ {{range .PopularThreads}}
+ <p class="mb-1"><a href="/{{(makeMessage .Headers).GetListNameFromSubject}}/message/{{.Id}}" class="text-muted">{{(makeMessage .Headers).GetHeaderField "Subject"}}</a></p>
+ {{end}}
+ </div>
+ </div>
+
+ </div>
+
+ <div id="lists-section" class="col-12 pt-3">
+ <p class="lead">
+ Here you can find the archives of our most important mailing lists.
+ </p>
+ <p>
+ For a complete list of available archives, see the <a href="/lists" class="btn btn-primary btn-sm px-1 py-0"><i class="fa fa-fw fa-archive"></i> All Archives</a> section.
+ </p>
+ </div>
+
+ <div class="col-12">
+ {{range .MailingLists}}
+ <hr/>
+ <div class="row">
+ <div class="col-12 col-md-4">
+ <h2 class="stick-top">{{.Name}}</h2>
+ <p>
+ <tt>{{.Name}}</tt> {{.Description}}.
+ </p>
+ <p class="ag-index-actions">
+ <a class="btn btn-primary btn-block" href="/{{.Name}}/threads/{{ $.CurrentMonth}}/"><span class="fa fa-fw fa-inbox"></span> This Month's Archives</a>
+ <a class="btn btn-outline-secondary text-dark btn-block" href="/{{.Name}}/"><span class="fa fa-fw fa-inbox"></span> Complete Archives</a>
+ </p>
+ </div>
+ <div class="col-12 col-md-8">
+ <h3 class="ag-mostrecent-header">Most recent messages</h3>
+ <div class="table-responsive">
+ <table class="table table-sm table-hover ag-mostrecent-table">
+ <tbody>
+ <tr>
+ <th>Subject</th>
+ <th class="ag-mostrecent-table-author">Author</th>
+ </tr>
+ {{$listName:=.Name}}
+ {{range .Messages}}
+ <tr>
+ <td><a href="/{{$listName}}/message/{{.Id}}">{{.GetHeaderField "Subject"}}</a></td>
+ <td>{{.GetAuthorName}}</td>
+ </tr>
+ {{end}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ {{end}}
+
+ <hr/>
+ <h2>Other Lists</h2>
+ <p>
+ All other archives are available here: <a href="/lists" class="btn btn-primary"><i class="fa fa-fw fa-archive"></i> All Archives</a>
+ </p>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+<script src="/assets/index.js"></script>
+
+</body>
+</html>
diff --git a/web/templates/layout/footer.tmpl b/web/templates/layout/footer.tmpl
new file mode 100644
index 0000000..a74b10a
--- /dev/null
+++ b/web/templates/layout/footer.tmpl
@@ -0,0 +1,36 @@
+{{define "footer"}}
+ <footer>
+ <div class="container">
+ <div class="row">
+ <div class="col-12 offset-md-2 col-md-7">
+ <p class="spacer">
+ All times displayed are in UTC (GMT+0).<br>
+ Contents reflect the opinion of the author, not the Gentoo project or the Gentoo Foundation.
+ </p>
+ </div>
+ <div class="col-12 col-md-3">
+ <h3 class="footerhead">Questions or comments?</h3>
+ Please feel free to <a href="https://www.gentoo.org/inside-gentoo/contact/">contact us</a>.
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-2 col-sm-3 col-md-2">
+ <ul class="footerlinks three-icons">
+ <li><a href="https://twitter.com/gentoo" title="@Gentoo on Twitter"><span class="fa fa-twitter fa-fw"></span></a></li>
+ <li><a href="https://www.facebook.com/gentoo.org" title="Gentoo on Facebook"><span class="fa fa-facebook fa-fw"></span></a></li>
+ <li><a href="https://www.reddit.com/r/Gentoo/" title="Gentoo on Reddit"><span class="fa fa-reddit-alien fa-fw"></span></a></li>
+ </ul>
+ </div>
+ <div class="col-10 col-sm-9 col-md-10">
+ <strong>&copy; 2001&ndash;2020 Gentoo Foundation, Inc.</strong><br>
+ <small>
+ Gentoo is a trademark of the Gentoo Foundation, Inc.
+ The contents of this document, unless otherwise expressly stated, are licensed under the
+ <a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="license">CC-BY-SA-4.0</a> license.
+ The <a href="https://www.gentoo.org/inside-gentoo/foundation/name-logo-guidelines.html">Gentoo Name and Logo Usage Guidelines</a> apply.
+ </small>
+ </div>
+ </div>
+ </div>
+ </footer>
+{{end}}
diff --git a/web/templates/layout/head.tmpl b/web/templates/layout/head.tmpl
new file mode 100644
index 0000000..8408128
--- /dev/null
+++ b/web/templates/layout/head.tmpl
@@ -0,0 +1,14 @@
+{{define "head"}}
+ <head>
+ <title>Gentoo Mailing List Archives</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="theme-color" content="#54487a">
+ <meta name="description" content="Gentoo Packages Database">
+ <!-- link rel="stylesheet" media="screen" href="/packs/css/application-024b2074.css" /-->
+ <!-- script src="/packs/js/application-cfb73e71df8935c3a9ed.js"></script -->
+ <script src="/assets/stylesheets.js"></script>
+ <script src="/assets/application.js"></script>
+ <link rel="icon" href="https://packages.gentoo.org/favicon.ico" type="image/x-icon">
+ </head>
+{{end}}
diff --git a/web/templates/layout/header.tmpl b/web/templates/layout/header.tmpl
new file mode 100644
index 0000000..571a58b
--- /dev/null
+++ b/web/templates/layout/header.tmpl
@@ -0,0 +1,6 @@
+{{define "header"}}
+ <header>
+ {{template "sitetitle"}}
+ {{template "tyrian-navbar" .}}
+ </header>
+{{end}}
diff --git a/web/templates/layout/sitetitle.tmpl b/web/templates/layout/sitetitle.tmpl
new file mode 100644
index 0000000..ca65b27
--- /dev/null
+++ b/web/templates/layout/sitetitle.tmpl
@@ -0,0 +1,37 @@
+{{define "sitetitle"}}
+ <div class="site-title">
+ <div class="container">
+ <div class="row justify-content-between">
+ <div class="logo">
+ <a href="/" title="Back to the homepage" class="site-logo">
+ <img src="https://assets.gentoo.org/tyrian/site-logo.png" alt="Gentoo" srcset="https://assets.gentoo.org/tyrian/site-logo.svg">
+ </a>
+ <span class="site-label">Archives</span>
+ </div>
+ <div class="site-title-buttons">
+ <div class="btn-group btn-group-sm">
+ <a href="https://get.gentoo.org/" role="button" class="btn get-gentoo"><span class="fa fa-fw fa-download"></span> <strong>Get Gentoo!</strong></a>
+ <div class="btn-group btn-group-sm">
+ <a class="btn gentoo-org-sites dropdown-toggle" data-toggle="dropdown" data-target="#" href="#">
+ <span class="fa fa-fw fa-map-o"></span> <span class="d-none d-sm-inline">gentoo.org sites</span> <span class="caret"></span>
+ </a>
+ <div class="dropdown-menu dropdown-menu-right">
+ <a class="dropdown-item" href="https://www.gentoo.org/" title="Main Gentoo website"><span class="fa fa-home fa-fw"></span> gentoo.org</a>
+ <a class="dropdown-item" href="https://wiki.gentoo.org/" title="Find and contribute documentation"><span class="fa fa-file-text-o fa-fw"></span> Wiki</a>
+ <a class="dropdown-item" href="https://bugs.gentoo.org/" title="Report issues and find common issues"><span class="fa fa-bug fa-fw"></span> Bugs</a>
+ <a class="dropdown-item" href="https://forums.gentoo.org/" title="Discuss with the community"><span class="fa fa-comments-o fa-fw"></span> Forums</a>
+ <a class="dropdown-item" href="https://packages.gentoo.org/" title="Find software for your Gentoo"><span class="fa fa-hdd-o fa-fw"></span> Packages</a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="https://planet.gentoo.org/" title="Find out what's going on in the developer community"><span class="fa fa-rss fa-fw"></span> Planet</a>
+ <a class="dropdown-item" href="https://archives.gentoo.org/" title="Read up on past discussions"><span class="fa fa-archive fa-fw"></span> Archives</a>
+ <a class="dropdown-item" href="https://sources.gentoo.org/" title="Browse our source code"><span class="fa fa-code fa-fw"></span> Sources</a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="https://infra-status.gentoo.org/" title="Get updates on the services provided by Gentoo"><span class="fa fa-server fa-fw"></span> Infra Status</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+{{end}}
diff --git a/web/templates/layout/tyriannav.tmpl b/web/templates/layout/tyriannav.tmpl
new file mode 100644
index 0000000..f7c83da
--- /dev/null
+++ b/web/templates/layout/tyriannav.tmpl
@@ -0,0 +1,39 @@
+{{define "tyrian-navbar"}}
+ <nav class="tyrian-navbar navbar navbar-dark navbar-expand-lg bg-primary" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-main-collapse" aria-controls="navbar-main-collapse" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ </div>
+ <div class="collapse navbar-collapse navbar-main-collapse" id="navbar-main-collapse">
+ <ul class="navbar-nav mr-auto">
+
+ <li class="nav-item {{ if (eq . "home")}}active{{end}}"><a class="nav-link" href="/">Home</a></li>
+ <li class="nav-item {{ if (eq . "browse")}}active{{end}}"><a class="nav-link" href="/lists"><i class="fa fa-fw fa-archive"></i> All Archives</a></li>
+ {{ if not (eq . "home" "search" "browse" "popular") }}
+ <li class="nav-item active"><a class="nav-link" href="/{{.}}/"><i class="fa fa-fw fa-inbox"></i> {{.}}</a></li>
+ {{end}}
+
+ </ul>
+
+ {{ if ne . "home"}}
+ <form class="form-inline inlinesearch" role="search" action="/search" method="get">
+
+ <div class="input-group">
+
+ <div class="input-group-prepend">
+ <span class="input-group-text" id="basic-addon1"><i class="fa fa-search" aria-hidden="true"></i></span>
+ </div>
+
+ <input class="form-control" type="text" name="q" type="text" placeholder="Find Messages" aria-label="Find Messages">
+ </div>
+
+ </form>
+ {{end}}
+
+
+ </div>
+ </div>
+ </nav>
+{{end}}
diff --git a/web/templates/list/browse.tmpl b/web/templates/list/browse.tmpl
new file mode 100644
index 0000000..9c2ea3f
--- /dev/null
+++ b/web/templates/list/browse.tmpl
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" "browse"}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12">
+ <h1 class="first-header">Gentoo Mailing List Archives</h1>
+
+ <h2>Current Mailing Lists</h2>
+
+ <div class="row">
+ <div class="col-12 col-md-6">
+ <div class="list-group">
+ {{range .CurrentMailingLists}}
+ <a href="/{{.Name}}/" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+ <span>
+ <span class="fa fa-fw fa-archive"></span>
+ {{.Name}}
+ </span>
+ <h4 class="mb-0">
+ <span class="badge badge-secondary badge-pill">{{.MessageCount}}</span>
+ </h4>
+ </a>
+ {{end}}
+ </div>
+ </div>
+ <div class="col-12 col-md-6">
+ <div class="alert alert-info" role="alert">
+ <strong>How to Participate</strong><br>
+ Please see our <a href="https://www.gentoo.org/main/en/lists.xml" class="alert-link">Mailing List information page</a> for more information on
+ how you can subscribe and participate in the discussions.
+ </div>
+ </div>
+ </div>
+
+ <h2 class="mt-5">Frozen Archives</h2>
+
+ <div class="row">
+ <div class="col-12 col-md-6">
+ <div class="list-group">
+ {{range .FrozenArchives}}
+ <a href="/{{.Name}}/" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+ <span>
+ <span class="fa fa-fw fa-archive"></span>
+ {{.Name}}
+ </span>
+ <h4 class="mb-0">
+ <span class="badge badge-secondary badge-pill">{{.MessageCount}}</span>
+ </h4>
+ </a>
+ {{end}}
+ </div>
+ </div>
+ <div class="col-12 col-md-6">
+ <div class="alert alert-warning" role="alert">
+ <strong>Inactive Lists</strong><br>
+ These mailing lists are inactive. You can not post or subscribe to them any more.
+ Archives are provided for future reference.
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>
diff --git a/web/templates/list/components/pagination.tmpl b/web/templates/list/components/pagination.tmpl
new file mode 100644
index 0000000..5ff13bd
--- /dev/null
+++ b/web/templates/list/components/pagination.tmpl
@@ -0,0 +1,44 @@
+{{define "pagination"}}
+ <nav class="pull-right">
+ <ul class="pagination ag-pager">
+ {{ if eq .CurrentPage 1}}
+ <li class="page-item disabled">
+ <a class="page-link" href="#" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ {{else}}
+ <li class="page-item">
+ <a class="page-link" href="{{ sub .CurrentPage 1 }}" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ {{end}}
+ </li>
+ {{ $min := max 1 (sub .CurrentPage 3) }}
+ {{ $max := min .MaxPages (add .CurrentPage 3) }}
+ {{if gt $min 3 }}
+ <li class="page-item"><a class="page-link" href="1">1</a></li>
+ <li class="page-item disabled"><a class="page-link" href="#">…</a></li>
+ {{end}}
+
+ {{range (makeRange $min $max)}}
+ <li {{if eq . $.CurrentPage}}class="page-item active"{{end}}><a class="page-link" href="{{.}}">{{.}}</a></li>
+ {{end}}
+ {{if gt (sub .MaxPages $max) 3}}
+ <li class="page-item disabled"><a class="page-link" href="#">…</a></li>
+ <li class="page-item"><a class="page-link" href="{{.MaxPages}}">{{.MaxPages}}</a></li>
+ {{end}}
+ {{if eq .CurrentPage .MaxPages}}
+ <li class="page-item disabled">
+ <a class="page-link" href="#" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ {{else}}
+ <li class="page-item">
+ <a class="page-link" href="{{ add .CurrentPage 1 }}" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ {{end}}
+ </li>
+ </ul>
+ </nav>
+{{end}}
diff --git a/web/templates/list/messages.tmpl b/web/templates/list/messages.tmpl
new file mode 100644
index 0000000..31084bd
--- /dev/null
+++ b/web/templates/list/messages.tmpl
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" .ListName}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Gentoo Archives: {{.ListName}} in {{.Date}}</h1>
+
+ <div class="d-none d-sm-block">
+ {{template "pagination" . }}
+ </div>
+
+ <div class="btn-group ag-view-selection" role="group" aria-label="Message view selection">
+ <a href="/{{.ListName}}/threads/{{.Date}}/" class="btn btn-outline-secondary">Threads</a>
+ <a href="/{{.ListName}}/messages/{{.Date}}/" class="btn btn-primary">Messages</a>
+ </div>
+
+ <div class="table-responsive">
+ <table class="table table-sm table-hover ag-message-table">
+ <tr>
+ <th class="ag-message-table-subject">Subject</th>
+ <th class="ag-message-table-from">From</th>
+ <th class="ag-message-table-date">Date</th>
+ </tr>
+
+ {{range .Messages}}
+ <tr>
+ <td><a href="../../message/{{.Id}}">{{.GetHeaderField "Subject"}}</a></td>
+ <td>{{.GetAuthorName}}</td>
+ <td><span class="ag-date">{{.Date.Format "Mon, 2 Jan 2006 15:04:05"}}</span></td>
+ </tr>
+ {{end}}
+
+ </table>
+ </div>
+
+ {{template "pagination" . }}
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>
diff --git a/web/templates/list/show.tmpl b/web/templates/list/show.tmpl
new file mode 100644
index 0000000..40b01ef
--- /dev/null
+++ b/web/templates/list/show.tmpl
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" .ListName}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Gentoo Archives: {{.ListName}}</h1>
+
+ <table class="table">
+ <tr>
+ <th>Month</th>
+ <th>Number of messages</th>
+ </tr>
+
+ {{range .MessageData}}
+ <tr>
+ <td><a href="threads/{{.CombinedDate}}/">{{.CombinedDate}}</a></td>
+ <td>{{.MessageCount}}</td>
+ </tr>
+ {{end}}
+
+ </table>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>
diff --git a/web/templates/list/threads.tmpl b/web/templates/list/threads.tmpl
new file mode 100644
index 0000000..ec07e49
--- /dev/null
+++ b/web/templates/list/threads.tmpl
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" .ListName}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Gentoo Archives: {{.ListName}} in {{.Date}}</h1>
+
+ <div class="d-none d-sm-block">
+ {{template "pagination" . }}
+ </div>
+
+ <div class="btn-group ag-view-selection" role="group" aria-label="Message view selection">
+ <a href="/{{.ListName}}/threads/{{.Date}}/" class="btn btn-primary">Threads</a>
+ <a href="/{{.ListName}}/messages/{{.Date}}/" class="btn btn-outline-secondary">Messages</a>
+ </div>
+
+ <div class="table-responsive">
+ <table class="table table-sm table-hover ag-message-table">
+ <tr>
+ <th class="ag-message-table-subject">Subject</th>
+ <th class="ag-message-table-from">From</th>
+ <th class="ag-message-table-date">Date</th>
+ </tr>
+
+ {{range .Messages}}
+ <tr>
+ <td><a href="../../message/{{.Id}}">{{.GetHeaderField "Subject"}}</a></td>
+ <td>{{.GetAuthorName}}</td>
+ <td><span class="ag-date">{{.Date.Format "Mon, 2 Jan 2006 15:04:05"}}</span></td>
+ </tr>
+ {{end}}
+
+ </table>
+ </div>
+
+ {{template "pagination" . }}
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>
diff --git a/web/templates/message/show.tmpl b/web/templates/message/show.tmpl
new file mode 100644
index 0000000..7b88c35
--- /dev/null
+++ b/web/templates/message/show.tmpl
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" .ListName}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Gentoo Archives: {{.ListName}}</h1>
+
+ <div class="table-responsive">
+ <table class="table table-sm ag-header-table">
+ <tr>
+ <th class="ag-header-name-col">From:</th>
+ <td>{{formatAddr (.Message.GetHeaderField "From")}}</td>
+ </tr>
+ <tr>
+ <th>To:</th>
+ <td>{{formatAddr (.Message.GetHeaderField "To")}}</td>
+ </tr>
+ {{if .Message.HasHeaderField "Cc"}}
+ <tr>
+ <th>Cc:</th>
+ <td>{{formatAddr (.Message.GetHeaderField "Cc")}}</td>
+ </tr>
+ {{end}}
+
+ <tr>
+ <th>Subject:</th>
+ <td><strong>{{.Message.GetHeaderField "Subject"}}</strong></td>
+ </tr>
+ <tr>
+ <th>Date:</th>
+ <td>{{.Message.Date.Format "Mon, 2 Jan 2006 15:04:05"}}</td>
+ </tr>
+ <tr>
+ <th>Message-Id:</th>
+ <td><tt>{{.Message.GetMessageId}}</tt></td>
+ </tr>
+
+ {{if .InReplyTo}}
+ <tr>
+ <th>In Reply to:</th>
+ <td colspan="3"><a href="/{{.ListName}}/messages/{{.InReplyTo.Id}}">{{.InReplyTo.GetHeaderField "Subject"}}</a> by {{.InReplyTo.GetAuthorName}}</td>
+ </tr>
+ {{end}}
+
+ </table>
+ </div>
+
+ <pre class="ag-message-content">{{.Message.GetBody}}</pre>
+
+ {{if .Message.HasAttachments }}
+ <h3>Attachments</h3>
+
+ <div class="table-responsive">
+ <table class="table table-sm ag-attachment-table">
+ <tr>
+ <th>File name</th>
+ <th>MIME type</th>
+ </tr>
+
+ {{range .Message.GetAttachments}}
+ <tr>
+ <td>{{.Filename}}</td>
+ <td>{{.Mime}}</td>
+ </tr>
+ {{end}}
+ </table>
+ </div>
+ {{end}}
+
+ {{if .Replies}}
+ <div class="table-responsive">
+ <table class="table table-sm ag-replies-table">
+ <tbody><tr>
+ <th>Subject</th>
+ <th>Author</th>
+ </tr>
+ {{range .Replies}}
+ <tr>
+ <td><a href="{{.Id}}">{{.GetHeaderField "Subject"}}</a></td>
+ <td>{{formatAddr (.GetHeaderField "From")}}</td>
+ </tr>
+ {{end}}
+ </tbody>
+ </table>
+ </div>
+ {{end}}
+
+ <div class="ag-message-actions">
+ <a href="mailto:infra@gentoo.org?subject=Reporting mail {{.Message.Id}} on archives.g.o" class="btn btn-danger btn-sm"><span class="fa fa-fw fa-ban"></span> Report Message</a>
+ <div class="btn-group btn-group-sm ml-3">
+ <a href="https://marc.info/?i={{.Message.GetMessageId}}" class="btn btn-outline-secondary"><span class="fa fa-fw fa-share-square"></span>Find on MARC</a>
+ <a href="https://groups.google.com/forum/#!search/messageid${{.Message.GetMessageId}}" class="btn btn-outline-secondary"><span class="fa fa-fw fa-share-square"></span>Find on Google Groups</a>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+<script src="/assets/message.js"></script>
+
+</body>
+</html>
diff --git a/web/templates/popular/threads.tmpl b/web/templates/popular/threads.tmpl
new file mode 100644
index 0000000..5b849eb
--- /dev/null
+++ b/web/templates/popular/threads.tmpl
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" "popular"}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Popular Recent Threads:</h1>
+
+ <table class="table">
+ <tr>
+ <th>Thread</th>
+ <th>Number of messages</th>
+ </tr>
+
+ {{range .}}
+ <tr>
+ <td><a href="/{{(makeMessage .Headers).GetListNameFromSubject}}/message/{{.Id}}/">{{(makeMessage .Headers).GetSubject}}</a></td>
+ <td>{{.Count}}</td>
+ </tr>
+ {{end}}
+
+ </table>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>
diff --git a/web/templates/search/components/pagination.tmpl b/web/templates/search/components/pagination.tmpl
new file mode 100644
index 0000000..e4df856
--- /dev/null
+++ b/web/templates/search/components/pagination.tmpl
@@ -0,0 +1,44 @@
+{{define "pagination"}}
+ <nav class="pull-right">
+ <ul class="pagination ag-pager">
+ {{ if eq .CurrentPage 1}}
+ <li class="page-item disabled">
+ <a class="page-link" href="#" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ {{else}}
+ <li class="page-item">
+ <a class="page-link" href="/search?q={{.SearchQuery}}&page={{ sub .CurrentPage 1 }}" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ {{end}}
+ </li>
+ {{ $min := max 1 (sub .CurrentPage 3) }}
+ {{ $max := min .MaxPages (add .CurrentPage 3) }}
+ {{if gt $min 3 }}
+ <li class="page-item"><a class="page-link" href="/search?q={{.SearchQuery}}&page=1">1</a></li>
+ <li class="page-item disabled"><a class="page-link" href="#">…</a></li>
+ {{end}}
+
+ {{range (makeRange $min $max)}}
+ <li {{if eq . $.CurrentPage}}class="page-item active"{{end}}><a class="page-link" href="/search?q={{$.SearchQuery}}&page={{.}}">{{.}}</a></li>
+ {{end}}
+ {{if gt (sub .MaxPages $max) 3}}
+ <li class="page-item disabled"><a class="page-link" href="#">…</a></li>
+ <li class="page-item"><a class="page-link" href="/search?q={{.SearchQuery}}&page={{.MaxPages}}">{{.MaxPages}}</a></li>
+ {{end}}
+ {{if eq .CurrentPage .MaxPages}}
+ <li class="page-item disabled">
+ <a class="page-link" href="#" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ {{else}}
+ <li class="page-item">
+ <a class="page-link" href="/search?q={{.SearchQuery}}&page={{ add .CurrentPage 1 }}" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ {{end}}
+ </li>
+ </ul>
+ </nav>
+{{end}}
diff --git a/web/templates/search/searchresults.tmpl b/web/templates/search/searchresults.tmpl
new file mode 100644
index 0000000..ecd64f0
--- /dev/null
+++ b/web/templates/search/searchresults.tmpl
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+{{template "head"}}
+<body>
+{{template "header" "search"}}
+
+<div class="container mb-5">
+ <div class="row">
+ <div class="col-12 pb-4">
+ <h1 class="first-header">Search Results <small>{{.SearchQuery}}</small></h1>
+
+ <div class="d-none d-sm-block">
+ {{template "pagination" . }}
+ </div>
+
+ <div class="btn-group ag-view-selection" role="group" aria-label="Message view selection">
+ <a href="/search?q={{.SearchQuery}}&page={{.CurrentPage}}&threads=true" class="btn {{if .ShowThreads}}btn-primary{{else}}btn-outline-secondary{{end}}">Threads</a>
+ <a href="/search?q={{.SearchQuery}}&page={{.CurrentPage}}" class="btn {{if not .ShowThreads}}btn-primary{{else}}btn-outline-secondary{{end}}">Messages</a>
+ </div>
+
+ <div class="table-responsive mt-5">
+ <table class="table table-sm table-hover ag-message-table">
+ <tr>
+ <th class="ag-message-table-subject">Subject</th>
+ <th class="ag-message-table-from">From</th>
+ <th class="ag-message-table-date">Date</th>
+ </tr>
+
+ {{range .Messages}}
+ <tr>
+ <td><a href="/{{.GetListNameFromSubject}}/message/{{.Id}}">{{.GetHeaderField "Subject"}}</a></td>
+ <td>{{.GetAuthorName}}</td>
+ <td><span class="ag-date">{{.Date.Format "Mon, 2 Jan 2006 15:04:05"}}</span></td>
+ </tr>
+ {{end}}
+
+ </table>
+ </div>
+
+ <div class="row">
+ <div class="col-6">
+ Showing {{add (mul (sub .CurrentPage 1) 50) 1}} to {{min (mul .CurrentPage 50) .SearchResultsCount}} of {{.SearchResultsCount}} entries
+ </div>
+ <div class="col-6">
+ {{template "pagination" . }}
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+
+{{template "footer"}}
+
+</body>
+</html>