summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjulian laplace <julescarbon@gmail.com>2022-10-26 17:05:14 +0200
committerjulian laplace <julescarbon@gmail.com>2022-10-26 17:05:14 +0200
commitdd72ab05da17309fd5ee6005cdc1fae686b5fa9e (patch)
tree9edc695fffa73a85d94e571f44c7d4c97de71654
parent3de2a5872fd0481568e918a1ea798b3f75ace610 (diff)
filter by keyword, thread, or username
-rw-r--r--bucky/app/api.js11
-rw-r--r--bucky/app/bucky.js19
-rw-r--r--bucky/app/pages.js16
-rw-r--r--bucky/db/index.js30
-rw-r--r--bucky/util/middleware.js54
-rw-r--r--public/assets/css/hootstream.css3
-rw-r--r--public/assets/js/lib/router.js20
-rw-r--r--public/assets/js/lib/views/stream/hootstream.js40
-rw-r--r--public/assets/js/lib/views/stream/index.js22
-rw-r--r--public/assets/js/vendor/view/router.js117
-rw-r--r--views/partials/hootstream.ejs114
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="/">&lt; Home</a> &middot; <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