diff --git a/.eslintrc b/.eslintrc index 5147a49..5dbc4d2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,6 +44,13 @@ "error", "always-multiline" ], + "comma-spacing": [ + "error", + { + "before": false, + "after": true + } + ], "@stylistic/dot-location": [ "error", "property" @@ -55,7 +62,25 @@ "@stylistic/function-call-spacing": "error", "@stylistic/implicit-arrow-linebreak": "error", "@stylistic/key-spacing": "error", - "@stylistic/lines-around-comment": "error", + "@stylistic/lines-around-comment": [ + "error", + { + "beforeBlockComment": true, + "afterBlockComment": false, + "beforeLineComment": true, + "afterLineComment": false, + "allowBlockStart": true, + "allowBlockEnd": false, + "allowClassStart": true, + "allowClassEnd": false, + "allowObjectStart": true, + "allowObjectEnd": false, + "allowArrayStart": true, + "allowArrayEnd": false, + "ignorePattern": "TODO|HACK|BUG|TEST", + "applyDefaultIgnorePatterns": false + } + ], "@stylistic/new-parens": "error", "@stylistic/no-confusing-arrow": "error", "@stylistic/no-extra-semi": "error", @@ -74,7 +99,8 @@ "@stylistic/no-trailing-spaces": [ "error", { - "skipBlankLines": true + "skipBlankLines": false, + "ignoreComments": false } ], "@stylistic/nonblock-statement-body-position": "error", @@ -91,7 +117,14 @@ "@stylistic/semi-spacing": "error", "@stylistic/semi-style": "error", "@stylistic/space-before-blocks": "error", - "@stylistic/space-before-function-paren": "error", + "@stylistic/space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ], "@stylistic/arrow-spacing": "error", "@stylistic/space-in-parens": "error", "@stylistic/space-infix-ops": "error", @@ -110,6 +143,13 @@ "allowEmptyCase": true } ], - "no-console": ["error", {"allow": ["warn", "error", "debug"]}] + "line-comment-position": [ + "error", + { + "position": "above", + "ignorePattern": "TODO|HACK|BUG|TEST", + "applyDefaultIgnorePatterns": false + } + ] } } \ No newline at end of file diff --git a/.eslintrc.dev b/.eslintrc.dev new file mode 100644 index 0000000..c38a17b --- /dev/null +++ b/.eslintrc.dev @@ -0,0 +1,8 @@ +{ + "extends": [ + "./.eslintrc" + ], + "rules": { + "no-console": "off", + } +} \ No newline at end of file diff --git a/.eslintrc.release b/.eslintrc.release new file mode 100644 index 0000000..91ed21d --- /dev/null +++ b/.eslintrc.release @@ -0,0 +1,21 @@ +{ + "extends": [ + "./.eslintrc" + ], + "rules": { + "no-console": [ + "error", + { + "allow": [ + "warn", + "error", + "debug" + ] + } + ], + "multiline-comment-style": [ + "error", + "starred-block" + ] + } +} \ No newline at end of file diff --git a/src/OpenEditorsView.ts b/src/OpenEditorsView.ts index a770fde..6cc2977 100644 --- a/src/OpenEditorsView.ts +++ b/src/OpenEditorsView.ts @@ -5,6 +5,7 @@ import { TreeItem } from './TreeItem'; export class OpenEditorsView extends View { // Array of top-level tree items representing open editor windows windows: TreeItem[]; + // The root container element for the tree structure treeEl: HTMLDivElement; @@ -12,22 +13,22 @@ export class OpenEditorsView extends View { * Initializes the OpenEditorsView with a given workspace leaf. * @param leaf - The workspace leaf where this view will be rendered. */ - constructor (leaf: WorkspaceLeaf) { + constructor(leaf: WorkspaceLeaf) { super(leaf); this.windows = []; this.icon = ICON_OPEN_EDITORS; } - getViewType (): string { + getViewType(): string { return VIEW_TYPE_OPEN_EDITORS; } - getDisplayText (): string { + getDisplayText(): string { return VIEW_DISPLAY_OPEN_EDITORS; } - async onOpen () { + async onOpen() { // Create the main container for the view this.containerEl.empty(); const container = this.containerEl.createEl('div', { cls: (VIEW_TYPE_OPEN_EDITORS + '-container') }); @@ -37,6 +38,7 @@ export class OpenEditorsView extends View { this.registerEvent(this.app.workspace.on('layout-change', async () => { // Clear the current tree structure this.clearTree(); + // Rebuild the tree based on the updated layout this.createTree(); })); @@ -45,7 +47,7 @@ export class OpenEditorsView extends View { /** * Creates the tree structure representing the current workspace layout. */ - createTree () { + createTree() { // Get the current workspace layout const layout = this.app.workspace.getLayout(); @@ -66,6 +68,7 @@ export class OpenEditorsView extends View { }]); }, }]); + // Add the tree item to the list of windows this.windows.push(tree); @@ -94,7 +97,7 @@ export class OpenEditorsView extends View { * @param layout - The current layout node being processed. * @param parent - The parent TreeItem to attach new child TreeItems to. */ - TreeWalker (layout: { id: string, type: string, children: object[], state: { title: string, type: string } }, parent: TreeItem | undefined) { + TreeWalker(layout: { id: string, type: string, children: object[], state: { title: string, type: string } }, parent: TreeItem | undefined) { // Counter for labeling groups and tabs let count = 1; switch (layout.type) { @@ -114,11 +117,13 @@ export class OpenEditorsView extends View { }]); }, }]); + // Add the tree item to the list of windows this.windows.push(parent); if (layout.children.length > 1) { 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, element.type, undefined, [{ @@ -146,6 +151,7 @@ export class OpenEditorsView extends View { } break; + // Handle split layouts case 'split': layout.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => { @@ -208,17 +214,20 @@ export class OpenEditorsView extends View { } }, }])); + // Recursively process tabs as children this.TreeWalker(element, group); count++; }); break; + // Handle tabs layouts case 'tabs': layout.children.forEach((element: { id: string, type: string, children: object[], state: { title: string, type: string } }) => { this.TreeWalker(element, parent); }); break; + // Handle individual leaves case 'leaf': parent?.addChild((container) => new TreeItem(container, layout.state.title, layout.id, layout.type, { @@ -249,9 +258,10 @@ export class OpenEditorsView extends View { /** * Clears the current tree structure and resets the windows array. */ - clearTree () { + clearTree() { // Delete all tree items and their children this.windows.forEach(window => window.deleteTree()); + // Reset the windows array this.windows = []; diff --git a/src/TreeItem.ts b/src/TreeItem.ts index 999ef2c..b773e7f 100644 --- a/src/TreeItem.ts +++ b/src/TreeItem.ts @@ -3,16 +3,22 @@ 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 treeItemEl: HTMLDivElement; + // Represents the tree item selfEl: HTMLDivElement; + // Container for child elements childrenEl: HTMLDivElement; + // Element for displaying collapsible icons iconEl: HTMLDivElement; @@ -24,7 +30,7 @@ export class TreeItem { * @param handler - Optional event handlers * @param actions - Optional actions (clickable icons with callbacks) */ - constructor ( + constructor( conteinerEl: HTMLDivElement, title: string, id: string, @@ -60,6 +66,7 @@ export class TreeItem { this.childrenEl.setCssProps({ 'display': 'none' }); } } + // Invoke custom click handler if provided if (handler?.onClickCallback) { handler.onClickCallback(ev); @@ -91,10 +98,12 @@ export class TreeItem { if (action.ariaLabel) { button.ariaLabel = action.ariaLabel; } + // Add a click listener to invoke the action callback button.onClickEvent((ev) => { // Prevent click from bubbling up ev.stopPropagation(); + // Call provided onClick Callback function action.onClickCallback(ev); }); @@ -106,7 +115,7 @@ export class TreeItem { * Recursively apply a callback function to the current tree item and its children. * @param callback - The function to call for each tree item. */ - rekursiveCall (callback: (tree: TreeItem) => void) { + rekursiveCall(callback: (tree: TreeItem) => void) { this.children.forEach((child) => { child.rekursiveCall(callback); }); @@ -118,7 +127,7 @@ export class TreeItem { * @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 }[]) { + rekursiveCallForType(actions: { callback: (tree: TreeItem) => void, type: string }[]) { this.children.forEach((child) => { child.rekursiveCallForType(actions); }); @@ -137,7 +146,7 @@ export class TreeItem { * @example * parent.addChild((containerEl) => new TreeItem(containerEl, "Title", "ID")) */ - addChild (treeCreateCallback: (containerEl: HTMLDivElement) => TreeItem): TreeItem { + addChild(treeCreateCallback: (containerEl: HTMLDivElement) => TreeItem): TreeItem { if (!this.selfEl.hasClass('mod-collapsible')) { // Make this tree item collapsible if it isn't already this.selfEl.addClass('mod-collapsible'); @@ -146,7 +155,9 @@ export class TreeItem { } const child = treeCreateCallback(this.childrenEl); - this.children.push(child); // Add the child to the children array + + // Add the child to the children array + this.children.push(child); return child; } @@ -154,15 +165,18 @@ export class TreeItem { * Remove a child TreeItem by its ID. * @param id - The unique identifier of the child to remove. */ - removeChild (id: string) { + removeChild(id: string) { const child = this.children.find((value) => value.id == id); if (child) { this.children.remove(child); + // Recursively remove all children child.removeAllChildren(); + // Remove the DOM elements for this child child.removeDom(); } + // If there are no more children, remove collapsible functionality if (this.children.length <= 0 && this.selfEl.hasClass('mod-collapsible')) { this.selfEl.removeClass('mod-collapsible'); @@ -173,16 +187,18 @@ export class TreeItem { /** * Remove all child TreeItems from this tree item. */ - removeAllChildren () { + removeAllChildren() { while (this.children.length > 0) { const child = this.children.pop(); if (child) { // Recursively remove all children child.removeAllChildren(); + // Remove the DOM elements for this child child.removeDom(); } } + // If there are no more children, remove collapsible functionality if (this.children.length <= 0 && this.selfEl.hasClass('mod-collapsible')) { this.selfEl.removeClass('mod-collapsible'); @@ -193,7 +209,7 @@ export class TreeItem { /** * Completely delete this tree and all of its children. */ - deleteTree () { + deleteTree() { this.removeAllChildren(); this.removeDom(); } @@ -201,7 +217,7 @@ export class TreeItem { /** * Private helper to remove DOM elements associated with this tree item. */ - private removeDom () { + private removeDom() { this.treeItemEl?.remove(); this.selfEl?.remove(); this.childrenEl?.remove(); diff --git a/src/constants.ts b/src/constants.ts index bf6888f..f4a177a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,9 +2,9 @@ export const VIEW_TYPE_OPEN_EDITORS = 'open-editors'; export const ICON_OPEN_EDITORS = 'layers'; export const VIEW_DISPLAY_OPEN_EDITORS = 'Open editors'; -export const ICON_CLOSE = 'x-square'; // x -export const ICON_CLOSE_GROUP = 'copy-x'; // folder-x -export const ICON_CLOSE_WINDOW = 'copy-x'; // monitor-x +export const ICON_CLOSE = 'x-square'; +export const ICON_CLOSE_GROUP = 'copy-x'; +export const ICON_CLOSE_WINDOW = 'copy-x'; 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'; diff --git a/src/main.ts b/src/main.ts index 5bb9846..1b07b4e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,11 +6,13 @@ import { OpenEditorsView } from './OpenEditorsView'; export default class OpenEditorsPlugin extends Plugin { settings: Settings; - async onload () { + async onload() { await this.loadSettings(); - // This adds a settings tab so the user can configure various aspects of the plugin - // this.addSettingTab(new OpenEditorsSettingTab(this.app, this)); + /* + * This adds a settings tab so the user can configure various aspects of the plugin + * this.addSettingTab(new OpenEditorsSettingTab(this.app, this)); + */ // Register the view this.registerView(VIEW_TYPE_OPEN_EDITORS, (leaf) => new OpenEditorsView(leaf)); @@ -30,19 +32,19 @@ export default class OpenEditorsPlugin extends Plugin { }); } - onunload () { + onunload() { } - async loadSettings () { + async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } - async saveSettings () { + async saveSettings() { await this.saveData(this.settings); } - async activateView (viewType: string) { + async activateView(viewType: string) { const { workspace } = this.app; let leaf: WorkspaceLeaf | null = null; @@ -53,8 +55,10 @@ export default class OpenEditorsPlugin extends Plugin { leaf = leaves[0]; } else { - // Our view could not be found in the workspace, create a new leaf - // in the right sidebar for it + /* + * Our view could not be found in the workspace, create a new leaf + * in the right sidebar for it + */ leaf = workspace.getRightLeaf(false); if (leaf) { await leaf.setViewState({ type: viewType, active: true }); diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 7efa3bc..d3f1b79 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -4,7 +4,7 @@ import OpenEditorsPlugin from 'src/main'; export class OpenEditorsSettingTab extends PluginSettingTab { plugin: OpenEditorsPlugin; - constructor ( + constructor( app: App, plugin: OpenEditorsPlugin ) { @@ -12,7 +12,7 @@ export class OpenEditorsSettingTab extends PluginSettingTab { this.plugin = plugin; } - display (): void { + display(): void { const { containerEl } = this; containerEl.empty(); diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..f0a641e --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,8 @@ +import { } from 'obsidian'; + +declare module 'obsidian' { + interface WorkspaceLeaf { + id: string, + parent: WorkspaceTabs | WorkspaceMobileDrawer; + } +} \ No newline at end of file