$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit 90154cf05da42020c33bee85f852c87e74916192
Author: Alicia <...>
Date:   Fri Sep 8 20:40:16 2017 +0000

    Initial feed page implementation.

diff --git a/libsocialjs.js b/libsocialjs.js
index e0bd206..b61284d 100644
--- a/libsocialjs.js
+++ b/libsocialjs.js
@@ -185,6 +185,7 @@ function user_getupdate(user, index)
 {
   var update=new Object();
   var ptr=user_getupdateptr(user.ptr, index);
+  update.user=user;
   update.type=update_gettype(ptr);
   update.timestamp=update_gettimestamp(ptr);
   update.privacy=getprivacy(update_getprivacy(ptr));
@@ -201,6 +202,15 @@ function user_getupdate(user, index)
   }
   return update;
 }
+function user_getupdates(user)
+{
+  var updates=[];
+  for(var i=0; i<user.updatecount; ++i)
+  {
+    updates.push(user_getupdate(user, i));
+  }
+  return updates;
+}
 function privacy(flags, circles)
 {
   this.flags=flags;
@@ -240,3 +250,18 @@ function getprivacy(ptr)
   }
   return new privacy(flags, circles);
 }
+function getfriends()
+{
+  var friends=[];
+  var circles=getcircles();
+  for(item of circles)
+  {
+    var count=circle_getcount(item.index);
+    for(var i=0; i<count; ++i)
+    {
+      var id=circle_getid(item.index, i);
+      if(friends.indexOf(id)==-1){friends.push(id);}
+    }
+  }
+  return friends;
+}
diff --git a/webpeer.c b/webpeer.c
index 73a129a..ba25a51 100644
--- a/webpeer.c
+++ b/webpeer.c
@@ -91,9 +91,9 @@ printf("Handling websocket input...\n");
       char buf[head.length];
       while(!websock_readcontent(conn, buf, &head));
       struct udpstream* stream=udpstream_find((struct sockaddr_storage*)addr, addrlen);
-      if(!stream) // TODO: Check against blocklist (don't let people use this service to flood others over UDP), also add a brief block for the entire host until we know this connection is ok
+      if(!stream) // Check against blocklist (don't let people use this service to flood others over UDP), also add a brief block for the entire host until we know this connection is ok
       {
-        if(!addrlist_check((struct sockaddr_storage*)addr, addrlen)){continue;}
+        if(!addrlist_check((struct sockaddr_storage*)addr, addrlen)){printf("Address blocklist check failed\n"); continue;}
         stream=udpstream_new(udpsock, (struct sockaddr_storage*)addr, addrlen);
       }
 // printf("Writing %u bytes\n", head.length);
diff --git a/websocial.html b/websocial.html
index 048a4b7..dd1d789 100644
--- a/websocial.html
+++ b/websocial.html
@@ -64,7 +64,7 @@
   </div>
   <!-- Main interface -->
   <div class="menu">
-    <a href="#" onclick="return false;">Feed</a><a href="#" onclick="page_friends();return false;">Friends</a><a href="#" onclick="page_user(false);return false;">Self</a>
+    <a href="#" onclick="page_feed();return false;">Feed</a><a href="#" onclick="page_friends();return false;">Friends</a><a href="#" onclick="page_user(false);return false;">Self</a>
   </div>
   <div id="display"></div>
 </body>
diff --git a/websocial.js b/websocial.js
index 8360a8f..767af5b 100644
--- a/websocial.js
+++ b/websocial.js
@@ -70,6 +70,67 @@ function initgui()
 // TODO: display any updates we may have (actually we probably won't have any yet, update when we get them, callback for 'updates' command?)
 }
 
+function linknode(name, callback)
+{
+  var link=document.createElement('a');
+  link.href='#';
+  link.onclick=callback;
+  link.appendChild(document.createTextNode(name));
+  return link;
+}
+
+function drawupdate(update)
+{
+  var box=document.createElement('div');
+  box.className='update';
+  var date=new Date(update.timestamp*1000);
+  box.appendChild(document.createTextNode('Posted at: '+date.toLocaleString()));
+  // Link to the update's author
+  var name=(update.user.fields['name']?update.user.fields['name'].value:update.user.id);
+  if(!name){name=update.user.id;}
+  var userlink=linknode(name, function(){page_user(this.dataset.id); return false;});
+  userlink.dataset.id=update.user.id;
+  // Display type-specific data
+  if(update.type=='Post')
+  {
+    box.appendChild(document.createTextNode(' by '));
+    box.appendChild(userlink);
+    box.appendChild(document.createElement('br'));
+    box.appendChild(document.createTextNode('Message: '+update.message));
+  }else{
+    box.appendChild(document.createTextNode(' by '));
+    box.appendChild(userlink);
+// TODO: Don't show update type, just handle each type
+    box.appendChild(document.createElement('br'));
+    box.appendChild(document.createTextNode('Update type: '+update.type));
+  }
+  return box;
+}
+
+function page_feed()
+{
+  var display=document.getElementById('display');
+  dom_clear(display);
+  // Get updates from every user
+  var updates=[];
+  var friends=getfriends();
+  for(var i=0; i<friends.length; ++i)
+  {
+    var upd=user_getupdates(getuser(friends[i]));
+    updates=updates.concat(upd);
+  }
+  // Sort updates
+  updates.sort(function(a,b){return b.timestamp-a.timestamp;});
+  for(var i=0; i<updates.length; ++i)
+  {
+    if(updates[i].type=='Circle'){continue;} // Don't display circle changes
+    display.appendChild(document.createElement('br'));
+    var box=drawupdate(updates[i]);
+    display.appendChild(box);
+  }
+  // TODO: option to load more updates
+}
+
 function page_friends()
 {
   var display=document.getElementById('display');
@@ -142,11 +203,8 @@ function page_friends()
       var name=(user.fields['name']?user.fields['name'].value:id);
       if(!name){name=id;}
       // Link to profile
-      var link=document.createElement('a');
-      link.href='#';
+      var link=linknode(name, function(){page_user(this.dataset.id); return false;});
       link.dataset.id=id;
-      link.onclick=function(){page_user(this.dataset.id); return false;};
-      link.appendChild(document.createTextNode(name));
       display.appendChild(link);
       display.appendChild(document.createElement('br'));
     }
@@ -204,23 +262,12 @@ function page_user(id)
     display.appendChild(document.createElement('br'));
   }
   display.appendChild(document.createTextNode(user.updatecount+' updates'));
-  for(var i=1; i<=user.updatecount && i<=20; ++i)
+  var updates=user_getupdates(user);
+  for(var i=0; i<updates.length; ++i)
   {
-    var update=user_getupdate(user, user.updatecount-i);
-    if(update.type=='Circle'){continue;} // Don't display circle changes
-    var box=document.createElement('div');
-    box.className='update';
-    var date=new Date(update.timestamp*1000);
-    box.appendChild(document.createTextNode('Posted at: '+date.toLocaleString()));
-    box.appendChild(document.createElement('br'));
-    box.appendChild(document.createTextNode('Update type: '+update.type));
-    // Display type-specific data
-    if(update.type=='Post')
-    {
-      box.appendChild(document.createElement('br'));
-      box.appendChild(document.createTextNode('Message: '+update.message));
-    }
+    if(updates[i].type=='Circle'){continue;} // Don't display circle changes
     display.appendChild(document.createElement('br'));
+    var box=drawupdate(updates[i]);
     display.appendChild(box);
   }
 // TODO: Option to load more updates