[Testbot] Plone 5.0 - Python 2.7 - Build # 2956 - Still failing! - 0 failure(s)

jenkins at plone.org jenkins at plone.org
Sun Aug 10 08:34:46 UTC 2014


-------------------------------------------------------------------------------
Plone 5.0 - Python 2.7 - Build # 2956 - Still Failing!
-------------------------------------------------------------------------------

http://jenkins.plone.org/job/plone-5.0-python-2.7/2956/


-------------------------------------------------------------------------------
CHANGES
-------------------------------------------------------------------------------

Repository: mockup
Branch: refs/heads/master
Date: 2014-08-10T09:28:36+02:00
Author: Nathan Van Gheem (vangheem) <vangheem at gmail.com>
Commit: https://github.com/plone/mockup/commit/c610a0974e67e268b93b83bacb8840291577b203

add texteditor, filemanager and thememapper patterns

Files changed:
A js/bundles/filemanager.js
A js/bundles/filemanager_develop.js
A less/filemanager.less
A less/popover.less
A patterns/filemanager/js/addnew.js
A patterns/filemanager/js/basepopover.js
A patterns/filemanager/js/customize.js
A patterns/filemanager/js/delete.js
A patterns/filemanager/js/newfolder.js
A patterns/filemanager/js/rename.js
A patterns/filemanager/js/upload.js
A patterns/filemanager/pattern.filemanager.less
A patterns/filemanager/pattern.js
A patterns/filemanager/templates/app.xml
A patterns/texteditor/pattern.js
A patterns/thememapper/pattern.js
A patterns/thememapper/pattern.thememapper.less
A patterns/thememapper/templates/inspector.xml
A tests/files/mapper.html
A tests/pattern-filemanager-test.js
M Gruntfile.js
M Makefile
M js/bundles/docs.js
M js/config.js
M js/utils.js
M less/docs.less
M patterns/structure/js/views/app.js
M patterns/structure/less/pattern.structure.less
M patterns/tree/pattern.js
M patterns/upload/pattern.js
M tests/fakeserver.js

diff --git a/Gruntfile.js b/Gruntfile.js
index b6e74b6..db4d483 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -59,6 +59,10 @@ module.exports = function(grunt) {
     url: '++resource++wildcard.foldercontents-structure'
   });
 
+  mockup.registerBundle('filemanager', {}, {
+    url: '++resource++plone.resourceeditor-filemanager'
+  });
+
   mockup.registerBundle('plone', {
     copy: {
       plone: {
diff --git a/Makefile b/Makefile
index ff7d696..e19d8ce 100644
--- a/Makefile
+++ b/Makefile
@@ -29,6 +29,9 @@ bundle-barceloneta:
 bundle-plone:
 	mkdir -p build
 	NODE_PATH=$(NODE_PATH) $(GRUNT) bundle-plone
+	
+bundle-filemanager:
+	NODE_PATH=$(NODE_PATH) $(GRUNT) bundle-filemanager
 
 docs:
 	if test ! -d docs; then $(GIT) clone git://github.com/plone/mockup.git -b gh-pages docs; fi
diff --git a/js/bundles/docs.js b/js/bundles/docs.js
index dc916b7..72120a7 100644
--- a/js/bundles/docs.js
+++ b/js/bundles/docs.js
@@ -131,7 +131,22 @@ require([
             title: 'Upload',
             description: 'File upload with drag and drop support.',
             url: 'patterns/upload/pattern.js'
-          }
+          },
+          { id: 'filemanager',
+            title: 'File Manager',
+            description: 'Manage file system-like resources',
+            url: 'patterns/filemanager/pattern.js'
+          },
+          { id: 'texteditor',
+            title: 'Text editor',
+            description: 'Edit files TTW nicely',
+            url: 'patterns/texteditor/pattern.js'
+          },
+          { id: 'thememapper',
+            title: 'Theme Mapper',
+            description: 'Map theme rules',
+            url: 'patterns/thememapper/pattern.js'
+          } 
         ]
       },
       { id: 'contribute',
diff --git a/js/bundles/filemanager.js b/js/bundles/filemanager.js
new file mode 100644
index 0000000..ad8dba1
--- /dev/null
+++ b/js/bundles/filemanager.js
@@ -0,0 +1,38 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+define([
+  'jquery',
+  'mockup-registry',
+  'mockup-patterns-base',
+  'mockup-patterns-filemanager'
+], function($, Registry, Base) {
+  'use strict';
+
+  // initialize only if we are in top frame
+  if (window.parent === window) {
+    $(document).ready(function() {
+      Registry.scan($('body'));
+    });
+  }
+});
\ No newline at end of file
diff --git a/js/bundles/filemanager_develop.js b/js/bundles/filemanager_develop.js
new file mode 100644
index 0000000..677e776
--- /dev/null
+++ b/js/bundles/filemanager_develop.js
@@ -0,0 +1,68 @@
+/* globals less:true, domready:true */
+
+(function() {
+  'use strict';
+
+  // https://github.com/QueueHammer/codecraftsman.js/blob/master/codecraftsman.js#L23
+  var ScriptPath = function() {
+    var scriptPath = '', pathParts;
+    try {
+      throw new Error();
+    } catch (e) {
+      var stackLines = e.stack.split('\n');
+      var callerIndex = 0;
+      for (var i in stackLines){
+        if (!stackLines[i].match(/http[s]?:\/\//)) {
+          continue;
+        }
+        callerIndex = Number(i) + 2;
+        break;
+      }
+      pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
+    }
+
+    this.fullPath = function() {
+      return pathParts[1];
+    };
+
+    this.path = function() {
+      return pathParts[2];
+    };
+
+    this.file = function() {
+      return pathParts[3];
+    };
+
+    this.fileNoExt = function() {
+      var parts = this.file().split('.');
+      parts.length = parts.length !== 1 ? parts.length - 1 : 1;
+      return parts.join('.');
+    };
+  };
+
+  window.getScriptPath = function () {
+    return new ScriptPath();
+  };
+
+
+  domready(function() {
+
+    var link = document.createElement('link'),
+        basePath = window.getScriptPath().path();
+    link.setAttribute('rel', 'stylesheet');
+    link.setAttribute('type', 'text/css');
+    link.setAttribute('href', basePath + '++resource++mockup/build/filemanager.min.css');
+    document.getElementsByTagName('head')[0].appendChild(link);
+
+    var script = document.createElement('script');
+    script.setAttribute('type', 'text/javascript');
+    script.setAttribute('src', basePath + '++resource++mockup/js/config.js');
+    script.onload = function() {
+      requirejs.config({ baseUrl: basePath + '++resource++mockup/' });
+      require(['mockup-bundles-filemanager']);
+    };
+    document.getElementsByTagName('head')[0].appendChild(script);
+
+  });
+
+}());
\ No newline at end of file
diff --git a/js/config.js b/js/config.js
index 2c97f51..132ff28 100644
--- a/js/config.js
+++ b/js/config.js
@@ -1,4 +1,4 @@
-/* globals module:true */
+  /* globals module:true */
 
 (function() {
   'use strict';
@@ -37,6 +37,7 @@
       'mockup-bundles-structure': 'js/bundles/structure',
       'mockup-bundles-tiles': 'js/bundles/widgets',
       'mockup-bundles-widgets': 'js/bundles/widgets',
+      'mockup-bundles-filemanager': 'js/bundles/filemanager',
       'mockup-docs': 'bower_components/mockup-core/js/docs/app',
       'mockup-docs-navigation': 'bower_components/mockup-core/js/docs/navigation',
       'mockup-docs-page': 'bower_components/mockup-core/js/docs/page',
@@ -59,6 +60,11 @@
       'mockup-patterns-select2': 'patterns/select2/pattern',
       'mockup-patterns-structure-url': 'patterns/structure',
       'mockup-patterns-structure': 'patterns/structure/pattern',
+      'mockup-patterns-texteditor': 'patterns/texteditor/pattern',
+      'mockup-patterns-filemanager-url': 'patterns/filemanager',
+      'mockup-patterns-filemanager': 'patterns/filemanager/pattern',
+      'mockup-patterns-thememapper-url': 'patterns/thememapper',
+      'mockup-patterns-thememapper': 'patterns/thememapper/pattern',
       'mockup-patterns-tablesorter': 'patterns/tablesorter/pattern',
       'mockup-patterns-tinymce-url': 'patterns/tinymce',
       'mockup-patterns-tinymce': 'patterns/tinymce/pattern',
diff --git a/js/utils.js b/js/utils.js
index 8005a0b..45db79e 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -318,6 +318,9 @@ define([
       return ['true', true, 1].indexOf(val) !== -1;
     },
     QueryHelper: QueryHelper,
-    ProgressIndicator: ProgressIndicator
+    ProgressIndicator: ProgressIndicator,
+    getAuthenticator: function() {
+      return $('input[name="_authenticator"]').val();
+    }
   };
 });
diff --git a/less/docs.less b/less/docs.less
index 4b81976..eb5592c 100644
--- a/less/docs.less
+++ b/less/docs.less
@@ -13,6 +13,8 @@
 @import "../patterns/tooltip/pattern.tooltip.less";
 @import "../patterns/tree/pattern.tree.less";
 @import "../patterns/upload/less/pattern.upload.less";
+ at import "../patterns/filemanager/pattern.filemanager.less";
+ at import "../patterns/thememapper/pattern.thememapper.less";
 
 @mockupColor1: #0083BE;
 @mockupColor2: #026B99;
diff --git a/less/filemanager.less b/less/filemanager.less
new file mode 100644
index 0000000..53b154b
--- /dev/null
+++ b/less/filemanager.less
@@ -0,0 +1,4 @@
+ at import "mixins.less";
+ at import "../patterns/filemanager/pattern.filemanager.less";
+ at import "../bower_components/bootstrap/less/bootstrap.less";
+ at icon-font-path: "@{pathPrefix}../bower_components/bootstrap/dist/fonts/";
diff --git a/less/popover.less b/less/popover.less
new file mode 100644
index 0000000..f7246b1
--- /dev/null
+++ b/less/popover.less
@@ -0,0 +1,19 @@
+.popover {
+    min-width: 111px;
+    max-width: 500px;
+
+    .popover-title {
+
+        padding-bottom: 0;
+        input {
+            margin-bottom: 0;
+        }
+    }
+    &.active {
+        display: block;
+    }
+}
+.backdrop-popover {
+    background-color: #fff;
+    z-index: 1009;
+}
\ No newline at end of file
diff --git a/patterns/filemanager/js/addnew.js b/patterns/filemanager/js/addnew.js
new file mode 100644
index 0000000..ddb4ec8
--- /dev/null
+++ b/patterns/filemanager/js/addnew.js
@@ -0,0 +1,73 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var AddNewView = PopoverView.extend({
+    className: 'popover addnew',
+    title: _.template('<%= translations.add_new_file %>'),
+    content: _.template(
+      '<span class="current-path"></span>' +
+      '<div class="form-group">' +
+        '<label for="filename-field"><%= translations.filename %></label>' +
+        '<input type="text" class="form-control" ' +
+                'id="filename-field" placeholder="<%= translations.enter_filename %>">' +
+      '</div>' +
+      '<button class="btn btn-block btn-primary"><%= translations.add %></button>'
+    ),
+    events: {
+      'click button': 'addButtonClicked'
+    },
+    addButtonClicked: function(e) {
+      var self = this;
+      var $input = self.$('input');
+      var filename = $input.val();
+      if (filename){
+        self.app.doAction('addFile', {
+          type: 'POST',
+          data: {
+            filename: filename,
+            path: self.getFolderPath()
+          },
+          success: function(data) {
+            self.hide();
+            self.app.$tree.tree('reload');
+          }
+        });
+        // XXX show loading
+      } else {
+        self.$('.form-group').addClass('has-error');
+      }
+    }
+  });
+
+  return AddNewView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/basepopover.js b/patterns/filemanager/js/basepopover.js
new file mode 100644
index 0000000..0b25fbc
--- /dev/null
+++ b/patterns/filemanager/js/basepopover.js
@@ -0,0 +1,65 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-ui-url/views/popover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var FileManagerPopover = PopoverView.extend({
+    className: 'popover',
+    title: _.template('nothing'),
+    content: _.template('<div/>'),
+    initialize: function(options) {
+      this.app = options.app;
+      options.translations = this.app.options.translations;
+      PopoverView.prototype.initialize.apply(this, [options]);
+    },
+    render: function() {
+      var self = this;
+      PopoverView.prototype.render.call(this);
+      return self;
+    },
+    toggle: function(button, e) {
+      PopoverView.prototype.toggle.apply(this, [button, e]);
+      var self = this;
+      if (!self.opened) {
+        return;
+      }
+      var $path = self.$('.current-path');
+      if ($path.length !== 0){
+        $path.html(self.getPath());
+      }
+    },
+    getPath: function() {
+      return this.app.getFolderPath();
+    }
+  });
+
+  return FileManagerPopover;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/customize.js b/patterns/filemanager/js/customize.js
new file mode 100644
index 0000000..76b50e3
--- /dev/null
+++ b/patterns/filemanager/js/customize.js
@@ -0,0 +1,98 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var CustomizeView = PopoverView.extend({
+    className: 'popover customize',
+    title: _.template('<%= translations.add_override %>'),
+    content: _.template(
+      '<form>' +
+        '<div class="input-group">' +
+          '<input type="text" class="search form-control" ' +
+                  'id="search-field" placeholder="<%= translations.search_resources %>">' +
+          '<span class="input-group-btn">' +
+            '<input type="submit" class="btn btn-primary" value="<%= translations.search %>"/>' +
+          '</span>' +
+        '</div>' +
+      '</form>' +
+      '<ul class="results list-group">' +
+      '</ul>'
+    ),
+    render: function() {
+      var self = this;
+      PopoverView.prototype.render.call(this);
+      self.$form = self.$('form');
+      self.$results = self.$('.results');
+      self.$form.submit(function(e){
+        e.preventDefault();
+        $.ajax({
+          url: self.app.options.resourceSearchUrl,
+          dataType: 'json',
+          success: function(data){
+            self.$results.empty();
+            _.each(data, function(item){
+              var $item = $(
+                '<li class="list-group-item" data-id="' + item.id + '">' +
+                  '<span class="badge"><a href=#">' + self.options.transitions.customize + '</a></span>' +
+                  item.id +
+                '</li>');
+              $('a', $item).click(function(e){
+                e.preventDefault();
+                self.customize($(this).parents('li').eq(0).attr('data-id'));
+              });
+              self.$results.append($item);
+            });
+          }
+        });
+      });
+      return self;
+    },
+    customize: function(resource) {
+      var self = this;
+      self.app.doAction('customize', {
+        type: 'POST',
+        data: {
+          resource: resource
+        },
+        success: function(data) {
+          self.hide();
+          // clear out
+          self.$('input.search').attr('value', '');
+          self.$results.empty();
+          self.app.$tree.tree('reload');
+        }
+      });
+    }
+  });
+
+  return CustomizeView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/delete.js b/patterns/filemanager/js/delete.js
new file mode 100644
index 0000000..2b2d9d4
--- /dev/null
+++ b/patterns/filemanager/js/delete.js
@@ -0,0 +1,65 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var DeleteView = PopoverView.extend({
+    className: 'popover delete',
+    title: _.template('<%= translations.delete %>'),
+    content: _.template(
+      '<span class="current-path"></span>' +
+      '<p><%= translations.delete_question %></p>' +
+      '<button class="btn btn-block btn-danger"><%= translations.yes_delete %></button>'
+    ),
+    events: {
+      'click button': 'deleteButtonClicked'
+    },
+    getPath: function() {
+      return this.app.getNodePath();
+    },
+    deleteButtonClicked: function(e) {
+      var self = this;
+      self.app.doAction('delete', {
+        type: 'POST',
+        data: {
+          path: self.app.getNodePath()
+        },
+        success: function(data) {
+          self.hide();
+          self.app.$tree.tree('reload');
+        }
+      });
+      // XXX show loading
+    }
+  });
+
+  return DeleteView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/newfolder.js b/patterns/filemanager/js/newfolder.js
new file mode 100644
index 0000000..2ddcad3
--- /dev/null
+++ b/patterns/filemanager/js/newfolder.js
@@ -0,0 +1,73 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var AddNewView = PopoverView.extend({
+    className: 'popover addfolder',
+    title: _.template('<%= translations.new_folder %>'),
+    content: _.template(
+      '<span class="current-path"></span>' +
+      '<div class="form-group">' +
+        '<label for="filename-field"><%= translations.folder_name %></label>' +
+        '<input type="email" class="form-control" ' +
+                'id="filename-field" placeholder="<%= translations.enter_folder_name %>">' +
+      '</div>' +
+      '<button class="btn btn-block btn-primary"><%= translations.add %></button>'
+    ),
+    events: {
+      'click button': 'addButtonClicked'
+    },
+    addButtonClicked: function(e) {
+      var self = this;
+      var $input = self.$('input');
+      var name = $input.val();
+      if (name){
+        self.app.doAction('addFolder', {
+          type: 'POST',
+          data: {
+            name: name,
+            path: self.app.getFolderPath()
+          },
+          success: function(data) {
+            self.hide();
+            self.app.$tree.tree('reload');
+          }
+        });
+        // XXX show loading
+      } else {
+        self.$('.form-group').addClass('has-error');
+      }
+    }
+  });
+
+  return AddNewView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/rename.js b/patterns/filemanager/js/rename.js
new file mode 100644
index 0000000..3b88fbd
--- /dev/null
+++ b/patterns/filemanager/js/rename.js
@@ -0,0 +1,83 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover'
+], function($, _, Backbone, PopoverView) {
+  'use strict';
+
+  var RenameView = PopoverView.extend({
+    className: 'popover addnew',
+    title: _.template('<%= translations.rename %>'),
+    content: _.template(
+      '<span class="current-path"></span>' +
+      '<div class="form-group">' +
+        '<label for="filename-field"><%= translations.filename %></label>' +
+        '<input type="text" class="form-control" ' +
+                'id="filename-field">' +
+      '</div>' +
+      '<button class="btn btn-block btn-primary"><%= translations.rename %></button>'
+    ),
+    events: {
+      'click button': 'renameButtonClicked'
+    },
+    toggle: function(button, e) {
+      PopoverView.prototype.toggle.apply(this, [button, e]);
+      var self = this;
+      if (!self.opened) {
+        return;
+      }
+      var node = self.app.getSelectedNode();
+      self.$('input').val(node.name);
+      self.$('.current-path').html(self.app.getNodePath(node));
+    },
+    renameButtonClicked: function(e) {
+      var self = this;
+      var $input = self.$('input');
+      var filename = $input.val();
+      if (filename){
+        self.app.doAction('renameFile', {
+          type: 'POST',
+          data: {
+            path: self.app.getNodePath(),
+            filename: filename
+          },
+          success: function(data) {
+            self.hide();
+            self.app.$tree.tree('reload');
+          }
+        });
+        // XXX show loading
+      } else {
+        self.$('.form-group').addClass('has-error');
+      }
+    }
+  });
+
+  return RenameView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/js/upload.js b/patterns/filemanager/js/upload.js
new file mode 100644
index 0000000..9115196
--- /dev/null
+++ b/patterns/filemanager/js/upload.js
@@ -0,0 +1,64 @@
+// Author: Nathan Van Gheem
+// Contact: nathan at vangheem.us
+// Version: 1.0
+//
+// Description:
+//
+// License:
+//
+// Copyright (C) 2010 Plone Foundation
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+
+
+define([
+  'jquery',
+  'underscore',
+  'backbone',
+  'mockup-patterns-filemanager-url/js/basepopover',
+  'mockup-patterns-upload'
+], function($, _, Backbone, PopoverView, Upload) {
+  'use strict';
+
+  var UploadView = PopoverView.extend({
+    className: 'popover upload',
+    title: _.template('<%= translations.upload %>'),
+    content: _.template(
+      '<span class="current-path"></span>' +
+      '<input type="text" name="upload" style="display:none" />' +
+      '<div class="uploadify-me"></div>'),
+    render: function() {
+      var self = this;
+      PopoverView.prototype.render.call(this);
+      self.upload = new Upload(self.$('.uploadify-me').addClass('pat-upload'), {
+        url: self.app.options.uploadUrl,
+        success: function() {
+        }
+      });
+      return this;
+    },
+    toggle: function(button, e) {
+      /* we need to be able to change the current default upload directory */
+      PopoverView.prototype.toggle.apply(this, [button, e]);
+      var self = this;
+      if (!this.opened) {
+        return;
+      }
+    }
+
+  });
+
+  return UploadView;
+});
\ No newline at end of file
diff --git a/patterns/filemanager/pattern.filemanager.less b/patterns/filemanager/pattern.filemanager.less
new file mode 100644
index 0000000..9d3defa
--- /dev/null
+++ b/patterns/filemanager/pattern.filemanager.less
@@ -0,0 +1,92 @@
+ at import "popover.less";
+
+.importFileManagerDep() when not (@isMockup) {
+  @import (reference) "popover.less";
+}
+.importFileManagerDep() when (@isMockup) {
+  @import "../../less/popover.less";
+}
+.importFileManagerDep();
+
+
+.pat-filemanager {
+    #fileselector li label {
+        display: inline;
+    }
+    .glyphicon {
+        top: 2px;
+    }
+    ul li,
+    ol li {
+        text-indent: 0;
+        padding-left: 0;
+    }
+    li::before {
+        content: "";
+        padding-right: 0;
+    }
+    #fileselector li label {
+       font-weight: normal;
+       margin-bottom: 0;
+    }
+    .popover.upload{
+        max-width: 700px;
+    }
+    .tree {
+        float: left;
+        width: 17%;
+        ul.jqtree-tree li.jqtree-selected > .jqtree-element, ul.jqtree-tree li.jqtree-selected > .jqtree-element:hover {
+        background: #E7E7E7;
+        }
+        ul.jqtree-tree {
+            margin-left: 0px;
+            li span {
+            padding-left: 12px;
+            }
+        }
+    }
+    .navbar-default {
+        min-height: 250px
+    }
+    .nav-and-editor {
+        float: left;
+        width: 75%;
+    }
+    .navbar-nav > li {
+        padding-left: 15px;
+        padding-right: 15px;
+        background-color: #F8F8F8;
+        -webkit-border-top-left-radius: 10px;
+        -webkit-border-top-right-radius: 10px;
+        -moz-border-radius-topleft: 10px;
+        -moz-border-radius-topright: 10px;
+        border-top-left-radius: 10px;
+        border-top-right-radius: 10px;
+        margin-right: 5px;
+        margin-top: 9px;
+        border-top: 1px solid #CCC;
+        border-right: 1px solid #CCC;
+        border-left: 1px solid #CCC;
+        &.active {
+            border-top: 1px solid #999;
+            border-right: 1px solid #999;
+            border-left: 1px solid #999;
+            background-color: #E7E7E7;
+            a {
+                background-color: #E7E7E7;
+            }
+        }
+        > a {
+            float: left;
+            padding: 8px 0px;
+            +a {
+                padding-left: 4px;
+            }
+            &:focus {
+                outline: none;
+            }
+        }
+    }
+    
+
+}
\ No newline at end of file
diff --git a/patterns/filemanager/pattern.js b/patterns/filemanager/pattern.js
new file mode 100644
index 0000000..80f8085
--- /dev/null
+++ b/patterns/filemanager/pattern.js
@@ -0,0 +1,387 @@
+/* Filemanager pattern.
+ *
+ * Options:
+ *    aceConfig(object): ace configuration ({})
+ *    actionUrl(string): base url to get/put data. Action is passed is an a parameters, ?action=(dataTree, newFile, deleteFile, getFile, saveFile)
+ *    uploadUrl(string): url to upload files to
+ *    resourceSearchUrl(string): url to search for resources to customize
+ *    translations(object): mapping of translation strings
+ *
+ * Documentation:
+ *
+ *
+ *   {{ example-1 }}
+ *
+ *   Example with upload
+ *
+ *   {{ example-2 }}
+ *
+ * Example: example-1
+ *    <div class="pat-filemanager"
+ *         data-pat-filemanager="actionUrl:/filemanager-actions;
+ *                               resourceSearchUrl:/search-resources;">
+ *    </div>
+ *
+ * Example: example-2
+ *    <div class="pat-filemanager"
+ *         data-pat-filemanager="actionUrl:/filemanager-actions;
+ *                               uploadUrl:/upload;
+ *                               resourceSearchUrl:/search-resources;">
+ *    </div>
+ *
+ * License:
+ *    Copyright (C) 2010 Plone Foundation
+ *
+ *    This program is free software; you can redistribute it and/or modify it
+ *    under the terms of the GNU General Public License as published by the
+ *    Free Software Foundation; either version 2 of the License.
+ *
+ *    This program is distributed in the hope that it will be useful, but
+ *    WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ *    Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+define([
+  'jquery',
+  'mockup-patterns-base',
+  'underscore',
+  'backbone',
+  'mockup-ui-url/views/base',
+  'mockup-patterns-tree',
+  'mockup-patterns-texteditor',
+  'text!mockup-patterns-filemanager-url/templates/app.xml',
+  'mockup-ui-url/views/toolbar',
+  'mockup-ui-url/views/button',
+  'mockup-ui-url/views/buttongroup',
+  'mockup-patterns-filemanager-url/js/addnew',
+  'mockup-patterns-filemanager-url/js/newfolder',
+  'mockup-patterns-filemanager-url/js/delete',
+  'mockup-patterns-filemanager-url/js/customize',
+  'mockup-patterns-filemanager-url/js/rename',
+  'mockup-patterns-filemanager-url/js/upload',
+  'mockup-utils',
+  'text!mockup-ui-url/templates/popover.xml'
+], function($, Base, _, Backbone, BaseView, Tree, TextEditor, AppTemplate, Toolbar,
+            ButtonView, ButtonGroup, AddNewView, NewFolderView, DeleteView,
+            CustomizeView, RenameView, UploadView, utils) {
+  'use strict';
+
+  var FileManager = Base.extend({
+    name: 'filemanager',
+    template: _.template(AppTemplate),
+    tabItemTemplate: _.template(
+      '<li class="active" data-path="<%= path %>">' +
+        '<a href="#" class="select"><%= path %></a>' +
+        '<a href="#" class="remove">' +
+          '<span class="glyphicon glyphicon-remove-circle"></span>' +
+        '</a>' +
+      '</li>'),
+    saveBtn: null,
+    fileData: {},  /* mapping of files to data that it describes */ 
+    defaults: {
+      aceConfig: {},
+      actionUrl: null,
+      uploadUrl: null,
+      resourceSearchUrl: null,
+      translations: {
+        add_new_file: 'New file',
+        add_new_file_tooltip: 'Add new file to current folder',
+        add_override: 'Add new override',
+        add_override_tooltip: 'Find resource in plone to override',
+        delete: 'Delete',
+        delete_tooltip: 'Delete currently selected file',
+        new_folder: 'New folder',
+        new_folder_tooltip: 'Add new folder to current directory',
+        rename: 'Rename',
+        rename_tooltip: 'Rename currently selected resource',
+        upload: 'Upload',
+        upload_tooltip: 'Upload file to current directory',
+        filename: 'Filename',
+        enter_filename: 'Enter filename',
+        add: 'Add',
+        search: 'Search',
+        search_resources: 'Search resources',
+        customize: 'Customize',
+        yes_delete: 'Yes, delete',
+        delete_question: 'Are you sure you want to delete this resource?',
+        folder_name: 'Folder name',
+        enter_folder_name: 'Enter folder name',
+        save: 'Save'
+      },
+      treeConfig: {
+        autoOpen: true
+      }
+    },
+    init: function() {
+      var self = this;
+      if (self.options.actionUrl === null) {
+        self.$el.html('Must specify actionUrl setting for pattern');
+        return;
+      }
+      self.options.treeConfig = $.extend(true, {}, self.treeConfig, {
+        dataUrl: self.options.actionUrl + '?action=dataTree'
+      });
+
+      var translations = self.options.translations;
+
+      self.fileData = {};
+      self.saveBtn = new ButtonView({
+        id: 'save',
+        title: translations.save,
+        context: 'success'
+      });
+
+      var newFolderView = new NewFolderView({
+        triggerView: new ButtonView({
+          id: 'newfolder',
+          title: translations.new_folder,
+          tooltip: translations.new_folder_tooltip,
+          context: 'default'
+        }),
+        app: self
+      });
+      var addNewView = new AddNewView({
+        triggerView: new ButtonView({
+          id: 'addnew',
+          title: translations.add_new_file,
+          tooltip: translations.add_new_file_tooltip,
+          context: 'default'
+        }),
+        app: self
+      });
+      var renameView = new RenameView({
+        triggerView: new ButtonView({
+          id: 'rename',
+          title: translations.rename,
+          tooltip: translations.rename_tooltip,
+          context: 'default'
+        }),
+        app: self
+      });
+      var deleteView = new DeleteView({
+        triggerView: new ButtonView({
+          id: 'delete',
+          title: translations.delete,
+          tooltip: translations.delete_tooltip,
+          context: 'danger'
+        }),
+        app: self
+      });
+
+      self.views = [
+        newFolderView,
+        addNewView,
+        renameView,
+        deleteView
+      ];
+      var mainButtons = [
+        newFolderView.triggerView,
+        addNewView.triggerView
+      ];
+
+      if (self.options.uploadUrl){
+        var uploadView = new UploadView({
+          triggerView: new ButtonView({
+            id: 'upload',
+            title: translations.upload,
+            tooltip: translations.upload_tooltip,
+            context: 'default'
+          }),
+          app: self
+        });
+        self.views.push(uploadView);
+        mainButtons.push(uploadView.triggerView);
+      }
+      if (self.options.resourceSearchUrl){
+        var customizeView = new CustomizeView({
+          triggerView: new ButtonView({
+            id: 'customize',
+            title: translations.add_override,
+            tooltip: translations.add_override_tooltip,
+            context: 'default'
+          }),
+          app: self
+        });
+        self.views.push(customizeView);
+        mainButtons.push(customizeView.triggerView);
+      }
+
+      self.toolbar = new Toolbar({
+        items: [
+          new ButtonGroup({
+            items: mainButtons,
+            id: 'main',
+            app: self
+          }),
+          new ButtonGroup({
+            items: [
+              renameView.triggerView,
+              deleteView.triggerView
+            ],
+            id: 'secondary',
+            app: self
+          }),
+          self.saveBtn
+        ]
+      });
+
+      self.saveBtn.on('button:click', function(e) {
+        self.doAction('saveFile', {
+          type: 'POST',
+          data: {
+            path: self.$tree.tree('getSelectedNode').label
+          },
+          success: function(data) {
+            /* XXX unhighlight save button */
+          }
+        });
+      });
+      self.render();
+    },
+    $: function(selector){
+      return this.$el.find(selector);
+    },
+    render: function(){
+      var self = this;
+      self.$el.html(self.template(self.options));
+      self.$('#toolbar').append(self.toolbar.render().el);
+      _.each(self.views, function(view) {
+        self.$('#toolbar').append(view.render().el);
+      });
+      self.$tree = self.$('.tree');
+      self.$nav = self.$('nav');
+      self.$tabs = $('ul.nav', self.$nav);
+      self.options.treeConfig.onLoad = function(tree) {
+        // on loading initial data, activate first node if available
+        var node = self.$tree.tree('getNodeById', 1);
+        if (node){
+          self.$tree.tree('selectNode', node);
+          self.openFile({node: node});
+        }
+      };
+      self.tree = new Tree(self.$tree, self.options.treeConfig);
+      self.$tree.bind('tree.click', function(e) {
+        self.openFile(e);
+      });
+      self.$editor = self.$('.editor');
+    },
+    openFile: function(event) {
+      var self = this;
+      var doc = event.node.name;
+      if (event.node.folder){
+        return true;
+      }
+
+      self.doAction('getFile', {
+        data: { path: doc },
+        dataType: 'json',
+        success: function(data) {
+          self.fileData[doc] = data;
+          $('li', self.$tabs).removeClass('active');
+          var $existing = $('[data-path="' + doc + '"]');
+          if ($existing.length === 0){
+            var $item = $(self.tabItemTemplate({path: doc}));
+            self.$tabs.append($item);
+            $('.remove', $item).click(function(e){
+              e.preventDefault();
+              if ($(this).parent().hasClass('active'))
+              {
+                var $siblings = $(this).parent().siblings();
+                if ($siblings.length > 0){
+                  var $item;
+                  if ($(this).parent().prev().length > 0){
+                    $item = $(this).parent().prev();
+                  } else {
+                    $item = $(this).parent().next();
+                  }
+                  $item.addClass('active');
+                  self.openEditor($item.attr('data-path'));
+                } else {
+                  self.ace.setText('');
+                }
+              }
+              $(this).parent().remove();
+            });
+            $('.select', $item).click(function(e){
+              e.preventDefault();
+              $('li', self.$tabs).removeClass('active');
+              var $li = $(this).parent();
+              $li.addClass('active');
+              self.openEditor($li.attr('data-path'));
+            });
+          }else{
+            $existing.addClass('active');
+          }
+          self.openEditor(doc);
+        }
+      });
+    },
+    doAction: function(action, options) {
+      var self = this;
+      if (!options){
+        options = {};
+      }
+      $.ajax({
+        url: self.options.actionUrl,
+        type: options.type || 'GET',
+        data: $.extend({}, {
+          _authenticator: utils.getAuthenticator(),
+          action: action
+        }, options.data || {}),
+        success: options.success,
+        failure: options.failure || function() {}
+      });
+    },
+    openEditor: function(path) {
+      var self = this;
+      if (self.ace !== undefined){
+        self.ace.editor.destroy();
+      }
+      self.ace = new TextEditor(self.$editor, {
+        width: self.$editor.width()
+      });
+      self.ace.setSyntax(path);
+      self.ace.setText(self.fileData[path].data);
+      self.ace.editor.clearSelection();
+    },
+    getSelectedNode: function() {
+      return this.$tree.tree('getSelectedNode');
+    },
+    getNodePath: function(node) {
+      var self = this;
+      if(node === undefined){
+        node = self.getSelectedNode();
+      }
+      var path = self.getFolderPath(node.parent);
+      if (path !== '/'){
+        path += '/';
+      }
+      return path + node.name;
+    },
+    getFolderPath: function(node){
+      var self = this;
+      if(node === undefined){
+        node = self.getSelectedNode();
+      }
+      var parts = [];
+      if (!node.folder && node.name){
+        node = node.parent;
+      }
+      while (node.name){
+        parts.push(node.name);
+        node = node.parent;
+      }
+      return '/' + parts.join('/');
+    } 
+
+  });
+
+  return FileManager;
+
+});
\ No newline at end of file
diff --git a/patterns/filemanager/templates/app.xml b/patterns/filemanager/templates/app.xml
new file mode 100644
index 0000000..11d594f
--- /dev/null
+++ b/patterns/filemanager/templates/app.xml
@@ -0,0 +1,17 @@
+<div id="toolbar">
+</div>
+<div class="container">
+    <div class="tree">
+    </div>
+    <div class="nav-and-editor">
+        <nav class="navbar navbar-default" role="navigation">
+            <div class="collapse navbar-collapse">
+              <ul class="nav navbar-nav">
+               </ul>
+            </div><!-- /.navbar-collapse -->
+            <div class="fileeditor">
+                <div class="editor">
+                </div>
+            </div>
+        </nav>
+</div>
\ No newline at end of file
diff --git a/patterns/structure/js/views/app.js b/patterns/structure/js/views/app.js
index d3b5e77..1949f53 100644
--- a/patterns/structure/js/views/app.js
+++ b/patterns/structure/js/views/app.js
@@ -267,7 +267,7 @@ define([
           // if selection is overridden by another mechanism
           data.selection = JSON.stringify(self.getSelectedUids());
         }
-        data._authenticator = $('input[name="_authenticator"]').val();
+        data._authenticator = utils.getAuthenticator();
         if (data.folder === undefined) {
           data.folder = self.options.queryHelper.getCurrentPath();
         }
@@ -437,7 +437,7 @@ define([
         data: {
           delta: delta,
           id: id,
-          _authenticator: $('[name="_authenticator"]').val(),
+          _authenticator: utils.getAuthenticator(),
           subsetIds: JSON.stringify(subsetIds)
         },
         dataType: 'json',
diff --git a/patterns/structure/less/pattern.structure.less b/patterns/structure/less/pattern.structure.less
index 2d6bd79..98f79c9 100644
--- a/patterns/structure/less/pattern.structure.less
+++ b/patterns/structure/less/pattern.structure.less
@@ -6,6 +6,7 @@
   @import (reference) "@{bowerPath}/bootstrap/less/button-groups.less";
   @import (reference) "@{bowerPath}/bootstrap/less/glyphicons.less";
   @import (reference) "@{bowerPath}/bootstrap/less/badges.less";
+  @import (reference) "popover.less";
 }
 .importStructureDep() when (@isMockup) {
   @icon-font-path: "../../../bower_components/bootstrap/dist/fonts/";
@@ -14,9 +15,11 @@
   @import "../../../bower_components/bootstrap/less/button-groups.less";
   @import "../../../bower_components/bootstrap/less/glyphicons.less";
   @import "../../../bower_components/bootstrap/less/badges.less";
+  @import "../../../less/popover.less";
 }
 .importStructureDep();
 
+
 .pat-structure {
     table{
         margin-bottom: 0;
@@ -92,15 +95,7 @@
 .pat-structure {
 
     .popover {
-        min-width: 111px;
-        max-width: 500px;
-
         .popover-title {
-
-            padding-bottom: 0;
-            input {
-                margin-bottom: 0;
-            }
             .remove-all {
                 display: inline-block;
                 clear: left;
@@ -116,9 +111,6 @@
                 }
             }
         }
-        &.active {
-            display: block;
-        }
     }
 
     .clear{
@@ -225,10 +217,4 @@
 
 .structure-dragging{
     outline: 1px dashed black;
-}
-
-
-.backdrop-popover {
-    background-color: #fff;
-    z-index: 1009;
-}
+}
\ No newline at end of file
diff --git a/patterns/texteditor/pattern.js b/patterns/texteditor/pattern.js
new file mode 100644
index 0000000..5d24e3a
--- /dev/null
+++ b/patterns/texteditor/pattern.js
@@ -0,0 +1,166 @@
+
+/* Text editor pattern
+ *
+ * Options:
+ *    theme(string): Theme to use with editor. defaults to whatever it ships with. (null)
+ *    mode(string): What type of syntax is it? ('text')
+ *    width(integer): Width of the editor. (500)
+ *    height(integer): Height of the editor. (200)
+ *    tabSize(integer): TODO (4)
+ *    softTabs(boolean): Use spaces for tabs. (true)
+ *    wrapMode(boolean): Wrap text. (false)
+ *    showGutter(boolean): TODO (true),
+ *    showPrintMargin(boolean): Show print margin. (false)
+ *    readOnly(boolean): Read only editor. (false)
+ *
+ * Documentation:
+ *    # Default
+ *
+ *    {{ example-1 }}
+ *
+ *    # Different theme
+ *
+ *    {{ example-2 }}
+ *
+ *    # Different options
+ *
+ *    {{ example-3 }}
+ *
+ * Example: example-1
+ *    <pre class="pat-texteditor" data-pat-texteditor="theme:clouds">
+ *    foobar
+ *    </pre>
+ *
+ * Example: example-2
+ *    <pre class="pat-texteditor" data-pat-texteditor="mode:javascript;theme:dawn;">
+ *    var foo = 'bar';
+ *    function foobar() {
+ *      return foo;
+ *    }
+ *    </pre>
+ *
+ * Example: example-3
+ *    <pre class="pat-texteditor"
+ *         data-pat-texteditor="mode:javascript;
+ *                       theme:ambiance;
+ *                       tabSize:2;
+ *                       showGutter:false;
+ *                       showPrintMargin:true;">
+ *    var foo = 'bar';
+ *    function foobar() {
+ *      return foo;
+ *    }
+ *    </pre>
+ *
+ * License:
+ *    Copyright (C) 2010 Plone Foundation
+ *
+ *    This program is free software; you can redistribute it and/or modify it
+ *    under the terms of the GNU General Public License as published by the
+ *    Free Software Foundation; either version 2 of the License.
+ *
+ *    This program is distributed in the hope that it will be useful, but
+ *    WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ *    Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+define([
+  'jquery',
+  'mockup-patterns-base',
+  'mockup-utils',
+  'ace',
+  'ace-theme-monokai',
+  'ace-mode-text'
+], function($, Base, utils) {
+  'use strict';
+
+  var AcePattern = Base.extend({
+    name: 'texteditor',
+    defaults: {
+      theme: null,
+      mode: 'text',
+      width: 500,
+      height: 200,
+      tabSize: 4,
+      softTabs: true,
+      wrapMode: false,
+      showGutter: true,
+      showPrintMargin: false,
+      readOnly: false
+    },
+    init: function() {
+      var self = this;
+      if (!window.ace){
+        // XXX hack...
+        // wait, try loading later
+        setTimeout(function() {
+          self.init();
+        }, 200);
+        return;
+      }
+      var ace = window.ace;
+
+      // set id on current element
+      var id = utils.setId(self.$el);
+      self.$wrapper = $('<div/>').css({
+        height: self.options.height + 25, // weird sizing issue here...
+        width: self.options.width,
+        position: 'relative'
+      });
+      self.$el.wrap(self.$wrapper);
+      self.$el.css({
+        width: self.options.width,
+        height: self.options.height,
+        position: 'absolute'
+      });
+
+      self.editor = ace.edit(id);
+      if (self.options.theme) {
+        self.setTheme(self.options.theme);
+      }
+      self.editor.getSession().setMode('ace/mode/' + self.options.mode);
+      self.editor.getSession().setTabSize(parseInt(self.options.tabSize, 10));
+      self.editor.getSession().setUseSoftTabs(utils.bool(self.options.softTabs));
+      self.editor.getSession().setUseWrapMode(utils.bool(self.options.wrapMode));
+      self.editor.renderer.setShowGutter(utils.bool(self.options.showGutter));
+      self.editor.setShowPrintMargin(utils.bool(self.options.showPrintMargin));
+      self.editor.setReadOnly(utils.bool(self.options.readOnly));
+    },
+    setSyntax: function(name)
+    {
+      var self = this;
+      var modes = {
+        'js': 'javascript',
+        'txt': 'text',
+        'css': 'css',
+        'html': 'html',
+        'xml': 'xml'
+      };
+
+      var extension = name.substr(name.lastIndexOf('.') + 1);
+      var mode = modes[extension];
+
+      if (mode !== undefined){
+        self.editor.getSession().setMode('ace/mode/' + mode);
+        return true;
+      }
+    },
+    setTheme: function(theme) {
+      var self = this;
+      self.editor.setTheme('ace/theme/' + theme);
+    },
+    setText: function(data) {
+      var self = this;
+      self.editor.setValue(data);
+    }
+  });
+
+  return AcePattern;
+
+});
\ No newline at end of file
diff --git a/patterns/thememapper/pattern.js b/patterns/thememapper/pattern.js
new file mode 100644
index 0000000..cb667d1
--- /dev/null
+++ b/patterns/thememapper/pattern.js
@@ -0,0 +1,567 @@
+/* Theme Mapper pattern.
+ *
+ * Options:
+ *    filemanagerConfig(object): The file manager pattern config ({})
+ *    mockupUrl(string): Mockup url (null)
+ *    unthemedUrl(string): unthemed site url (null)
+ *    helpUrl(string): Helper docs url (null)
+ *    previewUrl(string): url to preview theme (null)
+ *
+ *
+ * Documentation:
+ *
+ *    # Basic example
+ *
+ *    {{ example-1 }}
+ *
+ *
+ * Example: example-1
+ *
+ *    <div class="pat-thememapper"
+ *         data-pat-thememapper='filemanagerConfig:{"actionUrl":"/filemanager-actions"};
+ *                               mockupUrl:/tests/files/mapper.html;
+ *                               unthemedUrl:/tests/files/mapper.html;
+ *                               previewUrl:http://www.google.com;
+ *                               helpUrl:http://docs.diazo.org/en/latest'></div>
+ *
+ *
+ * License:
+ *    Copyright (C) 2010 Plone Foundation
+ *
+ *    This program is free software; you can redistribute it and/or modify it
+ *    under the terms of the GNU General Public License as published by the
+ *    Free Software Foundation; either version 2 of the License.
+ *
+ *    This program is distributed in the hope that it will be useful, but
+ *    WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ *    Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+define([
+  'jquery',
+  'mockup-patterns-base',
+  'underscore',
+  'text!mockup-patterns-thememapper-url/templates/inspector.xml',
+  'mockup-patterns-filemanager',
+  'mockup-ui-url/views/button',
+  'mockup-ui-url/views/buttongroup'
+], function($, Base, _, InspectorTemplate, FileManager, ButtonView, ButtonGroup) {
+  'use strict';
+
+  var inspectorTemplate = _.template(InspectorTemplate);
+
+
+  var RuleBuilder = function(callback){
+    /**
+      * Rule builder
+      *
+      * Contains functions to build CSS and XPath selectors as well as a Diazo rule
+      * from a given node, and acts as a state machine for the rules wizard.
+      *
+      * The callback is called whenever the state machine progresses.
+      */
+
+    var self = this;
+    self.callback = callback;
+
+    self.active = false;
+    self.currentScope = null;
+
+    self.ruleType = null;
+    self.subtype = null;
+
+    self._contentElement = null;
+    self._themeElement = null;
+
+    self.end = function() {
+      self._contentElement = null;
+      self._themeElement = null;
+      self.currentScope = null;
+      self.active = false;
+      self.ruleType = null;
+      self.subtype = null;
+
+      self.callback(this);
+    };
+
+    /**
+    * Build a diazo rule. 'themeChildren' and 'contentChildren' should be true or
+    * false to indicate whether a -children selector is to be used.
+    */
+    self.buildRule = function(themeChildren, contentChildren) {
+      if (self.ruleType === null) {
+        return '';
+      }
+
+      if (self.subtype !== null) {
+        if (self.subtype === 'content') {
+          return '<' + self.ruleType + '\n    ' +
+            self.calculateDiazoSelector(self._contentElement, 'content', contentChildren) +
+            '\n    />';
+        } else if (self.subtype === 'theme') {
+          return '<' + self.ruleType + '\n    ' +
+            self.calculateDiazoSelector(self._themeElement, 'theme', themeChildren) +
+            '\n    />';
+        }
+
+      } else {
+        return '<' + self.ruleType + '\n    ' +
+          self.calculateDiazoSelector(self._themeElement, 'theme', themeChildren) + '\n    ' +
+          self.calculateDiazoSelector(self._contentElement, 'content', contentChildren) +
+          '\n    />';
+      }
+
+      // Should never happen
+      return 'Error';
+    };
+
+    /**
+    * Return a valid (but not necessarily unique) CSS selector for the given
+    * element.
+    */
+    self.calculateCSSSelector = function(element) {
+      var selector = element.tagName.toLowerCase();
+
+      if (element.id) {
+        selector += '#' + element.id;
+      } else {
+        var classes = $(element).attr('class');
+        if(classes !== undefined) {
+          var splitClasses = classes.split(/\s+/);
+          for(var i = 0; i < splitClasses.length; i=i+1) {
+            if(splitClasses[i] !== '' && splitClasses[i].indexOf('_theming') === -1) {
+              selector += '.' + splitClasses[i];
+              break;
+            }
+          }
+        }
+      }
+
+      return selector;
+    };
+
+    /**
+    * Return a valid, unqiue CSS selector for the given element. Returns null if
+    * no reasoanble unique selector can be built.
+    */
+    self.calculateUniqueCSSSelector = function(element) {
+      var paths = [];
+      var path = null;
+
+      var parents = $(element).parents();
+      var ultimateParent = parents[parents.length - 1];
+
+      while (element && element.nodeType === 1) {
+        var selector = this.calculateCSSSelector(element);
+            paths.splice(0, 0, selector);
+            path = paths.join(' ');
+
+        // The ultimateParent constraint is necessary since
+        // this may be inside an iframe
+        if($(path, ultimateParent).length === 1) {
+          return path;
+        }
+
+        element = element.parentNode;
+      }
+
+      return null;
+    };
+
+    /**
+    * Return a valid, unique XPath selector for the given element.
+    */
+    self.calculateUniqueXPathExpression = function(element) {
+      var parents = $(element).parents();
+
+      function elementIndex(e) {
+        var siblings = $(e).siblings(e.tagName.toLowerCase());
+        if(siblings.length > 0) {
+          return '[' + ($(e).index() + 1) + ']';
+        } else {
+          return '';
+        }
+      }
+
+      var xpathString = '/' + element.tagName.toLowerCase();
+      if(element.id) {
+        return '/' + xpathString + '[@id="' + element.id + '"]';
+      } else {
+        xpathString += elementIndex(element);
+      }
+
+      for(var i = 0; i < parents.length; i=i+1) {
+        var p = parents[i];
+        var pString = '/' + p.tagName.toLowerCase();
+
+        if(p.id) {
+          return '/' + pString + '[@id="' + p.id + '"]' + xpathString;
+        } else {
+          xpathString = pString + elementIndex(p) + xpathString;
+        }
+      }
+
+      return xpathString;
+    };
+
+    /**
+    * Return a unique CSS or XPath selector, preferring a CSS one.
+    */
+    self.bestSelector = function(element) {
+      return self.calculateUniqueCSSSelector(element) ||
+             self.calculateUniqueXPathExpression(element);
+    };
+
+    /**
+    * Build a Diazo selector element with the appropriate namespace.
+    */
+    self.calculateDiazoSelector = function(element, scope, children) {
+      var selectorType = scope;
+      if(children) {
+        selectorType += '-children';
+      }
+
+      var cssSelector = self.calculateUniqueCSSSelector(element);
+      if(cssSelector) {
+        return 'css:' + selectorType + '="' + cssSelector + '"';
+      } else {
+        var xpathSelector = self.calculateUniqueXPathExpression(element);
+        return selectorType + '="' + xpathSelector + '"';
+      }
+
+    };
+  };
+
+  var Inspector = Base.extend({
+    defaults: {
+      name: 'name',
+      ruleBuilder: null,
+      onsave: function() {},
+      onselect: function() {},
+      showReload: false
+    },
+    init: function() {
+      var self = this;
+      self.enabled = true;
+      self.currentOutline = null;
+      self.activeClass = '_theming-highlighted';
+      self.saved = null;
+      self.ruleBuilder = self.options.ruleBuilder;
+
+      self.$el.html(inspectorTemplate(self.options));
+      self.$on = $('.turnon', self.$el);
+      self.$off = $('.turnoff', self.$el);
+      self.$frame = $('iframe', self.$el);
+      self.$frameInfo = $('.frame-info', self.$el);
+      self.$frameShelfContainer = $('.frame-shelf-container', self.$el);
+      self.$selectorInfo = $('.selector-info', self.$frameShelfContainer);
+      self.$currentSelector = $('.current-selector', self.$frameInfo);
+
+      self.$reloadBtn = $('a.refresh', self.$el);
+
+      $('a.clear', self.$frameShelfContainer).click(function(e) {
+        e.preventDefault();
+        self.save(null);
+      });
+      self.$reloadBtn.click(function(e) {
+        e.preventDefault();
+        self.$frame.attr('src', self.$frame.attr('src'));
+        self.setupFrame();
+      });
+      $('a.fullscreen', self.$el).click(function(e){
+        e.preventDefault();
+        if (self.$el.hasClass('show-fullscreen')){
+          self.$el.removeClass('show-fullscreen');
+        } else {
+          self.$el.addClass('show-fullscreen');
+        }
+      });
+
+      if (!self.options.showReload){
+        self.$reloadBtn.hide();
+      }
+
+      self.$on.click(function() {
+        self.on();
+      });
+      self.$off.click(function() {
+        self.off();
+      });
+
+      self.setupFrame();
+    },
+    on: function() {
+      var self = this;
+      self.$off.attr('disabled', null);
+      self.$on.attr('disabled', 'disabled');
+      self.enabled = true;
+    },
+    off: function() {
+      var self = this;
+      self.$on.attr('disabled', null);
+      self.$off.attr('disabled', 'disabled');
+      self.enabled = false;
+    },
+    setupFrame: function() {
+      var self = this;
+      /* messy way to check if iframe is loaded */
+      var checkit = function() {
+        if (self.$frame.contents().find('body').find('*').length > 0){
+          self._setupFrame();
+        } else {
+          setTimeout(checkit, 100);
+        }
+      };
+      setTimeout(checkit, 200);
+    },
+    _setupFrame: function() {
+      var self = this;
+
+      self.$frame.contents().find('*').hover(function(e) {
+        if(self.enabled) {
+          e.stopPropagation();
+          self.$frame.focus();
+          self.setOutline(this);
+        }
+      }, function() {
+        if($(this).hasClass(self.activeClass)) {
+          self.clearOutline(this);
+        }
+      }).click(function (e) {
+        if(self.enabled) {
+          e.stopPropagation();
+          e.preventDefault();
+
+          self.setOutline(this);
+          self.save(this);
+          return false;
+        }
+        return true;
+      });
+
+      self.$frame.contents().keyup(function(e) {
+        if (!self.enabled){
+          return true;
+        }
+
+        // ESC -> Move selection to parent node
+        if(e.keyCode === 27 && self.currentOutline !== null) {
+          e.stopPropagation();
+          e.preventDefault();
+
+          var parent = self.currentOutline.parentNode;
+          if(parent !== null && parent.tagName !== undefined) {
+            self.setOutline(parent);
+          }
+        }
+
+        // Enter -> Equivalent to clicking on selected node
+        if(e.keyCode === 13 && self.currentOutline !== null) {
+          e.stopPropagation();
+          e.preventDefault();
+
+          self.save(self.currentOutline);
+
+          return false;
+        }
+      });
+    },
+    save: function(element) {
+      var self = this;
+      this.saved = element;
+      if(element === null) {
+        self.$frameShelfContainer.hide();
+      } else {
+        self.$frameShelfContainer.show();
+      }
+
+      self.animateSelector();
+      self.$selectorInfo.text(element === null ? '' : self.ruleBuilder.bestSelector(element));
+
+      this.options.onsave(this, element);
+    },
+    clearOutline: function(element){
+      var self = this;
+      $(element).css('outline', '');
+      $(element).css('cursor', '');
+
+      $(element).removeClass(self.activeClass);
+
+      self.currentOutline = null;
+      self.$currentSelector.text('');
+      self.options.onselect(self, null);
+    },
+    setOutline: function(element) {
+      var self = this;
+      var $el = $(element);
+
+      $el.css('outline', 'solid red 1px');
+      $el.css('cursor', 'crosshair');
+
+      $el.addClass(self.activeClass);
+
+      if(self.currentOutline !== null) {
+        self.clearOutline(self.currentOutline);
+      }
+
+      self.currentOutline = element;
+      self.$currentSelector.text(self.ruleBuilder.bestSelector(element));
+
+      self.options.onselect(self, element);
+    },
+    animateSelector: function(highlightColor, duration) {
+      var self = this;
+      var highlightBg = highlightColor || '#FFFFE3';
+      var animateMs = duration || 750;
+      var originalBg = self.$frameInfo.css('background-color');
+
+      if (!originalBg || originalBg === highlightBg){
+          originalBg = '#FFFFFF'; // default to white
+      }
+
+      self.$frameInfo
+        .css('backgroundColor', highlightBg)
+        .animate({ backgroundColor: originalBg }, animateMs, null, function () {
+          self.$frameInfo.css('backgroundColor', originalBg);
+        });
+    }
+  });
+
+
+  var ThemeMapper = Base.extend({
+    name: 'thememapper',
+    defaults: {
+      filemanagerConfig: {},
+      mockupUrl: null,
+      unthemedUrl: null,
+      helpUrl: null,
+      previewUrl: null
+    },
+    buttonGroup: null,
+    showInspectorsButton: null,
+    buildRuleButton: null,
+    previewThemeButton: null,
+    helpButton: null,
+    hidden: true,
+    fileManager: null,
+    mockupInspector: null,
+    unthemedInspector: null,
+    $fileManager: null,
+    $container: null,
+    $mockupInspector: null,
+    $unthemedInspector: null,
+    init: function() {
+      var self = this;
+      if(typeof(self.options.filemanagerConfig) === 'string'){
+        self.options.filemanagerConfig = $.parseJSON(self.options.filemanagerConfig);
+      }
+      self.$fileManager = $('<div class="pat-filemanager"/>').appendTo(self.$el);
+      self.$container = $('<div class="row"></div>').appendTo(self.$el);
+      self.$mockupInspector = $('<div class="mockup-inspector"/>').appendTo(self.$container);
+      self.$unthemedInspector = $('<div class="unthemed-inspector"/>').appendTo(self.$container);
+
+      // initialize patterns now
+      self.ruleBuilder = new RuleBuilder(function(){
+        debugger; //callback
+      });
+      self.fileManager = new FileManager(self.$fileManager, self.options.filemanagerConfig);
+      self.mockupInspector = new Inspector(self.$mockupInspector, {
+        name: 'HTML mockup',
+        ruleBuilder: self.ruleBuilder,
+        url: self.options.mockupUrl,
+        showReload: true
+      });
+      self.unthemedInspector = new Inspector(self.$unthemedInspector, {
+        name: 'Unthemed content',
+        ruleBuilder: self.ruleBuilder,
+        url: self.options.unthemedUrl
+      });
+      self.setupButtons();
+
+      // initially, let's hide the panels
+      self.hideInspectors();
+    },
+    showInspectors: function(){
+      var self = this;
+      var $parent = self.$mockupInspector.parent();
+      $parent.slideDown();
+      self.hidden = false;
+      self.showInspectorsButton.options.title = 'Hide inspectors';
+      self.showInspectorsButton.applyTemplate();
+      $('html, body').animate({
+        scrollTop: $parent.offset().top - 50
+      }, 1000); 
+    },
+    hideInspectors: function(){
+      var self = this;
+      var $parent = self.$mockupInspector.parent();
+      $parent.slideUp();
+      self.hidden = true;
+      self.showInspectorsButton.options.title = 'Show inspectors';
+      self.showInspectorsButton.applyTemplate();
+    },
+    setupButtons: function(){
+      var self = this;
+      self.showInspectorsButton = new ButtonView({
+        id: 'showinspectors',
+        title: 'Show inspectors',
+        tooltip: 'Show inspector panels',
+        context: 'default'
+      });
+      self.showInspectorsButton.on('button:click', function(){
+        if (self.hidden) {
+          self.showInspectors();
+        } else {
+          self.hideInspectors();
+        }
+      });
+
+      self.buildRuleButton = new ButtonView({
+        id: 'buildrule',
+        title: 'Build rule',
+        tooltip: 'rule building wizard',
+        context: 'default'
+      });
+
+      self.previewThemeButton = new ButtonView({
+        id: 'previewtheme',
+        title: 'Preview theme',
+        tooltip: 'preview theme in a new window',
+        context: 'default'
+      });
+      self.previewThemeButton.on('button:click', function(){
+        window.open(self.options.previewUrl);
+      });
+
+      self.helpButton = new ButtonView({
+        id: 'helpbutton',
+        title: 'Help',
+        tooltip: 'Show help',
+        context: 'default'
+      });
+      self.helpButton.on('button:click', function(){
+        window.open(self.options.helpUrl);
+      });
+
+      self.buttonGroup = new ButtonGroup({
+        items: [
+          self.showInspectorsButton,
+          self.buildRuleButton,
+          self.previewThemeButton,
+          self.helpButton
+        ],
+        id: 'mapper'
+      });
+      $('#toolbar .navbar', self.$el).append(self.buttonGroup.render().el);
+    }
+  });
+
+  return ThemeMapper;
+
+});
\ No newline at end of file
diff --git a/patterns/thememapper/pattern.thememapper.less b/patterns/thememapper/pattern.thememapper.less
new file mode 100644
index 0000000..0259627
--- /dev/null
+++ b/patterns/thememapper/pattern.thememapper.less
@@ -0,0 +1,86 @@
+
+.pat-thememapper{
+    iframe{
+        width: 100%;
+        height: 300px;
+        border: solid #eee 1px;
+        position: relative;
+    }
+
+    iframe.fullscreen-frame {
+        height: 600px;
+    }
+
+    .fullscreen {
+        vertical-align: top;
+        display: inline-block;
+        line-height: 12px;
+        padding: 0 0 0 18px;
+        margin: 4px 0 0 2px;
+        background-image: url('./full.png');
+        background-repeat: no-repeat;
+        text-decoration: none;
+        border-bottom: none !important;
+        color: #333 !important;
+        font-size: 90%;
+        content: none;
+    }
+    .fullscreenActive {
+        background-image: url('./small.png');
+    }
+
+    .refresh {
+        vertical-align: top;
+        display: inline-block;
+        line-height: 12px;
+        padding: 0 0 0 12px;
+        margin: 4px 0 0 5px;
+        background-image: url('./refresh.png');
+        background-repeat: no-repeat;
+        text-decoration: none;
+        border-bottom: none !important;
+        color: #333 !important;
+        font-size: 90%;
+        content: none;
+    }
+
+
+    /* Frame info */
+    .frame-info {
+        background-color: #EEEEEE;
+        height: 1.5em;
+        margin-right: -2px;
+        padding: 0 5px;
+        vertical-align: middle;
+    }
+
+    .frame-shelf {
+        float: right;
+    }
+
+    .clear-shelf {
+        font-size: 85%;
+        vertical-align: top;
+        text-decoration: none;
+        border-bottom: none !important;
+    }
+
+    .panel-toolbar, .frame-shelf-container {
+        float: right;
+    }
+
+    .show-fullscreen .frame-panel {
+        position: fixed;
+        top: 0;
+        left: 0;
+        z-index: 99999999;
+        width: 100%;
+        height: 100%;
+        background-color: white;
+
+        iframe {
+            height: 80%;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/patterns/thememapper/templates/inspector.xml b/patterns/thememapper/templates/inspector.xml
new file mode 100644
index 0000000..a6837dc
--- /dev/null
+++ b/patterns/thememapper/templates/inspector.xml
@@ -0,0 +1,39 @@
+<div class="frame-panel mapper-box col-md-6">
+  <div class="panel-toolbar">
+    <a class="refresh" href="#" title="Refresh mockup. You can also right-click on the mockup to use your browser's native refresh."
+      i18n:attributes="title">
+      <span class="glyphicon glyphicon-refresh"></span>
+    </a>
+    <a class="fullscreen" href="#" title="Toggle fullscreen" i18n:attributes="title">
+      <span class="glyphicon glyphicon-fullscreen"></span>
+    </a>
+  </div>
+
+  <label i18n:translate="heading_theme"><%= name %></label>
+
+  <iframe class="frame" src="<%= url %>"></iframe>
+
+  <div class="frame-info">
+    <div class="frame-shelf-container" style="display:none">
+      <span i18n:translate="theming_mapper_shelf_label">Selected:</span>
+      <span class="selector-info"></span>
+      <a class="clear" href="#clear"
+         title="Clear selection" i18n:attributes="title">x</a>
+    </div>
+    <span class="current-selector"></span>
+  </div>
+
+  <div class="panel-footer">
+    <div class="btn-group">
+      <button class="btn btn-default turnon" disabled="disabled" i18n:translate="">Inspector on</button>
+      <button class="btn btn-default turnoff" i18n:translate="">Inspector off</button>
+    </div>
+  </div>
+
+  <div class="discreet footer-help" i18n:translate="help_highlighter_selection">
+    Hover over an element to see its selector.
+    Left-click or press <em>Enter</em> to save.
+    Press <em>Esc</em> to select parent.
+  </div>
+
+</div>
\ No newline at end of file
diff --git a/patterns/tree/pattern.js b/patterns/tree/pattern.js
index 8b5b765..2897817 100644
--- a/patterns/tree/pattern.js
+++ b/patterns/tree/pattern.js
@@ -1,9 +1,11 @@
 /* Tree pattern.
  *
- * Options: data(jSON): load data structure directly into tree (undefined)
- * dataUrl(jSON): Load data from remote url (undefined) autoOpen(boolean): auto
- * open tree contents (false) dragAndDrop(boolean): node drag and drop support
- * (false) selectable(boolean): if nodes can be selectable (true)
+ * Options:
+ * data(jSON): load data structure directly into tree (undefined)
+ * dataUrl(jSON): Load data from remote url (undefined)
+ * autoOpen(boolean): auto open tree contents (false)
+ * dragAndDrop(boolean): node drag and drop support (false)
+ * selectable(boolean): if nodes can be selectable (true)
  * keyboardSupport(boolean): if keyboard naviation is allowed (true)
  *
  * Documentation: # JSON node data
@@ -36,12 +38,12 @@
  *
  * Example: example-2
  *    <div class="pat-tree"
- *         data-pat-tree="dataUrl:/tests/json/fileTree.json;
+ *         data-pat-tree="dataUrl:/docs/dev/tests/json/fileTree.json;
  *                        autoOpen:true"></div>
  *
  * Example: example-3
  *    <div class="pat-tree"
- *         data-pat-tree="dataUrl:/tests/json/fileTree.json;
+ *         data-pat-tree="dataUrl:/docs/dev/tests/json/fileTree.json;
  *                        dragAndDrop: true;
  *                        autoOpen: true"></div>
  *
@@ -77,7 +79,8 @@ define([
       dragAndDrop: false,
       autoOpen: false,
       selectable: true,
-      keyboardSupport: true
+      keyboardSupport: true,
+      onLoad: null
     },
     init: function() {
       var self = this;
@@ -99,11 +102,22 @@ define([
       if (self.options.data && typeof(self.options.data) === 'string') {
         self.options.data = $.parseJSON(self.options.data);
       }
-      self.tree = self.$el.tree(self.options);
+      if (self.options.onLoad !== null){
+        // delay generating tree...
+        var options = $.extend({}, self.options);
+        $.getJSON(options.dataUrl, function(data) {
+          options.data = data;
+          delete options.dataUrl;
+          self.tree = self.$el.tree(options);
+          self.options.onLoad(self);
+        });
+      } else {
+        self.tree = self.$el.tree(self.options);
+      }
     }
   });
 
 
   return Tree;
 
-});
+});
\ No newline at end of file
diff --git a/patterns/upload/pattern.js b/patterns/upload/pattern.js
index 785d096..c762b4e 100644
--- a/patterns/upload/pattern.js
+++ b/patterns/upload/pattern.js
@@ -144,7 +144,7 @@ define([
         self.$pathInput = $('input[name="location"]', self.$el);
         self.relatedItems = self.setupRelatedItems(self.$pathInput);
       } else {
-        $('input[name="location"]', self.$el).remove();
+        $('input[name="location"]', self.$el).parent().remove();
         self.relatedItems = null;
       }
 
diff --git a/tests/fakeserver.js b/tests/fakeserver.js
index 84b3354..95764de 100644
--- a/tests/fakeserver.js
+++ b/tests/fakeserver.js
@@ -28,8 +28,8 @@ define([
     //whenever the this returns true the request will not faked
     return url.indexOf('tests/json/') !== -1 ||
            url.indexOf('ace/lib') !== -1 ||
-           url.indexOf('.xml') !== -1 ||
-           /.*\.js$/i.test(url);
+           /(?![filemanager])\..*\.xml$/i.test(url) ||
+           /(?![filemanager])\..*\.js$/i.test(url);
   });
   server.autoRespond = true;
   server.autoRespondAfter = 200;
@@ -552,6 +552,91 @@ define([
     xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
   });
 
+  server.respondWith('POST', /filemanager-actions/, function(xhr, id) {
+    xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({}));
+  });
+
+  server.respondWith('GET', /filemanager-actions/, function(xhr, id) {
+    server.autoRespondAfter = 200;
+    var action = getQueryVariable(xhr.url, 'action');
+    var data;
+
+    if (action === 'dataTree'){
+      data = [{
+        label: 'css',
+        folder: true,
+        children: [{
+          id: 1,
+          label: 'style.css',
+          folder: false
+        },{
+          id: 2,
+          label: 'tree.css',
+          folder: false
+        }]
+      },{
+        label: 'js',
+        folder: true,
+        children: [{
+          id: 3,
+          label: 'jquery.js',
+          folder: false
+        },{
+          id: 4,
+          label: 'tree.js',
+          folder: false
+        }]
+      },{
+        id: 5,
+        label: 'index.html',
+        folder: false
+      },{
+        id: 6,
+        label: 'rules.xml',
+        folder: false
+      }];
+      xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+    } else if (action === 'getFile'){
+
+      var path = getQueryVariable(xhr.url, 'path');
+      var extension = path.substr( path.lastIndexOf('.') + 1 );
+      data = '';
+
+      if (extension === 'js'){
+        data = 'var foo = function() { \n\talert("Hi!"); \n};';
+      } else if (extension === 'css'){
+        data = '#content.highlight { \n\tbackground-color: #D1F03A; \n}';
+      } else if (extension === 'html'){
+        data = '<html>\n\t<body>\n\t\t<p>Hi!</p>\n\t</body>\n</html>';
+      } else if (extension === 'xml'){
+        data = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+          '<rules\n' +
+            'xmlns="http://namespaces.plone.org/diazo"\n' +
+            'xmlns:css="http://namespaces.plone.org/diazo/css"\n' +
+            'xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\n' +
+            '<theme href="theme.html" />\n' +
+            '<replace css:theme="html head title" css:content="html head title" />\n' +
+            '<replace css:content-children="#content" css:theme-children="#content" />\n' +
+          '</rules>';
+      } else {
+        data = 'foobar';
+      }
+      xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+        path: path,
+        data: data
+      }));
+    }
+  });
+
+  server.respondWith('GET', /search-resources/, function(xhr, id) {
+    server.autoRespondAfter = 200;
+    xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify([{
+      id: 'plone.app.layout.viewlets.title.pt'
+    }, {
+      id: 'plonetheme.sunburst.resources.logo.png'
+    }]));
+  });
+
   return server;
 
-});
+});
\ No newline at end of file
diff --git a/tests/files/mapper.html b/tests/files/mapper.html
new file mode 100644
index 0000000..6e5c514
--- /dev/null
+++ b/tests/files/mapper.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>Starter Template for Bootstrap</title>
+
+    <!-- Bootstrap core CSS -->
+    <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- Custom styles for this template -->
+    <link href="http://getbootstrap.com/examples/starter-template/starter-template.css" rel="stylesheet">
+
+  </head>
+
+  <body>
+
+    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+      <div class="container">
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="#">Project name</a>
+        </div>
+        <div class="collapse navbar-collapse">
+          <ul class="nav navbar-nav">
+            <li class="active"><a href="#">Home</a></li>
+            <li><a href="#about">About</a></li>
+            <li><a href="#contact">Contact</a></li>
+          </ul>
+        </div><!--/.nav-collapse -->
+      </div>
+    </div>
+
+    <div class="container">
+
+      <div class="starter-template">
+        <h1>Bootstrap starter template</h1>
+        <p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
+      </div>
+
+    </div><!-- /.container -->
+
+
+  </body>
+</html>
\ No newline at end of file
diff --git a/tests/pattern-filemanager-test.js b/tests/pattern-filemanager-test.js
new file mode 100644
index 0000000..f40002f
--- /dev/null
+++ b/tests/pattern-filemanager-test.js
@@ -0,0 +1,46 @@
+define([
+  'sinon',
+  'expect',
+  'jquery',
+  'mockup-registry',
+  'mockup-patterns-filemanager'
+], function(sinon, expect, $, registry, Tree) {
+  'use strict';
+
+  window.mocha.setup('bdd');
+  $.fx.off = true;
+
+  describe('File Manager', function() {
+    it('loads the file manager', function() {
+      this.$el = $('' +
+        '<div class="pat-filemanager" ' +
+             'data-pat-filemanager="actionUrl:/filemanager-actions;' +
+                                 ' ">' +
+        '</div>').appendTo('body');
+
+      this.server = sinon.fakeServer.create();
+      this.server.autoRespond = true;
+      this.clock = sinon.useFakeTimers();
+
+      this.server.respondWith('GET', /filemanager-actions/, function (xhr, id) {
+        var data = [{
+          label: 'css',
+          folder: true,
+          children: [{
+            label: 'style.css',
+            folder: false
+          },{
+            label: 'tree.css',
+            folder: false
+          }]
+        }];
+        xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+      });
+
+      registry.scan(this.$el);
+      this.clock.tick(1000);
+      expect(this.$el.find('.tree ul').length).to.be.equal(2);
+    });
+  });
+
+});
\ No newline at end of file




-------------------------------------------------------------------------------
-------------- next part --------------
A non-text attachment was scrubbed...
Name: CHANGES.log
Type: application/octet-stream
Size: 82266 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140810/134cd208/attachment-0002.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: build.log
Type: application/octet-stream
Size: 93899 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140810/134cd208/attachment-0003.obj>


More information about the Testbot mailing list