mockup/master: Merge pull request #773 from plone/theme-filemanager-searchi

GitHub <jenkins-z4DKO/[email protected]>
Newsgroups gmane.comp.web.zope.plone.cvs
Message-ID <[email protected]>
Repository: mockup
Branch: refs/heads/master
Date: 2017-07-14T00:11:39+03:00
Author: Asko Soukka (datakurre) <[email protected]>
Commit: https://github.com/plone/mockup/commit/413bc2b66ef07d28d1a850cd61853397aacb5c22

Merge pull request #773 from plone/theme-filemanager-searching-feature

Add search menu to search files by name and contents in filemanager pattern

Files changed:
A mockup/js/ui/templates/dropdown.xml
A mockup/js/ui/views/anchor.js
A mockup/js/ui/views/dropdown.js
A mockup/patterns/filemanager/js/findfile.js
A mockup/patterns/filemanager/js/findinfiles.js
A mockup/patterns/filemanager/templates/popover.xml
M .gitignore
M CHANGES.rst
M mockup/js/bundles/filemanager.js
M mockup/patterns/filemanager/js/basepopover.js
M mockup/patterns/filemanager/pattern.filemanager.less
M mockup/patterns/filemanager/pattern.js
M mockup/patterns/filemanager/templates/app.xml
M mockup/patterns/thememapper/pattern.thememapper.less
M mockup/patterns/tree/pattern.js

diff --git a/.gitignore b/.gitignore
index 687ae184..69d17555 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.orig
 *.egg-info
 *.py?
 .idea
diff --git a/CHANGES.rst b/CHANGES.rst
index acb27972..a5f16fbb 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,6 +23,17 @@ Bug fixes:
 
 New features:
 
+ - Improve the user experience for the theme editor
+  - Search for files and text within files and opening the file upon click.
+  - Add Bootstrap Dropdown menu to the UI views.
+  - Enable Drag and Drop inside of the theme editor file tree.
+  - Fixed the Drag and Drop files to root of the file tree.
+  - Fixed the autoscrolling issue when dragging a file in the file tree.
+  - Add Anchor (a) tag to the UI views.
+  - Upgrade JQTree to 1.4.1
+  - Enable Drag and Drop inside of the theme editor file tree.
+  [b4oshany]
+
  - Make thumb scale in folder contents listing adjustable/supressable.
    Replace meaningless paper clip icon (fontello) with mime type icons from mimetype registry.
    https://github.com/plone/Products.CMFPlone/issues/1734
diff --git a/mockup/js/bundles/filemanager.js b/mockup/js/bundles/filemanager.js
index 18c3b196..84778497 100644
--- a/mockup/js/bundles/filemanager.js
+++ b/mockup/js/bundles/filemanager.js
@@ -1,6 +1,6 @@
 define([
   'pat-registry',
-  'mockup-patterns-filemanager'
+  'mockup-patterns-filemanager',
 ], function(registry) {
   'use strict';
   // initialize only if we are in top frame
diff --git a/mockup/js/ui/templates/dropdown.xml b/mockup/js/ui/templates/dropdown.xml
new file mode 100644
index 00000000..34c20d25
--- /dev/null
+++ b/mockup/js/ui/templates/dropdown.xml
@@ -0,0 +1,10 @@
+
+<a href="#" class="btn btn-default dropdown-toggle" type="button" id="dropdown-menu-" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+  <span class="glyphicon glyphicon-search"></span>
+  <span class="dropdown-title">
+
+  </span>
+  <span class="caret"></span>
+</a>
+<ul class="dropdown-menu items dropdown-content" aria-labelledby="dropdown-menu-">
+</ul>
diff --git a/mockup/js/ui/views/anchor.js b/mockup/js/ui/views/anchor.js
new file mode 100644
index 00000000..9e0f8e4a
--- /dev/null
+++ b/mockup/js/ui/views/anchor.js
@@ -0,0 +1,56 @@
+define([
+  'underscore',
+  'mockup-ui-url/views/base',
+  'mockup-patterns-tooltip'
+], function(_, BaseView, Tooltip) {
+  'use strict';
+
+  var AnchorView = BaseView.extend({
+    tagName: 'a',
+    className: 'alink',
+    eventPrefix: 'button',
+    context: 'default',
+    idPrefix: 'alink-',
+    attributes: {
+      'href': '#'
+    },
+    extraClasses: [],
+    tooltip: null,
+    template: '<% if (icon) { %><span class="glyphicon glyphicon-<%= icon %>"></span><% } %> <%= title %>',
+    events: {
+      'click': 'handleClick'
+    },
+    initialize: function(options) {
+      if (!options.id) {
+        var title = options.title || '';
+        options.id = title !== '' ? title.toLowerCase().replace(' ', '-') : this.cid;
+      }
+      BaseView.prototype.initialize.apply(this, [options]);
+
+      this.on('render', function() {
+        this.$el.attr('title', this.options.title || '');
+        this.$el.attr('aria-label', this.options.title || this.options.tooltip || '');
+        _.each(this.extraClasses, function(klass) {
+          this.$el.addClass(klass);
+        });
+      }, this);
+    },
+    handleClick: function(e) {
+      e.preventDefault();
+      if (!this.$el.prop('disabled')) {
+        this.uiEventTrigger('click', this, e);
+      }
+    },
+    serializedModel: function() {
+      return _.extend({'icon': '', 'title': ''}, this.options);
+    },
+    disable: function() {
+      this.$el.prop('disabled', true);
+    },
+    enable: function() {
+      this.$el.prop('disabled', false);
+    }
+  });
+
+  return AnchorView;
+});
diff --git a/mockup/js/ui/views/dropdown.js b/mockup/js/ui/views/dropdown.js
new file mode 100644
index 00000000..90eb79f9
--- /dev/null
+++ b/mockup/js/ui/views/dropdown.js
@@ -0,0 +1,59 @@
+define([
+  'jquery',
+  'underscore',
+  'mockup-ui-url/views/buttongroup',
+  'text!mockup-ui-url/templates/dropdown.xml',
+], function($, _, ButtonGroup, DropdownTemplate) {
+  'use strict';
+
+  var DropdownView = ButtonGroup.extend({
+    idPrefix: 'btngroup-dropdown-',
+    template: DropdownTemplate,
+    className: 'btn-group-dropdown',
+    itemContainer: "ul.dropdown-content",
+    title: null,
+
+    initialize: function(options) {
+      ButtonGroup.prototype.initialize.apply(this, [options]);
+
+      this.on('render', function() {
+        this.renderTitle();
+      }, this);
+    },
+
+    renderTitle: function() {
+      var title = this.options.title;
+      if(this.options.title === undefined) {
+        title = this.title;
+        if(this.title === null) {
+          title = "Menu Option";
+        }
+      }
+      this.$('.dropdown-title').empty().append(title);
+    },
+
+    renderItems: function() {
+      var self = this;
+      var $container;
+
+      if (this.itemContainer !== null) {
+        $container = $(this.itemContainer, this.$el);
+        if ($container.length === 0) {
+          throw 'Item Container element not found.';
+        }
+      } else {
+        $container = this.$el;
+      }
+
+      var $item = null;
+      _.each(this.items, function(view) {
+        $item = $("<li></li>");
+        $item.append(view.render().$el.removeClass("btn"));
+        $container.append($item);
+      }, this);
+    },
+
+  });
+
+  return DropdownView;
+});
diff --git a/mockup/patterns/filemanager/js/basepopover.js b/mockup/patterns/filemanager/js/basepopover.js
index fee2ab53..301002b3 100644
--- a/mockup/patterns/filemanager/js/basepopover.js
+++ b/mockup/patterns/filemanager/js/basepopover.js
@@ -1,22 +1,53 @@
 define([
+  'jquery',
   'underscore',
-  'mockup-ui-url/views/popover'
-], function(_, PopoverView) {
+  'mockup-ui-url/views/popover',
+  'text!mockup-patterns-filemanager-url/templates/popover.xml',
+], function($, _, PopoverView, PopoverTemplate) {
   'use strict';
 
   var FileManagerPopover = PopoverView.extend({
     className: 'popover',
     title: _.template('nothing'),
     content: _.template('<div/>'),
+    template: PopoverTemplate,
     initialize: function(options) {
       this.app = options.app;
       PopoverView.prototype.initialize.apply(this, [options]);
     },
+    afterRender: function () {
+      var self = this;
+      self.$el.find(".popover-close").click(function(e){
+        self.hide(true);
+      });
+      return self;
+    },
+    getBodyClassName: function(){
+      var name = 'popover-';
+      if(this.options.id){
+        name += this.options.id + '-';
+      }
+      name += 'active';
+      return name;
+    },
     render: function() {
       var self = this;
       PopoverView.prototype.render.call(this);
       return self;
     },
+    hide: function(closePopover) {
+      if(this.closeOnOutClick || closePopover == true){
+        this.opened = false;
+        this.$el.removeClass('active');
+        if (this.triggerView) {
+          this.triggerView.$el.removeClass('active');
+          this.triggerView.$el.attr('aria-hidden', 'true');
+        }
+        this.uiEventTrigger('hide', this);
+        this.$el.attr('aria-hidden', 'true');
+        $('body').removeClass(this.getBodyClassName());
+      }
+    },
     toggle: function(button, e) {
       PopoverView.prototype.toggle.apply(this, [button, e]);
       var self = this;
diff --git a/mockup/patterns/filemanager/js/findfile.js b/mockup/patterns/filemanager/js/findfile.js
new file mode 100644
index 00000000..d4c0c3dd
--- /dev/null
+++ b/mockup/patterns/filemanager/js/findfile.js
@@ -0,0 +1,101 @@
+define([
+  'jquery',
+  'underscore',
+  'mockup-patterns-filemanager-url/js/basepopover',
+  'translate'
+], function($, _, PopoverView, _t) {
+  'use strict';
+
+  var FindFile = PopoverView.extend({
+    className: 'popover filesearch',
+    closeOnOutClick: false,
+    backdropOptions: {
+      zIndex: '1009',
+      opacity: '0.4',
+      className: 'backdrop backdrop-popover',
+      classActiveName: 'backdrop-active',
+      closeOnEsc: false,
+      closeOnClick: false
+    },
+    title: _.template('<%= _t("Find File") %>'),
+    content: _.template(
+      '<form>' +
+        '<div class="input-group">' +
+          '<input type="text" class="search form-control" ' +
+                  'id="file-search-field" placeholder="<%= _t("Find theme resource in plone") %>">' +
+        '</div>' +
+        '<div class="input-group">' +
+          '<input type="submit" class="btn btn-primary" value="<%= _t("Search") %>"/>' +
+        '</div>' +
+      '</form><br/>' +
+      '<ul class="results list-group">' +
+      '</ul>'
+    ),
+    appendToResults: function(item){
+      var self = this;
+      var $item = $(
+        '<li class="list-group-item">' +
+          '<span class="badge"><a data-target="' + item.path + '" href=#">' +
+          _t(item.filename) + '</a></span>' +
+        '</li>');
+      $('a', $item).click(function(e) {
+        e.preventDefault();
+        self.findfile($(this).attr('data-target'));
+      });
+      self.$results.append($item);
+    },
+    filterFiles: function(patt, data){
+      var self = this;
+      _.each(data, function(item) {
+        if(item.folder){
+          self.filterFiles(patt, item.children);
+        }else{
+          if(patt.test(item.filename)){
+            self.appendToResults(item);
+            self.noMatches++;
+          }
+        }
+      });
+    },
+    render: function() {
+      var self = this;
+      PopoverView.prototype.render.call(this);
+      self.$form = self.$('form');
+      self.$searchFor = self.$("input");
+      self.$results = self.$('.results');
+      self.$form.submit(function(e) {
+        e.preventDefault();
+        $.ajax({
+          url: self.app.options.actionUrl + '?action=dataTree',
+          dataType: 'json',
+          success: function(data) {
+            self.$results.empty();
+            self.noMatches = 0;
+            var searchFor = self.$searchFor.val();
+            var patt = new RegExp(searchFor, "g");
+            self.filterFiles(patt, data);
+            if(self.noMatches == 0){
+              self.$results.append("<span>No results found for " + searchFor + "</span>");
+            }
+          }
+        });
+      });
+      return self;
+    },
+    findfile: function(resource) {
+      var self = this;
+      self.app.doAction('getFile', {
+        data: {
+          path: resource
+        },
+        dataType: 'json',
+        success: function(data) {
+          self.app.fileData[resource] = data;
+          self.app.openEditor(resource);
+        }
+      });
+    }
+  });
+
+  return FindFile;
+});
diff --git a/mockup/patterns/filemanager/js/findinfiles.js b/mockup/patterns/filemanager/js/findinfiles.js
new file mode 100644
index 00000000..32b4dd80
--- /dev/null
+++ b/mockup/patterns/filemanager/js/findinfiles.js
@@ -0,0 +1,137 @@
+define([
+  'jquery',
+  'underscore',
+  'mockup-patterns-filemanager-url/js/basepopover',
+  'translate'
+], function($, _, PopoverView, _t) {
+  'use strict';
+
+  var FindInFiles = PopoverView.extend({
+    className: 'popover filesearch',
+    closeOnOutClick: false,
+    backdropOptions: {
+      zIndex: '1009',
+      opacity: '0.4',
+      className: 'backdrop backdrop-popover',
+      classActiveName: 'backdrop-active',
+      closeOnEsc: false,
+      closeOnClick: false
+    },
+    title: _.template('<%= _t("File in File") %>'),
+    content: _.template(
+      '<form>' +
+        '<div class="input-group">' +
+          '<input type="text" class="search form-control" ' +
+                  'id="file-search-field" placeholder="<%= _t("Find text witin theme resource in plone") %>">' +
+        '</div>' +
+        '<div class="input-group">' +
+          '<input type="submit" class="btn btn-primary" value="<%= _t("Search") %>"/>' +
+        '</div>' +
+      '</form><br/>' +
+      '<ul style="max-height: 400px; overflow: auto;" class="results list-group">' +
+      '</ul>'
+    ),
+    appendToResults: function(item){
+      var self = this, seen = null;
+      var file_item =
+        '<li class="list-group-item" data-id="' + item.file.label + '">' +
+          '<span class="badge">' + _t(item.file.filename) + '</span><ul>';
+      for(var x in item.lines){
+        seen = item.lines[x];
+        file_item += '<li class="list-group-item" data-id="' + item.file.label + '">' +
+          '<span class="badge"><a class="ff-open-file" data-target="'+item.file.path+'" ' +
+          'target-line="'+seen.line+'" href="#">Line ' +
+          '<span style="display: inline-block; width: 100px;">' + seen.line +
+          '</span><span>'+seen.text+'</span><a></span></li>';
+      }
+      file_item += '</ul></li>';
+      var $item = $(file_item);
+      $('a', $item).click(function(e) {
+        e.preventDefault();
+        self.findinfiles(
+          $(this).attr("data-target"),
+          parseInt($(this).attr("target-line"))
+        );
+      });
+      self.$results.append($item);
+    },
+
+    filterFile: function(patt, item){
+      var self = this;
+      $.ajax({
+        url: self.app.options.actionUrl + '?action=getFile&path='+item.path.replace("/", "%2F"),
+        dataType: 'json',
+        success: function(data) {
+          var contents = data["contents"];
+          if(contents == undefined){
+            return;
+          }
+          var lines = contents.split("\n");
+          var seen = [], line = '';
+          var result = null;
+          for(var x in lines){
+            line = lines[x];
+            result = patt.exec(line);
+            if(result != null){
+              seen.push({
+                "line": parseInt(x) + 1,
+                "text": '<b>'+result[0]+'</b>'+line.substr(result["index"] + result[0].length, 20)
+              });
+            }
+          }
+          if(seen.length > 0){
+            self.appendToResults({file: item, lines: seen});
+            self.noMatches += seen.length;
+          }
+        }
+      });
+    },
+    filterFiles: function(patt, data){
+      var self = this;
+      _.each(data, function(item) {
+        if(item.folder){
+          self.filterFiles(patt, item.children);
+        }else{
+          self.filterFile(patt, item);
+        }
+      });
+    },
+    render: function() {
+      var self = this;
+      PopoverView.prototype.render.call(this);
+      self.$form = self.$('form');
+      self.$searchFor = self.$("input");
+      self.$results = self.$('.results');
+      self.$form.submit(function(e) {
+        e.preventDefault();
+        $.ajax({
+          url: self.app.options.actionUrl + '?action=dataTree',
+          dataType: 'json',
+          success: function(data) {
+            self.$results.empty();
+            self.noMatches = 0;
+            var searchFor = self.$searchFor.val();
+            var patt = new RegExp(searchFor, "g");
+            self.filterFiles(patt, data);
+          }
+        });
+      });
+      return self;
+    },
+    findinfiles: function(resource, line) {
+      var self = this;
+      self.app.doAction('getFile', {
+        data: {
+          path: resource
+        },
+        dataType: 'json',
+        success: function(data) {
+          self.app.fileData[resource] = data;
+          self.app.openEditor(resource, {goToLine: line});
+        }
+      });
+    }
+  });
+
+  return FindInFiles;
+});
diff --git a/mockup/patterns/filemanager/pattern.filemanager.less b/mockup/patterns/filemanager/pattern.filemanager.less
index b5fd4216..f2049f80 100644
--- a/mockup/patterns/filemanager/pattern.filemanager.less
+++ b/mockup/patterns/filemanager/pattern.filemanager.less
@@ -27,6 +27,11 @@
     .popover:extend(.popover all){}
     .popover-title:extend(.popover-title all){}
     .popover-content:extend(.popover-content all){}
+
+    .popover{
+        max-width: 600px;
+    }
+
     .arrow:extend(.arrow all){}
 
     .table:extend(.table all){}
@@ -38,6 +43,11 @@
     .btn-primary:extend(.btn-primary all){}
     .btn-group:extend(.btn-group all){}
     .btn-success:extend(.btn-success all){}
+    .btn-group-dropdown:extend(.btn-group all){
+        display: inline-block;
+        margin: 0 !important;
+        vertical-align: initial !important;
+    }
     .input-group:extend(.input-group all){}
     .input-group-addon:extend(.input-group-addon all){}
     .input-group-btn:extend(.input-group-btn all){}
@@ -112,6 +122,33 @@
             margin-bottom: 0.5em;
             text-decoration: none;
         }
+        .btn-group > .btn-group-dropdown{
+            float: none;
+            display: inline-block;
+            text-decoration: none;
+
+            & > a.btn {
+                margin-bottom: 0.5em;
+                border-radius: 0;
+                float: none;
+                display: inline-block;
+                text-decoration: none;
+                padding: 5px 5px 8px 5px;
+                line-height: 10px;
+                font-size: 13px;
+            }
+
+            &:last-child > a.btn{
+                border-bottom-right-radius: 4px;
+                border-top-right-radius: 4px;
+            }
+
+            &:first-child > a.btn{
+                border-bottom-left-radius: 4px;
+                border-top-left-radius: 4px;
+            }
+
+        }
     }
     .navbar-default {
         min-height: 250px
diff --git a/mockup/patterns/filemanager/pattern.js b/mockup/patterns/filemanager/pattern.js
index 24a14c54..d6a37f3c 100644
--- a/mockup/patterns/filemanager/pattern.js
+++ b/mockup/patterns/filemanager/pattern.js
@@ -40,17 +40,23 @@ define([
   'mockup-ui-url/views/toolbar',
   'mockup-ui-url/views/button',
   'mockup-ui-url/views/buttongroup',
+  'mockup-ui-url/views/anchor',
+  'mockup-ui-url/views/dropdown',
   'mockup-patterns-filemanager-url/js/addnew',
   'mockup-patterns-filemanager-url/js/newfolder',
+  'mockup-patterns-filemanager-url/js/findfile',
+  'mockup-patterns-filemanager-url/js/findinfiles',
   'mockup-patterns-filemanager-url/js/delete',
   'mockup-patterns-filemanager-url/js/customize',
   'mockup-patterns-filemanager-url/js/rename',
   'mockup-patterns-filemanager-url/js/upload',
   'translate',
   'mockup-utils',
-  'text!mockup-ui-url/templates/popover.xml'
+  'text!mockup-ui-url/templates/popover.xml',
+  'text!mockup-ui-url/templates/dropdown.xml'
 ], function($, Base, _, Tree, TextEditor, AppTemplate, Toolbar,
-  ButtonView, ButtonGroup, AddNewView, NewFolderView, DeleteView,
+  ButtonView, ButtonGroup, AnchorView, DropdownView,
+  AddNewView, NewFolderView, FindFileView, FindInFilesView, DeleteView,
   CustomizeView, RenameView, UploadView, _t, utils) {
   'use strict';
 
@@ -89,13 +95,21 @@ define([
 
       self.options.treeConfig = $.extend(true, {}, self.treeConfig, {
         dataUrl: self.options.actionUrl + '?action=dataTree',
+        dragAndDrop: true,
+        useContextMenu: true,
+        onCanMoveTo: function(moved, target, position) {
+          /* if not using folder option, just allow, otherwise, only allow if folder */
+          if (position === "inside") {
+            return target.folder === undefined || target.folder === true;
+          }
+          return true;
+        },
         onCreateLi: function(node, li) {
           var imageTypes = ['png', 'jpg', 'jpeg', 'gif', 'ico'];
           var themeTypes = ['css', 'html', 'htm', 'txt', 'xml', 'js', 'cfg', 'less'];
-
           $('span', li).addClass('glyphicon');
           if (node.folder) {
-            $('span', li).addClass('glyphicon-folder-close');
+            $('span', li).addClass('glyphicon-folder-close').addClass("droptarget");
           } else if ($.inArray(node.fileType, imageTypes) >= 0) {
             $('span', li).addClass('glyphicon-picture');
           } else if ($.inArray(node.fileType, themeTypes) >= 0) {
@@ -114,51 +128,92 @@ define([
         icon: 'floppy-disk',
         context: 'primary'
       });
-
-      var newFolderView = new NewFolderView({
-        triggerView: new ButtonView({
+      self.btns = {
+        "newfolder": new ButtonView({
           id: 'newfolder',
           title: _t('New folder'),
           tooltip: _t('Add new folder to current directory'),
           icon: 'folder-open',
           context: 'default'
         }),
-        app: self
-      });
-      var addNewView = new AddNewView({
-        triggerView: new ButtonView({
+        "newfile": new ButtonView({
           id: 'addnew',
           title: _t('Add new file'),
           tooltip: _t('Add new file to current folder'),
           icon: 'file',
           context: 'default'
         }),
-        app: self
-      });
-      var renameView = new RenameView({
-        triggerView: new ButtonView({
+        "findfile": new AnchorView({
+          id: 'findfile',
+          title: _t('Find File'),
+          tooltip: _t('Find theme resource in plone'),
+          icon: 'file',
+          context: 'default'
+        }),
+        "findtextinfile": new AnchorView({
+          id: 'findinfiles',
+          title: _t('Find in Files'),
+          tooltip: _t('Find text within theme resource in plone'),
+          icon: 'file',
+          context: 'default'
+        }),
+        "rename": new ButtonView({
           id: 'rename',
           title: _t('Rename'),
           tooltip: _t('Rename currently selected resource'),
           icon: 'random',
           context: 'default'
         }),
-        app: self
-      });
-      var deleteView = new DeleteView({
-        triggerView: new ButtonView({
+        "delete": new ButtonView({
           id: 'delete',
           title: _t('Delete'),
           tooltip: _t('Delete currently selected resource'),
           icon: 'trash',
           context: 'danger'
-        }),
+        })
+      };
+
+      var newFolderView = new NewFolderView({
+        triggerView: self.btns["newfolder"],
+        app: self
+      });
+      var addNewView = new AddNewView({
+        triggerView: self.btns["newfile"],
+        app: self
+      });
+      var findFileView = new FindFileView({
+        triggerView: self.btns["findfile"],
+        app: self
+      });
+      var findinFilesView = new FindInFilesView({
+        triggerView: self.btns["findtextinfile"],
+        app: self
+      });
+      var renameView = new RenameView({
+        triggerView: self.btns["rename"],
+        app: self
+      });
+      var deleteView = new DeleteView({
+        triggerView: self.btns["delete"],
         app: self
       });
 
+      var find_menu = new DropdownView({
+        title: _t('Find'),
+        items: [
+          findFileView.triggerView,
+          findinFilesView.triggerView
+        ],
+        id: 'find',
+        app: self,
+        disable: function() {}
+      })
+
       self.views = [
         newFolderView,
         addNewView,
+        findFileView,
+        findinFilesView,
         renameView,
         deleteView
       ];
@@ -166,6 +221,7 @@ define([
         self.saveBtn,
         newFolderView.triggerView,
         addNewView.triggerView,
+        find_menu,
         renameView.triggerView,
         deleteView.triggerView
       ];
@@ -299,6 +355,30 @@ define([
         }
       });
 
+      self.$tree.bind('tree.move', function(event) {
+        
+        var srcpath = event.move_info.moved_node.path;
+        var newpath = event.move_info.target_node.path;
+        if (event.move_info.position !== "inside" ){
+          newpath = newpath.substring(newpath.indexOf('/'), newpath.lastIndexOf('/'));
+        }
+        
+        self.doAction('move', {
+          data: {
+            source: srcpath,
+            destination: newpath
+          },
+          dataType: 'json',
+          success: function(data) {
+            console.log(data);
+            var jdata = JSON.parse(data);
+            if(jdata.error != ''){
+              alert(jdata.error);
+            }
+          }
+        });
+      });
+
       self.$tree.bind('tree.open', function(e) {
         var element = $(e.node.element).find(':first').find('.glyphicon');
         $(element).addClass('glyphicon-folder-open');
@@ -525,12 +605,15 @@ define([
         failure: options.failure || function() {}
       });
     },
-    openEditor: function(path) {
+    openEditor: function(path, options) {
       var self = this;
 
       if (path !== undefined) {
         self.updateTabs(path);
       }
+      if (options === undefined) {
+        options = {};
+      }
 
       // first we need to save the current editor content
       if (self.currentPath) {
@@ -567,6 +650,10 @@ define([
       }
 
       self.resizeEditor();
+      if(options.goToLine != undefined){
+        self.ace.editor.gotoLine(options.goToLine, 0, true);
+      }
+
       self.$el.trigger('fileChange');
       self.ace.editor.on('change', function() {
         if (self.ace.editor.curOp && self.ace.editor.curOp.command.name) {
diff --git a/mockup/patterns/filemanager/templates/app.xml b/mockup/patterns/filemanager/templates/app.xml
index 11d594ff..e5194500 100644
--- a/mockup/patterns/filemanager/templates/app.xml
+++ b/mockup/patterns/filemanager/templates/app.xml
@@ -1,5 +1,7 @@
 <div id="toolbar">
 </div>
+<div id="toolbar-action">
+</div>
 <div class="container">
     <div class="tree">
     </div>
@@ -14,4 +16,4 @@
                 </div>
             </div>
         </nav>
-</div>
\ No newline at end of file
+</div>
diff --git a/mockup/patterns/filemanager/templates/popover.xml b/mockup/patterns/filemanager/templates/popover.xml
new file mode 100644
index 00000000..a276f070
--- /dev/null
+++ b/mockup/patterns/filemanager/templates/popover.xml
@@ -0,0 +1,12 @@
+<div class="arrow"></div>
+<div class="popover-label">
+<div style="position: relative;" class="popover-title">
+</div>
+<a style="position: absolute; top: 5px; right: 3px; font-size: 16px; color: #999;"
+   href="#" class="popover-close">
+  <span class="glyphicon glyphicon-remove"></span>
+</a>
+</div>
+<div class="items popover-content">
+</div>
+
diff --git a/mockup/patterns/thememapper/pattern.thememapper.less b/mockup/patterns/thememapper/pattern.thememapper.less
index 7666065a..4c58cce6 100644
--- a/mockup/patterns/thememapper/pattern.thememapper.less
+++ b/mockup/patterns/thememapper/pattern.thememapper.less
@@ -1,6 +1,6 @@
 @import "@{mockuplessPath}/ui.less";
 /* just be lazy here and include all of bootstrap... */
-@import "@{mockupPath}/filemanager/pattern.filemanager.less";
+@import "@{mockup-patterns-filemanager}";
 
 body.plone-toolbar-left-default {
 	padding-left: 0px;
diff --git a/mockup/patterns/tree/pattern.js b/mockup/patterns/tree/pattern.js
index 2be19804..3667140c 100644
--- a/mockup/patterns/tree/pattern.js
+++ b/mockup/patterns/tree/pattern.js
@@ -79,11 +79,14 @@ define([
         }
       }
 
-      if (self.options.dragAndDrop && self.options.onCanMoveTo === undefined) {
+      if (self.options.onCanMoveTo === undefined) {
         self.options.onCanMoveTo = function(moved, target, position) {
           /* if not using folder option, just allow, otherwise, only allow if folder */
-          return target.folder === undefined || target.folder === true;
-        };
+          if (position === "inside") {
+            return target.folder === undefined || target.folder === true;
+          }
+          return true;
+        }
       }
 
       if (self.options.data && typeof(self.options.data) === 'string') {



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.