rebuild api

This commit is contained in:
delphi 2025-03-04 11:27:42 +08:00
parent 0b09074719
commit 5d1a864635
3 changed files with 254 additions and 169 deletions

View file

@ -64,20 +64,18 @@ interface FoldersResponse {
export class CuboxApi {
private endpoint: string;
private apiKey: string;
constructor(options: CuboxApiOptions) {
this.endpoint = `https://${options.domain}`;
this.apiKey = options.apiKey;
}
/**
* API
*/
async testConnection(): Promise<boolean> {
async testConnection(apiKey: string): Promise<boolean> {
try {
// 尝试获取一篇文章来测试连接
// const result = await this.getArticles(1, 1);
const result = await this.getArticles(apiKey, { lastCardId: null, pageSize: 1 });
new Notice('测试 Cubox 连接成功');
return true;
} catch (error) {
@ -87,10 +85,10 @@ export class CuboxApi {
}
}
private async request(path: string, options: RequestInit = {}) {
private async request(path: string, apiKey: string, options: RequestInit = {}) {
const url = `${this.endpoint}${path}`;
const headers = {
'Authorization': `Bearer ${this.apiKey}`,
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
@ -111,63 +109,65 @@ export class CuboxApi {
/**
*
* @param folderFilter
* @param typeFilter
* @param statusFilter 'all' | 'read' | 'starred' | 'annotated'
* @param lastCardId ID
* @param apiKey API密钥
* @param params
*/
async getArticles(
folderFilter: string[],
typeFilter: string[],
statusFilter: string[],
lastCardId: string | null = null
): Promise<{ articles: any[], hasMore: boolean, lastCardId: string | null }> {
apiKey: string,
params: {
lastCardId: string | null;
pageSize?: number;
folderFilter?: string[];
typeFilter?: string[];
statusFilter?: string[];
} = { lastCardId: null }
): Promise<{ articles: CuboxArticle[], hasMore: boolean, lastCardId: string | null }> {
try {
// 构建请求参数
const params: any = {
limit: 20,
};
const searchParams = new URLSearchParams();
const pageSize = params.pageSize || 50;
if (params.lastCardId !== null && params.lastCardId.length > 0) {
searchParams.append('lastCardId', params.lastCardId);
}
searchParams.append('pageSize', pageSize.toString());
// 添加文件夹过滤
if (folderFilter && folderFilter.length > 0) {
params.folder_id = folderFilter;
if (params.folderFilter && params.folderFilter.length > 0) {
params.folderFilter.forEach(folder => {
searchParams.append('folderId', folder);
});
}
// 添加类型过滤
if (typeFilter && typeFilter.length > 0) {
params.type = typeFilter;
if (params.typeFilter && params.typeFilter.length > 0) {
params.typeFilter.forEach(type => {
searchParams.append('type', type);
});
}
// 添加状态过滤 - 修改为支持多选
if (statusFilter && statusFilter.length > 0) {
// 添加状态过滤
if (params.statusFilter && params.statusFilter.length > 0) {
// 如果包含 'all',则不添加状态过滤
if (!statusFilter.includes('all')) {
// 处理多个状态过滤
if (statusFilter.includes('read')) params.is_read = true;
if (statusFilter.includes('starred')) params.is_starred = true;
if (statusFilter.includes('archived')) params.is_archived = true;
if (statusFilter.includes('annotated')) params.is_annotated = true;
if (!params.statusFilter.includes('all')) {
if (params.statusFilter.includes('read')) searchParams.append('isRead', 'true');
if (params.statusFilter.includes('starred')) searchParams.append('isStarred', 'true');
if (params.statusFilter.includes('archived')) searchParams.append('isArchived', 'true');
if (params.statusFilter.includes('annotated')) searchParams.append('isAnnotated', 'true');
}
}
const path = `/c/api/third-party/card/list?${searchParams.toString()}`;
const response = await this.request(path, apiKey) as ListResponse;
// 添加分页参数
if (lastCardId) {
params.last_id = lastCardId;
}
// 发送请求
const response = await this.request('/v2/article/list', params);
// 处理响应
if (response && response.data) {
return {
articles: response.data.data || [],
hasMore: response.data.has_more || false,
lastCardId: response.data.last_id || null
};
}
return { articles: [], hasMore: false, lastCardId: null };
const articles = response.data.list;
const hasMore = articles.length >= pageSize;
const lastCardId = articles.length > 0 ? articles[articles.length - 1].cardId : null;
return {
articles,
hasMore,
lastCardId
};
} catch (error) {
console.error('获取文章列表失败:', error);
throw error;
@ -176,12 +176,13 @@ export class CuboxApi {
/**
*
* @param apiKey API密钥
* @param articleId ID
*/
async getArticleDetail(articleId: string): Promise<string | null> {
async getArticleDetail(apiKey: string, articleId: string): Promise<string | null> {
try {
const path = `/c/api/third-party/card/content?cardId=${articleId}`;
const response = await this.request(path) as ContentResponse;
const response = await this.request(path, apiKey) as ContentResponse;
// 直接返回文章内容
return response.data;
@ -194,9 +195,10 @@ export class CuboxApi {
/**
*
* @param apiKey API密钥
* @param articleId ID
*/
async getHighlights(articleId: string): Promise<CuboxHighlight[]> {
async getHighlights(apiKey: string, articleId: string): Promise<CuboxHighlight[]> {
try {
// 这里需要实现获取高亮的API调用
// 暂时返回空数组
@ -210,11 +212,12 @@ export class CuboxApi {
/**
*
* @param apiKey API密钥
*/
async getFolders(): Promise<CuboxFolder[]> {
async getFolders(apiKey: string): Promise<CuboxFolder[]> {
try {
const path = '/c/api/third-party/folder/list';
const response = await this.request(path) as FoldersResponse;
const response = await this.request(path, apiKey) as FoldersResponse;
return response.data.list.map(folder => ({
...folder,

View file

@ -155,7 +155,7 @@ export default class CuboxSyncPlugin extends Plugin {
statusBarItemEl.setText(`正在同步 Cubox...`);
// 检查 API 连接
const isConnected = await this.cuboxApi.testConnection();
const isConnected = await this.cuboxApi.testConnection(this.settings.apiKey);
if (!isConnected) {
this.settings.syncing = false;
await this.saveSettings();
@ -175,10 +175,13 @@ export default class CuboxSyncPlugin extends Plugin {
while (hasMore) {
// 获取文章列表
const result = await this.cuboxApi.getArticles(
this.settings.folderFilter,
this.settings.typeFilter,
this.settings.statusFilter,
lastCardId
this.settings.apiKey,
{
lastCardId: lastCardId,
folderFilter: this.settings.folderFilter,
typeFilter: this.settings.typeFilter,
statusFilter: this.settings.statusFilter
}
);
const { articles, hasMore: moreArticles, lastCardId: newLastCardId } = result;
@ -190,11 +193,11 @@ export default class CuboxSyncPlugin extends Plugin {
// 处理每篇文章
for (const article of articles) {
// 获取文章详情和高亮
const content = await this.cuboxApi.getArticleDetail(article.cardId);
const content = await this.cuboxApi.getArticleDetail(this.settings.apiKey, article.cardId);
if (content === null) continue;
// 获取高亮内容
const highlights = await this.cuboxApi.getHighlights(article.cardId);
const highlights = await this.cuboxApi.getHighlights(this.settings.apiKey, article.cardId);
// 合并文章基本信息、内容和高亮
const fullArticle = {
@ -379,19 +382,29 @@ class CuboxSyncSettingTab extends PluginSettingTab {
// 显示加载状态
button.setButtonText('Loading folders...');
// 打开文件夹选择模态框
const modal = new FolderSelectModal(
this.app,
this.plugin.cuboxApi,
this.plugin.settings.folderFilter,
async (selectedFolders) => {
this.plugin.settings.folderFilter = selectedFolders;
await this.plugin.saveSettings();
// 更新按钮文本
button.setButtonText(this.getFolderFilterButtonText());
}
);
modal.open();
try {
// 先获取文件夹数据
const folders = await this.plugin.cuboxApi.getFolders(this.plugin.settings.apiKey);
// 打开文件夹选择模态框,传入已获取的文件夹数据
const modal = new FolderSelectModal(
this.app,
folders, // 直接传入获取到的文件夹数据
this.plugin.settings.folderFilter,
async (selectedFolders) => {
this.plugin.settings.folderFilter = selectedFolders;
await this.plugin.saveSettings();
// 更新按钮文本
button.setButtonText(this.getFolderFilterButtonText());
}
);
modal.open();
} catch (error) {
console.error('获取文件夹列表失败:', error);
new Notice('获取文件夹列表失败,请检查网络连接和 API Key');
// 恢复按钮文本
button.setButtonText(this.getFolderFilterButtonText());
}
}));
new Setting(containerEl)
@ -453,7 +466,7 @@ class CuboxSyncSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName('Sync Interval')
.setDesc('Auto sync interval (in minutes). 0 means manual sync. Each item syncs only once. Subsequent updates wont be synced, and modifications in Obsidian wont affect Cubox. We recommend avoiding frequent updates.')
.setDesc('Auto sync interval (in minutes). 0 means manual sync. Each item syncs only once. Subsequent updates won\'t be synced, and modifications in Obsidian won\'t affect Cubox. We recommend avoiding frequent updates.')
.addText(text => text
.setPlaceholder('Enter sync frequency in minutes')
.setValue(String(this.plugin.settings.syncFrequency))
@ -465,7 +478,7 @@ class CuboxSyncSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName('Folder')
.setDesc('Select the folder youd like to sync to')
.setDesc('Select the folder you\'d like to sync to')
.addText(text => text
.setPlaceholder('Enter target folder path')
.setValue(this.plugin.settings.targetFolder)
@ -569,7 +582,7 @@ class CuboxSyncSettingTab extends PluginSettingTab {
.addButton(button => button
.setButtonText('Test')
.onClick(async () => {
await this.plugin.cuboxApi.testConnection();
await this.plugin.cuboxApi.testConnection(this.plugin.settings.apiKey);
}));
}

View file

@ -1,21 +1,23 @@
import { App, Modal, Setting, Notice } from 'obsidian';
import { CuboxApi, CuboxFolder } from '../cuboxApi';
// 定义一个虚拟的 ALL 文件夹 ID
const ALL_FOLDERS_ID = 'all_folders';
export class FolderSelectModal extends Modal {
private cuboxApi: CuboxApi;
private selectedFolders: string[];
private onConfirm: (selectedFolders: string[]) => void;
private folders: CuboxFolder[] = [];
private loading: boolean = true;
private cuboxApi: CuboxApi;
constructor(
app: App,
cuboxApi: CuboxApi,
folders: CuboxFolder[],
initialSelectedFolders: string[],
onConfirm: (selectedFolders: string[]) => void
) {
super(app);
this.cuboxApi = cuboxApi;
this.folders = folders;
this.selectedFolders = [...initialSelectedFolders];
this.onConfirm = onConfirm;
}
@ -24,102 +26,169 @@ export class FolderSelectModal extends Modal {
const { contentEl } = this;
// 设置标题
contentEl.createEl('h2', { text: '选择要同步的文件夹' });
contentEl.createEl('h2', { text: 'Manage Cubox folders to be synced' });
// 添加说明
contentEl.createEl('p', {
text: '选择要从 Cubox 同步的文件夹。不选择任何文件夹将同步所有内容。'
});
// 创建文件夹列表
const folderListEl = contentEl.createEl('div', { cls: 'folder-list' });
// 添加加载指示器
const loadingEl = contentEl.createEl('div', { text: '正在加载文件夹...' });
try {
// 获取文件夹列表
this.folders = await this.cuboxApi.getFolders();
this.loading = false;
// 移除加载指示器
loadingEl.remove();
// 创建文件夹列表
const folderListEl = contentEl.createEl('div', { cls: 'folder-list' });
// 添加"全部"选项
new Setting(folderListEl)
.setName('全部文件夹')
.setDesc('同步所有文件夹的内容')
.addToggle(toggle => toggle
.setValue(this.selectedFolders.length === 0)
// 添加"All Items"选项
const allFoldersSetting = new Setting(folderListEl)
.setName('All Items')
.addToggle(toggle => {
// 初始化状态如果selectedFolders为空或包含ALL_FOLDERS_ID则选中
const isAllSelected = this.selectedFolders.length === 0 ||
this.selectedFolders.includes(ALL_FOLDERS_ID);
toggle
.setValue(isAllSelected)
.onChange(value => {
if (value) {
// 如果选择"全部",清空已选文件夹
this.selectedFolders = [];
// 更新所有其他切换按钮
folderListEl.querySelectorAll('.folder-toggle').forEach((el: HTMLElement) => {
if (el.hasClass('is-enabled')) {
// @ts-ignore
el.click();
}
});
// 如果选择"All Items"清空已选文件夹只保留ALL_FOLDERS_ID
this.selectedFolders = [ALL_FOLDERS_ID];
// 更新所有其他切换按钮为选中状态
this.updateAllFolderToggles(folderListEl, true);
} else {
// 从选中列表移除ALL_FOLDERS_ID
this.selectedFolders = this.selectedFolders.filter(id => id !== ALL_FOLDERS_ID);
// 如果没有其他选中的文件夹,默认选中第一个
if (this.selectedFolders.length === 0 && this.folders.length > 0) {
this.selectedFolders.push(this.folders[0].id);
this.updateFolderToggle(folderListEl, this.folders[0].id, true);
}
}
})
);
// 添加每个文件夹的选项
this.folders.forEach(folder => {
new Setting(folderListEl)
.setName(folder.nested_name)
.addToggle(toggle => {
toggle
.setValue(this.selectedFolders.includes(folder.id))
.onChange(value => {
if (value) {
// 添加到选中列表
this.selectedFolders.push(folder.id);
// 如果有选择具体文件夹,取消"全部"选项
const allToggle = folderListEl.querySelector('.setting:first-child .toggle');
if (allToggle && allToggle.hasClass('is-enabled')) {
// @ts-ignore
allToggle.click();
}
} else {
// 从选中列表移除
this.selectedFolders = this.selectedFolders.filter(id => id !== folder.id);
}
});
// 添加类名以便于选择
toggle.toggleEl.addClass('folder-toggle');
return toggle;
});
// 添加类名以便于选择
toggle.toggleEl.addClass('all-folders-toggle');
return toggle;
});
// 添加确认和取消按钮
const buttonContainer = contentEl.createEl('div', { cls: 'button-container' });
buttonContainer.createEl('button', { text: '取消' })
.addEventListener('click', () => {
this.close();
});
buttonContainer.createEl('button', { text: '确认', cls: 'mod-cta' })
.addEventListener('click', () => {
this.onConfirm(this.selectedFolders);
this.close();
});
} catch (error) {
console.error('获取文件夹列表失败:', error);
loadingEl.setText('获取文件夹列表失败,请检查网络连接');
// 添加重试按钮
contentEl.createEl('button', { text: '重试' })
.addEventListener('click', () => {
this.close();
new FolderSelectModal(this.app, this.cuboxApi, this.selectedFolders, this.onConfirm).open();
// 添加每个文件夹的选项
this.folders.forEach(folder => {
new Setting(folderListEl)
.setName(folder.nested_name)
.addToggle(toggle => {
// 初始化状态如果selectedFolders为空或包含ALL_FOLDERS_ID则所有文件夹都选中
const isSelected = this.selectedFolders.length === 0 ||
this.selectedFolders.includes(ALL_FOLDERS_ID) ||
this.selectedFolders.includes(folder.id);
toggle
.setValue(isSelected)
.onChange(value => {
if (value) {
// 添加到选中列表
if (!this.selectedFolders.includes(folder.id)) {
this.selectedFolders.push(folder.id);
}
// 检查是否所有文件夹都被选中,如果是,自动选中"All Items"
if (this.areAllFoldersSelected()) {
if (!this.selectedFolders.includes(ALL_FOLDERS_ID)) {
this.selectedFolders.push(ALL_FOLDERS_ID);
}
this.updateAllFoldersToggle(folderListEl, true);
}
} else {
// 从选中列表移除
this.selectedFolders = this.selectedFolders.filter(id => id !== folder.id);
// 如果取消选中任何文件夹,取消"All Items"选项
if (this.selectedFolders.includes(ALL_FOLDERS_ID)) {
this.selectedFolders = this.selectedFolders.filter(id => id !== ALL_FOLDERS_ID);
this.updateAllFoldersToggle(folderListEl, false);
}
// 如果没有选中的文件夹,默认选中"All Items"
if (this.selectedFolders.length === 0) {
this.selectedFolders.push(ALL_FOLDERS_ID);
this.updateAllFoldersToggle(folderListEl, true);
this.updateAllFolderToggles(folderListEl, true);
}
}
});
// 添加类名以便于选择
toggle.toggleEl.addClass('folder-toggle');
toggle.toggleEl.setAttribute('data-folder-id', folder.id);
return toggle;
});
});
// 添加确认和取消按钮
const buttonContainer = contentEl.createEl('div', { cls: 'button-container' });
buttonContainer.createEl('button', { text: '取消' })
.addEventListener('click', () => {
this.close();
});
buttonContainer.createEl('button', { text: '确认', cls: 'mod-cta' })
.addEventListener('click', () => {
// 如果选中了ALL_FOLDERS_ID则传递空数组表示同步所有内容
const resultFolders = this.selectedFolders.includes(ALL_FOLDERS_ID)
? []
: this.selectedFolders;
this.onConfirm(resultFolders);
this.close();
});
}
// 检查是否所有文件夹都被选中
private areAllFoldersSelected(): boolean {
// 如果没有文件夹返回false
if (this.folders.length === 0) return false;
// 检查每个文件夹是否都在selectedFolders中
return this.folders.every(folder => this.selectedFolders.includes(folder.id));
}
// 更新"All Items"切换按钮的状态
private updateAllFoldersToggle(containerEl: HTMLElement, value: boolean): void {
const allToggle = containerEl.querySelector('.all-folders-toggle') as HTMLElement;
if (allToggle) {
const isEnabled = allToggle.hasClass('is-enabled');
if (value && !isEnabled) {
// @ts-ignore
allToggle.click();
} else if (!value && isEnabled) {
// @ts-ignore
allToggle.click();
}
}
}
// 更新所有文件夹切换按钮的状态
private updateAllFolderToggles(containerEl: HTMLElement, value: boolean): void {
containerEl.querySelectorAll('.folder-toggle').forEach((el: HTMLElement) => {
const isEnabled = el.hasClass('is-enabled');
if (value && !isEnabled) {
// @ts-ignore
el.click();
} else if (!value && isEnabled) {
// @ts-ignore
el.click();
}
});
}
// 更新特定文件夹切换按钮的状态
private updateFolderToggle(containerEl: HTMLElement, folderId: string, value: boolean): void {
const folderToggle = containerEl.querySelector(`.folder-toggle[data-folder-id="${folderId}"]`) as HTMLElement;
if (folderToggle) {
const isEnabled = folderToggle.hasClass('is-enabled');
if (value && !isEnabled) {
// @ts-ignore
folderToggle.click();
} else if (!value && isEnabled) {
// @ts-ignore
folderToggle.click();
}
}
}