add help doc reference logic

This commit is contained in:
delphi 2025-03-17 16:26:24 +08:00
parent c057f10934
commit a73f747086
5 changed files with 109 additions and 322 deletions

View file

@ -1,281 +0,0 @@
# Obsidian Cubox Plugin - Development Guide
## Overview
The Obsidian Cubox plugin allows users to sync articles and annotations from Cubox to Obsidian. This document provides a comprehensive guide to the project's architecture, components, and workflows to help developers quickly understand and contribute to the project.
## Project Architecture
The plugin is structured around several core components that work together:
```mermaid
graph TD
A[main.ts] --> B[cuboxApi.ts]
A --> C[templateProcessor.ts]
A --> D[cuboxSetting.ts]
B --> E[utils.ts]
C --> E
C --> F[templateInstructions.ts]
D --> G[Modal Components]
D --> F
G --> H[folderSelectModal.ts]
G --> I[statusSelectModal.ts]
G --> J[tagSelectModal.ts]
G --> K[typeSelectModal.ts]
G --> L[modalStyles.ts]
```
### Core Components
1. **Main Plugin (main.ts)**: Entry point that initializes the plugin, manages syncing, and coordinates between components
2. **API Client (cuboxApi.ts)**: Handles communication with the Cubox service
3. **Template Processor (templateProcessor.ts)**: Processes templates for note filename, metadata, and content
4. **Settings Manager (cuboxSetting.ts)**: Manages plugin settings and UI
5. **Modal Components (modal/*)**: Provides UI for selecting filters and options
6. **Utilities (utils.ts)**: Common utility functions
## Data Flow
```mermaid
sequenceDiagram
participant User
participant Plugin as CuboxSyncPlugin
participant API as CuboxApi
participant TP as TemplateProcessor
participant Vault as Obsidian Vault
User->>Plugin: Triggers sync
Plugin->>API: Request articles with filters
API-->>Plugin: Return article list
loop For each article
Plugin->>API: Get article details
API-->>Plugin: Return article content
Plugin->>TP: Process templates
TP-->>Plugin: Return formatted content
Plugin->>Vault: Create/update note file
end
Plugin-->>User: Display sync results
```
## Key Interfaces
### Cubox Data Structures
```typescript
// Article structure from Cubox API
interface CuboxArticle {
id: string;
title: string;
article_title: string;
description: string;
url: string;
domain: string;
create_time: string;
update_time: string;
word_count: number;
content?: string;
cubox_url: string;
highlights?: CuboxHighlight[];
tags?: string[];
type: string;
}
// Highlight structure from Cubox API
interface CuboxHighlight {
id: string;
text: string;
image_url?: string;
cubox_url: string;
note?: string;
color: string;
create_time: string;
}
// Folder structure from Cubox API
interface CuboxFolder {
id: string;
name: string;
nested_name: string;
uncategorized: boolean;
}
// Tag structure from Cubox API
interface CuboxTag {
id: string;
name: string;
nested_name: string;
parent_id: string | null;
}
```
### Plugin Settings
```typescript
// Main plugin settings
interface CuboxSyncSettings {
domain: string;
apiKey: string;
syncFrequency: number;
targetFolder: string;
filenameTemplate: string;
frontMatterVariables: string[];
contentTemplate: string;
dateFormat: string;
lastSyncTime: number;
lastSyncCardId: string;
lastCardUpdateTime: string;
folderFilter: string[];
typeFilter: string[];
statusFilter: string[];
tagsFilter: string[];
isRead: boolean;
isStarred: boolean;
isAnnotated: boolean;
syncing: boolean;
}
```
## Cubox API Integration
The plugin interacts with the Cubox API through the `CuboxApi` class. Key endpoints include:
1. **Card Filtering**: `/c/api/third-party/card/filter`
- Retrieves articles matching specified filters
- Supports pagination via `last_card_id` and `last_card_update_time`
2. **Content Retrieval**: `/c/api/third-party/card/content`
- Retrieves detailed content of a specific article
3. **Folder List**: `/c/api/third-party/group/list`
- Retrieves user's folder structure
4. **Tag List**: `/c/api/third-party/tag/list`
- Retrieves user's tag list
### Authentication
The API uses Bearer token authentication via the user's Cubox API key:
```typescript
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
}
```
## Template System
The plugin uses [Mustache.js](https://mustache.github.io/) for template rendering with a rich set of variables:
### Filename Template
Variables like `{{title}}`, `{{article_title}}`, `{{create_time}}`, etc.
### Front Matter (Metadata)
Supports all article properties with optional aliases (e.g., `title::customTitle`).
### Content Template
Rich template with support for article content, highlights, and metadata.
Special features:
- `{{content_highlighted}}`: Content with highlights marked using Obsidian's highlight syntax (`==highlighted text==`)
- Nested highlight properties: `{{text}}`, `{{note}}`, `{{color}}`, etc.
## Modal System
The plugin includes custom modal dialogs for selecting:
1. **Folders**: `folderSelectModal.ts`
2. **Status**: `statusSelectModal.ts` (read, starred, annotated)
3. **Tags**: `tagSelectModal.ts`
4. **Types**: `typeSelectModal.ts` (article, webpage, etc.)
These modals share a common styling approach defined in `modalStyles.ts` and provide a consistent UI for filter selection.
## Syncing Workflow
1. **Initialization**:
- Plugin loads settings and initializes API and template processor
- Sets up automatic sync interval if configured
2. **Sync Process**:
- Ensures target folder exists
- Fetches articles matching filters from Cubox
- For each article, retrieves content and processes templates
- Creates/updates files in the target folder
- Tracks last sync position for pagination
- Skips existing files based on Cubox ID in front matter
3. **Deduplication**:
- Files are identified by their Cubox ID stored in front matter
- Prevents duplicate imports of the same content
## Utilities
The `utils.ts` module provides helper functions for:
1. **File Safety**: Handling illegal characters in filenames
2. **Date Formatting**: Using Luxon for consistent date formatting
## Dependencies
- **[Obsidian API](https://github.com/obsidianmd/obsidian-api)**: Core API for Obsidian integration
- **[Mustache.js](https://mustache.github.io/)**: Template rendering
- **[Luxon](https://moment.github.io/luxon/)**: Date manipulation and formatting
## Development Workflow
1. Clone the repository
2. Install dependencies: `npm install`
3. Build the plugin: `npm run build`
4. For testing, copy or symlink the build output to your Obsidian plugins folder
## Extending the Plugin
### Adding New Filters
1. Create a new modal component in the `modal/` directory
2. Add appropriate settings in `cuboxSetting.ts`
3. Update API parameters in `cuboxApi.ts`
### Enhancing Templates
1. Add new variables to the template system in `templateProcessor.ts`
2. Update template instructions in `templateInstructions.ts`
3. Ensure proper documentation in the settings UI
## Common Patterns
### API Response Handling
```typescript
try {
const response = await this.request(path) as ApiResponse;
return response.data ?? [];
} catch (error) {
console.error('API request failed:', error);
throw error;
}
```
### Template Processing
```typescript
// Create view model with formatted data
const view = {
title: article.title || '',
create_time: formatDateTime(article.create_time, this.dateFormat),
// Additional properties...
};
// Render template with Mustache
const result = Mustache.render(template, view);
```
### Settings Management
```typescript
// Update setting
this.plugin.settings.someSetting = value;
await this.plugin.saveSettings();
// Apply setting change
this.plugin.updateComponent(value);
```

View file

@ -2,7 +2,7 @@ import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
import type CuboxSyncPlugin from './main';
import { FRONT_MATTER_VARIABLES } from './templateProcessor';
import { ALL_FOLDERS_ID, FolderSelectModal } from './modal/folderSelectModal';
import { filenameTemplateInstructions, metadataVariablesInstructions, contentTemplateInstructions, cuboxDateFormat } from './templateInstructions';
import { filenameTemplateInstructions, metadataVariablesInstructions, contentTemplateInstructions, cuboxDateFormat, getFilenameReferenceLink, getMetadataReferenceLink, getContentReferenceLink, getDateReferenceLink } from './templateInstructions';
import { ALL_CONTENT_TYPES, TypeSelectModal } from './modal/typeSelectModal';
import { ALL_STATUS_ID, StatusSelectModal } from './modal/statusSelectModal';
import { ALL_ITEMS, TagSelectModal } from './modal/tagSelectModal';
@ -65,7 +65,7 @@ export const DEFAULT_SETTINGS: CuboxSyncSettings = {
tagsFilter: [ALL_ITEMS],
syncFrequency: 30, // 分钟
targetFolder: 'Cubox',
filenameTemplate: '{{title}}-{{create_time}}',
filenameTemplate: '{{{title}}}-{{{create_time}}}',
frontMatterVariables: ['id', 'cubox_url', 'url', 'tags'],
contentTemplate: DEFAULT_CONTENT_TEMPLATE,
highlightInContent: true,
@ -116,6 +116,9 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
// 更新 Cubox API 配置
this.plugin.updateCuboxApiConfig(value, this.plugin.settings.apiKey);
// 更新帮助链接
this.updateHelpLinks(value);
}));
// 添加 API Key 设置
@ -437,7 +440,7 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
})
// 设置文本区域的大小
.then(textArea => {
textArea.inputEl.rows = 20;
textArea.inputEl.rows = 24;
textArea.inputEl.cols = 30;
}))
.addButton(button => button
@ -472,6 +475,10 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
containerEl.createEl('h3', {text: 'Status'});
containerEl.createEl('p', {text: `Last sync: ${this.plugin.formatLastSyncTime()}`});
// 初始化帮助链接
setTimeout(() => {
this.updateHelpLinks(this.plugin.settings.domain);
}, 100);
}
// 更新 API Key 设置的描述和状态
@ -515,6 +522,23 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
}
}
// 更新帮助文档链接
private updateHelpLinks(domain: string): void {
// 查找所有需要更新的元素并更新
const updateDomainReference = (selector: string, getLinkFunction: (domain: string) => string) => {
const element = document.querySelector(selector);
if (element) {
element.innerHTML = getLinkFunction(domain);
}
};
// 更新各个引用链接
updateDomainReference('.domain-reference-filename', getFilenameReferenceLink);
updateDomainReference('.domain-reference-metadata', getMetadataReferenceLink);
updateDomainReference('.domain-reference-content', getContentReferenceLink);
updateDomainReference('.domain-reference-date', getDateReferenceLink);
}
private getFolderFilterButtonText(): string {
const folderFilters = this.plugin.settings.folderFilter;
if (!folderFilters || folderFilters.length === 0) {

View file

@ -21,7 +21,7 @@ export default class CuboxSyncPlugin extends Plugin {
// 添加左侧图标
const iconId = 'Cubox'
addIcon(iconId, '<svg viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_832_147)"><path d="M16.4832 14.9619C16.3985 19.9047 13.3129 19.7653 9.40222 19.7653C5.49152 19.7653 3.46973 18.9255 3.46973 14.6576C3.46973 10.2101 5.49152 6.90909 9.40222 6.90909C13.3129 6.90909 16.4832 10.5144 16.4832 14.9619Z" stroke="currentColor" stroke-width="1.72727"/><rect x="0.863636" y="0.863636" width="17.2727" height="17.2727" rx="8.63636" stroke="currentColor" stroke-width="1.72727"/></g><ellipse cx="6.90905" cy="12.2205" rx="0.863636" ry="0.993182" fill="currentColor"/><ellipse cx="9.49987" cy="12.2205" rx="0.863636" ry="0.993182" fill="currentColor"/><defs><clipPath id="clip0_832_147"><rect width="19" height="19" rx="9.5" fill="white"/></clipPath></defs></svg>');
addIcon(iconId, '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_812_101)"><path d="M20.0858 18.3243C19.9877 24.0475 16.415 23.8861 11.8868 23.8861C7.35861 23.8861 5.01758 22.9138 5.01758 17.9719C5.01758 12.8223 7.35861 9 11.8868 9C16.415 9 20.0858 13.1746 20.0858 18.3243Z" stroke="currentColor" stroke-width="2"/><rect x="2" y="2" width="20" height="20" rx="10" stroke="currentColor" stroke-width="2"/></g><ellipse cx="9" cy="15.15" rx="1" ry="1.15" fill="currentColor"/><ellipse cx="12" cy="15.15" rx="1" ry="1.15" fill="currentColor"/><defs> <clipPath id="clip0_812_101"><rect x="1" y="1" width="22" height="22" rx="11" fill="white"/></clipPath></defs></svg>');
const ribbonIconEl = this.addRibbonIcon(iconId, iconId, async (evt: MouseEvent) => {
new Notice('Syncing your Cubox…');
@ -32,7 +32,7 @@ export default class CuboxSyncPlugin extends Plugin {
// 添加同步命令
this.addCommand({
id: 'sync-cubox-data',
name: 'Sync Cubox Data',
name: 'Sync now',
callback: async () => {
await this.syncCubox();
}
@ -141,12 +141,10 @@ export default class CuboxSyncPlugin extends Plugin {
fullArticle
);
// 创建或更新文件
const filePath = `${this.settings.targetFolder}/${filename}.md`;
// 检查相同 id 收藏是否存在
const file = this.app.vault.getAbstractFileByPath(filePath);
// 如果是 TFile 类型,检查 frontmatter 中的 id
if (file instanceof TFile) {
let foundMatchingId = false
await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
@ -204,7 +202,7 @@ export default class CuboxSyncPlugin extends Plugin {
this.settings.syncing = false;
await this.saveSettings();
const message = `Cubox sync completed: ${syncCount} articles synchronized${skipCount > 0 ? `, ${skipCount} skipped` : ''}${errorCount > 0 ? `, ${errorCount} errors` : ''}`;
const message = `Cubox sync completed: ${syncCount} new items${skipCount > 0 ? `, ${skipCount} skipped` : ''}${errorCount > 0 ? `, ${errorCount} errors` : ''}`;
new Notice(message);
} catch (error) {
console.error('同步 Cubox 数据失败:', error);

View file

@ -1,22 +1,69 @@
export const getInstructionUrl = (domain: string, type: string): string => {
if (domain === 'cubox.pro') {
switch(type) {
case 'filename':
return 'https://help.cubox.pro/share/obplugin/#%E6%96%87%E4%BB%B6%E5%90%8D%E6%A8%A1%E6%9D%BF';
case 'metadata':
return 'https://help.cubox.pro/share/obplugin/#%E5%85%83%E5%B1%9E%E6%80%A7';
case 'content':
return 'https://help.cubox.pro/share/obplugin/#%E5%86%85%E5%AE%B9%E6%A8%A1%E6%9D%BF';
case 'date':
return 'https://help.cubox.pro/share/obplugin/#%E6%97%A5%E6%9C%9F%E6%A0%BC%E5%BC%8F';
default:
return '#';
}
} else if (domain === 'cubox.cc') {
switch(type) {
case 'filename':
return 'https://help.cubox.cc/share/obplugin/#filename-template';
case 'metadata':
return 'https://help.cubox.cc/share/obplugin/#metadata';
case 'content':
return 'https://help.cubox.cc/share/obplugin/#content-template';
case 'date':
return 'https://help.cubox.cc/share/obplugin/#date-format';
default:
return '#';
}
}
return '#';
};
export const getReferenceLink = (domain: string, type: string): string => {
// Default link when no domain is selected
if (!domain) {
return '<div class="cubox-reference">For more, refer to <a href="#" class="reference-link">reference</a>.</div>';
}
const url = getInstructionUrl(domain, type);
return `<div class="cubox-reference">For more, refer to <a href="${url}" class="reference-link" target="_blank">reference</a>.</div>`;
};
// Placeholder functions that will be replaced with actual implementations that use the domain
export let getFilenameReferenceLink = (domain: string) => getReferenceLink(domain, 'filename');
export let getMetadataReferenceLink = (domain: string) => getReferenceLink(domain, 'metadata');
export let getContentReferenceLink = (domain: string) => getReferenceLink(domain, 'content');
export let getDateReferenceLink = (domain: string) => getReferenceLink(domain, 'date');
export const filenameTemplateInstructions = `
Enter template for creating synced article file name.
<div class="cubox-variables-container">
<div class="cubox-variables-title">Available variables</div>
<ul class="cubox-variables-list">
<li>{{title}}</li>
<li>{{article_title}}</li>
<li>{{create_time}}</li>
<li>{{update_time}}</li>
<li>{{domain}}</li>
<li>{{type}}</li>
<li>{{{title}}}</li>
<li>{{{article_title}}}</li>
<li>{{{create_time}}}</li>
<li>{{{update_time}}}</li>
<li>{{{domain}}}</li>
<li>{{{type}}}</li>
</ul>
<div class="cubox-reference">For more, refer to <a href="#" class="reference-link">reference</a>.</div>
<div class="cubox-reference domain-reference-filename"></div>
</div>
`;
export const metadataVariablesInstructions = `
Enter the metadata separated by comma. you can also use custom aliases in the format of metadata::alias. For syncing purposes, the id will always be included.
Enter the metadata separated by comma. you can also use custom aliases in the format of metadata::alias. For syncing purposes, the id will always be included.
<div class="cubox-variables-container">
<div class="cubox-variables-title">Available variables</div>
@ -33,7 +80,7 @@ Enter the metadata separated by comma. you can also use custom aliases in the fo
<li>words_count</li>
<li>type</li>
</ul>
<div class="cubox-reference">For more, refer to <a href="#" class="reference-link">reference</a>.</div>
<div class="cubox-reference domain-reference-metadata"></div>
</div>
`;
@ -43,33 +90,32 @@ Enter template for creating synced article content.
<div class="cubox-variables-container">
<div class="cubox-variables-title">Available variables</div>
<ul class="cubox-variables-list">
<li>{{id}}</li>
<li>{{title}}</li>
<li>{{description}}</li>
<li>{{article_title}}</li>
<li>{{content}}</li>
<li>{{content_highlighted}}</li>
<li>{{highlights}}</li>
<li>{{{id}}}</li>
<li>{{{title}}}</li>
<li>{{{description}}}</li>
<li>{{{article_title}}}</li>
<li>{{{content}}}</li>
<li>{{{content_highlighted}}}</li>
<li>{{{highlights}}}</li>
<li class="highlight-item">
{{highlight_text}}
<ul class="highlight-sublist">
<li>{{text}}</li>
<li>{{image_url}}</li>
<li>{{cubox_url}}</li>
<li>{{note}}</li>
<li>{{color}}</li>
<li>{{create_time}}</li>
<li>{{{text}}}</li>
<li>{{{image_url}}}</li>
<li>{{{cubox_url}}}</li>
<li>{{{note}}}</li>
<li>{{{color}}}</li>
<li>{{{create_time}}}</li>
</ul>
</li>
<li>{{tags}}</li>
<li>{{create_time}}</li>
<li>{{update_time}}</li>
<li>{{domain}}</li>
<li>{{url}}</li>
<li>{{cubox_url}}</li>
<li>{{words_count}}</li>
<li>{{{tags}}}</li>
<li>{{{create_time}}}</li>
<li>{{{update_time}}}</li>
<li>{{{domain}}}</li>
<li>{{{url}}}</li>
<li>{{{cubox_url}}}</li>
<li>{{{words_count}}}</li>
</ul>
<div class="cubox-reference">For more, refer to <a href="#" class="reference-link">reference</a>.</div>
<div class="cubox-reference domain-reference-content"></div>
</div>
`;
@ -84,6 +130,6 @@ If date is used on above templates, enter the format date.
<li>dd.MM.yyyy</li>
<li>yyyy-MM-dd HH:mm:ss</li>
</ul>
<div class="cubox-reference">For more, refer to <a href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens" class="reference-link">reference</a>.</div>
<div class="cubox-reference domain-reference-date"></div>
</div>
`;

View file

@ -68,7 +68,7 @@ If your plugin does not need CSS, delete this file.
.cubox-reference {
margin-top: 10px;
font-size: 0.9em;
font-size: 1.0em;
}
.reference-link {