Merge branch 'develop' into private-pull-request-1118
This commit is contained in:
commit
6ab397ef44
573 changed files with 25280 additions and 5097 deletions
|
@ -3,7 +3,7 @@ before_script:
|
||||||
- which ruby
|
- which ruby
|
||||||
- env
|
- env
|
||||||
- test -n "$RNAME" && script/build/test_db_config.sh
|
- test -n "$RNAME" && script/build/test_db_config.sh
|
||||||
- test -n "$RNAME" && bundle install
|
- test -n "$RNAME" && bundle install --jobs 8
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- pre
|
- pre
|
||||||
|
@ -127,6 +127,17 @@ test:integration:email_deliver:
|
||||||
- ruby -I test/ test/integration/email_deliver_test.rb
|
- ruby -I test/ test/integration/email_deliver_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
|
test:integration:email_keep_on_server:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
script:
|
||||||
|
- export RAILS_ENV=test
|
||||||
|
- rake db:create
|
||||||
|
- rake db:migrate
|
||||||
|
- ruby -I test/ test/integration/email_keep_on_server_test.rb
|
||||||
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:twitter:
|
test:integration:twitter:
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
|
@ -229,7 +240,7 @@ test:integration:slack:
|
||||||
- rake db:create
|
- rake db:create
|
||||||
- rake db:migrate
|
- rake db:migrate
|
||||||
- echo "gem 'slack-api'" >> Gemfile.local
|
- echo "gem 'slack-api'" >> Gemfile.local
|
||||||
- bundle install
|
- bundle install --jobs 8
|
||||||
- ruby -I test test/integration/slack_test.rb
|
- ruby -I test test/integration/slack_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
|
@ -281,6 +292,7 @@ test:integration:es_mysql:
|
||||||
- ruby -I test/ test/integration/elasticsearch_test.rb
|
- ruby -I test/ test/integration/elasticsearch_test.rb
|
||||||
- ruby -I test/ test/controllers/search_controller_test.rb
|
- ruby -I test/ test/controllers/search_controller_test.rb
|
||||||
- ruby -I test/ test/integration/report_test.rb
|
- ruby -I test/ test/integration/report_test.rb
|
||||||
|
- ruby -I test/ test/controllers/form_controller_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:es_postgresql:
|
test:integration:es_postgresql:
|
||||||
|
@ -297,6 +309,7 @@ test:integration:es_postgresql:
|
||||||
- ruby -I test/ test/integration/elasticsearch_test.rb
|
- ruby -I test/ test/integration/elasticsearch_test.rb
|
||||||
- ruby -I test/ test/controllers/search_controller_test.rb
|
- ruby -I test/ test/controllers/search_controller_test.rb
|
||||||
- ruby -I test/ test/integration/report_test.rb
|
- ruby -I test/ test/integration/report_test.rb
|
||||||
|
- ruby -I test/ test/controllers/form_controller_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:zendesk_mysql:
|
test:integration:zendesk_mysql:
|
||||||
|
@ -330,7 +343,7 @@ test:integration:otrs_5_mysql:
|
||||||
- mysql
|
- mysql
|
||||||
script:
|
script:
|
||||||
- export RAILS_ENV=test
|
- export RAILS_ENV=test
|
||||||
- export IMPORT_OTRS_ENDPOINT="http://vz599.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||||
- rake db:create
|
- rake db:create
|
||||||
- rake db:migrate
|
- rake db:migrate
|
||||||
- ruby -I test/ test/integration/otrs_import_test.rb
|
- ruby -I test/ test/integration/otrs_import_test.rb
|
||||||
|
@ -343,7 +356,7 @@ test:integration:otrs_5_postgresql:
|
||||||
- postgresql
|
- postgresql
|
||||||
script:
|
script:
|
||||||
- export RAILS_ENV=test
|
- export RAILS_ENV=test
|
||||||
- export IMPORT_OTRS_ENDPOINT="http://vz599.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||||
- rake db:create
|
- rake db:create
|
||||||
- rake db:migrate
|
- rake db:migrate
|
||||||
- ruby -I test/ test/integration/otrs_import_test.rb
|
- ruby -I test/ test/integration/otrs_import_test.rb
|
||||||
|
@ -409,6 +422,7 @@ browser:build:
|
||||||
- rake assets:precompile
|
- rake assets:precompile
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
artifacts:
|
artifacts:
|
||||||
|
expire_in: 1 week
|
||||||
paths:
|
paths:
|
||||||
- public/assets/.sprockets-manifest*
|
- public/assets/.sprockets-manifest*
|
||||||
- public/assets/application-*
|
- public/assets/application-*
|
||||||
|
@ -426,7 +440,7 @@ test:browser:integration:api_client_ruby:
|
||||||
- script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1
|
- script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1
|
||||||
- git clone git@github.com:zammad/zammad-api-client-ruby.git || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1
|
- git clone git@github.com:zammad/zammad-api-client-ruby.git || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1
|
||||||
- cd zammad-api-client-ruby
|
- cd zammad-api-client-ruby
|
||||||
- bundle install
|
- bundle install --jobs 8
|
||||||
- export TEST_URL=http://$IP:$BROWSER_PORT
|
- export TEST_URL=http://$IP:$BROWSER_PORT
|
||||||
- rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1)
|
- rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1)
|
||||||
- cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1
|
- cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1
|
||||||
|
|
|
@ -5,19 +5,23 @@ notifications: false
|
||||||
targets:
|
targets:
|
||||||
centos-7:
|
centos-7:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
- elasticsearch
|
||||||
- nginx
|
- nginx
|
||||||
- postgresql-server
|
- postgresql-server
|
||||||
- which
|
- which
|
||||||
debian-8:
|
debian-8:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
- elasticsearch
|
||||||
- nginx|apache2
|
- nginx|apache2
|
||||||
- postgresql|mysql-server|mariadb-server|sqlite
|
- postgresql|mysql-server|mariadb-server|sqlite
|
||||||
ubuntu-16.04:
|
ubuntu-16.04:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
- elasticsearch
|
||||||
- nginx|apache2
|
- nginx|apache2
|
||||||
- postgresql|mysql-server|mariadb-server|sqlite
|
- postgresql|mysql-server|mariadb-server|sqlite
|
||||||
sles-12:
|
sles-12:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
- elasticsearch
|
||||||
- nginx
|
- nginx
|
||||||
- postgresql-server
|
- postgresql-server
|
||||||
before:
|
before:
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.3.1
|
2.4.1
|
||||||
|
|
|
@ -19,7 +19,7 @@ services:
|
||||||
- mysql
|
- mysql
|
||||||
language: ruby
|
language: ruby
|
||||||
rvm:
|
rvm:
|
||||||
- 2.3.1
|
- 2.4.1
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get -qq update
|
- sudo apt-get -qq update
|
||||||
- sudo apt-get install -y curl git-core patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev sqlite3 libsqlite3-dev autotools-dev libxslt1-dev libyaml-0-2 autoconf automake libreadline6-dev libyaml-dev libtool libgmp-dev libgdbm-dev libncurses5-dev pkg-config libffi-dev libmysqlclient-dev postfix
|
- sudo apt-get install -y curl git-core patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev sqlite3 libsqlite3-dev autotools-dev libxslt1-dev libyaml-0-2 autoconf automake libreadline6-dev libyaml-dev libtool libgmp-dev libgdbm-dev libncurses5-dev pkg-config libffi-dev libmysqlclient-dev postfix
|
||||||
|
@ -55,3 +55,4 @@ script:
|
||||||
- ruby -I test/ test/integration/user_device_controller_test.rb
|
- ruby -I test/ test/integration/user_device_controller_test.rb
|
||||||
- ruby -I test/ test/integration/sipgate_controller_test.rb
|
- ruby -I test/ test/integration/sipgate_controller_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
after_success: contrib/travis-ci.org/trigger-docker-compose-build.sh
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
## [1.6.0](https://github.com/zammad/zammad/tree/1.6.0) (2017-xx-xx)
|
## [2.1.0](https://github.com/zammad/zammad/tree/2.1.0) (2017-xx-xx)
|
||||||
[Full Changelog](https://github.com/zammad/zammad/compare/1.4.0...1.5.0)
|
[Full Changelog](https://github.com/zammad/zammad/compare/2.0.0...2.1.0)
|
||||||
|
|
||||||
**Implemented enhancements:**
|
**Implemented enhancements:**
|
||||||
|
|
||||||
|
|
12
Gemfile
12
Gemfile
|
@ -1,8 +1,8 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '2.3.1'
|
ruby '2.4.1'
|
||||||
|
|
||||||
gem 'rails', '4.2.7.1'
|
gem 'rails', '4.2.9'
|
||||||
gem 'rails-observers'
|
gem 'rails-observers'
|
||||||
gem 'activerecord-session_store'
|
gem 'activerecord-session_store'
|
||||||
|
|
||||||
|
@ -40,18 +40,20 @@ gem 'omniauth-gitlab'
|
||||||
gem 'omniauth-google-oauth2'
|
gem 'omniauth-google-oauth2'
|
||||||
gem 'omniauth-linkedin-oauth2'
|
gem 'omniauth-linkedin-oauth2'
|
||||||
gem 'omniauth-twitter'
|
gem 'omniauth-twitter'
|
||||||
|
gem 'omniauth-microsoft-office365'
|
||||||
|
|
||||||
gem 'twitter'
|
gem 'twitter'
|
||||||
gem 'telegramAPI'
|
gem 'telegramAPI'
|
||||||
gem 'koala'
|
gem 'koala'
|
||||||
gem 'mail'
|
gem 'mail'
|
||||||
gem 'email_verifier'
|
gem 'valid_email2'
|
||||||
gem 'htmlentities'
|
gem 'htmlentities'
|
||||||
|
|
||||||
gem 'mime-types'
|
gem 'mime-types'
|
||||||
|
|
||||||
gem 'biz'
|
gem 'biz'
|
||||||
|
|
||||||
|
gem 'composite_primary_keys'
|
||||||
gem 'delayed_job_active_record'
|
gem 'delayed_job_active_record'
|
||||||
gem 'daemons'
|
gem 'daemons'
|
||||||
|
|
||||||
|
@ -72,12 +74,16 @@ gem 'argon2'
|
||||||
|
|
||||||
gem 'writeexcel'
|
gem 'writeexcel'
|
||||||
gem 'icalendar'
|
gem 'icalendar'
|
||||||
|
gem 'icalendar-recurrence'
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
|
|
||||||
# integrations
|
# integrations
|
||||||
gem 'slack-notifier'
|
gem 'slack-notifier'
|
||||||
gem 'clearbit'
|
gem 'clearbit'
|
||||||
gem 'zendesk_api'
|
gem 'zendesk_api'
|
||||||
|
gem 'viewpoint'
|
||||||
|
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm.git'
|
||||||
|
gem 'autodiscover', git: 'https://github.com/thorsteneckel/autodiscover.git'
|
||||||
|
|
||||||
# event machine
|
# event machine
|
||||||
gem 'eventmachine'
|
gem 'eventmachine'
|
||||||
|
|
272
Gemfile.lock
272
Gemfile.lock
|
@ -1,67 +1,82 @@
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/thorsteneckel/autodiscover.git
|
||||||
|
revision: 29d713ee0c8c25fcf74c4292ff13fe1fa4d0d827
|
||||||
|
specs:
|
||||||
|
autodiscover (1.0.2)
|
||||||
|
httpclient
|
||||||
|
logging
|
||||||
|
nokogiri
|
||||||
|
nori
|
||||||
|
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/wimm/rubyntlm.git
|
||||||
|
revision: 53969639b87b9e5d5fef560f19cf0d977259591c
|
||||||
|
specs:
|
||||||
|
rubyntlm (0.1.2)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionmailer (4.2.7.1)
|
actionmailer (4.2.9)
|
||||||
actionpack (= 4.2.7.1)
|
actionpack (= 4.2.9)
|
||||||
actionview (= 4.2.7.1)
|
actionview (= 4.2.9)
|
||||||
activejob (= 4.2.7.1)
|
activejob (= 4.2.9)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
actionpack (4.2.7.1)
|
actionpack (4.2.9)
|
||||||
actionview (= 4.2.7.1)
|
actionview (= 4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
rack (~> 1.6)
|
rack (~> 1.6)
|
||||||
rack-test (~> 0.6.2)
|
rack-test (~> 0.6.2)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (4.2.7.1)
|
actionview (4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||||
activejob (4.2.7.1)
|
activejob (4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
globalid (>= 0.3.0)
|
globalid (>= 0.3.0)
|
||||||
activemodel (4.2.7.1)
|
activemodel (4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
activerecord (4.2.7.1)
|
activerecord (4.2.9)
|
||||||
activemodel (= 4.2.7.1)
|
activemodel (= 4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
arel (~> 6.0)
|
arel (~> 6.0)
|
||||||
activerecord-nulldb-adapter (0.3.6)
|
activerecord-nulldb-adapter (0.3.7)
|
||||||
activerecord (>= 2.0.0)
|
activerecord (>= 2.0.0)
|
||||||
activerecord-session_store (1.0.0)
|
activerecord-session_store (1.1.0)
|
||||||
actionpack (>= 4.0, < 5.1)
|
actionpack (>= 4.0, < 5.2)
|
||||||
activerecord (>= 4.0, < 5.1)
|
activerecord (>= 4.0, < 5.2)
|
||||||
multi_json (~> 1.11, >= 1.11.2)
|
multi_json (~> 1.11, >= 1.11.2)
|
||||||
rack (>= 1.5.2, < 3)
|
rack (>= 1.5.2, < 3)
|
||||||
railties (>= 4.0, < 5.1)
|
railties (>= 4.0, < 5.2)
|
||||||
activesupport (4.2.7.1)
|
activesupport (4.2.9)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
thread_safe (~> 0.3, >= 0.3.4)
|
thread_safe (~> 0.3, >= 0.3.4)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.4.0)
|
addressable (2.4.0)
|
||||||
arel (6.0.3)
|
arel (6.0.4)
|
||||||
argon2 (1.1.3)
|
argon2 (1.1.3)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
ffi-compiler (~> 0.1)
|
ffi-compiler (~> 0.1)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
autoprefixer-rails (6.4.1.1)
|
autoprefixer-rails (7.1.2.4)
|
||||||
execjs
|
execjs
|
||||||
biz (1.6.0)
|
biz (1.7.0)
|
||||||
clavius (~> 1.0)
|
clavius (~> 1.0)
|
||||||
tzinfo
|
tzinfo
|
||||||
browser (2.2.0)
|
browser (2.2.0)
|
||||||
buftok (0.2.0)
|
buftok (0.2.0)
|
||||||
builder (3.2.2)
|
builder (3.2.3)
|
||||||
childprocess (0.5.9)
|
childprocess (0.5.9)
|
||||||
ffi (~> 1.0, >= 1.0.11)
|
ffi (~> 1.0, >= 1.0.11)
|
||||||
clavius (1.0.2)
|
clavius (1.0.2)
|
||||||
clearbit (0.2.5)
|
clearbit (0.2.7)
|
||||||
nestful (~> 1.1.0)
|
nestful (~> 1.1.0)
|
||||||
coderay (1.1.1)
|
coderay (1.1.1)
|
||||||
coffee-rails (4.2.1)
|
coffee-rails (4.2.1)
|
||||||
|
@ -70,31 +85,32 @@ GEM
|
||||||
coffee-script (2.4.1)
|
coffee-script (2.4.1)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.10.0)
|
coffee-script-source (1.12.2)
|
||||||
coffeelint (1.14.0)
|
coffeelint (1.14.0)
|
||||||
coffee-script
|
coffee-script
|
||||||
execjs
|
execjs
|
||||||
json
|
json
|
||||||
concurrent-ruby (1.0.2)
|
composite_primary_keys (8.1.6)
|
||||||
coveralls (0.8.16)
|
activerecord (~> 4.2.0)
|
||||||
|
concurrent-ruby (1.0.5)
|
||||||
|
coveralls (0.8.21)
|
||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov (~> 0.12.0)
|
simplecov (~> 0.14.1)
|
||||||
term-ansicolor (~> 1.3.0)
|
term-ansicolor (~> 1.3)
|
||||||
thor (~> 0.19.1)
|
thor (~> 0.19.4)
|
||||||
tins (>= 1.6.0, < 2)
|
tins (~> 1.6)
|
||||||
crack (0.4.3)
|
crack (0.4.3)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
daemons (1.2.4)
|
daemons (1.2.4)
|
||||||
delayed_job (4.1.2)
|
delayed_job (4.1.3)
|
||||||
activesupport (>= 3.0, < 5.1)
|
activesupport (>= 3.0, < 5.2)
|
||||||
delayed_job_active_record (4.1.1)
|
delayed_job_active_record (4.1.2)
|
||||||
activerecord (>= 3.0, < 5.1)
|
activerecord (>= 3.0, < 5.2)
|
||||||
delayed_job (>= 3.0, < 5)
|
delayed_job (>= 3.0, < 5)
|
||||||
diff-lcs (1.2.5)
|
diff-lcs (1.2.5)
|
||||||
diffy (3.1.0)
|
diffy (3.1.0)
|
||||||
dnsruby (1.59.3)
|
|
||||||
docile (1.1.5)
|
docile (1.1.5)
|
||||||
domain_name (0.5.20160826)
|
domain_name (0.5.20170404)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (4.2.0)
|
doorkeeper (4.2.0)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
|
@ -106,8 +122,6 @@ GEM
|
||||||
em-websocket (0.5.1)
|
em-websocket (0.5.1)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
email_verifier (0.1.0)
|
|
||||||
dnsruby (>= 1.5)
|
|
||||||
equalizer (0.0.10)
|
equalizer (0.0.10)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
eventmachine (1.2.3)
|
eventmachine (1.2.3)
|
||||||
|
@ -136,8 +150,8 @@ GEM
|
||||||
rainbow (>= 2.1)
|
rainbow (>= 2.1)
|
||||||
rake (>= 10.0)
|
rake (>= 10.0)
|
||||||
retriable (~> 2.1)
|
retriable (~> 2.1)
|
||||||
globalid (0.3.7)
|
globalid (0.4.0)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.2.0)
|
||||||
guard (2.14.0)
|
guard (2.14.0)
|
||||||
formatador (>= 0.2.4)
|
formatador (>= 0.2.4)
|
||||||
listen (>= 2.7, < 4.0)
|
listen (>= 2.7, < 4.0)
|
||||||
|
@ -153,39 +167,49 @@ GEM
|
||||||
guard (~> 2.8)
|
guard (~> 2.8)
|
||||||
guard-compat (~> 1.0)
|
guard-compat (~> 1.0)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
guard-symlink (0.1.0)
|
guard-symlink (0.1.1)
|
||||||
|
guard
|
||||||
guard-compat (~> 1.1)
|
guard-compat (~> 1.1)
|
||||||
hashdiff (0.3.2)
|
hashdiff (0.3.5)
|
||||||
hashie (3.4.4)
|
hashie (3.5.6)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (1.0.4)
|
http (1.0.4)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
http-cookie (~> 1.0)
|
http-cookie (~> 1.0)
|
||||||
http-form_data (~> 1.0.1)
|
http-form_data (~> 1.0.1)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
http-cookie (1.0.2)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (1.0.1)
|
http-form_data (1.0.3)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
i18n (0.8.1)
|
httpclient (2.8.3)
|
||||||
|
i18n (0.8.6)
|
||||||
icalendar (2.4.1)
|
icalendar (2.4.1)
|
||||||
|
icalendar-recurrence (1.1.2)
|
||||||
|
icalendar (~> 2.0)
|
||||||
|
ice_cube (~> 0.13)
|
||||||
|
ice_cube (0.16.2)
|
||||||
inflection (1.0.0)
|
inflection (1.0.0)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
jwt (1.5.4)
|
jwt (1.5.6)
|
||||||
kgio (2.11.0)
|
kgio (2.11.0)
|
||||||
koala (2.4.0)
|
koala (2.4.0)
|
||||||
addressable
|
addressable
|
||||||
faraday
|
faraday
|
||||||
multi_json (>= 1.3.0)
|
multi_json (>= 1.3.0)
|
||||||
libv8 (3.16.14.15)
|
libv8 (3.16.14.19)
|
||||||
listen (3.1.5)
|
listen (3.1.5)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
ruby_dep (~> 1.2)
|
ruby_dep (~> 1.2)
|
||||||
|
little-plugger (1.1.4)
|
||||||
|
logging (2.2.2)
|
||||||
|
little-plugger (~> 1.1)
|
||||||
|
multi_json (~> 1.10)
|
||||||
loofah (2.0.3)
|
loofah (2.0.3)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
lumberjack (1.0.10)
|
lumberjack (1.0.10)
|
||||||
mail (2.6.4)
|
mail (2.6.6)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
memoizable (0.4.2)
|
memoizable (0.4.2)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
|
@ -193,50 +217,54 @@ GEM
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.2.0)
|
||||||
minitest (5.10.1)
|
minitest (5.10.3)
|
||||||
multi_json (1.12.1)
|
multi_json (1.12.1)
|
||||||
multi_xml (0.5.5)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
mysql2 (0.4.4)
|
mysql2 (0.4.6)
|
||||||
naught (1.1.0)
|
naught (1.1.0)
|
||||||
nenv (0.3.0)
|
nenv (0.3.0)
|
||||||
nestful (1.1.1)
|
nestful (1.1.1)
|
||||||
net-ldap (0.15.0)
|
net-ldap (0.15.0)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nokogiri (1.7.1)
|
nokogiri (1.8.0)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.2.0)
|
||||||
|
nori (2.6.0)
|
||||||
notiffany (0.1.1)
|
notiffany (0.1.1)
|
||||||
nenv (~> 0.1)
|
nenv (~> 0.1)
|
||||||
shellany (~> 0.0)
|
shellany (~> 0.0)
|
||||||
oauth (0.5.1)
|
oauth (0.5.1)
|
||||||
oauth2 (1.2.0)
|
oauth2 (1.4.0)
|
||||||
faraday (>= 0.8, < 0.10)
|
faraday (>= 0.8, < 0.13)
|
||||||
jwt (~> 1.0)
|
jwt (~> 1.0)
|
||||||
multi_json (~> 1.3)
|
multi_json (~> 1.3)
|
||||||
multi_xml (~> 0.5)
|
multi_xml (~> 0.5)
|
||||||
rack (>= 1.2, < 3)
|
rack (>= 1.2, < 3)
|
||||||
octokit (4.4.1)
|
octokit (4.4.1)
|
||||||
sawyer (~> 0.7.0, >= 0.5.3)
|
sawyer (~> 0.7.0, >= 0.5.3)
|
||||||
omniauth (1.3.1)
|
omniauth (1.6.1)
|
||||||
hashie (>= 1.2, < 4)
|
hashie (>= 3.4.6, < 3.6.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
omniauth-facebook (4.0.0)
|
omniauth-facebook (4.0.0)
|
||||||
omniauth-oauth2 (~> 1.2)
|
omniauth-oauth2 (~> 1.2)
|
||||||
omniauth-github (1.1.2)
|
omniauth-github (1.3.0)
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.5)
|
||||||
omniauth-oauth2 (~> 1.1)
|
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||||
omniauth-gitlab (1.0.2)
|
omniauth-gitlab (1.0.2)
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
omniauth-oauth2 (~> 1.0)
|
omniauth-oauth2 (~> 1.0)
|
||||||
omniauth-google-oauth2 (0.4.1)
|
omniauth-google-oauth2 (0.5.0)
|
||||||
jwt (~> 1.5.2)
|
jwt (~> 1.5)
|
||||||
multi_json (~> 1.3)
|
multi_json (~> 1.3)
|
||||||
omniauth (>= 1.1.1)
|
omniauth (>= 1.1.1)
|
||||||
omniauth-oauth2 (>= 1.3.1)
|
omniauth-oauth2 (>= 1.3.1)
|
||||||
omniauth-linkedin-oauth2 (0.1.5)
|
omniauth-linkedin-oauth2 (0.1.5)
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
omniauth-oauth2
|
omniauth-oauth2
|
||||||
|
omniauth-microsoft-office365 (0.0.7)
|
||||||
|
omniauth
|
||||||
|
omniauth-oauth2
|
||||||
omniauth-oauth (1.1.0)
|
omniauth-oauth (1.1.0)
|
||||||
oauth
|
oauth
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
|
@ -248,32 +276,32 @@ GEM
|
||||||
omniauth-oauth (~> 1.1)
|
omniauth-oauth (~> 1.1)
|
||||||
parser (2.3.1.2)
|
parser (2.3.1.2)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.18.4)
|
pg (0.20.0)
|
||||||
pluginator (1.3.0)
|
pluginator (1.5.0)
|
||||||
power_assert (0.3.1)
|
power_assert (0.3.1)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pre-commit (0.28.0)
|
pre-commit (0.35.0)
|
||||||
pluginator (~> 1.1)
|
pluginator (~> 1.5)
|
||||||
pry (0.10.4)
|
pry (0.10.4)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.8.1)
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
puma (3.6.0)
|
puma (3.9.1)
|
||||||
rack (1.6.4)
|
rack (1.6.8)
|
||||||
rack-livereload (0.3.16)
|
rack-livereload (0.3.16)
|
||||||
rack
|
rack
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (4.2.7.1)
|
rails (4.2.9)
|
||||||
actionmailer (= 4.2.7.1)
|
actionmailer (= 4.2.9)
|
||||||
actionpack (= 4.2.7.1)
|
actionpack (= 4.2.9)
|
||||||
actionview (= 4.2.7.1)
|
actionview (= 4.2.9)
|
||||||
activejob (= 4.2.7.1)
|
activejob (= 4.2.9)
|
||||||
activemodel (= 4.2.7.1)
|
activemodel (= 4.2.9)
|
||||||
activerecord (= 4.2.7.1)
|
activerecord (= 4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 4.2.7.1)
|
railties (= 4.2.9)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
rails-deprecated_sanitizer (1.0.3)
|
rails-deprecated_sanitizer (1.0.3)
|
||||||
activesupport (>= 4.2.0.alpha)
|
activesupport (>= 4.2.0.alpha)
|
||||||
|
@ -283,15 +311,16 @@ GEM
|
||||||
rails-deprecated_sanitizer (>= 1.0.1)
|
rails-deprecated_sanitizer (>= 1.0.1)
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
rails-observers (0.1.2)
|
rails-observers (0.1.5)
|
||||||
activemodel (~> 4.0)
|
activemodel (>= 4.0)
|
||||||
railties (4.2.7.1)
|
railties (4.2.9)
|
||||||
actionpack (= 4.2.7.1)
|
actionpack (= 4.2.9)
|
||||||
activesupport (= 4.2.7.1)
|
activesupport (= 4.2.9)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.1.0)
|
rainbow (2.2.2)
|
||||||
raindrops (0.17.0)
|
rake
|
||||||
|
raindrops (0.19.0)
|
||||||
rake (12.0.0)
|
rake (12.0.0)
|
||||||
rb-fsevent (0.9.7)
|
rb-fsevent (0.9.7)
|
||||||
rb-inotify (0.9.7)
|
rb-inotify (0.9.7)
|
||||||
|
@ -339,7 +368,6 @@ GEM
|
||||||
sawyer (0.7.0)
|
sawyer (0.7.0)
|
||||||
addressable (>= 2.3.5, < 2.5)
|
addressable (>= 2.3.5, < 2.5)
|
||||||
faraday (~> 0.8, < 0.10)
|
faraday (~> 0.8, < 0.10)
|
||||||
scrub_rb (1.0.1)
|
|
||||||
selenium-webdriver (2.53.4)
|
selenium-webdriver (2.53.4)
|
||||||
childprocess (~> 0.5)
|
childprocess (~> 0.5)
|
||||||
rubyzip (~> 1.0)
|
rubyzip (~> 1.0)
|
||||||
|
@ -347,19 +375,20 @@ GEM
|
||||||
shellany (0.0.1)
|
shellany (0.0.1)
|
||||||
simple-rss (1.3.1)
|
simple-rss (1.3.1)
|
||||||
simple_oauth (0.3.1)
|
simple_oauth (0.3.1)
|
||||||
simplecov (0.12.0)
|
simplecov (0.14.1)
|
||||||
docile (~> 1.1.0)
|
docile (~> 1.1.0)
|
||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.1)
|
||||||
simplecov-rcov (0.2.3)
|
simplecov-rcov (0.2.3)
|
||||||
simplecov (>= 0.4.1)
|
simplecov (>= 0.4.1)
|
||||||
slack-notifier (1.5.1)
|
slack-notifier (1.5.1)
|
||||||
slop (3.6.0)
|
slop (3.6.0)
|
||||||
spring (1.7.2)
|
spring (2.0.2)
|
||||||
|
activesupport (>= 4.2)
|
||||||
spring-commands-rspec (1.0.4)
|
spring-commands-rspec (1.0.4)
|
||||||
spring (>= 0.9.1)
|
spring (>= 0.9.1)
|
||||||
sprockets (3.7.0)
|
sprockets (3.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.2.0)
|
sprockets-rails (3.2.0)
|
||||||
|
@ -369,18 +398,18 @@ GEM
|
||||||
sqlite3 (1.3.11)
|
sqlite3 (1.3.11)
|
||||||
telegramAPI (1.2.2)
|
telegramAPI (1.2.2)
|
||||||
rest-client (~> 2.0, >= 1.7.3)
|
rest-client (~> 2.0, >= 1.7.3)
|
||||||
term-ansicolor (1.3.2)
|
term-ansicolor (1.6.0)
|
||||||
tins (~> 1.0)
|
tins (~> 1.0)
|
||||||
test-unit (3.2.1)
|
test-unit (3.2.1)
|
||||||
power_assert
|
power_assert
|
||||||
therubyracer (0.12.2)
|
therubyracer (0.12.3)
|
||||||
libv8 (~> 3.16.14.0)
|
libv8 (~> 3.16.14.15)
|
||||||
ref
|
ref
|
||||||
thor (0.19.1)
|
thor (0.19.4)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.5)
|
tilt (2.0.5)
|
||||||
tins (1.13.0)
|
tins (1.15.0)
|
||||||
twitter (5.16.0)
|
twitter (5.17.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
buftok (~> 0.2.0)
|
buftok (~> 0.2.0)
|
||||||
equalizer (= 0.0.10)
|
equalizer (= 0.0.10)
|
||||||
|
@ -391,30 +420,37 @@ GEM
|
||||||
memoizable (~> 0.4.0)
|
memoizable (~> 0.4.0)
|
||||||
naught (~> 1.0)
|
naught (~> 1.0)
|
||||||
simple_oauth (~> 0.3.0)
|
simple_oauth (~> 0.3.0)
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.3)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.0.2)
|
uglifier (3.0.2)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.2)
|
unf_ext (0.0.7.4)
|
||||||
unicode-display_width (1.1.1)
|
unicode-display_width (1.1.1)
|
||||||
unicorn (5.2.0)
|
unicorn (5.3.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
webmock (2.3.2)
|
valid_email2 (2.0.0)
|
||||||
|
activemodel (>= 3.2)
|
||||||
|
mail (~> 2.5)
|
||||||
|
viewpoint (1.1.0)
|
||||||
|
httpclient
|
||||||
|
logging
|
||||||
|
nokogiri
|
||||||
|
rubyntlm
|
||||||
|
webmock (3.0.1)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
websocket (1.2.3)
|
websocket (1.2.3)
|
||||||
writeexcel (1.0.5)
|
writeexcel (1.0.5)
|
||||||
zendesk_api (1.14.0)
|
zendesk_api (1.14.4)
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
hashie (>= 1.2, < 4.0, != 3.3.0)
|
hashie (>= 3.5.2, < 4.0.0)
|
||||||
inflection
|
inflection
|
||||||
mime-types
|
mime-types
|
||||||
multipart-post (~> 2.0)
|
multipart-post (~> 2.0)
|
||||||
scrub_rb (~> 1.0.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
@ -423,6 +459,7 @@ DEPENDENCIES
|
||||||
activerecord-nulldb-adapter
|
activerecord-nulldb-adapter
|
||||||
activerecord-session_store
|
activerecord-session_store
|
||||||
argon2
|
argon2
|
||||||
|
autodiscover!
|
||||||
autoprefixer-rails
|
autoprefixer-rails
|
||||||
biz
|
biz
|
||||||
browser
|
browser
|
||||||
|
@ -430,6 +467,7 @@ DEPENDENCIES
|
||||||
coffee-rails
|
coffee-rails
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
coffeelint
|
coffeelint
|
||||||
|
composite_primary_keys
|
||||||
coveralls
|
coveralls
|
||||||
daemons
|
daemons
|
||||||
delayed_job_active_record
|
delayed_job_active_record
|
||||||
|
@ -437,7 +475,6 @@ DEPENDENCIES
|
||||||
doorkeeper
|
doorkeeper
|
||||||
eco
|
eco
|
||||||
em-websocket
|
em-websocket
|
||||||
email_verifier
|
|
||||||
eventmachine
|
eventmachine
|
||||||
execjs
|
execjs
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
|
@ -448,6 +485,7 @@ DEPENDENCIES
|
||||||
guard-symlink
|
guard-symlink
|
||||||
htmlentities
|
htmlentities
|
||||||
icalendar
|
icalendar
|
||||||
|
icalendar-recurrence
|
||||||
json
|
json
|
||||||
koala
|
koala
|
||||||
libv8
|
libv8
|
||||||
|
@ -462,17 +500,19 @@ DEPENDENCIES
|
||||||
omniauth-gitlab
|
omniauth-gitlab
|
||||||
omniauth-google-oauth2
|
omniauth-google-oauth2
|
||||||
omniauth-linkedin-oauth2
|
omniauth-linkedin-oauth2
|
||||||
|
omniauth-microsoft-office365
|
||||||
omniauth-oauth2
|
omniauth-oauth2
|
||||||
omniauth-twitter
|
omniauth-twitter
|
||||||
pg
|
pg
|
||||||
pre-commit
|
pre-commit
|
||||||
puma
|
puma
|
||||||
rack-livereload
|
rack-livereload
|
||||||
rails (= 4.2.7.1)
|
rails (= 4.2.9)
|
||||||
rails-observers
|
rails-observers
|
||||||
rb-fsevent
|
rb-fsevent
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rubocop
|
rubocop
|
||||||
|
rubyntlm!
|
||||||
sass-rails
|
sass-rails
|
||||||
selenium-webdriver
|
selenium-webdriver
|
||||||
simple-rss
|
simple-rss
|
||||||
|
@ -489,12 +529,14 @@ DEPENDENCIES
|
||||||
twitter
|
twitter
|
||||||
uglifier
|
uglifier
|
||||||
unicorn
|
unicorn
|
||||||
|
valid_email2
|
||||||
|
viewpoint
|
||||||
webmock
|
webmock
|
||||||
writeexcel
|
writeexcel
|
||||||
zendesk_api
|
zendesk_api
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.3.1p112
|
ruby 2.4.1p111
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.13.7
|
1.15.3
|
||||||
|
|
|
@ -9,7 +9,7 @@ with a team of agents?
|
||||||
|
|
||||||
You're going to love Zammad!
|
You're going to love Zammad!
|
||||||
|
|
||||||
## Statusbadges
|
## Status
|
||||||
|
|
||||||
- Build: [![Build Status](https://travis-ci.org/zammad/zammad.svg?branch=develop)](https://travis-ci.org/zammad/zammad)
|
- Build: [![Build Status](https://travis-ci.org/zammad/zammad.svg?branch=develop)](https://travis-ci.org/zammad/zammad)
|
||||||
- Code: [![Code Climate](https://codeclimate.com/github/zammad/zammad/badges/gpa.svg)](https://codeclimate.com/github/zammad/zammad) [![Coverage Status](https://coveralls.io/repos/github/zammad/zammad/badge.svg)](https://coveralls.io/github/zammad/zammad)
|
- Code: [![Code Climate](https://codeclimate.com/github/zammad/zammad/badges/gpa.svg)](https://codeclimate.com/github/zammad/zammad) [![Coverage Status](https://coveralls.io/repos/github/zammad/zammad/badge.svg)](https://coveralls.io/github/zammad/zammad)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.6.x
|
2.1.x
|
||||||
|
|
|
@ -61,13 +61,13 @@ class App.Controller extends Spine.Controller
|
||||||
clearDelay: (delay_id) =>
|
clearDelay: (delay_id) =>
|
||||||
App.Delay.clear(delay_id, @controllerId)
|
App.Delay.clear(delay_id, @controllerId)
|
||||||
|
|
||||||
delay: (callback, timeout, delay_id, queue = true) =>
|
delay: (callback, timeout, delay_id, queue = false) =>
|
||||||
App.Delay.set(callback, timeout, delay_id, @controllerId, queue)
|
App.Delay.set(callback, timeout, delay_id, @controllerId, queue)
|
||||||
|
|
||||||
clearInterval: (interval_id) =>
|
clearInterval: (interval_id) =>
|
||||||
App.Interval.clear(interval_id, @controllerId)
|
App.Interval.clear(interval_id, @controllerId)
|
||||||
|
|
||||||
interval: (callback, interval, interval_id, queue = true) =>
|
interval: (callback, interval, interval_id, queue = false) =>
|
||||||
App.Interval.set(callback, interval, interval_id, @controllerId, queue)
|
App.Interval.set(callback, interval, interval_id, @controllerId, queue)
|
||||||
|
|
||||||
releaseController: =>
|
releaseController: =>
|
||||||
|
@ -185,6 +185,17 @@ class App.Controller extends Spine.Controller
|
||||||
formValidate: (data) ->
|
formValidate: (data) ->
|
||||||
App.ControllerForm.validate(data)
|
App.ControllerForm.validate(data)
|
||||||
|
|
||||||
|
# get all query params of the url
|
||||||
|
queryParam: ->
|
||||||
|
return if !@query
|
||||||
|
pairs = @query.split(';')
|
||||||
|
params = {}
|
||||||
|
for pair in pairs
|
||||||
|
result = pair.match('(.+?)=(.*)')
|
||||||
|
if result && result[1]
|
||||||
|
params[result[1]] = result[2]
|
||||||
|
params
|
||||||
|
|
||||||
# redirectToLogin: (data) ->
|
# redirectToLogin: (data) ->
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -344,7 +355,10 @@ class App.Controller extends Spine.Controller
|
||||||
title: ->
|
title: ->
|
||||||
userId = $(@).data('id')
|
userId = $(@).data('id')
|
||||||
user = App.User.find(userId)
|
user = App.User.find(userId)
|
||||||
App.Utils.htmlEscape(user.displayName())
|
headline = App.Utils.htmlEscape(user.displayName())
|
||||||
|
if user.isOutOfOffice()
|
||||||
|
headline += " (#{App.Utils.htmlEscape(user.outOfOfficeText())})"
|
||||||
|
headline
|
||||||
content: ->
|
content: ->
|
||||||
userId = $(@).data('id')
|
userId = $(@).data('id')
|
||||||
user = App.User.fullLocal(userId)
|
user = App.User.fullLocal(userId)
|
||||||
|
|
|
@ -229,7 +229,7 @@ class App.ControllerForm extends App.Controller
|
||||||
if attribute.type is 'hidden'
|
if attribute.type is 'hidden'
|
||||||
attribute.autocomplete = ''
|
attribute.autocomplete = ''
|
||||||
else
|
else
|
||||||
attribute.autocomplete = 'autocomplete="new-password"'
|
attribute.autocomplete = 'autocomplete="off"'
|
||||||
else
|
else
|
||||||
attribute.autocomplete = 'autocomplete="' + attribute.autocomplete + '"'
|
attribute.autocomplete = 'autocomplete="' + attribute.autocomplete + '"'
|
||||||
|
|
||||||
|
@ -426,8 +426,11 @@ class App.ControllerForm extends App.Controller
|
||||||
delete param[item.name]
|
delete param[item.name]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# collect all params, push it to an array if already exists
|
# collect all params, push it to an array item.value already exists
|
||||||
value = item.value.trim()
|
value = item.value
|
||||||
|
if item.value
|
||||||
|
value = item.value.trim()
|
||||||
|
|
||||||
if item.type is 'boolean'
|
if item.type is 'boolean'
|
||||||
if value is ''
|
if value is ''
|
||||||
value = undefined
|
value = undefined
|
||||||
|
|
|
@ -309,6 +309,23 @@ class App.ControllerConfirm extends App.ControllerModal
|
||||||
if @callback
|
if @callback
|
||||||
@callback()
|
@callback()
|
||||||
|
|
||||||
|
class App.ControllerErrorModal extends App.ControllerModal
|
||||||
|
buttonClose: true
|
||||||
|
buttonCancel: false
|
||||||
|
buttonSubmit: 'Close'
|
||||||
|
#buttonClass: 'btn--danger'
|
||||||
|
head: 'Error'
|
||||||
|
#small: true
|
||||||
|
#shown: true
|
||||||
|
|
||||||
|
content: ->
|
||||||
|
@message
|
||||||
|
|
||||||
|
onSubmit: =>
|
||||||
|
@close()
|
||||||
|
if @callback
|
||||||
|
@callback()
|
||||||
|
|
||||||
class App.ControllerDrox extends App.Controller
|
class App.ControllerDrox extends App.Controller
|
||||||
constructor: (params) ->
|
constructor: (params) ->
|
||||||
super
|
super
|
||||||
|
@ -659,8 +676,9 @@ class App.Sidebar extends App.Controller
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
localEl = $(App.view('generic/sidebar_tabs')(
|
localEl = $(App.view('generic/sidebar_tabs')(
|
||||||
items: @items
|
items: @items
|
||||||
scrollbarWidth: App.Utils.getScrollBarWidth()
|
scrollbarWidth: App.Utils.getScrollBarWidth()
|
||||||
|
dir: App.i18n.dir()
|
||||||
))
|
))
|
||||||
|
|
||||||
# init content callback
|
# init content callback
|
||||||
|
|
|
@ -21,7 +21,6 @@ class App.ChannelChat extends App.ControllerSubContent
|
||||||
'.js-chat-welcome': 'chatWelcome'
|
'.js-chat-welcome': 'chatWelcome'
|
||||||
'.js-testurl-input': 'urlInput'
|
'.js-testurl-input': 'urlInput'
|
||||||
'.js-backgroundColor': 'chatBackground'
|
'.js-backgroundColor': 'chatBackground'
|
||||||
'.js-paramsBlock': 'paramsBlock'
|
|
||||||
'.js-code': 'code'
|
'.js-code': 'code'
|
||||||
'.js-palette': 'palette'
|
'.js-palette': 'palette'
|
||||||
'.js-color': 'colorField'
|
'.js-color': 'colorField'
|
||||||
|
@ -361,7 +360,7 @@ class App.ChannelChat extends App.ControllerSubContent
|
||||||
@$('.js-modal-params').html(paramString)
|
@$('.js-modal-params').html(paramString)
|
||||||
|
|
||||||
# highlight
|
# highlight
|
||||||
@paramsBlock.each (i, block) ->
|
@code.each (i, block) ->
|
||||||
hljs.highlightBlock block
|
hljs.highlightBlock block
|
||||||
|
|
||||||
App.Config.set('Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, permission: ['admin.chat'] }, 'NavBarAdmin')
|
App.Config.set('Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, permission: ['admin.chat'] }, 'NavBarAdmin')
|
||||||
|
|
|
@ -45,9 +45,7 @@ class App.ChannelEmailFilter extends App.Controller
|
||||||
|
|
||||||
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn--success">' + App.i18n.translateContent('New') + '</a></div>' )
|
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn--success">' + App.i18n.translateContent('New') + '</a></div>' )
|
||||||
|
|
||||||
description = '''
|
description = 'With filters you can e. g. dispatch new tickets into certain groups or set a certain priority for tickets of a VIP customer.'
|
||||||
With Filters you can e. g. dispatch new Tickets into certain groups or set a certain priority for Tickets of an VIP customer.
|
|
||||||
'''
|
|
||||||
|
|
||||||
new App.ControllerTable(
|
new App.ControllerTable(
|
||||||
el: template.find('.overview')
|
el: template.find('.overview')
|
||||||
|
@ -110,7 +108,7 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
||||||
# show errors in form
|
# show errors in form
|
||||||
if errors
|
if errors
|
||||||
@log 'error', errors
|
@log 'error', errors
|
||||||
@formValidate( form: e.target, errors: errors )
|
@formValidate(form: e.target, errors: errors)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# disable form
|
# disable form
|
||||||
|
@ -120,8 +118,10 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
||||||
object.save(
|
object.save(
|
||||||
done: =>
|
done: =>
|
||||||
@close()
|
@close()
|
||||||
fail: =>
|
fail: (settings, details) =>
|
||||||
@close()
|
@log 'errors', details
|
||||||
|
@formEnable(e)
|
||||||
|
@form.showAlert(details.error_human || details.error || 'Unable to create object!')
|
||||||
)
|
)
|
||||||
|
|
||||||
class App.ChannelEmailSignature extends App.Controller
|
class App.ChannelEmailSignature extends App.Controller
|
||||||
|
@ -203,7 +203,7 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
||||||
# show errors in form
|
# show errors in form
|
||||||
if errors
|
if errors
|
||||||
@log 'error', errors
|
@log 'error', errors
|
||||||
@formValidate( form: e.target, errors: errors )
|
@formValidate(form: e.target, errors: errors)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# disable form
|
# disable form
|
||||||
|
@ -213,8 +213,10 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
||||||
object.save(
|
object.save(
|
||||||
done: =>
|
done: =>
|
||||||
@close()
|
@close()
|
||||||
fail: =>
|
fail: (settings, details) =>
|
||||||
|
@log 'errors', details
|
||||||
@formEnable(e)
|
@formEnable(e)
|
||||||
|
@form.showAlert(details.error_human || details.error || 'Unable to create object!')
|
||||||
)
|
)
|
||||||
|
|
||||||
class App.ChannelEmailAccountOverview extends App.Controller
|
class App.ChannelEmailAccountOverview extends App.Controller
|
||||||
|
@ -533,8 +535,8 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
|
|
||||||
# base
|
# base
|
||||||
configureAttributesBase = [
|
configureAttributesBase = [
|
||||||
{ name: 'realname', display: 'Organization & Department Name', tag: 'input', type: 'text', limit: 160, null: false, placeholder: 'Organization Support', autocomplete: 'new-password' },
|
{ name: 'realname', display: 'Organization & Department Name', tag: 'input', type: 'text', limit: 160, null: false, placeholder: 'Organization Support', autocomplete: 'off' },
|
||||||
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 120, null: false, placeholder: 'support@example.com', autocapitalize: false, autocomplete: 'new-password' },
|
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 120, null: false, placeholder: 'support@example.com', autocapitalize: false, autocomplete: 'off' },
|
||||||
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||||
{ name: 'group_id', display: 'Destination Group', tag: 'select', null: false, relation: 'Group', nulloption: true },
|
{ name: 'group_id', display: 'Destination Group', tag: 'select', null: false, relation: 'Group', nulloption: true },
|
||||||
]
|
]
|
||||||
|
@ -562,21 +564,24 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
|
|
||||||
# inbound
|
# inbound
|
||||||
configureAttributesInbound = [
|
configureAttributesInbound = [
|
||||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'off' },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||||
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
|
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' },
|
||||||
|
{ name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' },
|
||||||
]
|
]
|
||||||
|
|
||||||
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
||||||
return if !params
|
return if !params
|
||||||
if params.adapter is 'imap'
|
if params.adapter is 'imap'
|
||||||
ui.show('options::folder')
|
ui.show('options::folder')
|
||||||
|
ui.show('options::keep_on_server')
|
||||||
return
|
return
|
||||||
ui.hide('options::folder')
|
ui.hide('options::folder')
|
||||||
|
ui.hide('options::keep_on_server')
|
||||||
|
|
||||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||||
return if !params
|
return if !params
|
||||||
|
@ -608,9 +613,10 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
# fill user / password based on intro info
|
# fill user / password based on intro info
|
||||||
channel_used = { options: {} }
|
channel_used = { options: {} }
|
||||||
if @account['meta']
|
if @account['meta']
|
||||||
channel_used['options']['user'] = @account['meta']['email']
|
channel_used['options']['user'] = @account['meta']['email']
|
||||||
channel_used['options']['password'] = @account['meta']['password']
|
channel_used['options']['password'] = @account['meta']['password']
|
||||||
channel_used['options']['folder'] = @account['meta']['folder']
|
channel_used['options']['folder'] = @account['meta']['folder']
|
||||||
|
channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server']
|
||||||
|
|
||||||
# show used backend
|
# show used backend
|
||||||
@$('.base-outbound-settings').html('')
|
@$('.base-outbound-settings').html('')
|
||||||
|
@ -618,7 +624,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
if adapter is 'smtp'
|
if adapter is 'smtp'
|
||||||
configureAttributesOutbound = [
|
configureAttributesOutbound = [
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||||
]
|
]
|
||||||
|
@ -672,7 +678,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
for key, value of data.setting
|
for key, value of data.setting
|
||||||
@account[key] = value
|
@account[key] = value
|
||||||
|
|
||||||
if data.content_messages && data.content_messages > 0
|
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
||||||
|
@ -726,7 +732,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
# remember account settings
|
# remember account settings
|
||||||
@account.inbound = params
|
@account.inbound = params
|
||||||
|
|
||||||
if data.content_messages && data.content_messages > 0
|
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
||||||
|
@ -932,7 +938,7 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
|
||||||
if adapter is 'smtp'
|
if adapter is 'smtp'
|
||||||
configureAttributesOutbound = [
|
configureAttributesOutbound = [
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password' },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off' },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,12 +3,14 @@ class App.ChannelForm extends App.ControllerSubContent
|
||||||
requiredPermission: 'admin.channel_formular'
|
requiredPermission: 'admin.channel_formular'
|
||||||
header: 'Form'
|
header: 'Form'
|
||||||
events:
|
events:
|
||||||
'change form.js-params': 'updateParams'
|
'change form.js-paramsDesigner': 'updateParamsDesigner'
|
||||||
'keyup form.js-params': 'updateParams'
|
'keyup form.js-paramsDesigner': 'updateParamsDesigner'
|
||||||
'change .js-formSetting input': 'toggleFormSetting'
|
'change .js-formSetting input': 'toggleFormSetting'
|
||||||
|
'change .js-paramsSetting select': 'updateGroup'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-paramsBlock': 'paramsBlock'
|
'.js-code': 'code'
|
||||||
|
'.js-paramsSetting': 'paramsSetting'
|
||||||
'.js-formSetting input': 'formSetting'
|
'.js-formSetting input': 'formSetting'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
|
@ -20,22 +22,38 @@ class App.ChannelForm extends App.ControllerSubContent
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
setting = App.Setting.get('form_ticket_create')
|
setting = App.Setting.get('form_ticket_create')
|
||||||
@html App.view('channel/form')(
|
|
||||||
|
element = $(App.view('channel/form')(
|
||||||
baseurl: window.location.origin
|
baseurl: window.location.origin
|
||||||
formSetting: setting
|
formSetting: setting
|
||||||
)
|
))
|
||||||
|
|
||||||
@paramsBlock.each (i, block) ->
|
group_id = App.Setting.get('form_ticket_create_group_id')
|
||||||
|
selection = App.UiElement.select.render(
|
||||||
|
name: 'group_id'
|
||||||
|
multiple: false
|
||||||
|
null: false
|
||||||
|
relation: 'Group'
|
||||||
|
nulloption: false
|
||||||
|
value: group_id
|
||||||
|
#class: 'form-control--small'
|
||||||
|
)
|
||||||
|
console.log('s', element.find('.js-groupSelector'), selection)
|
||||||
|
element.find('.js-groupSelector').html(selection)
|
||||||
|
|
||||||
|
@html element
|
||||||
|
|
||||||
|
@code.each (i, block) ->
|
||||||
hljs.highlightBlock block
|
hljs.highlightBlock block
|
||||||
|
|
||||||
@updateParams()
|
@updateParamsDesigner()
|
||||||
|
|
||||||
updateParams: ->
|
updateParamsDesigner: ->
|
||||||
quote = (string) ->
|
quote = (string) ->
|
||||||
string = string.replace('\'', '\\\'')
|
string = string.replace('\'', '\\\'')
|
||||||
.replace(/\</g, '<')
|
.replace(/\</g, '<')
|
||||||
.replace(/\>/g, '>')
|
.replace(/\>/g, '>')
|
||||||
params = @formParam(@$('.js-params'))
|
params = @formParam(@$('.js-paramsDesigner'))
|
||||||
paramString = ''
|
paramString = ''
|
||||||
for key, value of params
|
for key, value of params
|
||||||
if value != ''
|
if value != ''
|
||||||
|
@ -63,4 +81,8 @@ class App.ChannelForm extends App.ControllerSubContent
|
||||||
value = @formSetting.prop('checked')
|
value = @formSetting.prop('checked')
|
||||||
App.Setting.set('form_ticket_create', value)
|
App.Setting.set('form_ticket_create', value)
|
||||||
|
|
||||||
|
updateGroup: =>
|
||||||
|
value = @paramsSetting.find('[name=group_id]').val()
|
||||||
|
App.Setting.set('form_ticket_create_group_id', value)
|
||||||
|
|
||||||
App.Config.set('Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, permission: ['admin.formular'] }, 'NavBarAdmin')
|
App.Config.set('Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, permission: ['admin.formular'] }, 'NavBarAdmin')
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
class Index extends App.ControllerIntegrationBase
|
||||||
|
featureIntegration: 'check_mk_integration'
|
||||||
|
featureName: 'Check_MK'
|
||||||
|
featureConfig: 'check_mk_config'
|
||||||
|
description: [
|
||||||
|
['This service receives http requests from %s and creates tickets with host and service.', 'Check_MK']
|
||||||
|
['If the host and service is recovered again, the ticket will be closed automatically.']
|
||||||
|
]
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
super
|
||||||
|
new App.SettingsForm(
|
||||||
|
area: 'Integration::CheckMK'
|
||||||
|
el: @$('.js-form')
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.ScriptSnipped(
|
||||||
|
el: @$('.js-scriptSnipped')
|
||||||
|
facility: 'check_mk'
|
||||||
|
style: 'bash'
|
||||||
|
content: "#!/bin/bash\n\ncurl -X POST -F 'event_id=123' -F 'host=host1' -F 'service=http' -F 'state=down' #{App.Config.get('http_type')}://#{App.Config.get('fqdn')}/api/v1/integration/check_mk/#{App.Setting.get('check_mk_token')}"
|
||||||
|
description: [
|
||||||
|
['To enable %s for sending http requests to %s, you need create "%s" in the admin interface if %s.', 'Check_MK', 'Zammad', 'Event Actions', 'Check_MK']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.HttpLog(
|
||||||
|
el: @$('.js-log')
|
||||||
|
facility: 'check_mk'
|
||||||
|
)
|
||||||
|
|
||||||
|
class State
|
||||||
|
@current: ->
|
||||||
|
App.Setting.get('check_mk_integration')
|
||||||
|
|
||||||
|
App.Config.set(
|
||||||
|
'IntegrationCheckMk'
|
||||||
|
{
|
||||||
|
name: 'Check_MK'
|
||||||
|
target: '#system/integration/check_mk'
|
||||||
|
description: 'An open source monitoring tool.'
|
||||||
|
controller: Index
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
'NavBarIntegrations'
|
||||||
|
)
|
|
@ -0,0 +1,528 @@
|
||||||
|
class Index extends App.ControllerIntegrationBase
|
||||||
|
featureIntegration: 'exchange_integration'
|
||||||
|
featureName: 'Exchange'
|
||||||
|
featureConfig: 'exchange_config'
|
||||||
|
description: [
|
||||||
|
['This service enables Zammad to connect with your Exchange server.']
|
||||||
|
]
|
||||||
|
events:
|
||||||
|
'change .js-switch input': 'switch'
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
super
|
||||||
|
new Form(
|
||||||
|
el: @$('.js-form')
|
||||||
|
)
|
||||||
|
|
||||||
|
#new App.ImportJob(
|
||||||
|
# el: @$('.js-importJob')
|
||||||
|
# facility: 'exchange'
|
||||||
|
#)
|
||||||
|
|
||||||
|
new App.HttpLog(
|
||||||
|
el: @$('.js-log')
|
||||||
|
facility: 'exchange'
|
||||||
|
)
|
||||||
|
|
||||||
|
switch: =>
|
||||||
|
super
|
||||||
|
active = @$('.js-switch input').prop('checked')
|
||||||
|
if active
|
||||||
|
job_start = =>
|
||||||
|
@ajax(
|
||||||
|
id: 'jobs_config'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_start"
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@render(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Delay.set(
|
||||||
|
job_start,
|
||||||
|
600,
|
||||||
|
'job_start',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Form extends App.Controller
|
||||||
|
elements:
|
||||||
|
'.js-lastImport': 'lastImport'
|
||||||
|
'.js-wizard': 'wizardButton'
|
||||||
|
events:
|
||||||
|
'click .js-wizard': 'startWizard'
|
||||||
|
'click .js-start-sync': 'startSync'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
@lastResult()
|
||||||
|
@activeDryRun()
|
||||||
|
|
||||||
|
currentConfig: ->
|
||||||
|
App.Setting.get('exchange_config') || {}
|
||||||
|
|
||||||
|
setConfig: (value) =>
|
||||||
|
App.Setting.set('exchange_config', value, {notify: true})
|
||||||
|
@startSync()
|
||||||
|
|
||||||
|
render: (top = false) =>
|
||||||
|
@config = @currentConfig()
|
||||||
|
|
||||||
|
folders = []
|
||||||
|
if !_.isEmpty(@config.folders)
|
||||||
|
for folder_id in @config.folders
|
||||||
|
folders.push @config.wizardData.backend_folders[folder_id]
|
||||||
|
|
||||||
|
@html App.view('integration/exchange')(
|
||||||
|
config: @config,
|
||||||
|
folders: folders
|
||||||
|
)
|
||||||
|
if _.isEmpty(@config)
|
||||||
|
@$('.js-notConfigured').removeClass('hide')
|
||||||
|
@$('.js-summary').addClass('hide')
|
||||||
|
else
|
||||||
|
@$('.js-notConfigured').addClass('hide')
|
||||||
|
@$('.js-summary').removeClass('hide')
|
||||||
|
|
||||||
|
if top
|
||||||
|
a = =>
|
||||||
|
@scrollToIfNeeded($('.content.active .page-header'))
|
||||||
|
@delay(a, 500)
|
||||||
|
|
||||||
|
startSync: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'jobs_config'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_start"
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@render(true)
|
||||||
|
@lastResult()
|
||||||
|
)
|
||||||
|
|
||||||
|
startWizard: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
new ConnectionWizard(
|
||||||
|
container: @el.closest('.content')
|
||||||
|
config: @config
|
||||||
|
callback: (config) =>
|
||||||
|
@setConfig(config)
|
||||||
|
)
|
||||||
|
|
||||||
|
lastResult: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'jobs_start_index'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_start"
|
||||||
|
processData: true
|
||||||
|
success: (job, status, xhr) =>
|
||||||
|
if !_.isEmpty(job)
|
||||||
|
if !@lastResultShowJob || @lastResultShowJob.updated_at != job.updated_at
|
||||||
|
@lastResultShowJob = job
|
||||||
|
@lastResultShow(job)
|
||||||
|
if job.finished_at
|
||||||
|
@wizardButton.attr('disabled', false)
|
||||||
|
else
|
||||||
|
@wizardButton.attr('disabled', true)
|
||||||
|
@delay(@lastResult, 5000)
|
||||||
|
)
|
||||||
|
|
||||||
|
lastResultShow: (job) =>
|
||||||
|
if _.isEmpty(job)
|
||||||
|
@lastImport.html('')
|
||||||
|
return
|
||||||
|
countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped + job.result.failed
|
||||||
|
if !job.result.roles
|
||||||
|
job.result.roles = {}
|
||||||
|
for role_id, statistic of job.result.role_ids
|
||||||
|
role = App.Role.find(role_id)
|
||||||
|
job.result.roles[role.displayName()] = statistic
|
||||||
|
el = $(App.view('integration/exchange_last_import')(job: job, countDone: countDone))
|
||||||
|
@lastImport.html(el)
|
||||||
|
|
||||||
|
activeDryRun: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'jobs_try_index'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_try"
|
||||||
|
data:
|
||||||
|
finished: false
|
||||||
|
processData: true
|
||||||
|
success: (job, status, xhr) =>
|
||||||
|
return if _.isEmpty(job)
|
||||||
|
|
||||||
|
# show analyzing
|
||||||
|
new ConnectionWizard(
|
||||||
|
container: @el.closest('.content')
|
||||||
|
config: job.payload
|
||||||
|
start: 'tryLoop'
|
||||||
|
callback: (config) =>
|
||||||
|
@wizardButton.attr('disabled', false)
|
||||||
|
@setConfig(config)
|
||||||
|
)
|
||||||
|
@wizardButton.attr('disabled', true)
|
||||||
|
)
|
||||||
|
|
||||||
|
class State
|
||||||
|
@current: ->
|
||||||
|
App.Setting.get('exchange_integration')
|
||||||
|
|
||||||
|
class ConnectionWizard extends App.WizardModal
|
||||||
|
wizardConfig: {}
|
||||||
|
slideMethod:
|
||||||
|
'js-folders': 'foldersShow'
|
||||||
|
'js-mapping': 'mappingShow'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'submit form.js-discover': 'discover'
|
||||||
|
'submit form.js-bind': 'folders'
|
||||||
|
'submit form.js-folders': 'mapping'
|
||||||
|
'click .js-mapping .js-submitTry': 'mappingChange'
|
||||||
|
'click .js-try .js-submitSave': 'save'
|
||||||
|
'click .js-close': 'hide'
|
||||||
|
'click .js-remove': 'removeRow'
|
||||||
|
'click .js-userMappingForm .js-add': 'addUserMapping'
|
||||||
|
'click .js-goToSlide': 'goToSlide'
|
||||||
|
|
||||||
|
elements:
|
||||||
|
'.modal-body': 'body'
|
||||||
|
'.js-foldersSelect': 'foldersSelect'
|
||||||
|
'.js-folders .js-submitTry': 'foldersSelectSubmit'
|
||||||
|
'.js-userMappingForm': 'userMappingForm'
|
||||||
|
'.js-expertForm': 'expertForm'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
if !_.isEmpty(@config)
|
||||||
|
@wizardConfig = @config
|
||||||
|
|
||||||
|
if @container
|
||||||
|
@el.addClass('modal--local')
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
@el.modal
|
||||||
|
keyboard: true
|
||||||
|
show: true
|
||||||
|
backdrop: true
|
||||||
|
container: @container
|
||||||
|
.on
|
||||||
|
'show.bs.modal': @onShow
|
||||||
|
'shown.bs.modal': @onShown
|
||||||
|
'hidden.bs.modal': =>
|
||||||
|
@el.remove()
|
||||||
|
|
||||||
|
if @slide
|
||||||
|
@showSlide(@slide)
|
||||||
|
else
|
||||||
|
@showDiscoverDetails()
|
||||||
|
|
||||||
|
if @start
|
||||||
|
@[@start]()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
@html App.view('integration/exchange_wizard')()
|
||||||
|
|
||||||
|
save: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@callback(@wizardConfig)
|
||||||
|
@hide(e)
|
||||||
|
|
||||||
|
showSlide: (slide) =>
|
||||||
|
method = @slideMethod[slide]
|
||||||
|
if method && @[method]
|
||||||
|
@[method](true)
|
||||||
|
super
|
||||||
|
|
||||||
|
showDiscoverDetails: =>
|
||||||
|
@$('.js-discover input[name="user"]').val(@wizardConfig.user)
|
||||||
|
@$('.js-discover input[name="password"]').val(@wizardConfig.password)
|
||||||
|
|
||||||
|
showBindDetails: =>
|
||||||
|
@$('.js-bind input[name="endpoint"]').val(@wizardConfig.endpoint)
|
||||||
|
@$('.js-bind input[name="user"]').val(@wizardConfig.user)
|
||||||
|
@$('.js-bind input[name="password"]').val(@wizardConfig.password)
|
||||||
|
|
||||||
|
discover: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@showSlide('js-connect')
|
||||||
|
params = @formParam(e.target)
|
||||||
|
@ajax(
|
||||||
|
id: 'exchange_discover'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/autodiscover"
|
||||||
|
data: JSON.stringify(params)
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.result isnt 'ok'
|
||||||
|
@showSlide('js-discover')
|
||||||
|
@showAlert('js-discover', data.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
@wizardConfig.endpoint = data.endpoint
|
||||||
|
@wizardConfig.user = params.user
|
||||||
|
@wizardConfig.password = params.password
|
||||||
|
|
||||||
|
@showSlide('js-bind')
|
||||||
|
@showBindDetails()
|
||||||
|
|
||||||
|
error: (xhr, statusText, error) =>
|
||||||
|
detailsRaw = xhr.responseText
|
||||||
|
details = {}
|
||||||
|
if !_.isEmpty(detailsRaw)
|
||||||
|
details = JSON.parse(detailsRaw)
|
||||||
|
@showSlide('js-discover')
|
||||||
|
@showAlert('js-discover', details.error || 'Unable to perform backend.')
|
||||||
|
)
|
||||||
|
|
||||||
|
folders: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@showSlide('js-analyze')
|
||||||
|
params = @formParam(e.target)
|
||||||
|
@ajax(
|
||||||
|
id: 'exchange_folders'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/folders"
|
||||||
|
data: JSON.stringify(params)
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.result isnt 'ok'
|
||||||
|
@showSlide('js-bind')
|
||||||
|
@showAlert('js-bind', data.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
@wizardConfig.endpoint = params.endpoint
|
||||||
|
@wizardConfig.user = params.user
|
||||||
|
@wizardConfig.password = params.password
|
||||||
|
|
||||||
|
# update wizard data
|
||||||
|
@wizardConfig.wizardData = {}
|
||||||
|
@wizardConfig.wizardData.backend_folders = data.folders
|
||||||
|
|
||||||
|
@foldersShow()
|
||||||
|
|
||||||
|
error: (xhr, statusText, error) =>
|
||||||
|
detailsRaw = xhr.responseText
|
||||||
|
details = {}
|
||||||
|
if !_.isEmpty(detailsRaw)
|
||||||
|
details = JSON.parse(detailsRaw)
|
||||||
|
@showSlide('js-bind')
|
||||||
|
@showAlert('js-bind', details.error || 'Unable to perform backend.')
|
||||||
|
)
|
||||||
|
|
||||||
|
foldersShow: (alreadyShown) =>
|
||||||
|
@showSlide('js-folders') if !alreadyShown
|
||||||
|
@foldersSelect.html(@createColumnSelection('folders', @wizardConfig.wizardData.backend_folders, @wizardConfig.folders))
|
||||||
|
|
||||||
|
createColumnSelection: (name, options, selected) ->
|
||||||
|
return App.UiElement.column_select.render(
|
||||||
|
name: name
|
||||||
|
null: false
|
||||||
|
nulloption: false
|
||||||
|
options: options
|
||||||
|
value: selected
|
||||||
|
onChange: (val) =>
|
||||||
|
if val && val.length > 0
|
||||||
|
@foldersSelectSubmit.removeClass('is-disabled')
|
||||||
|
else
|
||||||
|
@foldersSelectSubmit.addClass('is-disabled')
|
||||||
|
)
|
||||||
|
|
||||||
|
mapping: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@showSlide('js-analyze')
|
||||||
|
params = @formParam(e.target)
|
||||||
|
|
||||||
|
# folders might be a single selection so we
|
||||||
|
# have to ensure that is an Array so the
|
||||||
|
# backend and frontend can handle it properly
|
||||||
|
if typeof params.folders is 'string'
|
||||||
|
params.folders = [ params.folders ]
|
||||||
|
|
||||||
|
# add login params
|
||||||
|
params.endpoint = @wizardConfig.endpoint
|
||||||
|
params.user = @wizardConfig.user
|
||||||
|
params.password = @wizardConfig.password
|
||||||
|
|
||||||
|
@ajax(
|
||||||
|
id: 'exchange_mapping'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/mapping"
|
||||||
|
data: JSON.stringify(params)
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.result isnt 'ok'
|
||||||
|
@showSlide('js-folders')
|
||||||
|
@showAlert('js-folders', data.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
attributes = {}
|
||||||
|
for key, value of App.User.attributesGet()
|
||||||
|
continue if key == 'login'
|
||||||
|
if (value.tag is 'input' || value.tag is 'richtext' || value.tag is 'textarea') && value.type isnt 'password'
|
||||||
|
attributes[key] = value.display || key
|
||||||
|
|
||||||
|
@wizardConfig.wizardData.attributes = attributes
|
||||||
|
@wizardConfig.folders = params.folders
|
||||||
|
@wizardConfig.wizardData.backend_attributes = data.attributes
|
||||||
|
|
||||||
|
@mappingShow()
|
||||||
|
|
||||||
|
error: (xhr, statusText, error) =>
|
||||||
|
detailsRaw = xhr.responseText
|
||||||
|
details = {}
|
||||||
|
if !_.isEmpty(detailsRaw)
|
||||||
|
details = JSON.parse(detailsRaw)
|
||||||
|
@showSlide('js-folders')
|
||||||
|
@showAlert('js-folders', details.error || 'Unable to perform backend.')
|
||||||
|
)
|
||||||
|
|
||||||
|
mappingShow: (alreadyShown) =>
|
||||||
|
@showSlide('js-mapping') if !alreadyShown
|
||||||
|
user_attribute_map = @wizardConfig.attributes
|
||||||
|
|
||||||
|
if _.isEmpty(user_attribute_map)
|
||||||
|
user_attribute_map =
|
||||||
|
given_name: 'firstname'
|
||||||
|
surname: 'lastname'
|
||||||
|
'email_addresses.emailaddress1': 'email'
|
||||||
|
'phone_numbers.businessphone': 'phone'
|
||||||
|
|
||||||
|
@userMappingForm.find('tbody tr.js-entry').remove()
|
||||||
|
@userMappingForm.find('tbody tr').before(@buildRowsUserMap(user_attribute_map))
|
||||||
|
|
||||||
|
mappingChange: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
# user map
|
||||||
|
attributes = @formParam(@userMappingForm)
|
||||||
|
for key in ['source', 'dest']
|
||||||
|
if !_.isArray(attributes[key])
|
||||||
|
attributes[key] = [attributes[key]]
|
||||||
|
attributes_local =
|
||||||
|
item_id: 'login'
|
||||||
|
length = attributes.source.length-1
|
||||||
|
for count in [0..length]
|
||||||
|
if attributes.source[count] && attributes.dest[count]
|
||||||
|
attributes_local[attributes.source[count]] = attributes.dest[count]
|
||||||
|
@wizardConfig.attributes = attributes_local
|
||||||
|
|
||||||
|
@tryShow()
|
||||||
|
|
||||||
|
buildRowsUserMap: (user_attribute_map) =>
|
||||||
|
|
||||||
|
# show static login row
|
||||||
|
userUidDisplayValue = @wizardConfig.wizardData.backend_attributes['item_id']
|
||||||
|
el = [
|
||||||
|
$(App.view('integration/ldap_user_attribute_row_read_only')(
|
||||||
|
key: userUidDisplayValue,
|
||||||
|
value: 'Login'
|
||||||
|
))
|
||||||
|
]
|
||||||
|
|
||||||
|
for source, dest of user_attribute_map
|
||||||
|
continue if source == 'item_id'
|
||||||
|
continue if !(source of @wizardConfig.wizardData.backend_attributes)
|
||||||
|
el.push @buildRowUserAttribute(source, dest)
|
||||||
|
el
|
||||||
|
|
||||||
|
buildRowUserAttribute: (source, dest) =>
|
||||||
|
el = $(App.view('integration/exchange_user_attribute_row')())
|
||||||
|
el.find('.js-exchangeAttribute').html(@createSelection('source', @wizardConfig.wizardData.backend_attributes, source))
|
||||||
|
el.find('.js-userAttribute').html(@createSelection('dest', @wizardConfig.wizardData.attributes, dest))
|
||||||
|
el
|
||||||
|
|
||||||
|
createSelection: (name, options, selected, unknown) ->
|
||||||
|
return App.UiElement.searchable_select.render(
|
||||||
|
name: name
|
||||||
|
multiple: false
|
||||||
|
limit: 100
|
||||||
|
null: false
|
||||||
|
nulloption: false
|
||||||
|
options: options
|
||||||
|
value: selected
|
||||||
|
unknown: unknown
|
||||||
|
class: 'form-control--small'
|
||||||
|
)
|
||||||
|
|
||||||
|
removeRow: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
$(e.target).closest('tr').remove()
|
||||||
|
|
||||||
|
addUserMapping: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@userMappingForm.find('tbody tr').last().before(@buildRowUserAttribute())
|
||||||
|
|
||||||
|
tryShow: (e) =>
|
||||||
|
if e
|
||||||
|
e.preventDefault()
|
||||||
|
@showSlide('js-analyze')
|
||||||
|
|
||||||
|
# create import job
|
||||||
|
@ajax(
|
||||||
|
id: 'exchange_try'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_try"
|
||||||
|
data: JSON.stringify(@wizardConfig)
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@tryLoop()
|
||||||
|
)
|
||||||
|
|
||||||
|
tryLoop: =>
|
||||||
|
@showSlide('js-dry')
|
||||||
|
@ajax(
|
||||||
|
id: 'jobs_try_index'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/integration/exchange/job_try"
|
||||||
|
data:
|
||||||
|
finished: true
|
||||||
|
processData: true
|
||||||
|
success: (job, status, xhr) =>
|
||||||
|
if job.result && (job.result.error || job.result.info)
|
||||||
|
@showSlide('js-error')
|
||||||
|
@showAlert('js-error', (job.result.error || job.result.info))
|
||||||
|
return
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
if job.result && _.keys(job.result).length > 0
|
||||||
|
@$('.js-preprogress').addClass('hide')
|
||||||
|
@$('.js-analyzing').removeClass('hide')
|
||||||
|
|
||||||
|
analized = 0
|
||||||
|
total = job.result.sum
|
||||||
|
for action, sum of job.result
|
||||||
|
continue if action == 'folders'
|
||||||
|
continue if action == 'sum'
|
||||||
|
analized += sum
|
||||||
|
|
||||||
|
@$('.js-progress progress').attr('value', analized)
|
||||||
|
@$('.js-progress progress').attr('max', total)
|
||||||
|
|
||||||
|
if job.finished_at
|
||||||
|
# reset initial state in case the back button is used
|
||||||
|
@$('.js-preprogress').removeClass('hide')
|
||||||
|
@$('.js-analyzing').addClass('hide')
|
||||||
|
|
||||||
|
@tryResult(job, total)
|
||||||
|
else
|
||||||
|
@delay(@tryLoop, 4000)
|
||||||
|
)
|
||||||
|
|
||||||
|
tryResult: (job, total) =>
|
||||||
|
@showSlide('js-try')
|
||||||
|
el = $(App.view('integration/exchange_summary')(job: job, countDone: total))
|
||||||
|
@el.find('.js-summary').html(el)
|
||||||
|
|
||||||
|
App.Config.set(
|
||||||
|
'IntegrationExchange'
|
||||||
|
{
|
||||||
|
name: 'Exchange'
|
||||||
|
target: '#system/integration/exchange'
|
||||||
|
description: 'Exchange integration for contacts management.'
|
||||||
|
controller: Index
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
'NavBarIntegrations'
|
||||||
|
)
|
|
@ -0,0 +1,94 @@
|
||||||
|
class Index extends App.ControllerIntegrationBase
|
||||||
|
featureIntegration: 'idoit_integration'
|
||||||
|
featureName: 'i-doit'
|
||||||
|
featureConfig: 'idoit_config'
|
||||||
|
description: [
|
||||||
|
['This service allows you to connect i-doit objects with Zammad.']
|
||||||
|
]
|
||||||
|
events:
|
||||||
|
'change .js-switch input': 'switch'
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
super
|
||||||
|
new Form(
|
||||||
|
el: @$('.js-form')
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.HttpLog(
|
||||||
|
el: @$('.js-log')
|
||||||
|
facility: 'idoit'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Form extends App.Controller
|
||||||
|
events:
|
||||||
|
'submit form': 'update'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
currentConfig: ->
|
||||||
|
App.Setting.get('idoit_config')
|
||||||
|
|
||||||
|
setConfig: (value) ->
|
||||||
|
App.Setting.set('idoit_config', value, {notify: true})
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
@config = @currentConfig()
|
||||||
|
|
||||||
|
@html App.view('integration/idoit')(
|
||||||
|
config: @config
|
||||||
|
)
|
||||||
|
|
||||||
|
update: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@config = @formParam(e.target)
|
||||||
|
@validateAndSave()
|
||||||
|
|
||||||
|
validateAndSave: =>
|
||||||
|
@ajax(
|
||||||
|
id: 'idoit'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/idoit/verify"
|
||||||
|
data: JSON.stringify(
|
||||||
|
method: 'cmdb.object_types'
|
||||||
|
api_token: @config.api_token
|
||||||
|
endpoint: @config.endpoint
|
||||||
|
client_id: @config.client_id
|
||||||
|
)
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.result is 'failed'
|
||||||
|
new App.ControllerErrorModal(
|
||||||
|
message: data.message
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
return
|
||||||
|
@setConfig(@config)
|
||||||
|
|
||||||
|
error: (data, status) =>
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
|
||||||
|
details = data.responseJSON || {}
|
||||||
|
@notify(
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to save!')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
class State
|
||||||
|
@current: ->
|
||||||
|
App.Setting.get('idoit_integration')
|
||||||
|
|
||||||
|
App.Config.set(
|
||||||
|
'IntegrationIdoit'
|
||||||
|
{
|
||||||
|
name: 'i-doit'
|
||||||
|
target: '#system/integration/idoit'
|
||||||
|
description: 'CMDB to document complex relations of your network components.'
|
||||||
|
controller: Index
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
'NavBarIntegrations'
|
||||||
|
)
|
|
@ -28,13 +28,20 @@ class Index extends App.ControllerIntegrationBase
|
||||||
super
|
super
|
||||||
active = @$('.js-switch input').prop('checked')
|
active = @$('.js-switch input').prop('checked')
|
||||||
if active
|
if active
|
||||||
@ajax(
|
job_start = =>
|
||||||
id: 'jobs_config'
|
@ajax(
|
||||||
type: 'POST'
|
id: 'jobs_config'
|
||||||
url: "#{@apiPath}/integration/ldap/job_start"
|
type: 'POST'
|
||||||
processData: true
|
url: "#{@apiPath}/integration/ldap/job_start"
|
||||||
success: (data, status, xhr) =>
|
processData: true
|
||||||
@render(true)
|
success: (data, status, xhr) =>
|
||||||
|
@render(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Delay.set(
|
||||||
|
job_start,
|
||||||
|
600,
|
||||||
|
'job_start',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Form extends App.Controller
|
class Form extends App.Controller
|
||||||
|
@ -61,8 +68,15 @@ class Form extends App.Controller
|
||||||
render: (top = false) =>
|
render: (top = false) =>
|
||||||
@config = @currentConfig()
|
@config = @currentConfig()
|
||||||
|
|
||||||
|
group_role_map = {}
|
||||||
|
for source, dests of @config.group_role_map
|
||||||
|
group_role_map[source] = dests.map((dest) ->
|
||||||
|
App.Role.find(dest).displayName()
|
||||||
|
).join ', '
|
||||||
|
|
||||||
@html App.view('integration/ldap')(
|
@html App.view('integration/ldap')(
|
||||||
config: @config
|
config: @config,
|
||||||
|
group_role_map: group_role_map
|
||||||
)
|
)
|
||||||
if _.isEmpty(@config)
|
if _.isEmpty(@config)
|
||||||
@$('.js-notConfigured').removeClass('hide')
|
@$('.js-notConfigured').removeClass('hide')
|
||||||
|
@ -84,6 +98,7 @@ class Form extends App.Controller
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
@render(true)
|
@render(true)
|
||||||
|
@lastResult()
|
||||||
)
|
)
|
||||||
|
|
||||||
startWizard: (e) =>
|
startWizard: (e) =>
|
||||||
|
@ -280,12 +295,13 @@ class ConnectionWizard extends App.WizardModal
|
||||||
|
|
||||||
option = ''
|
option = ''
|
||||||
options = {}
|
options = {}
|
||||||
for dn in data.attributes.namingcontexts
|
if !_.isEmpty data.attributes
|
||||||
options[dn] = dn
|
for dn in data.attributes.namingcontexts
|
||||||
if option is ''
|
options[dn] = dn
|
||||||
option = dn
|
if option is ''
|
||||||
if option.length > dn.length
|
option = dn
|
||||||
option = dn
|
if option.length > dn.length
|
||||||
|
option = dn
|
||||||
|
|
||||||
@wizardConfig.options = options
|
@wizardConfig.options = options
|
||||||
@wizardConfig.option = option
|
@wizardConfig.option = option
|
||||||
|
@ -419,7 +435,9 @@ class ConnectionWizard extends App.WizardModal
|
||||||
length = group_role_map.source.length-1
|
length = group_role_map.source.length-1
|
||||||
for count in [0..length]
|
for count in [0..length]
|
||||||
if group_role_map.source[count] && group_role_map.dest[count]
|
if group_role_map.source[count] && group_role_map.dest[count]
|
||||||
group_role_map_local[group_role_map.source[count]] = group_role_map.dest[count]
|
if !_.isArray(group_role_map_local[group_role_map.source[count]])
|
||||||
|
group_role_map_local[group_role_map.source[count]] = []
|
||||||
|
group_role_map_local[group_role_map.source[count]].push group_role_map.dest[count]
|
||||||
@wizardConfig.group_role_map = group_role_map_local
|
@wizardConfig.group_role_map = group_role_map_local
|
||||||
|
|
||||||
expertSettings = @formParam(@expertForm)
|
expertSettings = @formParam(@expertForm)
|
||||||
|
@ -454,8 +472,9 @@ class ConnectionWizard extends App.WizardModal
|
||||||
|
|
||||||
buildRowsGroupRole: (group_role_map) =>
|
buildRowsGroupRole: (group_role_map) =>
|
||||||
el = []
|
el = []
|
||||||
for source, dest of group_role_map
|
for source, dests of group_role_map
|
||||||
el.push @buildRowGroupRole(source, dest)
|
for dest in dests
|
||||||
|
el.push @buildRowGroupRole(source, dest)
|
||||||
el
|
el
|
||||||
|
|
||||||
buildRowGroupRole: (source, dest) =>
|
buildRowGroupRole: (source, dest) =>
|
||||||
|
|
|
@ -9,43 +9,7 @@ class Index extends App.ControllerSubContent
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
auth_provider_all = {
|
auth_provider_all = App.Config.get('auth_provider_all')
|
||||||
facebook: {
|
|
||||||
url: '/auth/facebook'
|
|
||||||
name: 'Facebook'
|
|
||||||
config: 'auth_facebook'
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
url: '/auth/twitter'
|
|
||||||
name: 'Twitter'
|
|
||||||
config: 'auth_twitter'
|
|
||||||
},
|
|
||||||
linkedin: {
|
|
||||||
url: '/auth/linkedin'
|
|
||||||
name: 'LinkedIn'
|
|
||||||
config: 'auth_linkedin'
|
|
||||||
},
|
|
||||||
github: {
|
|
||||||
url: '/auth/github'
|
|
||||||
name: 'GitHub'
|
|
||||||
config: 'auth_github'
|
|
||||||
},
|
|
||||||
gitlab: {
|
|
||||||
url: '/auth/gitlab'
|
|
||||||
name: 'GitLab'
|
|
||||||
config: 'auth_gitlab'
|
|
||||||
},
|
|
||||||
google_oauth2: {
|
|
||||||
url: '/auth/google_oauth2'
|
|
||||||
name: 'Google'
|
|
||||||
config: 'auth_google_oauth2'
|
|
||||||
},
|
|
||||||
oauth2: {
|
|
||||||
url: '/auth/oauth2'
|
|
||||||
name: 'OAuth2'
|
|
||||||
config: 'auth_oauth2'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
auth_providers = {}
|
auth_providers = {}
|
||||||
for key, provider of auth_provider_all
|
for key, provider of auth_provider_all
|
||||||
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
||||||
|
@ -90,3 +54,45 @@ class Index extends App.ControllerSubContent
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index, permission: ['user_preferences.linked_accounts'] }, 'NavBarProfile')
|
App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index, permission: ['user_preferences.linked_accounts'] }, 'NavBarProfile')
|
||||||
|
App.Config.set('auth_provider_all', {
|
||||||
|
facebook:
|
||||||
|
url: '/auth/facebook'
|
||||||
|
name: 'Facebook'
|
||||||
|
config: 'auth_facebook'
|
||||||
|
class: 'facebook'
|
||||||
|
twitter:
|
||||||
|
url: '/auth/twitter'
|
||||||
|
name: 'Twitter'
|
||||||
|
config: 'auth_twitter'
|
||||||
|
class: 'twitter'
|
||||||
|
linkedin:
|
||||||
|
url: '/auth/linkedin'
|
||||||
|
name: 'LinkedIn'
|
||||||
|
config: 'auth_linkedin'
|
||||||
|
class: 'linkedin'
|
||||||
|
github:
|
||||||
|
url: '/auth/github'
|
||||||
|
name: 'GitHub'
|
||||||
|
config: 'auth_github'
|
||||||
|
class: 'github'
|
||||||
|
gitlab:
|
||||||
|
url: '/auth/gitlab'
|
||||||
|
name: 'GitLab'
|
||||||
|
config: 'auth_gitlab'
|
||||||
|
class: 'gitlab'
|
||||||
|
microsoft_office365:
|
||||||
|
url: '/auth/microsoft_office365'
|
||||||
|
name: 'Office 365'
|
||||||
|
config: 'auth_microsoft_office365'
|
||||||
|
class: 'office365'
|
||||||
|
google_oauth2:
|
||||||
|
url: '/auth/google_oauth2'
|
||||||
|
name: 'Google'
|
||||||
|
config: 'auth_google_oauth2'
|
||||||
|
class: 'google'
|
||||||
|
oauth2:
|
||||||
|
url: '/auth/oauth2'
|
||||||
|
name: 'OAuth2'
|
||||||
|
config: 'auth_oauth2'
|
||||||
|
class: 'oauth2'
|
||||||
|
})
|
||||||
|
|
|
@ -75,13 +75,14 @@ class Index extends App.ControllerSubContent
|
||||||
groups = []
|
groups = []
|
||||||
group_ids = @Session.get('group_ids')
|
group_ids = @Session.get('group_ids')
|
||||||
if group_ids
|
if group_ids
|
||||||
for group_id in group_ids
|
for group_id, access of group_ids
|
||||||
group = App.Group.find(group_id)
|
if _.contains(access, 'full')
|
||||||
groups.push group
|
group = App.Group.find(group_id)
|
||||||
if !user_group_config
|
groups.push group
|
||||||
if !config['group_ids']
|
if !user_group_config
|
||||||
config['group_ids'] = []
|
if !config['group_ids']
|
||||||
config['group_ids'].push group_id.toString()
|
config['group_ids'] = []
|
||||||
|
config['group_ids'].push group_id.toString()
|
||||||
|
|
||||||
for sound in @sounds
|
for sound in @sounds
|
||||||
sound.selected = sound.file is App.OnlineNotification.soundFile() ? true : false
|
sound.selected = sound.file is App.OnlineNotification.soundFile() ? true : false
|
||||||
|
@ -90,7 +91,7 @@ class Index extends App.ControllerSubContent
|
||||||
groups: groups
|
groups: groups
|
||||||
config: config
|
config: config
|
||||||
sounds: @sounds
|
sounds: @sounds
|
||||||
notification_sound_enabled: App.OnlineNotification.soundEnabled()
|
notificationSoundEnabled: App.OnlineNotification.soundEnabled()
|
||||||
|
|
||||||
update: (e) =>
|
update: (e) =>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
class Index extends App.ControllerSubContent
|
||||||
|
requiredPermission: 'user_preferences.out_of_office+ticket.agent'
|
||||||
|
header: 'Out of Office'
|
||||||
|
events:
|
||||||
|
'submit form': 'submit'
|
||||||
|
'click .js-disabled': 'disable'
|
||||||
|
'click .js-enable': 'enable'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
user = @Session.get()
|
||||||
|
if !@localData
|
||||||
|
@localData =
|
||||||
|
out_of_office: user.out_of_office
|
||||||
|
out_of_office_start_at: user.out_of_office_start_at
|
||||||
|
out_of_office_end_at: user.out_of_office_end_at
|
||||||
|
out_of_office_replacement_id: user.out_of_office_replacement_id
|
||||||
|
out_of_office_replacement_id_completion: user.preferences.out_of_office_replacement_id_completion
|
||||||
|
out_of_office_text: user.preferences.out_of_office_text
|
||||||
|
form = $(App.view('profile/out_of_office')(
|
||||||
|
user: user
|
||||||
|
localData: @localData
|
||||||
|
placeholder: App.User.outOfOfficeTextPlaceholder()
|
||||||
|
))
|
||||||
|
|
||||||
|
dateStart = new App.ControllerForm(
|
||||||
|
model:
|
||||||
|
configure_attributes:
|
||||||
|
[
|
||||||
|
name: 'out_of_office_start_at'
|
||||||
|
display: ''
|
||||||
|
tag: 'date'
|
||||||
|
past: false
|
||||||
|
future: true
|
||||||
|
null: false
|
||||||
|
]
|
||||||
|
noFieldset: true
|
||||||
|
params: @localData
|
||||||
|
)
|
||||||
|
form.find('.js-startDate').html(dateStart.form)
|
||||||
|
|
||||||
|
dateEnd = new App.ControllerForm(
|
||||||
|
model:
|
||||||
|
configure_attributes:
|
||||||
|
[
|
||||||
|
name: 'out_of_office_end_at'
|
||||||
|
display: ''
|
||||||
|
tag: 'date'
|
||||||
|
past: false
|
||||||
|
future: true
|
||||||
|
null: false
|
||||||
|
]
|
||||||
|
noFieldset: true
|
||||||
|
params: @localData
|
||||||
|
)
|
||||||
|
form.find('.js-endDate').html(dateEnd.form)
|
||||||
|
|
||||||
|
agentList = new App.ControllerForm(
|
||||||
|
model:
|
||||||
|
configure_attributes:
|
||||||
|
[
|
||||||
|
name: 'out_of_office_replacement_id'
|
||||||
|
display: ''
|
||||||
|
relation: 'User'
|
||||||
|
tag: 'user_autocompletion'
|
||||||
|
autocapitalize: false
|
||||||
|
multiple: false
|
||||||
|
limit: 30
|
||||||
|
minLengt: 2
|
||||||
|
placeholder: 'Enter Person or Organization/Company'
|
||||||
|
null: false
|
||||||
|
translate: false
|
||||||
|
disableCreateObject: true
|
||||||
|
value: @localData
|
||||||
|
]
|
||||||
|
noFieldset: true
|
||||||
|
params: @localData
|
||||||
|
)
|
||||||
|
form.find('.js-recipientDropdown').html(agentList.form)
|
||||||
|
if @localData.out_of_office is true
|
||||||
|
form.find('.js-disabled').removeClass('is-disabled')
|
||||||
|
#form.find('.js-enable').addClass('is-disabled')
|
||||||
|
else
|
||||||
|
form.find('.js-disabled').addClass('is-disabled')
|
||||||
|
#form.find('.js-enable').removeClass('is-disabled')
|
||||||
|
@html(form)
|
||||||
|
|
||||||
|
enable: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
params = @formParam(e.target)
|
||||||
|
params.out_of_office = true
|
||||||
|
@store(e, params)
|
||||||
|
|
||||||
|
disable: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
params = @formParam(e.target)
|
||||||
|
params.out_of_office = false
|
||||||
|
@store(e, params)
|
||||||
|
|
||||||
|
submit: (e, params) =>
|
||||||
|
e.preventDefault()
|
||||||
|
params = @formParam(e.target)
|
||||||
|
@store(e, params)
|
||||||
|
|
||||||
|
store: (e, params) =>
|
||||||
|
@formDisable(e)
|
||||||
|
for key, value of params
|
||||||
|
@localData[key] = value
|
||||||
|
App.Ajax.request(
|
||||||
|
id: 'user_out_of_office'
|
||||||
|
type: 'PUT'
|
||||||
|
url: "#{@apiPath}/users/out_of_office"
|
||||||
|
data: JSON.stringify(params)
|
||||||
|
processData: true
|
||||||
|
success: @success
|
||||||
|
error: @error
|
||||||
|
)
|
||||||
|
|
||||||
|
success: (data) =>
|
||||||
|
if data.message is 'ok'
|
||||||
|
@render()
|
||||||
|
@notify(
|
||||||
|
type: 'success'
|
||||||
|
msg: App.i18n.translateContent('Successfully!')
|
||||||
|
timeout: 1000
|
||||||
|
)
|
||||||
|
else
|
||||||
|
if data.notice
|
||||||
|
@notify
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent(data.notice[0], data.notice[1])
|
||||||
|
removeAll: true
|
||||||
|
else
|
||||||
|
@notify
|
||||||
|
type: 'error'
|
||||||
|
msg: 'Please contact your administrator.'
|
||||||
|
removeAll: true
|
||||||
|
@formEnable( @$('form') )
|
||||||
|
|
||||||
|
error: (xhr, status, error) =>
|
||||||
|
@formEnable( @$('form') )
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
data = JSON.parse(xhr.responseText)
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
if xhr.status is 401 || error is 'Unauthorized'
|
||||||
|
message = '» ' + App.i18n.translateInline('Unauthorized') + ' «'
|
||||||
|
else if xhr.status is 404 || error is 'Not Found'
|
||||||
|
message = '» ' + App.i18n.translateInline('Not Found') + ' «'
|
||||||
|
else if data.error
|
||||||
|
message = App.i18n.translateInline(data.error)
|
||||||
|
else
|
||||||
|
message = '» ' + App.i18n.translateInline('Error') + ' «'
|
||||||
|
@notify
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent(message)
|
||||||
|
removeAll: true
|
||||||
|
|
||||||
|
App.Config.set('OutOfOffice', { prio: 2800, name: 'Out of Office', parent: '#profile', target: '#profile/out_of_office', permission: ['user_preferences.out_of_office+ticket.agent'], controller: Index }, 'NavBarProfile')
|
|
@ -2,7 +2,7 @@ class App.SettingsAreaProxy extends App.Controller
|
||||||
events:
|
events:
|
||||||
'submit form': 'update'
|
'submit form': 'update'
|
||||||
'click .js-submit': 'update'
|
'click .js-submit': 'update'
|
||||||
'click .js-test': 'test2'
|
'click .js-test': 'testConnection'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@ -14,20 +14,21 @@ class App.SettingsAreaProxy extends App.Controller
|
||||||
proxy: App.Setting.get('proxy')
|
proxy: App.Setting.get('proxy')
|
||||||
proxy_username: App.Setting.get('proxy_username')
|
proxy_username: App.Setting.get('proxy_username')
|
||||||
proxy_password: App.Setting.get('proxy_password')
|
proxy_password: App.Setting.get('proxy_password')
|
||||||
|
proxy_no: App.Setting.get('proxy_no')
|
||||||
)
|
)
|
||||||
|
|
||||||
update: (e) =>
|
update: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@formDisable(e)
|
@formDisable(e)
|
||||||
params = @formParam(e)
|
params = @formParam(e)
|
||||||
console.log('params', params)
|
|
||||||
App.Setting.set('proxy', params.proxy)
|
App.Setting.set('proxy', params.proxy)
|
||||||
App.Setting.set('proxy_username', params.proxy_username)
|
App.Setting.set('proxy_username', params.proxy_username)
|
||||||
App.Setting.set('proxy_password', params.proxy_password)
|
App.Setting.set('proxy_password', params.proxy_password)
|
||||||
|
App.Setting.set('proxy_no', params.proxy_no)
|
||||||
@formEnable(e)
|
@formEnable(e)
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
test2: (e) =>
|
testConnection: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
params = @formParam(e)
|
params = @formParam(e)
|
||||||
@ajax(
|
@ajax(
|
||||||
|
|
|
@ -15,7 +15,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
attribute: attribute
|
attribute: attribute
|
||||||
params: params
|
params: params
|
||||||
))
|
))
|
||||||
@[localParams.data_type](element, localParams, params)
|
@[localParams.data_type](element, localParams, params, attribute)
|
||||||
localItem.find('.js-dataMap').html(element)
|
localItem.find('.js-dataMap').html(element)
|
||||||
localItem.find('.js-dataScreens').html(@dataScreens(attribute, localParams, params))
|
localItem.find('.js-dataScreens').html(@dataScreens(attribute, localParams, params))
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
date: 'Date'
|
date: 'Date'
|
||||||
input: 'Text'
|
input: 'Text'
|
||||||
select: 'Select'
|
select: 'Select'
|
||||||
|
tree_select: 'Tree Select'
|
||||||
boolean: 'Boolean'
|
boolean: 'Boolean'
|
||||||
integer: 'Integer'
|
integer: 'Integer'
|
||||||
|
|
||||||
|
@ -81,9 +82,9 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
view:
|
view:
|
||||||
shown: true
|
shown: true
|
||||||
invite_customer:
|
invite_customer:
|
||||||
show: false
|
shown: false
|
||||||
required: false
|
required: false
|
||||||
'admin.group':
|
'admin.user':
|
||||||
create:
|
create:
|
||||||
shown: true
|
shown: true
|
||||||
required: false
|
required: false
|
||||||
|
@ -93,10 +94,10 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
view:
|
view:
|
||||||
shown: true
|
shown: true
|
||||||
invite_agent:
|
invite_agent:
|
||||||
show: false
|
shown: false
|
||||||
required: false
|
required: false
|
||||||
invite_customer:
|
invite_customer:
|
||||||
show: false
|
shown: false
|
||||||
required: false
|
required: false
|
||||||
Organization:
|
Organization:
|
||||||
'ticket.customer':
|
'ticket.customer':
|
||||||
|
@ -111,7 +112,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
required: false
|
required: false
|
||||||
view:
|
view:
|
||||||
shown: true
|
shown: true
|
||||||
'admin.group':
|
'admin.organization':
|
||||||
create:
|
create:
|
||||||
shown: true
|
shown: true
|
||||||
required: false
|
required: false
|
||||||
|
@ -308,6 +309,69 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||||
lastSelected = value
|
lastSelected = value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@buildRow: (element, child, level = 0, parentElement) ->
|
||||||
|
newRow = element.find('.js-template').clone().removeClass('js-template')
|
||||||
|
newRow.find('.js-key').attr('level', level)
|
||||||
|
newRow.find('.js-key').val(child.name)
|
||||||
|
newRow.find('td').first().css('padding-left', "#{(level * 20) + 10}px")
|
||||||
|
if level is 5
|
||||||
|
newRow.find('.js-addChild').addClass('hide')
|
||||||
|
|
||||||
|
if parentElement
|
||||||
|
parentElement.after(newRow)
|
||||||
|
return
|
||||||
|
|
||||||
|
element.find('.js-treeTable').append(newRow)
|
||||||
|
if child.children
|
||||||
|
for subChild in child.children
|
||||||
|
@buildRow(element, subChild, level + 1)
|
||||||
|
|
||||||
|
@tree_select: (item, localParams, params, attribute) ->
|
||||||
|
params.data_option ||= {}
|
||||||
|
params.data_option.options ||= []
|
||||||
|
if _.isEmpty(params.data_option.options)
|
||||||
|
@buildRow(item, {})
|
||||||
|
else
|
||||||
|
for child in params.data_option.options
|
||||||
|
@buildRow(item, child)
|
||||||
|
|
||||||
|
item.on('click', '.js-addRow', (e) =>
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
addRow = $(e.currentTarget).closest('tr')
|
||||||
|
level = parseInt(addRow.find('.js-key').attr('level'))
|
||||||
|
@buildRow(item, {}, level, addRow)
|
||||||
|
)
|
||||||
|
|
||||||
|
item.on('click', '.js-addChild', (e) =>
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
addRow = $(e.currentTarget).closest('tr')
|
||||||
|
level = parseInt(addRow.find('.js-key').attr('level')) + 1
|
||||||
|
@buildRow(item, {}, level, addRow)
|
||||||
|
)
|
||||||
|
|
||||||
|
item.on('click', '.js-remove', (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPro
|
||||||
|
element = $(e.target).closest('tr')
|
||||||
|
level = parseInt(element.find('.js-key').attr('level'))
|
||||||
|
subElements = 0
|
||||||
|
nextElement = element
|
||||||
|
elementsToDelete = [element]
|
||||||
|
loop
|
||||||
|
nextElement = nextElement.next()
|
||||||
|
break if !nextElement.get(0)
|
||||||
|
nextLevel = parseInt(nextElement.find('.js-key').attr('level'))
|
||||||
|
break if nextLevel <= level
|
||||||
|
subElements += 1
|
||||||
|
elementsToDelete.push nextElement
|
||||||
|
return if subElements isnt 0 && !confirm("Delete #{subElements} sub elements?")
|
||||||
|
for element in elementsToDelete
|
||||||
|
element.remove()
|
||||||
|
)
|
||||||
|
|
||||||
@boolean: (item, localParams, params) ->
|
@boolean: (item, localParams, params) ->
|
||||||
lastSelected = undefined
|
lastSelected = undefined
|
||||||
item.on('click', '.js-selected', (e) ->
|
item.on('click', '.js-selected', (e) ->
|
||||||
|
|
|
@ -4,10 +4,25 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
|
||||||
|
|
||||||
permissions = App.Permission.search(sortBy: 'name')
|
permissions = App.Permission.search(sortBy: 'name')
|
||||||
|
|
||||||
|
# get selectable groups and selected groups
|
||||||
|
groups = []
|
||||||
|
groupsSelected = {}
|
||||||
|
groupsRaw = App.Group.search(sortBy: 'name')
|
||||||
|
for group in groupsRaw
|
||||||
|
if group.active
|
||||||
|
groups.push group
|
||||||
|
if params.group_ids
|
||||||
|
for group_id in params.group_ids
|
||||||
|
if group_id.toString() is group.id.toString()
|
||||||
|
groupsSelected[group.id] = true
|
||||||
|
|
||||||
item = $( App.view('generic/permission')(
|
item = $( App.view('generic/permission')(
|
||||||
attribute: attribute
|
attribute: attribute
|
||||||
params: params
|
params: params
|
||||||
permissions: permissions
|
permissions: permissions
|
||||||
|
groups: groups
|
||||||
|
groupsSelected: groupsSelected
|
||||||
|
groupAccesses: App.Group.accesses()
|
||||||
) )
|
) )
|
||||||
|
|
||||||
# show/hide trees
|
# show/hide trees
|
||||||
|
|
|
@ -20,7 +20,7 @@ class App.UiElement.postmaster_set
|
||||||
name: 'Customer'
|
name: 'Customer'
|
||||||
relation: 'User'
|
relation: 'User'
|
||||||
tag: 'user_autocompletion'
|
tag: 'user_autocompletion'
|
||||||
disableCreateUser: true
|
disableCreateObject: true
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
value: 'group_id'
|
value: 'group_id'
|
||||||
|
@ -32,7 +32,7 @@ class App.UiElement.postmaster_set
|
||||||
name: 'Owner'
|
name: 'Owner'
|
||||||
relation: 'User'
|
relation: 'User'
|
||||||
tag: 'user_autocompletion'
|
tag: 'user_autocompletion'
|
||||||
disableCreateUser: true
|
disableCreateObject: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
article:
|
article:
|
||||||
|
|
|
@ -9,24 +9,24 @@ class App.UiElement.searchable_select extends App.UiElement.ApplicationUiElement
|
||||||
attribute.multiple = ''
|
attribute.multiple = ''
|
||||||
|
|
||||||
# build options list based on config
|
# build options list based on config
|
||||||
@getConfigOptionList( attribute, params )
|
@getConfigOptionList(attribute, params)
|
||||||
|
|
||||||
# build options list based on relation
|
# build options list based on relation
|
||||||
@getRelationOptionList( attribute, params )
|
@getRelationOptionList(attribute, params)
|
||||||
|
|
||||||
# add null selection if needed
|
# add null selection if needed
|
||||||
@addNullOption( attribute, params )
|
@addNullOption(attribute, params)
|
||||||
|
|
||||||
# sort attribute.options
|
# sort attribute.options
|
||||||
@sortOptions( attribute, params )
|
@sortOptions(attribute, params)
|
||||||
|
|
||||||
# finde selected/checked item of list
|
# finde selected/checked item of list
|
||||||
@selectedOptions( attribute, params )
|
@selectedOptions(attribute, params)
|
||||||
|
|
||||||
# disable item of list
|
# disable item of list
|
||||||
@disabledOptions( attribute, params )
|
@disabledOptions(attribute, params)
|
||||||
|
|
||||||
# filter attributes
|
# filter attributes
|
||||||
@filterOption( attribute, params )
|
@filterOption(attribute, params)
|
||||||
|
|
||||||
new App.SearchableSelect( attribute: attribute ).element()
|
new App.SearchableSelect(attribute: attribute).element()
|
||||||
|
|
|
@ -33,14 +33,15 @@ class App.UiElement.ticket_perform_action
|
||||||
elements["#{groupKey}.#{config.name}"] = config
|
elements["#{groupKey}.#{config.name}"] = config
|
||||||
|
|
||||||
# add ticket deletion action
|
# add ticket deletion action
|
||||||
elements['ticket.action'] =
|
if attribute.ticket_delete
|
||||||
name: 'action'
|
elements['ticket.action'] =
|
||||||
display: 'Action'
|
name: 'action'
|
||||||
tag: 'select'
|
display: 'Action'
|
||||||
null: false
|
tag: 'select'
|
||||||
translate: true
|
null: false
|
||||||
options:
|
translate: true
|
||||||
delete: 'delete'
|
options:
|
||||||
|
delete: 'Delete'
|
||||||
|
|
||||||
[defaults, groups, elements]
|
[defaults, groups, elements]
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ class App.UiElement.ticket_selector
|
||||||
elementRow = $(e.target).closest('.js-filterElement')
|
elementRow = $(e.target).closest('.js-filterElement')
|
||||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||||
return if !groupAndAttribute
|
return if !groupAndAttribute
|
||||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute, false)
|
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||||
)
|
)
|
||||||
|
|
||||||
# bind for preview
|
# bind for preview
|
||||||
|
@ -244,9 +244,9 @@ class App.UiElement.ticket_selector
|
||||||
if groupAndAttribute
|
if groupAndAttribute
|
||||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||||
|
|
||||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute, true)
|
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||||
|
|
||||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute, buildValue) ->
|
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||||
|
|
||||||
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||||
|
@ -284,9 +284,9 @@ class App.UiElement.ticket_selector
|
||||||
|
|
||||||
elementRow.find('.js-operator select').replaceWith(selection)
|
elementRow.find('.js-operator select').replaceWith(selection)
|
||||||
|
|
||||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute, buildValue)
|
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||||
|
|
||||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig, buildValue = true) ->
|
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
|
||||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||||
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||||
|
|
||||||
|
@ -318,7 +318,6 @@ class App.UiElement.ticket_selector
|
||||||
if !preCondition
|
if !preCondition
|
||||||
elementRow.find('.js-preCondition select').html('')
|
elementRow.find('.js-preCondition select').html('')
|
||||||
elementRow.find('.js-preCondition').addClass('hide')
|
elementRow.find('.js-preCondition').addClass('hide')
|
||||||
return if !buildValue
|
|
||||||
toggleValue()
|
toggleValue()
|
||||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||||
return
|
return
|
||||||
|
@ -351,7 +350,6 @@ class App.UiElement.ticket_selector
|
||||||
toggleValue()
|
toggleValue()
|
||||||
)
|
)
|
||||||
|
|
||||||
return if !buildValue
|
|
||||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||||
toggleValue()
|
toggleValue()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# coffeelint: disable=camel_case_classes
|
||||||
|
class App.UiElement.tree_select extends App.UiElement.ApplicationUiElement
|
||||||
|
@optionsSelect: (children, value) ->
|
||||||
|
return if !children
|
||||||
|
for child in children
|
||||||
|
if child.value is value
|
||||||
|
child.selected = true
|
||||||
|
if child.children
|
||||||
|
@optionsSelect(child.children, value)
|
||||||
|
|
||||||
|
@render: (attribute, params) ->
|
||||||
|
|
||||||
|
# set multiple option
|
||||||
|
if attribute.multiple
|
||||||
|
attribute.multiple = 'multiple'
|
||||||
|
else
|
||||||
|
attribute.multiple = ''
|
||||||
|
|
||||||
|
# build options list based on config
|
||||||
|
@getConfigOptionList(attribute, params)
|
||||||
|
|
||||||
|
# build options list based on relation
|
||||||
|
@getRelationOptionList(attribute, params)
|
||||||
|
|
||||||
|
# add null selection if needed
|
||||||
|
@addNullOption(attribute, params)
|
||||||
|
|
||||||
|
# sort attribute.options
|
||||||
|
@sortOptions(attribute, params)
|
||||||
|
|
||||||
|
# finde selected/checked item of list
|
||||||
|
if attribute.options
|
||||||
|
@optionsSelect(attribute.options, attribute.value)
|
||||||
|
|
||||||
|
# disable item of list
|
||||||
|
@disabledOptions(attribute, params)
|
||||||
|
|
||||||
|
# filter attributes
|
||||||
|
@filterOption(attribute, params)
|
||||||
|
|
||||||
|
new App.SearchableSelect(attribute: attribute).element()
|
|
@ -2,5 +2,5 @@
|
||||||
class App.UiElement.user_autocompletion_search
|
class App.UiElement.user_autocompletion_search
|
||||||
@render: (attributeOrig, params = {}) ->
|
@render: (attributeOrig, params = {}) ->
|
||||||
attribute = _.clone(attributeOrig)
|
attribute = _.clone(attributeOrig)
|
||||||
attribute.disableCreateUser = true
|
attribute.disableCreateObject = true
|
||||||
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
|
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
|
||||||
|
|
|
@ -72,6 +72,7 @@ class App.UiElement.user_permission
|
||||||
rolesSelected: rolesSelected
|
rolesSelected: rolesSelected
|
||||||
groupsSelected: groupsSelected
|
groupsSelected: groupsSelected
|
||||||
hideGroups: hideGroups
|
hideGroups: hideGroups
|
||||||
|
groupAccesses: App.Group.accesses()
|
||||||
) )
|
) )
|
||||||
|
|
||||||
# if customer, remove admin and agent
|
# if customer, remove admin and agent
|
||||||
|
@ -105,7 +106,7 @@ class App.UiElement.user_permission
|
||||||
|
|
||||||
# select groups if only one is available
|
# select groups if only one is available
|
||||||
if hideGroups
|
if hideGroups
|
||||||
item.find('.js-groupList [name=group_ids]').prop('checked', false)
|
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', false)
|
||||||
return
|
return
|
||||||
|
|
||||||
# if role with groups plugin is selected, show group selection
|
# if role with groups plugin is selected, show group selection
|
||||||
|
@ -114,7 +115,7 @@ class App.UiElement.user_permission
|
||||||
|
|
||||||
# select groups if only one is available
|
# select groups if only one is available
|
||||||
if hideGroups
|
if hideGroups
|
||||||
item.find('.js-groupList [name=group_ids]').prop('checked', true)
|
item.find('.js-groupList .js-groupListItem[value=full]').prop('checked', true)
|
||||||
|
|
||||||
for trigger in triggers
|
for trigger in triggers
|
||||||
trigger.trigger('change')
|
trigger.trigger('change')
|
||||||
|
|
|
@ -9,6 +9,7 @@ class App.TicketCreate extends App.Controller
|
||||||
|
|
||||||
constructor: (params) ->
|
constructor: (params) ->
|
||||||
super
|
super
|
||||||
|
@sidebarState = {}
|
||||||
|
|
||||||
# define default type
|
# define default type
|
||||||
@default_type = 'phone-in'
|
@default_type = 'phone-in'
|
||||||
|
@ -91,6 +92,8 @@ class App.TicketCreate extends App.Controller
|
||||||
else
|
else
|
||||||
@$('[name="cc"]').closest('.form-group').addClass('hide')
|
@$('[name="cc"]').closest('.form-group').addClass('hide')
|
||||||
|
|
||||||
|
App.TaskManager.touch(@task_key)
|
||||||
|
|
||||||
meta: =>
|
meta: =>
|
||||||
text = ''
|
text = ''
|
||||||
if @articleAttributes
|
if @articleAttributes
|
||||||
|
@ -99,10 +102,10 @@ class App.TicketCreate extends App.Controller
|
||||||
if title
|
if title
|
||||||
text = "#{text}: #{title}"
|
text = "#{text}: #{title}"
|
||||||
meta =
|
meta =
|
||||||
url: @url()
|
url: @url()
|
||||||
head: text
|
head: text
|
||||||
title: text
|
title: text
|
||||||
id: @id
|
id: @id
|
||||||
iconClass: 'pen'
|
iconClass: 'pen'
|
||||||
|
|
||||||
url: =>
|
url: =>
|
||||||
|
@ -228,7 +231,7 @@ class App.TicketCreate extends App.Controller
|
||||||
type = @$('[name="formSenderType"]').val()
|
type = @$('[name="formSenderType"]').val()
|
||||||
|
|
||||||
if signature isnt undefined && signature.body && type is 'email-out'
|
if signature isnt undefined && signature.body && type is 'email-out'
|
||||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get() })
|
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), config: App.Config.all() })
|
||||||
|
|
||||||
body = @$('[data-name=body]')
|
body = @$('[data-name=body]')
|
||||||
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
||||||
|
@ -330,27 +333,47 @@ class App.TicketCreate extends App.Controller
|
||||||
# show text module UI
|
# show text module UI
|
||||||
@textModule = new App.WidgetTextModule(
|
@textModule = new App.WidgetTextModule(
|
||||||
el: @$('[data-name="body"]').parent()
|
el: @$('[data-name="body"]').parent()
|
||||||
)
|
data:
|
||||||
|
config: App.Config.all()
|
||||||
new Sidebar(
|
user: App.Session.get()
|
||||||
el: @sidebar
|
|
||||||
params: @formDefault
|
|
||||||
textModule: @textModule
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$('#tags').tokenfield()
|
$('#tags').tokenfield()
|
||||||
|
|
||||||
|
@sidebarWidget = new App.TicketCreateSidebar(
|
||||||
|
el: @sidebar
|
||||||
|
params: @formDefault
|
||||||
|
sidebarState: @sidebarState
|
||||||
|
task_key: @task_key
|
||||||
|
query: @query
|
||||||
|
)
|
||||||
|
|
||||||
|
if @formDefault.customer_id
|
||||||
|
callback = (customer) =>
|
||||||
|
@localUserInfoCallback(@formDefault, customer)
|
||||||
|
App.User.full(@formDefault.customer_id, callback)
|
||||||
|
|
||||||
# update taskbar with new meta data
|
# update taskbar with new meta data
|
||||||
App.TaskManager.touch(@task_key)
|
App.TaskManager.touch(@task_key)
|
||||||
|
|
||||||
localUserInfo: (e) =>
|
localUserInfo: (e) =>
|
||||||
|
return if !@sidebarWidget
|
||||||
params = App.ControllerForm.params($(e.target).closest('form'))
|
params = App.ControllerForm.params($(e.target).closest('form'))
|
||||||
|
|
||||||
new Sidebar(
|
if params.customer_id
|
||||||
el: @sidebar
|
callback = (customer) =>
|
||||||
params: params
|
@localUserInfoCallback(params, customer)
|
||||||
textModule: @textModule
|
App.User.full(params.customer_id, callback)
|
||||||
|
return
|
||||||
|
@localUserInfoCallback(params)
|
||||||
|
|
||||||
|
localUserInfoCallback: (params, customer = {}) =>
|
||||||
|
@sidebarWidget.render(params)
|
||||||
|
@textModule.reload(
|
||||||
|
config: App.Config.all()
|
||||||
|
user: App.Session.get()
|
||||||
|
ticket:
|
||||||
|
customer: customer
|
||||||
)
|
)
|
||||||
|
|
||||||
cancel: (e) ->
|
cancel: (e) ->
|
||||||
|
@ -475,11 +498,16 @@ class App.TicketCreate extends App.Controller
|
||||||
# scroll to top
|
# scroll to top
|
||||||
ui.scrollTo()
|
ui.scrollTo()
|
||||||
|
|
||||||
|
# add sidebar params
|
||||||
|
if ui.sidebarWidget
|
||||||
|
ui.sidebarWidget.commit(ticket_id: @id)
|
||||||
|
|
||||||
# access to group
|
# access to group
|
||||||
group_ids = _.map(App.Session.get('group_ids'), (id) -> id.toString())
|
for group_id, access of App.Session.get('group_ids')
|
||||||
if group_ids && _.contains(group_ids, @group_id.toString())
|
if @group_id.toString() is group_id.toString()
|
||||||
ui.navigate "#ticket/zoom/#{@id}"
|
if _.contains(access, 'read') || _.contains(access, 'full')
|
||||||
return
|
ui.navigate "#ticket/zoom/#{@id}"
|
||||||
|
return
|
||||||
|
|
||||||
# if not, show start screen
|
# if not, show start screen
|
||||||
ui.navigate '#'
|
ui.navigate '#'
|
||||||
|
@ -494,114 +522,6 @@ class App.TicketCreate extends App.Controller
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Sidebar extends App.Controller
|
|
||||||
constructor: ->
|
|
||||||
super
|
|
||||||
|
|
||||||
# load user
|
|
||||||
if @params['customer_id']
|
|
||||||
App.User.full(@params['customer_id'], @render)
|
|
||||||
return
|
|
||||||
|
|
||||||
# render ui
|
|
||||||
@render()
|
|
||||||
|
|
||||||
render: (user) =>
|
|
||||||
|
|
||||||
items = []
|
|
||||||
if user
|
|
||||||
|
|
||||||
showCustomer = (el) =>
|
|
||||||
# update text module UI
|
|
||||||
if @textModule
|
|
||||||
@textModule.reload(
|
|
||||||
ticket:
|
|
||||||
customer: user
|
|
||||||
user: App.Session.get()
|
|
||||||
)
|
|
||||||
|
|
||||||
new App.WidgetUser(
|
|
||||||
el: el
|
|
||||||
user_id: user.id
|
|
||||||
)
|
|
||||||
|
|
||||||
editCustomer = (e, el) =>
|
|
||||||
new App.ControllerGenericEdit(
|
|
||||||
id: @params.customer_id
|
|
||||||
genericObject: 'User'
|
|
||||||
screen: 'edit'
|
|
||||||
pageData:
|
|
||||||
title: 'Users'
|
|
||||||
object: 'User'
|
|
||||||
objects: 'Users'
|
|
||||||
container: @el.closest('.content')
|
|
||||||
)
|
|
||||||
items.push {
|
|
||||||
head: 'Customer'
|
|
||||||
name: 'customer'
|
|
||||||
icon: 'person'
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
title: 'Edit Customer'
|
|
||||||
name: 'Edit Customer'
|
|
||||||
class: 'glyphicon glyphicon-edit'
|
|
||||||
callback: editCustomer
|
|
||||||
},
|
|
||||||
]
|
|
||||||
callback: showCustomer
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.organization_id
|
|
||||||
editOrganization = (e, el) =>
|
|
||||||
new App.ControllerGenericEdit(
|
|
||||||
id: user.organization_id
|
|
||||||
genericObject: 'Organization'
|
|
||||||
pageData:
|
|
||||||
title: 'Organizations'
|
|
||||||
object: 'Organization'
|
|
||||||
objects: 'Organizations'
|
|
||||||
container: @el.closest('.content')
|
|
||||||
)
|
|
||||||
showOrganization = (el) ->
|
|
||||||
new App.WidgetOrganization(
|
|
||||||
el: el
|
|
||||||
organization_id: user.organization_id
|
|
||||||
)
|
|
||||||
items.push {
|
|
||||||
head: 'Organization'
|
|
||||||
name: 'organization'
|
|
||||||
icon: 'group'
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
title: 'Edit Organization'
|
|
||||||
name: 'Edit Organization'
|
|
||||||
class: 'glyphicon glyphicon-edit'
|
|
||||||
callback: editOrganization
|
|
||||||
},
|
|
||||||
]
|
|
||||||
callback: showOrganization
|
|
||||||
}
|
|
||||||
|
|
||||||
showTemplates = (el) ->
|
|
||||||
|
|
||||||
# show template UI
|
|
||||||
new App.WidgetTemplate(
|
|
||||||
el: el
|
|
||||||
#template_id: template['id']
|
|
||||||
)
|
|
||||||
|
|
||||||
items.push {
|
|
||||||
head: 'Templates'
|
|
||||||
name: 'template'
|
|
||||||
icon: 'templates'
|
|
||||||
callback: showTemplates
|
|
||||||
}
|
|
||||||
|
|
||||||
new App.Sidebar(
|
|
||||||
el: @el
|
|
||||||
items: items
|
|
||||||
)
|
|
||||||
|
|
||||||
class Router extends App.ControllerPermanent
|
class Router extends App.ControllerPermanent
|
||||||
requiredPermission: 'ticket.agent'
|
requiredPermission: 'ticket.agent'
|
||||||
constructor: (params) ->
|
constructor: (params) ->
|
||||||
|
@ -617,6 +537,9 @@ class Router extends App.ControllerPermanent
|
||||||
if params.customer_id
|
if params.customer_id
|
||||||
split = "/customer/#{params.customer_id}"
|
split = "/customer/#{params.customer_id}"
|
||||||
|
|
||||||
|
if params.query
|
||||||
|
split = "/query/#{params.query}"
|
||||||
|
|
||||||
id = Math.floor( Math.random() * 99999 )
|
id = Math.floor( Math.random() * 99999 )
|
||||||
@navigate "#ticket/create/id/#{id}#{split}"
|
@navigate "#ticket/create/id/#{id}#{split}"
|
||||||
return
|
return
|
||||||
|
@ -627,6 +550,7 @@ class Router extends App.ControllerPermanent
|
||||||
article_id: params.article_id
|
article_id: params.article_id
|
||||||
type: params.type
|
type: params.type
|
||||||
customer_id: params.customer_id
|
customer_id: params.customer_id
|
||||||
|
query: params.query
|
||||||
id: params.id
|
id: params.id
|
||||||
|
|
||||||
App.TaskManager.execute(
|
App.TaskManager.execute(
|
||||||
|
@ -642,6 +566,7 @@ App.Config.set('ticket/create/', Router, 'Routes')
|
||||||
App.Config.set('ticket/create/id/:id', Router, 'Routes')
|
App.Config.set('ticket/create/id/:id', Router, 'Routes')
|
||||||
App.Config.set('ticket/create/customer/:customer_id', Router, 'Routes')
|
App.Config.set('ticket/create/customer/:customer_id', Router, 'Routes')
|
||||||
App.Config.set('ticket/create/id/:id/customer/:customer_id', Router, 'Routes')
|
App.Config.set('ticket/create/id/:id/customer/:customer_id', Router, 'Routes')
|
||||||
|
App.Config.set('ticket/create/id/:id/query/:query', Router, 'Routes')
|
||||||
|
|
||||||
# split ticket
|
# split ticket
|
||||||
App.Config.set('ticket/create/:ticket_id/:article_id', Router, 'Routes')
|
App.Config.set('ticket/create/:ticket_id/:article_id', Router, 'Routes')
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
class App.TicketCreateSidebar extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
reload: (args) =>
|
||||||
|
for key, backend of @sidebarBackends
|
||||||
|
if backend && backend.reload
|
||||||
|
backend.reload(args)
|
||||||
|
|
||||||
|
commit: (args) =>
|
||||||
|
for key, backend of @sidebarBackends
|
||||||
|
if backend && backend.commit
|
||||||
|
backend.commit(args)
|
||||||
|
|
||||||
|
render: (params) =>
|
||||||
|
if params
|
||||||
|
@params = params
|
||||||
|
@sidebarBackends ||= {}
|
||||||
|
@sidebarItems = []
|
||||||
|
sidebarBackends = App.Config.get('TicketCreateSidebar')
|
||||||
|
keys = _.keys(sidebarBackends).sort()
|
||||||
|
for key in keys
|
||||||
|
if !@sidebarBackends[key] || !@sidebarBackends[key].reload
|
||||||
|
@sidebarBackends[key] = new sidebarBackends[key](
|
||||||
|
params: @params
|
||||||
|
query: @query
|
||||||
|
taskGet: @taskGet
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@sidebarBackends[key].reload(
|
||||||
|
params: @params
|
||||||
|
query: @query
|
||||||
|
)
|
||||||
|
item = @sidebarBackends[key].sidebarItem()
|
||||||
|
if item
|
||||||
|
@sidebarItems.push item
|
||||||
|
|
||||||
|
new App.Sidebar(
|
||||||
|
el: @el
|
||||||
|
sidebarState: @sidebarState
|
||||||
|
items: @sidebarItems
|
||||||
|
)
|
|
@ -0,0 +1,38 @@
|
||||||
|
class SidebarCustomer extends App.Controller
|
||||||
|
sidebarItem: =>
|
||||||
|
return if !@permissionCheck('ticket.agent')
|
||||||
|
return if !@params.customer_id
|
||||||
|
{
|
||||||
|
head: 'Customer'
|
||||||
|
name: 'customer'
|
||||||
|
icon: 'person'
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: 'Edit Customer'
|
||||||
|
name: 'customer-edit'
|
||||||
|
callback: @editCustomer
|
||||||
|
},
|
||||||
|
]
|
||||||
|
callback: @showCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
showCustomer: (el) =>
|
||||||
|
@el = el
|
||||||
|
new App.WidgetUser(
|
||||||
|
el: @el
|
||||||
|
user_id: @params.customer_id
|
||||||
|
)
|
||||||
|
|
||||||
|
editCustomer: =>
|
||||||
|
new App.ControllerGenericEdit(
|
||||||
|
id: @params.customer_id
|
||||||
|
genericObject: 'User'
|
||||||
|
screen: 'edit'
|
||||||
|
pageData:
|
||||||
|
title: 'Users'
|
||||||
|
object: 'User'
|
||||||
|
objects: 'Users'
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('200-Customer', SidebarCustomer, 'TicketCreateSidebar')
|
|
@ -0,0 +1,41 @@
|
||||||
|
class SidebarOrganization extends App.Controller
|
||||||
|
sidebarItem: =>
|
||||||
|
return if !@permissionCheck('ticket.agent')
|
||||||
|
return if !@params.customer_id
|
||||||
|
return if !App.User.exists(@params.customer_id)
|
||||||
|
customer = App.User.find(@params.customer_id)
|
||||||
|
@organization_id = customer.organization_id
|
||||||
|
return if !@organization_id
|
||||||
|
{
|
||||||
|
head: 'Organization'
|
||||||
|
name: 'organization'
|
||||||
|
icon: 'group'
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: 'Edit Organization'
|
||||||
|
name: 'organization-edit'
|
||||||
|
callback: @editOrganization
|
||||||
|
},
|
||||||
|
]
|
||||||
|
callback: @showOrganization
|
||||||
|
}
|
||||||
|
|
||||||
|
showOrganization: (el) =>
|
||||||
|
@el = el
|
||||||
|
new App.WidgetOrganization(
|
||||||
|
el: @el
|
||||||
|
organization_id: @organization_id
|
||||||
|
)
|
||||||
|
|
||||||
|
editOrganization: =>
|
||||||
|
new App.ControllerGenericEdit(
|
||||||
|
id: @organization_id,
|
||||||
|
genericObject: 'Organization'
|
||||||
|
pageData:
|
||||||
|
title: 'Organizations'
|
||||||
|
object: 'Organization'
|
||||||
|
objects: 'Organizations'
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('300-Organization', SidebarOrganization, 'TicketCreateSidebar')
|
|
@ -0,0 +1,21 @@
|
||||||
|
class SidebarTemplate extends App.Controller
|
||||||
|
sidebarItem: =>
|
||||||
|
return if !@permissionCheck('ticket.agent')
|
||||||
|
{
|
||||||
|
head: 'Templates'
|
||||||
|
name: 'template'
|
||||||
|
icon: 'templates'
|
||||||
|
actions: []
|
||||||
|
callback: @showTemplates
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemplates: (el) =>
|
||||||
|
@el = el
|
||||||
|
|
||||||
|
# show template UI
|
||||||
|
new App.WidgetTemplate(
|
||||||
|
el: el
|
||||||
|
#template_id: template['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('100-Template', SidebarTemplate, 'TicketCreateSidebar')
|
|
@ -98,9 +98,13 @@ class App.TicketMerge extends App.ControllerModal
|
||||||
type: 'error'
|
type: 'error'
|
||||||
msg: App.i18n.translateContent(data['message'])
|
msg: App.i18n.translateContent(data['message'])
|
||||||
timeout: 6000
|
timeout: 6000
|
||||||
|
|
||||||
@formEnable(e)
|
@formEnable(e)
|
||||||
|
|
||||||
error: =>
|
error: (data) =>
|
||||||
|
details = data.responseJSON || {}
|
||||||
|
@notify
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to merge!')
|
||||||
|
timeout: 6000
|
||||||
@formEnable(e)
|
@formEnable(e)
|
||||||
)
|
)
|
||||||
|
|
|
@ -462,6 +462,14 @@ class ChatWindow extends App.Controller
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# show text module UI
|
||||||
|
new App.WidgetTextModule(
|
||||||
|
el: @input
|
||||||
|
data:
|
||||||
|
user: App.Session.get()
|
||||||
|
config: App.Config.all()
|
||||||
|
)
|
||||||
|
|
||||||
focus: =>
|
focus: =>
|
||||||
@input.focus()
|
@input.focus()
|
||||||
|
|
||||||
|
@ -473,7 +481,7 @@ class ChatWindow extends App.Controller
|
||||||
if event.data and event.data.callback
|
if event.data and event.data.callback
|
||||||
event.data.callback()
|
event.data.callback()
|
||||||
|
|
||||||
@$('.js-customerChatInput').ce({
|
@input.ce({
|
||||||
mode: 'richtext'
|
mode: 'richtext'
|
||||||
multiline: true
|
multiline: true
|
||||||
maxlength: 40000
|
maxlength: 40000
|
||||||
|
@ -522,7 +530,7 @@ class ChatWindow extends App.Controller
|
||||||
|
|
||||||
switch event.keyCode
|
switch event.keyCode
|
||||||
when TABKEY
|
when TABKEY
|
||||||
allChatInputs = $('.js-customerChatInput').not('[disabled="disabled"]')
|
allChatInputs = @input.not('[disabled="disabled"]')
|
||||||
chatCount = allChatInputs.size()
|
chatCount = allChatInputs.size()
|
||||||
index = allChatInputs.index(@input)
|
index = allChatInputs.index(@input)
|
||||||
|
|
||||||
|
@ -542,7 +550,7 @@ class ChatWindow extends App.Controller
|
||||||
allChatInputs.eq(chatCount-1).focus()
|
allChatInputs.eq(chatCount-1).focus()
|
||||||
|
|
||||||
when ENTERKEY
|
when ENTERKEY
|
||||||
if !event.shiftKey
|
if !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@sendMessage()
|
@sendMessage()
|
||||||
|
|
||||||
|
@ -587,7 +595,7 @@ class ChatWindow extends App.Controller
|
||||||
@sounds.message.play()
|
@sounds.message.play()
|
||||||
@notifyDesktop(
|
@notifyDesktop(
|
||||||
title: @name
|
title: @name
|
||||||
body: message
|
body: App.Utils.html2text(message)
|
||||||
url: '#customer_chat'
|
url: '#customer_chat'
|
||||||
callback: =>
|
callback: =>
|
||||||
App.Event.trigger('chat_focus', { session_id: @session.session_id })
|
App.Event.trigger('chat_focus', { session_id: @session.session_id })
|
||||||
|
|
|
@ -450,8 +450,8 @@ class EmailNotification extends App.WizardFullScreen
|
||||||
if adapter is 'smtp'
|
if adapter is 'smtp'
|
||||||
configureAttributesOutbound = [
|
configureAttributesOutbound = [
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password' },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off' },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||||
]
|
]
|
||||||
@form = new App.ControllerForm(
|
@form = new App.ControllerForm(
|
||||||
|
@ -671,20 +671,24 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
|
|
||||||
# inbound
|
# inbound
|
||||||
configureAttributesInbound = [
|
configureAttributesInbound = [
|
||||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', single: true },
|
||||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||||
|
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' },
|
||||||
|
{ name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' },
|
||||||
]
|
]
|
||||||
|
|
||||||
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
||||||
return if !params
|
return if !params
|
||||||
if params.adapter is 'imap'
|
if params.adapter is 'imap'
|
||||||
ui.show('options::folder')
|
ui.show('options::folder')
|
||||||
|
ui.show('options::keep_on_server')
|
||||||
return
|
return
|
||||||
ui.hide('options::folder')
|
ui.hide('options::folder')
|
||||||
|
ui.hide('options::keep_on_server')
|
||||||
|
|
||||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||||
return if !params
|
return if !params
|
||||||
|
@ -700,7 +704,7 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
return
|
return
|
||||||
|
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @$('.base-inbound-settings'),
|
el: @$('.base-inbound-settings')
|
||||||
model:
|
model:
|
||||||
configure_attributes: configureAttributesInbound
|
configure_attributes: configureAttributesInbound
|
||||||
className: ''
|
className: ''
|
||||||
|
@ -716,8 +720,10 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
# fill user / password based on intro info
|
# fill user / password based on intro info
|
||||||
channel_used = { options: {} }
|
channel_used = { options: {} }
|
||||||
if @account['meta']
|
if @account['meta']
|
||||||
channel_used['options']['user'] = @account['meta']['email']
|
channel_used['options']['user'] = @account['meta']['email']
|
||||||
channel_used['options']['password'] = @account['meta']['password']
|
channel_used['options']['password'] = @account['meta']['password']
|
||||||
|
channel_used['options']['folder'] = @account['meta']['folder']
|
||||||
|
channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server']
|
||||||
|
|
||||||
# show used backend
|
# show used backend
|
||||||
@$('.base-outbound-settings').html('')
|
@$('.base-outbound-settings').html('')
|
||||||
|
@ -725,8 +731,8 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
if adapter is 'smtp'
|
if adapter is 'smtp'
|
||||||
configureAttributesOutbound = [
|
configureAttributesOutbound = [
|
||||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
|
||||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', },
|
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', },
|
||||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true },
|
||||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||||
]
|
]
|
||||||
@form = new App.ControllerForm(
|
@form = new App.ControllerForm(
|
||||||
|
@ -745,7 +751,7 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
@account.meta = params
|
@account.meta = params
|
||||||
|
|
||||||
@disable(e)
|
@disable(e)
|
||||||
@$('.js-probe .js-email').text( params.email )
|
@$('.js-probe .js-email').text(params.email)
|
||||||
@showSlide('js-probe')
|
@showSlide('js-probe')
|
||||||
|
|
||||||
@ajax(
|
@ajax(
|
||||||
|
@ -760,7 +766,7 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
for key, value of data.setting
|
for key, value of data.setting
|
||||||
@account[key] = value
|
@account[key] = value
|
||||||
|
|
||||||
if data.content_messages && data.content_messages > 0
|
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
||||||
|
@ -809,7 +815,7 @@ class ChannelEmail extends App.WizardFullScreen
|
||||||
# remember account settings
|
# remember account settings
|
||||||
@account.inbound = params
|
@account.inbound = params
|
||||||
|
|
||||||
if data.content_messages && data.content_messages > 0
|
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||||
message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
class App.IdoitObjectSelector extends App.ControllerModal
|
||||||
|
buttonClose: true
|
||||||
|
buttonCancel: true
|
||||||
|
buttonSubmit: true
|
||||||
|
head: 'i-doit'
|
||||||
|
|
||||||
|
content: ->
|
||||||
|
@ajax(
|
||||||
|
id: 'idoit-object-selector'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/idoit"
|
||||||
|
data: JSON.stringify(method: 'cmdb.object_types')
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.result is 'failed'
|
||||||
|
@contentInline = data.message
|
||||||
|
@render()
|
||||||
|
return
|
||||||
|
|
||||||
|
result = _.sortBy(data.response.result, 'title')
|
||||||
|
@contentInline = $(App.view('integration/idoit_object_selector')())
|
||||||
|
|
||||||
|
@contentInline.find('.js-typeSelect').html(@renderTypeSelector(result))
|
||||||
|
|
||||||
|
@contentInline.filter('.js-search').on('change', 'select, input', (e) =>
|
||||||
|
params = @formParam(e.target)
|
||||||
|
@search(params)
|
||||||
|
)
|
||||||
|
@contentInline.filter('.js-search').on('keyup', 'input', (e) =>
|
||||||
|
params = @formParam(e.target)
|
||||||
|
@search(params)
|
||||||
|
)
|
||||||
|
@render()
|
||||||
|
@$('.js-input').focus()
|
||||||
|
|
||||||
|
error: (xhr, status, error) =>
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
@contentInline = 'Unable to load content'
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
''
|
||||||
|
|
||||||
|
search: (filter) =>
|
||||||
|
if _.isEmpty(filter.title)
|
||||||
|
delete filter.title
|
||||||
|
else
|
||||||
|
filter.title = "%#{filter.title}%"
|
||||||
|
@ajax(
|
||||||
|
id: 'idoit-object-selector'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/idoit"
|
||||||
|
data: JSON.stringify(method: 'cmdb.objects', filter: filter)
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
@renderResult(data.response.result)
|
||||||
|
|
||||||
|
error: (xhr, status, error) =>
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
@contentInline = 'Unable to load content'
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
|
renderResult: (items) =>
|
||||||
|
table = App.view('integration/idoit_object_result')(
|
||||||
|
items: items
|
||||||
|
)
|
||||||
|
@el.find('.js-result').html(table)
|
||||||
|
|
||||||
|
renderTypeSelector: (result) ->
|
||||||
|
options = {}
|
||||||
|
for item in result
|
||||||
|
options[item.id] = item.title
|
||||||
|
return App.UiElement.searchable_select.render(
|
||||||
|
name: 'type'
|
||||||
|
multiple: false
|
||||||
|
limit: 100
|
||||||
|
null: false
|
||||||
|
nulloption: false
|
||||||
|
options: options
|
||||||
|
)
|
||||||
|
|
||||||
|
onSubmit: (e) =>
|
||||||
|
form = @el.find('.js-result')
|
||||||
|
params = @formParam(form)
|
||||||
|
return if _.isEmpty(params.object_id)
|
||||||
|
|
||||||
|
if _.isArray(params.object_id)
|
||||||
|
object_ids = params.object_id
|
||||||
|
else
|
||||||
|
object_ids = [params.object_id]
|
||||||
|
|
||||||
|
@formDisable(form)
|
||||||
|
@callback(object_ids, @)
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Index extends App.ControllerContent
|
||||||
'.zendesk-api-token-error': 'apiTokenErrorMessage'
|
'.zendesk-api-token-error': 'apiTokenErrorMessage'
|
||||||
'#zendesk-email': 'zendeskEmail'
|
'#zendesk-email': 'zendeskEmail'
|
||||||
'#zendesk-api-token': 'zendeskApiToken'
|
'#zendesk-api-token': 'zendeskApiToken'
|
||||||
|
'.js-ticket-count-info': 'ticketCountInfo'
|
||||||
updateMigrationDisplayLoop: 0
|
updateMigrationDisplayLoop: 0
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
@ -116,7 +117,8 @@ class Index extends App.ControllerContent
|
||||||
showCredentials: (e) =>
|
showCredentials: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@urlStatus.attr('data-state', '')
|
@urlStatus.attr('data-state', '')
|
||||||
@zendeskUrlApiToken.attr('href', @zendeskUrl.val() + 'agent/admin/api')
|
url = @zendeskUrl.val() + '/agent/admin/api'
|
||||||
|
@zendeskUrlApiToken.attr('href', url.replace(/([^:])\/\/+/g, '$1/'))
|
||||||
@zendeskUrlApiToken.val('HERE')
|
@zendeskUrlApiToken.val('HERE')
|
||||||
@$('[data-slide=zendesk-url]').toggleClass('hide')
|
@$('[data-slide=zendesk-url]').toggleClass('hide')
|
||||||
@$('[data-slide=zendesk-credentials]').toggleClass('hide')
|
@$('[data-slide=zendesk-credentials]').toggleClass('hide')
|
||||||
|
@ -171,6 +173,10 @@ class Index extends App.ControllerContent
|
||||||
for key, item of data.data
|
for key, item of data.data
|
||||||
if item.done > item.total
|
if item.done > item.total
|
||||||
item.done = item.total
|
item.done = item.total
|
||||||
|
|
||||||
|
if key == 'Ticket' && item.total >= 1000
|
||||||
|
@ticketCountInfo.removeClass('hide')
|
||||||
|
|
||||||
element = @$('.js-' + key.toLowerCase() )
|
element = @$('.js-' + key.toLowerCase() )
|
||||||
element.find('.js-done').text(item.done)
|
element.find('.js-done').text(item.done)
|
||||||
element.find('.js-total').text(item.total)
|
element.find('.js-total').text(item.total)
|
||||||
|
|
|
@ -1499,7 +1499,7 @@ class InputsRef extends App.ControllerContent
|
||||||
null: false
|
null: false
|
||||||
relation: 'User'
|
relation: 'User'
|
||||||
autocapitalize: false
|
autocapitalize: false
|
||||||
disableCreateUser: true
|
disableCreateObject: true
|
||||||
multiple: true
|
multiple: true
|
||||||
|
|
||||||
@$('.userOrganizationAutocompletePlaceholder').replaceWith( userOrganizationAutocomplete.element() )
|
@$('.userOrganizationAutocompletePlaceholder').replaceWith( userOrganizationAutocomplete.element() )
|
||||||
|
|
|
@ -38,50 +38,7 @@ class Index extends App.ControllerContent
|
||||||
)
|
)
|
||||||
|
|
||||||
render: (data = {}) ->
|
render: (data = {}) ->
|
||||||
auth_provider_all = {
|
auth_provider_all = App.Config.get('auth_provider_all')
|
||||||
facebook: {
|
|
||||||
url: '/auth/facebook',
|
|
||||||
name: 'Facebook',
|
|
||||||
config: 'auth_facebook',
|
|
||||||
class: 'facebook'
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
url: '/auth/twitter'
|
|
||||||
name: 'Twitter'
|
|
||||||
config: 'auth_twitter'
|
|
||||||
class: 'twitter'
|
|
||||||
},
|
|
||||||
linkedin: {
|
|
||||||
url: '/auth/linkedin'
|
|
||||||
name: 'LinkedIn'
|
|
||||||
config: 'auth_linkedin'
|
|
||||||
class: 'linkedin'
|
|
||||||
},
|
|
||||||
github: {
|
|
||||||
url: '/auth/github'
|
|
||||||
name: 'GitHub'
|
|
||||||
config: 'auth_github'
|
|
||||||
class: 'github'
|
|
||||||
},
|
|
||||||
gitlab: {
|
|
||||||
url: '/auth/gitlab'
|
|
||||||
name: 'GitLab'
|
|
||||||
config: 'auth_gitlab'
|
|
||||||
class: 'gitlab'
|
|
||||||
},
|
|
||||||
google_oauth2: {
|
|
||||||
url: '/auth/google_oauth2'
|
|
||||||
name: 'Google'
|
|
||||||
config: 'auth_google_oauth2'
|
|
||||||
class: 'google'
|
|
||||||
},
|
|
||||||
oauth2: {
|
|
||||||
url: '/auth/oauth2'
|
|
||||||
name: 'OAuth2'
|
|
||||||
config: 'auth_oauth2'
|
|
||||||
class: 'oauth2'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
auth_providers = []
|
auth_providers = []
|
||||||
for key, provider of auth_provider_all
|
for key, provider of auth_provider_all
|
||||||
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
||||||
|
|
|
@ -1,4 +1,46 @@
|
||||||
# coffeelint: disable=duplicate_key
|
# coffeelint: disable=duplicate_key
|
||||||
|
treeParams = (e, params) ->
|
||||||
|
tree = []
|
||||||
|
lastLevel = 0
|
||||||
|
lastLevels = []
|
||||||
|
valueLevels = []
|
||||||
|
|
||||||
|
$(e.target).closest('.modal').find('.js-treeTable .js-key').each( ->
|
||||||
|
$element = $(@)
|
||||||
|
level = parseInt($element.attr('level'))
|
||||||
|
name = $element.val()
|
||||||
|
item =
|
||||||
|
name: name
|
||||||
|
|
||||||
|
if level is 0
|
||||||
|
tree.push item
|
||||||
|
else if lastLevels[level-1]
|
||||||
|
lastLevels[level-1].children ||= []
|
||||||
|
lastLevels[level-1].children.push item
|
||||||
|
else
|
||||||
|
console.log('ERROR', item)
|
||||||
|
if level is 0
|
||||||
|
valueLevels = []
|
||||||
|
else if lastLevel is level
|
||||||
|
valueLevels.pop()
|
||||||
|
else if lastLevel > level
|
||||||
|
down = lastLevel - level
|
||||||
|
for count in [1..down]
|
||||||
|
valueLevels.pop()
|
||||||
|
if lastLevel <= level
|
||||||
|
valueLevels.push name
|
||||||
|
|
||||||
|
item.value = valueLevels.join('::')
|
||||||
|
lastLevels[level] = item
|
||||||
|
lastLevel = level
|
||||||
|
|
||||||
|
)
|
||||||
|
if tree[0]
|
||||||
|
if !params.data_option
|
||||||
|
params.data_option = {}
|
||||||
|
params.data_option.options = tree
|
||||||
|
params
|
||||||
|
|
||||||
class Index extends App.ControllerTabs
|
class Index extends App.ControllerTabs
|
||||||
requiredPermission: 'admin.object'
|
requiredPermission: 'admin.object'
|
||||||
constructor: ->
|
constructor: ->
|
||||||
|
@ -135,6 +177,7 @@ class New extends App.ControllerGenericNew
|
||||||
|
|
||||||
onSubmit: (e) =>
|
onSubmit: (e) =>
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
params = treeParams(e, params)
|
||||||
|
|
||||||
# show attributes for create_middle in two column style
|
# show attributes for create_middle in two column style
|
||||||
if params.screens && params.screens.create_middle
|
if params.screens && params.screens.create_middle
|
||||||
|
@ -184,6 +227,8 @@ class Edit extends App.ControllerGenericEdit
|
||||||
#if attribute.name is 'data_type'
|
#if attribute.name is 'data_type'
|
||||||
# attribute.disabled = true
|
# attribute.disabled = true
|
||||||
|
|
||||||
|
console.log('configure_attributes', configure_attributes)
|
||||||
|
|
||||||
@controller = new App.ControllerForm(
|
@controller = new App.ControllerForm(
|
||||||
model:
|
model:
|
||||||
configure_attributes: configure_attributes
|
configure_attributes: configure_attributes
|
||||||
|
@ -195,6 +240,7 @@ class Edit extends App.ControllerGenericEdit
|
||||||
|
|
||||||
onSubmit: (e) =>
|
onSubmit: (e) =>
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
params = treeParams(e, params)
|
||||||
|
|
||||||
# show attributes for create_middle in two column style
|
# show attributes for create_middle in two column style
|
||||||
if params.screens && params.screens.create_middle
|
if params.screens && params.screens.create_middle
|
||||||
|
|
|
@ -6,7 +6,7 @@ class App.TicketCustomer extends App.ControllerModal
|
||||||
|
|
||||||
content: ->
|
content: ->
|
||||||
configure_attributes = [
|
configure_attributes = [
|
||||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateUser: true },
|
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: true },
|
||||||
]
|
]
|
||||||
controller = new App.ControllerForm(
|
controller = new App.ControllerForm(
|
||||||
model:
|
model:
|
||||||
|
|
|
@ -221,7 +221,7 @@ class App.TicketOverview extends App.Controller
|
||||||
if @batchCountIndex == @batchCount
|
if @batchCountIndex == @batchCount
|
||||||
App.Event.trigger('overview:fetch')
|
App.Event.trigger('overview:fetch')
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if action is 'group_assign'
|
if action is 'group_assign'
|
||||||
@batchCount = items.length
|
@batchCount = items.length
|
||||||
|
|
|
@ -401,10 +401,11 @@ class App.TicketZoom extends App.Controller
|
||||||
nav: @nav
|
nav: @nav
|
||||||
isCustomer: @permissionCheck('ticket.customer')
|
isCustomer: @permissionCheck('ticket.customer')
|
||||||
scrollbarWidth: App.Utils.getScrollBarWidth()
|
scrollbarWidth: App.Utils.getScrollBarWidth()
|
||||||
|
dir: App.i18n.dir()
|
||||||
)
|
)
|
||||||
|
|
||||||
new App.TicketZoomOverviewNavigator(
|
new App.TicketZoomOverviewNavigator(
|
||||||
el: elLocal.find('.overview-navigator')
|
el: elLocal.find('.js-overviewNavigatorContainer')
|
||||||
ticket_id: @ticket_id
|
ticket_id: @ticket_id
|
||||||
overview_id: @overview_id
|
overview_id: @overview_id
|
||||||
)
|
)
|
||||||
|
@ -412,13 +413,13 @@ class App.TicketZoom extends App.Controller
|
||||||
new App.TicketZoomTitle(
|
new App.TicketZoomTitle(
|
||||||
object_id: @ticket_id
|
object_id: @ticket_id
|
||||||
overview_id: @overview_id
|
overview_id: @overview_id
|
||||||
el: elLocal.find('.ticket-title')
|
el: elLocal.find('.js-ticketTitleContainer')
|
||||||
task_key: @task_key
|
task_key: @task_key
|
||||||
)
|
)
|
||||||
|
|
||||||
new App.TicketZoomMeta(
|
new App.TicketZoomMeta(
|
||||||
object_id: @ticket_id
|
object_id: @ticket_id
|
||||||
el: elLocal.find('.ticket-meta')
|
el: elLocal.find('.js-ticketMetaContainer')
|
||||||
)
|
)
|
||||||
|
|
||||||
@attributeBar = new App.TicketZoomAttributeBar(
|
@attributeBar = new App.TicketZoomAttributeBar(
|
||||||
|
@ -445,7 +446,12 @@ class App.TicketZoom extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
@highligher = new App.TicketZoomHighlighter(
|
@highligher = new App.TicketZoomHighlighter(
|
||||||
el: elLocal.find('.highlighter')
|
el: elLocal.find('.js-highlighterContainer')
|
||||||
|
ticket_id: @ticket_id
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.TicketZoomSetting(
|
||||||
|
el: elLocal.find('.js-settingContainer')
|
||||||
ticket_id: @ticket_id
|
ticket_id: @ticket_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -467,6 +473,7 @@ class App.TicketZoom extends App.Controller
|
||||||
sidebarState: @sidebarState
|
sidebarState: @sidebarState
|
||||||
object_id: @ticket_id
|
object_id: @ticket_id
|
||||||
model: 'Ticket'
|
model: 'Ticket'
|
||||||
|
query: @query
|
||||||
taskGet: @taskGet
|
taskGet: @taskGet
|
||||||
task_key: @task_key
|
task_key: @task_key
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
|
@ -557,14 +564,16 @@ class App.TicketZoom extends App.Controller
|
||||||
return if !@ticket
|
return if !@ticket
|
||||||
currentStoreTicket = @ticket.attributes()
|
currentStoreTicket = @ticket.attributes()
|
||||||
delete currentStoreTicket.article
|
delete currentStoreTicket.article
|
||||||
|
internal = @Config.get('ui_ticket_zoom_article_note_new_internal')
|
||||||
currentStore =
|
currentStore =
|
||||||
ticket: currentStoreTicket
|
ticket: currentStoreTicket
|
||||||
article:
|
article:
|
||||||
to: ''
|
to: ''
|
||||||
cc: ''
|
cc: ''
|
||||||
|
subject: ''
|
||||||
type: 'note'
|
type: 'note'
|
||||||
body: ''
|
body: ''
|
||||||
internal: 'true'
|
internal: internal
|
||||||
in_reply_to: ''
|
in_reply_to: ''
|
||||||
|
|
||||||
if @permissionCheck('ticket.customer')
|
if @permissionCheck('ticket.customer')
|
||||||
|
@ -575,7 +584,7 @@ class App.TicketZoom extends App.Controller
|
||||||
formCurrent: =>
|
formCurrent: =>
|
||||||
currentParams =
|
currentParams =
|
||||||
ticket: @formParam(@el.find('.edit'))
|
ticket: @formParam(@el.find('.edit'))
|
||||||
article: @formParam(@el.find('.article-add'))
|
article: @articleNew.params()
|
||||||
|
|
||||||
# add attachments if exist
|
# add attachments if exist
|
||||||
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
|
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
|
||||||
|
@ -684,7 +693,7 @@ class App.TicketZoom extends App.Controller
|
||||||
tagAdd: (tag) =>
|
tagAdd: (tag) =>
|
||||||
return if !@sidebar
|
return if !@sidebar
|
||||||
return if !@sidebar.reload
|
return if !@sidebar.reload
|
||||||
@sidebar.reload(tagAdd: tag)
|
@sidebar.reload(tagAdd: tag, source: 'macro')
|
||||||
tagRemove: (tag) =>
|
tagRemove: (tag) =>
|
||||||
return if !@sidebar
|
return if !@sidebar
|
||||||
return if !@sidebar.reload
|
return if !@sidebar.reload
|
||||||
|
@ -789,6 +798,9 @@ class App.TicketZoom extends App.Controller
|
||||||
# reset form after save
|
# reset form after save
|
||||||
@reset()
|
@reset()
|
||||||
|
|
||||||
|
if @sidebar
|
||||||
|
@sidebar.commit()
|
||||||
|
|
||||||
if taskAction is 'closeNextInOverview'
|
if taskAction is 'closeNextInOverview'
|
||||||
if @overview_id
|
if @overview_id
|
||||||
current_position = 0
|
current_position = 0
|
||||||
|
|
|
@ -391,25 +391,41 @@ class App.TicketZoomArticleActions extends App.Controller
|
||||||
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
|
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
|
||||||
|
|
||||||
# check if quote need to be added
|
# check if quote need to be added
|
||||||
selectedText = App.ClipBoard.getSelected()
|
signaturePosition = 'bottom'
|
||||||
if selectedText
|
selected = App.ClipBoard.getSelected('html')
|
||||||
|
if selected
|
||||||
|
selected = App.Utils.htmlCleanup(selected).html()
|
||||||
|
if !selected
|
||||||
|
selected = App.ClipBoard.getSelected('text')
|
||||||
|
if selected
|
||||||
|
selected = App.Utils.textCleanup(selected)
|
||||||
|
selected = App.Utils.text2html(selected)
|
||||||
|
|
||||||
# clean selection
|
# full quote, if needed
|
||||||
selectedText = App.Utils.textCleanup(selectedText)
|
if !selected && article && App.Config.get('ui_ticket_zoom_article_email_full_quote')
|
||||||
|
signaturePosition = 'top'
|
||||||
|
if article.content_type.match('html')
|
||||||
|
selected = App.Utils.textCleanup(article.body)
|
||||||
|
if article.content_type.match('plain')
|
||||||
|
selected = App.Utils.textCleanup(article.body)
|
||||||
|
selected = App.Utils.text2html(selected)
|
||||||
|
|
||||||
# convert to html
|
if selected
|
||||||
selectedText = App.Utils.text2html(selectedText)
|
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
|
||||||
if selectedText
|
|
||||||
selectedText = "<div><br><br/></div><div><blockquote type=\"cite\">#{selectedText}</blockquote></div><div><br></div>"
|
|
||||||
|
|
||||||
# add selected text to body
|
# add selected text to body
|
||||||
body = selectedText + body
|
body = selected + body
|
||||||
|
|
||||||
articleNew.body = body
|
articleNew.body = body
|
||||||
|
|
||||||
type = App.TicketArticleType.findByAttribute(name:'email')
|
type = App.TicketArticleType.findByAttribute(name:'email')
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: @ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
signaturePosition: signaturePosition
|
||||||
|
})
|
||||||
|
|
||||||
telegramPersonalMessageReply: (e) =>
|
telegramPersonalMessageReply: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -28,7 +28,62 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
# set possble article types
|
@internalSelector = true
|
||||||
|
@type = @defaults['type'] || 'note'
|
||||||
|
@setPossibleArticleTypes()
|
||||||
|
|
||||||
|
if @permissionCheck('ticket.customer')
|
||||||
|
@internalSelector = false
|
||||||
|
|
||||||
|
@textareaHeight =
|
||||||
|
open: 148
|
||||||
|
closed: 20
|
||||||
|
|
||||||
|
@dragEventCounter = 0
|
||||||
|
@attachments = []
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
if @defaults.body or @isIE10()
|
||||||
|
@openTextarea(null, true)
|
||||||
|
|
||||||
|
# set article type and expand text area
|
||||||
|
@bind('ui::ticket::setArticleType', (data) =>
|
||||||
|
return if data.ticket.id.toString() isnt @ticket_id.toString()
|
||||||
|
|
||||||
|
@openTextarea(null, true)
|
||||||
|
for key, value of data.article
|
||||||
|
if key is 'body'
|
||||||
|
@$('[data-name="' + key + '"]').html(value)
|
||||||
|
else
|
||||||
|
@$('[name="' + key + '"]').val(value).trigger('change')
|
||||||
|
|
||||||
|
# preselect article type
|
||||||
|
@setArticleType(data.type.name, data.signaturePosition)
|
||||||
|
|
||||||
|
# set focus at end of field
|
||||||
|
if data.position is 'end'
|
||||||
|
@placeCaretAtEnd(@textarea.get(0))
|
||||||
|
return
|
||||||
|
|
||||||
|
# set focus into field
|
||||||
|
@textarea.focus()
|
||||||
|
)
|
||||||
|
|
||||||
|
# reset new article screen
|
||||||
|
@bind('ui::ticket::taskReset', (data) =>
|
||||||
|
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
||||||
|
@type = 'note'
|
||||||
|
@defaults = {}
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
|
# rerender, e. g. on language change
|
||||||
|
@bind('ui:rerender', =>
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
|
setPossibleArticleTypes: =>
|
||||||
possibleArticleType =
|
possibleArticleType =
|
||||||
note: true
|
note: true
|
||||||
phone: true
|
phone: true
|
||||||
|
@ -50,12 +105,9 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
possibleArticleType['email'] = true
|
possibleArticleType['email'] = true
|
||||||
|
|
||||||
# gets referenced in @setArticleType
|
# gets referenced in @setArticleType
|
||||||
@internalSelector = true
|
|
||||||
@type = @defaults['type'] || 'note'
|
|
||||||
@articleTypes = []
|
@articleTypes = []
|
||||||
if possibleArticleType.note
|
if possibleArticleType.note
|
||||||
internal = @Config.get('ui_ticket_zoom_article_new_internal')
|
internal = @Config.get('ui_ticket_zoom_article_note_new_internal')
|
||||||
|
|
||||||
@articleTypes.push {
|
@articleTypes.push {
|
||||||
name: 'note'
|
name: 'note'
|
||||||
icon: 'note'
|
icon: 'note'
|
||||||
|
@ -64,10 +116,13 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
features: ['attachment']
|
features: ['attachment']
|
||||||
}
|
}
|
||||||
if possibleArticleType.email
|
if possibleArticleType.email
|
||||||
|
attributes = ['to', 'cc', 'subject']
|
||||||
|
if !@Config.get('ui_ticket_zoom_article_email_subject')
|
||||||
|
attributes = ['to', 'cc']
|
||||||
@articleTypes.push {
|
@articleTypes.push {
|
||||||
name: 'email'
|
name: 'email'
|
||||||
icon: 'email'
|
icon: 'email'
|
||||||
attributes: ['to', 'cc']
|
attributes: attributes
|
||||||
internal: false,
|
internal: false,
|
||||||
features: ['attachment']
|
features: ['attachment']
|
||||||
}
|
}
|
||||||
|
@ -80,22 +135,28 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
features: []
|
features: []
|
||||||
}
|
}
|
||||||
if possibleArticleType['twitter status']
|
if possibleArticleType['twitter status']
|
||||||
|
attributes = ['body:limit', 'body:initials']
|
||||||
|
if !@Config.get('ui_ticket_zoom_article_twitter_initials')
|
||||||
|
attributes = ['body:limit']
|
||||||
@articleTypes.push {
|
@articleTypes.push {
|
||||||
name: 'twitter status'
|
name: 'twitter status'
|
||||||
icon: 'twitter'
|
icon: 'twitter'
|
||||||
attributes: []
|
attributes: []
|
||||||
internal: false,
|
internal: false,
|
||||||
features: ['body:limit']
|
features: ['body:limit', 'body:initials']
|
||||||
maxTextLength: 140
|
maxTextLength: 140
|
||||||
warningTextLength: 30
|
warningTextLength: 30
|
||||||
}
|
}
|
||||||
if possibleArticleType['twitter direct-message']
|
if possibleArticleType['twitter direct-message']
|
||||||
|
attributes = ['body:limit', 'body:initials']
|
||||||
|
if !@Config.get('ui_ticket_zoom_article_twitter_initials')
|
||||||
|
attributes = ['body:limit']
|
||||||
@articleTypes.push {
|
@articleTypes.push {
|
||||||
name: 'twitter direct-message'
|
name: 'twitter direct-message'
|
||||||
icon: 'twitter'
|
icon: 'twitter'
|
||||||
attributes: ['to']
|
attributes: ['to']
|
||||||
internal: false,
|
internal: false,
|
||||||
features: ['body:limit']
|
features: ['body:limit', 'body:initials']
|
||||||
maxTextLength: 10000
|
maxTextLength: 10000
|
||||||
warningTextLength: 500
|
warningTextLength: 500
|
||||||
}
|
}
|
||||||
|
@ -130,57 +191,6 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if @permissionCheck('ticket.customer')
|
|
||||||
@internalSelector = false
|
|
||||||
|
|
||||||
@textareaHeight =
|
|
||||||
open: 148
|
|
||||||
closed: 20
|
|
||||||
|
|
||||||
@dragEventCounter = 0
|
|
||||||
@attachments = []
|
|
||||||
|
|
||||||
@render()
|
|
||||||
|
|
||||||
if @defaults.body or @isIE10()
|
|
||||||
@openTextarea(null, true)
|
|
||||||
|
|
||||||
# set article type and expand text area
|
|
||||||
@bind('ui::ticket::setArticleType', (data) =>
|
|
||||||
return if data.ticket.id.toString() isnt @ticket_id.toString()
|
|
||||||
|
|
||||||
@openTextarea(null, true)
|
|
||||||
for key, value of data.article
|
|
||||||
if key is 'body'
|
|
||||||
@$('[data-name="' + key + '"]').html(value)
|
|
||||||
else
|
|
||||||
@$('[name="' + key + '"]').val(value).trigger('change')
|
|
||||||
|
|
||||||
# preselect article type
|
|
||||||
@setArticleType(data.type.name)
|
|
||||||
|
|
||||||
# set focus at end of field
|
|
||||||
if data.position is 'end'
|
|
||||||
@placeCaretAtEnd(@textarea.get(0))
|
|
||||||
return
|
|
||||||
|
|
||||||
# set focus into field
|
|
||||||
@textarea.focus()
|
|
||||||
)
|
|
||||||
|
|
||||||
# reset new article screen
|
|
||||||
@bind('ui::ticket::taskReset', (data) =>
|
|
||||||
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
|
||||||
@type = 'note'
|
|
||||||
@defaults = {}
|
|
||||||
@render()
|
|
||||||
)
|
|
||||||
|
|
||||||
# rerender, e. g. on language change
|
|
||||||
@bind('ui:rerender', =>
|
|
||||||
@render()
|
|
||||||
)
|
|
||||||
|
|
||||||
placeCaretAtEnd: (el) ->
|
placeCaretAtEnd: (el) ->
|
||||||
el.focus()
|
el.focus()
|
||||||
if typeof window.getSelection isnt 'undefined' && typeof document.createRange isnt 'undefined'
|
if typeof window.getSelection isnt 'undefined' && typeof document.createRange isnt 'undefined'
|
||||||
|
@ -229,7 +239,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_attributes = [
|
configure_attributes = [
|
||||||
{ name: 'customer_id', display: 'Recipients', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateUser: false },
|
{ name: 'customer_id', display: 'Recipients', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
controller = new App.ControllerForm(
|
controller = new App.ControllerForm(
|
||||||
|
@ -300,6 +310,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
data:
|
data:
|
||||||
ticket: ticket
|
ticket: ticket
|
||||||
user: App.Session.get()
|
user: App.Session.get()
|
||||||
|
config: App.Config.all()
|
||||||
)
|
)
|
||||||
callback = (ticket) ->
|
callback = (ticket) ->
|
||||||
textModule.reload(
|
textModule.reload(
|
||||||
|
@ -318,9 +329,6 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
params.form_id = @form_id
|
params.form_id = @form_id
|
||||||
params.content_type = 'text/html'
|
params.content_type = 'text/html'
|
||||||
|
|
||||||
if !params['internal']
|
|
||||||
params['internal'] = false
|
|
||||||
|
|
||||||
if @permissionCheck('ticket.customer')
|
if @permissionCheck('ticket.customer')
|
||||||
sender = App.TicketArticleSender.findByAttribute('name', 'Customer')
|
sender = App.TicketArticleSender.findByAttribute('name', 'Customer')
|
||||||
type = App.TicketArticleType.findByAttribute('name', 'web')
|
type = App.TicketArticleType.findByAttribute('name', 'web')
|
||||||
|
@ -332,15 +340,20 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
params.sender_id = sender.id
|
params.sender_id = sender.id
|
||||||
params.type_id = type.id
|
params.type_id = type.id
|
||||||
|
|
||||||
|
if params.internal
|
||||||
|
params.internal = true
|
||||||
|
else
|
||||||
|
params.internal = false
|
||||||
|
|
||||||
if params.type is 'twitter status'
|
if params.type is 'twitter status'
|
||||||
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
||||||
params.content_type = 'text/plain'
|
params.content_type = 'text/plain'
|
||||||
params.body = "#{App.Utils.html2text(params.body, true)}\n#{@signature.text()}"
|
params.body = App.Utils.html2text(params.body, true)
|
||||||
|
|
||||||
if params.type is 'twitter direct-message'
|
if params.type is 'twitter direct-message'
|
||||||
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
||||||
params.content_type = 'text/plain'
|
params.content_type = 'text/plain'
|
||||||
params.body = "#{App.Utils.html2text(params.body, true)}\n#{@signature.text()}"
|
params.body = App.Utils.html2text(params.body, true)
|
||||||
|
|
||||||
if params.type is 'facebook feed comment'
|
if params.type is 'facebook feed comment'
|
||||||
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
||||||
|
@ -352,6 +365,16 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
params.content_type = 'text/plain'
|
params.content_type = 'text/plain'
|
||||||
params.body = App.Utils.html2text(params.body, true)
|
params.body = App.Utils.html2text(params.body, true)
|
||||||
|
|
||||||
|
# add initals?
|
||||||
|
for articleType in @articleTypes
|
||||||
|
if articleType.name is @type
|
||||||
|
if _.contains(articleType.features, 'body:initials')
|
||||||
|
if params.content_type is 'text/html'
|
||||||
|
params.body = "#{params.body}</br>#{@signature.text()}"
|
||||||
|
else
|
||||||
|
params.body = "#{params.body}\n#{@signature.text()}"
|
||||||
|
break
|
||||||
|
|
||||||
params
|
params
|
||||||
|
|
||||||
validate: =>
|
validate: =>
|
||||||
|
@ -411,11 +434,11 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if params.type is 'twitter status'
|
if params.type is 'twitter status'
|
||||||
textLength = @maxTextLength - params.body.length
|
textLength = @maxTextLength - App.Utils.textLengthWithUrl(params.body)
|
||||||
return false if textLength < 0
|
return false if textLength < 0
|
||||||
|
|
||||||
if params.type is 'twitter direct-message'
|
if params.type is 'twitter direct-message'
|
||||||
textLength = @maxTextLength - params.body.length
|
textLength = @maxTextLength - App.Utils.textLengthWithUrl(params.body)
|
||||||
return false if textLength < 0
|
return false if textLength < 0
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -461,13 +484,15 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
|
|
||||||
@$('[name=internal]').val('')
|
@$('[name=internal]').val('')
|
||||||
|
|
||||||
setArticleType: (type) =>
|
setArticleType: (type, signaturePosition = 'bottom') =>
|
||||||
wasScrolledToBottom = @isScrolledToBottom()
|
wasScrolledToBottom = @isScrolledToBottom()
|
||||||
@type = type
|
@type = type
|
||||||
@$('[name=type]').val(type).trigger('change')
|
@$('[name=type]').val(type).trigger('change')
|
||||||
@articleNewEdit.attr('data-type', type)
|
@articleNewEdit.attr('data-type', type)
|
||||||
@$('.js-selectableTypes').addClass('hide').filter("[data-type='#{type}']").removeClass('hide')
|
@$('.js-selectableTypes').addClass('hide').filter("[data-type='#{type}']").removeClass('hide')
|
||||||
|
|
||||||
|
@setPossibleArticleTypes()
|
||||||
|
|
||||||
# get config
|
# get config
|
||||||
config = {}
|
config = {}
|
||||||
for articleTypeConfig in @articleTypes
|
for articleTypeConfig in @articleTypes
|
||||||
|
@ -500,7 +525,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
@$('[data-name=body] [data-signature="true"]').remove()
|
@$('[data-name=body] [data-signature="true"]').remove()
|
||||||
|
|
||||||
# apply new signature
|
# apply new signature
|
||||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent })
|
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent, config: App.Config.all() })
|
||||||
|
|
||||||
body = @$('[data-name=body]')
|
body = @$('[data-name=body]')
|
||||||
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
||||||
|
@ -508,7 +533,10 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
body.append('<br><br>')
|
body.append('<br><br>')
|
||||||
signature = $("<div data-signature=\"true\" data-signature-id=\"#{signature.id}\">#{signatureFinished}</div>")
|
signature = $("<div data-signature=\"true\" data-signature-id=\"#{signature.id}\">#{signatureFinished}</div>")
|
||||||
App.Utils.htmlStrip(signature)
|
App.Utils.htmlStrip(signature)
|
||||||
body.append(signature)
|
if signaturePosition is 'top'
|
||||||
|
body.prepend(signature)
|
||||||
|
else
|
||||||
|
body.append(signature)
|
||||||
@$('[data-name=body]').replaceWith(body)
|
@$('[data-name=body]').replaceWith(body)
|
||||||
|
|
||||||
# remove old signature
|
# remove old signature
|
||||||
|
@ -534,13 +562,28 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
for name in articleType.features
|
for name in articleType.features
|
||||||
if name is 'attachment'
|
if name is 'attachment'
|
||||||
@$('.article-attachment, .attachments').removeClass('hide')
|
@$('.article-attachment, .attachments').removeClass('hide')
|
||||||
|
if name is 'body:initials'
|
||||||
|
@updateInitials()
|
||||||
if name is 'body:limit'
|
if name is 'body:limit'
|
||||||
@maxTextLength = articleType.maxTextLength
|
@maxTextLength = articleType.maxTextLength
|
||||||
@warningTextLength = articleType.warningTextLength
|
@warningTextLength = articleType.warningTextLength
|
||||||
@delay(@updateLetterCount, 600)
|
@delay(@updateLetterCount, 600)
|
||||||
@updateInitials()
|
|
||||||
@$('.js-textSizeLimit').removeClass('hide')
|
@$('.js-textSizeLimit').removeClass('hide')
|
||||||
|
|
||||||
|
# convert remote src images to data uri
|
||||||
|
@$('[data-name=body] img').each( (i,image) ->
|
||||||
|
$image = $(image)
|
||||||
|
src = $image.attr('src')
|
||||||
|
if !_.isEmpty(src) && !src.match(/^data:image/i)
|
||||||
|
canvas = document.createElement('canvas')
|
||||||
|
canvas.width = image.width
|
||||||
|
canvas.height = image.height
|
||||||
|
ctx = canvas.getContext('2d')
|
||||||
|
ctx.drawImage(image, 0, 0)
|
||||||
|
dataURL = canvas.toDataURL()
|
||||||
|
$image.attr('src', dataURL)
|
||||||
|
)
|
||||||
|
|
||||||
@scrollToBottom() if wasScrolledToBottom
|
@scrollToBottom() if wasScrolledToBottom
|
||||||
|
|
||||||
isScrolledToBottom: ->
|
isScrolledToBottom: ->
|
||||||
|
@ -557,7 +600,8 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
return if !@maxTextLength
|
return if !@maxTextLength
|
||||||
return if !@warningTextLength
|
return if !@warningTextLength
|
||||||
params = @params()
|
params = @params()
|
||||||
textLength = @maxTextLength - params.body.length
|
textLength = App.Utils.textLengthWithUrl(params.body)
|
||||||
|
textLength = @maxTextLength - textLength
|
||||||
className = switch
|
className = switch
|
||||||
when textLength < 0 then 'label-danger'
|
when textLength < 0 then 'label-danger'
|
||||||
when textLength < @warningTextLength then 'label-warning'
|
when textLength < @warningTextLength then 'label-warning'
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
class App.TicketZoomSetting extends App.Controller
|
||||||
|
events:
|
||||||
|
'click .js-setting': 'show'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
return if !@permissionCheck('admin')
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
@html(App.view('ticket_zoom/setting')())
|
||||||
|
|
||||||
|
show: ->
|
||||||
|
new Modal()
|
||||||
|
|
||||||
|
class Modal extends App.ControllerModal
|
||||||
|
buttonClose: true
|
||||||
|
buttonCancel: true
|
||||||
|
buttonSubmit: false
|
||||||
|
head: 'Settings'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
super
|
||||||
|
|
||||||
|
post: =>
|
||||||
|
new App.SettingsArea(
|
||||||
|
area: 'UI::TicketZoom'
|
||||||
|
el: @el.find('.modal-body')
|
||||||
|
)
|
||||||
|
|
||||||
|
content: ->
|
||||||
|
App.view('generic/page_loading')()
|
|
@ -9,18 +9,32 @@ class App.TicketZoomSidebar extends App.ObserverController
|
||||||
if backend && backend.reload
|
if backend && backend.reload
|
||||||
backend.reload(args)
|
backend.reload(args)
|
||||||
|
|
||||||
|
commit: (args) =>
|
||||||
|
for key, backend of @sidebarBackends
|
||||||
|
if backend && backend.commit
|
||||||
|
backend.commit(args)
|
||||||
|
|
||||||
render: (ticket) =>
|
render: (ticket) =>
|
||||||
@sidebarBackends = {}
|
@sidebarBackends ||= {}
|
||||||
@sidebarItems = []
|
@sidebarItems = []
|
||||||
sidebarBackends = App.Config.get('TicketZoomSidebar')
|
sidebarBackends = App.Config.get('TicketZoomSidebar')
|
||||||
keys = _.keys(sidebarBackends).sort()
|
keys = _.keys(sidebarBackends).sort()
|
||||||
for key in keys
|
for key in keys
|
||||||
@sidebarBackends[key] = new sidebarBackends[key](
|
if !@sidebarBackends[key] || !@sidebarBackends[key].reload
|
||||||
ticket: ticket
|
@sidebarBackends[key] = new sidebarBackends[key](
|
||||||
taskGet: @taskGet
|
ticket: ticket
|
||||||
formMeta: @formMeta
|
query: @query
|
||||||
markForm: @markForm
|
taskGet: @taskGet
|
||||||
)
|
formMeta: @formMeta
|
||||||
|
markForm: @markForm
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@sidebarBackends[key].reload(
|
||||||
|
params: @params
|
||||||
|
query: @query
|
||||||
|
formMeta: @formMeta
|
||||||
|
markForm: @markForm
|
||||||
|
)
|
||||||
item = @sidebarBackends[key].sidebarItem()
|
item = @sidebarBackends[key].sidebarItem()
|
||||||
if item
|
if item
|
||||||
@sidebarItems.push item
|
@sidebarItems.push item
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class SidebarCustomer extends App.Controller
|
class SidebarCustomer extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@permissionCheck('ticket.agent')
|
return if !@permissionCheck('ticket.agent')
|
||||||
{
|
items = {
|
||||||
head: 'Customer'
|
head: 'Customer'
|
||||||
name: 'customer'
|
name: 'customer'
|
||||||
icon: 'person'
|
icon: 'person'
|
||||||
|
@ -11,14 +11,16 @@ class SidebarCustomer extends App.Controller
|
||||||
name: 'customer-change'
|
name: 'customer-change'
|
||||||
callback: @changeCustomer
|
callback: @changeCustomer
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Edit Customer'
|
|
||||||
name: 'customer-edit'
|
|
||||||
callback: @editCustomer
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
callback: @showCustomer
|
callback: @showCustomer
|
||||||
}
|
}
|
||||||
|
return items if @ticket && @ticket.customer_id == 1
|
||||||
|
items.actions.push {
|
||||||
|
title: 'Edit Customer'
|
||||||
|
name: 'customer-edit'
|
||||||
|
callback: @editCustomer
|
||||||
|
}
|
||||||
|
items
|
||||||
|
|
||||||
showCustomer: (el) =>
|
showCustomer: (el) =>
|
||||||
@el = el
|
@el = el
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
class SidebarIdoit extends App.Controller
|
||||||
|
sidebarItem: =>
|
||||||
|
return if !@Config.get('idoit_integration')
|
||||||
|
{
|
||||||
|
head: 'i-doit'
|
||||||
|
name: 'idoit'
|
||||||
|
icon: 'printer'
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: 'Change Objects'
|
||||||
|
name: 'objects-change'
|
||||||
|
callback: @changeObjects
|
||||||
|
},
|
||||||
|
]
|
||||||
|
callback: @showObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
changeObjects: =>
|
||||||
|
new App.IdoitObjectSelector(
|
||||||
|
task_key: @task_key
|
||||||
|
container: @el.closest('.content')
|
||||||
|
callback: (objectIds, objectSelectorUi) =>
|
||||||
|
if @ticket && @ticket.id
|
||||||
|
@updateTicket(@ticket.id, objectIds, =>
|
||||||
|
objectSelectorUi.close()
|
||||||
|
@showObjectsContent(objectIds)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
objectSelectorUi.close()
|
||||||
|
@showObjectsContent(objectIds)
|
||||||
|
)
|
||||||
|
|
||||||
|
showObjects: (el) =>
|
||||||
|
@el = el
|
||||||
|
|
||||||
|
# show placeholder
|
||||||
|
@objectIds ||= []
|
||||||
|
if @ticket && @ticket.preferences && @ticket.preferences.idoit && @ticket.preferences.idoit.object_ids
|
||||||
|
@objectIds = @ticket.preferences.idoit.object_ids
|
||||||
|
queryParams = @queryParam()
|
||||||
|
if queryParams && queryParams.idoit_object_ids
|
||||||
|
@objectIds.push queryParams.idoit_object_ids
|
||||||
|
@showObjectsContent()
|
||||||
|
|
||||||
|
showObjectsContent: (objectIds) =>
|
||||||
|
if objectIds
|
||||||
|
@objectIds = @objectIds.concat(objectIds)
|
||||||
|
|
||||||
|
# show placeholder
|
||||||
|
if _.isEmpty(@objectIds)
|
||||||
|
@html("<div>#{App.i18n.translateInline('none')}</div>")
|
||||||
|
return
|
||||||
|
|
||||||
|
# ajax call to show items
|
||||||
|
@ajax(
|
||||||
|
id: "idoit-#{@task_key}"
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/idoit"
|
||||||
|
data: JSON.stringify(method: 'cmdb.objects', filter: ids: @objectIds)
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
if data.response
|
||||||
|
@showList(data.response.result)
|
||||||
|
return
|
||||||
|
@showError('Unable to load data...')
|
||||||
|
|
||||||
|
error: (xhr, status, error) =>
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
@showError('Unable to load data...')
|
||||||
|
)
|
||||||
|
|
||||||
|
showList: (objects) =>
|
||||||
|
list = $(App.view('ticket_zoom/sidebar_idoit')(
|
||||||
|
objects: objects
|
||||||
|
))
|
||||||
|
list.delegate('.js-delete', 'click', (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
objectId = $(e.currentTarget).attr 'data-object-id'
|
||||||
|
@delete(objectId)
|
||||||
|
)
|
||||||
|
@html(list)
|
||||||
|
|
||||||
|
showError: (message) =>
|
||||||
|
@html App.i18n.translateInline(message)
|
||||||
|
|
||||||
|
delete: (objectId) =>
|
||||||
|
localObjects = []
|
||||||
|
for localObjectId in @objectIds
|
||||||
|
if objectId.toString() isnt localObjectId.toString()
|
||||||
|
localObjects.push localObjectId
|
||||||
|
@objectIds = localObjects
|
||||||
|
if @ticket && @ticket.id
|
||||||
|
@updateTicket(@ticket.id, @objectIds)
|
||||||
|
@showObjectsContent()
|
||||||
|
|
||||||
|
commit: (args) =>
|
||||||
|
return if @ticket && @ticket.id
|
||||||
|
return if !@objectIds
|
||||||
|
return if _.isEmpty(@objectIds)
|
||||||
|
return if !args
|
||||||
|
return if !args.ticket_id
|
||||||
|
@updateTicket(args.ticket_id, @objectIds)
|
||||||
|
|
||||||
|
updateTicket: (ticket_id, objectIds, callback) =>
|
||||||
|
App.Ajax.request(
|
||||||
|
id: "idoit-update-#{ticket_id}"
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/integration/idoit_ticket_update"
|
||||||
|
data: JSON.stringify(ticket_id: ticket_id, object_ids: objectIds)
|
||||||
|
success: (data, status, xhr) ->
|
||||||
|
if callback
|
||||||
|
callback(objectIds)
|
||||||
|
|
||||||
|
error: (xhr, status, details) =>
|
||||||
|
|
||||||
|
# do not close window if request is aborted
|
||||||
|
return if status is 'abort'
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
@log 'errors', details
|
||||||
|
@notify(
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
|
||||||
|
timeout: 6000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('500-Idoit', SidebarIdoit, 'TicketCreateSidebar')
|
||||||
|
App.Config.set('500-Idoit', SidebarIdoit, 'TicketZoomSidebar')
|
|
@ -1,5 +1,6 @@
|
||||||
class SidebarOrganization extends App.Controller
|
class SidebarOrganization extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
|
return if !@permissionCheck('ticket.agent')
|
||||||
return if !@ticket.organization_id
|
return if !@ticket.organization_id
|
||||||
{
|
{
|
||||||
head: 'Organization'
|
head: 'Organization'
|
||||||
|
|
|
@ -69,7 +69,7 @@ class SidebarTicket extends App.Controller
|
||||||
if args.tags
|
if args.tags
|
||||||
@tagWidget.reload(args.tags)
|
@tagWidget.reload(args.tags)
|
||||||
if args.tagAdd
|
if args.tagAdd
|
||||||
@tagWidget.add(args.tagAdd)
|
@tagWidget.add(args.tagAdd, args.source)
|
||||||
if args.tagRemove
|
if args.tagRemove
|
||||||
@tagWidget.remove(args.tagRemove)
|
@tagWidget.remove(args.tagRemove)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@ class App.WidgetAvatar extends App.ObserverController
|
||||||
email: true
|
email: true
|
||||||
image: true
|
image: true
|
||||||
vip: true
|
vip: true
|
||||||
|
out_of_office: true,
|
||||||
|
out_of_office_start_at: true,
|
||||||
|
out_of_office_end_at: true,
|
||||||
|
out_of_office_replacement_id: true,
|
||||||
|
active: true
|
||||||
|
|
||||||
globalRerender: false
|
globalRerender: false
|
||||||
|
|
||||||
render: (user) =>
|
render: (user) =>
|
||||||
|
|
|
@ -5,10 +5,10 @@ class Widget
|
||||||
banner = """
|
banner = """
|
||||||
|
|
|
|
||||||
| Welcome Zammad Developer!
|
| Welcome Zammad Developer!
|
||||||
| You can enable debugging by the following examples (value is a regex):
|
| You can enable debugging with the following examples (value is a regex):
|
||||||
|
|
|
|
||||||
| App.Log.config('module', '(websocket|delay|interval)') // enable debugging for websocket, delay and interval class
|
| App.Log.config('module', '(websocket|delay|interval)') // enable debugging for websocket, delay and interval class
|
||||||
| App.Log.config('content', 'send') // enable debugging for messages which contains the string 'send'
|
| App.Log.config('content', 'send') // enable debugging for messages which contain the string 'send'
|
||||||
| App.Log.config('banner', false) // disable this banner
|
| App.Log.config('banner', false) // disable this banner
|
||||||
|
|
|
|
||||||
| App.Log.config() // current settings
|
| App.Log.config() // current settings
|
||||||
|
|
|
@ -10,9 +10,9 @@ class Widget
|
||||||
|
|
|
|
||||||
| Hi there, nice to meet you!
|
| Hi there, nice to meet you!
|
||||||
|
|
|
|
||||||
| Visit %chttps://zammad.org/participate%c and let's make Zammad better.
|
| Visit %chttp://zammad.com/jobs%c to learn about our current job openings.
|
||||||
|
|
|
|
||||||
| The Zammad Team.
|
| Your Zammad Team
|
||||||
|
|
|
|
||||||
"""
|
"""
|
||||||
console.log(banner, 'text-decoration: underline;', 'text-decoration: none;')
|
console.log(banner, 'text-decoration: underline;', 'text-decoration: none;')
|
||||||
|
|
|
@ -25,6 +25,7 @@ class App.HttpLog extends App.Controller
|
||||||
render: =>
|
render: =>
|
||||||
@html App.view('widget/http_log')(
|
@html App.view('widget/http_log')(
|
||||||
records: @records
|
records: @records
|
||||||
|
description: @description
|
||||||
)
|
)
|
||||||
|
|
||||||
show: (e) =>
|
show: (e) =>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
class App.ScriptSnipped extends App.Controller
|
||||||
|
#events:
|
||||||
|
# 'click .js-record': 'show'
|
||||||
|
|
||||||
|
elements:
|
||||||
|
'.js-code': 'code'
|
||||||
|
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
#@fetch()
|
||||||
|
@records = []
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
@html App.view('widget/script_snipped')(
|
||||||
|
records: @records
|
||||||
|
description: @description
|
||||||
|
style: @style
|
||||||
|
content: @content
|
||||||
|
)
|
||||||
|
|
||||||
|
@code.each (i, block) ->
|
||||||
|
hljs.highlightBlock block
|
|
@ -86,16 +86,16 @@ class App.WidgetTag extends App.Controller
|
||||||
return
|
return
|
||||||
@add(item)
|
@add(item)
|
||||||
|
|
||||||
add: (items) =>
|
add: (items, source = '') =>
|
||||||
for item in items.split(',')
|
for item in items.split(',')
|
||||||
item = item.trim()
|
item = item.trim()
|
||||||
@addItem(item)
|
@addItem(item, source)
|
||||||
|
|
||||||
addItem: (item) =>
|
addItem: (item, source = '') =>
|
||||||
if _.contains(@localTags, item)
|
if _.contains(@localTags, item)
|
||||||
@render()
|
@render()
|
||||||
return
|
return
|
||||||
return if App.Config.get('tag_new') is false && !@possibleTags[item]
|
return if source != 'macro' && App.Config.get('tag_new') is false && !@possibleTags[item]
|
||||||
@localTags.push item
|
@localTags.push item
|
||||||
@render()
|
@render()
|
||||||
App[@object_type].tagAdd(@object.id, item)
|
App[@object_type].tagAdd(@object.id, item)
|
||||||
|
|
|
@ -8,7 +8,7 @@ class App.WidgetTextModule extends App.Controller
|
||||||
# remember instances
|
# remember instances
|
||||||
@bindElements = []
|
@bindElements = []
|
||||||
if @selector
|
if @selector
|
||||||
@bindElements = @$( @selector ).textmodule()
|
@bindElements = @$(@selector).textmodule()
|
||||||
else
|
else
|
||||||
if @el.attr('contenteditable')
|
if @el.attr('contenteditable')
|
||||||
@bindElements = @el.textmodule()
|
@bindElements = @el.textmodule()
|
||||||
|
|
57
app/assets/javascripts/app/lib/app_init/queue_manager.coffee
Normal file
57
app/assets/javascripts/app/lib/app_init/queue_manager.coffee
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
class App.QueueManager
|
||||||
|
_instance = undefined
|
||||||
|
|
||||||
|
@init: ->
|
||||||
|
_instance ?= new _queueSingleton
|
||||||
|
|
||||||
|
@add: (key, data) ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _queueSingleton
|
||||||
|
_instance.add(key, data)
|
||||||
|
|
||||||
|
@pull: (key) ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _queueSingleton
|
||||||
|
_instance.pull(key)
|
||||||
|
|
||||||
|
@all: (key) ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _queueSingleton
|
||||||
|
_instance.all(key)
|
||||||
|
|
||||||
|
@run: (key, callback) ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _queueSingleton
|
||||||
|
_instance.run(key, callback)
|
||||||
|
|
||||||
|
class _queueSingleton
|
||||||
|
constructor: ->
|
||||||
|
@queues = {}
|
||||||
|
@queueRunning = {}
|
||||||
|
|
||||||
|
add: (key, data) ->
|
||||||
|
if !@queues[key]
|
||||||
|
@queues[key] = []
|
||||||
|
@queues[key].push data
|
||||||
|
true
|
||||||
|
|
||||||
|
pull: (key) ->
|
||||||
|
return if !@queues[key]
|
||||||
|
@queues[key].shift()
|
||||||
|
|
||||||
|
all: (key) ->
|
||||||
|
@queues[key]
|
||||||
|
|
||||||
|
run: (key, callback) ->
|
||||||
|
return if !@queues[key]
|
||||||
|
return if @queueRunning[key]
|
||||||
|
localQueue = @queues[key]
|
||||||
|
return if _.isEmpty(localQueue)
|
||||||
|
@queueRunning[key] = true
|
||||||
|
loop
|
||||||
|
callback = localQueue.shift()
|
||||||
|
callback()
|
||||||
|
if !localQueue[0]
|
||||||
|
@queueRunning[key] = false
|
||||||
|
break
|
||||||
|
true
|
|
@ -5,9 +5,9 @@ class App._CollectionSingletonBase
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@callbacks = {}
|
@callbacks = {}
|
||||||
@counter = 0
|
@counter = 0
|
||||||
|
@key = "collection-#{@event}"
|
||||||
# read from cache
|
# read from cache
|
||||||
cache = App.SessionStorage.get("collection-#{@event}")
|
cache = App.SessionStorage.get(@key)
|
||||||
if cache
|
if cache
|
||||||
@set(cache)
|
@set(cache)
|
||||||
|
|
||||||
|
@ -73,6 +73,9 @@ class App._CollectionSingletonBase
|
||||||
|
|
||||||
callback: (data) =>
|
callback: (data) =>
|
||||||
for counter, attr of @callbacks
|
for counter, attr of @callbacks
|
||||||
attr.callback(data)
|
callback = ->
|
||||||
if attr.one
|
attr.callback(data)
|
||||||
delete @callbacks[counter]
|
if attr.one
|
||||||
|
delete @callbacks[counter]
|
||||||
|
App.QueueManager.add(@key, callback)
|
||||||
|
App.QueueManager.run(@key)
|
||||||
|
|
|
@ -113,6 +113,10 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
@createToken name, objectId
|
@createToken name, objectId
|
||||||
else
|
else
|
||||||
if object.email
|
if object.email
|
||||||
|
|
||||||
|
# quote name for special character
|
||||||
|
if name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|!|\*|\[|\]/)
|
||||||
|
name = "\"#{name}\""
|
||||||
name += " <#{object.email}>"
|
name += " <#{object.email}>"
|
||||||
|
|
||||||
@objectSelect.val(name)
|
@objectSelect.val(name)
|
||||||
|
@ -390,14 +394,14 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: 0
|
translateX: 0
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
|
|
||||||
# fade out list
|
# fade out list
|
||||||
@recipientList.velocity
|
@recipientList.velocity
|
||||||
properties:
|
properties:
|
||||||
translateX: '-100%'
|
translateX: '-100%'
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
complete: => @recipientList.height(@organizationList.height())
|
complete: => @recipientList.height(@organizationList.height())
|
||||||
|
|
||||||
hideOrganizationMembers: (e) =>
|
hideOrganizationMembers: (e) =>
|
||||||
|
@ -413,7 +417,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: 0
|
translateX: 0
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
|
|
||||||
# reset list height
|
# reset list height
|
||||||
@recipientList.height('')
|
@recipientList.height('')
|
||||||
|
@ -423,7 +427,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
properties:
|
properties:
|
||||||
translateX: '100%'
|
translateX: '100%'
|
||||||
options:
|
options:
|
||||||
speed: 300
|
duration: 240
|
||||||
complete: => @organizationList.addClass('hide')
|
complete: => @organizationList.addClass('hide')
|
||||||
|
|
||||||
newObject: (e) ->
|
newObject: (e) ->
|
||||||
|
|
|
@ -6,25 +6,25 @@ class App.ClipBoard
|
||||||
_instance ?= new _Singleton
|
_instance ?= new _Singleton
|
||||||
_instance.bind(el)
|
_instance.bind(el)
|
||||||
|
|
||||||
@getSelected: ->
|
@getSelected: (type) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _Singleton
|
_instance ?= new _Singleton
|
||||||
_instance.getSelected()
|
_instance.getSelected(type)
|
||||||
|
|
||||||
@getSelectedLast: ->
|
@getSelectedLast: (type) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _Singleton
|
_instance ?= new _Singleton
|
||||||
_instance.getSelectedLast()
|
_instance.getSelectedLast(type)
|
||||||
|
|
||||||
@getPosition: (el) ->
|
@getPosition: (el) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _Singleton
|
_instance ?= new _Singleton
|
||||||
_instance.getPosition(el)
|
_instance.getPosition(el)
|
||||||
|
|
||||||
@setPosition: ( el, pos ) ->
|
@setPosition: (el, pos) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _Singleton
|
_instance ?= new _Singleton
|
||||||
_instance.setPosition( el, pos )
|
_instance.setPosition(el, pos)
|
||||||
|
|
||||||
@keycode: (code) ->
|
@keycode: (code) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
|
@ -33,54 +33,68 @@ class App.ClipBoard
|
||||||
|
|
||||||
class _Singleton
|
class _Singleton
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@selection = ''
|
@selection =
|
||||||
@selectionLast = ''
|
html: ''
|
||||||
|
text: ''
|
||||||
|
@selectionLast =
|
||||||
|
html: ''
|
||||||
|
text: ''
|
||||||
|
|
||||||
# bind to fill selected text into
|
# bind to fill selected text into
|
||||||
bind: (el) ->
|
bind: (el) ->
|
||||||
$(el).bind('mouseup', =>
|
|
||||||
|
|
||||||
# check selection on mouse up
|
# check selection on mouse up
|
||||||
@selection = @_getSelected()
|
$(el).bind('mouseup', =>
|
||||||
if @selection
|
@_updateSelection()
|
||||||
@selectionLast = @selection
|
|
||||||
)
|
)
|
||||||
$(el).bind('keyup', (e) =>
|
$(el).bind('keyup', (e) =>
|
||||||
|
|
||||||
# check selection on sonder key
|
# check selection on sonder key
|
||||||
if e.keyCode == 91
|
if e.keyCode == 91
|
||||||
@selection = @_getSelected()
|
@_updateSelection()
|
||||||
if @selection
|
|
||||||
@selectionLast = @selection
|
|
||||||
|
|
||||||
# check selection of arrow keys
|
# check selection of arrow keys
|
||||||
if e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40
|
if e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40
|
||||||
@selection = @_getSelected()
|
@_updateSelection()
|
||||||
if @selection
|
|
||||||
@selectionLast = @selection
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_updateSelection: =>
|
||||||
|
for key in ['html', 'text']
|
||||||
|
@selection[key] = @_getSelected(key)
|
||||||
|
if @selection[key]
|
||||||
|
@selectionLast[key] = @selection[key]
|
||||||
|
|
||||||
# get cross browser selected string
|
# get cross browser selected string
|
||||||
_getSelected: ->
|
_getSelected: (type) ->
|
||||||
text = ''
|
text = ''
|
||||||
|
html = ''
|
||||||
if window.getSelection
|
if window.getSelection
|
||||||
text = window.getSelection()
|
sel = window.getSelection()
|
||||||
|
text = sel.toString()
|
||||||
else if document.getSelection
|
else if document.getSelection
|
||||||
text = document.getSelection()
|
sel = document.getSelection()
|
||||||
|
text = sel.toString()
|
||||||
else if document.selection
|
else if document.selection
|
||||||
text = document.selection.createRange().text
|
sel = document.selection.createRange()
|
||||||
if text
|
text = sel.text
|
||||||
# text = text.toString().trim()
|
if type is 'text'
|
||||||
text = $.trim( text.toString() )
|
return $.trim(text.toString()) if text
|
||||||
text
|
return ''
|
||||||
|
|
||||||
|
if sel && sel.rangeCount
|
||||||
|
container = document.createElement('div')
|
||||||
|
for i in [1..sel.rangeCount]
|
||||||
|
container.appendChild(sel.getRangeAt(i-1).cloneContents())
|
||||||
|
html = container.innerHTML
|
||||||
|
html
|
||||||
|
|
||||||
# get current selection
|
# get current selection
|
||||||
getSelected: ->
|
getSelected: (type) ->
|
||||||
@selection
|
@selection[type]
|
||||||
|
|
||||||
# get latest selection
|
# get latest selection
|
||||||
getSelectedLast: ->
|
getSelectedLast: (type) ->
|
||||||
@selectionLast
|
@selectionLast[type]
|
||||||
|
|
||||||
getPosition: (el) ->
|
getPosition: (el) ->
|
||||||
pos = 0
|
pos = 0
|
||||||
|
@ -104,13 +118,13 @@ class _Singleton
|
||||||
# IE Support
|
# IE Support
|
||||||
if el.setSelectionRange
|
if el.setSelectionRange
|
||||||
el.focus()
|
el.focus()
|
||||||
el.setSelectionRange( pos, pos )
|
el.setSelectionRange(pos, pos)
|
||||||
|
|
||||||
# Firefox support
|
# Firefox support
|
||||||
else if el.createTextRange
|
else if el.createTextRange
|
||||||
range = el.createTextRange()
|
range = el.createTextRange()
|
||||||
range.collapse(true)
|
range.collapse(true)
|
||||||
range.moveEnd( 'character', pos )
|
range.moveEnd('character', pos)
|
||||||
range.moveStart('character', pos)
|
range.moveStart('character', pos)
|
||||||
range.select()
|
range.select()
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,11 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
@select @pickedValue
|
@select @pickedValue
|
||||||
, 300, {trailing: false}
|
, 300, {trailing: false}
|
||||||
|
|
||||||
|
if @attribute.onChange
|
||||||
|
@shadow.on('change', =>
|
||||||
|
@attribute.onChange(@shadow.val())
|
||||||
|
)
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
@values = []
|
@values = []
|
||||||
_.each @options.attribute.options, (option) =>
|
_.each @options.attribute.options, (option) =>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
class App.Delay
|
class App.Delay
|
||||||
_instance = undefined
|
_instance = undefined
|
||||||
|
|
||||||
@set: (callback, timeout, key, level) ->
|
@set: (callback, timeout, key, level, queue) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _delaySingleton
|
_instance ?= new _delaySingleton
|
||||||
_instance.set(callback, timeout, key, level)
|
_instance.set(callback, timeout, key, level, queue)
|
||||||
|
|
||||||
@clear: (key, level) ->
|
@clear: (key, level) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
|
@ -21,6 +21,11 @@ class App.Delay
|
||||||
_instance ?= new _delaySingleton
|
_instance ?= new _delaySingleton
|
||||||
_instance.reset()
|
_instance.reset()
|
||||||
|
|
||||||
|
@count: ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _intervalSingleton
|
||||||
|
_instance.count()
|
||||||
|
|
||||||
@_all: ->
|
@_all: ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _delaySingleton
|
_instance ?= new _delaySingleton
|
||||||
|
@ -32,7 +37,7 @@ class _delaySingleton extends Spine.Module
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@levelStack = {}
|
@levelStack = {}
|
||||||
|
|
||||||
set: (callback, timeout, key, level) =>
|
set: (callback, timeout, key, level, queue) =>
|
||||||
|
|
||||||
if !level
|
if !level
|
||||||
level = '_all'
|
level = '_all'
|
||||||
|
@ -44,11 +49,15 @@ class _delaySingleton extends Spine.Module
|
||||||
key = Math.floor(Math.random() * 99999)
|
key = Math.floor(Math.random() * 99999)
|
||||||
|
|
||||||
# setTimeout
|
# setTimeout
|
||||||
@log 'debug', 'set', key, timeout, level, callback
|
@log 'debug', 'set', key, timeout, level, callback, queue
|
||||||
call = =>
|
localCallback = =>
|
||||||
@clear(key, level)
|
@clear(key, level)
|
||||||
callback()
|
if queue
|
||||||
delay_id = setTimeout(call, timeout)
|
App.QueueManager.add('delay', callback)
|
||||||
|
App.QueueManager.run('delay')
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
delay_id = setTimeout(localCallback, timeout)
|
||||||
|
|
||||||
# remember all delays
|
# remember all delays
|
||||||
if !@levelStack[level]
|
if !@levelStack[level]
|
||||||
|
@ -93,6 +102,13 @@ class _delaySingleton extends Spine.Module
|
||||||
@levelStack[level] = {}
|
@levelStack[level] = {}
|
||||||
true
|
true
|
||||||
|
|
||||||
|
count: =>
|
||||||
|
return 0 if !@levelStack
|
||||||
|
count = 0
|
||||||
|
for levelName, levelValue of @levelStack
|
||||||
|
count += Object.keys(levelValue).length
|
||||||
|
count
|
||||||
|
|
||||||
_all: =>
|
_all: =>
|
||||||
@levelStack
|
@levelStack
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,11 @@ class App.i18n
|
||||||
_instance ?= new _i18nSingleton()
|
_instance ?= new _i18nSingleton()
|
||||||
_instance.date(args, offset)
|
_instance.date(args, offset)
|
||||||
|
|
||||||
|
@dir: ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _i18nSingleton()
|
||||||
|
_instance.dir()
|
||||||
|
|
||||||
@get: ->
|
@get: ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _i18nSingleton()
|
_instance ?= new _i18nSingleton()
|
||||||
|
@ -88,6 +93,10 @@ class _i18nSingleton extends Spine.Module
|
||||||
@_notTranslated = {}
|
@_notTranslated = {}
|
||||||
@dateFormat = 'yyyy-mm-dd'
|
@dateFormat = 'yyyy-mm-dd'
|
||||||
@timestampFormat = 'yyyy-mm-dd HH:MM'
|
@timestampFormat = 'yyyy-mm-dd HH:MM'
|
||||||
|
@dirToSet = 'ltr'
|
||||||
|
|
||||||
|
dir: ->
|
||||||
|
@dirToSet
|
||||||
|
|
||||||
get: ->
|
get: ->
|
||||||
@locale
|
@locale
|
||||||
|
@ -96,12 +105,15 @@ class _i18nSingleton extends Spine.Module
|
||||||
|
|
||||||
# prepare locale
|
# prepare locale
|
||||||
localeToSet = localeToSet.toLowerCase()
|
localeToSet = localeToSet.toLowerCase()
|
||||||
|
@dirToSet = 'ltr'
|
||||||
|
|
||||||
# check if locale exists
|
# check if locale exists
|
||||||
localeFound = false
|
localeFound = false
|
||||||
locales = App.Locale.all()
|
locales = App.Locale.all()
|
||||||
for locale in locales
|
for locale in locales
|
||||||
if locale.locale is localeToSet
|
if locale.locale is localeToSet
|
||||||
|
localeToSet = locale.locale
|
||||||
|
@dirToSet = locale.dir
|
||||||
localeFound = true
|
localeFound = true
|
||||||
|
|
||||||
# try aliases
|
# try aliases
|
||||||
|
@ -109,6 +121,8 @@ class _i18nSingleton extends Spine.Module
|
||||||
for locale in locales
|
for locale in locales
|
||||||
if locale.alias is localeToSet
|
if locale.alias is localeToSet
|
||||||
localeToSet = locale.locale
|
localeToSet = locale.locale
|
||||||
|
@dirToSet = locale.dir
|
||||||
|
localeFound = true
|
||||||
|
|
||||||
# if no locale and no alias was found, try to find correct one
|
# if no locale and no alias was found, try to find correct one
|
||||||
if !localeFound
|
if !localeFound
|
||||||
|
@ -118,15 +132,9 @@ class _i18nSingleton extends Spine.Module
|
||||||
for locale in locales
|
for locale in locales
|
||||||
if locale.alias is localeToSet
|
if locale.alias is localeToSet
|
||||||
localeToSet = locale.locale
|
localeToSet = locale.locale
|
||||||
|
@dirToSet = locale.dir
|
||||||
localeFound = true
|
localeFound = true
|
||||||
|
|
||||||
# try to find by locale
|
|
||||||
if !localeFound
|
|
||||||
for locale in locales
|
|
||||||
if locale.locale is localeToSet
|
|
||||||
localeToSet = locale.locale
|
|
||||||
localeFound = true
|
|
||||||
|
|
||||||
# check if locale need to be changed
|
# check if locale need to be changed
|
||||||
return if localeToSet is @locale
|
return if localeToSet is @locale
|
||||||
|
|
||||||
|
@ -136,8 +144,9 @@ class _i18nSingleton extends Spine.Module
|
||||||
# set if not translated should be logged
|
# set if not translated should be logged
|
||||||
@_notTranslatedLog = @notTranslatedFeatureEnabled(@locale)
|
@_notTranslatedLog = @notTranslatedFeatureEnabled(@locale)
|
||||||
|
|
||||||
# set lang attribute of html tag
|
# set lang and dir attribute of html tag
|
||||||
$('html').prop('lang', @locale.substr(0, 2) )
|
$('html').prop('lang', localeToSet.substr(0, 2))
|
||||||
|
$('html').prop('dir', @dirToSet)
|
||||||
|
|
||||||
@mapString = {}
|
@mapString = {}
|
||||||
App.Ajax.request(
|
App.Ajax.request(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
class App.Interval
|
class App.Interval
|
||||||
_instance = undefined
|
_instance = undefined
|
||||||
|
|
||||||
@set: (callback, timeout, key, level) ->
|
@set: (callback, timeout, key, level, queue) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _intervalSingleton
|
_instance ?= new _intervalSingleton
|
||||||
_instance.set(callback, timeout, key, level)
|
_instance.set(callback, timeout, key, level, queue)
|
||||||
|
|
||||||
@clear: (key, level) ->
|
@clear: (key, level) ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
|
@ -21,6 +21,11 @@ class App.Interval
|
||||||
_instance ?= new _intervalSingleton
|
_instance ?= new _intervalSingleton
|
||||||
_instance.reset()
|
_instance.reset()
|
||||||
|
|
||||||
|
@count: ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _intervalSingleton
|
||||||
|
_instance.count()
|
||||||
|
|
||||||
@_all: ->
|
@_all: ->
|
||||||
if _instance == undefined
|
if _instance == undefined
|
||||||
_instance ?= new _intervalSingleton
|
_instance ?= new _intervalSingleton
|
||||||
|
@ -32,7 +37,7 @@ class _intervalSingleton extends Spine.Module
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@levelStack = {}
|
@levelStack = {}
|
||||||
|
|
||||||
set: (callback, timeout, key, level) =>
|
set: (callback, timeout, key, level, queue) =>
|
||||||
|
|
||||||
if !level
|
if !level
|
||||||
level = '_all'
|
level = '_all'
|
||||||
|
@ -44,9 +49,15 @@ class _intervalSingleton extends Spine.Module
|
||||||
key = Math.floor(Math.random() * 99999)
|
key = Math.floor(Math.random() * 99999)
|
||||||
|
|
||||||
# setTimeout
|
# setTimeout
|
||||||
@log 'debug', 'set', key, timeout, level, callback
|
@log 'debug', 'set', key, timeout, level, callback, queue
|
||||||
callback()
|
localCallback = ->
|
||||||
interval_id = setInterval(callback, timeout)
|
if queue
|
||||||
|
App.QueueManager.add('interval', callback)
|
||||||
|
App.QueueManager.run('interval')
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
localCallback()
|
||||||
|
interval_id = setInterval(localCallback, timeout)
|
||||||
|
|
||||||
# remember all interval
|
# remember all interval
|
||||||
if !@levelStack[level]
|
if !@levelStack[level]
|
||||||
|
@ -91,5 +102,12 @@ class _intervalSingleton extends Spine.Module
|
||||||
@levelStack[level] = {}
|
@levelStack[level] = {}
|
||||||
true
|
true
|
||||||
|
|
||||||
|
count: =>
|
||||||
|
return 0 if !@levelStack
|
||||||
|
count = 0
|
||||||
|
for levelName, levelValue of @levelStack
|
||||||
|
count += Object.keys(levelValue).length
|
||||||
|
count
|
||||||
|
|
||||||
_all: =>
|
_all: =>
|
||||||
@levelStack
|
@levelStack
|
||||||
|
|
|
@ -71,7 +71,10 @@ class _Singleton
|
||||||
callback: (view, data) =>
|
callback: (view, data) =>
|
||||||
for counter, meta of @callbacks
|
for counter, meta of @callbacks
|
||||||
if meta.view is view
|
if meta.view is view
|
||||||
meta.callback(data)
|
callback = ->
|
||||||
|
meta.callback(data)
|
||||||
|
App.QueueManager.add('ticket_overviews', callback)
|
||||||
|
App.QueueManager.run('ticket_overviews')
|
||||||
|
|
||||||
class App.OverviewListCollection
|
class App.OverviewListCollection
|
||||||
_instance = new _Singleton
|
_instance = new _Singleton
|
||||||
|
|
|
@ -38,11 +38,16 @@ class App.PrettyDate
|
||||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||||
month = months[created.getMonth()]
|
month = months[created.getMonth()]
|
||||||
|
|
||||||
# for less than 7 days
|
# for less than 6 days
|
||||||
if diff < (60 * 60 * 24 * 7)
|
# weekday HH::MM
|
||||||
|
if diff < (60 * 60 * 24 * 6)
|
||||||
string = "#{App.i18n.translateInline(weekday)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
string = "#{App.i18n.translateInline(weekday)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
||||||
else if diff < (60 * 60 * 24 * 7) * 365
|
# if it was this year
|
||||||
|
# weekday DD. MM HH::MM
|
||||||
|
else if created.getYear() is current.getYear()
|
||||||
string = "#{App.i18n.translateInline(weekday)} #{created.getDate()}. #{App.i18n.translateInline(month)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
string = "#{App.i18n.translateInline(weekday)} #{created.getDate()}. #{App.i18n.translateInline(month)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
||||||
|
# if it was the year before
|
||||||
|
# weekday YYYY-MM-DD HH::MM
|
||||||
else
|
else
|
||||||
string = "#{App.i18n.translateInline(weekday)} #{App.i18n.translateTimestamp(time)}"
|
string = "#{App.i18n.translateInline(weekday)} #{App.i18n.translateTimestamp(time)}"
|
||||||
if escalation
|
if escalation
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
class App.SearchableSelect extends Spine.Controller
|
class App.SearchableSelect extends Spine.Controller
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'input .js-input': 'onInput'
|
'input .js-input': 'onInput'
|
||||||
'blur .js-input': 'onBlur'
|
'blur .js-input': 'onBlur'
|
||||||
'focus .js-input': 'onFocus'
|
'focus .js-input': 'onFocus'
|
||||||
'click .js-option': 'selectItem'
|
'click .js-option': 'selectItem'
|
||||||
'mouseenter .js-option': 'highlightItem'
|
'click .js-enter': 'navigateIn'
|
||||||
'shown.bs.dropdown': 'onDropdownShown'
|
'click .js-back': 'navigateOut'
|
||||||
'hidden.bs.dropdown': 'onDropdownHidden'
|
'mouseenter .js-option': 'highlightItem'
|
||||||
|
'mouseenter .js-enter': 'highlightItem'
|
||||||
|
'mouseenter .js-back': 'highlightItem'
|
||||||
|
'shown.bs.dropdown': 'onDropdownShown'
|
||||||
|
'hidden.bs.dropdown': 'onDropdownHidden'
|
||||||
|
'keyup .js-input': 'onKeyUp'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-option': 'option_items'
|
'.js-dropdown': 'dropdown'
|
||||||
|
'.js-option, .js-enter': 'optionItems'
|
||||||
'.js-input': 'input'
|
'.js-input': 'input'
|
||||||
'.js-shadow': 'shadowInput'
|
'.js-shadow': 'shadowInput'
|
||||||
'.js-optionsList': 'optionsList'
|
'.js-optionsList': 'optionsList'
|
||||||
|
'.js-optionsSubmenu': 'optionsSubmenu'
|
||||||
'.js-autocomplete-invisible': 'invisiblePart'
|
'.js-autocomplete-invisible': 'invisiblePart'
|
||||||
'.js-autocomplete-visible': 'visiblePart'
|
'.js-autocomplete-visible': 'visiblePart'
|
||||||
|
|
||||||
|
@ -27,32 +34,99 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
firstSelected = _.find @options.attribute.options, (option) -> option.selected
|
firstSelected = _.find @attribute.options, (option) -> option.selected
|
||||||
|
|
||||||
if firstSelected
|
if firstSelected
|
||||||
@options.attribute.valueName = firstSelected.name
|
@attribute.valueName = firstSelected.name
|
||||||
@options.attribute.value = firstSelected.value
|
@attribute.value = firstSelected.value
|
||||||
else if @options.attribute.unknown && @options.attribute.value
|
else if @attribute.unknown && @attribute.value
|
||||||
@options.attribute.valueName = @options.attribute.value
|
@attribute.valueName = @attribute.value
|
||||||
|
else if @hasSubmenu @attribute.options
|
||||||
|
@attribute.valueName = @getName @attribute.value, @attribute.options
|
||||||
|
|
||||||
@options.attribute.renderedOptions = App.view('generic/searchable_select_options')
|
@html App.view('generic/searchable_select')
|
||||||
options: @options.attribute.options
|
attribute: @attribute
|
||||||
|
options: @renderAllOptions '', @attribute.options, 0
|
||||||
|
submenus: @renderSubmenus @attribute.options
|
||||||
|
|
||||||
@html App.view('generic/searchable_select')( @options.attribute )
|
# initial data
|
||||||
|
@currentMenu = @findMenuContainingValue @attribute.value
|
||||||
|
@level = @getIndex @currentMenu
|
||||||
|
|
||||||
@input.on 'keydown', @navigate
|
renderSubmenus: (options) ->
|
||||||
|
html = ''
|
||||||
|
if options
|
||||||
|
for option in options
|
||||||
|
if option.children
|
||||||
|
html += App.view('generic/searchable_select_submenu')
|
||||||
|
options: @renderOptions(option.children)
|
||||||
|
parentValue: option.value
|
||||||
|
title: option.name
|
||||||
|
|
||||||
|
if @hasSubmenu(option.children)
|
||||||
|
html += @renderSubmenus option.children
|
||||||
|
html
|
||||||
|
|
||||||
|
hasSubmenu: (options) ->
|
||||||
|
return false if !options
|
||||||
|
for option in options
|
||||||
|
return true if option.children
|
||||||
|
return false
|
||||||
|
|
||||||
|
getName: (value, options) ->
|
||||||
|
for option in options
|
||||||
|
if option.value is value
|
||||||
|
return option.name
|
||||||
|
if option.children
|
||||||
|
name = @getName value, option.children
|
||||||
|
return name if name isnt undefined
|
||||||
|
undefined
|
||||||
|
|
||||||
|
renderOptions: (options) ->
|
||||||
|
html = ''
|
||||||
|
for option in options
|
||||||
|
html += App.view('generic/searchable_select_option')
|
||||||
|
option: option
|
||||||
|
class: if option.children then 'js-enter' else 'js-option'
|
||||||
|
html
|
||||||
|
|
||||||
|
renderAllOptions: (parentName, options, level) ->
|
||||||
|
html = ''
|
||||||
|
if options
|
||||||
|
for option in options
|
||||||
|
className = if option.children then 'js-enter' else 'js-option'
|
||||||
|
if level && level > 0
|
||||||
|
className += ' is-hidden is-child'
|
||||||
|
|
||||||
|
html += App.view('generic/searchable_select_option')
|
||||||
|
option: option
|
||||||
|
class: className
|
||||||
|
detail: parentName
|
||||||
|
|
||||||
|
if option.children
|
||||||
|
html += @renderAllOptions "#{parentName} — #{option.name}", option.children, level+1
|
||||||
|
html
|
||||||
|
|
||||||
onDropdownShown: =>
|
onDropdownShown: =>
|
||||||
@input.on 'click', @stopPropagation
|
@input.on 'click', @stopPropagation
|
||||||
@highlightFirst()
|
@highlightFirst()
|
||||||
|
$(document).on 'keydown.searchable_select', @navigate
|
||||||
|
if @level > 0
|
||||||
|
@showSubmenu(@currentMenu)
|
||||||
@isOpen = true
|
@isOpen = true
|
||||||
|
|
||||||
onDropdownHidden: =>
|
onDropdownHidden: =>
|
||||||
@input.off 'click', @stopPropagation
|
@input.off 'click', @stopPropagation
|
||||||
@option_items.removeClass '.is-active'
|
@unhighlightCurrentItem()
|
||||||
|
$(document).off 'keydown.searchable_select'
|
||||||
@isOpen = false
|
@isOpen = false
|
||||||
|
|
||||||
|
onKeyUp: =>
|
||||||
|
return if @input.val().trim() isnt ''
|
||||||
|
@shadowInput.val('')
|
||||||
|
|
||||||
toggle: =>
|
toggle: =>
|
||||||
|
@currentItem = null
|
||||||
@$('[data-toggle="dropdown"]').dropdown('toggle')
|
@$('[data-toggle="dropdown"]').dropdown('toggle')
|
||||||
|
|
||||||
stopPropagation: (event) ->
|
stopPropagation: (event) ->
|
||||||
|
@ -62,8 +136,8 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
switch event.keyCode
|
switch event.keyCode
|
||||||
when 40 then @nudge event, 1 # down
|
when 40 then @nudge event, 1 # down
|
||||||
when 38 then @nudge event, -1 # up
|
when 38 then @nudge event, -1 # up
|
||||||
when 39 then @fillWithAutocompleteSuggestion event # right
|
when 39 then @autocompleteOrNavigateIn event # right
|
||||||
when 37 then @fillWithAutocompleteSuggestion event # left
|
when 37 then @autocompleteOrNavigateOut event # left
|
||||||
when 13 then @onEnter event
|
when 13 then @onEnter event
|
||||||
when 27 then @onEscape()
|
when 27 then @onEscape()
|
||||||
when 9 then @onTab event
|
when 9 then @onTab event
|
||||||
|
@ -71,12 +145,20 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
onEscape: ->
|
onEscape: ->
|
||||||
@toggle() if @isOpen
|
@toggle() if @isOpen
|
||||||
|
|
||||||
|
getCurrentOptions: ->
|
||||||
|
@currentMenu.find('.js-option, .js-enter, .js-back')
|
||||||
|
|
||||||
|
getOptionIndex: (menu, value) ->
|
||||||
|
menu.find('.js-option, .js-enter').filter("[data-value=\"#{value}\"]").index()
|
||||||
|
|
||||||
nudge: (event, direction) ->
|
nudge: (event, direction) ->
|
||||||
return @toggle() if not @isOpen
|
return @toggle() if not @isOpen
|
||||||
|
|
||||||
|
options = @getCurrentOptions()
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
visibleOptions = @option_items.not('.is-hidden')
|
visibleOptions = options.not('.is-hidden')
|
||||||
highlightedItem = @option_items.filter('.is-active')
|
highlightedItem = options.filter('.is-active')
|
||||||
currentPosition = visibleOptions.index(highlightedItem)
|
currentPosition = visibleOptions.index(highlightedItem)
|
||||||
|
|
||||||
currentPosition += direction
|
currentPosition += direction
|
||||||
|
@ -84,10 +166,24 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
return if currentPosition < 0
|
return if currentPosition < 0
|
||||||
return if currentPosition > visibleOptions.size() - 1
|
return if currentPosition > visibleOptions.size() - 1
|
||||||
|
|
||||||
@option_items.removeClass('is-active')
|
@unhighlightCurrentItem()
|
||||||
visibleOptions.eq(currentPosition).addClass('is-active')
|
@currentItem = visibleOptions.eq(currentPosition)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
@clearAutocomplete()
|
@clearAutocomplete()
|
||||||
|
|
||||||
|
autocompleteOrNavigateIn: (event) ->
|
||||||
|
if @currentItem.hasClass('js-enter')
|
||||||
|
@navigateIn(event)
|
||||||
|
else
|
||||||
|
@fillWithAutocompleteSuggestion(event)
|
||||||
|
|
||||||
|
autocompleteOrNavigateOut: (event) ->
|
||||||
|
# if we're in a depth then navigateOut
|
||||||
|
if @level != 0
|
||||||
|
@navigateOut(event)
|
||||||
|
else
|
||||||
|
@fillWithAutocompleteSuggestion(event)
|
||||||
|
|
||||||
fillWithAutocompleteSuggestion: (event) ->
|
fillWithAutocompleteSuggestion: (event) ->
|
||||||
if !@suggestion
|
if !@suggestion
|
||||||
return
|
return
|
||||||
|
@ -124,16 +220,101 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@invisiblePart.text('')
|
@invisiblePart.text('')
|
||||||
|
|
||||||
selectItem: (event) ->
|
selectItem: (event) ->
|
||||||
|
return if !event.currentTarget.textContent
|
||||||
@input.val event.currentTarget.textContent.trim()
|
@input.val event.currentTarget.textContent.trim()
|
||||||
@input.trigger('change')
|
@input.trigger('change')
|
||||||
@shadowInput.val event.currentTarget.getAttribute('data-value')
|
@shadowInput.val event.currentTarget.getAttribute('data-value')
|
||||||
@shadowInput.trigger('change')
|
@shadowInput.trigger('change')
|
||||||
|
|
||||||
|
navigateIn: (event) ->
|
||||||
|
event.stopPropagation()
|
||||||
|
@selectItem(event)
|
||||||
|
@navigateDepth(1)
|
||||||
|
|
||||||
|
navigateOut: (event) ->
|
||||||
|
event.stopPropagation()
|
||||||
|
@navigateDepth(-1)
|
||||||
|
|
||||||
|
navigateDepth: (dir) ->
|
||||||
|
return if @animating
|
||||||
|
if dir > 0
|
||||||
|
target = @currentItem.attr('data-value')
|
||||||
|
target_menu = @optionsSubmenu.filter("[data-parent-value=\"#{target}\"]")
|
||||||
|
else
|
||||||
|
target_menu = @findMenuContainingValue @currentMenu.attr('data-parent-value')
|
||||||
|
|
||||||
|
@animateToSubmenu(target_menu, dir)
|
||||||
|
|
||||||
|
@level+=dir
|
||||||
|
|
||||||
|
animateToSubmenu: (target_menu, direction) ->
|
||||||
|
@animating = true
|
||||||
|
target_menu.prop('hidden', false)
|
||||||
|
@dropdown.height(Math.max(target_menu.height(), @currentMenu.height()))
|
||||||
|
oldCurrentItem = @currentItem
|
||||||
|
|
||||||
|
@currentMenu.data('current_item_index', @currentItem.index())
|
||||||
|
# default: 1 (first item after the back button)
|
||||||
|
target_item_index = target_menu.data('current_item_index') || 1
|
||||||
|
# if the direction is out then we know the target item -> its the parent item
|
||||||
|
if direction is -1
|
||||||
|
value = @currentMenu.attr('data-parent-value')
|
||||||
|
target_item_index = @getOptionIndex(target_menu, value)
|
||||||
|
|
||||||
|
@currentItem = target_menu.children().eq(target_item_index)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
|
|
||||||
|
target_menu.velocity
|
||||||
|
properties:
|
||||||
|
translateX: [0, direction*100+'%']
|
||||||
|
options:
|
||||||
|
duration: 240
|
||||||
|
|
||||||
|
@currentMenu.velocity
|
||||||
|
properties:
|
||||||
|
translateX: [direction*-100+'%', 0]
|
||||||
|
options:
|
||||||
|
duration: 240
|
||||||
|
complete: =>
|
||||||
|
oldCurrentItem.removeClass('is-active')
|
||||||
|
$.Velocity.hook(@currentMenu, 'translateX', '')
|
||||||
|
@currentMenu.prop('hidden', true)
|
||||||
|
@dropdown.height(target_menu.height())
|
||||||
|
@currentMenu = target_menu
|
||||||
|
@animating = false
|
||||||
|
|
||||||
|
showSubmenu: (menu) ->
|
||||||
|
@currentMenu.prop('hidden', true)
|
||||||
|
menu.prop('hidden', false)
|
||||||
|
@dropdown.height(menu.height())
|
||||||
|
|
||||||
|
findMenuContainingValue: (value) ->
|
||||||
|
return @optionsList if !value
|
||||||
|
|
||||||
|
# in case of numbers
|
||||||
|
if !value.split && value.toString
|
||||||
|
value = value.toString()
|
||||||
|
path = value.split('::')
|
||||||
|
if path.length == 1
|
||||||
|
return @optionsList
|
||||||
|
else
|
||||||
|
path.pop()
|
||||||
|
return @optionsSubmenu.filter("[data-parent-value=\"#{path.join('::')}\"]")
|
||||||
|
|
||||||
|
getIndex: (menu) ->
|
||||||
|
parentValue = menu.attr('data-parent-value')
|
||||||
|
return 0 if !parentValue
|
||||||
|
return parentValue.split('::').length
|
||||||
|
|
||||||
onTab: (event) ->
|
onTab: (event) ->
|
||||||
return if not @isOpen
|
return if not @isOpen
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
onEnter: (event) ->
|
onEnter: (event) ->
|
||||||
|
if @currentItem
|
||||||
|
if @currentItem.hasClass('js-back')
|
||||||
|
return @navigateOut(event)
|
||||||
|
|
||||||
@clearAutocomplete()
|
@clearAutocomplete()
|
||||||
|
|
||||||
if not @isOpen
|
if not @isOpen
|
||||||
|
@ -144,15 +325,22 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
selected = @option_items.filter('.is-active')
|
if @currentItem || !@attribute.unknown
|
||||||
if selected.length || !@options.attribute.unknown
|
valueName = @currentItem.text().trim()
|
||||||
valueName = selected.text().trim()
|
value = @currentItem.attr('data-value')
|
||||||
value = selected.attr('data-value')
|
|
||||||
@input.val valueName
|
@input.val valueName
|
||||||
@shadowInput.val value
|
@shadowInput.val value
|
||||||
|
|
||||||
@input.trigger('change')
|
@input.trigger('change')
|
||||||
@shadowInput.trigger('change')
|
@shadowInput.trigger('change')
|
||||||
|
|
||||||
|
if @currentItem
|
||||||
|
if @currentItem.hasClass('js-enter')
|
||||||
|
@navigateIn(event)
|
||||||
|
@currentItem = null
|
||||||
|
return
|
||||||
|
@currentItem = null
|
||||||
|
|
||||||
@toggle()
|
@toggle()
|
||||||
|
|
||||||
onBlur: ->
|
onBlur: ->
|
||||||
|
@ -169,32 +357,46 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@query = @input.val()
|
@query = @input.val()
|
||||||
@filterByQuery @query
|
@filterByQuery @query
|
||||||
|
|
||||||
if @options.attribute.unknown
|
if @attribute.unknown
|
||||||
@shadowInput.val @query
|
@shadowInput.val @query
|
||||||
|
|
||||||
filterByQuery: (query) ->
|
filterByQuery: (query) ->
|
||||||
query = escapeRegExp(query)
|
query = escapeRegExp(query)
|
||||||
regex = new RegExp(query.split(' ').join('.*'), 'i')
|
regex = new RegExp(query.split(' ').join('.*'), 'i')
|
||||||
|
|
||||||
@option_items
|
@optionsList.addClass 'is-filtered'
|
||||||
|
|
||||||
|
@optionItems
|
||||||
.addClass 'is-hidden'
|
.addClass 'is-hidden'
|
||||||
.filter ->
|
.filter ->
|
||||||
@textContent.match(regex)
|
@textContent.match(regex)
|
||||||
.removeClass 'is-hidden'
|
.removeClass 'is-hidden'
|
||||||
|
|
||||||
if @options.attribute.unknown && @option_items.length == @option_items.filter('.is-hidden').length
|
if !query
|
||||||
@option_items.removeClass 'is-hidden'
|
@optionItems.filter('.is-child').addClass 'is-hidden'
|
||||||
@option_items.removeClass 'is-active'
|
|
||||||
|
# if all are hidden
|
||||||
|
if @attribute.unknown && @optionItems.length == @optionItems.filter('.is-hidden').length
|
||||||
|
@optionItems.not('.is-child').removeClass 'is-hidden'
|
||||||
|
@unhighlightCurrentItem()
|
||||||
|
@optionsList.removeClass 'is-filtered'
|
||||||
else
|
else
|
||||||
@highlightFirst(true)
|
@highlightFirst(true)
|
||||||
|
|
||||||
highlightFirst: (autocomplete) ->
|
highlightFirst: (autocomplete) ->
|
||||||
first = @option_items.removeClass('is-active').not('.is-hidden').first()
|
@unhighlightCurrentItem()
|
||||||
first.addClass 'is-active'
|
@currentItem = @getCurrentOptions().not('.is-hidden').first()
|
||||||
|
@currentItem.addClass 'is-active'
|
||||||
|
|
||||||
if autocomplete
|
if autocomplete
|
||||||
@autocomplete first.attr('data-value'), first.text().trim()
|
@autocomplete @currentItem.attr('data-value'), @currentItem.text().trim()
|
||||||
|
|
||||||
highlightItem: (event) =>
|
highlightItem: (event) =>
|
||||||
@option_items.removeClass('is-active')
|
@unhighlightCurrentItem()
|
||||||
$(event.currentTarget).addClass('is-active')
|
@currentItem = $(event.currentTarget)
|
||||||
|
@currentItem.addClass('is-active')
|
||||||
|
|
||||||
|
unhighlightCurrentItem: ->
|
||||||
|
return if !@currentItem
|
||||||
|
@currentItem.removeClass('is-active')
|
||||||
|
@currentItem = null
|
||||||
|
|
|
@ -1,5 +1,80 @@
|
||||||
# coffeelint: disable=no_unnecessary_double_quotes
|
# coffeelint: disable=no_unnecessary_double_quotes
|
||||||
class App.Utils
|
class App.Utils
|
||||||
|
@mapTagAttributes:
|
||||||
|
'TABLE': ['align', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'sortable', 'summary', 'width', 'style']
|
||||||
|
'TD': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'valign', 'width', 'style']
|
||||||
|
'TH': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'scope', 'sorted', 'valign', 'width', 'style']
|
||||||
|
'TR': ['width', 'style']
|
||||||
|
|
||||||
|
@mapCss:
|
||||||
|
'TABLE': [
|
||||||
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||||
|
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||||
|
'text-align',
|
||||||
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
]
|
||||||
|
'TH': [
|
||||||
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||||
|
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||||
|
'text-align',
|
||||||
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
]
|
||||||
|
'TR': [
|
||||||
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||||
|
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||||
|
'text-align',
|
||||||
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
]
|
||||||
|
'TD': [
|
||||||
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||||
|
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||||
|
'text-align',
|
||||||
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
]
|
||||||
|
|
||||||
# textCleand = App.Utils.textCleanup(rawText)
|
# textCleand = App.Utils.textCleanup(rawText)
|
||||||
@textCleanup: (ascii) ->
|
@textCleanup: (ascii) ->
|
||||||
|
@ -49,10 +124,12 @@ class App.Utils
|
||||||
@linkify: (string) ->
|
@linkify: (string) ->
|
||||||
window.linkify(string)
|
window.linkify(string)
|
||||||
|
|
||||||
# htmlEscapedAndLinkified = App.Utils.linkify(rawText)
|
# htmlEscapedAndPhoneified = App.Utils.phoneify(rawText)
|
||||||
@phoneify: (string) ->
|
@phoneify: (string) ->
|
||||||
string = string.replace(/\s+/g, '')
|
return string if _.isEmpty(string)
|
||||||
"tel://#{encodeURIComponent(string)}"
|
string = string.replace(/[^0-9,\+,#,\*]+/g, '')
|
||||||
|
.replace(/(.)\+/, '$1')
|
||||||
|
"tel:#{string}"
|
||||||
|
|
||||||
# wrappedText = App.Utils.wrap(rawText, maxLineLength)
|
# wrappedText = App.Utils.wrap(rawText, maxLineLength)
|
||||||
@wrap: (ascii, max = 82) ->
|
@wrap: (ascii, max = 82) ->
|
||||||
|
@ -125,6 +202,7 @@ class App.Utils
|
||||||
child = el.firstChild
|
child = el.firstChild
|
||||||
break if !child
|
break if !child
|
||||||
break if child.nodeType isnt 1 || child.tagName isnt 'BR'
|
break if child.nodeType isnt 1 || child.tagName isnt 'BR'
|
||||||
|
break if !child.remove
|
||||||
child.remove()
|
child.remove()
|
||||||
|
|
||||||
loop
|
loop
|
||||||
|
@ -133,6 +211,7 @@ class App.Utils
|
||||||
child = el.lastChild
|
child = el.lastChild
|
||||||
break if !child
|
break if !child
|
||||||
break if child.nodeType isnt 1 || child.tagName isnt 'BR'
|
break if child.nodeType isnt 1 || child.tagName isnt 'BR'
|
||||||
|
break if !child.remove
|
||||||
child.remove()
|
child.remove()
|
||||||
|
|
||||||
# true|false = App.Utils.htmlLastLineEmpty(element)
|
# true|false = App.Utils.htmlLastLineEmpty(element)
|
||||||
|
@ -155,12 +234,12 @@ class App.Utils
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
# remove tags, keep content
|
# remove tags, keep content
|
||||||
html.find('div, span, p, li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, td, h1, h2, h3, h4, h5, h6').replaceWith( ->
|
html.find('div, span, p, li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, th, td, h1, h2, h3, h4, h5, h6').replaceWith( ->
|
||||||
$(@).contents()
|
$(@).contents()
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove tags & content
|
# remove tags & content
|
||||||
html.find('div, span, p, li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, table, thead, tbody, tr, td, h1, h2, h3, h4, h5, h6, br, hr, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head').remove()
|
html.find('div, span, p, li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, table, thead, tbody, tr, th, td, h1, h2, h3, h4, h5, h6, br, hr, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head').remove()
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
||||||
|
@ -172,20 +251,19 @@ class App.Utils
|
||||||
# remove comments
|
# remove comments
|
||||||
@_removeComments(html)
|
@_removeComments(html)
|
||||||
|
|
||||||
# remove style and class
|
|
||||||
if parent
|
|
||||||
@_removeAttributes(html)
|
|
||||||
|
|
||||||
# remove work markup
|
# remove work markup
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
# remove tags, keep content
|
# remove tags, keep content
|
||||||
html.find('li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, td, h1, h2, h3, h4, h5, h6').replaceWith( ->
|
html.find('li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, th, td, h1, h2, h3, h4, h5, h6').replaceWith( ->
|
||||||
$(@).contents()
|
$(@).contents()
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove tags & content
|
# remove tags & content
|
||||||
html.find('li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, td, h1, h2, h3, h4, h5, h6, hr, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head').remove()
|
html.find('li, ul, ol, a, b, u, i, label, small, strong, strike, pre, code, center, blockquote, form, fieldset, textarea, font, address, table, thead, tbody, tr, th, td, h1, h2, h3, h4, h5, h6, hr, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head').remove()
|
||||||
|
|
||||||
|
# remove style and class
|
||||||
|
@_removeAttributes(html, parent)
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
||||||
|
@ -197,9 +275,6 @@ class App.Utils
|
||||||
# remove comments
|
# remove comments
|
||||||
@_removeComments(html)
|
@_removeComments(html)
|
||||||
|
|
||||||
# remove style and class
|
|
||||||
@_removeAttributes(html)
|
|
||||||
|
|
||||||
# remove work markup
|
# remove work markup
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
|
@ -230,6 +305,9 @@ class App.Utils
|
||||||
# remove tags & content
|
# remove tags & content
|
||||||
html.find('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset').remove()
|
html.find('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset').remove()
|
||||||
|
|
||||||
|
# remove style and class
|
||||||
|
@_cleanAttributes(html)
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
||||||
@_checkTypeOf: (item) ->
|
@_checkTypeOf: (item) ->
|
||||||
|
@ -250,26 +328,64 @@ class App.Utils
|
||||||
catch err
|
catch err
|
||||||
return $("<div>#{item}</div>")
|
return $("<div>#{item}</div>")
|
||||||
|
|
||||||
@_removeAttributes: (html, parent = true) ->
|
@_cleanAttribute: (element) ->
|
||||||
|
return if !element
|
||||||
|
|
||||||
|
if @mapTagAttributes[element.nodeName]
|
||||||
|
atts = element.attributes
|
||||||
|
for att in atts
|
||||||
|
if att && att.name && !_.contains(@mapTagAttributes[element.nodeName], att.name)
|
||||||
|
element.removeAttribute(att.name)
|
||||||
|
else
|
||||||
|
@_removeAttribute(element)
|
||||||
|
|
||||||
|
if @mapCss[element.nodeName]
|
||||||
|
elementStyle = element.style
|
||||||
|
styleOld = ''
|
||||||
|
for prop in elementStyle
|
||||||
|
styleOld += "#{prop}:#{elementStyle[prop]};"
|
||||||
|
|
||||||
|
if styleOld && styleOld.split
|
||||||
|
styleNew = ''
|
||||||
|
for local_pear in styleOld.split(';')
|
||||||
|
prop = local_pear.split(':')
|
||||||
|
if prop[0] && prop[0].trim
|
||||||
|
key = prop[0].trim()
|
||||||
|
if _.contains(@mapCss[element.nodeName], key)
|
||||||
|
styleNew += "#{local_pear};"
|
||||||
|
if styleNew isnt ''
|
||||||
|
element.setAttribute('style', styleNew)
|
||||||
|
else
|
||||||
|
element.removeAttribute('style')
|
||||||
|
|
||||||
|
@_cleanAttributes: (html, parent = true) ->
|
||||||
if parent
|
if parent
|
||||||
html.find('*')
|
html.each((index, element) => @_cleanAttribute(element) )
|
||||||
.removeAttr('style')
|
html.find('*').each((index, element) => @_cleanAttribute(element) )
|
||||||
.removeAttr('class')
|
|
||||||
.removeAttr('title')
|
|
||||||
.removeAttr('lang')
|
|
||||||
.removeAttr('type')
|
|
||||||
.removeAttr('id')
|
|
||||||
.removeAttr('wrap')
|
|
||||||
.removeAttrs(/data-/)
|
|
||||||
html
|
html
|
||||||
.removeAttr('style')
|
|
||||||
|
@_removeAttribute: (element) ->
|
||||||
|
return if !element
|
||||||
|
$element = $(element)
|
||||||
|
for att in element.attributes
|
||||||
|
if att && att.name
|
||||||
|
element.removeAttribute(att.name)
|
||||||
|
#$element.removeAttr(att.name)
|
||||||
|
|
||||||
|
$element.removeAttr('style')
|
||||||
.removeAttr('class')
|
.removeAttr('class')
|
||||||
.removeAttr('title')
|
|
||||||
.removeAttr('lang')
|
.removeAttr('lang')
|
||||||
.removeAttr('type')
|
.removeAttr('type')
|
||||||
|
.removeAttr('align')
|
||||||
.removeAttr('id')
|
.removeAttr('id')
|
||||||
.removeAttr('wrap')
|
.removeAttr('wrap')
|
||||||
|
.removeAttr('title')
|
||||||
.removeAttrs(/data-/)
|
.removeAttrs(/data-/)
|
||||||
|
|
||||||
|
@_removeAttributes: (html, parent = true) ->
|
||||||
|
if parent
|
||||||
|
html.each((index, element) => @_removeAttribute(element) )
|
||||||
|
html.find('*').each((index, element) => @_removeAttribute(element) )
|
||||||
html
|
html
|
||||||
|
|
||||||
@_removeComments: (html) ->
|
@_removeComments: (html) ->
|
||||||
|
@ -535,6 +651,7 @@ class App.Utils
|
||||||
# textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } )
|
# textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } )
|
||||||
@replaceTags: (template, objects) ->
|
@replaceTags: (template, objects) ->
|
||||||
template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, (index, key) ->
|
template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, (index, key) ->
|
||||||
|
key = key.replace(/<.+?>/g, '')
|
||||||
levels = key.split(/\./)
|
levels = key.split(/\./)
|
||||||
dataRef = objects
|
dataRef = objects
|
||||||
for level in levels
|
for level in levels
|
||||||
|
@ -781,3 +898,10 @@ class App.Utils
|
||||||
result = newOrderMethod(a, b, applyOrder)
|
result = newOrderMethod(a, b, applyOrder)
|
||||||
return false if !result
|
return false if !result
|
||||||
applyOrder
|
applyOrder
|
||||||
|
|
||||||
|
@textLengthWithUrl: (text, url_max_length = 23) ->
|
||||||
|
length = 0
|
||||||
|
return length if !text
|
||||||
|
placeholder = Array(url_max_length + 1).join('X')
|
||||||
|
text = text.replace(/http(s|):\/\/[-A-Za-z0-9+&@#\/%?=~_\|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]/img, placeholder)
|
||||||
|
text.length
|
||||||
|
|
|
@ -70,8 +70,7 @@ class App.SearchableAjaxSelect extends App.SearchableSelect
|
||||||
options.push data
|
options.push data
|
||||||
|
|
||||||
# fill template with gathered options
|
# fill template with gathered options
|
||||||
@optionsList.html App.view('generic/searchable_select_options')
|
@optionsList.html @renderOptions options
|
||||||
options: options
|
|
||||||
|
|
||||||
# refresh elements
|
# refresh elements
|
||||||
@refreshElements()
|
@refreshElements()
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -88,7 +88,7 @@
|
||||||
// handle enter
|
// handle enter
|
||||||
this.$element.on('keydown', function (e) {
|
this.$element.on('keydown', function (e) {
|
||||||
_this.log('keydown', e.keyCode)
|
_this.log('keydown', e.keyCode)
|
||||||
if ( _this.preventInput ) {
|
if (_this.preventInput) {
|
||||||
this.log('preventInput', _this.preventInput)
|
this.log('preventInput', _this.preventInput)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -97,18 +97,29 @@
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
|
|
||||||
// disbale multi line
|
// disbale multi line
|
||||||
if ( !_this.options.multiline ) {
|
if (!_this.options.multiline) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// break <blockquote> after enter on empty line
|
// break <blockquote> after enter on empty line
|
||||||
sel = window.getSelection()
|
sel = window.getSelection()
|
||||||
node = $(sel.anchorNode)
|
if (sel) {
|
||||||
if (node.parent().is('blockquote')) {
|
node = $(sel.anchorNode)
|
||||||
|
if (node && node.parent() && node.parent().is('blockquote')) {
|
||||||
|
e.preventDefault()
|
||||||
|
document.execCommand('Insertparagraph')
|
||||||
|
document.execCommand('Outdent')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// behavior to enter new line on alt+enter
|
||||||
|
// on alt + enter not realy newline is fired, to make
|
||||||
|
// it compat. to other systems, do it here
|
||||||
|
if (!e.shiftKey && e.altKey && !e.ctrlKey && !e.metaKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
document.execCommand('Insertparagraph')
|
_this.paste('<br><br>')
|
||||||
document.execCommand('Outdent')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +248,7 @@
|
||||||
|
|
||||||
// limit check
|
// limit check
|
||||||
if ( !_this.allowKey(e) ) {
|
if ( !_this.allowKey(e) ) {
|
||||||
if ( !_this.maxLengthOk( 1 ) ) {
|
if ( !_this.maxLengthOk(1) ) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -254,6 +265,9 @@
|
||||||
if (e.clipboardData) { // ie
|
if (e.clipboardData) { // ie
|
||||||
clipboardData = e.clipboardData
|
clipboardData = e.clipboardData
|
||||||
}
|
}
|
||||||
|
else if (window.clipboardData) { // ie
|
||||||
|
clipboardData = window.clipboardData
|
||||||
|
}
|
||||||
else if (e.originalEvent.clipboardData) { // other browsers
|
else if (e.originalEvent.clipboardData) { // other browsers
|
||||||
clipboardData = e.originalEvent.clipboardData
|
clipboardData = e.originalEvent.clipboardData
|
||||||
}
|
}
|
||||||
|
@ -292,7 +306,7 @@
|
||||||
else {
|
else {
|
||||||
img = "<img style=\"width: 100%; max-width: " + width + "px;\" src=\"" + result + "\">"
|
img = "<img style=\"width: 100%; max-width: " + width + "px;\" src=\"" + result + "\">"
|
||||||
}
|
}
|
||||||
document.execCommand('insertHTML', false, img)
|
_this.paste(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize if to big
|
// resize if to big
|
||||||
|
@ -307,15 +321,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// check existing + paste text for limit
|
// check existing + paste text for limit
|
||||||
var text = clipboardData.getData('text/html')
|
var text, docType
|
||||||
var docType = 'html'
|
try {
|
||||||
if (!text || text.length === 0) {
|
text = clipboardData.getData('text/html')
|
||||||
docType = 'text'
|
docType = 'html'
|
||||||
text = clipboardData.getData('text/plain')
|
if (!text || text.length === 0) {
|
||||||
|
docType = 'text'
|
||||||
|
text = clipboardData.getData('text/plain')
|
||||||
|
}
|
||||||
|
if (!text || text.length === 0) {
|
||||||
|
docType = 'text2'
|
||||||
|
text = clipboardData.getData('text')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!text || text.length === 0) {
|
catch (e) {
|
||||||
docType = 'text2'
|
console.log('Sorry, can\'t insert markup because browser is not supporting it.')
|
||||||
text = clipboardData.getData('text')
|
docType = 'text3'
|
||||||
|
text = clipboardData.getData('text')
|
||||||
}
|
}
|
||||||
_this.log('paste', docType, text)
|
_this.log('paste', docType, text)
|
||||||
|
|
||||||
|
@ -355,7 +377,8 @@
|
||||||
// cleanup
|
// cleanup
|
||||||
text = App.Utils.removeEmptyLines(text)
|
text = App.Utils.removeEmptyLines(text)
|
||||||
_this.log('insert', text)
|
_this.log('insert', text)
|
||||||
document.execCommand('insertHTML', false, text)
|
|
||||||
|
_this.paste(text)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -525,7 +548,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$.fn[pluginName] = function ( options ) {
|
// paste some content
|
||||||
|
Plugin.prototype.paste = function(string) {
|
||||||
|
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
|
||||||
|
|
||||||
|
// IE <= 10
|
||||||
|
if (document.selection && document.selection.createRange) {
|
||||||
|
var range = document.selection.createRange()
|
||||||
|
if (range.pasteHTML) {
|
||||||
|
range.pasteHTML(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IE == 11
|
||||||
|
else if (isIE11 && document.getSelection) {
|
||||||
|
var range = document.getSelection().getRangeAt(0)
|
||||||
|
var nnode = document.createElement('div')
|
||||||
|
range.surroundContents(nnode)
|
||||||
|
nnode.innerHTML = string
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.execCommand('insertHTML', false, string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn[pluginName] = function (options) {
|
||||||
return this.each(function () {
|
return this.each(function () {
|
||||||
if (!$.data(this, 'plugin_' + pluginName)) {
|
if (!$.data(this, 'plugin_' + pluginName)) {
|
||||||
$.data(this, 'plugin_' + pluginName,
|
$.data(this, 'plugin_' + pluginName,
|
||||||
|
@ -537,6 +583,9 @@
|
||||||
// get correct val if textbox
|
// get correct val if textbox
|
||||||
$.fn.ceg = function() {
|
$.fn.ceg = function() {
|
||||||
var plugin = $.data(this[0], 'plugin_' + pluginName)
|
var plugin = $.data(this[0], 'plugin_' + pluginName)
|
||||||
|
if (!plugin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
return plugin.value()
|
return plugin.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,17 +43,21 @@
|
||||||
|
|
||||||
this.$element.on('keydown', function (e) {
|
this.$element.on('keydown', function (e) {
|
||||||
|
|
||||||
// esc
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
_this.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// navigate through item
|
// navigate through item
|
||||||
if (_this.isActive()) {
|
if (_this.isActive()) {
|
||||||
|
|
||||||
|
// esc
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
_this.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// enter
|
// enter
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
var id = _this.$widget.find('.dropdown-menu li.is-active').data('id')
|
var id = _this.$widget.find('.dropdown-menu li.is-active').data('id')
|
||||||
|
|
||||||
// as fallback use hovered element
|
// as fallback use hovered element
|
||||||
|
@ -72,12 +76,14 @@
|
||||||
// arrow keys left/right
|
// arrow keys left/right
|
||||||
if (e.keyCode === 37 || e.keyCode === 39) {
|
if (e.keyCode === 37 || e.keyCode === 39) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// up or down
|
// up or down
|
||||||
if (e.keyCode === 38 || e.keyCode === 40) {
|
if (e.keyCode === 38 || e.keyCode === 40) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
var active = _this.$widget.find('.dropdown-menu li.is-active')
|
var active = _this.$widget.find('.dropdown-menu li.is-active')
|
||||||
active.removeClass('is-active')
|
active.removeClass('is-active')
|
||||||
|
|
||||||
|
@ -92,6 +98,9 @@
|
||||||
|
|
||||||
var menu = _this.$widget.find('.dropdown-menu')
|
var menu = _this.$widget.find('.dropdown-menu')
|
||||||
|
|
||||||
|
if (!active.get(0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (active.position().top < 0) {
|
if (active.position().top < 0) {
|
||||||
// scroll up
|
// scroll up
|
||||||
menu.scrollTop( menu.scrollTop() + active.position().top )
|
menu.scrollTop( menu.scrollTop() + active.position().top )
|
||||||
|
@ -102,7 +111,11 @@
|
||||||
menu.scrollTop( menu.scrollTop() + invisibleHeight )
|
menu.scrollTop( menu.scrollTop() + invisibleHeight )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// esc
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
_this.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -180,14 +193,14 @@
|
||||||
Plugin.prototype.renderBase = function() {
|
Plugin.prototype.renderBase = function() {
|
||||||
this.$element.after('<div class="shortcut dropdown"><ul class="dropdown-menu" style="max-height: 200px;"></ul></div>')
|
this.$element.after('<div class="shortcut dropdown"><ul class="dropdown-menu" style="max-height: 200px;"></ul></div>')
|
||||||
this.$widget = this.$element.next()
|
this.$widget = this.$element.next()
|
||||||
this.$widget.on('click', 'li', $.proxy(this.onEntryClick, this))
|
this.$widget.on('mousedown', 'li', $.proxy(this.onEntryClick, this))
|
||||||
this.$widget.on('mouseenter', 'li', $.proxy(this.onMouseEnter, this))
|
this.$widget.on('mouseenter', 'li', $.proxy(this.onMouseEnter, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
// set height of widget
|
// set height of widget
|
||||||
Plugin.prototype.movePosition = function() {
|
Plugin.prototype.movePosition = function() {
|
||||||
if (!this._position) return
|
if (!this._position) return
|
||||||
var height = this.$element.height() + 2
|
var height = this.$element.outerHeight() + 2
|
||||||
var widgetHeight = this.$widget.find('ul').height() //+ 60 // + height
|
var widgetHeight = this.$widget.find('ul').height() //+ 60 // + height
|
||||||
var top = -( widgetHeight + height ) + this._position.top
|
var top = -( widgetHeight + height ) + this._position.top
|
||||||
var left = this._position.left - 6
|
var left = this._position.left - 6
|
||||||
|
@ -250,9 +263,21 @@
|
||||||
|
|
||||||
// paste some content
|
// paste some content
|
||||||
Plugin.prototype.paste = function(string) {
|
Plugin.prototype.paste = function(string) {
|
||||||
if (document.selection) { // IE
|
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
|
||||||
|
|
||||||
|
// IE <= 10
|
||||||
|
if (document.selection && document.selection.createRange) {
|
||||||
var range = document.selection.createRange()
|
var range = document.selection.createRange()
|
||||||
range.pasteHTML(string)
|
if (range.pasteHTML) {
|
||||||
|
range.pasteHTML(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IE == 11
|
||||||
|
else if (isIE11 && document.getSelection) {
|
||||||
|
var range = document.getSelection().getRangeAt(0)
|
||||||
|
var nnode = document.createElement('div')
|
||||||
|
range.surroundContents(nnode)
|
||||||
|
nnode.innerHTML = string
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
document.execCommand('insertHTML', false, string)
|
document.execCommand('insertHTML', false, string)
|
||||||
|
@ -295,14 +320,7 @@
|
||||||
// for chrome, insert space again
|
// for chrome, insert space again
|
||||||
if (start) {
|
if (start) {
|
||||||
if (spacerChar === ' ') {
|
if (spacerChar === ' ') {
|
||||||
string = " "
|
this.paste(' ')
|
||||||
if (document.selection) { // IE
|
|
||||||
var range = document.selection.createRange()
|
|
||||||
range.pasteHTML(string)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.execCommand('insertHTML', false, string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,6 +331,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugin.prototype.onEntryClick = function(event) {
|
Plugin.prototype.onEntryClick = function(event) {
|
||||||
|
event.preventDefault()
|
||||||
var id = $(event.target).data('id')
|
var id = $(event.target).data('id')
|
||||||
this.take(id)
|
this.take(id)
|
||||||
}
|
}
|
||||||
|
@ -325,7 +344,7 @@
|
||||||
}
|
}
|
||||||
for (var i = 0; i < this.collection.length; i++) {
|
for (var i = 0; i < this.collection.length; i++) {
|
||||||
var item = this.collection[i]
|
var item = this.collection[i]
|
||||||
if ( item.id == id ) {
|
if (item.id == id) {
|
||||||
var content = item.content
|
var content = item.content
|
||||||
this.cutInput()
|
this.cutInput()
|
||||||
this.paste(content)
|
this.paste(content)
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
modified by Felix Jan-2014
|
modified by Felix Jan-2014
|
||||||
- add this.$body = $(options.container || document.body)
|
- add this.$body = $(options.container || document.body)
|
||||||
- adjustBackdrop: also adopt left, top and width from $body
|
- adjustBackdrop: also adopt left, top and width from $body
|
||||||
|
modified by Felix Jul-2017
|
||||||
|
- add rtl support
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,6 +246,10 @@
|
||||||
.css('height', 0)
|
.css('height', 0)
|
||||||
.css('height', this.$element[0].scrollHeight)
|
.css('height', this.$element[0].scrollHeight)
|
||||||
|
|
||||||
|
if(App.i18n.dir() == 'rtl'){
|
||||||
|
this.$backdrop.css('right', 'auto')
|
||||||
|
}
|
||||||
|
|
||||||
if(this.scrollbarWidth){
|
if(this.scrollbarWidth){
|
||||||
this.$backdrop.css('width', this.$body.width() - this.scrollbarWidth)
|
this.$backdrop.css('width', this.$body.width() - this.scrollbarWidth)
|
||||||
}
|
}
|
||||||
|
@ -251,14 +257,22 @@
|
||||||
|
|
||||||
Modal.prototype.adjustDialog = function () {
|
Modal.prototype.adjustDialog = function () {
|
||||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||||
|
var css = {
|
||||||
this.$element.css({
|
|
||||||
left: this.$body.offset().left,
|
left: this.$body.offset().left,
|
||||||
top: this.$body.offset().top,
|
top: this.$body.offset().top,
|
||||||
width: this.$body.width(),
|
width: this.$body.width(),
|
||||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if(App.i18n.dir() == 'rtl'){
|
||||||
|
css.right = 'auto'
|
||||||
|
var paddingLeft = css.paddingLeft
|
||||||
|
css.paddingLeft = css.paddingRight
|
||||||
|
css.paddingRight = paddingLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$element.css(css)
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.prototype.resetAdjustments = function () {
|
Modal.prototype.resetAdjustments = function () {
|
||||||
|
|
|
@ -32,6 +32,10 @@ class App.Model extends Spine.Model
|
||||||
return @title
|
return @title
|
||||||
if @subject
|
if @subject
|
||||||
return @subject
|
return @subject
|
||||||
|
if @phone
|
||||||
|
return @phone
|
||||||
|
if @login
|
||||||
|
return @login
|
||||||
return '???'
|
return '???'
|
||||||
|
|
||||||
displayNameLong: ->
|
displayNameLong: ->
|
||||||
|
@ -57,6 +61,12 @@ class App.Model extends Spine.Model
|
||||||
return @email
|
return @email
|
||||||
if @title
|
if @title
|
||||||
return @title
|
return @title
|
||||||
|
if @subject
|
||||||
|
return @subject
|
||||||
|
if @phone
|
||||||
|
return @phone
|
||||||
|
if @login
|
||||||
|
return @login
|
||||||
return '???'
|
return '???'
|
||||||
|
|
||||||
icon: (user) ->
|
icon: (user) ->
|
||||||
|
@ -165,6 +175,31 @@ class App.Model extends Spine.Model
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
set new attributes of model (remove already available attributes)
|
||||||
|
|
||||||
|
App.Model.attributesSet(attributes)
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
@attributesSet: (attributes) ->
|
||||||
|
|
||||||
|
configure_attributes = App[ @.className ].configure_attributes
|
||||||
|
attributesNew = []
|
||||||
|
for localAttribute in configure_attributes
|
||||||
|
found = false
|
||||||
|
for attribute in attributes
|
||||||
|
if attribute.name is localAttribute.name
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
if !found
|
||||||
|
attributesNew.push localAttribute
|
||||||
|
for attribute in attributes
|
||||||
|
App[@.className].attributes.push attribute.name
|
||||||
|
attributesNew.push attribute
|
||||||
|
App[ @.className ].configure_attributes = attributesNew
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
attributes = App.Model.attributesGet(optionalScreen, optionalAttributesList)
|
attributes = App.Model.attributesGet(optionalScreen, optionalAttributesList)
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
|
@ -35,3 +35,11 @@ class App.Group extends App.Model
|
||||||
|
|
||||||
return App.view('avatar_group')
|
return App.view('avatar_group')
|
||||||
cssClass: cssClass.join(' ')
|
cssClass: cssClass.join(' ')
|
||||||
|
|
||||||
|
@accesses: ->
|
||||||
|
read: 'Read'
|
||||||
|
create: 'Create'
|
||||||
|
change: 'Change'
|
||||||
|
delete: 'Delete'
|
||||||
|
overview: 'Overview'
|
||||||
|
full: 'Full'
|
||||||
|
|
|
@ -6,7 +6,7 @@ class App.Job extends App.Model
|
||||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||||
{ name: 'timeplan', display: 'When should the job run?', tag: 'timer', null: true },
|
{ name: 'timeplan', display: 'When should the job run?', tag: 'timer', null: true },
|
||||||
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: true },
|
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: true },
|
||||||
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true, notification: true },
|
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true, notification: true, ticket_delete: true },
|
||||||
{ name: 'disable_notification', display: 'Disable Notifications', tag: 'boolean', default: true },
|
{ name: 'disable_notification', display: 'Disable Notifications', tag: 'boolean', default: true },
|
||||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||||
|
|
|
@ -8,6 +8,7 @@ class App.Overview extends App.Model
|
||||||
{ name: 'role_ids', display: 'Available for Role', tag: 'column_select', multiple: true, null: false, relation: 'Role', translate: true },
|
{ name: 'role_ids', display: 'Available for Role', tag: 'column_select', multiple: true, null: false, relation: 'Role', translate: true },
|
||||||
{ name: 'user_ids', display: 'Available for User', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
{ name: 'user_ids', display: 'Available for User', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
||||||
{ name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
{ name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||||
|
{ name: 'out_of_office', display: 'Only available for Users which are replacements for other users.', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||||
{ name: 'condition', display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
|
{ name: 'condition', display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
|
||||||
{ name: 'prio', display: 'Prio', readonly: 1 },
|
{ name: 'prio', display: 'Prio', readonly: 1 },
|
||||||
{
|
{
|
||||||
|
@ -72,4 +73,4 @@ Sie können auch individuelle Übersichten für einzelne Agenten oder agenten Gr
|
||||||
'''
|
'''
|
||||||
|
|
||||||
uiUrl: ->
|
uiUrl: ->
|
||||||
'#ticket/view/' + @link
|
"#ticket/view/#{@link}"
|
||||||
|
|
|
@ -6,7 +6,7 @@ class App.PostmasterFilter extends App.Model
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 250, 'null': false },
|
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 250, 'null': false },
|
||||||
{ name: 'channel', display: 'Channel', type: 'input', readonly: 1 },
|
{ name: 'channel', display: 'Channel', type: 'input', readonly: 1 },
|
||||||
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match' },
|
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match', note: 'You can use regular expression by using "regex:your_reg_exp".' },
|
||||||
{ name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' },
|
{ name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' },
|
||||||
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
||||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class App.Role extends App.Model
|
class App.Role extends App.Model
|
||||||
@configure 'Role', 'name', 'permission_ids', 'default_at_signup', 'note', 'active', 'updated_at'
|
@configure 'Role', 'name', 'permission_ids', 'group_ids', 'default_at_signup', 'note', 'active', 'updated_at'
|
||||||
@extend Spine.Model.Ajax
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/roles'
|
@url: @apiPath + '/roles'
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
|
|
|
@ -53,8 +53,14 @@ class App.User extends App.Model
|
||||||
cssClass += ' ' if cssClass
|
cssClass += ' ' if cssClass
|
||||||
cssClass += "size-#{ size }"
|
cssClass += "size-#{ size }"
|
||||||
|
|
||||||
|
if @active is false
|
||||||
|
cssClass += ' avatar--inactive'
|
||||||
|
|
||||||
|
if @isOutOfOffice()
|
||||||
|
cssClass += ' avatar--vacation'
|
||||||
|
|
||||||
if placement
|
if placement
|
||||||
placement = " data-placement='#{ placement }'"
|
placement = " data-placement='#{placement}'"
|
||||||
|
|
||||||
if !avatar
|
if !avatar
|
||||||
if type is 'personal'
|
if type is 'personal'
|
||||||
|
@ -104,6 +110,19 @@ class App.User extends App.Model
|
||||||
vip: vip
|
vip: vip
|
||||||
url: @imageUrl()
|
url: @imageUrl()
|
||||||
|
|
||||||
|
isOutOfOffice: ->
|
||||||
|
return false if @out_of_office isnt true
|
||||||
|
start_time = @out_of_office_start_at
|
||||||
|
return false if !start_time
|
||||||
|
end_time = @out_of_office_end_at
|
||||||
|
return false if !end_time
|
||||||
|
start_time = new Date(Date.parse(start_time))
|
||||||
|
end_time = new Date(Date.parse(end_time))
|
||||||
|
now = new Date((new Date).toDateString())
|
||||||
|
if start_time <= now && end_time >= now
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
imageUrl: ->
|
imageUrl: ->
|
||||||
return if !@image
|
return if !@image
|
||||||
# set image url
|
# set image url
|
||||||
|
@ -237,3 +256,16 @@ class App.User extends App.Model
|
||||||
break
|
break
|
||||||
return access if access
|
return access if access
|
||||||
false
|
false
|
||||||
|
|
||||||
|
@outOfOfficeTextPlaceholder: ->
|
||||||
|
today = new Date()
|
||||||
|
outOfOfficeText = 'Christmas holiday'
|
||||||
|
if today.getMonth() < 3
|
||||||
|
outOfOfficeText = 'Easter holiday'
|
||||||
|
else if today.getMonth() < 9
|
||||||
|
outOfOfficeText = 'Summer holiday'
|
||||||
|
outOfOfficeText
|
||||||
|
|
||||||
|
outOfOfficeText: ->
|
||||||
|
return @preferences.out_of_office_text if !_.isEmpty(@preferences.out_of_office_text)
|
||||||
|
App.User.outOfOfficeTextPlaceholder()
|
||||||
|
|
|
@ -142,7 +142,7 @@
|
||||||
<h3><%- @T('Automatically show chat') %> (<%- @T('default') %>)</h3>
|
<h3><%- @T('Automatically show chat') %> (<%- @T('default') %>)</h3>
|
||||||
<p><%- @T('The chat will show up once the connection to the server got established and if there is someone online to chat with.') %></p>
|
<p><%- @T('The chat will show up once the connection to the server got established and if there is someone online to chat with.') %></p>
|
||||||
|
|
||||||
<pre><code class="language-html js-paramsBlock"><script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
<pre><code class="language-html js-code"><script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
$(function() {
|
||||||
new ZammadChat({
|
new ZammadChat({
|
||||||
|
@ -153,7 +153,7 @@ $(function() {
|
||||||
|
|
||||||
<h3><%- @T('Manually open chat') %></h3>
|
<h3><%- @T('Manually open chat') %></h3>
|
||||||
<p><%- @T('If you want to open the chat by the press of a button set the option §show§ to §false§ and add the class §open-zammad-chat§ to the button.') %></p>
|
<p><%- @T('If you want to open the chat by the press of a button set the option §show§ to §false§ and add the class §open-zammad-chat§ to the button.') %></p>
|
||||||
<pre><code class="language-html js-paramsBlock"><button class="open-zammad-chat">Chat with us</button>
|
<pre><code class="language-html js-code"><button class="open-zammad-chat">Chat with us</button>
|
||||||
|
|
||||||
<script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
<script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -10,8 +10,20 @@
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<p><%- @T('With form you can add a form to your web page which directly generates a ticket for you.') %></p>
|
<p><%- @T('With form you can add a form to your web page which directly generates a ticket for you.') %></p>
|
||||||
|
|
||||||
|
<h2><%- @T('Settings') %></h2>
|
||||||
|
<form class="js-paramsSetting">
|
||||||
|
<fieldset>
|
||||||
|
<div class="input form-group formGroup--halfSize">
|
||||||
|
<div class="formGroup-label">
|
||||||
|
<label for="form-group"><%- @T('Group selection for Ticket creation') %></label>
|
||||||
|
</div>
|
||||||
|
<div class="controls js-groupSelector" id="from-group"></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
<h2><%- @T('Designer') %></h2>
|
<h2><%- @T('Designer') %></h2>
|
||||||
<form class="js-params">
|
<form class="js-paramsDesigner">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="input form-group formGroup--halfSize">
|
<div class="input form-group formGroup--halfSize">
|
||||||
|
@ -114,6 +126,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h3><%- @T('Requirements') %></h3>
|
||||||
|
<p><%- @T("Zammad Forms requires jQuery. If you don't already use it on your website include it like this:") %></p>
|
||||||
|
<pre><code class="language-html js-code"><script src="https://code.jquery.com/jquery-2.1.4.min.js"></script></code></pre>
|
||||||
|
|
||||||
<p><%- @T('You need to add the following Javascript code snippet to your web page') %>:</p>
|
<p><%- @T('You need to add the following Javascript code snippet to your web page') %>:</p>
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
<div class="chat-body js-body"></div>
|
<div class="chat-body js-body"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-controls">
|
<div class="chat-controls">
|
||||||
<div class="chat-input form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div>
|
<div class="chat-input">
|
||||||
|
<div class="form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div>
|
||||||
|
</div>
|
||||||
<div class="btn btn--primary btn--slim btn--small js-send"><%- @T('Send') %></div>
|
<div class="btn btn--primary btn--slim btn--small js-send"><%- @T('Send') %></div>
|
||||||
</div>
|
</div>
|
|
@ -9,7 +9,7 @@
|
||||||
<label for="application_id">Facebook APP ID <span>*</span></label>
|
<label for="application_id">Facebook APP ID <span>*</span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input id="application_id" type="text" name="application_id" value="<% if @external_credential && @external_credential.credentials: %><%= @external_credential.credentials.application_id %><% end %>" class="form-control" required autocomplete="new-password">
|
<input id="application_id" type="text" name="application_id" value="<% if @external_credential && @external_credential.credentials: %><%= @external_credential.credentials.application_id %><% end %>" class="form-control" required autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input form-group">
|
<div class="input form-group">
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
<label for="application_secret">Facebook App Secret <span>*</span></label>
|
<label for="application_secret">Facebook App Secret <span>*</span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input id="application_secret" type="text" name="application_secret" value="<% if @external_credential && @external_credential.credentials: %><%= @external_credential.credentials.application_secret %><% end %>" class="form-control" required autocomplete="new-password">
|
<input id="application_secret" type="text" name="application_secret" value="<% if @external_credential && @external_credential.credentials: %><%= @external_credential.credentials.application_secret %><% end %>" class="form-control" required autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2><%- @T('Your callback URL') %></h2>
|
<h2><%- @T('Your callback URL') %></h2>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<input id="<%= @attribute.id %>" type="hidden" name="<%= @attribute.name %>" value="<%= @attribute.value %>" <%= @attribute.required %> />
|
<input id="<%= @attribute.id %>" type="hidden" name="<%= @attribute.name %>" value="<%= @attribute.value %>" <%= @attribute.required %> />
|
||||||
<input id="<%= @attribute.id %>_autocompletion" type="text" name="<%= @attribute.name %>_autocompletion" value="<%= @attribute.valueShown %>" class="form-control <%= @attribute.class %>" <%= @attribute.required %> <%= @attribute.autofocus %> <%- @attribute.autocapitalize %> <% if @attribute.placeholder: %>placeholder="<%- @Ti(@attribute.placeholder) %>"<% end %> autocomplete="new-password"/>
|
<input id="<%= @attribute.id %>_autocompletion" type="text" name="<%= @attribute.name %>_autocompletion" value="<%= @attribute.valueShown %>" class="form-control <%= @attribute.class %>" <%= @attribute.required %> <%= @attribute.autofocus %> <%- @attribute.autocapitalize %> <% if @attribute.placeholder: %>placeholder="<%- @Ti(@attribute.placeholder) %>"<% end %> autocomplete="off"/>
|
||||||
<input id="<%= @attribute.id %>_autocompletion_value_shown" type="hidden" name="<%= @attribute.name %>_autocompletion_value_shown" value="<%= @attribute.valueShown %>"/>
|
<input id="<%= @attribute.id %>_autocompletion_value_shown" type="hidden" name="<%= @attribute.name %>_autocompletion_value_shown" value="<%= @attribute.valueShown %>"/>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<input type="checkbox" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>/>
|
<input type="checkbox" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>/>
|
||||||
<%- @Icon('checkbox', 'icon-unchecked') %>
|
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||||
<span class="label-text"><%= row.name %> <% if row.note: %>- <span class="help-text"><%= row.note %></span><% end %></span>
|
<span class="label-text"><%= row.name %> <% if row.note: %>- <span class="help-text"><%- @T(row.note) %></span><% end %></span>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
|
@ -3,7 +3,7 @@
|
||||||
<% if @attribute.multiple: %>
|
<% if @attribute.multiple: %>
|
||||||
<%- @tokens %>
|
<%- @tokens %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<input name="<%- @attribute.name %>_completion" class="user-select token-input js-objectSelect" autocapitalize="off" placeholder="<%- @attribute.placeholder %>" autocomplete="new-password" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
<input name="<%- @attribute.name %>_completion" class="user-select token-input js-objectSelect" autocapitalize="off" placeholder="<%- @attribute.placeholder %>" autocomplete="off" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
||||||
<% if @attribute.disableCreateObject isnt true: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
<% if @attribute.disableCreateObject isnt true: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,36 @@
|
||||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||||
<span class="label-text"><%= permission.displayName().replace(/^.+?\./, '') %> - <span class="help-text"><%- @T.apply(@, [permission.note].concat(permission.preferences.translations)) %></span></span>
|
<span class="label-text"><%= permission.displayName().replace(/^.+?\./, '') %> - <span class="help-text"><%- @T.apply(@, [permission.note].concat(permission.preferences.translations)) %></span></span>
|
||||||
</label>
|
</label>
|
||||||
|
<% if _.contains(permission.preferences.plugin, 'groups'): %>
|
||||||
|
<div style="padding-left: 18px; padding-top: 10px;" class="js-groupList <% if @hideGroups: %>js-groupListHide hidden<% end %>">
|
||||||
|
<table class="settings-list">
|
||||||
|
<thead>
|
||||||
|
<th><%- @T('Group') %>
|
||||||
|
<% for key, text of @groupAccesses: %>
|
||||||
|
<th><%- @T(text) %>
|
||||||
|
<% end %>
|
||||||
|
<tbody>
|
||||||
|
<% for group in @groups: %>
|
||||||
|
<% accesses = [] %>
|
||||||
|
<% if @params.group_ids && @params.group_ids[group.id]: %>
|
||||||
|
<% accesses = @params.group_ids[group.id] %>
|
||||||
|
<% end %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= group.displayName() %>
|
||||||
|
<% for key, text of @groupAccesses: %>
|
||||||
|
<td>
|
||||||
|
<label class="inline-label checkbox-replacement">
|
||||||
|
<input type="checkbox" value="<%= key %>" name="group_ids::<%= group.id %>" <% if _.contains(accesses, key): %>checked<% end %>/>
|
||||||
|
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||||
|
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="<%= @attribute.class %>">
|
<div class="<%= @attribute.class %>">
|
||||||
<% for row in @attribute.options: %>
|
<% for row in @attribute.options: %>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>> <%= row.name %> <% if row.note: %>- <%= row.note %><% end %>
|
<input type="radio" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>> <%= row.name %> <% if row.note: %>- <%- @T(row.note) %><% end %>
|
||||||
<%- @Icon('radio') %>
|
<%- @Icon('radio') %>
|
||||||
<%- @Icon('radio-checked') %>
|
<%- @Icon('radio-checked') %>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
<div class="dropdown-toggle" data-toggle="dropdown">
|
<div class="dropdown-toggle" data-toggle="dropdown">
|
||||||
<input
|
<input
|
||||||
class="searchableSelect-shadow form-control js-shadow"
|
class="searchableSelect-shadow form-control js-shadow"
|
||||||
id="<%= @id %>"
|
id="<%= @attribute.id %>"
|
||||||
name="<%= @name %>"
|
name="<%= @attribute.name %>"
|
||||||
<%= @required %>
|
<%= @attribute.required %>
|
||||||
<%= @autofocus %>
|
<%= @attribute.autofocus %>
|
||||||
value="<%= @value %>"
|
value="<%= @attribute.value %>"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="searchableSelect-main form-control js-input<%= " #{ @class }" if @class %>"
|
class="searchableSelect-main form-control js-input<%= " #{ @attribute.class }" if @attribute.class %>"
|
||||||
placeholder="<%= @placeholder %>"
|
placeholder="<%= @attribute.placeholder %>"
|
||||||
value="<%= @valueName %>"
|
value="<%= @attribute.valueName %>"
|
||||||
autocomplete="new-password"
|
autocomplete="off"
|
||||||
|
<%= @attribute.required %>
|
||||||
>
|
>
|
||||||
<div class="searchableSelect-autocomplete">
|
<div class="searchableSelect-autocomplete">
|
||||||
<span class="searchableSelect-autocomplete-invisible js-autocomplete-invisible"></span>
|
<span class="searchableSelect-autocomplete-invisible js-autocomplete-invisible"></span>
|
||||||
<span class="searchableSelect-autocomplete-visible js-autocomplete-visible"></span>
|
<span class="searchableSelect-autocomplete-visible js-autocomplete-visible"></span>
|
||||||
</div>
|
</div>
|
||||||
<% if !@ajax: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
<% if !@attribute.ajax: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
||||||
<div class="small loading icon"></div>
|
<div class="small loading icon"></div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="dropdown-menu dropdown-menu-left js-optionsList" role="menu">
|
<div class="dropdown-menu dropdown-menu-left dropdown-menu--has-submenu js-dropdown">
|
||||||
<%- @renderedOptions %>
|
<ul class="js-optionsList" role="menu">
|
||||||
</ul>
|
<%- @options %>
|
||||||
|
</ul>
|
||||||
|
<%- @submenus %>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<li role="presentation" class="<%= @class %>" data-value="<%= @option.value %>" title="<%= @option.name %><% if @detail: %><%= @detail %><% end %>">
|
||||||
|
<span class="searchableSelect-option-text">
|
||||||
|
<%= @option.name %><% if @detail: %><span class="dropdown-detail"><%= @detail %></span><% end %>
|
||||||
|
</span>
|
||||||
|
<% if @option.children: %>
|
||||||
|
<%- @Icon('arrow-right', 'recipientList-arrow') %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
|
@ -1,5 +0,0 @@
|
||||||
<% if @options: %>
|
|
||||||
<% for option in @options: %>
|
|
||||||
<li role="presentation" class="js-option" data-value="<%= option.value %>"><%= option.name %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue