Download:
child 25:bab5df239765
parent 23:98a9a4b85104
24:04329aa80b7a
Anton Shestakov <av6@dwimlabs.net>, Mon, 21 Mar 2016 19:57:24 +0800
messages: new ui element and related things

8 файлов изменено, 379 вставок(+), 0 удалений(-) [+]
coffee/messages.coffee file | annotate | diff | comparison | revisions
coffee/rivets.coffee file | annotate | diff | comparison | revisions
css/tram-im.css file | annotate | diff | comparison | revisions
index.html file | annotate | diff | comparison | revisions
js/messages.js file | annotate | diff | comparison | revisions
js/messages.js.map file | annotate | diff | comparison | revisions
js/rivets.js file | annotate | diff | comparison | revisions
js/rivets.js.map file | annotate | diff | comparison | revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/coffee/messages.coffee Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,96 @@
+class Tram.Message extends Backbone.Model
+ initialize: ->
+ @on 'add change:stamp', ->
+ if @has('stamp')
+ @set('d/mstamp', moment(@get('stamp')))
+ else
+ @unset('d/mstamp')
+
+
+class Tram.Messages extends Backbone.Collection
+ model: Tram.Message
+ splitThreshold: 30 * 60 * 1000
+ foldThreshold: 60 * 1000
+
+ comparator: (model) ->
+ model.get('stamp').valueOf()
+
+ initialize: ->
+ @on('add', @onAdd)
+
+ onAdd: (model) ->
+ mi = @indexOf(model)
+
+ prev = @models[mi - 1]
+ @_splitOrFold(prev, model)
+
+ next = @models[mi + 1]
+ @_splitOrFold(model, next)
+
+ _splitOrFold: (m1, m2) ->
+ if not m2?
+ return
+
+ if not m1?
+ m2.unset('d/split')
+ m2.unset('d/fold')
+ return
+
+ if @_splittable(m1, m2)
+ m2.set('d/split', true)
+ else if @_foldable(m1, m2)
+ m2.set('d/fold', true)
+
+ _splittable: (m1, m2) ->
+ Math.abs(m1.get('stamp').valueOf() - m2.get('stamp').valueOf()) > @splitThreshold
+
+ _foldable: (m1, m2) ->
+ # check stamp, type, etc
+ m1.get('from') is m2.get('from') and Math.abs(m1.get('stamp').valueOf() - m2.get('stamp').valueOf()) < @foldThreshold
+
+
+class Tram.MessageView extends Backbone.View
+ tagName: 'div'
+ className: 'message'
+ template: $('#message-template').html()
+
+ initialize: ->
+ @$el.html(@template)
+
+ @bind()
+
+ bind: ->
+
+ getHandle: ->
+ @model.get('contact')?.get('d/handle') or @model.get('from')
+
+ render: (model) ->
+ @rivet = rivets.bind(@el, model: @model, view: this)
+ @
+
+ remove: ->
+ @rivet.unbind()
+ super
+
+
+class Tram.LogApp extends Backbone.View
+ initialize: ->
+ @listenTo(@collection, 'add', @onAdd)
+
+ onAdd: (model, collection) ->
+ mi = collection.indexOf(model)
+ view = new Tram.MessageView(model: model)
+ el = view.render().el
+ if mi is 0
+ @$el.prepend el
+ else
+ @$el.children().eq(mi - 1).after(el)
+
+ if @bottomed
+ @scroll()
+
+ bottomed: ->
+ return @$el.scrollTop() + @$el.height() == @$el.get(0).scrollHeight
+
+ scroll: ->
+ @$el.scrollTop(@$el.get(0).scrollHeight)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/coffee/rivets.coffee Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,24 @@
+rivets.adapters[':'] =
+ observe: (obj, keypath, callback) ->
+ obj.on('change:' + keypath, callback)
+
+ unobserve: (obj, keypath, callback) ->
+ obj.off('change:' + keypath, callback)
+
+ get: (obj, keypath) ->
+ obj.get(keypath)
+
+ set: (obj, keypath, value) ->
+ obj.set(keypath, value)
+
+
+rivets.formatters['format-date'] = (value, format) ->
+ if value? then value.format(format) else ''
+
+
+rivets.formatters['iso-date'] = (value) ->
+ if value? then value.toISOString() else ''
+
+
+rivets.formatters['from-now'] = (value) ->
+ if value? then value.fromNow() else ''
--- a/css/tram-im.css Mon Mar 21 19:50:21 2016 +0800
+++ b/css/tram-im.css Mon Mar 21 19:57:24 2016 +0800
@@ -11,3 +11,14 @@
margin-left: 115px;
}
}
+.splitter {
+ border-top: 1px solid lightgray;
+ margin: 10px 0;
+}
+.column {
+ display: table-cell;
+ vertical-align: middle;
+}
+.message > .column {
+ vertical-align: top;
+}
--- a/index.html Mon Mar 21 19:50:21 2016 +0800
+++ b/index.html Mon Mar 21 19:57:24 2016 +0800
@@ -10,6 +10,8 @@
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/rivets/0.8.1/rivets.bundled.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.25.0/js/uikit.min.js"></script>
<script src="/vendor/strophejs/strophe.min.js"></script>
</head>
@@ -55,9 +57,22 @@
<i class="uk-icon-sign-out"></i>&nbsp;Log out
</button>
</div>
+
+ <script type="text/template" id="message-template">
+ <div class="splitter" rv-if="model:d/split"></div>
+ <div class="column uk-width-1-1">
+ <div style="padding-top: 4px;" rv-hide="model:d/fold">
+ <strong rv-text="view.getHandle < model:contact:d/handle model:from"></strong>
+ <time class="stamp uk-text-muted" rv-datetime="model:d/mstamp | iso-date" rv-title="model:d/mstamp | format-date 'HH:mm:ss'" rv-text="model:d/mstamp | from-now"></time>
+ </div>
+ <div rv-data-id="model:id" rv-class="model:cls" rv-text="model:text"></div>
+ </div>
+ </script>
<script src='/js/tram.js'></script>
+ <script src='/js/rivets.js'></script>
<script src='/js/validation.js'></script>
<script src='/js/xmpp.js'></script>
+ <script src='/js/messages.js'></script>
<script src='/config.js'></script>
<script src='/js/index.js'></script>
</body>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/messages.js Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,169 @@
+// Generated by CoffeeScript 1.10.0
+(function() {
+ var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+ Tram.Message = (function(superClass) {
+ extend(Message, superClass);
+
+ function Message() {
+ return Message.__super__.constructor.apply(this, arguments);
+ }
+
+ Message.prototype.initialize = function() {
+ return this.on('add change:stamp', function() {
+ if (this.has('stamp')) {
+ return this.set('d/mstamp', moment(this.get('stamp')));
+ } else {
+ return this.unset('d/mstamp');
+ }
+ });
+ };
+
+ return Message;
+
+ })(Backbone.Model);
+
+ Tram.Messages = (function(superClass) {
+ extend(Messages, superClass);
+
+ function Messages() {
+ return Messages.__super__.constructor.apply(this, arguments);
+ }
+
+ Messages.prototype.model = Tram.Message;
+
+ Messages.prototype.splitThreshold = 30 * 60 * 1000;
+
+ Messages.prototype.foldThreshold = 60 * 1000;
+
+ Messages.prototype.comparator = function(model) {
+ return model.get('stamp').valueOf();
+ };
+
+ Messages.prototype.initialize = function() {
+ return this.on('add', this.onAdd);
+ };
+
+ Messages.prototype.onAdd = function(model) {
+ var mi, next, prev;
+ mi = this.indexOf(model);
+ prev = this.models[mi - 1];
+ this._splitOrFold(prev, model);
+ next = this.models[mi + 1];
+ return this._splitOrFold(model, next);
+ };
+
+ Messages.prototype._splitOrFold = function(m1, m2) {
+ if (m2 == null) {
+ return;
+ }
+ if (m1 == null) {
+ m2.unset('d/split');
+ m2.unset('d/fold');
+ return;
+ }
+ if (this._splittable(m1, m2)) {
+ return m2.set('d/split', true);
+ } else if (this._foldable(m1, m2)) {
+ return m2.set('d/fold', true);
+ }
+ };
+
+ Messages.prototype._splittable = function(m1, m2) {
+ return Math.abs(m1.get('stamp').valueOf() - m2.get('stamp').valueOf()) > this.splitThreshold;
+ };
+
+ Messages.prototype._foldable = function(m1, m2) {
+ return m1.get('from') === m2.get('from') && Math.abs(m1.get('stamp').valueOf() - m2.get('stamp').valueOf()) < this.foldThreshold;
+ };
+
+ return Messages;
+
+ })(Backbone.Collection);
+
+ Tram.MessageView = (function(superClass) {
+ extend(MessageView, superClass);
+
+ function MessageView() {
+ return MessageView.__super__.constructor.apply(this, arguments);
+ }
+
+ MessageView.prototype.tagName = 'div';
+
+ MessageView.prototype.className = 'message';
+
+ MessageView.prototype.template = $('#message-template').html();
+
+ MessageView.prototype.initialize = function() {
+ this.$el.html(this.template);
+ return this.bind();
+ };
+
+ MessageView.prototype.bind = function() {};
+
+ MessageView.prototype.getHandle = function() {
+ var ref;
+ return ((ref = this.model.get('contact')) != null ? ref.get('d/handle') : void 0) || this.model.get('from');
+ };
+
+ MessageView.prototype.render = function(model) {
+ this.rivet = rivets.bind(this.el, {
+ model: this.model,
+ view: this
+ });
+ return this;
+ };
+
+ MessageView.prototype.remove = function() {
+ this.rivet.unbind();
+ return MessageView.__super__.remove.apply(this, arguments);
+ };
+
+ return MessageView;
+
+ })(Backbone.View);
+
+ Tram.LogApp = (function(superClass) {
+ extend(LogApp, superClass);
+
+ function LogApp() {
+ return LogApp.__super__.constructor.apply(this, arguments);
+ }
+
+ LogApp.prototype.initialize = function() {
+ return this.listenTo(this.collection, 'add', this.onAdd);
+ };
+
+ LogApp.prototype.onAdd = function(model, collection) {
+ var el, mi, view;
+ mi = collection.indexOf(model);
+ view = new Tram.MessageView({
+ model: model
+ });
+ el = view.render().el;
+ if (mi === 0) {
+ this.$el.prepend(el);
+ } else {
+ this.$el.children().eq(mi - 1).after(el);
+ }
+ if (this.bottomed) {
+ return this.scroll();
+ }
+ };
+
+ LogApp.prototype.bottomed = function() {
+ return this.$el.scrollTop() + this.$el.height() === this.$el.get(0).scrollHeight;
+ };
+
+ LogApp.prototype.scroll = function() {
+ return this.$el.scrollTop(this.$el.get(0).scrollHeight);
+ };
+
+ return LogApp;
+
+ })(Backbone.View);
+
+}).call(this);
+
+//# sourceMappingURL=messages.js.map
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/messages.js.map Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "messages.js",
+ "sourceRoot": "..",
+ "sources": [
+ "coffee/messages.coffee"
+ ],
+ "names": [],
+ "mappings": "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"
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/rivets.js Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,44 @@
+// Generated by CoffeeScript 1.10.0
+(function() {
+ rivets.adapters[':'] = {
+ observe: function(obj, keypath, callback) {
+ return obj.on('change:' + keypath, callback);
+ },
+ unobserve: function(obj, keypath, callback) {
+ return obj.off('change:' + keypath, callback);
+ },
+ get: function(obj, keypath) {
+ return obj.get(keypath);
+ },
+ set: function(obj, keypath, value) {
+ return obj.set(keypath, value);
+ }
+ };
+
+ rivets.formatters['format-date'] = function(value, format) {
+ if (value != null) {
+ return value.format(format);
+ } else {
+ return '';
+ }
+ };
+
+ rivets.formatters['iso-date'] = function(value) {
+ if (value != null) {
+ return value.toISOString();
+ } else {
+ return '';
+ }
+ };
+
+ rivets.formatters['from-now'] = function(value) {
+ if (value != null) {
+ return value.fromNow();
+ } else {
+ return '';
+ }
+ };
+
+}).call(this);
+
+//# sourceMappingURL=rivets.js.map
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/rivets.js.map Mon Mar 21 19:57:24 2016 +0800
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "rivets.js",
+ "sourceRoot": "..",
+ "sources": [
+ "coffee/rivets.coffee"
+ ],
+ "names": [],
+ "mappings": ";AAAA;EAAA,MAAM,CAAC,QAAS,CAAA,GAAA,CAAhB,GACE;IAAA,OAAA,EAAS,SAAC,GAAD,EAAM,OAAN,EAAe,QAAf;aACP,GAAG,CAAC,EAAJ,CAAO,SAAA,GAAY,OAAnB,EAA4B,QAA5B;IADO,CAAT;IAGA,SAAA,EAAW,SAAC,GAAD,EAAM,OAAN,EAAe,QAAf;aACT,GAAG,CAAC,GAAJ,CAAQ,SAAA,GAAY,OAApB,EAA6B,QAA7B;IADS,CAHX;IAMA,GAAA,EAAK,SAAC,GAAD,EAAM,OAAN;aACH,GAAG,CAAC,GAAJ,CAAQ,OAAR;IADG,CANL;IASA,GAAA,EAAK,SAAC,GAAD,EAAM,OAAN,EAAe,KAAf;aACH,GAAG,CAAC,GAAJ,CAAQ,OAAR,EAAiB,KAAjB;IADG,CATL;;;EAaF,MAAM,CAAC,UAAW,CAAA,aAAA,CAAlB,GAAmC,SAAC,KAAD,EAAQ,MAAR;IACjC,IAAG,aAAH;aAAe,KAAK,CAAC,MAAN,CAAa,MAAb,EAAf;KAAA,MAAA;aAAyC,GAAzC;;EADiC;;EAInC,MAAM,CAAC,UAAW,CAAA,UAAA,CAAlB,GAAgC,SAAC,KAAD;IAC9B,IAAG,aAAH;aAAe,KAAK,CAAC,WAAN,CAAA,EAAf;KAAA,MAAA;aAAwC,GAAxC;;EAD8B;;EAIhC,MAAM,CAAC,UAAW,CAAA,UAAA,CAAlB,GAAgC,SAAC,KAAD;IAC9B,IAAG,aAAH;aAAe,KAAK,CAAC,OAAN,CAAA,EAAf;KAAA,MAAA;aAAoC,GAApC;;EAD8B;AAtBhC"
+}
\ No newline at end of file