From cc0d45c6786c11a701213a047df1d96eeff3609e Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 23 Jun 2014 17:25:45 +0100 Subject: [PATCH] Add right click to file tree --- .../app/views/project/editor/file-tree.jade | 177 ++++++++++++------ services/web/public/coffee/app/base.coffee | 2 + .../public/js/libs/ng-context-menu-0.1.4.js | 108 +++++++++++ .../web/public/stylesheets/app/editor.less | 5 + 4 files changed, 235 insertions(+), 57 deletions(-) create mode 100644 services/web/public/js/libs/ng-context-menu-0.1.4.js diff --git a/services/web/app/views/project/editor/file-tree.jade b/services/web/app/views/project/editor/file-tree.jade index dcfe426576..3c7860a296 100644 --- a/services/web/app/views/project/editor/file-tree.jade +++ b/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'" diff --git a/services/web/public/coffee/app/base.coffee b/services/web/public/coffee/app/base.coffee index 5a46b28f30..704b6c9285 100644 --- a/services/web/public/coffee/app/base.coffee +++ b/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 \ No newline at end of file diff --git a/services/web/public/js/libs/ng-context-menu-0.1.4.js b/services/web/public/js/libs/ng-context-menu-0.1.4.js new file mode 100644 index 0000000000..a072ba0e27 --- /dev/null +++ b/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); + }); + } + }; + }]); diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 4bf4f7315e..3048a1af01 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -140,6 +140,11 @@ padding: 0 12px; } } + + .context-menu { + position: fixed; + z-index: 100; + } } }