Added idle and inactive timeouts.
This commit is contained in:
parent
6ce3116c33
commit
d71090df6e
4 changed files with 183 additions and 43 deletions
|
@ -14,13 +14,15 @@ do($ = window.jQuery, window) ->
|
|||
host: ''
|
||||
debug: false
|
||||
flat: false
|
||||
lang: undefined,
|
||||
cssAutoload: true,
|
||||
cssUrl: undefined,
|
||||
lang: undefined
|
||||
cssAutoload: true
|
||||
cssUrl: undefined
|
||||
fontSize: undefined
|
||||
buttonClass: 'open-zammad-chat'
|
||||
inactiveClass: 'is-inactive'
|
||||
title: '<strong>Chat</strong> with us!'
|
||||
idleTimeout: 8
|
||||
inactiveTimeout: 20
|
||||
|
||||
_messageCount: 0
|
||||
isOpen: true
|
||||
|
@ -48,8 +50,8 @@ do($ = window.jQuery, window) ->
|
|||
'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>.'
|
||||
'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.'
|
||||
'minutes': 'Minuten'
|
||||
'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.'
|
||||
'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
|
||||
|
||||
T: (string, items...) =>
|
||||
|
@ -86,6 +88,10 @@ do($ = window.jQuery, window) ->
|
|||
@options = $.extend {}, @defaults, options
|
||||
|
||||
# check prerequisites
|
||||
if !$
|
||||
@state = 'unsupported'
|
||||
@log 'notice', 'Chat: no jquery found!'
|
||||
return
|
||||
if !window.WebSocket or !sessionStorage
|
||||
@state = 'unsupported'
|
||||
@log 'notice', 'Chat: Browser not supported!'
|
||||
|
@ -143,7 +149,9 @@ do($ = window.jQuery, window) ->
|
|||
@log 'debug', 'ws:onmessage', pipe
|
||||
switch pipe.event
|
||||
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'
|
||||
return if pipe.data.self_written
|
||||
@receiveMessage pipe.data
|
||||
|
@ -194,6 +202,8 @@ do($ = window.jQuery, window) ->
|
|||
$(".#{ @options.buttonClass }").hide()
|
||||
|
||||
reopenSession: (data) =>
|
||||
@inactiveTimeoutStart()
|
||||
|
||||
unfinishedMessage = sessionStorage.getItem 'unfinished_message'
|
||||
|
||||
# rerender chat history
|
||||
|
@ -236,6 +246,7 @@ do($ = window.jQuery, window) ->
|
|||
@isTyping = new Date()
|
||||
@send 'chat_session_typing',
|
||||
session_id: @sessionId
|
||||
@inactiveTimeoutStart()
|
||||
|
||||
onSubmit: (event) =>
|
||||
event.preventDefault()
|
||||
|
@ -243,9 +254,10 @@ do($ = window.jQuery, window) ->
|
|||
|
||||
sendMessage: ->
|
||||
message = @input.val()
|
||||
|
||||
return if !message
|
||||
|
||||
@inactiveTimeoutStart()
|
||||
|
||||
sessionStorage.removeItem 'unfinished_message'
|
||||
|
||||
messageElement = @view('message')
|
||||
|
@ -273,6 +285,8 @@ do($ = window.jQuery, window) ->
|
|||
session_id: @sessionId
|
||||
|
||||
receiveMessage: (data) =>
|
||||
@inactiveTimeoutStart()
|
||||
|
||||
# hide writing indicator
|
||||
@onAgentTypingEnd()
|
||||
|
||||
|
@ -307,10 +321,10 @@ do($ = window.jQuery, window) ->
|
|||
@isOpen = true
|
||||
|
||||
if !@sessionId
|
||||
@session_init()
|
||||
@sessionInit()
|
||||
|
||||
onOpenAnimationEnd: ->
|
||||
#@showTimeout()
|
||||
onOpenAnimationEnd: =>
|
||||
@idleTimeoutStop()
|
||||
|
||||
close: (event) =>
|
||||
return @state if @state is 'off' or @state is 'unsupported'
|
||||
|
@ -319,12 +333,25 @@ do($ = window.jQuery, window) ->
|
|||
# only close if session_id exists
|
||||
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
|
||||
if @onInitialQueueDelayId
|
||||
clearTimeout(@onInitialQueueDelayId)
|
||||
|
||||
if event
|
||||
@closeWindow()
|
||||
|
||||
@setSessionId undefined
|
||||
|
||||
closeWindow: =>
|
||||
@el.removeClass('zammad-chat-is-open')
|
||||
remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
|
||||
|
@ -335,12 +362,6 @@ do($ = window.jQuery, window) ->
|
|||
@disconnect()
|
||||
@isOpen = false
|
||||
|
||||
@send 'chat_session_close',
|
||||
session_id: @sessionId
|
||||
|
||||
@setSessionId undefined
|
||||
sessionStorage.removeItem 'unfinished_message'
|
||||
|
||||
# restart connection
|
||||
@onWebSocketOpen()
|
||||
|
||||
|
@ -448,7 +469,7 @@ do($ = window.jQuery, window) ->
|
|||
scrollToBottom: ->
|
||||
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
|
||||
|
||||
session_init: ->
|
||||
sessionInit: ->
|
||||
@send('chat_session_init')
|
||||
|
||||
detectHost: ->
|
||||
|
@ -484,6 +505,7 @@ do($ = window.jQuery, window) ->
|
|||
@reconnectDelayId = setTimeout(@wsConnect, 5000)
|
||||
|
||||
onWebSocketOpen: =>
|
||||
@idleTimeoutStart()
|
||||
@sessionId = sessionStorage.getItem('sessionId')
|
||||
@log 'debug', 'ws connected'
|
||||
|
||||
|
@ -511,6 +533,7 @@ do($ = window.jQuery, window) ->
|
|||
@addStatus @T('Chat closed by %s', data.realname)
|
||||
@disableInput()
|
||||
@setAgentOnlineState 'offline'
|
||||
@inactiveTimeoutStop()
|
||||
|
||||
disconnect: ->
|
||||
@showLoader()
|
||||
|
@ -552,8 +575,11 @@ do($ = window.jQuery, window) ->
|
|||
showTimeout: ->
|
||||
@el.find('.zammad-chat-body').html @view('timeout')
|
||||
agent: @agent.name
|
||||
delay: 10
|
||||
unit: @T('minutes')
|
||||
delay: @options.inactiveTimeout
|
||||
@close()
|
||||
reload = ->
|
||||
location.reload()
|
||||
@el.find('.js-restart').click reload
|
||||
|
||||
showLoader: ->
|
||||
@el.find('.zammad-chat-body').html @view('loader')()
|
||||
|
@ -583,4 +609,31 @@ do($ = window.jQuery, window) ->
|
|||
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
|
||||
|
|
|
@ -20,7 +20,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
fontSize: void 0,
|
||||
buttonClass: 'open-zammad-chat',
|
||||
inactiveClass: 'is-inactive',
|
||||
title: '<strong>Chat</strong> with us!'
|
||||
title: '<strong>Chat</strong> with us!',
|
||||
idleTimeout: 8,
|
||||
inactiveTimeout: 20
|
||||
};
|
||||
|
||||
ZammadChat.prototype._messageCount = 0;
|
||||
|
@ -61,8 +63,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
'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>.',
|
||||
'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.',
|
||||
'minutes': 'Minuten'
|
||||
'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.',
|
||||
'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.'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -117,6 +119,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
};
|
||||
|
||||
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.onConnectionEstablished = bind(this.onConnectionEstablished, this);
|
||||
this.setSessionId = bind(this.setSessionId, this);
|
||||
|
@ -133,6 +139,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
this.onCloseAnimationEnd = bind(this.onCloseAnimationEnd, this);
|
||||
this.closeWindow = bind(this.closeWindow, this);
|
||||
this.close = bind(this.close, this);
|
||||
this.onOpenAnimationEnd = bind(this.onOpenAnimationEnd, this);
|
||||
this.open = bind(this.open, this);
|
||||
this.renderMessage = bind(this.renderMessage, this);
|
||||
this.receiveMessage = bind(this.receiveMessage, this);
|
||||
|
@ -148,6 +155,11 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
this.log = bind(this.log, this);
|
||||
this.T = bind(this.T, this);
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
if (!$) {
|
||||
this.state = 'unsupported';
|
||||
this.log('notice', 'Chat: no jquery found!');
|
||||
return;
|
||||
}
|
||||
if (!window.WebSocket || !sessionStorage) {
|
||||
this.state = 'unsupported';
|
||||
this.log('notice', 'Chat: Browser not supported!');
|
||||
|
@ -211,7 +223,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
this.log('debug', 'ws:onmessage', pipe);
|
||||
switch (pipe.event) {
|
||||
case 'chat_error':
|
||||
this.log('error', pipe.data);
|
||||
this.log('notice', pipe.data);
|
||||
if (pipe.data && pipe.data.state === 'chat_disabled') {
|
||||
this.wsClose();
|
||||
}
|
||||
break;
|
||||
case 'chat_session_message':
|
||||
if (pipe.data.self_written) {
|
||||
|
@ -284,6 +299,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
|
||||
ZammadChat.prototype.reopenSession = function(data) {
|
||||
var i, len, message, ref, unfinishedMessage;
|
||||
this.inactiveTimeoutStart();
|
||||
unfinishedMessage = sessionStorage.getItem('unfinished_message');
|
||||
if (data.agent) {
|
||||
this.onConnectionEstablished(data);
|
||||
|
@ -322,9 +338,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
return;
|
||||
}
|
||||
this.isTyping = new Date();
|
||||
return this.send('chat_session_typing', {
|
||||
this.send('chat_session_typing', {
|
||||
session_id: this.sessionId
|
||||
});
|
||||
return this.inactiveTimeoutStart();
|
||||
};
|
||||
|
||||
ZammadChat.prototype.onSubmit = function(event) {
|
||||
|
@ -338,6 +355,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
if (!message) {
|
||||
return;
|
||||
}
|
||||
this.inactiveTimeoutStart();
|
||||
sessionStorage.removeItem('unfinished_message');
|
||||
messageElement = this.view('message')({
|
||||
message: message,
|
||||
|
@ -362,6 +380,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
};
|
||||
|
||||
ZammadChat.prototype.receiveMessage = function(data) {
|
||||
this.inactiveTimeoutStart();
|
||||
this.onAgentTypingEnd();
|
||||
this.maybeAddTimestamp();
|
||||
return this.renderMessage({
|
||||
|
@ -399,11 +418,13 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
}
|
||||
this.isOpen = true;
|
||||
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) {
|
||||
if (this.state === 'off' || this.state === 'unsupported') {
|
||||
|
@ -415,10 +436,18 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
if (!this.sessionId) {
|
||||
return;
|
||||
}
|
||||
this.send('chat_session_close', {
|
||||
session_id: this.sessionId
|
||||
});
|
||||
this.inactiveTimeoutStop();
|
||||
sessionStorage.removeItem('unfinished_message');
|
||||
if (this.onInitialQueueDelayId) {
|
||||
clearTimeout(this.onInitialQueueDelayId);
|
||||
}
|
||||
return this.closeWindow();
|
||||
if (event) {
|
||||
this.closeWindow();
|
||||
}
|
||||
return this.setSessionId(void 0);
|
||||
};
|
||||
|
||||
ZammadChat.prototype.closeWindow = function() {
|
||||
|
@ -434,11 +463,6 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
this.el.removeClass('zammad-chat-is-visible');
|
||||
this.disconnect();
|
||||
this.isOpen = false;
|
||||
this.send('chat_session_close', {
|
||||
session_id: this.sessionId
|
||||
});
|
||||
this.setSessionId(void 0);
|
||||
sessionStorage.removeItem('unfinished_message');
|
||||
return this.onWebSocketOpen();
|
||||
};
|
||||
|
||||
|
@ -555,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'));
|
||||
};
|
||||
|
||||
ZammadChat.prototype.session_init = function() {
|
||||
ZammadChat.prototype.sessionInit = function() {
|
||||
return this.send('chat_session_init');
|
||||
};
|
||||
|
||||
|
@ -604,6 +628,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
};
|
||||
|
||||
ZammadChat.prototype.onWebSocketOpen = function() {
|
||||
this.idleTimeoutStart();
|
||||
this.sessionId = sessionStorage.getItem('sessionId');
|
||||
this.log('debug', 'ws connected');
|
||||
this.send('chat_status_customer', {
|
||||
|
@ -630,7 +655,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
ZammadChat.prototype.onSessionClosed = function(data) {
|
||||
this.addStatus(this.T('Chat closed by %s', data.realname));
|
||||
this.disableInput();
|
||||
return this.setAgentOnlineState('offline');
|
||||
this.setAgentOnlineState('offline');
|
||||
return this.inactiveTimeoutStop();
|
||||
};
|
||||
|
||||
ZammadChat.prototype.disconnect = function() {
|
||||
|
@ -673,11 +699,16 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
};
|
||||
|
||||
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,
|
||||
delay: 10,
|
||||
unit: this.T('minutes')
|
||||
delay: this.options.inactiveTimeout
|
||||
}));
|
||||
this.close();
|
||||
reload = function() {
|
||||
return location.reload();
|
||||
};
|
||||
return this.el.find('.js-restart').click(reload);
|
||||
};
|
||||
|
||||
ZammadChat.prototype.showLoader = function() {
|
||||
|
@ -709,6 +740,49 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
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;
|
||||
|
||||
})();
|
||||
|
@ -1159,9 +1233,17 @@ window.zammadChatTemplates["timeout"] = function (__obj) {
|
|||
(function() {
|
||||
__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) {
|
||||
__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
|
@ -1,6 +1,11 @@
|
|||
<div class="zammad-chat-modal">
|
||||
<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>
|
||||
<div class="zammad-chat-button"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div>
|
||||
<% if @agent: %>
|
||||
<%- @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>
|
Loading…
Reference in a new issue