feat: Make splits resizable (#6866)

This commit is contained in:
Elian Doran 2025-09-07 10:48:37 +03:00 committed by GitHub
commit ac94ab6914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 105 additions and 10 deletions

View File

@ -10,6 +10,10 @@ let leftInstance: ReturnType<typeof Split> | null;
let rightPaneWidth: number; let rightPaneWidth: number;
let rightInstance: ReturnType<typeof Split> | null; let rightInstance: ReturnType<typeof Split> | null;
const noteSplitMap = new Map<string[], ReturnType<typeof Split> | undefined>(); // key: a group of ntxIds, value: the corresponding Split instance
const noteSplitRafMap = new Map<string[], number>();
let splitNoteContainer: HTMLElement | undefined;
function setupLeftPaneResizer(leftPaneVisible: boolean) { function setupLeftPaneResizer(leftPaneVisible: boolean) {
if (leftInstance) { if (leftInstance) {
leftInstance.destroy(); leftInstance.destroy();
@ -83,7 +87,86 @@ function setupRightPaneResizer() {
} }
} }
function findKeyByNtxId(ntxId: string): string[] | undefined {
// Find the corresponding key in noteSplitMap based on ntxId
for (const key of noteSplitMap.keys()) {
if (key.includes(ntxId)) return key;
}
return undefined;
}
function setupNoteSplitResizer(ntxIds: string[]) {
let targetNtxIds: string[] | undefined;
for (const ntxId of ntxIds) {
targetNtxIds = findKeyByNtxId(ntxId);
if (targetNtxIds) break;
}
if (targetNtxIds) {
noteSplitMap.get(targetNtxIds)?.destroy();
for (const id of ntxIds) {
if (!targetNtxIds.includes(id)) {
targetNtxIds.push(id)
};
}
} else {
targetNtxIds = [...ntxIds];
}
noteSplitMap.set(targetNtxIds, undefined);
createSplitInstance(targetNtxIds);
}
function delNoteSplitResizer(ntxIds: string[]) {
let targetNtxIds = findKeyByNtxId(ntxIds[0]);
if (!targetNtxIds) {
return;
}
noteSplitMap.get(targetNtxIds)?.destroy();
noteSplitMap.delete(targetNtxIds);
targetNtxIds = targetNtxIds.filter(id => !ntxIds.includes(id));
if (targetNtxIds.length >= 2) {
noteSplitMap.set(targetNtxIds, undefined);
createSplitInstance(targetNtxIds);
}
}
function moveNoteSplitResizer(ntxId: string) {
const targetNtxIds = findKeyByNtxId(ntxId);
if (!targetNtxIds) {
return;
}
noteSplitMap.get(targetNtxIds)?.destroy();
noteSplitMap.set(targetNtxIds, undefined);
createSplitInstance(targetNtxIds);
}
function createSplitInstance(targetNtxIds: string[]) {
const prevRafId = noteSplitRafMap.get(targetNtxIds);
if (prevRafId) {
cancelAnimationFrame(prevRafId);
}
const rafId = requestAnimationFrame(() => {
splitNoteContainer = splitNoteContainer ?? $("#center-pane").find(".split-note-container-widget")[0];
const splitPanels = [...splitNoteContainer.querySelectorAll<HTMLElement>(':scope > .note-split')]
.filter(el => targetNtxIds.includes(el.getAttribute('data-ntx-id') ?? ""));
const splitInstance = Split(splitPanels, {
gutterSize: DEFAULT_GUTTER_SIZE,
minSize: 150,
});
noteSplitMap.set(targetNtxIds, splitInstance);
noteSplitRafMap.delete(targetNtxIds);
});
noteSplitRafMap.set(targetNtxIds, rafId);
}
export default { export default {
setupLeftPaneResizer, setupLeftPaneResizer,
setupRightPaneResizer setupRightPaneResizer,
setupNoteSplitResizer,
delNoteSplitResizer,
moveNoteSplitResizer
}; };

View File

@ -1243,6 +1243,10 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
cursor: row-resize; cursor: row-resize;
} }
.hidden-ext.note-split + .gutter {
display: none;
}
#context-menu-cover.show { #context-menu-cover.show {
position: fixed; position: fixed;
top: 0; top: 0;
@ -1772,7 +1776,6 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
} }
.note-split { .note-split {
flex-basis: 0; /* so that each split has same width */
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }

View File

@ -81,7 +81,7 @@ body.background-effects.zen #root-widget {
* Gutter * Gutter
*/ */
.gutter { .gutter {
background: var(--gutter-color) !important; background: var(--gutter-color) !important;
transition: background 150ms ease-out; transition: background 150ms ease-out;
} }
@ -1167,6 +1167,11 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */ /* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
} }
.split-note-container-widget > .gutter {
background: var(--root-background) !important;
transition: background 150ms ease-out;
}
/* /*
* Ribbon & note header * Ribbon & note header
*/ */
@ -1175,10 +1180,6 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.note-split:not(.hidden-ext) + .note-split:not(.hidden-ext) {
border-left: 4px solid var(--root-background);
}
@keyframes note-entrance { @keyframes note-entrance {
from { from {
opacity: 0; opacity: 0;

View File

@ -3,7 +3,7 @@ import appContext, { type CommandData, type CommandListenerData, type EventData,
import type BasicWidget from "../basic_widget.js"; import type BasicWidget from "../basic_widget.js";
import type NoteContext from "../../components/note_context.js"; import type NoteContext from "../../components/note_context.js";
import Component from "../../components/component.js"; import Component from "../../components/component.js";
import splitService from "../../services/resizer.js";
interface NoteContextEvent { interface NoteContextEvent {
noteContext: NoteContext; noteContext: NoteContext;
} }
@ -52,6 +52,10 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
await widget.handleEvent("setNoteContext", { noteContext }); await widget.handleEvent("setNoteContext", { noteContext });
this.child(widget); this.child(widget);
if (noteContext.mainNtxId && noteContext.ntxId) {
splitService.setupNoteSplitResizer([noteContext.mainNtxId,noteContext.ntxId]);
}
} }
async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) { async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) {
@ -95,9 +99,9 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
} }
} }
closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) { async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
if (ntxId) { if (ntxId) {
appContext.tabManager.removeNoteContext(ntxId); await appContext.tabManager.removeNoteContext(ntxId);
} }
} }
@ -137,6 +141,8 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
// activate context that now contains the original note // activate context that now contains the original note
await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]);
splitService.moveNoteSplitResizer(ntxIds[leftIndex]);
} }
activeContextChangedEvent() { activeContextChangedEvent() {
@ -157,6 +163,8 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
recursiveCleanup(widget); recursiveCleanup(widget);
delete this.widgets[ntxId]; delete this.widgets[ntxId];
} }
splitService.delNoteSplitResizer(ntxIds);
} }
contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) { contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) {