Merge branch 'develop' of github.com:martini/zammad into develop
This commit is contained in:
commit
20abe7ff87
20 changed files with 610 additions and 323 deletions
|
@ -443,25 +443,11 @@ class App.Controller extends Spine.Controller
|
||||||
}
|
}
|
||||||
processData: true,
|
processData: true,
|
||||||
success: (data, status, xhr) ->
|
success: (data, status, xhr) ->
|
||||||
App.SessionStorage.set( "user-ticket-popover::#{params.user_id}", data )
|
|
||||||
|
|
||||||
# load assets
|
|
||||||
App.Collection.loadAssets( data.assets )
|
App.Collection.loadAssets( data.assets )
|
||||||
|
|
||||||
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
|
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
|
||||||
)
|
)
|
||||||
|
|
||||||
# get data
|
# get data
|
||||||
data = App.SessionStorage.get( "user-ticket-popover::#{params.user_id}" )
|
|
||||||
if data
|
|
||||||
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
|
|
||||||
@delay(
|
|
||||||
->
|
|
||||||
fetch(params)
|
|
||||||
1000
|
|
||||||
'fetch'
|
|
||||||
)
|
|
||||||
else
|
|
||||||
fetch(params)
|
fetch(params)
|
||||||
|
|
||||||
userTicketPopupsDestroy: =>
|
userTicketPopupsDestroy: =>
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
class App.ChannelChat extends App.Controller
|
class App.ChannelChat extends App.Controller
|
||||||
events:
|
events:
|
||||||
'click .js-add': 'new'
|
|
||||||
'click .js-edit': 'edit'
|
|
||||||
'click .js-remove': 'remove'
|
|
||||||
'click .js-widget': 'widget'
|
|
||||||
'change .js-params': 'updateParams'
|
'change .js-params': 'updateParams'
|
||||||
'input .js-params': 'updateParams'
|
'input .js-params': 'updateParams'
|
||||||
'submit .js-demo-head': 'onUrlSubmit'
|
'submit .js-demo-head': 'onUrlSubmit'
|
||||||
|
@ -11,6 +7,7 @@ class App.ChannelChat extends App.Controller
|
||||||
'click .js-selectBrowserWidth': 'selectBrowserWidth'
|
'click .js-selectBrowserWidth': 'selectBrowserWidth'
|
||||||
'click .js-swatch': 'usePaletteColor'
|
'click .js-swatch': 'usePaletteColor'
|
||||||
'click .js-toggle-chat': 'toggleChat'
|
'click .js-toggle-chat': 'toggleChat'
|
||||||
|
'click .js-chatSetting': 'toggleChatSetting'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-browser': 'browser'
|
'.js-browser': 'browser'
|
||||||
|
@ -27,13 +24,14 @@ class App.ChannelChat extends App.Controller
|
||||||
'.js-code': 'code'
|
'.js-code': 'code'
|
||||||
'.js-palette': 'palette'
|
'.js-palette': 'palette'
|
||||||
'.js-color': 'colorField'
|
'.js-color': 'colorField'
|
||||||
|
'.js-chatSetting': 'chatSetting'
|
||||||
|
|
||||||
apiOptions: [
|
apiOptions: [
|
||||||
{
|
{
|
||||||
name: 'channel'
|
name: 'chatId'
|
||||||
default: "'default'"
|
default: '1'
|
||||||
type: 'String'
|
type: 'Number'
|
||||||
description: 'Name of the chat-channel.'
|
description: 'Identifier of the chat-topic.'
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name: 'show'
|
name: 'show'
|
||||||
|
@ -54,18 +52,18 @@ class App.ChannelChat extends App.Controller
|
||||||
description: "If left empty, the host gets auto-detected - in this case %s. The auto-detection reads out the host from the <script> tag. If you don't include it via a <script> tag you need to specify the host."
|
description: "If left empty, the host gets auto-detected - in this case %s. The auto-detection reads out the host from the <script> tag. If you don't include it via a <script> tag you need to specify the host."
|
||||||
descriptionSubstitute: window.location.origin
|
descriptionSubstitute: window.location.origin
|
||||||
}
|
}
|
||||||
{
|
|
||||||
name: 'port'
|
|
||||||
default: 6042
|
|
||||||
type: 'Int'
|
|
||||||
description: ''
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
name: 'debug'
|
name: 'debug'
|
||||||
default: false
|
default: false
|
||||||
type: 'Boolean'
|
type: 'Boolean'
|
||||||
description: 'Enables console logging.'
|
description: 'Enables console logging.'
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name: 'title'
|
||||||
|
default: "'<strong>Chat</strong> with us!'"
|
||||||
|
type: 'String'
|
||||||
|
description: 'Welcome Title shown on the closed chat. Can contain HTML.'
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name: 'fontSize'
|
name: 'fontSize'
|
||||||
default: 'undefined'
|
default: 'undefined'
|
||||||
|
@ -75,7 +73,7 @@ class App.ChannelChat extends App.Controller
|
||||||
{
|
{
|
||||||
name: 'flat'
|
name: 'flat'
|
||||||
default: 'false'
|
default: 'false'
|
||||||
type: 'boolean'
|
type: 'Boolean'
|
||||||
description: 'Removes the shadows for a flat look.'
|
description: 'Removes the shadows for a flat look.'
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -91,18 +89,29 @@ class App.ChannelChat extends App.Controller
|
||||||
description: 'This class gets added to the button on initialization and gets removed once the chat connection got established.'
|
description: 'This class gets added to the button on initialization and gets removed once the chat connection got established.'
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name: 'title'
|
name: 'cssAutoload'
|
||||||
default: "'<strong>Chat</strong> with us!'"
|
default: 'true'
|
||||||
|
type: 'Boolean'
|
||||||
|
description: 'Automatically loads the chat.css file. If you want to use your own css, just set it to false.'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: 'cssUrl'
|
||||||
|
default: 'undefined'
|
||||||
type: 'String'
|
type: 'String'
|
||||||
description: 'Welcome Title shown on the closed chat. Can contain HTML.'
|
description: 'Location of an external chat.css file.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
isOpen: true
|
isOpen: true
|
||||||
browserWidth: 1280
|
browserWidth: 1280
|
||||||
|
previewUrl: ''
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@title 'Chat'
|
||||||
|
if @Session.get('email')
|
||||||
|
@previewUrl = "www.#{@Session.get('email').replace(/^.+?\@/, '')}"
|
||||||
|
|
||||||
@load()
|
@load()
|
||||||
|
|
||||||
@widgetDesignerPermanentParams =
|
@widgetDesignerPermanentParams =
|
||||||
|
@ -124,15 +133,15 @@ class App.ChannelChat extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
render: (data = {}) =>
|
render: (data = {}) =>
|
||||||
|
|
||||||
chats = []
|
|
||||||
for chat_id in data.chat_ids
|
|
||||||
chats.push App.Chat.find(chat_id)
|
|
||||||
|
|
||||||
@html App.view('channel/chat')(
|
@html App.view('channel/chat')(
|
||||||
baseurl: window.location.origin
|
baseurl: window.location.origin
|
||||||
chats: chats
|
|
||||||
apiOptions: @apiOptions
|
apiOptions: @apiOptions
|
||||||
|
previewUrl: @previewUrl
|
||||||
|
chatSetting: @Config.get('chat')
|
||||||
|
)
|
||||||
|
|
||||||
|
new Topics(
|
||||||
|
el: @$('.js-topics')
|
||||||
)
|
)
|
||||||
|
|
||||||
@code.each (i, block) ->
|
@code.each (i, block) ->
|
||||||
|
@ -236,7 +245,7 @@ class App.ChannelChat extends App.Controller
|
||||||
palette = _.map palette, tinycolor
|
palette = _.map palette, tinycolor
|
||||||
|
|
||||||
# filter white
|
# filter white
|
||||||
palette = _.filter palette, (color) =>
|
palette = _.filter palette, (color) ->
|
||||||
color.getLuminance() < 0.85
|
color.getLuminance() < 0.85
|
||||||
|
|
||||||
htmlString = ''
|
htmlString = ''
|
||||||
|
@ -264,47 +273,12 @@ class App.ChannelChat extends App.Controller
|
||||||
@isOpen = @chat.hasClass('is-open')
|
@isOpen = @chat.hasClass('is-open')
|
||||||
@updatePreview()
|
@updatePreview()
|
||||||
|
|
||||||
new: (e) =>
|
toggleChatSetting: =>
|
||||||
new App.ControllerGenericNew(
|
value = @chatSetting.prop('checked')
|
||||||
pageData:
|
setting = App.Setting.findByAttribute('name', 'chat')
|
||||||
title: 'Chats'
|
setting.state_current = { value: value }
|
||||||
object: 'Chat'
|
setting.save()
|
||||||
objects: 'Chats'
|
@Config.set('chat', value)
|
||||||
genericObject: 'Chat'
|
|
||||||
callback: @load
|
|
||||||
container: @el.closest('.content')
|
|
||||||
large: true
|
|
||||||
)
|
|
||||||
|
|
||||||
edit: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
id = $(e.target).closest('tr').data('id')
|
|
||||||
new App.ControllerGenericEdit(
|
|
||||||
id: id
|
|
||||||
genericObject: 'Chat'
|
|
||||||
pageData:
|
|
||||||
object: 'Chat'
|
|
||||||
container: @el.closest('.content')
|
|
||||||
callback: @load
|
|
||||||
)
|
|
||||||
|
|
||||||
remove: (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
id = $(e.target).closest('tr').data('id')
|
|
||||||
item = App.Chat.find(id)
|
|
||||||
new App.ControllerGenericDestroyConfirm(
|
|
||||||
item: item
|
|
||||||
container: @el.closest('.content')
|
|
||||||
callback: @load
|
|
||||||
)
|
|
||||||
|
|
||||||
widget: (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
id = $(e.target).closest('.action').data('id')
|
|
||||||
new Widget(
|
|
||||||
permanent:
|
|
||||||
id: id
|
|
||||||
)
|
|
||||||
|
|
||||||
updateParams: =>
|
updateParams: =>
|
||||||
quote = (value) ->
|
quote = (value) ->
|
||||||
|
@ -347,4 +321,53 @@ class App.ChannelChat extends App.Controller
|
||||||
@paramsBlock.each (i, block) ->
|
@paramsBlock.each (i, block) ->
|
||||||
hljs.highlightBlock block
|
hljs.highlightBlock block
|
||||||
|
|
||||||
App.Config.set( 'Chat Widget', { prio: 4000, name: 'Chat Widget', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
|
|
||||||
|
class Topics extends App.Controller
|
||||||
|
events:
|
||||||
|
'click .js-add': 'new'
|
||||||
|
'click .js-edit': 'edit'
|
||||||
|
'click .js-remove': 'remove'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
@html App.view('channel/topics')(
|
||||||
|
chats: App.Chat.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
new: (e) =>
|
||||||
|
new App.ControllerGenericNew(
|
||||||
|
pageData:
|
||||||
|
title: 'Chats'
|
||||||
|
object: 'Chat'
|
||||||
|
objects: 'Chats'
|
||||||
|
genericObject: 'Chat'
|
||||||
|
callback: @render
|
||||||
|
container: @el.closest('.content')
|
||||||
|
large: true
|
||||||
|
)
|
||||||
|
|
||||||
|
edit: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
id = $(e.target).closest('tr').data('id')
|
||||||
|
new App.ControllerGenericEdit(
|
||||||
|
id: id
|
||||||
|
genericObject: 'Chat'
|
||||||
|
pageData:
|
||||||
|
object: 'Chat'
|
||||||
|
container: @el.closest('.content')
|
||||||
|
callback: @render
|
||||||
|
)
|
||||||
|
|
||||||
|
remove: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
id = $(e.target).closest('tr').data('id')
|
||||||
|
item = App.Chat.find(id)
|
||||||
|
new App.ControllerGenericDestroyConfirm(
|
||||||
|
item: item
|
||||||
|
container: @el.closest('.content')
|
||||||
|
callback: @render
|
||||||
|
)
|
||||||
|
|
|
@ -3,22 +3,30 @@ class App.ChannelForm extends App.Controller
|
||||||
events:
|
events:
|
||||||
'change form.js-params': 'updateParams'
|
'change form.js-params': 'updateParams'
|
||||||
'keyup form.js-params': 'updateParams'
|
'keyup form.js-params': 'updateParams'
|
||||||
|
'click .js-formSetting': 'toggleFormSetting'
|
||||||
|
|
||||||
|
elements:
|
||||||
|
'.js-paramsBlock': 'paramsBlock'
|
||||||
|
'.js-formSetting': 'formSetting'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
@title 'Form'
|
@title 'Form'
|
||||||
@render()
|
@subscribeId = App.Setting.subscribe(@render, initFetch: true)
|
||||||
@updateParams()
|
|
||||||
new App.SettingsArea(
|
|
||||||
el: @$('.js-settings')
|
|
||||||
area: 'Form::Base'
|
|
||||||
)
|
|
||||||
|
|
||||||
render: ->
|
render: =>
|
||||||
|
App.Setting.unsubscribe(@subscribeId)
|
||||||
|
setting = App.Setting.findByAttribute('name', 'form_ticket_create')
|
||||||
@html App.view('channel/form')(
|
@html App.view('channel/form')(
|
||||||
baseurl: window.location.origin
|
baseurl: window.location.origin
|
||||||
|
formSetting: setting.state_current.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@paramsBlock.each (i, block) ->
|
||||||
|
hljs.highlightBlock block
|
||||||
|
|
||||||
|
@updateParams()
|
||||||
|
|
||||||
updateParams: ->
|
updateParams: ->
|
||||||
quote = (string) ->
|
quote = (string) ->
|
||||||
string = string.replace('\'', '\\\'')
|
string = string.replace('\'', '\\\'')
|
||||||
|
@ -36,4 +44,10 @@ class App.ChannelForm extends App.Controller
|
||||||
paramString += " #{key}: '#{quote(value)}'"
|
paramString += " #{key}: '#{quote(value)}'"
|
||||||
@$('.js-modal-params').html(paramString)
|
@$('.js-modal-params').html(paramString)
|
||||||
|
|
||||||
|
toggleFormSetting: =>
|
||||||
|
value = @formSetting.prop('checked')
|
||||||
|
setting = App.Setting.findByAttribute('name', 'form_ticket_create')
|
||||||
|
setting.state_current = { value: value }
|
||||||
|
setting.save()
|
||||||
|
|
||||||
App.Config.set( 'Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
|
|
|
@ -13,27 +13,17 @@ class App.WidgetTag extends App.Controller
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
@cacheKey = "tags::#{@object_type}::#{@object.id}"
|
@key = "tags::#{@object_type}::#{@object.id}"
|
||||||
|
|
||||||
if @tags
|
if @tags
|
||||||
@render()
|
@render()
|
||||||
return
|
return
|
||||||
|
|
||||||
@tags = App.SessionStorage.get( @cacheKey ) || []
|
|
||||||
if !_.isEmpty(@tags)
|
|
||||||
@render()
|
|
||||||
@delay(
|
|
||||||
=>
|
|
||||||
@fetch()
|
|
||||||
1000
|
|
||||||
'fetch'
|
|
||||||
)
|
|
||||||
else
|
|
||||||
@fetch()
|
@fetch()
|
||||||
|
|
||||||
fetch: =>
|
fetch: =>
|
||||||
@ajax(
|
@ajax(
|
||||||
id: @cacheKey
|
id: @key
|
||||||
type: 'GET'
|
type: 'GET'
|
||||||
url: @apiPath + '/tags'
|
url: @apiPath + '/tags'
|
||||||
data:
|
data:
|
||||||
|
@ -42,7 +32,6 @@ class App.WidgetTag extends App.Controller
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
@tags = data.tags
|
@tags = data.tags
|
||||||
App.SessionStorage.set( @cacheKey, @tags )
|
|
||||||
@render()
|
@render()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,44 +6,17 @@
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<p><%- @T('You can create chat widgets for your webpages to allow visitors to chat with you.') %></p>
|
<p><%- @T('You can create chat widgets for your webpages to allow visitors to chat with you.') %></p>
|
||||||
|
|
||||||
|
<h2><%- @T('Enable') %>/<%- @T('Disable') %></h2>
|
||||||
|
<form>
|
||||||
|
<div class="zammad-switch">
|
||||||
|
<input name="chat" type="checkbox" id="setting-chat" class="js-chatSetting" <% if @chatSetting: %>checked<% end %>>
|
||||||
|
<label for="setting-chat"></label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<h2><%- @T('Topics') %></h2>
|
<h2><%- @T('Topics') %></h2>
|
||||||
<p><%- @T('You can create multiple chat topics.') %></p>
|
<p><%- @T('You can create multiple chat topics.') %></p>
|
||||||
<table class="settings-list">
|
<div class="js-topics"></div>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="white-space: nowrap;"><%- @T('Name') %></th>
|
|
||||||
<th style="white-space: nowrap;"><%- @T('Note') %></th>
|
|
||||||
<th style="white-space: nowrap;"><%- @T('Max. clients in waitlist') %></th>
|
|
||||||
<th style="white-space: nowrap;"><%- @T('Delete') %></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<% for chat in @chats: %>
|
|
||||||
<tr data-id="<%= chat.id %>">
|
|
||||||
<td>
|
|
||||||
<label class="inline-label">
|
|
||||||
<a class="js-edit is-clickable"><%= chat.name %></a>
|
|
||||||
</label>
|
|
||||||
<td>
|
|
||||||
<label class="inline-label">
|
|
||||||
<%= chat.note %>
|
|
||||||
</label>
|
|
||||||
<td>
|
|
||||||
<label class="inline-label">
|
|
||||||
<%= chat.max_queue %>
|
|
||||||
</label>
|
|
||||||
<td>
|
|
||||||
<div class="settings-list-rowControls">
|
|
||||||
<div class="btn btn--text js-remove">
|
|
||||||
<%- @Icon('trash') %> <%- @T('Remove') %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="settings-list-action-cell js-add">
|
|
||||||
<%- @Icon('plus-small') %> <%- @T('Add') %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2><%- @T('Designer') %></h2>
|
<h2><%- @T('Designer') %></h2>
|
||||||
|
|
||||||
|
@ -62,7 +35,7 @@
|
||||||
<div class="browser chat-demo js-browser">
|
<div class="browser chat-demo js-browser">
|
||||||
<form class="browser-head js-demo-head" novalidate>
|
<form class="browser-head js-demo-head" novalidate>
|
||||||
<div class="browser-input">
|
<div class="browser-input">
|
||||||
<input type="url" class="js-testurl-input" id="preview-iframe" placeholder="zammad.org">
|
<input type="url" class="js-testurl-input" id="preview-iframe" value="<%= @previewUrl %>" placeholder="www.zammad.org">
|
||||||
<div class="loading icon small muted"></div>
|
<div class="loading icon small muted"></div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" class="btn" value="<%- @T('Load') %>">
|
<input type="submit" class="btn" value="<%- @T('Load') %>">
|
||||||
|
@ -119,7 +92,7 @@
|
||||||
<label for="form-chat-title"><%- @T('Chat Title') %></label>
|
<label for="form-chat-title"><%- @T('Chat Title') %></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input class="js-chatTitle" type="text" id="form-chat-title" name="title" value="<strong>Chat</strong> with us!">
|
<input class="js-chatTitle" type="text" id="form-chat-title" name="title" placeholder="<strong>Chat</strong> with us!">
|
||||||
</div>
|
</div>
|
||||||
<span class="help-block"><%- @T('Shown when the chat is closed.') %></span>
|
<span class="help-block"><%- @T('Shown when the chat is closed.') %></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,15 +6,19 @@
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<p><%- @T('With form you can add a formular to your web page witch directly generates a Ticket for you.') %></p>
|
<p><%- @T('With form you can add a formular to your web page witch directly generates a Ticket for you.') %></p>
|
||||||
|
|
||||||
<div class="js-settings"></div>
|
<h2><%- @T('Enable') %>/<%- @T('Disable') %></h2>
|
||||||
|
<form>
|
||||||
|
<div class="zammad-switch">
|
||||||
|
<input name="form_ticket_create" type="checkbox" id="setting-form" class="js-formSetting" <% if @formSetting: %>checked<% end %>>
|
||||||
|
<label for="setting-form"></label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<hr>
|
<h2><%- @T('Designer') %></h2>
|
||||||
|
|
||||||
<h2><%- @T('Widget Designer') %></h2>
|
|
||||||
<form class="js-params">
|
<form class="js-params">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="input form-group">
|
<div class="input form-group formGroup--halfSize">
|
||||||
<div class="formGroup-label">
|
<div class="formGroup-label">
|
||||||
<label for="form-message-title"><%- @T('Title of the form') %></label>
|
<label for="form-message-title"><%- @T('Title of the form') %></label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +26,7 @@
|
||||||
<input type="text" id="form-message-title" name="messageTitle" value="<%- @Ti('Feedback Form') %>">
|
<input type="text" id="form-message-title" name="messageTitle" value="<%- @Ti('Feedback Form') %>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input form-group">
|
<div class="input form-group formGroup--halfSize">
|
||||||
<div class="formGroup-label">
|
<div class="formGroup-label">
|
||||||
<label for="form-message-submit"><%- @T('Name of form submit button') %></label>
|
<label for="form-message-submit"><%- @T('Name of form submit button') %></label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,8 +99,7 @@
|
||||||
|
|
||||||
<p><%- @T('You need to add the following Java Script code snipped to your web page') %>:
|
<p><%- @T('You need to add the following Java Script code snipped to your web page') %>:
|
||||||
|
|
||||||
<pre>
|
<pre><code class="language-html js-paramsBlock"><button id="feedback-form">Feedback</button>
|
||||||
<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>
|
||||||
|
|
||||||
|
@ -106,5 +109,5 @@ $(function() {
|
||||||
<span class="js-modal-params"></span>
|
<span class="js-modal-params"></span>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script></pre>
|
</script></code></pre>
|
||||||
</div>
|
</div>
|
41
app/assets/javascripts/app/views/channel/topics.jst.eco
Normal file
41
app/assets/javascripts/app/views/channel/topics.jst.eco
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<table class="settings-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="white-space: nowrap;"><%- @T('chatId') %></th>
|
||||||
|
<th style="white-space: nowrap;"><%- @T('Name') %></th>
|
||||||
|
<th style="white-space: nowrap;"><%- @T('Note') %></th>
|
||||||
|
<th style="white-space: nowrap;"><%- @T('Max. clients in waitlist') %></th>
|
||||||
|
<th style="white-space: nowrap;"><%- @T('Delete') %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% for chat in @chats: %>
|
||||||
|
<tr data-id="<%= chat.id %>">
|
||||||
|
<td>
|
||||||
|
<label class="inline-label">
|
||||||
|
<%= chat.id %>
|
||||||
|
</label>
|
||||||
|
<td>
|
||||||
|
<label class="inline-label">
|
||||||
|
<a class="js-edit is-clickable"><%= chat.name %></a>
|
||||||
|
</label>
|
||||||
|
<td>
|
||||||
|
<label class="inline-label">
|
||||||
|
<%= chat.note %>
|
||||||
|
</label>
|
||||||
|
<td>
|
||||||
|
<label class="inline-label">
|
||||||
|
<%= chat.max_queue %>
|
||||||
|
</label>
|
||||||
|
<td>
|
||||||
|
<div class="settings-list-rowControls">
|
||||||
|
<div class="btn btn--text js-remove">
|
||||||
|
<%- @Icon('trash') %> <%- @T('Remove') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="settings-list-action-cell js-add">
|
||||||
|
<%- @Icon('plus-small') %> <%- @T('Add') %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -11,6 +11,8 @@ class ChatsController < ApplicationController
|
||||||
chat_ids.push chat.id
|
chat_ids.push chat.id
|
||||||
assets = chat.assets(assets)
|
assets = chat.assets(assets)
|
||||||
}
|
}
|
||||||
|
setting = Setting.find_by(name: 'chat')
|
||||||
|
assets = setting.assets(assets)
|
||||||
render json: {
|
render json: {
|
||||||
chat_ids: chat_ids,
|
chat_ids: chat_ids,
|
||||||
assets: assets,
|
assets: assets,
|
||||||
|
|
|
@ -134,9 +134,10 @@ class Channel::EmailParser
|
||||||
if !data[:body].valid_encoding?
|
if !data[:body].valid_encoding?
|
||||||
data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
|
data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# html attachment/body may exists and will be converted to text
|
# html attachment/body may exists and will be converted to text
|
||||||
else
|
if !mail.text_part || !data[:body] || data[:body] == ''
|
||||||
filename = '-no name-'
|
filename = '-no name-'
|
||||||
if mail.html_part && mail.html_part.body
|
if mail.html_part && mail.html_part.body
|
||||||
filename = 'message.html'
|
filename = 'message.html'
|
||||||
|
|
|
@ -14,10 +14,15 @@ do($ = window.jQuery, window) ->
|
||||||
host: ''
|
host: ''
|
||||||
debug: false
|
debug: false
|
||||||
flat: false
|
flat: false
|
||||||
|
lang: undefined
|
||||||
|
cssAutoload: true
|
||||||
|
cssUrl: undefined
|
||||||
fontSize: undefined
|
fontSize: undefined
|
||||||
buttonClass: 'open-zammad-chat'
|
buttonClass: 'open-zammad-chat'
|
||||||
inactiveClass: 'is-inactive'
|
inactiveClass: 'is-inactive'
|
||||||
title: '<strong>Chat</strong> with us!'
|
title: '<strong>Chat</strong> with us!'
|
||||||
|
idleTimeout: 8
|
||||||
|
inactiveTimeout: 20
|
||||||
|
|
||||||
_messageCount: 0
|
_messageCount: 0
|
||||||
isOpen: true
|
isOpen: true
|
||||||
|
@ -31,7 +36,10 @@ do($ = window.jQuery, window) ->
|
||||||
state: 'offline'
|
state: 'offline'
|
||||||
initialQueueDelay: 10000
|
initialQueueDelay: 10000
|
||||||
wsReconnectEnable: true
|
wsReconnectEnable: true
|
||||||
strings:
|
translations:
|
||||||
|
de:
|
||||||
|
'<strong>Chat</strong> with us!': '<strong>Chat</strong> mit uns!'
|
||||||
|
'Online': 'Online'
|
||||||
'Online': 'Online'
|
'Online': 'Online'
|
||||||
'Offline': 'Offline'
|
'Offline': 'Offline'
|
||||||
'Connecting': 'Verbinden'
|
'Connecting': 'Verbinden'
|
||||||
|
@ -42,19 +50,23 @@ do($ = window.jQuery, window) ->
|
||||||
'All colleges are busy.': 'Alle Kollegen sind belegt.'
|
'All colleges are busy.': 'Alle Kollegen sind belegt.'
|
||||||
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.'
|
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.'
|
||||||
'Start new conversation': 'Neue Konversation starten'
|
'Start new conversation': 'Neue Konversation starten'
|
||||||
'Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.': 'Da sie in den letzten %s nichts geschrieben haben wurde ihre Konversation mit <strong>%s</strong> geschlossen.'
|
'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.'
|
||||||
'minutes': 'Minuten'
|
'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.'
|
||||||
sessionId: undefined
|
sessionId: undefined
|
||||||
|
|
||||||
T: (string, items...) =>
|
T: (string, items...) =>
|
||||||
if !@strings[string]
|
if @options.lang && @options.lang isnt 'en'
|
||||||
|
if !@translations[@options.lang]
|
||||||
|
@log 'notice', "Translation '#{@options.lang}' needed!"
|
||||||
|
else
|
||||||
|
translations = @translations[@options.lang]
|
||||||
|
if !translations[string]
|
||||||
@log 'notice', "Translation needed for '#{string}'"
|
@log 'notice', "Translation needed for '#{string}'"
|
||||||
translation = @strings[string] || string
|
string = translations[string] || string
|
||||||
if items
|
if items
|
||||||
for item in items
|
for item in items
|
||||||
translation = translation.replace(/%s/, item)
|
string = string.replace(/%s/, item)
|
||||||
|
string
|
||||||
translation
|
|
||||||
|
|
||||||
log: (level, string...) =>
|
log: (level, string...) =>
|
||||||
return if !@options.debug && level is 'debug'
|
return if !@options.debug && level is 'debug'
|
||||||
|
@ -76,16 +88,26 @@ do($ = window.jQuery, window) ->
|
||||||
@options = $.extend {}, @defaults, options
|
@options = $.extend {}, @defaults, options
|
||||||
|
|
||||||
# check prerequisites
|
# check prerequisites
|
||||||
|
if !$
|
||||||
|
@state = 'unsupported'
|
||||||
|
@log 'notice', 'Chat: no jquery found!'
|
||||||
|
return
|
||||||
if !window.WebSocket or !sessionStorage
|
if !window.WebSocket or !sessionStorage
|
||||||
@state = 'unsupported'
|
@state = 'unsupported'
|
||||||
@log 'notice', 'Chat: Browser not supported!'
|
@log 'notice', 'Chat: Browser not supported!'
|
||||||
return
|
return
|
||||||
|
|
||||||
if !@options.chatId
|
if !@options.chatId
|
||||||
@state = 'unsupported'
|
@state = 'unsupported'
|
||||||
@log 'error', 'Chat: need chatId as option!'
|
@log 'error', 'Chat: need chatId as option!'
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# detect language
|
||||||
|
if !@options.lang
|
||||||
|
@options.lang = $('html').attr('lang')
|
||||||
|
if @options.lang
|
||||||
|
@options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
|
||||||
|
@log 'debug', "lang: #{@options.lang}"
|
||||||
|
|
||||||
@el = $(@view('chat')(
|
@el = $(@view('chat')(
|
||||||
title: @options.title
|
title: @options.title
|
||||||
))
|
))
|
||||||
|
@ -105,6 +127,8 @@ do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
@wsConnect()
|
@wsConnect()
|
||||||
|
|
||||||
|
@loadCss()
|
||||||
|
|
||||||
checkForEnter: (event) =>
|
checkForEnter: (event) =>
|
||||||
if not event.shiftKey and event.keyCode is 13
|
if not event.shiftKey and event.keyCode is 13
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -125,7 +149,9 @@ do($ = window.jQuery, window) ->
|
||||||
@log 'debug', 'ws:onmessage', pipe
|
@log 'debug', 'ws:onmessage', pipe
|
||||||
switch pipe.event
|
switch pipe.event
|
||||||
when 'chat_error'
|
when 'chat_error'
|
||||||
@log 'error', pipe.data
|
@log 'notice', pipe.data
|
||||||
|
if pipe.data && pipe.data.state is 'chat_disabled'
|
||||||
|
@wsClose()
|
||||||
when 'chat_session_message'
|
when 'chat_session_message'
|
||||||
return if pipe.data.self_written
|
return if pipe.data.self_written
|
||||||
@receiveMessage pipe.data
|
@receiveMessage pipe.data
|
||||||
|
@ -176,6 +202,8 @@ do($ = window.jQuery, window) ->
|
||||||
$(".#{ @options.buttonClass }").hide()
|
$(".#{ @options.buttonClass }").hide()
|
||||||
|
|
||||||
reopenSession: (data) =>
|
reopenSession: (data) =>
|
||||||
|
@inactiveTimeoutStart()
|
||||||
|
|
||||||
unfinishedMessage = sessionStorage.getItem 'unfinished_message'
|
unfinishedMessage = sessionStorage.getItem 'unfinished_message'
|
||||||
|
|
||||||
# rerender chat history
|
# rerender chat history
|
||||||
|
@ -218,6 +246,7 @@ do($ = window.jQuery, window) ->
|
||||||
@isTyping = new Date()
|
@isTyping = new Date()
|
||||||
@send 'chat_session_typing',
|
@send 'chat_session_typing',
|
||||||
session_id: @sessionId
|
session_id: @sessionId
|
||||||
|
@inactiveTimeoutStart()
|
||||||
|
|
||||||
onSubmit: (event) =>
|
onSubmit: (event) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -225,9 +254,10 @@ do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
sendMessage: ->
|
sendMessage: ->
|
||||||
message = @input.val()
|
message = @input.val()
|
||||||
|
|
||||||
return if !message
|
return if !message
|
||||||
|
|
||||||
|
@inactiveTimeoutStart()
|
||||||
|
|
||||||
sessionStorage.removeItem 'unfinished_message'
|
sessionStorage.removeItem 'unfinished_message'
|
||||||
|
|
||||||
messageElement = @view('message')
|
messageElement = @view('message')
|
||||||
|
@ -255,6 +285,8 @@ do($ = window.jQuery, window) ->
|
||||||
session_id: @sessionId
|
session_id: @sessionId
|
||||||
|
|
||||||
receiveMessage: (data) =>
|
receiveMessage: (data) =>
|
||||||
|
@inactiveTimeoutStart()
|
||||||
|
|
||||||
# hide writing indicator
|
# hide writing indicator
|
||||||
@onAgentTypingEnd()
|
@onAgentTypingEnd()
|
||||||
|
|
||||||
|
@ -289,10 +321,10 @@ do($ = window.jQuery, window) ->
|
||||||
@isOpen = true
|
@isOpen = true
|
||||||
|
|
||||||
if !@sessionId
|
if !@sessionId
|
||||||
@session_init()
|
@sessionInit()
|
||||||
|
|
||||||
onOpenAnimationEnd: ->
|
onOpenAnimationEnd: =>
|
||||||
#@showTimeout()
|
@idleTimeoutStop()
|
||||||
|
|
||||||
close: (event) =>
|
close: (event) =>
|
||||||
return @state if @state is 'off' or @state is 'unsupported'
|
return @state if @state is 'off' or @state is 'unsupported'
|
||||||
|
@ -301,12 +333,25 @@ do($ = window.jQuery, window) ->
|
||||||
# only close if session_id exists
|
# only close if session_id exists
|
||||||
return if !@sessionId
|
return if !@sessionId
|
||||||
|
|
||||||
|
# send close
|
||||||
|
@send 'chat_session_close',
|
||||||
|
session_id: @sessionId
|
||||||
|
|
||||||
|
# stop timer
|
||||||
|
@inactiveTimeoutStop()
|
||||||
|
|
||||||
|
# delete input store
|
||||||
|
sessionStorage.removeItem 'unfinished_message'
|
||||||
|
|
||||||
# stop delay of initial queue position
|
# stop delay of initial queue position
|
||||||
if @onInitialQueueDelayId
|
if @onInitialQueueDelayId
|
||||||
clearTimeout(@onInitialQueueDelayId)
|
clearTimeout(@onInitialQueueDelayId)
|
||||||
|
|
||||||
|
if event
|
||||||
@closeWindow()
|
@closeWindow()
|
||||||
|
|
||||||
|
@setSessionId undefined
|
||||||
|
|
||||||
closeWindow: =>
|
closeWindow: =>
|
||||||
@el.removeClass('zammad-chat-is-open')
|
@el.removeClass('zammad-chat-is-open')
|
||||||
remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
|
remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
|
||||||
|
@ -317,12 +362,6 @@ do($ = window.jQuery, window) ->
|
||||||
@disconnect()
|
@disconnect()
|
||||||
@isOpen = false
|
@isOpen = false
|
||||||
|
|
||||||
@send 'chat_session_close',
|
|
||||||
session_id: @sessionId
|
|
||||||
|
|
||||||
@setSessionId undefined
|
|
||||||
sessionStorage.removeItem 'unfinished_message'
|
|
||||||
|
|
||||||
# restart connection
|
# restart connection
|
||||||
@onWebSocketOpen()
|
@onWebSocketOpen()
|
||||||
|
|
||||||
|
@ -430,7 +469,7 @@ do($ = window.jQuery, window) ->
|
||||||
scrollToBottom: ->
|
scrollToBottom: ->
|
||||||
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
|
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
|
||||||
|
|
||||||
session_init: ->
|
sessionInit: ->
|
||||||
@send('chat_session_init')
|
@send('chat_session_init')
|
||||||
|
|
||||||
detectHost: ->
|
detectHost: ->
|
||||||
|
@ -466,6 +505,7 @@ do($ = window.jQuery, window) ->
|
||||||
@reconnectDelayId = setTimeout(@wsConnect, 5000)
|
@reconnectDelayId = setTimeout(@wsConnect, 5000)
|
||||||
|
|
||||||
onWebSocketOpen: =>
|
onWebSocketOpen: =>
|
||||||
|
@idleTimeoutStart()
|
||||||
@sessionId = sessionStorage.getItem('sessionId')
|
@sessionId = sessionStorage.getItem('sessionId')
|
||||||
@log 'debug', 'ws connected'
|
@log 'debug', 'ws connected'
|
||||||
|
|
||||||
|
@ -493,6 +533,7 @@ do($ = window.jQuery, window) ->
|
||||||
@addStatus @T('Chat closed by %s', data.realname)
|
@addStatus @T('Chat closed by %s', data.realname)
|
||||||
@disableInput()
|
@disableInput()
|
||||||
@setAgentOnlineState 'offline'
|
@setAgentOnlineState 'offline'
|
||||||
|
@inactiveTimeoutStop()
|
||||||
|
|
||||||
disconnect: ->
|
disconnect: ->
|
||||||
@showLoader()
|
@showLoader()
|
||||||
|
@ -534,8 +575,11 @@ do($ = window.jQuery, window) ->
|
||||||
showTimeout: ->
|
showTimeout: ->
|
||||||
@el.find('.zammad-chat-body').html @view('timeout')
|
@el.find('.zammad-chat-body').html @view('timeout')
|
||||||
agent: @agent.name
|
agent: @agent.name
|
||||||
delay: 10
|
delay: @options.inactiveTimeout
|
||||||
unit: @T('minutes')
|
@close()
|
||||||
|
reload = ->
|
||||||
|
location.reload()
|
||||||
|
@el.find('.js-restart').click reload
|
||||||
|
|
||||||
showLoader: ->
|
showLoader: ->
|
||||||
@el.find('.zammad-chat-body').html @view('loader')()
|
@el.find('.zammad-chat-body').html @view('loader')()
|
||||||
|
@ -548,4 +592,48 @@ do($ = window.jQuery, window) ->
|
||||||
.attr('data-status', state)
|
.attr('data-status', state)
|
||||||
.text @T(capitalizedState)
|
.text @T(capitalizedState)
|
||||||
|
|
||||||
|
loadCss: ->
|
||||||
|
return if !@options.cssAutoload
|
||||||
|
url = @options.cssUrl
|
||||||
|
if !url
|
||||||
|
url = @options.host
|
||||||
|
.replace(/^wss/i, 'https')
|
||||||
|
.replace(/^ws/i, 'http')
|
||||||
|
.replace(/\/ws/i, '')
|
||||||
|
url += '/assets/chat/chat.css'
|
||||||
|
|
||||||
|
@log 'debug', "load css from '#{url}'"
|
||||||
|
styles = "@import url('#{url}');"
|
||||||
|
newSS = document.createElement('link')
|
||||||
|
newSS.rel = 'stylesheet'
|
||||||
|
newSS.href = 'data:text/css,' + escape(styles)
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(newSS)
|
||||||
|
|
||||||
|
inactiveTimeoutStart: =>
|
||||||
|
@inactiveTimeoutStop()
|
||||||
|
delay = =>
|
||||||
|
@log 'debug', "Inactive timeout of #{@options.inactiveTimeout} minutes, show timeout screen."
|
||||||
|
@state = 'off'
|
||||||
|
@setAgentOnlineState 'offline'
|
||||||
|
@showTimeout()
|
||||||
|
@wsClose()
|
||||||
|
@inactiveTimeoutStopDelayId = setTimeout(delay, @options.inactiveTimeout * 1000 * 60)
|
||||||
|
|
||||||
|
inactiveTimeoutStop: =>
|
||||||
|
return if !@inactiveTimeoutStopDelayId
|
||||||
|
clearTimeout(@inactiveTimeoutStopDelayId)
|
||||||
|
|
||||||
|
idleTimeoutStart: =>
|
||||||
|
@idleTimeoutStop()
|
||||||
|
delay = =>
|
||||||
|
@log 'debug', "Idle timeout of #{@options.idleTimeout} minutes, hide widget"
|
||||||
|
@state = 'off'
|
||||||
|
@hide()
|
||||||
|
@wsClose()
|
||||||
|
@idleTimeoutStopDelayId = setTimeout(delay, @options.idleTimeout * 1000 * 60)
|
||||||
|
|
||||||
|
idleTimeoutStop: =>
|
||||||
|
return if !@idleTimeoutStopDelayId
|
||||||
|
clearTimeout(@idleTimeoutStopDelayId)
|
||||||
|
|
||||||
window.ZammadChat = ZammadChat
|
window.ZammadChat = ZammadChat
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
margin: 0 1em;
|
margin: 0 1em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
padding: 0 0.7em;
|
padding: 0 .7em;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
background: rgba(0, 0, 0, 0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04) inset; }
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04) inset; }
|
||||||
|
@ -296,6 +296,7 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
border-top: 1px solid #ededed;
|
border-top: 1px solid #ededed;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
box-shadow: 0 1px rgba(0, 0, 0, 0.01), 0 -1px rgba(0, 0, 0, 0.02);
|
box-shadow: 0 1px rgba(0, 0, 0, 0.01), 0 -1px rgba(0, 0, 0, 0.02);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -318,6 +319,7 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
-webkit-flex: 1;
|
-webkit-flex: 1;
|
||||||
-ms-flex: 1;
|
-ms-flex: 1;
|
||||||
|
@ -356,9 +358,7 @@
|
||||||
display: block; }
|
display: block; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
# Flat Design
|
# Flat Design
|
||||||
|
|
||||||
*/
|
*/
|
||||||
.zammad-chat--flat .zammad-chat-header,
|
.zammad-chat--flat .zammad-chat-header,
|
||||||
.zammad-chat--flat .zammad-chat-body {
|
.zammad-chat--flat .zammad-chat-body {
|
||||||
|
|
|
@ -14,10 +14,15 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
host: '',
|
host: '',
|
||||||
debug: false,
|
debug: false,
|
||||||
flat: false,
|
flat: false,
|
||||||
|
lang: void 0,
|
||||||
|
cssAutoload: true,
|
||||||
|
cssUrl: void 0,
|
||||||
fontSize: void 0,
|
fontSize: void 0,
|
||||||
buttonClass: 'open-zammad-chat',
|
buttonClass: 'open-zammad-chat',
|
||||||
inactiveClass: 'is-inactive',
|
inactiveClass: 'is-inactive',
|
||||||
title: '<strong>Chat</strong> with us!'
|
title: '<strong>Chat</strong> with us!',
|
||||||
|
idleTimeout: 8,
|
||||||
|
inactiveTimeout: 20
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype._messageCount = 0;
|
ZammadChat.prototype._messageCount = 0;
|
||||||
|
@ -44,7 +49,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
|
|
||||||
ZammadChat.prototype.wsReconnectEnable = true;
|
ZammadChat.prototype.wsReconnectEnable = true;
|
||||||
|
|
||||||
ZammadChat.prototype.strings = {
|
ZammadChat.prototype.translations = {
|
||||||
|
de: {
|
||||||
|
'<strong>Chat</strong> with us!': '<strong>Chat</strong> mit uns!',
|
||||||
|
'Online': 'Online',
|
||||||
'Online': 'Online',
|
'Online': 'Online',
|
||||||
'Offline': 'Offline',
|
'Offline': 'Offline',
|
||||||
'Connecting': 'Verbinden',
|
'Connecting': 'Verbinden',
|
||||||
|
@ -55,26 +63,34 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
'All colleges are busy.': 'Alle Kollegen sind belegt.',
|
'All colleges are busy.': 'Alle Kollegen sind belegt.',
|
||||||
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.',
|
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.',
|
||||||
'Start new conversation': 'Neue Konversation starten',
|
'Start new conversation': 'Neue Konversation starten',
|
||||||
'Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.': 'Da sie in den letzten %s nichts geschrieben haben wurde ihre Konversation mit <strong>%s</strong> geschlossen.',
|
'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.',
|
||||||
'minutes': 'Minuten'
|
'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.sessionId = void 0;
|
ZammadChat.prototype.sessionId = void 0;
|
||||||
|
|
||||||
ZammadChat.prototype.T = function() {
|
ZammadChat.prototype.T = function() {
|
||||||
var i, item, items, len, string, translation;
|
var i, item, items, len, string, translations;
|
||||||
string = arguments[0], items = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
string = arguments[0], items = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
||||||
if (!this.strings[string]) {
|
if (this.options.lang && this.options.lang !== 'en') {
|
||||||
|
if (!this.translations[this.options.lang]) {
|
||||||
|
this.log('notice', "Translation '" + this.options.lang + "' needed!");
|
||||||
|
} else {
|
||||||
|
translations = this.translations[this.options.lang];
|
||||||
|
if (!translations[string]) {
|
||||||
this.log('notice', "Translation needed for '" + string + "'");
|
this.log('notice', "Translation needed for '" + string + "'");
|
||||||
}
|
}
|
||||||
translation = this.strings[string] || string;
|
string = translations[string] || string;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (items) {
|
if (items) {
|
||||||
for (i = 0, len = items.length; i < len; i++) {
|
for (i = 0, len = items.length; i < len; i++) {
|
||||||
item = items[i];
|
item = items[i];
|
||||||
translation = translation.replace(/%s/, item);
|
string = string.replace(/%s/, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return translation;
|
return string;
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.log = function() {
|
ZammadChat.prototype.log = function() {
|
||||||
|
@ -103,6 +119,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
function ZammadChat(options) {
|
function ZammadChat(options) {
|
||||||
|
this.idleTimeoutStop = bind(this.idleTimeoutStop, this);
|
||||||
|
this.idleTimeoutStart = bind(this.idleTimeoutStart, this);
|
||||||
|
this.inactiveTimeoutStop = bind(this.inactiveTimeoutStop, this);
|
||||||
|
this.inactiveTimeoutStart = bind(this.inactiveTimeoutStart, this);
|
||||||
this.setAgentOnlineState = bind(this.setAgentOnlineState, this);
|
this.setAgentOnlineState = bind(this.setAgentOnlineState, this);
|
||||||
this.onConnectionEstablished = bind(this.onConnectionEstablished, this);
|
this.onConnectionEstablished = bind(this.onConnectionEstablished, this);
|
||||||
this.setSessionId = bind(this.setSessionId, this);
|
this.setSessionId = bind(this.setSessionId, this);
|
||||||
|
@ -119,11 +139,11 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
this.onCloseAnimationEnd = bind(this.onCloseAnimationEnd, this);
|
this.onCloseAnimationEnd = bind(this.onCloseAnimationEnd, this);
|
||||||
this.closeWindow = bind(this.closeWindow, this);
|
this.closeWindow = bind(this.closeWindow, this);
|
||||||
this.close = bind(this.close, this);
|
this.close = bind(this.close, this);
|
||||||
|
this.onOpenAnimationEnd = bind(this.onOpenAnimationEnd, this);
|
||||||
this.open = bind(this.open, this);
|
this.open = bind(this.open, this);
|
||||||
this.renderMessage = bind(this.renderMessage, this);
|
this.renderMessage = bind(this.renderMessage, this);
|
||||||
this.receiveMessage = bind(this.receiveMessage, this);
|
this.receiveMessage = bind(this.receiveMessage, this);
|
||||||
this.onSubmit = bind(this.onSubmit, this);
|
this.onSubmit = bind(this.onSubmit, this);
|
||||||
this.onTypingEnd = bind(this.onTypingEnd, this);
|
|
||||||
this.onInput = bind(this.onInput, this);
|
this.onInput = bind(this.onInput, this);
|
||||||
this.reopenSession = bind(this.reopenSession, this);
|
this.reopenSession = bind(this.reopenSession, this);
|
||||||
this.onError = bind(this.onError, this);
|
this.onError = bind(this.onError, this);
|
||||||
|
@ -135,6 +155,11 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
this.log = bind(this.log, this);
|
this.log = bind(this.log, this);
|
||||||
this.T = bind(this.T, this);
|
this.T = bind(this.T, this);
|
||||||
this.options = $.extend({}, this.defaults, options);
|
this.options = $.extend({}, this.defaults, options);
|
||||||
|
if (!$) {
|
||||||
|
this.state = 'unsupported';
|
||||||
|
this.log('notice', 'Chat: no jquery found!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!window.WebSocket || !sessionStorage) {
|
if (!window.WebSocket || !sessionStorage) {
|
||||||
this.state = 'unsupported';
|
this.state = 'unsupported';
|
||||||
this.log('notice', 'Chat: Browser not supported!');
|
this.log('notice', 'Chat: Browser not supported!');
|
||||||
|
@ -145,6 +170,13 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
this.log('error', 'Chat: need chatId as option!');
|
this.log('error', 'Chat: need chatId as option!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.options.lang) {
|
||||||
|
this.options.lang = $('html').attr('lang');
|
||||||
|
}
|
||||||
|
if (this.options.lang) {
|
||||||
|
this.options.lang = this.options.lang.replace(/-.+?$/, '');
|
||||||
|
this.log('debug', "lang: " + this.options.lang);
|
||||||
|
}
|
||||||
this.el = $(this.view('chat')({
|
this.el = $(this.view('chat')({
|
||||||
title: this.options.title
|
title: this.options.title
|
||||||
}));
|
}));
|
||||||
|
@ -159,6 +191,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
input: this.onInput
|
input: this.onInput
|
||||||
});
|
});
|
||||||
this.wsConnect();
|
this.wsConnect();
|
||||||
|
this.loadCss();
|
||||||
}
|
}
|
||||||
|
|
||||||
ZammadChat.prototype.checkForEnter = function(event) {
|
ZammadChat.prototype.checkForEnter = function(event) {
|
||||||
|
@ -190,7 +223,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
this.log('debug', 'ws:onmessage', pipe);
|
this.log('debug', 'ws:onmessage', pipe);
|
||||||
switch (pipe.event) {
|
switch (pipe.event) {
|
||||||
case 'chat_error':
|
case 'chat_error':
|
||||||
this.log('error', pipe.data);
|
this.log('notice', pipe.data);
|
||||||
|
if (pipe.data && pipe.data.state === 'chat_disabled') {
|
||||||
|
this.wsClose();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'chat_session_message':
|
case 'chat_session_message':
|
||||||
if (pipe.data.self_written) {
|
if (pipe.data.self_written) {
|
||||||
|
@ -263,6 +299,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
|
|
||||||
ZammadChat.prototype.reopenSession = function(data) {
|
ZammadChat.prototype.reopenSession = function(data) {
|
||||||
var i, len, message, ref, unfinishedMessage;
|
var i, len, message, ref, unfinishedMessage;
|
||||||
|
this.inactiveTimeoutStart();
|
||||||
unfinishedMessage = sessionStorage.getItem('unfinished_message');
|
unfinishedMessage = sessionStorage.getItem('unfinished_message');
|
||||||
if (data.agent) {
|
if (data.agent) {
|
||||||
this.onConnectionEstablished(data);
|
this.onConnectionEstablished(data);
|
||||||
|
@ -293,24 +330,18 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
ZammadChat.prototype.onInput = function() {
|
ZammadChat.prototype.onInput = function() {
|
||||||
this.el.find('.zammad-chat-message--unread').removeClass('zammad-chat-message--unread');
|
this.el.find('.zammad-chat-message--unread').removeClass('zammad-chat-message--unread');
|
||||||
sessionStorage.setItem('unfinished_message', this.input.val());
|
sessionStorage.setItem('unfinished_message', this.input.val());
|
||||||
return this.onTypingStart();
|
return this.onTyping();
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.onTypingStart = function() {
|
ZammadChat.prototype.onTyping = function() {
|
||||||
if (this.isTypingTimeout) {
|
if (this.isTyping && this.isTyping > new Date(new Date().getTime() - 1500)) {
|
||||||
clearTimeout(this.isTypingTimeout);
|
return;
|
||||||
}
|
}
|
||||||
this.isTypingTimeout = setTimeout(this.onTypingEnd, 1500);
|
this.isTyping = new Date();
|
||||||
if (!this.isTyping) {
|
this.send('chat_session_typing', {
|
||||||
this.isTyping = true;
|
|
||||||
return this.send('chat_session_typing', {
|
|
||||||
session_id: this.sessionId
|
session_id: this.sessionId
|
||||||
});
|
});
|
||||||
}
|
return this.inactiveTimeoutStart();
|
||||||
};
|
|
||||||
|
|
||||||
ZammadChat.prototype.onTypingEnd = function() {
|
|
||||||
return this.isTyping = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.onSubmit = function(event) {
|
ZammadChat.prototype.onSubmit = function(event) {
|
||||||
|
@ -324,6 +355,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.inactiveTimeoutStart();
|
||||||
sessionStorage.removeItem('unfinished_message');
|
sessionStorage.removeItem('unfinished_message');
|
||||||
messageElement = this.view('message')({
|
messageElement = this.view('message')({
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -340,7 +372,6 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
}
|
}
|
||||||
this.input.val('');
|
this.input.val('');
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
this.isTyping = false;
|
|
||||||
return this.send('chat_session_message', {
|
return this.send('chat_session_message', {
|
||||||
content: message,
|
content: message,
|
||||||
id: this._messageCount,
|
id: this._messageCount,
|
||||||
|
@ -349,6 +380,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.receiveMessage = function(data) {
|
ZammadChat.prototype.receiveMessage = function(data) {
|
||||||
|
this.inactiveTimeoutStart();
|
||||||
this.onAgentTypingEnd();
|
this.onAgentTypingEnd();
|
||||||
this.maybeAddTimestamp();
|
this.maybeAddTimestamp();
|
||||||
return this.renderMessage({
|
return this.renderMessage({
|
||||||
|
@ -386,11 +418,13 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
}
|
}
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
if (!this.sessionId) {
|
if (!this.sessionId) {
|
||||||
return this.session_init();
|
return this.sessionInit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.onOpenAnimationEnd = function() {};
|
ZammadChat.prototype.onOpenAnimationEnd = function() {
|
||||||
|
return this.idleTimeoutStop();
|
||||||
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.close = function(event) {
|
ZammadChat.prototype.close = function(event) {
|
||||||
if (this.state === 'off' || this.state === 'unsupported') {
|
if (this.state === 'off' || this.state === 'unsupported') {
|
||||||
|
@ -402,10 +436,18 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
if (!this.sessionId) {
|
if (!this.sessionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.send('chat_session_close', {
|
||||||
|
session_id: this.sessionId
|
||||||
|
});
|
||||||
|
this.inactiveTimeoutStop();
|
||||||
|
sessionStorage.removeItem('unfinished_message');
|
||||||
if (this.onInitialQueueDelayId) {
|
if (this.onInitialQueueDelayId) {
|
||||||
clearTimeout(this.onInitialQueueDelayId);
|
clearTimeout(this.onInitialQueueDelayId);
|
||||||
}
|
}
|
||||||
return this.closeWindow();
|
if (event) {
|
||||||
|
this.closeWindow();
|
||||||
|
}
|
||||||
|
return this.setSessionId(void 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.closeWindow = function() {
|
ZammadChat.prototype.closeWindow = function() {
|
||||||
|
@ -421,11 +463,6 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
this.el.removeClass('zammad-chat-is-visible');
|
this.el.removeClass('zammad-chat-is-visible');
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
this.send('chat_session_close', {
|
|
||||||
session_id: this.sessionId
|
|
||||||
});
|
|
||||||
this.setSessionId(void 0);
|
|
||||||
sessionStorage.removeItem('unfinished_message');
|
|
||||||
return this.onWebSocketOpen();
|
return this.onWebSocketOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -542,7 +579,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'));
|
return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'));
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.session_init = function() {
|
ZammadChat.prototype.sessionInit = function() {
|
||||||
return this.send('chat_session_init');
|
return this.send('chat_session_init');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -591,6 +628,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.onWebSocketOpen = function() {
|
ZammadChat.prototype.onWebSocketOpen = function() {
|
||||||
|
this.idleTimeoutStart();
|
||||||
this.sessionId = sessionStorage.getItem('sessionId');
|
this.sessionId = sessionStorage.getItem('sessionId');
|
||||||
this.log('debug', 'ws connected');
|
this.log('debug', 'ws connected');
|
||||||
this.send('chat_status_customer', {
|
this.send('chat_status_customer', {
|
||||||
|
@ -617,7 +655,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
ZammadChat.prototype.onSessionClosed = function(data) {
|
ZammadChat.prototype.onSessionClosed = function(data) {
|
||||||
this.addStatus(this.T('Chat closed by %s', data.realname));
|
this.addStatus(this.T('Chat closed by %s', data.realname));
|
||||||
this.disableInput();
|
this.disableInput();
|
||||||
return this.setAgentOnlineState('offline');
|
this.setAgentOnlineState('offline');
|
||||||
|
return this.inactiveTimeoutStop();
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.disconnect = function() {
|
ZammadChat.prototype.disconnect = function() {
|
||||||
|
@ -660,11 +699,16 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.showTimeout = function() {
|
ZammadChat.prototype.showTimeout = function() {
|
||||||
return this.el.find('.zammad-chat-body').html(this.view('timeout')({
|
var reload;
|
||||||
|
this.el.find('.zammad-chat-body').html(this.view('timeout')({
|
||||||
agent: this.agent.name,
|
agent: this.agent.name,
|
||||||
delay: 10,
|
delay: this.options.inactiveTimeout
|
||||||
unit: this.T('minutes')
|
|
||||||
}));
|
}));
|
||||||
|
this.close();
|
||||||
|
reload = function() {
|
||||||
|
return location.reload();
|
||||||
|
};
|
||||||
|
return this.el.find('.js-restart').click(reload);
|
||||||
};
|
};
|
||||||
|
|
||||||
ZammadChat.prototype.showLoader = function() {
|
ZammadChat.prototype.showLoader = function() {
|
||||||
|
@ -678,6 +722,67 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
return this.el.find('.zammad-chat-agent-status').attr('data-status', state).text(this.T(capitalizedState));
|
return this.el.find('.zammad-chat-agent-status').attr('data-status', state).text(this.T(capitalizedState));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.loadCss = function() {
|
||||||
|
var newSS, styles, url;
|
||||||
|
if (!this.options.cssAutoload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url = this.options.cssUrl;
|
||||||
|
if (!url) {
|
||||||
|
url = this.options.host.replace(/^wss/i, 'https').replace(/^ws/i, 'http').replace(/\/ws/i, '');
|
||||||
|
url += '/assets/chat/chat.css';
|
||||||
|
}
|
||||||
|
this.log('debug', "load css from '" + url + "'");
|
||||||
|
styles = "@import url('" + url + "');";
|
||||||
|
newSS = document.createElement('link');
|
||||||
|
newSS.rel = 'stylesheet';
|
||||||
|
newSS.href = 'data:text/css,' + escape(styles);
|
||||||
|
return document.getElementsByTagName('head')[0].appendChild(newSS);
|
||||||
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.inactiveTimeoutStart = function() {
|
||||||
|
var delay;
|
||||||
|
this.inactiveTimeoutStop();
|
||||||
|
delay = (function(_this) {
|
||||||
|
return function() {
|
||||||
|
_this.log('debug', "Inactive timeout of " + _this.options.inactiveTimeout + " minutes, show timeout screen.");
|
||||||
|
_this.state = 'off';
|
||||||
|
_this.setAgentOnlineState('offline');
|
||||||
|
_this.showTimeout();
|
||||||
|
return _this.wsClose();
|
||||||
|
};
|
||||||
|
})(this);
|
||||||
|
return this.inactiveTimeoutStopDelayId = setTimeout(delay, this.options.inactiveTimeout * 1000 * 60);
|
||||||
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.inactiveTimeoutStop = function() {
|
||||||
|
if (!this.inactiveTimeoutStopDelayId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return clearTimeout(this.inactiveTimeoutStopDelayId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.idleTimeoutStart = function() {
|
||||||
|
var delay;
|
||||||
|
this.idleTimeoutStop();
|
||||||
|
delay = (function(_this) {
|
||||||
|
return function() {
|
||||||
|
_this.log('debug', "Idle timeout of " + _this.options.idleTimeout + " minutes, hide widget");
|
||||||
|
_this.state = 'off';
|
||||||
|
_this.hide();
|
||||||
|
return _this.wsClose();
|
||||||
|
};
|
||||||
|
})(this);
|
||||||
|
return this.idleTimeoutStopDelayId = setTimeout(delay, this.options.idleTimeout * 1000 * 60);
|
||||||
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.idleTimeoutStop = function() {
|
||||||
|
if (!this.idleTimeoutStopDelayId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return clearTimeout(this.idleTimeoutStopDelayId);
|
||||||
|
};
|
||||||
|
|
||||||
return ZammadChat;
|
return ZammadChat;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -884,7 +989,7 @@ window.zammadChatTemplates["chat"] = function (__obj) {
|
||||||
|
|
||||||
__out.push('>\n <div class="zammad-chat-header-controls">\n <span class="zammad-chat-agent-status zammad-chat-is-hidden" data-status="online"></span>\n <span class="zammad-chat-header-icon">\n <svg class="zammad-chat-header-icon-open" viewBox="0 0 13 7"><path d="M10.807 7l1.4-1.428-5-4.9L6.5-.02l-.7.7-4.9 4.9 1.414 1.413L6.5 2.886 10.807 7z" fill-rule="evenodd"/></svg>\n <svg class="zammad-chat-header-icon-close js-chat-close" viewBox="0 0 13 13"><path d="m2.241.12l-2.121 2.121 4.243 4.243-4.243 4.243 2.121 2.121 4.243-4.243 4.243 4.243 2.121-2.121-4.243-4.243 4.243-4.243-2.121-2.121-4.243 4.243-4.243-4.243" fill-rule="evenodd"/></svg>\n </span>\n </div>\n <div class="zammad-chat-agent zammad-chat-is-hidden">\n </div>\n <div class="zammad-chat-welcome">\n <svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>\n <span class="zammad-chat-welcome-text">');
|
__out.push('>\n <div class="zammad-chat-header-controls">\n <span class="zammad-chat-agent-status zammad-chat-is-hidden" data-status="online"></span>\n <span class="zammad-chat-header-icon">\n <svg class="zammad-chat-header-icon-open" viewBox="0 0 13 7"><path d="M10.807 7l1.4-1.428-5-4.9L6.5-.02l-.7.7-4.9 4.9 1.414 1.413L6.5 2.886 10.807 7z" fill-rule="evenodd"/></svg>\n <svg class="zammad-chat-header-icon-close js-chat-close" viewBox="0 0 13 13"><path d="m2.241.12l-2.121 2.121 4.243 4.243-4.243 4.243 2.121 2.121 4.243-4.243 4.243 4.243 2.121-2.121-4.243-4.243 4.243-4.243-2.121-2.121-4.243 4.243-4.243-4.243" fill-rule="evenodd"/></svg>\n </span>\n </div>\n <div class="zammad-chat-agent zammad-chat-is-hidden">\n </div>\n <div class="zammad-chat-welcome">\n <svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>\n <span class="zammad-chat-welcome-text">');
|
||||||
|
|
||||||
__out.push(this.title);
|
__out.push(this.T(this.title));
|
||||||
|
|
||||||
__out.push('</span>\n </div>\n </div>\n <div class="zammad-chat-body"></div>\n <form class="zammad-chat-controls">\n <textarea class="zammad-chat-input" rows="1" placeholder="');
|
__out.push('</span>\n </div>\n </div>\n <div class="zammad-chat-body"></div>\n <form class="zammad-chat-controls">\n <textarea class="zammad-chat-input" rows="1" placeholder="');
|
||||||
|
|
||||||
|
@ -1128,9 +1233,17 @@ window.zammadChatTemplates["timeout"] = function (__obj) {
|
||||||
(function() {
|
(function() {
|
||||||
__out.push('<div class="zammad-chat-modal">\n <div class="zammad-chat-modal-text">\n ');
|
__out.push('<div class="zammad-chat-modal">\n <div class="zammad-chat-modal-text">\n ');
|
||||||
|
|
||||||
__out.push(this.T('Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.', this.delay + " " + this.unit, this.agent));
|
if (this.agent) {
|
||||||
|
__out.push('\n ');
|
||||||
|
__out.push(this.T('Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.', this.delay, this.agent));
|
||||||
|
__out.push('\n ');
|
||||||
|
} else {
|
||||||
|
__out.push('\n ');
|
||||||
|
__out.push(this.T('Since you didn\'t respond in the last %s minutes your conversation got closed.', this.delay));
|
||||||
|
__out.push('\n ');
|
||||||
|
}
|
||||||
|
|
||||||
__out.push('<br>\n <div class="zammad-chat-button"');
|
__out.push('\n <br>\n <div class="zammad-chat-button js-restart"');
|
||||||
|
|
||||||
if (this.background) {
|
if (this.background) {
|
||||||
__out.push(__sanitize(" style='background: " + this.background + "'"));
|
__out.push(__sanitize(" style='background: " + this.background + "'"));
|
||||||
|
|
2
public/assets/chat/chat.min.js
vendored
2
public/assets/chat/chat.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -294,6 +294,7 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
border-top: 1px solid hsl(0,0%,93%);
|
border-top: 1px solid hsl(0,0%,93%);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 1px rgba(0,0,0,.01),
|
0 1px rgba(0,0,0,.01),
|
||||||
|
@ -317,6 +318,7 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-height: 6em;
|
max-height: 6em;
|
||||||
|
@ -360,9 +362,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
# Flat Design
|
# Flat Design
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.zammad-chat--flat .zammad-chat-header,
|
.zammad-chat--flat .zammad-chat-header,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Zammad Chat</title>
|
<title>Zammad Chat</title>
|
||||||
<link rel="stylesheet" href="chat.css">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
|
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
|
||||||
<style>
|
<style>
|
||||||
|
@ -140,6 +139,7 @@
|
||||||
var chat = new ZammadChat({
|
var chat = new ZammadChat({
|
||||||
chatId: 1,
|
chatId: 1,
|
||||||
host: 'ws://localhost:6042',
|
host: 'ws://localhost:6042',
|
||||||
|
cssUrl: 'http://localhost:3000/assets/chat/chat.css',
|
||||||
debug: true
|
debug: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="zammad-chat-welcome">
|
<div class="zammad-chat-welcome">
|
||||||
<svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>
|
<svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>
|
||||||
<span class="zammad-chat-welcome-text"><%- @title %></span>
|
<span class="zammad-chat-welcome-text"><%- @T(@title) %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="zammad-chat-body"></div>
|
<div class="zammad-chat-body"></div>
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<div class="zammad-chat-modal">
|
<div class="zammad-chat-modal">
|
||||||
<div class="zammad-chat-modal-text">
|
<div class="zammad-chat-modal-text">
|
||||||
<%- @T('Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.', "#{ @delay } #{ @unit }", @agent) %><br>
|
<% if @agent: %>
|
||||||
<div class="zammad-chat-button"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div>
|
<%- @T('Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.', @delay, @agent) %>
|
||||||
|
<% else: %>
|
||||||
|
<%- @T('Since you didn\'t respond in the last %s minutes your conversation got closed.', @delay) %>
|
||||||
|
<% end %>
|
||||||
|
<br>
|
||||||
|
<div class="zammad-chat-button js-restart"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,7 +1,8 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<html lang="de-de">
|
||||||
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Zammad Chat</title>
|
<title>Zammad Chat</title>
|
||||||
<link rel="stylesheet" href="chat.css">
|
|
||||||
<link rel="stylesheet" href="znuny.css">
|
<link rel="stylesheet" href="znuny.css">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
|
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
|
||||||
|
@ -65,7 +66,8 @@
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<img class="mockup" width="100%" src="znuny.png">
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
|
@ -107,6 +109,7 @@
|
||||||
var chat = new ZammadChat({
|
var chat = new ZammadChat({
|
||||||
chatId: 1,
|
chatId: 1,
|
||||||
host: 'ws://localhost:6042',
|
host: 'ws://localhost:6042',
|
||||||
|
cssUrl: 'http://localhost:3000/assets/chat/chat.css',
|
||||||
debug: true,
|
debug: true,
|
||||||
background: '#494d52',
|
background: '#494d52',
|
||||||
flat: true,
|
flat: true,
|
||||||
|
@ -145,3 +148,5 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
29
test/fixtures/mail34.box
vendored
Normal file
29
test/fixtures/mail34.box
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
From: "Bay" <memberbay+12345@members.somewhat>
|
||||||
|
Subject: strange email with empty text/plain
|
||||||
|
To: bay@example.com
|
||||||
|
Date: 11 Nov 2015 12:07:51 +0000
|
||||||
|
Priority: normal
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
Importance: normal
|
||||||
|
X-David-SYM: 0
|
||||||
|
X-David-Flags: 0
|
||||||
|
Message-ID: <7ca10659-214c-4bd0-8438-b935a01c7601@stange>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative; boundary="----_=_NextPart_000_06409CFC.56433DA7"
|
||||||
|
|
||||||
|
This message is in MIME format. Since your mail reader does not understand
|
||||||
|
this format, some or all of this message may not be legible.
|
||||||
|
|
||||||
|
------_=_NextPart_000_06409CFC.56433DA7
|
||||||
|
Content-Type: text/plain; charset=iso-8859-1
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------_=_NextPart_000_06409CFC.56433DA7
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<b>some html text</b>
|
||||||
|
|
||||||
|
------_=_NextPart_000_06409CFC.56433DA7
|
|
@ -308,7 +308,7 @@ Hof
|
||||||
# spam email
|
# spam email
|
||||||
{
|
{
|
||||||
data: IO.read('test/fixtures/mail15.box'),
|
data: IO.read('test/fixtures/mail15.box'),
|
||||||
body_md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
body_md5: '5872ddcdfdf6bfe40f36cd0408fca667',
|
||||||
attachments: [
|
attachments: [
|
||||||
# :preferences=>{"Message-ID"=>"<temp@test>", "Content-Type"=>"application/octet-stream; name=\"\xBC\xA8\xD0\xA7\xB9\xDC\xC0\xED,\xBE\xBF\xBE\xB9\xCB\xAD\xB4\xED\xC1\xCB.xls\"", "Mime-Type"=>"application/octet-stream", "Charset"=>"UTF-8"}}
|
# :preferences=>{"Message-ID"=>"<temp@test>", "Content-Type"=>"application/octet-stream; name=\"\xBC\xA8\xD0\xA7\xB9\xDC\xC0\xED,\xBE\xBF\xBE\xB9\xCB\xAD\xB4\xED\xC1\xCB.xls\"", "Mime-Type"=>"application/octet-stream", "Charset"=>"UTF-8"}}
|
||||||
# mutt c1abb5fb77a9d2ab2017749a7987c074
|
# mutt c1abb5fb77a9d2ab2017749a7987c074
|
||||||
|
@ -466,14 +466,14 @@ Freemont and pulling out several minutes.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: IO.read('test/fixtures/mail24.box'),
|
data: IO.read('test/fixtures/mail24.box'),
|
||||||
body_md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
body_md5: '5872ddcdfdf6bfe40f36cd0408fca667',
|
||||||
params: {
|
params: {
|
||||||
from: 'oracle@IG0-1-DB01.example.com',
|
from: 'oracle@IG0-1-DB01.example.com',
|
||||||
from_email: 'oracle@IG0-1-DB01.example.com',
|
from_email: 'oracle@IG0-1-DB01.example.com',
|
||||||
from_display_name: '',
|
from_display_name: '',
|
||||||
subject: 'Regelsets im Test-Status gefunden: 1',
|
subject: 'Regelsets im Test-Status gefunden: 1',
|
||||||
to: 'support@example.com',
|
to: 'support@example.com',
|
||||||
body: '',
|
body: 'no visible content',
|
||||||
},
|
},
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
|
@ -672,9 +672,24 @@ Weil wir die Echtheit oder Vollständigkeit der in dieserNachricht enthaltenen I
|
||||||
to: 'info@znuny.inc',
|
to: 'info@znuny.inc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: IO.read('test/fixtures/mail34.box'),
|
||||||
|
body_md5: 'b6e46176404ec81b3ab412fe71dff0f0',
|
||||||
|
params: {
|
||||||
|
from: 'Bay <memberbay+12345@members.somewhat>',
|
||||||
|
from_email: 'memberbay+12345@members.somewhat',
|
||||||
|
from_display_name: 'Bay',
|
||||||
|
subject: 'strange email with empty text/plain',
|
||||||
|
to: 'bay@example.com',
|
||||||
|
body: 'some html text',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
count = 0
|
||||||
files.each { |file|
|
files.each { |file|
|
||||||
|
count += 1
|
||||||
|
#p "Count: #{count}"
|
||||||
parser = Channel::EmailParser.new
|
parser = Channel::EmailParser.new
|
||||||
data = parser.parse( file[:data] )
|
data = parser.parse( file[:data] )
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue