Compare commits
14 Commits
1.0.0-alph
...
1.0.1-alph
| Author | SHA1 | Date | |
|---|---|---|---|
| 8caed69151 | |||
| c4fe9d82d2 | |||
| 8ca46b911a | |||
| b6722fa3ad | |||
| 296a8de55b | |||
| 6135f7c708 | |||
| 9c14ad8c1f | |||
| c9d7aeb0c3 | |||
| 862ad9d122 | |||
| 0fbc4e352c | |||
| 0d152f3675 | |||
| 7f82902b5e | |||
| d1eb545fed | |||
| a02ebb85d5 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -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
|
||||
|
||||
213
docs/plans/2025-10-28-obsidian-review-bot-fixes-design.md
Normal file
213
docs/plans/2025-10-28-obsidian-review-bot-fixes-design.md
Normal 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
|
||||
555
docs/plans/2025-10-28-obsidian-review-bot-fixes.md
Normal file
555
docs/plans/2025-10-28-obsidian-review-bot-fixes.md
Normal 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
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "mcp-server",
|
||||
"name": "MCP Server",
|
||||
"version": "1.0.0-alpha.7",
|
||||
"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
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0-alpha.7",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0-alpha.7",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0-alpha.7",
|
||||
"version": "1.0.1-alpha.1",
|
||||
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
223
src/settings.ts
223
src/settings.ts
@@ -6,6 +6,9 @@ import { generateApiKey } from './utils/auth-utils';
|
||||
export class MCPServerSettingTab extends PluginSettingTab {
|
||||
plugin: MCPServerPlugin;
|
||||
private notificationDetailsEl: HTMLDetailsElement | null = null;
|
||||
private notificationToggleEl: HTMLElement | null = null;
|
||||
private authDetailsEl: HTMLDetailsElement | null = null;
|
||||
private configContainerEl: HTMLElement | null = null;
|
||||
private activeConfigTab: 'windsurf' | 'claude-code' = 'windsurf';
|
||||
|
||||
constructor(app: App, plugin: MCPServerPlugin) {
|
||||
@@ -118,8 +121,11 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
// Clear notification details reference for fresh render
|
||||
// Clear references for fresh render
|
||||
this.notificationDetailsEl = null;
|
||||
this.notificationToggleEl = null;
|
||||
this.authDetailsEl = null;
|
||||
this.configContainerEl = null;
|
||||
|
||||
containerEl.createEl('h2', {text: 'MCP Server Settings'});
|
||||
|
||||
@@ -189,30 +195,23 @@ 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
|
||||
this.authDetailsEl = authDetails;
|
||||
|
||||
// API Key Display (always show - auth is always enabled)
|
||||
new Setting(authDetails)
|
||||
.setName('API Key Management')
|
||||
.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'});
|
||||
@@ -234,61 +233,38 @@ 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.display();
|
||||
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.display();
|
||||
this.updateConfigSection();
|
||||
});
|
||||
|
||||
// Get configuration for active tab
|
||||
@@ -296,65 +272,43 @@ 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
|
||||
this.notificationDetailsEl = notifDetails;
|
||||
|
||||
// Enable notifications
|
||||
new Setting(notifDetails)
|
||||
// Enable notifications - create container for the toggle setting
|
||||
const notificationToggleContainer = notifDetails.createDiv({cls: 'mcp-notification-toggle'});
|
||||
this.notificationToggleEl = notificationToggleContainer;
|
||||
|
||||
new Setting(notificationToggleContainer)
|
||||
.setName('Enable notifications')
|
||||
.setDesc('Show when MCP tools are called')
|
||||
.addToggle(toggle => toggle
|
||||
@@ -376,7 +330,7 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
||||
* Update only the notification section without re-rendering entire page
|
||||
*/
|
||||
private updateNotificationSection(): void {
|
||||
if (!this.notificationDetailsEl) {
|
||||
if (!this.notificationDetailsEl || !this.notificationToggleEl) {
|
||||
// Fallback to full re-render if reference lost
|
||||
this.display();
|
||||
return;
|
||||
@@ -385,13 +339,16 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
||||
// Store current open state
|
||||
const wasOpen = this.notificationDetailsEl.open;
|
||||
|
||||
// Find and remove all child elements except the summary
|
||||
// Remove all children except the summary and the toggle container
|
||||
const summary = this.notificationDetailsEl.querySelector('summary');
|
||||
while (this.notificationDetailsEl.lastChild && this.notificationDetailsEl.lastChild !== summary) {
|
||||
this.notificationDetailsEl.removeChild(this.notificationDetailsEl.lastChild);
|
||||
const children = Array.from(this.notificationDetailsEl.children);
|
||||
for (const child of children) {
|
||||
if (child !== summary && child !== this.notificationToggleEl) {
|
||||
this.notificationDetailsEl.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild notification settings
|
||||
// Rebuild notification settings only if enabled
|
||||
if (this.plugin.settings.notificationsEnabled) {
|
||||
this.renderNotificationSettings(this.notificationDetailsEl);
|
||||
}
|
||||
@@ -399,4 +356,78 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
||||
// Restore open state
|
||||
this.notificationDetailsEl.open = wasOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update only the config section without re-rendering entire page
|
||||
*/
|
||||
private updateConfigSection(): void {
|
||||
if (!this.configContainerEl) {
|
||||
// Fallback to full re-render if reference lost
|
||||
this.display();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store current open state of the auth details
|
||||
const wasOpen = this.authDetailsEl?.open ?? false;
|
||||
|
||||
// Clear the config container
|
||||
this.configContainerEl.empty();
|
||||
|
||||
// Tab buttons for switching between clients
|
||||
const tabContainer = this.configContainerEl.createDiv({cls: 'mcp-config-tabs'});
|
||||
|
||||
// Windsurf tab button
|
||||
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',
|
||||
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
|
||||
});
|
||||
claudeCodeTab.addEventListener('click', () => {
|
||||
this.activeConfigTab = 'claude-code';
|
||||
this.updateConfigSection();
|
||||
});
|
||||
|
||||
// Get configuration for active tab
|
||||
const {filePath, config, usageNote} = this.generateConfigForClient(this.activeConfigTab);
|
||||
|
||||
// Tab content area
|
||||
const tabContent = this.configContainerEl.createDiv({cls: 'mcp-config-content'});
|
||||
|
||||
// File location label
|
||||
tabContent.createEl('p', {text: 'Configuration file location:', cls: 'mcp-label'});
|
||||
|
||||
// File path display
|
||||
tabContent.createEl('div', {text: filePath, cls: 'mcp-file-path'});
|
||||
|
||||
// Copy button
|
||||
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', {cls: 'mcp-config-display'});
|
||||
configDisplay.textContent = JSON.stringify(config, null, 2);
|
||||
|
||||
// Usage note
|
||||
tabContent.createEl('p', {text: usageNote, cls: 'mcp-usage-note'});
|
||||
|
||||
// Restore open state (only if authDetailsEl is available)
|
||||
if (this.authDetailsEl) {
|
||||
this.authDetailsEl.open = wasOpen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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' });
|
||||
|
||||
182
styles.css
182
styles.css
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"1.0.0": "0.15.0"
|
||||
"1.0.0": "0.15.0",
|
||||
"1.0.1-alpha.1": "0.15.0"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user