diff options
| -rw-r--r-- | bucky/app/router.js | 8 | ||||
| -rw-r--r-- | bucky/search/bdb.js | 69 | ||||
| -rw-r--r-- | bucky/search/middleware.js | 51 | ||||
| -rw-r--r-- | bucky/search/search.js | 20 | ||||
| -rw-r--r-- | bucky/search/snippet.js | 47 | ||||
| -rw-r--r-- | public/assets/css/bucky.css | 202 | ||||
| -rw-r--r-- | public/assets/js/lib/router.js | 6 | ||||
| -rw-r--r-- | public/assets/js/lib/views/details/files.js | 2 | ||||
| -rw-r--r-- | public/assets/js/lib/views/details/index.js | 24 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/index.js | 2 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/lastlog.js | 3 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/threadbox.js | 38 | ||||
| -rw-r--r-- | public/assets/js/lib/views/search/results.js | 71 | ||||
| -rw-r--r-- | public/assets/js/util/format.js | 6 | ||||
| -rw-r--r-- | views/pages/details.ejs | 12 | ||||
| -rw-r--r-- | views/pages/index.ejs | 32 | ||||
| -rw-r--r-- | views/pages/mailbox.ejs | 2 | ||||
| -rw-r--r-- | views/pages/message.ejs | 2 | ||||
| -rw-r--r-- | views/pages/search.ejs | 38 | ||||
| -rw-r--r-- | views/partials/header.ejs | 23 | ||||
| -rw-r--r-- | views/partials/metadata.ejs | 8 | ||||
| -rw-r--r-- | views/partials/scripts.ejs | 2 | ||||
| -rw-r--r-- | views/partials/threads.ejs | 11 |
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 }} - · - Last active {{ active }} - · - {{ 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 }} + · + Last active {{ active }} + · + {{ 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> · + </td> + <td> + <a href="/post/">Start a new thread!</a> + </td> + </tr> + </script> + <script class="keywordTemplate" type="text/html"> <tr class='keyword'> <td> |
