$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit 8fb4d64c949c48bfb95b3a70982bb2d6d81a6726
Author: Alicia <...>
Date:   Sun Jul 23 23:15:06 2017 +0200

    Implemented basic post updates.

diff --git a/Makefile b/Makefile
index 9d07c5d..2b9da73 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ SOCIALNETWORKREVISION=f5e92db17a617d4777df4b7a1107d67114d3b6f9
 REVISION=$(shell git log | sed -n -e 's/^commit //p;q')
 JSLIBS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --libs gnutls nettle hogweed) -lgmp
 JSCFLAGS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --cflags gnutls)
-JSSYMBOLS='_social_init','_peer_handlesocket','_peer_new_unique','_websockproxy_read','_websockproxy_setwrite','_getcirclecount','_circle_getcount','_circle_getname','_circle_getprivacyptr','_social_addfriend','_circle_getid','_social_finduser','_self_getid','_user_getupdatecount','_user_getupdatetype','_user_getupdatetimestamp','_setcircle','_privacy_getflags','_privacy_getcirclecount','_privacy_getcircle'
+JSSYMBOLS='_social_init','_peer_handlesocket','_peer_new_unique','_websockproxy_read','_websockproxy_setwrite','_getcirclecount','_circle_getcount','_circle_getname','_circle_getprivacyptr','_social_addfriend','_circle_getid','_social_finduser','_self_getid','_user_getupdatecount','_user_getupdateptr','_update_gettype','_update_gettimestamp','_update_post_getmessage','_setcircle','_privacy_getflags','_privacy_getcirclecount','_privacy_getcircle','_createpost'
 
 all: webpeer libsocial.js
 
diff --git a/jsglue.c b/jsglue.c
index e140e43..0e59ac7 100644
--- a/jsglue.c
+++ b/jsglue.c
@@ -80,9 +80,10 @@ void setcircle(uint32_t circle, const char* name, uint8_t flags, void* circles,
 }
 struct privacy* circle_getprivacyptr(uint32_t circle){return &social_self->circles[circle].privacy;}
 unsigned int user_getupdatecount(struct user* user){return user->updatecount;}
-const char* user_getupdatetype(struct user* user, unsigned int index)
+struct update* user_getupdateptr(struct user* user, unsigned int index){return &user->updates[index];}
+const char* update_gettype(struct update* update)
 {
-  switch(user->updates[index].type)
+  switch(update->type)
   {
   case UPDATE_FIELD: return "Field";
   case UPDATE_POST: return "Post";
@@ -92,10 +93,8 @@ const char* user_getupdatetype(struct user* user, unsigned int index)
   }
   return "Unknown";
 }
-uint64_t user_getupdatetimestamp(struct user* user, unsigned int index)
-{
-  return user->updates[index].timestamp;
-}
+uint64_t update_gettimestamp(struct update* update){return update->timestamp;}
+const char* update_post_getmessage(struct update* update){return update->post.message;}
 const char* self_getid(void)
 {
   unsigned char* bin=social_self->id;
@@ -106,3 +105,12 @@ const char* self_getid(void)
 uint8_t privacy_getflags(struct privacy* priv){return priv->flags;}
 uint32_t privacy_getcirclecount(struct privacy* priv){return priv->circlecount;}
 uint32_t privacy_getcircle(struct privacy* priv, uint32_t i){return priv->circles[i];}
+void createpost(const char* msg, uint8_t flags, void* circles, uint32_t circlecount)
+{
+  struct privacy priv={
+    .flags=flags,
+    .circles=circles,
+    .circlecount=circlecount
+  };
+  social_createpost(msg, &priv);
+}
diff --git a/libsocialjs.js b/libsocialjs.js
index 097d444..c934ef4 100644
--- a/libsocialjs.js
+++ b/libsocialjs.js
@@ -34,12 +34,15 @@ var circle_getprivacyptr;
 var social_addfriend;
 var social_finduser;
 var user_getupdatecount;
-var user_getupdatetype;
-var user_getupdatetimestamp;
+var user_getupdateptr;
+var update_gettype;
+var update_gettimestamp;
+var update_post_getmessage;
 var self_getid;
 var privacy_getflags;
 var privacy_getcirclecount;
 var privacy_getcircle;
+var createpost;
 
 var websockproxy_to=false;
 var firstpacket=true;
@@ -84,12 +87,15 @@ function init(privkey)
   social_addfriend=Module.cwrap('social_addfriend', null, ['array', 'number']);
   social_finduser=Module.cwrap('social_finduser', 'number', ['array']);
   user_getupdatecount=Module.cwrap('user_getupdatecount', 'number', ['number']);
-  user_getupdatetype=Module.cwrap('user_getupdatetype', 'string', ['number','number']);
-  user_getupdatetimestamp=Module.cwrap('user_getupdatetimestamp', 'number', ['number','number']);
+  user_getupdateptr=Module.cwrap('user_getupdateptr', 'number', ['number','number']);
+  update_gettype=Module.cwrap('update_gettype', 'string', ['number']);
+  update_gettimestamp=Module.cwrap('update_gettimestamp', 'number', ['number']);
+  update_post_getmessage=Module.cwrap('update_post_getmessage', 'string', ['number']);
   self_getid=Module.cwrap('self_getid', 'string', []);
   privacy_getflags=Module.cwrap('privacy_getflags', 'number', ['number']);
   privacy_getcirclecount=Module.cwrap('privacy_getcirclecount', 'number', ['number']);
   privacy_getcircle=Module.cwrap('privacy_getcircle', 'number', ['number', 'number']);
+  createpost=Module.cwrap('createpost', null, ['string', 'number', 'array', 'number']);
 
   _websockproxy_setwrite(Runtime.addFunction(websockwrite));
   FS.writeFile('privkey.pem', privkey, {});
@@ -138,18 +144,6 @@ function circle_getprivacy(index)
   var ptr=circle_getprivacyptr(index);
   return getprivacy(ptr);
 }
-function circle_set(index, name, priv)
-{
-  var circles=[];
-  for(circle of priv.circles) // Can only pass Uint8 arrays, so construct our Uint32 values from Uint8 pieces
-  {
-    circles.push(circle.index&0xff);
-    circles.push((circle.index&0xff00)/0x100);
-    circles.push((circle.index&0xff0000)/0x10000);
-    circles.push((circle.index&0xff000000)/0x1000000);
-  }
-  setcircle(configcircle_index, name, priv.flags, new Uint8Array(circles), priv.circles.length);
-}
 function hextobin(hex)
 {
   var bin=new Array();
@@ -173,15 +167,40 @@ function getuser(id)
 function user_getupdate(user, index)
 {
   var update=new Object();
-  update.type=user_getupdatetype(user.ptr, index);
-  update.timestamp=user_getupdatetimestamp(user.ptr, index);
-// TODO: Get type-specific data
+  var ptr=user_getupdateptr(user.ptr, index);
+  update.type=update_gettype(ptr);
+  update.timestamp=update_gettimestamp(ptr);
+  // Get type-specific data
+  switch(update.type)
+  {
+  case 'Post':
+    update.message=update_post_getmessage(ptr);
+    break;
+  }
   return update;
 }
 function privacy(flags, circles)
 {
   this.flags=flags;
   this.circles=circles;
+  this.toString=function()
+  {
+    if(this.flags&1){return 'Everyone';}
+    if(this.flags&2){return 'Friends';}
+    return 'Select circles only ('+this.circles.length+')';
+  };
+  this.bincircles=function()
+  { // Can only pass Uint8 arrays to C functions, so construct our Uint32 values from Uint8 pieces
+    var circles=[];
+    for(circle of this.circles)
+    {
+      circles.push(circle.index&0xff);
+      circles.push((circle.index&0xff00)/0x100);
+      circles.push((circle.index&0xff0000)/0x10000);
+      circles.push((circle.index&0xff000000)/0x1000000);
+    }
+    return new Uint8Array(circles);
+  };
 }
 function getprivacy(ptr)
 {
diff --git a/websocial.css b/websocial.css
index de10c55..d2b6278 100644
--- a/websocial.css
+++ b/websocial.css
@@ -47,3 +47,18 @@ div.menu>a:nth-child(1)
 {
   border-left-style:none;
 }
+div.update
+{
+  border-style:solid;
+  border-color:#808080;
+  border-width:1px;
+  border-radius:6px;
+  padding:4px;
+  margin:5px;
+  min-width:40%;
+  max-width:96%;
+  overflow-wrap:break-word;
+  display:inline-block;
+  box-shadow:0px 0px 2px #c0c0c0;
+  background-color:#ffffff;
+}
diff --git a/websocial.html b/websocial.html
index b205bca..048a4b7 100644
--- a/websocial.html
+++ b/websocial.html
@@ -53,6 +53,15 @@
     <div id="circle_privacy_circles"></div><br />
     <button onclick="circle_save();">OK</button> &nbsp; <button onclick="chdisplay('circle_window',false);">Cancel</button>
   </div>
+  <div class="modal" id="privacy_window" style="display:none;">
+    <select id="generic_privacy">
+      <option value="0">Select circles only</option>
+      <option value="2">Friends</option>
+      <option value="1">Everyone</option>
+    </select><br />
+    <div id="generic_privacy_circles"></div><br />
+    <button id="privacy_button">OK</button> &nbsp; <button onclick="chdisplay('privacy_window',false);">Cancel</button>
+  </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>
diff --git a/websocial.js b/websocial.js
index e6e4c0c..795a7ad 100644
--- a/websocial.js
+++ b/websocial.js
@@ -151,14 +151,30 @@ function page_user(id)
   var user=getuser(id);
   display.appendChild(document.createTextNode(id+':'));
   display.appendChild(document.createElement('br'));
+  var button=document.createElement('button');
+  button.name='postbutton';
+  button.onclick=postwidget;
+  button.appendChild(document.createTextNode('Create update'));
+  display.appendChild(button);
+  display.appendChild(document.createElement('br'));
   display.appendChild(document.createTextNode(user.updatecount+' updates'));
   for(var i=1; i<=user.updatecount && i<=20; ++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.appendChild(document.createTextNode('Posted at: '+update.timestamp));
+    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));
+    }
+    display.appendChild(document.createElement('br'));
     display.appendChild(box);
   }
 // TODO: Option to load more updates
@@ -180,6 +196,7 @@ function privacy_openconfig(prefix, priv)
     checkbox.type='checkbox';
     checkbox.name=prefix+'_privacy_circles';
     checkbox.value=item.index;
+    checkbox.dataset.name=item.name;
     label.appendChild(checkbox);
     label.appendChild(document.createTextNode(item.name+' '));
     circlebox.appendChild(label);
@@ -207,7 +224,7 @@ function privacy_save(prefix)
   var items=document.getElementsByName(prefix+'_privacy_circles');
   for(item of items)
   {
-    if(item.checked){circles.push({'index':item.value});}
+    if(item.checked){circles.push({'index':parseInt(item.value),'name':item.dataset.name});}
   }
   return new privacy(flags, circles);
 }
@@ -236,14 +253,14 @@ function circle_openconfig(index, option)
 
 function circle_save()
 {
-  var name=document.getElementById('circle_name');
+  var name=document.getElementById('circle_name').value;
   var priv=privacy_save('circle');
-  circle_set(configcircle_index, name.value, priv);
+  setcircle(configcircle_index, name, priv.flags, priv.bincircles(), priv.circles.length);
   if(configcircle_option)
   {
     var select=configcircle_option.parentNode;
     // Update list of circles
-    configcircle_option.text=name.value;
+    configcircle_option.text=name;
     configcircle_option.value=configcircle_index;
     configcircle_option=false;
     // Make a new 'new circle' option?
@@ -254,3 +271,56 @@ function circle_save()
   }
   chdisplay('circle_window',false);
 }
+
+var postwidgetbox=false;
+var postprivacy=new privacy(0,[]);
+function postwidget()
+{
+// TODO: Differences in widgets for posts on others walls or in response to other posts?
+  if(postwidgetbox && postwidgetbox.parentNode)
+  {
+    postwidgetbox.parentNode.removeChild(postwidgetbox);
+  }
+  postwidgetbox=document.createElement('div');
+// TODO: Configurable default privacy?
+  var privtext=document.createTextNode('Privacy: '+postprivacy.toString());
+  postwidgetbox.appendChild(privtext);
+  button=document.createElement('button');
+  button.appendChild(document.createTextNode('Change'));
+  button.onclick=function()
+  {
+    privacy_openconfig('generic', postprivacy);
+    document.getElementById('privacy_button').onclick=function()
+    {
+      postprivacy=privacy_save('generic');
+      chdisplay('privacy_window', false);
+      privtext.textContent='Privacy: '+postprivacy.toString();
+    };
+    chdisplay(false, 'privacy_window');
+  };
+  postwidgetbox.appendChild(button);
+  postwidgetbox.appendChild(document.createElement('br'));
+// TODO: Non-text posts, maybe media for starters
+  var text=document.createElement('textarea');
+  postwidgetbox.appendChild(text);
+  postwidgetbox.appendChild(document.createElement('br'));
+  // Submit button
+  button=document.createElement('button');
+  button.appendChild(document.createTextNode('Post'));
+  button.onclick=function()
+  {
+    createpost(text.value, postprivacy.flags, postprivacy.bincircles(), postprivacy.circles.length);
+// TODO: Instead of reloading the user page, insert the update and hide the postwidget (and re-show postbutton)
+    page_user(false);
+  };
+  postwidgetbox.appendChild(button);
+  // Re-show all postbuttons
+  var buttons=document.getElementsByName('postbutton');
+  for(button of buttons)
+  {
+    button.style.display='inline';
+  }
+  // Hide this one and insert postwidget
+  this.style.display='none';
+  this.parentNode.insertBefore(postwidgetbox, this);
+}