mirror of
https://github.com/mnaoumov/obsidian-backlink-full-path.git
synced 2026-07-22 05:41:39 +00:00
refactor!: migrate to obsidian-dev-utils v2 component architecture
- Delete PluginTypes.ts and remove generic from PluginBase - Convert PluginSettingsManager to PluginSettingsComponent with DI params - Use constructor registerComponent() pattern for settings and settings tab - Replace onSaveSettings override with event subscription on settings component - Rename all source files to kebab-case (Plugin.ts -> plugin.ts, etc.) - Add vitest test infrastructure with jsdom environment and obsidian-test-mocks - Add 14 tests covering settings defaults, component creation, and plugin construction - Update tsconfig: remove allowJs, svelte types; add vitest.config.ts to include - Add obsidian-test-mocks, sass-embedded, vitest, jsdom as dev dependencies BREAKING CHANGE: Requires obsidian-dev-utils v2 (component architecture).
This commit is contained in:
parent
ea3e30ce0c
commit
f32bbb7b58
23 changed files with 1327 additions and 8826 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -21,6 +21,7 @@ data.json
|
|||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
coverage
|
||||
dist
|
||||
.env
|
||||
/tsconfig.tsbuildinfo
|
||||
|
|
|
|||
5
CLAUDE.md
Normal file
5
CLAUDE.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# CLAUDE.md
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Lint, format, and spellcheck scripts fail when `obsidian-dev-utils` is installed via `file:` protocol because transitive tool dependencies (cspell, dprint, eslint-plugin-no-unsanitized) are not hoisted into the consumer's `node_modules`. This will resolve when obsidian-dev-utils v2 is published to npm.
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [
|
||||
"coverage",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"tsconfig.tsbuildinfo"
|
||||
|
|
@ -11,8 +12,11 @@
|
|||
"backlink",
|
||||
"backlinks",
|
||||
"dblclick",
|
||||
"jsdom",
|
||||
"lcov",
|
||||
"mnaoumov",
|
||||
"Naumov",
|
||||
"noopAsync",
|
||||
"tsbuildinfo"
|
||||
],
|
||||
"ignoreWords": [],
|
||||
|
|
|
|||
9580
package-lock.json
generated
9580
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
|
@ -21,6 +21,8 @@
|
|||
"lint:md:fix": "jiti scripts/lint-md-fix.ts",
|
||||
"prepare": "jiti scripts/prepare.ts",
|
||||
"spellcheck": "jiti scripts/spellcheck.ts",
|
||||
"test": "jiti scripts/test.ts",
|
||||
"test:coverage": "jiti scripts/test-coverage.ts",
|
||||
"version": "jiti scripts/version.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -30,16 +32,21 @@
|
|||
"@total-typescript/ts-reset": "latest",
|
||||
"@tsconfig/strictest": "latest",
|
||||
"@types/node": "25.0.3",
|
||||
"@vitest/coverage-v8": "latest",
|
||||
"better-typescript-lib": "latest",
|
||||
"commitizen": "latest",
|
||||
"cz-conventional-changelog": "latest",
|
||||
"eslint": "latest",
|
||||
"husky": "latest",
|
||||
"jiti": "latest",
|
||||
"jsdom": "latest",
|
||||
"nano-staged": "latest",
|
||||
"obsidian": "latest",
|
||||
"obsidian-dev-utils": "latest",
|
||||
"obsidian-typings": "obsidian-public-latest"
|
||||
"obsidian-dev-utils": "file:F:/tmp/obsidian-dev-utils-v2",
|
||||
"obsidian-test-mocks": "latest",
|
||||
"obsidian-typings": "obsidian-public-latest",
|
||||
"sass-embedded": "latest",
|
||||
"vitest": "latest"
|
||||
},
|
||||
"overrides": {
|
||||
"@antfu/utils": "9.2.0",
|
||||
|
|
|
|||
4
scripts/test-coverage.ts
Normal file
4
scripts/test-coverage.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { wrapCliTask } from 'obsidian-dev-utils/script-utils/cli-utils';
|
||||
import { testCoverage } from 'obsidian-dev-utils/script-utils/test-runners/vitest';
|
||||
|
||||
await wrapCliTask(() => testCoverage());
|
||||
4
scripts/test.ts
Normal file
4
scripts/test.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { wrapCliTask } from 'obsidian-dev-utils/script-utils/cli-utils';
|
||||
import { test } from 'obsidian-dev-utils/script-utils/test-runners/vitest';
|
||||
|
||||
await wrapCliTask(() => test());
|
||||
30
scripts/vitest-config.ts
Normal file
30
scripts/vitest-config.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export const config = defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
obsidian: 'obsidian-test-mocks/obsidian'
|
||||
}
|
||||
},
|
||||
test: {
|
||||
coverage: {
|
||||
exclude: [
|
||||
'src/**/*.test.ts'
|
||||
],
|
||||
include: ['src/**/*.ts'],
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'lcov', 'html'],
|
||||
reportsDirectory: './coverage'
|
||||
},
|
||||
environment: 'jsdom',
|
||||
globals: false,
|
||||
include: ['src/**/*.test.ts'],
|
||||
onConsoleLog: (): false => false,
|
||||
server: {
|
||||
deps: {
|
||||
inline: ['obsidian-typings']
|
||||
}
|
||||
},
|
||||
setupFiles: ['obsidian-test-mocks/setup']
|
||||
}
|
||||
});
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
export class PluginSettings {
|
||||
public pathDepth = 0;
|
||||
public rootPaths: string[] = [];
|
||||
public shouldDisplayParentPathOnSeparateLine = false;
|
||||
public shouldHighlightFileName = true;
|
||||
public shouldIncludeExtension = true;
|
||||
public shouldReversePathParts = false;
|
||||
public shouldShowEllipsisForSkippedPathParts = true;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { PluginSettingsManagerBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-manager-base';
|
||||
|
||||
import type { PluginTypes } from './PluginTypes.ts';
|
||||
|
||||
import { PluginSettings } from './PluginSettings.ts';
|
||||
|
||||
export class PluginSettingsManager extends PluginSettingsManagerBase<PluginTypes> {
|
||||
protected override createDefaultSettings(): PluginSettings {
|
||||
return new PluginSettings();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import type { PluginTypesBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-types-base';
|
||||
|
||||
import type { Plugin } from './Plugin.ts';
|
||||
import type { PluginSettings } from './PluginSettings.ts';
|
||||
import type { PluginSettingsManager } from './PluginSettingsManager.ts';
|
||||
import type { PluginSettingsTab } from './PluginSettingsTab.ts';
|
||||
|
||||
export interface PluginTypes extends PluginTypesBase {
|
||||
plugin: Plugin;
|
||||
pluginSettings: PluginSettings;
|
||||
pluginSettingsManager: PluginSettingsManager;
|
||||
pluginSettingsTab: PluginSettingsTab;
|
||||
}
|
||||
20
src/main.test.ts
Normal file
20
src/main.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Tests for the main module default export.
|
||||
*/
|
||||
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import { Plugin } from './plugin.ts';
|
||||
|
||||
describe('main', () => {
|
||||
it('should export Plugin as default export', async () => {
|
||||
const mainModule = await import('./main.ts') as { default: typeof Plugin };
|
||||
expect(mainModule.default).toBe(Plugin);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import './styles/main.scss';
|
||||
import { Plugin } from './Plugin.ts';
|
||||
import { Plugin } from './plugin.ts';
|
||||
|
||||
export default Plugin;
|
||||
|
|
|
|||
42
src/plugin-settings-component.test.ts
Normal file
42
src/plugin-settings-component.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Tests for PluginSettingsComponent.
|
||||
*/
|
||||
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi
|
||||
} from 'vitest';
|
||||
|
||||
import { PluginSettings } from './plugin-settings.ts';
|
||||
import { PluginSettingsComponent } from './plugin-settings-component.ts';
|
||||
|
||||
describe('PluginSettingsComponent', () => {
|
||||
it('should create default settings as PluginSettings instance', () => {
|
||||
const component = new PluginSettingsComponent({
|
||||
loadData: vi.fn().mockResolvedValue(null),
|
||||
saveData: vi.fn().mockResolvedValue(undefined)
|
||||
});
|
||||
|
||||
expect(component.defaultSettings).toBeInstanceOf(PluginSettings);
|
||||
});
|
||||
|
||||
it('should have all 7 default setting properties', () => {
|
||||
const component = new PluginSettingsComponent({
|
||||
loadData: vi.fn().mockResolvedValue(null),
|
||||
saveData: vi.fn().mockResolvedValue(undefined)
|
||||
});
|
||||
|
||||
const defaults = component.defaultSettings;
|
||||
expect(defaults).toHaveProperty('pathDepth');
|
||||
expect(defaults).toHaveProperty('rootPaths');
|
||||
expect(defaults).toHaveProperty('shouldDisplayParentPathOnSeparateLine');
|
||||
expect(defaults).toHaveProperty('shouldHighlightFileName');
|
||||
expect(defaults).toHaveProperty('shouldIncludeExtension');
|
||||
expect(defaults).toHaveProperty('shouldReversePathParts');
|
||||
expect(defaults).toHaveProperty('shouldShowEllipsisForSkippedPathParts');
|
||||
});
|
||||
});
|
||||
34
src/plugin-settings-component.ts
Normal file
34
src/plugin-settings-component.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Settings component that manages plugin settings persistence.
|
||||
*/
|
||||
|
||||
import type { PluginSettingsComponentParams } from 'obsidian-dev-utils/obsidian/plugin/components/plugin-settings-component';
|
||||
|
||||
import { PluginSettingsComponentBase } from 'obsidian-dev-utils/obsidian/plugin/components/plugin-settings-component';
|
||||
|
||||
import { PluginSettings } from './plugin-settings.ts';
|
||||
|
||||
/**
|
||||
* Manages persistence and lifecycle for {@link PluginSettings}.
|
||||
*/
|
||||
export class PluginSettingsComponent extends PluginSettingsComponentBase<PluginSettings> {
|
||||
/**
|
||||
* Creates a new settings component.
|
||||
*
|
||||
* @param params - Load/save data callbacks.
|
||||
*/
|
||||
public constructor(params: PluginSettingsComponentParams) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default settings instance.
|
||||
*
|
||||
* @returns A new {@link PluginSettings} with default values.
|
||||
*/
|
||||
protected override createDefaultSettings(): PluginSettings {
|
||||
return new PluginSettings();
|
||||
}
|
||||
}
|
||||
70
src/plugin-settings-tab.test.ts
Normal file
70
src/plugin-settings-tab.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Tests for the PluginSettingsTab class.
|
||||
*/
|
||||
|
||||
import type { Plugin as ObsidianPlugin } from 'obsidian';
|
||||
|
||||
import { App } from 'obsidian-test-mocks/obsidian';
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi
|
||||
} from 'vitest';
|
||||
|
||||
import { PluginSettingsComponent } from './plugin-settings-component.ts';
|
||||
import { PluginSettingsTab } from './plugin-settings-tab.ts';
|
||||
|
||||
/**
|
||||
* Creates a mock plugin object for testing.
|
||||
*
|
||||
* @returns A mock plugin.
|
||||
*/
|
||||
function createMockPlugin(): ObsidianPlugin {
|
||||
const app = App.createConfigured__();
|
||||
return {
|
||||
app: app.asOriginalType__(),
|
||||
manifest: {
|
||||
author: 'test',
|
||||
description: 'test',
|
||||
id: 'backlink-full-path',
|
||||
minAppVersion: '0.0.0',
|
||||
name: 'Backlink Full Path',
|
||||
version: '1.0.0'
|
||||
}
|
||||
} as unknown as ObsidianPlugin;
|
||||
}
|
||||
|
||||
describe('PluginSettingsTab', () => {
|
||||
it('should be constructable', () => {
|
||||
const plugin = createMockPlugin();
|
||||
const settingsComponent = new PluginSettingsComponent({
|
||||
loadData: vi.fn().mockResolvedValue(null),
|
||||
saveData: vi.fn().mockResolvedValue(undefined)
|
||||
});
|
||||
|
||||
const tab = new PluginSettingsTab({
|
||||
plugin,
|
||||
settingsComponent
|
||||
});
|
||||
|
||||
expect(tab).toBeInstanceOf(PluginSettingsTab);
|
||||
});
|
||||
|
||||
it('should have a containerEl after construction', () => {
|
||||
const plugin = createMockPlugin();
|
||||
const settingsComponent = new PluginSettingsComponent({
|
||||
loadData: vi.fn().mockResolvedValue(null),
|
||||
saveData: vi.fn().mockResolvedValue(undefined)
|
||||
});
|
||||
|
||||
const tab = new PluginSettingsTab({
|
||||
plugin,
|
||||
settingsComponent
|
||||
});
|
||||
|
||||
expect(tab.containerEl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,36 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Settings tab UI for the Backlink Full Path plugin.
|
||||
*/
|
||||
|
||||
import type { PluginSettingsTabBaseParams } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab';
|
||||
|
||||
import { Setting } from 'obsidian';
|
||||
import { appendCodeBlock } from 'obsidian-dev-utils/html-element';
|
||||
import { PluginSettingsTabBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab-base';
|
||||
import { PluginSettingsTabBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab';
|
||||
import { SettingEx } from 'obsidian-dev-utils/obsidian/setting-ex';
|
||||
|
||||
import type { PluginTypes } from './PluginTypes.ts';
|
||||
import type { PluginSettings } from './plugin-settings.ts';
|
||||
|
||||
export class PluginSettingsTab extends PluginSettingsTabBase<PluginTypes> {
|
||||
/**
|
||||
* Settings tab for the Backlink Full Path plugin.
|
||||
*/
|
||||
export class PluginSettingsTab extends PluginSettingsTabBase<PluginSettings> {
|
||||
/**
|
||||
* Creates a new settings tab.
|
||||
*
|
||||
* @param params - The settings tab params.
|
||||
*/
|
||||
public constructor(params: PluginSettingsTabBaseParams<PluginSettings>) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the settings tab UI.
|
||||
*/
|
||||
public override display(): void {
|
||||
super.display();
|
||||
this.containerEl.empty();
|
||||
|
||||
new Setting(this.containerEl)
|
||||
.setName('Include extension')
|
||||
50
src/plugin-settings.test.ts
Normal file
50
src/plugin-settings.test.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Tests for PluginSettings default values.
|
||||
*/
|
||||
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import { PluginSettings } from './plugin-settings.ts';
|
||||
|
||||
describe('PluginSettings', () => {
|
||||
it('should have correct default pathDepth', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.pathDepth).toBe(0);
|
||||
});
|
||||
|
||||
it('should have correct default rootPaths', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.rootPaths).toEqual([]);
|
||||
});
|
||||
|
||||
it('should have correct default shouldDisplayParentPathOnSeparateLine', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.shouldDisplayParentPathOnSeparateLine).toBe(false);
|
||||
});
|
||||
|
||||
it('should have correct default shouldHighlightFileName', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.shouldHighlightFileName).toBe(true);
|
||||
});
|
||||
|
||||
it('should have correct default shouldIncludeExtension', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.shouldIncludeExtension).toBe(true);
|
||||
});
|
||||
|
||||
it('should have correct default shouldReversePathParts', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.shouldReversePathParts).toBe(false);
|
||||
});
|
||||
|
||||
it('should have correct default shouldShowEllipsisForSkippedPathParts', () => {
|
||||
const settings = new PluginSettings();
|
||||
expect(settings.shouldShowEllipsisForSkippedPathParts).toBe(true);
|
||||
});
|
||||
});
|
||||
45
src/plugin-settings.ts
Normal file
45
src/plugin-settings.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Plugin settings data class with default values.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Settings for the Backlink Full Path plugin.
|
||||
*/
|
||||
export class PluginSettings {
|
||||
/**
|
||||
* The depth of the path to include in backlinks (0 for unlimited).
|
||||
*/
|
||||
public pathDepth = 0;
|
||||
|
||||
/**
|
||||
* The paths to be treated as root paths.
|
||||
*/
|
||||
public rootPaths: string[] = [];
|
||||
|
||||
/**
|
||||
* Whether to display the parent path on a separate line.
|
||||
*/
|
||||
public shouldDisplayParentPathOnSeparateLine = false;
|
||||
|
||||
/**
|
||||
* Whether to highlight the file name.
|
||||
*/
|
||||
public shouldHighlightFileName = true;
|
||||
|
||||
/**
|
||||
* Whether to include the file extension in backlinks.
|
||||
*/
|
||||
public shouldIncludeExtension = true;
|
||||
|
||||
/**
|
||||
* Whether to reverse the path parts.
|
||||
*/
|
||||
public shouldReversePathParts = false;
|
||||
|
||||
/**
|
||||
* Whether to show ellipsis for skipped path parts.
|
||||
*/
|
||||
public shouldShowEllipsisForSkippedPathParts = true;
|
||||
}
|
||||
48
src/plugin.test.ts
Normal file
48
src/plugin.test.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @file
|
||||
*
|
||||
* Tests for the Plugin class.
|
||||
*/
|
||||
|
||||
import type { PluginManifest } from 'obsidian';
|
||||
|
||||
import { App } from 'obsidian-test-mocks/obsidian';
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import { Plugin } from './plugin.ts';
|
||||
|
||||
/**
|
||||
* Creates a minimal PluginManifest for testing.
|
||||
*
|
||||
* @returns A test PluginManifest.
|
||||
*/
|
||||
function createTestManifest(): PluginManifest {
|
||||
return {
|
||||
author: 'test',
|
||||
description: 'test',
|
||||
id: 'backlink-full-path',
|
||||
minAppVersion: '0.0.0',
|
||||
name: 'Backlink Full Path',
|
||||
version: '1.0.0'
|
||||
};
|
||||
}
|
||||
|
||||
describe('Plugin', () => {
|
||||
it('should be constructable', () => {
|
||||
const app = App.createConfigured__();
|
||||
const manifest = createTestManifest();
|
||||
const plugin = new Plugin(app.asOriginalType__(), manifest);
|
||||
expect(plugin).toBeInstanceOf(Plugin);
|
||||
});
|
||||
|
||||
it('should have the correct manifest id', () => {
|
||||
const app = App.createConfigured__();
|
||||
const manifest = createTestManifest();
|
||||
const plugin = new Plugin(app.asOriginalType__(), manifest);
|
||||
expect(plugin.manifest.id).toBe('backlink-full-path');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,15 @@
|
|||
import type { TFile } from 'obsidian';
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Main plugin class for the Backlink Full Path plugin.
|
||||
*/
|
||||
|
||||
import type {
|
||||
App,
|
||||
PluginManifest,
|
||||
TFile
|
||||
} from 'obsidian';
|
||||
import type { PluginSettingsTabBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab';
|
||||
import type {
|
||||
BacklinkPlugin,
|
||||
BacklinkView,
|
||||
|
|
@ -14,32 +25,71 @@ import {
|
|||
import { invokeAsyncSafely } from 'obsidian-dev-utils/async';
|
||||
import { getPrototypeOf } from 'obsidian-dev-utils/object-utils';
|
||||
import { registerPatch } from 'obsidian-dev-utils/obsidian/monkey-around';
|
||||
import { PluginBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-base';
|
||||
import { PluginSettingsTabComponent } from 'obsidian-dev-utils/obsidian/plugin/components/plugin-settings-tab-component';
|
||||
import { PluginBase } from 'obsidian-dev-utils/obsidian/plugin/plugin';
|
||||
import {
|
||||
InternalPluginName,
|
||||
ViewType
|
||||
} from 'obsidian-typings/implementations';
|
||||
|
||||
import type { PluginTypes } from './PluginTypes.ts';
|
||||
import type { PluginSettings } from './plugin-settings.ts';
|
||||
|
||||
import { PluginSettingsManager } from './PluginSettingsManager.ts';
|
||||
import { PluginSettingsTab } from './PluginSettingsTab.ts';
|
||||
import { PluginSettingsComponent } from './plugin-settings-component.ts';
|
||||
import { PluginSettingsTab } from './plugin-settings-tab.ts';
|
||||
|
||||
/**
|
||||
* Type alias for the `addResult` method on `ResultDom`.
|
||||
*/
|
||||
type AddResultFn = ResultDom['addResult'];
|
||||
|
||||
export class Plugin extends PluginBase<PluginTypes> {
|
||||
public override async onSaveSettings(): Promise<void> {
|
||||
await this.refreshBacklinkPanels();
|
||||
/**
|
||||
* The Backlink Full Path plugin. Replaces backlink titles with full paths.
|
||||
*/
|
||||
export class Plugin extends PluginBase {
|
||||
private readonly pluginSettingsComponent: PluginSettingsComponent;
|
||||
|
||||
/**
|
||||
* Creates a new Backlink Full Path plugin.
|
||||
*
|
||||
* @param app - The Obsidian app instance.
|
||||
* @param manifest - The plugin manifest.
|
||||
*/
|
||||
public constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
|
||||
this.pluginSettingsComponent = this.registerComponent({
|
||||
component: new PluginSettingsComponent({
|
||||
loadData: this.loadData.bind(this),
|
||||
saveData: this.saveData.bind(this)
|
||||
}),
|
||||
shouldPreload: true
|
||||
});
|
||||
|
||||
this.registerComponent({
|
||||
component: new PluginSettingsTabComponent(
|
||||
this,
|
||||
new PluginSettingsTab({
|
||||
plugin: this,
|
||||
settingsComponent: this.pluginSettingsComponent
|
||||
}) as PluginSettingsTabBase<object>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
protected override createSettingsManager(): PluginSettingsManager {
|
||||
return new PluginSettingsManager(this);
|
||||
}
|
||||
|
||||
protected override createSettingsTab(): null | PluginSettingsTab {
|
||||
return new PluginSettingsTab(this);
|
||||
/**
|
||||
* Called after pre-loaded components are initialized.
|
||||
*/
|
||||
protected override async onloadImpl(): Promise<void> {
|
||||
await super.onloadImpl();
|
||||
|
||||
this.pluginSettingsComponent.on('saveSettings', () => {
|
||||
invokeAsyncSafely(() => this.refreshBacklinkPanels());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the workspace layout is ready.
|
||||
*/
|
||||
protected override async onLayoutReady(): Promise<void> {
|
||||
const backlinksCorePlugin = this.app.internalPlugins.getPluginById(InternalPluginName.Backlink);
|
||||
if (!backlinksCorePlugin) {
|
||||
|
|
@ -68,6 +118,10 @@ export class Plugin extends PluginBase<PluginTypes> {
|
|||
});
|
||||
}
|
||||
|
||||
private get pluginSettings(): PluginSettings {
|
||||
return this.pluginSettingsComponent.settings as PluginSettings;
|
||||
}
|
||||
|
||||
private addResult(next: AddResultFn, resultDom: ResultDom, file: TFile, result: ResultDomResult, content: string, shouldShowTitle?: boolean): ResultDomItem {
|
||||
const resultDomItem = next.call(resultDom, file, result, content, shouldShowTitle);
|
||||
const fileNameCaptionEl = resultDomItem.el.querySelector('.tree-item-inner');
|
||||
|
|
@ -79,40 +133,40 @@ export class Plugin extends PluginBase<PluginTypes> {
|
|||
}
|
||||
|
||||
private generateBacklinkTitle(file: TFile): HTMLDivElement {
|
||||
const fileNamePart = this.settings.shouldIncludeExtension ? file.name : file.basename;
|
||||
const fileNamePart = this.pluginSettings.shouldIncludeExtension ? file.name : file.basename;
|
||||
|
||||
let parentPathParts = file.path.split('/').slice(0, -1);
|
||||
|
||||
for (let length = parentPathParts.length; length >= 1; length--) {
|
||||
const rootPath = parentPathParts.slice(0, length).join('/');
|
||||
if (this.settings.rootPaths.includes(rootPath)) {
|
||||
if (this.pluginSettings.rootPaths.includes(rootPath)) {
|
||||
parentPathParts = parentPathParts.slice(length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.settings.pathDepth > 0) {
|
||||
const partsToSkipCount = Math.max(0, parentPathParts.length - this.settings.pathDepth + 1);
|
||||
if (this.pluginSettings.pathDepth > 0) {
|
||||
const partsToSkipCount = Math.max(0, parentPathParts.length - this.pluginSettings.pathDepth + 1);
|
||||
if (partsToSkipCount > 0) {
|
||||
parentPathParts.splice(0, partsToSkipCount);
|
||||
if (this.settings.shouldShowEllipsisForSkippedPathParts) {
|
||||
if (this.pluginSettings.shouldShowEllipsisForSkippedPathParts) {
|
||||
parentPathParts.unshift('...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.settings.shouldReversePathParts) {
|
||||
if (this.pluginSettings.shouldReversePathParts) {
|
||||
parentPathParts.reverse();
|
||||
}
|
||||
|
||||
const pathSeparator = this.settings.shouldReversePathParts ? ' ← ' : '/';
|
||||
const pathSeparator = this.pluginSettings.shouldReversePathParts ? ' \u2190 ' : '/';
|
||||
const parentStr = parentPathParts.join(pathSeparator);
|
||||
|
||||
const container = createDiv({
|
||||
cls: ['backlink-full-path', 'backlink-control']
|
||||
});
|
||||
container.dataset['shouldHighlightFileName'] = this.settings.shouldHighlightFileName.toString();
|
||||
container.dataset['shouldDisplayParentPathOnSeparateLine'] = this.settings.shouldDisplayParentPathOnSeparateLine.toString();
|
||||
container.dataset['shouldHighlightFileName'] = this.pluginSettings.shouldHighlightFileName.toString();
|
||||
container.dataset['shouldDisplayParentPathOnSeparateLine'] = this.pluginSettings.shouldDisplayParentPathOnSeparateLine.toString();
|
||||
container.createSpan({
|
||||
cls: 'full-path',
|
||||
text: file.path
|
||||
|
|
@ -128,14 +182,14 @@ export class Plugin extends PluginBase<PluginTypes> {
|
|||
|
||||
if (parentStr) {
|
||||
let text = parentStr;
|
||||
if (!this.settings.shouldDisplayParentPathOnSeparateLine) {
|
||||
text = this.settings.shouldReversePathParts ? pathSeparator + text : text + pathSeparator;
|
||||
if (!this.pluginSettings.shouldDisplayParentPathOnSeparateLine) {
|
||||
text = this.pluginSettings.shouldReversePathParts ? pathSeparator + text : text + pathSeparator;
|
||||
}
|
||||
shadowRoot.createSpan({
|
||||
attr: {
|
||||
part: 'parent-path'
|
||||
},
|
||||
prepend: !this.settings.shouldReversePathParts && !this.settings.shouldDisplayParentPathOnSeparateLine,
|
||||
prepend: !this.pluginSettings.shouldReversePathParts && !this.pluginSettings.shouldDisplayParentPathOnSeparateLine,
|
||||
text
|
||||
});
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
"compilerOptions": {
|
||||
"allowArbitraryExtensions": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
|
|
@ -22,8 +21,7 @@
|
|||
"types": [
|
||||
"@total-typescript/ts-reset",
|
||||
"node",
|
||||
"obsidian-typings",
|
||||
"svelte"
|
||||
"obsidian-typings"
|
||||
],
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
|
|
@ -31,7 +29,8 @@
|
|||
"./.markdownlint-cli2.mts",
|
||||
"./commitlint.config.ts",
|
||||
"./eslint.config.mts",
|
||||
"./scripts/**/*.ts",
|
||||
"./src/**/*.ts",
|
||||
"./scripts/**/*.ts"
|
||||
"./vitest.config.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
3
vitest.config.ts
Normal file
3
vitest.config.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { config } from './scripts/vitest-config.ts';
|
||||
|
||||
export default config;
|
||||
Loading…
Reference in a new issue