11 Commits

Author SHA1 Message Date
8caed69151 chore: match version to tag for deployment test (1.0.1-alpha.1) 2025-10-28 23:39:43 -04:00
c4fe9d82d2 chore: set version to 1.0.1 in code files (tag is 1.0.1-alpha.1 for testing) 2025-10-28 23:38:28 -04:00
8ca46b911a chore: bump version to 1.0.1-alpha.1 for deployment testing 2025-10-28 23:34:34 -04:00
b6722fa3ad docs: update changelog for ObsidianReviewBot fixes 2025-10-28 23:24:56 -04:00
296a8de55b docs: add implementation plan for ObsidianReviewBot fixes 2025-10-28 23:23:48 -04:00
6135f7c708 refactor: extract inline styles from notification-history to CSS
Moved 36 inline style assignments from notification-history.ts to CSS classes in styles.css following the mcp-* naming pattern. This improves maintainability and separates presentation from logic.

Changes:
- Created CSS classes for all static styles (mcp-history-filters, mcp-history-count, mcp-history-list, mcp-history-empty, mcp-history-entry, mcp-history-entry-header, mcp-history-entry-header-meta, mcp-history-entry-args, mcp-history-entry-error, mcp-history-actions)
- Created dynamic state classes for conditional styling (mcp-history-entry-border, mcp-history-entry-title-success, mcp-history-entry-title-error)
- Updated notification-history.ts to use CSS classes via addClass() instead of inline style assignments
- Retained only truly dynamic styles (borderBottom conditional, color conditional) as class toggles

All tests pass (716/716), build succeeds.
2025-10-28 23:11:30 -04:00
9c14ad8c1f refactor: extract inline styles to CSS classes
Replace 90+ JavaScript style assignments with semantic CSS classes in
settings panel. Improves maintainability and follows Obsidian plugin
guidelines requiring styles in CSS files rather than JavaScript.

Changes:
- Add semantic CSS classes to styles.css for auth sections, tabs,
  config display, labels, and helper text
- Replace all .style.* assignments in settings.ts with CSS classes
- Use conditional class application for dynamic tab active state
- Preserve all existing functionality and visual appearance

Addresses ObsidianReviewBot requirement for PR #8298
2025-10-28 19:57:38 -04:00
c9d7aeb0c3 fix: use fileManager.trashFile instead of vault.delete
Replace vault.delete() with fileManager.trashFile() to respect user's
trash preferences configured in Obsidian settings. This ensures deleted
files go to the user's configured trash location instead of being
permanently deleted without respecting system preferences.

Changes:
- src/tools/note-tools.ts: Replace vault.delete with fileManager.trashFile
  in createNote (overwrite conflict) and deleteNote (permanent delete)
- tests/note-tools.test.ts: Update test expectations to check for
  fileManager.trashFile calls instead of vault.delete

Addresses ObsidianReviewBot required issue #3.
2025-10-28 19:52:35 -04:00
862ad9d122 fix: update command names per plugin guidelines
Remove 'MCP Server' prefix from command display names to comply with
Obsidian plugin guidelines. Command IDs remain unchanged for API stability.

- Start MCP Server → Start server
- Stop MCP Server → Stop server
- Restart MCP Server → Restart server
2025-10-28 19:46:19 -04:00
0fbc4e352c docs: update config path examples to use generic folders
Replace hardcoded .obsidian references in exclude pattern examples
with generic 'templates' folder. Config directory location is
user-configurable in Obsidian, so examples should not reference
system directories.

Addresses ObsidianReviewBot feedback for plugin submission.
2025-10-28 19:40:29 -04:00
0d152f3675 docs: add design document for ObsidianReviewBot fixes 2025-10-28 19:35:18 -04:00
14 changed files with 1035 additions and 193 deletions

View File

@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
---
## [1.0.1] - 2025-10-28
### Fixed
- Updated config path examples from `.obsidian/**` to `templates/**` in tool descriptions to avoid implying hardcoded configuration directory paths
- Removed "MCP Server" from command display names per Obsidian plugin guidelines (commands now show as "Start server", "Stop server", etc.)
- Replaced deprecated `vault.delete()` with `app.fileManager.trashFile()` to respect user's trash preferences configured in Obsidian settings
- Extracted all inline JavaScript styles to semantic CSS classes with `mcp-*` namespace for better maintainability and Obsidian plugin compliance
- Applied CSS extraction to notification history modal for consistency
### Changed
- Command palette entries now display shorter names without redundant plugin name prefix
- File deletion operations now respect user's configured trash location (system trash or `.trash/` folder)
- Settings panel and notification history UI now use centralized CSS classes instead of inline styles
---
## [1.0.0] - 2025-10-26
### 🎉 Initial Public Release

View File

@@ -0,0 +1,213 @@
# ObsidianReviewBot Fixes Design
**Date:** 2025-10-28
**Status:** Approved
**PR:** https://github.com/obsidianmd/obsidian-releases/pull/8298
## Overview
This design addresses all required issues identified by ObsidianReviewBot for the MCP Server plugin submission to the Obsidian community plugin repository.
## Required Fixes
1. **Config path documentation** - Update hardcoded `.obsidian` examples to generic alternatives
2. **Command naming** - Remove "MCP Server" from command display names
3. **File deletion API** - Replace `vault.delete()` with `app.fileManager.trashFile()`
4. **Inline styles** - Extract 90+ JavaScript style assignments to CSS with semantic class names
## Implementation Strategy
**Approach:** Fix-by-fix across files - Complete one type of fix across all affected files before moving to the next fix type.
**Benefits:**
- Groups related changes together for clearer git history
- Easier to test each fix type independently
- Simpler code review with focused commits
## Fix Order and Details
### Fix 1: Config Path Documentation
**Files affected:** `src/tools/index.ts`
**Changes:**
- Line 235: Update exclude pattern example from `['.obsidian/**', '*.tmp']` to `['templates/**', '*.tmp']`
- Line 300: Same update for consistency
**Rationale:** Obsidian's configuration directory isn't necessarily `.obsidian` - users can configure this. Examples should use generic folders rather than system directories.
**Risk:** None - documentation only, no functional changes
### Fix 2: Command Naming
**Files affected:** `src/main.ts`
**Changes:**
- Line 54: "Start MCP Server" → "Start server"
- Line 62: "Stop MCP Server" → "Stop server"
- Line 70: "Restart MCP Server" → "Restart server"
**Note:** Command IDs remain unchanged (stable API requirement)
**Rationale:** Obsidian plugin guidelines state command names should not include the plugin name itself.
**Risk:** Low - purely cosmetic change to command palette display
### Fix 3: File Deletion API
**Files affected:** `src/tools/note-tools.ts`
**Changes:**
- Line 162: `await this.vault.delete(existingFile)``await this.fileManager.trashFile(existingFile)`
- Line 546: `await this.vault.delete(file)``await this.fileManager.trashFile(file)`
**Context:**
- Line 162: Overwrite conflict resolution when creating files
- Line 546: Permanent delete operation (when soft=false)
**Rationale:** Use `app.fileManager.trashFile()` instead of direct deletion to respect user's trash preferences configured in Obsidian settings.
**Risk:** Medium - changes deletion behavior, requires testing both scenarios
**Testing:**
- Verify overwrite conflict resolution still works
- Verify permanent delete operation respects user preferences
- Confirm files go to user's configured trash location
### Fix 4: Inline Styles to CSS
**Files affected:**
- `styles.css` (add new classes)
- `src/settings.ts` (remove inline styles, add CSS classes)
**New CSS Classes:**
```css
/* Authentication section */
.mcp-auth-section { margin-bottom: 20px; }
.mcp-auth-summary {
font-size: 1.17em;
font-weight: bold;
margin-bottom: 12px;
cursor: pointer;
}
/* API key display */
.mcp-key-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
word-break: break-all;
user-select: all;
cursor: text;
margin-bottom: 16px;
}
/* Tab navigation */
.mcp-config-tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
border-bottom: 1px solid var(--background-modifier-border);
}
.mcp-tab {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.mcp-tab-active {
border-bottom-color: var(--interactive-accent);
font-weight: bold;
}
/* Config display */
.mcp-config-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-size: 0.85em;
overflow-x: auto;
user-select: text;
cursor: text;
margin-bottom: 12px;
}
/* Helper text */
.mcp-file-path {
padding: 8px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
margin-bottom: 12px;
color: var(--text-muted);
}
.mcp-usage-note {
font-size: 0.9em;
color: var(--text-muted);
font-style: italic;
}
/* Additional utility classes */
.mcp-heading {
margin-top: 24px;
margin-bottom: 12px;
}
.mcp-container { margin-bottom: 20px; }
.mcp-button-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.mcp-label {
margin-bottom: 4px;
font-size: 0.9em;
color: var(--text-muted);
}
```
**Changes to settings.ts:**
- Remove all `.style.` property assignments (90+ lines)
- Add corresponding CSS class names using `.addClass()` or `className` property
- Preserve dynamic styling for tab active state (use conditional class application)
**Rationale:** Obsidian plugin guidelines require styles to be in CSS files rather than applied via JavaScript. This improves maintainability and follows platform conventions.
**Risk:** High - largest refactor, visual regression possible
**Testing:**
- Build and load in Obsidian
- Verify settings panel appearance unchanged in both light and dark themes
- Test all interactive elements: collapsible sections, tabs, buttons
- Confirm responsive behavior
## Testing Strategy
**After each fix:**
1. Run `npm test` - ensure no test failures
2. Run `npm run build` - verify TypeScript compilation
3. Check for linting issues
**Before final commit:**
1. Full test suite passes
2. Clean build with no warnings
3. Manual smoke test of all settings UI features
4. Visual verification in both light and dark themes
## Success Criteria
- All 4 ObsidianReviewBot required issues resolved
- No test regressions
- No visual regressions in settings panel
- Clean build with no TypeScript errors
- Ready for PR re-submission

View File

@@ -0,0 +1,555 @@
# ObsidianReviewBot Fixes Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Fix all required issues identified by ObsidianReviewBot for plugin submission to Obsidian community repository.
**Architecture:** Fix-by-fix approach across all affected files - complete one type of fix across all files before moving to next fix. Order: documentation → command naming → file deletion API → inline styles extraction.
**Tech Stack:** TypeScript, Obsidian API, CSS, Jest
---
## Task 1: Fix Config Path Documentation
**Files:**
- Modify: `src/tools/index.ts:235`
- Modify: `src/tools/index.ts:300`
**Step 1: Update first exclude pattern example (line 235)**
In `src/tools/index.ts`, find line 235 and change the example from `.obsidian/**` to a generic folder:
```typescript
description: "Glob patterns to exclude (e.g., ['templates/**', '*.tmp']). Files matching these patterns will be skipped. Takes precedence over includes."
```
**Step 2: Update second exclude pattern example (line 300)**
In `src/tools/index.ts`, find line 300 and make the same change:
```typescript
description: "Glob patterns to exclude (e.g., ['templates/**', '*.tmp']). Takes precedence over includes."
```
**Step 3: Verify changes**
Run: `npm run build`
Expected: Clean build with no TypeScript errors
**Step 4: Commit**
```bash
git add src/tools/index.ts
git commit -m "fix: use generic folder in exclude pattern examples
- Replace .obsidian references with templates folder
- Obsidian config directory can be customized by users
- Addresses ObsidianReviewBot feedback"
```
---
## Task 2: Fix Command Names
**Files:**
- Modify: `src/main.ts:54`
- Modify: `src/main.ts:62`
- Modify: `src/main.ts:70`
**Step 1: Update "Start MCP Server" command name**
In `src/main.ts`, find the command registration at line 52-58:
```typescript
this.addCommand({
id: 'start-mcp-server',
name: 'Start server',
callback: async () => {
await this.startServer();
}
});
```
**Step 2: Update "Stop MCP Server" command name**
In `src/main.ts`, find the command registration at line 60-66:
```typescript
this.addCommand({
id: 'stop-mcp-server',
name: 'Stop server',
callback: async () => {
await this.stopServer();
}
});
```
**Step 3: Update "Restart MCP Server" command name**
In `src/main.ts`, find the command registration at line 68-74:
```typescript
this.addCommand({
id: 'restart-mcp-server',
name: 'Restart server',
callback: async () => {
await this.stopServer();
await this.startServer();
}
});
```
**Step 4: Verify changes**
Run: `npm run build`
Expected: Clean build with no TypeScript errors
**Step 5: Run tests**
Run: `npm test`
Expected: All 716 tests pass
**Step 6: Commit**
```bash
git add src/main.ts
git commit -m "fix: remove plugin name from command display names
- 'Start MCP Server' → 'Start server'
- 'Stop MCP Server' → 'Stop server'
- 'Restart MCP Server' → 'Restart server'
- Command IDs unchanged (stable API)
- Addresses ObsidianReviewBot feedback"
```
---
## Task 3: Fix File Deletion API
**Files:**
- Modify: `src/tools/note-tools.ts:162`
- Modify: `src/tools/note-tools.ts:546`
**Step 1: Replace vault.delete() in overwrite scenario (line 162)**
In `src/tools/note-tools.ts`, find the overwrite conflict resolution code around line 157-163:
```typescript
} else if (onConflict === 'overwrite') {
// Delete existing file before creating
const existingFile = PathUtils.resolveFile(this.app, normalizedPath);
/* istanbul ignore next */
if (existingFile) {
await this.fileManager.trashFile(existingFile);
}
}
```
**Step 2: Replace vault.delete() in permanent delete (line 546)**
In `src/tools/note-tools.ts`, find the permanent deletion code around line 544-547:
```typescript
} else {
// Permanent deletion
await this.fileManager.trashFile(file);
}
```
**Step 3: Verify changes**
Run: `npm run build`
Expected: Clean build with no TypeScript errors
**Step 4: Run tests**
Run: `npm test`
Expected: All 716 tests pass (the test mocks should handle both APIs)
**Step 5: Run specific note-tools tests**
Run: `npm test -- tests/note-tools.test.ts`
Expected: All note-tools tests pass, including:
- createNote with onConflict='overwrite'
- deleteNote with soft=false
**Step 6: Commit**
```bash
git add src/tools/note-tools.ts
git commit -m "fix: use fileManager.trashFile instead of vault.delete
- Replace vault.delete() with app.fileManager.trashFile()
- Respects user's trash preferences in Obsidian settings
- Applies to both overwrite conflicts and permanent deletes
- Addresses ObsidianReviewBot feedback"
```
---
## Task 4: Extract Inline Styles to CSS
**Files:**
- Modify: `styles.css` (add new classes)
- Modify: `src/settings.ts` (remove inline styles, add CSS classes)
**Step 1: Add CSS classes to styles.css**
Append the following CSS classes to `styles.css`:
```css
/* MCP Settings Panel Styles */
/* Authentication section */
.mcp-auth-section {
margin-bottom: 20px;
}
.mcp-auth-summary {
font-size: 1.17em;
font-weight: bold;
margin-bottom: 12px;
cursor: pointer;
}
/* API key display */
.mcp-api-key-container {
margin-bottom: 20px;
margin-left: 0;
}
.mcp-button-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.mcp-key-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
word-break: break-all;
user-select: all;
cursor: text;
margin-bottom: 16px;
}
/* Headings and containers */
.mcp-heading {
margin-top: 24px;
margin-bottom: 12px;
}
.mcp-container {
margin-bottom: 20px;
}
/* Tab navigation */
.mcp-tab-container {
display: flex;
gap: 8px;
margin-bottom: 16px;
border-bottom: 1px solid var(--background-modifier-border);
}
.mcp-tab {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
border-bottom: 2px solid transparent;
font-weight: normal;
}
.mcp-tab-active {
border-bottom-color: var(--interactive-accent);
font-weight: bold;
}
/* Tab content */
.mcp-tab-content {
margin-top: 16px;
}
/* Labels and helper text */
.mcp-label {
margin-bottom: 4px;
font-size: 0.9em;
color: var(--text-muted);
}
.mcp-file-path {
padding: 8px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
margin-bottom: 12px;
color: var(--text-muted);
}
.mcp-usage-note {
font-size: 0.9em;
color: var(--text-muted);
font-style: italic;
}
/* Config display */
.mcp-config-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-size: 0.85em;
overflow-x: auto;
user-select: text;
cursor: text;
margin-bottom: 12px;
}
/* Copy button spacing */
.mcp-copy-button {
margin-bottom: 12px;
}
/* Notification section */
.mcp-notif-section {
margin-bottom: 20px;
}
.mcp-notif-summary {
font-size: 1.17em;
font-weight: bold;
margin-bottom: 12px;
cursor: pointer;
}
```
**Step 2: Update authentication section in settings.ts (lines 199-205)**
In `src/settings.ts`, find the `displayAuthenticationDetails` method around line 199 and replace inline styles:
```typescript
const authDetails = containerEl.createEl('details', { cls: 'mcp-auth-section' });
authDetails.open = true;
const authSummary = authDetails.createEl('summary', {
text: 'Authentication',
cls: 'mcp-auth-summary'
});
```
**Step 3: Update API key container styles (lines 217-224)**
Replace:
```typescript
const apiKeyContainer = containerEl.createDiv({ cls: 'mcp-api-key-container' });
const apiKeyButtonContainer = apiKeyContainer.createDiv({ cls: 'mcp-button-group' });
```
**Step 4: Update key display container styles (lines 247-255)**
Replace:
```typescript
const keyDisplayContainer = apiKeyContainer.createDiv({
text: apiKey,
cls: 'mcp-key-display'
});
```
**Step 5: Update config section headings (lines 260-264)**
Replace:
```typescript
const configHeading = containerEl.createEl('h3', {
text: 'Connection Configuration',
cls: 'mcp-heading'
});
const configContainer = containerEl.createDiv({ cls: 'mcp-container' });
```
**Step 6: Update tab container styles (lines 271-285)**
Replace the tab container creation:
```typescript
const tabContainer = configContainer.createDiv({ cls: 'mcp-tab-container' });
const windsurfTab = tabContainer.createEl('button', {
text: 'Windsurf',
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
const claudeCodeTab = tabContainer.createEl('button', {
text: 'Claude Code',
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
```
**Step 7: Update tab content and labels (lines 311-327)**
Replace:
```typescript
const tabContent = configContainer.createDiv({ cls: 'mcp-tab-content' });
const fileLocationLabel = tabContent.createDiv({
text: 'Configuration file location:',
cls: 'mcp-label'
});
const filePathDisplay = tabContent.createDiv({
text: filePath,
cls: 'mcp-file-path'
});
const copyConfigButton = tabContent.createEl('button', {
text: 'Copy to Clipboard',
cls: 'mcp-copy-button'
});
```
**Step 8: Update config display (lines 339-346)**
Replace:
```typescript
const configDisplay = tabContent.createEl('pre', { cls: 'mcp-config-display' });
const usageNoteDisplay = tabContent.createDiv({
text: usageNote,
cls: 'mcp-usage-note'
});
```
**Step 9: Update notification section (lines 357-362)**
Replace:
```typescript
const notifDetails = containerEl.createEl('details', { cls: 'mcp-notif-section' });
notifDetails.open = false;
const notifSummary = notifDetails.createEl('summary', {
text: 'Notification Settings',
cls: 'mcp-notif-summary'
});
```
**Step 10: Update updateConfigTabDisplay method (lines 439-521)**
Find the `updateConfigTabDisplay` method and update the tab button styling to use CSS classes with conditional application:
```typescript
private updateConfigTabDisplay(containerEl: HTMLElement) {
// ... existing code ...
const tabContainer = containerEl.createDiv({ cls: 'mcp-tab-container' });
const windsurfTab = tabContainer.createEl('button', {
text: 'Windsurf',
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
const claudeCodeTab = tabContainer.createEl('button', {
text: 'Claude Code',
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
// Update tab content with CSS classes
const tabContent = containerEl.createDiv({ cls: 'mcp-tab-content' });
const fileLocationLabel = tabContent.createDiv({
text: 'Configuration file location:',
cls: 'mcp-label'
});
const filePathDisplay = tabContent.createDiv({
text: filePath,
cls: 'mcp-file-path'
});
const copyConfigButton = tabContent.createEl('button', {
text: 'Copy to Clipboard',
cls: 'mcp-copy-button'
});
const configDisplay = tabContent.createEl('pre', { cls: 'mcp-config-display' });
const usageNoteDisplay = tabContent.createDiv({
text: usageNote,
cls: 'mcp-usage-note'
});
}
```
**Step 11: Verify all inline styles removed**
Run: `grep -n "\.style\." src/settings.ts`
Expected: No matches (or only legitimate dynamic styling that can't be in CSS)
**Step 12: Build and verify**
Run: `npm run build`
Expected: Clean build with no TypeScript errors
**Step 13: Run tests**
Run: `npm test`
Expected: All 716 tests pass
**Step 14: Commit**
```bash
git add styles.css src/settings.ts
git commit -m "fix: extract inline styles to CSS with semantic classes
- Add mcp-* prefixed CSS classes for all settings UI elements
- Remove 90+ inline style assignments from settings.ts
- Use Obsidian CSS variables for theming compatibility
- Preserve dynamic tab active state with conditional classes
- Addresses ObsidianReviewBot feedback"
```
---
## Task 5: Final Verification
**Step 1: Run full test suite**
Run: `npm test`
Expected: All 716 tests pass
**Step 2: Run build**
Run: `npm run build`
Expected: Clean build, no errors, no warnings
**Step 3: Check git status**
Run: `git status`
Expected: Clean working tree, all changes committed
**Step 4: Review commit history**
Run: `git log --oneline -5`
Expected: See all 4 fix commits plus design doc commit
**Step 5: Manual testing checklist (if Obsidian available)**
If you can test in Obsidian:
1. Copy built files to `.obsidian/plugins/mcp-server/`
2. Reload Obsidian
3. Open Settings → MCP Server
4. Verify settings panel appearance identical to before
5. Test both light and dark themes
6. Verify collapsible sections work
7. Verify tab switching works
8. Test command palette shows updated command names
---
## Success Criteria
✅ All 4 ObsidianReviewBot required issues fixed
✅ No test regressions (716 tests passing)
✅ Clean TypeScript build
✅ Settings panel visually unchanged
✅ All changes committed with clear messages
✅ Ready for PR re-submission

View File

@@ -1,7 +1,7 @@
{
"id": "mcp-server",
"name": "MCP Server",
"version": "1.0.0",
"version": "1.0.1-alpha.1",
"minAppVersion": "0.15.0",
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
"author": "William Ballou",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "mcp-server",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mcp-server",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "mcp-server",
"version": "1.0.0",
"version": "1.0.1-alpha.1",
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
"main": "main.js",
"scripts": {

View File

@@ -51,7 +51,7 @@ export default class MCPServerPlugin extends Plugin {
// Register commands
this.addCommand({
id: 'start-mcp-server',
name: 'Start MCP Server',
name: 'Start server',
callback: async () => {
await this.startServer();
}
@@ -59,7 +59,7 @@ export default class MCPServerPlugin extends Plugin {
this.addCommand({
id: 'stop-mcp-server',
name: 'Stop MCP Server',
name: 'Stop server',
callback: async () => {
await this.stopServer();
}
@@ -67,7 +67,7 @@ export default class MCPServerPlugin extends Plugin {
this.addCommand({
id: 'restart-mcp-server',
name: 'Restart MCP Server',
name: 'Restart server',
callback: async () => {
await this.stopServer();
await this.startServer();
@@ -76,7 +76,7 @@ export default class MCPServerPlugin extends Plugin {
this.addCommand({
id: 'view-notification-history',
name: 'View MCP Notification History',
name: 'View notification history',
callback: () => {
this.showNotificationHistory();
}

View File

@@ -195,13 +195,8 @@ export class MCPServerSettingTab extends PluginSettingTab {
}));
// Authentication (Always Enabled)
const authDetails = containerEl.createEl('details');
authDetails.style.marginBottom = '20px';
const authSummary = authDetails.createEl('summary');
authSummary.style.fontSize = '1.17em';
authSummary.style.fontWeight = 'bold';
authSummary.style.marginBottom = '12px';
authSummary.style.cursor = 'pointer';
const authDetails = containerEl.createEl('details', {cls: 'mcp-auth-section'});
const authSummary = authDetails.createEl('summary', {cls: 'mcp-auth-summary'});
authSummary.setText('Authentication & Configuration');
// Store reference for targeted updates
@@ -213,15 +208,10 @@ export class MCPServerSettingTab extends PluginSettingTab {
.setDesc('Use as Bearer token in Authorization header');
// Create a full-width container for buttons and key display
const apiKeyContainer = authDetails.createDiv({cls: 'mcp-api-key-section'});
apiKeyContainer.style.marginBottom = '20px';
apiKeyContainer.style.marginLeft = '0';
const apiKeyContainer = authDetails.createDiv({cls: 'mcp-container'});
// Create button container
const apiKeyButtonContainer = apiKeyContainer.createDiv({cls: 'mcp-api-key-buttons'});
apiKeyButtonContainer.style.display = 'flex';
apiKeyButtonContainer.style.gap = '8px';
apiKeyButtonContainer.style.marginBottom = '12px';
const apiKeyButtonContainer = apiKeyContainer.createDiv({cls: 'mcp-button-group'});
// Copy button
const copyButton = apiKeyButtonContainer.createEl('button', {text: '📋 Copy Key'});
@@ -243,61 +233,35 @@ export class MCPServerSettingTab extends PluginSettingTab {
});
// API Key display (static, copyable text)
const keyDisplayContainer = apiKeyContainer.createDiv({cls: 'mcp-api-key-display'});
keyDisplayContainer.style.padding = '12px';
keyDisplayContainer.style.backgroundColor = 'var(--background-secondary)';
keyDisplayContainer.style.borderRadius = '4px';
keyDisplayContainer.style.fontFamily = 'monospace';
keyDisplayContainer.style.fontSize = '0.9em';
keyDisplayContainer.style.wordBreak = 'break-all';
keyDisplayContainer.style.userSelect = 'all';
keyDisplayContainer.style.cursor = 'text';
keyDisplayContainer.style.marginBottom = '16px';
const keyDisplayContainer = apiKeyContainer.createDiv({cls: 'mcp-key-display'});
keyDisplayContainer.textContent = this.plugin.settings.apiKey || '';
// MCP Client Configuration heading
const configHeading = authDetails.createEl('h4', {text: 'MCP Client Configuration'});
configHeading.style.marginTop = '24px';
configHeading.style.marginBottom = '12px';
authDetails.createEl('h4', {text: 'MCP Client Configuration', cls: 'mcp-heading'});
const configContainer = authDetails.createDiv({cls: 'mcp-config-snippet'});
configContainer.style.marginBottom = '20px';
const configContainer = authDetails.createDiv({cls: 'mcp-container'});
// Store reference for targeted updates
this.configContainerEl = configContainer;
// Tab buttons for switching between clients
const tabContainer = configContainer.createDiv({cls: 'mcp-config-tabs'});
tabContainer.style.display = 'flex';
tabContainer.style.gap = '8px';
tabContainer.style.marginBottom = '16px';
tabContainer.style.borderBottom = '1px solid var(--background-modifier-border)';
// Windsurf tab button
const windsurfTab = tabContainer.createEl('button', {text: 'Windsurf'});
windsurfTab.style.padding = '8px 16px';
windsurfTab.style.border = 'none';
windsurfTab.style.background = 'none';
windsurfTab.style.cursor = 'pointer';
windsurfTab.style.borderBottom = this.activeConfigTab === 'windsurf'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
windsurfTab.style.fontWeight = this.activeConfigTab === 'windsurf' ? 'bold' : 'normal';
const windsurfTab = tabContainer.createEl('button', {
text: 'Windsurf',
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
windsurfTab.addEventListener('click', () => {
this.activeConfigTab = 'windsurf';
this.updateConfigSection();
});
// Claude Code tab button
const claudeCodeTab = tabContainer.createEl('button', {text: 'Claude Code'});
claudeCodeTab.style.padding = '8px 16px';
claudeCodeTab.style.border = 'none';
claudeCodeTab.style.background = 'none';
claudeCodeTab.style.cursor = 'pointer';
claudeCodeTab.style.borderBottom = this.activeConfigTab === 'claude-code'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
claudeCodeTab.style.fontWeight = this.activeConfigTab === 'claude-code' ? 'bold' : 'normal';
const claudeCodeTab = tabContainer.createEl('button', {
text: 'Claude Code',
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
claudeCodeTab.addEventListener('click', () => {
this.activeConfigTab = 'claude-code';
this.updateConfigSection();
@@ -308,58 +272,33 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Tab content area
const tabContent = configContainer.createDiv({cls: 'mcp-config-content'});
tabContent.style.marginTop = '16px';
// File location label
const fileLocationLabel = tabContent.createEl('p', {text: 'Configuration file location:'});
fileLocationLabel.style.marginBottom = '4px';
fileLocationLabel.style.fontSize = '0.9em';
fileLocationLabel.style.color = 'var(--text-muted)';
tabContent.createEl('p', {text: 'Configuration file location:', cls: 'mcp-label'});
// File path display
const filePathDisplay = tabContent.createEl('div', {text: filePath});
filePathDisplay.style.padding = '8px';
filePathDisplay.style.backgroundColor = 'var(--background-secondary)';
filePathDisplay.style.borderRadius = '4px';
filePathDisplay.style.fontFamily = 'monospace';
filePathDisplay.style.fontSize = '0.9em';
filePathDisplay.style.marginBottom = '12px';
filePathDisplay.style.color = 'var(--text-muted)';
tabContent.createEl('div', {text: filePath, cls: 'mcp-file-path'});
// Copy button
const copyConfigButton = tabContent.createEl('button', {text: '📋 Copy Configuration'});
copyConfigButton.style.marginBottom = '12px';
const copyConfigButton = tabContent.createEl('button', {
text: '📋 Copy Configuration',
cls: 'mcp-config-button'
});
copyConfigButton.addEventListener('click', async () => {
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
new Notice('✅ Configuration copied to clipboard');
});
// Config JSON display
const configDisplay = tabContent.createEl('pre');
configDisplay.style.padding = '12px';
configDisplay.style.backgroundColor = 'var(--background-secondary)';
configDisplay.style.borderRadius = '4px';
configDisplay.style.fontSize = '0.85em';
configDisplay.style.overflowX = 'auto';
configDisplay.style.userSelect = 'text';
configDisplay.style.cursor = 'text';
configDisplay.style.marginBottom = '12px';
const configDisplay = tabContent.createEl('pre', {cls: 'mcp-config-display'});
configDisplay.textContent = JSON.stringify(config, null, 2);
// Usage note
const usageNoteDisplay = tabContent.createEl('p', {text: usageNote});
usageNoteDisplay.style.fontSize = '0.9em';
usageNoteDisplay.style.color = 'var(--text-muted)';
usageNoteDisplay.style.fontStyle = 'italic';
tabContent.createEl('p', {text: usageNote, cls: 'mcp-usage-note'});
// Notification Settings
const notifDetails = containerEl.createEl('details');
notifDetails.style.marginBottom = '20px';
const notifSummary = notifDetails.createEl('summary');
notifSummary.style.fontSize = '1.17em';
notifSummary.style.fontWeight = 'bold';
notifSummary.style.marginBottom = '12px';
notifSummary.style.cursor = 'pointer';
const notifDetails = containerEl.createEl('details', {cls: 'mcp-auth-section'});
const notifSummary = notifDetails.createEl('summary', {cls: 'mcp-auth-summary'});
notifSummary.setText('UI Notifications');
// Store reference for targeted updates
@@ -436,36 +375,22 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Tab buttons for switching between clients
const tabContainer = this.configContainerEl.createDiv({cls: 'mcp-config-tabs'});
tabContainer.style.display = 'flex';
tabContainer.style.gap = '8px';
tabContainer.style.marginBottom = '16px';
tabContainer.style.borderBottom = '1px solid var(--background-modifier-border)';
// Windsurf tab button
const windsurfTab = tabContainer.createEl('button', {text: 'Windsurf'});
windsurfTab.style.padding = '8px 16px';
windsurfTab.style.border = 'none';
windsurfTab.style.background = 'none';
windsurfTab.style.cursor = 'pointer';
windsurfTab.style.borderBottom = this.activeConfigTab === 'windsurf'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
windsurfTab.style.fontWeight = this.activeConfigTab === 'windsurf' ? 'bold' : 'normal';
const windsurfTab = tabContainer.createEl('button', {
text: 'Windsurf',
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
windsurfTab.addEventListener('click', () => {
this.activeConfigTab = 'windsurf';
this.updateConfigSection();
});
// Claude Code tab button
const claudeCodeTab = tabContainer.createEl('button', {text: 'Claude Code'});
claudeCodeTab.style.padding = '8px 16px';
claudeCodeTab.style.border = 'none';
claudeCodeTab.style.background = 'none';
claudeCodeTab.style.cursor = 'pointer';
claudeCodeTab.style.borderBottom = this.activeConfigTab === 'claude-code'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
claudeCodeTab.style.fontWeight = this.activeConfigTab === 'claude-code' ? 'bold' : 'normal';
const claudeCodeTab = tabContainer.createEl('button', {
text: 'Claude Code',
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
});
claudeCodeTab.addEventListener('click', () => {
this.activeConfigTab = 'claude-code';
this.updateConfigSection();
@@ -476,49 +401,29 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Tab content area
const tabContent = this.configContainerEl.createDiv({cls: 'mcp-config-content'});
tabContent.style.marginTop = '16px';
// File location label
const fileLocationLabel = tabContent.createEl('p', {text: 'Configuration file location:'});
fileLocationLabel.style.marginBottom = '4px';
fileLocationLabel.style.fontSize = '0.9em';
fileLocationLabel.style.color = 'var(--text-muted)';
tabContent.createEl('p', {text: 'Configuration file location:', cls: 'mcp-label'});
// File path display
const filePathDisplay = tabContent.createEl('div', {text: filePath});
filePathDisplay.style.padding = '8px';
filePathDisplay.style.backgroundColor = 'var(--background-secondary)';
filePathDisplay.style.borderRadius = '4px';
filePathDisplay.style.fontFamily = 'monospace';
filePathDisplay.style.fontSize = '0.9em';
filePathDisplay.style.marginBottom = '12px';
filePathDisplay.style.color = 'var(--text-muted)';
tabContent.createEl('div', {text: filePath, cls: 'mcp-file-path'});
// Copy button
const copyConfigButton = tabContent.createEl('button', {text: '📋 Copy Configuration'});
copyConfigButton.style.marginBottom = '12px';
const copyConfigButton = tabContent.createEl('button', {
text: '📋 Copy Configuration',
cls: 'mcp-config-button'
});
copyConfigButton.addEventListener('click', async () => {
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
new Notice('✅ Configuration copied to clipboard');
});
// Config JSON display
const configDisplay = tabContent.createEl('pre');
configDisplay.style.padding = '12px';
configDisplay.style.backgroundColor = 'var(--background-secondary)';
configDisplay.style.borderRadius = '4px';
configDisplay.style.fontSize = '0.85em';
configDisplay.style.overflowX = 'auto';
configDisplay.style.userSelect = 'text';
configDisplay.style.cursor = 'text';
configDisplay.style.marginBottom = '12px';
const configDisplay = tabContent.createEl('pre', {cls: 'mcp-config-display'});
configDisplay.textContent = JSON.stringify(config, null, 2);
// Usage note
const usageNoteDisplay = tabContent.createEl('p', {text: usageNote});
usageNoteDisplay.style.fontSize = '0.9em';
usageNoteDisplay.style.color = 'var(--text-muted)';
usageNoteDisplay.style.fontStyle = 'italic';
tabContent.createEl('p', {text: usageNote, cls: 'mcp-usage-note'});
// Restore open state (only if authDetailsEl is available)
if (this.authDetailsEl) {

View File

@@ -232,7 +232,7 @@ export class ToolRegistry {
excludes: {
type: "array",
items: { type: "string" },
description: "Glob patterns to exclude (e.g., ['.obsidian/**', '*.tmp']). Files matching these patterns will be skipped. Takes precedence over includes."
description: "Glob patterns to exclude (e.g., ['templates/**', '*.tmp']). Files matching these patterns will be skipped. Takes precedence over includes."
},
folder: {
type: "string",
@@ -297,7 +297,7 @@ export class ToolRegistry {
excludes: {
type: "array",
items: { type: "string" },
description: "Glob patterns to exclude (e.g., ['.obsidian/**', '*.tmp']). Takes precedence over includes."
description: "Glob patterns to exclude (e.g., ['templates/**', '*.tmp']). Takes precedence over includes."
},
only: {
type: "string",

View File

@@ -159,7 +159,7 @@ export class NoteTools {
const existingFile = PathUtils.resolveFile(this.app, normalizedPath);
/* istanbul ignore next */
if (existingFile) {
await this.vault.delete(existingFile);
await this.fileManager.trashFile(existingFile);
}
} else if (onConflict === 'rename') {
// Generate a unique name
@@ -542,8 +542,8 @@ export class NoteTools {
await this.vault.trash(file, true);
destination = `.trash/${file.name}`;
} else {
// Permanent deletion
await this.vault.delete(file);
// Delete using user's preferred trash settings (system trash or .trash/ folder)
await this.fileManager.trashFile(file);
}
const result: DeleteNoteResult = {

View File

@@ -50,7 +50,6 @@ export class NotificationHistoryModal extends Modal {
*/
private createFilters(containerEl: HTMLElement): void {
const filterContainer = containerEl.createDiv({ cls: 'mcp-history-filters' });
filterContainer.style.marginBottom = '16px';
// Tool name filter using Setting component
new Setting(filterContainer)
@@ -80,9 +79,6 @@ export class NotificationHistoryModal extends Modal {
// Results count
this.countEl = filterContainer.createDiv({ cls: 'mcp-history-count' });
this.countEl.style.marginTop = '8px';
this.countEl.style.fontSize = '0.9em';
this.countEl.style.color = 'var(--text-muted)';
this.updateResultsCount();
}
@@ -91,11 +87,6 @@ export class NotificationHistoryModal extends Modal {
*/
private createHistoryListContainer(containerEl: HTMLElement): void {
this.listContainerEl = containerEl.createDiv({ cls: 'mcp-history-list' });
this.listContainerEl.style.maxHeight = '400px';
this.listContainerEl.style.overflowY = 'auto';
this.listContainerEl.style.marginBottom = '16px';
this.listContainerEl.style.border = '1px solid var(--background-modifier-border)';
this.listContainerEl.style.borderRadius = '4px';
// Initial render
this.updateHistoryList();
@@ -112,36 +103,31 @@ export class NotificationHistoryModal extends Modal {
if (this.filteredHistory.length === 0) {
const emptyEl = this.listContainerEl.createDiv({ cls: 'mcp-history-empty' });
emptyEl.style.padding = '24px';
emptyEl.style.textAlign = 'center';
emptyEl.style.color = 'var(--text-muted)';
emptyEl.textContent = 'No entries found';
return;
}
this.filteredHistory.forEach((entry, index) => {
const entryEl = this.listContainerEl!.createDiv({ cls: 'mcp-history-entry' });
entryEl.style.padding = '12px';
entryEl.style.borderBottom = index < this.filteredHistory.length - 1
? '1px solid var(--background-modifier-border)'
: 'none';
// Add border class to all entries except the last one
if (index < this.filteredHistory.length - 1) {
entryEl.addClass('mcp-history-entry-border');
}
// Header row
const headerEl = entryEl.createDiv({ cls: 'mcp-history-entry-header' });
headerEl.style.display = 'flex';
headerEl.style.justifyContent = 'space-between';
headerEl.style.marginBottom = '8px';
// Tool name and status
const titleEl = headerEl.createDiv();
const statusIcon = entry.success ? '✅' : '❌';
const toolName = titleEl.createEl('strong', { text: `${statusIcon} ${entry.toolName}` });
toolName.style.color = entry.success ? 'var(--text-success)' : 'var(--text-error)';
// Add dynamic color class based on success/error
toolName.addClass(entry.success ? 'mcp-history-entry-title-success' : 'mcp-history-entry-title-error');
// Timestamp and duration
const metaEl = headerEl.createDiv();
metaEl.style.fontSize = '0.85em';
metaEl.style.color = 'var(--text-muted)';
const metaEl = headerEl.createDiv({ cls: 'mcp-history-entry-header-meta' });
const timestamp = new Date(entry.timestamp).toLocaleTimeString();
const durationStr = entry.duration ? `${entry.duration}ms` : '';
metaEl.textContent = `${timestamp}${durationStr}`;
@@ -149,25 +135,12 @@ export class NotificationHistoryModal extends Modal {
// Arguments
if (entry.args && Object.keys(entry.args).length > 0) {
const argsEl = entryEl.createDiv({ cls: 'mcp-history-entry-args' });
argsEl.style.fontSize = '0.85em';
argsEl.style.fontFamily = 'monospace';
argsEl.style.backgroundColor = 'var(--background-secondary)';
argsEl.style.padding = '8px';
argsEl.style.borderRadius = '4px';
argsEl.style.marginBottom = '8px';
argsEl.style.overflowX = 'auto';
argsEl.textContent = JSON.stringify(entry.args, null, 2);
}
// Error message
if (!entry.success && entry.error) {
const errorEl = entryEl.createDiv({ cls: 'mcp-history-entry-error' });
errorEl.style.fontSize = '0.85em';
errorEl.style.color = 'var(--text-error)';
errorEl.style.backgroundColor = 'var(--background-secondary)';
errorEl.style.padding = '8px';
errorEl.style.borderRadius = '4px';
errorEl.style.fontFamily = 'monospace';
errorEl.textContent = entry.error;
}
});
@@ -186,9 +159,6 @@ export class NotificationHistoryModal extends Modal {
*/
private createActions(containerEl: HTMLElement): void {
const actionsContainer = containerEl.createDiv({ cls: 'mcp-history-actions' });
actionsContainer.style.display = 'flex';
actionsContainer.style.gap = '8px';
actionsContainer.style.justifyContent = 'flex-end';
// Export button
const exportButton = actionsContainer.createEl('button', { text: 'Export to Clipboard' });

View File

@@ -51,3 +51,185 @@
margin: 0.5em 0 0.25em 0;
font-weight: 600;
}
/* Authentication section */
.mcp-auth-section { margin-bottom: 20px; }
.mcp-auth-summary {
font-size: 1.17em;
font-weight: bold;
margin-bottom: 12px;
cursor: pointer;
}
/* API key display */
.mcp-key-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
word-break: break-all;
user-select: all;
cursor: text;
margin-bottom: 16px;
}
/* Tab navigation */
.mcp-config-tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
border-bottom: 1px solid var(--background-modifier-border);
}
.mcp-tab {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.mcp-tab-active {
border-bottom-color: var(--interactive-accent);
font-weight: bold;
}
/* Config display */
.mcp-config-display {
padding: 12px;
background-color: var(--background-secondary);
border-radius: 4px;
font-size: 0.85em;
overflow-x: auto;
user-select: text;
cursor: text;
margin-bottom: 12px;
}
/* Helper text */
.mcp-file-path {
padding: 8px;
background-color: var(--background-secondary);
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
margin-bottom: 12px;
color: var(--text-muted);
}
.mcp-usage-note {
font-size: 0.9em;
color: var(--text-muted);
font-style: italic;
}
/* Additional utility classes */
.mcp-heading {
margin-top: 24px;
margin-bottom: 12px;
}
.mcp-container { margin-bottom: 20px; }
.mcp-button-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.mcp-label {
margin-bottom: 4px;
font-size: 0.9em;
color: var(--text-muted);
}
.mcp-config-content {
margin-top: 16px;
}
.mcp-config-button {
margin-bottom: 12px;
}
/* Notification History Modal */
.mcp-notification-history-modal {
/* Base modal styling handled by Obsidian */
}
.mcp-history-filters {
margin-bottom: 16px;
}
.mcp-history-count {
margin-top: 8px;
font-size: 0.9em;
color: var(--text-muted);
}
.mcp-history-list {
max-height: 400px;
overflow-y: auto;
margin-bottom: 16px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
}
.mcp-history-empty {
padding: 24px;
text-align: center;
color: var(--text-muted);
}
.mcp-history-entry {
padding: 12px;
}
.mcp-history-entry-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.mcp-history-entry-header-meta {
font-size: 0.85em;
color: var(--text-muted);
}
.mcp-history-entry-args {
font-size: 0.85em;
font-family: monospace;
background-color: var(--background-secondary);
padding: 8px;
border-radius: 4px;
margin-bottom: 8px;
overflow-x: auto;
}
.mcp-history-entry-error {
font-size: 0.85em;
color: var(--text-error);
background-color: var(--background-secondary);
padding: 8px;
border-radius: 4px;
font-family: monospace;
}
.mcp-history-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
/* Dynamic state classes */
.mcp-history-entry-border {
border-bottom: 1px solid var(--background-modifier-border);
}
.mcp-history-entry-title-success {
color: var(--text-success);
}
.mcp-history-entry-title-error {
color: var(--text-error);
}

View File

@@ -137,7 +137,7 @@ describe('NoteTools', () => {
(PathUtils.fileExists as jest.Mock).mockReturnValue(true);
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
mockVault.delete = jest.fn().mockResolvedValue(undefined);
mockFileManager.trashFile = jest.fn().mockResolvedValue(undefined);
mockVault.create = jest.fn().mockResolvedValue(mockFile);
(PathUtils.folderExists as jest.Mock).mockReturnValue(false);
(PathUtils.getParentPath as jest.Mock).mockReturnValue('');
@@ -145,7 +145,7 @@ describe('NoteTools', () => {
const result = await noteTools.createNote('test.md', 'content', false, 'overwrite');
expect(result.isError).toBeUndefined();
expect(mockVault.delete).toHaveBeenCalledWith(mockFile);
expect(mockFileManager.trashFile).toHaveBeenCalledWith(mockFile);
expect(mockVault.create).toHaveBeenCalled();
});
@@ -344,12 +344,12 @@ describe('NoteTools', () => {
const mockFile = createMockTFile('test.md');
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
mockVault.delete = jest.fn().mockResolvedValue(undefined);
mockFileManager.trashFile = jest.fn().mockResolvedValue(undefined);
const result = await noteTools.deleteNote('test.md', false, false);
expect(result.isError).toBeUndefined();
expect(mockVault.delete).toHaveBeenCalledWith(mockFile);
expect(mockFileManager.trashFile).toHaveBeenCalledWith(mockFile);
const parsed = JSON.parse(result.content[0].text);
expect(parsed.deleted).toBe(true);
expect(parsed.soft).toBe(false);

View File

@@ -1,3 +1,4 @@
{
"1.0.0": "0.15.0"
"1.0.0": "0.15.0",
"1.0.1-alpha.1": "0.15.0"
}