Merge branch 'develop' into cast-boolean-object-attribute
This commit is contained in:
commit
49223a51c7
946 changed files with 22736 additions and 10335 deletions
33
.github/ISSUE_TEMPLATE.md
vendored
33
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,6 +1,18 @@
|
||||||
<!--
|
<!--
|
||||||
Hi there - thanks for filling an issue. Please ensure the following things before creating an issue - thank you! 🤓
|
Hi there - thanks for filing an issue. Please ensure the following things before creating an issue - thank you! 🤓
|
||||||
|
|
||||||
|
Since november 15th we handle all requests, except real bugs, at our community board.
|
||||||
|
Full explanation: https://community.zammad.org/t/major-change-regarding-github-issues-community-board/21
|
||||||
|
|
||||||
|
Please post:
|
||||||
|
- Feature requests
|
||||||
|
- Development questions
|
||||||
|
- Technical questions
|
||||||
|
|
||||||
|
on the board -> https://community.zammad.org !
|
||||||
|
|
||||||
|
|
||||||
|
If you think you hit a bug, please continue:
|
||||||
- Search existing issues and the CHANGELOG.md for your issue - there might be a solution already
|
- Search existing issues and the CHANGELOG.md for your issue - there might be a solution already
|
||||||
- Make sure to use the latest version of Zammad if possible
|
- Make sure to use the latest version of Zammad if possible
|
||||||
- Add the `log/production.log` file from your system. Attention: Make sure no confidential data is in it!
|
- Add the `log/production.log` file from your system. Attention: Make sure no confidential data is in it!
|
||||||
|
@ -8,30 +20,33 @@ Hi there - thanks for filling an issue. Please ensure the following things befor
|
||||||
- Don't remove the template - otherwise we will close the issue without further comments
|
- Don't remove the template - otherwise we will close the issue without further comments
|
||||||
- Ask questions about Zammad configuration and usage at our mailinglist. See: https://zammad.org/participate
|
- Ask questions about Zammad configuration and usage at our mailinglist. See: https://zammad.org/participate
|
||||||
|
|
||||||
Note: We always do our best. Unfortunately, sometimes the requests are too much and we can't handle everything at once. If you want to prioritize/escalate your issue, you can do so by means of a support contract (see https://zammad.com/pricing#selfhosted).
|
Note: We always do our best. Unfortunately, sometimes there are too many requests and we can't handle everything at once. If you want to prioritize/escalate your issue, you can do so by means of a support contract (see https://zammad.com/pricing#selfhosted).
|
||||||
|
|
||||||
* The upper textblock will be removed automatically when you submit your issue *
|
* The upper textblock will be removed automatically when you submit your issue *
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Infos:
|
### Infos:
|
||||||
|
|
||||||
* Used Zammad version:
|
* Used Zammad version:
|
||||||
* Used Zammad installation source: (source, package, ...)
|
* Installation method (source, package, ..):
|
||||||
* Operating system:
|
* Operating system:
|
||||||
* Browser + version:
|
* Database + version:
|
||||||
|
* Elasticsearch version:
|
||||||
|
* Browser + version:
|
||||||
|
|
||||||
|
|
||||||
### Expected behavior:
|
### Expected behavior:
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
|
||||||
### Actual behavior:
|
### Actual behavior:
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
|
||||||
### Steps to reproduce the behavior:
|
### Steps to reproduce the behavior:
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
Yes I'm sure this is a bug and no feature request or a general question.
|
||||||
|
|
|
@ -310,7 +310,8 @@ test:integration:es_mysql:
|
||||||
- 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
|
- ruby -I test/ test/controllers/form_controller_test.rb
|
||||||
- ruby -I test/ test/controllers/user_organization_controller_test.rb
|
- ruby -I test/ test/controllers/user_controller_test.rb
|
||||||
|
- ruby -I test/ test/controllers/organization_controller_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:es_postgresql:
|
test:integration:es_postgresql:
|
||||||
|
@ -328,7 +329,8 @@ test:integration:es_postgresql:
|
||||||
- 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
|
- ruby -I test/ test/controllers/form_controller_test.rb
|
||||||
- ruby -I test/ test/controllers/user_organization_controller_test.rb
|
- ruby -I test/ test/controllers/user_controller_test.rb
|
||||||
|
- ruby -I test/ test/controllers/organization_controller_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:zendesk_mysql:
|
test:integration:zendesk_mysql:
|
||||||
|
@ -355,24 +357,36 @@ test:integration:zendesk_postgresql:
|
||||||
- ruby -I test/ test/integration/zendesk_import_test.rb
|
- ruby -I test/ test/integration/zendesk_import_test.rb
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:otrs_5_mysql:
|
test:integration:otrs_6_mysql:
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
- mysql
|
- mysql
|
||||||
script:
|
script:
|
||||||
- export RAILS_ENV=test
|
- export RAILS_ENV=test
|
||||||
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
- export IMPORT_OTRS_ENDPOINT="http://vz1185.test.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
|
||||||
- rake db:drop
|
- rake db:drop
|
||||||
|
|
||||||
test:integration:otrs_5_postgresql:
|
test:integration:otrs_6_postgresql:
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
- postgresql
|
- postgresql
|
||||||
|
script:
|
||||||
|
- export RAILS_ENV=test
|
||||||
|
- export IMPORT_OTRS_ENDPOINT="http://vz1185.test.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||||
|
- rake db:create
|
||||||
|
- rake db:migrate
|
||||||
|
- ruby -I test/ test/integration/otrs_import_test.rb
|
||||||
|
- rake db:drop
|
||||||
|
|
||||||
|
test:integration:otrs_5:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
script:
|
script:
|
||||||
- export RAILS_ENV=test
|
- export RAILS_ENV=test
|
||||||
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||||
|
|
61
.rubocop.yml
61
.rubocop.yml
|
@ -45,29 +45,29 @@ Style/TrailingCommaInArguments:
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/SpaceInsideParens:
|
Layout/SpaceInsideParens:
|
||||||
Description: 'No spaces after ( or before ).'
|
Description: 'No spaces after ( or before ).'
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/SpaceAfterMethodName:
|
Layout/SpaceAfterMethodName:
|
||||||
Description: >-
|
Description: >-
|
||||||
Do not put a space between a method name and the opening
|
Do not put a space between a method name and the opening
|
||||||
parenthesis in a method definition.
|
parenthesis in a method definition.
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/LeadingCommentSpace:
|
Layout/LeadingCommentSpace:
|
||||||
Description: 'Comments should start with a space.'
|
Description: 'Comments should start with a space.'
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/MethodCallParentheses:
|
Style/MethodCallWithoutArgsParentheses:
|
||||||
Description: 'Do not use parentheses for method calls with no arguments.'
|
Description: 'Do not use parentheses for method calls with no arguments.'
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/SpaceInsideBrackets:
|
Layout/SpaceInsideBrackets:
|
||||||
Description: 'No spaces after [ or before ].'
|
Description: 'No spaces after [ or before ].'
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -83,19 +83,19 @@ Style/MethodDefParentheses:
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/EmptyLinesAroundClassBody:
|
Layout/EmptyLinesAroundClassBody:
|
||||||
Description: "Keeps track of empty lines around class bodies."
|
Description: "Keeps track of empty lines around class bodies."
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/EmptyLinesAroundMethodBody:
|
Layout/EmptyLinesAroundMethodBody:
|
||||||
Description: "Keeps track of empty lines around method bodies."
|
Description: "Keeps track of empty lines around method bodies."
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/EmptyLinesAroundBlockBody:
|
Layout/EmptyLinesAroundBlockBody:
|
||||||
Description: "Keeps track of empty lines around block bodies."
|
Description: "Keeps track of empty lines around block bodies."
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/EmptyLinesAroundModuleBody:
|
Layout/EmptyLinesAroundModuleBody:
|
||||||
Description: "Keeps track of empty lines around module bodies."
|
Description: "Keeps track of empty lines around module bodies."
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
@ -143,17 +143,29 @@ Rails/HasAndBelongsToMany:
|
||||||
# StyleGuide: 'https://github.com/bbatsov/rails-style-guide#has-many-through'
|
# StyleGuide: 'https://github.com/bbatsov/rails-style-guide#has-many-through'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Rails/SkipsModelValidations:
|
||||||
|
Description: >-
|
||||||
|
Use methods that skips model validations with caution.
|
||||||
|
See reference for more information.
|
||||||
|
Reference: 'http://guides.rubyonrails.org/active_record_validations.html#skipping-validations'
|
||||||
|
Enabled: true
|
||||||
|
Exclude:
|
||||||
|
- test/**/*
|
||||||
|
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
Description: 'Checks style of children classes and modules.'
|
Description: 'Checks style of children classes and modules.'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/FileName:
|
Naming/FileName:
|
||||||
Description: 'Use snake_case for source file names.'
|
Description: 'Use snake_case for source file names.'
|
||||||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'script/websocket-server.rb'
|
- 'script/websocket-server.rb'
|
||||||
|
|
||||||
|
Naming/VariableNumber:
|
||||||
|
Description: 'Use the configured style when numbering variables.'
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
# 2.0
|
# 2.0
|
||||||
|
|
||||||
|
@ -184,8 +196,23 @@ Metrics/ModuleLength:
|
||||||
Description: 'Avoid modules longer than 100 lines of code.'
|
Description: 'Avoid modules longer than 100 lines of code.'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Metrics/BlockLength:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Lint/RescueWithoutErrorClass:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Rails/ApplicationRecord:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
Rails/HasManyOrHasOneDependent:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Style/DateTime:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Description: 'Document classes and non-namespace modules.'
|
Description: 'Document classes and non-namespace modules.'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -193,7 +220,7 @@ Style/Documentation:
|
||||||
Lint/UselessAssignment:
|
Lint/UselessAssignment:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/ExtraSpacing:
|
Layout/ExtraSpacing:
|
||||||
Description: 'Do not use unnecessary spacing.'
|
Description: 'Do not use unnecessary spacing.'
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
@ -215,4 +242,14 @@ Style/NumericPredicate:
|
||||||
AutoCorrect: false
|
AutoCorrect: false
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Exclude:
|
Exclude:
|
||||||
- "**/*_spec.rb"
|
- "**/*_spec.rb"
|
||||||
|
|
||||||
|
Lint/AmbiguousBlockAssociation:
|
||||||
|
Description: >-
|
||||||
|
Checks for ambiguous block association with method when param passed without
|
||||||
|
parentheses.
|
||||||
|
StyleGuide: '#syntax'
|
||||||
|
Enabled: true
|
||||||
|
Exclude:
|
||||||
|
- "**/*_spec.rb"
|
||||||
|
- "**/*_examples.rb"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.4.1
|
2.4.2
|
||||||
|
|
|
@ -19,7 +19,7 @@ services:
|
||||||
- mysql
|
- mysql
|
||||||
language: ruby
|
language: ruby
|
||||||
rvm:
|
rvm:
|
||||||
- 2.4.1
|
- 2.4.2
|
||||||
before_install:
|
before_install:
|
||||||
- git fetch --unshallow
|
- git fetch --unshallow
|
||||||
- sudo apt-get -qq update
|
- sudo apt-get -qq update
|
||||||
|
@ -62,3 +62,4 @@ script:
|
||||||
after_success:
|
after_success:
|
||||||
- if [ "${DB}" = "mysql" ]; then contrib/travis-ci.org/trigger-docker-build.sh; fi
|
- if [ "${DB}" = "mysql" ]; then contrib/travis-ci.org/trigger-docker-build.sh; fi
|
||||||
- if [ "${DB}" = "mysql" ]; then contrib/travis-ci.org/trigger-docker-compose-build.sh; fi
|
- if [ "${DB}" = "mysql" ]; then contrib/travis-ci.org/trigger-docker-compose-build.sh; fi
|
||||||
|
- if [ "${DB}" = "mysql" ]; then contrib/travis-ci.org/trigger-docker-univention-build.sh; fi
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
## [2.2.0](https://github.com/zammad/zammad/tree/2.2.0) (2017-xx-xx)
|
## [2.4.0](https://github.com/zammad/zammad/tree/2.4.0) (2018-xx-xx)
|
||||||
[Full Changelog](https://github.com/zammad/zammad/compare/2.1.0...2.2.0)
|
[Full Changelog](https://github.com/zammad/zammad/compare/2.3.0...2.4.0)
|
||||||
|
|
||||||
**Implemented enhancements:**
|
**Implemented enhancements:**
|
||||||
|
|
||||||
|
|
133
Gemfile
133
Gemfile
|
@ -1,111 +1,131 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '2.4.1'
|
# core - base
|
||||||
|
ruby '2.4.2'
|
||||||
gem 'rails', '5.1.4'
|
gem 'rails', '5.1.4'
|
||||||
gem 'rails-observers'
|
|
||||||
|
# core - rails additions
|
||||||
gem 'activerecord-session_store'
|
gem 'activerecord-session_store'
|
||||||
|
gem 'composite_primary_keys'
|
||||||
# Bundle edge Rails instead:
|
|
||||||
#gem 'rails', :git => 'git://github.com/rails/rails.git'
|
|
||||||
|
|
||||||
gem 'json'
|
gem 'json'
|
||||||
|
gem 'rails-observers'
|
||||||
|
|
||||||
# Supported DBs
|
# core - application servers
|
||||||
|
gem 'puma', group: :puma
|
||||||
|
gem 'unicorn', group: :unicorn
|
||||||
|
|
||||||
|
# core - supported ORMs
|
||||||
gem 'activerecord-nulldb-adapter', group: :nulldb
|
gem 'activerecord-nulldb-adapter', group: :nulldb
|
||||||
gem 'mysql2', group: :mysql
|
gem 'mysql2', group: :mysql
|
||||||
gem 'pg', group: :postgres
|
gem 'pg', group: :postgres
|
||||||
|
|
||||||
|
# core - asynchrous task execution
|
||||||
|
gem 'daemons'
|
||||||
|
gem 'delayed_job_active_record'
|
||||||
|
|
||||||
|
# core - websocket
|
||||||
|
gem 'em-websocket'
|
||||||
|
gem 'eventmachine'
|
||||||
|
|
||||||
|
# core - password security
|
||||||
|
gem 'argon2'
|
||||||
|
|
||||||
|
# performance - Memcached
|
||||||
|
gem 'dalli'
|
||||||
|
|
||||||
|
# asset handling
|
||||||
group :assets do
|
group :assets do
|
||||||
gem 'sass-rails' #, github: 'rails/sass-rails'
|
# asset handling - coffee-script
|
||||||
gem 'coffee-rails'
|
gem 'coffee-rails'
|
||||||
gem 'coffee-script-source'
|
gem 'coffee-script-source'
|
||||||
|
|
||||||
gem 'sprockets'
|
# asset handling - frontend templating
|
||||||
|
|
||||||
gem 'uglifier'
|
|
||||||
gem 'eco'
|
gem 'eco'
|
||||||
|
|
||||||
|
# asset handling - SASS
|
||||||
|
gem 'sass-rails'
|
||||||
|
|
||||||
|
# asset handling - pipeline
|
||||||
|
gem 'sprockets'
|
||||||
|
gem 'uglifier'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'autoprefixer-rails'
|
gem 'autoprefixer-rails'
|
||||||
|
|
||||||
|
# asset handling - javascript execution for e.g. linux
|
||||||
|
gem 'execjs'
|
||||||
|
gem 'libv8'
|
||||||
|
gem 'therubyracer'
|
||||||
|
|
||||||
|
# authentication - provider
|
||||||
gem 'doorkeeper'
|
gem 'doorkeeper'
|
||||||
gem 'oauth2'
|
gem 'oauth2'
|
||||||
|
|
||||||
|
# authentication - third party
|
||||||
gem 'omniauth'
|
gem 'omniauth'
|
||||||
gem 'omniauth-oauth2'
|
|
||||||
gem 'omniauth-facebook'
|
gem 'omniauth-facebook'
|
||||||
gem 'omniauth-github'
|
gem 'omniauth-github'
|
||||||
gem 'omniauth-gitlab'
|
gem 'omniauth-gitlab'
|
||||||
gem 'omniauth-google-oauth2'
|
gem 'omniauth-google-oauth2'
|
||||||
gem 'omniauth-linkedin-oauth2'
|
gem 'omniauth-linkedin-oauth2'
|
||||||
gem 'omniauth-twitter'
|
|
||||||
gem 'omniauth-microsoft-office365'
|
gem 'omniauth-microsoft-office365'
|
||||||
|
gem 'omniauth-oauth2'
|
||||||
|
gem 'omniauth-twitter'
|
||||||
gem 'omniauth-weibo-oauth2'
|
gem 'omniauth-weibo-oauth2'
|
||||||
|
|
||||||
gem 'twitter'
|
# channels
|
||||||
gem 'telegramAPI'
|
|
||||||
gem 'koala'
|
gem 'koala'
|
||||||
gem 'mail'
|
gem 'telegramAPI'
|
||||||
gem 'valid_email2'
|
gem 'twitter'
|
||||||
|
|
||||||
|
# channels - email additions
|
||||||
gem 'htmlentities'
|
gem 'htmlentities'
|
||||||
|
gem 'mail', '2.6.6'
|
||||||
gem 'mime-types'
|
gem 'mime-types'
|
||||||
|
gem 'valid_email2'
|
||||||
|
|
||||||
|
# feature - business hours
|
||||||
gem 'biz'
|
gem 'biz'
|
||||||
|
|
||||||
gem 'composite_primary_keys'
|
# feature - signature diffing
|
||||||
gem 'delayed_job_active_record'
|
gem 'diffy'
|
||||||
gem 'daemons'
|
|
||||||
|
|
||||||
gem 'simple-rss'
|
|
||||||
|
|
||||||
# e. g. on linux we need a javascript execution
|
|
||||||
gem 'libv8'
|
|
||||||
gem 'execjs'
|
|
||||||
gem 'therubyracer'
|
|
||||||
|
|
||||||
require 'erb'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
gem 'net-ldap'
|
|
||||||
|
|
||||||
# password security
|
|
||||||
gem 'argon2'
|
|
||||||
|
|
||||||
|
# feature - excel output
|
||||||
gem 'writeexcel'
|
gem 'writeexcel'
|
||||||
gem 'icalendar'
|
|
||||||
gem 'icalendar-recurrence'
|
# feature - device logging
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
|
|
||||||
|
# feature - iCal export
|
||||||
|
gem 'icalendar'
|
||||||
|
gem 'icalendar-recurrence'
|
||||||
|
|
||||||
# integrations
|
# integrations
|
||||||
gem 'slack-notifier'
|
|
||||||
gem 'clearbit'
|
gem 'clearbit'
|
||||||
|
gem 'net-ldap'
|
||||||
|
gem 'slack-notifier'
|
||||||
gem 'zendesk_api'
|
gem 'zendesk_api'
|
||||||
gem 'viewpoint'
|
|
||||||
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm.git'
|
# integrations - exchange
|
||||||
gem 'autodiscover', git: 'https://github.com/thorsteneckel/autodiscover.git'
|
gem 'autodiscover', git: 'https://github.com/thorsteneckel/autodiscover.git'
|
||||||
|
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm.git'
|
||||||
# event machine
|
gem 'viewpoint'
|
||||||
gem 'eventmachine'
|
|
||||||
gem 'em-websocket'
|
|
||||||
|
|
||||||
gem 'diffy'
|
|
||||||
|
|
||||||
# Gems used only for develop/test and not required
|
# Gems used only for develop/test and not required
|
||||||
# in production environments by default.
|
# in production environments by default.
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|
||||||
|
# test frameworks
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
gem 'test-unit'
|
gem 'test-unit'
|
||||||
gem 'spring'
|
|
||||||
gem 'spring-commands-rspec'
|
# test DB
|
||||||
gem 'sqlite3'
|
gem 'sqlite3'
|
||||||
|
|
||||||
# code coverage
|
# code coverage
|
||||||
|
gem 'coveralls', require: false
|
||||||
gem 'simplecov'
|
gem 'simplecov'
|
||||||
gem 'simplecov-rcov'
|
gem 'simplecov-rcov'
|
||||||
gem 'coveralls', require: false
|
|
||||||
|
|
||||||
# UI tests w/ Selenium
|
# UI tests w/ Selenium
|
||||||
gem 'selenium-webdriver', '2.53.4'
|
gem 'selenium-webdriver', '2.53.4'
|
||||||
|
@ -120,9 +140,9 @@ group :development, :test do
|
||||||
gem 'guard-symlink', require: false
|
gem 'guard-symlink', require: false
|
||||||
|
|
||||||
# code QA
|
# code QA
|
||||||
|
gem 'coffeelint'
|
||||||
gem 'pre-commit'
|
gem 'pre-commit'
|
||||||
gem 'rubocop'
|
gem 'rubocop'
|
||||||
gem 'coffeelint'
|
|
||||||
|
|
||||||
# changelog generation
|
# changelog generation
|
||||||
gem 'github_changelog_generator'
|
gem 'github_changelog_generator'
|
||||||
|
@ -130,17 +150,14 @@ group :development, :test do
|
||||||
# Setting ENV for testing purposes
|
# Setting ENV for testing purposes
|
||||||
gem 'figaro'
|
gem 'figaro'
|
||||||
|
|
||||||
# Use Factory Girl for generating random test data
|
# Use Factory Bot for generating random test data
|
||||||
gem 'factory_girl_rails'
|
gem 'factory_bot_rails'
|
||||||
|
|
||||||
# mock http calls
|
# mock http calls
|
||||||
gem 'webmock'
|
gem 'webmock'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'puma', group: :puma
|
# load onw gems for development and testing purposes
|
||||||
gem 'unicorn', group: :unicorn
|
|
||||||
|
|
||||||
# load onw gem's
|
|
||||||
local_gemfile = File.join(File.dirname(__FILE__), 'Gemfile.local')
|
local_gemfile = File.join(File.dirname(__FILE__), 'Gemfile.local')
|
||||||
if File.exist?(local_gemfile)
|
if File.exist?(local_gemfile)
|
||||||
eval_gemfile local_gemfile
|
eval_gemfile local_gemfile
|
||||||
|
|
182
Gemfile.lock
182
Gemfile.lock
|
@ -65,22 +65,22 @@ GEM
|
||||||
addressable (2.5.2)
|
addressable (2.5.2)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
arel (8.0.0)
|
arel (8.0.0)
|
||||||
argon2 (1.1.3)
|
argon2 (1.1.4)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
ffi-compiler (~> 0.1)
|
ffi-compiler (~> 0.1)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
autoprefixer-rails (7.1.3)
|
autoprefixer-rails (7.1.6)
|
||||||
execjs
|
execjs
|
||||||
biz (1.7.0)
|
biz (1.7.0)
|
||||||
clavius (~> 1.0)
|
clavius (~> 1.0)
|
||||||
tzinfo
|
tzinfo
|
||||||
browser (2.5.1)
|
browser (2.5.2)
|
||||||
buftok (0.2.0)
|
buftok (0.2.0)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
childprocess (0.7.1)
|
childprocess (0.8.0)
|
||||||
ffi (~> 1.0, >= 1.0.11)
|
ffi (~> 1.0, >= 1.0.11)
|
||||||
clavius (1.0.3)
|
clavius (1.0.3)
|
||||||
clearbit (0.2.7)
|
clearbit (0.2.8)
|
||||||
nestful (~> 1.1.0)
|
nestful (~> 1.1.0)
|
||||||
coderay (1.1.2)
|
coderay (1.1.2)
|
||||||
coffee-rails (4.2.2)
|
coffee-rails (4.2.2)
|
||||||
|
@ -90,22 +90,24 @@ GEM
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.12.2)
|
coffee-script-source (1.12.2)
|
||||||
coffeelint (1.16.0)
|
coffeelint (1.16.1)
|
||||||
coffee-script
|
coffee-script
|
||||||
execjs
|
execjs
|
||||||
json
|
json
|
||||||
composite_primary_keys (10.0.0)
|
composite_primary_keys (10.0.1)
|
||||||
activerecord (~> 5.1.0)
|
activerecord (~> 5.1.0)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.0.5)
|
||||||
coveralls (0.8.21)
|
coveralls (0.7.1)
|
||||||
json (>= 1.8, < 3)
|
multi_json (~> 1.3)
|
||||||
simplecov (~> 0.14.1)
|
rest-client
|
||||||
term-ansicolor (~> 1.3)
|
simplecov (>= 0.7)
|
||||||
thor (~> 0.19.4)
|
term-ansicolor
|
||||||
tins (~> 1.6)
|
thor
|
||||||
crack (0.4.3)
|
crack (0.4.3)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
daemons (1.2.4)
|
crass (1.0.3)
|
||||||
|
daemons (1.2.5)
|
||||||
|
dalli (2.7.6)
|
||||||
delayed_job (4.1.3)
|
delayed_job (4.1.3)
|
||||||
activesupport (>= 3.0, < 5.2)
|
activesupport (>= 3.0, < 5.2)
|
||||||
delayed_job_active_record (4.1.2)
|
delayed_job_active_record (4.1.2)
|
||||||
|
@ -127,15 +129,15 @@ GEM
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
equalizer (0.0.11)
|
equalizer (0.0.11)
|
||||||
erubi (1.6.1)
|
erubi (1.7.0)
|
||||||
eventmachine (1.2.5)
|
eventmachine (1.2.5)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
factory_girl (4.8.0)
|
factory_bot (4.8.2)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
factory_girl_rails (4.8.0)
|
factory_bot_rails (4.8.2)
|
||||||
factory_girl (~> 4.8.0)
|
factory_bot (~> 4.8.2)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
faraday (0.11.0)
|
faraday (0.12.2)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-http-cache (2.0.0)
|
faraday-http-cache (2.0.0)
|
||||||
faraday (~> 0.8)
|
faraday (~> 0.8)
|
||||||
|
@ -154,7 +156,7 @@ GEM
|
||||||
rainbow (>= 2.1)
|
rainbow (>= 2.1)
|
||||||
rake (>= 10.0)
|
rake (>= 10.0)
|
||||||
retriable (~> 2.1)
|
retriable (~> 2.1)
|
||||||
globalid (0.4.0)
|
globalid (0.4.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
guard (2.14.1)
|
guard (2.14.1)
|
||||||
formatador (>= 0.2.4)
|
formatador (>= 0.2.4)
|
||||||
|
@ -174,20 +176,21 @@ GEM
|
||||||
guard-symlink (0.1.1)
|
guard-symlink (0.1.1)
|
||||||
guard
|
guard
|
||||||
guard-compat (~> 1.1)
|
guard-compat (~> 1.1)
|
||||||
hashdiff (0.3.6)
|
hashdiff (0.3.7)
|
||||||
hashie (3.5.6)
|
hashie (3.5.6)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (2.2.2)
|
http (3.0.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
http-cookie (~> 1.0)
|
http-cookie (~> 1.0)
|
||||||
http-form_data (~> 1.0.1)
|
http-form_data (>= 2.0.0.pre.pre2, < 3)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (1.0.3)
|
http-form_data (2.0.0)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (0.8.6)
|
i18n (0.9.1)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
icalendar (2.4.1)
|
icalendar (2.4.1)
|
||||||
icalendar-recurrence (1.1.2)
|
icalendar-recurrence (1.1.2)
|
||||||
icalendar (~> 2.0)
|
icalendar (~> 2.0)
|
||||||
|
@ -210,25 +213,26 @@ GEM
|
||||||
logging (2.2.2)
|
logging (2.2.2)
|
||||||
little-plugger (~> 1.1)
|
little-plugger (~> 1.1)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
loofah (2.0.3)
|
loofah (2.1.1)
|
||||||
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
lumberjack (1.0.12)
|
lumberjack (1.0.12)
|
||||||
mail (2.6.6)
|
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)
|
||||||
method_source (0.8.2)
|
method_source (0.9.0)
|
||||||
mime-types (2.99.3)
|
mime-types (2.99.3)
|
||||||
mini_portile2 (2.3.0)
|
mini_portile2 (2.3.0)
|
||||||
minitest (5.10.3)
|
minitest (5.10.3)
|
||||||
multi_json (1.12.2)
|
multi_json (1.12.2)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
mysql2 (0.4.9)
|
mysql2 (0.4.10)
|
||||||
naught (1.1.0)
|
naught (1.1.0)
|
||||||
nenv (0.3.0)
|
nenv (0.3.0)
|
||||||
nestful (1.1.1)
|
nestful (1.1.3)
|
||||||
net-ldap (0.16.0)
|
net-ldap (0.16.1)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nio4r (2.1.0)
|
nio4r (2.1.0)
|
||||||
nokogiri (1.8.1)
|
nokogiri (1.8.1)
|
||||||
|
@ -246,7 +250,7 @@ GEM
|
||||||
rack (>= 1.2, < 3)
|
rack (>= 1.2, < 3)
|
||||||
octokit (4.7.0)
|
octokit (4.7.0)
|
||||||
sawyer (~> 0.8.0, >= 0.5.3)
|
sawyer (~> 0.8.0, >= 0.5.3)
|
||||||
omniauth (1.6.1)
|
omniauth (1.7.1)
|
||||||
hashie (>= 3.4.6, < 3.6.0)
|
hashie (>= 3.4.6, < 3.6.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
omniauth-facebook (4.0.0)
|
omniauth-facebook (4.0.0)
|
||||||
|
@ -280,24 +284,24 @@ GEM
|
||||||
omniauth-weibo-oauth2 (0.4.5)
|
omniauth-weibo-oauth2 (0.4.5)
|
||||||
omniauth (~> 1.5)
|
omniauth (~> 1.5)
|
||||||
omniauth-oauth2 (>= 1.4.0)
|
omniauth-oauth2 (>= 1.4.0)
|
||||||
parser (2.4.0.0)
|
parallel (1.12.0)
|
||||||
ast (~> 2.2)
|
parser (2.4.0.2)
|
||||||
|
ast (~> 2.3)
|
||||||
pg (0.21.0)
|
pg (0.21.0)
|
||||||
pluginator (1.5.0)
|
pluginator (1.5.0)
|
||||||
power_assert (1.1.0)
|
power_assert (1.1.1)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pre-commit (0.35.0)
|
pre-commit (0.37.0)
|
||||||
pluginator (~> 1.5)
|
pluginator (~> 1.5)
|
||||||
pry (0.10.4)
|
pry (0.11.3)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.9.0)
|
||||||
slop (~> 3.4)
|
public_suffix (3.0.1)
|
||||||
public_suffix (3.0.0)
|
puma (3.11.0)
|
||||||
puma (3.10.0)
|
|
||||||
rack (2.0.3)
|
rack (2.0.3)
|
||||||
rack-livereload (0.3.16)
|
rack-livereload (0.3.16)
|
||||||
rack
|
rack
|
||||||
rack-test (0.7.0)
|
rack-test (0.8.2)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rails (5.1.4)
|
rails (5.1.4)
|
||||||
actioncable (= 5.1.4)
|
actioncable (= 5.1.4)
|
||||||
|
@ -327,7 +331,7 @@ GEM
|
||||||
rainbow (2.2.2)
|
rainbow (2.2.2)
|
||||||
rake
|
rake
|
||||||
raindrops (0.19.0)
|
raindrops (0.19.0)
|
||||||
rake (12.1.0)
|
rake (12.3.0)
|
||||||
rb-fsevent (0.10.2)
|
rb-fsevent (0.10.2)
|
||||||
rb-inotify (0.9.10)
|
rb-inotify (0.9.10)
|
||||||
ffi (>= 0.5.0, < 2)
|
ffi (>= 0.5.0, < 2)
|
||||||
|
@ -337,39 +341,40 @@ GEM
|
||||||
mime-types (>= 1.16, < 3.0)
|
mime-types (>= 1.16, < 3.0)
|
||||||
netrc (~> 0.7)
|
netrc (~> 0.7)
|
||||||
retriable (2.1.0)
|
retriable (2.1.0)
|
||||||
rspec-core (3.6.0)
|
rspec-core (3.7.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-expectations (3.6.0)
|
rspec-expectations (3.7.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-mocks (3.6.0)
|
rspec-mocks (3.7.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-rails (3.6.1)
|
rspec-rails (3.7.2)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
rspec-core (~> 3.6.0)
|
rspec-core (~> 3.7.0)
|
||||||
rspec-expectations (~> 3.6.0)
|
rspec-expectations (~> 3.7.0)
|
||||||
rspec-mocks (~> 3.6.0)
|
rspec-mocks (~> 3.7.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-support (3.6.0)
|
rspec-support (3.7.0)
|
||||||
rubocop (0.42.0)
|
rubocop (0.51.0)
|
||||||
parser (>= 2.3.1.1, < 3.0)
|
parallel (~> 1.10)
|
||||||
|
parser (>= 2.3.3.1, < 3.0)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
rainbow (>= 1.99.1, < 3.0)
|
rainbow (>= 2.2.2, < 3.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.9.0)
|
||||||
ruby_dep (1.5.0)
|
ruby_dep (1.5.0)
|
||||||
rubyzip (1.2.1)
|
rubyzip (1.2.1)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
sass (3.5.1)
|
sass (3.5.3)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
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)
|
||||||
sass-rails (5.0.6)
|
sass-rails (5.0.7)
|
||||||
railties (>= 4.0.0, < 6)
|
railties (>= 4.0.0, < 6)
|
||||||
sass (~> 3.1)
|
sass (~> 3.1)
|
||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
|
@ -383,9 +388,8 @@ GEM
|
||||||
rubyzip (~> 1.0)
|
rubyzip (~> 1.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
shellany (0.0.1)
|
shellany (0.0.1)
|
||||||
simple-rss (1.3.1)
|
|
||||||
simple_oauth (0.3.1)
|
simple_oauth (0.3.1)
|
||||||
simplecov (0.14.1)
|
simplecov (0.15.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)
|
||||||
|
@ -393,11 +397,6 @@ GEM
|
||||||
simplecov-rcov (0.2.3)
|
simplecov-rcov (0.2.3)
|
||||||
simplecov (>= 0.4.1)
|
simplecov (>= 0.4.1)
|
||||||
slack-notifier (2.3.1)
|
slack-notifier (2.3.1)
|
||||||
slop (3.6.0)
|
|
||||||
spring (2.0.2)
|
|
||||||
activesupport (>= 4.2)
|
|
||||||
spring-commands-rspec (1.0.4)
|
|
||||||
spring (>= 0.9.1)
|
|
||||||
sprockets (3.7.1)
|
sprockets (3.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
|
@ -410,26 +409,27 @@ GEM
|
||||||
rest-client (~> 1.7, >= 1.7.3)
|
rest-client (~> 1.7, >= 1.7.3)
|
||||||
term-ansicolor (1.6.0)
|
term-ansicolor (1.6.0)
|
||||||
tins (~> 1.0)
|
tins (~> 1.0)
|
||||||
test-unit (3.2.5)
|
test-unit (3.2.6)
|
||||||
power_assert
|
power_assert
|
||||||
therubyracer (0.12.3)
|
therubyracer (0.12.3)
|
||||||
libv8 (~> 3.16.14.15)
|
libv8 (~> 3.16.14.15)
|
||||||
ref
|
ref
|
||||||
thor (0.19.4)
|
thor (0.20.0)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
tins (1.15.0)
|
tins (1.15.1)
|
||||||
twitter (6.1.0)
|
twitter (6.2.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.3)
|
||||||
buftok (~> 0.2.0)
|
buftok (~> 0.2.0)
|
||||||
equalizer (= 0.0.11)
|
equalizer (~> 0.0.11)
|
||||||
faraday (~> 0.11.0)
|
http (~> 3.0)
|
||||||
http (~> 2.1)
|
http-form_data (~> 2.0)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
memoizable (~> 0.4.2)
|
memoizable (~> 0.4.0)
|
||||||
naught (~> 1.1)
|
multipart-post (~> 2.0)
|
||||||
simple_oauth (~> 0.3.1)
|
naught (~> 1.0)
|
||||||
tzinfo (1.2.3)
|
simple_oauth (~> 0.3.0)
|
||||||
|
tzinfo (1.2.4)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.2.0)
|
uglifier (3.2.0)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
|
@ -437,10 +437,10 @@ GEM
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.4)
|
unf_ext (0.0.7.4)
|
||||||
unicode-display_width (1.3.0)
|
unicode-display_width (1.3.0)
|
||||||
unicorn (5.3.0)
|
unicorn (5.3.1)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
valid_email2 (2.0.1)
|
valid_email2 (2.1.0)
|
||||||
activemodel (>= 3.2)
|
activemodel (>= 3.2)
|
||||||
mail (~> 2.5)
|
mail (~> 2.5)
|
||||||
viewpoint (1.1.0)
|
viewpoint (1.1.0)
|
||||||
|
@ -448,16 +448,16 @@ GEM
|
||||||
logging
|
logging
|
||||||
nokogiri
|
nokogiri
|
||||||
rubyntlm
|
rubyntlm
|
||||||
webmock (3.0.1)
|
webmock (3.1.1)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
websocket (1.2.4)
|
websocket (1.2.5)
|
||||||
websocket-driver (0.6.5)
|
websocket-driver (0.6.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.3)
|
||||||
writeexcel (1.0.5)
|
writeexcel (1.0.5)
|
||||||
zendesk_api (1.14.4)
|
zendesk_api (1.16.0)
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
hashie (>= 3.5.2, < 4.0.0)
|
hashie (>= 3.5.2, < 4.0.0)
|
||||||
inflection
|
inflection
|
||||||
|
@ -482,6 +482,7 @@ DEPENDENCIES
|
||||||
composite_primary_keys
|
composite_primary_keys
|
||||||
coveralls
|
coveralls
|
||||||
daemons
|
daemons
|
||||||
|
dalli
|
||||||
delayed_job_active_record
|
delayed_job_active_record
|
||||||
diffy
|
diffy
|
||||||
doorkeeper
|
doorkeeper
|
||||||
|
@ -489,7 +490,7 @@ DEPENDENCIES
|
||||||
em-websocket
|
em-websocket
|
||||||
eventmachine
|
eventmachine
|
||||||
execjs
|
execjs
|
||||||
factory_girl_rails
|
factory_bot_rails
|
||||||
figaro
|
figaro
|
||||||
github_changelog_generator
|
github_changelog_generator
|
||||||
guard
|
guard
|
||||||
|
@ -501,7 +502,7 @@ DEPENDENCIES
|
||||||
json
|
json
|
||||||
koala
|
koala
|
||||||
libv8
|
libv8
|
||||||
mail
|
mail (= 2.6.6)
|
||||||
mime-types
|
mime-types
|
||||||
mysql2
|
mysql2
|
||||||
net-ldap
|
net-ldap
|
||||||
|
@ -528,12 +529,9 @@ DEPENDENCIES
|
||||||
rubyntlm!
|
rubyntlm!
|
||||||
sass-rails
|
sass-rails
|
||||||
selenium-webdriver (= 2.53.4)
|
selenium-webdriver (= 2.53.4)
|
||||||
simple-rss
|
|
||||||
simplecov
|
simplecov
|
||||||
simplecov-rcov
|
simplecov-rcov
|
||||||
slack-notifier
|
slack-notifier
|
||||||
spring
|
|
||||||
spring-commands-rspec
|
|
||||||
sprockets
|
sprockets
|
||||||
sqlite3
|
sqlite3
|
||||||
telegramAPI
|
telegramAPI
|
||||||
|
@ -549,7 +547,7 @@ DEPENDENCIES
|
||||||
zendesk_api
|
zendesk_api
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.4.1p111
|
ruby 2.4.2p198
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.15.4
|
1.16.0
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
Zammad is a web based open source helpdesk/customer support system with many
|
Zammad is a web based open source helpdesk/customer support system with many
|
||||||
features to manage customer communication via several channels like telephone,
|
features to manage customer communication via several channels like telephone,
|
||||||
facebook, twitter, chat and e-mails. It is distributed under the GNU AFFERO
|
facebook, twitter, chat and e-mails. It is distributed under the GNU AFFERO
|
||||||
General Public License (AGPL) and tested on Linux, Solaris, AIX, FreeBSD,
|
General Public License (AGPL).
|
||||||
OpenBSD and Mac OS 10.x. Do you receive many e-mails and want to answer them
|
|
||||||
with a team of agents?
|
Do you receive many e-mails and want to answer them with a team of agents?
|
||||||
|
|
||||||
You're going to love Zammad!
|
You're going to love Zammad!
|
||||||
|
|
||||||
|
|
0
Rakefile
Normal file → Executable file
0
Rakefile
Normal file → Executable file
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
2.2.x
|
2.4.x
|
||||||
|
|
|
@ -700,6 +700,8 @@ class App.ControllerModal extends App.Controller
|
||||||
headPrefix: ''
|
headPrefix: ''
|
||||||
shown: true
|
shown: true
|
||||||
closeOnAnyClick: false
|
closeOnAnyClick: false
|
||||||
|
initalFormParams: {}
|
||||||
|
initalFormParamsIgnore: false
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'submit form': 'submit'
|
'submit form': 'submit'
|
||||||
|
@ -746,10 +748,10 @@ class App.ControllerModal extends App.Controller
|
||||||
centerButtons: @centerButtons
|
centerButtons: @centerButtons
|
||||||
leftButtons: @leftButtons
|
leftButtons: @leftButtons
|
||||||
)
|
)
|
||||||
modal.find('.modal-body').html content
|
modal.find('.modal-body').html(content)
|
||||||
if !@initRenderingDone
|
if !@initRenderingDone
|
||||||
@initRenderingDone = true
|
@initRenderingDone = true
|
||||||
@html modal
|
@html(modal)
|
||||||
else
|
else
|
||||||
@$('.modal-dialog').replaceWith(modal)
|
@$('.modal-dialog').replaceWith(modal)
|
||||||
@post()
|
@post()
|
||||||
|
@ -761,6 +763,8 @@ class App.ControllerModal extends App.Controller
|
||||||
@el
|
@el
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
|
@initalFormParamsIgnore = false
|
||||||
|
|
||||||
if @buttonSubmit is true
|
if @buttonSubmit is true
|
||||||
@buttonSubmit = 'Submit'
|
@buttonSubmit = 'Submit'
|
||||||
if @buttonCancel is true
|
if @buttonCancel is true
|
||||||
|
@ -775,19 +779,18 @@ class App.ControllerModal extends App.Controller
|
||||||
if @small
|
if @small
|
||||||
@el.addClass('modal--small')
|
@el.addClass('modal--small')
|
||||||
|
|
||||||
@el.modal
|
@el.modal(
|
||||||
keyboard: @keyboard
|
keyboard: @keyboard
|
||||||
show: true
|
show: true
|
||||||
backdrop: @backdrop
|
backdrop: @backdrop
|
||||||
container: @container
|
container: @container
|
||||||
.on
|
).on(
|
||||||
'show.bs.modal': @onShow
|
'show.bs.modal': @localOnShow
|
||||||
'shown.bs.modal': @onShown
|
'shown.bs.modal': @localOnShown
|
||||||
'hide.bs.modal': @onClose
|
'hide.bs.modal': @localOnClose
|
||||||
'hidden.bs.modal': =>
|
'hidden.bs.modal': @localOnClosed
|
||||||
@onClosed()
|
'dismiss.bs.modal': @localOnCancel
|
||||||
$('.modal').remove()
|
)
|
||||||
'dismiss.bs.modal': @onCancel
|
|
||||||
|
|
||||||
if @closeOnAnyClick
|
if @closeOnAnyClick
|
||||||
@el.on('click', =>
|
@el.on('click', =>
|
||||||
|
@ -797,6 +800,7 @@ class App.ControllerModal extends App.Controller
|
||||||
close: (e) =>
|
close: (e) =>
|
||||||
if e
|
if e
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@initalFormParamsIgnore = true
|
||||||
@el.modal('hide')
|
@el.modal('hide')
|
||||||
|
|
||||||
formParams: =>
|
formParams: =>
|
||||||
|
@ -804,28 +808,50 @@ class App.ControllerModal extends App.Controller
|
||||||
return @formParam(@container.find('.modal form'))
|
return @formParam(@container.find('.modal form'))
|
||||||
return @formParam(@$('.modal form'))
|
return @formParam(@$('.modal form'))
|
||||||
|
|
||||||
onShow: ->
|
localOnShow: (e) =>
|
||||||
|
@onShow(e)
|
||||||
|
|
||||||
|
onShow: (e) ->
|
||||||
# do nothing
|
# do nothing
|
||||||
|
|
||||||
onShown: =>
|
localOnShown: (e) =>
|
||||||
|
@onShown(e)
|
||||||
|
|
||||||
|
onShown: (e) =>
|
||||||
@$('input:not([disabled]):not([type="hidden"]):not(".btn"), textarea').first().focus()
|
@$('input:not([disabled]):not([type="hidden"]):not(".btn"), textarea').first().focus()
|
||||||
|
@initalFormParams = @formParams()
|
||||||
|
|
||||||
|
localOnClose: (e) =>
|
||||||
|
diff = difference(@initalFormParams, @formParams())
|
||||||
|
if @initalFormParamsIgnore is false && !_.isEmpty(diff)
|
||||||
|
if !confirm(App.i18n.translateContent('The form content has been changed. Do you want to close it and lose your changes?'))
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
@onClose(e)
|
||||||
|
|
||||||
onClose: ->
|
onClose: ->
|
||||||
# do nothing
|
# do nothing
|
||||||
|
|
||||||
onClosed: ->
|
localOnClosed: (e) =>
|
||||||
|
@onClosed(e)
|
||||||
|
$('.modal').remove()
|
||||||
|
|
||||||
|
onClosed: (e) ->
|
||||||
# do nothing
|
# do nothing
|
||||||
|
|
||||||
onSubmit: ->
|
localOnCancel: (e) =>
|
||||||
# do nothing
|
@onCancel(e)
|
||||||
|
|
||||||
onCancel: ->
|
onCancel: (e) ->
|
||||||
# do nothing
|
# do nothing
|
||||||
|
|
||||||
cancel: (e) =>
|
cancel: (e) =>
|
||||||
@close(e)
|
@close(e)
|
||||||
@onCancel(e)
|
@onCancel(e)
|
||||||
|
|
||||||
|
onSubmit: (e) ->
|
||||||
|
# do nothing
|
||||||
|
|
||||||
submit: (e) =>
|
submit: (e) =>
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -86,6 +86,9 @@ class App.ControllerForm extends App.Controller
|
||||||
for attribute in @attributes
|
for attribute in @attributes
|
||||||
attribute_count = attribute_count + 1
|
attribute_count = attribute_count + 1
|
||||||
|
|
||||||
|
if @isDisabled == true
|
||||||
|
attribute.disabled = true
|
||||||
|
|
||||||
# add item
|
# add item
|
||||||
item = @formGenItem(attribute, className, fieldset, attribute_count)
|
item = @formGenItem(attribute, className, fieldset, attribute_count)
|
||||||
item.appendTo(fieldset)
|
item.appendTo(fieldset)
|
||||||
|
|
|
@ -148,24 +148,26 @@ class App.ControllerGenericIndex extends App.Controller
|
||||||
return item
|
return item
|
||||||
)
|
)
|
||||||
|
|
||||||
# show description button, only if content exists
|
if !@table
|
||||||
showDescription = false
|
|
||||||
if App[ @genericObject ].description && !_.isEmpty(objects)
|
|
||||||
showDescription = true
|
|
||||||
|
|
||||||
@html App.view('generic/admin/index')(
|
# show description button, only if content exists
|
||||||
head: @pageData.objects
|
showDescription = false
|
||||||
notes: @pageData.notes
|
if App[ @genericObject ].description && !_.isEmpty(objects)
|
||||||
buttons: @pageData.buttons
|
showDescription = true
|
||||||
menus: @pageData.menus
|
|
||||||
showDescription: showDescription
|
|
||||||
)
|
|
||||||
|
|
||||||
# show description in content if no no content exists
|
@html App.view('generic/admin/index')(
|
||||||
if _.isEmpty(objects) && App[ @genericObject ].description
|
head: @pageData.objects
|
||||||
description = marked(App[ @genericObject ].description)
|
notes: @pageData.notes
|
||||||
@$('.table-overview').html(description)
|
buttons: @pageData.buttons
|
||||||
return
|
menus: @pageData.menus
|
||||||
|
showDescription: showDescription
|
||||||
|
)
|
||||||
|
|
||||||
|
# show description in content if no no content exists
|
||||||
|
if _.isEmpty(objects) && App[ @genericObject ].description
|
||||||
|
description = marked(App[ @genericObject ].description)
|
||||||
|
@$('.table-overview').html(description)
|
||||||
|
return
|
||||||
|
|
||||||
# append content table
|
# append content table
|
||||||
params = _.extend(
|
params = _.extend(
|
||||||
|
@ -184,7 +186,10 @@ class App.ControllerGenericIndex extends App.Controller
|
||||||
},
|
},
|
||||||
@pageData.tableExtend
|
@pageData.tableExtend
|
||||||
)
|
)
|
||||||
new App.ControllerTable(params)
|
if !@table
|
||||||
|
@table = new App.ControllerTable(params)
|
||||||
|
else
|
||||||
|
@table.update(objects: objects)
|
||||||
|
|
||||||
edit: (id, e) =>
|
edit: (id, e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -651,7 +656,7 @@ class App.Sidebar extends App.Controller
|
||||||
'.sidebar': 'sidebars'
|
'.sidebar': 'sidebars'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .tabsSidebar-tab': 'toggleTab'
|
'click .tabsSidebar-tab': 'toggleTab'
|
||||||
'click .tabsSidebar-close': 'toggleSidebar'
|
'click .tabsSidebar-close': 'toggleSidebar'
|
||||||
'click .sidebar-header .js-headline': 'toggleDropdown'
|
'click .sidebar-header .js-headline': 'toggleDropdown'
|
||||||
|
|
||||||
|
@ -675,26 +680,48 @@ class App.Sidebar extends App.Controller
|
||||||
@toggleTabAction(name)
|
@toggleTabAction(name)
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
|
itemsLocal = []
|
||||||
|
for item in @items
|
||||||
|
itemLocal = item.sidebarItem()
|
||||||
|
if itemLocal
|
||||||
|
itemsLocal.push itemLocal
|
||||||
|
|
||||||
|
# container
|
||||||
localEl = $(App.view('generic/sidebar_tabs')(
|
localEl = $(App.view('generic/sidebar_tabs')(
|
||||||
items: @items
|
items: itemsLocal
|
||||||
scrollbarWidth: App.Utils.getScrollBarWidth()
|
scrollbarWidth: App.Utils.getScrollBarWidth()
|
||||||
dir: App.i18n.dir()
|
dir: App.i18n.dir()
|
||||||
))
|
))
|
||||||
|
|
||||||
# init content callback
|
# init sidebar badget
|
||||||
for item in @items
|
for item in itemsLocal
|
||||||
area = localEl.filter('.sidebar[data-tab="' + item.name + '"]')
|
el = localEl.find('.tabsSidebar-tab[data-tab="' + item.name + '"]')
|
||||||
if item.callback
|
if item.badgeCallback
|
||||||
item.callback( area.find('.sidebar-content') )
|
item.badgeCallback(el)
|
||||||
if item.actions
|
else
|
||||||
new App.ActionRow(
|
@badgeRender(el, item)
|
||||||
el: area.find('.js-actions')
|
|
||||||
items: item.actions
|
# init sidebar content
|
||||||
type: 'small'
|
for item in itemsLocal
|
||||||
)
|
if item.sidebarCallback
|
||||||
|
el = localEl.filter('.sidebar[data-tab="' + item.name + '"]')
|
||||||
|
item.sidebarCallback(el.find('.sidebar-content'))
|
||||||
|
if !_.isEmpty(item.sidebarActions)
|
||||||
|
new App.ActionRow(
|
||||||
|
el: el.find('.js-actions')
|
||||||
|
items: item.sidebarActions
|
||||||
|
type: 'small'
|
||||||
|
)
|
||||||
|
|
||||||
@html localEl
|
@html localEl
|
||||||
|
|
||||||
|
badgeRender: (el, item) =>
|
||||||
|
@badgeEl = el
|
||||||
|
@badgeRenderLocal(item)
|
||||||
|
|
||||||
|
badgeRenderLocal: (item) =>
|
||||||
|
@badgeEl.html(App.view('generic/sidebar_tabs_item')(icon: item.badgeIcon))
|
||||||
|
|
||||||
toggleDropdown: (e) ->
|
toggleDropdown: (e) ->
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
$(e.currentTarget).next('.js-actions').find('.dropdown-toggle').dropdown('toggle')
|
$(e.currentTarget).next('.js-actions').find('.dropdown-toggle').dropdown('toggle')
|
||||||
|
@ -1170,7 +1197,6 @@ class App.ObserverController extends App.Controller
|
||||||
if @globalRerender
|
if @globalRerender
|
||||||
@bind('ui:rerender', =>
|
@bind('ui:rerender', =>
|
||||||
@lastAttributres = undefined
|
@lastAttributres = undefined
|
||||||
console.log('aaaa', @model, @template)
|
|
||||||
@maybeRender(App[@model].fullLocal(@object_id))
|
@maybeRender(App[@model].fullLocal(@object_id))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@ class App.ControllerTable extends App.Controller
|
||||||
checkBoxColWidth: 40
|
checkBoxColWidth: 40
|
||||||
radioColWidth: 22
|
radioColWidth: 22
|
||||||
sortableColWidth: 36
|
sortableColWidth: 36
|
||||||
|
destroyColWidth: 70
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-tableHead': 'tableHead'
|
'.js-tableHead': 'tableHead'
|
||||||
|
@ -133,6 +134,8 @@ class App.ControllerTable extends App.Controller
|
||||||
customOrderDirection: undefined
|
customOrderDirection: undefined
|
||||||
customOrderBy: undefined
|
customOrderBy: undefined
|
||||||
|
|
||||||
|
frontendTimeUpdateExecute: true
|
||||||
|
|
||||||
bindCol: {}
|
bindCol: {}
|
||||||
bindRow: {}
|
bindRow: {}
|
||||||
|
|
||||||
|
@ -269,6 +272,7 @@ class App.ControllerTable extends App.Controller
|
||||||
@currentRows = newCurrentRows
|
@currentRows = newCurrentRows
|
||||||
@log 'debug', 'table.fullRender.contentRemoved', removePositions, addPositions
|
@log 'debug', 'table.fullRender.contentRemoved', removePositions, addPositions
|
||||||
@renderPager(@el, true)
|
@renderPager(@el, true)
|
||||||
|
@frontendTimeUpdateElement(@el) if @frontendTimeUpdateExecute is true
|
||||||
return ['fullRender.contentRemoved', removePositions, addPositions]
|
return ['fullRender.contentRemoved', removePositions, addPositions]
|
||||||
|
|
||||||
if newRows.length isnt @currentRows.length
|
if newRows.length isnt @currentRows.length
|
||||||
|
@ -304,6 +308,7 @@ class App.ControllerTable extends App.Controller
|
||||||
else
|
else
|
||||||
@currentRows = clone(rows)
|
@currentRows = clone(rows)
|
||||||
container.find('.js-tableBody').html(rows)
|
container.find('.js-tableBody').html(rows)
|
||||||
|
@frontendTimeUpdateElement(container) if @frontendTimeUpdateExecute is true
|
||||||
|
|
||||||
@renderPager(container)
|
@renderPager(container)
|
||||||
|
|
||||||
|
@ -506,6 +511,7 @@ class App.ControllerTable extends App.Controller
|
||||||
|
|
||||||
# get header data
|
# get header data
|
||||||
@headers = []
|
@headers = []
|
||||||
|
availableWidth = @availableWidth
|
||||||
for item in @overviewAttributes
|
for item in @overviewAttributes
|
||||||
headerFound = false
|
headerFound = false
|
||||||
for attributeName, attribute of @attributesList
|
for attributeName, attribute of @attributesList
|
||||||
|
@ -520,7 +526,7 @@ class App.ControllerTable extends App.Controller
|
||||||
# e.g. column: owner
|
# e.g. column: owner
|
||||||
headerFound = true
|
headerFound = true
|
||||||
if @headerWidth[attribute.name]
|
if @headerWidth[attribute.name]
|
||||||
attribute.displayWidth = @headerWidth[attribute.name] * @availableWidth
|
attribute.displayWidth = @headerWidth[attribute.name] * availableWidth
|
||||||
else if !attribute.width
|
else if !attribute.width
|
||||||
attribute.displayWidth = @baseColWidth
|
attribute.displayWidth = @baseColWidth
|
||||||
else
|
else
|
||||||
|
@ -529,7 +535,7 @@ class App.ControllerTable extends App.Controller
|
||||||
unit = attribute.width.match(/[px|%]+/)[0]
|
unit = attribute.width.match(/[px|%]+/)[0]
|
||||||
|
|
||||||
if unit is '%'
|
if unit is '%'
|
||||||
attribute.displayWidth = value / 100 * @el.width()
|
attribute.displayWidth = value / 100 * availableWidth
|
||||||
else
|
else
|
||||||
attribute.displayWidth = value
|
attribute.displayWidth = value
|
||||||
@headers.push attribute
|
@headers.push attribute
|
||||||
|
@ -538,7 +544,7 @@ class App.ControllerTable extends App.Controller
|
||||||
if attributeName is "#{item}_id" || attributeName is "#{item}_ids"
|
if attributeName is "#{item}_id" || attributeName is "#{item}_ids"
|
||||||
headerFound = true
|
headerFound = true
|
||||||
if @headerWidth[attribute.name]
|
if @headerWidth[attribute.name]
|
||||||
attribute.displayWidth = @headerWidth[attribute.name] * @availableWidth
|
attribute.displayWidth = @headerWidth[attribute.name] * availableWidth
|
||||||
else if !attribute.width
|
else if !attribute.width
|
||||||
attribute.displayWidth = @baseColWidth
|
attribute.displayWidth = @baseColWidth
|
||||||
else
|
else
|
||||||
|
@ -547,7 +553,7 @@ class App.ControllerTable extends App.Controller
|
||||||
unit = attribute.width.match(/[px|%]+/)[0]
|
unit = attribute.width.match(/[px|%]+/)[0]
|
||||||
|
|
||||||
if unit is '%'
|
if unit is '%'
|
||||||
attribute.displayWidth = value / 100 * @el.width()
|
attribute.displayWidth = value / 100 * availableWidth
|
||||||
else
|
else
|
||||||
attribute.displayWidth = value
|
attribute.displayWidth = value
|
||||||
@headers.push attribute
|
@headers.push attribute
|
||||||
|
@ -741,8 +747,10 @@ class App.ControllerTable extends App.Controller
|
||||||
if @availableWidth is 0
|
if @availableWidth is 0
|
||||||
@availableWidth = @minTableWidth
|
@availableWidth = @minTableWidth
|
||||||
|
|
||||||
|
availableWidth = @availableWidth
|
||||||
|
|
||||||
widths = @getHeaderWidths()
|
widths = @getHeaderWidths()
|
||||||
shrinkBy = Math.ceil (widths - @availableWidth) / @getShrinkableHeadersCount()
|
shrinkBy = Math.ceil (widths - availableWidth) / @getShrinkableHeadersCount()
|
||||||
|
|
||||||
# make all cols evenly smaller
|
# make all cols evenly smaller
|
||||||
@headers = _.map @headers, (col) =>
|
@headers = _.map @headers, (col) =>
|
||||||
|
@ -751,7 +759,8 @@ class App.ControllerTable extends App.Controller
|
||||||
return col
|
return col
|
||||||
|
|
||||||
# give left-over space from rounding to last column to get to 100%
|
# give left-over space from rounding to last column to get to 100%
|
||||||
roundingLeftOver = @availableWidth - @getHeaderWidths()
|
roundingLeftOver = availableWidth - @getHeaderWidths()
|
||||||
|
|
||||||
# but only if there is something left over (will get negative when there are too many columns for each column to stay in their min width)
|
# but only if there is something left over (will get negative when there are too many columns for each column to stay in their min width)
|
||||||
if roundingLeftOver > 0
|
if roundingLeftOver > 0
|
||||||
@headers[@headers.length - 1].displayWidth = @headers[@headers.length - 1].displayWidth + roundingLeftOver
|
@headers[@headers.length - 1].displayWidth = @headers[@headers.length - 1].displayWidth + roundingLeftOver
|
||||||
|
@ -777,6 +786,9 @@ class App.ControllerTable extends App.Controller
|
||||||
if @dndCallback
|
if @dndCallback
|
||||||
widths += @sortableColWidth
|
widths += @sortableColWidth
|
||||||
|
|
||||||
|
if @destroy
|
||||||
|
widths += @destroyColWidth
|
||||||
|
|
||||||
widths
|
widths
|
||||||
|
|
||||||
setHeaderWidths: =>
|
setHeaderWidths: =>
|
||||||
|
|
|
@ -478,7 +478,6 @@ class App.ChannelEmailEdit extends App.ControllerModal
|
||||||
class App.ChannelEmailAccountWizard extends App.WizardModal
|
class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
elements:
|
elements:
|
||||||
'.modal-body': 'body'
|
'.modal-body': 'body'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'submit .js-intro': 'probeBasedOnIntro'
|
'submit .js-intro': 'probeBasedOnIntro'
|
||||||
'submit .js-inbound': 'probeInbound'
|
'submit .js-inbound': 'probeInbound'
|
||||||
|
@ -487,6 +486,9 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
'click .js-goToSlide': 'goToSlide'
|
'click .js-goToSlide': 'goToSlide'
|
||||||
'click .js-expert': 'probeBasedOnIntro'
|
'click .js-expert': 'probeBasedOnIntro'
|
||||||
'click .js-close': 'hide'
|
'click .js-close': 'hide'
|
||||||
|
inboundPassword: ''
|
||||||
|
outboundPassword: ''
|
||||||
|
passwordPlaceholder: '{{{{{{{{{{{{SECRTE_PASSWORD}}}}}}}}}}}}'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@ -503,9 +505,17 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
|
|
||||||
if @channel
|
if @channel
|
||||||
@account =
|
@account =
|
||||||
inbound: @channel.options.inbound
|
inbound: clone(@channel.options.inbound)
|
||||||
outbound: @channel.options.outbound
|
outbound: clone(@channel.options.outbound)
|
||||||
meta: {}
|
meta: {}
|
||||||
|
|
||||||
|
# remember passwords, do not show in ui
|
||||||
|
if @account.inbound.options && @account.inbound.options.password
|
||||||
|
@inboundPassword = @account.inbound.options.password
|
||||||
|
@account.inbound.options.password = @passwordPlaceholder
|
||||||
|
if @account.outbound.options && @account.outbound.options.password
|
||||||
|
@outboundPassword = @account.outbound.options.password
|
||||||
|
@account.outbound.options.password = @passwordPlaceholder
|
||||||
|
|
||||||
if @container
|
if @container
|
||||||
@el.addClass('modal--local')
|
@el.addClass('modal--local')
|
||||||
|
@ -515,17 +525,17 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
if @channel
|
if @channel
|
||||||
@$('.js-goToSlide[data-slide=js-intro]').addClass('hidden')
|
@$('.js-goToSlide[data-slide=js-intro]').addClass('hidden')
|
||||||
|
|
||||||
@el.modal
|
@el.modal(
|
||||||
keyboard: true
|
keyboard: true
|
||||||
show: true
|
show: true
|
||||||
backdrop: true
|
backdrop: true
|
||||||
container: @container
|
container: @container
|
||||||
.on
|
).on(
|
||||||
'hidden.bs.modal': =>
|
'hidden.bs.modal': =>
|
||||||
if @callback
|
if @callback
|
||||||
@callback()
|
@callback()
|
||||||
@el.remove()
|
@el.remove()
|
||||||
|
)
|
||||||
if @slide
|
if @slide
|
||||||
@showSlide(@slide)
|
@showSlide(@slide)
|
||||||
|
|
||||||
|
@ -712,6 +722,9 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
# get params
|
# get params
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
|
||||||
|
if params.options.password is @passwordPlaceholder
|
||||||
|
params.options.password = @inboundPassword
|
||||||
|
|
||||||
# let backend know about the channel
|
# let backend know about the channel
|
||||||
if @channel
|
if @channel
|
||||||
params.channel_id = @channel.id
|
params.channel_id = @channel.id
|
||||||
|
@ -771,6 +784,9 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
params['email'] = @account['meta']['email']
|
params['email'] = @account['meta']['email']
|
||||||
|
|
||||||
|
if params.options.password is @passwordPlaceholder
|
||||||
|
params.options.password = @outboundPassword
|
||||||
|
|
||||||
if !params['email'] && @channel
|
if !params['email'] && @channel
|
||||||
email_addresses = App.EmailAddress.search(filter: { channel_id: @channel.id })
|
email_addresses = App.EmailAddress.search(filter: { channel_id: @channel.id })
|
||||||
if email_addresses && email_addresses[0]
|
if email_addresses && email_addresses[0]
|
||||||
|
@ -867,11 +883,13 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
||||||
class App.ChannelEmailNotificationWizard extends App.WizardModal
|
class App.ChannelEmailNotificationWizard extends App.WizardModal
|
||||||
elements:
|
elements:
|
||||||
'.modal-body': 'body'
|
'.modal-body': 'body'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'change .js-outbound [name=adapter]': 'toggleOutboundAdapter'
|
'change .js-outbound [name=adapter]': 'toggleOutboundAdapter'
|
||||||
'submit .js-outbound': 'probleOutbound'
|
'submit .js-outbound': 'probleOutbound'
|
||||||
'click .js-close': 'hide'
|
'click .js-close': 'hide'
|
||||||
|
inboundPassword: ''
|
||||||
|
outboundPassword: ''
|
||||||
|
passwordPlaceholder: '{{{{{{{{{{{{SECRTE_PASSWORD}}}}}}}}}}}}'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@ -888,27 +906,35 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
|
||||||
|
|
||||||
if @channel
|
if @channel
|
||||||
@account =
|
@account =
|
||||||
inbound: @channel.options.inbound
|
inbound: clone(@channel.options.inbound)
|
||||||
outbound: @channel.options.outbound
|
outbound: clone(@channel.options.outbound)
|
||||||
|
|
||||||
|
# remember passwords, do not show in ui
|
||||||
|
if @account.inbound && @account.inbound.options && @account.inbound.options.password
|
||||||
|
@inboundPassword = @account.inbound.options.password
|
||||||
|
@account.inbound.options.password = @passwordPlaceholder
|
||||||
|
if @account.outbound && @account.outbound.options && @account.outbound.options.password
|
||||||
|
@outboundPassword = @account.outbound.options.password
|
||||||
|
@account.outbound.options.password = @passwordPlaceholder
|
||||||
|
|
||||||
if @container
|
if @container
|
||||||
@el.addClass('modal--local')
|
@el.addClass('modal--local')
|
||||||
|
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
@el.modal
|
@el.modal(
|
||||||
keyboard: true
|
keyboard: true
|
||||||
show: true
|
show: true
|
||||||
backdrop: true
|
backdrop: true
|
||||||
container: @container
|
container: @container
|
||||||
.on
|
).on(
|
||||||
'show.bs.modal': @onShow
|
'show.bs.modal': @onShow
|
||||||
'shown.bs.modal': @onShown
|
'shown.bs.modal': @onShown
|
||||||
'hidden.bs.modal': =>
|
'hidden.bs.modal': =>
|
||||||
if @callback
|
if @callback
|
||||||
@callback()
|
@callback()
|
||||||
@el.remove()
|
@el.remove()
|
||||||
|
)
|
||||||
if @slide
|
if @slide
|
||||||
@showSlide(@slide)
|
@showSlide(@slide)
|
||||||
|
|
||||||
|
@ -956,6 +982,9 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
|
||||||
# get params
|
# get params
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
|
||||||
|
if params.options && params.options.password is @passwordPlaceholder
|
||||||
|
params.options.password = @outboundPassword
|
||||||
|
|
||||||
# let backend know about the channel
|
# let backend know about the channel
|
||||||
params.channel_id = @channel.id
|
params.channel_id = @channel.id
|
||||||
|
|
||||||
|
|
|
@ -67,11 +67,15 @@ class App.ChannelForm extends App.ControllerSubContent
|
||||||
# rebuild preview
|
# rebuild preview
|
||||||
params.test = true
|
params.test = true
|
||||||
if params.modal
|
if params.modal
|
||||||
|
@$('.js-modal').removeClass('hide')
|
||||||
|
@$('.js-inlineForm').addClass('hide')
|
||||||
@$('.js-formInline').addClass('hide')
|
@$('.js-formInline').addClass('hide')
|
||||||
@$('.js-formBtn').removeClass('hide')
|
@$('.js-formBtn').removeClass('hide')
|
||||||
@$('.js-formBtn').ZammadForm(params)
|
@$('.js-formBtn').ZammadForm(params)
|
||||||
@$('.js-formBtn').text('Feedback')
|
@$('.js-formBtn').text('Feedback')
|
||||||
else
|
else
|
||||||
|
@$('.js-modal').addClass('hide')
|
||||||
|
@$('.js-inlineForm').removeClass('hide')
|
||||||
@$('.js-formBtn').addClass('hide')
|
@$('.js-formBtn').addClass('hide')
|
||||||
@$('.js-formInline').removeClass('hide')
|
@$('.js-formInline').removeClass('hide')
|
||||||
@$('.js-formInline').ZammadForm(params)
|
@$('.js-formInline').ZammadForm(params)
|
||||||
|
|
|
@ -131,13 +131,12 @@ class Form extends App.Controller
|
||||||
if _.isEmpty(job)
|
if _.isEmpty(job)
|
||||||
@lastImport.html('')
|
@lastImport.html('')
|
||||||
return
|
return
|
||||||
countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped + job.result.failed
|
|
||||||
if !job.result.roles
|
if !job.result.roles
|
||||||
job.result.roles = {}
|
job.result.roles = {}
|
||||||
for role_id, statistic of job.result.role_ids
|
for role_id, statistic of job.result.role_ids
|
||||||
role = App.Role.find(role_id)
|
role = App.Role.find(role_id)
|
||||||
job.result.roles[role.displayName()] = statistic
|
job.result.roles[role.displayName()] = statistic
|
||||||
el = $(App.view('integration/exchange_last_import')(job: job, countDone: countDone))
|
el = $(App.view('integration/exchange_last_import')(job: job))
|
||||||
@lastImport.html(el)
|
@lastImport.html(el)
|
||||||
|
|
||||||
activeDryRun: =>
|
activeDryRun: =>
|
||||||
|
@ -540,34 +539,26 @@ class ConnectionWizard extends App.WizardModal
|
||||||
@showAlert('js-error', (job.result.error || job.result.info))
|
@showAlert('js-error', (job.result.error || job.result.info))
|
||||||
return
|
return
|
||||||
|
|
||||||
total = 0
|
|
||||||
if job.result && _.keys(job.result).length > 0
|
if job.result && _.keys(job.result).length > 0
|
||||||
@$('.js-preprogress').addClass('hide')
|
@$('.js-preprogress').addClass('hide')
|
||||||
@$('.js-analyzing').removeClass('hide')
|
@$('.js-analyzing').removeClass('hide')
|
||||||
|
|
||||||
analized = 0
|
@$('.js-progress progress').attr('value', job.result.sum)
|
||||||
total = job.result.sum
|
@$('.js-progress progress').attr('max', job.result.total)
|
||||||
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
|
if job.finished_at
|
||||||
# reset initial state in case the back button is used
|
# reset initial state in case the back button is used
|
||||||
@$('.js-preprogress').removeClass('hide')
|
@$('.js-preprogress').removeClass('hide')
|
||||||
@$('.js-analyzing').addClass('hide')
|
@$('.js-analyzing').addClass('hide')
|
||||||
|
|
||||||
@tryResult(job, total)
|
@tryResult(job)
|
||||||
else
|
else
|
||||||
@delay(@tryLoop, 4000)
|
@delay(@tryLoop, 4000)
|
||||||
)
|
)
|
||||||
|
|
||||||
tryResult: (job, total) =>
|
tryResult: (job) =>
|
||||||
@showSlide('js-try')
|
@showSlide('js-try')
|
||||||
el = $(App.view('integration/exchange_summary')(job: job, countDone: total))
|
el = $(App.view('integration/exchange_summary')(job: job))
|
||||||
@el.find('.js-summary').html(el)
|
@el.find('.js-summary').html(el)
|
||||||
|
|
||||||
App.Config.set(
|
App.Config.set(
|
||||||
|
|
|
@ -132,13 +132,12 @@ class Form extends App.Controller
|
||||||
if _.isEmpty(job)
|
if _.isEmpty(job)
|
||||||
@lastImport.html('')
|
@lastImport.html('')
|
||||||
return
|
return
|
||||||
countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped + job.result.failed
|
|
||||||
if !job.result.roles
|
if !job.result.roles
|
||||||
job.result.roles = {}
|
job.result.roles = {}
|
||||||
for role_id, statistic of job.result.role_ids
|
for role_id, statistic of job.result.role_ids
|
||||||
role = App.Role.find(role_id)
|
role = App.Role.find(role_id)
|
||||||
job.result.roles[role.displayName()] = statistic
|
job.result.roles[role.displayName()] = statistic
|
||||||
el = $(App.view('integration/ldap_last_import')(job: job, countDone: countDone))
|
el = $(App.view('integration/ldap_last_import')(job: job))
|
||||||
@lastImport.html(el)
|
@lastImport.html(el)
|
||||||
|
|
||||||
activeDryRun: =>
|
activeDryRun: =>
|
||||||
|
@ -419,7 +418,7 @@ class ConnectionWizard extends App.WizardModal
|
||||||
if !_.isArray(user_attributes[key])
|
if !_.isArray(user_attributes[key])
|
||||||
user_attributes[key] = [user_attributes[key]]
|
user_attributes[key] = [user_attributes[key]]
|
||||||
user_attributes_local =
|
user_attributes_local =
|
||||||
"#{@wizardConfig['user_uid']}": 'login'
|
'samaccountname': 'login'
|
||||||
length = user_attributes.source.length-1
|
length = user_attributes.source.length-1
|
||||||
for count in [0..length]
|
for count in [0..length]
|
||||||
if user_attributes.source[count] && user_attributes.dest[count]
|
if user_attributes.source[count] && user_attributes.dest[count]
|
||||||
|
@ -450,7 +449,7 @@ class ConnectionWizard extends App.WizardModal
|
||||||
buildRowsUserMap: (user_attribute_map) =>
|
buildRowsUserMap: (user_attribute_map) =>
|
||||||
|
|
||||||
# show static login row
|
# show static login row
|
||||||
userUidDisplayValue = @wizardConfig.wizardData.backend_user_attributes[ @wizardConfig['user_uid'] ]
|
userUidDisplayValue = @wizardConfig.wizardData.backend_user_attributes['samaccountname']
|
||||||
|
|
||||||
el = [
|
el = [
|
||||||
$(App.view('integration/ldap_user_attribute_row_read_only')(
|
$(App.view('integration/ldap_user_attribute_row_read_only')(
|
||||||
|
@ -459,7 +458,7 @@ class ConnectionWizard extends App.WizardModal
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
for source, dest of user_attribute_map
|
for source, dest of user_attribute_map
|
||||||
continue if source == @wizardConfig['user_uid']
|
continue if source == 'samaccountname'
|
||||||
continue if !(source of @wizardConfig.wizardData.backend_user_attributes)
|
continue if !(source of @wizardConfig.wizardData.backend_user_attributes)
|
||||||
el.push @buildRowUserAttribute(source, dest)
|
el.push @buildRowUserAttribute(source, dest)
|
||||||
el
|
el
|
||||||
|
@ -539,22 +538,12 @@ class ConnectionWizard extends App.WizardModal
|
||||||
@showAlert('js-error', (job.result.error || job.result.info))
|
@showAlert('js-error', (job.result.error || job.result.info))
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.result && job.result.sum
|
if job.result && job.result.total
|
||||||
@$('.js-preprogress').addClass('hide')
|
@$('.js-preprogress').addClass('hide')
|
||||||
@$('.js-analyzing').removeClass('hide')
|
@$('.js-analyzing').removeClass('hide')
|
||||||
total = 0
|
|
||||||
if job.result.created
|
@$('.js-progress progress').attr('value', job.result.sum)
|
||||||
total += job.result.created
|
@$('.js-progress progress').attr('max', job.result.total)
|
||||||
if job.result.failed
|
|
||||||
total += job.result.failed
|
|
||||||
if job.result.skipped
|
|
||||||
total += job.result.skipped
|
|
||||||
if job.result.unchanged
|
|
||||||
total += job.result.unchanged
|
|
||||||
if job.result.updated
|
|
||||||
total += job.result.updated
|
|
||||||
@$('.js-progress progress').attr('value', total)
|
|
||||||
@$('.js-progress progress').attr('max', job.result.sum)
|
|
||||||
if job.finished_at
|
if job.finished_at
|
||||||
# reset initial state in case the back button is used
|
# reset initial state in case the back button is used
|
||||||
@$('.js-preprogress').removeClass('hide')
|
@$('.js-preprogress').removeClass('hide')
|
||||||
|
@ -574,9 +563,8 @@ class ConnectionWizard extends App.WizardModal
|
||||||
for role_id, statistic of job.result.role_ids
|
for role_id, statistic of job.result.role_ids
|
||||||
role = App.Role.find(role_id)
|
role = App.Role.find(role_id)
|
||||||
job.result.roles[role.displayName()] = statistic
|
job.result.roles[role.displayName()] = statistic
|
||||||
countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped
|
|
||||||
@showSlide('js-try')
|
@showSlide('js-try')
|
||||||
el = $(App.view('integration/ldap_summary')(job: job, countDone: countDone))
|
el = $(App.view('integration/ldap_summary')(job: job))
|
||||||
@el.find('.js-summary').html(el)
|
@el.find('.js-summary').html(el)
|
||||||
|
|
||||||
App.Config.set(
|
App.Config.set(
|
||||||
|
|
|
@ -32,7 +32,7 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @el.find('.form-item'),
|
el: @el.find('.form-item')
|
||||||
model: { configure_attributes: @configure_attributes, className: '' }
|
model: { configure_attributes: @configure_attributes, className: '' }
|
||||||
autofocus: false
|
autofocus: false
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
class App.SettingsAreaItemDefaultLocale extends App.SettingsAreaItem
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
|
||||||
|
options = {}
|
||||||
|
locales = App.Locale.all()
|
||||||
|
for locale in locales
|
||||||
|
options[locale.locale] = locale.name
|
||||||
|
configure_attributes = [
|
||||||
|
{ name: 'locale_default', display: '', tag: 'searchable_select', null: false, class: 'input', options: options, default: @setting.state_current.value },
|
||||||
|
]
|
||||||
|
|
||||||
|
@html App.view(@template)(
|
||||||
|
setting: @setting
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.ControllerForm(
|
||||||
|
el: @el.find('.form-item')
|
||||||
|
model: { configure_attributes: configure_attributes, className: '' }
|
||||||
|
autofocus: false
|
||||||
|
)
|
|
@ -42,7 +42,7 @@ class App.SettingsAreaTicketNumber extends App.Controller
|
||||||
number = "#{App.Config.get('ticket_hook')}#{App.Config.get('system_id')}"
|
number = "#{App.Config.get('ticket_hook')}#{App.Config.get('system_id')}"
|
||||||
counter = '1'
|
counter = '1'
|
||||||
if paramsItem.min_size
|
if paramsItem.min_size
|
||||||
minSize = parseInt(paramsItem.min_size)
|
minSize = parseInt(paramsItem.min_size) - "#{App.Config.get('system_id')}".length
|
||||||
if paramsItem.checksum
|
if paramsItem.checksum
|
||||||
minSize -= 1
|
minSize -= 1
|
||||||
if minSize > 1
|
if minSize > 1
|
||||||
|
|
|
@ -6,24 +6,24 @@ class App.UiElement.column_select extends App.UiElement.ApplicationUiElement
|
||||||
attribute.multiple = 'multiple'
|
attribute.multiple = '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)
|
||||||
|
|
||||||
# find selected/checked item of list
|
# find 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.ColumnSelect( attribute: attribute ).element()
|
new App.ColumnSelect(attribute: attribute).element()
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# coffeelint: disable=camel_case_classes
|
# coffeelint: disable=camel_case_classes
|
||||||
class App.UiElement.richtext
|
class App.UiElement.richtext
|
||||||
@render: (attribute) ->
|
@render: (attribute, params) ->
|
||||||
|
item = $( App.view('generic/richtext')(attribute: attribute) )
|
||||||
item = $( App.view('generic/richtext')( attribute: attribute ) )
|
|
||||||
item.find('[contenteditable]').ce(
|
item.find('[contenteditable]').ce(
|
||||||
mode: attribute.type
|
mode: attribute.type
|
||||||
maxlength: attribute.maxlength
|
maxlength: attribute.maxlength
|
||||||
|
@ -15,42 +14,42 @@ class App.UiElement.richtext
|
||||||
new App[plugin.controller](params)
|
new App[plugin.controller](params)
|
||||||
|
|
||||||
if attribute.upload
|
if attribute.upload
|
||||||
item.append( $( App.view('generic/attachment')( attribute: attribute ) ) )
|
@attachments = []
|
||||||
|
item.append( $( App.view('generic/attachment')(attribute: attribute) ) )
|
||||||
|
|
||||||
renderAttachment = (file) =>
|
renderFile = (file) =>
|
||||||
item.find('.attachments').append( App.view('generic/attachment_item')(
|
item.find('.attachments').append(App.view('generic/attachment_item')(file))
|
||||||
fileName: file.filename
|
@attachments.push file
|
||||||
fileSize: App.Utils.humanFileSize(file.size)
|
|
||||||
store_id: file.store_id
|
|
||||||
))
|
|
||||||
item.on(
|
|
||||||
'click'
|
|
||||||
"[data-id=#{file.store_id}]", (e) =>
|
|
||||||
@attachments = _.filter(
|
|
||||||
@attachments,
|
|
||||||
(item) ->
|
|
||||||
return if item.id isnt file.store_id
|
|
||||||
item
|
|
||||||
)
|
|
||||||
store_id = $(e.currentTarget).data('id')
|
|
||||||
|
|
||||||
# delete attachment from storage
|
if params && params.attachments
|
||||||
App.Ajax.request(
|
for file in params.attachments
|
||||||
type: 'DELETE'
|
renderFile(file)
|
||||||
url: "#{App.Config.get('api_path')}/ticket_attachment_upload"
|
|
||||||
data: JSON.stringify(store_id: store_id),
|
|
||||||
processData: false
|
|
||||||
)
|
|
||||||
|
|
||||||
# remove attachment from dom
|
# remove items
|
||||||
element = $(e.currentTarget).closest('.attachments')
|
item.find('.attachments').on('click', '.js-delete', (e) =>
|
||||||
$(e.currentTarget).closest('.attachment').remove()
|
id = $(e.currentTarget).data('id')
|
||||||
# empty .attachment (remove spaces) to keep css working, thanks @mrflix :-o
|
@attachments = _.filter(
|
||||||
if element.find('.attachment').length == 0
|
@attachments,
|
||||||
element.empty()
|
(item) ->
|
||||||
|
return if item.id.toString() is id.toString()
|
||||||
|
item
|
||||||
)
|
)
|
||||||
|
|
||||||
@attachments = []
|
# delete attachment from storage
|
||||||
|
App.Ajax.request(
|
||||||
|
type: 'DELETE'
|
||||||
|
url: "#{App.Config.get('api_path')}/ticket_attachment_upload"
|
||||||
|
data: JSON.stringify(id: id),
|
||||||
|
processData: false
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove attachment from dom
|
||||||
|
element = $(e.currentTarget).closest('.attachments')
|
||||||
|
$(e.currentTarget).closest('.attachment').remove()
|
||||||
|
if element.find('.attachment').length == 0
|
||||||
|
element.empty()
|
||||||
|
)
|
||||||
|
|
||||||
@progressBar = item.find('.attachmentUpload-progressBar')
|
@progressBar = item.find('.attachmentUpload-progressBar')
|
||||||
@progressText = item.find('.js-percentage')
|
@progressText = item.find('.js-percentage')
|
||||||
@attachmentPlaceholder = item.find('.attachmentPlaceholder')
|
@attachmentPlaceholder = item.find('.attachmentPlaceholder')
|
||||||
|
@ -84,7 +83,6 @@ class App.UiElement.richtext
|
||||||
# Called after received response from the server
|
# Called after received response from the server
|
||||||
onCompleted: (response) =>
|
onCompleted: (response) =>
|
||||||
response = JSON.parse(response)
|
response = JSON.parse(response)
|
||||||
@attachments.push response.data
|
|
||||||
|
|
||||||
@attachmentPlaceholder.removeClass('hide')
|
@attachmentPlaceholder.removeClass('hide')
|
||||||
@attachmentUpload.addClass('hide')
|
@attachmentUpload.addClass('hide')
|
||||||
|
@ -93,7 +91,7 @@ class App.UiElement.richtext
|
||||||
@progressBar.width(parseInt(0) + '%')
|
@progressBar.width(parseInt(0) + '%')
|
||||||
@progressText.text('')
|
@progressText.text('')
|
||||||
|
|
||||||
renderAttachment(response.data)
|
renderFile(response.data)
|
||||||
item.find('input').val('')
|
item.find('input').val('')
|
||||||
|
|
||||||
App.Log.debug 'UiElement.richtext', 'upload complete', response.data
|
App.Log.debug 'UiElement.richtext', 'upload complete', response.data
|
||||||
|
@ -111,4 +109,5 @@ class App.UiElement.richtext
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
App.Delay.set(u, 100, undefined, 'form_upload')
|
App.Delay.set(u, 100, undefined, 'form_upload')
|
||||||
|
|
||||||
item
|
item
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# coffeelint: disable=camel_case_classes
|
||||||
|
class App.UiElement.richtext_search
|
||||||
|
@render: (attribute) ->
|
||||||
|
$( App.view('generic/input')( attribute: attribute ) )
|
|
@ -0,0 +1,35 @@
|
||||||
|
# coffeelint: disable=camel_case_classes
|
||||||
|
class App.UiElement.select_search extends App.UiElement.ApplicationUiElement
|
||||||
|
@render: (attribute, params) ->
|
||||||
|
|
||||||
|
# set multiple option
|
||||||
|
if attribute.multiple
|
||||||
|
attribute.multiple = 'multiple'
|
||||||
|
else
|
||||||
|
attribute.multiple = ''
|
||||||
|
|
||||||
|
delete attribute.filter
|
||||||
|
|
||||||
|
# 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
|
||||||
|
@selectedOptions(attribute, params)
|
||||||
|
|
||||||
|
# disable item of list
|
||||||
|
@disabledOptions(attribute, params)
|
||||||
|
|
||||||
|
# filter attributes
|
||||||
|
@filterOption(attribute, params)
|
||||||
|
|
||||||
|
# return item
|
||||||
|
$( App.view('generic/select')(attribute: attribute) )
|
|
@ -22,9 +22,12 @@ class App.UiElement.ticket_selector
|
||||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
||||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)']
|
||||||
'boolean$': ['is', 'is not']
|
'boolean$': ['is', 'is not']
|
||||||
|
'integer$': ['is', 'is not']
|
||||||
'^radio$': ['is', 'is not']
|
'^radio$': ['is', 'is not']
|
||||||
'^select$': ['is', 'is not']
|
'^select$': ['is', 'is not']
|
||||||
|
'^tree_select$': ['is', 'is not']
|
||||||
'^input$': ['contains', 'contains not']
|
'^input$': ['contains', 'contains not']
|
||||||
|
'^richtext$': ['contains', 'contains not']
|
||||||
'^textarea$': ['contains', 'contains not']
|
'^textarea$': ['contains', 'contains not']
|
||||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||||
|
|
||||||
|
@ -34,9 +37,12 @@ class App.UiElement.ticket_selector
|
||||||
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'has changed']
|
'^timestamp$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'has changed']
|
||||||
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'has changed']
|
'^date$': ['before (absolute)', 'after (absolute)', 'before (relative)', 'after (relative)', 'within next (relative)', 'within last (relative)', 'has changed']
|
||||||
'boolean$': ['is', 'is not', 'has changed']
|
'boolean$': ['is', 'is not', 'has changed']
|
||||||
|
'integer$': ['is', 'is not', 'has changed']
|
||||||
'^radio$': ['is', 'is not', 'has changed']
|
'^radio$': ['is', 'is not', 'has changed']
|
||||||
'^select$': ['is', 'is not', 'has changed']
|
'^select$': ['is', 'is not', 'has changed']
|
||||||
|
'^tree_select$': ['is', 'is not', 'has changed']
|
||||||
'^input$': ['contains', 'contains not', 'has changed']
|
'^input$': ['contains', 'contains not', 'has changed']
|
||||||
|
'^richtext$': ['contains', 'contains not', 'has changed']
|
||||||
'^textarea$': ['contains', 'contains not', 'has changed']
|
'^textarea$': ['contains', 'contains not', 'has changed']
|
||||||
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
'^tag$': ['contains all', 'contains one', 'contains all not', 'contains one not']
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ class App.TicketCreate extends App.Controller
|
||||||
# define default type
|
# define default type
|
||||||
@default_type = 'phone-in'
|
@default_type = 'phone-in'
|
||||||
|
|
||||||
|
@formId = App.ControllerForm.formId()
|
||||||
|
|
||||||
# remember split info if exists
|
# remember split info if exists
|
||||||
@split = ''
|
@split = ''
|
||||||
if @ticket_id && @article_id
|
if @ticket_id && @article_id
|
||||||
|
@ -92,6 +94,10 @@ class App.TicketCreate extends App.Controller
|
||||||
else
|
else
|
||||||
@$('[name="cc"]').closest('.form-group').addClass('hide')
|
@$('[name="cc"]').closest('.form-group').addClass('hide')
|
||||||
|
|
||||||
|
# show notice
|
||||||
|
@$('.js-note').addClass('hide')
|
||||||
|
@$(".js-note[data-type='#{type}']").removeClass('hide')
|
||||||
|
|
||||||
App.TaskManager.touch(@task_key)
|
App.TaskManager.touch(@task_key)
|
||||||
|
|
||||||
meta: =>
|
meta: =>
|
||||||
|
@ -158,7 +164,7 @@ class App.TicketCreate extends App.Controller
|
||||||
# get data / in case also ticket data for split
|
# get data / in case also ticket data for split
|
||||||
buildScreen: (params) =>
|
buildScreen: (params) =>
|
||||||
|
|
||||||
if !params.ticket_id && !params.article_id
|
if _.isEmpty(params.ticket_id) && _.isEmpty(params.article_id)
|
||||||
if !_.isEmpty(params.customer_id)
|
if !_.isEmpty(params.customer_id)
|
||||||
@render(options: { customer_id: params.customer_id })
|
@render(options: { customer_id: params.customer_id })
|
||||||
return
|
return
|
||||||
|
@ -173,6 +179,7 @@ class App.TicketCreate extends App.Controller
|
||||||
data:
|
data:
|
||||||
ticket_id: params.ticket_id
|
ticket_id: params.ticket_id
|
||||||
article_id: params.article_id
|
article_id: params.article_id
|
||||||
|
form_id: @formId
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
|
|
||||||
|
@ -194,6 +201,9 @@ class App.TicketCreate extends App.Controller
|
||||||
else
|
else
|
||||||
t.body = App.Utils.text2html(a.body)
|
t.body = App.Utils.text2html(a.body)
|
||||||
|
|
||||||
|
# add attachments
|
||||||
|
t.attachments = data.attachments
|
||||||
|
|
||||||
# render page
|
# render page
|
||||||
@render(options: t)
|
@render(options: t)
|
||||||
)
|
)
|
||||||
|
@ -201,23 +211,20 @@ class App.TicketCreate extends App.Controller
|
||||||
render: (template = {}) ->
|
render: (template = {}) ->
|
||||||
|
|
||||||
# get params
|
# get params
|
||||||
params = {}
|
params = @prefilledParams || {}
|
||||||
if template && !_.isEmpty(template.options)
|
if template && !_.isEmpty(template.options)
|
||||||
params = template.options
|
params = template.options
|
||||||
else if App.TaskManager.get(@task_key) && !_.isEmpty(App.TaskManager.get(@task_key).state)
|
else if App.TaskManager.get(@task_key) && !_.isEmpty(App.TaskManager.get(@task_key).state)
|
||||||
params = App.TaskManager.get(@task_key).state
|
params = App.TaskManager.get(@task_key).state
|
||||||
|
if !_.isEmpty(params['form_id'])
|
||||||
|
@formId = params['form_id']
|
||||||
|
|
||||||
if params['form_id']
|
@html(App.view('agent_ticket_create')(
|
||||||
@form_id = params['form_id']
|
|
||||||
else
|
|
||||||
@form_id = App.ControllerForm.formId()
|
|
||||||
|
|
||||||
@html App.view('agent_ticket_create')(
|
|
||||||
head: 'New Ticket'
|
head: 'New Ticket'
|
||||||
agent: @permissionCheck('ticket.agent')
|
agent: @permissionCheck('ticket.agent')
|
||||||
admin: @permissionCheck('admin')
|
admin: @permissionCheck('admin')
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
)
|
))
|
||||||
|
|
||||||
signatureChanges = (params, attribute, attributes, classname, form, ui) =>
|
signatureChanges = (params, attribute, attributes, classname, form, ui) =>
|
||||||
if attribute && attribute.name is 'group_id'
|
if attribute && attribute.name is 'group_id'
|
||||||
|
@ -272,7 +279,7 @@ class App.TicketCreate extends App.Controller
|
||||||
}
|
}
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @$('.ticket-form-top')
|
el: @$('.ticket-form-top')
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
model: App.Ticket
|
model: App.Ticket
|
||||||
screen: 'create_top'
|
screen: 'create_top'
|
||||||
events:
|
events:
|
||||||
|
@ -288,14 +295,14 @@ class App.TicketCreate extends App.Controller
|
||||||
|
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @$('.article-form-top')
|
el: @$('.article-form-top')
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
model: App.TicketArticle
|
model: App.TicketArticle
|
||||||
screen: 'create_top'
|
screen: 'create_top'
|
||||||
params: params
|
params: params
|
||||||
)
|
)
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @$('.ticket-form-middle')
|
el: @$('.ticket-form-middle')
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
model: App.Ticket
|
model: App.Ticket
|
||||||
screen: 'create_middle'
|
screen: 'create_middle'
|
||||||
events:
|
events:
|
||||||
|
@ -310,7 +317,7 @@ class App.TicketCreate extends App.Controller
|
||||||
)
|
)
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @$('.ticket-form-bottom')
|
el: @$('.ticket-form-bottom')
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
model: App.Ticket
|
model: App.Ticket
|
||||||
screen: 'create_bottom'
|
screen: 'create_bottom'
|
||||||
events:
|
events:
|
||||||
|
@ -420,7 +427,7 @@ class App.TicketCreate extends App.Controller
|
||||||
body: params.body
|
body: params.body
|
||||||
type_id: type.id
|
type_id: type.id
|
||||||
sender_id: sender.id
|
sender_id: sender.id
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
content_type: 'text/html'
|
content_type: 'text/html'
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -432,7 +439,7 @@ class App.TicketCreate extends App.Controller
|
||||||
body: params.body
|
body: params.body
|
||||||
type_id: type.id
|
type_id: type.id
|
||||||
sender_id: sender.id
|
sender_id: sender.id
|
||||||
form_id: @form_id
|
form_id: @formId
|
||||||
content_type: 'text/html'
|
content_type: 'text/html'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,7 @@ class App.TicketCreateSidebar extends App.Controller
|
||||||
params: @params
|
params: @params
|
||||||
query: @query
|
query: @query
|
||||||
)
|
)
|
||||||
item = @sidebarBackends[key].sidebarItem()
|
@sidebarItems.push @sidebarBackends[key]
|
||||||
if item
|
|
||||||
@sidebarItems.push item
|
|
||||||
|
|
||||||
new App.Sidebar(
|
new App.Sidebar(
|
||||||
el: @el
|
el: @el
|
||||||
|
|
|
@ -1,26 +1,62 @@
|
||||||
class SidebarCustomer extends App.Controller
|
class SidebarCustomer extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@permissionCheck('ticket.agent')
|
return if !@permissionCheck('ticket.agent')
|
||||||
return if !@params.customer_id
|
return if _.isEmpty(@params.customer_id)
|
||||||
{
|
@item = {
|
||||||
head: 'Customer'
|
name: 'customer'
|
||||||
name: 'customer'
|
badgeCallback: @badgeRender
|
||||||
icon: 'person'
|
sidebarHead: 'Customer'
|
||||||
actions: [
|
sidebarCallback: @showCustomer
|
||||||
|
sidebarActions: [
|
||||||
{
|
{
|
||||||
title: 'Edit Customer'
|
title: 'Edit Customer'
|
||||||
name: 'customer-edit'
|
name: 'customer-edit'
|
||||||
callback: @editCustomer
|
callback: @editCustomer
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
callback: @showCustomer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metaBadge: (user) =>
|
||||||
|
counter = ''
|
||||||
|
cssClass = ''
|
||||||
|
counter = @sidebarItemCounter(user)
|
||||||
|
|
||||||
|
if @Config.get('ui_sidebar_open_ticket_indicator_colored') is true
|
||||||
|
if counter == 1
|
||||||
|
cssClass = 'tabsSidebar-tab-count--warning'
|
||||||
|
if counter > 1
|
||||||
|
cssClass = 'tabsSidebar-tab-count--danger'
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'customer'
|
||||||
|
icon: 'person'
|
||||||
|
counterPossible: true
|
||||||
|
counter: counter
|
||||||
|
cssClass: cssClass
|
||||||
|
}
|
||||||
|
|
||||||
|
badgeRender: (el) =>
|
||||||
|
@badgeEl = el
|
||||||
|
if App.User.exists(@params.customer_id)
|
||||||
|
user = App.User.find(@params.customer_id)
|
||||||
|
@badgeRenderLocal(user)
|
||||||
|
|
||||||
|
badgeRenderLocal: (user) =>
|
||||||
|
@badgeEl.html(App.view('generic/sidebar_tabs_item')(@metaBadge(user)))
|
||||||
|
|
||||||
|
sidebarItemCounter: (user) ->
|
||||||
|
counter = ''
|
||||||
|
if user && user.preferences && user.preferences.tickets_open
|
||||||
|
counter = user.preferences.tickets_open
|
||||||
|
counter
|
||||||
|
|
||||||
showCustomer: (el) =>
|
showCustomer: (el) =>
|
||||||
@el = el
|
@elSidebar = el
|
||||||
|
return if _.isEmpty(@params.customer_id)
|
||||||
new App.WidgetUser(
|
new App.WidgetUser(
|
||||||
el: @el
|
el: @elSidebar
|
||||||
user_id: @params.customer_id
|
user_id: @params.customer_id
|
||||||
|
callback: @badgeRenderLocal
|
||||||
)
|
)
|
||||||
|
|
||||||
editCustomer: =>
|
editCustomer: =>
|
||||||
|
@ -32,7 +68,7 @@ class SidebarCustomer extends App.Controller
|
||||||
title: 'Users'
|
title: 'Users'
|
||||||
object: 'User'
|
object: 'User'
|
||||||
objects: 'Users'
|
objects: 'Users'
|
||||||
container: @el.closest('.content')
|
container: @elSidebar.closest('.content')
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('200-Customer', SidebarCustomer, 'TicketCreateSidebar')
|
App.Config.set('200-Customer', SidebarCustomer, 'TicketCreateSidebar')
|
||||||
|
|
|
@ -6,24 +6,25 @@ class SidebarOrganization extends App.Controller
|
||||||
customer = App.User.find(@params.customer_id)
|
customer = App.User.find(@params.customer_id)
|
||||||
@organization_id = customer.organization_id
|
@organization_id = customer.organization_id
|
||||||
return if !@organization_id
|
return if !@organization_id
|
||||||
{
|
@item = {
|
||||||
head: 'Organization'
|
|
||||||
name: 'organization'
|
name: 'organization'
|
||||||
icon: 'group'
|
badgeIcon: 'group'
|
||||||
actions: [
|
sidebarHead: 'Organization'
|
||||||
|
sidebarCallback: @showOrganization
|
||||||
|
sidebarActions: [
|
||||||
{
|
{
|
||||||
title: 'Edit Organization'
|
title: 'Edit Organization'
|
||||||
name: 'organization-edit'
|
name: 'organization-edit'
|
||||||
callback: @editOrganization
|
callback: @editOrganization
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
callback: @showOrganization
|
|
||||||
}
|
}
|
||||||
|
@item
|
||||||
|
|
||||||
showOrganization: (el) =>
|
showOrganization: (el) =>
|
||||||
@el = el
|
@elSidebar = el
|
||||||
new App.WidgetOrganization(
|
new App.WidgetOrganization(
|
||||||
el: @el
|
el: @elSidebar
|
||||||
organization_id: @organization_id
|
organization_id: @organization_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ class SidebarOrganization extends App.Controller
|
||||||
title: 'Organizations'
|
title: 'Organizations'
|
||||||
object: 'Organization'
|
object: 'Organization'
|
||||||
objects: 'Organizations'
|
objects: 'Organizations'
|
||||||
container: @el.closest('.content')
|
container: @elSidebar.closest('.content')
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('300-Organization', SidebarOrganization, 'TicketCreateSidebar')
|
App.Config.set('300-Organization', SidebarOrganization, 'TicketCreateSidebar')
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
class SidebarTemplate extends App.Controller
|
class SidebarTemplate extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@permissionCheck('ticket.agent')
|
return if !@permissionCheck('ticket.agent')
|
||||||
{
|
@item = {
|
||||||
head: 'Templates'
|
name: 'template'
|
||||||
name: 'template'
|
badgeIcon: 'templates'
|
||||||
icon: 'templates'
|
badgeCallback: @badgeRender
|
||||||
actions: []
|
sidebarHead: 'Templates'
|
||||||
callback: @showTemplates
|
sidebarActions: []
|
||||||
|
sidebarCallback: @showTemplates
|
||||||
}
|
}
|
||||||
|
@item
|
||||||
|
|
||||||
showTemplates: (el) =>
|
showTemplates: (el) =>
|
||||||
@el = el
|
@el = el
|
||||||
|
|
|
@ -35,7 +35,7 @@ class App.CustomerChat extends App.Controller
|
||||||
active_agent_ids: []
|
active_agent_ids: []
|
||||||
|
|
||||||
@render()
|
@render()
|
||||||
@on 'layout-has-changed', @propagateLayoutChange
|
@on('layout-has-changed', @propagateLayoutChange)
|
||||||
|
|
||||||
# update navbar on new status
|
# update navbar on new status
|
||||||
@bind('chat_status_agent', (data) =>
|
@bind('chat_status_agent', (data) =>
|
||||||
|
@ -163,6 +163,12 @@ class App.CustomerChat extends App.Controller
|
||||||
@title 'Customer Chat', true
|
@title 'Customer Chat', true
|
||||||
@navupdate '#customer_chat'
|
@navupdate '#customer_chat'
|
||||||
|
|
||||||
|
if params.session_id
|
||||||
|
callback = (session) =>
|
||||||
|
@addChat(session)
|
||||||
|
App.ChatSession.full(params.session_id, callback)
|
||||||
|
@navigate '#customer_chat'
|
||||||
|
|
||||||
active: (state) =>
|
active: (state) =>
|
||||||
return @shown if state is undefined
|
return @shown if state is undefined
|
||||||
@shown = state
|
@shown = state
|
||||||
|
@ -264,10 +270,11 @@ class App.CustomerChat extends App.Controller
|
||||||
|
|
||||||
addChat: (session) ->
|
addChat: (session) ->
|
||||||
return if @chatWindows[session.session_id]
|
return if @chatWindows[session.session_id]
|
||||||
chat = new ChatWindow
|
chat = new ChatWindow(
|
||||||
session: session
|
session: session
|
||||||
removeCallback: @removeChat
|
removeCallback: @removeChat
|
||||||
messageCallback: @updateNavMenu
|
messageCallback: @updateNavMenu
|
||||||
|
)
|
||||||
|
|
||||||
@workspace.append chat.el
|
@workspace.append chat.el
|
||||||
chat.render()
|
chat.render()
|
||||||
|
@ -289,7 +296,7 @@ class App.CustomerChat extends App.Controller
|
||||||
propagateLayoutChange: (event) =>
|
propagateLayoutChange: (event) =>
|
||||||
# adjust scroll position on layoutChange
|
# adjust scroll position on layoutChange
|
||||||
for session_id, chat of @chatWindows
|
for session_id, chat of @chatWindows
|
||||||
chat.trigger 'layout-changed'
|
chat.trigger('layout-changed')
|
||||||
|
|
||||||
acceptChat: =>
|
acceptChat: =>
|
||||||
return if @windowCount() >= @maxChatWindows
|
return if @windowCount() >= @maxChatWindows
|
||||||
|
@ -324,19 +331,6 @@ class App.CustomerChat extends App.Controller
|
||||||
currentPosition: =>
|
currentPosition: =>
|
||||||
@$('.main').scrollTop()
|
@$('.main').scrollTop()
|
||||||
|
|
||||||
class CustomerChatRouter extends App.ControllerPermanent
|
|
||||||
requiredPermission: 'chat.agent'
|
|
||||||
constructor: (params) ->
|
|
||||||
super
|
|
||||||
|
|
||||||
App.TaskManager.execute(
|
|
||||||
key: 'CustomerChat'
|
|
||||||
controller: 'CustomerChat'
|
|
||||||
params: {}
|
|
||||||
show: true
|
|
||||||
persistent: true
|
|
||||||
)
|
|
||||||
|
|
||||||
class ChatWindow extends App.Controller
|
class ChatWindow extends App.Controller
|
||||||
className: 'chat-window'
|
className: 'chat-window'
|
||||||
|
|
||||||
|
@ -348,6 +342,9 @@ class ChatWindow extends App.Controller
|
||||||
'click .js-close': 'close'
|
'click .js-close': 'close'
|
||||||
'click .js-disconnect': 'disconnect'
|
'click .js-disconnect': 'disconnect'
|
||||||
'click .js-scrollHint': 'onScrollHintClick'
|
'click .js-scrollHint': 'onScrollHintClick'
|
||||||
|
'click .js-info': 'toggleMeta'
|
||||||
|
'click .js-createTicket': 'ticketCreate'
|
||||||
|
'submit .js-metaForm': 'sendMetaForm'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-customerChatInput': 'input'
|
'.js-customerChatInput': 'input'
|
||||||
|
@ -355,8 +352,11 @@ class ChatWindow extends App.Controller
|
||||||
'.js-close': 'closeButton'
|
'.js-close': 'closeButton'
|
||||||
'.js-disconnect': 'disconnectButton'
|
'.js-disconnect': 'disconnectButton'
|
||||||
'.js-body': 'body'
|
'.js-body': 'body'
|
||||||
|
'.js-meta': 'meta'
|
||||||
|
'.js-name': 'metaName'
|
||||||
'.js-scrollHolder': 'scrollHolder'
|
'.js-scrollHolder': 'scrollHolder'
|
||||||
'.js-scrollHint': 'scrollHint'
|
'.js-scrollHint': 'scrollHint'
|
||||||
|
'.js-metaForm': 'metaForm'
|
||||||
|
|
||||||
sounds:
|
sounds:
|
||||||
message: new Audio('assets/sounds/chat_message.mp3')
|
message: new Audio('assets/sounds/chat_message.mp3')
|
||||||
|
@ -374,9 +374,11 @@ class ChatWindow extends App.Controller
|
||||||
@scrollSnapTolerance = 10 # pixels
|
@scrollSnapTolerance = 10 # pixels
|
||||||
|
|
||||||
@chat = App.Chat.find(@session.chat_id)
|
@chat = App.Chat.find(@session.chat_id)
|
||||||
@name = "#{@chat.displayName()} ##{@session.id}"
|
@name = @chat.displayName()
|
||||||
|
if @session && !_.isEmpty(@session.name)
|
||||||
|
@name = @session.name
|
||||||
|
|
||||||
@on 'layout-change', @onLayoutChange
|
@on('layout-change', @onLayoutChange)
|
||||||
|
|
||||||
@bind('chat_session_typing', (data) =>
|
@bind('chat_session_typing', (data) =>
|
||||||
return if data.session_id isnt @session.session_id
|
return if data.session_id isnt @session.session_id
|
||||||
|
@ -413,12 +415,45 @@ class ChatWindow extends App.Controller
|
||||||
onLayoutChange: =>
|
onLayoutChange: =>
|
||||||
@scrollToBottom()
|
@scrollToBottom()
|
||||||
|
|
||||||
render: ->
|
toggleMeta: =>
|
||||||
@html App.view('customer_chat/chat_window')
|
if @meta.hasClass('hidden')
|
||||||
name: @name
|
@showMeta()
|
||||||
|
else
|
||||||
|
@hideMeta()
|
||||||
|
|
||||||
@el.one 'transitionend', @onTransitionend
|
hideMeta: =>
|
||||||
@scrollHolder.scroll @detectScrolledtoBottom
|
@body.removeClass('hidden')
|
||||||
|
@meta.addClass('hidden')
|
||||||
|
@sendMetaForm()
|
||||||
|
|
||||||
|
showMeta: =>
|
||||||
|
@body.addClass('hidden')
|
||||||
|
@meta.removeClass('hidden')
|
||||||
|
|
||||||
|
sendMetaForm: (e) =>
|
||||||
|
if e
|
||||||
|
e.preventDefault()
|
||||||
|
params = @formParam(@metaForm)
|
||||||
|
|
||||||
|
App.WebSocket.send(
|
||||||
|
event:'chat_session_update'
|
||||||
|
data:
|
||||||
|
session_id: @session.session_id
|
||||||
|
name: params.name
|
||||||
|
tags: params.tags
|
||||||
|
)
|
||||||
|
|
||||||
|
if !_.isEmpty(params.name)
|
||||||
|
@metaName.text(params.name)
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
@html App.view('customer_chat/chat_window')(
|
||||||
|
name: @name
|
||||||
|
session: @session
|
||||||
|
)
|
||||||
|
|
||||||
|
@el.one('transitionend', @onTransitionend)
|
||||||
|
@scrollHolder.scroll(@detectScrolledtoBottom)
|
||||||
|
|
||||||
# force repaint
|
# force repaint
|
||||||
@el.prop('offsetHeight')
|
@el.prop('offsetHeight')
|
||||||
|
@ -426,18 +461,24 @@ class ChatWindow extends App.Controller
|
||||||
|
|
||||||
# @addMessage 'Hello. My name is Roger, how can I help you?', 'agent'
|
# @addMessage 'Hello. My name is Roger, how can I help you?', 'agent'
|
||||||
if @session
|
if @session
|
||||||
|
|
||||||
|
# set chat to offline if state is already closed
|
||||||
|
activeChat = true
|
||||||
|
if @session.state is 'closed'
|
||||||
|
activeChat = false
|
||||||
|
|
||||||
if @session && @session.preferences && @session.preferences.url
|
if @session && @session.preferences && @session.preferences.url
|
||||||
@addNoticeMessage(@session.preferences.url)
|
@addNoticeMessage(@session.preferences.url, undefined, activeChat)
|
||||||
|
|
||||||
if @session.messages
|
if @session.messages
|
||||||
for message in @session.messages
|
for message in @session.messages
|
||||||
if message.created_by_id
|
if message.created_by_id
|
||||||
@addMessage message.content, 'agent'
|
@addMessage(message.content, 'agent', false, activeChat)
|
||||||
else
|
else
|
||||||
@addMessage message.content, 'customer'
|
@addMessage(message.content, 'customer', false, activeChat)
|
||||||
|
|
||||||
# send init reply
|
# send init reply
|
||||||
if !@session.messages || _.isEmpty(@session.messages)
|
if activeChat && _.isEmpty(@session.messages)
|
||||||
preferences = @Session.get('preferences')
|
preferences = @Session.get('preferences')
|
||||||
if preferences.chat && preferences.chat.phrase
|
if preferences.chat && preferences.chat.phrase
|
||||||
phrases = preferences.chat.phrase[@session.chat_id]
|
phrases = preferences.chat.phrase[@session.chat_id]
|
||||||
|
@ -447,20 +488,9 @@ class ChatWindow extends App.Controller
|
||||||
@input.html(phrase)
|
@input.html(phrase)
|
||||||
@sendMessage(1600)
|
@sendMessage(1600)
|
||||||
|
|
||||||
@$('.js-info').popover(
|
# set chat to offline if state is already closed
|
||||||
trigger: 'hover'
|
if !activeChat
|
||||||
html: true
|
@goOffline()
|
||||||
animation: false
|
|
||||||
delay: 0
|
|
||||||
placement: 'bottom'
|
|
||||||
container: 'body' # place in body do prevent it from animating
|
|
||||||
title: ->
|
|
||||||
App.i18n.translateContent('Details')
|
|
||||||
content: =>
|
|
||||||
App.view('customer_chat/chat_window_info')(
|
|
||||||
session: @session
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# show text module UI
|
# show text module UI
|
||||||
new App.WidgetTextModule(
|
new App.WidgetTextModule(
|
||||||
|
@ -470,6 +500,18 @@ class ChatWindow extends App.Controller
|
||||||
config: App.Config.all()
|
config: App.Config.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
configureAttributesOutbound = [
|
||||||
|
{ name: 'name', display: 'Name', tag: 'input', null: true, },
|
||||||
|
{ name: 'tags', display: 'Tags', tag: 'tag', null: true, },
|
||||||
|
]
|
||||||
|
new App.ControllerForm(
|
||||||
|
el: @$('.js-metaForm')
|
||||||
|
model:
|
||||||
|
configure_attributes: configureAttributesOutbound
|
||||||
|
className: ''
|
||||||
|
params: @session
|
||||||
|
)
|
||||||
|
|
||||||
focus: =>
|
focus: =>
|
||||||
@input.focus()
|
@input.focus()
|
||||||
|
|
||||||
|
@ -498,7 +540,8 @@ class ChatWindow extends App.Controller
|
||||||
@goOffline()
|
@goOffline()
|
||||||
|
|
||||||
close: =>
|
close: =>
|
||||||
@el.one 'transitionend', { callback: @release }, @onTransitionend
|
@sendMetaForm()
|
||||||
|
@el.one('transitionend', { callback: @release }, @onTransitionend)
|
||||||
@el.removeClass('is-open')
|
@el.removeClass('is-open')
|
||||||
if @removeCallback
|
if @removeCallback
|
||||||
@removeCallback(@session.session_id)
|
@removeCallback(@session.session_id)
|
||||||
|
@ -577,7 +620,8 @@ class ChatWindow extends App.Controller
|
||||||
)
|
)
|
||||||
@delay(send, delay)
|
@delay(send, delay)
|
||||||
|
|
||||||
@addMessage content, 'agent'
|
@hideMeta()
|
||||||
|
@addMessage(content, 'agent')
|
||||||
@input.html('')
|
@input.html('')
|
||||||
|
|
||||||
updateModified: (state) =>
|
updateModified: (state) =>
|
||||||
|
@ -614,18 +658,19 @@ class ChatWindow extends App.Controller
|
||||||
@messageCallback(@session.session_id)
|
@messageCallback(@session.session_id)
|
||||||
@unreadMessagesCounter = 0
|
@unreadMessagesCounter = 0
|
||||||
|
|
||||||
addMessage: (message, sender, isNew) =>
|
addMessage: (message, sender, isNew, useMaybeAddTimestamp = true) =>
|
||||||
@maybeAddTimestamp()
|
@maybeAddTimestamp() if useMaybeAddTimestamp
|
||||||
|
|
||||||
@lastAddedType = sender
|
@lastAddedType = sender
|
||||||
|
|
||||||
@body.append App.view('customer_chat/chat_message')
|
@body.append App.view('customer_chat/chat_message')(
|
||||||
message: message
|
message: message
|
||||||
sender: sender
|
sender: sender
|
||||||
isNew: isNew
|
isNew: isNew
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
|
)
|
||||||
|
|
||||||
@scrollToBottom showHint: true
|
@scrollToBottom(showHint: true)
|
||||||
|
|
||||||
showWritingLoader: =>
|
showWritingLoader: =>
|
||||||
if !@isTyping
|
if !@isTyping
|
||||||
|
@ -667,33 +712,37 @@ class ChatWindow extends App.Controller
|
||||||
@lastAddedType = 'timestamp'
|
@lastAddedType = 'timestamp'
|
||||||
|
|
||||||
addTimestamp: (label, time) =>
|
addTimestamp: (label, time) =>
|
||||||
@body.append App.view('customer_chat/chat_timestamp')
|
@body.append App.view('customer_chat/chat_timestamp')(
|
||||||
label: label
|
label: label
|
||||||
time: time
|
time: time
|
||||||
|
)
|
||||||
|
|
||||||
updateLastTimestamp: (label, time) ->
|
updateLastTimestamp: (label, time) ->
|
||||||
@body
|
@body
|
||||||
.find('.js-timestamp')
|
.find('.js-timestamp')
|
||||||
.last()
|
.last()
|
||||||
.replaceWith App.view('customer_chat/chat_timestamp')
|
.replaceWith App.view('customer_chat/chat_timestamp')(
|
||||||
label: label
|
label: label
|
||||||
time: time
|
time: time
|
||||||
|
)
|
||||||
|
|
||||||
addStatusMessage: (message, args) ->
|
addStatusMessage: (message, args, useMaybeAddTimestamp = true) ->
|
||||||
@maybeAddTimestamp()
|
@maybeAddTimestamp() if useMaybeAddTimestamp
|
||||||
|
|
||||||
@body.append App.view('customer_chat/chat_status_message')
|
@body.append App.view('customer_chat/chat_status_message')(
|
||||||
message: message
|
message: message
|
||||||
args: args
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
@scrollToBottom()
|
@scrollToBottom()
|
||||||
|
|
||||||
addNoticeMessage: (message, args) ->
|
addNoticeMessage: (message, args, useMaybeAddTimestamp = true) ->
|
||||||
@maybeAddTimestamp()
|
@maybeAddTimestamp() if useMaybeAddTimestamp
|
||||||
|
|
||||||
@body.append App.view('customer_chat/chat_notice_message')
|
@body.append App.view('customer_chat/chat_notice_message')(
|
||||||
message: message
|
message: message
|
||||||
args: args
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
@scrollToBottom()
|
@scrollToBottom()
|
||||||
|
|
||||||
|
@ -717,6 +766,37 @@ class ChatWindow extends App.Controller
|
||||||
else if showHint
|
else if showHint
|
||||||
@showScrollHint()
|
@showScrollHint()
|
||||||
|
|
||||||
|
ticketCreate: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
id = Math.floor( Math.random() * 99999 )
|
||||||
|
@navigate "#ticket/create/id/#{id}"
|
||||||
|
|
||||||
|
# cleanup params
|
||||||
|
fqdn = App.Config.get('fqdn')
|
||||||
|
http_type = App.Config.get('http_type')
|
||||||
|
url = ''
|
||||||
|
session = @session
|
||||||
|
|
||||||
|
# in case we do not have a model, create one
|
||||||
|
if session && !session.uiUrl
|
||||||
|
session = new App.ChatSession(session)
|
||||||
|
if session && session.uiUrl
|
||||||
|
url = session.uiUrl()
|
||||||
|
|
||||||
|
clean_params =
|
||||||
|
id: id
|
||||||
|
prefilledParams:
|
||||||
|
body: "#{http_type}://#{fqdn}/#{url}"
|
||||||
|
title: 'Chat'
|
||||||
|
|
||||||
|
App.TaskManager.execute(
|
||||||
|
key: "TicketCreateScreen-#{id}"
|
||||||
|
controller: 'TicketCreate'
|
||||||
|
params: clean_params
|
||||||
|
show: true
|
||||||
|
)
|
||||||
|
|
||||||
class Setting extends App.ControllerModal
|
class Setting extends App.ControllerModal
|
||||||
buttonClose: true
|
buttonClose: true
|
||||||
buttonCancel: true
|
buttonCancel: true
|
||||||
|
@ -784,6 +864,24 @@ class Setting extends App.ControllerModal
|
||||||
msg: App.i18n.translateContent(data.message)
|
msg: App.i18n.translateContent(data.message)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class CustomerChatRouter extends App.ControllerPermanent
|
||||||
|
requiredPermission: 'chat.agent'
|
||||||
|
constructor: (params) ->
|
||||||
|
super
|
||||||
|
|
||||||
|
# cleanup params
|
||||||
|
clean_params =
|
||||||
|
session_id: params.session_id
|
||||||
|
|
||||||
|
App.TaskManager.execute(
|
||||||
|
key: 'CustomerChat'
|
||||||
|
controller: 'CustomerChat'
|
||||||
|
params: clean_params
|
||||||
|
show: true
|
||||||
|
persistent: true
|
||||||
|
)
|
||||||
|
|
||||||
App.Config.set('customer_chat', CustomerChatRouter, 'Routes')
|
App.Config.set('customer_chat', CustomerChatRouter, 'Routes')
|
||||||
|
App.Config.set('customer_chat/session/:session_id', CustomerChatRouter, 'Routes')
|
||||||
App.Config.set('CustomerChat', { controller: 'CustomerChat', permission: ['chat.agent'] }, 'permanentTask')
|
App.Config.set('CustomerChat', { controller: 'CustomerChat', permission: ['chat.agent'] }, 'permanentTask')
|
||||||
App.Config.set('CustomerChat', { prio: 1200, parent: '', name: 'Customer Chat', target: '#customer_chat', key: 'CustomerChat', shown: false, permission: ['chat.agent'], class: 'chat' }, 'NavBar')
|
App.Config.set('CustomerChat', { prio: 1200, parent: '', name: 'Customer Chat', target: '#customer_chat', key: 'CustomerChat', shown: false, permission: ['chat.agent'], class: 'chat' }, 'NavBar')
|
||||||
|
|
|
@ -337,11 +337,9 @@ class Base extends App.WizardFullScreen
|
||||||
@hideAlerts()
|
@hideAlerts()
|
||||||
@disable(e)
|
@disable(e)
|
||||||
|
|
||||||
# get params
|
|
||||||
@params = @formParam(e.target)
|
@params = @formParam(e.target)
|
||||||
|
|
||||||
# add logo
|
|
||||||
@params.logo = @logoPreview.attr('src')
|
@params.logo = @logoPreview.attr('src')
|
||||||
|
@params.locale_default = App.i18n.detectBrowserLocale()
|
||||||
|
|
||||||
store = (logoResizeDataUrl) =>
|
store = (logoResizeDataUrl) =>
|
||||||
@params.logo_resize = logoResizeDataUrl
|
@params.logo_resize = logoResizeDataUrl
|
||||||
|
@ -354,7 +352,7 @@ class Base extends App.WizardFullScreen
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
if data.result is 'ok'
|
if data.result is 'ok'
|
||||||
for key, value of data.settings
|
for key, value of data.settings
|
||||||
App.Config.set( key, value )
|
App.Config.set(key, value)
|
||||||
if App.Config.get('system_online_service')
|
if App.Config.get('system_online_service')
|
||||||
@navigate 'getting_started/channel/email_pre_configured'
|
@navigate 'getting_started/channel/email_pre_configured'
|
||||||
else
|
else
|
||||||
|
|
|
@ -3,6 +3,7 @@ class App.IdoitObjectSelector extends App.ControllerModal
|
||||||
buttonCancel: true
|
buttonCancel: true
|
||||||
buttonSubmit: true
|
buttonSubmit: true
|
||||||
head: 'i-doit'
|
head: 'i-doit'
|
||||||
|
lastSearchTermEmpty: false
|
||||||
|
|
||||||
content: ->
|
content: ->
|
||||||
@ajax(
|
@ajax(
|
||||||
|
@ -44,16 +45,24 @@ class App.IdoitObjectSelector extends App.ControllerModal
|
||||||
''
|
''
|
||||||
|
|
||||||
search: (filter) =>
|
search: (filter) =>
|
||||||
|
if _.isEmpty(filter.type) && _.isEmpty(filter.title)
|
||||||
|
@lastSearchTermEmpty = true
|
||||||
|
@renderResult()
|
||||||
|
return
|
||||||
|
if _.isEmpty(filter.type)
|
||||||
|
delete filter.type
|
||||||
if _.isEmpty(filter.title)
|
if _.isEmpty(filter.title)
|
||||||
delete filter.title
|
delete filter.title
|
||||||
else
|
else
|
||||||
filter.title = "%#{filter.title}%"
|
filter.title = "%#{filter.title}%"
|
||||||
|
@lastSearchTermEmpty = false
|
||||||
@ajax(
|
@ajax(
|
||||||
id: 'idoit-object-selector'
|
id: 'idoit-object-selector'
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
url: "#{@apiPath}/integration/idoit"
|
url: "#{@apiPath}/integration/idoit"
|
||||||
data: JSON.stringify(method: 'cmdb.objects', filter: filter)
|
data: JSON.stringify(method: 'cmdb.objects', filter: filter)
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
|
return if @lastSearchTermEmpty
|
||||||
@renderResult(data.response.result)
|
@renderResult(data.response.result)
|
||||||
|
|
||||||
error: (xhr, status, error) =>
|
error: (xhr, status, error) =>
|
||||||
|
|
|
@ -154,35 +154,35 @@ class Index extends App.ControllerContent
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
|
|
||||||
if data.result is 'import_done'
|
if _.isEmpty(data.result) && @updateMigrationDisplayLoop > 16
|
||||||
window.location.reload()
|
|
||||||
return
|
|
||||||
|
|
||||||
if data.result is 'error'
|
|
||||||
@$('.js-error').removeClass('hide')
|
|
||||||
@$('.js-error').html(App.i18n.translateContent(data.message))
|
|
||||||
else
|
|
||||||
@$('.js-error').addClass('hide')
|
|
||||||
|
|
||||||
if data.message is 'not running' && @updateMigrationDisplayLoop > 16
|
|
||||||
@$('.js-error').removeClass('hide')
|
@$('.js-error').removeClass('hide')
|
||||||
@$('.js-error').html(App.i18n.translateContent('Background process did not start or has not finished! Please contact your support.'))
|
@$('.js-error').html(App.i18n.translateContent('Background process did not start or has not finished! Please contact your support.'))
|
||||||
return
|
return
|
||||||
|
|
||||||
if data.result is 'in_progress'
|
if !_.isEmpty(data.result['error'])
|
||||||
for key, item of data.data
|
@$('.js-error').removeClass('hide')
|
||||||
if item.done > item.total
|
@$('.js-error').html(App.i18n.translateContent(data.result['error']))
|
||||||
item.done = item.total
|
else
|
||||||
|
@$('.js-error').addClass('hide')
|
||||||
|
|
||||||
if key == 'Ticket' && item.total >= 1000
|
if !_.isEmpty(data.finished_at) && _.isEmpty(data.result['error'])
|
||||||
|
window.location.reload()
|
||||||
|
return
|
||||||
|
|
||||||
|
if !_.isEmpty(data.result)
|
||||||
|
for model, stats of data.result
|
||||||
|
if stats.sum > stats.total
|
||||||
|
stats.sum = stats.total
|
||||||
|
|
||||||
|
if model == 'Ticket' && stats.total >= 1000
|
||||||
@ticketCountInfo.removeClass('hide')
|
@ticketCountInfo.removeClass('hide')
|
||||||
|
|
||||||
element = @$('.js-' + key.toLowerCase() )
|
element = @$('.js-' + model.toLowerCase() )
|
||||||
element.find('.js-done').text(item.done)
|
element.find('.js-done').text(stats.sum)
|
||||||
element.find('.js-total').text(item.total)
|
element.find('.js-total').text(stats.total)
|
||||||
element.find('progress').attr('max', item.total )
|
element.find('progress').attr('max', stats.total )
|
||||||
element.find('progress').attr('value', item.done )
|
element.find('progress').attr('value', stats.sum )
|
||||||
if item.total <= item.done
|
if stats.total <= stats.sum
|
||||||
element.addClass('is-done')
|
element.addClass('is-done')
|
||||||
else
|
else
|
||||||
element.removeClass('is-done')
|
element.removeClass('is-done')
|
||||||
|
|
|
@ -349,7 +349,7 @@ class LayoutRefCommunicationReply extends App.ControllerContent
|
||||||
|
|
||||||
file = @uploadQueue.shift()
|
file = @uploadQueue.shift()
|
||||||
# console.log "working of", file, "from", @uploadQueue
|
# console.log "working of", file, "from", @uploadQueue
|
||||||
@fakeUpload file.name, file.size, @workOfUploadQueue
|
@fakeUpload(file.name, file.size, @workOfUploadQueue)
|
||||||
|
|
||||||
humanFileSize: (size) ->
|
humanFileSize: (size) ->
|
||||||
i = Math.floor( Math.log(size) / Math.log(1024) )
|
i = Math.floor( Math.log(size) / Math.log(1024) )
|
||||||
|
@ -363,27 +363,27 @@ class LayoutRefCommunicationReply extends App.ControllerContent
|
||||||
@attachmentPlaceholder.removeClass('hide')
|
@attachmentPlaceholder.removeClass('hide')
|
||||||
@attachmentUpload.addClass('hide')
|
@attachmentUpload.addClass('hide')
|
||||||
|
|
||||||
fakeUpload: (fileName, fileSize, callback) ->
|
fakeUpload: (filename, size, callback) ->
|
||||||
@attachmentPlaceholder.addClass('hide')
|
@attachmentPlaceholder.addClass('hide')
|
||||||
@attachmentUpload.removeClass('hide')
|
@attachmentUpload.removeClass('hide')
|
||||||
|
|
||||||
progress = 0
|
progress = 0
|
||||||
duration = fileSize / 1024
|
duration = size / 1024
|
||||||
|
|
||||||
for i in [0..100]
|
for i in [0..100]
|
||||||
setTimeout @updateUploadProgress, i*duration/100 , i
|
setTimeout @updateUploadProgress, i*duration/100 , i
|
||||||
|
|
||||||
setTimeout (=>
|
setTimeout (=>
|
||||||
callback()
|
callback()
|
||||||
@renderAttachment(fileName, fileSize)
|
@renderAttachment(filename, size)
|
||||||
), duration
|
), duration
|
||||||
|
|
||||||
renderAttachment: (fileName, fileSize) =>
|
renderAttachment: (filename, size) =>
|
||||||
@attachments.push([fileName, fileSize])
|
@attachments.push([filename, size])
|
||||||
@attachmentsHolder.append App.view('generic/attachment_item')
|
@attachmentsHolder.append(App.view('generic/attachment_item')
|
||||||
fileName: fileName
|
filename: filename
|
||||||
fileSize: @humanFileSize(fileSize)
|
size: @humanFileSize(size)
|
||||||
|
)
|
||||||
|
|
||||||
App.Config.set( 'layout_ref/communication_reply/:content', LayoutRefCommunicationReply, 'Routes' )
|
App.Config.set( 'layout_ref/communication_reply/:content', LayoutRefCommunicationReply, 'Routes' )
|
||||||
|
|
||||||
|
@ -2121,7 +2121,7 @@ class TwitterConversationRef extends App.ControllerContent
|
||||||
open: 88
|
open: 88
|
||||||
closed: 20
|
closed: 20
|
||||||
|
|
||||||
maxTextLength: 140
|
maxTextLength: 280
|
||||||
warningTextLength: 10
|
warningTextLength: 10
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
|
|
|
@ -23,17 +23,22 @@ class Index extends App.ControllerSubContent
|
||||||
]
|
]
|
||||||
container: @el.closest('.content')
|
container: @el.closest('.content')
|
||||||
large: true
|
large: true
|
||||||
dndCallback: =>
|
dndCallback: (e, item) =>
|
||||||
items = @el.find('table > tbody > tr')
|
items = @el.find('table > tbody > tr')
|
||||||
order = []
|
prios = []
|
||||||
prio = 0
|
prio = 0
|
||||||
for item in items
|
for item in items
|
||||||
prio += 1
|
prio += 1
|
||||||
id = $(item).data('id')
|
id = $(item).data('id')
|
||||||
overview = App.Overview.find(id)
|
prios.push [id, prio]
|
||||||
if overview.prio isnt prio
|
|
||||||
overview.prio = prio
|
@ajax(
|
||||||
overview.save()
|
id: 'overview_prio'
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/overviews_prio"
|
||||||
|
processData: true
|
||||||
|
data: JSON.stringify(prios: prios)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, permission: ['admin.overview'] }, 'NavBarAdmin')
|
App.Config.set('Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, permission: ['admin.overview'] }, 'NavBarAdmin')
|
||||||
|
|
|
@ -108,33 +108,40 @@ class Graph extends App.ControllerContent
|
||||||
|
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: =>
|
update: (data) =>
|
||||||
|
|
||||||
update = (data) =>
|
# show only selected lines
|
||||||
|
dataNew = {}
|
||||||
|
for key, value of data.data
|
||||||
|
if @params.backendSelected[key] is true
|
||||||
|
dataNew[key] = value
|
||||||
|
@ui.storeParams()
|
||||||
|
|
||||||
# show only selected lines
|
if !@lastNewData
|
||||||
dataNew = {}
|
@lastNewData = {}
|
||||||
for key, value of data.data
|
|
||||||
if @params.backendSelected[key] is true
|
|
||||||
dataNew[key] = value
|
|
||||||
@ui.storeParams()
|
|
||||||
|
|
||||||
if !@lastNewData
|
return if @lastNewData && JSON.stringify(dataNew) is JSON.stringify(@lastNewData)
|
||||||
@lastNewData = {}
|
@lastNewData = dataNew
|
||||||
|
|
||||||
return if @lastNewData && JSON.stringify(dataNew) is JSON.stringify(@lastNewData)
|
@draw(dataNew)
|
||||||
@lastNewData = dataNew
|
t = new Date
|
||||||
|
@el.find('#download-chart').html(t.toString())
|
||||||
@draw(dataNew)
|
if @downloadWidget
|
||||||
t = new Date
|
@downloadWidget.update(
|
||||||
@el.find('#download-chart').html(t.toString())
|
config: @config
|
||||||
new Download(
|
params: @params
|
||||||
|
ui: @ui
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@downloadWidget = new Download(
|
||||||
el: @el.find('.js-dataDownload')
|
el: @el.find('.js-dataDownload')
|
||||||
config: @config
|
config: @config
|
||||||
params: @params
|
params: @params
|
||||||
ui: @ui
|
ui: @ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
|
||||||
url = "#{@apiPath}/reports/generate"
|
url = "#{@apiPath}/reports/generate"
|
||||||
interval = 5 * 60000
|
interval = 5 * 60000
|
||||||
if @params.timeRange is 'year'
|
if @params.timeRange is 'year'
|
||||||
|
@ -142,9 +149,9 @@ class Graph extends App.ControllerContent
|
||||||
if @params.timeRange is 'month'
|
if @params.timeRange is 'month'
|
||||||
interval = 60000
|
interval = 60000
|
||||||
if @params.timeRange is 'week'
|
if @params.timeRange is 'week'
|
||||||
interval = 40000
|
interval = 50000
|
||||||
if @params.timeRange is 'day'
|
if @params.timeRange is 'day'
|
||||||
interval = 20000
|
interval = 30000
|
||||||
if @params.timeRange is 'realtime'
|
if @params.timeRange is 'realtime'
|
||||||
interval = 10000
|
interval = 10000
|
||||||
|
|
||||||
|
@ -164,7 +171,7 @@ class Graph extends App.ControllerContent
|
||||||
)
|
)
|
||||||
processData: true
|
processData: true
|
||||||
success: (data) =>
|
success: (data) =>
|
||||||
update(data)
|
@update(data)
|
||||||
@delay(@render, interval, 'report-update', 'page')
|
@delay(@render, interval, 'report-update', 'page')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -215,7 +222,7 @@ class Graph extends App.ControllerContent
|
||||||
|
|
||||||
class Download extends App.Controller
|
class Download extends App.Controller
|
||||||
events:
|
events:
|
||||||
'click .js-dataDownloadBackendSelector': 'tableUpdate'
|
'click .js-dataDownloadBackendSelector': 'selectBackend'
|
||||||
|
|
||||||
constructor: (data) ->
|
constructor: (data) ->
|
||||||
|
|
||||||
|
@ -225,7 +232,24 @@ class Download extends App.Controller
|
||||||
super
|
super
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: ->
|
selectBackend: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
@el.find('.js-dataDownloadBackendSelector').parent().removeClass('active')
|
||||||
|
$(e.target).parent().addClass('active')
|
||||||
|
@profileSelectedId = $(e.target).data('profile-id')
|
||||||
|
@params.downloadBackendSelected = $(e.target).data('backend')
|
||||||
|
@ui.storeParams()
|
||||||
|
@table = false
|
||||||
|
@render()
|
||||||
|
|
||||||
|
update: =>
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
|
||||||
|
if !@contentRendered
|
||||||
|
@contentRendered = true
|
||||||
|
@html(App.view('report/download_content')())
|
||||||
|
|
||||||
reports = []
|
reports = []
|
||||||
|
|
||||||
|
@ -244,44 +268,84 @@ class Download extends App.Controller
|
||||||
@profileSelectedId = key
|
@profileSelectedId = key
|
||||||
profiles.push App.ReportProfile.find(key)
|
profiles.push App.ReportProfile.find(key)
|
||||||
|
|
||||||
@html App.view('report/download_header')(
|
downloadHeaderHtml = App.view('report/download_header')(
|
||||||
reports: reports
|
reports: reports
|
||||||
profiles: profiles
|
profiles: profiles
|
||||||
downloadBackendSelected: @params.downloadBackendSelected
|
downloadBackendSelected: @params.downloadBackendSelected
|
||||||
metric: @config.metric[@params.metric]
|
metric: @config.metric[@params.metric]
|
||||||
)
|
)
|
||||||
|
if downloadHeaderHtml isnt @downloadHeaderHtml
|
||||||
|
@el.find('.js-dataDownloadHeader').html(downloadHeaderHtml)
|
||||||
|
@downloadHeaderHtml = downloadHeaderHtml
|
||||||
|
|
||||||
@tableUpdate()
|
@tableUpdate()
|
||||||
|
|
||||||
tableUpdate: (e) =>
|
tableRender: (tickets, count) =>
|
||||||
if e
|
if _.isEmpty(tickets)
|
||||||
e.preventDefault()
|
@$('.js-dataDownloadButton').html('')
|
||||||
@el.find('.js-dataDownloadBackendSelector').parent().removeClass('active')
|
@$('.js-dataDownloadTable').html('')
|
||||||
$(e.target).parent().addClass('active')
|
return
|
||||||
@profileSelectedId = $(e.target).data('profile-id')
|
|
||||||
@params.downloadBackendSelected = $(e.target).data('backend')
|
|
||||||
@ui.storeParams()
|
|
||||||
|
|
||||||
table = (tickets, count) =>
|
profile_id = 0
|
||||||
url = '#ticket/zoom/'
|
for key, value of @params.profileSelected
|
||||||
if App.Config.get('import_mode')
|
if value
|
||||||
url = App.Config.get('import_otrs_endpoint') + '/index.pl?Action=AgentTicketZoom;TicketID='
|
profile_id = key
|
||||||
if _.isEmpty(tickets)
|
downloadUrl = "#{@apiPath}/reports/sets?sheet=true;metric=#{@params.metric};year=#{@params.year};month=#{@params.month};week=#{@params.week};day=#{@params.day};timeRange=#{@params.timeRange};profile_id=#{profile_id};downloadBackendSelected=#{@params.downloadBackendSelected}"
|
||||||
@el.find('.js-dataDownloadTable').html('')
|
@$('.js-dataDownloadButton').html(App.view('report/download_button')(
|
||||||
else
|
count: count
|
||||||
profile_id = 0
|
downloadUrl: downloadUrl
|
||||||
for key, value of @params.profileSelected
|
))
|
||||||
if value
|
|
||||||
profile_id = key
|
|
||||||
downloadUrl = "#{@apiPath}/reports/sets?sheet=true;metric=#{@params.metric};year=#{@params.year};month=#{@params.month};week=#{@params.week};day=#{@params.day};timeRange=#{@params.timeRange};profile_id=#{profile_id};downloadBackendSelected=#{@params.downloadBackendSelected}"
|
|
||||||
html = App.view('report/download_list')(
|
|
||||||
tickets: tickets
|
|
||||||
count: count
|
|
||||||
url: url
|
|
||||||
download: downloadUrl
|
|
||||||
)
|
|
||||||
@el.find('.js-dataDownloadTable').html(html)
|
|
||||||
|
|
||||||
|
openTicket = (id,e) =>
|
||||||
|
ticket = App.Ticket.findNative(id)
|
||||||
|
@navigate ticket.uiUrl()
|
||||||
|
callbackTicketTitleAdd = (value, object, attribute, attributes) ->
|
||||||
|
attribute.title = object.title
|
||||||
|
value
|
||||||
|
callbackLinkToTicket = (value, object, attribute, attributes) ->
|
||||||
|
attribute.link = object.uiUrl()
|
||||||
|
value
|
||||||
|
callbackIconHeader = (headers) ->
|
||||||
|
attribute =
|
||||||
|
name: 'icon'
|
||||||
|
display: ''
|
||||||
|
translation: false
|
||||||
|
width: '28px'
|
||||||
|
displayWidth:28
|
||||||
|
unresizable: true
|
||||||
|
headers.unshift(0)
|
||||||
|
headers[0] = attribute
|
||||||
|
headers
|
||||||
|
callbackIcon = (value, object, attribute, header) ->
|
||||||
|
value = ' '
|
||||||
|
attribute.class = object.iconClass()
|
||||||
|
attribute.link = ''
|
||||||
|
attribute.title = object.iconTitle()
|
||||||
|
value
|
||||||
|
|
||||||
|
params =
|
||||||
|
el: @el.find('.js-dataDownloadTable')
|
||||||
|
model: App.Ticket
|
||||||
|
objects: tickets
|
||||||
|
overviewAttributes: ['number', 'title', 'state', 'group', 'created_at']
|
||||||
|
bindRow:
|
||||||
|
events:
|
||||||
|
'click': openTicket
|
||||||
|
callbackHeader: [ callbackIconHeader ]
|
||||||
|
callbackAttributes:
|
||||||
|
icon:
|
||||||
|
[ callbackIcon ]
|
||||||
|
title:
|
||||||
|
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||||
|
number:
|
||||||
|
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||||
|
|
||||||
|
if !@table
|
||||||
|
@table = new App.ControllerTable(params)
|
||||||
|
else
|
||||||
|
@table.update(objects: tickets)
|
||||||
|
|
||||||
|
tableUpdate: =>
|
||||||
@ajax(
|
@ajax(
|
||||||
id: 'report_download'
|
id: 'report_download'
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
|
@ -298,15 +362,14 @@ class Download extends App.Controller
|
||||||
downloadBackendSelected: @params.downloadBackendSelected
|
downloadBackendSelected: @params.downloadBackendSelected
|
||||||
)
|
)
|
||||||
processData: true
|
processData: true
|
||||||
success: (data) ->
|
success: (data) =>
|
||||||
App.Collection.loadAssets(data.assets)
|
App.Collection.loadAssets(data.assets)
|
||||||
ticket_collection = []
|
ticket_collection = []
|
||||||
if data.ticket_ids
|
if data.ticket_ids
|
||||||
for record_id in data.ticket_ids
|
for record_id in data.ticket_ids
|
||||||
ticket = App.Ticket.fullLocal( record_id )
|
ticket = App.Ticket.fullLocal(record_id)
|
||||||
ticket_collection.push ticket
|
ticket_collection.push ticket
|
||||||
|
@tableRender(ticket_collection, data.count)
|
||||||
table(ticket_collection, data.count)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class TimeRangePicker extends App.Controller
|
class TimeRangePicker extends App.Controller
|
||||||
|
|
|
@ -79,6 +79,7 @@ class App.Search extends App.Controller
|
||||||
|
|
||||||
@tabs = []
|
@tabs = []
|
||||||
for model in App.Config.get('models_searchable')
|
for model in App.Config.get('models_searchable')
|
||||||
|
model = model.replace(/::/, '')
|
||||||
tab =
|
tab =
|
||||||
name: model
|
name: model
|
||||||
model: model
|
model: model
|
||||||
|
|
|
@ -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, disableCreateObject: true },
|
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: false },
|
||||||
]
|
]
|
||||||
controller = new App.ControllerForm(
|
controller = new App.ControllerForm(
|
||||||
model:
|
model:
|
||||||
|
@ -18,8 +18,19 @@ class App.TicketCustomer extends App.ControllerModal
|
||||||
onSubmit: (e) =>
|
onSubmit: (e) =>
|
||||||
params = @formParam(e.target)
|
params = @formParam(e.target)
|
||||||
|
|
||||||
@customer_id = params['customer_id']
|
ticket = App.Ticket.find(@ticket_id)
|
||||||
|
ticket.customer_id = params['customer_id']
|
||||||
|
errors = ticket.validate()
|
||||||
|
|
||||||
|
if !_.isEmpty(errors)
|
||||||
|
@log 'error', errors
|
||||||
|
@formValidate(
|
||||||
|
form: e.target
|
||||||
|
errors: errors
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@customer_id = params['customer_id']
|
||||||
callback = =>
|
callback = =>
|
||||||
|
|
||||||
# close modal
|
# close modal
|
||||||
|
|
|
@ -956,7 +956,6 @@ class Table extends App.Controller
|
||||||
ticketListShow = []
|
ticketListShow = []
|
||||||
for ticket in tickets
|
for ticket in tickets
|
||||||
ticketListShow.push App.Ticket.find(ticket.id)
|
ticketListShow.push App.Ticket.find(ticket.id)
|
||||||
console.log('overview', overview)
|
|
||||||
@overview = App.Overview.find(overview.id)
|
@overview = App.Overview.find(overview.id)
|
||||||
@table.update(
|
@table.update(
|
||||||
overviewAttributes: @overview.view.s
|
overviewAttributes: @overview.view.s
|
||||||
|
|
|
@ -461,6 +461,7 @@ class App.TicketZoom extends App.Controller
|
||||||
ui: @
|
ui: @
|
||||||
highligher: @highligher
|
highligher: @highligher
|
||||||
ticket_article_ids: @ticket_article_ids
|
ticket_article_ids: @ticket_article_ids
|
||||||
|
form_id: @form_id
|
||||||
)
|
)
|
||||||
|
|
||||||
new App.TicketCustomerAvatar(
|
new App.TicketCustomerAvatar(
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
class Delete
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
if article.type.name is 'note'
|
||||||
|
user = undefined
|
||||||
|
if App.Session.get('id') == article.created_by_id
|
||||||
|
user = App.User.find(App.Session.get('id'))
|
||||||
|
if user.permission('ticket.agent')
|
||||||
|
actions.push {
|
||||||
|
name: 'delete'
|
||||||
|
type: 'delete'
|
||||||
|
icon: 'trash'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'delete'
|
||||||
|
|
||||||
|
callback = ->
|
||||||
|
article = App.TicketArticle.find(article.id)
|
||||||
|
article.destroy()
|
||||||
|
|
||||||
|
new App.ControllerConfirm(
|
||||||
|
message: 'Sure?'
|
||||||
|
callback: callback
|
||||||
|
container: ui.el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('900-Delete', Delete, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,203 @@
|
||||||
|
class EmailReply extends App.Controller
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
group = ticket.group
|
||||||
|
if group.email_address_id && (article.type.name is 'email' || article.type.name is 'web')
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'emailReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
recipients = []
|
||||||
|
if article.sender.name is 'Customer'
|
||||||
|
if article.from
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.from)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
if article.to
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.to)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
if article.cc
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.cc)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
|
||||||
|
# remove system addresses
|
||||||
|
localAddresses = App.EmailAddress.all()
|
||||||
|
forgeinRecipients = []
|
||||||
|
recipientUsed = {}
|
||||||
|
for recipient in recipients
|
||||||
|
if !_.isEmpty(recipient.address)
|
||||||
|
localRecipientAddress = recipient.address.toString().toLowerCase()
|
||||||
|
if !recipientUsed[localRecipientAddress]
|
||||||
|
recipientUsed[localRecipientAddress] = true
|
||||||
|
localAddress = false
|
||||||
|
for address in localAddresses
|
||||||
|
if localRecipientAddress is address.email.toString().toLowerCase()
|
||||||
|
recipientUsed[localRecipientAddress] = true
|
||||||
|
localAddress = true
|
||||||
|
if !localAddress
|
||||||
|
forgeinRecipients.push recipient
|
||||||
|
|
||||||
|
# check if reply all is neede
|
||||||
|
if forgeinRecipients.length > 1
|
||||||
|
actions.push {
|
||||||
|
name: 'reply all'
|
||||||
|
type: 'emailReplyAll'
|
||||||
|
icon: 'reply-all'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push {
|
||||||
|
name: 'forward'
|
||||||
|
type: 'emailForward'
|
||||||
|
icon: 'forward'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
if article.sender.name is 'Customer' && article.type.name is 'phone'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'emailReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
actions.push {
|
||||||
|
name: 'forward'
|
||||||
|
type: 'emailForward'
|
||||||
|
icon: 'forward'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
if article.sender.name is 'Agent' && article.type.name is 'phone'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'emailReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
actions.push {
|
||||||
|
name: 'forward'
|
||||||
|
type: 'emailForward'
|
||||||
|
icon: 'forward'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'emailReply' && type isnt 'emailReplyAll' && type isnt 'emailForward'
|
||||||
|
|
||||||
|
if type is 'emailReply'
|
||||||
|
@emailReply(false, ticket, article, ui)
|
||||||
|
|
||||||
|
else if type is 'emailReplyAll'
|
||||||
|
@emailReply(true, ticket, article, ui)
|
||||||
|
|
||||||
|
else if type is 'emailForward'
|
||||||
|
@emailForward(ticket, article, ui)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
@emailReply: (all = false, ticket, article, ui) ->
|
||||||
|
|
||||||
|
# get reference article
|
||||||
|
type = App.TicketArticleType.find(article.type_id)
|
||||||
|
article_created_by = App.User.find(article.created_by_id)
|
||||||
|
email_addresses = App.EmailAddress.all()
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
# empty form
|
||||||
|
articleNew = App.Utils.getRecipientArticle(ticket, article, article_created_by, type, email_addresses, all)
|
||||||
|
|
||||||
|
# get current body
|
||||||
|
body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
|
||||||
|
|
||||||
|
# check if quote need to be added
|
||||||
|
signaturePosition = 'bottom'
|
||||||
|
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)
|
||||||
|
|
||||||
|
# full quote, if needed
|
||||||
|
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)
|
||||||
|
|
||||||
|
if selected
|
||||||
|
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
|
||||||
|
|
||||||
|
# add selected text to body
|
||||||
|
body = selected + body
|
||||||
|
|
||||||
|
articleNew.body = body
|
||||||
|
|
||||||
|
type = App.TicketArticleType.findByAttribute(name:'email')
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
signaturePosition: signaturePosition
|
||||||
|
})
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
@emailForward: (ticket, article, ui) ->
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
signaturePosition = 'top'
|
||||||
|
body = ''
|
||||||
|
if article.content_type.match('html')
|
||||||
|
body = App.Utils.textCleanup(article.body)
|
||||||
|
if article.content_type.match('plain')
|
||||||
|
body = App.Utils.textCleanup(article.body)
|
||||||
|
body = App.Utils.text2html(body)
|
||||||
|
|
||||||
|
body = "<br/><div>---Begin forwarded message:---<br/><br/></div><div><blockquote type=\"cite\">#{body}</blockquote></div><div><br></div>"
|
||||||
|
|
||||||
|
articleNew = {}
|
||||||
|
articleNew.body = body
|
||||||
|
|
||||||
|
type = App.TicketArticleType.findByAttribute(name:'email')
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
signaturePosition: signaturePosition
|
||||||
|
})
|
||||||
|
|
||||||
|
# add attachments to form
|
||||||
|
App.Ajax.request(
|
||||||
|
id: "ticket_attachment_clone#{ui.form_id}"
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{App.Config.get('api_path')}/ticket_attachment_upload_clone_by_article/#{article.id}"
|
||||||
|
data: JSON.stringify(form_id: ui.form_id)
|
||||||
|
processData: true
|
||||||
|
success: (data, status, xhr) ->
|
||||||
|
return if _.isEmpty(data.attachments)
|
||||||
|
App.Event.trigger('ui::ticket::addArticleAttachent', {
|
||||||
|
ticket: ticket
|
||||||
|
article: article
|
||||||
|
attachments: data.attachments
|
||||||
|
form_id: ui.form_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('200-EmailReply', EmailReply, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,37 @@
|
||||||
|
class FacebookReply
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
if article.type.name is 'facebook feed post' || article.type.name is 'facebook feed comment'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'facebookFeedReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'facebookFeedReply'
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
type = App.TicketArticleType.findByAttribute('name', 'facebook feed comment')
|
||||||
|
|
||||||
|
articleNew = {
|
||||||
|
to: ''
|
||||||
|
cc: ''
|
||||||
|
body: ''
|
||||||
|
in_reply_to: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
})
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('300-FacebookReply', FacebookReply, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,40 @@
|
||||||
|
class Internal
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
if article.internal is true
|
||||||
|
actions.push {
|
||||||
|
name: 'set to public'
|
||||||
|
type: 'public'
|
||||||
|
icon: 'lock-open'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
actions.push {
|
||||||
|
name: 'set to internal'
|
||||||
|
type: 'internal'
|
||||||
|
icon: 'lock'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'internal' && type isnt 'public'
|
||||||
|
|
||||||
|
# storage update
|
||||||
|
internal = true
|
||||||
|
if article.internal == true
|
||||||
|
internal = false
|
||||||
|
ui.lastAttributres.internal = internal
|
||||||
|
article.updateAttributes(internal: internal)
|
||||||
|
|
||||||
|
# runtime update
|
||||||
|
if internal
|
||||||
|
articleContainer.addClass('is-internal')
|
||||||
|
else
|
||||||
|
articleContainer.removeClass('is-internal')
|
||||||
|
|
||||||
|
ui.render()
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('100-Internal', Internal, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,18 @@
|
||||||
|
class Split
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
actions.push {
|
||||||
|
name: 'split'
|
||||||
|
type: 'split'
|
||||||
|
icon: 'split'
|
||||||
|
href: "#ticket/create/#{article.ticket_id}/#{article.id}"
|
||||||
|
}
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'split'
|
||||||
|
ui.navigate "#ticket/create/#{article.ticket_id}/#{article.id}"
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('700-Split', Split, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,45 @@
|
||||||
|
class TelegramReply
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
if article.sender.name is 'Customer' && article.type.name is 'telegram personal-message'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'telegramPersonalMessageReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'telegramPersonalMessageReply'
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
# get reference article
|
||||||
|
type = App.TicketArticleType.find(article.type_id)
|
||||||
|
|
||||||
|
articleNew = {
|
||||||
|
to: ''
|
||||||
|
cc: ''
|
||||||
|
body: ''
|
||||||
|
in_reply_to: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if article.message_id
|
||||||
|
articleNew.in_reply_to = article.message_id
|
||||||
|
|
||||||
|
# get current body
|
||||||
|
articleNew.body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
position: 'end'
|
||||||
|
})
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
App.Config.set('300-TelegramReply', TelegramReply, 'TicketZoomArticleAction')
|
|
@ -0,0 +1,128 @@
|
||||||
|
class TwitterReply
|
||||||
|
@action: (actions, ticket, article, ui) ->
|
||||||
|
return actions if ui.permissionCheck('ticket.customer')
|
||||||
|
|
||||||
|
if article.type.name is 'twitter status'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'twitterStatusReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
if article.type.name is 'twitter direct-message'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'twitterDirectMessageReply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
|
||||||
|
@perform: (articleContainer, type, ticket, article, ui) ->
|
||||||
|
return true if type isnt 'twitterStatusReply' && type isnt 'twitterDirectMessageReply'
|
||||||
|
|
||||||
|
if type is 'twitterStatusReply'
|
||||||
|
@twitterStatusReply(ticket, article, ui)
|
||||||
|
|
||||||
|
else if type is 'twitterDirectMessageReply'
|
||||||
|
@twitterDirectMessageReply(ticket, article, ui)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
@twitterStatusReply: (ticket, article, ui) ->
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
# get reference article
|
||||||
|
type = App.TicketArticleType.find(article.type_id)
|
||||||
|
|
||||||
|
# empty form
|
||||||
|
articleNew = {
|
||||||
|
to: ''
|
||||||
|
cc: ''
|
||||||
|
body: ''
|
||||||
|
in_reply_to: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if article.message_id
|
||||||
|
articleNew.in_reply_to = article.message_id
|
||||||
|
|
||||||
|
# get current body
|
||||||
|
body = ui.el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
|
||||||
|
articleNew.body = body
|
||||||
|
|
||||||
|
recipients = article.from
|
||||||
|
if article.to
|
||||||
|
if recipients
|
||||||
|
recipients += ', '
|
||||||
|
recipients += article.to
|
||||||
|
|
||||||
|
if recipients
|
||||||
|
recipientString = ''
|
||||||
|
recipientScreenNames = recipients.split(',')
|
||||||
|
for recipientScreenName in recipientScreenNames
|
||||||
|
if recipientScreenName
|
||||||
|
recipientScreenName = recipientScreenName.trim().toLowerCase()
|
||||||
|
|
||||||
|
# exclude already listed screen name
|
||||||
|
exclude = false
|
||||||
|
if body && body.toLowerCase().match(recipientScreenName)
|
||||||
|
exclude = true
|
||||||
|
|
||||||
|
# exclude own screen_name
|
||||||
|
if recipientScreenName is "@#{ticket.preferences.channel_screen_name}".toLowerCase()
|
||||||
|
exclude = true
|
||||||
|
|
||||||
|
if exclude is false
|
||||||
|
if recipientString isnt ''
|
||||||
|
recipientString += ' '
|
||||||
|
recipientString += recipientScreenName
|
||||||
|
|
||||||
|
if body
|
||||||
|
articleNew.body = "#{recipientString} #{body} "
|
||||||
|
else
|
||||||
|
articleNew.body = "#{recipientString} "
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
position: 'end'
|
||||||
|
})
|
||||||
|
|
||||||
|
@twitterDirectMessageReply: (ticket, article, ui) ->
|
||||||
|
|
||||||
|
# get reference article
|
||||||
|
type = App.TicketArticleType.find(article.type_id)
|
||||||
|
sender = App.TicketArticleSender.find(article.sender_id)
|
||||||
|
customer = App.User.find(article.created_by_id)
|
||||||
|
|
||||||
|
ui.scrollToCompose()
|
||||||
|
|
||||||
|
# empty form
|
||||||
|
articleNew = {
|
||||||
|
to: ''
|
||||||
|
cc: ''
|
||||||
|
body: ''
|
||||||
|
in_reply_to: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if article.message_id
|
||||||
|
articleNew.in_reply_to = article.message_id
|
||||||
|
|
||||||
|
if sender.name is 'Agent'
|
||||||
|
articleNew.to = article.to
|
||||||
|
else
|
||||||
|
articleNew.to = article.from
|
||||||
|
|
||||||
|
if !articleNew.to
|
||||||
|
articleNew.to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', {
|
||||||
|
ticket: ticket
|
||||||
|
type: type
|
||||||
|
article: articleNew
|
||||||
|
})
|
||||||
|
|
||||||
|
App.Config.set('300-TwitterReply', TwitterReply, 'TicketZoomArticleAction')
|
|
@ -1,21 +1,13 @@
|
||||||
class App.TicketZoomArticleActions extends App.Controller
|
class App.TicketZoomArticleActions extends App.Controller
|
||||||
events:
|
events:
|
||||||
'click [data-type=public]': 'publicInternal'
|
'click .js-ArticleAction': 'actionPerform'
|
||||||
'click [data-type=internal]': 'publicInternal'
|
|
||||||
'click [data-type=emailReply]': 'emailReply'
|
|
||||||
'click [data-type=emailReplyAll]': 'emailReplyAll'
|
|
||||||
'click [data-type=twitterStatusReply]': 'twitterStatusReply'
|
|
||||||
'click [data-type=twitterDirectMessageReply]': 'twitterDirectMessageReply'
|
|
||||||
'click [data-type=facebookFeedReply]': 'facebookFeedReply'
|
|
||||||
'click [data-type=telegramPersonalMessageReply]': 'telegramPersonalMessageReply'
|
|
||||||
'click [data-type=delete]': 'delete'
|
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
actions = @actionRow(@article)
|
actions = @actionRow(@ticket, @article)
|
||||||
|
|
||||||
if actions
|
if actions
|
||||||
@html App.view('ticket_zoom/article_view_actions')(
|
@html App.view('ticket_zoom/article_view_actions')(
|
||||||
|
@ -25,371 +17,31 @@ class App.TicketZoomArticleActions extends App.Controller
|
||||||
else
|
else
|
||||||
@html ''
|
@html ''
|
||||||
|
|
||||||
publicInternal: (e) =>
|
actionRow: (ticket, article) ->
|
||||||
e.preventDefault()
|
actionConfig = App.Config.get('TicketZoomArticleAction')
|
||||||
articleContainer = $(e.target).closest('.ticket-article-item')
|
keys = _.keys(actionConfig).sort()
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
|
|
||||||
# storage update
|
|
||||||
article = App.TicketArticle.find(article_id)
|
|
||||||
internal = true
|
|
||||||
if article.internal == true
|
|
||||||
internal = false
|
|
||||||
@lastAttributres.internal = internal
|
|
||||||
article.updateAttributes(internal: internal)
|
|
||||||
|
|
||||||
# runntime update
|
|
||||||
if internal
|
|
||||||
articleContainer.addClass('is-internal')
|
|
||||||
else
|
|
||||||
articleContainer.removeClass('is-internal')
|
|
||||||
|
|
||||||
@render()
|
|
||||||
|
|
||||||
actionRow: (article) ->
|
|
||||||
if @permissionCheck('ticket.customer')
|
|
||||||
return []
|
|
||||||
|
|
||||||
actions = []
|
actions = []
|
||||||
if article.internal is true
|
for key in keys
|
||||||
actions = [
|
config = actionConfig[key]
|
||||||
{
|
if config
|
||||||
name: 'set to public'
|
actions = config.action(actions, ticket, article, @)
|
||||||
type: 'public'
|
|
||||||
icon: 'lock-open'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
else
|
|
||||||
actions = [
|
|
||||||
{
|
|
||||||
name: 'set to internal'
|
|
||||||
type: 'internal'
|
|
||||||
icon: 'lock'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
#if @article.type.name is 'note'
|
|
||||||
# actions.push []
|
|
||||||
group = @ticket.group
|
|
||||||
if group.email_address_id && (article.type.name is 'email' || article.type.name is 'web')
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'emailReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
recipients = []
|
|
||||||
if article.sender.name is 'Customer'
|
|
||||||
if article.from
|
|
||||||
localRecipients = emailAddresses.parseAddressList(article.from)
|
|
||||||
if localRecipients
|
|
||||||
recipients = recipients.concat localRecipients
|
|
||||||
if article.to
|
|
||||||
localRecipients = emailAddresses.parseAddressList(article.to)
|
|
||||||
if localRecipients
|
|
||||||
recipients = recipients.concat localRecipients
|
|
||||||
if article.cc
|
|
||||||
localRecipients = emailAddresses.parseAddressList(article.cc)
|
|
||||||
if localRecipients
|
|
||||||
recipients = recipients.concat localRecipients
|
|
||||||
|
|
||||||
# remove system addresses
|
|
||||||
localAddresses = App.EmailAddress.all()
|
|
||||||
forgeinRecipients = []
|
|
||||||
recipientUsed = {}
|
|
||||||
for recipient in recipients
|
|
||||||
if !_.isEmpty(recipient.address)
|
|
||||||
localRecipientAddress = recipient.address.toString().toLowerCase()
|
|
||||||
if !recipientUsed[localRecipientAddress]
|
|
||||||
recipientUsed[localRecipientAddress] = true
|
|
||||||
localAddress = false
|
|
||||||
for address in localAddresses
|
|
||||||
if localRecipientAddress is address.email.toString().toLowerCase()
|
|
||||||
recipientUsed[localRecipientAddress] = true
|
|
||||||
localAddress = true
|
|
||||||
if !localAddress
|
|
||||||
forgeinRecipients.push recipient
|
|
||||||
|
|
||||||
# check if reply all is neede
|
|
||||||
if forgeinRecipients.length > 1
|
|
||||||
actions.push {
|
|
||||||
name: 'reply all'
|
|
||||||
type: 'emailReplyAll'
|
|
||||||
icon: 'reply-all'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.sender.name is 'Customer' && article.type.name is 'phone'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'emailReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.sender.name is 'Agent' && article.type.name is 'phone'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'emailReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.type.name is 'twitter status'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'twitterStatusReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.type.name is 'twitter direct-message'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'twitterDirectMessageReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.type.name is 'facebook feed post' || article.type.name is 'facebook feed comment'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'facebookFeedReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
if article.sender.name is 'Customer' && article.type.name is 'telegram personal-message'
|
|
||||||
actions.push {
|
|
||||||
name: 'reply'
|
|
||||||
type: 'telegramPersonalMessageReply'
|
|
||||||
icon: 'reply'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.push {
|
|
||||||
name: 'split'
|
|
||||||
type: 'split'
|
|
||||||
icon: 'split'
|
|
||||||
href: '#ticket/create/' + article.ticket_id + '/' + article.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if article.type.name is 'note'
|
|
||||||
user = undefined
|
|
||||||
if App.Session.get('id') == article.created_by_id
|
|
||||||
user = App.User.find(App.Session.get('id'))
|
|
||||||
if user.permission('ticket.agent')
|
|
||||||
actions.push {
|
|
||||||
name: 'delete'
|
|
||||||
type: 'delete'
|
|
||||||
icon: 'trash'
|
|
||||||
href: '#'
|
|
||||||
}
|
|
||||||
actions
|
actions
|
||||||
|
|
||||||
facebookFeedReply: (e) =>
|
actionPerform: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
type = App.TicketArticleType.findByAttribute('name', 'facebook feed comment')
|
articleContainer = $(e.target).closest('.ticket-article-item')
|
||||||
@scrollToCompose()
|
type = $(e.currentTarget).attr('data-type')
|
||||||
|
ticket = App.Ticket.fullLocal(@ticket.id)
|
||||||
|
article = App.TicketArticle.fullLocal(@article.id)
|
||||||
|
|
||||||
# empty form
|
actionConfig = App.Config.get('TicketZoomArticleAction')
|
||||||
articleNew = {
|
keys = _.keys(actionConfig).sort()
|
||||||
to: ''
|
actions = []
|
||||||
cc: ''
|
for key in keys
|
||||||
body: ''
|
config = actionConfig[key]
|
||||||
in_reply_to: ''
|
if config
|
||||||
}
|
return if !config.perform(articleContainer, type, ticket, article, @)
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
|
|
||||||
|
|
||||||
twitterStatusReply: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# get reference article
|
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
article = App.TicketArticle.fullLocal(article_id)
|
|
||||||
sender = App.TicketArticleSender.find(article.sender_id)
|
|
||||||
type = App.TicketArticleType.find(article.type_id)
|
|
||||||
customer = App.User.find(article.created_by_id)
|
|
||||||
|
|
||||||
@scrollToCompose()
|
|
||||||
|
|
||||||
# empty form
|
|
||||||
articleNew = {
|
|
||||||
to: ''
|
|
||||||
cc: ''
|
|
||||||
body: ''
|
|
||||||
in_reply_to: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if article.message_id
|
|
||||||
articleNew.in_reply_to = article.message_id
|
|
||||||
|
|
||||||
# get current body
|
|
||||||
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
|
|
||||||
articleNew.body = body
|
|
||||||
|
|
||||||
recipients = article.from
|
|
||||||
if article.to
|
|
||||||
if recipients
|
|
||||||
recipients += ', '
|
|
||||||
recipients += article.to
|
|
||||||
|
|
||||||
if recipients
|
|
||||||
recipientString = ''
|
|
||||||
recipientScreenNames = recipients.split(',')
|
|
||||||
for recipientScreenName in recipientScreenNames
|
|
||||||
if recipientScreenName
|
|
||||||
recipientScreenName = recipientScreenName.trim().toLowerCase()
|
|
||||||
|
|
||||||
# exclude already listed screen name
|
|
||||||
exclude = false
|
|
||||||
if body && body.toLowerCase().match(recipientScreenName)
|
|
||||||
exclude = true
|
|
||||||
|
|
||||||
# exclude own screen_name
|
|
||||||
if recipientScreenName is "@#{@ticket.preferences.channel_screen_name}".toLowerCase()
|
|
||||||
exclude = true
|
|
||||||
|
|
||||||
if exclude is false
|
|
||||||
if recipientString isnt ''
|
|
||||||
recipientString += ' '
|
|
||||||
recipientString += recipientScreenName
|
|
||||||
|
|
||||||
if body
|
|
||||||
articleNew.body = "#{recipientString} #{body} "
|
|
||||||
else
|
|
||||||
articleNew.body = "#{recipientString} "
|
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew, position: 'end' } )
|
|
||||||
|
|
||||||
twitterDirectMessageReply: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# get reference article
|
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
article = App.TicketArticle.fullLocal(article_id)
|
|
||||||
type = App.TicketArticleType.find(article.type_id)
|
|
||||||
sender = App.TicketArticleSender.find(article.sender_id)
|
|
||||||
customer = App.User.find(article.created_by_id)
|
|
||||||
|
|
||||||
@scrollToCompose()
|
|
||||||
|
|
||||||
# empty form
|
|
||||||
articleNew = {
|
|
||||||
to: ''
|
|
||||||
cc: ''
|
|
||||||
body: ''
|
|
||||||
in_reply_to: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if article.message_id
|
|
||||||
articleNew.in_reply_to = article.message_id
|
|
||||||
|
|
||||||
if sender.name is 'Agent'
|
|
||||||
articleNew.to = article.to
|
|
||||||
else
|
|
||||||
articleNew.to = article.from
|
|
||||||
|
|
||||||
if !articleNew.to
|
|
||||||
articleNew.to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
|
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
|
|
||||||
|
|
||||||
emailReplyAll: (e) =>
|
|
||||||
@emailReply(e, true)
|
|
||||||
|
|
||||||
emailReply: (e, all = false) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# get reference article
|
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
article = App.TicketArticle.fullLocal(article_id)
|
|
||||||
ticket = App.Ticket.fullLocal(article.ticket_id)
|
|
||||||
type = App.TicketArticleType.find(article.type_id)
|
|
||||||
article_created_by = App.User.find(article.created_by_id)
|
|
||||||
email_addresses = App.EmailAddress.all()
|
|
||||||
|
|
||||||
@scrollToCompose()
|
|
||||||
|
|
||||||
# empty form
|
|
||||||
articleNew = App.Utils.getRecipientArticle(ticket, article, article_created_by, type, email_addresses, all)
|
|
||||||
|
|
||||||
# get current body
|
|
||||||
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
|
|
||||||
|
|
||||||
# check if quote need to be added
|
|
||||||
signaturePosition = 'bottom'
|
|
||||||
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)
|
|
||||||
|
|
||||||
# full quote, if needed
|
|
||||||
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)
|
|
||||||
|
|
||||||
if selected
|
|
||||||
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
|
|
||||||
|
|
||||||
# add selected text to body
|
|
||||||
body = selected + body
|
|
||||||
|
|
||||||
articleNew.body = body
|
|
||||||
|
|
||||||
type = App.TicketArticleType.findByAttribute(name:'email')
|
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', {
|
|
||||||
ticket: @ticket
|
|
||||||
type: type
|
|
||||||
article: articleNew
|
|
||||||
signaturePosition: signaturePosition
|
|
||||||
})
|
|
||||||
|
|
||||||
telegramPersonalMessageReply: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# get reference article
|
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
article = App.TicketArticle.fullLocal(article_id)
|
|
||||||
sender = App.TicketArticleSender.find(article.sender_id)
|
|
||||||
type = App.TicketArticleType.find(article.type_id)
|
|
||||||
customer = App.User.find(article.created_by_id)
|
|
||||||
|
|
||||||
@scrollToCompose()
|
|
||||||
|
|
||||||
# empty form
|
|
||||||
articleNew = {
|
|
||||||
to: ''
|
|
||||||
cc: ''
|
|
||||||
body: ''
|
|
||||||
in_reply_to: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if article.message_id
|
|
||||||
articleNew.in_reply_to = article.message_id
|
|
||||||
|
|
||||||
# get current body
|
|
||||||
articleNew.body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html().trim() || ''
|
|
||||||
|
|
||||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew, position: 'end' } )
|
|
||||||
|
|
||||||
delete: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
callback = ->
|
|
||||||
article_id = $(e.target).parents('[data-id]').data('id')
|
|
||||||
article = App.TicketArticle.find(article_id)
|
|
||||||
article.destroy()
|
|
||||||
|
|
||||||
new App.ControllerConfirm(
|
|
||||||
message: 'Sure?'
|
|
||||||
callback: callback
|
|
||||||
container: @el.closest('.content')
|
|
||||||
)
|
|
||||||
|
|
||||||
scrollToCompose: =>
|
scrollToCompose: =>
|
||||||
@el.closest('.content').find('.article-add').ScrollTo()
|
@el.closest('.content').find('.article-add').ScrollTo()
|
||||||
|
|
|
@ -70,6 +70,14 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
@textarea.focus()
|
@textarea.focus()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# add article attachment
|
||||||
|
@bind('ui::ticket::addArticleAttachent', (data) =>
|
||||||
|
return if data.ticket.id.toString() isnt @ticket_id.toString()
|
||||||
|
return if _.isEmpty(data.attachments)
|
||||||
|
for file in data.attachments
|
||||||
|
@renderAttachment(file)
|
||||||
|
)
|
||||||
|
|
||||||
# reset new article screen
|
# reset new article screen
|
||||||
@bind('ui::ticket::taskReset', (data) =>
|
@bind('ui::ticket::taskReset', (data) =>
|
||||||
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
||||||
|
@ -143,8 +151,8 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
icon: 'twitter'
|
icon: 'twitter'
|
||||||
attributes: []
|
attributes: []
|
||||||
internal: false,
|
internal: false,
|
||||||
features: ['body:limit', 'body:initials']
|
features: attributes
|
||||||
maxTextLength: 140
|
maxTextLength: 280
|
||||||
warningTextLength: 30
|
warningTextLength: 30
|
||||||
}
|
}
|
||||||
if possibleArticleType['twitter direct-message']
|
if possibleArticleType['twitter direct-message']
|
||||||
|
@ -156,7 +164,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
icon: 'twitter'
|
icon: 'twitter'
|
||||||
attributes: ['to']
|
attributes: ['to']
|
||||||
internal: false,
|
internal: false,
|
||||||
features: ['body:limit', 'body:initials']
|
features: attributes
|
||||||
maxTextLength: 10000
|
maxTextLength: 10000
|
||||||
warningTextLength: 500
|
warningTextLength: 500
|
||||||
}
|
}
|
||||||
|
@ -245,7 +253,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
controller = new App.ControllerForm(
|
controller = new App.ControllerForm(
|
||||||
el: @$('.recipients')
|
el: @$('.recipients')
|
||||||
model:
|
model:
|
||||||
configure_attributes: configure_attributes,
|
configure_attributes: configure_attributes
|
||||||
)
|
)
|
||||||
|
|
||||||
@$('[data-name="body"]').ce({
|
@$('[data-name="body"]').ce({
|
||||||
|
@ -255,12 +263,13 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
})
|
})
|
||||||
|
|
||||||
html5Upload.initialize(
|
html5Upload.initialize(
|
||||||
uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload',
|
uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload'
|
||||||
dropContainer: @$('.article-add').get(0),
|
dropContainer: @$('.article-add').get(0)
|
||||||
cancelContainer: @cancelContainer,
|
cancelContainer: @cancelContainer
|
||||||
inputField: @$('.article-attachment input').get(0),
|
inputField: @$('.article-attachment input').get(0)
|
||||||
key: 'File',
|
key: 'File'
|
||||||
data: { form_id: @form_id },
|
data:
|
||||||
|
form_id: @form_id
|
||||||
maxSimultaneousUploads: 1,
|
maxSimultaneousUploads: 1,
|
||||||
onFileAdded: (file) =>
|
onFileAdded: (file) =>
|
||||||
|
|
||||||
|
@ -303,6 +312,8 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@bindAttachmentDelete()
|
||||||
|
|
||||||
# show text module UI
|
# show text module UI
|
||||||
if !@permissionCheck('ticket.customer')
|
if !@permissionCheck('ticket.customer')
|
||||||
textModule = new App.WidgetTextModule(
|
textModule = new App.WidgetTextModule(
|
||||||
|
@ -737,33 +748,29 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
@articleNewEdit.parent().removeClass('is-dropTarget') if @dragEventCounter is 0
|
@articleNewEdit.parent().removeClass('is-dropTarget') if @dragEventCounter is 0
|
||||||
|
|
||||||
renderAttachment: (file) =>
|
renderAttachment: (file) =>
|
||||||
@attachmentsHolder.append App.view('generic/attachment_item')
|
@attachmentsHolder.append(App.view('generic/attachment_item')(file))
|
||||||
fileName: file.filename
|
|
||||||
fileSize: @humanFileSize( file.size )
|
|
||||||
store_id: file.store_id
|
|
||||||
@attachmentsHolder.on(
|
|
||||||
'click'
|
|
||||||
"[data-id=#{file.store_id}]", (e) =>
|
|
||||||
@attachments = _.filter(
|
|
||||||
@attachments,
|
|
||||||
(item) ->
|
|
||||||
return if item.id isnt file.store_id
|
|
||||||
item
|
|
||||||
)
|
|
||||||
store_id = $(e.currentTarget).data('id')
|
|
||||||
|
|
||||||
# delete attachment from storage
|
bindAttachmentDelete: =>
|
||||||
App.Ajax.request(
|
@attachmentsHolder.on('click', '.js-delete', (e) =>
|
||||||
type: 'DELETE'
|
id = $(e.currentTarget).data('id')
|
||||||
url: App.Config.get('api_path') + '/ticket_attachment_upload'
|
@attachments = _.filter(
|
||||||
data: JSON.stringify(store_id: store_id)
|
@attachments,
|
||||||
processData: false
|
(item) ->
|
||||||
)
|
return if item.id.toString() is id.toString()
|
||||||
|
item
|
||||||
|
)
|
||||||
|
|
||||||
# remove attachment from dom
|
# delete attachment from storage
|
||||||
element = $(e.currentTarget).closest('.attachments')
|
App.Ajax.request(
|
||||||
$(e.currentTarget).closest('.attachment').remove()
|
type: 'DELETE'
|
||||||
# empty .attachment (remove spaces) to keep css working, thanks @mrflix :-o
|
url: App.Config.get('api_path') + '/ticket_attachment_upload'
|
||||||
if element.find('.attachment').length == 0
|
data: JSON.stringify(id: id)
|
||||||
element.empty()
|
processData: false
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove attachment from dom
|
||||||
|
element = $(e.currentTarget).closest('.attachments')
|
||||||
|
$(e.currentTarget).closest('.attachment').remove()
|
||||||
|
if element.find('.attachment').length == 0
|
||||||
|
element.empty()
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ class App.TicketZoomArticleView extends App.Controller
|
||||||
el: el
|
el: el
|
||||||
ui: @ui
|
ui: @ui
|
||||||
highligher: @highligher
|
highligher: @highligher
|
||||||
|
form_id: @form_id
|
||||||
)
|
)
|
||||||
if !@ticketArticleInsertByIndex(index, el)
|
if !@ticketArticleInsertByIndex(index, el)
|
||||||
all.push el
|
all.push el
|
||||||
|
@ -193,6 +194,7 @@ class ArticleViewItem extends App.ObserverController
|
||||||
ticket: @ticket
|
ticket: @ticket
|
||||||
article: article
|
article: article
|
||||||
lastAttributres: @lastAttributres
|
lastAttributres: @lastAttributres
|
||||||
|
form_id: @form_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# set see more
|
# set see more
|
||||||
|
|
|
@ -32,16 +32,14 @@ class App.TicketZoomSidebar extends App.ObserverController
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
@sidebarBackends[key].reload(
|
@sidebarBackends[key].reload(
|
||||||
params: @params
|
params: @params
|
||||||
query: @query
|
query: @query
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
markForm: @markForm
|
markForm: @markForm
|
||||||
tags: @tags
|
tags: @tags
|
||||||
links: @links
|
links: @links
|
||||||
)
|
)
|
||||||
item = @sidebarBackends[key].sidebarItem()
|
@sidebarItems.push @sidebarBackends[key]
|
||||||
if item
|
|
||||||
@sidebarItems.push item
|
|
||||||
|
|
||||||
new App.Sidebar(
|
new App.Sidebar(
|
||||||
el: @el.find('.tabsSidebar')
|
el: @el.find('.tabsSidebar')
|
||||||
|
|
|
@ -1,32 +1,67 @@
|
||||||
class SidebarCustomer extends App.Controller
|
class SidebarCustomer extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@permissionCheck('ticket.agent')
|
return if !@permissionCheck('ticket.agent')
|
||||||
items = {
|
@item = {
|
||||||
head: 'Customer'
|
name: 'customer'
|
||||||
name: 'customer'
|
badgeCallback: @badgeRender
|
||||||
icon: 'person'
|
sidebarHead: 'Customer'
|
||||||
actions: [
|
sidebarCallback: @showCustomer
|
||||||
|
sidebarActions: [
|
||||||
{
|
{
|
||||||
title: 'Change Customer'
|
title: 'Change Customer'
|
||||||
name: 'customer-change'
|
name: 'customer-change'
|
||||||
callback: @changeCustomer
|
callback: @changeCustomer
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
callback: @showCustomer
|
|
||||||
}
|
}
|
||||||
return items if @ticket && @ticket.customer_id == 1
|
return @item if @ticket && @ticket.customer_id == 1
|
||||||
items.actions.push {
|
@item.sidebarActions.push {
|
||||||
title: 'Edit Customer'
|
title: 'Edit Customer'
|
||||||
name: 'customer-edit'
|
name: 'customer-edit'
|
||||||
callback: @editCustomer
|
callback: @editCustomer
|
||||||
}
|
}
|
||||||
items
|
@item
|
||||||
|
|
||||||
|
metaBadge: (user) =>
|
||||||
|
counter = ''
|
||||||
|
cssClass = ''
|
||||||
|
counter = @sidebarItemCounter(user)
|
||||||
|
|
||||||
|
if @Config.get('ui_sidebar_open_ticket_indicator_colored') is true
|
||||||
|
if counter == 1
|
||||||
|
cssClass = 'tabsSidebar-tab-count--warning'
|
||||||
|
if counter > 1
|
||||||
|
cssClass = 'tabsSidebar-tab-count--danger'
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'customer'
|
||||||
|
icon: 'person'
|
||||||
|
counterPossible: true
|
||||||
|
counter: counter
|
||||||
|
cssClass: cssClass
|
||||||
|
}
|
||||||
|
|
||||||
|
badgeRender: (el) =>
|
||||||
|
@badgeEl = el
|
||||||
|
if App.User.exists(@ticket.customer_id)
|
||||||
|
user = App.User.find(@ticket.customer_id)
|
||||||
|
@badgeRenderLocal(user)
|
||||||
|
|
||||||
|
badgeRenderLocal: (user) =>
|
||||||
|
@badgeEl.html(App.view('generic/sidebar_tabs_item')(@metaBadge(user)))
|
||||||
|
|
||||||
|
sidebarItemCounter: (user) ->
|
||||||
|
counter = ''
|
||||||
|
if user && user.preferences && user.preferences.tickets_open
|
||||||
|
counter = user.preferences.tickets_open
|
||||||
|
counter
|
||||||
|
|
||||||
showCustomer: (el) =>
|
showCustomer: (el) =>
|
||||||
@el = el
|
@elSidebar = el
|
||||||
new App.WidgetUser(
|
new App.WidgetUser(
|
||||||
el: @el
|
el: @elSidebar
|
||||||
user_id: @ticket.customer_id
|
user_id: @ticket.customer_id
|
||||||
|
callback: @badgeRenderLocal
|
||||||
)
|
)
|
||||||
|
|
||||||
editCustomer: =>
|
editCustomer: =>
|
||||||
|
@ -38,13 +73,13 @@ class SidebarCustomer extends App.Controller
|
||||||
title: 'Users'
|
title: 'Users'
|
||||||
object: 'User'
|
object: 'User'
|
||||||
objects: 'Users'
|
objects: 'Users'
|
||||||
container: @el.closest('.content')
|
container: @elSidebar.closest('.content')
|
||||||
)
|
)
|
||||||
|
|
||||||
changeCustomer: =>
|
changeCustomer: =>
|
||||||
new App.TicketCustomer(
|
new App.TicketCustomer(
|
||||||
ticket_id: @ticket.id
|
ticket_id: @ticket.id
|
||||||
container: @el.closest('.content')
|
container: @elSidebar.closest('.content')
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('200-Customer', SidebarCustomer, 'TicketZoomSidebar')
|
App.Config.set('200-Customer', SidebarCustomer, 'TicketZoomSidebar')
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
class SidebarIdoit extends App.Controller
|
class SidebarIdoit extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@Config.get('idoit_integration')
|
return if !@Config.get('idoit_integration')
|
||||||
{
|
@item = {
|
||||||
head: 'i-doit'
|
name: 'idoit'
|
||||||
name: 'idoit'
|
badgeIcon: 'printer'
|
||||||
icon: 'printer'
|
sidebarHead: 'i-doit'
|
||||||
actions: [
|
sidebarCallback: @showObjects
|
||||||
|
sidebarActions: [
|
||||||
{
|
{
|
||||||
title: 'Change Objects'
|
title: 'Change Objects'
|
||||||
name: 'objects-change'
|
name: 'objects-change'
|
||||||
callback: @changeObjects
|
callback: @changeObjects
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
callback: @showObjects
|
|
||||||
}
|
}
|
||||||
|
@item
|
||||||
|
|
||||||
changeObjects: =>
|
changeObjects: =>
|
||||||
new App.IdoitObjectSelector(
|
new App.IdoitObjectSelector(
|
||||||
|
|
|
@ -2,24 +2,25 @@ class SidebarOrganization extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
return if !@permissionCheck('ticket.agent')
|
return if !@permissionCheck('ticket.agent')
|
||||||
return if !@ticket.organization_id
|
return if !@ticket.organization_id
|
||||||
{
|
@item = {
|
||||||
head: 'Organization'
|
|
||||||
name: 'organization'
|
name: 'organization'
|
||||||
icon: 'group'
|
badgeIcon: 'group'
|
||||||
actions: [
|
sidebarHead: 'Organization'
|
||||||
|
sidebarCallback: @showOrganization
|
||||||
|
sidebarActions: [
|
||||||
{
|
{
|
||||||
title: 'Edit Organization'
|
title: 'Edit Organization'
|
||||||
name: 'organization-edit'
|
name: 'organization-edit'
|
||||||
callback: @editOrganization
|
callback: @editOrganization
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
callback: @showOrganization
|
|
||||||
}
|
}
|
||||||
|
@item
|
||||||
|
|
||||||
showOrganization: (el) =>
|
showOrganization: (el) =>
|
||||||
@el = el
|
@elSidebar = el
|
||||||
new App.WidgetOrganization(
|
new App.WidgetOrganization(
|
||||||
el: @el
|
el: @elSidebar
|
||||||
organization_id: @ticket.organization_id
|
organization_id: @ticket.organization_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ class SidebarOrganization extends App.Controller
|
||||||
title: 'Organizations'
|
title: 'Organizations'
|
||||||
object: 'Organization'
|
object: 'Organization'
|
||||||
objects: 'Organizations'
|
objects: 'Organizations'
|
||||||
container: @el.closest('.content')
|
container: @elSidebar.closest('.content')
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('300-Organization', SidebarOrganization, 'TicketZoomSidebar')
|
App.Config.set('300-Organization', SidebarOrganization, 'TicketZoomSidebar')
|
||||||
|
|
|
@ -20,8 +20,9 @@ class Edit extends App.ObserverController
|
||||||
handlers: [
|
handlers: [
|
||||||
@ticketFormChanges
|
@ticketFormChanges
|
||||||
]
|
]
|
||||||
filter: @formMeta.filter
|
filter: @formMeta.filter
|
||||||
params: defaults
|
params: defaults
|
||||||
|
isDisabled: !ticket.editable()
|
||||||
#bookmarkable: true
|
#bookmarkable: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,14 +37,14 @@ class Edit extends App.ObserverController
|
||||||
|
|
||||||
class SidebarTicket extends App.Controller
|
class SidebarTicket extends App.Controller
|
||||||
sidebarItem: =>
|
sidebarItem: =>
|
||||||
sidebarItem = {
|
@item = {
|
||||||
head: 'Ticket'
|
name: 'ticket'
|
||||||
name: 'ticket'
|
badgeIcon: 'message'
|
||||||
icon: 'message'
|
sidebarHead: 'Ticket'
|
||||||
callback: @editTicket
|
sidebarCallback: @editTicket
|
||||||
}
|
}
|
||||||
if @permissionCheck('ticket.agent')
|
if @permissionCheck('ticket.agent')
|
||||||
sidebarItem['actions'] = [
|
@item.sidebarActions = [
|
||||||
{
|
{
|
||||||
title: 'History'
|
title: 'History'
|
||||||
name: 'ticket-history'
|
name: 'ticket-history'
|
||||||
|
@ -60,7 +61,7 @@ class SidebarTicket extends App.Controller
|
||||||
callback: @changeCustomer
|
callback: @changeCustomer
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
sidebarItem
|
@item
|
||||||
|
|
||||||
reload: (args) =>
|
reload: (args) =>
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ class SidebarTicket extends App.Controller
|
||||||
|
|
||||||
editTicket: (el) =>
|
editTicket: (el) =>
|
||||||
@el = el
|
@el = el
|
||||||
localEl = $( App.view('ticket_zoom/sidebar_ticket')() )
|
localEl = $(App.view('ticket_zoom/sidebar_ticket')())
|
||||||
|
|
||||||
@edit = new Edit(
|
@edit = new Edit(
|
||||||
object_id: @ticket.id
|
object_id: @ticket.id
|
||||||
|
|
|
@ -145,7 +145,7 @@ class Index extends App.ControllerSubContent
|
||||||
query: @query
|
query: @query
|
||||||
limit: 140
|
limit: 140
|
||||||
role_ids: role_ids
|
role_ids: role_ids
|
||||||
full: 1
|
full: true
|
||||||
processData: true,
|
processData: true,
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
App.Collection.loadAssets(data.assets)
|
App.Collection.loadAssets(data.assets)
|
||||||
|
@ -167,7 +167,7 @@ class Index extends App.ControllerSubContent
|
||||||
data:
|
data:
|
||||||
limit: 50
|
limit: 50
|
||||||
role_ids: role_ids
|
role_ids: role_ids
|
||||||
full: 1
|
full: true
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
App.Collection.loadAssets(data.assets)
|
App.Collection.loadAssets(data.assets)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
class DefaultLocale extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
check = =>
|
||||||
|
|
||||||
|
preferences = App.Session.get('preferences')
|
||||||
|
return if !preferences
|
||||||
|
return if !_.isEmpty(preferences.locale)
|
||||||
|
locale = App.i18n.get()
|
||||||
|
@ajax(
|
||||||
|
id: "i18n-set-user-#{locale}"
|
||||||
|
type: 'PUT'
|
||||||
|
url: "#{App.Config.get('api_path')}/users/preferences"
|
||||||
|
data: JSON.stringify(locale: locale)
|
||||||
|
processData: true
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Event.bind('auth:login', (session) =>
|
||||||
|
@delay(check, 3500, 'default_locale')
|
||||||
|
)
|
||||||
|
|
||||||
|
App.Config.set('default_locale', DefaultLocale, 'Widgets')
|
|
@ -47,4 +47,4 @@ class Widget extends App.ControllerWidgetOnDemand
|
||||||
800
|
800
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set( 'switch_back_to_user', Widget, 'Widgets' )
|
App.Config.set('switch_back_to_user', Widget, 'Widgets')
|
||||||
|
|
|
@ -70,6 +70,7 @@ class App.TicketStats extends App.Controller
|
||||||
render: (data) =>
|
render: (data) =>
|
||||||
if !data
|
if !data
|
||||||
data = @data
|
data = @data
|
||||||
|
return if !data
|
||||||
|
|
||||||
user_total = 0
|
user_total = 0
|
||||||
if data.user.open_ids && data.user.closed_ids
|
if data.user.open_ids && data.user.closed_ids
|
||||||
|
|
|
@ -12,8 +12,8 @@ class App extends Spine.Controller
|
||||||
helper =
|
helper =
|
||||||
|
|
||||||
# define print name helper
|
# define print name helper
|
||||||
P: (object, attributeName, attributes) ->
|
P: (object, attributeName, attributes, table = false) ->
|
||||||
App.viewPrint(object, attributeName, attributes)
|
App.viewPrint(object, attributeName, attributes, table)
|
||||||
|
|
||||||
# define date format helper
|
# define date format helper
|
||||||
date: (time) ->
|
date: (time) ->
|
||||||
|
@ -136,7 +136,7 @@ class App extends Spine.Controller
|
||||||
return marked(string)
|
return marked(string)
|
||||||
App.i18n.translateContent(string)
|
App.i18n.translateContent(string)
|
||||||
|
|
||||||
@viewPrint: (object, attributeName, attributes) ->
|
@viewPrint: (object, attributeName, attributes, table) ->
|
||||||
if !attributes
|
if !attributes
|
||||||
attributes = {}
|
attributes = {}
|
||||||
if object.constructor.attributesGet
|
if object.constructor.attributesGet
|
||||||
|
@ -172,10 +172,10 @@ class App extends Spine.Controller
|
||||||
if object[attributeNameWithoutRef]
|
if object[attributeNameWithoutRef]
|
||||||
valueRef = object[attributeNameWithoutRef]
|
valueRef = object[attributeNameWithoutRef]
|
||||||
|
|
||||||
@viewPrintItem(value, attributeConfig, valueRef)
|
@viewPrintItem(value, attributeConfig, valueRef, table)
|
||||||
|
|
||||||
# define print name helper
|
# define print name helper
|
||||||
@viewPrintItem: (item, attributeConfig = {}, valueRef) ->
|
@viewPrintItem: (item, attributeConfig = {}, valueRef, table) ->
|
||||||
return '-' if item is undefined
|
return '-' if item is undefined
|
||||||
return '-' if item is ''
|
return '-' if item is ''
|
||||||
return item if item is null
|
return item if item is null
|
||||||
|
@ -238,7 +238,7 @@ class App extends Spine.Controller
|
||||||
# transform date
|
# transform date
|
||||||
if attributeConfig.tag is 'date'
|
if attributeConfig.tag is 'date'
|
||||||
isHtmlEscape = true
|
isHtmlEscape = true
|
||||||
resultLocal = App.i18n.translateDate(resultLocal)
|
resultLocal = App.i18n.translateDate(resultLocal)
|
||||||
|
|
||||||
# transform input tel|url to make it clickable
|
# transform input tel|url to make it clickable
|
||||||
if attributeConfig.tag is 'input'
|
if attributeConfig.tag is 'input'
|
||||||
|
@ -258,8 +258,10 @@ class App extends Spine.Controller
|
||||||
cssClass = attributeConfig.class || ''
|
cssClass = attributeConfig.class || ''
|
||||||
if cssClass.match 'escalation'
|
if cssClass.match 'escalation'
|
||||||
escalation = true
|
escalation = true
|
||||||
humanTime = App.PrettyDate.humanTime(resultLocal, escalation)
|
humanTime = ''
|
||||||
resultLocal = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{resultLocal}\" title=\"#{timestamp}\">#{humanTime}</time>"
|
if !table
|
||||||
|
humanTime = App.PrettyDate.humanTime(resultLocal, escalation)
|
||||||
|
resultLocal = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{resultLocal}\" title=\"#{timestamp}\">#{humanTime}</time>"
|
||||||
|
|
||||||
if !isHtmlEscape && typeof resultLocal is 'string'
|
if !isHtmlEscape && typeof resultLocal is 'string'
|
||||||
resultLocal = App.Utils.htmlEscape(resultLocal)
|
resultLocal = App.Utils.htmlEscape(resultLocal)
|
||||||
|
|
|
@ -77,7 +77,7 @@ class App._CollectionSingletonBase
|
||||||
|
|
||||||
callback: (data) =>
|
callback: (data) =>
|
||||||
for counter, attr of @callbacks
|
for counter, attr of @callbacks
|
||||||
callback = ->
|
callback = =>
|
||||||
attr.callback(data)
|
attr.callback(data)
|
||||||
if attr.one
|
if attr.one
|
||||||
delete @callbacks[counter]
|
delete @callbacks[counter]
|
||||||
|
|
|
@ -71,7 +71,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
@open()
|
@open()
|
||||||
|
|
||||||
focusInput: =>
|
focusInput: =>
|
||||||
@objectSelect.focus() if not @formControl.hasClass 'focus'
|
@objectSelect.focus() if not @formControl.hasClass('focus')
|
||||||
|
|
||||||
onBlur: =>
|
onBlur: =>
|
||||||
selectObject = @objectSelect.val()
|
selectObject = @objectSelect.val()
|
||||||
|
@ -85,6 +85,9 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
@objectId.val("guess:#{selectObject}")
|
@objectId.val("guess:#{selectObject}")
|
||||||
@formControl.removeClass 'focus'
|
@formControl.removeClass 'focus'
|
||||||
|
|
||||||
|
resetObjectSelection: =>
|
||||||
|
@objectId.val('').trigger('change')
|
||||||
|
|
||||||
onObjectClick: (e) =>
|
onObjectClick: (e) =>
|
||||||
objectId = $(e.currentTarget).data('object-id')
|
objectId = $(e.currentTarget).data('object-id')
|
||||||
@selectObject(objectId)
|
@selectObject(objectId)
|
||||||
|
@ -103,23 +106,23 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
# Only work with the last one since its the newest one
|
# Only work with the last one since its the newest one
|
||||||
objectId = @objectId.val().split(',').pop()
|
objectId = @objectId.val().split(',').pop()
|
||||||
|
|
||||||
return if !objectId
|
if objectId && App[@objectSingle].exists(objectId)
|
||||||
return if !App[@objectSingle].exists(objectId)
|
object = App[@objectSingle].find(objectId)
|
||||||
object = App[@objectSingle].find(objectId)
|
name = object.displayName()
|
||||||
name = object.displayName()
|
|
||||||
|
|
||||||
if @attribute.multiple
|
if @attribute.multiple
|
||||||
# create token
|
|
||||||
@createToken name, objectId
|
|
||||||
else
|
|
||||||
if object.email
|
|
||||||
|
|
||||||
# quote name for special character
|
# create token
|
||||||
if name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|!|\*|\[|\]/)
|
@createToken(name, objectId)
|
||||||
name = "\"#{name}\""
|
else
|
||||||
name += " <#{object.email}>"
|
if object.email
|
||||||
|
|
||||||
@objectSelect.val(name)
|
# quote name for special character
|
||||||
|
if name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|!|\*|\[|\]/)
|
||||||
|
name = "\"#{name}\""
|
||||||
|
name += " <#{object.email}>"
|
||||||
|
|
||||||
|
@objectSelect.val(name)
|
||||||
|
|
||||||
if @callback
|
if @callback
|
||||||
@callback(objectId)
|
@callback(objectId)
|
||||||
|
@ -321,12 +324,16 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||||
@hideOrganizationMembers()
|
@hideOrganizationMembers()
|
||||||
|
|
||||||
# hide dropdown
|
# hide dropdown
|
||||||
if !query
|
if _.isEmpty(query)
|
||||||
@emptyResultList()
|
@emptyResultList()
|
||||||
|
|
||||||
if !@attribute.disableCreateObject
|
if !@attribute.disableCreateObject
|
||||||
@recipientList.append(@buildObjectNew())
|
@recipientList.append(@buildObjectNew())
|
||||||
|
|
||||||
|
# reset object selection
|
||||||
|
@resetObjectSelection()
|
||||||
|
return
|
||||||
|
|
||||||
# show dropdown
|
# show dropdown
|
||||||
if query && ( !@attribute.minLengt || @attribute.minLengt <= query.length )
|
if query && ( !@attribute.minLengt || @attribute.minLengt <= query.length )
|
||||||
@lazySearch(query)
|
@lazySearch(query)
|
||||||
|
|
|
@ -79,8 +79,7 @@ class App.Auth
|
||||||
@_updateModelAttributes(data.models)
|
@_updateModelAttributes(data.models)
|
||||||
|
|
||||||
# set locale
|
# set locale
|
||||||
locale = window.navigator.userLanguage || window.navigator.language || 'en-us'
|
App.i18n.set(App.i18n.detectBrowserLocale())
|
||||||
App.i18n.set(locale)
|
|
||||||
|
|
||||||
# rebuild navbar with new navbar items
|
# rebuild navbar with new navbar items
|
||||||
App.Event.trigger('auth')
|
App.Event.trigger('auth')
|
||||||
|
@ -120,7 +119,7 @@ class App.Auth
|
||||||
if preferences && preferences.locale
|
if preferences && preferences.locale
|
||||||
locale = preferences.locale
|
locale = preferences.locale
|
||||||
if !locale
|
if !locale
|
||||||
locale = window.navigator.userLanguage || window.navigator.language || 'en-us'
|
locale = App.i18n.detectBrowserLocale()
|
||||||
App.i18n.set(locale)
|
App.i18n.set(locale)
|
||||||
|
|
||||||
App.Event.trigger('auth:login', data.session)
|
App.Event.trigger('auth:login', data.session)
|
||||||
|
|
|
@ -39,14 +39,27 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
|
if !_.isEmpty(@attribute.seperator)
|
||||||
|
values = []
|
||||||
|
if @attribute.value
|
||||||
|
values = @attribute.value.split(';')
|
||||||
|
else if @attribute.default
|
||||||
|
values = @attribute.default.split(';')
|
||||||
|
|
||||||
|
for value in values
|
||||||
|
for option in @options.attribute.options
|
||||||
|
if option.value is value
|
||||||
|
option.selected = true
|
||||||
|
|
||||||
@values = []
|
@values = []
|
||||||
_.each @options.attribute.options, (option) =>
|
_.each @options.attribute.options, (option) =>
|
||||||
if option.selected
|
if option.selected
|
||||||
@values.push option.value.toString()
|
@values.push option.value.toString()
|
||||||
|
|
||||||
@html App.view('generic/column_select')
|
@html App.view('generic/column_select')(
|
||||||
attribute: @options.attribute
|
attribute: @options.attribute
|
||||||
values: @values
|
values: @values
|
||||||
|
)
|
||||||
|
|
||||||
# keep inital height
|
# keep inital height
|
||||||
# disabled for now since controls in modals get rendered hidden
|
# disabled for now since controls in modals get rendered hidden
|
||||||
|
@ -60,13 +73,17 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
@throttledSelect()
|
@throttledSelect()
|
||||||
|
|
||||||
select: (value) ->
|
select: (value) ->
|
||||||
@selected.find("[data-value='#{value}']").removeClass 'is-hidden'
|
@selected.find("[data-value='#{value}']").removeClass('is-hidden')
|
||||||
@pool.find("[data-value='#{value}']").addClass 'is-hidden'
|
@pool.find("[data-value='#{value}']").addClass('is-hidden')
|
||||||
@values.push(value)
|
@values.push(value)
|
||||||
@shadow.val(@values)
|
|
||||||
@shadow.trigger('change')
|
|
||||||
|
|
||||||
@placeholder.addClass 'is-hidden'
|
if !_.isEmpty(@attribute.seperator)
|
||||||
|
@shadow.val(@values.join(';'))
|
||||||
|
else
|
||||||
|
@shadow.val(@values)
|
||||||
|
@shadow.trigger('change')
|
||||||
|
|
||||||
|
@placeholder.addClass('is-hidden')
|
||||||
|
|
||||||
if @search.val() and @poolOptions.not('.is-filtered').not('.is-hidden').size() is 0
|
if @search.val() and @poolOptions.not('.is-filtered').not('.is-hidden').size() is 0
|
||||||
@clear()
|
@clear()
|
||||||
|
@ -76,14 +93,17 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
@throttledRemove()
|
@throttledRemove()
|
||||||
|
|
||||||
remove: (value) ->
|
remove: (value) ->
|
||||||
@pool.find("[data-value='#{value}']").removeClass 'is-hidden'
|
@pool.find("[data-value='#{value}']").removeClass('is-hidden')
|
||||||
@selected.find("[data-value='#{value}']").addClass 'is-hidden'
|
@selected.find("[data-value='#{value}']").addClass('is-hidden')
|
||||||
@values.splice(@values.indexOf(value), 1)
|
@values.splice(@values.indexOf(value), 1)
|
||||||
@shadow.val(@values)
|
if !_.isEmpty(@attribute.seperator)
|
||||||
@shadow.trigger('change')
|
@shadow.val(@values.join(';'))
|
||||||
|
else
|
||||||
|
@shadow.val(@values)
|
||||||
|
@shadow.trigger('change')
|
||||||
|
|
||||||
if !@values.length
|
if !@values.length
|
||||||
@placeholder.removeClass 'is-hidden'
|
@placeholder.removeClass('is-hidden')
|
||||||
|
|
||||||
filter: (event) ->
|
filter: (event) ->
|
||||||
filter = $(event.currentTarget).val()
|
filter = $(event.currentTarget).val()
|
||||||
|
@ -92,16 +112,16 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
return if $(el).hasClass('is-hidden')
|
return if $(el).hasClass('is-hidden')
|
||||||
|
|
||||||
if $(el).text().toLowerCase().indexOf(filter.toLowerCase()) > -1
|
if $(el).text().toLowerCase().indexOf(filter.toLowerCase()) > -1
|
||||||
$(el).removeClass 'is-filtered'
|
$(el).removeClass('is-filtered')
|
||||||
else
|
else
|
||||||
$(el).addClass 'is-filtered'
|
$(el).addClass('is-filtered')
|
||||||
|
|
||||||
@clearButton.toggleClass 'is-hidden', filter.length is 0
|
@clearButton.toggleClass 'is-hidden', filter.length is 0
|
||||||
|
|
||||||
clear: ->
|
clear: ->
|
||||||
@search.val('')
|
@search.val('')
|
||||||
@poolOptions.removeClass 'is-filtered'
|
@poolOptions.removeClass('is-filtered')
|
||||||
@clearButton.addClass 'is-hidden'
|
@clearButton.addClass('is-hidden')
|
||||||
|
|
||||||
onFilterKeydown: (event) ->
|
onFilterKeydown: (event) ->
|
||||||
return if event.keyCode != 13
|
return if event.keyCode != 13
|
||||||
|
@ -111,4 +131,4 @@ class App.ColumnSelect extends Spine.Controller
|
||||||
|
|
||||||
firstVisibleOption = @poolOptions.not('.is-filtered').not('.is-hidden').first()
|
firstVisibleOption = @poolOptions.not('.is-filtered').not('.is-hidden').first()
|
||||||
if firstVisibleOption
|
if firstVisibleOption
|
||||||
@select firstVisibleOption.attr('data-value')
|
@select firstVisibleOption.attr('data-value')
|
||||||
|
|
|
@ -80,6 +80,24 @@ class App.i18n
|
||||||
_instance ?= new _i18nSingleton()
|
_instance ?= new _i18nSingleton()
|
||||||
_instance.mapTime
|
_instance.mapTime
|
||||||
|
|
||||||
|
@detectBrowserLocale: ->
|
||||||
|
return 'en-us' if !window.navigator.userLanguage && !window.navigator.language
|
||||||
|
|
||||||
|
if window.navigator.languages
|
||||||
|
allLocales = App.Locale.all()
|
||||||
|
for browserLocale in window.navigator.languages
|
||||||
|
for localAllLocale in allLocales
|
||||||
|
if browserLocale is localAllLocale.locale
|
||||||
|
return localAllLocale.locale
|
||||||
|
|
||||||
|
for browserLocale in window.navigator.languages
|
||||||
|
browserLocale = browserLocale.substr(0, 2)
|
||||||
|
for localAllLocale in allLocales
|
||||||
|
if browserLocale is localAllLocale.alias
|
||||||
|
return localAllLocale.locale
|
||||||
|
|
||||||
|
window.navigator.userLanguage || window.navigator.language || 'en-us'
|
||||||
|
|
||||||
class _i18nSingleton extends Spine.Module
|
class _i18nSingleton extends Spine.Module
|
||||||
@include App.LogInclude
|
@include App.LogInclude
|
||||||
|
|
||||||
|
@ -319,18 +337,20 @@ class _i18nSingleton extends Spine.Module
|
||||||
if offset
|
if offset
|
||||||
timeObject = new Date(timeObject.getTime() + (timeObject.getTimezoneOffset() * 60000))
|
timeObject = new Date(timeObject.getTime() + (timeObject.getTimezoneOffset() * 60000))
|
||||||
|
|
||||||
d = timeObject.getDate()
|
d = timeObject.getDate()
|
||||||
m = timeObject.getMonth() + 1
|
m = timeObject.getMonth() + 1
|
||||||
y = timeObject.getFullYear()
|
yfull = timeObject.getFullYear()
|
||||||
S = timeObject.getSeconds()
|
yshort = timeObject.getYear()-100
|
||||||
M = timeObject.getMinutes()
|
S = timeObject.getSeconds()
|
||||||
H = timeObject.getHours()
|
M = timeObject.getMinutes()
|
||||||
|
H = timeObject.getHours()
|
||||||
format = format
|
format = format
|
||||||
.replace(/dd/, s(d, 2))
|
.replace(/dd/, s(d, 2))
|
||||||
.replace(/d/, d)
|
.replace(/d/, d)
|
||||||
.replace(/mm/, s(m, 2))
|
.replace(/mm/, s(m, 2))
|
||||||
.replace(/m/, m)
|
.replace(/m/, m)
|
||||||
.replace(/yyyy/, y)
|
.replace(/yyyy/, yfull)
|
||||||
|
.replace(/yy/, yshort)
|
||||||
.replace(/SS/, s(S, 2))
|
.replace(/SS/, s(S, 2))
|
||||||
.replace(/MM/, s(M, 2))
|
.replace(/MM/, s(M, 2))
|
||||||
.replace(/HH/, s(H, 2))
|
.replace(/HH/, s(H, 2))
|
||||||
|
|
|
@ -20,37 +20,53 @@ class App.ImageService
|
||||||
imageWidth = imageObject.width
|
imageWidth = imageObject.width
|
||||||
imageHeight = imageObject.height
|
imageHeight = imageObject.height
|
||||||
console.log('ImageService', 'current size', imageWidth, imageHeight)
|
console.log('ImageService', 'current size', imageWidth, imageHeight)
|
||||||
|
console.log('ImageService', 'sizeFactor', sizeFactor)
|
||||||
if y is 'auto' && x is 'auto'
|
if y is 'auto' && x is 'auto'
|
||||||
x = imageWidth
|
x = imageWidth
|
||||||
y = imageHeight
|
y = imageHeight
|
||||||
|
|
||||||
|
# set max x/y
|
||||||
|
if x isnt 'auto' && x > imageWidth
|
||||||
|
x = imageWidth
|
||||||
|
|
||||||
|
if y isnt 'auto' && y > imageHeight
|
||||||
|
y = imageHeight
|
||||||
|
|
||||||
# get auto dimensions
|
# get auto dimensions
|
||||||
if y is 'auto'
|
if y is 'auto'# && (y * factor) >= imageHeight
|
||||||
factor = imageWidth / x
|
factor = imageWidth / x
|
||||||
y = imageHeight / factor
|
y = imageHeight / factor
|
||||||
|
|
||||||
if x is 'auto'
|
if x is 'auto'# && (y * factor) >= imageWidth
|
||||||
factor = imageWidth / y
|
factor = imageWidth / y
|
||||||
x = imageHeight / factor
|
x = imageHeight / factor
|
||||||
|
|
||||||
|
canvas = document.createElement('canvas')
|
||||||
|
|
||||||
# check if resize is needed
|
# check if resize is needed
|
||||||
resize = false
|
resize = false
|
||||||
if x < imageWidth || y < imageHeight
|
if (x < imageWidth && (x * sizeFactor < imageWidth)) || (y < imageHeight && (y * sizeFactor < imageHeight))
|
||||||
resize = true
|
resize = true
|
||||||
x = x * sizeFactor
|
x = x * sizeFactor
|
||||||
y = y * sizeFactor
|
y = y * sizeFactor
|
||||||
|
|
||||||
|
# set dimensions
|
||||||
|
canvas.width = x
|
||||||
|
canvas.height = y
|
||||||
|
|
||||||
|
# draw image on canvas and set image dimensions
|
||||||
|
context = canvas.getContext('2d')
|
||||||
|
context.drawImage(imageObject, 0, 0, x, y)
|
||||||
|
|
||||||
else
|
else
|
||||||
x = imageWidth
|
|
||||||
y = imageHeight
|
|
||||||
|
|
||||||
# create canvas and set dimensions
|
# set dimensions
|
||||||
canvas = document.createElement('canvas')
|
canvas.width = imageWidth
|
||||||
canvas.width = x
|
canvas.height = imageHeight
|
||||||
canvas.height = y
|
|
||||||
|
|
||||||
# draw image on canvas and set image dimensions
|
# draw image on canvas and set image dimensions
|
||||||
context = canvas.getContext('2d')
|
context = canvas.getContext('2d')
|
||||||
context.drawImage(imageObject, 0, 0, x, y)
|
context.drawImage(imageObject, 0, 0, imageWidth, imageHeight)
|
||||||
|
|
||||||
# set quallity based on image size
|
# set quallity based on image size
|
||||||
if quallity == 'auto'
|
if quallity == 'auto'
|
||||||
|
|
|
@ -5,6 +5,8 @@ class App.Utils
|
||||||
'TD': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'valign', 'width', 'style']
|
'TD': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'valign', 'width', 'style']
|
||||||
'TH': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'scope', 'sorted', 'valign', 'width', 'style']
|
'TH': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'scope', 'sorted', 'valign', 'width', 'style']
|
||||||
'TR': ['width', 'style']
|
'TR': ['width', 'style']
|
||||||
|
'A': ['href', 'hreflang', 'name', 'rel']
|
||||||
|
'IMG': ['align', 'alt', 'border', 'height', 'src', 'srcset', 'width', 'style']
|
||||||
|
|
||||||
@mapCss:
|
@mapCss:
|
||||||
'TABLE': [
|
'TABLE': [
|
||||||
|
@ -14,15 +16,9 @@ class App.Utils
|
||||||
'text-align',
|
'text-align',
|
||||||
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
'border-top-width',
|
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||||
'border-right-width',
|
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||||
'border-bottom-width',
|
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||||
'border-left-width',
|
|
||||||
|
|
||||||
'border-top-color',
|
|
||||||
'border-right-color',
|
|
||||||
'border-bottom-color',
|
|
||||||
'border-left-color',
|
|
||||||
]
|
]
|
||||||
'TH': [
|
'TH': [
|
||||||
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
@ -31,15 +27,10 @@ class App.Utils
|
||||||
'text-align',
|
'text-align',
|
||||||
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
'border-top-width',
|
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||||
'border-right-width',
|
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||||
'border-bottom-width',
|
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||||
'border-left-width',
|
|
||||||
|
|
||||||
'border-top-color',
|
|
||||||
'border-right-color',
|
|
||||||
'border-bottom-color',
|
|
||||||
'border-left-color',
|
|
||||||
]
|
]
|
||||||
'TR': [
|
'TR': [
|
||||||
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
@ -48,15 +39,10 @@ class App.Utils
|
||||||
'text-align',
|
'text-align',
|
||||||
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
'border-top-width',
|
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||||
'border-right-width',
|
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||||
'border-bottom-width',
|
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||||
'border-left-width',
|
|
||||||
|
|
||||||
'border-top-color',
|
|
||||||
'border-right-color',
|
|
||||||
'border-bottom-color',
|
|
||||||
'border-left-color',
|
|
||||||
]
|
]
|
||||||
'TD': [
|
'TD': [
|
||||||
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
'background', 'background-color', 'color', 'font-size', 'vertical-align',
|
||||||
|
@ -65,15 +51,13 @@ class App.Utils
|
||||||
'text-align',
|
'text-align',
|
||||||
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
|
||||||
|
|
||||||
'border-top-width',
|
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||||
'border-right-width',
|
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||||
'border-bottom-width',
|
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||||
'border-left-width',
|
|
||||||
|
|
||||||
'border-top-color',
|
]
|
||||||
'border-right-color',
|
'IMG': [
|
||||||
'border-bottom-color',
|
'width', 'height',
|
||||||
'border-left-color',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# textCleand = App.Utils.textCleanup(rawText)
|
# textCleand = App.Utils.textCleanup(rawText)
|
||||||
|
@ -230,7 +214,7 @@ class App.Utils
|
||||||
# remove comments
|
# remove comments
|
||||||
@_removeComments(html)
|
@_removeComments(html)
|
||||||
|
|
||||||
# remove work markup
|
# remove word markup
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
# remove tags, keep content
|
# remove tags, keep content
|
||||||
|
@ -251,7 +235,7 @@ class App.Utils
|
||||||
# remove comments
|
# remove comments
|
||||||
@_removeComments(html)
|
@_removeComments(html)
|
||||||
|
|
||||||
# remove work markup
|
# remove word markup
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
# remove tags, keep content
|
# remove tags, keep content
|
||||||
|
@ -275,11 +259,11 @@ class App.Utils
|
||||||
# remove comments
|
# remove comments
|
||||||
@_removeComments(html)
|
@_removeComments(html)
|
||||||
|
|
||||||
# remove work markup
|
# remove word markup
|
||||||
@_removeWordMarkup(html)
|
@_removeWordMarkup(html)
|
||||||
|
|
||||||
# remove tags, keep content
|
# remove tags, keep content
|
||||||
html.find('a, font, small, time, form, label').replaceWith( ->
|
html.find('font, small, time, form, label').replaceWith( ->
|
||||||
$(@).contents()
|
$(@).contents()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -303,7 +287,7 @@ 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, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset').remove()
|
||||||
|
|
||||||
# remove style and class
|
# remove style and class
|
||||||
@_cleanAttributes(html)
|
@_cleanAttributes(html)
|
||||||
|
@ -906,6 +890,29 @@ class App.Utils
|
||||||
text = text.replace(/http(s|):\/\/[-A-Za-z0-9+&@#\/%?=~_\|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]/img, placeholder)
|
text = text.replace(/http(s|):\/\/[-A-Za-z0-9+&@#\/%?=~_\|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]/img, placeholder)
|
||||||
text.length
|
text.length
|
||||||
|
|
||||||
|
@parseAddressListLocal: (line) ->
|
||||||
|
recipients = emailAddresses.parseAddressList(line)
|
||||||
|
result = []
|
||||||
|
if !_.isEmpty(recipients)
|
||||||
|
for recipient in recipients
|
||||||
|
if recipient && recipient.address
|
||||||
|
result.push recipient.address
|
||||||
|
return result
|
||||||
|
|
||||||
|
# workaround for email-addresses.js issue with this kind of
|
||||||
|
# mail headers "From: invalid sender, realname <sender@example.com>"
|
||||||
|
# email-addresses.js is returning null because it can't parse the
|
||||||
|
# whole header
|
||||||
|
if _.isEmpty(recipients) && line.match('@')
|
||||||
|
recipients = line.split(',')
|
||||||
|
re = /(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/
|
||||||
|
for recipient in recipients
|
||||||
|
if recipient && recipient.match('@')
|
||||||
|
localResult = recipient.match(re)
|
||||||
|
if localResult && localResult[0]
|
||||||
|
result.push localResult[0]
|
||||||
|
result
|
||||||
|
|
||||||
@getRecipientArticle: (ticket, article, article_created_by, type, email_addresses = [], all) ->
|
@getRecipientArticle: (ticket, article, article_created_by, type, email_addresses = [], all) ->
|
||||||
|
|
||||||
# empty form
|
# empty form
|
||||||
|
@ -954,16 +961,18 @@ class App.Utils
|
||||||
# check if article sender is local
|
# check if article sender is local
|
||||||
senderIsLocal = false
|
senderIsLocal = false
|
||||||
if !_.isEmpty(article.from)
|
if !_.isEmpty(article.from)
|
||||||
senders = emailAddresses.parseAddressList(article.from)
|
senders = App.Utils.parseAddressListLocal(article.from)
|
||||||
if senders && senders[0] && senders[0].address
|
if senders
|
||||||
senderIsLocal = isLocalAddress(senders[0].address)
|
for sender in senders
|
||||||
|
if sender && sender.match('@')
|
||||||
|
senderIsLocal = isLocalAddress(sender)
|
||||||
|
|
||||||
# check if article recipient is local
|
# check if article recipient is local
|
||||||
recipientIsLocal = false
|
recipientIsLocal = false
|
||||||
if !_.isEmpty(article.to)
|
if !_.isEmpty(article.to)
|
||||||
recipients = emailAddresses.parseAddressList(article.to)
|
recipients = App.Utils.parseAddressListLocal(article.to)
|
||||||
if recipients && recipients[0] && recipients[0].address
|
if recipients && recipients[0]
|
||||||
recipientIsLocal = isLocalAddress(recipients[0].address)
|
recipientIsLocal = isLocalAddress(recipients[0])
|
||||||
|
|
||||||
# sender is local
|
# sender is local
|
||||||
if senderIsLocal
|
if senderIsLocal
|
||||||
|
@ -987,14 +996,14 @@ class App.Utils
|
||||||
|
|
||||||
# filter for uniq recipients
|
# filter for uniq recipients
|
||||||
recipientAddresses = {}
|
recipientAddresses = {}
|
||||||
|
|
||||||
addAddresses = (addressLine, line) ->
|
addAddresses = (addressLine, line) ->
|
||||||
lineNew = ''
|
lineNew = ''
|
||||||
recipients = emailAddresses.parseAddressList(addressLine)
|
recipients = App.Utils.parseAddressListLocal(addressLine)
|
||||||
|
|
||||||
if !_.isEmpty(recipients)
|
if !_.isEmpty(recipients)
|
||||||
for recipient in recipients
|
for recipient in recipients
|
||||||
if !_.isEmpty(recipient.address)
|
if !_.isEmpty(recipient)
|
||||||
localRecipientAddress = recipient.address.toString().toLowerCase()
|
localRecipientAddress = recipient.toString().toLowerCase()
|
||||||
|
|
||||||
# check if address is not local
|
# check if address is not local
|
||||||
if !isLocalAddress(localRecipientAddress)
|
if !isLocalAddress(localRecipientAddress)
|
||||||
|
|
|
@ -161,7 +161,14 @@ window.linkify = (function(){
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push massaged link onto the array
|
// Push massaged link onto the array
|
||||||
parts.push([ link, href ]);
|
// 2018-10-30: me only link urls, not mailto link
|
||||||
|
//parts.push([ link, href ]);
|
||||||
|
if ( href && href.substr && href.substr(0,7) != 'mailto:') {
|
||||||
|
parts.push([ link, href ]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parts.push([ link, undefined ]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push remaining non-link text onto the array.
|
// Push remaining non-link text onto the array.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
// email-addresses.js - RFC 5322 email address parser
|
// email-addresses.js - RFC 5322 email address parser
|
||||||
// v 2.0.1
|
// v 3.0.1
|
||||||
//
|
//
|
||||||
// http://tools.ietf.org/html/rfc5322
|
// http://tools.ietf.org/html/rfc5322
|
||||||
//
|
//
|
||||||
|
@ -186,27 +186,7 @@ function parse5322(opts) {
|
||||||
// "First Last" -> "First Last"
|
// "First Last" -> "First Last"
|
||||||
// "First Last" -> "First Last"
|
// "First Last" -> "First Last"
|
||||||
function collapseWhitespace(s) {
|
function collapseWhitespace(s) {
|
||||||
function isWhitespace(c) {
|
return s.replace(/([ \t]|\r\n)+/g, ' ').replace(/^\s*/, '').replace(/\s*$/, '');
|
||||||
return c === ' ' ||
|
|
||||||
c === '\t' ||
|
|
||||||
c === '\r' ||
|
|
||||||
c === '\n';
|
|
||||||
}
|
|
||||||
var i, str;
|
|
||||||
str = "";
|
|
||||||
for (i = 0; i < s.length; i += 1) {
|
|
||||||
if (!isWhitespace(s[i]) || !isWhitespace(s[i + 1])) {
|
|
||||||
str += s[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isWhitespace(str[0])) {
|
|
||||||
str = str.substring(1);
|
|
||||||
}
|
|
||||||
if (isWhitespace(str[str.length - 1])) {
|
|
||||||
str = str.substring(0, str.length - 1);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTF-8 pseudo-production (RFC 6532)
|
// UTF-8 pseudo-production (RFC 6532)
|
||||||
|
@ -597,10 +577,14 @@ function parse5322(opts) {
|
||||||
return wrap('domain', function domainCheckTLD() {
|
return wrap('domain', function domainCheckTLD() {
|
||||||
var result = or(obsDomain, dotAtom, domainLiteral)();
|
var result = or(obsDomain, dotAtom, domainLiteral)();
|
||||||
if (opts.rejectTLD) {
|
if (opts.rejectTLD) {
|
||||||
if (result.semantic.indexOf('.') < 0) {
|
if (result && result.semantic && result.semantic.indexOf('.') < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// strip all whitespace from domains
|
||||||
|
if (result) {
|
||||||
|
result.semantic = result.semantic.replace(/\s+/g, '');
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
@ -612,6 +596,36 @@ function parse5322(opts) {
|
||||||
)());
|
)());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.6.2 Originator Fields
|
||||||
|
// Below we only parse the field body, not the name of the field
|
||||||
|
// like "From:", "Sender:", or "Reply-To:". Other libraries that
|
||||||
|
// parse email headers can parse those and defer to these productions
|
||||||
|
// for the "RFC 5322" part.
|
||||||
|
|
||||||
|
// RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
|
||||||
|
// from = "From:" (mailbox-list / address-list) CRLF
|
||||||
|
function fromSpec() {
|
||||||
|
return wrap('from', or(
|
||||||
|
mailboxList,
|
||||||
|
addressList
|
||||||
|
)());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
|
||||||
|
// sender = "Sender:" (mailbox / address) CRLF
|
||||||
|
function senderSpec() {
|
||||||
|
return wrap('sender', or(
|
||||||
|
mailbox,
|
||||||
|
address
|
||||||
|
)());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
|
||||||
|
// reply-to = "Reply-To:" address-list CRLF
|
||||||
|
function replyToSpec() {
|
||||||
|
return wrap('reply-to', addressList());
|
||||||
|
}
|
||||||
|
|
||||||
// 4.1. Miscellaneous Obsolete Tokens
|
// 4.1. Miscellaneous Obsolete Tokens
|
||||||
|
|
||||||
// obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
|
// obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
|
||||||
|
@ -766,92 +780,186 @@ function parse5322(opts) {
|
||||||
// ast analysis
|
// ast analysis
|
||||||
|
|
||||||
function findNode(name, root) {
|
function findNode(name, root) {
|
||||||
var i, queue, node;
|
var i, stack, node;
|
||||||
if (root === null || root === undefined) { return null; }
|
if (root === null || root === undefined) { return null; }
|
||||||
queue = [root];
|
stack = [root];
|
||||||
while (queue.length > 0) {
|
while (stack.length > 0) {
|
||||||
node = queue.shift();
|
node = stack.pop();
|
||||||
if (node.name === name) {
|
if (node.name === name) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
for (i = 0; i < node.children.length; i += 1) {
|
for (i = node.children.length - 1; i >= 0; i -= 1) {
|
||||||
queue.push(node.children[i]);
|
stack.push(node.children[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAllNodes(name, root) {
|
function findAllNodes(name, root) {
|
||||||
var i, queue, node, result;
|
var i, stack, node, result;
|
||||||
if (root === null || root === undefined) { return null; }
|
if (root === null || root === undefined) { return null; }
|
||||||
queue = [root];
|
stack = [root];
|
||||||
result = [];
|
result = [];
|
||||||
while (queue.length > 0) {
|
while (stack.length > 0) {
|
||||||
node = queue.shift();
|
node = stack.pop();
|
||||||
if (node.name === name) {
|
if (node.name === name) {
|
||||||
result.push(node);
|
result.push(node);
|
||||||
}
|
}
|
||||||
for (i = 0; i < node.children.length; i += 1) {
|
for (i = node.children.length - 1; i >= 0; i -= 1) {
|
||||||
queue.push(node.children[i]);
|
stack.push(node.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAllNodesNoChildren(names, root) {
|
||||||
|
var i, stack, node, result, namesLookup;
|
||||||
|
if (root === null || root === undefined) { return null; }
|
||||||
|
stack = [root];
|
||||||
|
result = [];
|
||||||
|
namesLookup = {};
|
||||||
|
for (i = 0; i < names.length; i += 1) {
|
||||||
|
namesLookup[names[i]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
node = stack.pop();
|
||||||
|
if (node.name in namesLookup) {
|
||||||
|
result.push(node);
|
||||||
|
// don't look at children (hence findAllNodesNoChildren)
|
||||||
|
} else {
|
||||||
|
for (i = node.children.length - 1; i >= 0; i -= 1) {
|
||||||
|
stack.push(node.children[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function giveResult(ast) {
|
function giveResult(ast) {
|
||||||
function grabSemantic(n) {
|
var addresses, groupsAndMailboxes, i, groupOrMailbox, result;
|
||||||
return n !== null ? n.semantic : null;
|
|
||||||
}
|
|
||||||
var i, ret, addresses, addr, name, aspec, local, domain;
|
|
||||||
if (ast === null) {
|
if (ast === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ret = { ast: ast };
|
addresses = [];
|
||||||
addresses = findAllNodes('address', ast);
|
|
||||||
ret.addresses = [];
|
|
||||||
for (i = 0; i < addresses.length; i += 1) {
|
|
||||||
addr = addresses[i];
|
|
||||||
name = findNode('display-name', addr);
|
|
||||||
aspec = findNode('addr-spec', addr);
|
|
||||||
local = findNode('local-part', aspec);
|
|
||||||
domain = findNode('domain', aspec);
|
|
||||||
ret.addresses.push({
|
|
||||||
node: addr,
|
|
||||||
parts: {
|
|
||||||
name: name,
|
|
||||||
address: aspec,
|
|
||||||
local: local,
|
|
||||||
domain: domain
|
|
||||||
},
|
|
||||||
name: grabSemantic(name),
|
|
||||||
address: grabSemantic(aspec),
|
|
||||||
local: grabSemantic(local),
|
|
||||||
domain: grabSemantic(domain)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.simple) {
|
// An address is a 'group' (i.e. a list of mailboxes) or a 'mailbox'.
|
||||||
ret = ret.addresses;
|
groupsAndMailboxes = findAllNodesNoChildren(['group', 'mailbox'], ast);
|
||||||
for (i = 0; i < ret.length; i += 1) {
|
for (i = 0; i < groupsAndMailboxes.length; i += 1) {
|
||||||
delete ret[i].node;
|
groupOrMailbox = groupsAndMailboxes[i];
|
||||||
|
if (groupOrMailbox.name === 'group') {
|
||||||
|
addresses.push(giveResultGroup(groupOrMailbox));
|
||||||
|
} else if (groupOrMailbox.name === 'mailbox') {
|
||||||
|
addresses.push(giveResultMailbox(groupOrMailbox));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
result = {
|
||||||
|
ast: ast,
|
||||||
|
addresses: addresses,
|
||||||
|
};
|
||||||
|
if (opts.simple) {
|
||||||
|
result = simplifyResult(result);
|
||||||
|
}
|
||||||
|
if (opts.oneResult) {
|
||||||
|
return oneResult(result);
|
||||||
|
}
|
||||||
|
if (opts.simple) {
|
||||||
|
return result && result.addresses;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function giveResultGroup(group) {
|
||||||
|
var i;
|
||||||
|
var groupName = findNode('display-name', group);
|
||||||
|
var groupResultMailboxes = [];
|
||||||
|
var mailboxes = findAllNodesNoChildren(['mailbox'], group);
|
||||||
|
for (i = 0; i < mailboxes.length; i += 1) {
|
||||||
|
groupResultMailboxes.push(giveResultMailbox(mailboxes[i]));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
node: group,
|
||||||
|
parts: {
|
||||||
|
name: groupName,
|
||||||
|
},
|
||||||
|
type: group.name, // 'group'
|
||||||
|
name: grabSemantic(groupName),
|
||||||
|
addresses: groupResultMailboxes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function giveResultMailbox(mailbox) {
|
||||||
|
var name = findNode('display-name', mailbox);
|
||||||
|
var aspec = findNode('addr-spec', mailbox);
|
||||||
|
var comments = findAllNodes('cfws', mailbox);
|
||||||
|
|
||||||
|
var local = findNode('local-part', aspec);
|
||||||
|
var domain = findNode('domain', aspec);
|
||||||
|
return {
|
||||||
|
node: mailbox,
|
||||||
|
parts: {
|
||||||
|
name: name,
|
||||||
|
address: aspec,
|
||||||
|
local: local,
|
||||||
|
domain: domain,
|
||||||
|
comments: comments
|
||||||
|
},
|
||||||
|
type: mailbox.name, // 'mailbox'
|
||||||
|
name: grabSemantic(name),
|
||||||
|
address: grabSemantic(aspec),
|
||||||
|
local: grabSemantic(local),
|
||||||
|
domain: grabSemantic(domain),
|
||||||
|
groupName: grabSemantic(mailbox.groupName),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function grabSemantic(n) {
|
||||||
|
return n !== null && n !== undefined ? n.semantic : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simplifyResult(result) {
|
||||||
|
var i;
|
||||||
|
if (result && result.addresses) {
|
||||||
|
for (i = 0; i < result.addresses.length; i += 1) {
|
||||||
|
delete result.addresses[i].node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function oneResult(result) {
|
||||||
|
if (!result) { return null; }
|
||||||
|
if (!opts.partial && result.addresses.length > 1) { return null; }
|
||||||
|
return result.addresses && result.addresses[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
var parseString, pos, len, parsed;
|
var parseString, pos, len, parsed, startProduction;
|
||||||
|
|
||||||
opts = handleOpts(opts, {});
|
opts = handleOpts(opts, {});
|
||||||
if (opts === null) { return null; }
|
if (opts === null) { return null; }
|
||||||
|
|
||||||
parseString = opts.input;
|
parseString = opts.input;
|
||||||
|
|
||||||
|
startProduction = {
|
||||||
|
'address': address,
|
||||||
|
'address-list': addressList,
|
||||||
|
'angle-addr': angleAddr,
|
||||||
|
'from': fromSpec,
|
||||||
|
'group': group,
|
||||||
|
'mailbox': mailbox,
|
||||||
|
'mailbox-list': mailboxList,
|
||||||
|
'reply-to': replyToSpec,
|
||||||
|
'sender': senderSpec,
|
||||||
|
}[opts.startAt] || addressList;
|
||||||
|
|
||||||
if (!opts.strict) {
|
if (!opts.strict) {
|
||||||
initialize();
|
initialize();
|
||||||
opts.strict = true;
|
opts.strict = true;
|
||||||
parsed = addressList(parseString);
|
parsed = startProduction(parseString);
|
||||||
if (opts.partial || !inStr()) {
|
if (opts.partial || !inStr()) {
|
||||||
return giveResult(parsed);
|
return giveResult(parsed);
|
||||||
}
|
}
|
||||||
|
@ -859,46 +967,51 @@ function parse5322(opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
parsed = addressList(parseString);
|
parsed = startProduction(parseString);
|
||||||
if (!opts.partial && inStr()) { return null; }
|
if (!opts.partial && inStr()) { return null; }
|
||||||
return giveResult(parsed);
|
return giveResult(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseOneAddressSimple(opts) {
|
function parseOneAddressSimple(opts) {
|
||||||
var result;
|
return parse5322(handleOpts(opts, {
|
||||||
|
oneResult: true,
|
||||||
opts = handleOpts(opts, {
|
|
||||||
rfc6532: true,
|
rfc6532: true,
|
||||||
simple: true
|
simple: true,
|
||||||
});
|
startAt: 'address-list',
|
||||||
if (opts === null) { return null; }
|
}));
|
||||||
|
|
||||||
result = parse5322(opts);
|
|
||||||
|
|
||||||
if ((!result) ||
|
|
||||||
(!opts.partial &&
|
|
||||||
(opts.simple && result.length > 1) ||
|
|
||||||
(!opts.simple && result.addresses.length > 1))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts.simple ?
|
|
||||||
result && result[0] :
|
|
||||||
result && result.addresses && result.addresses[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAddressListSimple(opts) {
|
function parseAddressListSimple(opts) {
|
||||||
var result;
|
return parse5322(handleOpts(opts, {
|
||||||
|
|
||||||
opts = handleOpts(opts, {
|
|
||||||
rfc6532: true,
|
rfc6532: true,
|
||||||
simple: true
|
simple: true,
|
||||||
});
|
startAt: 'address-list',
|
||||||
if (opts === null) { return null; }
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
result = parse5322(opts);
|
function parseFromSimple(opts) {
|
||||||
|
return parse5322(handleOpts(opts, {
|
||||||
|
rfc6532: true,
|
||||||
|
simple: true,
|
||||||
|
startAt: 'from',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return opts.simple ? result : result.addresses;
|
function parseSenderSimple(opts) {
|
||||||
|
return parse5322(handleOpts(opts, {
|
||||||
|
oneResult: true,
|
||||||
|
rfc6532: true,
|
||||||
|
simple: true,
|
||||||
|
startAt: 'sender',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseReplyToSimple(opts) {
|
||||||
|
return parse5322(handleOpts(opts, {
|
||||||
|
rfc6532: true,
|
||||||
|
simple: true,
|
||||||
|
startAt: 'reply-to',
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOpts(opts, defs) {
|
function handleOpts(opts, defs) {
|
||||||
|
@ -926,24 +1039,28 @@ function handleOpts(opts, defs) {
|
||||||
if (!defs) { return null; }
|
if (!defs) { return null; }
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
rfc6532: false,
|
oneResult: false,
|
||||||
partial: false,
|
partial: false,
|
||||||
|
rejectTLD: false,
|
||||||
|
rfc6532: false,
|
||||||
simple: false,
|
simple: false,
|
||||||
|
startAt: 'address-list',
|
||||||
strict: false,
|
strict: false,
|
||||||
rejectTLD: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (o in defaults) {
|
for (o in defaults) {
|
||||||
if (isNullUndef(opts[o])) {
|
if (isNullUndef(opts[o])) {
|
||||||
opts[o] = !isNullUndef(defs[o]) ? defs[o] : defaults[o];
|
opts[o] = !isNullUndef(defs[o]) ? defs[o] : defaults[o];
|
||||||
}
|
}
|
||||||
opts[o] = !!opts[o];
|
|
||||||
}
|
}
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
parse5322.parseOneAddress = parseOneAddressSimple;
|
parse5322.parseOneAddress = parseOneAddressSimple;
|
||||||
parse5322.parseAddressList = parseAddressListSimple;
|
parse5322.parseAddressList = parseAddressListSimple;
|
||||||
|
parse5322.parseFrom = parseFromSimple;
|
||||||
|
parse5322.parseSender = parseSenderSimple;
|
||||||
|
parse5322.parseReplyTo = parseReplyToSimple;
|
||||||
|
|
||||||
// in Zammad context, go back to non CommonJS
|
// in Zammad context, go back to non CommonJS
|
||||||
// if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
// if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||||
|
|
|
@ -289,15 +289,16 @@
|
||||||
var result = e.target.result
|
var result = e.target.result
|
||||||
var img = document.createElement('img')
|
var img = document.createElement('img')
|
||||||
img.src = result
|
img.src = result
|
||||||
|
maxWidth = _this.$element.width() || 500
|
||||||
|
scaleFactor = 2
|
||||||
|
//scaleFactor = 1
|
||||||
|
//if (window.isRetina && window.isRetina()) {
|
||||||
|
// scaleFactor = 2
|
||||||
|
//}
|
||||||
|
|
||||||
insert = function(dataUrl, width, height, isRetina) {
|
insert = function(dataUrl, width, height, isResized) {
|
||||||
//console.log('dataUrl', dataUrl)
|
//console.log('dataUrl', dataUrl)
|
||||||
|
//console.log('scaleFactor', scaleFactor, isResized, maxWidth, width, height)
|
||||||
// adapt image if we are on retina devices
|
|
||||||
if (!isRetina && window.isRetina && window.isRetina()) {
|
|
||||||
width = width / 2
|
|
||||||
height = height / 2
|
|
||||||
}
|
|
||||||
_this.log('image inserted')
|
_this.log('image inserted')
|
||||||
result = dataUrl
|
result = dataUrl
|
||||||
if (_this.options.imageWidth == 'absolute') {
|
if (_this.options.imageWidth == 'absolute') {
|
||||||
|
@ -310,7 +311,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize if to big
|
// resize if to big
|
||||||
App.ImageService.resize(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
|
App.ImageService.resize(img.src, maxWidth, 'auto', scaleFactor, 'image/jpeg', 'auto', insert)
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(imageFile)
|
reader.readAsDataURL(imageFile)
|
||||||
imageInserted = true
|
imageInserted = true
|
||||||
|
@ -416,17 +417,18 @@
|
||||||
var result = e.target.result
|
var result = e.target.result
|
||||||
var img = document.createElement('img')
|
var img = document.createElement('img')
|
||||||
img.src = result
|
img.src = result
|
||||||
|
maxWidth = _this.$element.width() || 500
|
||||||
|
scaleFactor = 2
|
||||||
|
//scaleFactor = 1
|
||||||
|
//if (window.isRetina && window.isRetina()) {
|
||||||
|
// scaleFactor = 2
|
||||||
|
//}
|
||||||
|
|
||||||
//Insert the image at the carat
|
//Insert the image at the carat
|
||||||
insert = function(dataUrl, width, height, isRetina) {
|
insert = function(dataUrl, width, height, isResized) {
|
||||||
|
|
||||||
// adapt image if we are on retina devices
|
|
||||||
if (!isRetina && window.isRetina && window.isRetina()) {
|
|
||||||
width = width / 2
|
|
||||||
height = height / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log('dataUrl', dataUrl)
|
//console.log('dataUrl', dataUrl)
|
||||||
|
//console.log('scaleFactor', scaleFactor, isResized, maxWidth, width, height)
|
||||||
_this.log('image inserted')
|
_this.log('image inserted')
|
||||||
result = dataUrl
|
result = dataUrl
|
||||||
if (_this.options.imageWidth == 'absolute') {
|
if (_this.options.imageWidth == 'absolute') {
|
||||||
|
@ -454,7 +456,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize if to big
|
// resize if to big
|
||||||
App.ImageService.resize(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
|
App.ImageService.resize(img.src, maxWidth, 'auto', scaleFactor, 'image/jpeg', 'auto', insert)
|
||||||
})
|
})
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,9 @@ window.word_filter = function(editor){
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$('[style]', editor).removeAttr('style');
|
// style and align is handled by utils.coffee it self, don't clean it here
|
||||||
$('[align]', editor).removeAttr('align');
|
//$('[style]', editor).removeAttr('style');
|
||||||
|
//$('[align]', editor).removeAttr('align');
|
||||||
$('span', editor).replaceWith(function() {return $(this).contents();});
|
$('span', editor).replaceWith(function() {return $(this).contents();});
|
||||||
$('span:empty', editor).remove();
|
$('span:empty', editor).remove();
|
||||||
$("[class^='Mso']", editor).removeAttr('class');
|
$("[class^='Mso']", editor).removeAttr('class');
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
- allow custom template as options parameter
|
- allow custom template as options parameter
|
||||||
- fix that place method doesn't think that the container is the window, but rather the real window is the window
|
- fix that place method doesn't think that the container is the window, but rather the real window is the window
|
||||||
- added rerender method to show correct today if task is longer open the 24 hours
|
- added rerender method to show correct today if task is longer open the 24 hours
|
||||||
|
- scroll into view
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function(factory){
|
(function(factory){
|
||||||
|
@ -515,7 +516,9 @@
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
this.setValue();
|
this.setValue();
|
||||||
this._trigger('hide');
|
// 2018-01-22 trigger locale hide event - conflicts with modal hide
|
||||||
|
//this._trigger('hide');
|
||||||
|
this._trigger('hide.bs.datepicker');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -757,6 +760,16 @@
|
||||||
zIndex: zIndex
|
zIndex: zIndex
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjust scroll of scrollParent
|
||||||
|
var scrollParent = this.picker.scrollParent();
|
||||||
|
var bottomEdge = offset.top + height + this.picker.outerHeight();
|
||||||
|
var scrollBottomEdge = scrollParent.scrollTop() + scrollParent.height();
|
||||||
|
|
||||||
|
if(bottomEdge > scrollBottomEdge){
|
||||||
|
scrollParent.scrollTop(scrollParent.scrollTop() + (bottomEdge - scrollBottomEdge) + 10);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -387,15 +387,17 @@ set new attributes of model (remove already available attributes)
|
||||||
=>
|
=>
|
||||||
return if _.isEmpty(@SUBSCRIPTION_COLLECTION)
|
return if _.isEmpty(@SUBSCRIPTION_COLLECTION)
|
||||||
App.Log.debug('Model', "server notify collection change #{@className}")
|
App.Log.debug('Model', "server notify collection change #{@className}")
|
||||||
@fetchFull(
|
callback = =>
|
||||||
->
|
@fetchFull(
|
||||||
clear: true
|
->
|
||||||
)
|
clear: true
|
||||||
|
)
|
||||||
|
App.Delay.set(callback, 200, "full-#{@className}")
|
||||||
|
|
||||||
"Collection::Subscribe::#{@className}"
|
"Collection::Subscribe::#{@className}"
|
||||||
)
|
)
|
||||||
|
|
||||||
key = @className + '-' + Math.floor( Math.random() * 99999 )
|
key = "#{@className}-#{Math.floor(Math.random() * 99999)}"
|
||||||
@SUBSCRIPTION_COLLECTION[key] = callback
|
@SUBSCRIPTION_COLLECTION[key] = callback
|
||||||
|
|
||||||
# fetch init collection
|
# fetch init collection
|
||||||
|
|
|
@ -1,16 +1,260 @@
|
||||||
class App.Chat extends App.Model
|
class App.Chat extends App.Model
|
||||||
@configure 'Chat', 'name', 'active', 'public', 'max_queue', 'note'
|
@configure 'Chat', 'name', 'active', 'public', 'max_queue', 'block_ip', 'block_country', 'note'
|
||||||
@extend Spine.Model.Ajax
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/chats'
|
@url: @apiPath + '/chats'
|
||||||
|
@countries:
|
||||||
|
AF: 'Afghanistan'
|
||||||
|
AL: 'Albania'
|
||||||
|
DZ: 'Algeria'
|
||||||
|
AS: 'American Samoa'
|
||||||
|
AD: 'Andorra'
|
||||||
|
AO: 'Angola'
|
||||||
|
AI: 'Anguilla'
|
||||||
|
AQ: 'Antarctica'
|
||||||
|
AG: 'Antigua And Barbuda'
|
||||||
|
AR: 'Argentina'
|
||||||
|
AM: 'Armenia'
|
||||||
|
AW: 'Aruba'
|
||||||
|
AU: 'Australia'
|
||||||
|
AT: 'Austria'
|
||||||
|
AZ: 'Azerbaijan'
|
||||||
|
BS: 'Bahamas'
|
||||||
|
BH: 'Bahrain'
|
||||||
|
BD: 'Bangladesh'
|
||||||
|
BB: 'Barbados'
|
||||||
|
BY: 'Belarus'
|
||||||
|
BE: 'Belgium'
|
||||||
|
BZ: 'Belize'
|
||||||
|
BJ: 'Benin'
|
||||||
|
BM: 'Bermuda'
|
||||||
|
BT: 'Bhutan'
|
||||||
|
BO: 'Bolivia'
|
||||||
|
BA: 'Bosnia And Herzegovina'
|
||||||
|
BW: 'Botswana'
|
||||||
|
BV: 'Bouvet Island'
|
||||||
|
BR: 'Brazil'
|
||||||
|
IO: 'British Indian Ocean Territory'
|
||||||
|
BN: 'Brunei Darussalam'
|
||||||
|
BG: 'Bulgaria'
|
||||||
|
BF: 'Burkina Faso'
|
||||||
|
BI: 'Burundi'
|
||||||
|
KH: 'Cambodia'
|
||||||
|
CM: 'Cameroon'
|
||||||
|
CA: 'Canada'
|
||||||
|
CV: 'Cape Verde'
|
||||||
|
KY: 'Cayman Islands'
|
||||||
|
CF: 'Central African Republic'
|
||||||
|
TD: 'Chad'
|
||||||
|
CL: 'Chile'
|
||||||
|
CN: 'China'
|
||||||
|
CX: 'Christmas Island'
|
||||||
|
CC: 'Cocos (keeling) Islands'
|
||||||
|
CO: 'Colombia'
|
||||||
|
KM: 'Comoros'
|
||||||
|
CG: 'Congo'
|
||||||
|
CD: 'Congo, The Democratic Republic Of The'
|
||||||
|
CK: 'Cook Islands'
|
||||||
|
CR: 'Costa Rica'
|
||||||
|
CI: 'Cote D\'ivoire'
|
||||||
|
HR: 'Croatia'
|
||||||
|
CU: 'Cuba'
|
||||||
|
CY: 'Cyprus'
|
||||||
|
CZ: 'Czech Republic'
|
||||||
|
DK: 'Denmark'
|
||||||
|
DJ: 'Djibouti'
|
||||||
|
DM: 'Dominica'
|
||||||
|
DO: 'Dominican Republic'
|
||||||
|
TP: 'East Timor'
|
||||||
|
EC: 'Ecuador'
|
||||||
|
EG: 'Egypt'
|
||||||
|
SV: 'El Salvador'
|
||||||
|
GQ: 'Equatorial Guinea'
|
||||||
|
ER: 'Eritrea'
|
||||||
|
EE: 'Estonia'
|
||||||
|
ET: 'Ethiopia'
|
||||||
|
FK: 'Falkland Islands (malvinas)'
|
||||||
|
FO: 'Faroe Islands'
|
||||||
|
FJ: 'Fiji'
|
||||||
|
FI: 'Finland'
|
||||||
|
FR: 'France'
|
||||||
|
GF: 'French Guiana'
|
||||||
|
PF: 'French Polynesia'
|
||||||
|
TF: 'French Southern Territories'
|
||||||
|
GA: 'Gabon'
|
||||||
|
GM: 'Gambia'
|
||||||
|
GE: 'Georgia'
|
||||||
|
DE: 'Germany'
|
||||||
|
GH: 'Ghana'
|
||||||
|
GI: 'Gibraltar'
|
||||||
|
GR: 'Greece'
|
||||||
|
GL: 'Greenland'
|
||||||
|
GD: 'Grenada'
|
||||||
|
GP: 'Guadeloupe'
|
||||||
|
GU: 'Guam'
|
||||||
|
GT: 'Guatemala'
|
||||||
|
GN: 'Guinea'
|
||||||
|
GW: 'Guinea-bissau'
|
||||||
|
GY: 'Guyana'
|
||||||
|
HT: 'Haiti'
|
||||||
|
HM: 'Heard Island And Mcdonald Islands'
|
||||||
|
VA: 'Holy See (vatican City State)'
|
||||||
|
HN: 'Honduras'
|
||||||
|
HK: 'Hong Kong'
|
||||||
|
HU: 'Hungary'
|
||||||
|
IS: 'Iceland'
|
||||||
|
IN: 'India'
|
||||||
|
ID: 'Indonesia'
|
||||||
|
IR: 'Iran, Islamic Republic Of'
|
||||||
|
IQ: 'Iraq'
|
||||||
|
IE: 'Ireland'
|
||||||
|
IL: 'Israel'
|
||||||
|
IT: 'Italy'
|
||||||
|
JM: 'Jamaica'
|
||||||
|
JP: 'Japan'
|
||||||
|
JO: 'Jordan'
|
||||||
|
KZ: 'Kazakstan'
|
||||||
|
KE: 'Kenya'
|
||||||
|
KI: 'Kiribati'
|
||||||
|
KP: 'Korea, Democratic People\'s Republic Of'
|
||||||
|
KR: 'Korea, Republic Of'
|
||||||
|
KV: 'Kosovo'
|
||||||
|
KW: 'Kuwait'
|
||||||
|
KG: 'Kyrgyzstan'
|
||||||
|
LA: 'Lao People\'s Democratic Republic'
|
||||||
|
LV: 'Latvia'
|
||||||
|
LB: 'Lebanon'
|
||||||
|
LS: 'Lesotho'
|
||||||
|
LR: 'Liberia'
|
||||||
|
LY: 'Libyan Arab Jamahiriya'
|
||||||
|
LI: 'Liechtenstein'
|
||||||
|
LT: 'Lithuania'
|
||||||
|
LU: 'Luxembourg'
|
||||||
|
MO: 'Macau'
|
||||||
|
MK: 'Macedonia, The Former Yugoslav Republic Of'
|
||||||
|
MG: 'Madagascar'
|
||||||
|
MW: 'Malawi'
|
||||||
|
MY: 'Malaysia'
|
||||||
|
MV: 'Maldives'
|
||||||
|
ML: 'Mali'
|
||||||
|
MT: 'Malta'
|
||||||
|
MH: 'Marshall Islands'
|
||||||
|
MQ: 'Martinique'
|
||||||
|
MR: 'Mauritania'
|
||||||
|
MU: 'Mauritius'
|
||||||
|
YT: 'Mayotte'
|
||||||
|
MX: 'Mexico'
|
||||||
|
FM: 'Micronesia, Federated States Of'
|
||||||
|
MD: 'Moldova, Republic Of'
|
||||||
|
MC: 'Monaco'
|
||||||
|
MN: 'Mongolia'
|
||||||
|
MS: 'Montserrat'
|
||||||
|
ME: 'Montenegro'
|
||||||
|
MA: 'Morocco'
|
||||||
|
MZ: 'Mozambique'
|
||||||
|
MM: 'Myanmar'
|
||||||
|
NA: 'Namibia'
|
||||||
|
NR: 'Nauru'
|
||||||
|
NP: 'Nepal'
|
||||||
|
NL: 'Netherlands'
|
||||||
|
AN: 'Netherlands Antilles'
|
||||||
|
NC: 'New Caledonia'
|
||||||
|
NZ: 'New Zealand'
|
||||||
|
NI: 'Nicaragua'
|
||||||
|
NE: 'Niger'
|
||||||
|
NG: 'Nigeria'
|
||||||
|
NU: 'Niue'
|
||||||
|
NF: 'Norfolk Island'
|
||||||
|
MP: 'Northern Mariana Islands'
|
||||||
|
NO: 'Norway'
|
||||||
|
OM: 'Oman'
|
||||||
|
PK: 'Pakistan'
|
||||||
|
PW: 'Palau'
|
||||||
|
PS: 'Palestinian Territory, Occupied'
|
||||||
|
PA: 'Panama'
|
||||||
|
PG: 'Papua New Guinea'
|
||||||
|
PY: 'Paraguay'
|
||||||
|
PE: 'Peru'
|
||||||
|
PH: 'Philippines'
|
||||||
|
PN: 'Pitcairn'
|
||||||
|
PL: 'Poland'
|
||||||
|
PT: 'Portugal'
|
||||||
|
PR: 'Puerto Rico'
|
||||||
|
QA: 'Qatar'
|
||||||
|
RE: 'Reunion'
|
||||||
|
RO: 'Romania'
|
||||||
|
RU: 'Russian Federation'
|
||||||
|
RW: 'Rwanda'
|
||||||
|
SH: 'Saint Helena'
|
||||||
|
KN: 'Saint Kitts And Nevis'
|
||||||
|
LC: 'Saint Lucia'
|
||||||
|
PM: 'Saint Pierre And Miquelon'
|
||||||
|
VC: 'Saint Vincent And The Grenadines'
|
||||||
|
WS: 'Samoa'
|
||||||
|
SM: 'San Marino'
|
||||||
|
ST: 'Sao Tome And Principe'
|
||||||
|
SA: 'Saudi Arabia'
|
||||||
|
SN: 'Senegal'
|
||||||
|
RS: 'Serbia'
|
||||||
|
SC: 'Seychelles'
|
||||||
|
SL: 'Sierra Leone'
|
||||||
|
SG: 'Singapore'
|
||||||
|
SK: 'Slovakia'
|
||||||
|
SI: 'Slovenia'
|
||||||
|
SB: 'Solomon Islands'
|
||||||
|
SO: 'Somalia'
|
||||||
|
ZA: 'South Africa'
|
||||||
|
GS: 'South Georgia And The South Sandwich Islands'
|
||||||
|
ES: 'Spain'
|
||||||
|
LK: 'Sri Lanka'
|
||||||
|
SD: 'Sudan'
|
||||||
|
SR: 'Suriname'
|
||||||
|
SJ: 'Svalbard And Jan Mayen'
|
||||||
|
SZ: 'Swaziland'
|
||||||
|
SE: 'Sweden'
|
||||||
|
CH: 'Switzerland'
|
||||||
|
SY: 'Syrian Arab Republic'
|
||||||
|
TW: 'Taiwan, Province Of China'
|
||||||
|
TJ: 'Tajikistan'
|
||||||
|
TZ: 'Tanzania, United Republic Of'
|
||||||
|
TH: 'Thailand'
|
||||||
|
TG: 'Togo'
|
||||||
|
TK: 'Tokelau'
|
||||||
|
TO: 'Tonga'
|
||||||
|
TT: 'Trinidad And Tobago'
|
||||||
|
TN: 'Tunisia'
|
||||||
|
TR: 'Turkey'
|
||||||
|
TM: 'Turkmenistan'
|
||||||
|
TC: 'Turks And Caicos Islands'
|
||||||
|
TV: 'Tuvalu'
|
||||||
|
UG: 'Uganda'
|
||||||
|
UA: 'Ukraine'
|
||||||
|
AE: 'United Arab Emirates'
|
||||||
|
GB: 'United Kingdom'
|
||||||
|
US: 'United States'
|
||||||
|
UM: 'United States Minor Outlying Islands'
|
||||||
|
UY: 'Uruguay'
|
||||||
|
UZ: 'Uzbekistan'
|
||||||
|
VU: 'Vanuatu'
|
||||||
|
VE: 'Venezuela'
|
||||||
|
VN: 'Viet Nam'
|
||||||
|
VG: 'Virgin Islands, British'
|
||||||
|
VI: 'Virgin Islands, U.s.'
|
||||||
|
WF: 'Wallis And Futuna'
|
||||||
|
EH: 'Western Sahara'
|
||||||
|
YE: 'Yemen'
|
||||||
|
ZM: 'Zambia'
|
||||||
|
ZW: 'Zimbabwe'
|
||||||
|
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
{ 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: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
||||||
#{ name: 'public', display: 'Public', tag: 'boolean', default: true },
|
{ name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 },
|
||||||
{ name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 },
|
{ name: 'block_ip', display: 'Blocked IPs (separated by ;)', tag: 'input', default: '', null: true },
|
||||||
|
{ name: 'block_country', display: 'Blocked countries', tag: 'column_select', multiple: true, null: true, default: '', options: @countries, seperator: ';' },
|
||||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
32
app/assets/javascripts/app/models/chat_sessions.coffee
Normal file
32
app/assets/javascripts/app/models/chat_sessions.coffee
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
class App.ChatSession extends App.Model
|
||||||
|
@configure 'ChatSession', 'name', 'note'
|
||||||
|
@extend Spine.Model.Ajax
|
||||||
|
@url: @apiPath + '/chat_sessions'
|
||||||
|
|
||||||
|
@configure_attributes = [
|
||||||
|
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false }
|
||||||
|
{ name: 'state', display: 'State', readonly: 1 }
|
||||||
|
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }
|
||||||
|
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }
|
||||||
|
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }
|
||||||
|
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }
|
||||||
|
]
|
||||||
|
|
||||||
|
@configure_overview = [
|
||||||
|
'name',
|
||||||
|
'state',
|
||||||
|
'created_at',
|
||||||
|
]
|
||||||
|
|
||||||
|
uiUrl: ->
|
||||||
|
"#customer_chat/session/#{@id}"
|
||||||
|
|
||||||
|
searchResultAttributes: ->
|
||||||
|
displayName = ''
|
||||||
|
if !_.isEmpty(@name)
|
||||||
|
displayName = @displayName()
|
||||||
|
display: "##{@id} #{displayName}"
|
||||||
|
id: @id
|
||||||
|
class: 'chat_session chat_session-popover'
|
||||||
|
url: @uiUrl()
|
||||||
|
icon: 'chat'
|
|
@ -1,5 +1,5 @@
|
||||||
class App.Overview extends App.Model
|
class App.Overview extends App.Model
|
||||||
@configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'view', 'user_ids', 'organization_shared', 'role_ids', 'order', 'group_by', 'active', 'updated_at'
|
@configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'view', 'user_ids', 'organization_shared', 'role_ids', 'active'
|
||||||
@extend Spine.Model.Ajax
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/overviews'
|
@url: @apiPath + '/overviews'
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
|
|
|
@ -200,6 +200,26 @@ class App.Ticket extends App.Model
|
||||||
result = true if objectValue.toString().match(contains_regex)
|
result = true if objectValue.toString().match(contains_regex)
|
||||||
else if condition.operator == 'contains not'
|
else if condition.operator == 'contains not'
|
||||||
result = true if !objectValue.toString().match(contains_regex)
|
result = true if !objectValue.toString().match(contains_regex)
|
||||||
|
else if condition.operator == 'contains all'
|
||||||
|
result = true
|
||||||
|
for loopConditionValue in conditionValue
|
||||||
|
if !_.contains(objectValue, loopConditionValue)
|
||||||
|
result = false
|
||||||
|
else if condition.operator == 'contains one'
|
||||||
|
result = false
|
||||||
|
for loopConditionValue in conditionValue
|
||||||
|
if _.contains(objectValue, loopConditionValue)
|
||||||
|
result = true
|
||||||
|
else if condition.operator == 'contains all not'
|
||||||
|
result = true
|
||||||
|
for loopObjectValue in objectValue
|
||||||
|
if _.contains(conditionValue, loopObjectValue)
|
||||||
|
result = false
|
||||||
|
else if condition.operator == 'contains one not'
|
||||||
|
result = false
|
||||||
|
for loopObjectValue in objectValue
|
||||||
|
if !_.contains(conditionValue, loopObjectValue)
|
||||||
|
result = true
|
||||||
else if condition.operator == 'is'
|
else if condition.operator == 'is'
|
||||||
result = true if objectValue.toString().trim().toLowerCase() is loopConditionValue.toString().trim().toLowerCase()
|
result = true if objectValue.toString().trim().toLowerCase() is loopConditionValue.toString().trim().toLowerCase()
|
||||||
else if condition.operator == 'is not'
|
else if condition.operator == 'is not'
|
||||||
|
@ -224,3 +244,19 @@ class App.Ticket extends App.Model
|
||||||
throw "Unknown operator: #{condition.operator}"
|
throw "Unknown operator: #{condition.operator}"
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
||||||
|
editable: (permission = 'change') ->
|
||||||
|
user_id = App.Session.get('id')
|
||||||
|
return true if user_id is @customer_id
|
||||||
|
group_ids = App.Session.get('group_ids')
|
||||||
|
if group_ids
|
||||||
|
return true if group_ids[@group_id] && (_.include(group_ids[@group_id], permission) || _.include(group_ids[@group_id], 'full'))
|
||||||
|
role_ids = App.Session.get('role_ids')
|
||||||
|
if role_ids
|
||||||
|
for role_id in role_ids
|
||||||
|
if App.Role.exists(role_id)
|
||||||
|
role = App.Role.find(role_id)
|
||||||
|
if role.group_ids
|
||||||
|
return true if role.group_ids[@group_id] && (_.include(role.group_ids[@group_id], permission) || _.include(role.group_ids[@group_id], 'full'))
|
||||||
|
false
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,31 @@
|
||||||
<div class="newTicket">
|
<div class="newTicket">
|
||||||
<div class="box box--newTicket">
|
<div class="box box--newTicket">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1><%- @T( @head ) %></h1>
|
<h1><%- @T(@head) %></h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<ul class="tabs type-tabs">
|
<ul class="tabs type-tabs">
|
||||||
|
|
||||||
<li class="tab u-textTruncate" data-type="phone-in">
|
<li class="tab u-textTruncate" data-type="phone-in">
|
||||||
<%- @Icon('received-calls', 'tab-icon') %>
|
<%- @Icon('received-calls', 'tab-icon') %>
|
||||||
<%- @T('Received Call') %>
|
<%- @T('Received Call') %>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="tab u-textTruncate" data-type="phone-out">
|
<li class="tab u-textTruncate" data-type="phone-out">
|
||||||
<%- @Icon('outbound-calls', 'tab-icon') %>
|
<%- @Icon('outbound-calls', 'tab-icon') %>
|
||||||
<%- @T('Outbound Call') %>
|
<%- @T('Outbound Call') %>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="tab u-textTruncate" data-type="email-out">
|
||||||
<li class="tab u-textTruncate" data-type="email-out">
|
|
||||||
<%- @Icon('email', 'tab-icon') %>
|
<%- @Icon('email', 'tab-icon') %>
|
||||||
<%- @T('Send Email') %>
|
<%- @T('Send Email') %>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<% if !_.isEmpty(@C('ui_ticket_create_notes')): %>
|
||||||
|
<% for type, note of @C('ui_ticket_create_notes'): %>
|
||||||
|
<div class="alert alert--warning js-note" role="alert" data-type="<%= type %>"><%- @T(note) %></div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<form role="form" class="ticket-create">
|
<form role="form" class="ticket-create">
|
||||||
<input type="hidden" name="formSenderType"/>
|
<input type="hidden" name="formSenderType"/>
|
||||||
<input type="hidden" name="form_id" value="<%= @form_id %>"/>
|
<input type="hidden" name="form_id" value="<%= @form_id %>"/>
|
||||||
|
@ -46,7 +49,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tabsSidebar vertical"></div>
|
<div class="tabsSidebar vertical"></div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<pre><code class="language-html js-paramsBlock"><button id="feedback-form">Feedback</button>
|
<pre class="js-modal"><code class="language-html js-code js-paramsBlock"><button id="feedback-form">Feedback</button>
|
||||||
|
|
||||||
<script id="zammad_form_script" src="<%= @baseurl %>/assets/form/form.js"></script>
|
<script id="zammad_form_script" src="<%= @baseurl %>/assets/form/form.js"></script>
|
||||||
|
|
||||||
|
@ -144,3 +144,16 @@ $(function() {
|
||||||
});
|
});
|
||||||
</script></code></pre>
|
</script></code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<pre class="js-inlineForm"><code class="language-html js-code js-paramsBlock"><div id="feedback-form">form will be placed in here</div>
|
||||||
|
|
||||||
|
<script id="zammad_form_script" src="<%= @baseurl %>/assets/form/form.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
$('#feedback-form').ZammadForm({
|
||||||
|
<span class="js-modal-params"></span>
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script></code></pre>
|
||||||
|
</div>
|
|
@ -1,5 +1,3 @@
|
||||||
<!--
|
|
||||||
<div class="chat-footer">
|
<div class="chat-footer">
|
||||||
<div class="btn btn--primary js-createTicket">Turn chat into ticket</div>
|
<div class="btn btn--primary js-createTicket"><%- @T('Turn chat into ticket') %></div>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
|
@ -7,9 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-name">
|
<div class="chat-name">
|
||||||
<%= @name %> <div class="status-badge js-info">
|
<span class="js-name js-info u-clickable"><%= @name %><span> #<%= @session.id %>
|
||||||
<div class="info-badge"><%- @Icon('info') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-disconnect js-disconnect">
|
<div class="chat-disconnect js-disconnect">
|
||||||
<div class="btn btn--action btn--small"><%- @T('disconnect') %></div>
|
<div class="btn btn--action btn--small"><%- @T('disconnect') %></div>
|
||||||
|
@ -24,6 +22,25 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-body-holder js-scrollHolder">
|
<div class="chat-body-holder js-scrollHolder">
|
||||||
<div class="chat-body js-body"></div>
|
<div class="chat-body js-body"></div>
|
||||||
|
<div class="chat-body js-meta hidden">
|
||||||
|
<% if @session: %>
|
||||||
|
<ul>
|
||||||
|
<li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %></li>
|
||||||
|
<% if @session && @session.preferences: %>
|
||||||
|
<% if @session.preferences.geo_ip: %>
|
||||||
|
<li>GeoIP: <%= @session.preferences.geo_ip.country_name %> <%= @session.preferences.geo_ip.city_name %></li>
|
||||||
|
<% end %>
|
||||||
|
<% if @session.preferences.remote_ip: %>
|
||||||
|
<li>IP: <%= @session.preferences.remote_ip %></li>
|
||||||
|
<% end %>
|
||||||
|
<% if @session.preferences.dns_name: %>
|
||||||
|
<li>DNS: <%= @session.preferences.dns_name %></li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
<form class="js-metaForm" style="max-width: 200px; width: 100%;"></form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-controls">
|
<div class="chat-controls">
|
||||||
<div class="chat-input">
|
<div class="chat-input">
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<hr>
|
|
||||||
<ul>
|
|
||||||
<% if @session: %>
|
|
||||||
<li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %>
|
|
||||||
<% end %>
|
|
||||||
<% if @session && @session.preferences: %>
|
|
||||||
<% if @session.preferences.geo_ip: %>
|
|
||||||
<li>GeoIP: <%= @session.preferences.geo_ip.country_name %> <%= @session.preferences.geo_ip.city_name %>
|
|
||||||
<% end %>
|
|
||||||
<% if @session.preferences.remote_ip: %>
|
|
||||||
<li>IP: <%= @session.preferences.remote_ip %>
|
|
||||||
<% end %>
|
|
||||||
<% if @session.preferences.dns_name: %>
|
|
||||||
<li>DNS: <%= @session.preferences.dns_name %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="attachment">
|
<div class="attachment">
|
||||||
<div class="attachment-name"><%= @fileName %></div>
|
<div class="attachment-name"><%= @filename %></div>
|
||||||
<div class="attachment-size"><%= @fileSize %></div>
|
<div class="attachment-size"><%= @humanFileSize(@size) %></div>
|
||||||
<div class="attachment-delete js-delete" data-id="<%= @store_id %>">
|
<div class="attachment-delete js-delete" data-id="<%= @id %>">
|
||||||
<%- @Icon('diagonal-cross') %><%- @T('Delete File') %>
|
<%- @Icon('diagonal-cross') %><%- @T('Delete File') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,3 +1,6 @@
|
||||||
|
<% if @attribute.seperator: %>
|
||||||
|
<input class="js-shadow hide" id="<%= @attribute.id %>" name="<%= @attribute.name %>" value="<%= @attribute.value %>">
|
||||||
|
<% else: %>
|
||||||
<select
|
<select
|
||||||
class="columnSelect-shadow js-shadow"
|
class="columnSelect-shadow js-shadow"
|
||||||
id="<%= @attribute.id %>"
|
id="<%= @attribute.id %>"
|
||||||
|
@ -11,6 +14,7 @@
|
||||||
<option value="<%= option.value %>" <%= ' selected' if option.selected %>><%= option.name %></option>
|
<option value="<%= option.value %>" <%= ' selected' if option.selected %>><%= option.name %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
|
<% end %>
|
||||||
<div class="columnSelect-column columnSelect-column--selected js-selected" data-name="<%= @attribute.name %>">
|
<div class="columnSelect-column columnSelect-column--selected js-selected" data-name="<%= @attribute.name %>">
|
||||||
<div class="u-placeholder u-unselectable js-placeholder<%= ' is-hidden' if @values.length %>"><%- @T('Nothing selected') %></div>
|
<div class="u-placeholder u-unselectable js-placeholder<%= ' is-hidden' if @values.length %>"><%- @T('Nothing selected') %></div>
|
||||||
<% for option in @attribute.options: %>
|
<% for option in @attribute.options: %>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<% for item in @items: %>
|
<% for item in @items: %>
|
||||||
<div class="sidebar bottom-form-shadow flex hide" data-tab="<%= item.name %>">
|
<div class="sidebar bottom-form-shadow flex hide" data-tab="<%= item.name %>">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<h2 class="sidebar-header-headline js-headline"><%- @T(item.head) %></h2>
|
<h2 class="sidebar-header-headline js-headline"><%- @T(item.sidebarHead) %></h2>
|
||||||
<div class="sidebar-header-actions js-actions"></div>
|
<div class="sidebar-header-actions js-actions"></div>
|
||||||
<div class="tabsSidebar-close">
|
<div class="tabsSidebar-close">
|
||||||
<%- @Icon('long-arrow-right') %>
|
<%- @Icon('long-arrow-right') %>
|
||||||
|
@ -13,8 +13,6 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="tabsSidebar-tabs" style="<%- if @dir is 'rtl' then 'margin-right' else 'margin-left' %>: -<%- @scrollbarWidth %>px">
|
<div class="tabsSidebar-tabs" style="<%- if @dir is 'rtl' then 'margin-right' else 'margin-left' %>: -<%- @scrollbarWidth %>px">
|
||||||
<% for item in @items: %>
|
<% for item in @items: %>
|
||||||
<div class="tabsSidebar-tab" data-tab="<%= item.name %>">
|
<div class="tabsSidebar-tab" data-tab="<%= item.name %>"></div>
|
||||||
<%- @Icon(item.icon) %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<% if @counterPossible is true: %>
|
||||||
|
<div class="tabsSidebar-tab-count js-tabCounter <% if !@counter || @counter is 0: %>hide<% end %><% if @cssClass: %><%= @cssClass %><% end %>"><%= @counter %></div>
|
||||||
|
<% end %>
|
||||||
|
<%- @Icon(@icon) %>
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="js-pager"></div>
|
<div class="js-pager"></div>
|
||||||
<table class="table table-hover<%- " #{@class}" if @class %>">
|
<table class="table table-hover<% if @class: %> <%= @class %><% end %>">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<% if @sortable: %>
|
<% if @sortable: %>
|
||||||
|
@ -19,14 +19,10 @@
|
||||||
<th style="width: 40px" class="table-radio"></th>
|
<th style="width: 40px" class="table-radio"></th>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% for header, i in @headers: %>
|
<% for header, i in @headers: %>
|
||||||
<th class="js-tableHead<%= " #{ header.className }" if header.className %><%= " align-#{ header.align }" if header.align %>" style="<% if header.displayWidth: %>width:<%= header.displayWidth %>px<% end %>" data-column-key="<%= header.name %>">
|
<th class="js-tableHead<% if header.className: %> <%= header.className %><% end %><% if header.align: %> align-<%= header.align %><% end %>" style="<% if header.displayWidth: %>width:<%= header.displayWidth %>px<% end %>" data-column-key="<%= header.name %>">
|
||||||
<div class="table-column-head<%= ' js-sort' if @tableId %>">
|
<div class="table-column-head<%= ' js-sort' if @tableId %>">
|
||||||
<div class="table-column-title"><%- @T(header.display) %></div>
|
<div class="table-column-title"><%- @T(header.display) %></div>
|
||||||
<div class="table-column-sortIcon">
|
<div class="table-column-sortIcon"><% if header.sortOrderIcon: %><%- @Icon(header.sortOrderIcon[0], header.sortOrderIcon[1]) %><% end %></div>
|
||||||
<% if header.sortOrderIcon: %>
|
|
||||||
<%- @Icon(header.sortOrderIcon[0], header.sortOrderIcon[1]) %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<% if @tableId && !header.unresizable && i < @headers.length - 1: %>
|
<% if @tableId && !header.unresizable && i < @headers.length - 1: %>
|
||||||
<div class="table-col-resize js-col-resize"></div>
|
<div class="table-col-resize js-col-resize"></div>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</td>
|
</td>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% for header in @headers: %>
|
<% for header in @headers: %>
|
||||||
<% value = @P(@object, header.name, @attributes) %>
|
<% value = @P(@object, header.name, @attributes, true) %>
|
||||||
<% if @callbacks: %>
|
<% if @callbacks: %>
|
||||||
<% for attribute, callbacksAll of @callbacks: %>
|
<% for attribute, callbacksAll of @callbacks: %>
|
||||||
<% if attribute is header.name: %>
|
<% if attribute is header.name: %>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<td<%- " class='#{ header.parentClass }'" if header.parentClass %><%- " title='#{ header.title }'" if header.title %><%- " style='text-align:#{ header.align }'" if header.align %>>
|
<td<% if header.parentClass: %> class="<%= header.parentClass %>"<% end %><% if header.title: %> title="<%= header.title %>"<% end %><% if header.align: %> style="text-align:<%= header.align %>"<% end %>>
|
||||||
<% if header.name is 'icon': %>
|
<% if header.name is 'icon': %>
|
||||||
<%- @Icon('task-state', header.class) %>
|
<%- @Icon('task-state', header.class) %>
|
||||||
<% else if header.icon: %>
|
<% else if header.icon: %>
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
<p>
|
<p>
|
||||||
<%- @T('Download and install the %s Migration Plugin on your %s instance.', 'OTRS', 'OTRS') %>:
|
<%- @T('Download and install the %s Migration Plugin on your %s instance.', 'OTRS', 'OTRS') %>:
|
||||||
</p>
|
</p>
|
||||||
<a class="btn btn--primary btn--download js-download" target=_blank href="https://portal.znuny.com/api/addon_repos/public/617/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 5') %></a>
|
<a class="btn btn--primary btn--download js-download" target=_blank href="https://addons.znuny.com/api/addon_repos/public/1085/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 6') %></a>
|
||||||
<a class="btn btn--primary btn--download js-download" target=_blank href="https://portal.znuny.com/api/addon_repos/public/383/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 4') %></a>
|
<a class="btn btn--primary btn--download js-download" target=_blank href="https://addons.znuny.com/api/addon_repos/public/617/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 5') %></a>
|
||||||
<a class="btn btn--primary btn--download js-download" target=_blank href="https://portal.znuny.com/api/addon_repos/public/287/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 3.3-3.1') %></a>
|
<a class="btn btn--primary btn--download js-download" target=_blank href="https://addons.znuny.com/api/addon_repos/public/383/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 4') %></a>
|
||||||
|
<a class="btn btn--primary btn--download js-download" target=_blank href="https://addons.znuny.com/api/addon_repos/public/287/latest" download><%- @Icon('download') %> <%- @T('Migration Plugin for %s', 'OTRS 3.3-3.1') %></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="wizard-controls horizontal center">
|
<div class="wizard-controls horizontal center">
|
||||||
<a class="btn btn--text btn--secondary" href="#import"><%- @T('Go Back') %></a>
|
<a class="btn btn--text btn--secondary" href="#import"><%- @T('Go Back') %></a>
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<div class="alert alert--info hide js-ticket-count-info" role="alert"><%- @T("There are more than 1000 tickets in the Zendesk system. Due to API rate limit restrictions we can't get the exact number of tickets yet and have to fetch them in batches of 1000. This might take some time, better grab a cup of coffee. The total number of tickets gets updated as soon as the currently known number is surpassed.") %></div>
|
<div class="alert alert--info hide js-ticket-count-info" role="alert"><%- @T("There are more than 1000 tickets in the Zendesk system. Due to API rate limit restrictions we can't get the exact number of tickets yet and have to fetch them in batches of 1000. This might take some time, better grab a cup of coffee. The total number of tickets gets updated as soon as the currently known number is surpassed.") %></div>
|
||||||
<div class="wizard-body flex vertical justified">
|
<div class="wizard-body flex vertical justified">
|
||||||
<table class="progressTable">
|
<table class="progressTable">
|
||||||
<tr class="js-group">
|
<tr class="js-groups">
|
||||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||||
<td><span><%- @T('Groups') %></span>
|
<td><span><%- @T('Groups') %></span>
|
||||||
<td class="progressTable-progressCell">
|
<td class="progressTable-progressCell">
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
<%- @Icon('checkmark') %>
|
<%- @Icon('checkmark') %>
|
||||||
</div>
|
</div>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="js-organization">
|
<tr class="js-organizations">
|
||||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||||
<td><span><%- @T('Organizations') %></span>
|
<td><span><%- @T('Organizations') %></span>
|
||||||
<td class="progressTable-progressCell">
|
<td class="progressTable-progressCell">
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
<%- @Icon('checkmark') %>
|
<%- @Icon('checkmark') %>
|
||||||
</div>
|
</div>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="js-user">
|
<tr class="js-users">
|
||||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||||
<td><span><%- @T('Users') %></span>
|
<td><span><%- @T('Users') %></span>
|
||||||
<td class="progressTable-progressCell">
|
<td class="progressTable-progressCell">
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
<%- @Icon('checkmark') %>
|
<%- @Icon('checkmark') %>
|
||||||
</div>
|
</div>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="js-ticket">
|
<tr class="js-tickets">
|
||||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||||
<td><span><%- @T('Tickets') %></span>
|
<td><span><%- @T('Tickets') %></span>
|
||||||
<td class="progressTable-progressCell">
|
<td class="progressTable-progressCell">
|
||||||
|
|
|
@ -20,18 +20,18 @@
|
||||||
<% if @job.result && @job.result.error: %>
|
<% if @job.result && @job.result.error: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %></p>
|
<p><%- @Ttimestamp(@job.started_at) %></p>
|
||||||
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
||||||
<% else if !@countDone: %>
|
<% else if @job.result && !@job.result.sum: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Counting entries. This may take a while.') %></p>
|
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Counting entries. This may take a while.') %></p>
|
||||||
<% else: %>
|
<% else: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Running...') %></p>
|
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Running...') %></p>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<progress max="<%= @job.result.sum %>" value="<%= @countDone %>"></progress>
|
<progress max="<%= @job.result.total %>" value="<%= @job.result.sum %>"></progress>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !_.isEmpty(@job.result) && @countDone: %>
|
<% if !_.isEmpty(@job.result) && @job.result.sum: %>
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @countDone %>/<%= @job.result.sum %>):
|
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @job.result.sum %>/<%= @job.result.total %>):
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @countDone %>):
|
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @job.result.total %>):
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -5,19 +5,17 @@
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<th style="width: 30px"></th>
|
<th style="width: 30px"></th>
|
||||||
<th style="width: 50px"><%- @T('ID') %></th>
|
<th style="width: 100px"><%- @T('ID') %></th>
|
||||||
<th><%- @T('Name') %></th>
|
<th><%- @T('Name') %></th>
|
||||||
<th><%- @T('Status') %></th>
|
<th style="width: 100px;"><%- @T('Status') %></th>
|
||||||
<th><%- @T('Link') %></th>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% for item in @items: %>
|
<% for item in @items: %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" name="object_id" value="<%= item.id %>"/></td>
|
<td><input type="checkbox" name="object_id" value="<%= item.id %>"/></td>
|
||||||
<td><%= item.id %></td>
|
<td title="<%= item.id %>"><%= item.id %></td>
|
||||||
<td><%= item.title %></td>
|
<td title="<%= item.title %>"><a href="<%- item.link %>" target="_blank"><%= item.title %></a></td>
|
||||||
<td><%= item.cmdb_status_title %></td>
|
<td><%= item.cmdb_status_title %></td>
|
||||||
<td><a href="<%- item.link %>" target="_blank">i-doit</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -20,18 +20,18 @@
|
||||||
<% if @job.result && @job.result.error: %>
|
<% if @job.result && @job.result.error: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %></p>
|
<p><%- @Ttimestamp(@job.started_at) %></p>
|
||||||
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
||||||
<% else if !@countDone: %>
|
<% else if !@job.result.sum: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Counting entries. This may take a while.') %></p>
|
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Counting entries. This may take a while.') %></p>
|
||||||
<% else: %>
|
<% else: %>
|
||||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Running...') %></p>
|
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Running...') %></p>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<progress max="<%= @job.result.sum %>" value="<%= @countDone %>"></progress>
|
<progress max="<%= @job.result.total %>" value="<%= @job.result.sum %>"></progress>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !_.isEmpty(@job.result) && @countDone: %>
|
<% if !_.isEmpty(@job.result) && @job.result.sum: %>
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('%s user to %s user', 'LDAP', 'Zammad') %> (<%= @countDone %>/<%= @job.result.sum %>):
|
<li><%- @T('%s user to %s user', 'LDAP', 'Zammad') %> (<%= @job.result.sum %>/<%= @job.result.total %>):
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>, <%= @job.result.deactivated %> <%- @T('deactivated') %>
|
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>, <%= @job.result.deactivated %> <%- @T('deactivated') %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('%s user to %s user', 'LDAP', 'Zammad') %> (<%= @countDone %>):
|
<li><%- @T('%s user to %s user', 'LDAP', 'Zammad') %> (<%= @job.result.sum %>):
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>, <%= @job.result.deactivated %> <%- @T('deactivated') %>
|
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>, <%= @job.result.deactivated %> <%- @T('deactivated') %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -440,7 +440,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="textBubble-footer">
|
<div class="textBubble-footer">
|
||||||
<div class="textBubble-signatur"><span class="js-signature">/je</span></div>
|
<div class="textBubble-signatur"><span class="js-signature">/je</span></div>
|
||||||
<div class="textBubble-letterCount js-letterCount">140</div>
|
<div class="textBubble-letterCount js-letterCount">280</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<a class="btn btn--action" href="<%- @downloadUrl %>" target="_blank" data-type="attachment"><%- @Icon('download') %><span><%- @T('Download %s record(s)', @count) %></span></a>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue