diff options
| -rw-r--r-- | bucky/app/api.js | 11 | ||||
| -rw-r--r-- | bucky/app/bucky.js | 19 | ||||
| -rw-r--r-- | bucky/app/pages.js | 16 | ||||
| -rw-r--r-- | bucky/db/index.js | 30 | ||||
| -rw-r--r-- | bucky/util/middleware.js | 54 | ||||
| -rw-r--r-- | public/assets/css/hootstream.css | 3 | ||||
| -rw-r--r-- | public/assets/js/lib/router.js | 20 | ||||
| -rw-r--r-- | public/assets/js/lib/views/stream/hootstream.js | 40 | ||||
| -rw-r--r-- | public/assets/js/lib/views/stream/index.js | 22 | ||||
| -rw-r--r-- | public/assets/js/vendor/view/router.js | 117 | ||||
| -rw-r--r-- | views/partials/hootstream.ejs | 114 |
11 files changed, 265 insertions, 181 deletions
diff --git a/bucky/app/api.js b/bucky/app/api.js index a2f85d0..0645145 100644 --- a/bucky/app/api.js +++ b/bucky/app/api.js @@ -120,6 +120,7 @@ function route(app) { bucky.checkMail, function (req, res) { res.json({ + query: res.query, threads: res.threads, files: res.files, comments: res.comments, @@ -138,6 +139,16 @@ function route(app) { res.json({ keyword: res.keyword }); } ); + + 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, diff --git a/bucky/app/bucky.js b/bucky/app/bucky.js index 03a8b87..86982f8 100644 --- a/bucky/app/bucky.js +++ b/bucky/app/bucky.js @@ -67,17 +67,20 @@ var bucky = (module.exports = { }); }, ensureHootstream: function (req, res, next) { + const query = { + thread: parseInt(req.query?.thread) || null, + keyword: req.query?.keyword || null, + username: req.query?.username || null, + limit: req.query?.limit || 20, + offset: req.query?.offset || 0, + }; + query.has_query = query.thread || query.keyword || query.username; Promise.all([ - db.getHootstreamFiles({ - limit: req.query.limit || 20, - offset: req.query.offset || 0, - }), - db.getHootstreamComments({ - limit: req.query.limit || 20, - offset: req.query.offset || 0, - }), + db.getHootstreamFiles(query), + db.getHootstreamComments(query), ]).then(([files, comments]) => { db.getHootstreamThreads({ files, comments }).then((threads) => { + res.query = query; res.files = files; res.comments = comments.map((comment) => { comment.comment = comment.comment.toString(); diff --git a/bucky/app/pages.js b/bucky/app/pages.js index 94ae46e..0967b9b 100644 --- a/bucky/app/pages.js +++ b/bucky/app/pages.js @@ -31,6 +31,22 @@ function route(app) { hoot_text: fortune("hoots"), }); }); + app.get("/stream/:type", middleware.ensureAuthenticated, function (req, res) { + res.render("pages/stream", { + title: fortune("titles"), + hoot_text: fortune("hoots"), + }); + }); + app.get( + "/stream/:type/:id", + middleware.ensureAuthenticated, + function (req, res) { + res.render("pages/stream", { + title: fortune("titles"), + hoot_text: fortune("hoots"), + }); + } + ); app.get("/keywords", middleware.ensureAuthenticated, function (req, res) { res.render("pages/keywords", { title: "Bucky's keywords" }); }); diff --git a/bucky/db/index.js b/bucky/db/index.js index 36541f8..2a1a3bf 100644 --- a/bucky/db/index.js +++ b/bucky/db/index.js @@ -116,19 +116,41 @@ db.getLastlog = function (limit) { /** HOOTSTREAM */ -db.getHootstreamFiles = ({ limit, offset }) => +db.getHootstreamFiles = ({ limit, offset, thread, keyword, username }) => knex("files") .join("threads", "threads.id", "=", "files.thread") .select("files.*") - .where("threads.privacy", false) + .where((builder) => { + builder.where("threads.privacy", false); + if (keyword) { + builder.where("threads.keyword", keyword); + } + if (thread) { + builder.where("threads.id", thread); + } + if (username) { + builder.where("comments.username", username); + } + }) .orderBy("files.id", "desc") .offset(offset) .limit(limit); -db.getHootstreamComments = ({ limit, offset }) => +db.getHootstreamComments = ({ limit, offset, thread, keyword, username }) => knex("comments") .join("threads", "threads.id", "=", "comments.thread") .select("comments.*") - .where("threads.privacy", false) + .where((builder) => { + builder.where("threads.privacy", false); + if (keyword) { + builder.where("threads.keyword", keyword); + } + if (thread) { + builder.where("threads.id", thread); + } + if (username) { + builder.where("comments.username", username); + } + }) .orderBy("comments.id", "desc") .offset(offset) .limit(limit); diff --git a/bucky/util/middleware.js b/bucky/util/middleware.js index b91631d..37ec124 100644 --- a/bucky/util/middleware.js +++ b/bucky/util/middleware.js @@ -1,38 +1,34 @@ const buildDate = +Date.now(); -var middleware = module.exports = { - +var middleware = (module.exports = { ensureAuthenticated: function (req, res, next) { - if (! req.isAuthenticated()) { - req.session.returnTo = req.path - return res.redirect('/login') + if (!req.isAuthenticated()) { + req.session.returnTo = req.path; + return res.redirect("/login"); } - next() + next(); }, - + ensureLocals: function (req, res, next) { - res.locals.csrfToken = req.csrfToken ? req.csrfToken() : 'csrf' - res.locals.title = "bucky" - res.locals.buildDate = buildDate, - res.locals.env = process.env.NODE_ENV + res.locals.csrfToken = req.csrfToken ? req.csrfToken() : "csrf"; + res.locals.title = "bucky"; + (res.locals.buildDate = buildDate), (res.locals.env = process.env.NODE_ENV); if (req.isAuthenticated()) { - res.locals.show_header = true - res.locals.preload = JSON.stringify({ - env: res.locals.env, - buildDate: buildDate, - s3: { - bucket: process.env.S3_BUCKET, - path: process.env.S3_PATH, - } - }) - } - else { - res.locals.show_header = false - res.locals.preload = JSON.stringify({ - env: res.locals.env, - }) + res.locals.show_header = true; + res.locals.preload = JSON.stringify({ + env: res.locals.env, + buildDate: buildDate, + s3: { + bucket: process.env.S3_BUCKET, + path: process.env.S3_PATH, + }, + }); + } else { + res.locals.show_header = false; + res.locals.preload = JSON.stringify({ + env: res.locals.env, + }); } - next() + next(); }, - -} +}); diff --git a/public/assets/css/hootstream.css b/public/assets/css/hootstream.css index 597c82f..ee9e370 100644 --- a/public/assets/css/hootstream.css +++ b/public/assets/css/hootstream.css @@ -47,6 +47,9 @@ /** metadata */ +#hootevents .keyword { + font-size: 13px; +} #hootevents .keywordLink { text-decoration: none; opacity: 0.8; diff --git a/public/assets/js/lib/router.js b/public/assets/js/lib/router.js index bedf0b9..848c813 100644 --- a/public/assets/js/lib/router.js +++ b/public/assets/js/lib/router.js @@ -6,6 +6,12 @@ var SiteRouter = Router.extend({ "/index/:keyword": "index", "/index": "index", "/stream": "stream", + "/stream/thread": "stream", + "/stream/thread/:id": "streamThread", + "/stream/keyword": "streamKeyword", + "/stream/keyword/:keyword": "streamKeyword", + "/stream/profile": "streamProfile", + "/stream/profile/:username": "streamItem", "/login": "login", "/logout": "logout", "/signup": "signup", @@ -39,9 +45,19 @@ var SiteRouter = Router.extend({ app.view.load(keyword); }, - stream: function (keyword) { + stream: function (target) { app.view = new StreamView(); - app.view.load(keyword); + app.view.load(target || {}); + }, + + streamKeyword: function (keyword) { + this.stream({ keyword: keyword }); + }, + streamThread: function (thread) { + this.stream({ thread: parseInt(thread) }); + }, + streamProfile: function (username) { + this.stream({ username: username }); }, login: function () { diff --git a/public/assets/js/lib/views/stream/hootstream.js b/public/assets/js/lib/views/stream/hootstream.js index 016c987..4be70bd 100644 --- a/public/assets/js/lib/views/stream/hootstream.js +++ b/public/assets/js/lib/views/stream/hootstream.js @@ -3,6 +3,10 @@ const IMAGE_REGEXP = /(.gif|.jpg|.jpeg|.png)$/gi; var HootStream = View.extend({ el: "#hootstream", + events: { + "click a": "onClickLink", + }, + initialize: function ({ parent }) { this.parent = parent; this.$hootevents = this.$("#hootevents"); @@ -10,6 +14,25 @@ var HootStream = View.extend({ this.threadTemplate = this.$(".threadTemplate").html(); this.lastlogTemplate = this.$(".lastlogTemplate").html(); this.fileTemplate = this.$(".fileTemplate").html(); + this.onClickLink = this.onClickLink.bind(this); + }, + + onClickLink: function (event) { + // console.log(event.target.className, event.target.href); + const url = new URL(event.target.href); + switch (event.target.className) { + case "file": + // play audio? + break; + case "userLink": + case "threadLink": + case "keywordLink": + event.preventDefault(); + console.log(">>", url.pathname); + app.router.go(url.pathname); + break; + } + // this.parent.onKeyword(keyword) }, load: function (data) { @@ -34,6 +57,10 @@ var HootStream = View.extend({ }, render: (template, object) => { + if (!template) { + console.log("No template", object); + return $("<div>No template</div>"); + } const rendered = Object.entries(object).reduce( (newTemplate, [key, value]) => newTemplate.replace(new RegExp(`{{${key}}}`, "g"), value), @@ -99,9 +126,9 @@ var HootStream = View.extend({ "<div class='divider dark'></div>", this.renderHoot({ template: this.threadTemplate, - hoot: `<a class="threadLink" href="/details/${thread.id}">${thread.title}</a>`, + hoot: `<a class="threadLink" href="/stream/thread/${thread.id}">${thread.title}</a>`, keyword_link: thread.keyword - ? `<a class="keywordLink" href="/stream/${thread.keyword}">${thread.keyword}</a>` + ? `<a class="keywordLink" href="/stream/keyword/${thread.keyword}">${thread.keyword}</a>` : "", username: thread.username, className: postedToday ? "isRecent" : "", @@ -155,7 +182,7 @@ var HootStream = View.extend({ }); }, - agglutinate: ({ threads, comments, files, hootbox, lastlog }) => + agglutinate: ({ query, threads, comments, files, hootbox, lastlog }) => [ ...threads.map((thread) => [ thread.id, @@ -172,7 +199,12 @@ var HootStream = View.extend({ IMAGE_REGEXP.test(file.filename) ? "images" : "files", file, ]), - ...hootbox.map((hoot) => [1, hoot.date, "hoot", hoot]), + ...(query.has_query ? [] : hootbox).map((hoot) => [ + 1, + hoot.date, + "hoot", + hoot, + ]), ...lastlog.map((user) => [1, user.lastseen, "lastlog", user]), ] .sort((a, b) => b[1] - a[1]) diff --git a/public/assets/js/lib/views/stream/index.js b/public/assets/js/lib/views/stream/index.js index d55a9c3..76dbfa8 100644 --- a/public/assets/js/lib/views/stream/index.js +++ b/public/assets/js/lib/views/stream/index.js @@ -1,8 +1,5 @@ var StreamView = View.extend({ - events: {}, - - action: "/api/stream", - keywordAction: "/api/keyword/", + action: "/api/stream/", initialize: function (opt) { // opt.parent = parent @@ -14,21 +11,10 @@ var StreamView = View.extend({ // this.countdown = new Countdown({ parent: this }); }, - load: function (keyword) { + load: function ({ ...target }) { $("body").addClass("index"); - // if (keyword) { - // $(".subtitle").html( - // '<a href="/">< Home</a> · <a href="/keywords">Keywords</a>' - // ); - // this.threadbox.options.latest = false; - // this.threadbox.options.welcome = false; - // $.get(this.keywordAction + keyword, this.populate.bind(this)); - // } else { - // this.hootbox.options.required = true; - // this.threadbox.options.latest = true; - // this.threadbox.options.welcome = true; - // } - $.get(this.action, this.populate.bind(this)); + console.log(target); + $.get(this.action, target, this.populate.bind(this)); }, populate: function (data) { diff --git a/public/assets/js/vendor/view/router.js b/public/assets/js/vendor/view/router.js index 3b0d939..0f977e0 100644 --- a/public/assets/js/vendor/view/router.js +++ b/public/assets/js/vendor/view/router.js @@ -1,82 +1,83 @@ var Router = View.extend({ + routeByHash: false, - routeByHash: false, + go: function (url) { + this.parseRoute(url); + }, - go: function(url){ - this.parseRoute(url) - }, - - pushState: function(url){ - if (this.routeByHash) { - window.location.hash = url - } - else if (window.history) { - window.history.pushState(null, null, url) - } - }, + pushState: function (url) { + if (this.routeByHash) { + window.location.hash = url; + } else if (window.history) { + window.history.pushState(null, null, url); + } + }, - route: function(){ - var path = this.routeByHash ? window.location.hash.substr(0) : window.location.pathname - path = path || "/" - this.originalPath = path - this.parseRoute(path) + route: function () { + var path = this.routeByHash + ? window.location.hash.substr(0) + : window.location.pathname; + path = path || "/"; + this.originalPath = path; + this.parseRoute(path); }, - - parseRoute: function(pathname){ - - pathname = pathname.replace(/^#/, "") - - if (pathname[0] !== "/") { pathname = "/" + pathname } + + parseRoute: function (pathname) { + pathname = pathname.replace(/^#/, ""); + + if (pathname[0] !== "/") { + pathname = "/" + pathname; + } var routes = this.routes, - path = pathname.split("/"); + path = pathname.split("/"); for (var i = 0; i < path.length; i++) { - if (! path[i].length) { - path[i] = null + if (!path[i].length) { + path[i] = null; } } - if (pathname in routes) { - this[this.routes[pathname]]() - return + if (pathname in routes) { + this[this.routes[pathname]](); + return; } - - if (path[path.length-1] == null) { - path.pop() + + if (path[path.length - 1] == null) { + path.pop(); } for (var route in routes) { - var routePath = route.split("/") + var routePath = route.split("/"); if (routePath[1] == path[1]) { - if (routePath[2] && routePath[2].indexOf(":") !== -1 && path[2] && (path[3] === routePath[3]) ) { - this[this.routes[route]](path[2]) - return - } - else if (routePath[2] == path[2]) { + if ( + routePath[2] && + routePath[2].indexOf(":") !== -1 && + path[2] && + path[3] === routePath[3] + ) { + this[this.routes[route]](path[2]); + return; + } else if (routePath[2] == path[2]) { if (routePath[3] && path[3]) { if (routePath[3].indexOf(":") !== -1) { - this[this.routes[route]](path[3]) - return - } - else if (routePath[3] == path[3]) { - this[this.routes[route]]() - return + this[this.routes[route]](path[3]); + return; + } else if (routePath[3] == path[3]) { + this[this.routes[route]](); + return; } + } else if (!routePath[3] && !path[3]) { + this[this.routes[route]](); + return; } - else if (! routePath[3] && ! path[3]) { - this[this.routes[route]]() - return - } - } - else if (! routePath[2] && (! path[2].length || ! path[2])) { - this[this.routes[route]]() - return + } else if (!routePath[2] && (!path[2].length || !path[2])) { + this[this.routes[route]](); + return; } } } - // Redirect to root on 404 - window.location = '/' - } - -}) + // Redirect to root on 404 + window.location = "/"; + }, +}); diff --git a/views/partials/hootstream.ejs b/views/partials/hootstream.ejs index c96291e..b94d62c 100644 --- a/views/partials/hootstream.ejs +++ b/views/partials/hootstream.ejs @@ -7,69 +7,67 @@ <div class="errors"></div> </div> <div id="hootevents"> + </div> + <script class="hootTemplate" type="text/html"> + <div class="{{className}}"> + <a class="userLink" href="/stream/username/{{username}}" style="opacity: {{age_opacity}}"> + {{username}} + </a> + <a class="avatarLink" href="/stream/username/{{username}}" style="opacity: {{age_opacity}}"> + <div class="avatar" title="{{username}}" style="background-image:url({{image}});opacity:{{showAvatar}};"></div> + </a> + <div class="text" style="opacity: {{age_opacity}}">{{hoot}}</div> + <div class="age date" style="opacity: {{age_opacity}}">{{age}}</div> + </div> + </script> - <script class="hootTemplate" type="text/html"> - <div class="{{className}}"> - <a class="userLink" href="/profile/{{username}}" style="opacity: {{age_opacity}}"> - {{username}} - </a> - <a class="avatarLink" href="/profile/{{username}}" style="opacity: {{age_opacity}}"> - <div class="avatar" title="{{username}}" style="background-image:url({{image}});opacity:{{showAvatar}};"></div> - </a> - <div class="text" style="opacity: {{age_opacity}}">{{hoot}}</div> - <div class="age date" style="opacity: {{age_opacity}}">{{age}}</div> - </div> - </script> + <script class="threadTemplate" type="text/html"> + <div class="{{className}}"> + <a class="userLink" href="/stream/username/{{username}}" style="opacity: {{age_opacity}}"> + {{username}} + </a> + <a class="avatarLink" href="/stream/username/{{username}}" style="opacity: {{age_opacity}}"> + <div class="avatar" title="{{username}}" style="background-image:url({{image}});opacity:{{showAvatar}};"></div> + </a> + <div class="text" style="opacity: {{age_opacity}}">{{hoot}}</div> + <div class="keyword">{{keyword_link}}</div> + <div class="commentCount" style="opacity: {{comment_opacity}}">{{comment_count}}</div> + <div class="fileCount" style="opacity: {{file_opacity}}">{{file_count}}</div> + <div class="age date" style="opacity: {{age_opacity}}">{{age}}</div> + </div> + </script> - <script class="threadTemplate" type="text/html"> - <div class="{{className}}"> - <a class="userLink" href="/profile/{{username}}" style="opacity: {{age_opacity}}"> - {{username}} - </a> - <a class="avatarLink" href="/profile/{{username}}" style="opacity: {{age_opacity}}"> - <div class="avatar" title="{{username}}" style="background-image:url({{image}});opacity:{{showAvatar}};"></div> - </a> - <div class="text" style="opacity: {{age_opacity}}">{{hoot}}</div> - <div class="keyword">{{keyword_link}}</div> - <div class="commentCount" style="opacity: {{comment_opacity}}">{{comment_count}}</div> - <div class="fileCount" style="opacity: {{file_opacity}}">{{file_count}}</div> - <div class="age date" style="opacity: {{age_opacity}}">{{age}}</div> - </div> - </script> + <script class="lastlogTemplate" type="text/html"> + <div class="{{className}}" style="opacity: {{opacity}}"> + <a class="userLink" href="/stream/username/{{username}}"> + {{username}} + </a> + <div class="text">{{hoot}}</div> + <div class="age date">{{age}}</div> + </div> + </script> - <script class="lastlogTemplate" type="text/html"> - <div class="{{className}}" style="opacity: {{opacity}}"> - <a class="userLink" href="/profile/{{username}}"> - {{username}} - </a> - <div class="text">{{hoot}}</div> - <div class="age date">{{age}}</div> + <script class="fileTemplate" type="text/html"> + <div class="fileRow"> + <div class="filename" style="opacity: {{age_opacity}}"> + <a href="{{link}}" title="{{id}}" class="file">{{filename}}</a> </div> - </script> - - <script class="fileTemplate" type="text/html"> - <div class="fileRow"> - <div class="filename" style="opacity: {{age_opacity}}"> - <a href="{{link}}" title="{{id}}" class="file">{{filename}}</a> - </div> - <div class="size" style="opacity: {{age_opacity}}"> - {{size}} - </div> - <div class="age {{date_class}}" style="opacity: {{age_opacity}}"> - {{age}} - </div> + <div class="size" style="opacity: {{age_opacity}}"> + {{size}} </div> - </script> - - <script class="imageTemplate" type="text/html"> - <div class="image"><a href="{{link}}"><img src="{{src}}"></a></div> - </script> + <div class="age {{date_class}}" style="opacity: {{age_opacity}}"> + {{age}} + </div> + </div> + </script> - <script class="hootFirstPost" type="text/html"> - </script> + <script class="imageTemplate" type="text/html"> + <div class="image"><a href="{{link}}"><img src="{{src}}"></a></div> + </script> - <script class="hootImages" type="text/html"> - </script> + <script class="hootFirstPost" type="text/html"> + </script> - </div> -</div> + <script class="hootImages" type="text/html"> + </script> +</div>
\ No newline at end of file |
