Better click outside handling
This commit is contained in:
@@ -606,7 +606,14 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
component.window = componentWindow;
|
component.window = componentWindow;
|
||||||
component.sessionKey = Neeto.crypto.generateUUID();
|
component.sessionKey = Neeto.crypto.generateUUID();
|
||||||
this.sendMessageToComponent(component, {action: "component-registered", sessionKey: component.sessionKey, componentData: component.componentData});
|
this.sendMessageToComponent(component, {
|
||||||
|
action: "component-registered",
|
||||||
|
sessionKey: component.sessionKey,
|
||||||
|
componentData: component.componentData,
|
||||||
|
data: {
|
||||||
|
environment: isDesktopApplication() ? "desktop" : "web"
|
||||||
|
}
|
||||||
|
});
|
||||||
this.postThemeToComponent(component);
|
this.postThemeToComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,102 @@
|
|||||||
|
// via https://github.com/IamAdamJowett/angular-click-outside
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('app.frontend')
|
.module('app.frontend')
|
||||||
.directive('clickOutside', ['$document', function($document) {
|
.directive('clickOutside', ['$document', '$parse', '$timeout', function($document, $parse, $timeout) {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
replace: false,
|
replace: false,
|
||||||
link : function($scope, $element, attrs) {
|
link: function($scope, elem, attr) {
|
||||||
|
$timeout(() => {
|
||||||
|
// postpone linking to next digest to allow for unique id generation
|
||||||
|
var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.split(/[ ,]+/) : [],
|
||||||
|
fn;
|
||||||
|
|
||||||
var didApplyClickOutside = false;
|
function eventHandler(e) {
|
||||||
|
var i,
|
||||||
|
element,
|
||||||
|
r,
|
||||||
|
id,
|
||||||
|
classNames,
|
||||||
|
l;
|
||||||
|
|
||||||
$element.bind('click', function(e) {
|
// check if our element already hidden and abort if so
|
||||||
didApplyClickOutside = false;
|
if (angular.element(elem).hasClass("ng-hide")) {
|
||||||
if (attrs.isOpen) {
|
return;
|
||||||
e.stopPropagation();
|
}
|
||||||
|
|
||||||
|
// if there is no click target, no point going on
|
||||||
|
if (!e || !e.target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop through the available elements, looking for classes in the class list that might match and so will eat
|
||||||
|
for (element = e.target; element; element = element.parentNode) {
|
||||||
|
// check if the element is the same element the directive is attached to and exit if so (props @CosticaPuntaru)
|
||||||
|
if (element === elem[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have done the initial checks, start gathering id's and classes
|
||||||
|
id = element.id,
|
||||||
|
classNames = element.className,
|
||||||
|
l = classList.length;
|
||||||
|
|
||||||
|
// Unwrap SVGAnimatedString classes
|
||||||
|
if (classNames && classNames.baseVal !== undefined) {
|
||||||
|
classNames = classNames.baseVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no class names on the element clicked, skip the check
|
||||||
|
if (classNames || id) {
|
||||||
|
|
||||||
|
// loop through the elements id's and classnames looking for exceptions
|
||||||
|
for (i = 0; i < l; i++) {
|
||||||
|
//prepare regex for class word matching
|
||||||
|
r = new RegExp('\\b' + classList[i] + '\\b');
|
||||||
|
|
||||||
|
// check for exact matches on id's or classes, but only if they exist in the first place
|
||||||
|
if ((id !== undefined && id === classList[i]) || (classNames && r.test(classNames))) {
|
||||||
|
// now let's exit out as it is an element that has been defined as being ignored for clicking outside
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute
|
||||||
|
$timeout(function() {
|
||||||
|
fn = $parse(attr['clickOutside']);
|
||||||
|
fn($scope, { event: e });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
$document.bind('click', function() {
|
// if the devices has a touchscreen, listen for this event
|
||||||
if(!didApplyClickOutside) {
|
if (_hasTouch()) {
|
||||||
$scope.$apply(attrs.clickOutside);
|
$document.on('touchstart', eventHandler);
|
||||||
didApplyClickOutside = true;
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
}
|
// still listen for the click event even if there is touch to cater for touchscreen laptops
|
||||||
|
$document.on('click', eventHandler);
|
||||||
|
|
||||||
|
// when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around
|
||||||
|
$scope.$on('$destroy', function() {
|
||||||
|
if (_hasTouch()) {
|
||||||
|
$document.off('touchstart', eventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
$document.off('click', eventHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Private function to attempt to figure out if we are on a touch device
|
||||||
|
* @private
|
||||||
|
**/
|
||||||
|
function _hasTouch() {
|
||||||
|
// works on most browsers, IE10/11 and Surface
|
||||||
|
return 'ontouchstart' in window || navigator.maxTouchPoints;
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}]);
|
}
|
||||||
|
}]);
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class ComponentModal {
|
|||||||
if(component.active) {
|
if(component.active) {
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
var iframe = componentManager.iframeForComponent(component);
|
var iframe = componentManager.iframeForComponent(component);
|
||||||
console.log("iframe", iframe, component);
|
|
||||||
if(iframe) {
|
if(iframe) {
|
||||||
iframe.onload = function() {
|
iframe.onload = function() {
|
||||||
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
||||||
@@ -44,7 +43,6 @@ class ComponentModal {
|
|||||||
},
|
},
|
||||||
actionHandler: function(component, action, data) {
|
actionHandler: function(component, action, data) {
|
||||||
if(action == "set-size") {
|
if(action == "set-size") {
|
||||||
console.log("componentModalReceivedAction SetSize", component);
|
|
||||||
componentManager.handleSetSizeEvent(component, data);
|
componentManager.handleSetSizeEvent(component, data);
|
||||||
}
|
}
|
||||||
}.bind(this)});
|
}.bind(this)});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class EditorMenu {
|
|||||||
if(editor) {
|
if(editor) {
|
||||||
editor.conflict_of = null; // clear conflict if applicable
|
editor.conflict_of = null; // clear conflict if applicable
|
||||||
if(editor.local && !isDesktopApplication()) {
|
if(editor.local && !isDesktopApplication()) {
|
||||||
alert("This editor is installed ")
|
alert("This editor is installed locally and is available only through Standard Notes for Desktop.")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,19 @@ class PermissionsModal {
|
|||||||
$scope.callback(false);
|
$scope.callback(false);
|
||||||
$scope.dismiss();
|
$scope.dismiss();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, modelManager) {
|
||||||
$scope.formattedPermissions = $scope.permissions.map(function(permission){
|
$scope.formattedPermissions = $scope.permissions.map(function(permission){
|
||||||
if(permission.name === "stream-items") {
|
if(permission.name === "stream-items") {
|
||||||
var title = "Access to ";
|
var title = "Access to ";
|
||||||
var types = permission.content_types.map(function(type){
|
var types = permission.content_types.map(function(type){
|
||||||
return (type + "s").toLowerCase();
|
var desc = modelManager.humanReadableDisplayForContentType(type);
|
||||||
|
if(desc) {
|
||||||
|
return desc + "s";
|
||||||
|
} else {
|
||||||
|
return "items of type " + type;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
var typesString = "";
|
var typesString = "";
|
||||||
var separator = ", ";
|
var separator = ", ";
|
||||||
|
|||||||
@@ -47,10 +47,14 @@ class RoomBar {
|
|||||||
if(room.show) {
|
if(room.show) {
|
||||||
this.componentManager.activateComponent(room);
|
this.componentManager.activateComponent(room);
|
||||||
} else {
|
} else {
|
||||||
this.componentManager.deactivateComponent(room);
|
$scope.hideRoom(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.hideRoom = function(room) {
|
||||||
|
room.show = false;
|
||||||
|
this.componentManager.deactivateComponent(room);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -400,6 +400,25 @@ class ModelManager {
|
|||||||
|
|
||||||
return JSON.stringify(data, null, 2 /* pretty print */);
|
return JSON.stringify(data, null, 2 /* pretty print */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Misc
|
||||||
|
*/
|
||||||
|
|
||||||
|
humanReadableDisplayForContentType(contentType) {
|
||||||
|
return {
|
||||||
|
"Note" : "note",
|
||||||
|
"Tag" : "tag",
|
||||||
|
"Extension" : "action-based extension",
|
||||||
|
"SN|Component" : "component",
|
||||||
|
"SN|Editor" : "editor",
|
||||||
|
"SN|Theme" : "theme",
|
||||||
|
"SF|Extension" : "server extension",
|
||||||
|
"SF|MFA" : "server multi-factor authentication setting"
|
||||||
|
}[contentType];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('app.frontend').service('modelManager', ModelManager);
|
angular.module('app.frontend').service('modelManager', ModelManager);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"}
|
.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)", "click-outside" => "hideRoom(room)"}
|
||||||
%img.icon{"ng-src" => "{{room.package_info.icon_bar}}"}
|
%img.icon{"ng-src" => "{{room.package_info.icon_bar}}"}
|
||||||
.label {{room.name}}
|
.label {{room.name}}
|
||||||
.room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"}
|
.room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
%input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();",
|
%input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();",
|
||||||
"ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"}
|
"ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"}
|
||||||
%ul.section-menu-bar{"ng-if" => "ctrl.note"}
|
%ul.section-menu-bar{"ng-if" => "ctrl.note"}
|
||||||
%li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
|
%li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;"}
|
||||||
%label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu
|
%label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu
|
||||||
|
|
||||||
%ul.dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"}
|
%ul.dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"}
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
%li{"ng-if" => "ctrl.hasDisabledStackComponents()"}
|
%li{"ng-if" => "ctrl.hasDisabledStackComponents()"}
|
||||||
%label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components
|
%label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components
|
||||||
|
|
||||||
%li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
|
%li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;"}
|
||||||
%label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor
|
%label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor
|
||||||
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"}
|
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"}
|
||||||
|
|
||||||
%li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
|
%li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;"}
|
||||||
%label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions
|
%label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions
|
||||||
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
|
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#footer-bar
|
#footer-bar
|
||||||
.pull-left
|
.pull-left
|
||||||
.footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"}
|
.footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;"}
|
||||||
%a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account
|
%a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account
|
||||||
%account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"}
|
%account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"}
|
||||||
|
|
||||||
.footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"}
|
.footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;"}
|
||||||
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
|
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
|
||||||
%global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"}
|
%global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
%label Options
|
%label Options
|
||||||
.subtitle {{ctrl.optionsSubtitle()}}
|
.subtitle {{ctrl.optionsSubtitle()}}
|
||||||
|
|
||||||
.sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu"}
|
.sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu", "click-outside" => "ctrl.showMenu = false;"}
|
||||||
%ul
|
%ul
|
||||||
.header
|
.header
|
||||||
.title Sort by
|
.title Sort by
|
||||||
|
|||||||
Reference in New Issue
Block a user