Merge pull request #7 from 4Source/feat-2

[FEAT]: Close Editor
This commit is contained in:
4Source 2025-03-01 16:11:29 +01:00 committed by GitHub
commit 1548a3bad5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 229 additions and 137 deletions

219
.eslintrc
View file

@ -1,108 +1,115 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint",
"@stylistic/eslint-plugin"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"@stylistic/indent": [
"error",
"tab"
],
"@stylistic/indent-binary-ops": [
"error",
"tab"
],
"@stylistic/quotes": [
"error",
"single"
],
"@stylistic/semi": [
"error",
"always"
],
"@stylistic/brace-style": [
"error",
"stroustrup",
{
"allowSingleLine": true
}
],
"@stylistic/comma-dangle": [
"error",
"always-multiline"
],
"@stylistic/dot-location": [
"error",
"property"
],
"@stylistic/function-call-argument-newline": [
"error",
"consistent"
],
"@stylistic/function-call-spacing": "error",
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/key-spacing": "error",
"@stylistic/lines-around-comment": "error",
"@stylistic/new-parens": "error",
"@stylistic/no-confusing-arrow": "error",
"@stylistic/no-extra-semi": "error",
"@stylistic/no-floating-decimal": "error",
"@stylistic/no-mixed-operators": "error",
"@stylistic/no-mixed-spaces-and-tabs": "error",
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-multiple-empty-lines": [
"error",
{
"max": 1,
"maxEOF": 0,
"maxBOF": 0
}
],
"@stylistic/no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
],
"@stylistic/nonblock-statement-body-position": "error",
"@stylistic/operator-linebreak": [
"error",
"after"
],
"@stylistic/padded-blocks": [
"error",
"never"
],
"@stylistic/padding-line-between-statements": "error",
"@stylistic/rest-spread-spacing": "error",
"@stylistic/semi-spacing": "error",
"@stylistic/semi-style": "error",
"@stylistic/space-before-blocks": "error",
"@stylistic/space-before-function-paren": "error",
"@stylistic/arrow-spacing": "error",
"@stylistic/space-in-parens": "error",
"@stylistic/space-infix-ops": "error",
"@stylistic/space-unary-ops": "error",
"@stylistic/spaced-comment": [
"error",
"always"
],
"@stylistic/switch-colon-spacing": "error",
"@stylistic/template-curly-spacing": "error",
"@stylistic/type-generic-spacing": "error",
"@stylistic/type-named-tuple-spacing": "error"
}
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint",
"@stylistic/eslint-plugin"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"@stylistic/indent": [
"error",
"tab"
],
"@stylistic/indent-binary-ops": [
"error",
"tab"
],
"@stylistic/quotes": [
"error",
"single"
],
"@stylistic/semi": [
"error",
"always"
],
"@stylistic/brace-style": [
"error",
"stroustrup",
{
"allowSingleLine": true
}
],
"@stylistic/comma-dangle": [
"error",
"always-multiline"
],
"@stylistic/dot-location": [
"error",
"property"
],
"@stylistic/function-call-argument-newline": [
"error",
"consistent"
],
"@stylistic/function-call-spacing": "error",
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/key-spacing": "error",
"@stylistic/lines-around-comment": "error",
"@stylistic/new-parens": "error",
"@stylistic/no-confusing-arrow": "error",
"@stylistic/no-extra-semi": "error",
"@stylistic/no-floating-decimal": "error",
"@stylistic/no-mixed-operators": "error",
"@stylistic/no-mixed-spaces-and-tabs": "error",
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-multiple-empty-lines": [
"error",
{
"max": 1,
"maxEOF": 0,
"maxBOF": 0
}
],
"@stylistic/no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
],
"@stylistic/nonblock-statement-body-position": "error",
"@stylistic/operator-linebreak": [
"error",
"after"
],
"@stylistic/padded-blocks": [
"error",
"never"
],
"@stylistic/padding-line-between-statements": "error",
"@stylistic/rest-spread-spacing": "error",
"@stylistic/semi-spacing": "error",
"@stylistic/semi-style": "error",
"@stylistic/space-before-blocks": "error",
"@stylistic/space-before-function-paren": "error",
"@stylistic/arrow-spacing": "error",
"@stylistic/space-in-parens": "error",
"@stylistic/space-infix-ops": "error",
"@stylistic/space-unary-ops": "error",
"@stylistic/spaced-comment": [
"error",
"always"
],
"@stylistic/switch-colon-spacing": "error",
"@stylistic/template-curly-spacing": "error",
"@stylistic/type-generic-spacing": "error",
"@stylistic/type-named-tuple-spacing": "error",
"no-fallthrough": [
"error",
{
"allowEmptyCase": true
}
],
"no-console": ["error", {"allow": ["warn", "error", "debug"]}]
}
}

View file

@ -1,4 +1,4 @@
import { View, WorkspaceLeaf } from 'obsidian';
import { View, WorkspaceLeaf, WorkspaceSplit } from 'obsidian';
import { ICON_CLOSE, ICON_CLOSE_GROUP, ICON_CLOSE_WINDOW, ICON_OPEN_EDITORS, VIEW_DISPLAY_OPEN_EDITORS, VIEW_TYPE_OPEN_EDITORS } from './constants';
import { TreeItem } from './TreeItem';
@ -53,23 +53,31 @@ export class OpenEditorsView extends View {
const main = layout['main'];
if (main) {
// Create a top-level tree item for the main window
const tree = new TreeItem(this.treeEl, 'Main window', main.id, undefined, [{
const tree = new TreeItem(this.treeEl, 'Main window', main.id, main.type, undefined, [{
iconId: ICON_CLOSE_WINDOW,
ariaLabel: 'Close all',
onClickCallback: () => {
// Recursively detach all leaves in the tree
tree.rekursiveCall((tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
});
tree.rekursiveCallForType([{
callback: (tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
},
type: 'leaf',
}]);
},
ariaLabel: 'Close all',
}]);
// Add the tree item to the list of windows
this.windows.push(tree);
// Recursively build the tree structure for all child elements
main.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => {
this.TreeWalker(element, tree);
});
if (main.children.length > 1) {
this.TreeWalker(main, tree);
}
else {
main.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => {
this.TreeWalker(element, tree);
});
}
}
// Handle floating windows (popout windows)
@ -93,15 +101,18 @@ export class OpenEditorsView extends View {
// Handle popout window layouts
case 'window':
// Create a tree item for each floating window
parent = new TreeItem(this.treeEl, `Window ${count}`, layout.id, undefined, [{
parent = new TreeItem(this.treeEl, `Window ${count}`, layout.id, layout.type, undefined, [{
iconId: ICON_CLOSE_WINDOW,
ariaLabel: 'Close all',
onClickCallback: () => {
// Recursively detach all leaves in the tree
parent?.rekursiveCall((parent) => {
this.app.workspace.getLeafById(parent.id)?.detach();
});
parent?.rekursiveCallForType([{
callback: (parent) => {
this.app.workspace.getLeafById(parent.id)?.detach();
},
type: 'leaf',
}]);
},
ariaLabel: 'Close all',
}]);
// Add the tree item to the list of windows
this.windows.push(parent);
@ -110,14 +121,17 @@ export class OpenEditorsView extends View {
let groupCount = 1;
// Recursively build the tree structure for all child elements
layout.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => {
const group = parent?.addChild((containerEl) => new TreeItem(containerEl, `Group ${groupCount}`, element.id, undefined, [{
const group = parent?.addChild((containerEl) => new TreeItem(containerEl, `Group ${groupCount}`, element.id, element.type, undefined, [{
iconId: ICON_CLOSE_GROUP,
onClickCallback: () => {
parent?.rekursiveCall((tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
});
},
ariaLabel: 'Close all',
onClickCallback: () => {
parent?.rekursiveCallForType([{
callback: (tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
},
type: 'leaf',
}]);
},
}]));
this.TreeWalker(element, group);
groupCount++;
@ -136,17 +150,66 @@ export class OpenEditorsView extends View {
case 'split':
layout.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => {
// Create a child tree item for each split group
const tree = parent?.addChild((container) => new TreeItem(container, `Group ${count}`, element.id, undefined, [{
iconId: ICON_CLOSE_GROUP,
const group = parent?.addChild((container) => new TreeItem(container, `Group ${count}`, element.id, element.type, undefined, [{
iconId: ICON_CLOSE,
ariaLabel: 'Close group',
onClickCallback: () => {
tree?.rekursiveCall((tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
});
group?.rekursiveCallForType([{
callback: (tree) => {
this.app.workspace.getLeafById(tree.id)?.detach();
},
type: 'leaf',
}]);
},
}, {
iconId: ICON_CLOSE_GROUP,
ariaLabel: 'Close all editors',
onClickCallback: () => {
if (group) {
// Find leaf parent
let leafParent: WorkspaceSplit | undefined;
group.rekursiveCallForType([{
callback: tree => {
const leaf = this.app.workspace.getLeafById(tree.id);
if (!leaf) {
console.debug('Leaf not found with the id ', tree.id);
return;
}
const parent = leaf?.parent;
if (!parent) {
console.debug('Parent of leaf not found!');
return;
}
leafParent = parent;
},
type: 'leaf',
}]);
if (!leafParent) {
console.warn('Parent of leaf not found!');
return;
}
// Add a new tab to parent
const newId = this.app.workspace.createLeafInParent(leafParent, 0).id;
if (!newId) {
console.warn('No id of new leaf!');
return;
}
// Recursively detach all leaves in the tree except for new tab
group?.rekursiveCallForType([{
callback: (tree) => {
if (tree.id != newId) {
this.app.workspace.getLeafById(tree.id)?.detach();
}
},
type: 'leaf',
}]);
}
},
ariaLabel: 'Close all',
}]));
// Recursively process tabs as children
this.TreeWalker(element, tree);
this.TreeWalker(element, group);
count++;
});
break;
@ -158,7 +221,7 @@ export class OpenEditorsView extends View {
break;
// Handle individual leaves
case 'leaf':
parent?.addChild((container) => new TreeItem(container, layout.state.title, layout.id, {
parent?.addChild((container) => new TreeItem(container, layout.state.title, layout.id, layout.type, {
onClickCallback: () => {
// Set the clicked leaf as the active leaf in the workspace
const leaf = this.app.workspace.getLeafById(layout.id);
@ -171,10 +234,10 @@ export class OpenEditorsView extends View {
},
}, [{
iconId: ICON_CLOSE,
ariaLabel: 'Close',
onClickCallback: () => {
this.app.workspace.getLeafById(layout.id)?.detach();
},
ariaLabel: 'Close',
}]));
break;
default:
@ -195,4 +258,4 @@ export class OpenEditorsView extends View {
// Clear the tree container in the DOM
this.treeEl.empty();
}
}
}

View file

@ -3,6 +3,8 @@ import { IconName, setIcon } from 'obsidian';
export class TreeItem {
// The ID of the tree item
id: string;
// The type of the workspace item behind this tree item
type: string;
// Tree items inside this tree item
children: TreeItem[];
// Main container element for the tree item
@ -26,6 +28,7 @@ export class TreeItem {
conteinerEl: HTMLDivElement,
title: string,
id: string,
type: string,
handler?: {
onClickCallback?: (ev: MouseEvent) => void,
onDragCallback?: (ev: DragEvent, delegateTarget: HTMLElement) => void
@ -34,6 +37,7 @@ export class TreeItem {
) {
this.children = [];
this.id = id;
this.type = type;
// Create the main tree item element
this.treeItemEl = conteinerEl.createEl('div', { cls: 'tree-item' });
@ -109,6 +113,23 @@ export class TreeItem {
callback(this);
}
/**
* Recursively apply a callback funstion to the current tree item and its children if the type matches the given type.
* @param callback - The function to call for each tree item.
* @param type - The type TreeItem needs to have
*/
rekursiveCallForType (actions: { callback: (tree: TreeItem) => void, type: string }[]) {
this.children.forEach((child) => {
child.rekursiveCallForType(actions);
});
actions.forEach(action => {
if (this.type === action.type) {
action.callback(this);
}
});
}
/**
* Add a child TreeItem to this tree item.
* @param treeCreateCallback - A function that creates and returns a new TreeItem.

View file

@ -9,4 +9,5 @@ export const ICON_MOVE_TO_NEW_WINDOW = 'picture-in-picture';
export const ICON_OPEN_IN_NEW_WINDOW = 'picture-in-picture-2';
export const ICON_SPLIT_RIGHT = 'separator-vertical';
export const ICON_SPLIT_DOWN = 'separator-horizontal';
export const ICON_REVEAL_IN_NAVIGATION = 'folder-open';
export const ICON_REVEAL_IN_NAVIGATION = 'folder-open';
export const ICON_COMBINE_GROUP = 'shrink';