the removal the huge Package.js

* converted various Files to use Models now
This commit is contained in:
Sean McArthur 2011-10-27 16:53:58 -05:00
Родитель 5a0584a6ba
Коммит de89cd75fb
16 изменённых файлов: 2079 добавлений и 2137 удалений

Просмотреть файл

@ -16,7 +16,6 @@
<script src="/media/lib/ace/mode-html.js"></script> <script src="/media/lib/ace/mode-html.js"></script>
<script src="/media/lib/meio/Meio.Autocomplete.HTML-1.3.js"></script> <script src="/media/lib/meio/Meio.Autocomplete.HTML-1.3.js"></script>
<script src="/media/base/js/FlightDeck.Autocomplete.js"></script> <script src="/media/base/js/FlightDeck.Autocomplete.js"></script>
<script src="/media/jetpack/js/Package.js"></script>
<script src="/media/lib/FloatingTips.js"></script> <script src="/media/lib/FloatingTips.js"></script>
<script type="text/javascript"> <script type="text/javascript">

Просмотреть файл

@ -21,7 +21,6 @@
<script src="/media/lib/ace/mode-html.js"></script> <script src="/media/lib/ace/mode-html.js"></script>
<script src="/media/lib/meio/Meio.Autocomplete.HTML-1.3.js"></script> <script src="/media/lib/meio/Meio.Autocomplete.HTML-1.3.js"></script>
<script src="/media/base/js/FlightDeck.Autocomplete.js"></script> <script src="/media/base/js/FlightDeck.Autocomplete.js"></script>
<script src="/media/jetpack/js/Package.js"></script>
<script src="/media/lib/FloatingTips.js"></script> <script src="/media/lib/FloatingTips.js"></script>
<script type="text/javascript"> <script type="text/javascript">

Просмотреть файл

@ -1,325 +0,0 @@
var File = new Class({
Implements: [Options, Events],
options: {
path: null
//events
//onDestroy: function() {}
},
initialize: function(pack, options) {
this.pack = pack;
this.setOptions(options);
},
getShortName: function() {
return this.getFullName().split('/').pop();
},
getFullName: function() {
var name = this.options.filename;
if(this.options.type) {
name += '.' + this.options.type;
}
return name;
},
is_editable: function() {
return ['css', 'txt', 'js', 'html'].contains(this.options.type);
},
destroy: function() {
if (this.active) {
// switch editor!
mod = null;
// try to switch to first element
first = false;
Object.each(this.pack.modules, function(mod) {
if (!first) {
first = true;
editor.sidebar.setSelectedFile(mod);
}
});
if (!first) {
this.pack.editor.setContent('');
}
}
if(this.tab) {
this.tab.destroy();
}
this.fireEvent('destroy');
},
setChanged: function(isChanged) {
if (this.changed != isChanged) {
this.fireEvent(isChanged ? 'change' : 'reset');
}
this.changed = isChanged;
}
});
File.sanitize = function(name) {
return name.replace(/[^a-zA-Z0-9=!@#\$%\^&\(\)\+\-_\/\.]+/g, '-');
};
var Library = new Class({
Extends: File,
options: {
append: true
},
initialize: function(pack, options) {
this.parent(pack, options);
this.addEvent('destroy', function(){
delete pack.libraries[this.options.id_number];
});
},
getID: function() {
return 'Library-' + this.options.id_number;
},
getShortName: function() {
return this.options.full_name;
},
getFullName: function() {
return this.getID();
},
storeNewVersion: function(version_data) {
this._latest_version = version_data;
},
retrieveNewVersion: function() {
return this._latest_version;
}
});
var Attachment = new Class({
Extends: File,
options: {
code_trigger_suffix: '_attachment_switch', // id of an element which is used to switch editors
code_editor_suffix: '_attachment_textarea', // id of the textarea
active: false,
type: 'js',
append: false,
filename: '',
readonly: false,
counter: 'attachments'
},
is_image: function() {
return ['jpg', 'gif', 'png'].contains(this.options.type);
},
initialize: function(pack, options) {
this.parent(pack, options);
this.options.path = options.filename + '.' + options.type;
// uid for editor items
this.uid = this.getEditorID();
this.addEvent('destroy', function(){
delete pack.attachments[this.options.uid];
});
// create editor
pack.editor.registerItem(this);
},
loadContent: function() {
var that = this,
spinnerEl = $(this.tab);
new Request({
method: 'get',
url: this.options.get_url,
useSpinner: !!spinnerEl,
spinnerTarget: spinnerEl,
spinnerOptions: {
img: {
'class': 'spinner-img spinner-16'
},
maskBorder: false
},
onSuccess: function() {
var content = this.response.text || '';
that.content = content;
that.original_content = content;
that.fireEvent('loadcontent', content);
}
}).send();
},
isLoaded: function() {
return this.content != null;
},
getID: function() {
return 'Attachment-'+this.uid;
},
getEditorID: function() {
return this.options.uid + this.options.code_editor_suffix;
},
reassign: function(options) {
// every revision, attachments that have changed get a new `uid`.
// since Attachments are currently kept track of via the `uid`,
// we must adjust all instances that keep track of this
// attachment to use the new id, and any other new options that
// comes with it
var packAttachments = this.pack.attachments,
editorItems = this.pack.editor.items,
oldUID = this.options.uid;
delete packAttachments[oldUID];
this.setOptions(options);
this.options.path = options.filename + '.' + options.type;
packAttachments[options.uid] = this;
var editorUID = this.getEditorID();
editorItems[editorUID] = editorItems[this.uid];
delete editorItems[this.uid];
this.uid = editorUID;
if (options.append) {
this.append();
}
if (this.tab) {
this.tab.setLabel(this.getShortName());
}
this.fireEvent('reassign', this.options.uid);
}
});
Attachment.exists = function(filename, ext) {
return Object.some(fd.item.attachments, function(att) {
return (att.options.filename == filename) &&
att.options.type == ext;
});
};
var Module = new Class({
Extends: File,
options: {
// data
// filename: '',
// code: '',
// author: '',
// DOM
code_trigger_suffix: '_switch', // id of an element which is used to switch editors
suffix: '_module',
readonly: false,
main: false,
executable: false,
active: false,
type: 'js',
append: false,
counter: 'modules'
},
initialize: function(pack, options) {
this.parent(pack, options);
this.options.path = this.options.filename + '.' + this.options.type;
this.addEvent('destroy', function(){
delete pack.modules[this.options.filename];
});
// an uid for the editor
this.uid = this.options.filename + this.options.suffix;
// create editor
pack.editor.registerItem(this);
},
loadContent: function() {
// load data synchronously
var spinnerEl = $(this.tab);
new Request.JSON({
method: 'get',
url: this.options.get_url,
useSpinner: !!spinnerEl,
spinnerTarget: spinnerEl,
spinnerOptions: {
img: {
'class': 'spinner-img spinner-16'
},
maskBorder: false
},
onSuccess: function(mod) {
var code = mod.code || '';
this.original_content = code;
this.content = code;
this.fireEvent('loadcontent', code);
}.bind(this)
}).send();
},
isLoaded: function() {
return this.content != null;
},
getID: function() {
return 'Module-' + this.options.filename.replace(/\//g, '-');
}
});
Module.exists = function(filename) {
return Object.some(fd.item.modules, function(mod) {
return mod.options.filename == filename;
});
};
var Folder = new Class({
Extends: File,
options: {
root_dir: 'l',
name: ''
},
initialize: function(pack, options) {
this.parent(pack, options);
this.addEvent('destroy', function(){
delete pack.folders[this.options.root_dir + '/' +this.options.name];
});
},
getFullName: function() {
return this.options.name;
},
getID: function() {
return this.options.root_dir + '-'+
this.options.name.replace(/\//g, '-');
}
});
Folder.ROOT_DIR_LIB = 'l';
Folder.ROOT_DIR_DATA = 'd';
Folder.exists = function(filename, root_dir) {
return Object.some(fd.item.folders, function(folder) {
return (folder.options.root_dir == root_dir &&
folder.options.name == filename);
});
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -41,7 +41,7 @@ module.exports = new Class({
if(file.changed) { if(file.changed) {
fd.showQuestion({ fd.showQuestion({
title: 'Lose unsaved changes?', title: 'Lose unsaved changes?',
message: 'The tab "'+file.getShortName()+'" that you are trying to close has unsaved changes.', message: 'The tab "'+file.get('shortName')+'" that you are trying to close has unsaved changes.',
buttons: [ buttons: [
{ {
'type': 'reset', 'type': 'reset',
@ -83,7 +83,7 @@ module.exports = new Class({
addTab: function(file) { addTab: function(file) {
var controller = this; var controller = this;
var tab = new tabs.Tab(this.tabs, { var tab = new tabs.Tab(this.tabs, {
title: file.getShortName() title: file.get('shortName')
}); });
function change() { function change() {

Просмотреть файл

@ -19,6 +19,10 @@ module.exports = new Class({
fields: { fields: {
url: fields.TextField(), url: fields.TextField(),
data: fields.TextField() data: fields.TextField()
},
uid: function uid() {
return this.get('pk');
} }
}); });

Просмотреть файл

@ -2,7 +2,9 @@
var Class = require('shipyard/class/Class'), var Class = require('shipyard/class/Class'),
Model = require('shipyard/model/Model'), Model = require('shipyard/model/Model'),
fields = require('shipyard/model/fields'), fields = require('shipyard/model/fields'),
Syncable = require('shipyard/sync/Syncable'); Syncable = require('shipyard/sync/Syncable'),
Request = require('shipyard/http/Request'),
string = require('shipyard/utils/string');
var File = module.exports = new Class({ var File = module.exports = new Class({
@ -14,7 +16,12 @@ var File = module.exports = new Class({
id: fields.NumberField(), id: fields.NumberField(),
filename: fields.TextField({ required: true }), filename: fields.TextField({ required: true }),
ext: fields.TextField({ 'default': 'js' }), ext: fields.TextField({ 'default': 'js' }),
content: fields.TextField() content: fields.TextField(),
main: fields.BooleanField({ 'default': false, write: false }),
readonly: fields.BooleanField({ 'default': false, write: false }),
url: fields.TextField({ write: false }),
get_url: fields.TextField({ write: false })
}, },
shortName: function() { shortName: function() {
@ -32,10 +39,63 @@ var File = module.exports = new Class({
} }
}, },
uid: function() {
//TODO: this should be the unique hash from the new API
return this._uid || (this._uid = string.uniqueID());
},
isEditable: function() { isEditable: function() {
return this.constructor.EDITABLE_EXTS.indexOf(this.get('ext')) !== -1; return this.constructor.EDITABLE_EXTS.indexOf(this.get('ext')) !== -1;
},
//TODO: Shipyard Models should probably has a isDirty API
setChanged: function(isChanged) {
this.changed = isChanged;
if (isChanged) {
this.fireEvent('change');
} else {
this.fireEvent('reset');
}
},
loadContent: function(callback) {
var file = this;
var spinnerEl;
return new Request({
method: 'get',
url: this.get('get_url'),
useSpinner: !!spinnerEl,
spinnerTarget: spinnerEl,
spinnerOptions: {
img: {
'class': 'spinner-img spinner-16'
},
maskBorder: false
},
onSuccess: function(text) {
var content = text || '';
file.original_content = content;
file.set('content', content);
file.fireEvent('loadcontent', content);
if (callback) callback.call(this, content);
}
}).send();
},
isLoaded: function() {
return this.get('content') != null;
},
toString: function() {
return this.get('fullName');
} }
}); });
File.EDITABLE_EXTS = ['js', 'html', 'css', 'txt', 'json', 'md']; File.EDITABLE_EXTS = ['js', 'html', 'css', 'txt', 'json', 'md'];
var sanitizeRE = /[^a-zA-Z0-9=!@#\$%\^&\(\)\+\-_\/\.]+/g;
File.sanitize = function(name) {
return name.replace(sanitizeRE, '-');
};

Просмотреть файл

@ -0,0 +1,33 @@
var Class = require('shipyard/class/Class'),
Model = require('shipyard/model/Model'),
fields = require('shipyard/model/fields'),
Syncable = require('shipyard/sync/Syncable'),
string = require('shipyard/utils/string');
var Folder = module.exports = new Class({
Extends: Model,
Implements: Syncable,
fields: {
name: fields.TextField({ required: true }),
root_dir: fields.TextField({ required: true }) //ChoiceField
},
shortName: function() {
return this.get('name');
},
fullName: function() {
return this.get('name');
},
uid: function() {
return this.get('root_dir') + '/' + this.get('name');
}
});
Folder.ROOT_DIR_LIB = 'l';
Folder.ROOT_DIR_DATA = 'd';

Просмотреть файл

@ -1,7 +1,8 @@
var Class = require('shipyard/class/Class'), var Class = require('shipyard/class/Class'),
File = require('./File'), File = require('./File'),
fields = require('shipyard/model/fields'), fields = require('shipyard/model/fields'),
ServerSync = require('shipyard/sync/Server'); ServerSync = require('shipyard/sync/Server'),
Request = require('shipyard/http/Request');
module.exports = new Class({ module.exports = new Class({
@ -15,6 +16,35 @@ module.exports = new Class({
}, },
fields: { fields: {
} },
uid: function() {
return this.get('filename');
},
loadContent: function(callback) {
var spinnerEl,
file = this;
return new Request({
method: 'get',
url: this.get('get_url'),
useSpinner: !!spinnerEl,
spinnerTarget: spinnerEl,
spinnerOptions: {
img: {
'class': 'spinner-img spinner-16'
},
maskBorder: false
},
onSuccess: function(text) {
var mod = JSON.parse(text);
var code = mod.code || '';
this.original_content = code;
this.set('content', code);
this.fireEvent('loadcontent', code);
if (callback) callback.call(this, code);
}.bind(this)
}).send();
}
}); });

Просмотреть файл

@ -32,13 +32,33 @@ var Package = module.exports = new Class({
version_name: fields.TextField(), version_name: fields.TextField(),
revision_number: fields.NumberField(), revision_number: fields.NumberField(),
latest: fields.NumberField(), // a FK to PackageRevision latest: fields.NumberField() // a FK to PackageRevision
// modules: FK from Module // modules: FK from Module
// attachments: FK from Attachment // attachments: FK from Attachment
// dependencies: ManyToManyField('self') // dependencies: ManyToManyField('self')
}, },
uid: function() {
return this.get('id_number');
},
shortName: function() {
return this.get('name');
},
fullName: function() {
return this.get('full_name');
},
storeNewVersion: function(version_data) {
this._latest_version = version_data;
},
retrieveNewVersion: function() {
return this._latest_version;
},
isAddon: function isAddon() { isAddon: function isAddon() {
return this.get('type') === this.constructor.TYPE_ADDON; return this.get('type') === this.constructor.TYPE_ADDON;
}, },

Просмотреть файл

@ -4,7 +4,9 @@
"shipyard": { "shipyard": {
"app": "./", "app": "./",
"mini_require": true, "mini_require": true,
"min": "../editor-min.js", "target": "../editor-min.js",
"min": false,
"test": "./tests" "test": "./tests"
} }
} }

Просмотреть файл

@ -0,0 +1,16 @@
var File = require('../../models/File');
module.exports = {
'File.sanitize': function(it, setup) {
it('should clean out unwanted characters', function(expect) {
var val = File.sanitize('<script>window.open()');
expect(val).toBe('-script-window.open()');
});
it('should not strip plus characters', function(expect) {
var val = File.sanitize('unload+');
expect(val).toBe('unload+');
});
}
};

Просмотреть файл

@ -1,4 +1,6 @@
var Module = require('../../models/Module'); var Module = require('../../models/Module'),
mockXHR = require('shipyard/test/mockXHR'),
Spy = require('shipyard/test/Spy');
module.exports = { module.exports = {
'Module': function(it, setup) { 'Module': function(it, setup) {
@ -14,6 +16,19 @@ module.exports = {
m.set('filename', 'events/key.down'); m.set('filename', 'events/key.down');
expect(m.get('shortName')).toBe('key.down.js'); expect(m.get('shortName')).toBe('key.down.js');
}) });
it('should be able to loadContent', function(expect) {
mockXHR('test content');
var m = new Module();
var fn = new Spy;
m.addEvent('loadcontent', fn);
m.loadContent(function(content) {
expect(content).toBe('test content');
expect(m.get('content')).toBe('test content');
expect(fn.getCallCount()).toBe(1);
});
});
} }
} }

Просмотреть файл

@ -8,10 +8,10 @@
*/ */
var Class = require('shipyard/class/Class'), var Class = require('shipyard/class/Class'),
Events = require('shipyard/class/Events'), Events = require('shipyard/class/Events'),
Options = require('shipyard/class/Options') Options = require('shipyard/class/Options'),
object = require('shipyard/utils/object'); object = require('shipyard/utils/object');
// globals: Element, Spinner, Element.addVolatileEvent, fd // globals: Element, Spinner, fd
var FDEditor = module.exports = new Class({ var FDEditor = module.exports = new Class({
@ -45,8 +45,8 @@ var FDEditor = module.exports = new Class({
}); });
}, },
registerItem: function(item){ registerItem: function(uid, item){
this.items[item.uid] = item; this.items[uid] = item;
}, },
getItem: function(uid){ getItem: function(uid){
@ -62,38 +62,39 @@ var FDEditor = module.exports = new Class({
activateItem: function(item){ activateItem: function(item){
// activate load and hook events // activate load and hook events
var editor = this;
this.current = item; this.current = item;
this.current.active = true; this.current.active = true;
if (!this.current.isLoaded()) { if (!this.current.isLoaded()) {
this.spinner.show(); this.spinner.show();
this.setContent('', true); this.setContent('', true);
this.current.addVolatileEvent('loadcontent', function(content) { this.current.loadContent(function(content) {
//if item == this.current still if (item == editor.current) {
if (item == this.current) { editor.setContent(content);
this.setContent(content); editor.spinner.hide();
this.spinner.hide();
} }
//else another file has become active //else another file has become active
}.bind(this)); });
this.current.loadContent();
} else { } else {
this.setContent(this.current.content); this.setContent(this.current.get('content'));
this.spinner.hide(); this.spinner.hide();
} }
if (this.current.options.readonly) { if (this.current.get('readonly')) {
this.setReadOnly(); this.setReadOnly();
} else { } else {
this.setEditable(); this.setEditable();
} }
this.setSyntax(this.current.options.type); this.setSyntax(this.current.get('ext'));
}, },
switchTo: function(item){ switchTo: function(uid){
$log('FD: DEBUG: FDEditor.switchTo ' + item.uid); $log('FD: DEBUG: FDEditor.switchTo ' + uid);
var self = this; var self = this;
this.switching = true; this.switching = true;
if (!this.getItem(item.uid)) { var item = this.getItem(uid);
this.registerItem(item); if (!item) {
//this.registerItem(item);
$log('no item wtf');
} }
if (this.current) { if (this.current) {
this.deactivateCurrent(); this.deactivateCurrent();
@ -103,7 +104,7 @@ var FDEditor = module.exports = new Class({
}, },
dumpCurrent: function() { dumpCurrent: function() {
this.current.content = this.getContent(); this.current.set('content', this.getContent());
return this; return this;
}, },
@ -126,13 +127,13 @@ var FDEditor = module.exports = new Class({
item.setChanged(false); item.setChanged(false);
item.change_hooked = false; item.change_hooked = false;
// refresh original content // refresh original content
item.original_content = item.content; item.original_content = item.get('content');
}); });
this.hookChangeIfNeeded(); this.hookChangeIfNeeded();
}, },
hookChangeIfNeeded: function() { hookChangeIfNeeded: function() {
if (!this.current.options.readonly) { if (!this.current.get('readonly')) {
if (!this.current.changed && !this.change_hooked) { if (!this.current.changed && !this.change_hooked) {
this.hookChange(); this.hookChange();
} else if (this.current.changed && this.change_hooked) { } else if (this.current.changed && this.change_hooked) {
@ -156,8 +157,8 @@ var FDEditor = module.exports = new Class({
if (!this.switching && this.getContent() != this.current.original_content) { if (!this.switching && this.getContent() != this.current.original_content) {
this.current.setChanged(true); this.current.setChanged(true);
this.fireEvent('change'); this.fireEvent('change');
$log('FD: DEBUG: changed, code is considered dirty and will remain' $log('DEBUG: changed, code is considered dirty and will remain'+
+'be treated as such even if changes are reverted'); 'be treated as such even if changes are reverted');
this.unhookChange(); this.unhookChange();
} else if (!this.switching && this.current.changed) { } else if (!this.switching && this.current.changed) {
this.current.setChanged(false); this.current.setChanged(false);

Просмотреть файл

@ -1,348 +1,355 @@
var /*Class = require('shipyard/class'), var /*Class = require('shipyard/class'),
shipyard/class only lets Class extends from other shipyard/class, shipyard/class only lets Class extends from other shipyard/class,
and FileTree extends from Tree (which is a regular Moo Class) and FileTree extends from Tree (which is a regular Moo Class)
*/ */
object = require('shipyard/utils/object'); object = require('shipyard/utils/object'),
Module = require('../models/Module'),
Attachment = require('../models/Attachment');
// globals: Class, Tree, Collapse.LocalStorage, Element, String.implement // globals: Class, Tree, Collapse.LocalStorage, Element, String.implement
var FileTree = module.exports = new Class({ var FileTree = module.exports = new Class({
Extends: Tree, Extends: Tree,
options: { options: {
branch: { branch: {
'rel': 'file', 'rel': 'file',
'title': 'Untitled', 'title': 'Untitled',
'id': null, 'id': null,
'class': '' 'class': ''
}, },
editable: true, editable: true,
actions: { actions: {
//add: false, //add: false,
//edit: false, //edit: false,
//remove: false //remove: false
}, },
snap: 3, snap: 3,
id_prefix: '', id_prefix: '',
// if container is null, container will default to the Tree el // if container is null, container will default to the Tree el
// "false" will cancel the container // "false" will cancel the container
container: true, container: true
//onAddBranch: function(el, attributes, target){} //onAddBranch: function(el, attributes, target){}
//onRenameStart: function(li, span){} //onRenameStart: function(li, span){}
//onRenameComplete: function(li, span){} //onRenameComplete: function(li, span){}
//onDeleteBranch: function(li, span){} //onDeleteBranch: function(li, span){}
}, },
initialize: function(element, options) { initialize: function(element, options) {
this.addEvent('change', function() { this.addEvent('change', function() {
this.setFullPath(this.current); this.setFullPath(this.current);
}, true); }, true);
this.parent(element, options); this.parent(element, options);
}, },
attach: function(){ attach: function(){
this.parent(); this.parent();
var that = this; var that = this;
this.element.addEvents({ this.element.addEvents({
'mousedown:relay(.actions .edit)': function(e) { 'mousedown:relay(.actions .edit)': function(e) {
var li = e.target.getParent('li'); var li = e.target.getParent('li');
if (li.hasClass('editing')) { if (li.hasClass('editing')) {
that.renameBranchEnd($(e.target).getParent('li')); that.renameBranchEnd($(e.target).getParent('li'));
} else { } else {
that.renameBranch($(e.target).getParent('li')); that.renameBranch($(e.target).getParent('li'));
} }
}, },
'click:relay(li[rel="directory"] > .holder .label, li[rel="directory"] > .holder .icon)': function(e, labelEl){ 'click:relay(li[rel="directory"] > .holder .label, li[rel="directory"] > .holder .icon)': function(e, labelEl){
var li = e.target.getParent('li'); var li = e.target.getParent('li');
that.toggleBranch(li); that.toggleBranch(li);
}, },
'keypress:relay(span)': function(e){ 'keypress:relay(span)': function(e){
if(e.key == 'enter') that.renameBranchEnd($(e.target).getParent('li')); if(e.key == 'enter') that.renameBranchEnd($(e.target).getParent('li'));
} }
}); });
return this; return this;
}, },
mousedown: function(element, event) { mousedown: function(element, event) {
//tree.js prevents event immediately, when really we only want //tree.js prevents event immediately, when really we only want
//the event prevents when it drags. This is because if the element //the event prevents when it drags. This is because if the element
//has contentEditable active, we want the default mousedown action, //has contentEditable active, we want the default mousedown action,
//which is to move the cursor around the text node. If it's not, //which is to move the cursor around the text node. If it's not,
//then preventDefault will be called twice, and the dragging will still //then preventDefault will be called twice, and the dragging will still
//work. :) //work. :)
var oldDefault = event.preventDefault; var oldDefault = event.preventDefault;
event.preventDefault = function(){ event.preventDefault = function(){
event.preventDefault = oldDefault; event.preventDefault = oldDefault;
}; };
this.parent(element, event); this.parent(element, event);
if (this.clone) { if (this.clone) {
this.clone.setStyle('display', 'none'); this.clone.setStyle('display', 'none');
} }
return this; return this;
}, },
onDrag: function(el, event) { onDrag: function(el, event) {
this.parent(el, event); this.parent(el, event);
if (this.clone) { if (this.clone) {
this.clone.setStyle('display', null); //default snap is already 6px this.clone.setStyle('display', null); //default snap is already 6px
} }
}, },
toggleBranch: function(branch) { toggleBranch: function(branch) {
if (branch && this.collapse) { if (branch && this.collapse) {
this.collapse.toggle(branch); this.collapse.toggle(branch);
} }
}, },
removeBranch: function(branch) { removeBranch: function(branch) {
var parent = branch.getParent('li'); var parent = branch.getParent('li');
branch.dispose(); branch.dispose();
if (parent && !parent.getElements('li').length && this.collapse) { if (parent && !parent.getElements('li').length && this.collapse) {
this.collapse.collapse(parent); this.collapse.collapse(parent);
} }
}, },
addBranch: function(attr, target, options){ addBranch: function(attr, target, options){
attr = object.merge({}, this.options.branch, attr); attr = object.merge({}, this.options.branch, attr);
target = $(target) || this.element; target = $(target) || this.element;
if (target.get('tag') !== 'ul') { if (target.get('tag') !== 'ul') {
target = target.getElement('ul'); target = target.getElement('ul');
} }
var isEditable = this.options.editable; var isEditable = this.options.editable;
options = object.merge({}, { options = object.merge({}, {
add: attr.rel == 'directory', add: attr.rel == 'directory',
edit: attr.rel != 'directory', edit: attr.rel != 'directory',
remove: true, //can delete anything remove: true, //can delete anything
collapsed: true collapsed: true
}, this.options.actions, options); }, this.options.actions, options);
if (!isEditable) { if (!isEditable) {
delete options.add; delete options.add;
delete options.edit; delete options.edit;
delete options.remove; delete options.remove;
} }
attr.html = ('<a class="expand" href="#"></a>' + attr.html = ('<a class="expand" href="#"></a>' +
'<div class="holder">' + '<div class="holder">' +
'<span id="{id}" class="label" title="{title}">{title}</span><span class="icon"></span>' + '<span id="{id}" class="label" title="{title}">{title}</span><span class="icon"></span>' +
'<div class="actions">{add}{edit}{remove}</div>' + '<div class="actions">{add}{edit}{remove}</div>' +
'</div>{dir}').substitute({ '</div>{dir}').substitute({
title: attr.title, title: attr.title,
id: attr.name ? attr.name + '_switch' : attr.title + '_folder', id: attr.name ? attr.name + '_switch' : attr.title + '_folder',
dir: attr.rel == 'directory' ? '<ul' + (options.collapsed ? ' style="display:none;"' : '') + '></ul>' : '', dir: attr.rel == 'directory' ? '<ul' + (options.collapsed ? ' style="display:none;"' : '') + '></ul>' : '',
add: options.add ? '<span class="add" title="Add"></span>' : '', add: options.add ? '<span class="add" title="Add"></span>' : '',
edit: options.edit ? '<span class="edit" title="Rename"></span>' : '', edit: options.edit ? '<span class="edit" title="Rename"></span>' : '',
remove: options.remove ? '<span class="delete" title="Delete"></span>' : '' remove: options.remove ? '<span class="delete" title="Delete"></span>' : ''
}); });
var li = new Element('li', attr), var li = new Element('li', attr),
where = 'bottom'; where = 'bottom';
//branches should always be in alpha order //branches should always be in alpha order
//so, find the place to inject the new branch //so, find the place to inject the new branch
target.getChildren('li').some(function(el) { target.getChildren('li').some(function(el) {
if (el.get('title') > attr.title) { if (el.get('title') > attr.title) {
target = el; target = el;
where = 'before'; where = 'before';
return true; return true;
} }
return false; return false;
}); });
li.inject(target, where); li.inject(target, where);
this.fireEvent('addBranch', [li].combine(arguments)); this.fireEvent('addBranch', [li].combine(arguments));
return li; return li;
}, },
renameBranch: function(element, hasExtension){ renameBranch: function(element, hasExtension){
var li = (element.get('tag') == 'li') ? element : element.getParent('li'),
label = li.getElement('.label'),
text = label.get('text').trim();
this.fireEvent('renameStart', [li, label]);
label.set('tabIndex', 0).set('contenteditable', true).focus();
li.addClass('editing');
label.store('$text', text);
label.store('$blur', function blur(e) {
label.removeEvent('blur', blur);
this.renameBranchCancel(element);
}.bind(this))
label.addEvent('blur', label.retrieve('$blur'))
hasExtension = hasExtension || !!text.getFileExtension();
var range = document.createRange(),
node = label.firstChild;
range.setStart(node, 0);
range.setEnd(node, hasExtension ? text.length - text.getFileExtension().length -1 : text.length);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
return this;
},
renameBranchCancel: function(element) {
var li = (element.get('tag') == 'li') ? element : element.getParent('li'), var li = (element.get('tag') == 'li') ? element : element.getParent('li'),
label = li.getElement('.label'), label = li.getElement('.label'),
text = label.retrieve('$text').trim(); text = label.get('text').trim();
label.set('contenteditable', false); this.fireEvent('renameStart', [li, label]);
if (text) {
label.set('text', text);
} label.set('tabIndex', 0).set('contenteditable', true).focus();
label.eliminate('$text'); li.addClass('editing');
li.removeClass('editing'); label.store('$text', text);
}, label.store('$blur', function blur(e) {
label.removeEvent('blur', blur);
renameBranchEnd: function(element) { this.renameBranchCancel(element);
var li = (element.get('tag') == 'li') ? element : element.getParent('li'), }.bind(this));
label = li.getElement('.label'),
text = label.get('text').trim(); label.addEvent('blur', label.retrieve('$blur'));
if(label.get('contenteditable') == 'true'){ hasExtension = hasExtension || !!text.getFileExtension();
//validation var range = document.createRange(),
text = File.sanitize(text); node = label.firstChild;
range.setStart(node, 0);
range.setEnd(node, hasExtension ? text.length - text.getFileExtension().length -1 : text.length);
if (!text.getFileName()) { sel = window.getSelection();
fd.error.alert('Filename must be valid', 'Your file must not contain special characters, and requires a file extension.'); sel.removeAllRanges();
return this; sel.addRange(range);
}
return this;
label.removeEvent('blur', label.retrieve('$blur')); },
label.eliminate('$text');
label.set('contenteditable', false).blur(); renameBranchCancel: function(element) {
window.getSelection().removeAllRanges(); var li = (element.get('tag') == 'li') ? element : element.getParent('li'),
label = li.getElement('.label'),
text = label.retrieve('$text').trim();
li.removeClass('editing');
//fire a renameCancel if the name didnt change label.set('contenteditable', false);
if (text == label.get('title').trim()) { if (text) {
this.fireEvent('renameCancel', li); label.set('text', text);
return this; }
} label.eliminate('$text');
li.removeClass('editing');
label.set('title', text);
label.set('text', text); },
renameBranchEnd: function(element) {
var li = (element.get('tag') == 'li') ? element : element.getParent('li'),
label = li.getElement('.label'),
text = label.get('text').trim();
if(label.get('contenteditable') == 'true'){
//validation
text = File.sanitize(text);
if (!text.getFileName()) {
fd.error.alert('Filename must be valid', 'Your file must not contain special characters, and requires a file extension.');
return this;
}
label.removeEvent('blur', label.retrieve('$blur'));
label.eliminate('$text');
label.set('contenteditable', false).blur();
window.getSelection().removeAllRanges();
li.removeClass('editing');
//fire a renameCancel if the name didnt change
if (text == label.get('title').trim()) {
this.fireEvent('renameCancel', li);
return this;
}
label.set('title', text);
label.set('text', text);
li.set('name', text); li.set('name', text);
li.set('title', text); li.set('title', text);
var path = this.getFullPath(li) var path = this.getFullPath(li);
li.set('path', path); li.set('path', path);
this.fireEvent('renameComplete', [li, path]); this.fireEvent('renameComplete', [li, path]);
return false; return false;
} }
}, },
deleteBranch: function(element) { deleteBranch: function(element) {
element.dispose(); element.dispose();
this.collapse.prepare(); this.collapse.prepare();
this.fireEvent('deleteBranch', element); this.fireEvent('deleteBranch', element);
}, },
addPath: function(obj, options){ addPath: function(obj, options){
options = options || {}; options = options || {};
var suffix = options.suffix || '', var suffix = options.suffix || '',
splitted = obj.getFullName().split('/'), splitted = obj.get('fullName').split('/'),
elements = Array.clone(splitted), elements = Array.clone(splitted),
end = splitted.length - 1, end = splitted.length - 1,
selector = '', selector = '',
el, el,
target = options.target, url = options.url,
id_prefix = this.options.id_prefix; target = options.target,
id_prefix = this.options.id_prefix,
if (id_prefix) { rel = (obj instanceof Module || obj instanceof Attachment) ?
id_prefix += '-'; 'file':
} 'directory';
elements.each(function(name, i){ if (id_prefix) {
var path = splitted.slice(0, i + 1).join('/'); id_prefix += '-';
if (i == end){ }
var previous = elements[i - 1] ? elements[i - 1].getElement('ul') : (options.target.getElement('ul') || options.target);
el = elements[i] = previous.getChildren(selector += 'li[title='+ name + suffix +'] ')[0] || this.addBranch({ elements.each(function(name, i){
'title': obj.getShortName(), var path = splitted.slice(0, i + 1).join('/');
'name': obj.getShortName(), if (i == end){
'path': path, var previous = elements[i - 1] ? elements[i - 1].getElement('ul') : (options.target.getElement('ul') || options.target);
'url': obj.options.url, el = elements[i] = previous.getChildren(selector += 'li[title='+ name + suffix +'] ')[0] || this.addBranch({
'id': obj.getID(), 'title': obj.get('shortName'),
'rel': obj.options.type ? 'file' : 'directory', 'name': obj.get('shortName'),
'class': 'UI_File_Normal' + (options.nodrag ? ' nodrag' : '') 'path': path,
}, previous, options); 'url': url,
'id': options.id,
elements[i].store('file', obj); 'rel': rel,
} else { 'class': 'UI_File_Normal' + (options.nodrag ? ' nodrag' : '')
target = elements[i] = options.target.getElement(selector += '> ul > li[title='+ name +'] ') || this.addBranch({ }, previous, options);
'title': name,
'name': name, elements[i].store('file', obj);
'rel': 'directory', } else {
'id': id_prefix + path.replace(/\//g, '-'), target = elements[i] = options.target.getElement(selector += '> ul > li[title='+ name +'] ') || this.addBranch({
'path': path 'title': name,
}, target, options); 'name': name,
} 'rel': 'directory',
'id': id_prefix + path.replace(/\//g, '-'),
}, this); 'path': path
}, target, options);
return el; }
},
}, this);
getFullPath: function(branch) {
var name = branch.get('title'), return el;
parentEl = branch.getParent('li'); },
if (!parentEl.hasClass('top_branch')) { getFullPath: function(branch) {
name = this.getFullPath(parentEl) + '/' + name; var name = branch.get('title'),
} parentEl = branch.getParent('li');
return name;
}, if (!parentEl.hasClass('top_branch')) {
name = this.getFullPath(parentEl) + '/' + name;
setFullPath: function(branch, path) { }
if (!path) path = this.getFullPath(branch); return name;
branch.set('path', path); },
return branch;
}, setFullPath: function(branch, path) {
if (!path) path = this.getFullPath(branch);
toElement: function() { branch.set('path', path);
return this.element; return branch;
} },
toElement: function() {
return this.element;
}
}); });
FileTree.Collapse = new Class({ FileTree.Collapse = new Class({
Extends: Collapse.LocalStorage, Extends: Collapse.LocalStorage,
updateElement: function(element){ updateElement: function(element){
this.parent(element); this.parent(element);
this.updatePath(element); this.updatePath(element);
}, },
updatePath: function(element){ updatePath: function(element){
var parent = element.getParent('li'), var parent = element.getParent('li'),
path = parent ? parent.get('path') : false; path = parent ? parent.get('path') : false;
element.set('path', (path ? path + '/' : '') + (element.get('path') || '').split('/').getLast()); element.set('path', (path ? path + '/' : '') + (element.get('path') || '').split('/').getLast());
} }
}); });
String.implement('getFileExtension', function() { String.implement('getFileExtension', function() {

Разница между файлами не показана из-за своего большого размера Загрузить разницу