diff options
| -rw-r--r-- | README.adoc | 2 | ||||
| -rw-r--r-- | xC-proto | 18 | ||||
| -rw-r--r-- | xC.c | 13 | ||||
| -rw-r--r-- | xP/public/xP.css | 13 | ||||
| -rw-r--r-- | xP/public/xP.js | 87 | 
5 files changed, 114 insertions, 19 deletions
diff --git a/README.adoc b/README.adoc index 67a7ea5..6d61bcc 100644 --- a/README.adoc +++ b/README.adoc @@ -26,7 +26,7 @@ As a unique bonus, you can launch a full text editor from within.  xP  --  The web frontend for 'xC', making use of its networked relay interface. -So far it's quite basic, yet usable. +So far it's somewhat basic, yet usable.  xF  -- @@ -3,6 +3,8 @@ const VERSION = 1;  // From the frontend to the relay.  struct CommandMessage { +	// The command sequence number will be repeated in responses +	// in the respective fields.  	u32 command_seq;  	union CommandData switch (enum Command {  		HELLO, @@ -15,6 +17,9 @@ struct CommandMessage {  	} command) {  	case HELLO:  		u32 version; +		// If the version check succeeds, the client will receive +		// an initial stream of BUFFER_UPDATE, BUFFER_LINE, +		// and finally a BUFFER_ACTIVATE message.  	case PING:  		void;  	case ACTIVE: @@ -51,6 +56,15 @@ struct EventMessage {  		void;  	case BUFFER_UPDATE:  		string buffer_name; +		// These are cumulative, even for lines flushed out from buffers. +		// Updates to these values aren't broadcasted, thus handle: +		//  - BUFFER_LINE by bumping/setting them as appropriate, +		//  - BUFFER_ACTIVATE by clearing them for the previous buffer +		//    (this way, they can be used to mark unread messages). +		// Any updates received after the initial sync should be ignored. +		u32 new_messages; +		u32 new_unimportant_messages; +		bool highlighted;  	case BUFFER_RENAME:  		string buffer_name;  		string new; @@ -60,6 +74,7 @@ struct EventMessage {  		string buffer_name;  	case BUFFER_LINE:  		string buffer_name; +		// Whether the line should also be displayed in the active buffer.  		bool leak_to_active;  		bool is_unimportant;  		bool is_highlight; @@ -106,7 +121,8 @@ struct EventMessage {  	case BUFFER_CLEAR:  		string buffer_name; -	// Restriction: command_seq is strictly increasing, across both of these. +	// Restriction: command_seq strictly follows the sequence received +	// by the relay, across both of these replies.  	case ERROR:  		u32 command_seq;  		string error; @@ -3068,6 +3068,11 @@ relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)  	struct relay_event_data_buffer_update *e = &m->data.buffer_update;  	e->event = RELAY_EVENT_BUFFER_UPDATE;  	e->buffer_name = str_from_cstr (buffer->name); +	e->new_messages = MIN (UINT32_MAX, +		buffer->new_messages_count - buffer->new_unimportant_count); +	e->new_unimportant_messages = MIN (UINT32_MAX, +		buffer->new_unimportant_count); +	e->highlighted = buffer->highlighted;  }  static void @@ -5015,8 +5020,16 @@ buffer_merge (struct app_context *ctx,  	buffer->lines_count += n;  	// And since there is no log_*() call, send them to relays manually +	buffer->highlighted |= merged->highlighted;  	LIST_FOR_EACH (struct buffer_line, line, start)  	{ +		if (buffer->new_messages_count) +		{ +			buffer->new_messages_count++; +			if (line->flags & BUFFER_LINE_UNIMPORTANT) +				buffer->new_unimportant_count++; +		} +  		relay_prepare_buffer_line (ctx, buffer, line, false);  		relay_broadcast (ctx);  	} diff --git a/xP/public/xP.css b/xP/public/xP.css index a13afc6..81025c9 100644 --- a/xP/public/xP.css +++ b/xP/public/xP.css @@ -58,7 +58,13 @@ body {  	padding: .05rem .3rem;  	cursor: default;  } -.item.active { +.item.highlighted { +	color: #ff5f00; +} +.item.activity { +	font-weight: bold; +} +.item.current {  	font-style: italic;  	background: #f8f8f8;  	border-top: 1px solid #eee; @@ -97,6 +103,11 @@ body {  	grid-column: span 2;  	font-weight: bold;  } +.unread { +	height: 1px; +	grid-column: span 2; +	background: #ff5f00; +}  .time {  	padding: .1rem .3rem;  	background: #f8f8f8; diff --git a/xP/public/xP.js b/xP/public/xP.js index a941752..42a7a55 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -156,10 +156,13 @@ rpc.addEventListener('close', event => {  rpc.addEventListener('BufferUpdate', event => {  	let e = event.detail, b = buffers.get(e.bufferName)  	if (b === undefined) { -		b = {lines: []} -		buffers.set(e.bufferName, b) +		buffers.set(e.bufferName, { +			lines: [], +			newMessages: e.newMessages, +			newUnimportantMessages: e.newUnimportantMessages, +			highlighted: e.highlighted, +		})  	} -	// TODO: Update any buffer properties.  })  rpc.addEventListener('BufferRename', event => { @@ -174,8 +177,14 @@ rpc.addEventListener('BufferRemove', event => {  })  rpc.addEventListener('BufferActivate', event => { -	let e = event.detail, b = buffers.get(e.bufferName)  	let old = buffers.get(bufferCurrent) +	if (old !== undefined) { +		old.newMessages = 0 +		old.newUnimportantMessages = 0 +		old.highlighted = false +	} + +	let e = event.detail, b = buffers.get(e.bufferName)  	bufferCurrent = e.bufferName  	bufferLog = undefined  	bufferAutoscroll = true @@ -195,12 +204,41 @@ rpc.addEventListener('BufferActivate', event => {  })  rpc.addEventListener('BufferLine', event => { -	let e = event.detail, b = buffers.get(e.bufferName), -		line = {when: e.when, rendition: e.rendition, items: e.items} -	if (b !== undefined) -		b.lines.push({...line}) -	if (e.leakToActive && (b = buffers.get(bufferCurrent)) !== undefined) -		b.lines.push({leaked: true, ...line}) +	let e = event.detail, b = buffers.get(e.bufferName), line = {...e} +	delete line.event +	delete line.leakToActive +	if (b === undefined) +		return + +	// Initial sync: skip all other processing, let highlights be. +	if (bufferCurrent === undefined) { +		b.lines.push(line) +		return +	} + +	let visible = e.bufferName == bufferCurrent || e.leakToActive +	b.lines.push({...line}) +	if (!visible || b.newMessages || b.newUnimportantMessages) { +		if (line.isUnimportant) +			b.newUnimportantMessages++ +		else +			b.newMessages++ +	} +	if (e.leakToActive) { +		let bc = buffers.get(bufferCurrent) +		bc.lines.push({...line, leaked: true}) +		if (bc.newMessages || bc.newUnimportantMessages) { +			if (line.isUnimportant) +				bc.newUnimportantMessages++ +			else +				bc.newMessages++ +		} +	} + +	// TODO: Find some way of highlighting the tab in a browser. +	// TODO: Also highlight on unseen private messages, like xC does. +	if (!visible && line.isHighlight) +		b.highlighted = true  })  rpc.addEventListener('BufferClear', event => { @@ -267,10 +305,21 @@ let BufferList = {  	view: vnode => {  		let items = Array.from(buffers, ([name, b]) => { -			let attrs = {onclick: event => BufferList.activate(name)} -			if (name == bufferCurrent) -				attrs.class = 'active' -			return m('.item', attrs, name) +			let classes = [], displayName = name +			if (name == bufferCurrent) { +				classes.push('current') +			} else { +				if (b.highlighted) +					classes.push('highlighted') +				if (b.newMessages) { +					classes.push('activity') +					displayName += ` (${b.newMessages})` +				} +			} +			return m('.item', { +				onclick: event => BufferList.activate(name), +				class: classes.join(' '), +			}, displayName)  		})  		return m('.list', {}, items)  	}, @@ -387,7 +436,9 @@ let Buffer = {  			return  		let lastDateMark = undefined -		b.lines.forEach(line => { +		let markBefore = b.lines.length +			- b.newMessages - b.newUnimportantMessages +		b.lines.forEach((line, i) => {  			let date = new Date(line.when)  			let dateMark = date.toLocaleDateString()  			if (dateMark !== lastDateMark) { @@ -395,16 +446,20 @@ let Buffer = {  				lastDateMark = dateMark  			} +			if (i == markBefore) +				lines.push(m('.unread')) +  			let attrs = {}  			if (line.leaked)  				attrs.class = 'leaked' +			// TODO: Make use of isUnimportant.  			lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))  			lines.push(m(Content, {...attrs}, line))  		})  		let dateMark = new Date().toLocaleDateString() -		if (dateMark !== lastDateMark) +		if (dateMark !== lastDateMark && lastDateMark !== undefined)  			lines.push(m('.date', {}, dateMark))  		return m('.buffer', {}, lines)  	},  | 
