diff options
Diffstat (limited to 'public/assets')
| -rw-r--r-- | public/assets/css/bucky.css | 4 | ||||
| -rw-r--r-- | public/assets/js/lib/router.js | 212 | ||||
| -rw-r--r-- | public/assets/js/lib/views/details/files.js | 4 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/hootbox.js | 91 | ||||
| -rw-r--r-- | public/assets/js/lib/views/index/index.js | 78 | ||||
| -rw-r--r-- | public/assets/js/lib/views/stream/hootform.js | 42 | ||||
| -rw-r--r-- | public/assets/js/lib/views/stream/hootstream.js | 205 | ||||
| -rw-r--r-- | public/assets/js/lib/views/stream/index.js | 69 | ||||
| -rw-r--r-- | public/assets/js/util/format.js | 23 |
9 files changed, 542 insertions, 186 deletions
diff --git a/public/assets/css/bucky.css b/public/assets/css/bucky.css index 60c1e9a..5ecb837 100644 --- a/public/assets/css/bucky.css +++ b/public/assets/css/bucky.css @@ -28,6 +28,7 @@ body.black { background: #211; color: #eee; } + small { font-size: 10px; } @@ -1626,7 +1627,8 @@ audio { #search .search_hit a { color: #; } - a { + a, + a:link { color: #38f; } a:visited { diff --git a/public/assets/js/lib/router.js b/public/assets/js/lib/router.js index ebdfa78..bedf0b9 100644 --- a/public/assets/js/lib/router.js +++ b/public/assets/js/lib/router.js @@ -1,133 +1,139 @@ var SiteRouter = Router.extend({ + el: "body", - el: "body", - - routes: { - "/": 'login', - "/index/:keyword": 'index', - "/index": 'index', - "/login": 'login', - "/logout": 'logout', - "/signup": 'signup', - "/details/:id": 'details', - "/details/:id/settings": 'threadSettings', - "/post": 'post', - "/post/:keyword": 'post', - "/comment/:id/edit": 'editComment', - "/keywords": 'keywords', - "/search": 'search', - "/mail": 'mailbox', - "/mail/:mailbox": 'mailbox', - "/mail/compose": 'compose', - "/mail/compose/:username": 'compose', - "/mail/read/:id": 'message', - "/mail/reply/:id": 'compose', - "/users": 'users', - "/users/all": 'usersAll', - "/profile": 'profile', - "/profile/:username": 'profile', - "/profile/:username/edit": 'editProfile', - "/adminz": 'adminz', - }, - - initialize: function(){ - $(".logout").click(() => this.logout()) + routes: { + "/": "login", + "/index/:keyword": "index", + "/index": "index", + "/stream": "stream", + "/login": "login", + "/logout": "logout", + "/signup": "signup", + "/details/:id": "details", + "/details/:id/settings": "threadSettings", + "/post": "post", + "/post/:keyword": "post", + "/comment/:id/edit": "editComment", + "/keywords": "keywords", + "/search": "search", + "/mail": "mailbox", + "/mail/:mailbox": "mailbox", + "/mail/compose": "compose", + "/mail/compose/:username": "compose", + "/mail/read/:id": "message", + "/mail/reply/:id": "compose", + "/users": "users", + "/users/all": "usersAll", + "/profile": "profile", + "/profile/:username": "profile", + "/profile/:username/edit": "editProfile", + "/adminz": "adminz", }, - - index: function(keyword){ - app.view = new IndexView () - app.view.load(keyword) + + initialize: function () { + $(".logout").click(() => this.logout()); }, - - login: function(){ - app.view = new LoginView () + + index: function (keyword) { + app.view = new IndexView(); + app.view.load(keyword); }, - - logout: function(){ - auth.log_out() - window.location.href = "/logout" + + stream: function (keyword) { + app.view = new StreamView(); + app.view.load(keyword); }, - - signup: function(){ - app.view = new SignupView () + + login: function () { + app.view = new LoginView(); }, - - details: function(id){ - app.view = new DetailsView () - app.view.load(id) + + logout: function () { + auth.log_out(); + window.location.href = "/logout"; }, - threadSettings: function(id){ - app.view = new DetailsView ({ settings: true }) - app.view.load(id) + signup: function () { + app.view = new SignupView(); }, - - editComment: function(id){ - app.view = new EditCommentForm () - app.view.load(id) + + details: function (id) { + app.view = new DetailsView(); + app.view.load(id); }, - keywords: function(){ - app.view = new KeywordsView () - app.view.load() + threadSettings: function (id) { + app.view = new DetailsView({ settings: true }); + app.view.load(id); }, - mailbox: function(box){ - app.view = new MailboxView () - app.view.load(box) + editComment: function (id) { + app.view = new EditCommentForm(); + app.view.load(id); }, - - message: function(id){ - app.view = new MessageView () - app.view.load(id) + + keywords: function () { + app.view = new KeywordsView(); + app.view.load(); }, - post: function(keyword){ - app.view = new ThreadForm () - app.view.load(keyword || "") + mailbox: function (box) { + app.view = new MailboxView(); + app.view.load(box); }, - - users: function(username){ - app.view = new UsersView () - app.view.load() + + message: function (id) { + app.view = new MessageView(); + app.view.load(id); }, - usersAll: function(username){ - app.view = new UsersView ({ all: true }) - app.view.load() + post: function (keyword) { + app.view = new ThreadForm(); + app.view.load(keyword || ""); }, - - profile: function(username){ - app.view = new ProfileView () - app.view.load(username || auth.user.username) + + users: function (username) { + app.view = new UsersView(); + app.view.load(); }, - - editProfile: function(username){ - app.view = new ProfileForm () - app.view.load(username || auth.user.username) + + usersAll: function (username) { + app.view = new UsersView({ all: true }); + app.view.load(); }, - - compose: function(username){ - app.view = new ComposeView () - app.view.load(username) + + profile: function (username) { + app.view = new ProfileView(); + app.view.load(username || auth.user.username); }, - search: function(){ - app.view = new SearchResults () - app.view.load() + editProfile: function (username) { + app.view = new ProfileForm(); + app.view.load(username || auth.user.username); }, - adminz: function(){ - app.view = new AdminView () - app.view.load() + compose: function (username) { + app.view = new ComposeView(); + app.view.load(username); }, - - error404: function(){ - $("content").hide() - $("#error_404").show() - $("h1").html("404 not found") - $("body").removeClass("loading") - $("#error_404").append('<a href="/"><img src="/assets/img/castro.jpg"></a>') + + search: function () { + app.view = new SearchResults(); + app.view.load(); }, -})
\ No newline at end of file + adminz: function () { + app.view = new AdminView(); + app.view.load(); + }, + + error404: function () { + $("content").hide(); + $("#error_404").show(); + $("h1").html("404 not found"); + $("body").removeClass("loading"); + $("#error_404").append( + '<a href="/"><img src="/assets/img/castro.jpg"></a>' + ); + }, +}); diff --git a/public/assets/js/lib/views/details/files.js b/public/assets/js/lib/views/details/files.js index bbea7da..cd6b776 100644 --- a/public/assets/js/lib/views/details/files.js +++ b/public/assets/js/lib/views/details/files.js @@ -46,8 +46,8 @@ var FilesView = FormView.extend({ this.resort(sort); } }, - - add: function(files) { + + add: function (files) { this.files = this.files.concat(files); this.resort(this.currentSort, this.reversed ? "desc" : "asc"); }, diff --git a/public/assets/js/lib/views/index/hootbox.js b/public/assets/js/lib/views/index/hootbox.js index 72fbfde..a5d2270 100644 --- a/public/assets/js/lib/views/index/hootbox.js +++ b/public/assets/js/lib/views/index/hootbox.js @@ -1,56 +1,63 @@ var HootBox = FormView.extend({ - el: "#hootbox", - - events: { - }, - + + events: {}, + action: "/api/thread/1/comment", - - initialize: function(){ - this.__super__.initialize.call(this) - this.template = this.$(".template").html() - this.$hoots = this.$("#hoots") - this.$comment = this.$("[name=comment]") + + initialize: function () { + this.__super__.initialize.call(this); + this.template = this.$(".template").html(); + this.$hoots = this.$("#hoots"); + this.$comment = this.$("[name=comment]"); + }, + + show: function () { + this.$el.show(); }, - - load: function(comments){ - if (!comments || !comments.length && ! this.options.required) { - this.$el.hide() - return + + hide: function () { + this.$el.hide(); + }, + + load: function (comments) { + if (!comments || (!comments.length && !this.options.required)) { + this.$el.hide(); + return; } - comments.forEach(this.appendComment.bind(this)) + comments.forEach(this.appendComment.bind(this)); }, - - parse: function(comment){ - var t = this.template.replace(/{{image}}/g, profile_image(comment.username)) - .replace(/{{username}}/g, comment.username) - .replace(/{{comment}}/g, tidy_urls(comment.comment, true)) - var $t = $(t) - return $t + + parse: function (comment) { + var t = this.template + .replace(/{{image}}/g, profile_image(comment.username)) + .replace(/{{username}}/g, comment.username) + .replace(/{{comment}}/g, tidy_urls(comment.comment, true)); + var $t = $(t); + return $t; }, - - prependComment: function(comment){ - var $el = this.parse(comment) - this.$hoots.prepend($el) + + prependComment: function (comment) { + var $el = this.parse(comment); + this.$hoots.prepend($el); }, - appendComment: function(comment){ - var $el = this.parse(comment) - this.$hoots.append($el) + appendComment: function (comment) { + var $el = this.parse(comment); + this.$hoots.append($el); }, - validate: function(){ - var errors = [] - var comment = $("[name=comment]").val() - if (! comment || ! comment.length) { - errors.push("Please enter a comment.") + validate: function () { + var errors = []; + var comment = $("[name=comment]").val(); + if (!comment || !comment.length) { + errors.push("Please enter a comment."); } - return errors.length ? errors : null + return errors.length ? errors : null; }, - success: function(data){ - this.prependComment(data.comment) - this.$("[name=comment]").val("") - } -})
\ No newline at end of file + success: function (data) { + this.prependComment(data.comment); + this.$("[name=comment]").val(""); + }, +}); diff --git a/public/assets/js/lib/views/index/index.js b/public/assets/js/lib/views/index/index.js index 08da455..cd5f650 100644 --- a/public/assets/js/lib/views/index/index.js +++ b/public/assets/js/lib/views/index/index.js @@ -1,57 +1,59 @@ var IndexView = View.extend({ - - events: { - }, + events: {}, action: "/api/index", keywordAction: "/api/keyword/", - initialize: function(opt){ + initialize: function (opt) { // opt.parent = parent - this.hootbox = new HootBox ({ parent: this }) - this.threadbox = new ThreadBox ({ parent: this }) - this.lastlog = new LastLog ({ parent: this }) - this.countdown = new Countdown ({ parent: this }) + this.hootbox = new HootBox({ parent: this }); + this.threadbox = new ThreadBox({ parent: this }); + this.lastlog = new LastLog({ parent: this }); + this.countdown = new Countdown({ parent: this }); }, - load: function(keyword){ - $("body").addClass("index") + load: function (keyword) { + $("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)) + $(".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)); } }, - populate: function(data){ - $("body").removeClass('loading') - this.hootbox.load(data.hootbox) - this.threadbox.load(data) - this.lastlog.load(data.lastlog) + populate: function (data) { + $("body").removeClass("loading"); + this.data = data; + this.hootbox.load(data.hootbox); + this.threadbox.load(data); + this.lastlog.load(data.lastlog); if (data.mail.count) { - $(".alert").show().html( - "<a href='/mail'>" + - "You have " + - data.mail.count + - " new message" + - courtesy_s(data.mail.count) + - "!</a>") + $(".alert") + .show() + .html( + "<a href='/mail'>" + + "You have " + + data.mail.count + + " new message" + + courtesy_s(data.mail.count) + + "!</a>" + ); if (is_mobile) { - $("#content").prepend( $(".alert") ) + $("#content").prepend($(".alert")); } } - $(".search_form input").focus() + $(".search_form input").focus(); }, - success: function(){ - window.location.href = "/index" + success: function () { + window.location.href = "/index"; }, - -}) +}); diff --git a/public/assets/js/lib/views/stream/hootform.js b/public/assets/js/lib/views/stream/hootform.js new file mode 100644 index 0000000..368e616 --- /dev/null +++ b/public/assets/js/lib/views/stream/hootform.js @@ -0,0 +1,42 @@ +var HootForm = FormView.extend({ + el: "#hootform", + + events: {}, + + action: "/api/thread/1/comment", + + initialize: function (opt) { + this.__super__.initialize.call(this); + this.$comment = this.$("[name=comment]"); + }, + + show: function () { + this.$el.show(); + }, + + hide: function () { + this.$el.hide(); + }, + + load: function (comments) { + if (!comments || (!comments.length && !this.options.required)) { + this.$el.hide(); + return; + } + comments.forEach(this.appendComment.bind(this)); + }, + + validate: function () { + var errors = []; + var comment = $("[name=comment]").val(); + if (!comment || !comment.length) { + errors.push("Please enter a comment."); + } + return errors.length ? errors : null; + }, + + success: function (data) { + this.parent.onComment(data.comment); + this.$("[name=comment]").val(""); + }, +}); diff --git a/public/assets/js/lib/views/stream/hootstream.js b/public/assets/js/lib/views/stream/hootstream.js new file mode 100644 index 0000000..cdc7acf --- /dev/null +++ b/public/assets/js/lib/views/stream/hootstream.js @@ -0,0 +1,205 @@ +const IMAGE_REGEXP = /(.gif|.jpg|.jpeg|.png)$/gi; + +var HootStream = View.extend({ + el: "#hootstream", + + initialize: function ({ parent }) { + this.parent = parent; + this.$hootevents = this.$("#hootevents"); + this.hootTemplate = this.$(".hootTemplate").html(); + this.lastlogTemplate = this.$(".lastlogTemplate").html(); + this.fileTemplate = this.$(".fileTemplate").html(); + }, + + load: function (data) { + this.$hootevents.empty(); + const { order, threadLookup } = this.agglutinate(data); + console.log(data, threadLookup, order); + const $els = order.map( + function (item) { + return item.type === "thread" + ? this.renderThread(threadLookup[item.thread_id]).reduce( + ($el, $item) => $el.append($item), + $("<div class='thread'>") + ) + : 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) => { + 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, + }) { + return this.render(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), + }); + }, + + renderHoots: function ({ hoots, className }) { + const els = []; + for (hoot of hoots) { + els.push(this.renderHoot({ ...hoot, className })); + } + return els; + }, + + renderThread: function ({ thread, comments, files, images }) { + thread = thread.shift(); + // console.log(thread, comments, files, images); + const postedToday = +new Date() / 1000 - thread.date < 86400; + + if (postedToday) { + return [ + "<div class='divider dark'></div>", + this.renderHoot({ + hoot: `<a class="threadLink" href="/details/${thread.id}">${thread.title}</a>`, + username: thread.username, + className: "isRecent", + date: thread.lastmodified, + ...this.renderFiles(files), + ...this.renderHoots({ + hoots: comments.slice(0, 1), + tag: "first_post", + }), + ...this.renderHoots({ hoots: comments.slice(1) }), + }), + "<div class='divider'></div>", + ]; + // display very proud headline + // title, avatar, first image, full file list, + } else { + return [ + "<div class='divider dark'></div>", + this.renderHoot({ + hoot: `<a class="threadLink" href="/details/${thread.id}">${thread.title}</a>`, + username: thread.username, + className: "", + date: thread.lastmodified, + }), + this.renderFiles(files.slice(0, 3)), + ...this.renderHoots({ hoots: comments.slice(0, 1), tag: "first_post" }), + ...this.renderHoots({ hoots: comments.slice(1).slice(-3) }), + "<div class='divider'></div>", + ]; + // say "in ... " + // audio player OR recent file list + // recent 3 comments + } + }, + + renderFiles: function (files) { + if (!files.length) { + return null; + } + const $table = $("<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), + date_class, + // date: datetime[0], + // time: datetime[1], + size_class: size[0], + size: size[1], + }); + }, + + agglutinate: ({ 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, + ]), + ...hootbox.map((hoot) => [1, hoot.date, "hoot", hoot]), + ...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: [] } + ), +}); diff --git a/public/assets/js/lib/views/stream/index.js b/public/assets/js/lib/views/stream/index.js new file mode 100644 index 0000000..d55a9c3 --- /dev/null +++ b/public/assets/js/lib/views/stream/index.js @@ -0,0 +1,69 @@ +var StreamView = View.extend({ + events: {}, + + action: "/api/stream", + keywordAction: "/api/keyword/", + + initialize: function (opt) { + // opt.parent = parent + // this.hootbox = new HootBox({ parent: this }); + this.hootform = new HootForm({ parent: this }); + this.hootstream = new HootStream({ parent: this }); + // this.threadbox = new ThreadBox({ parent: this }); + // this.lastlog = new LastLog({ parent: this }); + // this.countdown = new Countdown({ 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; + // 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)); + }, + + populate: function (data) { + $("body").removeClass("loading"); + this.data = data; + this.hootstream.load(data); + // this.hootbox.load(data.hootbox); + // this.hootbox.hide(); + // this.threadbox.load(data); + // this.lastlog.load(data.lastlog); + if (data.mail.count) { + $(".alert") + .show() + .html( + "<a href='/mail'>" + + "You have " + + data.mail.count + + " new message" + + courtesy_s(data.mail.count) + + "!</a>" + ); + if (is_mobile) { + $("#content").prepend($(".alert")); + } + } + $(".search_form input").focus(); + }, + + onComment: function (comment) { + this.data.hootbox.comments.push(comment); + this.data.hootstream.comments.push(comment); + this.populate(this.data); + }, + + success: function () { + window.location.href = "/index"; + }, +}); diff --git a/public/assets/js/util/format.js b/public/assets/js/util/format.js index 6a23430..e6104e5 100644 --- a/public/assets/js/util/format.js +++ b/public/assets/js/util/format.js @@ -257,6 +257,29 @@ function get_age(t, long) { age /= 12; return r(age) + (long ? " years" : "y"); } +const ages = { + newest: 60 * 10, + newer: 60 * 60, + new: 60 * 60 * 8, + cool: 60 * 60 * 24, + cold: 60 * 60 * 24 * 7, + colder: 60 * 60 * 24 * 7 * 4, + coldest: 60 * 60 * 24 * 7 * 4 * 12, +}; +function get_age_opacity(t) { + var age = Math.abs(Date.now() / 1000 - t); + if (age < ages.newest) return 1; + if (age < ages.newer) + return lerp(norm(age, ages.newest, ages.newer), 1.0, 0.95); + if (age < ages.new) return lerp(norm(age, ages.newer, ages.new), 0.95, 0.9); + if (age < ages.cool) return lerp(norm(age, ages.new, ages.cool), 0.9, 0.85); + if (age < ages.cold) return lerp(norm(age, ages.cool, ages.cold), 0.85, 0.6); + if (age < ages.colder) + return lerp(norm(age, ages.cold, ages.colder), 0.6, 0.5); + if (age < ages.coldest) + return lerp(norm(age, ages.colder, ages.coldest), 0.5, 0.4); + return 0.39; +} function tidy_urls(s, short_urls) { var ret = s |
