Markdown Converter
Agent skill for markdown-converter
- Initial documentation for UI module
Sign in to like and favorite skills
Root Directory > src > ui
The UI module provides user interface components for the WeWe RSS plugin:
Framework: Obsidian's custom UI framework (extends HTMLElement, Modal, etc.)
UI components are registered in
main.ts:
async onload() { // Register sidebar view this.registerView( VIEW_TYPE_WEWE_RSS, (leaf) => new WeWeRssSidebarView(leaf, this) ); // Add ribbon icon to open sidebar this.addRibbonIcon('rss', 'WeWe RSS', () => { this.activateView(); }); // Register settings tab this.addSettingTab(new WeWeRssSettingTab(this.app, this)); }
User Activation:
Purpose: Main interface for viewing feeds and articles
Location: Right sidebar (docked panel)
View Type:
VIEW_TYPE_WEWE_RSS = 'wewe-rss-sidebar'
class WeWeRssSidebarView extends ItemView { // Lifecycle onload(): void // Build DOM onClose(): Promise<void> // Cleanup // View metadata getViewType(): string // Return 'wewe-rss-sidebar' getDisplayText(): string // Return 'WeWe RSS' getIcon(): string // Return 'rss' // Rendering private renderStats(): void // Stats bar (feeds, articles, sync time) private renderFeedsList(): void // Feed cards private renderArticlesList(): void // Article list private renderEmpty(): void // Empty state }
┌─────────────────────────────────────┐ │ WeWe RSS [X] │ ← Header ├─────────────────────────────────────┤ │ Stats Bar: │ │ 🔢 5 Feeds | 📄 23 Articles │ │ ⏱️ Last sync: 2 mins ago │ ├─────────────────────────────────────┤ │ [+ Account] [+ Feed] [⟳ Sync] │ ← Actions ├─────────────────────────────────────┤ │ Feeds: │ │ ┌─────────────────────────────────┐ │ │ │ 📰 Tech Blog (5 articles)│ │ │ │ 📰 News Feed (12 articles│ │ │ └─────────────────────────────────┘ │ ├─────────────────────────────────────┤ │ Recent Articles: │ │ • Article Title 1 [2h ago] │ │ • Article Title 2 [5h ago] │ │ • Article Title 3 [1d ago] │ └─────────────────────────────────────┘
User Interactions:
Purpose: WeChat Reading login via QR code
Extends:
Modal (Obsidian base class)
class AddAccountModal extends Modal { onOpen(): void { // 1. Call apiClient.createLoginUrl() // 2. Display QR code image // 3. Start long-polling apiClient.getLoginResult() // 4. On success: save account, close modal } }
┌─────────────────────────────────┐ │ Add WeChat Account [X] │ ├─────────────────────────────────┤ │ Scan QR Code with WeChat: │ │ │ │ ┌─────────────┐ │ │ │ QR CODE │ │ │ │ IMAGE │ │ │ └─────────────┘ │ │ │ │ Status: Waiting for scan... │ │ │ │ [Cancel] │ └─────────────────────────────────┘
Auto-Close: Modal closes on successful login or timeout
Purpose: Subscribe to WeChat public account
Extends:
Modal
class AddFeedModal extends Modal { onOpen(): void { // 1. Show input for WeChat share link // 2. Call apiClient.getMpInfo(link) // 3. Display feed preview // 4. On confirm: feedService.subscribeFeed() } }
┌─────────────────────────────────┐ │ Add Feed [X] │ ├─────────────────────────────────┤ │ WeChat Share Link: │ │ ┌─────────────────────────────┐ │ │ │ https://mp.weixin.qq.com/...│ │ │ └─────────────────────────────┘ │ │ │ │ Feed Preview: │ │ Name: Tech Blog │ │ Description: Latest tech news │ │ │ │ ☑ Fetch historical articles │ │ │ │ [Cancel] [Subscribe] │ └─────────────────────────────────┘
Purpose: Main plugin configuration interface (general settings)
Extends:
PluginSettingTab
1. Account Management
2. Sync Settings
3. Note Settings
4. Content Settings
5. Title Filtering
6. API Settings
7. Database Backup
8. Advanced
Purpose: Dedicated interface for AI summarization configuration
Extends:
PluginSettingTab
Location:
src/ui/settings/AISettingsTab.ts (~220 lines)
1. Enable/Disable
2. AI Provider Selection
3. API Configuration
4. Summary Configuration
5. Manual Execution
// Default API Endpoints { 'openai': 'https://api.openai.com/v1', 'gemini': 'https://generativelanguage.googleapis.com/v1', 'claude': 'https://api.anthropic.com/v1', 'deepseek': 'https://api.deepseek.com/v1', 'glm': 'https://open.bigmodel.cn/api/paas/v4', 'generic': 'https://api.openai.com/v1' } // Default Models { 'openai': 'gpt-3.5-turbo', 'gemini': 'gemini-pro', 'claude': 'claude-3-haiku-20240307', 'deepseek': 'deepseek-chat', 'glm': 'glm-4', 'generic': 'gpt-3.5-turbo' }
generateDailySummary() for manual generationplugin.scheduleAutomaticSummarization()WeWeRssSettings interfaceclass WeWeRssSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; containerEl.empty(); // Add settings new Setting(containerEl) .setName('Auto Sync') .setDesc('Enable automatic feed synchronization') .addToggle(toggle => toggle .setValue(this.plugin.settings.autoSync) .onChange(async (value) => { this.plugin.settings.autoSync = value; await this.plugin.saveSettings(); }) ); } }
Base Classes:
ItemView - For custom views (sidebar panels)Modal - For dialog boxesPluginSettingTab - For settings interfaceUtilities:
Setting - Fluent API for form controlssetIcon() - Add icons to elementsNotice - Toast notificationsStyling:
All UI components depend on:
WeWeRssPlugin instanceplugin.accountService, plugin.syncService, etc.)UI components are stateless - they query services on each render:
// No internal state class WeWeRssSidebarView { private renderStats(): void { // Query live data every time const feeds = this.plugin.databaseService.feeds.findAll(); const articles = this.plugin.databaseService.articles.findAll(); // Update DOM } }
Why?
75% coverage (UI components partially tested)
Challenges:
Mock Obsidian APIs:
// src/__tests__/mocks/obsidian.ts export class Modal { open() {} close() {} onOpen() {} } export class ItemView { containerEl = document.createElement('div'); leaf = {}; }
Future Improvement: E2E tests with Electron
Steps:
WeWeRssSettings interface in src/types/index.tsDEFAULT_SETTINGSWeWeRssSettingTab.display()Example:
// types/index.ts export interface WeWeRssSettings { // ...existing fields... newSetting: boolean; } // WeWeRssSettingTab.ts new Setting(containerEl) .setName('New Setting') .setDesc('Description here') .addToggle(toggle => toggle .setValue(this.plugin.settings.newSetting) .onChange(async (value) => { this.plugin.settings.newSetting = value; await this.plugin.saveSettings(); }) );
Pattern:
// Trigger re-render this.renderStats(); this.renderFeedsList(); this.renderArticlesList();
Or force full reload:
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_WEWE_RSS); leaves.forEach(leaf => leaf.detach()); await this.plugin.activateView(); // Re-open
Use Obsidian's Notice:
import { Notice } from 'obsidian'; const notice = new Notice('Syncing...', 0); // 0 = infinite try { await syncService.syncAll(); notice.hide(); new Notice('Sync complete!'); } catch (error) { notice.hide(); new Notice('Sync failed: ' + error.message); }
Technically yes, but:
Alternative: Use Web Components or lit-html for complex UIs
src/ui/views/WeWeRssSidebarView.ts (~400 lines)src/ui/modals/AddAccountModal.ts (~200 lines)src/ui/modals/AddFeedModal.ts (~250 lines)src/ui/modals/CleanupArticlesModal.ts (~150 lines)src/ui/settings/WeWeRssSettingTab.ts (~560 lines)src/ui/settings/AISettingsTab.ts (~220 lines)src/__tests__/unit/ui/AISettingsTab.test.ts (14 tests, 100% passing)styles.css (minimal custom styles)src/__tests__/mocks/obsidian.ts - Obsidian API mockssrc/main.ts - UI registrationsrc/types/index.ts - Settings interfaceLast Updated: 2025-11-19 (Added AISettingsTab documentation)