kopia lustrzana https://github.com/c9/core
Loading in debug mode of runners, builders, outline, ace themes and keymaps
rodzic
3d433105f9
commit
6907f7e482
10
package.json
10
package.json
|
@ -49,7 +49,7 @@
|
|||
},
|
||||
"licenses": [],
|
||||
"c9plugins": {
|
||||
"c9.ide.language": "#afda452919",
|
||||
"c9.ide.language": "#b201fe581a",
|
||||
"c9.ide.language.css": "#ef8a28943e",
|
||||
"c9.ide.language.generic": "#8a3be4533a",
|
||||
"c9.ide.language.html": "#bbe81afed1",
|
||||
|
@ -62,14 +62,14 @@
|
|||
"c9.ide.language.jsonalyzer": "#7261f47b26",
|
||||
"c9.ide.collab": "#7b09419b5c",
|
||||
"c9.ide.local": "#cf624506cc",
|
||||
"c9.ide.find": "#ef82bc4f0d",
|
||||
"c9.ide.find": "#e0ec892635",
|
||||
"c9.ide.find.infiles": "#1b83cf12f1",
|
||||
"c9.ide.find.replace": "#e4daf722b8",
|
||||
"c9.ide.run.debug": "#638e6b00b3",
|
||||
"c9.automate": "#86bf1ee1ca",
|
||||
"c9.ide.ace.emmet": "#e5f1a92ac3",
|
||||
"c9.ide.ace.gotoline": "#4d1a93172c",
|
||||
"c9.ide.ace.keymaps": "#6c4bb65b1f",
|
||||
"c9.ide.ace.keymaps": "#43445d6306",
|
||||
"c9.ide.ace.repl": "#864dc3aea1",
|
||||
"c9.ide.ace.split": "#0ae0151c78",
|
||||
"c9.ide.ace.statusbar": "#d7b45bb7c3",
|
||||
|
@ -95,8 +95,8 @@
|
|||
"c9.ide.readonly": "#f6f07bbe42",
|
||||
"c9.ide.recentfiles": "#7c099abf40",
|
||||
"c9.ide.remote": "#cd45e81d2f",
|
||||
"c9.ide.run": "#f3ac81cc10",
|
||||
"c9.ide.run.build": "#fd57b3341d",
|
||||
"c9.ide.run": "#71c5562e42",
|
||||
"c9.ide.run.build": "#ad45874c88",
|
||||
"c9.ide.run.debug.xdebug": "#b91d23f48b",
|
||||
"c9.ide.save": "#b876d87d55",
|
||||
"c9.ide.terminal.monitor": "#b0b4d03280",
|
||||
|
|
|
@ -209,6 +209,14 @@ define(function(require, exports, module) {
|
|||
return JSON.stringify(sortByKeys(obj), replacer, spaces);
|
||||
};
|
||||
|
||||
plugin.safeParseJson = function(strJson, cb){
|
||||
// Remove comments
|
||||
var data = strJson.replace(/(^|\n)\s*\/\/.*/g, "");
|
||||
|
||||
try { return JSON.parse(data); }
|
||||
catch (e) { cb(e); return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -90,7 +90,8 @@ define(function(require, exports, module) {
|
|||
|
||||
var isMinimal = options.minimal;
|
||||
var themeLoaded = {};
|
||||
var lastTheme, grpSyntax;
|
||||
var themeCounter = 100;
|
||||
var lastTheme, grpSyntax, grpThemes;
|
||||
|
||||
var theme;
|
||||
var skin = settings.get("user/general/@skin");
|
||||
|
@ -140,8 +141,10 @@ define(function(require, exports, module) {
|
|||
|
||||
function setTheme(path, isPreview, fromServer, $err) {
|
||||
// Get Theme or wait for theme to load
|
||||
try{
|
||||
theme = fromServer || require(path);
|
||||
try {
|
||||
theme = typeof path == "object"
|
||||
? path
|
||||
: fromServer || require(path);
|
||||
|
||||
// fixes a problem with Ace architect loading /lib/ace
|
||||
// creating a conflict with themes
|
||||
|
@ -1089,69 +1092,83 @@ define(function(require, exports, module) {
|
|||
|
||||
/**** Themes ****/
|
||||
|
||||
var grpThemes = new ui.group();
|
||||
var mnuThemes = new ui.menu({
|
||||
grpThemes = new ui.group();
|
||||
|
||||
menus.addItemByPath("View/Themes/", new ui.menu({
|
||||
"onprop.visible" : function(e) {
|
||||
if (e.value)
|
||||
grpThemes.setValue(settings.get("user/ace/@theme"));
|
||||
}
|
||||
});
|
||||
menus.addItemByPath("View/Themes/", mnuThemes, 350000, handle);
|
||||
|
||||
var preview;
|
||||
var setMenuThemeDelayed = lang.delayedCall(function(){
|
||||
setMenuTheme(preview, true);
|
||||
}, 150);
|
||||
function setMenuTheme(path, isPreview) {
|
||||
setTheme(path || settings.get("user/ace/@theme"), isPreview);
|
||||
}
|
||||
|
||||
function addThemeMenu(name, path, index) {
|
||||
menus.addItemByPath("View/Themes/" + name, new ui.item({
|
||||
type: "radio",
|
||||
value: path || themes[name],
|
||||
group: grpThemes,
|
||||
|
||||
onmouseover: function(e) {
|
||||
preview = this.value;
|
||||
setMenuThemeDelayed.schedule();
|
||||
},
|
||||
|
||||
onmouseout: function(e) {
|
||||
preview = null;
|
||||
setMenuThemeDelayed.schedule();
|
||||
},
|
||||
|
||||
onclick: function(e) {
|
||||
setMenuTheme(e.currentTarget.value);
|
||||
}
|
||||
}), index, handle);
|
||||
}
|
||||
}), 350000, handle);
|
||||
|
||||
// Create Theme Menus
|
||||
var mainCounter = 100;
|
||||
for (var name in themes) {
|
||||
if (themes[name] instanceof Array) {
|
||||
|
||||
// Add Menu Item (for submenu)
|
||||
menus.addItemByPath("View/Themes/" + name + "/", null, mainCounter++, handle);
|
||||
menus.addItemByPath("View/Themes/" + name + "/", null, themeCounter++, handle);
|
||||
|
||||
themes[name].forEach(function (n) {
|
||||
// Add Menu Item
|
||||
var themeprop = Object.keys(n)[0];
|
||||
addThemeMenu(name + "/" + themeprop, n[themeprop]);
|
||||
addThemeMenu(name + "/" + themeprop, n[themeprop], -1);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Add Menu Item
|
||||
addThemeMenu(name, null, mainCounter++);
|
||||
addThemeMenu(name, null, themeCounter++);
|
||||
}
|
||||
}
|
||||
|
||||
/**** Syntax ****/
|
||||
|
||||
grpSyntax = new ui.group();
|
||||
handle.addElement(grpNewline, grpSyntax, grpThemes);
|
||||
}
|
||||
|
||||
var preview;
|
||||
var setMenuThemeDelayed = lang.delayedCall(function(){
|
||||
setMenuTheme(preview, true);
|
||||
}, 150);
|
||||
function setMenuTheme(path, isPreview) {
|
||||
setTheme(path || settings.get("user/ace/@theme"), isPreview);
|
||||
}
|
||||
function addThemeMenu(name, path, index, plugin) {
|
||||
menus.addItemByPath("View/Themes/" + name, new ui.item({
|
||||
type: "radio",
|
||||
value: path || themes[name],
|
||||
group: grpThemes,
|
||||
|
||||
onmouseover: function(e) {
|
||||
preview = this.value;
|
||||
setMenuThemeDelayed.schedule();
|
||||
},
|
||||
|
||||
onmouseout: function(e) {
|
||||
preview = null;
|
||||
setMenuThemeDelayed.schedule();
|
||||
},
|
||||
|
||||
onclick: function(e) {
|
||||
setMenuTheme(e.currentTarget.value);
|
||||
}
|
||||
}), index == -1 ? undefined : index || themeCounter++, plugin || handle);
|
||||
}
|
||||
function addTheme(css, plugin){
|
||||
var theme = { cssText: css };
|
||||
var firstLine = css.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim();
|
||||
firstLine.split(";").forEach(function(n){
|
||||
if (!n) return;
|
||||
var info = n.split(":");console.log(info)
|
||||
theme[info[0].trim()] = info[1].trim();
|
||||
});
|
||||
theme.isDark = theme.isDark == "true";
|
||||
|
||||
themes[theme.name] = theme;
|
||||
|
||||
ui.insertCss(exports.cssText, plugin);
|
||||
addThemeMenu(theme.name, theme, null, plugin);
|
||||
}
|
||||
|
||||
function rebuildSyntaxMenu() {
|
||||
menus.remove("View/Syntax/");
|
||||
|
@ -1500,7 +1517,7 @@ define(function(require, exports, module) {
|
|||
/**
|
||||
* Set the theme for ace.
|
||||
*
|
||||
* Here's a list of known themes:
|
||||
* Here's a list of default themes:
|
||||
*
|
||||
* * ace/theme/ambiance
|
||||
* * ace/theme/chrome
|
||||
|
@ -1559,6 +1576,13 @@ define(function(require, exports, module) {
|
|||
return mode && mode.caption || "Text";
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a menu item for a new theme
|
||||
* @param {String} css
|
||||
* @param {Plugin} plugin
|
||||
*/
|
||||
addTheme: addTheme,
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
|
|
@ -16,7 +16,7 @@ define(function(require, exports, module) {
|
|||
|
||||
var plugin = new PreferencePanel("Ajax.org", main.consumes, {
|
||||
caption: "Themes",
|
||||
className: "keybindings",
|
||||
className: "flatform",
|
||||
form: true,
|
||||
noscroll: true,
|
||||
colwidth: 150,
|
||||
|
@ -183,12 +183,6 @@ define(function(require, exports, module) {
|
|||
});
|
||||
plugin.on("draw", function(e) {
|
||||
draw(e);
|
||||
});
|
||||
plugin.on("enable", function() {
|
||||
|
||||
});
|
||||
plugin.on("disable", function() {
|
||||
|
||||
});
|
||||
plugin.on("unload", function() {
|
||||
loaded = false;
|
||||
|
|
|
@ -38,6 +38,8 @@ define(function(require, exports, module) {
|
|||
});
|
||||
// var emit = plugin.getEmitter();
|
||||
|
||||
var customKeymaps = {};
|
||||
|
||||
var model, datagrid, changed, container, filterbox;
|
||||
var appliedCustomSets, intro, reloading;
|
||||
|
||||
|
@ -57,6 +59,15 @@ define(function(require, exports, module) {
|
|||
}
|
||||
}, plugin);
|
||||
|
||||
settings.on("user/ace/@keyboardmode", function(){
|
||||
var mode = settings.getJson("user/ace/@keyboardmode");
|
||||
if (customKeymaps[mode]) {
|
||||
settings.set("user/ace/@keyboardmode", "default");
|
||||
settings.setJson("user/key-bindings", customKeymaps[mode]);
|
||||
updateCommandsFromSettings();
|
||||
}
|
||||
});
|
||||
|
||||
settings.on("read", function(e) {
|
||||
updateCommandsFromSettings();
|
||||
}, plugin);
|
||||
|
@ -184,6 +195,7 @@ define(function(require, exports, module) {
|
|||
title: "Keyboard Mode",
|
||||
type: "dropdown",
|
||||
path: "user/ace/@keyboardmode",
|
||||
name: "kbmode",
|
||||
items: [
|
||||
{ caption: "Default", value: "default" },
|
||||
{ caption: "Vim", value: "vim" },
|
||||
|
@ -495,6 +507,47 @@ define(function(require, exports, module) {
|
|||
}
|
||||
}
|
||||
|
||||
function addCustomKeymap(name, keymap, plugin){
|
||||
customKeymaps[name] = keymap;
|
||||
|
||||
if (!Object.keys(customKeymaps).length) {
|
||||
menus.addItemByPath("Edit/Keyboard Mode/~",
|
||||
new ui.divider(), 10000, plugin);
|
||||
}
|
||||
|
||||
menus.addItemByPath("Edit/Keyboard Mode/" + name, new ui.item({
|
||||
type: "radio",
|
||||
value: name.toLowerCase(),
|
||||
onclick: function(e) {
|
||||
settings.set("user/ace/@keyboardmode", name);
|
||||
}
|
||||
}), 10000 + Object.keys(customKeymaps).length, plugin);
|
||||
|
||||
plugin.addOther(function(){ delete customKeymaps[name]; });
|
||||
|
||||
if (plugin.visible)
|
||||
updateKeymaps();
|
||||
}
|
||||
|
||||
function updateKeymaps(){
|
||||
var items = [
|
||||
{ caption: "Default", value: "default" },
|
||||
{ caption: "Vim", value: "vim" },
|
||||
{ caption: "Emacs", value: "emacs" },
|
||||
{ caption: "Sublime", value: "sublime" }
|
||||
];
|
||||
|
||||
for (var name in customKeymaps) {
|
||||
items.push({ caption: name, value: name });
|
||||
}
|
||||
|
||||
plugin.form.update([{
|
||||
type: "dropdown",
|
||||
name: "kbmode",
|
||||
items: items
|
||||
}])
|
||||
}
|
||||
|
||||
/***** Lifecycle *****/
|
||||
|
||||
plugin.on("load", function() {
|
||||
|
@ -504,7 +557,10 @@ define(function(require, exports, module) {
|
|||
draw(e);
|
||||
});
|
||||
plugin.on("activate", function(e) {
|
||||
datagrid && datagrid.resize();
|
||||
if (!drawn) return;
|
||||
|
||||
datagrid.resize();
|
||||
updateKeymaps();
|
||||
});
|
||||
plugin.on("resize", function(e) {
|
||||
datagrid && datagrid.resize();
|
||||
|
@ -532,7 +588,12 @@ define(function(require, exports, module) {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
editUserKeys: editUserKeys
|
||||
editUserKeys: editUserKeys,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addCustomKeymap: addCustomKeymap
|
||||
});
|
||||
|
||||
register(null, {
|
||||
|
|
|
@ -3,7 +3,7 @@ define(function(require, exports, module) {
|
|||
main.consumes = [
|
||||
"Plugin", "vfs", "fs", "plugin.loader", "c9", "ext", "watcher",
|
||||
"dialog.notification", "ui", "menus", "commands", "settings", "auth",
|
||||
"installer"
|
||||
"installer", "find", "util"
|
||||
];
|
||||
main.provides = ["plugin.debug"];
|
||||
return main;
|
||||
|
@ -13,6 +13,8 @@ define(function(require, exports, module) {
|
|||
var vfs = imports.vfs;
|
||||
var watcher = imports.watcher;
|
||||
var ext = imports.ext;
|
||||
var util = imports.util;
|
||||
var find = imports.find;
|
||||
var ui = imports.ui;
|
||||
var menus = imports.menus;
|
||||
var installer = imports.installer;
|
||||
|
@ -26,6 +28,7 @@ define(function(require, exports, module) {
|
|||
|
||||
var dirname = require("path").dirname;
|
||||
var join = require("path").join;
|
||||
var async = require("async");
|
||||
|
||||
var architect;
|
||||
|
||||
|
@ -37,6 +40,8 @@ define(function(require, exports, module) {
|
|||
var ENABLED = c9.location.indexOf("debug=2") > -1;
|
||||
var HASSDK = c9.location.indexOf("sdk=0") === -1;
|
||||
|
||||
var reParts = /^(builders|keymaps|modes|outline|runners|snippets|themes)\/(.*)/
|
||||
|
||||
var loaded = false;
|
||||
function load() {
|
||||
if (loaded) return false;
|
||||
|
@ -118,52 +123,81 @@ define(function(require, exports, module) {
|
|||
}
|
||||
|
||||
// Fetch package.json
|
||||
fs.readFile("~/.c9/plugins/" + name + "/package.json", function(err, data){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next();
|
||||
}
|
||||
async.parallel([
|
||||
function(next){
|
||||
fs.readFile("~/.c9/plugins/" + name + "/package.json", function(err, data){
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
try {
|
||||
var options = JSON.parse(data);
|
||||
if (!options.plugins)
|
||||
throw new Error("Missing plugins property in package.json of " + name);
|
||||
}
|
||||
catch(e){
|
||||
console.error(err);
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
var options = JSON.parse(data);
|
||||
if (!options.plugins)
|
||||
throw new Error("Missing plugins property in package.json of " + name);
|
||||
}
|
||||
catch(e){
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var host = vfs.baseUrl + "/";
|
||||
var base = join(String(c9.projectId),
|
||||
"plugins", auth.accessToken);
|
||||
var host = vfs.baseUrl + "/";
|
||||
var base = join(String(c9.projectId),
|
||||
"plugins", auth.accessToken);
|
||||
|
||||
// Start the installer if one is included
|
||||
if (options.installer) {
|
||||
var version = options.installer.version;
|
||||
var url = host + join(base, name, options.installer.main);
|
||||
installer.createVersion(name, version, function(v, o){
|
||||
require([url], function(fn){
|
||||
fn(v, o);
|
||||
// Start the installer if one is included
|
||||
if (options.installer) {
|
||||
var version = options.installer.version;
|
||||
var url = host + join(base, name, options.installer.main);
|
||||
installer.createVersion(name, version, function(v, o){
|
||||
require([url], function(fn){
|
||||
fn(v, o);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add the plugin to the config
|
||||
Object.keys(options.plugins).forEach(function(path){
|
||||
var pluginPath = name + "/" + path + ".js";
|
||||
|
||||
// Watch project path
|
||||
watch("~/.c9/plugins/" + pluginPath);
|
||||
|
||||
var cfg = options.plugins[path];
|
||||
cfg.packagePath = host + join(base, pluginPath.replace(/^plugins\//, ""));
|
||||
cfg.staticPrefix = host + join(base, name);
|
||||
cfg.apikey = "0000000000000000000000000000=";
|
||||
|
||||
config.push(cfg);
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next){
|
||||
var path = join(c9.home, "plugins", + name);
|
||||
var rePath = new RegExp("^" + util.escapeRegExp(path), "g");
|
||||
find.getFileList({
|
||||
path: path,
|
||||
nocache: true,
|
||||
buffer: true
|
||||
}, function(err, data){
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
// Remove the base path
|
||||
data = data.replace(rePath, "");
|
||||
|
||||
// Process all the submodules
|
||||
var parallel = processModules(path, data);
|
||||
async.parallel(parallel, function(err, data){
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
// Done
|
||||
next();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add the plugin to the config
|
||||
Object.keys(options.plugins).forEach(function(path){
|
||||
var pluginPath = name + "/" + path + ".js";
|
||||
|
||||
// Watch project path
|
||||
watch("~/.c9/plugins/" + pluginPath);
|
||||
|
||||
var cfg = options.plugins[path];
|
||||
cfg.packagePath = host + join(base, pluginPath.replace(/^plugins\//, ""));
|
||||
cfg.staticPrefix = host + join(base, name);
|
||||
cfg.apikey = "0000000000000000000000000000=";
|
||||
|
||||
config.push(cfg);
|
||||
});
|
||||
|
||||
], function(err, results){
|
||||
if (err) console.error(err);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
@ -189,6 +223,113 @@ define(function(require, exports, module) {
|
|||
list.forEach(next);
|
||||
}
|
||||
|
||||
function processModules(path, data){
|
||||
var parallel = [];
|
||||
var services = architect.services;
|
||||
|
||||
var placeholder = new Plugin();
|
||||
|
||||
data.split("\n").forEach(function(line){
|
||||
if (!line.match(reParts)) return;
|
||||
|
||||
var type = RegExp.$1;
|
||||
var filename = RegExp.$2;
|
||||
if (filename.indexOf("/") > -1) return;
|
||||
|
||||
switch (type) {
|
||||
case "builders":
|
||||
parallel.push(function(next){
|
||||
fs.readFile(join(path, filename), function(err, data){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data = util.safeParseJson(data, next);
|
||||
if (!data) return;
|
||||
|
||||
services.build.addBuilder(filename, data, placeholder);
|
||||
next();
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "keymaps":
|
||||
parallel.push(function(next){
|
||||
fs.readFile(join(path, filename), function(err, data){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data = util.safeParseJson(data, next);
|
||||
if (!data) return;
|
||||
|
||||
services["preferences.keybindings"].addCustomKeymap(filename, data, placeholder);
|
||||
next();
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "modes":
|
||||
parallel.push(function(next){
|
||||
|
||||
});
|
||||
break;
|
||||
case "outline":
|
||||
parallel.push(function(next){
|
||||
fs.readFile(join(path, filename), function(err, data){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data = util.safeParseJson(data, next);
|
||||
if (!data) return;
|
||||
|
||||
services.outline.addOutlinePlugin(filename, data, placeholder);
|
||||
next();
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "runners":
|
||||
parallel.push(function(next){
|
||||
fs.readFile(join(path, filename), function(err, data){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data = util.safeParseJson(data, next);
|
||||
if (!data) return;
|
||||
|
||||
services.run.addRunner(filename, data, placeholder);
|
||||
next();
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "snippets":
|
||||
parallel.push(function(next){
|
||||
|
||||
});
|
||||
break;
|
||||
case "themes":
|
||||
parallel.push(function(next){
|
||||
fs.readFile(join(path, filename), function(err, theme){
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
services.ace.addTheme(theme, placeholder);
|
||||
next();
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return parallel;
|
||||
}
|
||||
|
||||
// Check if require.s.contexts._ can help watching all dependencies
|
||||
function watch(path){
|
||||
watcher.watch(path);
|
||||
|
|
|
@ -352,6 +352,13 @@ define(function(require, module, exports) {
|
|||
*/
|
||||
get form(){ return form },
|
||||
|
||||
/**
|
||||
* Whether this panel is active
|
||||
* @property {Boolean} active
|
||||
* @readonly
|
||||
*/
|
||||
get active(){ return amlBar.visible; },
|
||||
|
||||
_events: [
|
||||
/**
|
||||
* Fired when the panel container is drawn.
|
||||
|
@ -361,7 +368,19 @@ define(function(require, module, exports) {
|
|||
* @param {AMLElement} e.aml The aml container.
|
||||
* @param {AMLElement} e.navHtml The html element that represents the navigation.
|
||||
*/
|
||||
"draw"
|
||||
"draw",
|
||||
/**
|
||||
* @event activate
|
||||
*/
|
||||
"activate",
|
||||
/**
|
||||
* @event deactivate
|
||||
*/
|
||||
"deactivate",
|
||||
/**
|
||||
* @event resize
|
||||
*/
|
||||
"resize"
|
||||
],
|
||||
|
||||
/**
|
||||
|
|
Ładowanie…
Reference in New Issue