Establishes basic functionality for viewing conversations in two column
mode, including message area and message list resizing, and maintaining
scroll position.
Various subviews need to be retooled but are more or less still
functional, i.e., new message, message detail, key verification, etc...
When resolving conflicts, we should not only discard the old key, but
set the new trusted key to the one the user has verified. Previously, we
would end up trusting the first-seen new key, which may not be the one
the user verified.
// FREEBIE
Related with #278. Redone to include keeping scroll at the bottom when resizing the window, as suggested in #305, and to better fit the current code structure.
We're overriding the default with null often enough that we should
just change the default.
Consequently, no more phantom blank conversations with oneself should
appear after receiving a group update. They were being added to the
inbox because they were incorrectly initialized with an active_at value.
Fixes#281
Refreshing the background page does re-open the socket, but the inbox
and other windows don't reattach correctly. Reload the whole runtime to
force close all windows, reload the background, and re open the inbox.
In lieu of a click event, the change event was being fired when clicking
out of the search input. The input event seems to be what we actually want.
Fixes#273
Line breaks can now be insterted into message box using Shift+Enter or Alt+Enter. Messages with new lines are properly displayed in the conversation view (but only there, to keep inbox clean). The template was modified to allow HTML, but the message itself is sanitized before new line handling is run.
Protocol and handling is all analogous to contact sync: Multiple
GroupDetails structs are packed into a single attachment blob and parsed
on our end. We don't display the synced groups in the conversation list
until a new message is sent to one of them.
// FREEBIE
* Fix a css bug preventing bottom bar from sticking to the bottom.
* Resize discussion container as a function of the overall window
height. The previous difference-based method gives the wrong result
when the window height changes but the bottom-bar height stays the
same.
// FREEBIE
Fixes#264
Implement the equivalent of java's String.hashCode on the conversation model.
Change avatar template and attributes. Use css classes for colors.
This behavior was intended to help keep the websocket alive, but keeping
the inbox window around can cause some stale frontend state. Also we now
have a keepalive alarm to check for new messages once a minute.
As a chrome packaged app, we have to keep at least one window open in
order to maintain our websocket connection in the background page.
This change replaces the system window frame with custom buttons in the
inbox header, such that the 'close' button merely hides the window
rather than unloading it.
Fixes#237
FREEBIE
Creating a group with a member who's identity key has changed would
previously fail silently. Now, we catch and save the error, allowing the
same conflict resolution process as with regular messages.
Fixes#205
Previously there was a long pause between confirming the group details
and opening the conversation. Fix by first saving/opening the
conversation, rather than waiting for the initial group update to finish
transmitting.
This reverts commit 31e7d285e3.
This seemed like a nice feature, but the popup bubble isn't very
conducive to nontrivial user inputs, e.g. file inputs.
Fixes#211
Clicking on a key conflict message opens the message detail view,
which displays the contact(s) in this conversation. If the message
contains a key conflict with any of these contacts, a button is
displayed which attempts to resolve that conflict and any other
conflicts in the conversation that are related to that contact.
Rather than opening the inbox in its own window, let it appear as a
browser action popup by default, but allow promotion to its own window
if requested.
`emoji.init_colons` creates and populates `emoji.map.colons`, a global
map from common names to emoji code points. It's safe to call
repeatedly, but unecessary.
Update unreadCounts per-conversation on incoming messages. Render unread
conversations with font-weigh: bold in the inbox view.
To ensure that the inbox and conversation views remain in sync, the
background page now ensures that the same models objects are used for
both views.
Templatize the inbox view and use the same pattern for in-window view
switching as is now used with the conversation/message detail views.
This means doing more with markup and less jquery manipulation of
individual subelements of the inbox view.
Closes#173
Previously, in the event of a failed websocket auth, we would attempt to
reconnect once a second ad infinitum. This changeset ensures that we
only reconnect automatically if the socket closed 'normally' as
indicated by the code on the socket's CloseEvent. Otherwise, show a
'Websocket closed' error on the inbox view.
Ideally we would show a more contextual error (ie, 'Unauthorized'), but
unfortunately the actual server response code is not available to our
code. It can be observed in the console output from the background page,
but programmatically, we only receive the WebSocket CloseEvent codes
listed here:
https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
The websocket error message is displayed by a normally-hidden but ever
present socket status element. Clicking this element will immediately
refresh the background page, which will try again to open the websocket
connection.
Only re-render a message if the body changed. Re-render only the
delivery receipt checkmark if the delivered property changes.
Fix a bug where attachments flash in and out of existance when a
delivery receipt arrives.
Define a Whisper.View base class that automatically parses and renders
templates and attributes defined by the subclass. This saves us a good
number of lines of code as well as some marginal memory overhead, since
we are no longer saving per-instance copies of template strings.
Although I find the previous implementation more elegant, it results in
a deeper nesting of Promises than necessary, which can make debugging
more complicated. The canvas scaling and compression apis are actually
synchronous, so the callback structure isn't really recessary here.
Converting to a loop also makes this process easier to understand at
a glance.
Fixed some bugs along the way:
* accidentally scaling small images up to 1920px
* jpeg compressing gifs and other formats even if unnecessary
Previously we would not scale large resolution images with small file
sizes, but in fact, both resolution and file size constraints should be
enforced.
Converting attachment data to base64-encoded data uris takes O(n) and
there's no need! URL.createObjectURL returns a magic link that can be
set as the `src` attribute to `img`, `video`, and `audio` tags to load
blob data directly without copying.
https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
Add contentType-specific limits, switch to lazy-init iff we encounter an
oversized file, and restyle as a toast, factoring out a generic
ToastView along the way.
Wait a little longer on initial scroll down. Previous timeout sometimes
triggered before all text is finished rendering.
Remove redundant resize calls.
Background page conversations were trying to trigger events on the inbox
list view which had been destroyed, resulting in a background page
console error of "can't read innerHeight of null".
Avoid this by removing listeners when the inbox window is closed.
The message view has three flavors so far, a normal text+attachments
message, a group update, and an end session message. This changeset
extracts the normal message rendering into its own subview, and adds
some convenience functions to the message model in order to simplify
some of that flavoring logic.
The first message sent to a new contact was throwing 'Unknown Group'.
This was because we didn't wait for the initial save to sync the `type`
attribute to indexedDB. Instead, don't trigger the conversation to open
until it has finished saving.
This is an artifact of a time when conversation elements would pop in
and out of the dom at a moment's notice, and thus needed to rebind their
event listeners regularly.
When a new message arrives, if its conversation is not already opened,
the background page opens it. If it is alrady open the window is
focused. Finally, the 'message' event is triggered, resulting in
1. the inbox refetches conversations
2. all conversations fetch new messages
TODO: only send this event to the target window
This collection is just an in-memory indexer used for typeaheads. For
display, the matching models are added to a separate collection. Thus,
the order of the elements in the typeahead collection does not matter.
It feels a little weird when you can't see the matching member. Would
consider putting this back in if we display the member list in the
contact list item view.
Previously, the ugly file input was hidden with opacity, and styled as a
square paperclip icon, but its drop and click zones were not constrained
to the visible square. They remained active across the whole 'Choose
File' button, which overlapped with the textarea. Instead, hide the file
input complete (display: none) and transmit click events from the
paperclip to the input programmatically.
Eventually, we'll need to address drag and drop events, but I want to do
that at the window level. Otherwise dropping a file outside the file
input drop zone causes the browser to navigate to the file://... url.
Render the entire conversation from a template, because some parts of it
must be rendered conditionally if it is a group vs private conversation.
Also apply some style fixes and restore lost functionality:
* Make conversation title bar fixed.
* Widens message bubbles.
* Unhide message list.
* Restore attachment rendering.
* Restore message sending and attachment file selection.
* Style attachments file input as a paperclip.
* Style send button like on Android and make it a submit input.
Don't auto open the last conversation. It doesn't make sense now that we
no longer have two column layout.
Don't trigger/listen for selected events. There's no need since the list
item opens a new popup now.
Just display a sensible default in the frontend if it's unset.
For private conversations this should be the phone number, for
groups, the list of numbers.
Uses app-level timestamps for outgoing messages.
Adds timestamp property to the outgoing jsonData.
Triggers a runtime event to notify frontend on delivery receipts.
Renders delivered messages with a 'delivered' class.
This change removes the timestamp field from messages and conversations
in favor of multiple semantically named timestamp fields: sent_at,
received_at on messages; active_at on conversations. This requires/lets
us rethink and improve our indexing scheme thusly:
The inbox index on conversations will order entries by the
conversation.active_at property, which should only appear on
conversations destined for the inbox.
The receipt index will use the message.sent_at property, for effecient
lookup of outgoing messages by timestamp, for use in processing delivery
receipts.
The group index on conversation.members is multi-entry, meaning that
looking up any phone number in this index will efficiently yield all
groups the number belongs to.
The conversation index lets us scan messages in a single conversation,
in the order they were received (or the reverse order). It is a compound
index on [conversationId, received_at].
This was used to conditionally render messages in the group style, but
it's actually unnecessary. We can render the same markup in both cases
and change the appearance with css.
Move base64 encoding of attachments to an AttachmentView. This makes
image rendering an asynchronous task so we fire an update event to
indicate to the parent MessageListView that its content has changed
height and it is time to scroll down.
Register the runtime callback at the top level view rather than having
each conversation view register independently.
Also refactors Layout into InboxView.
After a message is saved asynchronsly, fire an event and pass the
message attributes to frontend listeners via the chrome-runtime API.
This behavior is similar to the 'storage' event fired by localStorage.
Getting up and running with IndexedDB was pretty easy, thanks to
backbone. The tricky part was making reads and writes asynchronous.
In that process I did some refactoring on Whisper.Threads, which
has been renamed Conversations for consistency with the view names.
This change also adds the unlimitedStorage permission.
This dependency may be a little heavy for our current use case, but we can
roll with it for now and find something slimmer if it turns out yagni.
Closes#77Closes#40
Also,
* moved fetch out of the list view
* removed unused #last() function
* put test setup lines in their own tiny file.
* added data-cover to view script tags for code coveage reports.
The layout class is the only class that should have knowledge of
page-level constant markup, such as #gutter and #contacts, and
should be pretty much the only place we find elements by id (with
the exception of template elements).
This change removes references to #gutter from views. Rather than
hardcoding assumptions about page layout, view elements should
ask the layout to insert themselves into the main content area by
calling Whisper.Layout.setContent.
Each conversation views now manages its own separate elements
rather than all binding to a shared #conversation element, and
similarly for message composition ui.
Also includes the beginnings of group creation UI (not working yet),
featuring bootstrap-tagsinput field for entering group recipients
Let ConversationListItemView save a reference to its corresponding
ConversationView. This lets it render or delegate/undelegate events
when opening and closing a conversation.
Similarly for ConversationView itself, which contains a MessageListView.
When a thread is 'destroyed' from the UI we delete its messages and mark
the thread as inactive, (in other words, keep it around as contact info).
Additionally, we only load active threads when initializing the UI, and
reactivate threads when new messages are added to them.
Conflicts:
js/models/messages.js
js/models/threads.js
js/views/conversations/show.js
There were a few problems.
1. The message event was being triggered in background, not popup
2. The initial message/thread fetches from localStorage were mis-ordered
3. The timestamp wasn't being extracted from the right place
4. #3 caused messages to fail validation and not be saved
1-3 are fixed. To address 4 I switched validate() to log a warning
instead of preventing save.
Adds thread model/collection for managing conversation-level state, such
as unreadCounts, group membership, thread order, etc... plus various UI
improvements enabled by thread model, including an improved compose
flow, and thread-destroy button.
Adds Whisper.notify for presenting messages to the user in an orderly
fashion. Currently using a growl-style fade in/out effect.
Also some housekeeping:
Cut up views into separate files.
Partial fix for formatTimestamp.
Tweaked buttons and other styles.
Slight revert from said commit. We really do need the
IncomingPushMessageSignal protobuf at the UI layer, mostly because
it contains the 'source' attribute, without which we don't know
who sent the message.
Also fix a crash when there are no attachments on a message.
The 'sender' field actually holds the recipient for outgoing
messages. Rename that field to 'person', indicating the 2nd
party generically.
Also decouples the thread name from thread recipients at the
view layer, in preparation for group support.
Adds Backbone-based Whisper.Messages model/collection with local storage
extension. Saves sent and received messages in Whisper.Messages instead
of message map. This will assign a unique id to the message and save it
to localStorage.
Adds Backbone-based view to popup.html
Automatically updates itself when new messages are saved to
Whisper.Messages db from the background page.
Added some shiny new styles, and started splitting up css into multiple
files for sanity's sake.