Skip to content

Commit

Permalink
Client side UI and input handling improvements
Browse files Browse the repository at this point in the history
* Add distinction between users and channels of the same name
* Allow connecting to channels with names that contain dots
* Fix tab notifications about unread messages
* Increase performance of members list UI
  • Loading branch information
syrio committed Sep 24, 2011
1 parent 38f9e3e commit 5296229
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 865 deletions.
143 changes: 92 additions & 51 deletions app/client/app.coffee
Expand Up @@ -13,6 +13,8 @@ SS.events.remove = (name) ->
# This method is called automatically when the websocket connection is established. Do not rename/delete
exports.init = ->

name_strip = (name) ->
name.replace /\.|\+|@|#/g, ''

subscribe_to_channel_events = (channel, user) ->

Expand All @@ -29,17 +31,21 @@ exports.init = ->
delete @cache[member]

existing_members_names = new MembersCache()
channel_ui = $(".#{channel}")
bare_channel = name_strip channel
channel_ui = $(".tabs-content.#{bare_channel}.channel")

SS.events.on "#{channel}:newMessage", (message) ->
message_view = $("<p><strong>#{message.user}:</strong> #{message.text}</p>")
chatlog = channel_ui.find('.chatlog')
message_view.appendTo(chatlog)

# dont annoy user while he is obviously reading by suddenly moving the scroll bar to the bototm
unless Math.abs(chatlog[0].scrollHeight - chatlog[0].scrollTop) > 350
# none moved the scroll bar to a custom location up away (i.e further than 350), so scroll down
chatlog[0].scrollTop = chatlog[0].scrollHeight

set_tab_unread(channel, 'channel')

SS.events.on "#{channel}:newMember", (member) ->
return if member.name == user
return if existing_members_names.in(member.name)
Expand Down Expand Up @@ -78,13 +84,15 @@ exports.init = ->
channel_ui.find('.members-items-list').replaceWith("<ul class='members-items-list'>#{member_ui_list_html}</ul>")


show_channel = (channel) ->
$(".visible").css('display', 'none')
bare_channel = channel.replace(/@|#/, '')
invisble_chatbox = $(".#{bare_channel}")
invisble_chatbox.css('display', 'block')
invisble_chatbox.addClass('visible')
invisble_chatbox.find('.message').focus()
show_tab = (name, type) ->
$('.visible').removeClass('visible')
bare_name = name_strip(name)
invisible_tab = $(".tabs-content.#{bare_name}.#{type}")
invisible_tab.addClass('visible')
invisible_tab.find('.message').focus()

# remove the unread styling on the tab link (if it exsits)
$(".tabs-link.#{bare_name}.#{type}").css('color', '#999')

update_tab_list = ->
# bind a handler for clicking on a the channel in the channels names tabs-list
Expand All @@ -96,12 +104,16 @@ exports.init = ->
unless event_text?
return

channel = event_text.split(' ')[0]
bare_channel = channel.replace(/@|#/, '')
# remove the unread styling on the tab link (if it exsits)
$(event.target).css('color', '#999')
tab_name = event_text.split(' ')[0]
bare_tab_name = name_strip(tab_name)

target = $(event.target)

type = 'private' if target.hasClass 'private'
type = 'channel' if target.hasClass 'channel'

# make the chatbox visible after user clicked on the channel name in the channels names tab-list
show_channel(bare_channel)
show_tab(bare_tab_name, type)

$('.tabs-close').unbind('click')
$('.tabs-close').click (event) ->
Expand All @@ -110,9 +122,13 @@ exports.init = ->
# this sperates clicking 'x' to close tab from switching tabs
event.stopPropagation()

tabs_link = $(event.target).parent('.tabs-link')
target = $(event.target)
type = 'private' if target.parent().hasClass 'private'
type = 'channel' if target.parent().hasClass 'channel'

tabs_link = target.parent('.tabs-link')
channel = tabs_link.text().split(' ')[0]
bare_channel = channel.replace(/@|#/, '')
bare_channel = name_strip(channel)

SS.server.app.leaveChannel channel, (success) ->
return unless success
Expand All @@ -125,22 +141,32 @@ exports.init = ->
SS.events.remove "#{bare_channel}:currentTopic"

prev_tab = tabs_link.prev()
prev_tab = tabs_link.next() if prev_tab.length == 0

tabs_link.remove()
$(event.target).remove()
$(".#{bare_channel}").remove()
target.remove()

$(".tabs-content.#{bare_channel}.#{type}").remove()

$('#status').removeClass().addClass('success').text("Left channel ##{bare_channel}")
leaving_status = if type == 'private' then 'chat with ' else 'channel #'
status = "Left #{leaving_status}#{bare_channel}"
$('#status').removeClass().addClass('success').text(status)

new_channel = prev_tab.text().split(' ')[0]
new_bare_channel = new_channel.replace(/@|#/, '')
show_channel(new_bare_channel)
new_bare_channel = name_strip(new_channel)

prev_type = 'private' if prev_tab.hasClass 'private'
# last one wins, but there really shouldn't be a situation where a tab has both 'private' and 'channel' types
prev_type = 'channel' if prev_tab.hasClass 'channel'

show_tab(new_bare_channel, prev_type)

add_tab_chatbox_handler = (channel, user) ->
add_tab_chatbox_handler = (channel, user, type) ->

bare_channel = channel.replace(/@|#/, '')
destination = channel.replace('@', '') # replace '@' for sending to ops users, don't touch '#' for channels
bare_channel = name_strip(channel)
destination = channel.replace(/\+|@/, '') # replace for sending to ops users, don't touch '#' or '.' for channels

form = $(".#{bare_channel}")
form = $(".tabs-content.#{bare_channel}.#{type}")
# handle submiting a new message in the channel chatbox
form.submit ->
message = form.find('.message').val()
Expand All @@ -157,59 +183,74 @@ exports.init = ->
else
$('#status').removeClass().addClass('error').text('Oops! You must type a message first')


create_channel_tab = (channel, user) ->
tab_nav = $('#tabs-nav').tmpl(channel: channel)

create_tab = (name, user, type) ->
bare_name = name_strip(name)

tab_nav = $('#tabs-nav').tmpl(channel: name)
tab_nav.addClass("#{bare_name}")
tab_nav.addClass(type)
$('.tabs').append(tab_nav)

tab_content = $('#tabs-content').tmpl()
tab_content.addClass("#{bare_name}")
tab_content.addClass(type)
$('#tabs').append(tab_content)

# add a #{channel} class to identify the new tab with the channel name
bare_channel = channel.replace(/@|#/, '')
$('.tabs-content').last().addClass("#{bare_channel}")

add_tab_chatbox_handler channel, user
add_tab_members_handler channel, user

add_tab_chatbox_handler name, user, type
add_tab_members_handler name, user

is_there_such_channel_tab = (tab) ->
create_channel_tab = (channel, user) ->
create_tab channel, user, 'channel'

$(".tabs-link:contains('#{tab}')").length > 0
create_private_tab = (member, user) ->
create_tab member, user, 'private'

is_there_such_channel_tab = (tab) ->
tab_element = name_strip(tab)
$(".tabs-link.#{tab_element}.channel").length > 0

is_there_such_private_tab = (tab) ->
tab_element = name_strip(tab)
$(".tabs-link.#{tab_element}.private").length > 0

set_tab_unread = (tab) ->
unread = $(".tabs-link:contains('#{tab}')")
unless unread? and $(".#{tab}").hasClass('visible')
set_tab_unread = (tab, type) ->
tab_element = name_strip(tab)
tab = $(".tabs-content.#{tab_element}.#{type}")
unless tab.hasClass('visible')
# force tab link text color change on unread
unread.css('color', '#C20303')
$(".tabs-link.#{tab_element}.#{type}").css('color', '#C20303')

add_tab_members_handler = (channel, user) ->

bare_channel = channel.replace(/@|#/, '')
bare_channel = name_strip(channel)

channel_members_items = $(".#{bare_channel}").find('.members')
channel_members_items = $(".tabs-content.#{bare_channel}.channel").find('.members')
channel_members_items.click (event) ->
member = event.target.text
return unless member?
unless is_there_such_channel_tab(member)
create_channel_tab(member, user)
unless is_there_such_private_tab(member)
create_private_tab(member, user)
update_tab_list()
show_channel(member)
show_tab(member, 'private')

handle_private_message = (to) ->
SS.events.on "newPrivateMessage", (message) ->

bare_from = message.from.replace(/@|#/, '')
bare_from = name_strip(message.from)

unless is_there_such_channel_tab(bare_from)
unless is_there_such_private_tab(message.from)
# create a tab for the user that sent us the priavte message if one doesn't already exist
# explictly send the receives username, this might later passed to IRC and we need it be exact (not bare)
create_channel_tab(message.from, to)
create_private_tab(message.from, to)
update_tab_list()

message_view = $("<p>#{message.text}</p>")
chatlog = $(".#{bare_from}").find('.chatlog')
chatlog = $(".tabs-content.#{bare_from}.private").find('.chatlog')
message_view.appendTo(chatlog)
chatlog[0].scrollTop = chatlog[0].scrollHeight # scroll down

set_tab_unread(bare_from)
set_tab_unread(message.from, 'private')

$('.join button').show().click ->

Expand Down Expand Up @@ -240,7 +281,7 @@ exports.init = ->

subscribe_to_channel_events(bare_channel, user)

show_channel(bare_channel)
show_tab(bare_channel, 'channel')


$('#reconnectButton').click (event) ->
Expand Down
16 changes: 12 additions & 4 deletions app/css/app.styl
Expand Up @@ -4,7 +4,6 @@
body
font normal 1em sans-serif
text-align center
background-image '/images/bkg.png'

h1
font-size 2.5em
Expand Down Expand Up @@ -61,14 +60,20 @@ h1
.members
cursor default
margin-top 10px
margin-left 45px
margin-left 10px
overflow auto
overflow-x hidden
height 290px
width 235px
height 340px
width 275px

.members-items-list
list-style-type none
float left

.members li
padding 0 80px 0 50px
float left


#currentUser
height 1.4em
Expand Down Expand Up @@ -143,3 +148,6 @@ h1
.error
color #C20303

.visible
display block

2 changes: 1 addition & 1 deletion app/views/tabs/members.jade
@@ -1,2 +1,2 @@
li
a.member(href='#') ${member.name}
a(href='#') ${member.name}
4 changes: 2 additions & 2 deletions config/app.coffee
Expand Up @@ -50,7 +50,7 @@ exports.config =
interval: 990

irc:
#server: 'irc.freenode.net' # example server
server: "localhost" # work with local server liked ircdjs
server: 'irc.freenode.net' # example server
#server: "localhost" # work with local server liked ircdjs
name: "constant-stream"
real_name: "IRC streamer"
54 changes: 0 additions & 54 deletions lib/css/tabs.css
Expand Up @@ -165,61 +165,7 @@
position: relative;
right: 50%;
}

.member {
display: block;
float: left;
font: 15px/35px Arial, Helvetica, Sans-serif;
padding: 0 40px 0 50px;
color: #999;
text-shadow: 0 1px 0 #FFF;
border-left: solid 1px rgba(0,0,0,0.05);
border-right: solid 1px rgba(255,255,255,0.7);
position: relative;
overflow: hidden;
}

.member:after {
content: "@";
position: absolute;
top: 0;
left: 10px;
line-height: 21px;
font-size: 10px;
width: 21px;
text-align: center;
margin: 7px 10px 5px 0;
background: #000;
font-size: 12px;
-moz-border-radius: 21px;
-webkit-border-radius: 21px;
border-radius: 21px;
background: #bdbdbd;
background: -moz-linear-gradient(center top , #d4d4d4, #bdbdbd);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d4d4d4), color-stop(100%,#bdbdbd));
-webkit-box-shadow: 0 1px 0 0 #FFF, 0 1px 0 0 rgba(0,0,0,0.25) inset;
-moz-box-shadow: 0 1px 0 0 #FFF, 0 1px 0 0 rgba(0,0,0,0.25) inset;
box-shadow: 0 1px 0 0 #FFF, 0 1px 0 0 rgba(0,0,0,0.25) inset;
text-shadow: 0 1px 0 #999;
color: #ffffff;
}
.member:hover {
background: #FFF;
border-left-color: #CCC;
}
.member:hover:after {
background: #038bd5;
background: -moz-linear-gradient(center top , #2dc3fc, #038bd5);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2dc3fc), color-stop(100%,#038bd5));
text-shadow: 0 1px 0 #096c9e;
-webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.45), 0 1px 0 0 rgba(0, 0, 0, 0.25) inset, 0 0 5px 0 rgba(0,148,255,0.85);
-moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.45), 0 1px 0 0 rgba(0, 0, 0, 0.25) inset, 0 0 5px 0 rgba(0,148,255,0.85);
box-shadow: 0 1px 0 0 rgba(255,255,255,0.45), 0 1px 0 0 rgba(0, 0, 0, 0.25) inset, 0 0 5px 0 rgba(0,148,255,0.85)
}


/* place this here since members a elements inherit all of their styling
from #tabs-items a styling, we just change the html entity to @' */

.tabs-close {
border-bottom-left-radius: 2px 2px;
Expand Down

0 comments on commit 5296229

Please sign in to comment.