diff --git a/server/content/content.coffee b/server/content/content.coffee index d492b741..6b9f54bd 100644 --- a/server/content/content.coffee +++ b/server/content/content.coffee @@ -17,6 +17,7 @@ class @Content @projects = {} @tags = {} + @sorted_tags = [] @project_count = 0 @user_count = 0 @@ -116,6 +117,7 @@ class @Content if not tag? tag = new Tag(t) @tags[t] = tag + @sorted_tags.push tag tag.add project return @@ -151,17 +153,20 @@ class @Content time = Date.now() @top_projects.sort (a,b)-> b.likes-a.likes @new_projects.sort (a,b)-> b.first_published-a.first_published + @sorted_tags.sort (a,b)-> b.uses+b.num_users*10-a.uses-a.num_users*10 - return if @top_projects.length<2 or @new_projects.length<2 + return if @top_projects.length<5 now = Date.now() - maxLikes = Math.max(1,@top_projects[0].likes) - maxAge = now-@new_projects[@new_projects.length-1].first_published + maxLikes = Math.max(1,@top_projects[4].likes) + + fade = (x)-> + 1-Math.max(0,Math.min(1,x)) note = (p)-> - hours = Math.max(0,(now-p.first_published))/1000/3600 - agemark = Math.exp(-hours/24/7) - p.likes/maxLikes*50+50*agemark + recent = fade (now-p.first_published)/1000/3600/24/4 + rating = p.likes/maxLikes*(.15+2*fade((now-p.first_published)/1000/3600/24/180)) + recent+rating @hot_projects.sort (a,b)-> note(b)-note(a) @@ -207,6 +212,7 @@ class @Content if not tag? tag = new Tag(t) @tags[t] = tag + @sorted_tags.push tag tag.add project removeProjectTag:(project,t)-> diff --git a/server/content/content.js b/server/content/content.js index b5f530d3..9bb42a2a 100644 --- a/server/content/content.js +++ b/server/content/content.js @@ -27,6 +27,7 @@ this.Content = (function() { this.tokens = {}; this.projects = {}; this.tags = {}; + this.sorted_tags = []; this.project_count = 0; this.user_count = 0; this.guest_count = 0; @@ -156,6 +157,7 @@ this.Content = (function() { if (tag == null) { tag = new Tag(t); this.tags[t] = tag; + this.sorted_tags.push(tag); } tag.add(project); } @@ -204,7 +206,7 @@ this.Content = (function() { }; Content.prototype.sortPublicProjects = function() { - var maxAge, maxLikes, note, now, time; + var fade, maxLikes, note, now, time; time = Date.now(); this.top_projects.sort(function(a, b) { return b.likes - a.likes; @@ -212,17 +214,22 @@ this.Content = (function() { this.new_projects.sort(function(a, b) { return b.first_published - a.first_published; }); - if (this.top_projects.length < 2 || this.new_projects.length < 2) { + this.sorted_tags.sort(function(a, b) { + return b.uses + b.num_users * 10 - a.uses - a.num_users * 10; + }); + if (this.top_projects.length < 5) { return; } now = Date.now(); - maxLikes = Math.max(1, this.top_projects[0].likes); - maxAge = now - this.new_projects[this.new_projects.length - 1].first_published; + maxLikes = Math.max(1, this.top_projects[4].likes); + fade = function(x) { + return 1 - Math.max(0, Math.min(1, x)); + }; note = function(p) { - var agemark, hours; - hours = Math.max(0, now - p.first_published) / 1000 / 3600; - agemark = Math.exp(-hours / 24 / 7); - return p.likes / maxLikes * 50 + 50 * agemark; + var rating, recent; + recent = fade((now - p.first_published) / 1000 / 3600 / 24 / 4); + rating = p.likes / maxLikes * (.15 + 2 * fade((now - p.first_published) / 1000 / 3600 / 24 / 180)); + return recent + rating; }; this.hot_projects.sort(function(a, b) { return note(b) - note(a); @@ -285,6 +292,7 @@ this.Content = (function() { if (tag == null) { tag = new Tag(t); this.tags[t] = tag; + this.sorted_tags.push(tag); } return tag.add(project); }; diff --git a/server/content/tag.coffee b/server/content/tag.coffee index 3b830038..af43345e 100644 --- a/server/content/tag.coffee +++ b/server/content/tag.coffee @@ -2,11 +2,16 @@ class @Tag constructor:(@tag)-> @targets = {} @uses = 0 + @users = {} + @num_users = 0 add:(target)-> if not @targets[target.id]? @targets[target.id] = target @uses++ + if target.owner? and target.owner.id? and not @users[target.owner.id] + @users[target.owner.id] = true + @num_users += 1 remove:(target)-> if @targets[target.id]? diff --git a/server/content/tag.js b/server/content/tag.js index 0a280d74..f8fe2248 100644 --- a/server/content/tag.js +++ b/server/content/tag.js @@ -3,12 +3,18 @@ this.Tag = (function() { this.tag = tag; this.targets = {}; this.uses = 0; + this.users = {}; + this.num_users = 0; } Tag.prototype.add = function(target) { if (this.targets[target.id] == null) { this.targets[target.id] = target; - return this.uses++; + this.uses++; + if ((target.owner != null) && (target.owner.id != null) && !this.users[target.owner.id]) { + this.users[target.owner.id] = true; + return this.num_users += 1; + } } }; diff --git a/server/session/session.coffee b/server/session/session.coffee index f0504622..19e4519b 100644 --- a/server/session/session.coffee +++ b/server/session/session.coffee @@ -875,9 +875,36 @@ class @Session source = @content.hot_projects list = [] - for p,i in source - break if list.length>=300 + tags = if Array.isArray(data.tags) then data.tags else [] + search = if typeof data.search == "string" then data.search else "" + search = search.trim() + offset = data.offset or 0 + + for i in [offset..source.length-1] by 1 + p = source[i] + + break if list.length >= 25 + offset = i+1 + if p.public and not p.deleted and not p.owner.flags.censored + if search + found = false + found |= p.title.toLowerCase().includes(search) + found |= p.description.toLowerCase().includes(search) + found |= p.owner.nick.toLowerCase().includes(search) + for t in p.tags + found |= t.includes(search) + + continue if not found + + if tags.length > 0 + found = false + for t in tags + if p.tags.indexOf(t)>=0 + found = true + break + continue if not found + list.push id: p.id title: p.title @@ -899,9 +926,16 @@ class @Session libs: p.libs tabs: p.tabs + tags = [] + for t in @content.sorted_tags + tags.push t.tag + break if tags.length>50 + @send name: "public_projects" list: list + tags: tags + offset: offset request_id: data.request_id getPublicProject:(msg)-> diff --git a/server/session/session.js b/server/session/session.js index 2de1117e..99ae0719 100644 --- a/server/session/session.js +++ b/server/session/session.js @@ -1377,7 +1377,7 @@ this.Session = (function() { }; Session.prototype.getPublicProjects = function(data) { - var i, j, len1, list, p, source; + var found, i, j, k, l, len1, len2, len3, list, m, offset, p, ref, ref1, ref2, ref3, search, source, t, tags; switch (data.ranking) { case "new": source = this.content.new_projects; @@ -1389,12 +1389,44 @@ this.Session = (function() { source = this.content.hot_projects; } list = []; - for (i = j = 0, len1 = source.length; j < len1; i = ++j) { + tags = Array.isArray(data.tags) ? data.tags : []; + search = typeof data.search === "string" ? data.search : ""; + search = search.trim(); + offset = data.offset || 0; + for (i = j = ref = offset, ref1 = source.length - 1; j <= ref1; i = j += 1) { p = source[i]; - if (list.length >= 300) { + if (list.length >= 25) { break; } + offset = i + 1; if (p["public"] && !p.deleted && !p.owner.flags.censored) { + if (search) { + found = false; + found |= p.title.toLowerCase().includes(search); + found |= p.description.toLowerCase().includes(search); + found |= p.owner.nick.toLowerCase().includes(search); + ref2 = p.tags; + for (k = 0, len1 = ref2.length; k < len1; k++) { + t = ref2[k]; + found |= t.includes(search); + } + if (!found) { + continue; + } + } + if (tags.length > 0) { + found = false; + for (l = 0, len2 = tags.length; l < len2; l++) { + t = tags[l]; + if (p.tags.indexOf(t) >= 0) { + found = true; + break; + } + } + if (!found) { + continue; + } + } list.push({ id: p.id, title: p.title, @@ -1419,9 +1451,20 @@ this.Session = (function() { }); } } + tags = []; + ref3 = this.content.sorted_tags; + for (m = 0, len3 = ref3.length; m < len3; m++) { + t = ref3[m]; + tags.push(t.tag); + if (tags.length > 50) { + break; + } + } return this.send({ name: "public_projects", list: list, + tags: tags, + offset: offset, request_id: data.request_id }); }; diff --git a/static/js/appstate.coffee b/static/js/appstate.coffee index fbc695bb..031c26c4 100644 --- a/static/js/appstate.coffee +++ b/static/js/appstate.coffee @@ -53,18 +53,10 @@ class @AppState else s = location.pathname.split("/") if s[2]? and s[3]? - if @app.explore.projects? - p = @app.explore.findProject(s[2],s[3]) - if p - @app.explore.openProject(p) - @app.appui.setMainSection "explore" - else - @app.explore.update ()=> - p = @app.explore.findProject(s[2],s[3]) - if p - @app.explore.openProject(p) - @app.appui.setMainSection "explore" - + p = @app.explore.findProject(s[2],s[3]) + if p + @app.explore.openProject(p) + @app.appui.setMainSection "explore" initState:()-> if location.pathname.startsWith("/login/") diff --git a/static/js/appstate.js b/static/js/appstate.js index 54b1ea01..5783c031 100644 --- a/static/js/appstate.js +++ b/static/js/appstate.js @@ -74,22 +74,10 @@ this.AppState = (function() { } else { s = location.pathname.split("/"); if ((s[2] != null) && (s[3] != null)) { - if (this.app.explore.projects != null) { - p = this.app.explore.findProject(s[2], s[3]); - if (p) { - this.app.explore.openProject(p); - return this.app.appui.setMainSection("explore"); - } - } else { - return this.app.explore.update((function(_this) { - return function() { - p = _this.app.explore.findProject(s[2], s[3]); - if (p) { - _this.app.explore.openProject(p); - return _this.app.appui.setMainSection("explore"); - } - }; - })(this)); + p = this.app.explore.findProject(s[2], s[3]); + if (p) { + this.app.explore.openProject(p); + return this.app.appui.setMainSection("explore"); } } } diff --git a/static/js/explore/explore.coffee b/static/js/explore/explore.coffee index c382184a..7872c268 100644 --- a/static/js/explore/explore.coffee +++ b/static/js/explore/explore.coffee @@ -5,6 +5,12 @@ class @Explore @app.appui.setMainSection "explore",true @sort = "hot" + @active_tags = [] + @search = "" + + @tags = [] + + @visited_projects = {} @sort_types = ["hot","new","top"] @@ -24,16 +30,25 @@ class @Explore else e.classList.remove s document.querySelector("#explore-sort-button span").innerText = @app.translator.get @sort.substring(0,1).toUpperCase()+@sort.substring(1) - @loadProjects() + @query() document.getElementById("explore-search-input").addEventListener "input",()=> - @loadProjects() + @search = document.getElementById("explore-search-input").value + if @search_timeout? + clearTimeout @search_timeout + @search_timeout = setTimeout (()=>@query()),1500 document.getElementById("explore-contents").addEventListener "scroll",()=> contents = document.getElementById "explore-box-list" scrollzone = document.getElementById "explore-contents" - while @remaining? and @remaining.length>0 and contents.getBoundingClientRect().height h1-h2-100 #contents.getBoundingClientRect().height < scrollzone.scrollTop+window.innerHeight*2 + if not @completed + pos = @projects.length + if pos != @query_position + @query pos + return @cloned = {} @@ -79,21 +94,21 @@ class @Explore findBestTag:(p)-> tag = p.tags[0] - score = 0 + score = @tags.indexOf(tag) + if score < 0 then score = 1000 for t in p.tags - if @tag_scores[t]>score - score = @tag_scores[t] + index = @tags.indexOf t + if index >= 0 and index < score + score = index tag = t tag createProjectBox:(p)-> - if @boxes[p.owner+"/"+p.slug] - return @boxes[p.owner+"/"+p.slug] element = document.createElement "div" element.classList.add "explore-project-box" - if p.tags.length>0 and @tag_scores? + if p.tags.length>0 tag = document.createElement "div" tag.innerText = @findBestTag p tag.classList.add "project-tag" @@ -188,19 +203,25 @@ class @Explore @openProject(p) @canBack = true - @boxes[p.owner+"/"+p.slug] = element + element get:(id)-> document.getElementById(id) findProject:(owner,slug)-> + id = "#{owner}.#{slug}" + if @visited_projects[id]? + return @visited_projects[id] + if @projects? for p in @projects if p.owner == owner and p.slug == slug return p + return null openProject:(p)-> + @visited_projects["#{p.owner}.#{p.slug}"] = p @project = p if @cloned[@project.id] @get("project-details-clonebutton").style.display = "none" @@ -319,130 +340,45 @@ class @Explore @project = null @closed() - createTags:()-> - @active_tags = [] - tags = {} - count = {} - - for p in @projects - for t in p.tags - if not tags[t]? - tags[t] = {} - count[t] = 1 - - tags[t][p.owner] = true - count[t] += 1 - - for t of tags - tags[t] = Object.keys(tags[t]).length+count[t]*.001 - - list = [] - for tag,count of tags - list.push - tag: tag - count: count - - @tag_scores = tags - - list.sort (a,b)->b.count-a.count - + createTags:(@tags)-> document.getElementById("explore-tags").innerHTML = "" - for t in list + for t in @tags div = document.createElement "div" - div.innerText = t.tag + div.innerText = t + if @active_tags.includes t + div.classList.add "active" #span = document.createElement "span" #span.innerText = t.count #div.appendChild span document.getElementById("explore-tags").appendChild div do (t,div)=> div.addEventListener "click",()=> - index = @active_tags.indexOf(t.tag) + index = @active_tags.indexOf(t) if index>=0 @active_tags.splice index,1 div.classList.remove "active" else - @active_tags.push t.tag + @active_tags.push t div.classList.add "active" - @loadProjects() + @query() return - loadProjects:()-> + loadProjects:(pos=0)-> return if not @projects? contents = document.getElementById "explore-box-list" scrollzone = document.getElementById "explore-contents" - contents.innerHTML = "" - @remaining = [] - - if @sort == "hot" and @projects.length>4 - now = Date.now() - @projects.sort @sort_functions.top - maxLikes = Math.max(1,@projects[4].likes) - @projects.sort @sort_functions.new - maxAge = now-@projects[@projects.length-1].date_published - - h = 1/24/7 - h = Math.max(0,(now-@projects[4].date_published))/1000/3600 - - # note = (p)-> - # hours = Math.max(0,(now-p.date_published))/1000/3600 - # agemark = Math.min(1,Math.exp(-hours/h)/Math.exp(-1)) - # p.likes/maxLikes*50+50*agemark - - # note = (p)-> - # bump = Math.min(1,(now-p.date_published)/1000/3600/24/30) - # bump = .5+.5*Math.cos(bump*Math.PI) - # p.likes + maxLikes*(bump*1+.4*Math.exp(Math.log(.5)*(now-p.date_published)/1000/3600/24/4)) - - fade = (x)-> - 1-Math.max(0,Math.min(1,x)) - - note = (p)-> - recent = fade (now-p.date_published)/1000/3600/24/4 - rating = p.likes/maxLikes*(.15+2*fade((now-p.date_published)/1000/3600/24/180)) - recent+rating - - @sort_functions.hot = (a,b)-> - note(b)-note(a) - - @projects.sort @sort_functions[@sort] - search = document.getElementById("explore-search-input").value.toLowerCase() - for p in @projects - if @active_tags.length>0 - found = false - for t in @active_tags - if p.tags.indexOf(t)>=0 - found = true - continue if not found - - if search.length>0 - found = p.title.toLowerCase().indexOf(search)>=0 - found |= p.description.toLowerCase().indexOf(search)>=0 - found |= p.owner.toLowerCase().indexOf(search)>=0 - if found - console.info "found: "+p.owner - for t in p.tags - found |= t.toLowerCase().indexOf(search)>=0 - - continue if not found - - if contents.getBoundingClientRect().height - if @list_received - callback() if callback? - return + for i in [pos..@projects.length-1] by 1 + p = @projects[i] + contents.appendChild @createProjectBox p + return + update:()-> if not @initialized and location.pathname.startsWith "/i/" document.getElementById("explore-section").style.opacity = 0 - contents = document.getElementById "explore-box-list" - contents.innerHTML = "" - if not @initialized and location.pathname.startsWith "/i/" @initialized = true owner = location.pathname.split("/")[2] @@ -458,22 +394,45 @@ class @Explore document.getElementById("explore-section").style.opacity = 1 return else - @app.client.sendRequest { - name:"get_public_projects" - ranking: "hot" - tags: [] - },(msg)=> + if not @projects? or @projects.length == 0 + @query() + + query:(position=0)-> + @query_position = position + + if position == 0 or not @current_offset? + @current_offset = 0 + + f = ()=> + @app.client.sendRequest { + name:"get_public_projects" + ranking: @sort + tags: @active_tags + search: @search.toLowerCase() + position: position + offset: @current_offset + },(msg)=> + if position == 0 + @current_position = position + @current_offset = msg.offset + @completed = false @projects = msg.list - @list_received = true - #console.info "PUBLIC PROJECTS SIZE: "+(JSON.stringify(@projects)).length - @boxes = {} - @createTags() + @createTags(msg.tags) @loadProjects() + document.getElementById("explore-contents").scrollTop = 0 + else + if msg.list.length == 0 + @completed = true - if not @initialized - @initialized = true - document.getElementById("explore-section").style.opacity = 1 + @current_position = position + @current_offset = msg.offset + pos = @projects.length + @projects = @projects.concat msg.list + @loadProjects(pos) - #@app.setHomeState() - callback() if callback? - return + if not @initialized + @initialized = true + document.getElementById("explore-section").style.opacity = 1 + + #@app.setHomeState() + return diff --git a/static/js/explore/explore.js b/static/js/explore/explore.js index 6fd4daa5..d0257404 100644 --- a/static/js/explore/explore.js +++ b/static/js/explore/explore.js @@ -9,6 +9,10 @@ this.Explore = (function() { }; })(this)); this.sort = "hot"; + this.active_tags = []; + this.search = ""; + this.tags = []; + this.visited_projects = {}; this.sort_types = ["hot", "new", "top"]; this.sort_functions = { hot: function(a, b) { @@ -23,14 +27,14 @@ this.Explore = (function() { }; document.getElementById("explore-sort-button").addEventListener("click", (function(_this) { return function() { - var e, i, len, ref, s; + var e, j, len, ref, s; s = _this.sort_types.indexOf(_this.sort); s = (s + 1) % _this.sort_types.length; _this.sort = _this.sort_types[s]; e = document.getElementById("explore-sort-button"); ref = _this.sort_types; - for (i = 0, len = ref.length; i < len; i++) { - s = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + s = ref[j]; if (s === _this.sort) { e.classList.add(s); } else { @@ -38,21 +42,34 @@ this.Explore = (function() { } } document.querySelector("#explore-sort-button span").innerText = _this.app.translator.get(_this.sort.substring(0, 1).toUpperCase() + _this.sort.substring(1)); - return _this.loadProjects(); + return _this.query(); }; })(this)); document.getElementById("explore-search-input").addEventListener("input", (function(_this) { return function() { - return _this.loadProjects(); + _this.search = document.getElementById("explore-search-input").value; + if (_this.search_timeout != null) { + clearTimeout(_this.search_timeout); + } + return _this.search_timeout = setTimeout((function() { + return _this.query(); + }), 1500); }; })(this)); document.getElementById("explore-contents").addEventListener("scroll", (function(_this) { return function() { - var contents, scrollzone; + var contents, h1, h2, pos, scrollzone; contents = document.getElementById("explore-box-list"); scrollzone = document.getElementById("explore-contents"); - while ((_this.remaining != null) && _this.remaining.length > 0 && contents.getBoundingClientRect().height < scrollzone.scrollTop + window.innerHeight * 2) { - contents.appendChild(_this.createProjectBox(_this.remaining.splice(0, 1)[0])); + h1 = contents.getBoundingClientRect().height; + h2 = scrollzone.getBoundingClientRect().height; + if (scrollzone.scrollTop > h1 - h2 - 100) { + if (!_this.completed) { + pos = _this.projects.length; + if (pos !== _this.query_position) { + _this.query(pos); + } + } } }; })(this)); @@ -111,14 +128,18 @@ this.Explore = (function() { }; Explore.prototype.findBestTag = function(p) { - var i, len, ref, score, t, tag; + var index, j, len, ref, score, t, tag; tag = p.tags[0]; - score = 0; + score = this.tags.indexOf(tag); + if (score < 0) { + score = 1000; + } ref = p.tags; - for (i = 0, len = ref.length; i < len; i++) { - t = ref[i]; - if (this.tag_scores[t] > score) { - score = this.tag_scores[t]; + for (j = 0, len = ref.length; j < len; j++) { + t = ref[j]; + index = this.tags.indexOf(t); + if (index >= 0 && index < score) { + score = index; tag = t; } } @@ -127,12 +148,9 @@ this.Explore = (function() { Explore.prototype.createProjectBox = function(p) { var author, element, icon, infobox, likes, runbutton, smallicon, tag, title; - if (this.boxes[p.owner + "/" + p.slug]) { - return this.boxes[p.owner + "/" + p.slug]; - } element = document.createElement("div"); element.classList.add("explore-project-box"); - if (p.tags.length > 0 && (this.tag_scores != null)) { + if (p.tags.length > 0) { tag = document.createElement("div"); tag.innerText = this.findBestTag(p); tag.classList.add("project-tag"); @@ -236,7 +254,7 @@ this.Explore = (function() { } }; })(this)); - return this.boxes[p.owner + "/" + p.slug] = element; + return element; }; Explore.prototype.get = function(id) { @@ -244,11 +262,15 @@ this.Explore = (function() { }; Explore.prototype.findProject = function(owner, slug) { - var i, len, p, ref; + var id, j, len, p, ref; + id = owner + "." + slug; + if (this.visited_projects[id] != null) { + return this.visited_projects[id]; + } if (this.projects != null) { ref = this.projects; - for (i = 0, len = ref.length; i < len; i++) { - p = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + p = ref[j]; if (p.owner === owner && p.slug === slug) { return p; } @@ -258,7 +280,8 @@ this.Explore = (function() { }; Explore.prototype.openProject = function(p) { - var desc, div, i, j, len, len1, lib, likes, list, ref, ref1, t; + var desc, div, j, k, len, len1, lib, likes, list, ref, ref1, t; + this.visited_projects[p.owner + "." + p.slug] = p; this.project = p; if (this.cloned[this.project.id]) { this.get("project-details-clonebutton").style.display = "none"; @@ -281,8 +304,8 @@ this.Explore = (function() { this.get("project-details-info").style.background = "none"; } ref = p.libs; - for (i = 0, len = ref.length; i < len; i++) { - lib = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + lib = ref[j]; desc = ("

" + (this.app.translator.get("This project uses an experimental integration of this library:")) + " " + lib + "

") + desc; } if (p.graphics !== "M1") { @@ -309,8 +332,8 @@ this.Explore = (function() { list = this.get("project-details-tags"); list.innerHTML = ""; ref1 = p.tags; - for (j = 0, len1 = ref1.length; j < len1; j++) { - t = ref1[j]; + for (k = 0, len1 = ref1.length; k < len1; k++) { + t = ref1[k]; div = document.createElement("div"); div.classList.add("tag"); div.innerText = t; @@ -398,151 +421,63 @@ this.Explore = (function() { return this.closed(); }; - Explore.prototype.createTags = function() { - var count, div, fn, i, j, k, len, len1, len2, list, p, ref, ref1, t, tag, tags; - this.active_tags = []; - tags = {}; - count = {}; - ref = this.projects; - for (i = 0, len = ref.length; i < len; i++) { - p = ref[i]; - ref1 = p.tags; - for (j = 0, len1 = ref1.length; j < len1; j++) { - t = ref1[j]; - if (tags[t] == null) { - tags[t] = {}; - count[t] = 1; - } - tags[t][p.owner] = true; - count[t] += 1; - } - } - for (t in tags) { - tags[t] = Object.keys(tags[t]).length + count[t] * .001; - } - list = []; - for (tag in tags) { - count = tags[tag]; - list.push({ - tag: tag, - count: count - }); - } - this.tag_scores = tags; - list.sort(function(a, b) { - return b.count - a.count; - }); + Explore.prototype.createTags = function(tags) { + var div, fn, j, len, ref, t; + this.tags = tags; document.getElementById("explore-tags").innerHTML = ""; + ref = this.tags; fn = (function(_this) { return function(t, div) { return div.addEventListener("click", function() { var index; - index = _this.active_tags.indexOf(t.tag); + index = _this.active_tags.indexOf(t); if (index >= 0) { _this.active_tags.splice(index, 1); div.classList.remove("active"); } else { - _this.active_tags.push(t.tag); + _this.active_tags.push(t); div.classList.add("active"); } - return _this.loadProjects(); + return _this.query(); }); }; })(this); - for (k = 0, len2 = list.length; k < len2; k++) { - t = list[k]; + for (j = 0, len = ref.length; j < len; j++) { + t = ref[j]; div = document.createElement("div"); - div.innerText = t.tag; + div.innerText = t; + if (this.active_tags.includes(t)) { + div.classList.add("active"); + } document.getElementById("explore-tags").appendChild(div); fn(t, div); } }; - Explore.prototype.loadProjects = function() { - var contents, fade, found, h, i, j, k, len, len1, len2, maxAge, maxLikes, note, now, p, ref, ref1, ref2, scrollzone, search, t; + Explore.prototype.loadProjects = function(pos) { + var contents, i, j, p, ref, ref1, scrollzone; + if (pos == null) { + pos = 0; + } if (this.projects == null) { return; } contents = document.getElementById("explore-box-list"); scrollzone = document.getElementById("explore-contents"); - contents.innerHTML = ""; - this.remaining = []; - if (this.sort === "hot" && this.projects.length > 4) { - now = Date.now(); - this.projects.sort(this.sort_functions.top); - maxLikes = Math.max(1, this.projects[4].likes); - this.projects.sort(this.sort_functions["new"]); - maxAge = now - this.projects[this.projects.length - 1].date_published; - h = 1 / 24 / 7; - h = Math.max(0, now - this.projects[4].date_published) / 1000 / 3600; - fade = function(x) { - return 1 - Math.max(0, Math.min(1, x)); - }; - note = function(p) { - var rating, recent; - recent = fade((now - p.date_published) / 1000 / 3600 / 24 / 4); - rating = p.likes / maxLikes * (.15 + 2 * fade((now - p.date_published) / 1000 / 3600 / 24 / 180)); - return recent + rating; - }; - this.sort_functions.hot = function(a, b) { - return note(b) - note(a); - }; + if (pos === 0) { + contents.innerHTML = ""; } - this.projects.sort(this.sort_functions[this.sort]); - search = document.getElementById("explore-search-input").value.toLowerCase(); - ref = this.projects; - for (i = 0, len = ref.length; i < len; i++) { - p = ref[i]; - if (this.active_tags.length > 0) { - found = false; - ref1 = this.active_tags; - for (j = 0, len1 = ref1.length; j < len1; j++) { - t = ref1[j]; - if (p.tags.indexOf(t) >= 0) { - found = true; - } - } - if (!found) { - continue; - } - } - if (search.length > 0) { - found = p.title.toLowerCase().indexOf(search) >= 0; - found |= p.description.toLowerCase().indexOf(search) >= 0; - found |= p.owner.toLowerCase().indexOf(search) >= 0; - if (found) { - console.info("found: " + p.owner); - } - ref2 = p.tags; - for (k = 0, len2 = ref2.length; k < len2; k++) { - t = ref2[k]; - found |= t.toLowerCase().indexOf(search) >= 0; - } - if (!found) { - continue; - } - } - if (contents.getBoundingClientRect().height < scrollzone.scrollTop + window.innerHeight * 2) { - contents.appendChild(this.createProjectBox(p)); - } else { - this.remaining.push(p); - } + for (i = j = ref = pos, ref1 = this.projects.length - 1; j <= ref1; i = j += 1) { + p = this.projects[i]; + contents.appendChild(this.createProjectBox(p)); } }; - Explore.prototype.update = function(callback) { - var contents, owner, project; - if (this.list_received) { - if (callback != null) { - callback(); - } - return; - } + Explore.prototype.update = function() { + var owner, project; if (!this.initialized && location.pathname.startsWith("/i/")) { document.getElementById("explore-section").style.opacity = 0; } - contents = document.getElementById("explore-box-list"); - contents.innerHTML = ""; if (!this.initialized && location.pathname.startsWith("/i/")) { this.initialized = true; owner = location.pathname.split("/")[2]; @@ -561,27 +496,58 @@ this.Explore = (function() { }; })(this)); } else { - this.app.client.sendRequest({ - name: "get_public_projects", - ranking: "hot", - tags: [] - }, (function(_this) { - return function(msg) { + if ((this.projects == null) || this.projects.length === 0) { + return this.query(); + } + } + }; + + Explore.prototype.query = function(position) { + var f; + if (position == null) { + position = 0; + } + this.query_position = position; + if (position === 0 || (this.current_offset == null)) { + this.current_offset = 0; + } + f = (function(_this) { + return function() {}; + })(this); + this.app.client.sendRequest({ + name: "get_public_projects", + ranking: this.sort, + tags: this.active_tags, + search: this.search.toLowerCase(), + position: position, + offset: this.current_offset + }, (function(_this) { + return function(msg) { + var pos; + if (position === 0) { + _this.current_position = position; + _this.current_offset = msg.offset; + _this.completed = false; _this.projects = msg.list; - _this.list_received = true; - _this.boxes = {}; - _this.createTags(); + _this.createTags(msg.tags); _this.loadProjects(); - if (!_this.initialized) { - _this.initialized = true; - return document.getElementById("explore-section").style.opacity = 1; + document.getElementById("explore-contents").scrollTop = 0; + } else { + if (msg.list.length === 0) { + _this.completed = true; } - }; - })(this)); - if (callback != null) { - callback(); - } - } + _this.current_position = position; + _this.current_offset = msg.offset; + pos = _this.projects.length; + _this.projects = _this.projects.concat(msg.list); + _this.loadProjects(pos); + } + if (!_this.initialized) { + _this.initialized = true; + return document.getElementById("explore-section").style.opacity = 1; + } + }; + })(this)); }; return Explore;