diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2017-12-22 07:11:26 +0100 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2017-12-22 07:11:26 +0100 |
| commit | fa4ed0f3b1ce5decfc6ee59d38e63e78a7793de4 (patch) | |
| tree | 74658c754cbdff432aa8dc8b4f3521c993993c45 | |
| parent | 1a67f6f262364ee4fed0c1538ef2f41d772bf1b1 (diff) | |
new keyword form
| -rw-r--r-- | bucky/app/api.js | 23 | ||||
| -rw-r--r-- | bucky/app/bucky.js | 31 | ||||
| -rw-r--r-- | bucky/db/index.js | 2 | ||||
| -rw-r--r-- | public/assets/js/lib/views/details/settings.js | 38 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/index.js | 14 | ||||
| -rw-r--r-- | public/assets/js/lib/views/keywords/keywords.js | 25 | ||||
| -rw-r--r-- | public/assets/js/lib/views/keywords/newkeyword.js | 34 | ||||
| -rw-r--r-- | public/assets/js/util/format.js | 18 | ||||
| -rw-r--r-- | public/assets/js/vendor/util.js | 10 | ||||
| -rw-r--r-- | views/pages/keywords.ejs | 27 | ||||
| -rw-r--r-- | views/partials/scripts.ejs | 1 |
11 files changed, 158 insertions, 65 deletions
diff --git a/bucky/app/api.js b/bucky/app/api.js index ff17626..63e480e 100644 --- a/bucky/app/api.js +++ b/bucky/app/api.js @@ -41,7 +41,7 @@ function route (app){ /* threads */ - + app.get("/api/index", bucky.ensureLastlog, middleware.ensureAuthenticated, @@ -59,6 +59,13 @@ function route (app){ lastlog: res.lastlog, }) }) + app.post("/api/keyword/new", + bucky.ensureLastlog, + middleware.ensureAuthenticated, + bucky.createKeyword, + function(req, res){ + res.json({ keyword: res.keyword }) + }) app.get("/api/keyword/:keyword", bucky.ensureLastlog, middleware.ensureAuthenticated, @@ -123,10 +130,10 @@ function route (app){ function(req, res){ res.send({ status: 'ok' }) }) - + /* comments */ - // one endpoint handles comments + files + // one endpoint handles comments + files app.post("/api/thread/:id/comment", middleware.ensureAuthenticated, bucky.ensureThread, @@ -178,7 +185,7 @@ function route (app){ }) /* search */ - + app.get("/api/search", middleware.ensureAuthenticated, search.search, @@ -190,7 +197,7 @@ function route (app){ ) /* keywords */ - + app.get("/api/keywords", middleware.ensureAuthenticated, bucky.ensureKeywords, @@ -223,10 +230,10 @@ function route (app){ threads: res.threads, }) }) - - + + /* mail */ - + app.get("/api/mailbox/:box", middleware.ensureAuthenticated, bucky.ensureMailboxes, diff --git a/bucky/app/bucky.js b/bucky/app/bucky.js index dbb980d..cd70790 100644 --- a/bucky/app/bucky.js +++ b/bucky/app/bucky.js @@ -94,9 +94,9 @@ var bucky = module.exports = { next() }) }, - + /* DETAILS */ - + ensureThread: function (req, res, next){ var id = req.params.id.replace(/\D/g, "") if (! id) { @@ -253,7 +253,7 @@ var bucky = module.exports = { res.sendStatus(500) }) }, - + /* KEYWORDS */ ensureKeyword: function (req, res, next){ @@ -296,7 +296,24 @@ var bucky = module.exports = { next() }) }, - + createKeyword: function (req, res, next){ + if (! req.body.keyword || ! req.body.keyword.length) { + res.json({ error: "no keyword" }) + return + } + var data = { + keyword: req.body.keyword, + owner: req.user.get('username'), + createdate: util.now(), + public: 1, + color: req.body.color || 'blue', + } + db.createKeyword(data).then(function(keyword){ + res.keyword = keyword + next() + }) + }, + /* POSTING */ verifyFilesOrComment: function (req, res, next){ @@ -308,9 +325,9 @@ var bucky = module.exports = { } next() }, - + /* COMMENTS */ - + ensureComment: function (req, res, next){ var id = req.params.id.replace(/\D/g, "") if (! id) { @@ -442,7 +459,7 @@ var bucky = module.exports = { /* PROFILE / USER */ - + ensureUser: function (req, res, next){ var username = util.sanitizeName(req.params.username) if (! username) { diff --git a/bucky/db/index.js b/bucky/db/index.js index e26124a..d3ee2ea 100644 --- a/bucky/db/index.js +++ b/bucky/db/index.js @@ -224,7 +224,7 @@ db.getKeyword = function (keyword) { return Keyword.query("where", "keyword", "=", keyword).fetch() } db.getThreadGroups = function (keyword) { - return knex.column('keyword').sum('viewed').as('viewed').column('id').column('title').column('lastmodified').column('privacy').select().from('threads').groupBy('keyword') + return knex.column('keyword').sum('viewed').as('viewed').count('*').as('count').column('id').column('title').column('lastmodified').column('privacy').select().from('threads').groupBy('keyword') } db.createKeyword = function(data){ return new db.Keyword(data).save() diff --git a/public/assets/js/lib/views/details/settings.js b/public/assets/js/lib/views/details/settings.js index 1d048ab..c8e53db 100644 --- a/public/assets/js/lib/views/details/settings.js +++ b/public/assets/js/lib/views/details/settings.js @@ -1,7 +1,7 @@ var ThreadSettingsForm = FormView.extend({ - + el: "#thread_settings", - + events: { "click": "hide", "click .inner": "stopPropagation", @@ -14,17 +14,17 @@ var ThreadSettingsForm = FormView.extend({ "keydown [name=allowed_field]": "keydownAllowed", "blur [name=allowed_field]": "updateAllowed", }, - + action: "", method: 'put', - + initialize: function(){ this.__super__.initialize.call(this) this.template = this.$(".template").html() this.allowedTemplate = this.$(".allowedTemplate").html() this.filesTemplate = this.$(".settingsFilesTemplate").html() }, - + populate: function(){ var data = this.options.parent.data var keywords = data.keywords @@ -34,7 +34,7 @@ var ThreadSettingsForm = FormView.extend({ var files = data.files var settings = thread.settings var display = thread.display - + this.thread = thread this.files = data.files this.action = "/api/thread/" + thread.id @@ -48,7 +48,7 @@ var ThreadSettingsForm = FormView.extend({ this.$("[name=shorturls]").prop("checked", !!thread.settings.shorturls) this.$("[name=noupload]").prop("checked", !!thread.settings.noupload) this.$("[name=privacy]").prop("checked", !!thread.privacy) - + var $color = this.$('[name=color]') Object.keys(COLORS).forEach((color) => { var option = document.createElement('option') @@ -57,10 +57,10 @@ var ThreadSettingsForm = FormView.extend({ $color.append(option) }) $color.val(thread && thread.color? thread.color: keyword? keyword.color: "") - + this.toggleAllowed() this.fetchKeywords() - + var $files = this.$(".files") $files.empty() files.sort((a,b) => cmp(a.filename, b.filename)) @@ -84,7 +84,7 @@ var ThreadSettingsForm = FormView.extend({ $("body").removeClass("loading") }, - + fetchKeywords: function(thread){ $.get('/api/keywords', function(data){ var $keyword = this.$('[name=keyword]') @@ -100,7 +100,7 @@ var ThreadSettingsForm = FormView.extend({ $keyword.val(this.thread.keyword) }.bind(this)) }, - + toggleAllowed: function(e){ var checked = this.$('[name=privacy]').prop('checked') this.$(".allowed_field_container").toggle(checked) @@ -109,7 +109,7 @@ var ThreadSettingsForm = FormView.extend({ this.$("[name=allowed_field]").focus() this.displayAllowed() }, - + displayAllowed: function(e){ var $allowedNames = this.$(".allowed_names").empty() this.allowed.forEach(username => { @@ -117,7 +117,7 @@ var ThreadSettingsForm = FormView.extend({ $allowedNames.append(t) }) }, - + keydownAllowed: function(e){ if (e.keyCode === 13) { // enter e.preventDefault() @@ -125,7 +125,7 @@ var ThreadSettingsForm = FormView.extend({ this.updateAllowed() } }, - + updateAllowed: function(){ var usernames = this.$('[name=allowed_field]').val().replace(/,/g, ' ').split(' ').map(s => s.trim()).filter(s => !! s) this.$('[name=allowed_field]').val('') @@ -145,14 +145,14 @@ var ThreadSettingsForm = FormView.extend({ }.bind(this), }) }, - + removeAllowed: function(){ this.allowed = this.$("#allowed_names input[type=checkbox]:checked").map(function(){ return $(this).val() }) this.displayAllowed() }, - + validate: function(){ var errors = [] var title = $("[name=title]").val() @@ -184,7 +184,7 @@ var ThreadSettingsForm = FormView.extend({ }, visible: false, - + show: function(){ this.visible = true app.typing = true @@ -205,7 +205,7 @@ var ThreadSettingsForm = FormView.extend({ if (this.visible) this.hide() else this.show() }, - + changeColor: function(){ var color_name = this.$("[name=color]").val() set_background_color(color_name) @@ -264,4 +264,4 @@ var ThreadSettingsForm = FormView.extend({ } }, -})
\ 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 94bc57d..3f217fc 100644 --- a/public/assets/js/lib/views/index/index.js +++ b/public/assets/js/lib/views/index/index.js @@ -2,18 +2,19 @@ var IndexView = View.extend({ events: { }, - + action: "/api/index", keywordAction: "/api/keyword/", - + initialize: function(opt){ // opt.parent = parent this.hootbox = new HootBox ({ parent: this }) this.threadbox = new ThreadBox ({ parent: this }) this.lastlog = new LastLog ({ parent: this }) }, - + load: function(keyword){ + $("body").addClass("index") if (keyword) { $(".subtitle").html('<a href="/">< Home</a> · <a href="/keywords">Keywords</a>') this.threadbox.options.latest = false @@ -25,10 +26,9 @@ var IndexView = View.extend({ this.threadbox.options.latest = true this.threadbox.options.welcome = true $.get(this.action, this.populate.bind(this)) - $("body").addClass("index") } }, - + populate: function(data){ $("body").removeClass('loading') this.hootbox.load(data.hootbox) @@ -36,9 +36,9 @@ var IndexView = View.extend({ this.lastlog.load(data.lastlog) $(".search_form input").focus() }, - + success: function(){ window.location.href = "/index" }, -})
\ No newline at end of file +}) diff --git a/public/assets/js/lib/views/keywords/keywords.js b/public/assets/js/lib/views/keywords/keywords.js index 37fab16..9b2eadc 100644 --- a/public/assets/js/lib/views/keywords/keywords.js +++ b/public/assets/js/lib/views/keywords/keywords.js @@ -1,22 +1,23 @@ var KeywordsView = View.extend({ - + el: "#keyword_list", - + events: { }, - + action: "/api/keywords/statistics", - + initialize: function(opt){ this.template = this.$(".template").html() + this.form = new NewKeywordForm ({ parent: this }) }, - + load: function(){ $.get(this.action, this.populate.bind(this)) }, - + populate: function(data){ - console.log(data) + // console.log(data) var keywordThreads = {} data.threadGroups.forEach(kw => { keywordThreads[kw.keyword] = kw @@ -29,16 +30,18 @@ var KeywordsView = View.extend({ var thread = keywordThreads[keyword.keyword.toLowerCase()] || { title: '', } - // { + // { // keyword: "warez", // sum(`viewed`): "498", // id: 701, // title: "EMS SYNTHI PLUG FOR MAC", // lastmodified: 1192401724 // }, - console.log(keyword, thread) + // console.log(keyword, thread) var viewed = thread['sum(`viewed`)'] var views = viewed ? hush_views(viewed) : ['',''] + var threadCountNum = thread['count(*)'] + var threadCount = threadCountNum ? hush_threads(threadCountNum) : ['',''] var dot = privacy_dot(thread.privacy) var datetime = verbose_date(keyword.createdate) var age = get_age(thread.lastmodified) @@ -53,6 +56,8 @@ var KeywordsView = View.extend({ .replace(/{{time}}/g, datetime[1]) .replace(/{{date_class}}/g, carbon_date(thread.lastmodified) ) .replace(/{{views}}/g, views[1]) + .replace(/{{threadcount}}/, threadCount[1]) + .replace(/{{threadcount_class}}/, threadCount[0]) // .replace(/{{comments}}/g, comments[1]) // .replace(/{{files}}/g, files[1]) // .replace(/{{size}}/g, size[1] ) @@ -68,4 +73,4 @@ var KeywordsView = View.extend({ $("body").removeClass('loading') }, -})
\ No newline at end of file +}) diff --git a/public/assets/js/lib/views/keywords/newkeyword.js b/public/assets/js/lib/views/keywords/newkeyword.js new file mode 100644 index 0000000..9db528a --- /dev/null +++ b/public/assets/js/lib/views/keywords/newkeyword.js @@ -0,0 +1,34 @@ +var NewKeywordForm = FormView.extend({ + + el: "#new_keyword", + + action: "/api/keyword/new", + + initialize: function(){ + this.__super__.initialize.call(this) + var $color = this.$('[name=color]') + Object.keys(COLORS).forEach((color) => { + var option = document.createElement('option') + option.value = color + option.innerHTML = color + $color.append(option) + }) + $color.val(choice(Object.keys(COLORS))) + }, + + validate: function(){ + var errors = [] + var keyword = $("[name=keyword]").val().trim() + if (! keyword || ! keyword.length) { + errors.push("Please enter a keyword.") + } + if (keyword === "new") { + errors.push("Keyword cannot be called 'new'.") + } + return errors.length ? errors : null + }, + + success: function(data){ + window.location.href = "/post/" + data.keyword.keyword + } +}) diff --git a/public/assets/js/util/format.js b/public/assets/js/util/format.js index bc114b9..36a3d37 100644 --- a/public/assets/js/util/format.js +++ b/public/assets/js/util/format.js @@ -5,6 +5,8 @@ var is_mobile = is_iphone || is_ipad || is_android var is_desktop = ! is_mobile; document.body.classList.add(is_desktop ? 'desktop' : 'mobile'); +function choice(a){ return a[randint(a.length)] } + function csrf() { return $("[name=_csrf]").attr("value") } @@ -58,11 +60,11 @@ function verbose_date (date, no_pad_hours) { if (d < 10) d = "0" + d if (m < 10) m = "0" + m if (! no_pad_hours && h < 10) h = "0" + h - + // non-breaking hyphen: ‑ var date = d + ' ' + short_months[date.getMonth()] + ' ' + date.getFullYear() var time = h + ':' + m + meridian - + return [date, time] } function carbon_date (date, no_bold) { @@ -94,6 +96,16 @@ function hush_views (n, bias, no_bold) { else if (no_bold || n < 10000) { return ["recent", txt + " kv."] } else { return ["new", txt + " kv."] } } +function hush_threads (n, bias, no_bold) { + var txt = commatize(n, 1000) + bias = bias || 1 + n = n || 0 + if (n < 10) { return["quiet", n + " t."] } + else if (n < 25) { return ["old", txt + " t."] } + else if (n < 50) { return ["med", txt + " t."] } + else if (no_bold || n < 100) { return ["recent", txt + " t."] } + else { return ["new", txt + " t."] } +} function hush_size (n, bias, no_bold) { var txt = commatize(Math.floor(n / 1024)) @@ -243,4 +255,4 @@ function metadata(thread){ .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/vendor/util.js b/public/assets/js/vendor/util.js index b08a914..cab7a6c 100644 --- a/public/assets/js/vendor/util.js +++ b/public/assets/js/vendor/util.js @@ -85,7 +85,7 @@ function rgbpixel(d,x,y){ function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) } function step(a, b){ - return (b >= a) + 0 + return (b >= a) + 0 // ^^ bool -> int } @@ -225,20 +225,20 @@ if (!Function.prototype.bind) { var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; - window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } - + if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; - + if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { clearTimeout(id); diff --git a/views/pages/keywords.ejs b/views/pages/keywords.ejs index d844e39..89f44a2 100644 --- a/views/pages/keywords.ejs +++ b/views/pages/keywords.ejs @@ -16,6 +16,9 @@ <div class="views {{views_class}}"> {{views}} </div> + <div class="threadcount {{threadcount_class}}"> + {{threadcount}} + </div> <div class="{{color}} title"> <a href="/details/{{id}}">{{title}}</a> </div> @@ -24,6 +27,17 @@ </div> </div> +<div id="sidebar"> + <div class="bluebox" id="new_keyword"> + <form> + <input type="text" name="keyword" placeholder="Create a new keyword"> + <select name="color"></select> + <input type="submit" value="Create"> + <div class="errors"></div> + </form> + </div> +</div> + <% include ../partials/footer %> <style> @@ -55,14 +69,17 @@ margin-left: 3px; } .keyword_row .views { - min-width: 30px; - text-align: center; + min-width: 40px; + justify-content: center; +} +.keyword_row .threadcount { + min-width: 40px; + justify-content: flex-end; + margin-right: 10px; } .keyword_row .title a { display: block; padding: 5px; margin-top: -4px; - } - -</style>
\ No newline at end of file +</style> diff --git a/views/partials/scripts.ejs b/views/partials/scripts.ejs index 54ecf8f..5ed307e 100644 --- a/views/partials/scripts.ejs +++ b/views/partials/scripts.ejs @@ -29,6 +29,7 @@ <script src="/assets/js/lib/views/search/results.js"></script> <script src="/assets/js/lib/views/keywords/keywords.js"></script> +<script src="/assets/js/lib/views/keywords/newkeyword.js"></script> <script src="/assets/js/lib/views/profile/profile.js"></script> <script src="/assets/js/lib/views/profile/profile_edit.js"></script> |
