summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bucky/app/router.js8
-rw-r--r--bucky/search/bdb.js69
-rw-r--r--bucky/search/middleware.js51
-rw-r--r--bucky/search/search.js20
-rw-r--r--bucky/search/snippet.js47
-rw-r--r--public/assets/css/bucky.css202
-rw-r--r--public/assets/js/lib/router.js6
-rw-r--r--public/assets/js/lib/views/details/files.js2
-rw-r--r--public/assets/js/lib/views/details/index.js24
-rw-r--r--public/assets/js/lib/views/index/index.js2
-rw-r--r--public/assets/js/lib/views/index/lastlog.js3
-rw-r--r--public/assets/js/lib/views/index/threadbox.js38
-rw-r--r--public/assets/js/lib/views/search/results.js71
-rw-r--r--public/assets/js/util/format.js6
-rw-r--r--views/pages/details.ejs12
-rw-r--r--views/pages/index.ejs32
-rw-r--r--views/pages/mailbox.ejs2
-rw-r--r--views/pages/message.ejs2
-rw-r--r--views/pages/search.ejs38
-rw-r--r--views/partials/header.ejs23
-rw-r--r--views/partials/metadata.ejs8
-rw-r--r--views/partials/scripts.ejs2
-rw-r--r--views/partials/threads.ejs11
23 files changed, 477 insertions, 202 deletions
diff --git a/bucky/app/router.js b/bucky/app/router.js
index 8104bd5..2fd8d83 100644
--- a/bucky/app/router.js
+++ b/bucky/app/router.js
@@ -91,7 +91,13 @@ module.exports = function(app){
function(req, res){
// delete a comment
})
-
+
+ app.get("/search/",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ res.render("pages/search", {title: "search" })
+ }
+ )
app.get("/api/search",
middleware.ensureAuthenticated,
search.search,
diff --git a/bucky/search/bdb.js b/bucky/search/bdb.js
index ba0124d..a7ced4a 100644
--- a/bucky/search/bdb.js
+++ b/bucky/search/bdb.js
@@ -1,38 +1,55 @@
var bdb_lib = require('berkeleydb')
var dbenv = new bdb_lib.DbEnv();
var bdb_status = dbenv.open('./search/db/env')
-console.log('open /search/db:', bdb_status)
-
-var db
-
-function exitHandler(options, err) {
- db.close()
- // if (options.cleanup) console.log('clean');
- if (err) console.log(err.stack);
- if (options.exit) process.exit();
+if (bdb_status) {
+ console.log('open dbenv failed:', bdb_status)
+ process.exit()
}
-// do something when app is closing
-process.on('exit', exitHandler.bind(null, {cleanup: true}));
+function db(fn){
+ var db
+ fn = "./" + fn + ".db"
-// catches ctrl+c event
-process.on('SIGINT', exitHandler.bind(null, {exit: true}));
+ function exitHandler(options, err) {
+ db.close()
+ // if (options.cleanup) console.log('clean');
+ if (err) console.log(err.stack);
+ if (options.exit) process.exit();
+ }
-// catches "kill pid" (for example: nodemon restart)
-process.on('SIGUSR1', exitHandler.bind(null, {exit: true}));
-process.on('SIGUSR2', exitHandler.bind(null, {exit: true}));
+ // do something when app is closing
+ process.on('exit', exitHandler.bind(null, {cleanup: true}));
-//catches uncaught exceptions
-process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
+ // catches ctrl+c event
+ process.on('SIGINT', exitHandler.bind(null, {exit: true}));
-function open(){
- if (db) db.close()
- var _db = new bdb_lib.Db(dbenv);
- var bdb_status = _db.open('./search.db')
- console.log('open ./search.db:', bdb_status)
- db = _db
-}
+ // catches "kill pid" (for example: nodemon restart)
+ process.on('SIGUSR1', exitHandler.bind(null, {exit: true}));
+ process.on('SIGUSR2', exitHandler.bind(null, {exit: true}));
-open()
+ //catches uncaught exceptions
+ process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
+ function open(fn){
+ if (db) db.close()
+ var _db = new bdb_lib.Db(dbenv);
+ var bdb_status = _db.open(fn)
+ if (bdb_status) {
+ console.log('open ' + fn + ' failed:', bdb_status)
+ process.exit()
+ }
+ db = _db
+ }
+
+ open(fn)
+
+ return {
+ put: function(term, serialized){
+ db.put(term, serialized)
+ },
+ get: function(term){
+ return db.get(term)
+ },
+ }
+}
module.exports = db
diff --git a/bucky/search/middleware.js b/bucky/search/middleware.js
index 39d7a71..32e3321 100644
--- a/bucky/search/middleware.js
+++ b/bucky/search/middleware.js
@@ -6,16 +6,26 @@ module.exports = {
search: function (req, res, next) {
res.search = search.search(req.query.query, req.query.start, req.query.limit)
- console.log(res.search)
+ if (! res.search) {
+ res.sendStatus(400)
+ return
+ }
next()
},
getThreads: function (req, res, next){
var thread_ids = res.search.thread_ids;
if (! thread_ids || ! thread_ids.length) {
+ res.search.threads = []
return next()
}
db.getThreadsById(thread_ids).then(function(threads){
+ threads.forEach((thread) => {
+ var flag_id = thread.get('flagged')
+ if (flag_id) {
+ res.search.file_ids.push(flag_id)
+ }
+ })
res.search.threads = threads
next()
})
@@ -24,11 +34,14 @@ module.exports = {
getComments: function (req, res, next){
var comment_ids = res.search.comment_ids;
if (! comment_ids || ! comment_ids.length) {
+ res.search.comments = []
return next()
}
db.getCommentsById(comment_ids).then(function(comments){
+ var terms = res.search.meta.terms
comments.forEach(function(comment){
- comment.set('comment', comment.get('comment').toString())
+ const snip = snippet(comment.get('comment').toString(), terms)
+ comment.set('comment', snip)
})
res.search.comments = comments
next()
@@ -38,6 +51,7 @@ module.exports = {
getFiles: function (req, res, next){
var file_ids = res.search.file_ids
if (! file_ids || ! file_ids.length) {
+ res.search.files = []
return next()
}
db.getFilesById(file_ids).then(function(files){
@@ -52,7 +66,38 @@ module.exports = {
},
success: function(req, res, next){
- res.send(res.search)
+ var terms = res.search.meta.terms
+ var threads = {}, comments = {}, files = {}
+ res.search.threads.forEach((t) => { threads[t.id] = t })
+ res.search.comments.forEach((t) => { comments[t.id] = t })
+ res.search.files.forEach((t) => { files[t.id] = t })
+ var results = res.search.results.map((r) => {
+ var m = {}
+ m.thread = threads[r.thread]
+ m.comment = comments[r.comment]
+ m.file = files[r.file]
+ m.count = r.count
+ m.strength = r.strength
+ if (m.thread) {
+ var flagged = m.thread.get('flagged')
+ if (flagged) {
+ m.thread.set('flagged', files[flagged])
+ }
+ var allowed = m.thread.get('allowed')
+ if (allowed) {
+ m.thread.set('allowed', allowed.toString().split(" "))
+ }
+ var display = m.thread.get('display')
+ if (display) {
+ m.thread.set('display', display.toString().split(" "))
+ }
+ }
+ return m
+ })
+ res.send({
+ meta: res.search.meta,
+ results: results,
+ })
},
}
diff --git a/bucky/search/search.js b/bucky/search/search.js
index a28d49c..4818ef9 100644
--- a/bucky/search/search.js
+++ b/bucky/search/search.js
@@ -1,5 +1,5 @@
var db = require('../db')
-var bdb = require('./bdb')
+var bdb = require('./bdb')('search')
var STOPWORDS = require('./stopwords')
var wordRegexp = new RegExp("(\W+)");
@@ -28,7 +28,7 @@ function find_term(term) {
strength: parseInt(partz[3]) || 1,
}
})
- console.log(matches)
+ // console.log(matches)
return matches
}
@@ -69,7 +69,7 @@ function search (query, start, limit) {
if (i < start) return false
if (to_display-- === 0) return true
results.push(match)
- console.log(match)
+ // console.log(match)
thread_ids.push(match.thread)
if (match.comment) comment_ids.push(match.comment)
if (match.file) file_ids.push(match.file)
@@ -77,16 +77,18 @@ function search (query, start, limit) {
})
return {
- query: query,
- start: start,
- next: start + limit,
- limit: limit,
- total: total,
+ meta: {
+ query: query,
+ terms: terms,
+ start: start,
+ next: start + limit,
+ limit: limit,
+ total: total,
+ },
results: results,
thread_ids: thread_ids,
comment_ids: comment_ids,
file_ids: file_ids,
- terms: terms,
};
}
diff --git a/bucky/search/snippet.js b/bucky/search/snippet.js
index cd0657f..17988d2 100644
--- a/bucky/search/snippet.js
+++ b/bucky/search/snippet.js
@@ -1,19 +1,11 @@
var util = require('../util/util')
var STOPWORDS = require('./stopwords')
-function bold_snippet(s, terms) {
- return bold_terms(snippet(s, terms), terms)
-}
-function bold_terms (s, terms) {
- s = util.sanitize(s)
- terms.forEach( (term) => {
- s.replace(new RegExp("\b" + term + "\b", "i"), "<b>" + term + "</b>")
- })
-}
function snippet(s, terms) {
s = util.sanitize(s)
- var term_re = new RegExp("\b(" + terms.join("|") + ")\b", "i")
- var words = s.split(/\s+/)
+ var term_set = new Set(terms)
+
+ var words = s.split(/[^a-zA-Z0-9]+/)
var snippet = "";
// deduper for matching @words indexes, so we don't add a word twice
@@ -26,27 +18,30 @@ function snippet(s, terms) {
var aggr = 0;
// amount of context to show, in number of words surrounding a match
- var $pad = 4;
+ var pad = 10;
// loop over each of the words in the string
- words.some((word, i) => {
+ var word
+ for (var i = 0, len = words.length; i < len; i++) {
+ word = words[i]
+
// if the word matches...
- if (term_re.match(word) && ! STOPWORDS.has(word.toLowerCase())) {
+ if (term_set.has(word.toLowerCase()) && ! STOPWORDS.has(word.toLowerCase())) {
// if we aren't already aggregating, add an ellipsis
- if (! $aggr) {
+ if (! aggr) {
words_matched.push("...")
}
-
+
// look backward $pad words
var idx;
- for (var j = -pad; j < 1; j++) {
+ INNER: for (var j = -pad; j < 1; j++) {
// create a new index from the offset
idx = i + j;
// is this a valid index? has it already been encountered?
- if (idx < 0) continue;
- if (idx > words.length) continue;
- if (index_matches[idx]) continue;
+ if (idx < 0) continue INNER;
+ if (idx > words.length) continue INNER;
+ if (index_matches[idx]) continue INNER;
// checks out, save this word
words_matched.push(words[idx])
@@ -69,20 +64,18 @@ function snippet(s, terms) {
// one less word to aggregate
aggr--;
}
-
+
// keep snippets to a modest length
- return words_matched.length > 30;
- })
+ if (words_matched.length > 30) break
+ }
// add a trailing ellipsis
words_matched.push("...")
// create the snippet from the saved context words
snippet = words_matched.join(" ")
-
+
return snippet
}
-module.exports = {
- bold_snippet, bold_terms, snippet,
-} \ No newline at end of file
+module.exports = snippet \ No newline at end of file
diff --git a/public/assets/css/bucky.css b/public/assets/css/bucky.css
index c791ca1..a8901c3 100644
--- a/public/assets/css/bucky.css
+++ b/public/assets/css/bucky.css
@@ -41,11 +41,16 @@ h1 {
font-weight: bold;
margin: 0;
}
+.subtitle {
+ display: none;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
.bluebox {
background-color: #d8e0ec;
color: #000000;
text-align: center;
- border: 2px solid #201010;
+ border: 1px solid #321;
padding: 3px;
margin: 0 0 5px 0;
}
@@ -56,6 +61,13 @@ h1 {
top: 1px;
margin-bottom: 5px;
}
+.bluebox big {
+ display: block;
+ text-align: center;
+ margin-bottom: 4px;
+ margin-left: 3px;
+ margin-top: 2px;
+}
a:link { color: #2050ca; text-decoration: underline; }
a:visited { color: #1030aa; text-decoration: none; }
a:active { color: #a0a0c7; text-decoration: underline; }
@@ -63,6 +75,10 @@ a:hover { color: #2040f0; text-decoration: underline; }
hr {
border-color: #000;
}
+#menu {
+ margin: 7px 0 14px;
+}
+
table, tr {
margin: 0; padding: 0;
border-spacing: 0;
@@ -119,10 +135,10 @@ table, tr {
#hoots td {
margin: 0;
padding: 0;
- border-top: 2px solid #000;
+ border-top: 1px solid #321;
}
#hoots td:nth-child(1){
- border-right: 2px solid #000;
+ border-right: 1px solid #321;
width: 40px;
height: 40px;
background-size: cover;
@@ -155,12 +171,12 @@ table, tr {
#threads .keyword:first-child td:nth-child(2) {
border-top: 0;
}
-#threads .keyword td:nth-child(2) {
- border-top: 1px solid #b3b3b3;
- border-bottom: 1px solid #b3b3b3;
+.row.first td:nth-child(2) {
+ border-top: 1px solid #876;
}
+.row.last td:nth-child(2),
#threads .row:last-child td:nth-child(2) {
- border-bottom: 1px solid #b3b3b3;
+ border-bottom: 1px solid #876;
}
.ledger {
@@ -180,21 +196,19 @@ table, tr {
text-align: left;
font-family: Georgia, serif;
font-size: 120%;
- padding: 2px 4px;
- border-left: 1px solid #b6aeab; border-right: 1px solid #b6aeab;
+ border-left: 1px solid #876;
+ border-right: 1px solid #876;
overflow: hidden;
text-overflow: ellipsis;
max-width: 30vw;
white-space: nowrap;
+ padding: 0;
}
-.ledger tr.row:first-child td:nth-child(2) {
- border-top: 1px solid #b6aeab;
-}
-.ledger tr.row:last-child td:nth-child(2) {
- border-bottom: 1px solid #b6aeab;
-}
-.ledger td:nth-child(2) a {
+.ledger .row td:nth-child(2) a {
display: block;
+ padding: 4px 5px;
+ text-decoration: none;
+ color: #444;
}
#threads td:nth-child(6) {
text-align: center;
@@ -211,59 +225,59 @@ tr:nth-child(even) td.row { background-color: #e0e8e8; }
tr:nth-child(even) td.row:hover { background-color: #d8e0ec; color: #000000; }
tr:nth-child(odd) td.plain{ background-color: #d0dBe8; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(odd) td.plain:hover { background-color: #d7e4f0; color: #000000; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(even) td.plain { background-color: #edf8f6; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(even) td.plain:hover { background-color: #f3fafa; color: #000000; border-bottom: 1px solid #ded2dd; }
+tr:nth-child(odd) td.plain:hover { background-color: #d7e4f0; color: #000000; border-bottom-color: #d3d3d0; }
+tr:nth-child(even) td.plain { background-color: #edf8f6; border-bottom-color: #d3d3d0; }
+tr:nth-child(even) td.plain:hover { background-color: #f3fafa; color: #000000; border-bottom-color: #ded2dd; }
-tr:nth-child(odd) td.ivory{ background-color: #e0e0d8; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(odd) td.ivory:hover { background-color: #f8f0e9; color: #000000; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(even) td.ivory { background-color: #f0f0ea; border-bottom: 1px solid #d3d3d0; }
-tr:nth-child(even) td.ivory:hover { background-color: #f8f0e9; color: #000000; border-bottom: 1px solid #d3d3d0; }
+tr:nth-child(odd) td.ivory{ background-color: #e0e0d8; border-bottom-color: #d3d3d0; }
+tr:nth-child(odd) td.ivory:hover { background-color: #f8f0e9; color: #000000; border-bottom-color: #d3d3d0; }
+tr:nth-child(even) td.ivory { background-color: #f0f0ea; border-bottom-color: #d3d3d0; }
+tr:nth-child(even) td.ivory:hover { background-color: #f8f0e9; color: #000000; border-bottom-color: #d3d3d0; }
-tr:nth-child(odd) td.tan { background-color: #e1e1bf; border-bottom: 1px solid #ced2cd; }
-tr:nth-child(odd) td.tan:hover { background-color: #f7f6ed; color: #000000; border-bottom: 1px solid #ced2cd; }
-tr:nth-child(even) td.tan { background-color: #f0eddf; border-bottom: 1px solid #ced2cd; }
-tr:nth-child(even) td.tan:hover { background-color: #f7f6ed; color: #000000; border-bottom: 1px solid #ced2cd; }
+tr:nth-child(odd) td.tan { background-color: #e1e1bf; border-bottom-color: #ced2cd; }
+tr:nth-child(odd) td.tan:hover { background-color: #f7f6ed; color: #000000; border-bottom-color: #ced2cd; }
+tr:nth-child(even) td.tan { background-color: #f0eddf; border-bottom-color: #ced2cd; }
+tr:nth-child(even) td.tan:hover { background-color: #f7f6ed; color: #000000; border-bottom-color: #ced2cd; }
-tr:nth-child(odd) td.red { background-color: #fcc7c2; border-bottom: 1px solid #E8B1AC; }
-tr:nth-child(odd) td.red:hover { background-color: #FFACB2; color: #000000; border-bottom: 1px solid #E8B1AC; }
-tr:nth-child(even) td.red { background-color: #fde1df; border-bottom: 1px solid #E8B1AC; }
-tr:nth-child(even) td.red:hover { background-color: #FFACB2; color: #000000; border-bottom: 1px solid #E8B1AC; }
+tr:nth-child(odd) td.red { background-color: #fcc7c2; border-bottom-color: #E8B1AC; }
+tr:nth-child(odd) td.red:hover { background-color: #FFACB2; color: #000000; border-bottom-color: #E8B1AC; }
+tr:nth-child(even) td.red { background-color: #fde1df; border-bottom-color: #E8B1AC; }
+tr:nth-child(even) td.red:hover { background-color: #FFACB2; color: #000000; border-bottom-color: #E8B1AC; }
-tr:nth-child(odd) td.orange { background-color: #F8E0C6; border-bottom: 1px solid #F5D1AA; }
-tr:nth-child(odd) td.orange:hover{ background-color: #FFD799; color: #000000; border-bottom: 1px solid #F5D1AA; }
-tr:nth-child(even) td.orange { background-color: #FBEEE1; border-bottom: 1px solid #F5D1AA; }
-tr:nth-child(even) td.orange:hover{ background-color: #FFD799; color: #000000; border-bottom: 1px solid #F5D1AA; }
+tr:nth-child(odd) td.orange { background-color: #F8E0C6; border-bottom-color: #F5D1AA; }
+tr:nth-child(odd) td.orange:hover{ background-color: #FFD799; color: #000000; border-bottom-color: #F5D1AA; }
+tr:nth-child(even) td.orange { background-color: #FBEEE1; border-bottom-color: #F5D1AA; }
+tr:nth-child(even) td.orange:hover{ background-color: #FFD799; color: #000000; border-bottom-color: #F5D1AA; }
-tr:nth-child(odd) td.yellow { background-color: #FAFDC2; border-bottom: 1px solid #CED3D2; }
-tr:nth-child(odd) td.yellow:hover{ background-color: #FFF7A8; color: #000000; border-bottom: 1px solid #CED3D2; }
-tr:nth-child(even) td.yellow { background-color: #FDFEDF; border-bottom: 1px solid #CED3D2; }
-tr:nth-child(even) td.yellow:hover{ background-color: #FFF7A8; color: #000000; border-bottom: 1px solid #CED3D2; }
+tr:nth-child(odd) td.yellow { background-color: #FAFDC2; border-bottom-color: #CED3D2; }
+tr:nth-child(odd) td.yellow:hover{ background-color: #FFF7A8; color: #000000; border-bottom-color: #CED3D2; }
+tr:nth-child(even) td.yellow { background-color: #FDFEDF; border-bottom-color: #CED3D2; }
+tr:nth-child(even) td.yellow:hover{ background-color: #FFF7A8; color: #000000; border-bottom-color: #CED3D2; }
-tr:nth-child(odd) td.green { background-color: #E6FFCC; border-bottom: 1px solid #AFD8AB; }
-tr:nth-child(odd) td.green:hover { background-color: #d8F4b4; color: #000000; border-bottom: 1px solid #AFD8AB; }
-tr:nth-child(even) td.green { background-color: #F0FFE2; border-bottom: 1px solid #AFD8AB; }
-tr:nth-child(even) td.green:hover { background-color: #d4eeb0; color: #000000; border-bottom: 1px solid #AFD8AB; }
+tr:nth-child(odd) td.green { background-color: #E6FFCC; border-bottom-color: #AFD8AB; }
+tr:nth-child(odd) td.green:hover { background-color: #d8F4b4; color: #000000; border-bottom-color: #AFD8AB; }
+tr:nth-child(even) td.green { background-color: #F0FFE2; border-bottom-color: #AFD8AB; }
+tr:nth-child(even) td.green:hover { background-color: #d4eeb0; color: #000000; border-bottom-color: #AFD8AB; }
-tr:nth-child(odd) td.blue { background-color: #ddE2FF; border-bottom: 1px solid #c9c9F5; }
-tr:nth-child(odd) td.blue:hover { background-color: #cac8fe; color: #000000; border-bottom: 1px solid #c9c9F5; }
-tr:nth-child(even) td.blue { background-color: #ebefff; border-bottom: 1px solid #c9c9F5; }
-tr:nth-child(even) td.blue:hover { background-color: #cac8fe; color: #000000; border-bottom: 1px solid #c9c9F5; }
+tr:nth-child(odd) td.blue { background-color: #ddE2FF; border-bottom-color: #c9c9F5; }
+tr:nth-child(odd) td.blue:hover { background-color: #cac8fe; color: #000000; border-bottom-color: #c9c9F5; }
+tr:nth-child(even) td.blue { background-color: #ebefff; border-bottom-color: #c9c9F5; }
+tr:nth-child(even) td.blue:hover { background-color: #cac8fe; color: #000000; border-bottom-color: #c9c9F5; }
-tr:nth-child(odd) td.purple { background-color: #E0CBF4; border-bottom: 1px solid #D6A9EA; }
-tr:nth-child(odd) td.purple:hover{ background-color: #DDB1FF; color: #000000; border-bottom: 1px solid #D6A9EA; }
-tr:nth-child(even) td.purple { background-color: #EFE4F9; border-bottom: 1px solid #D6A9EA; }
-tr:nth-child(even) td.purple:hover{ background-color: #DDB1FF; color: #000000; border-bottom: 1px solid #D6A9EA; }
+tr:nth-child(odd) td.purple { background-color: #E0CBF4; border-bottom-color: #D6A9EA; }
+tr:nth-child(odd) td.purple:hover{ background-color: #DDB1FF; color: #000000; border-bottom-color: #D6A9EA; }
+tr:nth-child(even) td.purple { background-color: #EFE4F9; border-bottom-color: #D6A9EA; }
+tr:nth-child(even) td.purple:hover{ background-color: #DDB1FF; color: #000000; border-bottom-color: #D6A9EA; }
-tr:nth-child(odd) td.pink { background-color: #F1CDE4; border-bottom: 1px solid #E8ABD2; }
-tr:nth-child(odd) td.pink:hover { background-color: #FFAECD; color: #000000; border-bottom: 1px solid #E8ABD2; }
-tr:nth-child(even) td.pink { background-color: #F8E5F1; border-bottom: 1px solid #E8ABD2; }
-tr:nth-child(even) td.pink:hover { background-color: #FFAECD; color: #000000; border-bottom: 1px solid #E8ABD2; }
+tr:nth-child(odd) td.pink { background-color: #F1CDE4; border-bottom-color: #E8ABD2; }
+tr:nth-child(odd) td.pink:hover { background-color: #FFAECD; color: #000000; border-bottom-color: #E8ABD2; }
+tr:nth-child(even) td.pink { background-color: #F8E5F1; border-bottom-color: #E8ABD2; }
+tr:nth-child(even) td.pink:hover { background-color: #FFAECD; color: #000000; border-bottom-color: #E8ABD2; }
-tr:nth-child(odd) td.black { background-color: #ccc; border-bottom: 1px solid #aaa; }
-tr:nth-child(odd) td.black:hover { background-color: #f8f8f8; color: #000000; border-bottom: 1px solid #aaa; }
-tr:nth-child(even) td.black { background-color: #eee; border-bottom: 1px solid #aaa; }
-tr:nth-child(even) td.black:hover { background-color: #f8f8f8; color: #000000; border-bottom: 1px solid #aaa; }
+tr:nth-child(odd) td.black { background-color: #ccc; border-bottom-color: #aaa; }
+tr:nth-child(odd) td.black:hover { background-color: #f8f8f8; color: #000000; border-bottom-color: #aaa; }
+tr:nth-child(even) td.black { background-color: #eee; border-bottom-color: #aaa; }
+tr:nth-child(even) td.black:hover { background-color: #f8f8f8; color: #000000; border-bottom-color: #aaa; }
.new { color: #000000; font-weight: bold; }
.recent { color: #001111; }
@@ -390,10 +404,73 @@ tr:nth-child(odd) td.comment { background-color: #fcf8f8; }
text-align: center;
padding: 0 0 12px 0;
}
+#gallery div a:first-child .thumb {
+ max-width: 450px;
+ max-height: 450px;
+}
#gallery .thumb {
max-width: 150px;
}
+#search {
+ background: #f8f8f8;
+ padding: 5px;
+ border: 1px solid #ddd;
+ color: #333;
+}
+#search b {
+ color: #000;
+}
+#search a b {
+ color: #000;
+}
+
+#search .preamble {
+ font-size: 14px;
+ margin: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #ddd;
+}
+#search .result {
+ display: flex;
+ flex-direction: row;
+ margin: 10px 0 20px;
+}
+#search .image {
+ flex: 0 0 100px;
+ width: 100px;
+ height: 50px;
+ background-size: cover;
+ display: block;
+}
+#search .desc {
+ display: block;
+ max-width: 500px;
+ padding-left: 10px;
+}
+#search .meta {
+ margin-top: 5px;
+ font-size: 10px;
+ color: #444;
+}
+#search .search_hit {
+ font-size: 15px;
+ display: block;
+}
+#search .snippet {
+ font-size: 13px;
+ margin: 10px;
+}
+#search .file a {
+ border: 1px solid #bbb;
+ background: #eee;
+ padding: 5px;
+ margin: 10px 0;
+}
+#search .next_page {
+ font-size: 14px;
+}
+
#messages {
width: 100%;
}
@@ -410,11 +487,8 @@ tr:nth-child(odd) td.comment { background-color: #fcf8f8; }
text-align: center;
padding-left: 3px;
}
-#messages tr:first-child td:nth-child(2) {
- border-top: 1px solid #ccc;
-}
-#messages tr:last-child td:nth-child(2) {
- border-bottom: 1px solid #ccc;
+#messages .row td:nth-child(2) {
+ border-bottom: 1px solid;
}
#boxes table {
width: 200px;
diff --git a/public/assets/js/lib/router.js b/public/assets/js/lib/router.js
index 2bbc8d1..9879c6c 100644
--- a/public/assets/js/lib/router.js
+++ b/public/assets/js/lib/router.js
@@ -7,6 +7,7 @@ var SiteRouter = Router.extend({
"/index": 'index',
"/login": 'login',
"/details/:id": 'details',
+ "/search": 'search',
"/mail": 'mailbox',
"/mail/:mailbox": 'mailbox',
"/mail/compose": 'compose',
@@ -44,4 +45,9 @@ var SiteRouter = Router.extend({
app.view = new ComposeView ()
},
+ search: function(){
+ app.view = new SearchResults ()
+ app.view.load()
+ },
+
}) \ No newline at end of file
diff --git a/public/assets/js/lib/views/details/files.js b/public/assets/js/lib/views/details/files.js
index 295b26c..b81d20c 100644
--- a/public/assets/js/lib/views/details/files.js
+++ b/public/assets/js/lib/views/details/files.js
@@ -65,7 +65,7 @@ var FilesView = FormView.extend({
pick: function(e){
if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) return
- if (! e.target.href.match(/(mp3|wav|ogg)/i)) return
+ if (! e.target.href || ! e.target.href.match(/(mp3|wav|ogg)/i)) return
e.preventDefault()
audio.play( e.target.dataset.index )
},
diff --git a/public/assets/js/lib/views/details/index.js b/public/assets/js/lib/views/details/index.js
index cd8045a..0a40dbc 100644
--- a/public/assets/js/lib/views/details/index.js
+++ b/public/assets/js/lib/views/details/index.js
@@ -13,7 +13,6 @@ var DetailsView = View.extend({
this.gallery = new GalleryView ({ parent: this })
this.form = new CommentForm ({ parent: this })
this.threadbox = new ThreadBox ({ parent: this })
- this.metadataTemplate = $(".metadata_template").html()
},
load: function(id){
@@ -24,15 +23,7 @@ var DetailsView = View.extend({
populate: function(data){
var thread = data.thread
$("h1").html(thread.title)
- var datetime = verbose_date(thread.createdate, true)
- var age = get_age(thread.lastmodified, true)
- var t = this.metadataTemplate
- .replace(/{{ username }}/g, thread.username)
- .replace(/{{ date }}/g, datetime[0])
- .replace(/{{ time }}/g, datetime[1])
- .replace(/{{ active }}/g, age + " ago")
- .replace(/{{ views }}/g, thread.viewed + " view" + courtesy_s(thread.viewed))
- $(".metadata").html(t)
+ $(".subtitle").show().html(metadata(thread))
this.form.load(data.thread)
this.comments.load(data.comments)
this.files.load(data.files)
@@ -51,3 +42,16 @@ var DetailsView = View.extend({
},
})
+
+var metadataTemplate = $(".metadata_template").html()
+function metadata(thread){
+ var datetime = verbose_date(thread.createdate, true)
+ var age = get_age(thread.lastmodified, true)
+ var t = metadataTemplate
+ .replace(/{{ username }}/g, thread.username)
+ .replace(/{{ date }}/g, datetime[0])
+ .replace(/{{ time }}/g, datetime[1])
+ .replace(/{{ active }}/g, age + " ago")
+ .replace(/{{ views }}/g, thread.viewed + " view" + courtesy_s(thread.viewed))
+ return t
+} \ No newline at end of file
diff --git a/public/assets/js/lib/views/index/index.js b/public/assets/js/lib/views/index/index.js
index d66ea1c..171133b 100644
--- a/public/assets/js/lib/views/index/index.js
+++ b/public/assets/js/lib/views/index/index.js
@@ -8,7 +8,7 @@ var IndexView = View.extend({
initialize: function(opt){
// opt.parent = parent
this.hootbox = new HootBox ({ parent: this })
- this.threadbox = new ThreadBox ({ parent: this, latest: true })
+ this.threadbox = new ThreadBox ({ parent: this, latest: true, welcome: true, })
this.lastlog = new LastLog ({ parent: this })
this.load()
},
diff --git a/public/assets/js/lib/views/index/lastlog.js b/public/assets/js/lib/views/index/lastlog.js
index 2f7b224..0fed101 100644
--- a/public/assets/js/lib/views/index/lastlog.js
+++ b/public/assets/js/lib/views/index/lastlog.js
@@ -18,11 +18,12 @@ var LastLog = View.extend({
},
load: function(lastlog){
- var s = lastlog.map(this.parse.bind(this)).join(', ')
+ var s = lastlog.map(this.parse.bind(this)).filter(s => s).join(', ')
this.$el.html(s)
},
parse: function(user){
+ if (Date.now()/1000 - user.lastseen > 86400 * 14 *10000) return ''
var t = this.template
.replace(/{{username}}/g, user.username)
.replace(/{{age}}/g, get_age(user.lastseen) )
diff --git a/public/assets/js/lib/views/index/threadbox.js b/public/assets/js/lib/views/index/threadbox.js
index 8d8cb02..6efce22 100644
--- a/public/assets/js/lib/views/index/threadbox.js
+++ b/public/assets/js/lib/views/index/threadbox.js
@@ -8,28 +8,34 @@ var ThreadBox = View.extend({
this.__super__.initialize.call(this)
this.template = this.$(".template").html()
this.keywordTemplate = this.$(".keywordTemplate").html()
+ this.welcomeTemplate = this.$(".welcomeTemplate").html()
},
load: function(data){
+ if (this.options.welcome) {
+ this.appendWelcome()
+ }
if (data.keyword) {
this.appendKeyword(data.keyword)
- data.threads.forEach(this.appendThread.bind(this))
- }
- else if (this.options.latest) {
- data.threads.sort( (a,b) => {
- return b.lastmodified - a.lastmodified
- }).slice(0, 50).forEach(this.appendThread.bind(this))
+ this.appendThreads(data.threads)
}
else {
+ if (this.options.latest) {
+ var latest = data.threads.sort( (a,b) => {
+ return b.lastmodified - a.lastmodified
+ }).slice(0, 15)
+ this.appendThreads(latest)
+ data.threads = data.threads.filter((thread) => thread && !! thread.keyword)
+ }
var keywords = {}
data.threads.forEach((thread) => {
- var keyword = thread.keyword || '__unsorted'
+ var keyword = thread.keyword || 'unsorted'
keywords[keyword] = keywords[keyword] || []
keywords[keyword].push(thread)
})
Object.keys(keywords).sort().forEach((keyword) => {
this.appendKeyword({ keyword })
- keywords[keyword].forEach(this.appendThread.bind(this))
+ this.appendThreads(keywords[keyword])
})
}
},
@@ -62,7 +68,7 @@ var ThreadBox = View.extend({
.replace(/{{files_class}}/g, files[0])
.replace(/{{show_files}}/g, thread.file_count == 0 ? "hidden" : "")
.replace(/{{size_class}}/g, size[0] )
- .replace(/{{color}}/g, thread.color || "ivory" )
+ .replace(/{{color}}/g, thread.color || "blue" )
return t
},
@@ -76,9 +82,18 @@ var ThreadBox = View.extend({
var $row = $( this.parse(thread) )
this.$el.prepend($row)
},
+
+ appendThreads: function(threads){
+ threads[0].first = true
+ threads[threads.length-1].last = true
+ threads.forEach(this.appendThread.bind(this))
+ },
appendThread: function(thread){
+ thread.appended = true
var $row = $( this.parse(thread) )
+ if (thread.first) $row.addClass('first')
+ if (thread.last) $row.addClass('last')
this.$el.append($row)
},
@@ -86,4 +101,9 @@ var ThreadBox = View.extend({
var $row = $( this.parseKeyword(keyword) )
this.$el.append($row)
},
+
+ appendWelcome: function(){
+ this.$el.append(this.welcomeTemplate)
+ },
+
})
diff --git a/public/assets/js/lib/views/search/results.js b/public/assets/js/lib/views/search/results.js
new file mode 100644
index 0000000..89004e3
--- /dev/null
+++ b/public/assets/js/lib/views/search/results.js
@@ -0,0 +1,71 @@
+var SearchResults = View.extend({
+
+ el: "#search",
+ template: $("#search .template").html(),
+
+ events: {
+ },
+
+ action: "/api/search",
+
+ initialize: function(opt){
+ },
+
+ load: function(){
+ var query = window.location.search.substr(1)
+ if (! query || ! query.length) {
+ $("#search").hide()
+ return
+ }
+ $.get(this.action, query, this.populate.bind(this))
+ },
+
+ populate: function(res){
+ var query = sanitize(res.meta.query)
+ var terms = res.meta.terms
+ console.log(res)
+ $("[name=query]").val(query)
+ this.$(".query").html(query)
+ this.$(".total").html(parseInt(res.meta.total))
+ var next_page = {
+ query: res.meta.terms.join("+"),
+ start: res.meta.start + res.meta.limit,
+ limit: res.meta.limit,
+ }
+ this.$(".next_page").toggle(res.meta.start + res.meta.limit > res.meta.count)
+ this.$(".next_page").attr("href", querystring(next_page))
+ res.results.forEach((result) => {
+ var image
+ if (result.file && is_image(result.file.filename)) {
+ image = result.file
+ }
+ else if (result.thread.flagged && is_image(result.thread.flagged.filename)) {
+ image = result.thread.flagged
+ }
+ var image_path = image ? '/data/' + result.thread.id + '/' + sanitize(image.filename) : ''
+ var file_tag = result.file ? '<a href="' + make_link(result.file) + '">' + bold_terms(sanitize(result.file.filename), terms) + '</a>' : ''
+ var t = this.template
+ .replace(/{{thread_id}}/g, sanitize("" + result.thread.id))
+ .replace(/{{meta}}/, metadata(result.thread))
+ .replace(/{{image}}/, image_path)
+ .replace(/{{title}}/, bold_terms(sanitize(result.thread.title), terms))
+ .replace(/{{comment}}/, result.comment ? bold_terms(sanitize(result.comment.comment), terms) : '')
+ .replace(/{{file}}/, file_tag)
+ this.$("#results").append(t)
+ })
+ },
+
+})
+
+function bold_terms (s, terms) {
+ s = sanitize(s)
+ terms.forEach( (term) => {
+ s = s.replace(new RegExp(term, "ig"), "<b>" + term + "</b>")
+ })
+ return s
+}
+function querystring(opt){
+ var s = Object.keys(opt).map((key) => {
+ return encodeURIComponent(key) + "=" + encodeURIComponent(opt[key])
+ }).join("&")
+}
diff --git a/public/assets/js/util/format.js b/public/assets/js/util/format.js
index 889ab09..a162229 100644
--- a/public/assets/js/util/format.js
+++ b/public/assets/js/util/format.js
@@ -199,7 +199,7 @@ function is_image(url){
}
function make_link(file){
if (file.filename.indexOf("http") !== 0) {
- return "//carbonpictures.com/bucky/data/" + file.thread + "/" + file.filename
+ return "/data/" + file.thread + "/" + encodeURIComponent(file.filename)
}
else {
return file.filename
@@ -207,10 +207,10 @@ function make_link(file){
}
function make_thumb(file){
if (file.filename.indexOf("http") !== 0) {
- return "//carbonpictures.com/bucky/data/" + file.thread + "/" + file.filename
+ return "/data/" + file.thread + "/" + file.filename
}
else {
- return "//carbonpictures.com/bucky/data/" + file.thread + "/" + file.filename
+ return "/data/" + file.thread + "/" + file.filename
// var partz = file.filename.toLowerCase().split("/")
// return partz.splice(partz.length-2, 0, ".thumb").join("/")
}
diff --git a/views/pages/details.ejs b/views/pages/details.ejs
index b508560..a5db176 100644
--- a/views/pages/details.ejs
+++ b/views/pages/details.ejs
@@ -1,17 +1,7 @@
<% include ../partials/header %>
-<hr>
-
<div id="details_rapper">
- <script type="text/html" class="metadata_template">
- Posted by {{ username }}
- on {{ date }} {{ time }}
- &middot;
- Last active {{ active }}
- &middot;
- {{ views }}
- </script>
-
+ <% include ../partials/metadata %>
<table id="details">
<tr>
<td class="left">
diff --git a/views/pages/index.ejs b/views/pages/index.ejs
index 454e358..7919344 100644
--- a/views/pages/index.ejs
+++ b/views/pages/index.ejs
@@ -1,22 +1,34 @@
<% include ../partials/header %>
-<hr>
+<div id="content">
+ <% include ../partials/threads %>
+</div>
<div id="sidebar">
- <div class="bluebox">
- <b><big>welcome to bucky</big></b>
- </div>
<div class="bluebox" id="searchbox">
- <form>
- <input type="text" name="search" autofocus>
+ <b><big>welcome to bucky</big></b>
+
+ <form action="/search" method="get">
+ <input type="text" name="query" autofocus>
<button>SEARCH</button>
</form>
+
</div>
- <% include ../partials/hootbox %>
-</div>
+ <div class="bluebox">
+ <a href="/post">new post</a> |
+ <a href="/mail">inbox</a> |
+ <a href="/message">message</a> |
+ <a href="/profile">profile</a> |
+ <a href="/logout">logout</a>
+ </div>
+ <span class="lastlog bluebox">
+ <script class="template" type="text/html">
+ <a href="/profile/{{username}}">{{username}}</a>
+ [{{age}}]
+ </script>
+ </span>
-<div id="content">
- <% include ../partials/threads %>
+ <% include ../partials/hootbox %>
</div>
<% include ../partials/footer %>
diff --git a/views/pages/mailbox.ejs b/views/pages/mailbox.ejs
index 87fa30e..fa06b8e 100644
--- a/views/pages/mailbox.ejs
+++ b/views/pages/mailbox.ejs
@@ -1,7 +1,5 @@
<% include ../partials/header %>
-<hr>
-
<div id="sidebar">
<div class="bluebox">
<b><big>message center</big></b>
diff --git a/views/pages/message.ejs b/views/pages/message.ejs
index bd71a0a..0d1233d 100644
--- a/views/pages/message.ejs
+++ b/views/pages/message.ejs
@@ -1,7 +1,5 @@
<% include ../partials/header %>
-<hr>
-
<div class="bluebox" id="message">
<script class="template" type="text/html">
<a href="/profile/{{sender}}" class="av"><img src="/data/profile/.thumb/al.{{sender}}.jpg"></a>
diff --git a/views/pages/search.ejs b/views/pages/search.ejs
new file mode 100644
index 0000000..0bf7819
--- /dev/null
+++ b/views/pages/search.ejs
@@ -0,0 +1,38 @@
+<% include ../partials/header %>
+
+<div id="content">
+
+ <div id="search">
+ <form action="/search" method="get" style="margin: 10px;">
+ <input type="text" name="query" autofocus>
+ <button>SEARCH</button>
+ </form>
+
+ <div id="results">
+ <div class="preamble">
+ Results for <b class="query"></b> (<span class='total'></span> results)
+ </div>
+ <% include ../partials/metadata %>
+ <script type="text/html" class="template">
+ <div class='result'>
+ <div class='desc'>
+ <a href="/details/{{thread_id}}" class='search_hit'>
+ {{title}}
+ </a>
+ <div class='meta'>{{meta}}</div>
+ <div class='snippet'>{{comment}}</div>
+ <div class='file'>{{file}}</div>
+ </div>
+ <a href="/details/{{thread_id}}">
+ <div class='image' style='background-image:url({{image}})'></div>
+ </a>
+ </div>
+ </script>
+ </div>
+
+ <a class="next_page" href="">Next page</a>
+ </div>
+
+</div>
+
+<% include ../partials/footer %>
diff --git a/views/partials/header.ejs b/views/partials/header.ejs
index f5b21dc..87b4dd8 100644
--- a/views/partials/header.ejs
+++ b/views/partials/header.ejs
@@ -8,25 +8,4 @@
<body>
<h1><%= title %></h1>
-<span class="metadata"></span>
-<hr>
-
-<% if (show_header) { %>
- <div id="menu">
- <span class="lastlog">
- <script class="template" type="text/html">
- <a href="/profile/{{username}}">{{username}}</a>
- [{{age}}]
- </script>
- </span>
- <span class="links">
- <a href="/index">home</a> |
- <a href="/search">search</a> |
- <a href="/post">post</a> |
- <a href="/mail">inbox</a> |
- <a href="/message">message</a> |
- <a href="/profile">profile</a> |
- <a href="/logout">logout</a>
- </span>
- </div>
-<% } %> \ No newline at end of file
+<div class="subtitle"></div>
diff --git a/views/partials/metadata.ejs b/views/partials/metadata.ejs
new file mode 100644
index 0000000..956ec18
--- /dev/null
+++ b/views/partials/metadata.ejs
@@ -0,0 +1,8 @@
+<script type="text/html" class="metadata_template">
+ Posted by {{ username }}
+ on {{ date }} {{ time }}
+ &middot;
+ Last active {{ active }}
+ &middot;
+ {{ views }}
+</script>
diff --git a/views/partials/scripts.ejs b/views/partials/scripts.ejs
index 8dc99a1..2786fa8 100644
--- a/views/partials/scripts.ejs
+++ b/views/partials/scripts.ejs
@@ -19,6 +19,8 @@
<script src="/assets/js/lib/views/index/hootbox.js"></script>
<script src="/assets/js/lib/views/index/threadbox.js"></script>
+<script src="/assets/js/lib/views/search/results.js"></script>
+
<script src="/assets/js/lib/views/details/index.js"></script>
<script src="/assets/js/lib/views/details/audio.js"></script>
<script src="/assets/js/lib/views/details/comments.js"></script>
diff --git a/views/partials/threads.ejs b/views/partials/threads.ejs
index b3a6e75..ec93efa 100644
--- a/views/partials/threads.ejs
+++ b/views/partials/threads.ejs
@@ -1,6 +1,17 @@
<table class="ledger" id="threads">
+ <script class="welcomeTemplate" type="text/html">
+ <tr class='keyword'>
+ <td>
+ <b><i>the latest</i></b>&nbsp;&middot;
+ </td>
+ <td>
+ <a href="/post/">Start a new thread!</a>
+ </td>
+ </tr>
+ </script>
+
<script class="keywordTemplate" type="text/html">
<tr class='keyword'>
<td>