rails 3.2.2 added tweeter bootstrap

This commit is contained in:
Wojciech Todryk 2012-03-10 18:08:39 +01:00
parent 74c23fa0d1
commit cacd9575d0
58 changed files with 4112 additions and 357 deletions

5
AUTHORS Executable file
View file

@ -0,0 +1,5 @@
* Luben Manolov <luben.manolov@gmail.com>
* Nick Penkov <nick.penkov@gmail.com>
* Eugene Korbut
* Emilio Blanco
* Wojciech Todryk <wojciech(at)todryk(dot).pl>

19
CHANGES.markdown Executable file
View file

@ -0,0 +1,19 @@
## Changes
#### 0.8.6
* new calendar view
#### 0.8.5
* servers view
* identity modification
#### 0.8.4
* calendar view as separate gem
* adding bluecloth for rendering markdown text
#### 0.8.3
* export, imports of contact

View file

@ -37,3 +37,10 @@ gem 'jquery-rails'
# To use debugger
# gem 'ruby-debug'
gem 'will_paginate'
gem "ezcrypto"
gem "calendar_view"
gem 'bluecloth'
gem 'sass'
gem 'haml'

View file

@ -29,7 +29,10 @@ GEM
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
bluecloth (2.2.0)
builder (3.0.0)
calendar_view (0.0.6)
rails (>= 3.0.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
@ -40,6 +43,8 @@ GEM
erubis (2.7.0)
execjs (1.3.0)
multi_json (~> 1.0)
ezcrypto (0.7.2)
haml (3.1.4)
hike (1.2.1)
i18n (0.6.0)
journey (1.0.3)
@ -98,15 +103,22 @@ GEM
uglifier (1.2.3)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
will_paginate (3.0.3)
PLATFORMS
ruby
DEPENDENCIES
bluecloth
calendar_view
coffee-rails (~> 3.2.1)
ezcrypto
haml
jquery-rails
json
mysql2
rails (= 3.2.2)
sass
sass-rails (~> 3.2.3)
uglifier (>= 1.0.3)
will_paginate

20
TODO.markdown Executable file
View file

@ -0,0 +1,20 @@
## Todo
app/controllers/folders_controller.rb:
* [ 30] [TODO] recreate local copy of folders
* [ 99] [TODO] save system folders
app/controllers/messages_controller.rb:
* [101] [FIXME] missing fields and support arrays
app/controllers/messages_ops_controller.rb:
* [249] [FIXME] edit does not support attachments
* [304] [TODO] check if email address is valid if not get address from contacts
app/models/prefs.rb:
* [ 19] [TODO] move refresh to prefs and make refresh page with messages

24
UNLICENSE.markdown Executable file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
app/assets/images/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

632
app/assets/stylesheets/bootstrap.min.css vendored Normal file
View file

@ -0,0 +1,632 @@
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
audio:not([controls]){display:none;}
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
a:hover,a:active{outline:0;}
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
button,input{*overflow:visible;line-height:normal;}
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
textarea{overflow:auto;vertical-align:top;}
.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
.clearfix:after{clear:both;}
body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
a{color:#0088cc;text-decoration:none;}
a:hover{color:#005580;text-decoration:underline;}
.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}
.row:after{clear:both;}
[class*="span"]{float:left;margin-left:20px;}
.span1{width:60px;}
.span2{width:140px;}
.span3{width:220px;}
.span4{width:300px;}
.span5{width:380px;}
.span6{width:460px;}
.span7{width:540px;}
.span8{width:620px;}
.span9{width:700px;}
.span10{width:780px;}
.span11{width:860px;}
.span12,.container{width:940px;}
.offset1{margin-left:100px;}
.offset2{margin-left:180px;}
.offset3{margin-left:260px;}
.offset4{margin-left:340px;}
.offset5{margin-left:420px;}
.offset6{margin-left:500px;}
.offset7{margin-left:580px;}
.offset8{margin-left:660px;}
.offset9{margin-left:740px;}
.offset10{margin-left:820px;}
.offset11{margin-left:900px;}
.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";}
.row-fluid:after{clear:both;}
.row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;}
.row-fluid>[class*="span"]:first-child{margin-left:0;}
.row-fluid>.span1{width:6.382978723%;}
.row-fluid>.span2{width:14.89361702%;}
.row-fluid>.span3{width:23.404255317%;}
.row-fluid>.span4{width:31.914893614%;}
.row-fluid>.span5{width:40.425531911%;}
.row-fluid>.span6{width:48.93617020799999%;}
.row-fluid>.span7{width:57.446808505%;}
.row-fluid>.span8{width:65.95744680199999%;}
.row-fluid>.span9{width:74.468085099%;}
.row-fluid>.span10{width:82.97872339599999%;}
.row-fluid>.span11{width:91.489361693%;}
.row-fluid>.span12{width:99.99999998999999%;}
.container{width:940px;margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";}
.container:after{clear:both;}
.container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";}
.container-fluid:after{clear:both;}
p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;}
.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
h1,h2,h3,h4,h5,h6{margin:0;font-weight:bold;color:#333333;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;}
h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}
h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}
h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;}
h4,h5,h6{line-height:18px;}
h4{font-size:14px;}h4 small{font-size:12px;}
h5{font-size:12px;}
h6{font-size:11px;color:#999999;text-transform:uppercase;}
.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
.page-header h1{line-height:1;}
ul,ol{padding:0;margin:0 0 9px 25px;}
ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
ul{list-style:disc;}
ol{list-style:decimal;}
li{line-height:18px;}
ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
dl{margin-bottom:18px;}
dt,dd{line-height:18px;}
dt{font-weight:bold;}
dd{margin-left:9px;}
hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
strong{font-weight:bold;}
em{font-style:italic;}
.muted{color:#999999;}
abbr{font-size:90%;text-transform:uppercase;border-bottom:1px dotted #ddd;cursor:help;}
blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
q:before,q:after,blockquote:before,blockquote:after{content:"";}
address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
small{font-size:100%;}
cite{font-style:normal;}
code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
code{padding:3px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;}pre.prettyprint{margin-bottom:18px;}
pre code{padding:0;color:inherit;background-color:transparent;border:0;}
.pre-scrollable{max-height:340px;overflow-y:scroll;}
form{margin:0 0 18px;}
fieldset{padding:0;margin:0;border:0;}
legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}legend small{font-size:13.5px;color:#999999;}
label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;}
input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
label{display:block;margin-bottom:5px;color:#333333;}
input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.uneditable-textarea{width:auto;height:auto;}
label input,label textarea,label select{display:block;}
input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0 \9;}
input[type="image"]{border:0;}
input[type="file"]{width:auto;padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;}
select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;}
input[type="file"]{line-height:18px \9;}
select{width:220px;background-color:#ffffff;}
select[multiple],select[size]{height:auto;}
input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
textarea{height:auto;}
input[type="hidden"]{display:none;}
.radio,.checkbox{padding-left:18px;}
.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}
input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);outline:0;outline:thin dotted \9;}
input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.input-mini{width:60px;}
.input-small{width:90px;}
.input-medium{width:150px;}
.input-large{width:210px;}
.input-xlarge{width:270px;}
.input-xxlarge{width:530px;}
input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{float:none;margin-left:0;}
input.span1,textarea.span1,.uneditable-input.span1{width:50px;}
input.span2,textarea.span2,.uneditable-input.span2{width:130px;}
input.span3,textarea.span3,.uneditable-input.span3{width:210px;}
input.span4,textarea.span4,.uneditable-input.span4{width:290px;}
input.span5,textarea.span5,.uneditable-input.span5{width:370px;}
input.span6,textarea.span6,.uneditable-input.span6{width:450px;}
input.span7,textarea.span7,.uneditable-input.span7{width:530px;}
input.span8,textarea.span8,.uneditable-input.span8{width:610px;}
input.span9,textarea.span9,.uneditable-input.span9{width:690px;}
input.span10,textarea.span10,.uneditable-input.span10{width:770px;}
input.span11,textarea.span11,.uneditable-input.span11{width:850px;}
input.span12,textarea.span12,.uneditable-input.span12{width:930px;}
input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;}
.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;}
.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;}
.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #ddd;}
.uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
:-moz-placeholder{color:#999999;}
::-webkit-input-placeholder{color:#999999;}
.help-block{display:block;margin-top:5px;margin-bottom:0;color:#999999;}
.help-inline{display:inline-block;*display:inline;*zoom:1;margin-bottom:9px;vertical-align:middle;padding-left:5px;}
.input-prepend,.input-append{margin-bottom:5px;*zoom:1;}.input-prepend:before,.input-append:before,.input-prepend:after,.input-append:after{display:table;content:"";}
.input-prepend:after,.input-append:after{clear:both;}
.input-prepend input,.input-append input,.input-prepend .uneditable-input,.input-append .uneditable-input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{position:relative;z-index:2;}
.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;}
.input-prepend .add-on,.input-append .add-on{float:left;display:block;width:auto;min-width:16px;height:18px;margin-right:-1px;padding:4px 5px;font-weight:normal;line-height:18px;color:#999999;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#f5f5f5;border:1px solid #ccc;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;}
.input-prepend .add-on{*margin-top:1px;}
.input-append input,.input-append .uneditable-input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-append .uneditable-input{border-left-color:#eee;border-right-color:#ccc;}
.input-append .add-on{margin-right:0;margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.input-append input:first-child{*margin-left:-160px;}.input-append input:first-child+.add-on{*margin-left:-21px;}
.search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;}
.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input{display:inline-block;margin-bottom:0;}
.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
.form-search label,.form-inline label,.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{display:inline-block;}
.form-search .input-append .add-on,.form-inline .input-prepend .add-on,.form-search .input-append .add-on,.form-inline .input-prepend .add-on{vertical-align:middle;}
.form-search .radio,.form-inline .radio,.form-search .checkbox,.form-inline .checkbox{margin-bottom:0;vertical-align:middle;}
.control-group{margin-bottom:9px;}
legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;}
.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";}
.form-horizontal .control-group:after{clear:both;}
.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;}
.form-horizontal .controls{margin-left:160px;}
.form-horizontal .form-actions{padding-left:160px;}
table{max-width:100%;border-collapse:collapse;border-spacing:0;}
.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #ddd;}
.table th{font-weight:bold;}
.table thead th{vertical-align:bottom;}
.table thead:first-child tr th,.table thead:first-child tr td{border-top:0;}
.table tbody+tbody{border-top:2px solid #ddd;}
.table-condensed th,.table-condensed td{padding:4px 5px;}
.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th+th,.table-bordered td+td,.table-bordered th+td,.table-bordered td+th{border-left:1px solid #ddd;}
.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}
table .span1{float:none;width:44px;margin-left:0;}
table .span2{float:none;width:124px;margin-left:0;}
table .span3{float:none;width:204px;margin-left:0;}
table .span4{float:none;width:284px;margin-left:0;}
table .span5{float:none;width:364px;margin-left:0;}
table .span6{float:none;width:444px;margin-left:0;}
table .span7{float:none;width:524px;margin-left:0;}
table .span8{float:none;width:604px;margin-left:0;}
table .span9{float:none;width:684px;margin-left:0;}
table .span10{float:none;width:764px;margin-left:0;}
table .span11{float:none;width:844px;margin-left:0;}
table .span12{float:none;width:924px;margin-left:0;}
[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}
.icon-white{background-image:url("../img/glyphicons-halflings-white.png");}
.icon-glass{background-position:0 0;}
.icon-music{background-position:-24px 0;}
.icon-search{background-position:-48px 0;}
.icon-envelope{background-position:-72px 0;}
.icon-heart{background-position:-96px 0;}
.icon-star{background-position:-120px 0;}
.icon-star-empty{background-position:-144px 0;}
.icon-user{background-position:-168px 0;}
.icon-film{background-position:-192px 0;}
.icon-th-large{background-position:-216px 0;}
.icon-th{background-position:-240px 0;}
.icon-th-list{background-position:-264px 0;}
.icon-ok{background-position:-288px 0;}
.icon-remove{background-position:-312px 0;}
.icon-zoom-in{background-position:-336px 0;}
.icon-zoom-out{background-position:-360px 0;}
.icon-off{background-position:-384px 0;}
.icon-signal{background-position:-408px 0;}
.icon-cog{background-position:-432px 0;}
.icon-trash{background-position:-456px 0;}
.icon-home{background-position:0 -24px;}
.icon-file{background-position:-24px -24px;}
.icon-time{background-position:-48px -24px;}
.icon-road{background-position:-72px -24px;}
.icon-download-alt{background-position:-96px -24px;}
.icon-download{background-position:-120px -24px;}
.icon-upload{background-position:-144px -24px;}
.icon-inbox{background-position:-168px -24px;}
.icon-play-circle{background-position:-192px -24px;}
.icon-repeat{background-position:-216px -24px;}
.icon-refresh{background-position:-240px -24px;}
.icon-list-alt{background-position:-264px -24px;}
.icon-lock{background-position:-287px -24px;}
.icon-flag{background-position:-312px -24px;}
.icon-headphones{background-position:-336px -24px;}
.icon-volume-off{background-position:-360px -24px;}
.icon-volume-down{background-position:-384px -24px;}
.icon-volume-up{background-position:-408px -24px;}
.icon-qrcode{background-position:-432px -24px;}
.icon-barcode{background-position:-456px -24px;}
.icon-tag{background-position:0 -48px;}
.icon-tags{background-position:-25px -48px;}
.icon-book{background-position:-48px -48px;}
.icon-bookmark{background-position:-72px -48px;}
.icon-print{background-position:-96px -48px;}
.icon-camera{background-position:-120px -48px;}
.icon-font{background-position:-144px -48px;}
.icon-bold{background-position:-167px -48px;}
.icon-italic{background-position:-192px -48px;}
.icon-text-height{background-position:-216px -48px;}
.icon-text-width{background-position:-240px -48px;}
.icon-align-left{background-position:-264px -48px;}
.icon-align-center{background-position:-288px -48px;}
.icon-align-right{background-position:-312px -48px;}
.icon-align-justify{background-position:-336px -48px;}
.icon-list{background-position:-360px -48px;}
.icon-indent-left{background-position:-384px -48px;}
.icon-indent-right{background-position:-408px -48px;}
.icon-facetime-video{background-position:-432px -48px;}
.icon-picture{background-position:-456px -48px;}
.icon-pencil{background-position:0 -72px;}
.icon-map-marker{background-position:-24px -72px;}
.icon-adjust{background-position:-48px -72px;}
.icon-tint{background-position:-72px -72px;}
.icon-edit{background-position:-96px -72px;}
.icon-share{background-position:-120px -72px;}
.icon-check{background-position:-144px -72px;}
.icon-move{background-position:-168px -72px;}
.icon-step-backward{background-position:-192px -72px;}
.icon-fast-backward{background-position:-216px -72px;}
.icon-backward{background-position:-240px -72px;}
.icon-play{background-position:-264px -72px;}
.icon-pause{background-position:-288px -72px;}
.icon-stop{background-position:-312px -72px;}
.icon-forward{background-position:-336px -72px;}
.icon-fast-forward{background-position:-360px -72px;}
.icon-step-forward{background-position:-384px -72px;}
.icon-eject{background-position:-408px -72px;}
.icon-chevron-left{background-position:-432px -72px;}
.icon-chevron-right{background-position:-456px -72px;}
.icon-plus-sign{background-position:0 -96px;}
.icon-minus-sign{background-position:-24px -96px;}
.icon-remove-sign{background-position:-48px -96px;}
.icon-ok-sign{background-position:-72px -96px;}
.icon-question-sign{background-position:-96px -96px;}
.icon-info-sign{background-position:-120px -96px;}
.icon-screenshot{background-position:-144px -96px;}
.icon-remove-circle{background-position:-168px -96px;}
.icon-ok-circle{background-position:-192px -96px;}
.icon-ban-circle{background-position:-216px -96px;}
.icon-arrow-left{background-position:-240px -96px;}
.icon-arrow-right{background-position:-264px -96px;}
.icon-arrow-up{background-position:-289px -96px;}
.icon-arrow-down{background-position:-312px -96px;}
.icon-share-alt{background-position:-336px -96px;}
.icon-resize-full{background-position:-360px -96px;}
.icon-resize-small{background-position:-384px -96px;}
.icon-plus{background-position:-408px -96px;}
.icon-minus{background-position:-433px -96px;}
.icon-asterisk{background-position:-456px -96px;}
.icon-exclamation-sign{background-position:0 -120px;}
.icon-gift{background-position:-24px -120px;}
.icon-leaf{background-position:-48px -120px;}
.icon-fire{background-position:-72px -120px;}
.icon-eye-open{background-position:-96px -120px;}
.icon-eye-close{background-position:-120px -120px;}
.icon-warning-sign{background-position:-144px -120px;}
.icon-plane{background-position:-168px -120px;}
.icon-calendar{background-position:-192px -120px;}
.icon-random{background-position:-216px -120px;}
.icon-comment{background-position:-240px -120px;}
.icon-magnet{background-position:-264px -120px;}
.icon-chevron-up{background-position:-288px -120px;}
.icon-chevron-down{background-position:-313px -119px;}
.icon-retweet{background-position:-336px -120px;}
.icon-shopping-cart{background-position:-360px -120px;}
.icon-folder-close{background-position:-384px -120px;}
.icon-folder-open{background-position:-408px -120px;}
.icon-resize-vertical{background-position:-432px -119px;}
.icon-resize-horizontal{background-position:-456px -118px;}
.dropdown{position:relative;}
.dropdown-toggle{*margin-bottom:-3px;}
.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
.caret{display:inline-block;width:0;height:0;text-indent:-99999px;*text-indent:0;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;opacity:0.3;filter:alpha(opacity=30);content:"\2193";}
.dropdown .caret{margin-top:8px;margin-left:2px;}
.dropdown:hover .caret,.open.dropdown .caret{opacity:1;filter:alpha(opacity=100);}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;_width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.bottom-up{top:auto;bottom:100%;margin-bottom:2px;}
.dropdown-menu .divider{height:1px;margin:5px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#555555;white-space:nowrap;}
.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;}
.dropdown.open{*z-index:1000;}.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
.dropdown.open .dropdown-menu{display:block;}
.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
.collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
.btn{display:inline-block;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);cursor:pointer;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);*margin-left:.3em;}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;}
.btn:active,.btn.active{background-color:#cccccc \9;}
.btn:first-child{*margin-left:0;}
.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.btn.active,.btn:active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);background-color:#e6e6e6;background-color:#d9d9d9 \9;outline:0;}
.btn.disabled,.btn[disabled]{cursor:default;background-image:none;background-color:#e6e6e6;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-large [class^="icon-"]{margin-top:1px;}
.btn-small{padding:5px 9px;font-size:11px;line-height:16px;}
.btn-small [class^="icon-"]{margin-top:-1px;}
.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;}
.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;}
.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-dark.active{color:rgba(255, 255, 255, 0.75);}
.btn-primary{background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-ms-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(top, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0044cc;}
.btn-primary:active,.btn-primary.active{background-color:#003399 \9;}
.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;}
.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;}
.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;}
.btn-success:active,.btn-success.active{background-color:#408140 \9;}
.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;}
.btn-info:active,.btn-info.active{background-color:#24748c \9;}
.btn-inverse{background-color:#393939;background-image:-moz-linear-gradient(top, #454545, #262626);background-image:-ms-linear-gradient(top, #454545, #262626);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#454545), to(#262626));background-image:-webkit-linear-gradient(top, #454545, #262626);background-image:-o-linear-gradient(top, #454545, #262626);background-image:linear-gradient(top, #454545, #262626);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#454545', endColorstr='#262626', GradientType=0);border-color:#262626 #262626 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#262626;}
.btn-inverse:active,.btn-inverse.active{background-color:#0c0c0c \9;}
button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
button.btn.large,input[type="submit"].btn.large{*padding-top:7px;*padding-bottom:7px;}
button.btn.small,input[type="submit"].btn.small{*padding-top:3px;*padding-bottom:3px;}
.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";}
.btn-group:after{clear:both;}
.btn-group:first-child{*margin-left:0;}
.btn-group+.btn-group{margin-left:5px;}
.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
.btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
.btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
.btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
.btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
.btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active,.btn-group .btn.active{z-index:2;}
.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
.btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:5px;*padding-bottom:5px;}
.btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);}
.btn .caret{margin-top:7px;margin-left:0;}
.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);}
.btn-primary .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
.btn-small .caret{margin-top:4px;}
.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.alert,.alert-heading{color:#c09853;}
.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
.alert-success{background-color:#dff0d8;border-color:#d6e9c6;}
.alert-success,.alert-success .alert-heading{color:#468847;}
.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;}
.alert-danger,.alert-error,.alert-danger .alert-heading,.alert-error .alert-heading{color:#b94a48;}
.alert-info{background-color:#d9edf7;border-color:#bce8f1;}
.alert-info,.alert-info .alert-heading{color:#3a87ad;}
.alert-block{padding-top:14px;padding-bottom:14px;}
.alert-block>p,.alert-block>ul{margin-bottom:0;}
.alert-block p+p{margin-top:5px;}
.nav{margin-left:0;margin-bottom:18px;list-style:none;}
.nav>li>a{display:block;}
.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
.nav li+.nav-header{margin-top:9px;}
.nav-list{padding-left:14px;padding-right:14px;margin-bottom:0;}
.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
.nav-list>li>a{padding:3px 15px;}
.nav-list .active>a,.nav-list .active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
.nav-list [class^="icon-"]{margin-right:2px;}
.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";}
.nav-tabs:after,.nav-pills:after{clear:both;}
.nav-tabs>li,.nav-pills>li{float:left;}
.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
.nav-tabs{border-bottom:1px solid #ddd;}
.nav-tabs>li{margin-bottom:-1px;}
.nav-tabs>li>a{padding-top:9px;padding-bottom:9px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.nav-pills .active>a,.nav-pills .active>a:hover{color:#ffffff;background-color:#0088cc;}
.nav-stacked>li{float:none;}
.nav-stacked>li>a{margin-right:0;}
.nav-tabs.nav-stacked{border-bottom:0;}
.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
.nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;margin-top:6px;}
.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;}
.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;}
.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
.tabs-stacked .open>a:hover{border-color:#999999;}
.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";}
.tabbable:after{clear:both;}
.tab-content{overflow:hidden;}
.tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;}
.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
.tab-content>.active,.pill-content>.active{display:block;}
.tabs-below .nav-tabs{border-top:1px solid #ddd;}
.tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
.tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
.tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
.tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;}
.tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
.tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
.tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
.tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
.tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
.tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
.tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
.tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
.tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
.navbar{overflow:visible;margin-bottom:18px;}
.navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
.btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#222222;}
.btn-navbar:active,.btn-navbar.active{background-color:#080808 \9;}
.btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
.nav-collapse.collapse{height:auto;}
.navbar .brand:hover{text-decoration:none;}
.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
.navbar .navbar-text{margin-bottom:0;line-height:40px;color:#999999;}.navbar .navbar-text a:hover{color:#ffffff;background-color:transparent;}
.navbar .btn,.navbar .btn-group{margin-top:5px;}
.navbar .btn-group .btn{margin-top:0;}
.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";}
.navbar-form:after{clear:both;}
.navbar-form input,.navbar-form select{display:inline-block;margin-top:5px;margin-bottom:0;}
.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;color:rgba(255, 255, 255, 0.75);background:#666;background:rgba(255, 255, 255, 0.3);border:1px solid #111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query :-moz-placeholder{color:#eeeeee;}
.navbar-search .search-query::-webkit-input-placeholder{color:#eeeeee;}
.navbar-search .search-query:hover{color:#ffffff;background-color:#999999;background-color:rgba(255, 255, 255, 0.5);}
.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
.navbar-fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030;}
.navbar-fixed-top .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
.navbar .nav.pull-right{float:right;}
.navbar .nav>li{display:block;float:left;}
.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;}
.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;}
.navbar .nav.pull-right{margin-left:10px;margin-right:0;}
.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;}
.navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
.navbar .nav.pull-right .dropdown-menu{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before{left:auto;right:12px;}
.navbar .nav.pull-right .dropdown-menu:after{left:auto;right:13px;}
.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;text-shadow:0 1px 0 #ffffff;}
.breadcrumb .divider{padding:0 5px;color:#999999;}
.breadcrumb .active a{color:#333333;}
.pagination{height:36px;margin:18px 0;}
.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
.pagination li{display:inline;}
.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;}
.pagination a:hover,.pagination .active a{background-color:#f5f5f5;}
.pagination .active a{color:#999999;cursor:default;}
.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;}
.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.pagination-centered{text-align:center;}
.pagination-right{text-align:right;}
.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";}
.pager:after{clear:both;}
.pager li{display:inline;}
.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
.pager .next a{float:right;}
.pager .previous a{float:left;}
.modal-open .dropdown-menu{z-index:2050;}
.modal-open .dropdown.open{*z-index:2050;}
.modal-open .popover{z-index:2060;}
.modal-open .tooltip{z-index:2070;}
.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
.modal{position:fixed;top:50%;left:50%;z-index:1050;max-height:500px;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
.modal.fade.in{top:50%;}
.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
.modal-body{padding:15px;}
.modal-body .modal-form{margin-bottom:0;}
.modal-footer{padding:14px 15px 15px;margin-bottom:0;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";}
.modal-footer:after{clear:both;}
.modal-footer .btn{float:right;margin-left:5px;margin-bottom:0;}
.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
.tooltip.top{margin-top:-2px;}
.tooltip.right{margin-left:2px;}
.tooltip.bottom{margin-top:2px;}
.tooltip.left{margin-left:-2px;}
.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.tooltip-arrow{position:absolute;width:0;height:0;}
.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}
.popover.right{margin-left:5px;}
.popover.bottom{margin-top:5px;}
.popover.left{margin-left:-5px;}
.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.popover .arrow{position:absolute;width:0;height:0;}
.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}
.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";}
.thumbnails:after{clear:both;}
.thumbnails>li{float:left;margin:0 0 18px 20px;}
.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}
a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
.thumbnail .caption{padding:9px;}
.label{padding:2px 4px 3px;font-size:11.049999999999999px;font-weight:bold;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.label:hover{color:#ffffff;text-decoration:none;}
.label-important{background-color:#b94a48;}
.label-important:hover{background-color:#953b39;}
.label-warning{background-color:#f89406;}
.label-warning:hover{background-color:#c67605;}
.label-success{background-color:#468847;}
.label-success:hover{background-color:#356635;}
.label-info{background-color:#3a87ad;}
.label-info:hover{background-color:#2d6987;}
@-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);}
.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);}
.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);}
.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.accordion{margin-bottom:18px;}
.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.accordion-heading{border-bottom:0;}
.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
.carousel{position:relative;margin-bottom:18px;line-height:1;}
.carousel-inner{overflow:hidden;width:100%;position:relative;}
.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
.carousel .item>img{display:block;line-height:1;}
.carousel .active,.carousel .next,.carousel .prev{display:block;}
.carousel .active{left:0;}
.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
.carousel .next{left:100%;}
.carousel .prev{left:-100%;}
.carousel .next.left,.carousel .prev.right{left:0;}
.carousel .active.left{left:-100%;}
.carousel .active.right{left:100%;}
.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);}
.carousel-caption h4,.carousel-caption p{color:#ffffff;}
.hero-unit{padding:60px;margin-bottom:30px;background-color:#f5f5f5;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
.pull-right{float:right;}
.pull-left{float:left;}
.hide{display:none;}
.show{display:block;}
.invisible{visibility:hidden;}

View file

@ -0,0 +1,7 @@
body.simple
margin-top: 20px
margin-bottom: 20px
#footer-simple
text-align: center
.top-pix18
margin-top: 18px

96
app/controllers/application_controller.rb Normal file → Executable file
View file

@ -1,3 +1,99 @@
require 'yaml'
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :load_settings,:current_user,:set_locale
#before_filter :plugins_configuration
def load_settings
$defaults ||= YAML::load(File.open(Rails.root.join('config','settings.yml')))
end
################################# protected section ###########################################
protected
def theme_resolver
if @current_user.nil?
$defaults['theme']
else
@current_user.prefs.theme || $defaults['theme']
end
end
def set_locale
if @current_user.nil?
I18n.locale = $defaults['locale'] || I18n.default_locale
else
I18n.locale = @current_user.prefs.locale.to_sym || I18n.default_locale
end
end
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def check_current_user
if @current_user.nil?
session["return_to"] = request.fullpath
redirect_to :controller => 'user', :action => 'login'
return false
end
end
def selected_folder
if session[:selected_folder]
@selected_folder = session[:selected_folder]
else
folder = @current_user.folders.inbox.first
if not folder.nil?
@selected_folder = folder.full_name
end
end
end
def get_current_folders
@folders_shown = @current_user.folders.shown.order("name asc")
if not @selected_folder.nil?
@current_folder = @current_user.folders.find_by_full_name(@selected_folder)
end
end
def prepare_compose_buttons
@buttons = []
@buttons << {:text => 'sendout',:scope=>:compose,:image => 'email.png'}
@buttons << {:text => 'save',:scope=>:compose,:image => 'save.png'}
end
def create_message_with_params
@message = Message.new(params[:message])
# if params[:message]
# @message.update_attributes(params[:message])
# end
files = Dir.glob(File.join($defaults["msg_upload_dir"],@current_user.username + "*"))
@attachments = []
files.each do |f|
@attachments << {:name => File.basename(f).gsub!(/#{@current_user.username}_/,"") , :size => File.stat(f).size }
end
end
def get_system_folders
@drafts_folder = @current_user.folders.drafts.first
@sent_folder = @current_user.folders.sent.first
@inbox_folder = @current_user.folders.inbox.first
@trash_folder = @current_user.folders.trash.first
end
##################################### private section ##########################################
private
def plugins_configuration
WillPaginate::ViewHelpers.pagination_options[:previous_label] = t(:previous_page,:scope=>:common)
WillPaginate::ViewHelpers.pagination_options[:next_label] = t(:next_page,:scope=>:common)
end
end

View file

@ -0,0 +1,135 @@
require 'tempfile'
class ContactsController < ApplicationController
before_filter :check_current_user,:selected_folder, :get_current_folders
before_filter :get_contacts, :only => [:index]
before_filter :prepare_ops_buttons, :prepare_export_import_buttons,:only => [:index]
#theme :theme_resolver
def index
end
def ops
if params["create_new"]
redirect_to(new_contact_path)
return
end
if !params["cids"]
flash[:warning] = t(:no_selected,:scope=>:contact)
else
if params["delete_selected"]
params["cids"].each do |id|
@current_user.contacts.find_by_id(id).destroy
end
elsif params["compose_to_selected"]
redirect_to :controller=>'messages',:action=>'compose',:cids=>params["cids"]
return
end
end
redirect_to(contacts_path)
end
#problem http://binary10ve.blogspot.com/2011/05/migrating-to-rails-3-got-stuck-with.html
#def destroy
# @current_user.contacts.find(params[:id]).destroy
# redirect_to(contacts_path)
#end
def new
@contact = Contact.new
end
def edit
@contact = @current_user.contacts.find(params[:id])
render 'edit'
end
def create
@contact = @current_user.contacts.build(params[:contact])
if @contact.valid?
@contact.save
flash[:success] = t(:was_created,:scope=>:contact)
redirect_to(contacts_path)
else
render 'new'
end
end
def update
@contact = @current_user.contacts.find(params[:id])
if @contact.update_attributes(params[:contact])
redirect_to(contacts_path)
else
render 'edit'
end
end
def external
if params["export"]
redirect_to :action => 'export'
return
elsif params["import"]
begin
raise t(:no_file_chosen,:scope=>:common) if not params[:upload]
raise t(:no_tmp_dir,:scope=>:common) if not File.exists?($defaults["msg_upload_dir"])
tmp_file = Tempfile.new($defaults["contact_tmp_filename"],$defaults["msg_upload_dir"])
tmp_file.write(params[:upload][:datafile].read)
tmp_file.flush
tmp_file.rewind
tmp_file.readlines.each do |line|
next if line =~ /^#/
Contact.import(@current_user,line)
end
rescue ActiveRecord::RecordInvalid => e
flash[:error] = {:title => e.to_s,:info => e.record.inspect + e.record.errors.inspect}
rescue Exception => e
flash[:error] = e.to_s
else
flash[:success] = t(:were_imported,:scope=>:contact)
end
end
redirect_to :action => 'index'
end
def export
contacts = @current_user.contacts
s = ""
contacts.each do |c|
s += c.export + "\r\n"
end
headers['Content-type'] = "text/csv"
headers['Content-Disposition'] = %(attachment; filename="contacts.csv")
render :text => s
end
####################################### protected section ################################
protected
def prepare_ops_buttons
@buttons = []
@buttons << {:text => 'compose_to_selected',:scope=> 'contact', :image => 'email.png'}
@buttons << {:text => 'create_new',:scope=> 'contact', :image => 'plus.png'}
@buttons << {:text => 'delete_selected',:scope=>'contact',:image => 'minus.png'}
end
def prepare_export_import_buttons
@ei_buttons = []
@ei_buttons << {:text => 'import',:scope=>'contact',:image => 'right.png'}
@ei_buttons << {:text => 'export',:scope=>'contact',:image => 'left.png'}
end
####################################### private section ##################################
private
def get_contacts
@contacts = Contact.getPageForUser(@current_user,params[:page],params[:sort_field],params[:sort_dir])
end
end

View file

@ -0,0 +1,172 @@
require 'imap_mailbox'
require 'imap_session'
class FoldersController < ApplicationController
include ImapMailboxModule
include ImapSessionModule
before_filter :check_current_user,:selected_folder, :get_current_folders
before_filter :open_imap_session, :except => [:index,:show_hide,:system]
after_filter :close_imap_session, :except => [:index,:show_hide,:system]
before_filter :get_folders
before_filter :prepare_buttons_to_folders
#theme :theme_resolver
def index
#before_filter
end
def create
if params[:folder][:target].empty?
flash[:warning] = t(:to_create_empty,:scope=>:folder)
render "index"
else
begin
#TODO recreate local copy of folders
if params[:folder][:parent].empty?
@mailbox.create_folder(params[:folder][:target])
else
parent_folder = @current_user.folders.find(params[:folder][:parent])
if parent_folder.depth >= $defaults["mailbox_max_parent_folder_depth"].to_i
raise Exception, t(:max_depth,:scope=>:folder)
end
@mailbox.create_folder(parent_folder.full_name + parent_folder.delim + params[:folder][:target])
end
rescue Exception => e
flash[:error] = t(:can_not_create,:scope=>:folder) + ' (' + e.to_s + ')'
render 'index'
return
end
flash[:success] = t(:was_created,:scope=>:folder)
redirect_to :action => 'index'
end
end
def delete
if params[:folder][:delete].empty?
flash[:warning] = t(:to_delete_empty,:scope=>:folder)
render "index"
else
begin
folder = @current_user.folders.find(params[:folder][:delete])
if @folders_system.include?(folder)
raise Exception, t(:system,:scope=>:folder)
end
@mailbox.delete_folder(folder.full_name)
if @current_folder.eql? folder
session[:selected_folder] = nil
end
folder.destroy
rescue Exception => e
flash[:error] = t(:can_not_delete,:scope=>:folder) + ' (' + e.to_s + ')'
render 'index'
return
end
flash[:success] = t(:was_deleted,:scope=>:folder)
redirect_to :action => 'index'
end
end
def system
logger.custom('sss',params[:folder].inspect)
@folders.each do |f|
logger.custom('s',f.inspect)
if f.isSystem?
f.setNone
end
if f.id == params[:folder][:mailbox_inbox].to_i
f.setInbox
end
if f.id == params[:folder][:mailbox_sent].to_i
f.setSent
end
if f.id == params[:folder][:mailbox_trash].to_i
f.setTrash
end
if f.id == params[:folder][:mailbox_drafts].to_i
f.setDrafts
end
end
redirect_to :action => 'index'
end
def refresh
# TODO save system folders
if params[:refresh]
Folder.refresh(@mailbox,@current_user)
flash.keep
elsif params[:show_hide]
if !params["folders_to_show"].nil?
@folders.each do |f|
if params["folders_to_show"].include?(f.id.to_s)
f.shown = true
f.save
else
f.shown = false
f.save
end
end
end
end
redirect_to :action => 'index'
end
def select
session[:selected_folder] = params[:id]
redirect_to :controller => 'messages', :action => 'index'
end
def refresh_status
@folders_shown.each do |f|
@mailbox.set_folder(f.full_name)
folder_status = @mailbox.status
f.update_attributes(:total => folder_status['MESSAGES'], :unseen => folder_status['UNSEEN'])
end
redirect_to :controller=> 'messages', :action => 'index'
end
def emptybin
begin
trash_folder = @current_user.folders.trash.first
if trash_folder.nil?
raise Exception, t(:not_configured_trash,:scope=>:folder)
end
@mailbox.set_folder(trash_folder.full_name)
trash_folder.messages.each do |m|
@mailbox.delete_message(m.uid)
end
@mailbox.expunge
trash_folder.messages.destroy_all
trash_folder.update_attributes(:unseen => 0, :total => 0)
rescue Exception => e
flash[:error] = "#{t(:imap_error,:scope=>:common)} (#{e.to_s})"
end
redirect_to :controller => 'messages', :action => 'index'
end
############################################# protected section #######################################
protected
def prepare_buttons_to_folders
@buttons = []
@buttons << {:text => 'show_hide',:scope=>'folder',:image => 'flag.png'}
@buttons << {:text => 'refresh',:scope=>'folder',:image => 'refresh.png'}
end
def get_folders
@folders = @current_user.folders
@folders_shown = @current_user.folders.shown
#@folders_system = @current_user.folders.sys
@current_user.folders.inbox.first.nil? ? @folder_inbox = "" : @folder_inbox = @current_user.folders.inbox.first.id
@current_user.folders.drafts.first.nil? ? @folder_drafts = "" : @folder_drafts = @current_user.folders.drafts.first.id
@current_user.folders.sent.first.nil? ? @folder_sent = "" : @folder_sent = @current_user.folders.sent.first.id
@current_user.folders.trash.first.nil? ? @folder_trash = "" : @folder_trash = @current_user.folders.trash.first.id
end
end

View file

@ -0,0 +1,53 @@
class InternalController < ApplicationController
before_filter :check_current_user ,:selected_folder, :get_current_folders, :only => [:about]
#theme :theme_resolver
layout "simple"
ERRORS = [
:internal_server_error,
:not_found,
:unprocessable_entity
].freeze
ERRORS.each do |e|
define_method e do
@title = t(e,:scope=>:internal)
@error = t(e,:scope=>:internal)
render 'error'
end
end
def error
@title = t(:unspecified_error,:scope=>:internal)
@error = params[:error] || t(:unspecified_error,:scope=>:internal)
end
def imaperror
@title = t(:imap_error,:scope => :internal)
@error = params[:error] || t(:unspecified_error, :scope => :internal)
logger.error "!!! InternalControllerImapError: " + @error
render 'error'
end
def loginfailure
reset_session
flash[:error] = t(:login_failure,:scope=>:user)
@current_user = nil
redirect_to :controller=>'user', :action => 'login'
end
def onlycanlogins
reset_session
flash[:error] = t(:only_can_logins,:scope=>:user)
@current_user = nil
redirect_to :controller=>'user', :action => 'login'
end
def about
render 'internal/about', :layout => 'application'
end
end

View file

@ -0,0 +1,133 @@
require 'tempfile'
class LinksController < ApplicationController
before_filter :check_current_user,:selected_folder, :get_current_folders
before_filter :get_links, :only => [:index]
before_filter :prepare_ops_buttons, :only => [:index]
#, :prepare_export_import_buttons,:only => [:index]
#theme :theme_resolver
def index
end
def ops
if params["create_new"]
redirect_to(new_link_path)
return
end
if !params["ids"]
flash[:warning] = t(:no_selected,:scope=>:link)
else
if params["delete_selected"]
params["ids"].each do |id|
@current_user.links.find_by_id(id).destroy
end
end
end
redirect_to(links_path)
end
#problem http://binary10ve.blogspot.com/2011/05/migrating-to-rails-3-got-stuck-with.html
#def destroy
# @current_user.contacts.find(params[:id]).destroy
# redirect_to(contacts_path)
#end
def new
@link = Link.new
end
def edit
@link = @current_user.links.find(params[:id])
render 'edit'
end
def create
@link = @current_user.links.build(params[:link])
if @link.valid?
@link.save
flash[:success] = t(:was_created,:scope=>:link)
redirect_to(links_path)
else
render 'new'
end
end
def update
@link = @current_user.links.find(params[:id])
if @link.update_attributes(params[:link])
redirect_to(links_path)
else
render 'edit'
end
end
def external
if params["export"]
redirect_to :action => 'export'
return
elsif params["import"]
begin
raise t(:no_file_chosen,:scope=>:common) if not params[:upload]
raise t(:no_tmp_dir,:scope=>:common) if not File.exists?($defaults["msg_upload_dir"])
tmp_file = Tempfile.new($defaults["contact_tmp_filename"],$defaults["msg_upload_dir"])
tmp_file.write(params[:upload][:datafile].read)
tmp_file.flush
tmp_file.rewind
tmp_file.readlines.each do |line|
next if line =~ /^#/
Contact.import(@current_user,line)
end
rescue ActiveRecord::RecordInvalid => e
flash[:error] = {:title => e.to_s,:info => e.record.inspect + e.record.errors.inspect}
rescue Exception => e
flash[:error] = e.to_s
else
flash[:success] = t(:were_imported,:scope=>:contact)
end
end
redirect_to :action => 'index'
end
def export
contacts = @current_user.contacts
s = ""
contacts.each do |c|
s += c.export + "\r\n"
end
headers['Content-type'] = "text/csv"
headers['Content-Disposition'] = %(attachment; filename="contacts.csv")
render :text => s
end
####################################### protected section ################################
protected
def prepare_ops_buttons
@buttons = []
@buttons << {:text => 'create_new',:scope=> 'link', :image => 'plus.png'}
@buttons << {:text => 'delete_selected',:scope=>'link',:image => 'minus.png'}
end
def prepare_export_import_buttons
@ei_buttons = []
@ei_buttons << {:text => 'import',:scope=>'link',:image => 'right.png'}
@ei_buttons << {:text => 'export',:scope=>'link',:image => 'left.png'}
end
####################################### private section ##################################
private
def get_links
@links = Link.getPageForUser(@current_user,params[:page],params[:sort_field],params[:sort_dir])
end
end

View file

@ -0,0 +1,210 @@
require 'imap_session'
require 'imap_mailbox'
require 'imap_message'
require 'mail'
require 'mail_plugin_extension'
class MessagesController < ApplicationController
include ImapMailboxModule
include ImapSessionModule
include ImapMessageModule
include MessagesHelper
before_filter :check_current_user ,:selected_folder,:get_current_folders
before_filter :open_imap_session, :select_imap_folder
before_filter :prepare_compose_buttons, :only => [:compose]
before_filter :get_system_folders, :only => [:index]
before_filter :create_message_with_params, :only => [:compose]
before_filter :prepare_multi1_buttons, :only => [:index,:show]
before_filter :prepare_multi2_buttons, :only => [:index]
before_filter :prepare_multi3_buttons, :only => [:show]
after_filter :close_imap_session
#theme :theme_resolver
def index
if @sent_folder.nil? || @drafts_folder.nil? || @inbox_folder.nil? || @trash_folder.nil?
flash[:warning] = t(:not_all_configured,:scope => :folder)
end
if @current_folder.nil?
flash[:warning] = t(:no_selected,:scope => :folder)
redirect_to :controller => 'folders', :action => 'index'
return
end
@messages = []
folder_status = @mailbox.status
@current_folder.update_attributes(:total => folder_status['MESSAGES'], :unseen => folder_status['UNSEEN'])
folder_status['MESSAGES'].zero? ? uids_remote = [] : uids_remote = @mailbox.fetch_uids
uids_local = @current_user.messages.where(:folder_id => @current_folder).collect(&:uid)
logger.custom('current_folder',@current_folder.inspect)
logger.custom('uids_local',uids_local.join(","))
logger.custom('uids_remote',uids_remote.join(","))
logger.custom('to_delete',(uids_local-uids_remote).join(","))
logger.custom('to_fetch',(uids_remote-uids_local).join(","))
(uids_local-uids_remote).each do |uid|
@current_folder.messages.find_by_uid(uid).destroy
end
(uids_remote-uids_local).each_slice($defaults["imap_fetch_slice"].to_i) do |slice|
messages = @mailbox.uid_fetch(slice, ImapMessageModule::IMAPMessage.fetch_attr)
messages.each do |m|
Message.createForUser(@current_user,@current_folder,m)
end
end
@messages = Message.getPageForUser(@current_user,@current_folder,params[:page],params[:sort_field],params[:sort_dir])
end
def compose
#before filter :prepare_compose_buttons, :create_message_with_params
@operation = :new
if params["cid"].present?
contact = @current_user.contacts.find_by_id(params["cid"])
if not contact.nil?
@message.to_addr = contact.email
end
elsif params["cids"].present?
contacts = []
params["cids"].each do |c|
contact = @current_user.contacts.find_by_id(c)
if not contact.nil?
contacts << contact.email
end
end
@message.to_addr = contacts.join(';')
end
end
def show
@images = []
@attachments = []
@text_part = nil
@html_part = nil
@message = @current_user.messages.where('folder_id = ? and uid = ?',@current_folder,params[:id]).first
@message.update_attributes(:unseen => false)
imap_message = @mailbox.fetch_body(@message.uid)
mail = Mail.new(imap_message)
@plain_header = mail.header.to_s
# FIXME missing fields and support arrays
#@from = mail.From.addrs.presence
#@to = mail.To.addrs.presence
@from = @message.from_addr
@to = @message.to_addr
@cc = mail.Cc.presence
@bcc = mail.Bcc.presence
#@subject = mail.Subject
@date = mail.date.presence
if mail.multipart? == true
if not mail.text_part.nil?
@text_part = mail.text_part.decoded_and_charseted
end
if not mail.html_part.nil?
@html_part = mail.html_part.decoded_and_charseted
end
attachments = mail.attachments
if not attachments.size.zero?
for idx in 0..attachments.size - 1
a = attachments[idx]
a.idx = idx
a.parent_id = @message.uid
if a.isImage? and @current_user.prefs.msg_image_view_as.to_sym.eql?(:thumbnail)
@images << a
else
@attachments << a
end
end
end
else
part = Mail::Part.new(mail)
part.idx = 0
part.parent_id = @message.uid
if part.isText?
@text_part = part.decoded_and_charseted
elsif part.isImage? and @current_user.prefs.msg_image_view_as.to_sym.eql?(:thumbnail)
@images << part
elsif part.isHtml?
@html_part = part.decoded_and_charseted
else
@attachments << part
end
end
end
def html_body
message = @current_user.messages.where('folder_id = ? and uid = ?',@current_folder,params[:id]).first
mail = Mail.new(@mailbox.fetch_body(message.uid))
if mail.multipart?
@body = mail.html_part.decoded_and_charseted
else
@body = mail.decoded_and_charseted
end
if @body.nil?
@body = t(:no_body,:scope=>:message)
else
if @body=~/cid:([\w@\.]+)/
attachments = mail.attachments
if not attachments.size.zero?
for idx in 0..attachments.size - 1
@body.gsub!(/cid:#{attachments[idx].cid}/,attachment_download_path(message.uid,idx))
end
end
end
end
render 'html_body',:layout => 'html_body'
end
def attachment
attachments = []
message = @current_user.messages.where('folder_id = ? and uid = ?',@current_folder,params[:id]).first
mail = Mail.new(@mailbox.fetch_body(message.uid))
if mail.multipart? == true
attachments = mail.attachments
else
attachments << Mail::Part.new(mail)
end
a = attachments[params[:idx].to_i]
headers['Content-type'] = a.main_type + "/" + a.sub_type
headers['Content-Disposition'] = %(attachment; filename="#{a.filename}")
render :text => a.decoded
end
############################################# protected section ##########################################
protected
def prepare_multi2_buttons
@multi2_buttons = []
@multi2_buttons << {:text => 'trash',:scope=>:message,:image => 'trash.png'}
@multi2_buttons << {:text => 'set_unread',:scope=>:message,:image => 'unseen.png'}
@multi2_buttons << {:text => 'set_read',:scope=>:message,:image => 'seen.png'}
end
def prepare_multi1_buttons
@multi1_buttons = []
@multi1_buttons << {:text => 'copy',:scope=>:message,:image => 'copy.png'}
@multi1_buttons << {:text => 'move',:scope=>:message,:image => 'move.png'}
end
def prepare_multi3_buttons
@multi3_buttons = []
@multi3_buttons << {:text => 'show_header',:scope=>:show,:image => 'zoom.png'}
@multi3_buttons << {:text => 'trash',:scope=>:show,:image => 'trash.png'}
@multi3_buttons << {:text => 'reply',:scope=>:show,:image => 'reply.png'}
end
end

View file

@ -0,0 +1,354 @@
require 'imap_session'
require 'imap_mailbox'
require 'imap_message'
require 'mail'
require 'mail_plugin_extension'
require 'net/smtp'
class MessagesOpsController < ApplicationController
include ImapMailboxModule
include ImapSessionModule
include ImapMessageModule
include MessagesHelper
before_filter :check_current_user ,:selected_folder,:get_current_folders
before_filter :open_imap_session, :select_imap_folder
before_filter :prepare_compose_buttons
before_filter :get_system_folders, :only => [:composed,:single,:multi]
before_filter :prepare_composed , :only => [:composed]
before_filter :create_message_with_params, :only=> [:composed,:single,:multi]
after_filter :close_imap_session
#theme :theme_resolver
############################################### single #####################################
def single
if params[:reply]
reply
return
elsif params[:trash]
trash
elsif params[:move]
move
elsif params[:copy]
copy
end
redirect_to :controller => 'messages', :action => 'index'
end
############################################### multi ######################################
def multi
begin
if !params[:uids]
flash[:warning] = t(:no_selected,:scope=>:message)
elsif params[:set_unread]
set_unread
elsif params[:set_read]
set_read
elsif params[:trash]
trash
elsif params[:copy]
copy
elsif params[:move]
move
end
rescue Exception => e
flash[:error] = "#{t(:imap_error,:scope=>:internal)} (#{e.to_s})"
end
redirect_to :controller => 'messages', :action => 'index'
end
############################################### ################################################
def set_unread
params["uids"].each do |uid|
@mailbox.set_unread(uid)
@current_user.messages.where('folder_id = ? and uid = ?',@current_folder,uid).first.update_attributes(:unseen => 1)
end
end
def set_read
params["uids"].each do |uid|
@mailbox.set_read(uid)
@current_user.messages.where('folder_id = ? and uid = ?',@current_folder,uid).first.update_attributes(:unseen => 0)
end
end
def trash
if @trash_folder.nil?
flash[:warning] = t(:not_configured_trash, :scope=>:folder)
else
params["uids"].each do |uid|
@mailbox.move_message(uid,@trash_folder.full_name)
message = @current_folder.messages.find_by_uid(uid)
message.change_folder(@trash_folder)
end
@mailbox.expunge
@trash_folder.update_stats
@current_folder.update_stats
end
end
def copy
if params[:folder][:target].empty?
flash[:warning] = t(:no_selected,:scope=>:folder)
else
dest_folder = @current_user.folders.find(params[:folder][:target])
params["uids"].each do |uid|
@mailbox.copy_message(uid,dest_folder.full_name)
message = @current_folder.messages.find_by_uid(uid)
new_message = message.clone
new_message.folder_id = dest_folder.id
new_message.save
end
dest_folder.update_stats
@current_folder.update_stats
end
end
def move
if params[:folder][:target].empty?
flash[:warning] = t(:no_selected,:scope=>:folder)
else
dest_folder = @current_user.folders.find(params[:folder][:target])
logger.info "DEST: "+dest_folder.inspect
params["uids"].each do |uid|
logger.info "UID: "+uid
logger.info "DEST_FULL: "+dest_folder.full_name
@mailbox.move_message(uid,dest_folder.full_name)
message = @current_folder.messages.find_by_uid(uid)
logger.info "M: "+message.inspect
logger.info "UPDATE_DEST_BEFORE1: "+dest_folder.inspect
message.change_folder(dest_folder)
logger.info "UPDATE_DEST_BEFORE2: "+dest_folder.inspect
end
logger.info "UPDATE_DEST_BEFORE: "+dest_folder.inspect
@mailbox.expunge
dest_folder.update_stats
logger.info "UPDATE_DEST: "+dest_folder.inspect
@current_folder.update_stats
logger.info "UPDATE_CUT: "+@current_folder.inspect
end
end
def upload
begin
raise MailrException.new :cause=>:no_tmp_dir,:scope=>:common if not File.exists?($defaults["msg_upload_dir"])
raise MailrException.new :cause=>:no_file_chosen,:scope=>:common if not params[:upload]
@operation = :upload
name = params[:file][:data].original_filename
upload_dir = $defaults["msg_upload_dir"]
path = File.join(upload_dir, @current_user.username + "_" + name)
File.open(path, "wb") { |f| f.write(params[:file][:data].read) }
rescue MailrException => e
flash[:error] = t(e.message[:cause],:scope => e.message[:scope])
rescue Exception => e
flash[:error] = t(:general_error,:scope=>:internal) + " (" + e.class.name + " " + e.to_s + ")"
end
create_message_with_params
render 'messages/compose'
end
# Files uploaded from Internet Explorer:
#
#Internet Explorer includes the entire path of a file in the filename sent, so the original_filename routine will return something like:
#
#C:\Documents and Files\user_name\Pictures\My File.jpg
#
#instead of just:
#
#My File.jpg
#
#This is easily handled by File.basename, which strips out everything before the filename.
#
#def sanitize_filename(file_name)
# # get only the filename, not the whole path (from IE)
# just_filename = File.basename(file_name)
# # replace all none alphanumeric, underscore or perioids
# # with underscore
# just_filename.sub(/[^\w\.\-]/,'_')
#end
#
#Deleting an existing File:
#
#If you want to delete any existing file then its simple and need to write following code:
#
# def cleanup
# File.delete("#{RAILS_ROOT}/dirname/#{@filename}")
# if File.exist?("#{RAILS_ROOT}/dirname/#{@filename}")
# end
def composed
if params[:delete_marked] and params[:files]
params[:files].each do |filename|
path = File.join(Rails.root,$defaults["msg_upload_dir"],@current_user.username + "_" +filename)
File.delete(path) if File.exist?(path)
end
create_message_with_params
@operation = :new
render 'messages/compose'
return
elsif params[:upload]
upload
elsif params[:save]
save
elsif params[:sendout]
sendout
else
redirect_to :controller => 'messages', :action => 'index'
end
end
def sendout
begin
smtp_server = @current_user.servers.primary_for_smtp
raise MailrException.new :cause=>:not_configured_smtp,:scope => :compose if smtp_server.nil?
raise MailrException.new :cause=>:has_no_domain,:scope=>:user if @current_user.has_domain?.nil?
raise MailrException.new :cause=>:not_configured_sent,:scope=>:compose if @sent_folder.nil?
send_mail_message( smtp_server,
@current_user.has_domain?,
@current_user.login,
@current_user.get_cached_password(session),
@mail.to_s,
@current_user.email,
params[:message][:to_addr]
)
@mailbox.append(@sent_folder.full_name,@mail.to_s,[:Seen])
upload_dir = $defaults["msg_upload_dir"]
@attachments.each do |file|
path = File.join(upload_dir, @current_user.username + "_" + file[:name])
File.delete(path) if File.exist?(path)
end
rescue MailrException => e
flash[:error] = t(e.message[:cause],:scope => e.message[:scope])
rescue Exception => e
flash[:error] = t(:general_error,:scope=>:internal) + " (" + e.class.name + " " + e.to_s + ")"
else
flash[:success] = t(:was_sent,:scope => :compose)
redirect_to :controller => 'messages', :action => 'index'
return
end
@operation = :new
render 'messages/compose'
end
def save
begin
raise MailrException.new :cause=>:not_configured_drafts,:scope=>:folder if @drafts_folder.nil?
@mailbox.append(@drafts_folder.full_name,@mail.to_s,[:Seen])
if params[:olduid].present?
@mailbox.move_message(params[:olduid],@trash_folder.full_name)
@mailbox.expunge
end
rescue MailrException => e
flash[:error] = t(e.message[:cause],:scope => e.message[:scope])
rescue Exception => e
flash[:error] = t(:general_error,:scope=>:internal) + " (" + e.class.name + " " + e.to_s + ")"
else
@attachments.each do |filename|
path = File.join(Rails.root,filename)
File.delete(path) if File.exist?(path)
end
flash[:success] = t(:was_saved,:scope => :compose)
end
redirect_to :controller => 'messages', :action => 'index'
end
#FIXME edit does not support attachments
def edit
old_message = @current_user.messages.where('folder_id = ? and uid = ?',@current_folder,params[:uids].first).first
@message = Message.new
@message.to_addr = old_message.to_addr
@message.subject = old_message.subject
imap_message = @mailbox.fetch_body(old_message.uid)
mail = Mail.new(imap_message)
if mail.multipart?
@message.body = mail.text_part.nil? ? "" : mail.text_part.decoded_and_charseted.gsub(/<\/?[^>]*>/, "")
else
@message.body = mail.decoded_and_charseted.gsub(/<\/?[^>]*>/, "")
end
@attachments = []
@operation = :edit
@olduid = old_message.uid
render 'messages/compose'
end
def reply
old_message = @current_user.messages.where('folder_id = ? and uid = ?',@current_folder,params[:uids].first).first
@message = Message.new
@message.to_addr = old_message.from_addr
@message.subject = old_message.subject
imap_message = @mailbox.fetch_body(old_message.uid)
mail = Mail.new(imap_message)
if mail.multipart?
@message.body = mail.text_part.nil? ? "" : mail.text_part.decoded_and_charseted.gsub(/<\/?[^>]*>/, "")
else
@message.body = mail.decoded_and_charseted.gsub(/<\/?[^>]*>/, "")
end
@attachments = []
@operation = :reply
render 'messages/compose'
end
###################################### protected section #######################################
protected
def send_mail_message(smtp_server,domain,username,password,msgstr,from,to)
if smtp_server.auth.nil?
smtp = Net::SMTP.start(smtp_server.name, smtp_server.port, domain)
else
smtp = Net::SMTP.start(smtp_server.name, smtp_server.port, domain, username, password, smtp_server.auth)
end
smtp.send_message msgstr, from, to
smtp.finish
end
def prepare_composed
@mail = Mail.new
@mail.subject = params[:message][:subject]
@mail.from = @current_user.full_id
#TODO check if email address is valid if not get address from contacts
@mail.to = params[:message][:to_addr]
@mail.body = params[:message][:body]
@attachments = Dir.glob(File.join($defaults["msg_upload_dir"],@current_user.username + "*"))
@attachments.each do |a|
@mail.add_file :filename => File.basename(a.gsub(/#{@current_user.username}_/,"")), :content => File.read(a)
end
end
############################################ set_mail_defaults ####################################
def set_mail_defaults(user,server,session)
if server.auth.nil? or server.auth == 'none'
password = nil
authentication = nil
enable_starttls_auto = nil
openssl_verify_mode = nil
user_name = nil
else
password = user.get_cached_password(session)
authentication = server.auth
enable_starttls_auto = server.use_tls
openssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
user_name = user.login
end
Mail.defaults do
delivery_method :smtp, {:address => server.name,
:port => server.port,
:domain => user.domain,
:user_name => user_name,
:password => password,
:authentication => authentication,
:enable_starttls_auto => enable_starttls_auto,
:openssl_verify_mode => openssl_verify_mode
}
end
end
end

View file

@ -0,0 +1,55 @@
class PrefsController < ApplicationController
before_filter :check_current_user,:selected_folder
before_filter :get_current_folders
before_filter :get_prefs, :only => [:look,:update_look]
#theme :theme_resolver
def update_look
if params[:prefs]
@prefs.update_attributes(params[:prefs])
end
flash[:success] = t(:were_saved,:scope=>:prefs)
redirect_to :action => 'look'
end
def update_servers
redirect_to :action => 'servers'
end
def update_identity
if params[:user]
@current_user.first_name = params[:user][:first_name]
@current_user.last_name = params[:user][:last_name]
@current_user.domain = params[:user][:domain]
if @current_user.valid?
@current_user.save
flash[:success] = t(:were_saved,:scope=>:prefs)
redirect_to :action => 'identity'
else
render 'prefs/identity'
end
end
end
def look
end
def identity
end
def servers
@servers = @current_user.servers
end
############################# protected section ##################################
def get_prefs
@prefs = @current_user.prefs
end
end

View file

@ -0,0 +1,75 @@
class UserController < ApplicationController
#theme :theme_resolver
layout "simple"
def login
end
def logout
reset_session
flash[:success] = t(:logged_out,:scope=>:user)
redirect_to :action => "login"
end
def authenticate
if not $defaults["only_can_logins"].nil?
if not $defaults["only_can_logins"].include?(params[:user][:login])
redirect_to :controller => 'internal', :action => 'onlycanlogins'
return false
end
end
user = User.find_by_login(params[:user][:login])
if user.nil?
redirect_to :action => 'unknown' ,:login=> params[:user][:login]
else
session[:user_id] = user.id
user.set_cached_password(session,params[:user][:password])
if session["return_to"]
redirect_to(session["return_to"])
session["return_to"] = nil
else
redirect_to :controller=> 'messages', :action=> 'index'
end
end
end
def loginfailure
end
def setup
@user = User.new
@server = Server.new
end
def unknown
end
def create
@user = User.new
@user.login = params[:user][:login]
@user.first_name = params[:user][:first_name]
@user.last_name = params[:user][:last_name]
@server = Server.new
@server.name = params[:server][:name]
if @user.valid? and @server.valid?
@user.save
#@server.user_id = @user.id
#@server.save
Prefs.create_default(@user)
Server.create_defaults(@user)
flash[:success] = t(:setup_done,:scope=>:user)
redirect_to :action => 'login'
else
render "setup"
end
end
end

332
app/helpers/application_helper.rb Normal file → Executable file
View file

@ -1,2 +1,334 @@
require 'iconv'
module ApplicationHelper
def form_field(object,field,flabel,example,val)
model_name = eval(object.class.model_name)
html = ""
html << "<div class=\"param_group\">"
if not object.errors[field.to_sym].empty?
html << "<div class=\"fieldWithErrors\">"
end
html << "<label class=\"label\">"
flabel.nil? ? html << model_name.human_attribute_name(field) : html << t(flabel.to_sym)
html << "</label>"
if not object.errors[field.to_sym].empty?
html << "<span class=\"error\"> "
html << object.errors[field.to_sym].to_s
html << "</span>"
html << "</div>"
end
html << "<input id=\""
html << object.class.name.downcase+"_"+field
html << "\""
html << " name=\"#{object.class.name.downcase}[#{field}]\""
html << " type=\"text\" class=\"text_field\" value=\""
value = val || object.instance_eval(field) || ""
html << value
html << "\"/>"
html << "<span class=\"description\">"
html << t(:example,:scope=>:common)
html << ": "
html << example
html << "</span>"
html << "</div>"
end
def show_param_view(object,field,value)
model_name = eval(object.class.model_name)
html = ""
html << "<div class=\"group clearfix\">"
html << "<label class=\"label\">#{model_name.human_attribute_name(field)}: </label>"
html << value
html << "</div>"
html
end
def area_field(object,field,flabel,example,val,cols,rows)
model_name = eval(object.class.model_name)
html = ""
html << "<div class=\"group\">"
if not object.errors[field.to_sym].empty?
html << "<div class=\"fieldWithErrors\">"
end
html << "<label class=\"label\">"
flabel.nil? ? html << model_name.human_attribute_name(field) : html << t(flabel.to_sym)
html << "</label>"
if not object.errors[field.to_sym].empty?
html << "<span class=\"error\">"
html << object.errors[field.to_sym].to_s
html << "</span>"
html << "</div>"
end
name = object.class.name.downcase + '[' + field + ']'
id = object.class.name.downcase+"_"+field
value = val || object.instance_eval(field) || ""
html << "<textarea id=\"#{id}\" name=\"#{name}\" class=\"text_area\" cols=\"#{cols}\" rows=\"#{rows}\">#{value}</textarea>"
desc = t(:example,:scope=>:common) + ": " + example
html << "<span class=\"description\">#{desc}</span>"
html << "</div>"
end
def form_button(text,image)
html = ""
html << "<div class=\"group\">"
html << "<button class=\"button\" type=\"submit\">"
html << "<img src=\""
html << current_theme_image_path(image)
html << "\" alt=\""
html << t(text.to_sym)
html << "\" />"
html << t(text.to_sym)
html << "</button></div>"
end
def single_action(text,scope,image)
html = ""
html << "<div class=\"actiongroup clearfix\">"
html << "<button class=\"button\" name=\"#{text}\" type=\"submit\">"
html << "<img src=\""
html << current_theme_image_path(image)
html << "\" alt=\""
html << t(text.to_sym, :scope => scope.to_sym)
html << "\" />"
html << t(text.to_sym, :scope => scope.to_sym)
html << "</button></div>"
end
def single_action_onclick(text,scope,image,onclick)
html = ""
html << "<div class=\"actiongroup clearfix\">"
html << "<button class=\"button\" type=\"submit\" onclick=\"window.location='"
html << onclick
html << "'\">"
html << "<img src=\""
html << current_theme_image_path(image)
html << "\" alt=\""
html << t(text.to_sym, :scope => scope.to_sym)
html << "\" />"
html << t(text.to_sym, :scope => scope.to_sym)
html << "</button>"
html << "</div>"
end
def group_action(buttons)
html = ""
html << "<div class=\"actiongroup clearfix\">"
buttons.each do |b|
html << "<button class=\"button\" type=\"submit\" name=\"#{b[:text]}\">"
html << "<img src=\""
html << current_theme_image_path(b[:image])
html << "\" alt=\""
html << t(b[:text].to_sym,:scope=>b[:scope].to_sym)
html << "\" />"
html << t(b[:text].to_sym,:scope=>b[:scope].to_sym)
html << "</button> "
end
html << "</div>"
end
def group_action_text(buttons,text)
html = ""
html << "<div class=\"group\">"
buttons.each do |b|
html << "<button class=\"button\" type=\"submit\" name=\"#{b[:text]}\">"
html << "<img src=\""
html << current_theme_image_path(b[:image])
html << "\" alt=\""
html << t(b[:text].to_sym,:scope=>b[:scope].to_sym)
html << "\" />"
html << t(b[:text].to_sym,:scope=>b[:scope].to_sym)
html << "</button> "
end
html << text
html << "</div>"
end
def form_buttons(buttons)
html = ""
html << "<div class=\"group\">"
buttons.each do |b|
html << "<button class=\"button\" type=\"submit\" name=\"#{b[:text]}\">"
html << "<img src=\""
html << current_theme_image_path(b[:image])
html << "\" alt=\""
html << t(b[:text].to_sym)
html << "\" />"
html << t(b[:text].to_sym)
html << "</button> "
end
html << "</div>"
end
def form_button_value(text,image,onclick)
html = ""
html << "<div class=\"group\">"
html << "<button class=\"button\" type=\"submit\" onclick=\"window.location='"
html << onclick
html << "'\">"
html << "<img src=\""
html << current_theme_image_path(image)
html << "\" alt=\""
html << text
html << "\" />"
html << t(text.to_sym)
html << "</button></div>"
end
def simple_input_field(name,id,label,value)
html = ""
html << "<div class=\"param_group\">"
html << "<label class=\"label\">#{label}</label>"
html << "<input name=\"#{name}[#{id}]\" id=\"#{name}_#{id} class=\"text_field\" type=\"text\" value=\"#{value}\">"
html << "</div>"
end
def select_field(name,object,label,blank)
html = ""
html << "<div class=\"group\">"
html << "<label class=\"label\">#{label}</label>"
html << select(name, name, object.all.collect {|p| [ p.name, p.id ] }, { :include_blank => (blank == true ? true : false)})
html << "</div>"
end
def select_field_table(object,field,table_choices,choice,blank)
model_name = eval(object.class.model_name)
html = ""
html << "<div class=\"param_group\">"
html << "<label class=\"label\">#{model_name.human_attribute_name(field)}</label>"
html << select(object.class.to_s.downcase, field, options_for_select(table_choices,choice), {:include_blank => blank})
html << "</div>"
end
def select_field_table_t(object,field,table_choices,choice,blank)
model_name = eval(object.class.model_name)
html = ""
html << "<div class=\"param_group\">"
html << "<label class=\"label\">#{model_name.human_attribute_name(field)}</label>"
t = []
table_choices.each do |c|
t << [t(c.to_sym,:scope=>:prefs),c.to_s]
end
html << select(object.class.to_s.downcase, field, options_for_select(t,choice), {:include_blank => blank})
html << "</div>"
end
#def form_simle_field(name,label,value)
# html = ""
# html << "<div class=\"group\">"
# html << "<label class=\"label\">#{label}</label>"
# html << "<input class=\"text_field\" type=\"text\" value=\"#{value}\">"
# html << "</div>"
#end
#def nav_to_folders
# link_to( t(:folders,:scope=>:folder), :controller=>:folders, :action=>:index )
#end
#
#def nav_to_messages
# link_to( t(:messages,:scope=>:message), :controller=>:messages, :action=>:index )
#end
#
#def nav_to_compose
# link_to( t(:compose,:scope=>:compose), :controller=>:messages, :action=>:compose )
#end
#
#def nav_to_contacts
# link_to( t(:contacts,:scope=>:contact), contacts_path )
#end
#
#def nav_to_prefs
# link_to( t(:prefs,:scope=>:prefs), prefs_look_path )
#end
def single_navigation(label,scope)
s = ""
s += "<ul>"
s += "<li class=\"first active\">#{link_to(t(label,:scope=>scope),'#')}</li>"
s += "<li class=\"last\">&nbsp;</li>"
s += "</ul>"
end
def main_navigation(active)
instance_variable_set("@#{active}", "active")
s = ""
s += "<ul>"
s += "<li class=\"first #{@messages_tab}\">#{link_to( t(:messages,:scope=>:message), messages_path )}</li>"
s += "<li class=\"#{@compose_tab}\">#{link_to( t(:compose,:scope=>:compose), compose_path )}</li>"
s += "<li class=\"#{@folders_tab}\">#{link_to( t(:folders,:scope=>:folder), folders_path )}</li>"
s += "<li class=\"#{@contacts_tab}\">#{link_to( t(:contacts,:scope=>:contact), contacts_path )}</li>"
s += "<li class=\"#{@prefs_tab}\">#{link_to( t(:prefs,:scope=>:prefs), prefs_look_path )}</li>"
s += "<li class=\"last #{@links_tab}\">#{link_to( t(:links,:scope=>:link), links_path )}</li>"
s += "</ul>"
end
def prefs_navigation(active)
instance_variable_set("@#{active}", "active")
s = ""
s += "<ul>"
s += "<li class=\"first #{@look_tab}\">#{link_to( t(:look,:scope=>:prefs), prefs_look_path )}</li>"
s += "<li class=\"#{@identity_tab}\">#{link_to( t(:identity,:scope=>:prefs), prefs_identity_path )}</li>"
s += "<li class=\"last #{@servers_tab}\">#{link_to( t(:servers,:scope=>:prefs), prefs_servers_path )}</li>"
s += "</ul>"
end
def multi_select(id, name, objects, selected_objects, label, value,joiner,content = {})
options = ""
objects.each do |o|
selected = selected_objects.include?(o) ? " selected=\"selected\"" : ""
option_value = escape_once(o.send(value))
text = [option_value]
unless content[:text].nil?
text = []
content[:text].each do |t|
text << o.send(t)
end
text = text.join(joiner)
end
text.gsub!(/^\./,'')
bracket = []
unless content[:bracket].nil?
content[:bracket].each do |b|
bracket << o.send(b)
end
bracket = bracket.join(joiner)
end
option_content = bracket.empty? ? "#{text}" : "#{text} (#{bracket})"
options << "<option value=\"#{option_value}\"#{selected}>&nbsp;&nbsp;#{option_content}&nbsp;&nbsp;</option>\n"
end
"<div class=\"param_group\"><label class=\"label\">#{label}</label><select multiple=\"multiple\" size=10 id=\"#{id}\" name=\"#{name}\">\n#{options}</select></div>"
end
def force_charset(text)
begin
Iconv.iconv("UTF-8",$defaults["msg_unknown_charset"],text)
rescue
text
end
end
def content_for_sidebar
s = render :partial => 'sidebar/logo'
s += render :partial => 'folders/list'
s += render :partial => 'sidebar/calendar_view'
s += render :partial => 'internal/version'
s
end
def boolean_answer(answer)
answer == true ? t(:true_answer,:scope=>:common) : t(:false_answer,:scope=>:common)
end
end

54
app/models/contact.rb Executable file
View file

@ -0,0 +1,54 @@
class Contact < ActiveRecord::Base
validates_length_of :nick, :within => 5..15
validates_length_of :first_name,:last_name, :within => 3..20
validates_length_of :email, :within => 5..50
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
validates_length_of :info, :maximum => 100
validate :check_unique_nick, :on => :create
default_scope :order => 'nick ASC'
belongs_to :user
def self.getPageForUser(user,page,sort_field,sort_dir)
if sort_field
if Contact.attribute_method?(sort_field) == true
order = sort_field
sort_dir == 'desc' ? order += ' desc' : sort_dir
end
end
Contact.paginate :page => page , :per_page => $defaults["contacts_per_page"], :conditions=> ['user_id = ?', user.id],:order => order
end
def full_name
first_name + ' ' + last_name
end
def check_unique_nick
if !Contact.where('upper(nick) = ? and user_id = ?',nick.upcase,user_id).size.zero?
errors.add(:nick, :not_unique)
end
end
def export
fields = []
fields << nick.presence || ""
fields << first_name || ""
fields << last_name || ""
fields << email || ""
fields << info || ""
fields.join(';')
end
def self.import(user,line)
fields = line.split(/;/)
contact = user.contacts.build( :nick => fields[0].strip,
:first_name => fields[1].strip,
:last_name => fields[2].strip,
:email => fields[3].strip,
:info => fields[4].strip)
contact.save!
end
end

146
app/models/folder.rb Executable file
View file

@ -0,0 +1,146 @@
class Folder < ActiveRecord::Base
belongs_to :user
validates_presence_of :name, :on => :create
before_save :check_fill_params, :on => :create
has_many :messages, :dependent => :destroy
SYS_NONE = 0
SYS_TRASH = 1
SYS_INBOX = 2
SYS_SENT = 3
SYS_DRAFTS = 4
default_scope :order => 'name asc'
scope :shown, where(['shown = ?',true])
scope :inbox, where(['sys = ?',SYS_INBOX])
scope :sent, where(['sys = ?',SYS_SENT])
scope :drafts, where(['sys = ?',SYS_DRAFTS])
scope :trash, where(['sys = ?',SYS_TRASH])
scope :sys, where(['sys > ?',SYS_NONE])
def full_name
if parent.empty?
name
else
parent + delim + name
end
end
def depth
parent.split('.').size
end
def selected?(session_folder)
fields = session_folder.split("#")
fields[1].nil? ? fields.insert(0,"") : fields
(fields[1].downcase == name.downcase) && (fields[0].downcase == parent.downcase)
end
def update_stats
logger.info "MESS_BEFORE: "+messages.inspect
unseen = messages.where(:unseen => true).count
total = messages.count
logger.info "MESS: "+messages.inspect
logger.info "MESS: #{unseen} #{total}"
update_attributes(:unseen => unseen, :total => total)
end
def hasFullName?(folder_name)
full_name.downcase == folder_name.downcase
end
def isSystem?
sys > SYS_NONE
end
def isTrash?
sys == SYS_TRASH
end
def isSent?
sys == SYS_SENT
end
def isInbox?
sys == SYS_INBOX
end
def isDrafts?
sys == SYS_DRAFTS
end
def setNone
update_attributes(:sys => SYS_NONE)
end
def setTrash
update_attributes(:sys => SYS_TRASH)
end
def setSent
update_attributes(:sys => SYS_SENT)
end
def setInbox
update_attributes(:sys => SYS_INBOX)
end
def setDrafts
update_attributes(:sys => SYS_DRAFTS)
end
############################################## private section #####################################
private
def check_fill_params
self.total.nil? ? self.total = 0 : self.total
self.unseen.nil? ? self.unseen = 0 : self.unseen
self.parent.nil? ? self.parent = "" : self.parent
self.haschildren.nil? ? self.haschildren = false : self.haschildren
self.delim.nil? ? self.delim = "." : self.delim
self.sys.nil? ? self.sys = SYS_NONE : self.sys
end
def self.createBulk(user,imapFolders)
imapFolders.each do |name,data|
data.attribs.find_index(:Haschildren).nil? ? has_children = 0 : has_children = 1
name_fields = name.split(data.delim)
if name_fields.count > 1
name = name_fields.delete_at(name_fields.size - 1)
parent = name_fields.join(data.delim)
else
name = name_fields[0]
parent = ""
end
user.folders.create(
:name => name,
:parent => parent,
:haschildren => has_children,
:delim => data.delim,
:total => data.messages,
:unseen => data.unseen,
:sys => SYS_NONE)
end
end
def self.find_by_full_name(data)
folder = data.gsub(/\./,'#')
fields = folder.split("#")
nam = fields.delete_at(fields.size - 1)
fields.size.zero? == true ? par = "" : par = fields.join(".")
where(['name = ? and parent = ?',nam,par]).first
end
def self.refresh(mailbox,user)
user.folders.destroy_all
folders=mailbox.folders
Folder.createBulk(user,folders)
end
end

19
app/models/link.rb Normal file
View file

@ -0,0 +1,19 @@
class Link < ActiveRecord::Base
validates_length_of :name, :within => 5..30
validates_length_of :url, :within => 5..150
validates_length_of :info, :maximum => 50
belongs_to :user
default_scope :order => 'name asc'
def self.getPageForUser(user,page,sort_field,sort_dir)
if sort_field
if Link.attribute_method?(sort_field) == true
order = sort_field
sort_dir == 'desc' ? order += ' desc' : sort_dir
end
end
Link.paginate :page => page , :per_page => $defaults["links_per_page"], :conditions=> ['user_id = ?', user.id],:order => order
end
end

73
app/models/message.rb Executable file
View file

@ -0,0 +1,73 @@
require 'iconv'
require 'mail'
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :folder
#set_primary_key :uid
self.primary_key = :uid
attr_accessible :unseen, :to_addr, :size, :content_type, :folder_id, :subject, :date, :uid, :from_addr, :user_id, :msg_id, :body,:cc_addr,:bcc_addr
attr_accessor :body
def self.addr_to_db(addr)
ret = ""
name = addr.name
name.nil? ? ret : ret << ApplicationController.decode_quoted(name)
ret << "<" + addr.mailbox + "@" + addr.host
ret
end
def self.getPageForUser(user,folder,page,sort_field,sort_dir)
order = 'date desc'
if sort_field
if Message.attribute_method?(sort_field) == true
order = sort_field
sort_dir == 'desc' ? order += ' desc' : sort_dir
end
end
Message.paginate :page => page , :per_page => user.prefs.msgs_per_page.to_i, :conditions=> ['user_id = ? and folder_id = ?', user.id,folder.id],:order => order
end
def self.createForUser(user,folder,message)
# envelope = imap_message.attr['ENVELOPE']
#
# envelope.from.nil? ? from = "" : from = addr_to_db(envelope.from[0])
# envelope.to.nil? ? to = "" : to = addr_to_db(envelope.to[0])
# envelope.subject.nil? ? subject = "" : subject = ApplicationController.decode_quoted(envelope.subject)
mail = Mail.new(message.attr['RFC822.HEADER'])
mail.date.nil? ? date = nil : date = mail.date.to_s
mail.From.nil? ? from = nil : from = mail.From.charseted
mail.To.nil? ? to = nil : to = mail.To.charseted
mail.Subject.nil? ? subject = nil : subject = mail.Subject.charseted
#logger.custom('subject',mail.Subject.encoded)
#logger.custom('subject',subject)
#logger.custom('mail',mail.inspect)
create(
:user_id => user.id,
:folder_id => folder.id,
:msg_id => mail.message_id,
:uid => message.attr['UID'].to_i,
:from_addr => from,
:to_addr => to,
:subject => subject,
:content_type => mail.content_type,
:date => date,
:unseen => !(message.attr['FLAGS'].member? :Seen),
:size => message.attr['RFC822.SIZE']
)
end
def change_folder(folder)
update_attributes(:folder_id => folder.id)
end
end

19
app/models/prefs.rb Executable file
View file

@ -0,0 +1,19 @@
class Prefs < ActiveRecord::Base
validates_presence_of :theme,:locale
has_one :user
protected
def self.create_default(user)
Prefs.create(:user_id => user.id,
:theme => $defaults['theme'],
:locale => $defaults['locale'],
:msgs_per_page => $defaults['msgs_per_page'],
:msg_send_type => $defaults['msg_send_type']
)
end
end
# TODO move refresh to prefs and make refresh page with messages

41
app/models/server.rb Executable file
View file

@ -0,0 +1,41 @@
class Server < ActiveRecord::Base
validates_presence_of :name
belongs_to :user
#before_save :fill_params
def self.primary_for_imap
where(:for_imap=>true).first
end
def self.primary_for_smtp
where(:for_smtp=>true).first
end
def self.create_defaults(user)
create( :user_id=>user.id,
:name=>"localhost",
:port=>$defaults['imap_port'],
:use_ssl=>false,
:use_tls=>false,
:for_smtp=>false,
:for_imap=>true
)
create( :user_id=>user.id,
:name=>"localhost",
:port=>$defaults['smtp_port'],
:use_ssl=>false,
:use_tls=>false,
:for_smtp=>true,
:for_imap=>false
)
end
# private
# def fill_params
# port.nil? ? port = $defaults['imap_port'] : port
# $defaults['imap_use_ssl'] == true ? self.use_ssl = 1 : self.use_ssl = 0
# end
end

64
app/models/user.rb Executable file
View file

@ -0,0 +1,64 @@
require 'ezcrypto'
class User < ActiveRecord::Base
#acts_as_notes_owner
validates_presence_of :first_name,:last_name
validates_uniqueness_of :login
has_many :servers, :dependent => :destroy
has_one :prefs, :dependent => :destroy
has_many :folders, :dependent => :destroy
has_many :messages, :dependent => :destroy
has_many :contacts, :dependent => :destroy
has_many :links, :dependent => :destroy
def set_cached_password(session,password)
if $defaults['session_encryption']
session[:session_salt] = generate_salt
session[:user_password] = EzCrypto::Key.encrypt_with_password($defaults['session_password'], session[:session_salt], password)
else
session[:user_password] = password
end
end
def get_cached_password(session)
if $defaults['session_encryption']
EzCrypto::Key.decrypt_with_password($defaults['session_password'], session[:session_salt], session[:user_password])
else
session[:user_password]
end
end
def generate_salt
(0...8).map{65.+(rand(25)).chr}.join
end
def name
first_name + " " + last_name
end
def full_id
(name + " <" + email + ">") if email
end
def email
if login =~ /\@/
login
else
(login + "@" + domain) if domain.presence
end
end
def username
login.gsub(/\@/,"_").gsub(/\./,"_")
end
def has_domain?
return domain if domain.presence
if login =~ /\@/
login.split(/\@/)[1]
end
end
end

44
app/views/layouts/application.html.erb Normal file → Executable file
View file

@ -1,14 +1,36 @@
<!DOCTYPE html>
<html>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Mailr</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<title>
<%= t(:mailr,:scope=>:common) %>
<%= yield :title %>
</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<%=stylesheet_link_tag current_theme_stylesheet_path('olive') %>
<%=stylesheet_link_tag current_theme_stylesheet_path('calendar_olive') %>
<%=stylesheet_link_tag current_theme_stylesheet_path('jquery-ui-dialog') %>
<%=stylesheet_link_tag current_theme_stylesheet_path('jquery-ui-custom') %>
<%=javascript_include_tag current_theme_javascript_path('jquery') %>
<%=javascript_include_tag current_theme_javascript_path('jquery-ui') %>
<%=javascript_include_tag current_theme_javascript_path('rails') %>
<%=javascript_include_tag current_theme_javascript_path('application') %>
<link rel="icon" type="image/x-icon" href="/favicon.ico"/>
</head>
<body>
<%= yield %>
</body>
<body>
<div id="container">
<div id="sidebar">
<%= yield :sidebar %>
</div>
<div id="main">
<%= render :partial=>'layouts/flash', :object => flash %>
<%= calendar_window(:title=>t(:calendar,:scope=>:common)) %>
<%= yield %>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
<%= yield %>

View file

@ -0,0 +1,32 @@
!!!
%html
%head
%title
=t(:mailr,:scope=>:common)
= yield :title
= stylesheet_link_tag "application", :media => "all"
= javascript_include_tag "application"
= csrf_meta_tags
%body{:class=>"simple"}
.container
.row
.span6.offset3
.row
%a{:href=>"/"}
= image_tag "logo.png"
.row
%hr/
- if flash[:error]
.alert.alert-error
= flash[:error]
- elsif flash[:info]
.alert.alert-info
= flash[:info]
- elsif flash[:success]
.alert.alert-success
= flash[:success]
= yield
.row
#footer-simple
%a{:href=>"https://github.com/musashimm/mailr"} MailR
\- open source web mail client

View file

@ -0,0 +1,18 @@
%form{:class=>"form-horizontal top-pix18",:action=>"#{url_for(:controller => 'user', :action => 'authenticate')}",:method=>"post"}
%fieldset
.control-group
%label{:class=>"control-label",:for=>"login"}
= User.human_attribute_name('login')
.controls
%input{:id=>"user_login",:name=>"user[login]"}
.control-group
%label{:class=>"control-label",:for=>"password"}
= User.human_attribute_name('password')
.controls
%input{:id=>"user_password",:name=>"user[password]"}
.control-group
.controls
%button{:class=>"btn btn-primary",:type=>"submit"}
= t(:please_login,:scope=>:user)

20
app/views/user/setup.html.erb Executable file
View file

@ -0,0 +1,20 @@
<% content_for :title do %>
<%= t(:setup,:scope=>:user) %>
<% end %>
<div class="box">
<div class="box2">
<div class="logo"><a href="/"><img src="<%= current_theme_image_path('logo_small.png')%>" alt="<%= t(:mailr,:scope=>:common) %>"/></a>
</div>
<h2><%= t(:setup,:scope=>:user) %></h2>
<div class="content">
<form action="<%=url_for(:controller => 'user', :action => 'create')%>" method="post" class="form">
<%= raw form_field(@user,"login",nil,"joe.doe",params[:user].nil? ? params[:login] : params[:user][:login]) %>
<%= raw form_field(@user,"first_name",nil,"Joe","") %>
<%= raw form_field(@user,"last_name",nil,"Doe","") %>
<%= raw form_field(@server,"name",nil,"server.domain","") %>
<%= raw single_action('send','compose',"up.png") %>
</form>
</div>
</div>
</div>

16
app/views/user/unknown.html.erb Executable file
View file

@ -0,0 +1,16 @@
<% content_for :title do %>
<%= t(:unknown_title,:scope=>:user) %>
<% end %>
<div class="box">
<div class="box2">
<div class="logo"><a href="/"><img src="" alt="Mailr"/></a>
</div>
<h2><%= t(:unknown_title,:scope=>:user) %></h2>
<div class="content">
<div class="flash"><div class="message warning"><p><%= t(:unknown_flash,:scope=>:user) %></p></div></div>
<p><%= t(:unknown_login,:scope=>:user) %> -> <%= link_to t(:login,:scope=>:user),user_login_path %></p>
<p><%= t(:unknown_setup,:scope=>:user) %> -> <%= link_to t(:setup,:scope=>:user),:controller => "user",:action => "setup",:login => params[:login] %></p>
</div>
</div>
</div>

BIN
arts/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
arts/favicon.xcf Executable file

Binary file not shown.

BIN
arts/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
arts/logo.xcf Executable file

Binary file not shown.

BIN
arts/logo2.xcf Executable file

Binary file not shown.

BIN
arts/logo3.xcf Executable file

Binary file not shown.

View file

@ -17,6 +17,7 @@ module Mailr
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib)
# 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.
@ -28,6 +29,7 @@ module Mailr
# 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)'
config.time_zone = 'Warsaw'
# 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]
@ -57,3 +59,14 @@ module Mailr
config.assets.version = '1.0'
end
end
class ActiveSupport::BufferedLogger
def custom(desc,t)
info "\n**** #{desc} *****"
info t
info "**********************\n\n"
end
end
class MailrException < Exception
end

View file

@ -1,42 +0,0 @@
# MySQL. Versions 4.1 and 5.0 are recommended.
#
# Install the MYSQL driver
# gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
# gem 'mysql2'
#
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_development
pool: 5
username: root
password:
host: localhost
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_test
pool: 5
username: root
password:
host: localhost
production:
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_production
pool: 5
username: root
password:
host: localhost

View file

@ -1,20 +1,42 @@
# MySQL. Versions 4.1 and 5.0 are recommended.
#
# Install the MYSQL driver
# gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
# gem 'mysql2'
#
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
adapter: mysql
database: mailr_dev
host: localhost
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_development
pool: 5
username: root
password:
socket: /var/run/mysqld/mysqld.sock
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: mysql
database: mailr_tests
host: localhost
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_test
pool: 5
username: root
password:
socket: /var/run/mysqld/mysqld.sock
production:
adapter: mysql
database: mailr
host: localhost
adapter: mysql2
encoding: utf8
reconnect: false
database: mailr_production
pool: 5
username: root
password:
socket: /var/run/mysqld/mysqld.sock

66
config/locales/en.yml Normal file → Executable file
View file

@ -1,5 +1,63 @@
# 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:
hello: "Hello world"
activerecord:
attributes:
prefs:
theme: Theme
locale: Locale
msgs_per_page: Messages per page
msg_send_type: Message send format
contact:
compose_to_selected: Compose to selected
delete_selected: Delete selected
modifying: Modifying contact
creating_new: Creating new contact
create_new: Create new contact
contacts: Contacts
contact: Contact
no_selected: No contact selected
total_entries: Total contacts entries
no_entries: No contacts
was_created: Contact was created
are_you_sure_to_delete_contact: Are You sure to delete contact?
prefs:
prefs: Preferences
folder:
folders: Folders
no_shown: No folders shown
parent: Parent folder
to_create: Folder to create
to_delete: Folder to delete
shown: Shown folders
emptybin: Empty
current: Current folder
no_selected: No folder selected
to_create_empty: No folder name
max_depth: Maximum folder depth reached
can_not_create: Can not create folder
was_created: Folder was created
to_delete_empty: No folder to delete
can_not_delete: Can not delete folder
was_deleted: Folder was deleted
message:
messages: Messages
compose:
compose: Compose
user:
login_failure: Login failure. Bad email or password
logged_out: User was logged out
setup_done: Setup done. Please login in
must_be_unique: must be unique
some_add_info: some additional information
example: example
refresh: Refresh
create: Create
delete: Delete
show_hide: Show/Hide
mailr: Mailr
save: Save

91
config/locales/es-ES.yml Executable file
View file

@ -0,0 +1,91 @@
es-ES:
mailr: Mailr
email: Email
password: Contrase&ntilde;a
log_in: Iniciar sesi&oacute;n
wrong_email_or_password: Email o contrase&ntilde;a incorrecta
mailbox: Buzon de Correo
folders: Carpetas
folder: Carpeta
empty: Vaciar
logout: Cerrar sesi&oacute;n
compose: Redactar
preferences: Opciones
filters: Filtros
contacts: Contactos
search: Buscar
search_txt: Buscar en el campo
refresh: Refrescar
operations: Operaciones
operations_txt: Operaciones en mensajes marcados
delete: borrar
copy: copiar
move: mover
junk: Basura
sent: Enviados
inbox: Bandeja de Entrada
trash: Basura
drafts: Borradores
mark_read: marcar como leido
mark_unread: marcar comono leido
destination_txt: Destino para operaciones de copiado y movido
for: para
to: Para
subject: Asunto
date: Fecha
size: Tama&ntilde;o
from: De
show_all: Mostrar todos
pages: P&aacute;ginas
first: Primero
prev: Previo
next: Siguiente
last: &Uacute;ltimo
back_to_list: Volver a la lista
back_to_message: Volver al mensaje
reply: Responder
forward: Reenviar
view_source: Ver fuente
add_filter: Agregar filtro
cc: CC
bcc: BCC
send: Enviar
choose_address: Elegir direcci&oacute;n desde contactos
choose: Elegir
compose_txt: Componer nuevo correo
groups: Grupos
attachment: Adjunto
add: Agregar
first_name: Nombre
last_name: Apellido
send_type: Enviar mensaje tipo
messages_per_page: Mensajes por p&aacute;gina
check_external_mail: Comprobar correos externos?
check_external_mail_txt: Note that by selecting this option webmail system will try to log you using your original email on a local server.
save: Guardar
save_and_add_another: Guardar y agregar otro
cancel: Cancelar
add_one_contact: Agregar un contacto
add_multiple: Agregar multiples
add_multiple_contacts: Agregar multiples contactos
name: nombre
add_folder: Agregar carpeta
total_messages: Mensajes totales
unseen: No leidos
edit_create_contact: Editar o crear contacto
back_to_contacts: Volver a contactos
email_exists: Una cuenta con ese correo electr&oacute;nico ya existe.
delete_contact_question: "Desea borrar este contacto? {{name}} - {{email}}"
validate_fname_error: Por favor, ingrese un nombre (2 a 20 caracteres)
validate_lname_error: Por favor, ingrese un apellido (2 a 20 caracteres)
validate_email_error: Por favor, ingrese una direcci&oacute;n email v&aacute;lida.
contact_cannot_be_changed: El email del contacto no puede ser cambiado
csv_file: Archivo separado por comas(CSV)
tab_file: Archivo separado por tabulaciones
select_file: Selecionar archivo
import: Importar
back_to_folders: Volver a carpetas
add_edit_folder: agregar/editar
user_logged_out: Sesi&oacute;n cerrada correctamente
please_login: Por favor, inicie sesi&oacute;n
want_to_empty_trash_message: ¿Realmente quieres vaciar la papelera?

227
config/locales/pl.yml Executable file
View file

@ -0,0 +1,227 @@
pl:
date:
day_names: [Niedziela, Poniedziałek, Wtorek, Środa, Czwartek, Piątek, Sobota]
abbr_day_names: [Ni, Po, Wt, Śr, Cz, Pi, So]
month_names: [~, Styczeń, Luty, Marzec, Kwiecień, Maj, Czerwiec, Lipiec, Sierpień, Wrzesień, Październik, Listopad, Grudzień]
abbr_month_names: [~, Sty, Lut, Mar, Kwi, Maj, Czer, Lip, Sier, Wrze, Paź, Lis, Grudz]
order: [ :year, :month, :day ]
activerecord:
errors:
messages:
blank: "nie może być pust(a/e/y) "
too_short: "za krótki(e) (min. %{count} znak(i/ów)) "
too_long: "za długi(e) (max. %{count} znak(i/ów)) "
invalid: "ma niepoprawny format "
not_unique: "musi być unikalny "
taken: "musi być unikalny"
record_invalid: Nieprawidłowy format danych
models:
contact: Kontakt
server: Serwer
link: Sznurek
attributes:
link:
name: Nazwa
url: Adres
info: Informacje
contact:
nick: Pseudonim
first_name: Imię
last_name: Nazwisko
email: E-mail
info: Informacje dodatkowe
prefs:
theme: Temat
locale: Ustawienia językowe
msgs_per_page: Ilość wiadomości wyświetlanych na stronie
msg_send_type: Format wysyłanej wiadomości
msg_image_view_as: Prezentuj obraz jako
msg_image_thumbnail_size: Rozmiar miniaturki obrazu
message:
from_addr: Od
to_addr: Do
subject: Temat
size: Rozmiar
date: Data
body: Treść
cc_addr: Dw
bcc_addr: UDw
user:
email: E-mail
password: Hasło
first_name: Imię
last_name: Nazwisko
domain: Domena
server:
name: Nazwa serwera
port: Port
use_ssl: SSL?
use_tls: TLS?
for_imap: IMAP?
for_smtp: SMTP?
auth: Metoda autoryzacji
contact:
compose_to_selected: Napisz do wybranych
delete_selected: Usuń wybrane
modifying: Modyfikacja kontaktu
creating_new: Nowy kontakt
create_new: Utwórz nowy kontakt
contacts: Kontakty
contact: Kontakt
no_selected: Nie wybrano żadnego kontaktu
total_entries: Liczba kontaktów
no_entries: Brak kontaktów
was_created: Kontakt został utworzony
are_you_sure_to_delete_contact: Czy na pewno chcesz usunąć kontakt?
export: Export
import: Import
were_imported: Kontakty zostały zaimportowane
format_error: Nieprawidłowy format kontaktu
link:
link: Sznurek
links: Sznurki
create_new: Utwórz nowy sznurek
no_entries: Brak sznurków
total_entries: Liczba sznurków
delete_selected: Usuń wybrane
prefs:
prefs: Ustawienia
look: Wygląd
identity: Tożsamość
servers: Serwery
were_saved: Ustawienia zostały zapisane
thumbnail: Miniaturka
attachment: Załącznik
folder:
folder: Folder
folders: Foldery
no_shown: Nie prezentowany jest żaden folder. Skonfiguruj widok folderów w zakładce
parent: Folder nadrzędny
to_create: Folder do utworzenia
to_delete: Folder do usunięcia
shown: Wyświetlane foldery
emptybin: Opróżnij
current: Folder bieżący
no_selected: Nie wybrano żadnego folderu
to_create_empty: Nie podano nazwy folderu
max_depth: Osiągnięto maksymalne zagnieżdzenie fodlderów
can_not_create: Nie można utworzyć folderu
was_created: Folder został utworzony
to_delete_empty: Nie wybrano folderu do usunięcia
can_not_delete: Nie można usunąć folderu
was_deleted: Folder został usunięty
to_folder: do folderu
system: Folder systemowy
not_all_configured: Nie wszystkie systemowe foldery zostały skonfigurowane
inbox_name: Odebrane
sent_name: Wysłane
trash_name: Kosz
drafts_name: Roboczy
not_configured_drafts: Folder Roboczy nie został przypisany
not_configured_sent: Folder Wysłany nie został przypisany
not_configured_trash: Folder Kosz nie został przypisany
not_configured_inbox: Folder Odebrane nie został przypisany
show_hide: Pokaż/Ukryj
refresh: Odśwież
message:
messages: Wiadomości
no_selected: Nie wybrano żadnej wiadomości
total: Liczba wszystkich wiadomości
checked: zaznaczone wiadomości
set_read: Ustaw jako przeczytane
set_unread: Ustaw jako nieprzeczytane
no_in: Nie ma żadnych wiadomości w bieżącym folderze
no_subject: Brak tematu
no_content: Wiadomość nie zawiera treści
attachments: Załączniki
content: Treść wiadomości
header_source: Nagłówek wiadomości
show_header: Pokaż nagłówek
edit: Edycja
images: Obrazy
delete: Usuń zaznaczone
move: Przenieś
copy: Skopiuj
trash: Usuń zaznaczone
no_date: Brak daty
compose:
compose: Nowa wiadomość
new_message: Nowa wiadomość
not_contain_at: ",jeżeli nie zawiera znaku @, adres będzie szukany w kontaktach"
subject_of_the_message: Temat wiadomości
write_your_message_here: Tu wpisz swoją wiadomość
was_sent: Wiadomość została wysłana
was_saved: Wiadomość została zapisana w katalogu roboczym
not_configured_smtp: Brak konfiguracji SMTP
select_file: Wybierz plik
delete_marked: Usuń zaznaczone
upload: Wyślij plik
sendout: Wyślij
save: Zapisz w katalogu roboczym
show:
reply: Odpowiedz
show_header: Pokaż nagłówek
delete: Usuń
reply_string: "Odp: "
trash: Usuń
user:
login_failure: Nieudane logowanie. Podano błędny e-mail lub hasło.
logged_out: Użytkownik wylogowany
setup_done: Konfiguracja zakończona. Proszę się zalogować.
please_login: Logowanie
unknown_title: Nieznany użytkownik
unknown_flash: Twój identyfikator użytkownika nie został odnaleziony w bazie
unknown_login: Idź do strony logowania i zaloguj sie jeszcze raz
unknown_setup: Idź do strony konfiguracyjnej i skonfiguruj swój dostęp
setup: Konfiguracja
login: Logowanie
only_can_logins: Podany identyfikator użytkownika nie uprawnia do korzystania z aplikacji
logout: Wyloguj
has_no_domain: Użytkownik nie ma ustawionej domeny
internal:
imap_error: Błąd protokołu IMAP
unspecified_error: Nieoczekiwany błąd
not_found: Nie znaleziono żądanej strony
internal_server_error: Błąd aplikacji
unprocessable_entity: Błąd procesowania
about: Informacje o programie
current_version: Aktualna wersja
general_error: Błąd aplikacji
common:
false_answer: Nie
true_answer: Tak
file_format_error: Błędny format pliku
no_tmp_dir: Brak katalogu tymczasowego
must_be_unique: musi być unikalny
some_add_info: jakieś dodatkowe informacje
example: przykład
create: Utwórz
delete: Usuń
mailr: MailR
save: Zapisz
copy: Skopiuj
move: Przenieś
to: do
previous_page: Poprzednia
next_page: Następna
bytes: B
kbytes: kB
mbytes: MB
site_link: https://github.com/lmanolov/mailr
no_data: Brak danych
download: Pobierz
view: Pokaż
version: Wersja
set: Ustaw
no_file_chosen: Nie wybrano żadnego pliku
calendar: Kalendarz

113
config/routes.rb Normal file → Executable file
View file

@ -1,58 +1,79 @@
Mailr::Application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
# Sample of regular route:
# match 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
namespace :prefs do
post "update_look"
post "update_identity"
post "update_servers"
end
match "prefs/look" => "prefs#look", :as => :prefs_look
match "prefs/identity" => "prefs#identity", :as => :prefs_identity
match "prefs/servers" => "prefs#servers", :as => :prefs_servers
# Sample of named route:
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
namespace :contacts do
post "ops"
get "export"
end
match "/external" => "contacts#external", :as => :contacts_external
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
resources :contacts
# Sample resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
namespace :links do
post "ops"
get "export"
end
#match "/external" => "contacts#external", :as => :contacts_external
# Sample resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Sample resource route with more complex sub-resources
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', :on => :collection
# end
# end
resources :links
# Sample resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
namespace :folders do
post "create"
post "delete"
post "system"
post "show_hide"
post "refresh"
get "refresh_status"
get "emptybin"
end
match "/folders/index" => 'folders#index', :as => :folders
match "/folders/select/:id" => 'folders#select', :as => :folders_select
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => 'welcome#index'
namespace :internal do
get "error"
get "imaperror"
get "loginfailure"
get "onlycanlogins"
end
match "/internal/about" => 'internal#about' ,:as => :about
# See how all your routes lay out with "rake routes"
match "/messages_ops/single" => 'messages_ops#single'
match "/messages_ops/multi" => 'messages_ops#multi'
match "/messages_ops/sendout_or_save" => 'messages_ops#sendout_or_save' ,:as =>:sendout_or_save
match "/messages_ops/upload" => 'messages_ops#upload',:as => :upload
match "/messages_ops/edit/:id" => 'messages_ops#edit', :as => :edit
match "/messages_ops/composed" => 'messages_ops#composed', :as => :composed
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id))(.:format)'
root :to => "messages#index"
match "/messages/index" => 'messages#index', :as => :messages
match "/messages/compose" => 'messages#compose', :as => :compose
match "/messages/compose/:cid" => 'messages#compose', :as => :compose_contact
match "/messages/show/:id" => 'messages#show'
match "/messages/html_body/:id" => 'messages#html_body' , :as => :html_body
match "/messages/attachment/:id/:idx" => 'messages#attachment', :as => :attachment_download
namespace :user do
get "logout"
post "authenticate"
post "create"
get "login"
get "setup"
get "unknown"
end
match "/user/setup/:login" => 'user#setup'
#themes_for_rails
#match '*a', :to => 'internal#not_found'
#match ':controller(/:action(/:id(.:format)))'
end

View file

@ -0,0 +1,53 @@
version: 0.9
theme: olive
locale: pl
themes: [olive]
locales: [en, pl]
msgs_per_page_table: [15, 20, 25, 30, 35, 40, 45, 50]
msg_send_type: [html, text]
msg_image_view_as: [attachment, thumbnail]
msg_image_thumbnail_size: [128x128, 128x96, 192x192, 192x144, 256x256, 256x192]
contacts_table_fields: [nick, first_name, last_name, email, info]
contacts_per_page: 25
links_table_fields: [name, url, info]
links_per_page: 30
servers_table_fields: [name, port, use_ssl, use_tls, for_imap, for_smtp, auth]
msgs_per_page: 20
msgs_refresh_time: 300
msgs_send_type: html
msgs_update_time: 600
msgs_inbox_view_fields: [from_addr, subject, date, size]
msgs_sent_view_fields: [to_addr, subject, date, size]
msg_subject_length: 50
msg_address_length: 35
msg_search_fields: [subject, from, to]
msg_upload_dir: "tmp/uploads"
contact_tmp_filename: contact_import
# if encoding can not be get from data
msg_unknown_charset: ISO-8859-2
imap_debug: true
imap_use_ssl: 'false'
imap_port: 143
imap_ssl_port: 993
imap_bye_timeout_retry_seconds: 2
imap_fetch_slice: 20
smtp_port: 25
session_encryption: true
session_password: asDD3s2@sAdc983#
mailbox_max_parent_folder_depth: 3
# array of logins which only can login to application, comment it to allow everyone to login
only_can_logins: ['somelogin1']

View file

@ -0,0 +1,111 @@
class CreateAllTables < ActiveRecord::Migration
def up
create_table "contacts", :force => true do |t|
t.string "nick"
t.string "email"
t.string "first_name"
t.string "last_name"
t.string "info"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "events", :force => true do |t|
t.integer "user_id"
t.integer "priority"
t.text "description"
t.string "category"
t.datetime "start"
t.datetime "stop"
t.boolean "allday"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "folders", :force => true do |t|
t.string "name"
t.string "delim"
t.boolean "haschildren"
t.integer "total"
t.integer "unseen"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "parent"
t.boolean "shown"
t.string "alter_name"
t.integer "sys"
end
create_table "messages", :id => false, :force => true do |t|
t.integer "folder_id"
t.integer "user_id"
t.string "msg_id"
t.string "from_addr"
t.string "to_addr"
t.string "subject"
t.string "content_type"
t.integer "uid"
t.integer "size"
t.boolean "unseen"
t.datetime "date"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "prefs", :force => true do |t|
t.string "theme"
t.string "locale"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "msgs_per_page"
t.string "msg_send_type"
t.string "msg_image_view_as"
t.string "msg_image_thumbnail_size"
end
create_table "servers", :force => true do |t|
t.string "name"
t.string "port"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "use_ssl"
t.boolean "use_tls"
t.boolean "for_imap"
t.boolean "for_smtp"
t.string "auth"
end
create_table "users", :force => true do |t|
t.string "login"
t.string "first_name"
t.string "last_name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "domain"
end
create_table :links, :force => true do |t|
t.integer :user_id
t.integer :lgroup_id
t.string :name
t.string :url
t.string :info
end
end
def down
drop_table "contacts"
drop_table "events"
drop_table "folders"
drop_table "prefs"
drop_table "servers"
drop_table "users"
drop_table "links"
end
end

111
db/schema.rb Normal file
View file

@ -0,0 +1,111 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120303202800) do
create_table "contacts", :force => true do |t|
t.string "nick"
t.string "email"
t.string "first_name"
t.string "last_name"
t.string "info"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "events", :force => true do |t|
t.integer "user_id"
t.integer "priority"
t.text "description"
t.string "category"
t.datetime "start"
t.datetime "stop"
t.boolean "allday"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "folders", :force => true do |t|
t.string "name"
t.string "delim"
t.boolean "haschildren"
t.integer "total"
t.integer "unseen"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "parent"
t.boolean "shown"
t.string "alter_name"
t.integer "sys"
end
create_table "links", :force => true do |t|
t.integer "user_id"
t.integer "lgroup_id"
t.string "name"
t.string "url"
t.string "info"
end
create_table "messages", :id => false, :force => true do |t|
t.integer "folder_id"
t.integer "user_id"
t.string "msg_id"
t.string "from_addr"
t.string "to_addr"
t.string "subject"
t.string "content_type"
t.integer "uid"
t.integer "size"
t.boolean "unseen"
t.datetime "date"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "prefs", :force => true do |t|
t.string "theme"
t.string "locale"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "msgs_per_page"
t.string "msg_send_type"
t.string "msg_image_view_as"
t.string "msg_image_thumbnail_size"
end
create_table "servers", :force => true do |t|
t.string "name"
t.string "port"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "use_ssl"
t.boolean "use_tls"
t.boolean "for_imap"
t.boolean "for_smtp"
t.string "auth"
end
create_table "users", :force => true do |t|
t.string "login"
t.string "first_name"
t.string "last_name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "domain"
end
end

23
lib/imap_folder.rb Executable file
View file

@ -0,0 +1,23 @@
require 'net/imap'
module ImapFolderModule
class IMAPFolder
attr_reader :utf7_name
attr_reader :delim
attr_reader :attribs
attr_reader :name
attr_accessor :messages
attr_accessor :unseen
def initialize(utf7_name,delim,attribs)
@utf7_name = utf7_name
@name = Net::IMAP.decode_utf7 utf7_name
@delim = delim
@attribs = attribs
end
end
end

207
lib/imap_mailbox.rb Executable file
View file

@ -0,0 +1,207 @@
require 'net/imap'
require 'imap_folder'
module ImapMailboxModule
class IMAPError < RuntimeError
end
class IMAPMailbox
attr_reader :connected
attr_accessor :sfolder
attr_accessor :logger
def initialize(logger,debug)
@sfolder = ''
@folders = {}
@connected = false
@logger = logger
Net::IMAP.debug = true
end
def connect(server,username,password)
server_name = server.name
server_port = server.port
server_use_ssl = server.use_ssl
unless @connected
begin
@imap = Net::IMAP.new(server_name, server_port, server_use_ssl)
rescue Net::IMAP::ByeResponseError => bye
begin
System.sleep($defaults["imap_bye_timeout_retry_seconds"])
@imap = Net::IMAP.new(server_name, server_port, server_use_ssl)
rescue Exception => ex
raise IMAPError, ex.inspect
end
rescue Exception => ex
raise IMAPError, ex.inspect
end
@username = username
begin
@imap.login(username, password)
@connected = true
rescue Exception => ex
raise IMAPError, ex.inspect
end
end
end
def disconnect
if @connected
@imap.logout
@imap.disconnect
@imap = nil
@connected = false
end
end
def folders
@folders = {}
folders = @imap.list('', '*')
if folders
folders.each do |f|
folder = ImapFolderModule::IMAPFolder.new(f.name,f.delim,f.attr)
status = @imap.status(folder.name, ["MESSAGES", "UNSEEN"])
folder.messages = status["MESSAGES"]
folder.unseen = status["UNSEEN"]
@folders[folder.name] = folder
end
end
@folders
end
def create_folder(name)
begin
@imap.create(Net::IMAP.encode_utf7(name))
rescue Exception => e
raise e
end
end
def fetch_uids
begin
uids = []
imap_uids = @imap.fetch(1..-1, "UID")
imap_uids.each do |u|
uids << u.attr['UID']
end
return uids
rescue Exception => e
raise e
end
end
def delete_folder(name)
begin
@imap.delete(Net::IMAP.decode_utf7(name))
rescue Exception => e
raise e
end
end
def fetch(range,attribs)
begin
@imap.fetch(range,attribs)
rescue Exception => e
raise e
end
end
def uid_fetch(range,attribs)
begin
@imap.uid_fetch(range,attribs)
rescue Exception => e
raise e
end
end
def set_folder(folder_name)
begin
if folder_name != @sfolder
@imap.select(folder_name)
@sfolder = folder_name
end
rescue Exception => e
raise e
end
end
def status
begin
@imap.status(@sfolder, ["MESSAGES", "RECENT", "UNSEEN"])
rescue Exception => e
raise e
end
end
def fetch_body(uid)
begin
uid_fetch(uid,"BODY[]").first.attr["BODY[]"]
rescue Exception => e
raise e
end
end
def delete_message(uid)
begin
@imap.uid_store(uid.to_i, "+FLAGS", :Deleted)
rescue Exception => e
raise e
end
end
def expunge
begin
@imap.expunge
rescue Exception => e
raise e
end
end
def set_read(uid)
begin
@imap.uid_store(uid.to_i, "+FLAGS", :Seen)
rescue Exception => e
raise e
end
end
def set_unread(uid)
begin
@imap.uid_store(uid.to_i, "-FLAGS", :Seen)
rescue Exception => e
raise e
end
end
def copy_message(uid,dest_folder)
begin
@imap.uid_copy(uid.to_i, dest_folder)
rescue Exception => e
raise e
end
end
def move_message(uid,dest_folder)
begin
copy_message(uid,dest_folder)
delete_message(uid)
rescue Exception => e
raise e
end
end
def append(folder,message,flags)
begin
@imap.append(folder,message,flags)
rescue Exception => e
raise e
end
end
end
end

91
lib/imap_message.rb Executable file
View file

@ -0,0 +1,91 @@
require 'net/imap'
module ImapMessageModule
class IMAPAddress
attr_accessor :name,:mailbox,:host
def initialize()
name = ""
mailbox = ""
host = ""
end
def self.from_address(addr)
a = IMAPAddress.new()
a.name = addr.name || ""
a.mailbox = addr.mailbox || ""
a.host = addr.host || ""
a
end
def to_db
name + "#" + mailbox + "#" + host
end
def self.parse(addr)
a = IMAPAddress.new()
f = addr.split("#")
a.name = f[0]
a.mailbox = f[1]
a.host = f[2]
a
end
def friendly
if name.empty?
mailbox + host
else
name
end
end
end
class IMAPMessage
@@fetch_attr = ['RFC822.HEADER', 'FLAGS', 'UID', 'RFC822.SIZE']
#@@fetch_attr = ['RFC822','FLAGS', 'UID', 'RFC822.SIZE']
# attr_accessor :envelope,:uid,:content_type,:size,:unseen,:from,:message_id,:to,:from,:subject,:date
#
# def initialize
# end
#
# def self.fromImap(message)
# m = IMAPMessage.new
# envelope = message.attr['ENVELOPE']
# m.envelope = envelope
# m.message_id = envelope.message_id
# m.date = envelope.date
# m.subject = envelope.subject
# m.uid = message.attr['UID']
# #content_type = m.attr['BODYSTRUCTURE'].multipart? ? 'multipart' : 'text'
# m.content_type = message.attr['BODYSTRUCTURE'].media_type.downcase
# m.size = message.attr['RFC822.SIZE']
# m.unseen = !(message.attr['FLAGS'].member? :Seen)
# m.from = IMAPAddress.from_address(envelope.from[0])
# m.to = IMAPAddress.from_address(envelope.to[0])
# m
# end
#
def self.fetch_attr
@@fetch_attr
end
#
# def from_to_db
# from.to_db
# end
#
# def to_to_db
# to.to_db
# end
end
end

25
lib/imap_session.rb Executable file
View file

@ -0,0 +1,25 @@
require 'net/imap'
require 'imap_mailbox'
module ImapSessionModule
def open_imap_session
begin
@mailbox ||= ImapMailboxModule::IMAPMailbox.new(logger,$defaults["imap_debug"])
@mailbox.connect(@current_user.servers.primary_for_imap,@current_user.login, @current_user.get_cached_password(session))
rescue Exception => ex
redirect_to :controller => 'internal', :action => 'loginfailure'
end
end
def close_imap_session
return if @mailbox.nil? or not(@mailbox.connected)
@mailbox.disconnect
@mailbox = nil
end
def select_imap_folder
@mailbox.set_folder(@current_folder.full_name) if not @current_folder.nil?
end
end

114
lib/mail_plugin_extension.rb Executable file
View file

@ -0,0 +1,114 @@
require 'iconv'
module Mail
class Message
def decoded_and_charseted
begin
if not charset.upcase == 'UTF-8'
charset.nil? ? source_charset = $defaults["msg_unknown_charset"] : source_charset = charset
charseted = Iconv.iconv("UTF-8",source_charset,decoded).first
else
charseted = decoded
end
rescue
decoded
end
end
end
class Part
attr_accessor :idx,:parent_id
def isImage?
not (content_type =~ /^image/).nil?
end
def isText?
not (content_type =~ /^text\/plain/).nil?
end
def isHtml?
not (content_type =~ /^text\/html/).nil?
end
def getSize
body.raw_source.size
end
def decoded_and_charseted
begin
if not charset.upcase == 'UTF-8'
charset.nil? ? source_charset = $defaults["msg_unknown_charset"] : source_charset = charset
charseted = Iconv.iconv("UTF-8",source_charset,decoded).first
else
charseted = decoded
end
rescue
decoded
end
end
end
class Field
def charseted
begin
if value =~ /\=\?([\w\-]+)\?/
source_charset = $1
if source_charset.upcase == 'UTF-8'
return decoded
end
else
source_charset = $defaults["msg_unknown_charset"]
end
Iconv.iconv("UTF-8",source_charset,decoded).first
rescue
decoded
end
end
end
class Address
def charseted
begin
if value =~ /\=\?([\w\-]+)\?/
source_charset = $1
if source_charset.upcase == 'UTF-8'
return decoded
end
else
source_charset = $defaults["msg_unknown_charset"]
end
Iconv.iconv("UTF-8",source_charset,decoded).first
rescue
decoded
end
end
end
class Part
def filename_charseted
begin
if content_type =~ /\=\?([\w\-]+)\?/
source_charset = $1
if source_charset.upcase == 'UTF-8'
return filename
end
else
source_charset = $defaults["msg_unknown_charset"]
end
Iconv.iconv("UTF-8",source_charset,filename).first
rescue
filename
end
end
end
end

View file

@ -1,241 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails: Welcome aboard</title>
<style type="text/css" media="screen">
body {
margin: 0;
margin-bottom: 25px;
padding: 0;
background-color: #f0f0f0;
font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
font-size: 13px;
color: #333;
}
h1 {
font-size: 28px;
color: #000;
}
a {color: #03c}
a:hover {
background-color: #03c;
color: white;
text-decoration: none;
}
#page {
background-color: #f0f0f0;
width: 750px;
margin: 0;
margin-left: auto;
margin-right: auto;
}
#content {
float: left;
background-color: white;
border: 3px solid #aaa;
border-top: none;
padding: 25px;
width: 500px;
}
#sidebar {
float: right;
width: 175px;
}
#footer {
clear: both;
}
#header, #about, #getting-started {
padding-left: 75px;
padding-right: 30px;
}
#header {
background-image: url("assets/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
}
#header h1, #header h2 {margin: 0}
#header h2 {
color: #888;
font-weight: normal;
font-size: 16px;
}
#about h3 {
margin: 0;
margin-bottom: 10px;
font-size: 14px;
}
#about-content {
background-color: #ffd;
border: 1px solid #fc0;
margin-left: -55px;
margin-right: -10px;
}
#about-content table {
margin-top: 10px;
margin-bottom: 10px;
font-size: 11px;
border-collapse: collapse;
}
#about-content td {
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;
}
#about-content td.name {color: #555}
#about-content td.value {color: #000}
#about-content ul {
padding: 0;
list-style-type: none;
}
#about-content.failure {
background-color: #fcc;
border: 1px solid #f00;
}
#about-content.failure p {
margin: 0;
padding: 10px;
}
#getting-started {
border-top: 1px solid #ccc;
margin-top: 25px;
padding-top: 15px;
}
#getting-started h1 {
margin: 0;
font-size: 20px;
}
#getting-started h2 {
margin: 0;
font-size: 14px;
font-weight: normal;
color: #333;
margin-bottom: 25px;
}
#getting-started ol {
margin-left: 0;
padding-left: 0;
}
#getting-started li {
font-size: 18px;
color: #888;
margin-bottom: 25px;
}
#getting-started li h2 {
margin: 0;
font-weight: normal;
font-size: 18px;
color: #333;
}
#getting-started li p {
color: #555;
font-size: 13px;
}
#sidebar ul {
margin-left: 0;
padding-left: 0;
}
#sidebar ul h3 {
margin-top: 25px;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
#sidebar li {
list-style-type: none;
}
#sidebar ul.links li {
margin-bottom: 5px;
}
.filename {
font-style: italic;
}
</style>
<script type="text/javascript">
function about() {
info = document.getElementById('about-content');
if (window.XMLHttpRequest)
{ xhr = new XMLHttpRequest(); }
else
{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
xhr.open("GET","rails/info/properties",false);
xhr.send("");
info.innerHTML = xhr.responseText;
info.style.display = 'block'
}
</script>
</head>
<body>
<div id="page">
<div id="sidebar">
<ul id="sidebar-items">
<li>
<h3>Browse the documentation</h3>
<ul class="links">
<li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
<li><a href="http://api.rubyonrails.org/">Rails API</a></li>
<li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li>
<li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li>
</ul>
</li>
</ul>
</div>
<div id="content">
<div id="header">
<h1>Welcome aboard</h1>
<h2>You&rsquo;re riding Ruby on Rails!</h2>
</div>
<div id="about">
<h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
<div id="about-content" style="display: none"></div>
</div>
<div id="getting-started">
<h1>Getting started</h1>
<h2>Here&rsquo;s how to get rolling:</h2>
<ol>
<li>
<h2>Use <code>rails generate</code> to create your models and controllers</h2>
<p>To see all available options, run it without parameters.</p>
</li>
<li>
<h2>Set up a default route and remove <span class="filename">public/index.html</span></h2>
<p>Routes are set up in <span class="filename">config/routes.rb</span>.</p>
</li>
<li>
<h2>Create your database</h2>
<p>Run <code>rake db:create</code> to create your database. If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p>
</li>
</ol>
</div>
</div>
<div id="footer">&nbsp;</div>
</div>
</body>
</html>