.generators 0000664 0000000 0000000 00000003323 13345106031 0013225 0 ustar 00root root 0000000 0000000
.gitignore 0000775 0000000 0000000 00000000723 13345106031 0013047 0 ustar 00root root 0000000 0000000 # See http://help.github.com/ignore-files/ for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile ~/.gitignore_global
# Ignore bundler config
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
/config/initializers/secret_token.rb .rakeTasks 0000664 0000000 0000000 00000022523 13345106031 0013007 0 ustar 00root root 0000000 0000000
Gemfile 0000775 0000000 0000000 00000002517 13345106031 0012355 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gem 'rails', '~> 3.2.11'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'mongoid', '~> 3.0.20'
gem 'xmpp4r', '~> 0.5'
gem 'thin', '~> 1.5.0'
gem 'websocket-rails', '~> 0.4.3'
gem 'bcrypt-ruby'
gem 'rmagick', '~> 2.13.2'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'normalize-rails'
gem 'compass-rails'
gem 'backbone-rails'
gem 'haml_coffee_assets'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platforms => :ruby
gem 'uglifier', '>= 1.0.3'
end
group :development, :test do
end
group :development do
gem 'meta_request'
gem 'better_errors'
gem 'binding_of_caller'
end
group :test do
gem 'minitest'
gem 'minitest-reporters', '>= 0.5.0'
gem 'test-unit'
gem 'timecop'
gem 'rspec-rails'
gem 'capybara'
gem 'launchy'
gem 'factory_girl_rails'
gem 'guard-spork'
gem 'spork'
gem 'phantomjs-binaries'
gem 'casperjs'
end
gem 'jquery-rails', '~> 2.2.1'
gem 'haml', '~> 3.1.7'
gem 'haml-rails', '~> 0.3.5'
gem 'i18n-js'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
# Deploy with Capistrano
gem 'capistrano'
# To use debugger
# gem 'debugger'
Gemfile.lock 0000775 0000000 0000000 00000015105 13345106031 0013301 0 ustar 00root root 0000000 0000000 GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.3)
actionpack (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
activerecord (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
addressable (2.3.3)
ansi (1.4.3)
arel (3.0.2)
backbone-rails (1.0.0)
rails (>= 3.0.0)
bcrypt-ruby (3.0.1)
better_errors (0.7.2)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
binding_of_caller (0.7.1)
debug_inspector (>= 0.0.1)
builder (3.0.4)
capistrano (2.14.2)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
net-ssh (>= 2.0.14)
net-ssh-gateway (>= 1.1.0)
capybara (2.0.3)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 1.0.0)
casperjs (1.0.0)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
chunky_png (1.2.8)
coderay (1.0.9)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.6.2)
compass (0.12.2)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
compass-rails (1.0.3)
compass (>= 0.12.2, < 0.14)
daemons (1.1.9)
debug_inspector (0.0.2)
diff-lcs (1.2.2)
em-synchrony (1.0.3)
eventmachine (>= 1.0.0.beta.1)
erubis (2.7.0)
eventmachine (1.0.3)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
faye-websocket (0.4.7)
eventmachine (>= 0.12.0)
ffi (1.6.0)
formatador (0.2.4)
fssm (0.2.10)
guard (1.7.0)
formatador (>= 0.2.4)
listen (>= 0.6.0)
lumberjack (>= 1.0.2)
pry (>= 0.9.10)
thor (>= 0.14.6)
guard-spork (1.5.0)
childprocess (>= 0.2.3)
guard (>= 1.1)
spork (>= 0.8.4)
haml (3.1.8)
haml-rails (0.3.5)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
haml (~> 3.1)
railties (>= 3.1, < 4.1)
haml_coffee_assets (1.11.1)
coffee-script (>= 1.0.0)
sprockets (>= 2.0.3)
tilt (>= 1.3.3)
hashie (2.0.3)
highline (1.6.16)
hike (1.2.2)
hiredis (0.4.5)
i18n (0.6.1)
i18n-js (2.1.2)
i18n
journey (1.0.4)
jquery-rails (2.2.1)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.0)
launchy (2.2.0)
addressable (~> 2.3)
listen (0.7.3)
lumberjack (1.0.3)
mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
meta_request (0.2.3)
rack-contrib
railties
method_source (0.8.1)
mime-types (1.22)
minitest (4.7.0)
minitest-reporters (0.14.12)
ansi
builder
minitest (>= 2.12, < 5.0)
powerbar
mongoid (3.0.23)
activemodel (~> 3.1)
moped (~> 1.2)
origin (~> 1.0)
tzinfo (~> 0.3.22)
moped (1.4.5)
multi_json (1.7.3)
net-scp (1.1.0)
net-ssh (>= 2.6.5)
net-sftp (2.1.1)
net-ssh (>= 2.6.5)
net-ssh (2.6.6)
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
nokogiri (1.5.9)
normalize-rails (2.0.1)
origin (1.0.11)
phantomjs-binaries (1.8.1.1)
sys-uname (= 0.9.0)
polyglot (0.3.3)
powerbar (1.0.11)
ansi (~> 1.4.0)
hashie (>= 1.1.0)
pry (0.9.12)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-contrib (1.1.0)
rack (>= 0.9.1)
rack-ssl (1.3.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.13)
actionmailer (= 3.2.13)
actionpack (= 3.2.13)
activerecord (= 3.2.13)
activeresource (= 3.2.13)
activesupport (= 3.2.13)
bundler (~> 1.0)
railties (= 3.2.13)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.4)
rdoc (3.12.2)
json (~> 1.4)
redis (3.0.3)
rmagick (2.13.2)
rspec-core (2.13.1)
rspec-expectations (2.13.0)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.13.0)
rspec-rails (2.13.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.13.0)
rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0)
rubyzip (0.9.9)
sass (3.2.7)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.31.0)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
slop (3.4.4)
spork (0.9.2)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sys-uname (0.9.0)
ffi (>= 1.0.0)
test-unit (2.5.4)
thin (1.5.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.18.1)
tilt (1.4.1)
timecop (0.6.1)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.37)
uglifier (1.3.0)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
websocket (1.0.7)
websocket-rails (0.4.3)
em-synchrony
faye-websocket
hiredis
rack
rails
redis
thin
xmpp4r (0.5)
xpath (1.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
backbone-rails
bcrypt-ruby
better_errors
binding_of_caller
capistrano
capybara
casperjs
coffee-rails (~> 3.2.1)
compass-rails
factory_girl_rails
guard-spork
haml (~> 3.1.7)
haml-rails (~> 0.3.5)
haml_coffee_assets
i18n-js
jquery-rails (~> 2.2.1)
launchy
meta_request
minitest
minitest-reporters (>= 0.5.0)
mongoid (~> 3.0.20)
normalize-rails
phantomjs-binaries
rails (~> 3.2.11)
rmagick (~> 2.13.2)
rspec-rails
sass-rails (~> 3.2.3)
spork
test-unit
thin (~> 1.5.0)
timecop
uglifier (>= 1.0.3)
websocket-rails (~> 0.4.3)
xmpp4r (~> 0.5)
README.rdoc 0000775 0000000 0000000 00000021770 13345106031 0012672 0 ustar 00root root 0000000 0000000 == Welcome to Rails
Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the Model-View-Control pattern.
This pattern splits the view (also called the presentation) into "dumb"
templates that are primarily responsible for inserting pre-built data in between
HTML tags. The model contains the "smart" domain objects (such as Account,
Product, Person, Post) that holds all the business logic and knows how to
persist themselves to a database. The controller handles the incoming requests
(such as Save New Account, Update Product, Show Post) by manipulating the model
and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in
link:files/vendor/rails/activerecord/README.html.
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
Rails. You can read more about Action Pack in
link:files/vendor/rails/actionpack/README.html.
== Getting Started
1. At the command prompt, create a new Rails application:
rails new myapp (where myapp is the application name)
2. Change directory to myapp and start the web server:
cd myapp; rails server (run with --help for options)
3. Go to http://localhost:3000/ and you'll see:
"Welcome aboard: You're riding Ruby on Rails!"
4. Follow the guidelines to start developing your application. You can find
the following resources handy:
* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
== Debugging Rails
Sometimes your application goes wrong. Fortunately there are a lot of tools that
will help you debug it and get it back on the rails.
First area to check is the application log files. Have "tail -f" commands
running on the server.log and development.log. Rails will automatically display
debugging and runtime information to these files. Debugging info will also be
shown in the browser on requests from 127.0.0.1.
You can also log your own messages directly into the log file from your code
using the Ruby logger class from inside your controllers. Example:
class WeblogController < ActionController::Base
def destroy
@weblog = Weblog.find(params[:id])
@weblog.destroy
logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
end
end
The result will be a message in your log file along the lines of:
Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
More information on how to use the logger is at http://www.ruby-doc.org/core/
Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
several books available online as well:
* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
These two books will bring you up to speed on the Ruby language and also on
programming in general.
== Debugger
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
resume execution! You need to install ruby-debug to run the server in debugging
mode. With gems, use sudo gem install ruby-debug. Example:
class WeblogController < ActionController::Base
def index
@posts = Post.all
debugger
end
end
So the controller will accept the action, run the first line, then present you
with a IRB prompt in the server window. Here you can do things like:
>> @posts.inspect
=> "[#nil, "body"=>nil, "id"=>"1"}>,
#"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
>> @posts.first.title = "hello from a debugger"
=> "hello from a debugger"
...and even better, you can examine how your runtime objects actually work:
>> f = @posts.first
=> #nil, "body"=>nil, "id"=>"1"}>
>> f.
Display all 152 possibilities? (y or n)
Finally, when you're ready to resume execution, you can enter "cont".
== Console
The console is a Ruby shell, which allows you to interact with your
application's domain model. Here you'll have all parts of the application
configured, just like it is when the application is running. You can inspect
domain models, change values, and save to the database. Starting the script
without arguments will launch it in the development environment.
To start the console, run rails console from the application
directory.
Options:
* Passing the -s, --sandbox argument will rollback any modifications
made to the database.
* Passing an environment name as an argument will load the corresponding
environment. Example: rails console production.
To reload your controllers and models after launching the console run
reload!
More information about irb can be found at:
link:http://www.rubycentral.org/pickaxe/irb.html
== dbconsole
You can go to the command line of your database directly through rails
dbconsole. You would be connected to the database with the credentials
defined in database.yml. Starting the script without arguments will connect you
to the development database. Passing an argument will connect you to a different
database, like rails dbconsole production. Currently works for MySQL,
PostgreSQL and SQLite 3.
== Description of Contents
The default directory structure of a generated Ruby on Rails application:
|-- app
| |-- assets
| |-- images
| |-- javascripts
| `-- stylesheets
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
| `-- layouts
|-- config
| |-- environments
| |-- initializers
| `-- locales
|-- db
|-- doc
|-- lib
| `-- tasks
|-- log
|-- public
|-- script
|-- test
| |-- fixtures
| |-- functional
| |-- integration
| |-- performance
| `-- unit
|-- tmp
| |-- cache
| |-- pids
| |-- sessions
| `-- sockets
`-- vendor
|-- assets
`-- stylesheets
`-- plugins
app
Holds all the code that's specific to this particular application.
app/assets
Contains subdirectories for images, stylesheets, and JavaScript files.
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from
ApplicationController which itself descends from ActionController::Base.
app/models
Holds models that should be named like post.rb. Models descend from
ActiveRecord::Base by default.
app/views
Holds the template files for the view that should be named like
weblogs/index.html.erb for the WeblogsController#index action. All views use
eRuby syntax by default.
app/views/layouts
Holds the template files for layouts to be used with views. This models the
common header/footer method of wrapping views. In your views, define a layout
using the layout :default and create a file named default.html.erb.
Inside default.html.erb, call <% yield %> to render the view using this
layout.
app/helpers
Holds view helpers that should be named like weblogs_helper.rb. These are
generated for you automatically when using generators for controllers.
Helpers can be used to wrap functionality for your views into methods.
config
Configuration files for the Rails environment, the routing map, the database,
and other dependencies.
db
Contains the database schema in schema.rb. db/migrate contains all the
sequence of Migrations for your schema.
doc
This directory is where your application documentation will be stored when
generated using rake doc:app
lib
Application specific libraries. Basically, any kind of custom code that
doesn't belong under controllers, models, or helpers. This directory is in
the load path.
public
The directory available for the web server. Also contains the dispatchers and the
default HTML files. This should be set as the DOCUMENT_ROOT of your web
server.
script
Helper scripts for automation and generation.
test
Unit and functional tests along with fixtures. When using the rails generate
command, template test files will be generated for you and placed in this
directory.
vendor
External libraries that the application depends on. Also includes the plugins
subdirectory. If the app has frozen rails, those gems also go here, under
vendor/rails/. This directory is in the load path.
Rakefile 0000775 0000000 0000000 00000000415 13345106031 0012522 0 ustar 00root root 0000000 0000000 #!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
Xmpp::Application.load_tasks
app/ 0000775 0000000 0000000 00000000000 13345106031 0011632 5 ustar 00root root 0000000 0000000 app/assets/ 0000775 0000000 0000000 00000000000 13345106031 0013134 5 ustar 00root root 0000000 0000000 app/assets/javascripts/ 0000775 0000000 0000000 00000000000 13345106031 0015465 5 ustar 00root root 0000000 0000000 app/assets/javascripts/_main.js.coffee 0000775 0000000 0000000 00000023125 13345106031 0020342 0 ustar 00root root 0000000 0000000 this.App =
debug: (msg) ->
console.log msg
stripJid: (jid) ->
r = new RegExp('(^.*)\/.*$')
match = r.exec(jid)
if match and (match.length > 0)
return match[1]
else
return jid
UI:
setAutoHeight: ->
App.UI.resizeRoster()
App.UI.resizeMessages()
resizeRoster: ->
if (windowHeight < $('.roster').css('min-height').replace(/[^-\d\.]/g, ''))
return
windowHeight = $(window).height()
toolboxHeight = $("#height-setter-1 .toolbox").height()
$(".friends").css({height: windowHeight - toolboxHeight - $('#js-me').height() - 40})
$("#height-setter-1").css({height: windowHeight - toolboxHeight - $('#js-active-friends').height() - 120})
resizeMessages: ->
windowHeight = $(window).height()
$("#height-setter-2").css({height: windowHeight - $('#tabbar').outerHeight() - $('#msg-writer').outerHeight() - 62})
messagesContainer = $('.messages')
messagesContainer.animate(scrollTop: messagesContainer.height(), 'fast')
filterContacts: (searchTerm) ->
App.Collections.contacts.filter(searchTerm)
Com:
connect: (callback) ->
App._dispatcher = new WebSocketRails('www.xmpp.dev:3000/websocket')
App._dispatcher.on_open = =>
@_setupBackboneComponents()
@_bindListeners(callback)
trigger: (options) ->
_.defaults(options, {data: {} })
App._dispatcher.trigger(options.event, options.data, options.success, options.error)
initRoster: (callback) ->
App.Com.trigger(
event: 'app.roster.initRoster'
success: (data) ->
_.each(data.contacts, (contact) ->
newContact = new Xmpp.Models.Contact(
id: contact.jid
jid: contact.jid
belongsTo: [contact.belongsTo]
)
App.Collections.contacts.add(newContact, merge: true)
)
callback?()
)
startFetchingVcards: ->
App.Com.trigger(event: 'app.roster.startFetchingVcards')
startPollingRoster: ->
App.Com.trigger(event: 'app.roster.startPolling')
startPollingMessages: ->
App.Com.trigger(event: 'app.chat.startPollingMessages')
setPresence: ->
App.Com.trigger(event: 'app.roster.setPresence')
getMe: ->
App.Com.trigger(event: 'app.roster.myself', success: (response) ->
App.Models.me = new Xmpp.Models.Me(
jid: response.jid
name: response.vcard.name
status: response.status
avatar: response.vcard.avatar
)
)
sendMessage: (message, to, from, callbackOk, callbackFail) ->
App.Com.trigger(event: 'app.chat.sendMessage', data: {message: message, to: to, from: from}, success: callbackOk, error: callbackFail)
sendMultiMessage: (message, chatId, from, callbackOk, callbackFail) ->
App.Com.trigger(event: 'app.chat.sendMessage', data: {message: message, chatId: chatId, from: from}, success: callbackOk, error: callbackFail)
openNewMultiChat: (chatOwner, attendant, chat) ->
@trigger(event: 'app.chat.newMultiChat', data: {chatOwner: chatOwner.get('jid')}, success: (response) =>
chat.setChatId(response.id)
chat.appendWithWhom(attendant)
@trigger(event: 'app.chat.addToMultiChat', data: {chatOwner: chatOwner.get('jid'), chatId: response.id, jid: attendant.get('jid')}, success: ->
Backbone.Events.trigger('openChat', chat)
)
)
inviteToChat: (chat, toAdd, me) ->
@trigger(event: 'app.chat.addToMultiChat', data: {chatOwner: me, chatId: chat.get('chatId'), jid: toAdd.get('jid')}, success: ->
chat.appendWithWhom(toAdd)
Backbone.Events.trigger('openChat', chat)
)
iClosedMultichat: (chatId, jid) ->
@trigger(event: 'app.chat.iClosedMultichat', data: {chatId: chatId, me: jid})
kickFromMultichat: (chatId, me, jidToKick) ->
@trigger(event: 'app.chat.kickFromMultichat', data: {chatId: chatId, me: me, kick: jidToKick})
switchMultiChatOwner: (me, newOwner, chatId) ->
@trigger(event: 'app.chat.switchOwnership', data: {chatId: chatId, me: me, newOwner: newOwner})
syncMultiChatContacts: (me, chatId) ->
@trigger(event: 'app.chat.syncMultiChatContacts', data: {me: me, chatId: chatId})
syncedContacts: () ->
updateMyStatus: (message, state)->
App.Com.trigger(event: 'app.roster.updateMyStatus', data: {message: message, state: state})
updateMyVcardName: (name) ->
App.Com.trigger(event: 'app.roster.updateMyVcard', data: {name: name})
updateMyVcardAvatar: (imageBase64) ->
App.Com.trigger(event: 'app.roster.updateMyVcard', data: {avatar: imageBase64})
removeContactRemote: (contact, client) ->
App.Com.trigger(event: 'app.roster.removeContact', data: {jid: contact, client: client})
addContact: (jid) ->
App.Com.trigger(event: 'app.roster.addContact', data: {jid: jid})
answerFriendRequest: (jid, answer) ->
App.Com.trigger(event: 'app.roster.answerFriendRequest', data: { jid: jid, answer: answer})
_setupBackboneComponents: ->
App.Collections.contacts = new Xmpp.Collections.ContactsCollection()
App.Collections.chats = new Xmpp.Collections.ChatsCollection()
App.Views.tabbar = new Xmpp.Views.Tabbar.TabbarView()
_bindListeners: (callback) ->
App._dispatcher.bind('connection_closed', ->
window.location.reload()
)
App._dispatcher.bind('app.client.connected', (jid) ->
callback?(jid)
)
App._dispatcher.bind('app.client.cannot_connect', (jid) ->
alert('cannot connect to ' + jid)
)
App._dispatcher.bind('app.roster.statusChanged', (result) ->
App.debug 'change contact state'
App.Collections.contacts.updateStatus(result)
)
App._dispatcher.bind('app.roster.vcard', (result) ->
App.debug 'got vcard'
App.Collections.contacts.udpateVcard(result)
)
App._dispatcher.bind('app.roster.subscriptionChanged', (subscription) ->
App.debug 'subscription changed'
App.Collections.contacts.subscriptionChanged(subscription)
)
App._dispatcher.bind('app.roster.using_this_app', (person) ->
App.debug ['is using this app', person.jid]
contact = App.Collections.contacts.get(person.jid)
if (contact)
contact.setUsingMyApp()
)
App._dispatcher.bind('app.chat.destroyMultichat', (data) ->
chatId = data.chat_id
chat = App.Collections.chats.findById(chatId)
if chat
App.debug 'destroying multicat from outside'
Backbone.Events.trigger('closeChat', chat, true)
)
App._dispatcher.bind('app.chat.makeMeNewOwner', (chatData) ->
App.debug 'Makeing me new chat owner'
chat = App.Collections.chats.findById(chatData.chat_id)
if chat
attendants = chat.get('withWhom')
attendants = _.without(attendants, App.Models.me) #TODO zamenit .me za model zo serveru
chat.set('who', App.Models.me)
chat.set('withWhom', attendants)
)
App._dispatcher.bind('app.roster.friendRequest', (request) ->
App.debug ['receiving friend request', request]
popup = new Xmpp.Views.Popup.FriendRequestView(jid: request.jid, name: request.name)
popup.render()
)
App._dispatcher.bind('app.chat.importChat', (chatData) ->
chatId = chatData.chat_id
chat = App.Collections.chats.findById(chatId)
if not chat
chat = new Xmpp.Models.Chat(
chatId: chatId,
isMultiChat: true
)
App.Collections.chats.add(chat);
chat.syncContacts(chatData.contacts, chatData.owner)
Backbone.Events.trigger('openChat', chat)
)
App._dispatcher.bind('app.chat.updateSyncedContacts', (syncData) ->
App.debug ['new multichat contacts arrived', syncData.contacts]
chat = App.Collections.chats.findById(syncData.chat_id)
if chat
chat.syncContacts(syncData.contacts, syncData.owner)
Backbone.Events.trigger('openChat', chat)
)
App._dispatcher.bind('app.chat.messageReceived', (result) ->
App.debug ['message received', result]
if (result.chat_id)
chat = App.Collections.chats.findById(result.chat_id)
Backbone.Events.trigger('openChat', chat)
tab = _.find(App.Views.tabbar.tabs, (tab) ->
tab.getChatId() == result.chat_id
)
tab.showChat()
contact = App.Collections.contacts.findByJid(App.stripJid(result.from))
tab.chatWindow.appendMessage(contact, new Date(), result.message)
else
#TODO: prepisat tuto hrozu
contact = App.Collections.contacts.findByJid(App.stripJid(result.from))
if contact
contactView = App.Collections.contacts.friendsList.hasContact(contact) ||
App.Collections.contacts.activeList.hasContact(contact)
else
contact = new Xmpp.Models.Contact(
id: App.stripJid(result.from)
jid: App.stripJid(result.from)
belongsTo: [result.to]
)
App.Collections.contacts.add(contact, merge: true)
contactView = App.Collections.contacts.friendsList.hasContact(contact)
contact.setPreferredResource(result.from)
contactView.startChat()
tab = _.find(App.Views.tabbar.tabs, (tab) ->
tab.hasParticipants(App.Models.me, contact)
)
tab.chatWindow.appendMessage(contact, new Date(), result.message)
)
Models:
me: null
Collections:
contacts: null
chats: null
Views:
tabbar: null
chat: null
app/assets/javascripts/application.js.coffee 0000775 0000000 0000000 00000000051 13345106031 0021553 0 ustar 00root root 0000000 0000000 #= require jquery
#= require jquery_ujs
app/assets/javascripts/backbone-ws.js.coffee 0000664 0000000 0000000 00000000753 13345106031 0021451 0 ustar 00root root 0000000 0000000 #= require websocket_rails/main
#= require hamlcoffee
#= require json2
#= require underscore
#= require backbone
#= require backbone/xmpp
#= require i18n
#= require i18n/translations
#= require _main
#= require chat
#aa = new Xmpp.Views.Popup.NewAccountView()
#aa.render();
App.Com.connect( (connected_jid) ->
App.Com.initRoster(->
App.Com.startFetchingVcards()
App.Com.startPollingRoster()
App.Com.setPresence()
App.Com.getMe()
App.Com.startPollingMessages()
)
)
app/assets/javascripts/backbone/ 0000775 0000000 0000000 00000000000 13345106031 0017231 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/models/ 0000775 0000000 0000000 00000000000 13345106031 0020514 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/models/.gitkeep 0000664 0000000 0000000 00000000000 13345106031 0022133 0 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/models/chat.js.coffee 0000664 0000000 0000000 00000005056 13345106031 0023225 0 ustar 00root root 0000000 0000000 class Xmpp.Models.Chat extends Xmpp.Models.Model
namespace: 'app.chat'
defaults:
who: null
withWhom: null
chatId: null
isMultiChat: false
initialize: ->
_.bindAll(this)
if (@get('isMultiChat'))
@set('withWhom', [])
appendWithWhom: (newPerson) ->
allWithWhom = @get('withWhom');
exists = _.find(allWithWhom, (withWhom) ->
withWhom == newPerson
)?
if not exists
allWithWhom.push(newPerson)
setChatId: (id) ->
App.debug ['multichat has id', id]
@set('chatId', id)
isMeOwner: ->
@get('who') == App.Models.me
isAttending: (contact) ->
attendants = @get('withWhom')
if _.isArray(attendants)
return _.find(attendants, (somebody) -> somebody == contact)
else
return contact == attendants
syncContacts: (contacts, owner) ->
contactsWithoutMe = _.filter(contacts, (jid) ->
jid != App.Models.me.get('jid')
)
if owner and owner != App.Models.me.get('jid')
contactsWithoutMe = contactsWithoutMe.concat([owner])
attendants = _.map(contactsWithoutMe, (jid) =>
contact = App.Collections.contacts.findByJid(jid)
if not contact
contact = new Xmpp.Models.Contact(
id: App.stripJid(jid)
jid: App.stripJid(jid)
belongsTo: [App.Models.me.get('jid')]
usingMyApp: true
)
App.Collections.contacts.add(contact)
contact
)
@set('withWhom', attendants);
if owner
ownerContact = App.Collections.contacts.findByJid(owner)
@set('who', ownerContact)
App.debug ['importing contacts to chat', this, this.get('chatId')]
class Xmpp.Collections.ChatsCollection extends Backbone.Collection
model: Xmpp.Models.Chat
activeChat: null
initialize: ->
_.bindAll(this)
Backbone.Events.on('closeChat', (chat, ignoreCheckIfMultichat) =>
@removeChat(chat)
if not ignoreCheckIfMultichat
if (chat.get('isMultiChat'))
App.Com.iClosedMultichat(chat.get('chatId'), App.Models.me.get('jid'))
)
Backbone.Events.on('openChat', (chat) =>
@activeChat = chat
)
find: (who, withWhom) ->
_.find(@models, (chat) ->
chat.get('who') == who and chat.get('withWhom') == withWhom and not chat.get('isMultiChat')
)
findById: (id) ->
_.find(@models, (chat) ->
chat.get('chatId') == id
)
removeChat: (chat) ->
@activeChat = null
@models = _.without(@openedChats, chat)
createTempContact: (jid) ->
newTempContact = new Xmpp.Models.Contact(jid: jid, belongsTo: [App.Models.me])
newTempContact app/assets/javascripts/backbone/models/contact.js.coffee 0000664 0000000 0000000 00000010503 13345106031 0023732 0 ustar 00root root 0000000 0000000 class Xmpp.Models.Contact extends Xmpp.Models.Model
namespace: 'app.roster'
defaults:
jid: ''
preferredJid: null
name: ''
status: 'offline'
message: ''
avatar: 'assets/avatar.png'
belongsTo: []
usingMyApp: false
initialize: ->
_.bindAll(this)
if ! @get('name')
@set(name: @get('jid'))
@set('id', @get('jid'))
firstname: ->
@get('name').split(' ')[0]
setUsingMyApp: ->
@set('usingMyApp', true)
setPreferredResource: (jid) ->
@set('preferredJid', jid)
getPreferredResource: ->
@get('preferredJid') || @get('jid')
class Xmpp.Collections.ContactsCollection extends Backbone.Collection
model: Xmpp.Models.Contact
initialize: ->
_.bindAll(this)
@friendsList = new Xmpp.Views.Contacts.ListView(collection: this, attributes: {title: 'chat.roster.friends', id: 'js-inactive-friends'})
@friendsList.createListContainer()
@activeList = new Xmpp.Views.Contacts.ListView(collection: this, attributes: {title: 'chat.roster.chat-group', id: 'js-active-friends'})
@activeList.setAsActiveChatGroup()
@on('add', @appendContact)
@on('change:belongsTo', @mergeContacts)
Backbone.Events.on('openChat', (chat) =>
@moveToInactiveList('all')
# App.debug chat
@moveToActiveList(chat.get('withWhom'))
Backbone.Events.trigger('resizeWorkspace')
)
Backbone.Events.on('closeChat', (chat) =>
@moveToInactiveList('all')
)
appendContact: (contact) ->
App.debug ['apppend', contact]
@friendsList.appendContact(contact)
Backbone.Events.trigger('resizeWorkspace')
mergeContacts: (contact) ->
App.debug ['merging contacts', contact]
#TODO: ak ten isty clovek je vo viacerych rosteroch, treba nastavit spravne belongsTo
existingContact = @get(contact.get('id'))
if existingContact?
alreadyBelongsTo = existingContact.get('belongsTo')
willBelongTo = contact.get('belongsTo')
existingContact.set(belongsTo: _.union(alreadyBelongsTo, willBelongTo))
else
@add(contact)
moveToActiveList: (contacts) ->
App.debug ['move to active list', contacts];
contacts = [contacts] unless _.isArray(contacts)
_.each(contacts, (contact) =>
@_switchContactBelongingList(@get(contact), @friendsList, @activeList) && @activeList.reOrder()
)
moveToInactiveList: (contact) ->
if contact == 'all'
_.each(@activeList.contactViews, (view) =>
@_switchContactBelongingList(view.model, @activeList, @friendsList)
)
@friendsList.reOrder()
else
@_switchContactBelongingList(@get(contact), @activeList, @friendsList) && @friendsList.reOrder()
updateStatus: (response) ->
contact = @get(response.jid)
return unless contact
contact.set(message: response.status.message, status: response.status.status)
if response.status.status == 'offline'
contact.setPreferredResource(null)
udpateVcard: (response) ->
@get(response.jid).set(name: response.vcard.name, avatar: response.vcard.avatar)
subscriptionChanged: (newSubscription) ->
if (newSubscription.action == 'subscribed')
App.debug ['subscribed', newSubscription]
newContact = new Xmpp.Models.Contact(
id: newSubscription.jid
jid: newSubscription.jid
belongsTo: [newSubscription.belongsTo]
)
@mergeContacts(newContact)
else if (newSubscription.action == 'unsubscribed')
# TODO: show info and ask if user want to remove him from roster
App.debug ['unsubscribed', newSubscription]
contact = @get(newSubscription.jid)
App.Com.removeContactRemote(contact.get('jid'), newSubscription.belongsTo)
nowBelongsTo = _.without(contact.get('belongsTo'), newSubscription.belongsTo)
if (nowBelongsTo.length == 0)
Backbone.Events.trigger('removeContact', contact)
@remove(contact)
# ignore 'unsubscribe' type of action
Backbone.Events.trigger('resizeWorkspace')
# toto sem asi nepatri, ale asi do views. Nic to v skutocnosti nerobi s modelmi.
filter: (searchTerm) ->
@friendsList.filter(searchTerm)
@activeList.filter(searchTerm)
Backbone.Events.trigger('resizeWorkspace')
findByJid: (jid) ->
@get(jid)
_switchContactBelongingList: (contact, fromList, toList) ->
if !contact?
false
fromList.detachContact(contact) && toList.appendContact(contact)
true app/assets/javascripts/backbone/models/me.js.coffee 0000664 0000000 0000000 00000001177 13345106031 0022707 0 ustar 00root root 0000000 0000000 class Xmpp.Models.Me extends Xmpp.Models.Contact
namespace: 'app.roster'
defaults:
jid: ''
name: ''
status: 'online'
message: ''
avatar: ''
initialize: ->
_.bindAll(this)
if ! @get('name')
@set(name: @get('jid'))
@view = new Xmpp.Views.Contacts.MeView(model: this)
@view.render()
sendMessage: (message, chatId, to, callbackOk, callbackFail) ->
App.debug ['sending from:', @get('jid'), message]
if (to)
App.Com.sendMessage(message, to, @get('jid'), callbackOk, callbackFail)
else
App.Com.sendMultiMessage(message, chatId, @get('jid'), callbackOk, callbackFail) app/assets/javascripts/backbone/models/model.js.coffee 0000664 0000000 0000000 00000001543 13345106031 0023403 0 ustar 00root root 0000000 0000000 class Xmpp.Models.Model extends Backbone.Model
sync: (method, model, options) ->
params = {}
if (!options.event)
params.event = model.namespace + '.' + method
if (!options.data? && model && (method == 'create' || method == 'update' || method == 'patch'))
params.data = JSON.stringify(options.attrs || model.toJSON(options))
else
params.data = JSON.stringify({})
success = options.success;
success = (resp) ->
if (success)
success(model, resp, options)
model.trigger('sync', model, resp, options)
error = options.error
options.error = (xhr) ->
if (error)
error(model, xhr, options)
model.trigger('error', model, xhr, options);
result = options.result = App.Com.trigger(_.extend(params, options))
model.trigger('request', model, result, options)
return result app/assets/javascripts/backbone/routers/ 0000775 0000000 0000000 00000000000 13345106031 0020734 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/routers/.gitkeep 0000664 0000000 0000000 00000000000 13345106031 0022353 0 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/ 0000775 0000000 0000000 00000000000 13345106031 0021227 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/.gitkeep 0000664 0000000 0000000 00000000000 13345106031 0022646 0 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/chat/ 0000775 0000000 0000000 00000000000 13345106031 0022146 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/chat/event.hamlc 0000664 0000000 0000000 00000000031 13345106031 0024267 0 ustar 00root root 0000000 0000000 .event
Monday 13.2.2013 app/assets/javascripts/backbone/templates/chat/message.hamlc 0000664 0000000 0000000 00000000205 13345106031 0024575 0 ustar 00root root 0000000 0000000 .user
.avatar
%img{src: "#{@avatar || 'assets/avatar.png'}", alt: "#{@username}"}
.date #{@username} @ #{@date}:
%p #{@message} app/assets/javascripts/backbone/templates/chat/message_writer.hamlc 0000664 0000000 0000000 00000000274 13345106031 0026177 0 ustar 00root root 0000000 0000000 %form.message-writer#msg-writer
%input{ type: 'text', placeholder: "#{I18n.t('chat.messages.new_msg_placeholder')}" }
%input{ type: 'submit', value: "#{I18n.t('chat.messages.send')}" } app/assets/javascripts/backbone/templates/chat/window.hamlc 0000664 0000000 0000000 00000000247 13345106031 0024466 0 ustar 00root root 0000000 0000000 .conversation.clear.border
.messages#height-setter-2
-for view in @history
#{view}
-if @showWriter
!= JST['backbone/templates/chat/message_writer']() app/assets/javascripts/backbone/templates/contacts/ 0000775 0000000 0000000 00000000000 13345106031 0023045 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/contacts/contact.hamlc 0000664 0000000 0000000 00000002420 13345106031 0025504 0 ustar 00root root 0000000 0000000 .avatar
%img{src: "#{@avatar || 'assets/avatar.png'}", alt: I18n.t("chat.roster.avatar_alt") , title: "#{@jid}" }
%h1
%span{ title: "#{@jid}" } #{@name}
%span.status.icon-record{class: "#{@status}"}
%h2 #{@message}
%span.action
%span.icon-bubbles.multichat{ title: "#{I18n.t('chat.roster.new-multichat', user: @name )}" }
-# ak nemam otvoreny multichat
%span #{I18n.t('chat.roster.icons.multi')}
%span.icon-bubbles.invite{ title: "#{I18n.t('chat.roster.invite', user: @name)}" }
-# ak nie je v multichate a mam otvoreny multichat a som admin toho multichatu
%span #{I18n.t('chat.roster.icons.invite')}
%span.icon-x-full.kick{ title: "#{I18n.t('chat.roster.kick', user: @name)}" }
-# ak som admin otvoreneho multichatu
%span #{I18n.t('chat.roster.icons.kick')}
%span.icon-owner.owner{ title: "#{I18n.t('chat.roster.is-owner', user: @name)}"}
-# ak niesom admin otvoreneho multichatu
%span #{I18n.t('chat.roster.icons.owner')}
%span.icon-make-owner.make-owner{ title: "#{I18n.t('chat.roster.make-owner', user: @name)}" }
-# ak som admin otvoreneho multichatu
%span #{I18n.t('chat.roster.icons.owner')}
%span.icon-x-full.remove{ title: "#{I18n.t('chat.roster.remove', user: @name )}" }
%span #{I18n.t('chat.roster.icons.remove')}
app/assets/javascripts/backbone/templates/contacts/contact_list.hamlc 0000664 0000000 0000000 00000000341 13345106031 0026537 0 ustar 00root root 0000000 0000000 - if @isActiveGroup
%h1.group-header.active #{@title}
%ul.active-group
%li.floating #{I18n.t('chat.roster.group-empty')}
- else
%h1.group-header #{@title}
%ul
%li.floating #{I18n.t('chat.roster.group-empty')}
app/assets/javascripts/backbone/templates/contacts/me.hamlc 0000664 0000000 0000000 00000002055 13345106031 0024456 0 ustar 00root root 0000000 0000000 .user
.avatar.bigger{ title: "#{@jid}" }
%img{ src: "#{@avatar || 'assets/avatar.png'}", alt: I18n.t('chat.roster.avatar_alt') }
%input.hidden{ type: 'file', name: 'avatar', accept: 'image/png,image/jpeg,image/pjpeg,image/gif,image/webp'}
.change-avatar.hidden
.background
%p change
%h1
%input{ type: 'text', title: "#{@jid}", class: 'js-edit-name', value: "#{@name}"}
%h2 #{@message || '' }
-#todo: nastylovat placeholder
%h3
.rolldown.js-change-state
.js-state-clickable{ 'data-state' => 'online' }
%span.status.icon-record.online
%a.active{ href: '#' }
Online
%br
.hidden.js-state-clickable{ 'data-state' => 'away' }
%span.status.icon-record.away
%a{ href: '#' }
Away
%br
.hidden.js-state-clickable{ 'data-state' => 'dnd' }
%span.status.icon-record.dnd
%a{ href: '#' }
DND
%span.icon-arrow-down.drop-down app/assets/javascripts/backbone/templates/popup/ 0000775 0000000 0000000 00000000000 13345106031 0022372 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/popup/add_contact.hamlc 0000664 0000000 0000000 00000000362 13345106031 0025644 0 ustar 00root root 0000000 0000000 %h1 Add a new contact
%form{ action: '#' }
%label{for: 'jid'}
%strong Jabber ID
%input{type: 'text', name: 'jid', class: 'text', placeholder: 'nick@example.com'}
%input{type: 'submit', value: "Send friend request", class: 'button'} app/assets/javascripts/backbone/templates/popup/friend_request.hamlc 0000664 0000000 0000000 00000000445 13345106031 0026422 0 ustar 00root root 0000000 0000000 %h1 Friend request
%form{ action: '#' }
%strong #{@new_contact}
wants to add you to his contacts. Allow?
%br
%input.left{type: 'submit', value: "Yes", name: '1', class: 'button', style: 'width: 60%'}
%input.close.inactive{type: 'button', value: "No", style: 'width: 30%; padding: 0'} app/assets/javascripts/backbone/templates/popup/popup.hamlc 0000664 0000000 0000000 00000000074 13345106031 0024544 0 ustar 00root root 0000000 0000000 %a.close.icon-x{ alt: "#{I18n.t('chat.close')}", href: '#' } app/assets/javascripts/backbone/templates/tabbar/ 0000775 0000000 0000000 00000000000 13345106031 0022462 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/templates/tabbar/tab.hamlc 0000664 0000000 0000000 00000000436 13345106031 0024241 0 ustar 00root root 0000000 0000000 %a{ href: '#' }
-if @multiChat
%div{ title: 'multichat'}
MultiChat
%span.right.icon.icon-x.js-close
-else
%div{ title: "#{@withWhom} #{I18n.t('chat.tabbar.and')} #{@who}"}
#{@withWhom} #{I18n.t('chat.tabbar.and')} #{@who}
%span.right.icon.icon-x.js-close app/assets/javascripts/backbone/templates/tabbar/tabbar.hamlc 0000664 0000000 0000000 00000000000 13345106031 0024711 0 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/ 0000775 0000000 0000000 00000000000 13345106031 0020366 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/.gitkeep 0000664 0000000 0000000 00000000000 13345106031 0022005 0 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/chat/ 0000775 0000000 0000000 00000000000 13345106031 0021305 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/chat/message.js.coffee 0000664 0000000 0000000 00000002005 13345106031 0024512 0 ustar 00root root 0000000 0000000 Xmpp.Views.Chat ||= {}
class Xmpp.Views.Chat.MessageView extends Backbone.View
template: JST["backbone/templates/chat/message"]
className: 'message-box'
initialize: (parts) ->
_.bindAll(this)
@user = parts.user
@dateSent = parts.date
@message = parts.message
render: ->
$(@el).html(@template(username: @user.get('name'), avatar: @user.get('avatar'), date: @formatTime(), message: @message))
return this
formatTime: ->
nowDate = new Date()
if (nowDate.getDate() != @dateSent.getDate() &&
nowDate.getMonth() != @dateSent.getMonth() &&
nowDate.getFullYear() != @dateSent.getFullYear())
return @dateSent.getFullYear() + '/' + @_padDate((@dateSent.getMonth()+1)) + '/' + @_padDate(@dateSent.getDate()) +
' @ ' + @_padDate(@dateSent.getHours()) + ':' + @_padDate(@dateSent.getMinutes())
return @_padDate(@dateSent.getHours()) + ':' + @_padDate(@dateSent.getMinutes())
_padDate: (number) ->
if number < 10
'0' + number
else
number app/assets/javascripts/backbone/views/chat/window.js.coffee 0000664 0000000 0000000 00000004055 13345106031 0024404 0 ustar 00root root 0000000 0000000 Xmpp.Views.Chat ||= {}
class Xmpp.Views.Chat.WindowView extends Backbone.View
template: JST["backbone/templates/chat/window"]
el: $('#conversation-js')
inputSelector: 'input[type=text]'
maxHistoryLength: 20
events:
'submit #msg-writer': (e) -> @sendMessage(e)
initialize: () ->
_.bindAll(this)
@tab = @attributes['tab']
@historyStack = []
render: ->
historyStackHtml = _.map(@historyStack, (view) ->
view.render().el.outerHTML
)
$(@el).html(@template(showWriter: true, history: historyStackHtml))
@show()
return this
hide: ->
$(@el).addClass('hidden')
@undelegateEvents()
this
show: ->
$(@el).removeClass('hidden')
@delegateEvents()
$(@el).find(@inputSelector).focus()
this
remove: ->
@undelegateEvents()
$(@el).empty()
@stopListening()
_.each(@historyStack, (item)->
item.remove()
)
return this
sendMessage: (e) ->
e.preventDefault()
$this = $(e.currentTarget)
input = $this.find(@inputSelector)
message = input.val()
input.val('').focus()
if (message.trim())
chatId = @tab.getChatId()
attendant = if not chatId then @tab.getAttendant().getPreferredResource() else null
App.Models.me.sendMessage(message, chatId, attendant
, (message) =>
@sendSuccess(@tab.getOwner(), message)
, (message) =>
@sendFail(message))
sendSuccess: (me, msg) ->
@appendMessage(me, new Date(), msg)
sendFail: ->
@appendEvent(I18n.t('chat.window.sendFailed'), false)
appendEvent: (msg, logMe) ->
App.debug ['append event', msg]
# @log(eventView) if logMe == true
appendMessage: (user, date, msg) ->
messageView = new Xmpp.Views.Chat.MessageView(user: user, date: date, message: msg)
$(@el).find('.messages').append(messageView.render().el)
App.UI.resizeMessages()
@log(messageView)
log: (view) ->
if (@historyStack.length + 1 >= @maxHistoryLength)
@historyStack = @historyStack.slice(@historyStack.length - @maxHistoryLength + 1)
@historyStack.push(view) app/assets/javascripts/backbone/views/contacts/ 0000775 0000000 0000000 00000000000 13345106031 0022204 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/contacts/contact.js.coffee 0000664 0000000 0000000 00000007344 13345106031 0025433 0 ustar 00root root 0000000 0000000 Xmpp.Views.Contacts ||= {}
class Xmpp.Views.Contacts.ContactView extends Backbone.View
template: JST["backbone/templates/contacts/contact"]
tagName: 'li'
className: 'clear user'
events:
'click .invite': 'inviteToMultichat'
'click .kick': 'kickFromMultiChat'
'click .multichat': 'newMultiChat'
'click .make-owner': 'switchOwner'
'click .remove': 'removeContact'
click: 'startChat'
mouseover: 'showIcons'
mouseleave: 'hideIcons'
@iconClasses = ['multichat', 'invite', 'kick', 'make-owner', 'remove']
initialize: () ->
_.bindAll(this)
# Patrim nejakemu zoznamu
@parentList = @attributes['listView']
@model.on('change', @updateContact)
@model.on('change:status', => @parentList.reOrder())
updateContact: (contact) ->
@model = contact
@render()
hide: ->
$(@el).hide()
unhide: ->
$(@el).show()
render: ->
contact = @model.toJSON()
$(@el).html(@template(contact))
return this
belongsToActiveList: ->
@parentList.activeGroup == true
startChat: ->
who = App.Models.me
withWhom = @model
App.debug ['opening chat with: ', who.get('jid'), withWhom.get('jid')]
chat = App.Collections.chats.find(who, withWhom)
if (! chat)
App.debug ['not found in opened chats, creating new']
chat = new Xmpp.Models.Chat(who: who, withWhom: withWhom)
App.Collections.chats.add(chat)
Backbone.Events.trigger('openChat', chat)
inviteToMultichat: (e) ->
e.stopPropagation()
App.debug 'iniviting to multichat'
multichat = App.Collections.chats.activeChat
if not multichat or not multichat.get('isMultiChat')
return
App.Com.inviteToChat(multichat, @model, App.Models.me.get('jid'))
newMultiChat: (e) ->
e.stopPropagation()
App.debug 'new multichat'
newChat = new Xmpp.Models.Chat(isMultiChat: true)
newChat.set('who', App.Models.me)
App.Collections.chats.add(newChat)
App.Com.openNewMultiChat(App.Models.me, @model, newChat)
kickFromMultiChat: (e) ->
e.stopPropagation()
App.debug 'kick him from multichat!'
multichat = App.Collections.chats.activeChat
if not multichat or (multichat and not multichat.get('isMultiChat'))
return
App.Com.kickFromMultichat(multichat.get('chatId'), App.Models.me.get('jid'), @model.get('jid'))
App.Collections.contacts.moveToInactiveList(@model)
switchOwner: (e) ->
e.stopPropagation()
App.debug 'make him new multichat owner'
multichat = App.Collections.chats.activeChat
if not multichat or (multichat and not multichat.get('isMultiChat'))
return
App.Com.switchMultiChatOwner(App.Models.me.get('jid'), @model.get('jid'), multichat.get('chatId'))
removeContact: (e) ->
e.stopPropagation()
App.debug 'remove from roster'
App.Com.removeContactRemote(@model.get('jid'))
Backbone.Events.trigger('removeContact', @model)
openChatById: (chatId) ->
App.Collections.chats.findById(chatId)
showIcons: ->
activeChat = App.Collections.chats.activeChat
@hideIcons()
if @model.get('usingMyApp') and @model.get('status') != 'offline'
if not activeChat or not activeChat.get('isMultiChat')
$(@el).find('.action .multichat').show()
else if activeChat.get('isMultiChat') and activeChat.isMeOwner() and not activeChat.isAttending(@model)
$(@el).find('.action .invite').show()
else if activeChat.get('isMultiChat') and activeChat.isMeOwner()
$(@el).find('.action .kick').show()
$(@el).find('.action .make-owner').show()
if not activeChat or not activeChat.get('isMultiChat')
$(@el).find('.action .remove').show()
hideIcons: ->
all_icons = '.action .' + ContactView.iconClasses.join(', .action .')
$(@el).find(all_icons).hide()
app/assets/javascripts/backbone/views/contacts/contacts_list.js.coffee 0000664 0000000 0000000 00000004252 13345106031 0026644 0 ustar 00root root 0000000 0000000 Xmpp.Views.Contacts ||= {}
class Xmpp.Views.Contacts.ListView extends Backbone.View
template: JST["backbone/templates/contacts/contact_list"]
initialize: () ->
_.bindAll(this)
@el = '#' + @attributes['id']
@title = I18n.t(@attributes['title'])
@titleClass = @listClass = ''
@activeGroup = false
@contactViews = []
Backbone.Events.on('removeContact', @detachContact)
createListContainer: ->
$(@el).html(@template(title: @title, isActiveGroup: @activeGroup))
appendContact: (contact) ->
if @hasContact(contact)
return false
if @contactViews.length == 0
@createListContainer()
view = new Xmpp.Views.Contacts.ContactView(model: contact, attributes: {listView: this})
@contactViews.push view
@render(view.render().el)
detachContact: (contact) ->
matchingView = @hasContact(contact)
if matchingView
@contactViews = _.without(@contactViews, matchingView)
matchingView.remove()
if @contactViews.length == 0
$(@el).html('')
return !!matchingView
hasContact: (contact) ->
_.find(@contactViews, (view) ->
view.model == contact
)
setAsActiveChatGroup: ->
@activeGroup = true
render: (contactHtml) ->
if (contactHtml?)
$(@el).find('ul').append(contactHtml)
else
_.each(@contactViews, (contact) =>
@render(contact.render().el)
)
return this
filter: (searchTerm) ->
regex = new RegExp(searchTerm.trim(), 'i')
_.each(@contactViews, (view) ->
searchInAttributes = view.model.pick('jid', 'name', 'message')
searchString = _.toArray(searchInAttributes).join(' ')
found = (searchString.search(regex) != -1)
if found then view.unhide() else view.hide()
)
reOrder: ->
groupedByStatus = _.groupBy(@contactViews, (view) ->
view.model.get('status')
)
sortedGroupNames = {
online: groupedByStatus.online
away: groupedByStatus.away
offline: groupedByStatus.offline
}
sorted = _.map(sortedGroupNames, (group) ->
_.sortBy(group, (view) ->
view.model.get('name') || view.model.get('jid')
)
)
@contactViews = _.flatten(sorted)
@render() app/assets/javascripts/backbone/views/contacts/me.js.coffee 0000664 0000000 0000000 00000005551 13345106031 0024377 0 ustar 00root root 0000000 0000000 Xmpp.Views.Contacts ||= {}
class Xmpp.Views.Contacts.MeView extends Backbone.View
template: JST["backbone/templates/contacts/me"]
el: $('#js-me')
events:
'click .js-edit-status': (e) -> @editTextInput(e)
'keypress .js-edit-status': (e) -> @confirmMyStatus(e)
'click .js-edit-name': (e) -> @editTextInput(e)
'keypress .js-edit-name': (e) -> @confirmMyName(e)
'click .js-change-state': (e) -> @openChangeState(e)
'click .js-state-clickable': (e) -> @confirmChangeState(e)
#avatar change events:
'mouseover .avatar': -> $('.change-avatar').show()
'mouseout .avatar': -> $('.change-avatar').hide()
'click .change-avatar': -> $('input[name=avatar]').click()
'change input[name=avatar]': 'uploadAvatar'
initialize: () ->
_.bindAll(this)
@model.on('change', @updateContact, this)
updateContact: (me) ->
@model = me
@render()
render: ->
contact = @model.toJSON()
$(@el).html(@template(contact))
this
editTextInput: (e) ->
$this = $(e.currentTarget)
if ($this.hasClass('empty'))
$this.val('')
if (! $this.hasClass('editing'))
$this.removeClass('empty')
.addClass('editing')
confirmMyStatus: (e) ->
if (e.which != 13 && e.which != 10)
return
$this = $(e.currentTarget)
if ($this.val() == '')
$this.removeClass('editing')
.addClass('empty')
.val(I18n.t('chat.roster.change_status_msg'))
else
$this.removeClass('editing')
$this.blur()
App.Com.updateMyStatus(@_getStatusMessage(), @_getState())
confirmMyName: (e) ->
if (e.which != 13 && e.which != 10)
return
$this = $(e.currentTarget)
if ($this.val() == '')
$this.removeClass('editing')
.val(@model.get('jid'))
else
$this.removeClass('editing')
$this.blur()
App.Com.updateMyVcardName(@_getName())
@model.set('name', @_getName())
openChangeState: (e) ->
$this = $(e.currentTarget)
$this.addClass('editing')
.find('.hidden').removeClass('hidden')
$('.rolldown').removeClass('js-change-state')
confirmChangeState: (e) ->
$this = $(e.currentTarget)
$('.rolldown').removeClass('editing')
.addClass('js-change-state')
.children().addClass('hidden')
$('.rolldown').find('a').removeClass('active')
$this.removeClass('hidden')
.find('a').addClass('active')
App.Com.updateMyStatus(@_getStatusMessage(), @_getState())
uploadAvatar: (e) ->
fr = new FileReader;
fr.onloadend = ->
App.Models.me.set(avatar: fr.result)
App.Com.updateMyVcardAvatar(fr.result)
fr.readAsDataURL(e.target.files[0]);
_getStatusMessage: ->
if $('.js-edit-status').hasClass('empty')
return ''
else
$('.js-edit-status').val()
_getState: ->
$('.js-state-clickable:not(.hidden)').data('state')
_getName: ->
$('.js-edit-name').val() app/assets/javascripts/backbone/views/popup/ 0000775 0000000 0000000 00000000000 13345106031 0021531 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/popup/_popup.js.coffee 0000664 0000000 0000000 00000000601 13345106031 0024614 0 ustar 00root root 0000000 0000000 Xmpp.Views.Popup ||= {}
class Xmpp.Views.Popup.PopupView extends Backbone.View
superTemplate: JST["backbone/templates/popup/popup"]
el: $('#js-popup')
superEvents:
'click .close': 'close'
render: (childHtml) ->
$(@el).html(@superTemplate()).append(childHtml)
close: (e) ->
e.preventDefault()
@remove()
remove: ->
@stopListening()
$(@el).empty() app/assets/javascripts/backbone/views/popup/add_contact.js.coffee 0000664 0000000 0000000 00000001207 13345106031 0025560 0 ustar 00root root 0000000 0000000 Xmpp.Views.Popup ||= {}
class Xmpp.Views.Popup.AddContactView extends Xmpp.Views.Popup.PopupView
template: JST["backbone/templates/popup/add_contact"]
events:
'submit form': 'addContact'
initialize: ->
@events = _.extend(@events, @superEvents)
render: ->
super(@template())
$(@el).css(width: '20%', 'z-index': 999)
$(@el).find('input[type=text]').focus()
this
addContact: (e) ->
e.preventDefault()
jid = $(@el).find('input[name=jid]').val()
r = new RegExp('^.+@.+\..+$')
match = r.exec(jid)
if match and (match.length > 0)
jid = App.stripJid(match[0])
App.Com.addContact(jid)
app/assets/javascripts/backbone/views/popup/friend_request.coffee 0000664 0000000 0000000 00000001342 13345106031 0025721 0 ustar 00root root 0000000 0000000 Xmpp.Views.Popup ||= {}
class Xmpp.Views.Popup.FriendRequestView extends Xmpp.Views.Popup.PopupView
template: JST["backbone/templates/popup/friend_request"]
events:
'submit form': 'approve'
initialize: (options) ->
@name = options.name
@jid = options.jid
@events = _.extend(@events, @superEvents)
render: ->
super(@template(new_contact: @name))
$(@el).css(width: '20%', 'z-index': 999)
$(@el).find('input[type=text]').focus()
this
approve: (e) ->
e.preventDefault()
App.debug 'approve request'
App.Com.answerFriendRequest(@jid, true)
@remove()
close: (e) ->
e.preventDefault()
App.debug 'cancel request'
App.Com.answerFriendRequest(@jid, false)
@remove() app/assets/javascripts/backbone/views/tabbar/ 0000775 0000000 0000000 00000000000 13345106031 0021621 5 ustar 00root root 0000000 0000000 app/assets/javascripts/backbone/views/tabbar/tab.js.coffee 0000664 0000000 0000000 00000003406 13345106031 0024156 0 ustar 00root root 0000000 0000000 Xmpp.Views.Tabbar ||= {}
class Xmpp.Views.Tabbar.TabView extends Backbone.View
template: JST["backbone/templates/tabbar/tab"]
tagName: 'div'
className: 'tab'
events:
'click .js-close': 'fireCloseChatTrigger'
'click a div': 'switchChatWindow'
initialize: () ->
_.bindAll(this)
@active = false
Backbone.Events.on('closeChat', (chat) =>
if chat == @model
@destroy()
)
@model.get('who').on('change:name', @render)
fireCloseChatTrigger: ->
Backbone.Events.trigger('closeChat', @model)
setActive: ->
$(@el).addClass('active')
@active = true
@render()
setInactive: ->
$(@el).removeClass('active')
@active = false
@render()
render: ->
if (@model.get('isMultiChat'))
$(@el).html(@template(
who: @model.get('who').firstname(),
multiChat: true
))
else
$(@el).html(@template(
who: @model.get('who').firstname(),
withWhom: @model.get('withWhom').firstname(),
multiChat: false
))
return this
showChat: ->
if !@chatWindow
@chatWindow = new Xmpp.Views.Chat.WindowView(attributes: {tab: this})
App.debug 'zobrazujem NOVY chat window'
else
App.debug 'zobrazujem EXISTUJUCI chat window'
@chatWindow.render()
destroy: ->
App.debug 'destroy chatWindow and tab'
if @active
@chatWindow.remove()
@remove()
hideChat: ->
if @chatWindow
@chatWindow.hide();
hasParticipants: (who, withWhom) ->
@model && @model.get('who') == who && @model.get('withWhom') == withWhom
switchChatWindow: ->
Backbone.Events.trigger('openChat', @model)
getOwner: ->
@model.get('who')
getChatId: ->
@model.get('chatId')
getAttendant: ->
@model.get('withWhom') app/assets/javascripts/backbone/views/tabbar/tabbar.js.coffee 0000664 0000000 0000000 00000003274 13345106031 0024646 0 ustar 00root root 0000000 0000000 Xmpp.Views.Tabbar ||= {}
class Xmpp.Views.Tabbar.TabbarView extends Backbone.View
template: JST["backbone/templates/tabbar/tabbar"]
el: $('#js-tabbar')
initialize: () ->
_.bindAll(this)
Backbone.Events.on('openChat', (chat) =>
@hideCurrentChatWindow()
@addOrSelect(chat)
)
Backbone.Events.on('closeChat', (chat) =>
tab = _.find(@tabs, (_tab) =>
_tab.model == chat
)
@closeTab(tab) if tab
)
Backbone.Events.on('removeContact', (contact) =>
tab = _.find(@tabs, (_tab) =>
_tab.model.get('withWhom') == contact
)
Backbone.Events.trigger('closeChat', tab.model) if tab
)
@tabs = []
@activeTab = null
@render()
addOrSelect: (chat) ->
tab = _.find(@tabs, (tab) ->
tab.hasParticipants(chat.get('who'), chat.get('withWhom'))
)
if (!! tab)
App.debug 'selecting existing tab'
else
App.debug 'adding new tab'
tab = new Xmpp.Views.Tabbar.TabView(model: chat)
@tabs.push tab
$(@el).append(tab.render().el)
_.each(@tabs, (tab) ->
tab.setInactive()
)
@activeTab = tab
@activeTab.setActive()
@activeTab.showChat()
hideCurrentChatWindow: ->
if @activeTab
App.debug 'zakryvam chat window'
@activeTab.hideChat()
closeTab: (tab) ->
@removeTab(tab)
if tab == @activeTab
firstTab = @selectFirstTab()
if firstTab
App.debug 'fire openChat'
Backbone.Events.trigger('openChat', firstTab.model)
#TODO: vybrat naposledy pouzity tab
removeTab: (tab) ->
@tabs = _.without(@tabs, tab)
render: ->
$(@el).html(@template())
return this
selectFirstTab: ->
_.first(@tabs) app/assets/javascripts/backbone/xmpp.js.coffee 0000664 0000000 0000000 00000000336 13345106031 0022003 0 ustar 00root root 0000000 0000000 #= require_self
#= require_tree ./templates
#= require ./models/model
#= require_tree ./models
#= require_tree ./views
#= require_tree ./routers
window.Xmpp =
Models: {}
Collections: {}
Routers: {}
Views: {}
app/assets/javascripts/chat.js.coffee 0000664 0000000 0000000 00000001064 13345106031 0020171 0 ustar 00root root 0000000 0000000 $().ready(->
$(window).resize( ->
App.UI.setAutoHeight()
).trigger('resize')
Backbone.Events.on('resizeWorkspace', -> App.UI.setAutoHeight())
$('#js-search-contacts').keyup( ->
searchTerm = $(this).val()
App.UI.filterContacts(searchTerm)
)
# $('.chat-alert').on('click', '.close', (e) ->
# e.preventDefault()
# $('.chat-alert').fadeOut(250, -> $(this).html(''))
# )
$('#add-contact').click((e) ->
e.preventDefault()
$('#js-popup .close').click()
popup = new Xmpp.Views.Popup.AddContactView()
popup.render()
)
) app/assets/javascripts/i18n/ 0000775 0000000 0000000 00000000000 13345106031 0016244 5 ustar 00root root 0000000 0000000 app/assets/javascripts/i18n/translations.js 0000664 0000000 0000000 00000063431 13345106031 0021332 0 ustar 00root root 0000000 0000000 var I18n = I18n || {};
I18n.translations = {"en":{"date":{"formats":{"default":"%Y-%m-%d","short":"%b %d","long":"%B %d, %Y"},"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"abbr_day_names":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"month_names":[null,"January","February","March","April","May","June","July","August","September","October","November","December"],"abbr_month_names":[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"order":["year","month","day"]},"time":{"formats":{"default":"%a, %d %b %Y %H:%M:%S %z","short":"%d %b %H:%M","long":"%B %d, %Y %H:%M"},"am":"am","pm":"pm"},"support":{"array":{"words_connector":", ","two_words_connector":" and ","last_word_connector":", and "}},"errors":{"format":"%{attribute} %{message}","messages":{"inclusion":"is not included in the list","exclusion":"is reserved","invalid":"is invalid","confirmation":"doesn't match confirmation","accepted":"must be accepted","empty":"can't be empty","blank":"can't be blank","too_long":"is too long (maximum is %{count} characters)","too_short":"is too short (minimum is %{count} characters)","wrong_length":"is the wrong length (should be %{count} characters)","not_a_number":"is not a number","not_an_integer":"must be an integer","greater_than":"must be greater than %{count}","greater_than_or_equal_to":"must be greater than or equal to %{count}","equal_to":"must be equal to %{count}","less_than":"must be less than %{count}","less_than_or_equal_to":"must be less than or equal to %{count}","odd":"must be odd","even":"must be even"}},"number":{"format":{"separator":".","delimiter":",","precision":3,"significant":false,"strip_insignificant_zeros":false},"currency":{"format":{"format":"%u%n","unit":"$","separator":".","delimiter":",","precision":2,"significant":false,"strip_insignificant_zeros":false}},"percentage":{"format":{"delimiter":""}},"precision":{"format":{"delimiter":""}},"human":{"format":{"delimiter":"","precision":3,"significant":true,"strip_insignificant_zeros":true},"storage_units":{"format":"%n %u","units":{"byte":{"one":"Byte","other":"Bytes"},"kb":"KB","mb":"MB","gb":"GB","tb":"TB"}},"decimal_units":{"format":"%n %u","units":{"unit":"","thousand":"Thousand","million":"Million","billion":"Billion","trillion":"Trillion","quadrillion":"Quadrillion"}}}},"datetime":{"distance_in_words":{"half_a_minute":"half a minute","less_than_x_seconds":{"one":"less than 1 second","other":"less than %{count} seconds"},"x_seconds":{"one":"1 second","other":"%{count} seconds"},"less_than_x_minutes":{"one":"less than a minute","other":"less than %{count} minutes"},"x_minutes":{"one":"1 minute","other":"%{count} minutes"},"about_x_hours":{"one":"about 1 hour","other":"about %{count} hours"},"x_days":{"one":"1 day","other":"%{count} days"},"about_x_months":{"one":"about 1 month","other":"about %{count} months"},"x_months":{"one":"1 month","other":"%{count} months"},"about_x_years":{"one":"about 1 year","other":"about %{count} years"},"over_x_years":{"one":"over 1 year","other":"over %{count} years"},"almost_x_years":{"one":"almost 1 year","other":"almost %{count} years"}},"prompts":{"year":"Year","month":"Month","day":"Day","hour":"Hour","minute":"Minute","second":"Seconds"}},"helpers":{"select":{"prompt":"Please select"},"submit":{"create":"Create %{model}","update":"Update %{model}","submit":"Save %{model}"},"button":{"create":"Create %{model}","update":"Update %{model}","submit":"Save %{model}"}},"mongoid":{"errors":{"messages":{"blank_in_locale":"can't be blank in %{location}","ambiguous_relationship":{"message":"Ambiguous relations %{candidates} defined on %{klass}.","summary":"When Mongoid attempts to set an inverse document of a relation in memory, it needs to know which relation it belongs to. When setting %{name}, Mongoid looked on the class %{inverse} for a matching relation, but multiples were found that could potentially match: %{candidates}.","resolution":"On the %{name} relation on %{inverse} you must add an :inverse_of option to specify the exact relationship on %{klass} that is the opposite of %{name}."},"callbacks":{"message":"Calling %{method} on %{klass} resulted in a false return from a callback.","summary":"If a before callback returns false when using Document.create!, Document#save!, or Documnet#update_attributes! this error will get raised since the document did not actually get saved.","resolution":"Double check all before callbacks to make sure they are not unintentionally returning false."},"calling_document_find_with_nil_is_invalid":{"message":"Calling Document.find with nil is invalid.","summary":"Document.find expects the parameters to be 1 or more ids, and will return a single document if 1 id is provided, otherwise an array of documents if multiple ids are provided.","resolution":"Most likely this is caused by passing parameters directly through to the find, and the parameter either is not present or the key from which it is accessed is incorrect."},"document_not_found":{"message":"Document(s) not found for class %{klass} with id(s) %{missing}.","summary":"When calling %{klass}.find with an id or array of ids, each parameter must match a document in the database or this error will be raised. The search was for the id(s): %{searched} (%{total} total) and the following ids were not found: %{missing}.","resolution":"Search for an id that is in the database or set the Mongoid.raise_not_found_error configuration option to false, which will cause a nil to be returned instead of raising this error when searching for a single id, or only the matched documents when searching for multiples."},"document_with_attributes_not_found":{"message":"Document not found for class %{klass} with attributes %{attributes}.","summary":"When calling %{klass}.find_by with a hash of attributes, all attributes provided must match a document in the database or this error will be raised.","resolution":"Search for attributes that are in the database or set the Mongoid.raise_not_found_error configuration option to false, which will cause a nil to be returned instead of raising this error."},"eager_load":{"message":"Eager loading :%{name} is not supported since it is a polymorphic belongs_to relation.","summary":"Mongoid cannot currently determine the classes it needs to eager load when the relation is polymorphic. The parents reside in different collections so a simple id lookup is not sufficient enough.","resolution":"Don't attempt to perform this action and have patience, maybe this will be supported in the future."},"invalid_collection":{"message":"Access to the collection for %{klass} is not allowed.","summary":"%{klass}.collection was called, and %{klass} is an embedded document - it resides within the collection of the root document of the hierarchy.","resolution":"For access to the collection that the embedded document is in, use %{klass}#_root.collection, or do not attempt to persist an embedded document without a parent set."},"invalid_config_option":{"message":"Invalid configuration option: %{name}.","summary":"A invalid configuration option was provided in your mongoid.yml, or a typo is potentially present. The valid configuration options are: %{options}.","resolution":"Remove the invalid option or fix the typo. If you were expecting the option to be there, please consult the following page with repect to Mongoid's configuration:\n\n http://mongoid.org/docs/installation.html"},"invalid_database":{"message":"Database should be a Mongo::DB, not %{name}.","summary":"When setting a master database in the Mongoid configuration it must be an actual instance of a Mongo::DB, and not just a name of the database. This check is performed when calling Mongoid.master = object.","resolution":"Make sure that when setting the configuration programatically that you are passing an actual db instance."},"invalid_field":{"message":"Defining a field named '%{name}' is not allowed.","summary":"Defining this field would override the method '%{name}', which would cause issues with expectations around the original method and cause extremely hard to debug issues. The original method was defined in:\n Object: %{origin}\n File: %{file}\n Line: %{line}","resolution":"Use Mongoid.destructive_fields to see what names are not allowed, and don't use these names. These include names that also conflict with core Ruby methods on Object, Module, Enumerable, or included gems that inject methods into these or Mongoid internals."},"invalid_field_option":{"message":"Invalid option :%{option} provided for field :%{name}.","summary":"Mongoid requires that you only provide valid options on each field definition in order to prevent unexpected behaviour later on.","resolution":"When defining the field :%{name} on '%{klass}', please provide valid options for the field. These are currently: %{valid}. If you meant to define a custom field option, please do so first like so:\n\n Mongoid::Fields.option :%{option} do |model, field, value|\n # Your logic here...\n end\n class %{klass}\n include Mongoid::Document\n field :%{name}, %{option}: true\n end\n\n"},"invalid_includes":{"message":"Invalid includes directive: %{klass}.includes(%{args})","summary":"Eager loading in Mongoid only supports providing arguments to %{klass}.includes that are the names of relations on the %{klass} model, and only supports one level of eager loading. (ie, eager loading associations not on the %{klass} but one step away via another relation is not allowed.","resolution":"Ensure that each parameter passed to %{klass}.includes is a valid name of a relation on the %{klass} model. These are: %{relations}."},"invalid_index":{"message":"Invalid index specification on %{klass}: %{spec}, %{options}","summary":"Indexes in Mongoid are defined as a hash of field name and direction/2d pairs, with a hash for any additional options.","resolution":"Ensure that the index conforms to the correct syntax and has the correct options.\n\n Valid options are:\n background: true|false\n drop_dups: true|false\n name: 'index_name'\n sparse: true|false\n unique: true|false\n min: 1\n max: 1\n bits: 26\n bucket_size : 1\n Valid types are: 1, -1, '2d', 'geoHaystack'\n\n Example:\n class Band\n include Mongoid::Document\n index({ name: 1, label: -1 }, { sparse: true })\n index({ location: '2d' }, { background: true })\n end\n\n"},"invalid_options":{"message":"Invalid option :%{invalid} provided to relation :%{name}.","summary":"Mongoid checks the options that are passed to the relation macros to ensure that no ill side effects occur by letting something slip by.","resolution":"Valid options are: %{valid}, make sure these are the ones you are using."},"invalid_path":{"message":"Having a root path assigned for %{klass} is invalid.","summary":"Mongoid has two different path objects for determining the location of a document in the database, Root and Embedded. This error is raised when an embedded document somehow gets a root path assigned.","resolution":"Most likely your embedded model, %{klass} is also referenced via a has_many from a root document in another collection. Double check the relation definitions and fix any instances where embedded documents are improperly referenced from other collections."},"invalid_scope":{"message":"Defining a scope of value %{value} on %{klass} is not allowed.","summary":"Scopes in Mongoid must be either criteria objects or procs that wrap criteria objects.","resolution":"Change the scope to be a criteria or proc wrapped critera.\n\n Example:\n class Band\n include Mongoid::Document\n field :active, type: Boolean, default: true\n scope :active, where(active: true)\n scope :inactive, ->{ where(active: false) }\n end\n\n"},"invalid_storage_options":{"message":"Invalid options passed to %{klass}.store_in: %{options}.","summary":"The :store_in macro takes only a hash of parameters with the keys :database, :collection, or :session.","resolution":"Change the options passed to store_in to match the documented API, and ensure all keys in the options hash are symbols.\n\n Example:\n class Band\n include Mongoid::Document\n store_in collection: 'artists', database: 'secondary'\n end\n\n"},"invalid_time":{"message":"'%{value}' is not a valid Time.","summary":"Mongoid tries to serialize the values for Date, DateTime, and Time into proper UTC times to store in the database. The provided value could not be parsed.","resolution":"Make sure to pass parsable values to the field setter for Date, DateTime, and Time objects. When this is a String it needs to be valid for Time.parse. Other objects must be valid to pass to Time.local."},"inverse_not_found":{"message":"When adding a(n) %{klass} to %{base}#%{name}, Mongoid could not determine the inverse foreign key to set. The attempted key was '%{inverse}'.","summary":"When adding a document to a relation, Mongoid attempts to link the newly added document to the base of the relation in memory, as well as set the foreign key to link them on the database side. In this case Mongoid could not determine what the inverse foreign key was.","resolution":"If an inverse is not required, like a belongs_to or has_and_belongs_to_many, ensure that :inverse_of => nil is set on the relation. If the inverse is needed, most likely the inverse cannot be figured out from the names of the relations and you will need to explicitly tell Mongoid on the relation what the inverse is.\n\n Example:\n class Lush\n include Mongoid::Document\n has_one :whiskey, class_name: \"Drink\", inverse_of: :alcoholic\n end\n\n class Drink\n include Mongoid::Document\n belongs_to :alcoholic, class_name: \"Lush\", inverse_of: :whiskey\n end"},"invalid_set_polymorphic_relation":{"message":"The %{name} attribute can't be set to an instance of %{other_klass} as %{other_klass} has multiple relations referencing %{klass} as %{name}.","summary":"If the parent class of a polymorphic relation has multiple definitions for the same relation, the values must be set from the parent side and not the child side since Mongoid cannot determine from the child side which relation to go in.","resolution":"Set the values from the parent, or redefine the relation with only a single definition in the parent."},"mixed_relations":{"message":"Referencing a(n) %{embedded} document from the %{root} document via a relational association is not allowed since the %{embedded} is embedded.","summary":"In order to properly access a(n) %{embedded} from %{root} the reference would need to go through the root document of %{embedded}. In a simple case this would require Mongoid to store an extra foreign key for the root, in more complex cases where %{embedded} is multiple levels deep a key would need to be stored for each parent up the hierarchy.","resolution":"Consider not embedding %{embedded}, or do the key storage and access in a custom manner in the application code."},"mixed_session_configuration":{"message":"Both uri and standard configuration options defined for session: '%{name}'.","summary":"Instead of simply giving uri or standard options a preference order, Mongoid assumes that you have made a mistake in your configuration and requires that you provide one or the other, but not both. The options that were provided were: %{config}.","resolution":"Provide either only a uri as configuration or only standard options."},"nested_attributes_metadata_not_found":{"message":"Could not find metadata for relation '%{name}' on model: %{klass}.","summary":"When defining nested attributes for a relation, Mongoid needs to access the metadata for the relation '%{name}' in order if add autosave functionality to it, if applicable. Either no relation named '%{name}' could be found, or the relation had not been defined yet.","resolution":"Make sure that there is a relation defined named '%{name}' on %{klass} or that the relation definition comes before the accepts_nested_attributes_for macro in the model - order matters so that Mongoid has access to the metadata.\n\n Example:\n class Band\n include Mongoid::Document\n has_many :albums\n accepts_nested_attributes_for :albums\n end\n\n"},"no_default_session":{"message":"No default session configuration is defined.","summary":"The configuration provided settings for: %{keys}, but Mongoid requires a :default to be defined at minimum.","resolution":"If configuring via a mongoid.yml, ensure that within your :sessions section a :default session is defined.\n\n Example:\n development:\n sessions:\n default:\n hosts:\n - localhost:27017\n\n"},"no_environment":{"message":"Could not load the configuration since no environment was defined.","summary":"Mongoid attempted to find the appropriate environment but no Rails.env, Sinatra::Base.environment, RACK_ENV, or MONGOID_ENV could be found.","resolution":"Make sure some environment is set from the mentioned options. Mongoid cannot load configuration from the yaml without knowing which environment it is in, and we have considered defaulting to development an undesireable side effect of this not being defined."},"no_map_reduce_output":{"message":"No output location was specified for the map/reduce operation.","summary":"When executing a map/reduce, you must provide the output location of the results. The attempted command was: %{command}.","resolution":"Provide the location that the output of the operation is to go by chaining an #out call to the map/reduce.\n\n Example:\n Band.map_reduce(map, reduce).out(inline: 1)\n\n Valid options for the out function are:\n inline: 1\n merge: 'collection-name'\n replace: 'collection-name'\n reduce: 'collection-name'\n\n"},"no_metadata":{"message":"Metadata not found for document of type %{klass}.","summary":"Mongoid sets the metadata of a relation on the document when it is either loaded from within the relation, or added to one. The presence of the metadata is required in order to provide various functionality around relations. Most likely you are getting this error because the document is embedded and was attempted to be persisted without being associated with a parent, or the relation was not properly defined.","resolution":"Ensure that your relations on the %{klass} model are all properly defined, and that the inverse relations are also properly defined. Embedded relations must have both the parent (embeds_one/embeds_many) and the inverse (embedded_in) present in order to work properly."},"no_parent":{"message":"Cannot persist embedded document %{klass} without a parent document.","summary":"If the document is embedded, in order to be persisted it must always have a reference to it's parent document. This is most likely cause by either calling %{klass}.create or %{klass}.create! without setting the parent document as an attribute.","resolution":"Ensure that you've set the parent relation if instantiating the embedded document direcly, or always create new embedded documents via the parent relation."},"no_session_config":{"message":"No configuration could be found for a session named '%{name}'.","summary":"When attempting to create the new session, Mongoid could not find a session configuration for the name: '%{name}'. This is necessary in order to know the host, port, and options needed to connect.","resolution":"Double check your mongoid.yml to make sure under the sessions key that a configuration exists for '%{name}'. If you have set the configuration programatically, ensure that '%{name}' exists in the configuration hash."},"no_sessions_config":{"message":"No sessions configuration provided.","summary":"Mongoid's configuration requires that you provide details about each session that can be connected to, and requires in the sessions config at least 1 default session to exist.","resolution":"Double check your mongoid.yml to make sure that you have a top-level sessions key with at least 1 default session configuration for it. You can regenerate a new mongoid.yml for assistance via `rails g mongoid:config`.\n\n Example:\n development:\n sessions:\n default:\n database: mongoid_dev\n hosts:\n - localhost:27017\n\n"},"no_session_database":{"message":"No database provided for session configuration: :%{name}.","summary":"Each session configuration must provide a database so Mongoid knows where the default database to persist to. What was provided was: %{config}.","resolution":"If configuring via a mongoid.yml, ensure that within your :%{name} section a :database value for the session's default database is defined.\n\n Example:\n development:\n sessions:\n %{name}:\n database: my_app_db\n hosts:\n - localhost:27017\n\n"},"no_session_hosts":{"message":"No hosts provided for session configuration: :%{name}.","summary":"Each session configuration must provide hosts so Mongoid knows where the database server is located. What was provided was: %{config}.","resolution":"If configuring via a mongoid.yml, ensure that within your :%{name} section a :hosts value for the session hosts is defined.\n\n Example:\n development:\n sessions:\n %{name}:\n database: my_app_db\n hosts:\n - localhost:27017\n\n"},"readonly_attribute":{"message":"Attempted to set the readonly attribute '%{name}' with the value: %{value}.","summary":"Attributes flagged as readonly via Model.attr_readonly can only have values set when the document is a new record.","resolution":"Don't define '%{name}' as readonly, or do not attempt to update it's value after the document is persisted."},"scope_overwrite":{"message":"Cannot create scope :%{scope_name}, because of existing method %{model_name}.%{scope_name}.","summary":"When defining a scope that conflicts with a method that already exists on the model, this error will get raised if Mongoid.scope_overwrite_exception is set to true.","resolution":"Change the name of the scope so it does not conflict with the already defined method %{model_name}, or set the configuration option Mongoid.scope_overwrite_exception to false, which is it's default. In this case a warning will be logged."},"taken":"is already taken","too_many_nested_attribute_records":{"message":"Accepting nested attributes for %{association} is limited to %{limit} records.","summary":"More documents were sent to be processed than the allowed limit.","resolution":"The limit is set as an option to the macro, for example: accepts_nested_attributes_for :%{association}, limit: %{limit}. Consider raising this limit or making sure no more are sent than the set value."},"unknown_attribute":{"message":"Attempted to set a value for '%{name}' which is not allowed on the model %{klass}.","summary":"When setting Mongoid.allow_dynamic_fields to false and the attribute does not already exist in the attributes hash, attempting to call %{klass}#%{name}= for it is not allowed. This is also triggered by passing the attribute to any method that accepts an attributes hash, and is raised instead of getting a NoMethodError.","resolution":"You can set Mongoid.allow_dynamic_fields to true if you expect to be writing values for undefined fields often."},"unsaved_document":{"message":"Attempted to save %{document} before the parent %{base}.","summary":"You cannot call create or create! through the relation (%{document}) who's parent (%{base}) is not already saved. This would case the database to be out of sync since the child could potentially reference a nonexistant parent.","resolution":"Make sure to only use create or create! when the parent document %{base} is persisted."},"unsupported_javascript":{"message":"Executing Javascript $where selector on an embedded criteria is not supported.","summary":"Mongoid only supports providing a hash of arguments to #where criterion on embedded documents. Since %{klass} is embedded, the expression %{javascript} is not allowed.","resolution":"Please provide a standard hash to #where when the criteria is for an embedded relation."},"validations":{"message":"Validation of %{document} failed.","summary":"The following errors were found: %{errors}","resolution":"Try persisting the document with valid data or remove the validations."},"versioning_not_on_root":{"message":"Versioning not allowed on embedded document: %{klass}.","summary":"Mongoid::Versioning behaviour is only allowed on documents that are the root document in the hierarchy.","resolution":"Remove the versioning from the embedded %{klass} or consider moving it to a root location in the hierarchy if versioning is needed."},"delete_restriction":{"message":"Cannot delete %{document} because of dependent '%{relation}'.","summary":"When defining '%{relation}' with a :dependent => :restrict, Mongoid will raise an error when attempting to delete the %{document} when the child '%{relation}' still has documents in it.","resolution":"Don't attempt to delete the parent %{document} when it has children, or change the dependent option on the relation."}}}},"login":{"error":"Invalid email/password combination","success":"Successfully logged in","access-denied":"Access denied. Please, sign in first."},"sessions":{"title":"Login","new":{"form-header":"Login form","form-send":"Login"}},"chat":{"title":"Chat","close":"Close","roster":{"friends":"Friends","chat-group":"Chat group","avatar_alt":"Avatar","change_status_msg":"Change your status message","search":"Search contacts","group-empty":"Empty","new-multichat":"Create new multichat with %{user}","invite":"Invite %{user} to the opened multichat","kick":"Kick %{user} from the opened multichat","is-owner":"%{user} is the owner of the opened multichat","make-owner":"Make %{user} a new owner of the opened multichat","remove":"Remove %{user} from my roster","icons":{"multi":"multi","invite":"invite","kick":"kick","owner":"owner","remove":"rem"}},"tabbar":{"and":"and"},"messages":{"new_msg_placeholder":"Type your message here","send":"Send"},"window":{"sendFailed":"An error occured while sending your message"}}}}; app/assets/javascripts/roster.js.coffee 0000664 0000000 0000000 00000000345 13345106031 0020571 0 ustar 00root root 0000000 0000000 # Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
app/assets/javascripts/sessions.js.coffee 0000664 0000000 0000000 00000000345 13345106031 0021121 0 ustar 00root root 0000000 0000000 # Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
app/assets/stylesheets/ 0000775 0000000 0000000 00000000000 13345106031 0015510 5 ustar 00root root 0000000 0000000 app/assets/stylesheets/_config.scss 0000664 0000000 0000000 00000000340 13345106031 0020006 0 ustar 00root root 0000000 0000000 $bg-color: #DEDBDB;
$app-color: #24a396;
$bg-roster: #F4F4F4;
$inactive-color: #658489;
$white: #fff;
$gray: #b0b7b9;
$gray-light: #dde0e3;
$black: #282828;
$border-color: #c5c6c9;
$status-color: #b0b7b9;
$error: #AD3A3A; app/assets/stylesheets/application.css.scss 0000664 0000000 0000000 00000003054 13345106031 0021501 0 ustar 00root root 0000000 0000000 /*
*= require normalize-rails
*= require iconfonts
*= require_self
*/
@import "compass/css3/box-sizing";
@import "compass/css3/opacity";
@import "config";
@import "chat.css.scss";
@import "sessions.css.scss";
@import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700);
body {
background: $bg-color;
border-top: $inactive-color 7px solid;
font-size: 12px;
font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif;
}
a {
text-decoration: none;
}
a:visited {
color: inherit
}
input {
border: 1px $gray-light solid;
border-radius: 4px;
width: 100%;
margin-bottom: 10px;
padding: 0 5px;
height: 30px;
@include box-sizing(border-box);
&[type=submit], &[type=button] {
text-transform:lowercase;
font-variant: small-caps;
font-weight: bold;
font-size: 18px;
margin-top: 16px;
}
&[type=submit] {
background: $app-color;
color: #f9f9f9;
border: none 0px #000000;
}
&[type=button] {
background: $app-color;
color: #f9f9f9;
border: none 0px #000000;
}
&.inactive {
background-color: $inactive-color;
}
&:focus {
outline: none;
box-shadow: 0px 0px 1px 1px $app-color;
border: 1px solid white !important;
}
}
.top-border {
box-shadow: 0 -5px 0 $app-color;
}
.left-border {
box-shadow: -5px 0 0 $app-color;
}
.border {
border: solid 1px $border-color;
}
.no-top-border {
border-top: 0;
}
.no-left-border {
border-left: 0;
}
.left {
float: left;
}
.right {
float: right;
}
.clear {
clear: both;
}
.hidden {
display: none;
} app/assets/stylesheets/chat.css.scss 0000664 0000000 0000000 00000020577 13345106031 0020126 0 ustar 00root root 0000000 0000000 .container {
width: 1000px;
padding: 10px 17px;
}
.chat-alert {
width: 400px;
left: 490px;
background: white;
margin-bottom: 58px;
padding: 0 11px;
position: absolute;
h1 {
font-size: 1.4em;
}
.close {
font-size: 18px;
float: right;
padding: 11px 0 0;
}
}
.leftside {
width: 23%;
}
.rightside {
width: 75%;
}
.roster {
background: $bg-roster;
padding: 16px 9px 9px;
min-height: 400px;
position: relative;
&:hover, &:active {
cursor: default;
}
.wrap {
overflow-y: auto;
overflow-x: hidden;
}
}
.my-info {
height: 53px;
}
.user {
padding: 4px 0 4px 3px;
margin-right: -9px;
h1, h2, h3 {
margin: 0 0 1px;
}
//.name {
// font-size: 12px;
// color: $black;
// text-shadow: white 1px 1px;
// margin: 0;
// width: auto;
//}
h1 {
font-size: 12px;
color: $black;
text-shadow: white 1px 1px;
}
h3 {
font-size: 12px;
color: $status-color;
margin-top: -3px;
.rolldown {
clear: both;
left: 67px;
position: absolute;
width: 67px;
font-weight: normal;
float: left;
a {
color: $status-color;
}
&.editing {
background: white;
}
.active {
font-weight: bold;
}
}
.drop-down {
position: relative;
top: 1px;
left: 54px;
font-size: 13px;
}
}
h1 input, h2 input {
border: 0;
background: transparent;
width: 151px;
padding: 0;
outline: none;
margin: 0;
font-weight: normal;
color: #7e8b90;
text-shadow: white 1px 1px;
height: 18px;
&:focus {
border: none !important;
}
&.editing {
padding: 3px 0;
font-style: normal;
text-shadow: none;
}
}
h1 input {
color: $black;
font-size: 12px;
font-weight: bold;
}
h2 input {
font-style: italic;
font-size: 11px;
}
.avatar {
float: left;
height: 32px;
width: 32px;
margin-right: 8px;
&.bigger {
height: 46px;
width: 46px;
position: relative;
img {
height: 46px;
max-width: 46px;
}
.change-avatar {
&:hover {
cursor: pointer;
}
.background {
position: absolute;
top: 0;
left: 0;
height: 46px;
width: 46px;
background: $inactive-color;
@include opacity(0.8);
}
p {
position: absolute;
top: 3px;
left: 4px;
color: $gray-light;
font-weight: bold;
&:hover {
text-decoration: underline;
}
}
}
}
img {
height: 32px;
margin: 0 auto;
display: block;
}
}
}
.friends {
height: inherit;
//overflow-x: hidden;
//overflow-y: auto;
.group-header {
margin: 16px 3px 3px;
font-size: 12px;
text-transform: uppercase;
color: #9db0b7;
&.active {
color: $app-color;
}
}
ul {
padding: 0;
margin: 0;
position: relative;
li {
list-style: none;
background: $bg-roster;
position: relative;
}
}
.floating {
position: absolute;
// TODO
}
.user {
h2 {
font-size: 11px;
font-weight: normal;
color: #7e8b90;
text-shadow: white 1px 1px;
}
&:hover {
background: #fbfbfb;
}
}
ul.active-group {
@extend .no-left-border;
@extend .left-border;
@extend .border;
margin: 0 -16px 0 -5px;
padding: 8px 10px;
background: white;
border-right: 0 !important;
li {
background: white;
}
&::before {
content: '';
border-left: 5px solid #24a396 !important;
display: block;
width: 0;
height: 0;
}
.user {
margin-right: -5px;
&:hover {
background: $bg-roster;
}
}
}
.action {
margin-right: 2px;
margin-top: -31px;
float: right;
width: 67px;
color: #7e8b90;
.icon-bubble, .icon-bubbles, .icon-x-full, .icon-owner, .icon-make-owner {
display: none;
width: 21px;
overflow: hidden;
float: right;
padding-right: 10px;
padding-top: 1px;
&:before {
margin-left: 6px;
}
span {
font-variant: small-caps;
font-weight: bold;
position: relative;
top: -3px;
font-size: 0.9em;
}
}
.icon-bubble {
span {
left: 2px;
}
}
.icon-bubbles {
span {
left: 1px;
}
}
.icon-x-full {
span {
left: 7px;
}
}
.icon-x-full {
&:before {
margin-left: 9px;
}
}
}
}
#js-inactive-friends, js-active-friends {
position: relative;
}
.status {
font-size: 15px;
position: relative;
top: 2px;
&.online, &.away, &.dnd, &.offline {
font-size: 11px;
}
&.online {
color: #83C91F;
}
&.away {
color: #E6D105;
}
&.dnd {
color: #DC4E06;
}
&.offline {
color: #848F99;
}
}
.toolbox {
position: absolute;
bottom: 0;
width: 93%;
input {
width: 75%;
height: 22px;
border-radius: 16px;
margin-right: 6px;
padding-left: 10px;
padding-right: 10px;
display: block;
float: left;
}
a, a:visited {
color: $app-color;
border: 1px solid #dde0e3;
border-radius: 12px;
padding: 4px 4px 4px 5px;
margin-left: 0;
width: 13px;
height: 14px;
background: white;
display: inline-block;
}
}
.chatting-window {
padding-left: 5px;
}
.tabbar {
display: inline-block;
margin-bottom: -3px;
width: 750px;
.tab {
background: #f7f6f5;
width: 132px;
border: $border-color 1px solid;
float: left;
padding: 10px 7px 6px 7px;
margin: 0 4px -1px 0;
font-size: 15px;
color: $bg-color;
&:last-child {
margin-right: 0;
}
&.active {
width: 240px;
background: $bg-roster;
font-weight: bold;
color: $app-color;
padding-bottom: 7px;
border-top: 0;
padding-top: 10px;
line-height: 19px;
@extend .top-border;
}
a {
color: $app-color;
}
div {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
width: 84%;
float: left;
}
.icon {
margin-right: 4px;
}
}
}
.conversation {
background: white;
padding: 9px;
min-height: 367px;
width: 730px;
position: relative;
.event {
color: $status-color;
font-weight: bold;
font-size: 12px;
margin: 10px auto 0;
width: 50%;
text-align: center;
}
.messages {
min-height: 296px;
overflow: auto;
}
.message-box {
width: 75%;
color: #393939;
background: $bg-roster;
border: 1px solid $gray-light;
border-radius: 5px;
padding: 0 10px;
margin-top: 10px;
margin-left: 45px;
position: relative;
&::after, &::before {
content: '';
display: block;
width: 0;
height: 0;
border-right: 8px solid #f4f4f4;
border-bottom: 8px solid rgba(255,255,255,0);
border-top: 8px solid rgba(255,255,255,0);
position: absolute;
left: -8px;
top: 14px;
}
&::before {
z-index: 20;
}
&::after {
height: 2px;
border-right: 8px solid $gray-light;
left: -9px;
top: 13px;
}
&.active {
background: #dfecea;
&::before {
border-right: 8px solid #dfecea;
}
}
.date {
color: #949494;
text-shadow: white 1px 1px;
}
p {
margin: 4px 0 8px;
}
.user {
margin-left: -59px;
&:hover {
background: transparent;
}
}
}
}
.message-writer {
padding-top: 20px;
padding-left: 9px;
border-top: 1px solid $gray-light;
margin: 10px -9px 0;
position: relative;
input {
margin-top: 0;
border-color: black;
font-weight: bold;
}
input[type="text"] {
font-size: 12px;
width: 90%;
margin-right: 5px;
border-color: $gray-light;
color: $app-color;
}
::-webkit-input-placeholder {
color: $app-color;
opacity: 0.8;
}
::-moz-placeholder {
color: $app-color;
opacity: 0.8;
}
:-ms-input-placeholder {
color: $app-color;
opacity: 0.8;
}
input[type="submit"] {
font-size: 18px;
width: 8%;
position: absolute;
right: 8px;
margin: 0;
padding-bottom: 2px;
}
}
app/assets/stylesheets/sessions.css.scss 0000664 0000000 0000000 00000000754 13345106031 0021050 0 ustar 00root root 0000000 0000000 label {
display: block;
color: $gray;
font-size: 12px;
margin: 11px 0 2px;
}
$form-wrapper-padding: 14px;
.form-wrapper {
width: 250px;
background: $white;
padding: 0 $form-wrapper-padding;
margin: 100px auto;
h1 {
color: $app-color;
background: #f4f4f4;
font-size: 17px;
padding: 7px 14px;
}
}
.login-form-header {
margin: 0 -1*$form-wrapper-padding;
}
.alert {
font-size: 12px;
&.alert-notice {
}
&.alert-error {
color: $error;
}
} app/controllers/ 0000775 0000000 0000000 00000000000 13345106031 0014200 5 ustar 00root root 0000000 0000000 app/controllers/application_controller.rb 0000775 0000000 0000000 00000003062 13345106031 0021277 0 ustar 00root root 0000000 0000000 class ApplicationController < ActionController::Base
before_filter :require_login
def require_login
if authenticated?
create_new_authentication()
unless controller_name == 'chat'
redirect_to chat_path
end
else
unless controller_name == 'sessions' && (action_name == 'new' || action_name == 'create')
redirect_to root_url, flash: {error: I18n.t('login.access-denied')}
end
end
end
protected
def authenticated?
@token = Token.authenticate(session)
end
def create_new_authentication(user_credentials = nil)
if @token
user_id = @token.user_id
@token.delete
else
user_id = nil
end
@token = Token.new
save_session(user_id, user_credentials)
end
private
def save_session(user_id, user_credentials)
session[:token] = Token.generate_token()
session[:created_at] = Time.now
session[:ip] = request.remote_ip
if user_credentials
session[:users] = {} unless session[:users]
encrypted_pass = Security::encrypt(user_credentials[:password])
cookies[:key] = Security::cipher_key
cookies[:iv] = Security::cipher_iv
session[:users][user_credentials[:jid]] = encrypted_pass
end
@token.save_session(session, user_id)
end
def create_new_user(user_credentials)
jid = user_credentials[:jid]
user = User.existing_jid(jid) || User.create_jid(jid)
user.id
end
end
app/controllers/chat_controller.rb 0000664 0000000 0000000 00000000133 13345106031 0017704 0 ustar 00root root 0000000 0000000 class ChatController < ApplicationController
def index
end
def ws
end
end
app/controllers/sessions_controller.rb 0000664 0000000 0000000 00000001010 13345106031 0020626 0 ustar 00root root 0000000 0000000 class SessionsController < ApplicationController
def new
end
def create
begin
Signin.try_login(params[:jid].downcase, params[:password])
rescue Signin::LoginError
flash.now[:error] = I18n.t 'login.error'
return render 'new'
end
create_new_authentication({
jid: params[:jid].downcase,
password: params[:password]
})
redirect_to chat_path, flash: {notice: I18n.t('login.success')}
end
def destroy
self.remove_old_session(session[:token])
end
end
app/controllers/ws_chat_controller.rb 0000664 0000000 0000000 00000022757 13345106031 0020435 0 ustar 00root root 0000000 0000000 class WsChatController < WsController
def start_polling_messages
connection_store[:clients].each do |client|
client.add_message_callback do |message|
#noinspection RubyAssignmentExpressionInConditionalInspection
if message.body
process_incoming_message(message.from, message.to, message.body, message.attribute('chat_id').to_s)
elsif request = message.first_element('sync_contacts_request')
# toto mozem prijat len ako admin multichatu
send_contacts(message.from, message.to, request.attribute('chat_id').to_s)
elsif answer = message.first_element('synced_contacts')
# toto mozem prijat len ako ucastnik multichatu (nie admin)
contacts = xml_contacts_to_array(answer)
set_contacts_in_multichat(find_client(message.to.strip.to_s), answer.attribute('chat_id').to_s, message.from.to_s, contacts)
sync_contacts_frontend(message.from, message.to, answer.attribute('chat_id').to_s, contacts)
elsif answer = message.first_element('exported_chat')
contacts = xml_contacts_to_array(message.first_element('exported_chat'))
import_people_in_chat(message.from, message.to, answer.attribute('chat_id').to_s, contacts)
elsif message.attribute('destroy_multichat')
destroy_multichat(message.to, message.attribute('chat_id').to_s)
elsif message.attribute('req_update_contacts')
added = xml_contacts_to_array(message.first_element('added'))
removed = xml_contacts_to_array(message.first_element('removed'))
update_attendants_in_multichat(message.from, message.to, message.attribute('chat_id').to_s, removed, added)
elsif message.attribute('new_owner')
make_me_new_owner(message.to, message.attribute('chat_id').to_s)
end
#TODO: upozornit na pisanie spravy
#TODO: odoslat informaciu o tom, ze pisem spravu
#else
# send_message 'app.chat.messageState',
# state: message.chat_state,
# from: message.from.strip.to_s,
# to: message.to.strip.to_s,
# message: message.body,
# chat_id: if message.attribute(:is_simulating) then message.attribute(:chat_id) end
end
end
end
def make_me_new_owner(me, chat_id)
client = find_client(me.strip.to_s)
connection_store[:opened_chats][client][chat_id][:attendants] -= [me.to_s]
connection_store[:opened_chats][client][chat_id][:attendants] += [connection_store[:opened_chats][client][chat_id][:owner]]
connection_store[:opened_chats][client][chat_id][:owner] = me.to_s
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
contacts.each do |contact|
client.send(MessageBuilder::send_multichat_contacts(client.jid.to_s, contact, chat_id, contacts))
end
send_message 'app.chat.makeMeNewOwner',
me: me.strip.to_s,
chat_id: chat_id
end
def import_people_in_chat(owner, me, chat_id, contacts)
#Rails.logger.debug ['imported chat arrived', message.to_s, chat_id]
client = find_client(me.strip.to_s)
set_contacts_in_multichat(client, chat_id, owner.to_s, contacts)
send_message 'app.chat.importChat',
owner: owner.strip.to_s,
chat_id: chat_id,
contacts: strip_all(contacts)
end
# Owner vytvori najprv u seba novy multichat
def new_multichat
me = message[:chatOwner]
hash = Digest::SHA2.hexdigest(me)
chat_id = hash + Time.now.to_f.to_s
client = find_client(me)
set_contacts_in_multichat(client, chat_id, client.jid.to_s)
trigger_success id: chat_id
end
# Owner posle novemu cloveku informaciu o chat_id a kontaktoch
def add_to_multichat
client = find_client(message[:chatOwner])
chat_id = message[:chatId]
add = message[:jid]
add_resource = find_multichat_supported(add)
if add_resource
connection_store[:opened_chats][client][chat_id][:attendants] << add_resource
#Rails.logger.debug ['adding to multichat', add_resource]
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
contacts.each do |contact|
client.send(MessageBuilder::export_multichat(client.jid.to_s, contact, chat_id, contacts))
end
trigger_success
else
#Rails.logger.debug ['adding to multichat failure', connection_store[:presences][add]]
trigger_failure
end
end
def send_chat_message
me = message[:from]
client = find_client(me)
my_jid = client.jid.to_s
if client
chat_id = message[:chatId]
if chat_id
attendants = connection_store[:opened_chats][client][chat_id][:attendants] + [connection_store[:opened_chats][client][chat_id][:owner]]
attendants -= [my_jid]
messages = MessageBuilder::build_multi_messages(message[:message], my_jid, attendants, chat_id)
else
messages = [MessageBuilder::build_message(message[:message], my_jid, message[:to])]
end
# Xmpp4r doesn't support XEP-0033 (multicast messages)
messages.each do |message|
client.send(message)
end
trigger_success message[:message]
else
trigger_failure
end
end
def request_sync_chat_contacts
client = find_client(message[:me])
chat_id = message[:chatId]
owner = connection_store[:opened_chats][client][chat_id][:owner]
client.send(MessageBuilder::ask_for_multichat_contacts(client.jid.strip.to_s, owner, chat_id))
end
def i_closed_multichat
chat_id = message[:chatId]
client = find_client(message[:me])
me = client.jid.to_s
owner = connection_store[:opened_chats][client][chat_id][:owner]
if owner == me
attendants = connection_store[:opened_chats][client][chat_id][:attendants]
attendants.each do |attendant|
client.send(MessageBuilder::kick_from_multichat(me, attendant, chat_id))
end
else
changes = {removed: [me]}
client.send(MessageBuilder::req_update_multichat_contacts(me, owner, chat_id, changes))
end
connection_store[:opened_chats][client].delete(chat_id)
end
def kick_from_multichat
chat_id = message[:chatId]
client = find_client(message[:me])
kick_stripped = message[:kick]
kick = find_full_jid_in_multichat(kick_stripped, client, chat_id)
return if kick.nil?
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
contacts -= [kick]
connection_store[:opened_chats][client][chat_id][:attendants] = contacts
client.send(MessageBuilder::kick_from_multichat(client.jid.to_s, kick, chat_id))
if contacts.empty?
connection_store[:opened_chats][client].delete(chat_id)
send_message 'app.chat.destroyMultichat', chat_id: chat_id
else
contacts.each do |contact|
client.send(MessageBuilder::send_multichat_contacts(client.jid.to_s, contact, chat_id, contacts))
end
end
end
def switch_ownership
chat_id = message[:chatId]
client = find_client(message[:me])
new_owner = message[:newOwner]
new_owner_jid = find_full_jid_in_multichat(new_owner, client, chat_id)
client.send(MessageBuilder::make_new_owner(client.jid.to_s, new_owner_jid, chat_id)) if new_owner_jid
end
private
def process_incoming_message(from, to, body, chat_id = nil)
send_message 'app.chat.messageReceived',
from: from.to_s,
to: to.strip.to_s,
message: body,
chat_id: chat_id
end
def send_contacts(to, me, chat_id)
client = find_client(me.strip.to_s)
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
client.send(MessageBuilder::send_multichat_contacts(me.to_s, to.to_s, chat_id, contacts))
end
def update_attendants_in_multichat(owner, me, chat_id, removed, added)
client = find_client(me.strip.to_s)
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
contacts -= removed
contacts += added
connection_store[:opened_chats][client][chat_id][:attendants] = contacts
if contacts.empty?
destroy_multichat(me, chat_id)
else
contacts.each do |contact|
client.send(MessageBuilder::send_multichat_contacts(me.to_s, contact, chat_id, contacts))
end
sync_contacts_frontend(owner, me, chat_id, contacts)
end
end
def find_full_jid_in_multichat(kick_stripped, client, chat_id)
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
contacts.find do |contact|
contact =~ /^#{kick_stripped}/
end
end
end app/controllers/ws_controller.rb 0000664 0000000 0000000 00000006402 13345106031 0017423 0 ustar 00root root 0000000 0000000 class WsController < WebsocketRails::BaseController
protected
def set_contacts_in_multichat(client, chat_id, owner, attendants = [])
connection_store[:opened_chats] = {} if ! connection_store[:opened_chats]
connection_store[:opened_chats][client] = {} if ! connection_store[:opened_chats][client]
connection_store[:opened_chats][client][chat_id] = {owner: owner, attendants: attendants}
end
def find_client(client_jid)
connection_store[:clients].find do |client|
client.jid.strip.to_s == client_jid
end
end
def where_i_am_multichat_owner(client)
chats_owner = []
if connection_store[:opened_chats] and connection_store[:opened_chats][client]
connection_store[:opened_chats][client].each do |chat_id, contacts|
if contacts[:owner] == client.jid.to_s
chats_owner << chat_id
end
end
end
chats_owner
end
def kick_from_all_multichats(client, somebody_to_kick)
chats = where_i_am_multichat_owner(client)
chats.each do |chat_id|
contacts = connection_store[:opened_chats][client][chat_id][:attendants]
size_before = contacts.size
contacts -= [somebody_to_kick.to_s]
size_after = contacts.size
return if size_after == size_before
if contacts.empty?
destroy_multichat(client.jid, chat_id)
else
contacts.each do |contact|
client.send(MessageBuilder::send_multichat_contacts(client.jid.to_s, contact, chat_id, contacts))
end
sync_contacts_frontend(client.jid, client.jid, chat_id, contacts)
end
end
end
def kick_myself_from_multichat(client, owner)
if connection_store[:opened_chats] and connection_store[:opened_chats][client]
connection_store[:opened_chats][client].each do |chat_id, chat|
if chat[:owner] == owner.to_s and chat[:attendants].include? client.jid.to_s
destroy_multichat(client.jid, chat_id)
end
end
end
end
def sync_contacts_frontend(owner, me, chat_id, contacts)
send_message 'app.chat.updateSyncedContacts',
me: me.strip.to_s,
contacts: strip_all(contacts),
owner: owner.strip.to_s,
chat_id: chat_id
end
def destroy_multichat(me, chat_id)
client = find_client(me.strip.to_s)
connection_store[:opened_chats][client].delete(chat_id)
send_message 'app.chat.destroyMultichat', chat_id: chat_id
end
def xml_contacts_to_array(xml_contacts)
xml_contacts.get_elements('contact').map do |contact|
contact.text
end
end
def find_multichat_supported(jid_stripped)
contact = connection_store[:presences][jid_stripped.to_sym].find do |resource, info|
info[:multichat]
end
contact.first
end
def strip_all(contacts)
if contacts.is_a? Array
contacts.map do |jid|
Jabber::JID.new(jid).strip!.to_s
end
else
contacts
end
end
end app/controllers/ws_roster_controller.rb 0000664 0000000 0000000 00000027762 13345106031 0021035 0 ustar 00root root 0000000 0000000 class WsRosterController < WsController
require 'xmpp4r/roster'
require 'xmpp4r/vcard'
require 'RMagick'
def initialize
super
@storages_arr = [:clients, :rosters]
@storages_hash = [:link_roster_client, :my_presences, :presences]
end
def initialize_storage
@storages_arr.each do |storage|
connection_store[storage] = []
end
@storages_hash.each do |storage|
connection_store[storage] = {}
end
end
##
# Pripoj sa na jabber ucty.
def connect
initialize_storage()
# TODO: Pouzit najprv:
# clients = Token.fing_user_accounts_having_to_token(session[:token])
# ale toto, az ked budem mat dokonceny multiaccount (settings a popup)
# TODO: skusit zrychlit
cookies = env['rack.request.cookie_hash'] # TODO: nahlasit bug na websocket-rails, lebo sa neda pristupit ku `cookies'
cipher_key = cookies['key']
cipher_iv = cookies['iv']
clients = session[:users].map do |jid, encrypted_pass|
decrypted_pass = Security::decrypt(encrypted_pass, cipher_key, cipher_iv)
{jid: jid, pass: decrypted_pass}
end
clients.each do |client|
Thread.new do
begin
client = Signin.try_login(client[:jid], client[:pass])
connection_store[:clients] << client
send_message 'app.client.connected', client.jid.strip.to_s
#Rails.logger.debug '!!!!! finished for ' + client_id.to_s
rescue Signin::LoginError
#Rails.logger.debug '!!!!! finished FAILED for ' + client_id.to_s
send_message 'app.client.cannot_connect', client.jid.strip.to_s
end
end
end
end
##
# Inicializuj roster so zoznamom ludi v nom.
# Vrat zoznam ludi (ich JID).
def init_roster
all_jids = []
connection_store[:clients].each do |client|
roster = Jabber::Roster::Helper.new(client)
connection_store[:rosters] << roster
connection_store[:link_roster_client][roster] = client
roster.get_roster()
roster.wait_for_roster()
roster.items.each do |jid, contact|
all_jids << {
jid: jid.to_s,
belongsTo: client.jid.strip.to_s
}
end
end
trigger_success contacts: all_jids
end
##
# Stiahni vcard ludi v rosteri
def start_fetching_vcards
connection_store[:rosters].each do |roster|
client = connection_store[:link_roster_client][roster]
roster.items.each do |jid, contact|
Thread.new do
vcard = get_vcard_info(client, jid.to_s)
send_message 'app.roster.vcard', jid: jid.to_s, vcard: vcard
end
end
end
end
##
# Zacni pocuvat zmeny stavov v rosteri
def start_polling_contacts_state
start_polling_control_answer()
connection_store[:rosters].each do |roster|
start_polling_subscription(roster)
start_polling_presence(roster)
start_polling_friend_requests(roster)
end
end
##
# Nastav ma ako online
#
# Musi sa zavolat az po start_polling_contacts_state, inak sa nemusia
# zachytit stavy ostatnych v rosteri.
def set_presence
connection_store[:clients].each do |client|
presence = Jabber::Presence.new.set_type(:available)
client.send(presence)
connection_store[:my_presences][client] = presence
end
end
def remove_contact
jid = message[:jid]
client_jid = message[:client]
found_roster_items = []
connection_store[:rosters].each do |roster|
roster_item = roster.find(jid)
contact_obj = roster_item.first[1]
if contact_obj
if client_jid && connection_store[:link_roster_client][roster].jid.strip.to_s == client_jid
found_roster_items = [ contact_obj ]
break
else
found_roster_items.push(contact_obj)
end
end
end
found_roster_items.each do |contact|
contact.remove()
end
end
##
# Ziskaj informacie o mne (meno, stav, status...)
def myself
# TODO: v pripade viacerych uctov zjednotit meno a stav
vcard = {}
jid = presence = ''
connection_store[:clients].each do |client|
vcard = get_vcard_info(client)
jid = client.jid.strip.to_s
presence = uniform_presence(connection_store[:my_presences][client].show)
end
trigger_success jid: jid, vcard: vcard, status: presence
end
def me_update_status
status_message = message[:message]
state = message[:state]
xmpp_state = case state
when 'away' then :away
when 'dnd' then :dnd
else nil
end
presence = Jabber::Presence.new
presence.show = xmpp_state
presence.status = status_message
connection_store[:clients].each do |client|
client.send(presence)
end
end
def me_update_vcard
connection_store[:clients].each do |client|
Thread.new do
my_vcard = Jabber::Vcard::Helper.get(client)
if message[:name]
my_vcard['FN'] = my_vcard['NICKNAME'] = message[:name]
end
if message[:avatar]
begin
im = Magick::Image::read_inline(message[:avatar]).first
type = im.format.downcase
allowed_types = %w(png jpeg pjpeg gif webp)
return unless allowed_types.include? type
im.resize_to_fit! 128,128
base64 = Base64.encode64(im.to_blob)
my_vcard['PHOTO/TYPE'] = 'image/' + type
my_vcard['PHOTO/BINVAL'] = base64
rescue
# ignored
end
end
Jabber::Vcard::Helper.set(client, my_vcard)
end
end
end
def disconnect
connection_store[:clients] && connection_store[:clients].each do |client|
client.close()
end
@storages_arr.each do |storage|
connection_store.delete(storage)
end
@storages_hash.each do |storage|
connection_store.delete(storage)
end
end
def add_contact(jid = nil)
new_jid = message[:jid] || jid
if is_valid_jid? new_jid
connection_store[:rosters].each do |roster|
Thread.new do
roster.add(new_jid, nil, true)
end
end
end
end
def answer_friend_request
jid = message[:jid]
answer = message[:answer]
connection_store[:rosters].each do |roster|
if answer
roster.accept_subscription(jid)
add_contact(jid)
else
roster.decline_subscription(jid)
end
end
end
private
def select_most_online_status(jid_stripped)
if connection_store[:presences][jid_stripped].nil? || connection_store[:presences][jid_stripped].empty?
return uniform_presence(:offline)
end
statuses = { offline: 0, dnd: 1, away: 2, online: 3 }
Hash[connection_store[:presences][jid_stripped].map do |jid, data|
[ statuses[data[:status]], data[:status] ]
end].max.second
end
def get_vcard_info(me, contact_jid = nil)
vcard = Jabber::Vcard::Helper.get(me, contact_jid)
{ name: pull_name_from_vcard(vcard) || contact_jid,
avatar: vcard['PHOTO/TYPE'] && ('data:' + vcard['PHOTO/TYPE'] + ';base64,' + vcard['PHOTO/BINVAL']) || ''
}
end
def pull_name_from_vcard(vcard)
vcard && (vcard['FN'] || vcard['NICKNAME'])
end
def uniform_presence(xmpp_presence)
case xmpp_presence
when :offline then :offline
when :away, :xa then :away
when :dnd then :dnd
else :online
end
end
def start_polling_control_answer
connection_store[:clients].each do |client|
client.add_message_callback do |message|
if message.attribute('i_am_using_same_app')
connection_store[:presences][message.from.strip.to_s][message.from.to_s.to_sym][:multichat] = true
send_message 'app.roster.using_this_app',
jid: message.from.strip.to_s
elsif message.attribute('are_you_using_my_app')
client.send(MessageBuilder::control_answer(client.jid.to_s, message.from.to_s))
end
end
end
end
def start_polling_presence(roster)
roster.add_presence_callback do |roster_item, old_presence, new_presence|
if new_presence.type == :unavailable
result = {message: ''}
unless connection_store[:presences][roster_item.jid.strip.to_s].nil?
connection_store[:presences][roster_item.jid.strip.to_s.to_sym].delete(new_presence.from.to_s)
end
client = connection_store[:link_roster_client][roster]
# mozno treba vyhodit cloveka z multichatu, ak som jeho owner
kick_from_all_multichats(client, new_presence.from)
# mozno treba sa odpojit z multichatu, ak sa odpojil jeho owner
kick_myself_from_multichat(client, new_presence.from)
else
status = uniform_presence(new_presence.show)
result = {message: new_presence.status.to_s}
if connection_store[:presences][roster_item.jid.strip.to_s.to_sym].nil?
connection_store[:presences][roster_item.jid.strip.to_s.to_sym] = Hash.new()
end
connection_store[:presences][roster_item.jid.strip.to_s][new_presence.from.to_s.to_sym] = {
status: status,
multichat: false,
priority: new_presence.priority
}
ask_if_using_this_app(connection_store[:link_roster_client][roster], new_presence.from.to_s)
end
result[:status] = select_most_online_status(roster_item.jid.strip.to_s)
send_message 'app.roster.statusChanged',
jid: roster_item.jid.strip.to_s, status: result
end
end
def start_polling_subscription(roster)
roster.add_subscription_callback do |roster_item, stanza|
# stan unsubscribe = niekto uz ma nesubscribuje
# stan unsubscribed = niekto ma uz nesubscribuje a aj si ma vymazal
# stan subscribed = niekto moze vidiet moj stav
client = connection_store[:link_roster_client][roster]
send_message 'app.roster.subscriptionChanged',
action: stanza.type,
jid: roster_item.jid.strip.to_s,
belongsTo: client.jid.strip.to_s
end
end
def start_polling_friend_requests(roster)
client = connection_store[:link_roster_client][roster]
roster.add_subscription_request_callback do |roster_item, stanza|
jid = stanza.from.to_s
send_message 'app.roster.friendRequest',
jid: jid,
name: get_vcard_info(client, jid)[:name]
end
end
def ask_if_using_this_app(client, contact)
client.send(MessageBuilder::control_question(client.jid.to_s, contact))
end
def is_valid_jid?(jid)
!! jid.scan(/^\w+@\w+\.\w+$/).first
end
end
app/helpers/ 0000775 0000000 0000000 00000000000 13345106031 0013274 5 ustar 00root root 0000000 0000000 app/helpers/application_helper.rb 0000775 0000000 0000000 00000000035 13345106031 0017464 0 ustar 00root root 0000000 0000000 module ApplicationHelper
end
app/helpers/chat_helper.rb 0000664 0000000 0000000 00000000026 13345106031 0016075 0 ustar 00root root 0000000 0000000 module ChatHelper
end
app/helpers/roster_helper.rb 0000664 0000000 0000000 00000000030 13345106031 0016467 0 ustar 00root root 0000000 0000000 module RosterHelper
end
app/helpers/sessions_helper.rb 0000664 0000000 0000000 00000000032 13345106031 0017021 0 ustar 00root root 0000000 0000000 module SessionsHelper
end
app/libs/ 0000775 0000000 0000000 00000000000 13345106031 0012563 5 ustar 00root root 0000000 0000000 app/libs/message_builder.rb 0000664 0000000 0000000 00000006333 13345106031 0016247 0 ustar 00root root 0000000 0000000 module MessageBuilder
def self.build_multi_messages(message, from, attendants, chat_id)
attendants.map do |person|
new_message = Jabber::Message.new(person, message)
new_message.from = from
new_message.add_attribute('chat_id', chat_id)
new_message
end
end
def self.build_message(message, from, to)
message = Jabber::Message.new(to, message)
message.from = from
message
end
def self.ask_for_multichat_contacts(from, to, chat_id)
message = Jabber::Message.new(to)
message.from = from
sync_request = REXML::Element.new('sync_contacts_request')
sync_request.add_attributes('chat_id' => chat_id)
message.add_element sync_request
message
end
def self.send_multichat_contacts(from, to, chat_id, contacts)
message = Jabber::Message.new(to)
message.from = from
sync_answer = REXML::Element.new('synced_contacts')
collect_contacts(sync_answer, contacts)
sync_answer.add_attributes('chat_id' => chat_id)
message.add_element sync_answer
message
end
def self.export_multichat(from, to, chat_id, contacts)
message = Jabber::Message.new(to)
message.from = from
chat = REXML::Element.new('exported_chat')
collect_contacts(chat, contacts)
chat.add_attributes('chat_id' => chat_id)
message.add_element chat
message
end
def self.control_answer(from, to)
message = Jabber::Message.new(to)
message.from = from
message.add_attributes('i_am_using_same_app' => 'true')
message
end
def self.control_question(from, to)
message = Jabber::Message.new(to)
message.from = from
message.add_attributes('are_you_using_my_app' => '?')
message
end
def self.req_update_multichat_contacts(from, to, chat_id, changes)
message = Jabber::Message.new(to)
message.from = from
message.add_attributes('req_update_contacts' => 'true')
message.add_attributes('chat_id' => chat_id)
added = REXML::Element.new('added')
removed = REXML::Element.new('removed')
collect_contacts(added, changes[:added])
collect_contacts(removed, changes[:removed])
message.add_element added
message.add_element removed
message
end
def self.kick_from_multichat(from, to, chat_id)
message = Jabber::Message.new(to)
message.from = from
message.add_attributes('destroy_multichat' => 'true')
message.add_attributes('chat_id' => chat_id)
message
end
def self.make_new_owner(from, to, chat_id)
message = Jabber::Message.new(to)
message.from = from
message.add_attributes('chat_id' => chat_id)
message.add_attributes('new_owner' => true)
message
end
private
def self.collect_contacts(to_element, contacts)
if contacts.kind_of?(Array)
contacts.each do |contact|
contact_xml = REXML::Element.new('contact')
contact_xml.text = contact
to_element.add_element contact_xml
end
end
end
end app/libs/security.rb 0000664 0000000 0000000 00000002074 13345106031 0014762 0 ustar 00root root 0000000 0000000 require 'bcrypt'
module Security
mattr_reader :cipher_iv, :cipher_key
def self.encrypt(unencrypted_message, password = nil)
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.encrypt()
pass = password ? password : generate_token()
cipher.key = @@cipher_key = Digest::SHA1.hexdigest(pass)
cipher.iv = @@cipher_iv = cipher.random_iv
encrypted = cipher.update(unencrypted_message)
encrypted << cipher.final
end
def self.decrypt(encrypted_message, key, iv)
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.decrypt()
cipher.key = @@cipher_key = key
cipher.iv = @@cipher_iv = iv
begin
decrypted = cipher.update(encrypted_message)
decrypted << cipher.final
rescue
decrypted = ''
end
decrypted
end
def self.generate_token()
BCrypt::Engine.generate_salt()
end
def self.save_cookies
cookies[:key] = @@cipher_key
cookies[:iv] = @@cipher_iv
end
end app/libs/signin.rb 0000664 0000000 0000000 00000000674 13345106031 0014406 0 ustar 00root root 0000000 0000000 require 'xmpp4r'
require 'xmpp4r/roster'
require 'cgi'
module Signin
include Jabber
def self.try_login(jid, pass)
@client = Client.new(jid)
begin
connect(pass)
@client
rescue Exception => e
raise LoginError, e.message
end
end
def self.connect(pass)
@client.connect()
@client.auth(pass)
end
class LoginError < Exception
end
end app/mailers/ 0000775 0000000 0000000 00000000000 13345106031 0013266 5 ustar 00root root 0000000 0000000 app/mailers/.gitkeep 0000775 0000000 0000000 00000000000 13345106031 0014710 0 ustar 00root root 0000000 0000000 app/models/ 0000775 0000000 0000000 00000000000 13345106031 0013115 5 ustar 00root root 0000000 0000000 app/models/.gitkeep 0000775 0000000 0000000 00000000000 13345106031 0014537 0 ustar 00root root 0000000 0000000 app/models/token.rb 0000664 0000000 0000000 00000002517 13345106031 0014567 0 ustar 00root root 0000000 0000000 require 'ipaddr'
class Token
include Mongoid::Document
include Mongoid::Timestamps::Created
index({ token: 1 })
belongs_to :user
field :token, type: String
field :date_expiring, type: DateTime
field :ip, type: Integer
field :user_id, type: Moped::BSON::ObjectId
##
# @return [Token]
def self.authenticate(session)
if session[:created_at].nil?
return false
end
matched_token = self.where(:token => session[:token],
:ip => IPAddr.new(session[:ip]).to_i,
:date_expiring.lte => session[:created_at] + Rails.application.config.max_lifetime,
:created_at => session[:created_at]
).limit(1).first
matched_token
end
def save_session(session, user)
self.token = session[:token]
self.date_expiring = session[:created_at] + Rails.application.config.max_lifetime
self.created_at = session[:created_at]
self.ip = IPAddr.new(session[:ip]).to_i
self.user_id = user
save
end
#def self.remove_old_session(token)
# self.delete_all(token: token)
#end
def self.fing_user_accounts_having_to_token(token)
where(token: token).first().user.jids
end
private
def self.generate_token
begin
token = SecureRandom.urlsafe_base64
end while Token.where(token: token).exists?
token
end
end
app/models/user.rb 0000664 0000000 0000000 00000001130 13345106031 0014413 0 ustar 00root root 0000000 0000000 class User
include Mongoid::Document
field :jids, type: Array
def update_pass(jid, pass)
account_credentials = jids.detect do |f|
f[:jid] == jid || f["jid"] == jid
end
account_credentials[:pass] = pass
save
end
def self.existing_jid(jid)
where("jids.jid" => jid).only(:jids).first
end
def self.create_jid(jid)
new_user = new(jids: [ {jid: jid} ])
new_user.save
new_user
end
def add_account(another_jid, password)
jids << {jid: another_jid, pass: password}
save
end
end app/views/ 0000775 0000000 0000000 00000000000 13345106031 0012767 5 ustar 00root root 0000000 0000000 app/views/chat/ 0000775 0000000 0000000 00000000000 13345106031 0013706 5 ustar 00root root 0000000 0000000 app/views/chat/index.html.haml 0000664 0000000 0000000 00000004403 13345106031 0016624 0 ustar 00root root 0000000 0000000 - content_for :title, t("chat.title")
- content_for :javascript, javascript_include_tag("application")
- content_for :javascript, javascript_include_tag("backbone-ws")
- content_for :javascript do
:javascript
I18n.defaultLocale = "#{I18n.default_locale}";
I18n.locale = "#{I18n.locale}";
%div.chat-alert.alert-notice.no-top-border.top-border#js-popup
- if flash[:notice]
%a.close.icon-x{ alt: "#{I18n.t('chat.close')}", href: '#' }
%h1
= flash[:notice]
.container
.leftside.left
.roster.border.no-top-border.top-border
.my-info#js-me
.friends
#js-active-friends
.wrap#height-setter-1
#js-inactive-friends
.toolbox
%input#js-search-contacts{type: 'search', placeholder: "#{I18n.t('chat.roster.search')}", name: 'contact'}
%a{ href: '#', title: 'Add new contact', id: 'add-contact' }
%span.icon-plus
.chatting-window.left.rightside
.tabbar#tabbar
.wrap#js-tabbar
#conversation-js
-#.conversation.clear.border
-#.messages#height-setter-2{style: "overflow: auto;"}
-# %a{ href: "#", class: "event" }
-# Load older messages
-# %span.icon-arrow-up
-# .message-box
-# .user
-# .avatar
-# %img{src: 'assets/avatar.png', alt: t("chat.avatar_alt")}
-# -#todo: user name missing in design
-# .date 3 hours ago
-# %p Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Supendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula...
-#
-# .message-box.active
-# .user
-# .avatar
-# %img{src: 'assets/avatar.png', alt: t("chat.avatar_alt")}
-# -#todo: user name missing in design
-# .date 3 hours ago
-# %p Etiam ullamcorper. Supendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Lorem ipsum dolor sit amet enim.
-#
-# .event
-# Adam left the room | 13:45
-#
-#.message-writer#msg-writer
-# %input{ type: "text", placeholder: "Type your message here" }
-# %input{ type: "submit", value: "Send" }
app/views/layouts/ 0000775 0000000 0000000 00000000000 13345106031 0014467 5 ustar 00root root 0000000 0000000 app/views/layouts/application.html.haml 0000775 0000000 0000000 00000000275 13345106031 0020607 0 ustar 00root root 0000000 0000000 !!! 5
%html{:lang => :en}
%head
%title= yield(:title) + " | App"
%meta{ :charset => "utf-8" }/
= stylesheet_link_tag "application"
%body
= yield
= yield(:javascript) app/views/sessions/ 0000775 0000000 0000000 00000000000 13345106031 0014635 5 ustar 00root root 0000000 0000000 app/views/sessions/new.html.haml 0000664 0000000 0000000 00000001234 13345106031 0017234 0 ustar 00root root 0000000 0000000 - content_for(:title, t("sessions.title"))
.form-wrapper
%h1{ class: "top-border login-form-header" }
= t(".form-header")
%form{ action: sessions_path, method: "post"}
%label{for: "jid"}
%strong Jabber ID
%input{type: "text", name: "jid", class: "text", id: "jid", placeholder: "nick@example.com"}
%label{for: "pass"}
%strong Password
%input{type: "password", name: "password", class: "text", id: "pass"}
- if flash[:error]
%div.alert.alert-error
= flash[:error]
- if flash[:notice]
%div.alert.alert-notice
= flash[:notice]
%input{type: "submit", value: t(".form-send"), class: "button"} config.ru 0000775 0000000 0000000 00000000232 13345106031 0012667 0 ustar 00root root 0000000 0000000 # This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Xmpp::Application
config/ 0000775 0000000 0000000 00000000000 13345106031 0012317 5 ustar 00root root 0000000 0000000 config/application.rb 0000775 0000000 0000000 00000005725 13345106031 0015163 0 ustar 00root root 0000000 0000000 require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
# require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
module Xmpp
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password, :users]
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Enforce whitelist mode for mass assignment.
# This will create an empty whitelist of attributes available for mass-assignment for all models
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
# parameters by using an attr_accessible or attr_protected declaration.
# config.active_record.whitelist_attributes = true
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
end
end
config/boot.rb 0000775 0000000 0000000 00000000277 13345106031 0013620 0 ustar 00root root 0000000 0000000 require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
config/compass.rb 0000664 0000000 0000000 00000000104 13345106031 0014304 0 ustar 00root root 0000000 0000000 # Require any additional compass plugins here.
project_type = :rails config/environment.rb 0000775 0000000 0000000 00000000224 13345106031 0015211 0 ustar 00root root 0000000 0000000 # Load the rails application
require File.expand_path('../application', __FILE__)
# Initialize the rails application
Xmpp::Application.initialize!
config/environments/ 0000775 0000000 0000000 00000000000 13345106031 0015046 5 ustar 00root root 0000000 0000000 config/environments/development.rb 0000775 0000000 0000000 00000002124 13345106031 0017717 0 ustar 00root root 0000000 0000000 Xmpp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
config.middleware.delete Rack::Lock
end
config/environments/production.rb 0000775 0000000 0000000 00000004405 13345106031 0017567 0 ustar 00root root 0000000 0000000 Xmpp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
end
config/environments/test.rb 0000775 0000000 0000000 00000002556 13345106031 0016365 0 ustar 00root root 0000000 0000000 Xmpp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
# Log error messages when you accidentally call methods on nil
config.whiny_nils = true
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
end
config/i18n-js.yml 0000664 0000000 0000000 00000001271 13345106031 0014234 0 ustar 00root root 0000000 0000000 # Split context in several files.
# By default only one file with all translations is exported and
# no configuration is required. Your settings for asset pipeline
# are automatically recognized.
#
# If you want to split translations into several files or specify
# locale contexts that will be exported, just use this file to do
# so.
#
# If you're going to use the Rails 3.1 asset pipeline, change
# the following configuration to something like this:
#
# translations:
# - file: "app/assets/javascripts/i18n/translations.js"
#
# If you're running an old version, you can use something
# like this:
#
# translations:
# - file: "public/javascripts/translations.js"
# only: "*"
# config/initializers/ 0000775 0000000 0000000 00000000000 13345106031 0015025 5 ustar 00root root 0000000 0000000 config/initializers/backtrace_silencers.rb 0000775 0000000 0000000 00000000624 13345106031 0021345 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
config/initializers/events.rb 0000664 0000000 0000000 00000007434 13345106031 0016666 0 ustar 00root root 0000000 0000000 WebsocketRails.setup do |config|
# Uncomment to override the default log level. The log level can be
# any of the standard Logger log levels. By default it will mirror the
# current Rails environment log level.
# config.log_level = :debug
# Uncomment to change the default log file path.
# config.log_path = "#{Rails.root}/log/websocket_rails.log"
# Set to true if you wish to log the internal websocket_rails events
# such as the keepalive `websocket_rails.ping` event.
# config.log_internal_events = false
# Change to true to enable standalone server mode
# Start the standalone server with rake websocket_rails:start_server
# * Requires Redis
config.standalone = false
# Change to true to enable channel synchronization between
# multiple server instances.
# * Requires Redis.
config.synchronize = false
# Uncomment and edit to point to a different redis instance.
# Will not be used unless standalone or synchronization mode
# is enabled.
# config.redis_options = {:host => 'localhost', :port => '6379'}
end
WebsocketRails::EventMap.describe do
# You can use this file to map incoming events to controller actions.
# One event can be mapped to any number of controller actions. The
# actions will be executed in the order they were subscribed.
#
# Uncomment and edit the next line to handle the client connected event:
# subscribe :client_connected, :to => Controller, :with_method => :method_name
#
# Here is an example of mapping namespaced events:
# namespace :product do
# subscribe :new, :to => ProductController, :with_method => :new_product
# end
# The above will handle an event triggered on the client like `product.new`.
#toto asi poide do nadtriedy spajajucej roster a chat a ine veci
subscribe :client_connected, to: WsRosterController, with_method: :connect
subscribe :client_disconnected, to: WsRosterController, with_method: :disconnect
namespace :app do
namespace :roster do
subscribe :initRoster, to: WsRosterController, with_method: :init_roster
subscribe :startFetchingVcards, to: WsRosterController, with_method: :start_fetching_vcards
subscribe :startPolling, to: WsRosterController, with_method: :start_polling_contacts_state
subscribe :setPresence, to: WsRosterController, with_method: :set_presence
subscribe :myself, to: WsRosterController, with_method: :myself
subscribe :updateMyStatus, to: WsRosterController, with_method: :me_update_status
subscribe :updateMyVcard, to: WsRosterController, with_method: :me_update_vcard
subscribe :removeContact, to: WsRosterController, with_method: :remove_contact
subscribe :addContact, to: WsRosterController, with_method: :add_contact
subscribe :answerFriendRequest, to: WsRosterController, with_method: :answer_friend_request
end
namespace :chat do
subscribe :newMultiChat, to: WsChatController, with_method: :new_multichat
subscribe :addToMultiChat, to: WsChatController, with_method: :add_to_multichat
subscribe :sendMessage, to: WsChatController, with_method: :send_chat_message
subscribe :startPollingMessages, to: WsChatController, with_method: :start_polling_messages
subscribe :syncMultiChatContacts, to: WsChatController, with_method: :request_sync_chat_contacts
subscribe :iClosedMultichat, to: WsChatController, with_method: :i_closed_multichat
subscribe :kickFromMultichat, to: WsChatController, with_method: :kick_from_multichat
subscribe :switchOwnership, to: WsChatController, with_method: :switch_ownership
end
end
end
config/initializers/inflections.rb 0000775 0000000 0000000 00000001025 13345106031 0017670 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
#
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.acronym 'RESTful'
# end
config/initializers/lib_reload.rb 0000664 0000000 0000000 00000000000 13345106031 0017434 0 ustar 00root root 0000000 0000000 config/initializers/log_formatting.rb 0000664 0000000 0000000 00000001370 13345106031 0020366 0 ustar 00root root 0000000 0000000 class ActiveSupport::BufferedLogger
def formatter=(formatter)
@log.formatter = formatter
end
end
class Formatter
SEVERITY_TO_COLOR_MAP = {'DEBUG'=>'33', 'INFO'=>'0;37', 'WARN'=>'36', 'ERROR'=>'31', 'FATAL'=>'31', 'UNKNOWN'=>'37'}
USE_HUMOROUS_SEVERITIES = false
def call(severity, time, progname, msg)
formatted_severity = sprintf("%s","#{severity}")
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S.") << time.usec.to_s[0..2].rjust(3)
color = SEVERITY_TO_COLOR_MAP[severity]
if msg.empty?
"\n"
else
"\033[0;37m#{formatted_time}\033[0m [#{formatted_severity}] \033[#{color}m#{msg.strip}\033[0m\n"
end
end
end
Rails.logger.formatter = Formatter.new config/initializers/mime_types.rb 0000775 0000000 0000000 00000000315 13345106031 0017527 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone
config/initializers/secret_token.rb 0000775 0000000 0000000 00000001623 13345106031 0020044 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
if Rails.env == 'development'
Xmpp::Application.config.secret_token = '48c49768f9ec3134de5f76e352f13bf8fd66258941sa5d489g4fhj8k4uk8499075975aaaef1c32be9d596120302e8e3f7b6200c207835463ecdbb6c1610705c2'
elsif Rails.env == 'test'
Xmpp::Application.config.secret_token = '48c49768f9ec3134de5f76e352f13bf8fd66252d67f6d285c1c6de8457f0499075975aaaesdjfdsaf84dasf4das89f47d44hgf7yuuy35463ecdbb6c1610705c2'
else
Xmpp::Application.config.secret_token = '8as5dg8fd4s5f76e352f13bf8fd66289dsa41s4tyijk8uy494j4e8457f0499075975aaaef89asd4302e8e3f7b6200c207835463ecdbb6cugsadbfy31610705c2'
end
config/initializers/session_settings.rb 0000664 0000000 0000000 00000000150 13345106031 0020751 0 ustar 00root root 0000000 0000000 # How long session will be remembered. 2 Weeks.
Xmpp::Application.config.max_lifetime = 60 * 60 * 24 * 2 config/initializers/session_store.rb 0000775 0000000 0000000 00000000624 13345106031 0020256 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
Xmpp::Application.config.session_store :cookie_store, key: '_xmpp_session'
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")
# Xmpp::Application.config.session_store :active_record_store
config/initializers/wrap_parameters.rb 0000775 0000000 0000000 00000000525 13345106031 0020553 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
#
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
config/locales/ 0000775 0000000 0000000 00000000000 13345106031 0013741 5 ustar 00root root 0000000 0000000 config/locales/en.yml 0000775 0000000 0000000 00000002521 13345106031 0015071 0 ustar 00root root 0000000 0000000 # Sample localization file for English. Add more files in this directory for other locales.
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
login:
error: 'Invalid email/password combination'
success: 'Successfully logged in'
access-denied: 'Access denied. Please, sign in first.'
sessions:
title: "Login"
new:
form-header: "Login form"
form-send: "Login"
chat:
title: "Chat"
close: "Close"
roster:
friends: "Friends"
chat-group: "Chat group"
avatar_alt: "Avatar"
change_status_msg: "Change your status message"
search: "Search contacts"
group-empty: "Empty"
new-multichat: "Create new multichat with %{user}"
invite: "Invite %{user} to the opened multichat"
kick: "Kick %{user} from the opened multichat"
is-owner: "%{user} is the owner of the opened multichat"
make-owner: "Make %{user} a new owner of the opened multichat"
remove: "Remove %{user} from my roster"
icons:
multi: "multi"
invite: "invite"
kick: "kick"
owner: "owner"
remove: "rem"
tabbar:
and: 'and'
messages:
new_msg_placeholder: "Type your message here"
send: "Send"
window:
sendFailed: "An error occured while sending your message"
config/mongoid.yml 0000775 0000000 0000000 00000005346 13345106031 0014511 0 ustar 00root root 0000000 0000000 development:
# Configure available database sessions. (required)
sessions:
# Defines the default session. (required)
default:
# Defines the name of the default database that Mongoid can connect to.
# (required).
database: xmpp_development
# Provides the hosts the default session can connect to. Must be an array
# of host:port pairs. (required)
hosts:
- localhost:27017
options:
# Change whether the session persists in safe mode by default.
# (default: false)
# safe: false
# Change the default consistency model to :eventual or :strong.
# :eventual will send reads to secondaries, :strong sends everything
# to master. (default: :eventual)
# consistency: :eventual
# How many times Moped should attempt to retry an operation after
# failure. (default: 30)
# max_retries: 30
# The time in seconds that Moped should wait before retrying an
# operation on failure. (default: 1)
# retry_interval: 1
# Configure Mongoid specific options. (optional)
options:
# Configuration for whether or not to allow access to fields that do
# not have a field definition on the model. (default: true)
# allow_dynamic_fields: true
# Enable the identity map, needed for eager loading. (default: false)
# identity_map_enabled: false
# Includes the root model name in json serialization. (default: false)
# include_root_in_json: false
# Include the _type field in serializaion. (default: false)
# include_type_for_serialization: false
# Preload all models in development, needed when models use
# inheritance. (default: false)
# preload_models: false
# Protect id and type from mass assignment. (default: true)
# protect_sensitive_fields: true
# Raise an error when performing a #find and the document is not found.
# (default: true)
# raise_not_found_error: true
# Raise an error when defining a scope with the same name as an
# existing method. (default: false)
# scope_overwrite_exception: false
# Skip the database version check, used when connecting to a db without
# admin access. (default: false)
# skip_version_check: false
# User Active Support's time zone in conversions. (default: true)
# use_activesupport_time_zone: true
# Ensure all times are UTC in the app side. (default: false)
# use_utc: false
test:
sessions:
default:
database: xmpp_test
hosts:
- localhost:27017
options:
consistency: :strong
# In the test environment we lower the retries and retry interval to
# low amounts for fast failures.
max_retries: 1
retry_interval: 0
config/routes.rb 0000775 0000000 0000000 00000000436 13345106031 0014173 0 ustar 00root root 0000000 0000000 Xmpp::Application.routes.draw do
get "chat/index"
resources :sessions, only: [:new, :create, :destroy]
root :to => 'sessions#new'
match '/signin', to: 'sessions#new'
match '/signout', to: 'sessions#destroy', via: :delete
match '/chat', to: 'chat#index'
end
db/ 0000775 0000000 0000000 00000000000 13345106031 0011437 5 ustar 00root root 0000000 0000000 db/initialize.sh 0000664 0000000 0000000 00000000036 13345106031 0014133 0 ustar 00root root 0000000 0000000 rake db:mongoid:create_indexes db/seeds.rb 0000775 0000000 0000000 00000000527 13345106031 0013076 0 ustar 00root root 0000000 0000000 # This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
doc/ 0000775 0000000 0000000 00000000000 13345106031 0011617 5 ustar 00root root 0000000 0000000 doc/README_FOR_APP 0000775 0000000 0000000 00000000323 13345106031 0013706 0 ustar 00root root 0000000 0000000 Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
lib/ 0000775 0000000 0000000 00000000000 13345106031 0011620 5 ustar 00root root 0000000 0000000 lib/assets/ 0000775 0000000 0000000 00000000000 13345106031 0013122 5 ustar 00root root 0000000 0000000 lib/assets/.gitkeep 0000775 0000000 0000000 00000000000 13345106031 0014544 0 ustar 00root root 0000000 0000000 lib/tasks/ 0000775 0000000 0000000 00000000000 13345106031 0012745 5 ustar 00root root 0000000 0000000 lib/tasks/.gitkeep 0000775 0000000 0000000 00000000000 13345106031 0014367 0 ustar 00root root 0000000 0000000 log/ 0000775 0000000 0000000 00000000000 13345106031 0011633 5 ustar 00root root 0000000 0000000 log/.gitkeep 0000775 0000000 0000000 00000000000 13345106031 0013255 0 ustar 00root root 0000000 0000000 public/ 0000775 0000000 0000000 00000000000 13345106031 0012330 5 ustar 00root root 0000000 0000000 public/404.html 0000775 0000000 0000000 00000001330 13345106031 0013525 0 ustar 00root root 0000000 0000000
The page you were looking for doesn't exist (404)
The page you were looking for doesn't exist.
You may have mistyped the address or the page may have moved.
public/422.html 0000775 0000000 0000000 00000001307 13345106031 0013531 0 ustar 00root root 0000000 0000000
The change you wanted was rejected (422)
The change you wanted was rejected.
Maybe you tried to change something you didn't have access to.
public/500.html 0000775 0000000 0000000 00000001203 13345106031 0013521 0 ustar 00root root 0000000 0000000
We're sorry, but something went wrong (500)