Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c574a237ce | |||
| 8caed69151 | |||
| c4fe9d82d2 | |||
| 8ca46b911a | |||
| b6722fa3ad | |||
| 296a8de55b | |||
| 6135f7c708 | |||
| 9c14ad8c1f | |||
| c9d7aeb0c3 | |||
| 862ad9d122 | |||
| 0fbc4e352c | |||
| 0d152f3675 |
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
|
## [1.0.0] - 2025-10-26
|
||||||
|
|
||||||
### 🎉 Initial Public Release
|
### 🎉 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",
|
"id": "mcp-server",
|
||||||
"name": "MCP Server",
|
"name": "MCP Server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
|
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
|
||||||
"author": "William Ballou",
|
"author": "William Ballou",
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-server",
|
"name": "mcp-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mcp-server",
|
"name": "mcp-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-server",
|
"name": "mcp-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
|
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
// Register commands
|
// Register commands
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'start-mcp-server',
|
id: 'start-mcp-server',
|
||||||
name: 'Start MCP Server',
|
name: 'Start server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.startServer();
|
await this.startServer();
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'stop-mcp-server',
|
id: 'stop-mcp-server',
|
||||||
name: 'Stop MCP Server',
|
name: 'Stop server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.stopServer();
|
await this.stopServer();
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'restart-mcp-server',
|
id: 'restart-mcp-server',
|
||||||
name: 'Restart MCP Server',
|
name: 'Restart server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.stopServer();
|
await this.stopServer();
|
||||||
await this.startServer();
|
await this.startServer();
|
||||||
@@ -76,7 +76,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'view-notification-history',
|
id: 'view-notification-history',
|
||||||
name: 'View MCP Notification History',
|
name: 'View notification history',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.showNotificationHistory();
|
this.showNotificationHistory();
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/settings.ts
177
src/settings.ts
@@ -195,13 +195,8 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Authentication (Always Enabled)
|
// Authentication (Always Enabled)
|
||||||
const authDetails = containerEl.createEl('details');
|
const authDetails = containerEl.createEl('details', {cls: 'mcp-auth-section'});
|
||||||
authDetails.style.marginBottom = '20px';
|
const authSummary = authDetails.createEl('summary', {cls: 'mcp-auth-summary'});
|
||||||
const authSummary = authDetails.createEl('summary');
|
|
||||||
authSummary.style.fontSize = '1.17em';
|
|
||||||
authSummary.style.fontWeight = 'bold';
|
|
||||||
authSummary.style.marginBottom = '12px';
|
|
||||||
authSummary.style.cursor = 'pointer';
|
|
||||||
authSummary.setText('Authentication & Configuration');
|
authSummary.setText('Authentication & Configuration');
|
||||||
|
|
||||||
// Store reference for targeted updates
|
// Store reference for targeted updates
|
||||||
@@ -213,15 +208,10 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
.setDesc('Use as Bearer token in Authorization header');
|
.setDesc('Use as Bearer token in Authorization header');
|
||||||
|
|
||||||
// Create a full-width container for buttons and key display
|
// Create a full-width container for buttons and key display
|
||||||
const apiKeyContainer = authDetails.createDiv({cls: 'mcp-api-key-section'});
|
const apiKeyContainer = authDetails.createDiv({cls: 'mcp-container'});
|
||||||
apiKeyContainer.style.marginBottom = '20px';
|
|
||||||
apiKeyContainer.style.marginLeft = '0';
|
|
||||||
|
|
||||||
// Create button container
|
// Create button container
|
||||||
const apiKeyButtonContainer = apiKeyContainer.createDiv({cls: 'mcp-api-key-buttons'});
|
const apiKeyButtonContainer = apiKeyContainer.createDiv({cls: 'mcp-button-group'});
|
||||||
apiKeyButtonContainer.style.display = 'flex';
|
|
||||||
apiKeyButtonContainer.style.gap = '8px';
|
|
||||||
apiKeyButtonContainer.style.marginBottom = '12px';
|
|
||||||
|
|
||||||
// Copy button
|
// Copy button
|
||||||
const copyButton = apiKeyButtonContainer.createEl('button', {text: '📋 Copy Key'});
|
const copyButton = apiKeyButtonContainer.createEl('button', {text: '📋 Copy Key'});
|
||||||
@@ -243,61 +233,35 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// API Key display (static, copyable text)
|
// API Key display (static, copyable text)
|
||||||
const keyDisplayContainer = apiKeyContainer.createDiv({cls: 'mcp-api-key-display'});
|
const keyDisplayContainer = apiKeyContainer.createDiv({cls: 'mcp-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';
|
|
||||||
keyDisplayContainer.textContent = this.plugin.settings.apiKey || '';
|
keyDisplayContainer.textContent = this.plugin.settings.apiKey || '';
|
||||||
|
|
||||||
// MCP Client Configuration heading
|
// MCP Client Configuration heading
|
||||||
const configHeading = authDetails.createEl('h4', {text: 'MCP Client Configuration'});
|
authDetails.createEl('h4', {text: 'MCP Client Configuration', cls: 'mcp-heading'});
|
||||||
configHeading.style.marginTop = '24px';
|
|
||||||
configHeading.style.marginBottom = '12px';
|
|
||||||
|
|
||||||
const configContainer = authDetails.createDiv({cls: 'mcp-config-snippet'});
|
const configContainer = authDetails.createDiv({cls: 'mcp-container'});
|
||||||
configContainer.style.marginBottom = '20px';
|
|
||||||
|
|
||||||
// Store reference for targeted updates
|
// Store reference for targeted updates
|
||||||
this.configContainerEl = configContainer;
|
this.configContainerEl = configContainer;
|
||||||
|
|
||||||
// Tab buttons for switching between clients
|
// Tab buttons for switching between clients
|
||||||
const tabContainer = configContainer.createDiv({cls: 'mcp-config-tabs'});
|
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
|
// Windsurf tab button
|
||||||
const windsurfTab = tabContainer.createEl('button', {text: 'Windsurf'});
|
const windsurfTab = tabContainer.createEl('button', {
|
||||||
windsurfTab.style.padding = '8px 16px';
|
text: 'Windsurf',
|
||||||
windsurfTab.style.border = 'none';
|
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
|
||||||
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';
|
|
||||||
windsurfTab.addEventListener('click', () => {
|
windsurfTab.addEventListener('click', () => {
|
||||||
this.activeConfigTab = 'windsurf';
|
this.activeConfigTab = 'windsurf';
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Claude Code tab button
|
// Claude Code tab button
|
||||||
const claudeCodeTab = tabContainer.createEl('button', {text: 'Claude Code'});
|
const claudeCodeTab = tabContainer.createEl('button', {
|
||||||
claudeCodeTab.style.padding = '8px 16px';
|
text: 'Claude Code',
|
||||||
claudeCodeTab.style.border = 'none';
|
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
|
||||||
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';
|
|
||||||
claudeCodeTab.addEventListener('click', () => {
|
claudeCodeTab.addEventListener('click', () => {
|
||||||
this.activeConfigTab = 'claude-code';
|
this.activeConfigTab = 'claude-code';
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
@@ -308,58 +272,33 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
// Tab content area
|
// Tab content area
|
||||||
const tabContent = configContainer.createDiv({cls: 'mcp-config-content'});
|
const tabContent = configContainer.createDiv({cls: 'mcp-config-content'});
|
||||||
tabContent.style.marginTop = '16px';
|
|
||||||
|
|
||||||
// File location label
|
// File location label
|
||||||
const fileLocationLabel = tabContent.createEl('p', {text: 'Configuration file location:'});
|
tabContent.createEl('p', {text: 'Configuration file location:', cls: 'mcp-label'});
|
||||||
fileLocationLabel.style.marginBottom = '4px';
|
|
||||||
fileLocationLabel.style.fontSize = '0.9em';
|
|
||||||
fileLocationLabel.style.color = 'var(--text-muted)';
|
|
||||||
|
|
||||||
// File path display
|
// File path display
|
||||||
const filePathDisplay = tabContent.createEl('div', {text: filePath});
|
tabContent.createEl('div', {text: filePath, cls: 'mcp-file-path'});
|
||||||
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)';
|
|
||||||
|
|
||||||
// Copy button
|
// Copy button
|
||||||
const copyConfigButton = tabContent.createEl('button', {text: '📋 Copy Configuration'});
|
const copyConfigButton = tabContent.createEl('button', {
|
||||||
copyConfigButton.style.marginBottom = '12px';
|
text: '📋 Copy Configuration',
|
||||||
|
cls: 'mcp-config-button'
|
||||||
|
});
|
||||||
copyConfigButton.addEventListener('click', async () => {
|
copyConfigButton.addEventListener('click', async () => {
|
||||||
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
|
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
|
||||||
new Notice('✅ Configuration copied to clipboard');
|
new Notice('✅ Configuration copied to clipboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Config JSON display
|
// Config JSON display
|
||||||
const configDisplay = tabContent.createEl('pre');
|
const configDisplay = tabContent.createEl('pre', {cls: 'mcp-config-display'});
|
||||||
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';
|
|
||||||
configDisplay.textContent = JSON.stringify(config, null, 2);
|
configDisplay.textContent = JSON.stringify(config, null, 2);
|
||||||
|
|
||||||
// Usage note
|
// Usage note
|
||||||
const usageNoteDisplay = tabContent.createEl('p', {text: usageNote});
|
tabContent.createEl('p', {text: usageNote, cls: 'mcp-usage-note'});
|
||||||
usageNoteDisplay.style.fontSize = '0.9em';
|
|
||||||
usageNoteDisplay.style.color = 'var(--text-muted)';
|
|
||||||
usageNoteDisplay.style.fontStyle = 'italic';
|
|
||||||
|
|
||||||
// Notification Settings
|
// Notification Settings
|
||||||
const notifDetails = containerEl.createEl('details');
|
const notifDetails = containerEl.createEl('details', {cls: 'mcp-auth-section'});
|
||||||
notifDetails.style.marginBottom = '20px';
|
const notifSummary = notifDetails.createEl('summary', {cls: 'mcp-auth-summary'});
|
||||||
const notifSummary = notifDetails.createEl('summary');
|
|
||||||
notifSummary.style.fontSize = '1.17em';
|
|
||||||
notifSummary.style.fontWeight = 'bold';
|
|
||||||
notifSummary.style.marginBottom = '12px';
|
|
||||||
notifSummary.style.cursor = 'pointer';
|
|
||||||
notifSummary.setText('UI Notifications');
|
notifSummary.setText('UI Notifications');
|
||||||
|
|
||||||
// Store reference for targeted updates
|
// Store reference for targeted updates
|
||||||
@@ -436,36 +375,22 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
// Tab buttons for switching between clients
|
// Tab buttons for switching between clients
|
||||||
const tabContainer = this.configContainerEl.createDiv({cls: 'mcp-config-tabs'});
|
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
|
// Windsurf tab button
|
||||||
const windsurfTab = tabContainer.createEl('button', {text: 'Windsurf'});
|
const windsurfTab = tabContainer.createEl('button', {
|
||||||
windsurfTab.style.padding = '8px 16px';
|
text: 'Windsurf',
|
||||||
windsurfTab.style.border = 'none';
|
cls: this.activeConfigTab === 'windsurf' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
|
||||||
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';
|
|
||||||
windsurfTab.addEventListener('click', () => {
|
windsurfTab.addEventListener('click', () => {
|
||||||
this.activeConfigTab = 'windsurf';
|
this.activeConfigTab = 'windsurf';
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Claude Code tab button
|
// Claude Code tab button
|
||||||
const claudeCodeTab = tabContainer.createEl('button', {text: 'Claude Code'});
|
const claudeCodeTab = tabContainer.createEl('button', {
|
||||||
claudeCodeTab.style.padding = '8px 16px';
|
text: 'Claude Code',
|
||||||
claudeCodeTab.style.border = 'none';
|
cls: this.activeConfigTab === 'claude-code' ? 'mcp-tab mcp-tab-active' : 'mcp-tab'
|
||||||
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';
|
|
||||||
claudeCodeTab.addEventListener('click', () => {
|
claudeCodeTab.addEventListener('click', () => {
|
||||||
this.activeConfigTab = 'claude-code';
|
this.activeConfigTab = 'claude-code';
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
@@ -476,49 +401,29 @@ export class MCPServerSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
// Tab content area
|
// Tab content area
|
||||||
const tabContent = this.configContainerEl.createDiv({cls: 'mcp-config-content'});
|
const tabContent = this.configContainerEl.createDiv({cls: 'mcp-config-content'});
|
||||||
tabContent.style.marginTop = '16px';
|
|
||||||
|
|
||||||
// File location label
|
// File location label
|
||||||
const fileLocationLabel = tabContent.createEl('p', {text: 'Configuration file location:'});
|
tabContent.createEl('p', {text: 'Configuration file location:', cls: 'mcp-label'});
|
||||||
fileLocationLabel.style.marginBottom = '4px';
|
|
||||||
fileLocationLabel.style.fontSize = '0.9em';
|
|
||||||
fileLocationLabel.style.color = 'var(--text-muted)';
|
|
||||||
|
|
||||||
// File path display
|
// File path display
|
||||||
const filePathDisplay = tabContent.createEl('div', {text: filePath});
|
tabContent.createEl('div', {text: filePath, cls: 'mcp-file-path'});
|
||||||
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)';
|
|
||||||
|
|
||||||
// Copy button
|
// Copy button
|
||||||
const copyConfigButton = tabContent.createEl('button', {text: '📋 Copy Configuration'});
|
const copyConfigButton = tabContent.createEl('button', {
|
||||||
copyConfigButton.style.marginBottom = '12px';
|
text: '📋 Copy Configuration',
|
||||||
|
cls: 'mcp-config-button'
|
||||||
|
});
|
||||||
copyConfigButton.addEventListener('click', async () => {
|
copyConfigButton.addEventListener('click', async () => {
|
||||||
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
|
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
|
||||||
new Notice('✅ Configuration copied to clipboard');
|
new Notice('✅ Configuration copied to clipboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Config JSON display
|
// Config JSON display
|
||||||
const configDisplay = tabContent.createEl('pre');
|
const configDisplay = tabContent.createEl('pre', {cls: 'mcp-config-display'});
|
||||||
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';
|
|
||||||
configDisplay.textContent = JSON.stringify(config, null, 2);
|
configDisplay.textContent = JSON.stringify(config, null, 2);
|
||||||
|
|
||||||
// Usage note
|
// Usage note
|
||||||
const usageNoteDisplay = tabContent.createEl('p', {text: usageNote});
|
tabContent.createEl('p', {text: usageNote, cls: 'mcp-usage-note'});
|
||||||
usageNoteDisplay.style.fontSize = '0.9em';
|
|
||||||
usageNoteDisplay.style.color = 'var(--text-muted)';
|
|
||||||
usageNoteDisplay.style.fontStyle = 'italic';
|
|
||||||
|
|
||||||
// Restore open state (only if authDetailsEl is available)
|
// Restore open state (only if authDetailsEl is available)
|
||||||
if (this.authDetailsEl) {
|
if (this.authDetailsEl) {
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export class ToolRegistry {
|
|||||||
excludes: {
|
excludes: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: { type: "string" },
|
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: {
|
folder: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -297,7 +297,7 @@ export class ToolRegistry {
|
|||||||
excludes: {
|
excludes: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: { type: "string" },
|
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: {
|
only: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export class NoteTools {
|
|||||||
const existingFile = PathUtils.resolveFile(this.app, normalizedPath);
|
const existingFile = PathUtils.resolveFile(this.app, normalizedPath);
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (existingFile) {
|
if (existingFile) {
|
||||||
await this.vault.delete(existingFile);
|
await this.fileManager.trashFile(existingFile);
|
||||||
}
|
}
|
||||||
} else if (onConflict === 'rename') {
|
} else if (onConflict === 'rename') {
|
||||||
// Generate a unique name
|
// Generate a unique name
|
||||||
@@ -542,8 +542,8 @@ export class NoteTools {
|
|||||||
await this.vault.trash(file, true);
|
await this.vault.trash(file, true);
|
||||||
destination = `.trash/${file.name}`;
|
destination = `.trash/${file.name}`;
|
||||||
} else {
|
} else {
|
||||||
// Permanent deletion
|
// Delete using user's preferred trash settings (system trash or .trash/ folder)
|
||||||
await this.vault.delete(file);
|
await this.fileManager.trashFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: DeleteNoteResult = {
|
const result: DeleteNoteResult = {
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
*/
|
*/
|
||||||
private createFilters(containerEl: HTMLElement): void {
|
private createFilters(containerEl: HTMLElement): void {
|
||||||
const filterContainer = containerEl.createDiv({ cls: 'mcp-history-filters' });
|
const filterContainer = containerEl.createDiv({ cls: 'mcp-history-filters' });
|
||||||
filterContainer.style.marginBottom = '16px';
|
|
||||||
|
|
||||||
// Tool name filter using Setting component
|
// Tool name filter using Setting component
|
||||||
new Setting(filterContainer)
|
new Setting(filterContainer)
|
||||||
@@ -80,9 +79,6 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
|
|
||||||
// Results count
|
// Results count
|
||||||
this.countEl = filterContainer.createDiv({ cls: 'mcp-history-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();
|
this.updateResultsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +87,6 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
*/
|
*/
|
||||||
private createHistoryListContainer(containerEl: HTMLElement): void {
|
private createHistoryListContainer(containerEl: HTMLElement): void {
|
||||||
this.listContainerEl = containerEl.createDiv({ cls: 'mcp-history-list' });
|
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
|
// Initial render
|
||||||
this.updateHistoryList();
|
this.updateHistoryList();
|
||||||
@@ -112,36 +103,31 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
|
|
||||||
if (this.filteredHistory.length === 0) {
|
if (this.filteredHistory.length === 0) {
|
||||||
const emptyEl = this.listContainerEl.createDiv({ cls: 'mcp-history-empty' });
|
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';
|
emptyEl.textContent = 'No entries found';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filteredHistory.forEach((entry, index) => {
|
this.filteredHistory.forEach((entry, index) => {
|
||||||
const entryEl = this.listContainerEl!.createDiv({ cls: 'mcp-history-entry' });
|
const entryEl = this.listContainerEl!.createDiv({ cls: 'mcp-history-entry' });
|
||||||
entryEl.style.padding = '12px';
|
|
||||||
entryEl.style.borderBottom = index < this.filteredHistory.length - 1
|
// Add border class to all entries except the last one
|
||||||
? '1px solid var(--background-modifier-border)'
|
if (index < this.filteredHistory.length - 1) {
|
||||||
: 'none';
|
entryEl.addClass('mcp-history-entry-border');
|
||||||
|
}
|
||||||
|
|
||||||
// Header row
|
// Header row
|
||||||
const headerEl = entryEl.createDiv({ cls: 'mcp-history-entry-header' });
|
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
|
// Tool name and status
|
||||||
const titleEl = headerEl.createDiv();
|
const titleEl = headerEl.createDiv();
|
||||||
const statusIcon = entry.success ? '✅' : '❌';
|
const statusIcon = entry.success ? '✅' : '❌';
|
||||||
const toolName = titleEl.createEl('strong', { text: `${statusIcon} ${entry.toolName}` });
|
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
|
// Timestamp and duration
|
||||||
const metaEl = headerEl.createDiv();
|
const metaEl = headerEl.createDiv({ cls: 'mcp-history-entry-header-meta' });
|
||||||
metaEl.style.fontSize = '0.85em';
|
|
||||||
metaEl.style.color = 'var(--text-muted)';
|
|
||||||
const timestamp = new Date(entry.timestamp).toLocaleTimeString();
|
const timestamp = new Date(entry.timestamp).toLocaleTimeString();
|
||||||
const durationStr = entry.duration ? ` • ${entry.duration}ms` : '';
|
const durationStr = entry.duration ? ` • ${entry.duration}ms` : '';
|
||||||
metaEl.textContent = `${timestamp}${durationStr}`;
|
metaEl.textContent = `${timestamp}${durationStr}`;
|
||||||
@@ -149,25 +135,12 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
// Arguments
|
// Arguments
|
||||||
if (entry.args && Object.keys(entry.args).length > 0) {
|
if (entry.args && Object.keys(entry.args).length > 0) {
|
||||||
const argsEl = entryEl.createDiv({ cls: 'mcp-history-entry-args' });
|
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);
|
argsEl.textContent = JSON.stringify(entry.args, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error message
|
// Error message
|
||||||
if (!entry.success && entry.error) {
|
if (!entry.success && entry.error) {
|
||||||
const errorEl = entryEl.createDiv({ cls: 'mcp-history-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;
|
errorEl.textContent = entry.error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -186,9 +159,6 @@ export class NotificationHistoryModal extends Modal {
|
|||||||
*/
|
*/
|
||||||
private createActions(containerEl: HTMLElement): void {
|
private createActions(containerEl: HTMLElement): void {
|
||||||
const actionsContainer = containerEl.createDiv({ cls: 'mcp-history-actions' });
|
const actionsContainer = containerEl.createDiv({ cls: 'mcp-history-actions' });
|
||||||
actionsContainer.style.display = 'flex';
|
|
||||||
actionsContainer.style.gap = '8px';
|
|
||||||
actionsContainer.style.justifyContent = 'flex-end';
|
|
||||||
|
|
||||||
// Export button
|
// Export button
|
||||||
const exportButton = actionsContainer.createEl('button', { text: 'Export to Clipboard' });
|
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;
|
margin: 0.5em 0 0.25em 0;
|
||||||
font-weight: 600;
|
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.fileExists as jest.Mock).mockReturnValue(true);
|
||||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
(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);
|
mockVault.create = jest.fn().mockResolvedValue(mockFile);
|
||||||
(PathUtils.folderExists as jest.Mock).mockReturnValue(false);
|
(PathUtils.folderExists as jest.Mock).mockReturnValue(false);
|
||||||
(PathUtils.getParentPath as jest.Mock).mockReturnValue('');
|
(PathUtils.getParentPath as jest.Mock).mockReturnValue('');
|
||||||
@@ -145,7 +145,7 @@ describe('NoteTools', () => {
|
|||||||
const result = await noteTools.createNote('test.md', 'content', false, 'overwrite');
|
const result = await noteTools.createNote('test.md', 'content', false, 'overwrite');
|
||||||
|
|
||||||
expect(result.isError).toBeUndefined();
|
expect(result.isError).toBeUndefined();
|
||||||
expect(mockVault.delete).toHaveBeenCalledWith(mockFile);
|
expect(mockFileManager.trashFile).toHaveBeenCalledWith(mockFile);
|
||||||
expect(mockVault.create).toHaveBeenCalled();
|
expect(mockVault.create).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -344,12 +344,12 @@ describe('NoteTools', () => {
|
|||||||
const mockFile = createMockTFile('test.md');
|
const mockFile = createMockTFile('test.md');
|
||||||
|
|
||||||
(PathUtils.resolveFile as jest.Mock).mockReturnValue(mockFile);
|
(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);
|
const result = await noteTools.deleteNote('test.md', false, false);
|
||||||
|
|
||||||
expect(result.isError).toBeUndefined();
|
expect(result.isError).toBeUndefined();
|
||||||
expect(mockVault.delete).toHaveBeenCalledWith(mockFile);
|
expect(mockFileManager.trashFile).toHaveBeenCalledWith(mockFile);
|
||||||
const parsed = JSON.parse(result.content[0].text);
|
const parsed = JSON.parse(result.content[0].text);
|
||||||
expect(parsed.deleted).toBe(true);
|
expect(parsed.deleted).toBe(true);
|
||||||
expect(parsed.soft).toBe(false);
|
expect(parsed.soft).toBe(false);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"1.0.0": "0.15.0"
|
"1.0.0": "0.15.0",
|
||||||
|
"1.0.1": "0.15.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user