Merge pull request #24 from 4Source/update-plugin-template

Update plugin template
This commit is contained in:
4Source 2025-08-12 22:17:29 +02:00 committed by GitHub
commit ff6f87be1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1171 additions and 701 deletions

View file

@ -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
}
]
}
}

8
.eslintrc.dev Normal file
View file

@ -0,0 +1,8 @@
{
"extends": [
"./.eslintrc"
],
"rules": {
"no-console": "off",
}
}

21
.eslintrc.release Normal file
View file

@ -0,0 +1,21 @@
{
"extends": [
"./.eslintrc"
],
"rules": {
"no-console": [
"error",
{
"allow": [
"warn",
"error",
"debug"
]
}
],
"multiline-comment-style": [
"error",
"starred-block"
]
}
}

View file

@ -1,46 +1,91 @@
# Welcome to Settings profiles contributing guide
Thank you for investing your time in contributing to this plugin!
Thank you for taking the time to contribute to this plugin!
In this guide you will get an overview of the contribution workflow from opening an issue, creating a pull request, and testing the plugin.
This guide will walk you through the contribution workflow - from [reporting issues](#reporting-issues) and [making changes](#making-changes), to [submitting a pull request](#pull-requests) and [testing the plugin](#testing-the-plugin).
## Getting started
### Issues
#### Create a new issue
If you spot a problem with the plugin, [search if an issue already exists](https://github.com/4Source/open-editors-obsidian-plugin/issues). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/4Source/open-editors-obsidian-plugin/issues/new/choose).
# Getting started
## Reporting Issues
### 1. Check for existing issues
If you encounter a problem, first <a href="../../../issues">search the issue tracker</a> to see if it has already been reported.
- If an there is an existing issue and you can provide additional information add a comment with your information.
- If you just want to indicate that you are experiencing the same issue, add a :+1: reaction to the issue.
### 2. Create a new Issue
If not related issue exists, open a new one using the appropriate <a href="../../../issues/new/choose">issue form</a>.
#### Solve an issue
Scan through the [existing issues](https://github.com/4Source/open-editors-obsidian-plugin/issues) to find one that interests you. If you find an issue to work on, you are welcome to [open a PR](#pull-request) with a fix.
## Working on an Issue
#### 1. Browse <a href="../../../issues">existing issues</a> to find something you'd like to work on.
#### 2. Comment on the issue to let others know you're working on it.
#### 3. Follow the [making changes](#making-changes) guide below.
#### 4. When ready, submit a [pull request](#pull-request).
### Make changes
1. Fork the repository.
2. Install [**Git**](https://git-scm.com/) on your local machine.
3. Install or update [**Node.js**](https://nodejs.org/en) version at least `18.x`.
4. You will need a code editor, such as [Visual Studio Code](https://code.visualstudio.com/)
5. Clone repository to your local machine.
- Create a new vault
- ``cd path/to/vault``
- ``mkdir .obsidian/plugins``
- ``cd .obsidian/plugins``
- ``git clone <your-repository-url> open-editors``
6. Install dependencies
- ``cd open-editors``
- ``npm install``
7. Create a working branch and start with your changes!
- ``git branch <working-branch> master``
- ``git checkout <working-branch>``
8. Compile the source code. The following command keeps running in the terminal and rebuilds the plugin when you modify the source code.
- ``npm run dev``
9. Enable the plugin in Obsidian settings
## Making Changes
### What you need:
- [**Git**](https://git-scm.com/) on your local machine.
- [**Node.js**](https://nodejs.org/en) at least version `18.x`.
- Code editor, such as [Visual Studio Code](https://code.visualstudio.com/)
### Start working:
#### 1. Fork the repository.
#### 2. Create a new vault
Just for testing the plugin so you **can't** exedentially destroy your real vault
#### 3. Clone repository to your local machine.
```shell
cd path/to/vault
mkdir .obsidian/plugins
cd .obsidian/plugins
git clone <your-fork-url> <plugin-id>
```
#### 4. Install dependencies
```shell
cd <plugin-id>
npm install
```
#### 5. Create a working branch
```shell
git checkout -b <working-branch>
```
#### 6. Compile the source code
The following command keeps running in the terminal and rebuilds the plugin when you modify the source code.
```shell
npm run dev
```
#### 7. Enable the plugin in Obsidian settings
> [!TIP]
> Install the [Hot-Reload](https://github.com/pjeby/hot-reload) plugin to automatically reload the plugin while developing.
### Commit your update
Commit the changes once you are happy with them.
## Committing Your Changes
#### 1. Check the formatting of your code
Before committing the following command will fix most of the formatting issues according to our ESLint configuration.
```shell
npm run lint-fix
```
If you see the following output without any errors the formatting matches the the requirements if any errors remain, fix them manually before proceeding.
```output
> eslint . --fix --config .eslintrc.release
```
#### 2. Create commit
Than commit your changes with a clear, descriptive commit message.
### Pull Request
When you're finished with the changes, push them to your fork and create a pull request. Make sure you fill the pull request template.
## Pull Requests
#### 1. Run tests before submitting
```shell
npm run test
```
- Ensure all tests pass.
- If new tests are needed, add them or note what should be tested in your PR.
#### 2. Push your Branch to your fork
#### 3. Open a Pull Request using the provided template
#### 4. Reference any related issues
### Test plugin
*Not a developer* but still want to help? You can help with testing. Either you test the current version for possible problems or, if there are currently any, you can also test [pre-releases (alpha/beta)](https://github.com/4Source/open-editors-obsidian-plugin/releases). You need to install the version you wanna test in a vault. It is not recommendet to test in your acctual vault create a copy of it or create new one for testing. If you found a bug or unexpected behavior create an [issue](https://github.com/4Source/open-editors-obsidian-plugin/issues) with the version you have tested. Before creating an issue, make sure you know how to reproduce the bug. If there is already an issue for the bug you found that is still open but you have additional information, add it to the issue.
## Testing the Plugin
*Not a developer* but still want to help? You can help with testing:
- Test the latest <a href="../../../releases">release</a> for problems
- Test <a href="../../../releases">pre-releases</a>
### How to test
#### 1. Do **not** test in your actual vault - create new vault just for testing
#### 2. Install the version you want to test in the vault
> [!TIP]
> Install the [VARE](https://obsidian.md/plugins?id=vare) plugin to easily switch versions while testing.
#### 3. If you found a bug or unexpected behavior
- Checkout [reporting issues](#reporting-issues)
- Make sure you know how to reproduce the bug
- Mention the version you have tested
- Provide screenshots/videos or your testfiles to make it easier to reproduce

View file

@ -35,7 +35,7 @@ body:
id: operating-system
attributes:
label: OS of your device
description: What operating system is your device runnging
description: What operating system is your device running
placeholder: e.g. Win10
validations:
required: true

View file

@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Community Support
url: https://github.com/4Source/obsidian-plugin-template/discussions/new/choose
url: https://github.com/4Source/open-editors-obsidian-plugin/discussions
about: Please ask and answer questions here.
- name: Documentation
url: https://github.com/4Source/obsidian-plugin-template/wiki
url: https://github.com/4Source/open-editors-obsidian-plugin/wiki
about: Check out the documentation if you want to know what's possible.
- name: Contributing
url: https://github.com/4Source/obsidian-plugin-template/blob/master/.github/CONTRIBUTING.md
url: https://github.com/4Source/open-editors-obsidian-plugin/blob/master/.github/CONTRIBUTING.md
about: Overview of the contribution workflow

View file

@ -5,10 +5,20 @@
## Related Issues
Fixes # <!-- Issue number --->
### Missing changes
<!-- Describe the changes what are missing which are describe in the Issue but are not implemented by this pull request --->
## Tests
### How has this been tested?
**OS:** <!-- e.g. Win10 --->
**Obsidian Version:** <!-- e.g. v1.5.1 --->
<!-- Please describe the tests that you ran to verify your changes and if there was successful or not. --->
## Checklist before requesting a review
- [ ] I have performed a self-review of my code
- [ ] My code follows the style guidelines of this project
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My code fixes the feature discussed in [Related Issues](#related-issues)
- [<!-- x --> ] I have performed a self-review of my code
- [<!-- x --> ] I have commented my code, particularly in hard-to-understand areas
- [<!-- x --> ] I have made corresponding changes to the documentation
- [<!-- x --> ] I have tested my changes in Obsidian and describte how I tested in [Tests](#tests)
- [<!-- x --> ] My code fixes the feature discussed in [Related Issues](#related-issues) if not or not fully describe what is missing in [Missing changes](#missing-changes)

View file

@ -6,11 +6,13 @@ on:
- master
paths:
- 'src/**'
- 'test/**'
push:
branches:
- master
paths:
- 'src/**'
- 'test/**'
workflow_dispatch:
jobs:
@ -74,6 +76,25 @@ jobs:
- name: Run Build
run: npm run build
audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: "18.x"
- name: Install Dependencies
run: npm install
- name: Audit for vulnerabilities
run: npm audit --audit-level=moderate
version:
name: Version Bump

View file

@ -1,4 +1,4 @@
name: Manual Realease
name: Manual Release
on:
workflow_dispatch:
inputs:

1515
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,22 +5,24 @@
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"lint": "eslint . --config .eslintrc.release",
"lint-fix": "eslint . --fix --config .eslintrc.release",
"dev:lint": "eslint . --config .eslintrc.dev",
"dev:lint-fix": "eslint . --fix --config .eslintrc.dev",
"version": "node version-change.mjs",
"test": "jest --silent"
"test": "jest --silent",
"dev:test": "jest --coverage"
},
"author": "4Source",
"license": "MIT",
"devDependencies": {
"@stylistic/eslint-plugin": "^1.6.3",
"@stylistic/eslint-plugin": "^3.1.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.10.7",
"@types/obsidian-typings": "npm:obsidian-typings@^1.0.3",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"esbuild": "^0.25.8",
"eslint": "^8.57.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@ -31,4 +33,4 @@
"typescript": "4.7.4"
},
"version": "0.2.0"
}
}

View file

@ -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 = [];

View file

@ -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();

View file

@ -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';

View file

@ -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 });

View file

@ -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();

8
src/types.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
import { } from 'obsidian';
declare module 'obsidian' {
interface WorkspaceLeaf {
id: string,
parent: WorkspaceTabs | WorkspaceMobileDrawer;
}
}