kopia lustrzana https://github.com/c9/core
1557 wiersze
56 KiB
JavaScript
1557 wiersze
56 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"Panel", "c9", "util", "fs", "settings", "ui", "menus",
|
|
"panels", "commands", "tabManager", "fs.cache", "watcher",
|
|
"preferences", "clipboard", "dialog.alert", "dialog.fileremove",
|
|
"dialog.fileoverwrite", "dialog.error", "layout"
|
|
];
|
|
main.provides = ["tree"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var c9 = imports.c9;
|
|
var util = imports.util;
|
|
var Panel = imports.Panel;
|
|
var fs = imports.fs;
|
|
var panels = imports.panels;
|
|
var settings = imports.settings;
|
|
var layout = imports.layout;
|
|
var ui = imports.ui;
|
|
var menus = imports.menus;
|
|
var tabs = imports.tabManager;
|
|
var clipboard = imports.clipboard;
|
|
var watcher = imports.watcher;
|
|
var prefs = imports.preferences;
|
|
var fsCache = imports["fs.cache"];
|
|
var alert = imports["dialog.alert"].show;
|
|
var confirmRemove = imports["dialog.fileremove"].show;
|
|
var confirmRename = imports["dialog.fileoverwrite"].show;
|
|
var showError = imports["dialog.error"].show;
|
|
|
|
var Tree = require("ace_tree/tree");
|
|
var TreeEditor = require("ace_tree/edit");
|
|
var markup = require("text!./tree.xml");
|
|
|
|
var basename = require("path").basename;
|
|
var dirname = require("path").dirname;
|
|
|
|
var staticPrefix = options.staticPrefix;
|
|
var defaultExtension = "";
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Panel("Ajax.org", main.consumes, {
|
|
index: options.index || 100,
|
|
caption: "Workspace",
|
|
elementName: "winFilesViewer",
|
|
minWidth: 130,
|
|
where: options.where || "left"
|
|
});
|
|
var emit = plugin.getEmitter();
|
|
|
|
var container, winFilesViewer; //UI elements
|
|
var showHideScrollPos, scrollTimer;
|
|
var tree;
|
|
|
|
var expandedList = {};
|
|
var scrollPos = -1;
|
|
var loadedSettings = 0;
|
|
var refreshing = false;
|
|
var changed = false;
|
|
var refreshTimer;
|
|
|
|
function $hookIntoApfFocus(ace, amlNode) {
|
|
// makes apf to treat barTerminal as codeEditor
|
|
amlNode.$isTextInput = function(e){return true;};
|
|
ace.on("focus", function() {
|
|
amlNode.focus();
|
|
});
|
|
ace.on("blur", function() {
|
|
// amlNode.blur();
|
|
});
|
|
amlNode.$focus = function(e, fromContextMenu) {
|
|
if (fromContextMenu) {
|
|
ace.renderer.visualizeFocus();
|
|
} else {
|
|
ace.textInput.focus();
|
|
}
|
|
};
|
|
amlNode.$blur = function(e) {
|
|
if (!ace.isFocused())
|
|
ace.renderer.visualizeBlur();
|
|
else
|
|
ace.textInput.blur();
|
|
};
|
|
}
|
|
|
|
var loaded = false;
|
|
function load(){
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
// Register this panel on the left-side panels
|
|
plugin.setCommand({
|
|
name: "toggletree",
|
|
hint: "show the workspace files panel",
|
|
bindKey: { mac: "Command-U", win: "Ctrl-I" }
|
|
});
|
|
panels.on("afterAnimate", function(e) {
|
|
if (panels.isActive("tree"))
|
|
tree && tree.resize();
|
|
});
|
|
|
|
// On Ready Resize initially
|
|
c9.once("ready", function(){ tree && tree.resize(); });
|
|
|
|
// Settings
|
|
settings.on("read", function(e) {
|
|
settings.setDefaults("user/general", [
|
|
["preview-tree", "false"],
|
|
["treestyle", "default"],
|
|
]);
|
|
settings.setDefaults("user/projecttree", [
|
|
["hiddenFilePattern", "*.pyc, __pycache__, .*"]
|
|
]);
|
|
|
|
fsCache.hiddenFilePattern = settings.get("user/projecttree/@hiddenFilePattern");
|
|
fsCache.showHidden = settings.getBool("user/projecttree/@showhidden");
|
|
|
|
scrollPos = settings.getNumber("state/projecttree/@scrollpos");
|
|
|
|
expandedList = {};
|
|
|
|
// auto/projecttree contains the saved expanded nodes
|
|
if (settings.exist("state/projecttree/expanded")) {
|
|
var paths = settings.getJson("state/projecttree/expanded") || ["/"];
|
|
paths.forEach(function(path){ expandedList[path] = true; });
|
|
|
|
loadedSettings = 1;
|
|
refreshing = true; // Prevent selection to change prior to loading the file tree at init
|
|
|
|
// Please see note above about waiting for both the model and
|
|
// the settings to be loaded before loading the project tree
|
|
if (container)
|
|
ready();
|
|
}
|
|
else {
|
|
loadedSettings = 2;
|
|
if (container)
|
|
ready();
|
|
}
|
|
}, plugin);
|
|
|
|
settings.on("write", function(e) {
|
|
if (!changed)
|
|
return;
|
|
|
|
settings.setJson("state/projecttree/expanded",
|
|
Object.keys(expandedList));
|
|
changed = false;
|
|
}, plugin);
|
|
|
|
// Prefs
|
|
prefs.add({
|
|
"General" : {
|
|
"Tree & Navigate" : {
|
|
position: 25,
|
|
"Enable Preview on Tree Selection" : {
|
|
type: "checkbox",
|
|
position: 3000,
|
|
path: "user/general/@preview-tree"
|
|
},
|
|
"Hidden File Pattern" : {
|
|
type: "textbox",
|
|
path: "user/projecttree/@hiddenFilePattern",
|
|
position: 4000
|
|
},
|
|
},
|
|
"User Interface" : {
|
|
"Workspace Files Icon and Selection Style" : {
|
|
type: "dropdown",
|
|
position: 3000,
|
|
path: "user/general/@treestyle",
|
|
items: [
|
|
{ value: "default", caption: "Default" },
|
|
{ value: "alternative", caption: "Alternative" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}, plugin);
|
|
|
|
settings.on("user/projecttree/@hiddenFilePattern", function(){
|
|
var str = settings.get("user/projecttree/@hiddenFilePattern");
|
|
if (fsCache.hiddenFilePattern != str) {
|
|
fsCache.hiddenFilePattern = str;
|
|
clearTimeout(refreshTimer);
|
|
refreshTimer = setTimeout(function() {
|
|
if (!refreshing)
|
|
refresh(function(err) {});
|
|
}, 200);
|
|
}
|
|
});
|
|
|
|
// Import CSS
|
|
// Moved it here to enable it for filesave dialog. This should be
|
|
// optimized away in the packed version
|
|
var css = require("text!./style.css");
|
|
ui.insertCss(css, staticPrefix, plugin);
|
|
ui.insertCss(util.getFileIconCss(staticPrefix), false, plugin);
|
|
|
|
layout.on("eachTheme", function(e){
|
|
var height = parseInt(ui.getStyleRule(".filetree .tree-row", "height"), 10) || 22;
|
|
fsCache.model.rowHeightInner = height;
|
|
fsCache.model.rowHeight = height + 1;
|
|
|
|
if (e.changed && tree) (tree).resize(true);
|
|
});
|
|
}
|
|
|
|
var drawn = false;
|
|
function draw(options) {
|
|
if (drawn) return;
|
|
drawn = true;
|
|
|
|
// Create UI elements
|
|
ui.insertMarkup(options.aml, markup, plugin);
|
|
|
|
// Fetch UI elements
|
|
container = plugin.getElement("container");
|
|
winFilesViewer = plugin.getElement("winFilesViewer");
|
|
|
|
// Create the Ace Tree
|
|
tree = new Tree(container.$int);
|
|
$hookIntoApfFocus(tree, container);
|
|
tree.renderer.setScrollMargin(10, 10);
|
|
tree.renderer.setTheme({cssClass: "filetree"});
|
|
tree.setDataProvider(fsCache.model);
|
|
tree.setOption("enableDragDrop", true);
|
|
|
|
fsCache.model.$indentSize = 12;
|
|
fsCache.model.getIconHTML = function(node) {
|
|
var icon = node.isFolder ? "folder" : util.getFileIcon(node.label);
|
|
if (node.status === "loading") icon = "loading";
|
|
return "<span class='filetree-icon " + icon + "'></span>";
|
|
};
|
|
|
|
if (settings.get("user/general/@treestyle") == "alternative")
|
|
ui.setStyleClass(container.$int, "alternative");
|
|
|
|
settings.on("user/general", function(){
|
|
if (settings.get("user/general/@treestyle") == "alternative")
|
|
ui.setStyleClass(container.$int, "alternative");
|
|
else
|
|
ui.setStyleClass(container.$int, "", ["alternative"]);
|
|
});
|
|
|
|
tree.edit = new TreeEditor(tree);
|
|
|
|
layout.on("resize", function(){ tree.resize() }, plugin);
|
|
|
|
var btnTreeSettings = plugin.getElement("btnTreeSettings");
|
|
var mnuFilesSettings = plugin.getElement("mnuFilesSettings");
|
|
|
|
btnTreeSettings.setAttribute("submenu", mnuFilesSettings);
|
|
tree.renderer.on("scrollbarVisibilityChanged", updateScrollBarSize);
|
|
tree.renderer.on("resize", updateScrollBarSize);
|
|
function updateScrollBarSize() {
|
|
var w = tree.renderer.scrollBarV.getWidth();
|
|
btnTreeSettings.$ext.style.marginRight = Math.max(w - 2, 0) + "px";
|
|
tree.renderer.scroller.style.right = Math.max(w, 10) + "px";
|
|
}
|
|
|
|
tree.on("drop", function(e) {
|
|
if (e.target && e.selectedNodes) {
|
|
(e.isCopy ? copy : move)(e.selectedNodes, e.target);
|
|
}
|
|
});
|
|
|
|
// Set the panel var for the panels extension
|
|
plugin.panel = winFilesViewer;
|
|
|
|
c9.on("stateChange", function(e) {
|
|
mnuCtxTree.setAttribute("disabled", !(e.state & c9.STORAGE));
|
|
|
|
// Online
|
|
if (e.state & c9.NETWORK) {
|
|
ui.setStyleRule(".filetree.ace_tree", "opacity", "");
|
|
}
|
|
// Offline
|
|
else {
|
|
ui.setStyleRule(".filetree.ace_tree", "opacity", "0.5");
|
|
}
|
|
}, plugin);
|
|
|
|
// This adds a "Show Hidden Files" item to the settings dropdown
|
|
// from the Project Files header
|
|
ui.insertByIndex(mnuFilesSettings, new ui.item({
|
|
caption: "Refresh File Tree",
|
|
onclick: function(){
|
|
refresh(true, function(){});
|
|
}
|
|
}), 100, plugin);
|
|
ui.insertByIndex(mnuFilesSettings, new ui.item({
|
|
caption: "Collapse All Folders",
|
|
onclick: function(){
|
|
collapseAll();
|
|
expand("/", function(){});
|
|
select("/");
|
|
},
|
|
enableOffline: true,
|
|
}), 120, plugin);
|
|
ui.insertByIndex(mnuFilesSettings, new ui.divider(), 200, plugin);
|
|
ui.insertByIndex(mnuFilesSettings, new ui.item({
|
|
id: "mnuitemHiddenFiles",
|
|
type: "check",
|
|
caption: "Show Hidden Files",
|
|
visible: "{tree.container.visible}",
|
|
checked: "user/projecttree/@showhidden",
|
|
onclick: function(e) {
|
|
setTimeout(function() {
|
|
changed = true;
|
|
settings.save();
|
|
|
|
fsCache.showHidden = e.currentTarget.checked;
|
|
|
|
refresh(function(err) {});
|
|
});
|
|
}
|
|
}), 300, plugin);
|
|
|
|
mnuFilesSettings.on("prop.visible", function(e) {
|
|
|
|
}, plugin);
|
|
|
|
// todo
|
|
winFilesViewer.on("prop.visible", function(e) {
|
|
|
|
}, plugin);
|
|
|
|
// After an item in the tree has been clicked on, this saves that
|
|
// selection in the settings model
|
|
// @todo optimize this with a timeout if needed
|
|
tree.on("changeSelection", function(e) {
|
|
if (!refreshing) {
|
|
var nodes = tree.selection.getSelectedNodes();
|
|
var paths = nodes.map(function(node) {
|
|
return node.path;
|
|
});
|
|
settings.setJson("state/tree_selection", paths);
|
|
|
|
emit("select", { paths: paths, nodes: nodes });
|
|
}
|
|
}, plugin);
|
|
|
|
var adhocPreview;
|
|
|
|
tree.on("userSelect", function(e) {
|
|
var selected = tree.selection.getCursor();
|
|
if ((adhocPreview || settings.getBool("user/general/@preview-tree"))
|
|
&& selected && !selected.isFolder) {
|
|
tabs.preview({ path: selected.path }, function(){});
|
|
}
|
|
});
|
|
|
|
tree.commands.bindKey("Space", function(e) {
|
|
var selected = tree.selection.getCursor();
|
|
if (selected && !selected.isFolder) {
|
|
var tab = tabs.findTab(selected.path);
|
|
|
|
adhocPreview = !(tab && tab.document.meta.preview);
|
|
if (!adhocPreview)
|
|
tabs.preview({ cancel: true }, function(){});
|
|
else
|
|
tabs.preview({ path: selected.path }, function(){});
|
|
}
|
|
tree.once("blur", function() { adhocPreview = false; });
|
|
});
|
|
|
|
tree.commands.bindKey("Shift-Enter", function(e) {
|
|
openSelection({focusNewTab: "soft"});
|
|
});
|
|
|
|
// Opens a file after the user has double-clicked
|
|
tree.on("afterChoose", openSelection);
|
|
tree.on("delete", function(){ remove(); });
|
|
|
|
tree.provider.on("changeScrollTop", scrollHandler);
|
|
|
|
// When a folder has been expanded, save it in expandedList
|
|
tree.provider.on("expand", function(e) {
|
|
if (!e) return;
|
|
|
|
var node = e;
|
|
var id = node.path;
|
|
if (id === undefined && node === tree.provider.root)
|
|
return;
|
|
|
|
expandedList[id] = node;
|
|
|
|
emit("expand", { path: id });
|
|
|
|
if (node.justLoaded) {
|
|
delete node.justLoaded;
|
|
return;
|
|
}
|
|
|
|
// Only save if we are not loading the tree
|
|
if (!refreshing || loadedSettings != -1) {
|
|
if (!node.isRoot) {
|
|
var refresh = !refreshing && node.status == "loaded";
|
|
watcher.watch(id, refresh);
|
|
|
|
// watch children
|
|
var substr = id[id.length - 1] !== "/" ? id + "/" : id;
|
|
Object.keys(expandedList).forEach(function(path) {
|
|
if (path.lastIndexOf(substr, 0) === 0) {
|
|
watcher.watch(path, refresh);
|
|
}
|
|
});
|
|
}
|
|
|
|
changed = true;
|
|
settings.save();
|
|
}
|
|
}, plugin);
|
|
|
|
// When a folder has been collapsed, remove it from expandedList
|
|
tree.provider.on("collapse", function(e) {
|
|
if (!e) return;
|
|
var node = e;
|
|
|
|
var id = node.path;
|
|
delete expandedList[id];
|
|
|
|
emit("collapse", { path: id });
|
|
|
|
if (!node.isRoot) {
|
|
watcher.unwatch(id);
|
|
|
|
// unwatch children
|
|
if (id[id.length - 1] !== "/") id += "/";
|
|
Object.keys(expandedList).forEach(function(path) {
|
|
if (path.lastIndexOf(id, 0) === 0) {
|
|
watcher.unwatch(path);
|
|
}
|
|
});
|
|
}
|
|
|
|
changed = true;
|
|
settings.save();
|
|
}, plugin);
|
|
|
|
function abortNoStorage() {
|
|
if (!c9.has(c9.STORAGE))
|
|
return false;
|
|
}
|
|
|
|
// Rename
|
|
tree.on("beforeRename", function(e) {
|
|
if (emit("beforeRename", e) === false)
|
|
e.preventDefault();
|
|
});
|
|
tree.on("rename", function(e) {
|
|
if (!c9.has(c9.STORAGE))
|
|
return false;
|
|
|
|
if (getSelectedNode().path == "/") {
|
|
alert(
|
|
"Cannot rename project folder",
|
|
"Unable to rename the project folder",
|
|
"The project folder name is related to the url of your project and cannot be renamed here."
|
|
);
|
|
return false;
|
|
}
|
|
|
|
var node = e.node;
|
|
var name = e.value;
|
|
|
|
// check for a path with the same name, which is not allowed to rename to:
|
|
var path = node.path;
|
|
var newpath = path.replace(/[^\/]+$/, name);
|
|
|
|
// No point in renaming when the name is the same
|
|
if (basename(path) == name)
|
|
return;
|
|
|
|
// Returning false from this function will cancel the rename. We do this
|
|
// when the name to which the file is to be renamed contains invalid
|
|
// characters
|
|
if (/[\\\/\n\r]/.test(name)) {
|
|
// todo is this still needed?
|
|
showError(
|
|
"Could not rename to '" + ui.htmlentities(name)
|
|
+ "'. Names can only contain alfanumeric characters, space, . (dot)"
|
|
+ ", - and _ (underscore). Use the terminal to rename to other names."
|
|
);
|
|
return false;
|
|
}
|
|
|
|
fs.rename(path, newpath, {}, function(err, success) { });
|
|
|
|
emit("rename", { path: newpath, oldpath: path });
|
|
|
|
return false;
|
|
}, plugin);
|
|
|
|
// Context Menu
|
|
var mnuCtxTree = plugin.getElement("mnuCtxTree");
|
|
menus.decorate(mnuCtxTree);
|
|
plugin.addElement(mnuCtxTree);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file",
|
|
class: "strong",
|
|
caption: "Open",
|
|
onclick: openSelection
|
|
}), 100, plugin);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.divider(), 200, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
caption: "Refresh",
|
|
onclick: function(){ refresh(tree.selection.getSelectedNodes(), function(){}); }
|
|
}), 210, plugin);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder",
|
|
write: true,
|
|
caption: "Rename",
|
|
onclick: function(){ tree.edit.startRename() }
|
|
}), 300, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder",
|
|
write: true,
|
|
caption: "Delete",
|
|
onclick: function(){ remove() }
|
|
}), 310, plugin);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.divider({}), 700, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder",
|
|
write: true,
|
|
command: "cut",
|
|
caption: "Cut"
|
|
}), 710, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder",
|
|
write: true,
|
|
command: "copy",
|
|
caption: "Copy"
|
|
}), 720, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "clipboard",
|
|
write: true,
|
|
command: "paste",
|
|
caption: "Paste"
|
|
}), 730, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder",
|
|
write: true,
|
|
caption: "Duplicate",
|
|
onclick: function() {
|
|
var nodes = tree.selection.getSelectedNodes();
|
|
copy(nodes);
|
|
}
|
|
}), 740, plugin);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.divider({}), 800, plugin);
|
|
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
id: "itemCtxTreeNewFile",
|
|
match: "file|folder|project",
|
|
write: true,
|
|
caption: "New File",
|
|
onclick: function(){ createFile(null, false, function(){}); }
|
|
}), 1520, plugin);
|
|
menus.addItemToMenu(mnuCtxTree, new ui.item({
|
|
match: "file|folder|project",
|
|
write: true,
|
|
caption: "New Folder",
|
|
onclick: function(){ createFolder("New Folder", false, function(){}); }
|
|
}), 1540, plugin);
|
|
|
|
container.setAttribute("contextmenu", mnuCtxTree);
|
|
|
|
function updateTreeMenuItems(e) {
|
|
if (!e.value)
|
|
return;
|
|
|
|
var node = tree.selection.getCursor();
|
|
var type = node && node.isFolder
|
|
? node === tree.provider.projectDir
|
|
? "project"
|
|
: "folder"
|
|
: "file";
|
|
var hasNetwork = c9.has(c9.NETWORK);
|
|
this.childNodes.forEach(function(item) {
|
|
var match = item.match;
|
|
var disabled = false;
|
|
if (!hasNetwork && !item.enableOffline) {
|
|
disabled = true;
|
|
}
|
|
else if (item.write == c9.readonly) {
|
|
disabled = true;
|
|
}
|
|
else if (match == "clipboard") {
|
|
disabled = !isClipboardAvailable({ type: item.command });
|
|
}
|
|
else if (match) {
|
|
disabled = match.indexOf(type) === -1;
|
|
}
|
|
item.setAttribute("disabled", disabled);
|
|
});
|
|
emit("menuUpdate", { node: node, menu: this });
|
|
}
|
|
mnuCtxTree.addEventListener("prop.visible", updateTreeMenuItems);
|
|
mnuFilesSettings.addEventListener("prop.visible", updateTreeMenuItems);
|
|
|
|
// Clipboard support
|
|
function isClipboardAvailable(e) {
|
|
if (tree.edit.renaming)
|
|
return;
|
|
|
|
var cursor = tree.selection.getCursor();
|
|
if (e.type == "cut")
|
|
return cursor && cursor.path != "/";
|
|
|
|
var nodes = clipboard.clipboardData.getData("c9/tree-nodes");
|
|
if (e.type == "clearcut")
|
|
return nodes && nodes.isCut;
|
|
if (e.type == "paste")
|
|
return nodes && getSelectedFolder();
|
|
|
|
return true;
|
|
}
|
|
function clearcut(){
|
|
var nodes = clipboard.clipboardData.getData("c9/tree-nodes");
|
|
if (!nodes) return false;
|
|
|
|
nodes.forEach(function(node) {
|
|
node.isCut = false;
|
|
});
|
|
tree.provider.setAttribute(nodes, "isCut", false);
|
|
}
|
|
|
|
clipboard.registerHandler(container, {
|
|
isClipboardAvailable: isClipboardAvailable,
|
|
cut: function(e) {
|
|
if (isClipboardAvailable({ type: "cut" })) {
|
|
clearcut();
|
|
var nodes = tree.selection.getSelectedNodes();
|
|
nodes.forEach(function(node) {
|
|
node.isCut = true;
|
|
});
|
|
tree.provider.setAttribute(nodes, "isCut", true);
|
|
clipboard.clipboardData.setData("c9/tree-nodes", nodes);
|
|
}
|
|
},
|
|
copy: function(e) {
|
|
if (isClipboardAvailable({ type: "copy" })) {
|
|
clearcut();
|
|
clipboard.clipboardData.setData("c9/tree-nodes", tree.selection.getSelectedNodes());
|
|
}
|
|
},
|
|
paste: function(e) {
|
|
if (isClipboardAvailable({ type: "paste" })) {
|
|
var nodes = clipboard.clipboardData.getData("c9/tree-nodes");
|
|
var target = getSelectedFolder();
|
|
if (nodes.isCut) {
|
|
clearcut();
|
|
move(nodes, target);
|
|
} else {
|
|
if (nodes.some(function(node) {
|
|
if (node.path == target.path)
|
|
return true;
|
|
})) {
|
|
target = target.parent;
|
|
}
|
|
|
|
copy(nodes, target);
|
|
}
|
|
}
|
|
},
|
|
clearcut: function(e) {
|
|
if (isClipboardAvailable({ type: "clearcut" }))
|
|
clearcut();
|
|
else
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (loadedSettings > 0)
|
|
ready();
|
|
}
|
|
|
|
// Remove
|
|
function remove(selection) {
|
|
if (!c9.has(c9.STORAGE))
|
|
return false;
|
|
|
|
if (!selection)
|
|
selection = tree.selection.getSelectedNodes();
|
|
if (selection.indexOf(fsCache.model.projectDir) > -1) {
|
|
alert(
|
|
"Cannot remove project folder",
|
|
"Unable to remove the project folder",
|
|
"The project folder can not be deleted. To delete this project go to the dashboard."
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (emit("delete", { selection: selection }) === false)
|
|
return false;
|
|
|
|
return confirmRemove(selection, function(file) {
|
|
if (file.isFolder)
|
|
fs.rmdir(file.path, {recursive: true}, function(){});
|
|
else
|
|
fs.rmfile(file.path, function(){});
|
|
});
|
|
}
|
|
|
|
// Move
|
|
function move(files, to, options, cb) {
|
|
if (!c9.has(c9.STORAGE))
|
|
return false;
|
|
|
|
var overwrite = options && options.overwrite;
|
|
var paths = [];
|
|
var errors = [];
|
|
var toOverwrite = [];
|
|
var counter = 0;
|
|
files.forEach(function(item) {
|
|
var path = item.path;
|
|
var name = item.label;
|
|
var parent = to.path;
|
|
var newpath = (parent + "/" + name).replace("//", "/");
|
|
|
|
if (path === newpath)
|
|
return;
|
|
|
|
paths.push(newpath);
|
|
|
|
fs.rename(path, newpath, {overwrite: overwrite}, function(err, result) {
|
|
if (err) {
|
|
var shouldOverwrite = err.code == "EEXIST" && !overwrite;
|
|
if (shouldOverwrite)
|
|
toOverwrite.push(item);
|
|
else
|
|
errors.push(path);
|
|
}
|
|
if (++counter == paths.length)
|
|
done();
|
|
|
|
return !shouldOverwrite;
|
|
});
|
|
});
|
|
|
|
function done() {
|
|
if (toOverwrite.length && !overwrite) {
|
|
var item = toOverwrite[0];
|
|
confirmRename(
|
|
"File already exists",
|
|
"File already exists",
|
|
'"' + item.path + '" already exists, do you want to replace it? '
|
|
+ "Replacing it will overwrite its current contents.",
|
|
function(all){ // Overwrite
|
|
var files = toOverwrite.splice(0, all ? toOverwrite.length : 1);
|
|
move(files, to, {overwrite: true});
|
|
done();
|
|
},
|
|
function(all){ // Skip
|
|
toOverwrite.splice(0, all ? toOverwrite.length : 1);
|
|
done();
|
|
},
|
|
{ all: toOverwrite.length > 1 }
|
|
);
|
|
}
|
|
else if (errors.length) {
|
|
alert(
|
|
"cannot move files",
|
|
"cannot move files" + errors.join("\n"),
|
|
""
|
|
);
|
|
cb && cb(errors);
|
|
errors = [];
|
|
}
|
|
else {
|
|
cb && cb();
|
|
}
|
|
}
|
|
|
|
expandNode(findNode(to.path));
|
|
selectList(paths);
|
|
scrollToSelection();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Copy
|
|
function copy(files, to, cb) {
|
|
if (!c9.has(c9.STORAGE))
|
|
return false;
|
|
|
|
var paths = [];
|
|
var parentPaths = [];
|
|
var count = 0;
|
|
var total = files.length;
|
|
var prevent;
|
|
|
|
files.forEach(function(item) {
|
|
var path = item.path;
|
|
var name = item.label;
|
|
var parent = to ? to.path : item.parent.path;
|
|
var newpath = (parent + "/" + name).replace("//", "/");
|
|
|
|
if (parentPaths.indexOf(parent) == -1)
|
|
parentPaths.push(parent);
|
|
|
|
fs.copy(path, newpath, {
|
|
overwrite: false,
|
|
recursive: true
|
|
}, function(err, data) {
|
|
if (!err && data)
|
|
path = data.to;
|
|
|
|
if (paths.indexOf(path) == -1)
|
|
paths.push(path);
|
|
if (++count == total && !prevent) {
|
|
selectList(paths);
|
|
scrollToSelection();
|
|
cb && cb(err, paths);
|
|
}
|
|
});
|
|
|
|
if (fsCache.findNode(newpath))
|
|
paths.push(newpath);
|
|
});
|
|
|
|
if (paths.length) {
|
|
selectList(paths);
|
|
scrollToSelection();
|
|
paths = [];
|
|
}
|
|
|
|
parentPaths.forEach(function(p) {
|
|
expandNode(findNode(p));
|
|
});
|
|
|
|
// Prevent selection if it changed in the mean time
|
|
tree.on("changeSelection", function listen(){
|
|
prevent = true;
|
|
container.off("changeSelection", listen);
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function focus(){
|
|
tree && tree.focus();
|
|
}
|
|
|
|
function scrollToSelection(){
|
|
tree.renderer.scrollCaretIntoView(null, 0.5);
|
|
}
|
|
|
|
function scrollHandler() {
|
|
showHideScrollPos = tree.provider.getScrollTop();
|
|
|
|
// Set to -1 in case the user scrolls before the tree is done loading,
|
|
// in which case we don't want to set the scroll pos to the saved one
|
|
scrollPos = -1;
|
|
|
|
if (!scrollTimer) {
|
|
scrollTimer = setTimeout(function() {
|
|
settings.set("state/projecttree/@scrollpos",
|
|
tree.provider.getScrollTop());
|
|
scrollTimer = null;
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
function ready() {
|
|
tree.setDataProvider(fsCache.model);
|
|
|
|
if (loadedSettings === 1) {
|
|
var done = function(){
|
|
loadedSettings = -1;
|
|
emit.sticky("ready");
|
|
};
|
|
|
|
if (c9.connected) { //was c9.inited
|
|
setTimeout(function() {
|
|
loadProjectTree(null, done);
|
|
}, 200);
|
|
}
|
|
else {
|
|
loadProjectTree(null, done);
|
|
}
|
|
}
|
|
else if (options.defaultExpanded) {
|
|
var nodes = tree.provider.getChildren(tree.provider.root);
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
expand(nodes[i], function(){});
|
|
}
|
|
emit.sticky("ready");
|
|
}
|
|
else {
|
|
emit.sticky("ready");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the project tree based on expandedNodes, which is an array of
|
|
* folders that were previously expanded, otherwise it contains only the
|
|
* root identifier (i.e. c9.davPrefix)
|
|
*
|
|
* @param {Boolean} animateScrollOnFinish
|
|
*/
|
|
function loadProjectTree(animateScrollOnFinish, callback) {
|
|
var foldersLoaded = 0;
|
|
var expandedNodes = Object.keys(expandedList);
|
|
var count = expandedNodes.length;
|
|
|
|
if (!count) {
|
|
refreshing = false; // Needed because settings.on("read") sets it
|
|
return callback && callback("Nothing to do");
|
|
}
|
|
|
|
refreshing = true;
|
|
|
|
// Sort the cached list so it's more probable that nodes near the top of
|
|
// the tree are loaded first, giving the user more visual feedback that
|
|
// something is happening
|
|
expandedNodes.sort();
|
|
|
|
function increment(){
|
|
if (++foldersLoaded == count)
|
|
finish();
|
|
}
|
|
|
|
// todo this leaks event listener on refresh
|
|
fsCache.on("orphan-append", function(e) {
|
|
if (expandedNodes.indexOf(e.path) > -1)
|
|
expandNode(fsCache.findNode(e.path));
|
|
});
|
|
|
|
// Load up the saved list of project tree folders in expandedNodes
|
|
expandedNodes.forEach(function(path) {
|
|
var node = fsCache.findNode(path, "refresh");
|
|
if (node === false || path.charAt(0) == "!")
|
|
return increment();
|
|
|
|
if (node && node.status == "loaded") {
|
|
expandNode(node);
|
|
increment();
|
|
return;
|
|
}
|
|
|
|
fs.readdir(path, function(err, data) {
|
|
if (err) {
|
|
delete expandedList[path];
|
|
|
|
changed = true;
|
|
settings.save();
|
|
}
|
|
else {
|
|
var node = fsCache.findNode(path);
|
|
if (node) //Otherwise orphan-append will pick it up
|
|
expandNode(node);
|
|
}
|
|
|
|
increment();
|
|
});
|
|
});
|
|
|
|
// Called when every cached node has been loaded
|
|
function finish() {
|
|
// There is the possibility that we are calling this more than once | why?
|
|
if (!refreshing)
|
|
return;
|
|
|
|
refreshing = false;
|
|
|
|
// Re-select the last selected item
|
|
var selection = settings.getJson("state/tree_selection");
|
|
if (selection && selection.length)
|
|
selectList(selection);
|
|
else
|
|
tree.selection.selectNode(tree.getFirstNode());
|
|
|
|
// Scroll to last set scroll pos
|
|
if (scrollPos && scrollPos > -1) {
|
|
tree.provider.setScrollTop(scrollPos);
|
|
if (animateScrollOnFinish)
|
|
tree.renderer.animateScrolling(0);
|
|
}
|
|
|
|
end();
|
|
|
|
function end(){
|
|
callback && callback();
|
|
settings.save();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the user hits the refresh button in the Project Files header
|
|
*/
|
|
function refresh(fsNodes, callback) {
|
|
if (refreshing && fsNodes !== true)
|
|
return false;
|
|
|
|
emit("refresh");
|
|
|
|
tabs.checkAllTabs();
|
|
|
|
if (typeof fsNodes == "function") {
|
|
callback = fsNodes;
|
|
fsNodes = null;
|
|
}
|
|
|
|
if (!fsNodes || fsNodes === true)
|
|
fsNodes = Object.keys(expandedList);
|
|
|
|
// When we clear the model below, it dispatches a scroll event which
|
|
// we don't want to process, so remove that event listener
|
|
tree.provider.off("changeScrollTop", scrollHandler);
|
|
|
|
scrollPos = tree.provider.getScrollTop();
|
|
|
|
fsNodes.forEach(function(node) {
|
|
if (typeof node == "string")
|
|
node = fsCache.findNode(node, "refresh");
|
|
|
|
if (node && node.status === "loaded") {
|
|
tree.provider.setAttribute(node, "status", "pending");
|
|
node.children = null;
|
|
}
|
|
});
|
|
|
|
//c9.dispatchEvent("track_action", { type: "reloadtree" });
|
|
|
|
loadProjectTree(false, function(err) {
|
|
var expandedNodes = Object.keys(expandedList);
|
|
expandedList = {};
|
|
|
|
expandedNodes.forEach(function(path) {
|
|
var node = fsCache.findNode(path, "refresh");
|
|
if (node) {
|
|
var id = node.path;
|
|
expandedList[id] = node;
|
|
}
|
|
});
|
|
callback(err);
|
|
tree.provider.on("changeScrollTop", scrollHandler);
|
|
|
|
emit("refreshComplete")
|
|
});
|
|
}
|
|
|
|
function openSelection(opts) {
|
|
if (!c9.has(c9.STORAGE))
|
|
return;
|
|
|
|
var focus = opts && opts.focusNewTab || true;
|
|
var sel = tree.selection.getSelectedNodes();
|
|
var main = tree.selection.getCursor();
|
|
|
|
sel.forEach(function(node) {
|
|
if (!node || node.isFolder)
|
|
return;
|
|
|
|
var pane = tabs.focussedTab && tabs.focussedTab.pane;
|
|
if (tabs.getPanes(tabs.container).indexOf(pane) == -1)
|
|
pane = null;
|
|
|
|
tabs.open({
|
|
path: node.path,
|
|
pane: pane,
|
|
noanim: sel.length > 1,
|
|
active: node === main,
|
|
focus: node === main && focus
|
|
}, function(){});
|
|
});
|
|
}
|
|
|
|
function expandAndSelect(path_or_node) {
|
|
var node = findNode(path_or_node);
|
|
expand(node, function(){
|
|
tree.select(node);
|
|
scrollToSelection();
|
|
});
|
|
}
|
|
function expandNode(node) {
|
|
// Expand Node
|
|
fsCache.model.expand(node);
|
|
}
|
|
|
|
function expand(node, callback) {
|
|
var path;
|
|
|
|
if (typeof node == "string") {
|
|
path = node;
|
|
node = fsCache.findNode(path, "expand");
|
|
}
|
|
if (!callback) callback = function(){};
|
|
|
|
if (!node) {
|
|
if (!path)
|
|
return callback(new Error("Missing Node"));
|
|
|
|
fs.exists(path, function(exists) {
|
|
if (!exists)
|
|
return callback(new Error("File Not Found"));
|
|
recur(node, path, callback);
|
|
});
|
|
}
|
|
else {
|
|
recur(node, node.path, callback);
|
|
}
|
|
|
|
function recur(node, path, next) {
|
|
// Break from loop
|
|
if (path == -1)
|
|
return next();
|
|
|
|
// Fetch Parent
|
|
var ppath, pnode;
|
|
if (path == "~") {
|
|
ppath = -1;
|
|
pnode = null;
|
|
}
|
|
else if (node && (node.isRootContext || emit("isRootContext", node))) {
|
|
pnode = node;
|
|
ppath = -1;
|
|
}
|
|
else {
|
|
ppath = dirname(path);
|
|
pnode = fsCache.findNode(ppath, "expand");
|
|
if (path == ppath)
|
|
ppath = -1;
|
|
}
|
|
|
|
// Next Loop
|
|
recur(pnode, ppath, function(){
|
|
if (!node)
|
|
node = fsCache.findNode(path, "expand");
|
|
if (!node) return; // Raygun #3082
|
|
|
|
// Node needs its files loaded
|
|
if (node.status === "pending") {
|
|
fs.readdir(path, function(err, files) {
|
|
expandNode(node);
|
|
next();
|
|
});
|
|
}
|
|
// Node is already loading
|
|
else if (node.status === "loading") {
|
|
fs.on("afterReaddir", function listener(e) {
|
|
if (e.path == node.path) {
|
|
fs.off("afterReaddir", listener);
|
|
expandNode(node);
|
|
next();
|
|
}
|
|
});
|
|
}
|
|
// Node is already loaded
|
|
else {
|
|
expandNode(node);
|
|
next();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function findNode(path_or_node, type) {
|
|
return typeof path_or_node == "string"
|
|
? fsCache.findNode(path_or_node, type)
|
|
: path_or_node;
|
|
}
|
|
|
|
function collapse(path_or_node) {
|
|
fsCache.model.collapse(findNode(path_or_node, "collapse"));
|
|
}
|
|
|
|
function collapseAll(){
|
|
Object.keys(expandedList).sort().reverse().forEach(function(path) {
|
|
collapse(path);
|
|
});
|
|
expandedList = {};
|
|
}
|
|
|
|
function getAllExpanded(){
|
|
return Object.keys(expandedList);
|
|
}
|
|
|
|
function resize(){
|
|
tree && tree.resize();
|
|
}
|
|
|
|
function select(path_or_node) {
|
|
tree.select(findNode(path_or_node));
|
|
}
|
|
|
|
function selectList(list) {
|
|
tree.selection.setSelection(list.map(function(n) {
|
|
return findNode(n);
|
|
}));
|
|
}
|
|
|
|
function _nextName(path) {
|
|
return path.replace(/(?:\.([\d+]))?(\.[^\.\/\\]*)?$/, function(m, d, e) {
|
|
return "." + (parseInt(d, 10)+1 || 1) + (e ? e : "");
|
|
});
|
|
}
|
|
|
|
function createFolder(dirname, noRename, callback, otherTree) {
|
|
if (!otherTree)
|
|
otherTree = tree;
|
|
|
|
var node = getSelectedFolder(otherTree);
|
|
if (!node)
|
|
return callback(new Error("Tree has no nodes"));
|
|
|
|
|
|
var path = (node.path + "/" + (dirname || "New Folder")).replace("//", "/");
|
|
var count = 0;
|
|
|
|
(function tryPath(path) {
|
|
fs.exists(path, function(exists) {
|
|
if (exists) {
|
|
path = _nextName(path);
|
|
return tryPath(path);
|
|
}
|
|
|
|
var newpath = path + (count ? "." + count : "");
|
|
|
|
fs.mkdir(newpath, function(err, data) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (otherTree == tree)
|
|
expandAndSelect(newpath);
|
|
|
|
if (!noRename)
|
|
otherTree.edit.startRename(findNode(newpath));
|
|
|
|
callback(err, newpath);
|
|
});
|
|
});
|
|
})(path);
|
|
}
|
|
|
|
function createFile(filename, noRename, callback) {
|
|
var node = getSelectedFolder();
|
|
if (!node)
|
|
return callback(new Error("Tree has no nodes"));
|
|
|
|
var path = (node.path
|
|
+ "/" + (filename || "Untitled" + defaultExtension)).replace(/\/\//g, "/");
|
|
|
|
function tryPath(path) {
|
|
fs.exists(path, function(exists) {
|
|
if (exists) {
|
|
path = _nextName(path);
|
|
return tryPath(path);
|
|
}
|
|
|
|
var newpath = path;
|
|
|
|
fs.writeFile(newpath, null, function(err, data) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
var node = fsCache.findNode(newpath, "expand");
|
|
expandAndSelect(node);
|
|
|
|
if (!noRename)
|
|
tree.edit.startRename(node);
|
|
|
|
callback(err, data);
|
|
});
|
|
});
|
|
}
|
|
|
|
expand(dirname(path), function(){ tryPath(path); });
|
|
}
|
|
|
|
function getSelectedNode(otherTree) {
|
|
if (!otherTree) otherTree = tree;
|
|
return otherTree && (otherTree.selection.getCursor()
|
|
|| otherTree.getFirstNode()) || fsCache.findNode("/");
|
|
}
|
|
|
|
function getSelectedFolder(otherTree) {
|
|
var node = getSelectedNode(otherTree);
|
|
if (!node)
|
|
return;
|
|
|
|
if (!node.isFolder)
|
|
node = node.parent;
|
|
return node;
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function(){
|
|
load();
|
|
});
|
|
plugin.on("draw", function(e) {
|
|
draw(e);
|
|
});
|
|
plugin.on("enable", function(){
|
|
|
|
});
|
|
plugin.on("disable", function(){
|
|
|
|
});
|
|
plugin.on("unload", function(){
|
|
tree && tree.destroy();
|
|
loaded = false;
|
|
drawn = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* The file tree plugin for Cloud9. This plugin allows a user to
|
|
* view and edit files and folders.
|
|
*
|
|
* If you are looking for an API to easily manipulate the files of
|
|
* your workspace, then check out the {@link fs} plugin.
|
|
*
|
|
* @singleton
|
|
* @extends Panel
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* @property {Object} tree The tree implementation
|
|
* @private
|
|
*/
|
|
get tree() { return tree; },
|
|
/**
|
|
* @property {String[]} selection A list of paths of files that
|
|
* are selected.
|
|
* @readonly
|
|
*/
|
|
get selection() {
|
|
return tree.selection.getSelectedNodes().map(function(node) {
|
|
return node.path;
|
|
});
|
|
},
|
|
/**
|
|
* @property {String} selected The path of the selected file
|
|
* that has the selection caret.
|
|
* @readonly
|
|
*/
|
|
get selected() {
|
|
var node = tree && (tree.selection.getCursor() || tree.getFirstNode());
|
|
return node ? node.path : false;
|
|
},
|
|
/**
|
|
* @property {fs.cache.Node[]} selectedNodes A list of nodes of files that
|
|
* are selected.
|
|
* @readonly
|
|
*/
|
|
get selectedNodes() {
|
|
return tree && tree.selection.getSelectedNodes() || [];
|
|
},
|
|
/**
|
|
* @property {fs.cache.Node} selectedNode The node representing the selected file
|
|
* that has the selection caret.
|
|
* @readonly
|
|
*/
|
|
get selectedNode() {
|
|
return tree && (tree.selection.getCursor()
|
|
|| tree.getFirstNode()) || null;
|
|
},
|
|
|
|
_events: [
|
|
/**
|
|
* Fires when (a part of) the tree is being refreshed.
|
|
* @event refresh
|
|
*/
|
|
"refresh",
|
|
/**
|
|
* Fires when the selection of the tree changes.
|
|
* @event select
|
|
* @param {Object} e
|
|
* @param {String[]} e.paths A list of paths of the selected
|
|
* files and folders.
|
|
* @param {fs.cache.Node[]} e.nodes A list of nodes representing the selected
|
|
* files and folders.
|
|
*/
|
|
"select",
|
|
/**
|
|
* Fires when a folder in the tree expands.
|
|
* @event expand
|
|
* @param {Object} e
|
|
* @param {String} e.path The path of the expanded folder.
|
|
*/
|
|
"expand",
|
|
/**
|
|
* Fires when a folder in the tree collapses.
|
|
* @event collapse
|
|
* @param {Object} e
|
|
* @param {String} e.path The path of the collapsed folder.
|
|
*/
|
|
"collapse",
|
|
/**
|
|
*
|
|
*/
|
|
"beforeRename",
|
|
/**
|
|
*
|
|
*/
|
|
"delete",
|
|
/**
|
|
*
|
|
*/
|
|
"findNode",
|
|
/**
|
|
*
|
|
*/
|
|
"refreshComplete",
|
|
/**
|
|
*
|
|
*/
|
|
"menuUpdate"
|
|
],
|
|
|
|
/**
|
|
* Retrieves the tree child node of the selected file
|
|
*/
|
|
getSelectedNode: getSelectedNode,
|
|
|
|
/**
|
|
* Gives the tree focus
|
|
*/
|
|
focus: focus,
|
|
|
|
/**
|
|
* Scrolls the selected item into the viewport.
|
|
*/
|
|
scrollToSelection: scrollToSelection,
|
|
|
|
/**
|
|
* Refresh a (sub-)tree of the nodes in the tree by reading them
|
|
* from disk again.
|
|
* @param {String[]/Boolean} [paths] A list of paths to refresh,
|
|
* or `true` to refresh all the expanded folders.
|
|
* @param {Function} callback Called when all folders are refreshed.
|
|
* @param {Error} callback.err Error object if an error occured.
|
|
*/
|
|
refresh: refresh,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
resize: resize,
|
|
|
|
/**
|
|
* Opens all selected files from the tree in the editor.
|
|
*/
|
|
openSelection: openSelection,
|
|
|
|
/**
|
|
* Retrieves a list of paths of all the expanded folders
|
|
* @return {String[]}
|
|
*/
|
|
getAllExpanded: getAllExpanded,
|
|
|
|
/**
|
|
* Expands a tree node (if it has children).
|
|
* @param {String} path The path of the folder to expand.
|
|
* @param {Function} callback Called when the folder is expanded.
|
|
* @fires expand
|
|
*/
|
|
expand: expand,
|
|
|
|
/**
|
|
* Expands all parent nodes and then select the child the path
|
|
* points to.
|
|
* @param {String} path The path of the folder to expand.
|
|
*/
|
|
expandAndSelect: expandAndSelect,
|
|
|
|
/**
|
|
* Collapses a tree node (if it has children).
|
|
* @param {String} path The path of the folder to collapse.
|
|
* @param {Function} callback Called when the folder is collapsed.
|
|
* @fires collapse
|
|
*/
|
|
collapse: collapse,
|
|
|
|
/**
|
|
* Collapse all expanded tree nodes.
|
|
*/
|
|
collapseAll: collapseAll,
|
|
|
|
/**
|
|
* Selects a tree file or folder.
|
|
* @param {String} path The path of the file or folder to select.
|
|
*/
|
|
select: select,
|
|
|
|
/**
|
|
* Selects multiple file and/or folders.
|
|
* @param {String[]} paths The paths of the files and/or folders to select.
|
|
*/
|
|
selectList: selectList,
|
|
|
|
/**
|
|
* Creates a folder below the current folder selected in the tree.
|
|
* @param {String} dirname The name of the folder to create.
|
|
* @param {Boolean} noRename Whether to give the user an option to rename the newly created folder.
|
|
* @param {Function} callback Called when the folder is created.
|
|
* @param {Error} callback.err The error object, if an error occured.
|
|
*/
|
|
createFolder: createFolder,
|
|
|
|
/**
|
|
* Creates a file below the current folder selected in the tree.
|
|
* @param {String} filename The name of the file to create.
|
|
* @param {Boolean} noRename Whether to give the user an option to rename the newly created file.
|
|
* @param {Function} callback Called when the folder is created.
|
|
* @param {Error} callback.err The error object, if an error occured.
|
|
*/
|
|
createFile: createFile,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
remove: remove,
|
|
|
|
// needed for tests
|
|
// TODO add documentation or remove
|
|
/**
|
|
* @ignore
|
|
*/
|
|
copy: copy,
|
|
/**
|
|
* @ignore
|
|
*/
|
|
move: move,
|
|
/**
|
|
* @ignore
|
|
*/
|
|
getSelectedFolder: getSelectedFolder,
|
|
|
|
/**
|
|
* @see newresource#defaultExtension
|
|
* @ignore
|
|
*/
|
|
set defaultExtension(extension) {
|
|
defaultExtension = extension ? "." + extension : "";
|
|
}
|
|
});
|
|
|
|
register(null, {
|
|
tree: plugin
|
|
});
|
|
}
|
|
}); |