Updates app.bundle.js.
This commit is contained in:
parent
502eab7278
commit
5fc868ee96
|
@ -20,7 +20,7 @@
|
||||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||||
<script src="interface_config.js?v=5"></script>
|
<script src="interface_config.js?v=5"></script>
|
||||||
<script src="libs/app.bundle.js?v=124"></script>
|
<script src="libs/app.bundle.js?v=125"></script>
|
||||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||||
<link rel="stylesheet" href="css/font.css?v=7"/>
|
<link rel="stylesheet" href="css/font.css?v=7"/>
|
||||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||||
|
|
|
@ -975,6 +975,8 @@ var currentBrowser;
|
||||||
|
|
||||||
var browserVersion;
|
var browserVersion;
|
||||||
|
|
||||||
|
var isAndroid;
|
||||||
|
|
||||||
var RTCBrowserType = {
|
var RTCBrowserType = {
|
||||||
|
|
||||||
RTC_BROWSER_CHROME: "rtc_browser.chrome",
|
RTC_BROWSER_CHROME: "rtc_browser.chrome",
|
||||||
|
@ -1027,6 +1029,13 @@ var RTCBrowserType = {
|
||||||
|
|
||||||
usesUnifiedPlan: function() {
|
usesUnifiedPlan: function() {
|
||||||
return RTCBrowserType.isFirefox();
|
return RTCBrowserType.isFirefox();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the browser is running on an android device.
|
||||||
|
*/
|
||||||
|
isAndroid: function() {
|
||||||
|
return isAndroid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add version getters for other browsers when needed
|
// Add version getters for other browsers when needed
|
||||||
|
@ -1129,6 +1138,7 @@ function detectBrowser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
browserVersion = detectBrowser();
|
browserVersion = detectBrowser();
|
||||||
|
isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
||||||
|
|
||||||
module.exports = RTCBrowserType;
|
module.exports = RTCBrowserType;
|
||||||
},{}],9:[function(require,module,exports){
|
},{}],9:[function(require,module,exports){
|
||||||
|
@ -1156,7 +1166,8 @@ function getPreviousResolution(resolution) {
|
||||||
return resName;
|
return resName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setResolutionConstraints(constraints, resolution, isAndroid) {
|
function setResolutionConstraints(constraints, resolution) {
|
||||||
|
var isAndroid = RTCBrowserType.isAndroid();
|
||||||
|
|
||||||
if (Resolutions[resolution]) {
|
if (Resolutions[resolution]) {
|
||||||
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
||||||
|
@ -1178,8 +1189,7 @@ function setResolutionConstraints(constraints, resolution, isAndroid) {
|
||||||
constraints.video.mandatory.minHeight;
|
constraints.video.mandatory.minHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
|
function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
|
||||||
{
|
|
||||||
var constraints = {audio: false, video: false};
|
var constraints = {audio: false, video: false};
|
||||||
|
|
||||||
if (um.indexOf('video') >= 0) {
|
if (um.indexOf('video') >= 0) {
|
||||||
|
@ -1188,7 +1198,7 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
||||||
|
|
||||||
constraints.video.optional.push({ googLeakyBucket: true });
|
constraints.video.optional.push({ googLeakyBucket: true });
|
||||||
|
|
||||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
setResolutionConstraints(constraints, resolution);
|
||||||
}
|
}
|
||||||
if (um.indexOf('audio') >= 0) {
|
if (um.indexOf('audio') >= 0) {
|
||||||
if (!RTCBrowserType.isFirefox()) {
|
if (!RTCBrowserType.isFirefox()) {
|
||||||
|
@ -1343,7 +1353,7 @@ function RTCUtils(RTCService, onTemasysPluginReady)
|
||||||
};
|
};
|
||||||
// DTLS should now be enabled by default but..
|
// DTLS should now be enabled by default but..
|
||||||
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
|
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
|
||||||
if (navigator.userAgent.indexOf('Android') != -1) {
|
if (RTCBrowserType.isAndroid()) {
|
||||||
this.pc_constraints = {}; // disable DTLS on Android
|
this.pc_constraints = {}; // disable DTLS on Android
|
||||||
}
|
}
|
||||||
if (!webkitMediaStream.prototype.getVideoTracks) {
|
if (!webkitMediaStream.prototype.getVideoTracks) {
|
||||||
|
@ -1420,11 +1430,9 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||||
um, success_callback, failure_callback, resolution,bandwidth, fps,
|
um, success_callback, failure_callback, resolution,bandwidth, fps,
|
||||||
desktopStream) {
|
desktopStream) {
|
||||||
currentResolution = resolution;
|
currentResolution = resolution;
|
||||||
// Check if we are running on Android device
|
|
||||||
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
|
||||||
|
|
||||||
var constraints = getConstraints(
|
var constraints = getConstraints(
|
||||||
um, resolution, bandwidth, fps, desktopStream, isAndroid);
|
um, resolution, bandwidth, fps, desktopStream);
|
||||||
|
|
||||||
console.info("Get media constraints", constraints);
|
console.info("Get media constraints", constraints);
|
||||||
|
|
||||||
|
@ -3280,14 +3288,17 @@ function registerListeners() {
|
||||||
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
|
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
|
||||||
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
|
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
|
||||||
onAuthenticationRequired);
|
onAuthenticationRequired);
|
||||||
APP.xmpp.addListener(XMPPEvents.VIDEO_TYPE, onPeerVideoTypeChanged);
|
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
|
||||||
|
onPeerVideoTypeChanged);
|
||||||
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
|
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
|
||||||
function (resource, devices) {
|
function (resource, devices) {
|
||||||
VideoLayout.setDeviceAvailabilityIcons(resource, devices);
|
VideoLayout.setDeviceAvailabilityIcons(resource, devices);
|
||||||
});
|
});
|
||||||
|
|
||||||
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
|
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
|
||||||
APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
|
VideoLayout.onAudioMute);
|
||||||
|
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
|
||||||
|
VideoLayout.onVideoMute);
|
||||||
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
|
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
|
||||||
UI.setAudioMuted(doMuteAudio);
|
UI.setAudioMuted(doMuteAudio);
|
||||||
});
|
});
|
||||||
|
@ -14780,10 +14791,11 @@ JingleSessionPC.prototype.setLocalDescription = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||||
|
// TODO: is this hack (along with the XMPPEvent-s used only for it) still needed
|
||||||
|
// now that we announce an SSRC for receive-only streams?
|
||||||
function sendKeyframe(pc) {
|
function sendKeyframe(pc) {
|
||||||
console.log('sendkeyframe', pc.iceConnectionState);
|
console.log('sendkeyframe', pc.iceConnectionState);
|
||||||
if (pc.iceConnectionState !== 'connected') return; // safe...
|
if (pc.iceConnectionState !== 'connected') return; // safe...
|
||||||
var self = this;
|
|
||||||
pc.setRemoteDescription(
|
pc.setRemoteDescription(
|
||||||
pc.remoteDescription,
|
pc.remoteDescription,
|
||||||
function () {
|
function () {
|
||||||
|
@ -14856,6 +14868,8 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
|
||||||
|
|
||||||
var isVideo = data.stream.getVideoTracks().length > 0;
|
var isVideo = data.stream.getVideoTracks().length > 0;
|
||||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||||
|
// TODO: is this hack still needed now that we announce an SSRC for
|
||||||
|
// receive-only streams?
|
||||||
if (isVideo &&
|
if (isVideo &&
|
||||||
data.peerjid && this.peerjid === data.peerjid &&
|
data.peerjid && this.peerjid === data.peerjid &&
|
||||||
data.stream.getVideoTracks().length === 0 &&
|
data.stream.getVideoTracks().length === 0 &&
|
||||||
|
@ -17461,10 +17475,11 @@ module.exports = function(XMPP, eventEmitter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var url;
|
||||||
// Parse prezi tag.
|
// Parse prezi tag.
|
||||||
var presentation = $(pres).find('>prezi');
|
var presentation = $(pres).find('>prezi');
|
||||||
if (presentation.length) {
|
if (presentation.length) {
|
||||||
var url = presentation.attr('url');
|
url = presentation.attr('url');
|
||||||
var current = presentation.find('>current').text();
|
var current = presentation.find('>current').text();
|
||||||
|
|
||||||
console.log('presentation info received from', from, url);
|
console.log('presentation info received from', from, url);
|
||||||
|
@ -17479,7 +17494,7 @@ module.exports = function(XMPP, eventEmitter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.preziMap[from] != null) {
|
else if (this.preziMap[from] != null) {
|
||||||
var url = this.preziMap[from];
|
url = this.preziMap[from];
|
||||||
delete this.preziMap[from];
|
delete this.preziMap[from];
|
||||||
$(document).trigger('presentationremoved.muc', [from, url]);
|
$(document).trigger('presentationremoved.muc', [from, url]);
|
||||||
}
|
}
|
||||||
|
@ -17487,22 +17502,22 @@ module.exports = function(XMPP, eventEmitter) {
|
||||||
// Parse audio info tag.
|
// Parse audio info tag.
|
||||||
var audioMuted = $(pres).find('>audiomuted');
|
var audioMuted = $(pres).find('>audiomuted');
|
||||||
if (audioMuted.length) {
|
if (audioMuted.length) {
|
||||||
eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
|
eventEmitter.emit(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
|
||||||
from, (audioMuted.text() === "true"));
|
from, (audioMuted.text() === "true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse video info tag.
|
// Parse video info tag.
|
||||||
var videoMuted = $(pres).find('>videomuted');
|
var videoMuted = $(pres).find('>videomuted');
|
||||||
if (videoMuted.length) {
|
if (videoMuted.length) {
|
||||||
eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
|
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
|
||||||
from, (videoMuted.text() === "true"));
|
from, (videoMuted.text() === "true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var startMuted = $(pres).find('>startmuted');
|
var startMuted = $(pres).find('>startmuted');
|
||||||
if (startMuted.length)
|
if (startMuted.length && Moderator.isPeerModerator(from)) {
|
||||||
{
|
|
||||||
eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
|
eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
|
||||||
startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
|
startMuted.attr("audio") === "true",
|
||||||
|
startMuted.attr("video") === "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
var devices = $(pres).find('>devices');
|
var devices = $(pres).find('>devices');
|
||||||
|
@ -17529,7 +17544,7 @@ module.exports = function(XMPP, eventEmitter) {
|
||||||
{
|
{
|
||||||
if (videoType.text().length)
|
if (videoType.text().length)
|
||||||
{
|
{
|
||||||
eventEmitter.emit(XMPPEvents.VIDEO_TYPE,
|
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
|
||||||
Strophe.getResourceFromJid(from), videoType.text());
|
Strophe.getResourceFromJid(from), videoType.text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37770,49 +37785,87 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},{}],161:[function(require,module,exports){
|
},{}],161:[function(require,module,exports){
|
||||||
var XMPPEvents = {
|
var XMPPEvents = {
|
||||||
|
// Designates an event indicating that the connection to the XMPP server
|
||||||
|
// failed.
|
||||||
CONNECTION_FAILED: "xmpp.connection.failed",
|
CONNECTION_FAILED: "xmpp.connection.failed",
|
||||||
// Indicates an interrupted connection event.
|
// Designates an event indicating that the media (ICE) connection was
|
||||||
|
// interrupted. This should go to the RTC module.
|
||||||
CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
|
CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
|
||||||
// Indicates a restored connection event.
|
// Designates an event indicating that the media (ICE) connection was
|
||||||
|
// restored. This should go to the RTC module.
|
||||||
CONNECTION_RESTORED: "xmpp.connection.restored",
|
CONNECTION_RESTORED: "xmpp.connection.restored",
|
||||||
CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
|
// Designates an event indicating that an offer (e.g. Jingle
|
||||||
|
// session-initiate) was received.
|
||||||
CALL_INCOMING: "xmpp.callincoming.jingle",
|
CALL_INCOMING: "xmpp.callincoming.jingle",
|
||||||
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
|
// Designates an event indicating that we were kicked from the XMPP MUC.
|
||||||
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
|
|
||||||
KICKED: "xmpp.kicked",
|
KICKED: "xmpp.kicked",
|
||||||
BRIDGE_DOWN: "xmpp.bridge_down",
|
// Designates an event indicating that the userID for a specific JID has
|
||||||
|
// changed.
|
||||||
USER_ID_CHANGED: "xmpp.user_id_changed",
|
USER_ID_CHANGED: "xmpp.user_id_changed",
|
||||||
// We joined the MUC
|
// Designates an event indicating that we have joined the XMPP MUC.
|
||||||
MUC_JOINED: "xmpp.muc_joined",
|
MUC_JOINED: "xmpp.muc_joined",
|
||||||
// A member joined the MUC
|
// Designates an event indicating that a participant joined the XMPP MUC.
|
||||||
MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
|
MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
|
||||||
// A member left the MUC
|
// Designates an event indicating that a participant left the XMPP MUC.
|
||||||
MUC_MEMBER_LEFT: "xmpp.muc_member_left",
|
MUC_MEMBER_LEFT: "xmpp.muc_member_left",
|
||||||
|
// Designates an event indicating that the MUC role of a participant has
|
||||||
|
// changed.
|
||||||
MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
|
MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
|
||||||
|
// Designates an event indicating that the XMPP MUC was destroyed.
|
||||||
MUC_DESTROYED: "xmpp.muc_destroyed",
|
MUC_DESTROYED: "xmpp.muc_destroyed",
|
||||||
|
// Designates an event indicating that the display name of a participant
|
||||||
|
// has changed.
|
||||||
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
|
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
|
||||||
|
// Designates an event indicating that we received statistics from a
|
||||||
|
// participant in the MUC.
|
||||||
REMOTE_STATS: "xmpp.remote_stats",
|
REMOTE_STATS: "xmpp.remote_stats",
|
||||||
|
// Designates an event indicating that our role in the XMPP MUC has changed.
|
||||||
LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
|
LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
|
||||||
PRESENCE_STATUS: "xmpp.presence_status",
|
// Designates an event indicating that the subject of the XMPP MUC has
|
||||||
RESERVATION_ERROR: "xmpp.room_reservation_error",
|
// changed.
|
||||||
SUBJECT_CHANGED: "xmpp.subject_changed",
|
SUBJECT_CHANGED: "xmpp.subject_changed",
|
||||||
|
// Designates an event indicating that an XMPP message in the MUC was
|
||||||
|
// received.
|
||||||
MESSAGE_RECEIVED: "xmpp.message_received",
|
MESSAGE_RECEIVED: "xmpp.message_received",
|
||||||
|
// Designates an event indicating that we sent an XMPP message to the MUC.
|
||||||
SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
|
SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
|
||||||
|
// Designates an event indicating that the video type (e.g. 'camera' or
|
||||||
|
// 'screen') for a participant has changed.
|
||||||
|
PARTICIPANT_VIDEO_TYPE_CHANGED: "xmpp.video_type",
|
||||||
|
// Designates an event indicating that a participant in the XMPP MUC has
|
||||||
|
// advertised that they have audio muted (or unmuted).
|
||||||
|
PARTICIPANT_AUDIO_MUTED: "xmpp.audio_muted",
|
||||||
|
// Designates an event indicating that a participant in the XMPP MUC has
|
||||||
|
// advertised that they have video muted (or unmuted).
|
||||||
|
PARTICIPANT_VIDEO_MUTED: "xmpp.video_muted",
|
||||||
|
// Designates an event indicating that the focus has asked us to mute our
|
||||||
|
// audio.
|
||||||
|
AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
|
||||||
|
// Designates an event indicating that a moderator in the room changed the
|
||||||
|
// "start muted" settings for the conference.
|
||||||
|
START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
|
||||||
|
// Designates an event indicating that we should join the conference with
|
||||||
|
// audio and/or video muted.
|
||||||
|
START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
|
||||||
|
|
||||||
|
|
||||||
|
PEERCONNECTION_READY: "xmpp.peerconnection_ready",
|
||||||
|
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
|
||||||
PASSWORD_REQUIRED: "xmpp.password_required",
|
PASSWORD_REQUIRED: "xmpp.password_required",
|
||||||
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
|
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
|
||||||
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
|
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
|
||||||
ETHERPAD: "xmpp.etherpad",
|
ETHERPAD: "xmpp.etherpad",
|
||||||
DEVICE_AVAILABLE: "xmpp.device_available",
|
DEVICE_AVAILABLE: "xmpp.device_available",
|
||||||
VIDEO_TYPE: "xmpp.video_type",
|
BRIDGE_DOWN: "xmpp.bridge_down",
|
||||||
PEERCONNECTION_READY: "xmpp.peerconnection_ready",
|
PRESENCE_STATUS: "xmpp.presence_status",
|
||||||
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
|
RESERVATION_ERROR: "xmpp.room_reservation_error",
|
||||||
AUDIO_MUTED: "xmpp.audio_muted",
|
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
|
||||||
VIDEO_MUTED: "xmpp.video_muted",
|
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
|
||||||
AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
|
// TODO: only used in a hack, should probably be removed.
|
||||||
START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
|
|
||||||
START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
|
|
||||||
SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
|
SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
|
||||||
|
// TODO: only used in a hack, should probably be removed.
|
||||||
SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
|
SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
|
||||||
|
// TODO: only used in a hack, should probably be removed.
|
||||||
CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
|
CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
|
||||||
JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
|
JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
|
||||||
PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
|
PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
|
||||||
|
|
Loading…
Reference in New Issue