middle click can now open links in new tab (and close tab)

This commit is contained in:
zadam 2019-05-15 21:50:27 +02:00
parent dd1fc23fe8
commit f22cc37df7
10 changed files with 1411 additions and 956 deletions

File diff suppressed because it is too large Load Diff

View File

@ -33,11 +33,11 @@ function createPanZoom(domElement, options) {
if (!panController) { if (!panController) {
if (domElement instanceof SVGElement) { if (domElement instanceof SVGElement) {
panController = makeSvgController(domElement) panController = makeSvgController(domElement, options)
} }
if (domElement instanceof HTMLElement) { if (domElement instanceof HTMLElement) {
panController = makeDomController(domElement) panController = makeDomController(domElement, options)
} }
} }
@ -57,7 +57,8 @@ function createPanZoom(domElement, options) {
} }
var filterKey = typeof options.filterKey === 'function' ? options.filterKey : noop; var filterKey = typeof options.filterKey === 'function' ? options.filterKey : noop;
var realPinch = typeof options.realPinch === 'boolean' ? options.realPinch : false // TODO: likely need to unite pinchSpeed with zoomSpeed
var pinchSpeed = typeof options.pinchSpeed === 'number' ? options.pinchSpeed : 1;
var bounds = options.bounds var bounds = options.bounds
var maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY var maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY
var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0 var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0
@ -101,7 +102,7 @@ function createPanZoom(domElement, options) {
var moveByAnimation var moveByAnimation
var zoomToAnimation var zoomToAnimation
var multitouch var multiTouch
var paused = false var paused = false
listenForEvents() listenForEvents()
@ -144,7 +145,8 @@ function createPanZoom(domElement, options) {
function showRectangle(rect) { function showRectangle(rect) {
// TODO: this duplicates autocenter. I think autocenter should go. // TODO: this duplicates autocenter. I think autocenter should go.
var size = transformToScreen(owner.clientWidth, owner.clientHeight) var clientRect = owner.getBoundingClientRect()
var size = transformToScreen(clientRect.width, clientRect.height)
var rectWidth = rect.right - rect.left var rectWidth = rect.right - rect.left
var rectHeight = rect.bottom - rect.top var rectHeight = rect.bottom - rect.top
@ -503,7 +505,7 @@ function createPanZoom(domElement, options) {
} else if (e.touches.length === 2) { } else if (e.touches.length === 2) {
// handleTouchMove() will care about pinch zoom. // handleTouchMove() will care about pinch zoom.
pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]) pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1])
multitouch = true multiTouch = true
startTouchListenerIfNeeded() startTouchListenerIfNeeded()
} }
} }
@ -568,25 +570,14 @@ function createPanZoom(domElement, options) {
internalMoveBy(point.x, point.y) internalMoveBy(point.x, point.y)
} else if (e.touches.length === 2) { } else if (e.touches.length === 2) {
// it's a zoom, let's find direction // it's a zoom, let's find direction
multitouch = true multiTouch = true
var t1 = e.touches[0] var t1 = e.touches[0]
var t2 = e.touches[1] var t2 = e.touches[1]
var currentPinchLength = getPinchZoomLength(t1, t2) var currentPinchLength = getPinchZoomLength(t1, t2)
var scaleMultiplier = 1 // since the zoom speed is always based on distance from 1, we need to apply
// pinch speed only on that distance from 1:
if (realPinch) { var scaleMultiplier = 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed
scaleMultiplier = currentPinchLength / pinchZoomLength
} else {
var delta = 0
if (currentPinchLength < pinchZoomLength) {
delta = 1
} else if (currentPinchLength > pinchZoomLength) {
delta = -1
}
scaleMultiplier = getScaleMultiplier(delta)
}
mouseX = (t1.clientX + t2.clientX)/2 mouseX = (t1.clientX + t2.clientX)/2
mouseY = (t1.clientY + t2.clientY)/2 mouseY = (t1.clientY + t2.clientY)/2
@ -619,8 +610,9 @@ function createPanZoom(domElement, options) {
} }
function getPinchZoomLength(finger1, finger2) { function getPinchZoomLength(finger1, finger2) {
return Math.sqrt((finger1.clientX - finger2.clientX) * (finger1.clientX - finger2.clientX) + var dx = finger1.clientX - finger2.clientX
(finger1.clientY - finger2.clientY) * (finger1.clientY - finger2.clientY)) var dy = finger1.clientY - finger2.clientY
return Math.sqrt(dx * dx + dy * dy)
} }
function onDoubleClick(e) { function onDoubleClick(e) {
@ -630,12 +622,6 @@ function createPanZoom(domElement, options) {
} }
function onMouseDown(e) { function onMouseDown(e) {
if (options.onMouseDown && !options.onMouseDown(e)) {
// if they return `false` from onTouch, we don't want to stop
// events propagation. Fixes https://github.com/anvaka/panzoom/issues/46
return
}
if (touchInProgress) { if (touchInProgress) {
// modern browsers will fire mousedown for touch events too // modern browsers will fire mousedown for touch events too
// we do not want this: touch is handled separately. // we do not want this: touch is handled separately.
@ -698,7 +684,7 @@ function createPanZoom(domElement, options) {
document.removeEventListener('touchend', handleTouchEnd) document.removeEventListener('touchend', handleTouchEnd)
document.removeEventListener('touchcancel', handleTouchEnd) document.removeEventListener('touchcancel', handleTouchEnd)
panstartFired = false panstartFired = false
multitouch = false multiTouch = false
} }
function onMouseWheel(e) { function onMouseWheel(e) {
@ -775,8 +761,8 @@ function createPanZoom(domElement, options) {
function triggerPanEnd() { function triggerPanEnd() {
if (panstartFired) { if (panstartFired) {
// we should never run smooth scrolling if it was multitouch (pinch zoom animation): // we should never run smooth scrolling if it was multiTouch (pinch zoom animation):
if (!multitouch) smoothScroll.stop() if (!multiTouch) smoothScroll.stop()
triggerEvent('panend') triggerEvent('panend')
} }
} }
@ -828,11 +814,13 @@ function autoRun() {
if (!scripts) return; if (!scripts) return;
var panzoomScript; var panzoomScript;
Array.from(scripts).forEach(function(x) { for (var i = 0; i < scripts.length; ++i) {
var x = scripts[i];
if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) { if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) {
panzoomScript = x panzoomScript = x
break;
} }
}) }
if (!panzoomScript) return; if (!panzoomScript) return;
@ -894,7 +882,7 @@ autoRun();
},{"./lib/domController.js":2,"./lib/kinetic.js":3,"./lib/svgController.js":4,"./lib/textSelectionInterceptor.js":5,"./lib/transform.js":6,"amator":7,"ngraph.events":9,"wheel":10}],2:[function(require,module,exports){ },{"./lib/domController.js":2,"./lib/kinetic.js":3,"./lib/svgController.js":4,"./lib/textSelectionInterceptor.js":5,"./lib/transform.js":6,"amator":7,"ngraph.events":9,"wheel":10}],2:[function(require,module,exports){
module.exports = makeDomController module.exports = makeDomController
function makeDomController(domElement) { function makeDomController(domElement, options) {
var elementValid = (domElement instanceof HTMLElement) var elementValid = (domElement instanceof HTMLElement)
if (!elementValid) { if (!elementValid) {
throw new Error('svg element is required for svg.panzoom to work') throw new Error('svg element is required for svg.panzoom to work')
@ -908,7 +896,10 @@ function makeDomController(domElement) {
} }
domElement.scrollTop = 0; domElement.scrollTop = 0;
owner.setAttribute('tabindex', 1); // TODO: not sure if this is really polite
if (!options.disableKeyboardInteraction) {
owner.setAttribute('tabindex', 0);
}
var api = { var api = {
getBBox: getBBox, getBBox: getBBox,
@ -1067,7 +1058,7 @@ function kinetic(getPoint, scroll, settings) {
},{}],4:[function(require,module,exports){ },{}],4:[function(require,module,exports){
module.exports = makeSvgController module.exports = makeSvgController
function makeSvgController(svgElement) { function makeSvgController(svgElement, options) {
var elementValid = (svgElement instanceof SVGElement) var elementValid = (svgElement instanceof SVGElement)
if (!elementValid) { if (!elementValid) {
throw new Error('svg element is required for svg.panzoom to work') throw new Error('svg element is required for svg.panzoom to work')
@ -1081,7 +1072,9 @@ function makeSvgController(svgElement) {
'As of March 2016 only FireFox supported transform on the root element') 'As of March 2016 only FireFox supported transform on the root element')
} }
owner.setAttribute('tabindex', 1); // TODO: not sure if this is really polite if (!options.disableKeyboardInteraction) {
owner.setAttribute('tabindex', 0);
}
var api = { var api = {
getBBox: getBBox, getBBox: getBBox,
@ -1578,7 +1571,7 @@ function removeWheelListener( elem, callback, useCapture ) {
// unsubscription in some browsers. But in practice, I don't think we should // unsubscription in some browsers. But in practice, I don't think we should
// worry too much about it (those browsers are on the way out) // worry too much about it (those browsers are on the way out)
function _addWheelListener( elem, eventName, callback, useCapture ) { function _addWheelListener( elem, eventName, callback, useCapture ) {
elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function( originalEvent ) { elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function(originalEvent ) {
!originalEvent && ( originalEvent = window.event ); !originalEvent && ( originalEvent = window.event );
// create a normalized event object // create a normalized event object
@ -1620,7 +1613,10 @@ function _addWheelListener( elem, eventName, callback, useCapture ) {
// it's time to fire the callback // it's time to fire the callback
return callback( event ); return callback( event );
}, useCapture || false ); }, {
capture: useCapture || false ,
passive: false
});
} }
function _removeWheelListener( elem, eventName, callback, useCapture ) { function _removeWheelListener( elem, eventName, callback, useCapture ) {

View File

@ -43,18 +43,19 @@ function getNotePathFromLink($link) {
} }
function goToLink(e) { function goToLink(e) {
e.preventDefault();
const $link = $(e.target); const $link = $(e.target);
const notePath = getNotePathFromLink($link); const notePath = getNotePathFromLink($link);
if (notePath) { if (notePath) {
if (e.ctrlKey) { if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); noteDetailService.openInTab(notePath);
}
else if (e.which === 1) {
treeService.activateNote(notePath);
} }
else { else {
treeService.activateNote(notePath); return false;
} }
} }
else { else {
@ -64,6 +65,11 @@ function goToLink(e) {
window.open(address, '_blank'); window.open(address, '_blank');
} }
} }
e.preventDefault();
e.stopPropagation();
return true;
} }
function addLinkToEditor(linkTitle, linkHref) { function addLinkToEditor(linkTitle, linkHref) {
@ -129,22 +135,24 @@ $(document).on('contextmenu', ".note-detail-render a", tabContextMenu);
// when click on link popup, in case of internal link, just go the the referenced note instead of default behavior // when click on link popup, in case of internal link, just go the the referenced note instead of default behavior
// of opening the link in new window/tab // of opening the link in new window/tab
$(document).on('click', "a[data-action='note']", goToLink); $(document).on('mousedown', "a[data-action='note']", goToLink);
$(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToLink); $(document).on('mousedown', 'div.popover-content a, div.ui-tooltip-content a', goToLink);
$(document).on('dblclick', '.note-detail-text a', goToLink); $(document).on('dblclick', '.note-detail-text a', goToLink);
$(document).on('click', '.note-detail-text a', function (e) { $(document).on('mousedown', '.note-detail-text a', function (e) {
const notePath = getNotePathFromLink($(e.target)); const notePath = getNotePathFromLink($(e.target));
if (notePath && e.ctrlKey) { if (notePath && ((e.which === 1 && e.ctrlKey) || e.which === 2)) {
// if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog) // if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog)
e.preventDefault(); e.preventDefault();
noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); noteDetailService.loadNoteDetail(notePath, { newTab: true });
return true;
} }
}); });
$(document).on('click', '.note-detail-render a', goToLink); $(document).on('mousedown', '.note-detail-render a', goToLink);
$(document).on('click', '.note-detail-text.ck-read-only a', goToLink); $(document).on('mousedown', '.note-detail-text.ck-read-only a', goToLink);
$(document).on('click', 'span.ck-button__label', e => { $(document).on('mousedown', 'span.ck-button__label', e => {
// this is a link preview dialog from CKEditor link editing // this is a link preview dialog from CKEditor link editing
// for some reason clicked element is span // for some reason clicked element is span
@ -163,5 +171,6 @@ export default {
createNoteLink, createNoteLink,
addLinkToEditor, addLinkToEditor,
addTextToEditor, addTextToEditor,
init init,
goToLink
}; };

View File

@ -46,7 +46,8 @@ function initNoteAutocomplete($el, options) {
.prop("title", "Show recent notes"); .prop("title", "Show recent notes");
const $goToSelectedNoteButton = $("<a>") const $goToSelectedNoteButton = $("<a>")
.addClass("input-group-text go-to-selected-note-button jam jam-arrow-right"); .addClass("input-group-text go-to-selected-note-button jam jam-arrow-right")
.attr("data-action", "note");
const $sideButtons = $("<div>") const $sideButtons = $("<div>")
.addClass("input-group-append") .addClass("input-group-append")
@ -69,14 +70,6 @@ function initNoteAutocomplete($el, options) {
return false; return false;
}); });
$goToSelectedNoteButton.click(() => {
if ($el.hasClass("disabled")) {
return;
}
treeService.activateNote($el.getSelectedPath());
});
$el.autocomplete({ $el.autocomplete({
appendTo: document.querySelector('body'), appendTo: document.querySelector('body'),
hint: false, hint: false,

View File

@ -372,11 +372,14 @@ $(tabRow.el).on('contextmenu', '.note-tab', e => {
contextMenuService.initContextMenu(e, { contextMenuService.initContextMenu(e, {
getContextMenuItems: () => { getContextMenuItems: () => {
return [ return [
{title: "Close all tabs", cmd: "removeAllTabs", uiIcon: "empty"},
{title: "Close all tabs except for this", cmd: "removeAllTabsExceptForThis", uiIcon: "empty"} {title: "Close all tabs except for this", cmd: "removeAllTabsExceptForThis", uiIcon: "empty"}
]; ];
}, },
selectContextMenuItem: (e, cmd) => { selectContextMenuItem: (e, cmd) => {
if (cmd === 'removeAllTabsExceptForThis') { if (cmd === 'removeAllTabs') {
tabRow.removeAllTabs();
} else if (cmd === 'removeAllTabsExceptForThis') {
tabRow.removeAllTabsExceptForThis(tab[0]); tabRow.removeAllTabsExceptForThis(tab[0]);
} }
} }

View File

@ -90,6 +90,7 @@ class NoteDetailRelationMap {
contextMenuWidget.initContextMenu(e, { contextMenuWidget.initContextMenu(e, {
getContextMenuItems: () => { getContextMenuItems: () => {
return [ return [
{title: "Open in new tab", cmd: "open-in-new-tab", uiIcon: "empty"},
{title: "Remove note", cmd: "remove", uiIcon: "trash"}, {title: "Remove note", cmd: "remove", uiIcon: "trash"},
{title: "Edit title", cmd: "edit-title", uiIcon: "pencil"}, {title: "Edit title", cmd: "edit-title", uiIcon: "pencil"},
]; ];
@ -125,7 +126,7 @@ class NoteDetailRelationMap {
this.$resetPanZoomButton.click(() => { this.$resetPanZoomButton.click(() => {
// reset to initial pan & zoom state // reset to initial pan & zoom state
this.pzInstance.zoomTo(0, 0, 1 / getZoom()); this.pzInstance.zoomTo(0, 0, 1 / this.getZoom());
this.pzInstance.moveTo(0, 0); this.pzInstance.moveTo(0, 0);
}); });
@ -138,7 +139,10 @@ class NoteDetailRelationMap {
const $title = $noteBox.find(".title a"); const $title = $noteBox.find(".title a");
const noteId = this.idToNoteId($noteBox.prop("id")); const noteId = this.idToNoteId($noteBox.prop("id"));
if (cmd === "remove") { if (cmd === "open-in-new-tab") {
noteDetailService.openInTab(noteId);
}
else if (cmd === "remove") {
if (!await confirmDialog.confirmDeleteNoteBoxWithNote($title.text())) { if (!await confirmDialog.confirmDeleteNoteBoxWithNote($title.text())) {
return; return;
} }
@ -310,7 +314,7 @@ class NoteDetailRelationMap {
maxZoom: 2, maxZoom: 2,
minZoom: 0.3, minZoom: 0.3,
smoothScroll: false, smoothScroll: false,
onMouseDown: function(event) { onMouseDown: event => {
if (this.clipboard) { if (this.clipboard) {
let {x, y} = this.getMousePosition(event); let {x, y} = this.getMousePosition(event);
@ -402,9 +406,6 @@ class NoteDetailRelationMap {
this.jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays }); this.jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays });
this.jsPlumbInstance.bind("connection", (info, originalEvent) => this.connectionCreatedHandler(info, originalEvent)); this.jsPlumbInstance.bind("connection", (info, originalEvent) => this.connectionCreatedHandler(info, originalEvent));
// so that canvas is not panned when clicking/dragging note box
this.$relationMapContainer.on('mousedown touchstart', '.note-box, .connection-label', e => e.stopPropagation());
} }
async connectionCreatedHandler(info, originalEvent) { async connectionCreatedHandler(info, originalEvent) {
@ -490,10 +491,17 @@ class NoteDetailRelationMap {
} }
async createNoteBox(noteId, title, x, y) { async createNoteBox(noteId, title, x, y) {
const $link = await linkService.createNoteLink(noteId, title);
$link.mousedown(e => {
console.log(e);
linkService.goToLink(e);
});
const $noteBox = $("<div>") const $noteBox = $("<div>")
.addClass("note-box") .addClass("note-box")
.prop("id", this.noteIdToId(noteId)) .prop("id", this.noteIdToId(noteId))
.append($("<span>").addClass("title").html(await linkService.createNoteLink(noteId, title))) .append($("<span>").addClass("title").append($link))
.append($("<div>").addClass("endpoint").attr("title", "Start dragging relations from here and drop them on another note.")) .append($("<div>").addClass("endpoint").attr("title", "Start dragging relations from here and drop them on another note."))
.css("left", x + "px") .css("left", x + "px")
.css("top", y + "px"); .css("top", y + "px");

View File

@ -177,6 +177,14 @@ class TabRow {
setTabCloseEventListener(tabEl) { setTabCloseEventListener(tabEl) {
tabEl.querySelector('.note-tab-close').addEventListener('click', _ => this.removeTab(tabEl)); tabEl.querySelector('.note-tab-close').addEventListener('click', _ => this.removeTab(tabEl));
tabEl.addEventListener('mousedown', e => {
if (e.which === 2) {
this.removeTab(tabEl);
return true; // event has been handled
}
});
} }
get activeTabEl() { get activeTabEl() {
@ -251,6 +259,12 @@ class TabRow {
this.setVisibility(); this.setVisibility();
} }
async removeAllTabs() {
for (const tabEl of this.tabEls) {
await this.removeTab(tabEl);
}
}
async removeAllTabsExceptForThis(remainingTabEl) { async removeAllTabsExceptForThis(remainingTabEl) {
for (const tabEl of this.tabEls) { for (const tabEl of this.tabEls) {
if (remainingTabEl !== tabEl) { if (remainingTabEl !== tabEl) {

View File

@ -852,6 +852,22 @@ $(window).bind('hashchange', async function() {
} }
}); });
// fancytree doesn't support middle click so this is a way to support it
$tree.on('mousedown', '.fancytree-title', e => {
if (e.which === 2) {
const node = $.ui.fancytree.getNode(e);
treeUtils.getNotePath(node).then(notePath => {
if (notePath) {
noteDetailService.openInTab(notePath);
}
});
e.stopPropagation();
e.preventDefault();
}
});
utils.bindShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument utils.bindShortcut('alt+c', () => collapseTree()); // don't use shortened form since collapseTree() accepts argument
$collapseTreeButton.click(() => collapseTree()); $collapseTreeButton.click(() => collapseTree());

View File

@ -93,6 +93,7 @@ body {
#context-menu-container, #context-menu-container .dropdown-menu { #context-menu-container, #context-menu-container .dropdown-menu {
padding: 3px 0 0; padding: 3px 0 0;
z-index: 1111;
} }
#context-menu-container .dropdown-item { #context-menu-container .dropdown-item {

View File

@ -1,9 +1,9 @@
#note-detail-relation-map { .note-detail-relation-map {
height: 100%; height: 100%;
overflow: hidden !important; overflow: hidden !important;
} }
#relation-map-wrapper { .relation-map-wrapper {
position: relative; position: relative;
height: 100%; height: 100%;
outline: none; /* remove dotted outline on click */ outline: none; /* remove dotted outline on click */