Skip to content
Snippets Groups Projects
Commit 7a0254e5 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Updated search a lot, fixed many things

parent 920914fc
Branches
Tags
No related merge requests found
...@@ -37,19 +37,40 @@ class Backend { ...@@ -37,19 +37,40 @@ class Backend {
return true; return true;
} }
public function search($arg_query, $arg_limit, $arg_offset) { public function find_buffers($arg_query, $arg_limit, $arg_offset) {
$sql = "SELECT backlog.messageid, buffer.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, ts_headline(backlog.message, query) AS message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid, to_tsquery('simple', ?) query WHERE type = 1 AND buffer.userid = ? AND to_tsvector('simple', message) @@ query ORDER BY messageid DESC LIMIT ? OFFSET ?;"; $sql = "SELECT DISTINCT backlog.bufferid FROM backlog JOIN buffer ON backlog.bufferid = buffer.bufferid, to_tsquery('simple', ?) query WHERE type = 1 AND buffer.userid = ? AND to_tsvector('simple', message) @@ query LIMIT ? OFFSET ?;";
$stmt = $this->dbh->prepare($sql); $stmt = $this->dbh->prepare($sql);
$limit = max(min($arg_limit, 50), 10); $limit = max(min($arg_limit, 20), 5);
$offset = max(0, $arg_offset); $offset = max(0, $arg_offset);
$stmt->execute(array($arg_query, $this->user['id'], $limit, $offset)); $stmt->execute(array($arg_query, $this->user['id'], $limit, $offset));
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
public function search($arg_query, $arg_limit, $arg_offset, $arg_limit_each) {
$buffers = $this->find_buffers($arg_query, $arg_limit, $arg_offset);
$data = array();
foreach ($buffers as $buffer) {
$data = array_merge($data, $this->search_buffer($arg_query, $buffer['bufferid'], 4, 0));
}
return $data;
}
public function search_buffer($arg_query, $arg_buffer, $arg_limit, $arg_offset) {
$sql = "SELECT backlog.messageid, backlog.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, ts_headline(backlog.message, query) AS message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid, to_tsquery('simple', ?) query WHERE type = 1 AND buffer.userid = ? AND backlog.bufferid = ? AND to_tsvector('simple', message) @@ query ORDER BY messageid DESC LIMIT ? OFFSET ?;";
$stmt = $this->dbh->prepare($sql);
$limit = max(min($arg_limit, 50), 1);
$offset = max(0, $arg_offset);
$stmt->execute(array($arg_query, $this->user['id'], $arg_buffer, $limit, $offset));
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function after($arg_id, $arg_buffer, $arg_limit) { public function after($arg_id, $arg_buffer, $arg_limit) {
$sql = "SELECT backlog.messageid, buffer.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, backlog.message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid WHERE buffer.userid = ? AND buffer.bufferid = ? AND messageid >= ? ORDER BY messageid ASC LIMIT ?;"; $sql = "SELECT backlog.messageid, backlog.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, backlog.message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid WHERE buffer.userid = ? AND backlog.bufferid = ? AND messageid >= ? ORDER BY messageid ASC LIMIT ?;";
$stmt = $this->dbh->prepare($sql); $stmt = $this->dbh->prepare($sql);
$limit = max(min($arg_limit+1, 50), 1); $limit = max(min($arg_limit+1, 50), 1);
...@@ -59,7 +80,7 @@ class Backend { ...@@ -59,7 +80,7 @@ class Backend {
} }
public function before($arg_id, $arg_buffer, $arg_limit) { public function before($arg_id, $arg_buffer, $arg_limit) {
$sql = "SELECT backlog.messageid, buffer.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, backlog.message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid WHERE buffer.userid = ? AND buffer.bufferid = ? AND messageid < ? ORDER BY messageid DESC LIMIT ?;"; $sql = "SELECT backlog.messageid, backlog.bufferid, buffer.buffername, sender.sender, backlog.\"time\", network.networkname, backlog.message FROM backlog JOIN sender ON backlog.senderid = sender.senderid JOIN buffer ON backlog.bufferid = buffer.bufferid JOIN network ON buffer.networkid = network.networkid WHERE buffer.userid = ? AND backlog.bufferid = ? AND messageid < ? ORDER BY messageid DESC LIMIT ?;";
$stmt = $this->dbh->prepare($sql); $stmt = $this->dbh->prepare($sql);
$limit = max(min($arg_limit, 50), 0); $limit = max(min($arg_limit, 50), 0);
......
<!DOCTYPE html> <!DOCTYPE html>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Quassel Search</title> <title>Quassel Search</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<nav> <nav>
<div id="searchbar"> <div id="searchbar">
<input type="text" name="q" id="q"> <input type="text" name="q" id="q" placeholder="Search">
<div id="searchicon"><span>Search</span></div> <div id="searchicon" class="icon">search</div>
</div> </div>
</nav> </nav>
...@@ -15,73 +18,4 @@ ...@@ -15,73 +18,4 @@
</section> </section>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script> <script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script> <script src="script.js"></script>
var userdata = { "username": localStorage.getItem("username"), "password": localStorage.getItem("password") }; \ No newline at end of file
var result = undefined;
var renderBuffer = function (buffer) {
return (
"<buffer>" +
"<h2>" + buffer.network + "" + buffer.name + "</h2>" +
"<article>" +
buffer.messages.map(renderMessage).reduce((x, y) => x + y) +
"</article>" +
"</buffer>"
)
}
var renderMessage = function (message) {
return (
"<message data-id='" + message.messageid + "'>" +
"<time>" + message.time.toLocaleString() + "</time>" +
"<sender>" + message.sender.split("!")[0] + "</sender>" +
"<content>" + message.message + "</content>" +
"</message>"
)
}
var render = function (data) {
return $.map(data, renderBuffer).reduce((x, y) => x + y);
}
var display = function (data) {
$("#results").children().remove();
$("#results").append(data);
}
var sortData = function (data) {
var buffers = {};
data.forEach(message => {
if (!buffers.hasOwnProperty(message.bufferid)) {
buffers[message.bufferid] = {
id: message.bufferid,
name: message.buffername,
network: message.networkname,
messages: []
}
}
message.time = new Date(message.time.replace(" ", "T") + "Z");
buffers[message.bufferid].messages.push(message);
});
return buffers;
}
var show = function (data) {
result = data;
display(render(sortData(data)));
}
var load = function() {
$("#results").children().remove()
$.post("search.php?query="+$("#q").val(), userdata, show, "json");
}
$("#q").keypress(function (e) {
var key = e.which || e.keyCode;
if (key === 13) {
load();
}
})
$("#login").click(function() {
localStorage.setItem("username", prompt("username"));
localStorage.setItem("password", prompt("password"));
})
$("#logout").click(function() {
localStorage.removeItem("username");
localStorage.removeItem("password");
})
</script>
\ No newline at end of file
script.js 0 → 100644
var userdata = { "username": localStorage.getItem("username"), "password": localStorage.getItem("password") };
var state = { "query": "", "offset": 0};
var buffers = {};
var sortData = function (data) {
var bufs = [];
data.forEach(message => {
if (!buffers.hasOwnProperty(message.bufferid)) {
buffers[message.bufferid] = {
id: message.bufferid,
name: message.buffername,
network: message.networkname,
selected: false,
contexts: [],
offset: 0
}
}
if (bufs.indexOf(message.bufferid) === -1) {
bufs.push(message.bufferid);
}
buffers[message.bufferid].contexts.push({"selected": false, "original": message, "before": [], "after": [], "buffer": message.bufferid, "id": buffers[message.bufferid].contexts.length});
});
return bufs;
}
$("#q").keypress(function (e) {
var key = e.which || e.keyCode;
if (key === 13) {
search();
}
})
$("#login").click(function() {
localStorage.setItem("username", prompt("username"));
localStorage.setItem("password", prompt("password"));
})
$("#logout").click(function() {
localStorage.removeItem("username");
localStorage.removeItem("password");
})
var sendercolor = function (nick) {
return "";
}
var render_buffer_full = function (buffer) {
return (
"<buffer id='buffer"+buffer.id+"' data-bufferid='"+buffer.id+"' class='selected'>" +
"<h2>" + buffer.network + "" + buffer.name + "</h2>" +
"<article>" +
buffer.contexts.map(render_context).reduce((x, y) => x + y, "") +
"<inline-button class='load_more'>Load More</inline-button>" +
"</article>" +
"</buffer>"
)
}
var render_buffer_overview = function (buffer) {
return (
"<buffer id='buffer"+buffer.id+"' data-bufferid='"+buffer.id+"'>" +
"<h2>" + buffer.network + "" + buffer.name + "</h2>" +
"<article>" +
buffer.contexts.slice(0, 4).map(render_context).reduce((x, y) => x + y, "") +
"<inline-button class='load_more'>Load More</inline-button>" +
"</article>" +
"</buffer>"
)
}
var render_buffer = function (buffer) {
return (buffer.selected) ? render_buffer_full(buffer) : render_buffer_overview(buffer);
}
var attach_buffer = function (elem) {
elem.unbind();
var id = elem.data("bufferid");
elem.click(make_toggle_buffer(id));
elem.find(".load_more").click((e) => {
if (buffers[id].selected || buffers[id].contexts.length <= 4)
more_buffer(id);
select_buffer(id);
e.stopPropagation();
});
buffers[id].contexts.forEach(context => {
attach_context(elem.find("#context"+context.id));
})
}
var render_context_overview = function (context) {
return (
"<context id='context"+context.id+"' data-contextid='"+context.id+"' data-bufferid='"+context.buffer+"'>" +
render_message(context.original, true) +
"</context>"
)
}
var render_context_full = function (context) {
return (
"<context id='context"+context.id+"' data-contextid='"+context.id+"' data-bufferid='"+context.buffer+"' class='selected'>" +
"<inline-button class='load_before'>Load Earlier</inline-button>" +
context.before.map(render_message).reduce((x, y) => x + y, "") +
render_message(context.original, true) +
context.after.map(render_message).reduce((x, y) => x + y, "") +
"<inline-button class='load_after'>Load Later</inline-button>" +
"</context>"
)
}
var render_context = function (context) {
return context.selected ? render_context_full(context) : render_context_overview(context);
}
var attach_context = function (elem) {
elem.unbind();
var id = elem.data("contextid");
var bufferid = elem.data("bufferid");
elem.click(make_toggle_context(bufferid, id));
elem.find(".load_before").click((e) => {
earlier(bufferid, id, 5);
e.stopPropagation();
});
elem.find(".load_after").click((e) => {
later(bufferid, id, 5);
e.stopPropagation();
});
}
var render_message = function (message, highlight) {
return (
"<message id='message" + message.messageid + "' data-messageid='"+message.messageid+"' "+(highlight===true ? "" : "class='faded'")+">" +
"<time>" + new Date(message.time.replace(" ", "T") + "Z").toLocaleString() + "</time>" +
"<sender style='color: " + sendercolor(message.sender.split("!")[0]) + "'>" + message.sender.split("!")[0] + "</sender>" +
"<content>" + message.message + "</content>" +
"</message>"
)
}
var load_search_overview = function (query, offset, limit, callback) {
$.post("search.php?" + $.param({"query": query, "offset": offset, "limit": limit}), userdata, callback, "json");
}
var load_search_buffer = function (query, buffer, offset, limit, callback) {
$.post("search_buffer.php?" + $.param({"query": query, "buffer": buffer, "offset": offset, "limit": limit}), userdata, callback, "json");
}
var load_context = function (msg, buffer, before, after, callback) {
$.post("context.php?" + $.param({"msg": msg, "buffer": buffer, "before": before, "after": after}), userdata, callback, "json");
}
var show_overview = function (ids) {
ids.forEach(id => {
if ($("#buffer"+id).length)
$("#buffer"+id).replaceWith(render_buffer(buffers[id]))
else
$("#load_more").before(render_buffer(buffers[id]))
attach_buffer($("#buffer"+id));
});
}
var process_data = function (callback, limit) {
return function(data) {
var changed_ids = sortData(data);
state.offset += changed_ids.length;
if (changed_ids.length < limit) show_no_more_msg();
callback(changed_ids);
}
}
var search = function () {
$("#results").children().remove();
buffers = {};
$("#results").append("<inline-button id='load_more'>Load More</inline-button>");
$("#load_more").click(more);
$("#results").click(deselect_buffers);
state = {
"query": $("#q").val(),
"offset": 0
};
more();
}
var more = function () {
var limit = 4;
load_search_overview(state.query, state.offset, limit, process_data(show_overview, limit));
}
var more_buffer = function (id) {
var limit = 10;
load_search_buffer(state.query, id, buffers[id].contexts.length, limit, (data) => {
buffers[id].contexts = buffers[id].contexts.concat(data.map(message => ({"selected": false, "original": message, "before": [], "after": [], "buffer": id, "id": buffers[id].contexts.length})));
show_overview([id])
});
}
var deselect_buffers = function (except) {
$.each(buffers, (key, buffer) => {
if (key !== except && buffers[key].selected) {
buffers[key].selected = false;
unselect_contexts(key);
update_buffer(key);
}
})
}
var unselect_contexts = function (bufferid) {
buffers[bufferid].contexts = buffers[bufferid].contexts.map(context => {
context.selected = false;
return context
})
}
var make_toggle_buffer = function (id) {
return function (e) {
if (buffers[id].selected) {
deselect_buffers();
buffers[id].selected = false;
} else {
deselect_buffers();
buffers[id].selected = true;
}
update_buffer(id);
e.stopPropagation();
}
}
var select_buffer = function (id) {
deselect_buffers(id);
buffers[id].selected = true;
update_buffer(id);
}
var update_buffer = function (id) {
$("#buffer"+id).replaceWith(render_buffer(buffers[id]));
attach_buffer($("#buffer"+id));
}
var make_toggle_context = function (buffer, id) {
return function (e) {
var context = buffers[buffer].contexts[id];
if (context.selected) {
unselect_contexts(buffer);
context.selected = false;
} else {
unselect_contexts(buffer);
buffers[buffer].selected = true;
context.selected = true;
if (context.before.length === 0) earlier(buffer, id, 5);
if (context.after.length === 0) later(buffer, id, 5);
}
update_buffer(buffer);
e.stopPropagation();
}
}
var show_no_more_msg = function () {
$("#load_more").before("<div id='no_more'><img src='https://raw.githubusercontent.com/xiprox/ErrorView/master/library/src/main/res/drawable-xxhdpi/error_view_cloud.png'><h2>No more results</h2></div>");
$("#load_more").remove();
}
var earlier = function (bufferid, contextid, amount) {
var buffer = buffers[bufferid];
var context = buffer.contexts[contextid];
var earliest = (context.before[0] || context.original).messageid;
load_context(earliest, bufferid, amount, 0, (messages) => {
var newmsgs = messages.slice(0, messages.length - 1)
context.before = sort_messages(newmsgs.concat(context.before));
update_buffer(bufferid);
})
}
var later = function (bufferid, contextid, amount) {
var buffer = buffers[bufferid];
var context = buffer.contexts[contextid];
var latest = (context.after[context.after.length - 1] || context.original).messageid;
load_context(latest, bufferid, 0, amount, (messages) => {
var newmsgs = messages.slice(1)
context.after = sort_messages(context.after.concat(newmsgs));
update_buffer(bufferid);
})
}
var sort_messages = function (arr) {
return arr.sort((x, y) => x.messageid - y.messageid).filter((item, pos, ary) => {
return !pos || item.messageid != ary[pos - 1].messageid;
})
}
\ No newline at end of file
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
} }
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode($backend->search($_GET['query'],$_GET['limit'],$_GET['offset']))."\n"; echo json_encode($backend->search($_GET['query'],$_GET['limit'],$_GET['offset'], 5))."\n";
?> ?>
\ No newline at end of file
<?php
require_once('backend.php');
$backend = new Backend();
$backend->connect('/var/www/config.ini');
if (!$backend->auth($_POST['username'], $_POST['password'])) {
header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden');
header('Status: 403 Forbidden');
exit;
}
header('Content-Type: application/json');
echo json_encode($backend->search_buffer($_GET['query'],$_GET['buffer'],$_GET['limit'],$_GET['offset']))."\n";
?>
\ No newline at end of file
...@@ -12,14 +12,26 @@ body { ...@@ -12,14 +12,26 @@ body {
section { section {
max-width: 1200px; max-width: 1200px;
padding: 56px 4rem 0 4rem; padding: 56px 4rem 8rem 4rem;
margin: 0 auto; margin: 0 auto;
} }
buffer {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
buffer.selected {
background-color: #ddd;
border: rgba(0,0,0,.4);
padding: 20px 16px;
margin-left: -16px;
margin-right: -16px;
}
buffer > article { buffer > article {
background: #fff;
font-size: 13px; font-size: 13px;
box-shadow: 0 -1px 0 #e5e5e5,0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24)
} }
buffer > h2 { buffer > h2 {
...@@ -39,6 +51,12 @@ message { ...@@ -39,6 +51,12 @@ message {
line-height: 48px; line-height: 48px;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
color: #212121; color: #212121;
background: #fff;
position: relative;
}
message.faded * {
opacity: .6
} }
message sender { message sender {
...@@ -74,6 +92,7 @@ nav { ...@@ -74,6 +92,7 @@ nav {
height: 56px; height: 56px;
background: #4285f4; background: #4285f4;
padding: 0px 4rem; padding: 0px 4rem;
z-index: 1;
} }
nav input[type=text]::-moz-placeholder { nav input[type=text]::-moz-placeholder {
...@@ -113,19 +132,73 @@ nav input[type=text] { ...@@ -113,19 +132,73 @@ nav input[type=text] {
} }
#searchicon { #searchicon {
background-image: url(https://ssl.gstatic.com/bt/C3341AA7A1A076756462EE2E5CD71C11/1x/ic_search_wht_24dp_r1.png);
display: inline-block; display: inline-block;
width: 72px; width: 72px;
height: 36px; height: 36px;
position: absolute; position: absolute;
text-align: center;
line-height: 36px;
color: #ffffff;
left: 0; left: 0;
top: 0; top: 0;
background-repeat: no-repeat;
background-position: center center;
} }
#searchicon span { #no_more img {
display: none; margin: 8rem auto 0px;
display: block;
}
#no_more h2 {
display: block;
text-align: center;
margin: 1rem;
color: rgb(113, 113, 113);
font-weight: 300;
font-size: 20pt;
}
inline-button {
display: block;
background: rgb(255, 255, 255) none repeat scroll 0% 0%;
font-size: 16px;
height: 48px;
line-height: 48px;
color: rgb(113, 113, 113);
text-align: center;
font-style: italic;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
position: relative;
}
context {
display: block;
}
context.selected {
padding: 50px 0;
margin: 0 -20px;
}
message:before, inline-button:before {
bottom: 0;
box-shadow: 0 -1px 0 #e5e5e5,0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
content: '';
display: block;
left: 0;
pointer-events: none;
position: absolute;
right: 0;
top: 0;
}
section > inline-button {
margin-top: 20px;
}
article > inline-button {
background: #f5f5f5;
} }
nav input[type=text]:hover { nav input[type=text]:hover {
...@@ -139,5 +212,32 @@ nav input[type=text]:focus { ...@@ -139,5 +212,32 @@ nav input[type=text]:focus {
} }
nav input[type=text]:focus+#searchicon { nav input[type=text]:focus+#searchicon {
background-image: url(https://ssl.gstatic.com/bt/C3341AA7A1A076756462EE2E5CD71C11/1x/ic_search_blk_24dp_r1.png); color: #212121;
}
.icon {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
display: inline-block;
width: 1em;
height: 1em;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
} }
\ No newline at end of file
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment