diff --git a/lib/models.py b/lib/models.py
index 48bd90a..647e218 100644
--- a/lib/models.py
+++ b/lib/models.py
@@ -354,12 +354,13 @@ class Message(db.Model):
def ToEvent(self):
return {
- 'event_type': 'message',
- 'id': self.id_,
- 'sender': str(Message.sender.get_value_for_datastore(self)),
- 'subject': self.parent().ToDict(),
- 'created': self.created,
- 'message': self.message,
+ 'event_type': 'message',
+ 'id': self.id_,
+ 'sender': str(Message.sender.get_value_for_datastore(self)),
+ 'subject': self.parent().ToDict(),
+ 'created': self.created,
+ 'sender_message_id': self.sender_message_id,
+ 'message': self.message,
}
@@ -374,12 +375,13 @@ class Pin(db.Model):
def ToEvent(self, event_type='pin'):
return {
- 'event_type': event_type,
- 'id': str(self.key()),
- 'sender': str(Pin.sender.get_value_for_datastore(self)),
- 'subject': self.parent().ToDict(),
- 'created': self.created,
- 'message': self.message,
+ 'event_type': event_type,
+ 'id': str(self.key()),
+ 'sender': str(Pin.sender.get_value_for_datastore(self)),
+ 'subject': self.parent().ToDict(),
+ 'created': self.created,
+ 'sender_message_id': self.sender_message_id,
+ 'message': self.message,
}
def Delete(self):
diff --git a/static/debug.css b/static/debug.css
index 841dc01..691d3e9 100644
--- a/static/debug.css
+++ b/static/debug.css
@@ -1,4 +1,8 @@
-@import url(http://fonts.googleapis.com/css?family=Roboto);
+@import url(http://fonts.googleapis.com/css?family=Roboto:300,400|Source+Code+Pro);
+
+body {
+ overflow: hidden;
+}
container {
position: fixed;
@@ -13,15 +17,15 @@ container {
}
subjectcontainer {
- flex-grow: 1;
+ flex-basis: 20%;
}
messagecontainer {
- flex-grow: 2;
+ flex-basis: 40%;
}
pincontainer {
- flex-grow: 2;
+ flex-basis: 40%;
}
.verticalcontainer {
@@ -30,6 +34,19 @@ pincontainer {
align-items: stretch;
}
+.panel {
+ overflow: hidden;
+}
+
+status {
+ border-color: #ebccd1 !important;
+}
+
+status panelheading {
+ background-image: linear-gradient(to bottom, #f2dede 0, #ebcccc 100%) !important;
+ color: #4d1f1e !important;
+}
+
subjectcontainer .panel {
border-color: #bce8f1;
}
@@ -44,7 +61,7 @@ messagecontainer .panel {
}
messagecontainer panelheading {
- background-image: linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);
+ background-image: linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);
color: #274d05;
}
@@ -53,7 +70,7 @@ pincontainer .panel {
}
pincontainer panelheading {
- background-image: linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);
+ background-image: linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);
color: #4d3d21;
}
@@ -63,8 +80,10 @@ pincontainer panelheading {
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
- min-height: 60px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
}
panelheading {
@@ -75,8 +94,192 @@ panelheading {
border-top-right-radius: 3px;
font-size: 18px;
font-variant: small-caps;
+ -webkit-user-select: none;
+ cursor: default;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+panelbody {
+ flex-grow: 1;
+ overflow-x: hidden;
+ overflow-y: scroll;
}
.fixed {
flex-grow: 0;
+ flex-shrink: 0;
+}
+
+row {
+ white-space: pre-wrap;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ justify-content: center;
+}
+
+status row {
+ justify-content: flex-start;
+}
+
+subjectlist row {
+ justify-content: flex-start;
+}
+
+messagelist row {
+ justify-content: flex-start;
+}
+
+pinlist row {
+ justify-content: flex-start;
+}
+
+input[type=text] {
+ font-family: Roboto;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ background: linear-gradient(rgba(0, 0, 0, 0.025), white);
+ box-shadow: inset 1px 1px rgba(0, 0, 0, 0.05);
+ transition: all 0.1s ease-out;
+ padding: 5px;
+ color: #555;
+ flex-grow: 1;
+}
+
+input[type=text]:focus {
+ outline: none;
+ border: 1px solid rgba(0,0,0,0.5);
+}
+
+button {
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ color: #fff;
+ border-radius: 6px;
+ border: 1px solid transparent;
+ font-size: 14px;
+ font-variant: small-caps;
+ padding: 4px 4px;
+ outline: none;
+ background-repeat: repeat-x;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+button:hover {
+ background-position: 0 -10px;
+}
+
+button:active {
+ background-image: none;
+}
+
+messagecontainer button {
+ background-image: linear-gradient(to bottom, #5cb85c 0, #419641 100%);
+ border-color: #3e8f3e;
+}
+
+messagecontainer button:hover {
+ background-color: #419641;
+}
+
+messagecontainer button:active {
+ background-color: #419641;
+ border-color: #3e8f3e;
+}
+
+pincontainer button {
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+ border-color: #e38d13;
+}
+
+pincontainer button:hover {
+ background-color: #eb9316;
+}
+
+pincontainer button:active {
+ background-color: #eb9316;
+ border-color: #e38d13;
+}
+
+subjectcontainer button {
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+ border-color: #28a4c9;
+}
+
+subjectcontainer button:hover {
+ background-color: #2aabd2;
+}
+
+subjectcontainer button:active {
+ background-color: #2aabd2;
+ border-color: #28a4c9;
+}
+
+label {
+ padding: 4px 4px;
+ font-variant: small-caps;
+}
+
+value {
+ padding: 4px 0;
+ font-weight: 300;
+}
+
+select {
+ margin-top: 4px;
+ font-family: Roboto;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ background: linear-gradient(rgba(0, 0, 0, 0.025), white);
+ box-shadow: inset 1px 1px rgba(0, 0, 0, 0.05);
+ transition: all 0.1s ease-out;
+ padding: 5px;
+ color: #555;
+}
+
+item {
+ font-family: 'Source Code Pro';
+ display: block;
+ width: 100%;
+ padding: 5px;
+ border-bottom: 1px solid;
+ cursor: default;
+}
+
+item.selected {
+ background-color: #eeeeee;
+}
+
+subjectcontainer item {
+ border-color: #bce8f1;
+}
+
+subjectcontainer row {
+ color: #235066;
+}
+
+messagecontainer item {
+ border-color: #d6e9c6;
+}
+
+messagecontainer row {
+ color: #336606;
+}
+
+pincontainer item {
+ border-color: #faebcc;
+}
+
+pincontainer row {
+ color: #66512c;
+}
+
+error item {
+ background-color: #ebccd1;
+}
+
+error item.selected {
+ background-color: #ccb1b6;
}
diff --git a/static/debug.html b/static/debug.html
index c493dbb..2c8ce7c 100644
--- a/static/debug.html
+++ b/static/debug.html
@@ -1,32 +1,88 @@
-
+
+
+
+ Status
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Subjects
+
Subscribe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Messages
+
Send Message
+
+
+
+
+
+
Pins
+
Pin
+
+
+
+
+
+
diff --git a/static/debug.js b/static/debug.js
new file mode 100644
index 0000000..c752d7c
--- /dev/null
+++ b/static/debug.js
@@ -0,0 +1,273 @@
+var cosmo;
+var elements = {};
+var selectedSubject = null;
+var pins = {};
+var senders = {};
+
+var onReady = function() {
+ var elementIDs = [
+ 'connectionStatus',
+ 'loginAction',
+ 'loginStatus',
+ 'messageList',
+ 'messageText',
+ 'pinList',
+ 'pinText',
+ 'readID',
+ 'subjectList',
+ 'subjectName',
+ 'username',
+ 'writeID'
+ ];
+ for (var i = 0; i < elementIDs.length; i++) {
+ elements[elementIDs[i]] = document.getElementById(elementIDs[i]);
+ }
+
+ var callbacks = {
+ 'onConnect': onConnect,
+ 'onDisconnect': onDisconnect,
+ 'onLogin': onLogin,
+ 'onLogout': onLogout,
+ 'onMessage': onMessage,
+ 'onPin': onPin,
+ 'onUnpin': onUnpin,
+ }
+ cosmo = new Cosmopolite(callbacks);
+
+ document.getElementById('pin').addEventListener('click', pin);
+ document.getElementById('sendMessage').addEventListener('click', sendMessage);
+ document.getElementById('subscribe').addEventListener('click', subscribe);
+};
+
+document.addEventListener('DOMContentLoaded', onReady);
+
+var onConnect = function() {
+ elements['connectionStatus'].innerHTML = '';
+ elements['connectionStatus'].appendChild(
+ document.createTextNode('Connected'));
+};
+
+var onDisconnect = function() {
+ elements['connectionStatus'].innerHTML = '';
+ elements['connectionStatus'].appendChild(
+ document.createTextNode('Disconnected'));
+};
+
+var onLogin = function(username, logout_url) {
+ elements['loginStatus'].innerHTML = '';
+ elements['loginStatus'].appendChild(document.createTextNode('Logged in'));
+
+ elements['username'].innerHTML = '';
+ elements['username'].appendChild(document.createTextNode(username));
+
+ elements['loginAction'].innerHTML = '';
+ var link = document.createElement('a');
+ link.href = logout_url;
+ link.target = '_blank';
+ link.appendChild(document.createTextNode('Log out'));
+ elements['loginAction'].appendChild(link);
+};
+
+var onLogout = function(login_url) {
+ elements['loginStatus'].innerHTML = '';
+ elements['loginStatus'].appendChild(
+ document.createTextNode('Not logged in'));
+
+ elements['username'].innerHTML = '';
+
+ elements['loginAction'].innerHTML = '';
+ var link = document.createElement('a');
+ link.href = login_url;
+ link.target = '_blank';
+ link.appendChild(document.createTextNode('Log in'));
+ elements['loginAction'].appendChild(link);
+};
+
+var onMessage = function(msg) {
+ addToList(msg, elements['messageList']);
+};
+
+var onPin = function(msg) {
+ var item = addToList(msg, elements['pinList'], pins);
+ if (msg['sender'] == cosmo.currentProfile()) {
+ item.addEventListener('contextmenu', deletePin);
+ }
+};
+
+var onUnpin = function(msg) {
+ var item = pins[msg['id']];
+ item.parentNode.removeChild(item);
+};
+
+var selectSubject = function() {
+ if (selectedSubject == this) {
+ return;
+ }
+ if (selectedSubject) {
+ selectedSubject.className = '';
+ }
+ this.className = 'selected';
+ selectedSubject = this;
+
+ elements['messageList'].innerHTML = '';
+ cosmo.getMessages(this.subject).forEach(onMessage);
+
+ elements['pinList'].innerHTML = '';
+ cosmo.getPins(this.subject).forEach(onPin);
+};
+
+var addToList = function(msg, list, trackobj) {
+ if (selectedSubject && (
+ msg['subject']['name'] != selectedSubject.subject['name'] ||
+ msg['subject']['readable_only_by'] !=
+ selectedSubject.subject['readable_only_by'] ||
+ msg['subject']['writable_only_by'] !=
+ selectedSubject.subject['writable_only_by'])) {
+ return;
+ }
+
+ var item = document.createElement('item');
+ item.message = msg;
+
+ {
+ var row = document.createElement('row');
+ row.appendChild(document.createTextNode('Sender: '));
+ row.appendChild(document.createTextNode(senderID(msg)));
+ item.appendChild(row);
+ }
+ {
+ var row = document.createElement('row');
+ row.appendChild(document.createTextNode('Created: '));
+ row.appendChild(document.createTextNode(
+ (new Date(msg['created'] * 1000)).toString()));
+ item.appendChild(row);
+ }
+ item.appendChild(document.createTextNode(msg['message']));
+
+ list.insertBefore(item, list.firstChild);
+
+ if (trackobj) {
+ trackobj[msg['id']] = item;
+ }
+
+ return item;
+};
+
+var deletePin = function(e) {
+ cosmo.unpin(this.message['sender_message_id']);
+ e.preventDefault();
+};
+
+var deleteSubject = function(e) {
+ cosmo.unsubscribe(this.subject);
+ if (selectedSubject == this) {
+ selectedSubject = null;
+ elements['messageList'].innerHTML = '';
+ elements['pinList'].innerHTML = '';
+ }
+ this.parentNode.removeChild(this);
+ e.preventDefault();
+};
+
+var addSubject = function(subject, error) {
+ var item = document.createElement('item');
+ item.subject = subject;
+
+ item.appendChild(document.createTextNode(subject['name']));
+
+ {
+ var row = document.createElement('row');
+ row.appendChild(document.createTextNode('Read: '));
+ row.appendChild(
+ document.createTextNode(elements['readID'].selectedOptions[0].text));
+ item.appendChild(row);
+ }
+
+ {
+ var row = document.createElement('row');
+ row.appendChild(document.createTextNode('Write: '));
+ row.appendChild(
+ document.createTextNode(elements['writeID'].selectedOptions[0].text));
+ item.appendChild(row);
+ }
+
+ item.addEventListener('click', selectSubject);
+ item.addEventListener('contextmenu', deleteSubject);
+ if (error) {
+ var error = document.createElement('error');
+ error.appendChild(item);
+ elements['subjectList'].appendChild(error);
+ } else {
+ elements['subjectList'].appendChild(item);
+ }
+
+ if (!selectedSubject) {
+ selectSubject.bind(item)();
+ }
+};
+
+var subscribe = function() {
+ var subject = {
+ 'name': elements['subjectName'].value
+ };
+ if (elements['readID'].value != '(all)') {
+ var value = elements['readID'].value;
+ if (value == 'me') {
+ value = cosmo.currentProfile();
+ }
+ subject['readable_only_by'] = value;
+ }
+ if (elements['writeID'].value != '(all)') {
+ var value = elements['writeID'].value;
+ if (value == 'me') {
+ value = cosmo.currentProfile();
+ }
+ subject['writable_only_by'] = value;
+ }
+ cosmo.subscribe(subject, -1).then(function() {
+ addSubject(subject);
+ }, function() {
+ addSubject(subject, true);
+ });
+};
+
+var sendMessage = function() {
+ if (!selectedSubject) {
+ alert('Please select a subject.');
+ return;
+ }
+ cosmo.sendMessage(selectedSubject.subject, elements['messageText'].value);
+ elements['messageText'].value = '';
+};
+
+var pin = function() {
+ if (!selectedSubject) {
+ alert('Please select a subject.');
+ return;
+ }
+ cosmo.pin(selectedSubject.subject, elements['pinText'].value);
+ elements['pinText'].value = '';
+};
+
+var senderID = function(msg) {
+ var id = Math.abs(msg['sender'].hashCode() % 1000000);
+ if (msg['sender'] == cosmo.currentProfile()) {
+ return 'me';
+ }
+ if (!senders[id]) {
+ senders[id] = msg['sender'];
+ {
+ var option = document.createElement('option');
+ option.value = msg['sender'];
+ option.appendChild(document.createTextNode(id));
+ elements['readID'].appendChild(option);
+ }
+ {
+ var option = document.createElement('option');
+ option.value = msg['sender'];
+ option.appendChild(document.createTextNode(id));
+ elements['writeID'].appendChild(option);
+ }
+ }
+ return id;
+};