VRFS-3190 : Several bug fixes for band profile
* Storing validator in window was screwing things up now that we have multiple of these. This is now stored at the class instance level. * Deleting a performance sample didn’t delete it from the validator, as the comparison was flaky. * Styling fixes
This commit is contained in:
parent
972b65c4ef
commit
cb058e851b
|
|
@ -5,14 +5,14 @@
|
|||
context.JK = context.JK || {};
|
||||
|
||||
// TODO: Add a target type, which can be band or user -- call the
|
||||
// appropriate API methods.
|
||||
// appropriate API methods.
|
||||
context.JK.AccountProfileSamples = function(app, parent, loadFn, updateFn) {
|
||||
var $document = $(document)
|
||||
|
||||
// used to initialize RecordingSourceValidator in site_validator.js.coffee
|
||||
window.jamkazamRecordingSources = [];
|
||||
window.soundCloudRecordingSources = [];
|
||||
window.youTubeRecordingSources = [];
|
||||
var jamkazamRecordingSources = [];
|
||||
var soundCloudRecordingSources = [];
|
||||
var youTubeRecordingSources = [];
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
var ui = new context.JK.UIHelper(JK.app);
|
||||
var target = {};
|
||||
var profileUtils = context.JK.ProfileUtils;
|
||||
var $screen = $('.profile-online-sample-controls', parent);
|
||||
var $screen = $('.profile-online-sample-controls', parent);
|
||||
// online presences
|
||||
var $website = $screen.find('.website');
|
||||
var $website = $screen.find('.website');
|
||||
var $soundCloudUsername = $screen.find('.soundcloud-username');
|
||||
var $reverbNationUsername = $screen.find('.reverbnation-username');
|
||||
var $bandCampUsername = $screen.find('.bandcamp-username');
|
||||
|
|
@ -42,10 +42,22 @@
|
|||
var $btnBack = $screen.find('.account-edit-profile-back');
|
||||
var $btnSubmit = $screen.find('.account-edit-profile-submit');
|
||||
|
||||
|
||||
var urlValidator=null;
|
||||
var soundCloudValidator=null;
|
||||
var reverbNationValidator=null;
|
||||
var bandCampValidator=null;
|
||||
var fandalismValidator=null;
|
||||
var youTubeValidator=null;
|
||||
var facebookValidator=null;
|
||||
var twitterValidator=null;
|
||||
var soundCloudRecordingValidator=null;
|
||||
var youTubeRecordingValidator=null;
|
||||
|
||||
function beforeShow(data) {
|
||||
}
|
||||
|
||||
function afterShow(data) {
|
||||
function afterShow(data) {
|
||||
$.when(loadFn())
|
||||
.done(function(targetPlayer) {
|
||||
if (targetPlayer && targetPlayer.keys && targetPlayer.keys.length > 0) {
|
||||
|
|
@ -56,7 +68,7 @@
|
|||
|
||||
function renderPlayer(targetPlayer) {
|
||||
renderPresence(targetPlayer);
|
||||
renderSamples(targetPlayer);
|
||||
renderSamples(targetPlayer);
|
||||
}
|
||||
|
||||
function renderPresence(targetPlayer) {
|
||||
|
|
@ -108,20 +120,20 @@
|
|||
function renderSamples(targetPlayer) {
|
||||
// JamKazam recordings
|
||||
var samples = profileUtils.jamkazamSamples(targetPlayer.performance_samples);
|
||||
loadSamples(samples, 'jamkazam', $jamkazamSampleList, window.jamkazamRecordingSources);
|
||||
loadSamples(samples, 'jamkazam', $jamkazamSampleList, jamkazamRecordingSources);
|
||||
|
||||
// SoundCloud recordings
|
||||
samples = profileUtils.soundCloudSamples(targetPlayer.performance_samples);
|
||||
loadSamples(samples, 'soundcloud', $soundCloudSampleList, window.soundCloudRecordingSources);
|
||||
loadSamples(samples, 'soundcloud', $soundCloudSampleList, soundCloudRecordingSources);
|
||||
|
||||
// YouTube videos
|
||||
samples = profileUtils.youTubeSamples(targetPlayer.performance_samples);
|
||||
loadSamples(samples, 'youtube', $youTubeSampleList, window.youTubeRecordingSources);
|
||||
loadSamples(samples, 'youtube', $youTubeSampleList, youTubeRecordingSources);
|
||||
}
|
||||
|
||||
function loadSamples(samples, type, $sampleList, recordingSources) {
|
||||
$sampleList.empty();
|
||||
|
||||
$sampleList.find(":not(.empty)").remove();
|
||||
|
||||
if (type === 'jamkazam') {
|
||||
$.each(samples, function(index, val) {
|
||||
recordingSources.push({
|
||||
|
|
@ -147,9 +159,7 @@
|
|||
var recordingTitleAttr = ' data-recording-title="' + val.description + '"';
|
||||
var title = formatTitle(val.description);
|
||||
$sampleList.append('<div class="clearall recording-row left entry"' + recordingIdAttr + recordingUrlAttr + recordingTitleAttr + '>' + title + '</div>');
|
||||
|
||||
var onclick = "onclick=removeRow(\'" + val.service_id + "\',\'" + type + "\');";
|
||||
$sampleList.append('<div class="right close-button" ' + recordingIdAttr + onclick + '>X</div>');
|
||||
$sampleList.append('<div class="right close-button" data-recording-type="' + type + '"' + recordingIdAttr + '>X</div>');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -160,9 +170,7 @@
|
|||
|
||||
var recordingIdAttr = ' data-recording-id="' + recordingId + '" ';
|
||||
$jamkazamSampleList.append('<div class="clearall recording-row left entry"' + recordingIdAttr + '>' + title + '</div>');
|
||||
|
||||
var onclick = "onclick=removeRow(\'" + recordingId + "\',\'jamkazam\');";
|
||||
$jamkazamSampleList.append('<div class="right close-button" ' + recordingIdAttr + onclick + '>X</div>');
|
||||
$jamkazamSampleList.append('<div class="right close-button" data-recording-type="jamkazam"' + recordingIdAttr + '>X</div>');
|
||||
}
|
||||
|
||||
function events() {
|
||||
|
|
@ -174,14 +182,14 @@
|
|||
// retrieve recordings and pass to modal dialog
|
||||
api.getClaimedRecordings()
|
||||
.done(function(response) {
|
||||
ui.launchRecordingSelectorDialog(response, window.jamkazamRecordingSources, function(selectedRecordings) {
|
||||
ui.launchRecordingSelectorDialog(response, jamkazamRecordingSources, function(selectedRecordings) {
|
||||
$jamkazamSampleList.empty();
|
||||
|
||||
window.jamkazamRecordingSources = [];
|
||||
jamkazamRecordingSources = [];
|
||||
|
||||
// update the list with the selected recordings
|
||||
$.each(selectedRecordings, function(index, val) {
|
||||
window.jamkazamRecordingSources.push({
|
||||
jamkazamRecordingSources.push({
|
||||
'claimed_recording_id': val.id,
|
||||
'description': val.name
|
||||
});
|
||||
|
|
@ -190,7 +198,7 @@
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
|
@ -208,8 +216,9 @@
|
|||
|
||||
enableSubmits();
|
||||
|
||||
$(".sample-list .close-button", $screen).on("click", function(e) {
|
||||
}
|
||||
$screen.find(".sample-list").off("click").on("click", ".close-button", function(e) {
|
||||
removeRow($(this).data("recording-id"), $(this).data("recording-type"))
|
||||
})
|
||||
}
|
||||
|
||||
function enableSubmits() {
|
||||
|
|
@ -291,7 +300,7 @@
|
|||
function buildPlayer() {
|
||||
// extract online presences
|
||||
var op = [];
|
||||
var presenceTypes = profileUtils.ONLINE_PRESENCE_TYPES;
|
||||
var presenceTypes = profileUtils.ONLINE_PRESENCE_TYPES;
|
||||
addOnlinePresence(op, $soundCloudUsername.val(), presenceTypes.SOUNDCLOUD.description);
|
||||
addOnlinePresence(op, $reverbNationUsername.val(), presenceTypes.REVERBNATION.description);
|
||||
addOnlinePresence(op, $bandCampUsername.val(), presenceTypes.BANDCAMP.description);
|
||||
|
|
@ -306,12 +315,12 @@
|
|||
addPerformanceSamples(ps, $jamkazamSampleList, performanceSampleTypes.JAMKAZAM.description);
|
||||
addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description);
|
||||
addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description);
|
||||
|
||||
|
||||
return {
|
||||
website: $website.val(),
|
||||
online_presences: op,
|
||||
performance_samples: ps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function postUpdateProfileSuccess(response) {
|
||||
|
|
@ -334,16 +343,14 @@
|
|||
$('div[data-recording-id=' + recordingId + ']').remove();
|
||||
var sampleList = $('.sample-list[source-type="' + type + '"]')
|
||||
var rowCnt = sampleList.find('.recording-row').length
|
||||
|
||||
if (rowCnt==0) {
|
||||
if (0==parseInt(rowCnt)) {
|
||||
sampleList.find(".empty").removeClass("hidden")
|
||||
}
|
||||
|
||||
if (type === 'soundcloud') {
|
||||
window.soundCloudRecordingValidator.removeRecordingId(recordingId);
|
||||
}
|
||||
else if (type === 'youtube') {
|
||||
window.youTubeRecordingValidator.removeRecordingId(recordingId);
|
||||
soundCloudRecordingValidator.removeRecordingId(recordingId);
|
||||
} else if (type === 'youtube') {
|
||||
youTubeRecordingValidator.removeRecordingId(recordingId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,13 +359,13 @@
|
|||
}
|
||||
|
||||
// This function is a bit of a mess. It was pulled
|
||||
// from the html.erb file verbatim, and could use a
|
||||
// from the html.erb file verbatim, and could use a
|
||||
// refactor:
|
||||
function initializeValidators() {
|
||||
function initializeValidators() {
|
||||
var initialized = false;
|
||||
//$document.on('JAMKAZAM_READY', function(e, data) {
|
||||
window.JK.JamServer.get$Server().on(window.JK.EVENTS.CONNECTION_UP, function() {
|
||||
if(initialized) {
|
||||
//$document.on('JAMKAZAM_READY', function(e, data) {
|
||||
JK.JamServer.get$Server().on(JK.EVENTS.CONNECTION_UP, function() {
|
||||
if(initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
|
@ -368,36 +375,35 @@
|
|||
var $btnAddYouTubeVideo = $screen.find('.btn-add-youtube-video');
|
||||
// var $soundCloudSampleList = $screen.find('.samples.soundcloud');
|
||||
// var $youTubeSampleList = $screen.find('.samples.youtube');
|
||||
|
||||
|
||||
setTimeout(function() {
|
||||
window.urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.urlValidator.init();
|
||||
|
||||
window.soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.soundCloudValidator.init();
|
||||
setTimeout(function() {
|
||||
urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
urlValidator.init();
|
||||
|
||||
window.reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.reverbNationValidator.init();
|
||||
soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
soundCloudValidator.init();
|
||||
|
||||
window.bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.bandCampValidator.init();
|
||||
reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
reverbNationValidator.init();
|
||||
|
||||
window.fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.fandalismValidator.init();
|
||||
bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
bandCampValidator.init();
|
||||
|
||||
window.youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.youTubeValidator.init();
|
||||
fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
fandalismValidator.init();
|
||||
|
||||
window.facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.facebookValidator.init();
|
||||
youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
youTubeValidator.init();
|
||||
|
||||
window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
window.twitterValidator.init();
|
||||
facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
facebookValidator.init();
|
||||
|
||||
window.soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', soundCloudSuccessCallback, siteFailCallback, parent);
|
||||
logger.debug("window.soundCloudRecordingValidator", window.soundCloudRecordingValidator, parent)
|
||||
window.youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', youTubeSuccessCallback, siteFailCallback, parent);
|
||||
twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback, parent);
|
||||
twitterValidator.init();
|
||||
|
||||
soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', soundCloudSuccessCallback, siteFailCallback, parent);
|
||||
youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', youTubeSuccessCallback, siteFailCallback, parent);
|
||||
|
||||
$document.triggerHandler('INIT_SITE_VALIDATORS');
|
||||
}, 1);
|
||||
|
|
@ -408,21 +414,21 @@
|
|||
$inputDiv.find('.error-text').remove();
|
||||
}
|
||||
|
||||
function userNameFailCallback($inputDiv) {
|
||||
function userNameFailCallback($inputDiv) {
|
||||
$inputDiv.addClass('error');
|
||||
$inputDiv.find('.error-text').remove();
|
||||
$inputDiv.append("<span class='error-text'>Invalid username</span>").show();
|
||||
}
|
||||
|
||||
function soundCloudSuccessCallback($inputDiv) {
|
||||
siteSuccessCallback($inputDiv, window.soundCloudRecordingValidator, $soundCloudSampleList, 'soundcloud');
|
||||
function soundCloudSuccessCallback($inputDiv) {
|
||||
siteSuccessCallback($inputDiv, soundCloudRecordingValidator, $soundCloudSampleList, 'soundcloud');
|
||||
}
|
||||
|
||||
function youTubeSuccessCallback($inputDiv) {
|
||||
siteSuccessCallback($inputDiv, window.youTubeRecordingValidator, $youTubeSampleList, 'youtube');
|
||||
siteSuccessCallback($inputDiv, youTubeRecordingValidator, $youTubeSampleList, 'youtube');
|
||||
}
|
||||
|
||||
function siteSuccessCallback($inputDiv, recordingSiteValidator, sampleList, type) {
|
||||
function siteSuccessCallback($inputDiv, recordingSiteValidator, sampleList, type) {
|
||||
sampleList.find(".empty").addClass("hidden")
|
||||
$inputDiv.removeClass('error');
|
||||
$inputDiv.find('.error-text').remove();
|
||||
|
|
@ -437,9 +443,7 @@
|
|||
var recordingTitleAttr = ' data-recording-title="' + addedRecording.recording_title + '"';
|
||||
var title = formatTitle(addedRecording.recording_title);
|
||||
sampleList.append('<div class="clearall recording-row left entry"' + recordingIdAttr + recordingUrlAttr + recordingTitleAttr + '>' + title + '</div>');
|
||||
|
||||
var onclick = "onclick=removeRow(\'" + addedRecording.recording_id + "\',\'" + type + "\');";
|
||||
sampleList.append('<div class="right close-button" ' + recordingIdAttr + onclick + '>X</div>');
|
||||
sampleList.append('<div class="right close-button" data-recording-type="' + type + '"' + recordingIdAttr + '>X</div>');
|
||||
}
|
||||
|
||||
$inputDiv.find('input').val('');
|
||||
|
|
@ -454,16 +458,15 @@
|
|||
//});
|
||||
|
||||
$document.on('INIT_SITE_VALIDATORS', function(e, data) {
|
||||
logger.debug("ZZZwindow.soundCloudRecordingValidator", window.soundCloudRecordingValidator)
|
||||
window.soundCloudRecordingValidator.init(window.soundCloudRecordingSources);
|
||||
window.youTubeRecordingValidator.init(window.youTubeRecordingSources);
|
||||
soundCloudRecordingValidator.init(soundCloudRecordingSources);
|
||||
youTubeRecordingValidator.init(youTubeRecordingSources);
|
||||
});
|
||||
|
||||
|
||||
} // end initializeValidators.
|
||||
|
||||
} // end initializeValidators.
|
||||
|
||||
function resetForm() {
|
||||
$("input", $screen).val("")
|
||||
$("input", $screen).val("")
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ context.JK.SiteValidator = class SiteValidator
|
|||
@rest = context.JK.Rest()
|
||||
@site_type = site_type
|
||||
@input_div = $(".site_validator."+site_type+"_validator", parent)
|
||||
@data_input = @input_div.find('input')
|
||||
|
||||
@data_input = @input_div.find('input')
|
||||
|
||||
@logger = context.JK.logger
|
||||
@spinner = @input_div.find('span.spinner-small')
|
||||
@checkmark = @input_div.find('.validate-checkmark')
|
||||
|
|
@ -34,7 +34,7 @@ context.JK.SiteValidator = class SiteValidator
|
|||
@site_status = null
|
||||
|
||||
dataToValidate: () =>
|
||||
url = @data_input.val()
|
||||
url = @data_input.val()
|
||||
if url && 0 < url.length
|
||||
url.substring(0,2000)
|
||||
else
|
||||
|
|
@ -50,7 +50,7 @@ context.JK.SiteValidator = class SiteValidator
|
|||
@checkmark.hide()
|
||||
yn
|
||||
|
||||
didBlur: () =>
|
||||
didBlur: () =>
|
||||
if this.showFormatStatus()
|
||||
this.validateSite()
|
||||
|
||||
|
|
@ -100,14 +100,14 @@ context.JK.SiteValidator = class SiteValidator
|
|||
@checkmark.show()
|
||||
else
|
||||
@checkmark.hide()
|
||||
|
||||
siteIsValid: () =>
|
||||
|
||||
siteIsValid: () =>
|
||||
this.setSiteStatus(true)
|
||||
|
||||
siteIsInvalid: () =>
|
||||
|
||||
siteIsInvalid: () =>
|
||||
this.setSiteStatus(false)
|
||||
|
||||
renderErrors: (errors) =>
|
||||
|
||||
renderErrors: (errors) =>
|
||||
errdiv = @input_div.find('.error')
|
||||
if errmsg = context.JK.format_errors("site", errors)
|
||||
errdiv.show()
|
||||
|
|
@ -120,7 +120,7 @@ context.JK.SiteValidator = class SiteValidator
|
|||
dfr = $.Deferred()
|
||||
if null == @site_status
|
||||
@deferred_status_check = dfr
|
||||
this.validateSite()
|
||||
this.validateSite()
|
||||
else
|
||||
if true == @site_status
|
||||
dfr.resolve()
|
||||
|
|
@ -128,9 +128,9 @@ context.JK.SiteValidator = class SiteValidator
|
|||
dfr.reject()
|
||||
return dfr.promise()
|
||||
|
||||
|
||||
context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator
|
||||
constructor: (site_type, success_callback, fail_callback, parent) ->
|
||||
|
||||
context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator
|
||||
constructor: (site_type, success_callback, fail_callback, parent) ->
|
||||
super(site_type, success_callback, fail_callback, parent)
|
||||
@recording_sources = []
|
||||
@is_rec_src = true
|
||||
|
|
@ -142,7 +142,7 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit
|
|||
super()
|
||||
if sources
|
||||
@recording_sources = sources
|
||||
@add_btn.on 'click', =>
|
||||
@add_btn.off('click').on 'click', =>
|
||||
this.attemptAdd()
|
||||
|
||||
processSiteCheckSucceed: (response) =>
|
||||
|
|
@ -163,7 +163,7 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit
|
|||
if @site_fail_callback
|
||||
@site_fail_callback(@input_div)
|
||||
|
||||
didBlur: () =>
|
||||
didBlur: () =>
|
||||
# do nothing, validate on add only
|
||||
|
||||
validateSite: () =>
|
||||
|
|
@ -177,13 +177,14 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit
|
|||
|
||||
removeRecordingId: (recording_id) =>
|
||||
start_len = @recording_sources.length
|
||||
@recording_sources = $.grep @recording_sources, (src_data) ->
|
||||
src_data['recording_id'] != recording_id
|
||||
@recording_sources = @recording_sources.filter (src) ->
|
||||
src["recording_id"] isnt recording_id.toString()
|
||||
start_len != @recording_sources.length
|
||||
|
||||
containsRecordingUrl: (url) =>
|
||||
vals = $.grep @recording_sources, (src_data) ->
|
||||
src_data['url'] == url
|
||||
src_data['url'] is url
|
||||
|
||||
0 < vals.length
|
||||
|
||||
recordingSources: () =>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
@import "common.css.scss";
|
||||
@import "site_validator.css.scss";
|
||||
|
||||
.profile-online-sample-controls {
|
||||
.profile-online-sample-controls {
|
||||
table.profile-table {
|
||||
width: 100%;
|
||||
tr:nth-child(even) td {
|
||||
padding: 0.25em 0.25em 1em 0.25em;
|
||||
padding: 0.25em 0.25em 1em 0.25em;
|
||||
vertical-align: top;
|
||||
}
|
||||
tr:nth-child(odd) td {
|
||||
|
|
@ -22,12 +22,15 @@
|
|||
}
|
||||
min-height: 150px;
|
||||
overflow: scroll;
|
||||
.close-button {
|
||||
cursor:pointer;
|
||||
}
|
||||
}
|
||||
|
||||
table.control-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.sample-row {
|
||||
position: relative;
|
||||
clear: both;
|
||||
|
|
@ -39,12 +42,12 @@
|
|||
}
|
||||
|
||||
.presence {
|
||||
margin: 3px 30px 15px 0px;
|
||||
margin: 3px 30px 15px 0px;
|
||||
}
|
||||
|
||||
.site_validator {
|
||||
a, .spinner-small {
|
||||
margin: 1px 1px 2px 2px;
|
||||
margin: 1px 1px 2px 2px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue