James Allen 12 лет назад
Родитель
Сommit
cc0d45c678

+ 120 - 57
services/web/app/views/project/editor/file-tree.jade

@@ -56,6 +56,9 @@ script(type='text/ng-template', id='entityListItemTemplate')
 				ng-click="select()"
 				ng-dblclick="startRenaming()"
 				draggable
+				context-menu
+				data-target="context-menu-{{ entity.id }}"
+				context-menu-container="body"
 			)
 				//- Just a spacer to align with folders
 				i.fa.fa-fw.toggle(ng-if="entity.type != 'folder'")
@@ -92,6 +95,23 @@ script(type='text/ng-template', id='entityListItemTemplate')
 								ng-click="openDeleteModal()"
 							) Delete
 
+				div.dropdown.context-menu(id="context-menu-{{ entity.id }}")
+					ul.dropdown-menu
+						li
+							a(
+								href="#"
+								prevent-default="click"
+								stop-propagation="click"
+								ng-click="startRenaming()"
+							) Rename
+						li
+							a(
+								href="#"
+								prevent-default="click"
+								stop-propagation="click"
+								ng-click="openDeleteModal()"
+							) Delete
+
 
 		.entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController")
 			.entity-name(
@@ -101,69 +121,112 @@ script(type='text/ng-template', id='entityListItemTemplate')
 				droppable
 				on-drop-callback="onDrop"
 			)
-				i.fa.fa-fw.toggle(
-					ng-if="entity.type == 'folder'"
-					ng-class="{'fa-chevron-right': !expanded, 'fa-chevron-down': expanded}"
-					ng-click="toggleExpanded()"
+				div(
+					context-menu
+					data-target="context-menu-{{ entity.id }}"
+					context-menu-container="body"
 				)
+					i.fa.fa-fw.toggle(
+						ng-if="entity.type == 'folder'"
+						ng-class="{'fa-chevron-right': !expanded, 'fa-chevron-down': expanded}"
+						ng-click="toggleExpanded()"
+					)
 
-				i.fa.fa-fw(
-					ng-if="entity.type == 'folder'"
-					ng-class="{\
-						'fa-folder': !expanded, \
-						'fa-folder-open': expanded \
-					}"
-					ng-click="select()"
-				)
+					i.fa.fa-fw(
+						ng-if="entity.type == 'folder'"
+						ng-class="{\
+							'fa-folder': !expanded, \
+							'fa-folder-open': expanded \
+						}"
+						ng-click="select()"
+					)
 
-				span(
-					ng-hide="entity.renaming"
-				)  {{ entity.name }}
-				input(
-					ng-show="entity.renaming",
-					ng-model="inputs.name",
-					ng-blur="finishRenaming()",
-					select-name-when="entity.renaming",
-					on-enter="finishRenaming()"
-				)
+					span(
+						ng-hide="entity.renaming"
+					)  {{ entity.name }}
+					input(
+						ng-show="entity.renaming",
+						ng-model="inputs.name",
+						ng-blur="finishRenaming()",
+						select-name-when="entity.renaming",
+						on-enter="finishRenaming()"
+					)
 
-				span.dropdown(ng-show="entity.selected")
-					a.dropdown-toggle(href)
-						i.fa.fa-chevron-down
+					span.dropdown(ng-show="entity.selected")
+						a.dropdown-toggle(href)
+							i.fa.fa-chevron-down
 
-					ul.dropdown-menu.dropdown-menu-right
-						li
-							a(
-								href="#"
-								prevent-default="click"
-								ng-click="startRenaming()"
-							) Rename
-						li
-							a(
-								href="#"
-								prevent-default="click"
-								ng-click="openDeleteModal()"
-							) Delete
-						li.divider
-						li
-							a(
-								href="#"
-								prevent-default="click"
-								ng-click="openNewDocModal()"
-							) New File
-						li
-							a(
-								href="#"
-								prevent-default="click"
-								ng-click="openNewFolderModal()"
-							) New Folder
-						li
-							a(
-								href="#"
-								prevent-default="click"
-								ng-click="openUploadFileModal()"
-							) Upload File
+						ul.dropdown-menu.dropdown-menu-right
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									ng-click="startRenaming()"
+								) Rename
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									ng-click="openDeleteModal()"
+								) Delete
+							li.divider
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									ng-click="openNewDocModal()"
+								) New File
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									ng-click="openNewFolderModal()"
+								) New Folder
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									ng-click="openUploadFileModal()"
+								) Upload File
 
+					.dropdown.context-menu(id="context-menu-{{ entity.id }}")
+						ul.dropdown-menu
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									stop-propagation="click"
+									ng-click="startRenaming()"
+								) Rename
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									stop-propagation="click"
+									ng-click="openDeleteModal()"
+								) Delete
+							li.divider
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									stop-propagation="click"
+									ng-click="openNewDocModal()"
+								) New File
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									stop-propagation="click"
+									ng-click="openNewFolderModal()"
+								) New Folder
+							li
+								a(
+									href="#"
+									prevent-default="click"
+									stop-propagation="click"
+									ng-click="openUploadFileModal()"
+								) Upload File
 
 			ul.list-unstyled(
 				ng-if="entity.type == 'folder'"

+ 2 - 0
services/web/public/coffee/app/base.coffee

@@ -2,11 +2,13 @@ define [
 	"../libs/angular-autocomplete/angular-autocomplete"
 	"../libs/ui-bootstrap"
 	"modules/recursionHelper"
+	"../libs/ng-context-menu-0.1.4"
 ], () ->
 	App = angular.module("SharelatexApp", [
 		"ui.bootstrap"
 		"autocomplete"
 		"RecursionHelper"
+		"ng-context-menu"
 	])
 
 	return App

+ 108 - 0
services/web/public/js/libs/ng-context-menu-0.1.4.js

@@ -0,0 +1,108 @@
+/**
+ * ng-context-menu - v0.1.4 - An AngularJS directive to display a context menu when a right-click event is triggered
+ *
+ * @author Ian Kennington Walter (http://ianvonwalter.com)
+ */
+angular
+  .module('ng-context-menu', [])
+  .factory('ContextMenuService', function() {
+    return {
+      element: null,
+      menuElement: null,
+      container: null
+    };
+  })
+  .directive('contextMenu', ['$document', 'ContextMenuService', function($document, ContextMenuService) {
+    return {
+      restrict: 'A',
+      scope: {
+        'callback': '&contextMenu',
+        'disabled': '&contextMenuDisabled'
+      },
+      link: function($scope, $element, $attrs) {
+        var opened = false;
+
+        function open(event, menuElement, container) {
+          menuElement.addClass('open');
+
+          if (container) {
+            container.append(menuElement);
+          }
+
+          var doc = $document[0].documentElement;
+          var docLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
+            docTop = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0),
+            elementHeight = menuElement[0].scrollHeight;
+          var docHeight = doc.clientHeight + docTop,
+            totalHeight = elementHeight + event.pageY,
+            top = Math.max(event.pageY - docTop, 0);
+
+          if (totalHeight > docHeight) {
+            top = top - (totalHeight - docHeight);
+          }
+
+          menuElement.css('top', top + 'px');
+          menuElement.css('left', Math.max(event.pageX - docLeft, 0) + 'px');
+          opened = true;
+        }
+
+        function close(menuElement) {
+          menuElement.removeClass('open');
+          opened = false;
+        }
+
+        $element.bind('contextmenu', function(event) {
+          if (!$scope.disabled()) {
+            if (ContextMenuService.menuElement !== null) {
+              close(ContextMenuService.menuElement);
+            }
+            ContextMenuService.menuElement = angular.element(document.getElementById($attrs.target));
+            if (typeof($attrs.contextMenuContainer) != "undefined") {
+              ContextMenuService.container = angular.element($attrs.contextMenuContainer)
+            }
+            ContextMenuService.element = event.target;
+            console.log('set', ContextMenuService.element);
+
+            event.preventDefault();
+            event.stopPropagation();
+            $scope.$apply(function() {
+              $scope.callback({ $event: event });
+              open(event, ContextMenuService.menuElement, ContextMenuService.container);
+            });
+          }
+        });
+
+        function handleKeyUpEvent(event) {
+          //console.log('keyup');
+          if (!$scope.disabled() && opened && event.keyCode === 27) {
+            $scope.$apply(function() {
+              close(ContextMenuService.menuElement);
+            });
+          }
+        }
+
+        function handleClickEvent(event) {
+          if (!$scope.disabled() &&
+            opened &&
+            (event.button !== 2 || event.target !== ContextMenuService.element)) {
+            $scope.$apply(function() {
+              close(ContextMenuService.menuElement);
+            });
+          }
+        }
+
+        $document.bind('keyup', handleKeyUpEvent);
+        // Firefox treats a right-click as a click and a contextmenu event while other browsers
+        // just treat it as a contextmenu event
+        $document.bind('click', handleClickEvent);
+        $document.bind('contextmenu', handleClickEvent);
+
+        $scope.$on('$destroy', function() {
+          //console.log('destroy');
+          $document.unbind('keyup', handleKeyUpEvent);
+          $document.unbind('click', handleClickEvent);
+          $document.unbind('contextmenu', handleClickEvent);
+        });
+      }
+    };
+  }]);

+ 5 - 0
services/web/public/stylesheets/app/editor.less

@@ -140,6 +140,11 @@
 					padding: 0 12px;
 				}
 			}
+
+			.context-menu {
+				position: fixed;
+				z-index: 100;
+			}
 		}
 	}