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");
this.hootTemplate = this.$(".hootTemplate").html();
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":
case "readMore":
event.preventDefault();
console.log(">>", url.pathname);
app.router.pushState(url.pathname);
app.router.go(url.pathname);
break;
}
// this.parent.onKeyword(keyword)
},
load: function (data) {
this.$hootevents.empty();
const { order, threadLookup } = this.agglutinate(data);
// console.log(data, threadLookup, order);
const $els = order.map(
function (item) {
// console.log(item.type, item.thread_id);
return item.type === "thread"
? this.renderThread({
...threadLookup[item.thread_id],
query: data.query,
}).reduce(
($el, $item) => $el.append($item),
$("
")
)
: item.type === "hoot"
? this.renderHoot(item.data)
: item.type === "lastlog"
? this.renderLastlog(item.data)
: "Unknown item";
}.bind(this)
);
this.$hootevents.append($els);
},
render: (template, object) => {
if (!template) {
console.error("No template", object);
return $("
No template
");
}
const rendered = Object.entries(object).reduce(
(newTemplate, [key, value]) =>
newTemplate.replace(new RegExp(`{{${key}}}`, "g"), value),
template
);
return $(rendered);
},
renderLastlog: function ({ username, lastseen }) {
const age = get_age(lastseen);
const age_ago = age === "now" ? age : `${age} ago`;
return this.render(this.lastlogTemplate, {
className: "hoot streamLastlog",
username,
age,
opacity: 0.6,
showAvatar: 0,
hoot: "last seen " + age_ago,
});
},
renderHoot: function ({
id,
thread,
date,
username,
hoot,
comment,
hidden,
className,
showAvatar,
template,
...options
}) {
// console.log(hoot, comment);
return this.render(template || this.hootTemplate, {
username,
className: className ? `hoot ${className}` : "hoot",
image: profile_image(username),
showAvatar: showAvatar === false ? 0 : 1,
hoot:
hoot || "
" + tidy_urls(comment, true) + "
",
age: get_age(date),
age_opacity: get_age_opacity(date),
...options,
});
},
renderHoots: function ({ hoots, className }) {
const els = [];
for (hoot of hoots) {
els.push(this.renderHoot({ ...hoot, className }));
}
return els;
},
renderThread: function ({ query, thread, comments, files, images }) {
if (!thread || !thread.length) {
console.error("Missing thread");
console.error(thread, comments, files, images);
return ["
Missing thread!
"];
}
thread = thread.shift();
console.log(query, thread.id);
const isViewingThread = query.thread === thread.id;
// console.log(thread, comments, files, images);
const postedToday = +new Date() / 1000 - thread.lastmodified < 86400;
const age_opacity = get_age_opacity(thread.lastmodified);
return [
"
",
this.renderHoot({
template: this.threadTemplate,
hoot: `
${thread.title}`,
keyword_link: thread.keyword
? `
${thread.keyword}`
: "",
username: thread.username,
className: postedToday ? "isRecent" : "",
date: thread.lastmodified,
file_count: `${files.length || 0} f.`,
file_opacity: age_opacity * get_size_opacity(files.length),
comment_count: `${comments.length || 0} c.`,
comment_opacity: age_opacity * get_size_opacity(comments.length),
}),
this.renderFiles(postedToday ? files : files.slice(0, 10)),
...this.renderHoots({
hoots: comments.slice(0, 1).map(trimComment(isViewingThread)),
tag: "first_post",
}),
...this.renderHoots({
hoots: postedToday ? comments.slice(1) : comments.slice(1).slice(-5),
}),
"
",
];
// say "in ... "
// audio player OR recent file list
// recent 3 comments
},
renderFiles: function (files) {
if (!files.length) {
return null;
}
const $table = $("
");
for (const file of files) {
const $el = this.renderFile(file);
$table.append($el);
}
return $table;
},
renderFile: function (file) {
var size = hush_size(file.size);
var datetime = verbose_date(file.date, true);
var date_class = carbon_date(file.date);
var link = make_link(file);
return this.render(this.fileTemplate, {
id: file.id,
username: file.username,
link,
filename: file.filename,
age: get_age(file.date),
age_opacity: get_age_opacity(file.date),
date_class,
date: datetime[0],
// time: datetime[1],
// size_class: size[0],
size: size[1],
});
},
agglutinate: ({ query, threads, comments, files, hootbox, lastlog }) =>
[
...threads.map((thread) => [
thread.id,
thread.createdate,
"thread",
thread,
]),
...comments
.filter((comment) => comment.thread !== 1)
.map((comment) => [comment.thread, comment.date, "comments", comment]),
...files.map((file) => [
file.thread,
file.date,
IMAGE_REGEXP.test(file.filename) ? "images" : "files",
file,
]),
...(query.has_query ? [] : hootbox).map((hoot) => [
1,
hoot.date,
"hoot",
hoot,
]),
...(query.has_query ? [] : lastlog).map((user) => [
1,
user.lastseen,
"lastlog",
user,
]),
]
.sort((a, b) => b[1] - a[1])
.reduce(
({ threadLookup, order }, [thread_id, date, type, data]) => {
if (type === "hoot") {
order.push({ type: "hoot", date, data });
} else if (type === "lastlog") {
order.push({ type: "lastlog", date, data });
} else if (thread_id !== 1) {
if (!(thread_id in threadLookup)) {
threadLookup[thread_id] = {
thread: [],
comments: [],
files: [],
images: [],
};
order.push({ type: "thread", date, thread_id });
}
threadLookup[thread_id][type].push(data);
}
return { threadLookup, order };
},
{ threadLookup: {}, order: [] }
),
});