Slowly remove DSL and split frontend into components
This commit is contained in:
parent
0ebb5f7013
commit
3cd7b78d81
11 changed files with 329 additions and 243 deletions
0
backend/__init__.py
Normal file
0
backend/__init__.py
Normal file
|
@ -1,62 +1,26 @@
|
||||||
from datetime import datetime
|
|
||||||
from flask import Flask, jsonify, request
|
from flask import Flask, jsonify, request
|
||||||
from elasticsearch_dsl import Document, Date, Integer, Keyword, Text, FacetedSearch, TermsFacet, DateHistogramFacet
|
from elasticsearch_dsl import Search
|
||||||
from elasticsearch_dsl.connections import connections
|
from elasticsearch_dsl.connections import connections
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
from beeprint import pp
|
||||||
import csv
|
import csv
|
||||||
import click
|
import click
|
||||||
import re
|
|
||||||
|
|
||||||
class Question(Document):
|
from .question import Question
|
||||||
title = Text(analyzer="snowball")
|
from .question_search import QuestionSearch
|
||||||
body = Text(analyzer="snowball")
|
|
||||||
category = Keyword()
|
|
||||||
date = Date()
|
|
||||||
|
|
||||||
class Index:
|
|
||||||
name = "goeievraag"
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
return super(Question, self).save(**kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
id_ = self.meta.id
|
|
||||||
if not self.category:
|
|
||||||
return f"https://www.startpagina.nl/v/vraag/{id_}"
|
|
||||||
|
|
||||||
category = self.category.lower()
|
|
||||||
category = re.sub("[^A-Za-z ]", "", category).replace(" ", "-")
|
|
||||||
return f"https://www.startpagina.nl/v/{category}/vraag/{id_}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def summary(self, length=128):
|
|
||||||
if len(self.body) > length:
|
|
||||||
return self.body[:length - 3] + "..."
|
|
||||||
|
|
||||||
return self.body
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QuestionSearch(FacetedSearch):
|
|
||||||
doc_types = Question,
|
|
||||||
fields = "category", "title", "body"
|
|
||||||
|
|
||||||
facets = {
|
|
||||||
"date_frequency": DateHistogramFacet(field="date", interval="month"),
|
|
||||||
"category": TermsFacet(field="category")
|
|
||||||
}
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
connections.create_connection(hosts=["localhost"])
|
connections.create_connection(hosts=["localhost"])
|
||||||
Question.init()
|
Question.init()
|
||||||
|
|
||||||
def round_sigfig(value, figures):
|
|
||||||
return float(format(value, f".{figures}g"))
|
|
||||||
|
|
||||||
@app.route("/api/")
|
@app.route("/api/")
|
||||||
def index():
|
def index():
|
||||||
|
def round_sigfig(value, figures):
|
||||||
|
return float(format(value, f".{figures}g"))
|
||||||
|
|
||||||
query = request.args.get("q")
|
query = request.args.get("q")
|
||||||
categories = request.args.get("categories", None)
|
categories = request.args.get("categories", None)
|
||||||
|
|
||||||
|
@ -65,19 +29,41 @@ def index():
|
||||||
category_list = categories.split(",")
|
category_list = categories.split(",")
|
||||||
facets["category"] = category_list
|
facets["category"] = category_list
|
||||||
|
|
||||||
search = QuestionSearch(query, facets)
|
search = Search.from_dict({
|
||||||
|
"query": {
|
||||||
|
"query_string": {
|
||||||
|
"query": query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"aggregations": {
|
||||||
|
"category": {
|
||||||
|
"terms": {"field": "category"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
response = search.execute()
|
response = search.execute()
|
||||||
|
|
||||||
date_facets = [{"timestamp": date.timestamp(), "count": count}
|
#date_facets = [{"timestamp": date.timestamp(), "count": count}
|
||||||
for date, count, _ in response.facets.date_frequency]
|
#for date, count, _ in response.facets.date_frequency]
|
||||||
category_facets = [{"category": category, "count": round_sigfig(count, 3)}
|
category_facets = [
|
||||||
for category, count, _ in response.facets.category]
|
{"category": bucket.key, "count": round_sigfig(bucket.doc_count, 3)}
|
||||||
|
for bucket in response.aggregations.category.buckets
|
||||||
|
]
|
||||||
|
|
||||||
results = [{"id": hit.meta.id, "score": hit.meta.score, "title": hit.title,
|
date_facets = []
|
||||||
"body": hit.summary, "category": hit.category,
|
|
||||||
"date": hit.date, "url": hit.url}
|
results = []
|
||||||
for hit in response]
|
for hit in response:
|
||||||
|
summary = Question.summary(hit)
|
||||||
|
url = Question.url(hit)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"id": hit.meta.id, "score": hit.meta.score,
|
||||||
|
"title": hit.title, "body": summary,
|
||||||
|
"category": hit.category, "date": hit.date,
|
||||||
|
"url": url,
|
||||||
|
})
|
||||||
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
facets={"months": date_facets, "categories": category_facets},
|
facets={"months": date_facets, "categories": category_facets},
|
||||||
|
|
24
backend/question.py
Normal file
24
backend/question.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from elasticsearch_dsl import Document, Date, Keyword, Text
|
||||||
|
|
||||||
|
|
||||||
|
class Question(Document):
|
||||||
|
title = Text(analyzer="snowball")
|
||||||
|
body = Text(analyzer="snowball")
|
||||||
|
category = Keyword()
|
||||||
|
date = Date()
|
||||||
|
|
||||||
|
class Index:
|
||||||
|
name = "goeievraag"
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
return super(Question, self).save(**kwargs)
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
id_ = self.meta.id
|
||||||
|
return f"https://www.startpagina.nl/v/vraag/{id_}/"
|
||||||
|
|
||||||
|
def summary(self, length=128):
|
||||||
|
if len(self.body) > length:
|
||||||
|
return self.body[:length - 3] + "..."
|
||||||
|
|
||||||
|
return self.body
|
12
backend/question_search.py
Normal file
12
backend/question_search.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from elasticsearch_dsl import FacetedSearch, TermsFacet, DateHistogramFacet
|
||||||
|
from .question import Question
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionSearch(FacetedSearch):
|
||||||
|
doc_types = Question,
|
||||||
|
fields = "title", "body"
|
||||||
|
|
||||||
|
facets = {
|
||||||
|
"date_frequency": DateHistogramFacet(field="date", interval="month"),
|
||||||
|
"category": TermsFacet(field="category"),
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<title>zoekmachine</title>
|
<title>Goeievraagle</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
@ -14,6 +14,10 @@ body {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
|
|
|
@ -4,15 +4,33 @@
|
||||||
<img class="logo__image" src="@/assets/logo.png">
|
<img class="logo__image" src="@/assets/logo.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="query">
|
<div class="query">
|
||||||
<input class="query__input">
|
<input class="query__input" v-model="query" @keyup.enter="search">
|
||||||
<div class="query__buttons">
|
<div class="query__buttons">
|
||||||
<input type="button" value="Goeievraagle zoeken" class="query__submit">
|
<input type="button" value="Goeievraagle zoeken" class="query__submit" @click="search">
|
||||||
<input type="button" value="Ik doe een gok" class="query__submit">
|
<input type="button" value="Ik doe een gok" class="query__submit" @click="lucky">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from "vue";
|
||||||
|
import {Component} from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class Index extends Vue {
|
||||||
|
query = "";
|
||||||
|
|
||||||
|
search() {
|
||||||
|
const query = encodeURIComponent(this.query);
|
||||||
|
this.$router.push(`/search?q=${query}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lucky() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.index {
|
.index {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
174
frontend/src/components/ResultBody.vue
Normal file
174
frontend/src/components/ResultBody.vue
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<div class="body">
|
||||||
|
<div class="result-count">
|
||||||
|
Ongeveer {{hits}} resultaten ({{responseTime.toFixed(2)}} seconden)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body__inner">
|
||||||
|
<div class="body__facets">
|
||||||
|
<div v-for="facet in facets.categories" v-bind:key="facet.category"
|
||||||
|
v-bind:class="{active: activeCategories.includes(facet.category)}"
|
||||||
|
class="category facet" @click="toggleCategory(facet)">
|
||||||
|
<div class="category__facet">{{facet.category}}</div>
|
||||||
|
|
||||||
|
<div v-if="facet.count < 10000" class="category__count">{{facet.count}}</div>
|
||||||
|
<div v-else class="category__count">{{(facet.count / 1000).toFixed()}}K</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body__results">
|
||||||
|
<div v-for="result in results" v-bind:key="result.id" class="result">
|
||||||
|
<a :href="result.url" target="_blank">
|
||||||
|
<div class="result__title">{{result.title}}</div>
|
||||||
|
<div class="result__link">
|
||||||
|
{{result.url}} • {{result.category}} • {{result.score.toFixed(3)}}
|
||||||
|
</div>
|
||||||
|
<div class="result__body">
|
||||||
|
<span class="result__date">{{result.date}} -</span>
|
||||||
|
{{result.body}}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from "vue";
|
||||||
|
import {Component, Prop, Watch} from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class ResultBody extends Vue {
|
||||||
|
@Prop() query;
|
||||||
|
|
||||||
|
activeCategories = [];
|
||||||
|
hits = 0;
|
||||||
|
results = [];
|
||||||
|
responseTime = 0;
|
||||||
|
facets = {};
|
||||||
|
|
||||||
|
@Watch("query")
|
||||||
|
async onQueryChanged(value) {
|
||||||
|
await this.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleCategory(category) {
|
||||||
|
const name = category.category;
|
||||||
|
|
||||||
|
const index = this.activeCategories.indexOf(name);
|
||||||
|
if (index === -1) {
|
||||||
|
this.activeCategories.push(name);
|
||||||
|
} else {
|
||||||
|
this.activeCategories.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
async search() {
|
||||||
|
const query = encodeURIComponent(this.query);
|
||||||
|
let queryString = `?q=${query}`;
|
||||||
|
|
||||||
|
if (this.activeCategories.length > 0) {
|
||||||
|
const categories = encodeURIComponent(this.activeCategories.join(","));
|
||||||
|
queryString += `&categories=${categories}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.history.push(`/search${queryString}`);
|
||||||
|
const url = `/api/${queryString}`;
|
||||||
|
|
||||||
|
let response = await fetch(url);
|
||||||
|
let json = await response.json();
|
||||||
|
|
||||||
|
this.results = json.results;
|
||||||
|
this.hits = json.hits;
|
||||||
|
this.responseTime = json.took;
|
||||||
|
this.facets = json.facets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.body {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body__inner {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-count {
|
||||||
|
line-height: 43px;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
margin-left: 208px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
margin: 24px 0;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result:first-child {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result__title {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 1px 0;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result__link {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #006621;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result__body {
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
color: rgba(0, 0, 0, .68);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result__date {
|
||||||
|
color: rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body__facets {
|
||||||
|
width: 184px;
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facet {
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facet:hover {
|
||||||
|
background-color: rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.facet.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category__facet {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category__count {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,202 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="topbar">
|
<top-bar v-model="query" />
|
||||||
<div class="logo">
|
<result-body :query="query" />
|
||||||
<img class="logo__image" src="@/assets/logo.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="query">
|
|
||||||
<input class="query__input" v-model="query" @change="getResults">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body">
|
|
||||||
<div class="result-count">
|
|
||||||
Ongeveer {{hits}} resultaten ({{responseTime.toFixed(2)}} seconden)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body__inner">
|
|
||||||
<div class="body__facets">
|
|
||||||
<div v-for="facet in facets.categories" v-bind:key="facet.category"
|
|
||||||
v-bind:class="{active: activeCategories.includes(facet.category)}"
|
|
||||||
class="category facet" @click="toggleCategory(facet)">
|
|
||||||
<div class="category__facet">{{facet.category}}</div>
|
|
||||||
|
|
||||||
<div v-if="facet.count < 10000" class="category__count">{{facet.count}}</div>
|
|
||||||
<div v-else class="category__count">{{(facet.count / 1000).toFixed()}}K</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body__results">
|
|
||||||
<div v-for="result in results" v-bind:key="result.id" class="result">
|
|
||||||
<a :href="result.url" target="_blank">
|
|
||||||
<div class="result__title">{{result.title}}</div>
|
|
||||||
<div class="result__link">{{result.url}}</div>
|
|
||||||
<div class="result__body">
|
|
||||||
<span class="result__date">{{result.date}} -</span>
|
|
||||||
{{result.body}}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
background-color: rgb(250, 250, 250);
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo__image {
|
|
||||||
width: 180px;
|
|
||||||
margin: 20px 16px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query__input {
|
|
||||||
width: 550px;
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body__inner {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-count {
|
|
||||||
line-height: 43px;
|
|
||||||
color: rgba(0, 0, 0, 0.5);
|
|
||||||
margin-left: 208px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
margin: 24px 0;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result:first-child {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result__title {
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 1px 0;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result__link {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #006621;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result__body {
|
|
||||||
margin: 4px 0;
|
|
||||||
|
|
||||||
color: rgba(0, 0, 0, .68);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result__date {
|
|
||||||
color: rgba(0, 0, 0, .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.body__facets {
|
|
||||||
width: 184px;
|
|
||||||
margin-right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facet {
|
|
||||||
padding: 4px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facet:hover {
|
|
||||||
background-color: rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.facet.active {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category__facet {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category__count {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import {Component} from "vue-property-decorator";
|
import {Component} from "vue-property-decorator";
|
||||||
|
|
||||||
@Component
|
import ResultBody from "@/components/ResultBody";
|
||||||
|
import TopBar from "@/components/TopBar";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
"components": {ResultBody, TopBar},
|
||||||
|
})
|
||||||
export default class Results extends Vue {
|
export default class Results extends Vue {
|
||||||
query = "";
|
query = "";
|
||||||
|
|
||||||
hits = 0;
|
|
||||||
results = [];
|
|
||||||
facets = {};
|
|
||||||
|
|
||||||
activeCategories = [];
|
|
||||||
|
|
||||||
responseTime = 0.0;
|
|
||||||
|
|
||||||
async toggleCategory(category) {
|
|
||||||
const name = category.category;
|
|
||||||
|
|
||||||
const index = this.activeCategories.indexOf(name);
|
|
||||||
if (index === -1) {
|
|
||||||
this.activeCategories.push(name);
|
|
||||||
} else {
|
|
||||||
this.activeCategories.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.getResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResults() {
|
|
||||||
let url = `/api/?q=${this.query}`;
|
|
||||||
|
|
||||||
if (this.activeCategories.length > 0) {
|
|
||||||
const categories = encodeURIComponent(this.activeCategories.join(","));
|
|
||||||
url += `&categories=${categories}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = await fetch(url);
|
|
||||||
let json = await response.json();
|
|
||||||
|
|
||||||
this.results = json.results;
|
|
||||||
this.hits = json.hits;
|
|
||||||
this.responseTime = json.took;
|
|
||||||
this.facets = json.facets;
|
|
||||||
}
|
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getResults();
|
this.query = this.$route.query.q;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
44
frontend/src/components/TopBar.vue
Normal file
44
frontend/src/components/TopBar.vue
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<div class="topbar">
|
||||||
|
<router-link class="logo" to="/">
|
||||||
|
<img class="logo__image" src="@/assets/logo.png">
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="query">
|
||||||
|
<input class="query__input" @keyup.enter="search" :value="value">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.topbar {
|
||||||
|
background-color: rgb(250, 250, 250);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo__image {
|
||||||
|
width: 180px;
|
||||||
|
margin: 20px 16px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query__input {
|
||||||
|
width: 550px;
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from "vue";
|
||||||
|
import {Component, Prop} from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class TopBar extends Vue {
|
||||||
|
@Prop() value = "";
|
||||||
|
|
||||||
|
search(event) {
|
||||||
|
this.$emit("input", event.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -7,6 +7,7 @@ import Results from "@/components/Results";
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export default new Router({
|
export default new Router({
|
||||||
|
mode: "history",
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/search",
|
path: "/search",
|
||||||
|
|
Loading…
Reference in a new issue