mirror of
https://github.com/zadam/trilium.git
synced 2025-01-09 16:49:11 +08:00
middle click can now open links in new tab (and close tab)
This commit is contained in:
parent
dd1fc23fe8
commit
f22cc37df7
10 changed files with 1411 additions and 956 deletions
2179
libraries/jsplumb.js
2179
libraries/jsplumb.js
File diff suppressed because it is too large
Load diff
|
@ -33,11 +33,11 @@ function createPanZoom(domElement, options) {
|
|||
|
||||
if (!panController) {
|
||||
if (domElement instanceof SVGElement) {
|
||||
panController = makeSvgController(domElement)
|
||||
panController = makeSvgController(domElement, options)
|
||||
}
|
||||
|
||||
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 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 maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY
|
||||
var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0
|
||||
|
@ -101,7 +102,7 @@ function createPanZoom(domElement, options) {
|
|||
var moveByAnimation
|
||||
var zoomToAnimation
|
||||
|
||||
var multitouch
|
||||
var multiTouch
|
||||
var paused = false
|
||||
|
||||
listenForEvents()
|
||||
|
@ -144,7 +145,8 @@ function createPanZoom(domElement, options) {
|
|||
|
||||
function showRectangle(rect) {
|
||||
// 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 rectHeight = rect.bottom - rect.top
|
||||
|
@ -503,7 +505,7 @@ function createPanZoom(domElement, options) {
|
|||
} else if (e.touches.length === 2) {
|
||||
// handleTouchMove() will care about pinch zoom.
|
||||
pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1])
|
||||
multitouch = true
|
||||
multiTouch = true
|
||||
startTouchListenerIfNeeded()
|
||||
}
|
||||
}
|
||||
|
@ -568,25 +570,14 @@ function createPanZoom(domElement, options) {
|
|||
internalMoveBy(point.x, point.y)
|
||||
} else if (e.touches.length === 2) {
|
||||
// it's a zoom, let's find direction
|
||||
multitouch = true
|
||||
multiTouch = true
|
||||
var t1 = e.touches[0]
|
||||
var t2 = e.touches[1]
|
||||
var currentPinchLength = getPinchZoomLength(t1, t2)
|
||||
|
||||
var scaleMultiplier = 1
|
||||
|
||||
if (realPinch) {
|
||||
scaleMultiplier = currentPinchLength / pinchZoomLength
|
||||
} else {
|
||||
var delta = 0
|
||||
if (currentPinchLength < pinchZoomLength) {
|
||||
delta = 1
|
||||
} else if (currentPinchLength > pinchZoomLength) {
|
||||
delta = -1
|
||||
}
|
||||
|
||||
scaleMultiplier = getScaleMultiplier(delta)
|
||||
}
|
||||
// since the zoom speed is always based on distance from 1, we need to apply
|
||||
// pinch speed only on that distance from 1:
|
||||
var scaleMultiplier = 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed
|
||||
|
||||
mouseX = (t1.clientX + t2.clientX)/2
|
||||
mouseY = (t1.clientY + t2.clientY)/2
|
||||
|
@ -619,8 +610,9 @@ function createPanZoom(domElement, options) {
|
|||
}
|
||||
|
||||
function getPinchZoomLength(finger1, finger2) {
|
||||
return Math.sqrt((finger1.clientX - finger2.clientX) * (finger1.clientX - finger2.clientX) +
|
||||
(finger1.clientY - finger2.clientY) * (finger1.clientY - finger2.clientY))
|
||||
var dx = finger1.clientX - finger2.clientX
|
||||
var dy = finger1.clientY - finger2.clientY
|
||||
return Math.sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
function onDoubleClick(e) {
|
||||
|
@ -630,12 +622,6 @@ function createPanZoom(domElement, options) {
|
|||
}
|
||||
|
||||
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) {
|
||||
// modern browsers will fire mousedown for touch events too
|
||||
// we do not want this: touch is handled separately.
|
||||
|
@ -698,7 +684,7 @@ function createPanZoom(domElement, options) {
|
|||
document.removeEventListener('touchend', handleTouchEnd)
|
||||
document.removeEventListener('touchcancel', handleTouchEnd)
|
||||
panstartFired = false
|
||||
multitouch = false
|
||||
multiTouch = false
|
||||
}
|
||||
|
||||
function onMouseWheel(e) {
|
||||
|
@ -775,8 +761,8 @@ function createPanZoom(domElement, options) {
|
|||
|
||||
function triggerPanEnd() {
|
||||
if (panstartFired) {
|
||||
// we should never run smooth scrolling if it was multitouch (pinch zoom animation):
|
||||
if (!multitouch) smoothScroll.stop()
|
||||
// we should never run smooth scrolling if it was multiTouch (pinch zoom animation):
|
||||
if (!multiTouch) smoothScroll.stop()
|
||||
triggerEvent('panend')
|
||||
}
|
||||
}
|
||||
|
@ -828,11 +814,13 @@ function autoRun() {
|
|||
if (!scripts) return;
|
||||
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/)) {
|
||||
panzoomScript = x
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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){
|
||||
module.exports = makeDomController
|
||||
|
||||
function makeDomController(domElement) {
|
||||
function makeDomController(domElement, options) {
|
||||
var elementValid = (domElement instanceof HTMLElement)
|
||||
if (!elementValid) {
|
||||
throw new Error('svg element is required for svg.panzoom to work')
|
||||
|
@ -908,7 +896,10 @@ function makeDomController(domElement) {
|
|||
}
|
||||
|
||||
domElement.scrollTop = 0;
|
||||
owner.setAttribute('tabindex', 1); // TODO: not sure if this is really polite
|
||||
|
||||
if (!options.disableKeyboardInteraction) {
|
||||
owner.setAttribute('tabindex', 0);
|
||||
}
|
||||
|
||||
var api = {
|
||||
getBBox: getBBox,
|
||||
|
@ -1067,7 +1058,7 @@ function kinetic(getPoint, scroll, settings) {
|
|||
},{}],4:[function(require,module,exports){
|
||||
module.exports = makeSvgController
|
||||
|
||||
function makeSvgController(svgElement) {
|
||||
function makeSvgController(svgElement, options) {
|
||||
var elementValid = (svgElement instanceof SVGElement)
|
||||
if (!elementValid) {
|
||||
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')
|
||||
}
|
||||
|
||||
owner.setAttribute('tabindex', 1); // TODO: not sure if this is really polite
|
||||
if (!options.disableKeyboardInteraction) {
|
||||
owner.setAttribute('tabindex', 0);
|
||||
}
|
||||
|
||||
var api = {
|
||||
getBBox: getBBox,
|
||||
|
@ -1315,7 +1308,7 @@ function makeAggregateRaf() {
|
|||
|
||||
var t = backBuffer;
|
||||
backBuffer = frontBuffer;
|
||||
frontBuffer = t;
|
||||
frontBuffer = t;
|
||||
|
||||
frontBuffer.forEach(function(callback) {
|
||||
callback();
|
||||
|
@ -1578,7 +1571,7 @@ function removeWheelListener( elem, callback, useCapture ) {
|
|||
// 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)
|
||||
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 );
|
||||
|
||||
// create a normalized event object
|
||||
|
@ -1620,7 +1613,10 @@ function _addWheelListener( elem, eventName, callback, useCapture ) {
|
|||
// it's time to fire the callback
|
||||
return callback( event );
|
||||
|
||||
}, useCapture || false );
|
||||
}, {
|
||||
capture: useCapture || false ,
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
|
||||
function _removeWheelListener( elem, eventName, callback, useCapture ) {
|
||||
|
|
|
@ -43,18 +43,19 @@ function getNotePathFromLink($link) {
|
|||
}
|
||||
|
||||
function goToLink(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const $link = $(e.target);
|
||||
|
||||
const notePath = getNotePathFromLink($link);
|
||||
|
||||
if (notePath) {
|
||||
if (e.ctrlKey) {
|
||||
noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true });
|
||||
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
|
||||
noteDetailService.openInTab(notePath);
|
||||
}
|
||||
else if (e.which === 1) {
|
||||
treeService.activateNote(notePath);
|
||||
}
|
||||
else {
|
||||
treeService.activateNote(notePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -64,6 +65,11 @@ function goToLink(e) {
|
|||
window.open(address, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
// of opening the link in new window/tab
|
||||
$(document).on('click', "a[data-action='note']", goToLink);
|
||||
$(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToLink);
|
||||
$(document).on('mousedown', "a[data-action='note']", goToLink);
|
||||
$(document).on('mousedown', 'div.popover-content a, div.ui-tooltip-content 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));
|
||||
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)
|
||||
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('click', '.note-detail-text.ck-read-only a', goToLink);
|
||||
$(document).on('click', 'span.ck-button__label', e => {
|
||||
$(document).on('mousedown', '.note-detail-render a', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-text.ck-read-only a', goToLink);
|
||||
$(document).on('mousedown', 'span.ck-button__label', e => {
|
||||
// this is a link preview dialog from CKEditor link editing
|
||||
// for some reason clicked element is span
|
||||
|
||||
|
@ -163,5 +171,6 @@ export default {
|
|||
createNoteLink,
|
||||
addLinkToEditor,
|
||||
addTextToEditor,
|
||||
init
|
||||
init,
|
||||
goToLink
|
||||
};
|
|
@ -46,7 +46,8 @@ function initNoteAutocomplete($el, options) {
|
|||
.prop("title", "Show recent notes");
|
||||
|
||||
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>")
|
||||
.addClass("input-group-append")
|
||||
|
@ -69,14 +70,6 @@ function initNoteAutocomplete($el, options) {
|
|||
return false;
|
||||
});
|
||||
|
||||
$goToSelectedNoteButton.click(() => {
|
||||
if ($el.hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
treeService.activateNote($el.getSelectedPath());
|
||||
});
|
||||
|
||||
$el.autocomplete({
|
||||
appendTo: document.querySelector('body'),
|
||||
hint: false,
|
||||
|
|
|
@ -372,11 +372,14 @@ $(tabRow.el).on('contextmenu', '.note-tab', e => {
|
|||
contextMenuService.initContextMenu(e, {
|
||||
getContextMenuItems: () => {
|
||||
return [
|
||||
{title: "Close all tabs", cmd: "removeAllTabs", uiIcon: "empty"},
|
||||
{title: "Close all tabs except for this", cmd: "removeAllTabsExceptForThis", uiIcon: "empty"}
|
||||
];
|
||||
},
|
||||
selectContextMenuItem: (e, cmd) => {
|
||||
if (cmd === 'removeAllTabsExceptForThis') {
|
||||
if (cmd === 'removeAllTabs') {
|
||||
tabRow.removeAllTabs();
|
||||
} else if (cmd === 'removeAllTabsExceptForThis') {
|
||||
tabRow.removeAllTabsExceptForThis(tab[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ class NoteDetailRelationMap {
|
|||
contextMenuWidget.initContextMenu(e, {
|
||||
getContextMenuItems: () => {
|
||||
return [
|
||||
{title: "Open in new tab", cmd: "open-in-new-tab", uiIcon: "empty"},
|
||||
{title: "Remove note", cmd: "remove", uiIcon: "trash"},
|
||||
{title: "Edit title", cmd: "edit-title", uiIcon: "pencil"},
|
||||
];
|
||||
|
@ -125,7 +126,7 @@ class NoteDetailRelationMap {
|
|||
|
||||
this.$resetPanZoomButton.click(() => {
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
@ -138,7 +139,10 @@ class NoteDetailRelationMap {
|
|||
const $title = $noteBox.find(".title a");
|
||||
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())) {
|
||||
return;
|
||||
}
|
||||
|
@ -310,7 +314,7 @@ class NoteDetailRelationMap {
|
|||
maxZoom: 2,
|
||||
minZoom: 0.3,
|
||||
smoothScroll: false,
|
||||
onMouseDown: function(event) {
|
||||
onMouseDown: event => {
|
||||
if (this.clipboard) {
|
||||
let {x, y} = this.getMousePosition(event);
|
||||
|
||||
|
@ -402,9 +406,6 @@ class NoteDetailRelationMap {
|
|||
this.jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays });
|
||||
|
||||
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) {
|
||||
|
@ -490,10 +491,17 @@ class NoteDetailRelationMap {
|
|||
}
|
||||
|
||||
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>")
|
||||
.addClass("note-box")
|
||||
.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."))
|
||||
.css("left", x + "px")
|
||||
.css("top", y + "px");
|
||||
|
|
|
@ -177,6 +177,14 @@ class TabRow {
|
|||
|
||||
setTabCloseEventListener(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() {
|
||||
|
@ -251,6 +259,12 @@ class TabRow {
|
|||
this.setVisibility();
|
||||
}
|
||||
|
||||
async removeAllTabs() {
|
||||
for (const tabEl of this.tabEls) {
|
||||
await this.removeTab(tabEl);
|
||||
}
|
||||
}
|
||||
|
||||
async removeAllTabsExceptForThis(remainingTabEl) {
|
||||
for (const tabEl of this.tabEls) {
|
||||
if (remainingTabEl !== tabEl) {
|
||||
|
|
|
@ -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
|
||||
$collapseTreeButton.click(() => collapseTree());
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ body {
|
|||
|
||||
#context-menu-container, #context-menu-container .dropdown-menu {
|
||||
padding: 3px 0 0;
|
||||
z-index: 1111;
|
||||
}
|
||||
|
||||
#context-menu-container .dropdown-item {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#note-detail-relation-map {
|
||||
.note-detail-relation-map {
|
||||
height: 100%;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
#relation-map-wrapper {
|
||||
.relation-map-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
outline: none; /* remove dotted outline on click */
|
||||
|
|
Loading…
Reference in a new issue