+
{{#if ready}}{{#if prevTimestamp}}
See earlier messages
@@ -79,18 +108,7 @@
{{nickOrName message.nick}} {{body}}
{{else}}
-
+ {{> media_message }}
{{/if}}
{{#if isLastRead message.timestamp}}
read
diff --git a/chat.less b/chat.less
index 965e6d874..663e0a91b 100644
--- a/chat.less
+++ b/chat.less
@@ -47,6 +47,37 @@
.media-heading { display: none; }
}
}
+ &.bb-starred-messages {
+ padding: 0px;
+ clear: both;
+ }
+ .bb-message-star {
+ display: inline-block;
+ width: 14px; height: 14px; margin-top: 1px; line-height: 14px;
+ vertical-align: text-top;
+ @mask_image: url("/packages/bootstrap/img/glyphicons-halflings.png");
+ -webkit-mask-image: @mask_image;
+ mask-image: @mask_image;
+ float: right;
+ &.starred {
+ -webkit-mask-position: -120px 0;
+ mask-position: -120px 0;
+ background: palegoldenrod;
+ }
+ &:not(.starred) {
+ -webkit-mask-position: -144px 0;
+ mask-position: -144px 0;
+ background: lightgrey;
+ }
+ }
+ &.can-modify-star .bb-message-star {
+ &.starred {
+ background: gold;
+ }
+ &:not(.starred) {
+ background: black;
+ }
+ }
.media {
position: relative; /* establish context for arrow placement */
margin-bottom: 6px;
diff --git a/client/chat.coffee b/client/chat.coffee
index 20254ae40..9b6afd3ce 100644
--- a/client/chat.coffee
+++ b/client/chat.coffee
@@ -122,6 +122,47 @@ nickEmail = (nick) ->
n = model.Nicks.findOne canon: cn
return model.getTag(n, 'Gravatar') or "#{cn}@#{settings.DEFAULT_HOST}"
+Template.starred_messages.onCreated ->
+ this.autorun =>
+ this.subscribe 'starred-messages', Session.get 'room_name'
+
+Template.starred_messages.helpers
+ messages: ->
+ # It would be nice to write a concatenation cursor, but it seems overkill
+ # for the number of starred messages we're ever likely to have.
+ r = []
+ for coll in [ model.OldMessages, model.Messages ]
+ c = coll.find {room_name: (Session.get 'room_name'), starred: true },
+ sort: [['timestamp', 'asc']]
+ transform: messageTransform
+ r.push c.fetch()...
+ r
+
+Template.media_message.events
+ 'click .bb-message-star.starred': (event, template) ->
+ return unless $(event.target).closest('.can-modify-star').size() > 0
+ Meteor.call 'setStarred', this._id, false
+ 'click .bb-message-star:not(.starred)': (event, template) ->
+ return unless $(event.target).closest('.can-modify-star').size() > 0
+ Meteor.call 'setStarred', this._id, true
+
+
+messageTransform = (m) ->
+ _id: m._id
+ message: m
+ # optional server-side follow-up info
+ followup: (( model.followupStyle() is 'server') and m.followup) or false
+ email: nickEmail m.nick
+ body: ->
+ body = m.body or ''
+ unless m.bodyIsHtml
+ body = UI._escape body
+ body = body.replace /\n|\r\n?/g, '
'
+ body = convertURLsToLinksAndImages body, m._id
+ if doesMentionNick m
+ body = highlightNick body, m.bodyIsHtml
+ new Spacebars.SafeString(body)
+
# Template Binding
Template.messages.helpers
room_name: -> Session.get('room_name')
@@ -148,28 +189,11 @@ Template.messages.helpers
"/chat/#{p.room_name}/#{p.to}"
messages: ->
room_name = Session.get 'room_name'
- nick = model.canonical(Session.get('nick') or '')
p = pageForTimestamp room_name, +Session.get('timestamp')
- serverFollowup = model.followupStyle() is 'server'
messages = messagesForPage p,
sort: [['timestamp','asc']]
- transform: (m) ->
- _id: m._id
- # optional server-side follow-up info
- followup: (serverFollowup and m.followup) or false
- message: m
-
- email: -> nickEmail this.message.nick
- body: ->
- body = this.message.body or ''
- unless this.message.bodyIsHtml
- body = UI._escape(body)
- body = body.replace(/\n|\r\n?/g, '
')
- body = convertURLsToLinksAndImages(body, this.message._id)
- if doesMentionNick(this.message)
- body = highlightNick(body, this.message.bodyIsHtml)
- new Spacebars.SafeString(body)
-
+ transform: messageTransform
+
selfScroll = null
touchSelfScroll = ->
@@ -337,7 +361,7 @@ scrollMessagesView = ->
touchSelfScroll()
instachat.scrolledToBottom = true
# first try using html5, then fallback to jquery
- last = document?.querySelector?('.bb-chat-messages > *:last-child')
+ last = document?.querySelector?('#messages > *:last-child')
if last?.scrollIntoView?
last.scrollIntoView()
else
diff --git a/lib/model.coffee b/lib/model.coffee
index 841f992ac..812bf3ebb 100644
--- a/lib/model.coffee
+++ b/lib/model.coffee
@@ -269,6 +269,7 @@ if Meteor.isServer
# presence: optional string ('join'/'part' for presence-change only)
# bot_ignore: optional boolean (true for messages from e.g. email or twitter)
# to: destination of pm (optional)
+# starred: boolean. Pins this message to the top of the puzzle page or blackboard.
# room_name: "
/", ie "puzzle/1", "round/1".
# "general/0" for main chat.
# "oplog/0" for the operation log.
@@ -305,6 +306,8 @@ if Meteor.isServer
M._ensureIndex {to:1, room_name:1, timestamp:-1}, {}
M._ensureIndex {nick:1, room_name:1, timestamp:-1}, {}
M._ensureIndex {room_name:1, timestamp:-1}, {}
+ M._ensureIndex {room_name:1, starred: -1, timestamp: 1},
+ partialFilterExpression: starred: true
# watch messages collection and set the followup field as appropriate
# (followup field should already be set properly when the field is
# archived into the OldMessages collection)
@@ -1170,6 +1173,26 @@ spread_id_to_link = (id) ->
newMsg._id = Messages.insert newMsg
return newMsg
+ setStarred: (id, starred) ->
+ check id, NonEmptyString
+ check starred, Boolean
+ # Entirely premature optimization: if starring a message, assume it's
+ # recent; if unstarring, assume it's old.
+ if starred
+ colls = [ Messages, OldMessages]
+ else
+ colls = [ OldMessages, Messages ]
+ for coll in colls
+ num = coll.update (
+ _id: id
+ to: null
+ system: $in: [false, null]
+ action: $in: [false, null]
+ oplog: $in: [false, null]
+ presence: null
+ ), $set: {starred: starred or null}
+ return if num > 0
+
updateLastRead: (args) ->
check args, ObjectWith
nick: NonEmptyString
diff --git a/puzzle.html b/puzzle.html
index 1dd3f206d..e07e754d4 100644
--- a/puzzle.html
+++ b/puzzle.html
@@ -18,7 +18,7 @@ Puzzle {{puzzle.name}}
{{#with puzzle}}{{> blackboard_tags }}{{/with}}
{{#if puzzle.solved}}{{#with puzzle}}{{> puzzle_correct_answer}}{{/with}}{{/if}}
{{#with puzzle}}{{> puzzle_incorrect_answers}}{{/with}}
-
+{{> starred_messages canModify=nick}}
{{#unless "true"}}
- Round: {{link id=round._id}}
diff --git a/server/server.coffee b/server/server.coffee
index 0151b4bcf..d2c159658 100644
--- a/server/server.coffee
+++ b/server/server.coffee
@@ -135,6 +135,11 @@ for messages in [ 'messages', 'oldmessages' ]
timestamp: cond
$or: [ { nick: nick }, { to: nick } ]
+Meteor.publish 'starred-messages', (room_name) ->
+ for messages in [ model.OldMessages, model.Messages ]
+ messages.find { room_name: room_name, starred: true },
+ sort: [["timestamp", "asc"]]
+
Meteor.publish 'callins', ->
model.CallIns.find {},
sort: [["created","asc"]]