解决Chartero插件跨平台阅读历史同步难题:从技术原理到实战方案

解决Chartero插件跨平台阅读历史同步难题:从技术原理到实战方案

【免费下载链接】Chartero Chart in Zotero 【免费下载链接】Chartero 项目地址: https://gitcode.com/gh_mirrors/ch/Chartero

引言:跨设备阅读的痛点与解决方案

你是否曾在电脑上阅读学术论文,切换到平板继续时发现阅读进度丢失?是否在团队协作中因无法同步阅读记录而导致重复工作?Chartero作为Zotero的重要插件,为文献管理提供了强大的阅读历史跟踪功能,但跨平台同步问题一直困扰着用户。本文将深入剖析Chartero阅读历史记录同步的技术原理,揭示跨平台使用中的核心障碍,并提供一套完整的解决方案,帮助你实现无缝的跨设备阅读体验。

读完本文,你将获得:

  • 理解Chartero阅读历史记录的存储机制
  • 识别跨平台同步的关键技术障碍
  • 掌握三种同步方案的实施步骤
  • 学会配置自动同步与冲突解决策略
  • 获取高级优化技巧与常见问题排查方法

技术原理:Chartero如何记录阅读历史

数据结构设计

Chartero采用分层数据结构记录阅读历史,主要包含两个核心类:AttachmentRecordPageRecord

// 核心数据结构关系
classDiagram
    class AttachmentRecord {
        +pages: { [page: number]: PageRecord }
        +numPages?: number
        +readPages: number
        +firstTime: number
        +lastTime: number
        +totalS: number
        +getUserTotalSeconds(userID: number): number
        +mergeJSON(json: object): void
    }
    
    class PageRecord {
        +period?: { [timestamp: number]: number }
        +userSeconds?: { [user: number]: number }
        +totalSeconds?: number
        +selectText?: number
        +getUserTotalSeconds(userID: number): number
        +mergeJSON(json: object): void
    }
    
    AttachmentRecord "1" -- "*" PageRecord: 包含多个

AttachmentRecord对应一个文献附件,包含:

  • 所有页面的阅读记录(pages)
  • 总页数(numPages)
  • 阅读时长统计(totalS)
  • 用户阅读时间分布(getUserTotalSeconds)

PageRecord记录单页阅读数据:

  • 时间戳与阅读时长映射(period)
  • 用户阅读时间统计(userSeconds)
  • 文本选择次数(selectText)

存储机制解析

Chartero将阅读历史存储在Zotero的笔记条目中,通过特殊格式与文献附件关联:

chartero#文献唯一标识
{
  "numPages": 25,
  "pages": {
    "0": {
      "period": { "1620000000": 60, "1620000060": 45 },
      "userSeconds": { "123": 105 }
    },
    // 更多页面...
  }
}

每个文献附件对应一个笔记条目,存储在专用的"主条目"(Main Item)下。主条目通过以下条件识别:

  • 条目类型为"computerProgram"
  • 短标题为插件名称
  • 存档位置为文库URI
// 主条目识别逻辑
isMainItem(item: Zotero.Item) {
    return item.itemType == "computerProgram" &&
           item.getField("shortTitle") == packageName &&
           item.getField("archiveLocation") == Zotero.URI.getLibraryURI(item.libraryID);
}

记录周期与触发机制

Chartero通过定时任务记录阅读行为,核心参数由偏好设置控制:

// 定时记录逻辑
register(scanPeriod: number) {
    this._scanPeriod = Number(scanPeriod);
    this._intervalID = Zotero
        .getMainWindow()
        .setInterval(this.schedule.bind(this), this._scanPeriod * 1000);
}

// 周期检查与记录
private async schedule() {
    if (this._mutex) return; // 防止并发问题
    
    this._activeReader = Zotero.Reader._readers.find((r) =>
        r._iframeWindow?.document.hasFocus() && r.type != "snapshot"
    );
    
    if (this._activeReader?.itemID) {
        this._mutex = true;
        try {
            const cache = await this.getCache(this._activeReader.itemID);
            this.record(cache.record); // 记录到缓存
            this.saveNote(cache); // 保存到笔记
        } finally {
            this._mutex = false;
        }
    }
}

默认扫描周期(scanPeriod)为60秒,可在插件设置中调整。系统通过检查阅读器状态变化判断用户是否活跃,连续相同状态超过scanTimeout阈值(默认300秒)则停止记录。

跨平台同步的核心障碍

数据存储的本地化限制

Chartero将阅读历史存储在本地Zotero数据库中,采用"一文献一笔记"的存储模式。这种设计带来三个主要限制:

  1. 存储位置依赖:历史记录绑定到特定设备的本地数据库
  2. 同步范围限制:Zotero同步默认不包含插件生成的笔记
  3. 设备标识冲突:不同设备可能分配不同的内部ID

数据格式与版本兼容性问题

Chartero的历史数据格式可能随版本更新而变化,导致不同设备上的插件版本不兼容:

// 数据格式迁移示例
function importLegacyHistory(str: string) {
    const json = JSON.parse(str);
    const newJson = {
        numPages: json.n,
        pages: {}
    };
    
    // 转换旧格式到新格式
    for (const page in json.p) {
        newJson.pages[page] = { p: json.p[page].t };
    }
    
    const record = new AttachmentRecord(newJson);
    addon.history.compress(record);
    // 保存为新格式...
}

当不同设备使用不同版本的Chartero时,可能出现数据格式不兼容导致同步失败。

冲突解决机制缺失

多设备同时编辑同一文献时,缺乏内置的冲突检测与解决机制:

// 冲突场景示例
timeline
    Device A: 阅读第5页,记录时间戳10:00-10:05
    Device B: 阅读第5页,记录时间戳10:02-10:08
    同步后: 数据覆盖而非合并,导致部分阅读记录丢失

默认情况下,后同步的设备会覆盖先同步的设备数据,导致阅读记录不完整。

解决方案:三种同步策略的实现

方案一:基于Zotero同步的内置方案

实施步骤
  1. 确认主条目同步状态

    检查主条目(Chartero Main Item)是否设置为同步:

    // 主条目同步检查
    async function checkMainItemSyncStatus(libraryID: number = 1) {
        const mainItem = await addon.history.getMainItem(libraryID);
        return !mainItem.getField("excludeFromSync");
    }
    

    如果主条目被排除在同步之外,需要在Zotero中勾选"包含在同步中"选项。

  2. 配置同步频率

    修改Chartero偏好设置,调整同步相关参数:

    // 调整同步相关参数
    addon.setPref("scanPeriod", 60); // 60秒记录一次
    addon.setPref("syncThreshold", 300); // 5分钟自动同步一次
    
  3. 验证同步功能

    使用以下代码验证同步是否正常工作:

    // 同步测试代码
    async function testSyncFunctionality() {
        const testDoc = await createTestDocument();
        const initialHistory = addon.history.getByAttachment(testDoc.id);
    
        // 模拟阅读
        simulateReading(testDoc, 5); // 阅读5分钟
    
        // 强制同步
        await Zotero.Sync.run();
    
        // 在另一设备上检查
        const syncedHistory = addon.history.getByAttachment(testDoc.id);
        return initialHistory.totalS < syncedHistory.totalS;
    }
    
优缺点分析
优点缺点
无需额外工具,配置简单仅支持Zotero同步的文献库
自动处理版本兼容性同步延迟,不实时
与Zotero生态深度整合群组文库可能受权限限制

方案二:基于外部存储的同步方案

实施步骤
  1. 配置外部存储服务

    创建配置文件存储外部存储信息:

    // config/sync.json
    {
      "service": "dropbox",
      "accessToken": "your_access_token",
      "syncFolder": "/Zotero/CharteroSync",
      "autoSync": true,
      "syncInterval": 300
    }
    
  2. 实现导出/导入功能

    // 导出阅读历史到外部存储
    async function exportHistoryToExternalStorage() {
        const allHistory = addon.history.getAll();
        const exportData = {};
    
        for (const history of allHistory) {
            if (history) {
                exportData[history.key] = {
                    record: history.record.toJSON(),
                    lastModified: new Date().toISOString()
                };
            }
        }
    
        await externalStorageService.upload(
            "/Zotero/CharteroSync/history.json",
            JSON.stringify(exportData)
        );
    }
    
    // 从外部存储导入阅读历史
    async function importHistoryFromExternalStorage() {
        const data = await externalStorageService.download(
            "/Zotero/CharteroSync/history.json"
        );
        const importData = JSON.parse(data);
    
        for (const key in importData) {
            const history = addon.history.getByKey(key);
            if (history) {
                // 合并历史记录
                history.record.mergeJSON(importData[key].record);
                await addon.history.saveNote(history);
            }
        }
    }
    
  3. 设置自动同步任务

    // 设置自动同步
    function setupAutoSync(interval: number = 300) {
        return setInterval(async () => {
            if (Zotero.isOnline() && !addon.history._mutex) {
                await exportHistoryToExternalStorage();
                await importHistoryFromExternalStorage();
            }
        }, interval * 1000);
    }
    
支持的存储服务
存储服务实现难度优缺点
Dropbox中等API稳定,免费额度有限
Google Drive中等存储空间大,国内访问受限
OneDrive中等与Windows生态整合好
WebDAV较高自托管灵活,需服务器支持

方案三:基于WebDAV的高级同步方案

实施步骤
  1. 搭建WebDAV服务器

    可使用Nextcloud、ownCloud或纯WebDAV服务器(如Apache mod_dav)。以Nextcloud为例:

    # Nextcloud安装示例(Docker)
    docker run -d -p 8080:80 \
      -v nextcloud_data:/var/www/html \
      nextcloud:latest
    
  2. 配置Chartero WebDAV客户端

    // WebDAV客户端配置
    const webdavClient = createClient({
      url: 'https://your-nextcloud.example.com/remote.php/dav/files/your_username/CharteroSync/',
      username: 'your_username',
      password: 'your_app_password'
    });
    
  3. 实现双向同步逻辑

    // WebDAV双向同步实现
    async function webdavSync() {
        // 1. 获取本地所有历史记录
        const localHistories = addon.history.getAll()
            .filter(h => h) as AttachmentHistory[];
    
        // 2. 获取远程历史记录列表
        const remoteFiles = await webdavClient.list();
    
        // 3. 下载并合并远程更新
        for (const file of remoteFiles) {
            if (file.name.endsWith('.json') && file.mtime) {
                const localHistory = localHistories.find(
                    h => h.key === file.name.replace('.json', '')
                );
    
                if (!localHistory || 
                    new Date(localHistory.note.dateModified) < new Date(file.mtime)) {
                    // 远程版本更新,下载并合并
                    const remoteData = await webdavClient.getFileContents(file.name);
                    await mergeRemoteHistory(localHistory, JSON.parse(remoteData));
                }
            }
        }
    
        // 4. 上传本地更新
        for (const history of localHistories) {
            const fileName = `${history.key}.json`;
            const remoteFile = remoteFiles.find(f => f.name === fileName);
    
            if (!remoteFile || 
                new Date(history.note.dateModified) > new Date(remoteFile.mtime)) {
                // 本地版本更新,上传到WebDAV
                await webdavClient.putFileContents(
                    fileName,
                    JSON.stringify(history.record)
                );
            }
        }
    }
    
  4. 配置定时同步与冲突处理

    // 设置定时同步
    setInterval(webdavSync, 300000); // 每5分钟同步一次
    
    // 冲突解决策略
    function resolveConflict(local: AttachmentRecord, remote: AttachmentRecord) {
        // 合并策略:以时间戳较新的记录为准,合并不冲突部分
        const merged = new AttachmentRecord();
    
        // 合并页面记录
        const allPageNumbers = new Set([
            ...Object.keys(local.pages).map(Number),
            ...Object.keys(remote.pages).map(Number)
        ]);
    
        for (const pageNum of allPageNumbers) {
            const localPage = local.pages[pageNum];
            const remotePage = remote.pages[pageNum];
    
            if (localPage && remotePage) {
                // 都有记录,合并时间戳
                merged.pages[pageNum] = mergePageRecords(localPage, remotePage);
            } else if (localPage) {
                merged.pages[pageNum] = localPage;
            } else {
                merged.pages[pageNum] = remotePage;
            }
        }
    
        return merged;
    }
    
高级配置选项
配置项说明推荐值
syncInterval同步间隔(秒)300 (5分钟)
conflictResolution冲突解决策略"timestamp"或"merge"
compression是否压缩传输true
encryption是否加密存储true
backupCount备份历史版本数5

配置与优化:提升同步体验

自动同步配置

时间触发型同步
// 时间触发型同步配置
function configureTimeBasedSync(intervalMinutes: number = 5) {
    // 清除现有定时器
    if (window.charteroSyncTimer) {
        clearInterval(window.charteroSyncTimer);
    }
    
    // 设置新定时器
    window.charteroSyncTimer = setInterval(() => {
        // 仅在Zotero运行且有网络连接时同步
        if (Zotero.isOnline() && !Zotero正在退出) {
            performSync();
        }
    }, intervalMinutes * 60 * 1000);
    
    // 保存配置
    addon.setPref("syncInterval", intervalMinutes);
}
事件触发型同步
// 事件触发型同步配置
function configureEventBasedSync() {
    // 阅读器关闭时同步
    Zotero.Reader.events.on("close", async (reader) => {
        if (reader.itemID) {
            const history = addon.history.getByAttachment(reader.itemID);
            if (history) {
                await addon.history.saveNote(history);
                await performSync();
            }
        }
    });
    
    // 文献关闭时同步
    Zotero.getMainWindow().addEventListener("unload", async () => {
        await performSync();
    });
    
    // 网络恢复时同步
    Zotero.connectionChecker.registerCallback(async (online) => {
        if (online) {
            await performSync();
        }
    });
}

冲突解决策略配置

时间戳策略
// 时间戳冲突解决策略
function timestampConflictResolution(local: AttachmentRecord, remote: AttachmentRecord) {
    // 比较最后修改时间
    if (local.lastTime > remote.lastTime) {
        return local; // 保留本地版本
    } else {
        return remote; // 采用远程版本
    }
}
合并策略
// 合并策略实现
function mergeConflictResolution(local: AttachmentRecord, remote: AttachmentRecord) {
    const merged = new AttachmentRecord();
    
    // 合并基本信息
    merged.numPages = local.numPages || remote.numPages;
    
    // 合并页面记录
    const allPageNumbers = new Set([
        ...Object.keys(local.pages).map(Number),
        ...Object.keys(remote.pages).map(Number)
    ]);
    
    for (const pageNum of allPageNumbers) {
        const localPage = local.pages[pageNum];
        const remotePage = remote.pages[pageNum];
        
        if (localPage && remotePage) {
            // 合并两个页面记录
            merged.pages[pageNum] = mergePageRecords(localPage, remotePage);
        } else if (localPage) {
            merged.pages[pageNum] = localPage;
        } else {
            merged.pages[pageNum] = remotePage;
        }
    }
    
    return merged;
}

// 页面记录合并
function mergePageRecords(local: PageRecord, remote: PageRecord) {
    const merged = new PageRecord();
    
    // 合并时间周期记录
    merged.period = { ...local.period, ...remote.period };
    
    // 合并用户阅读时间
    merged.userSeconds = {};
    const allUsers = new Set([
        ...Object.keys(local.userSeconds || {}).map(Number),
        ...Object.keys(remote.userSeconds || {}).map(Number)
    ]);
    
    for (const user of allUsers) {
        merged.userSeconds[user] = 
            (local.userSeconds?.[user] || 0) + 
            (remote.userSeconds?.[user] || 0);
    }
    
    // 合并其他属性
    merged.totalSeconds = (local.totalSeconds || 0) + (remote.totalSeconds || 0);
    merged.selectText = Math.max(local.selectText || 0, remote.selectText || 0);
    
    return merged;
}

性能优化建议

历史记录压缩
// 高级历史记录压缩
function advancedCompress(record: AttachmentRecord) {
    record.pageArr.forEach((page) => {
        if (!page.period) return;
        
        // 1. 合并连续时间戳
        let compressed = {},
            start = 0,
            total = 0;
            
        Object.keys(page.period)
            .map(t => parseInt(t))
            .sort((a, b) => a - b)
            .forEach((t, i, arr) => {
                if (i === 0) {
                    start = t;
                    total = page.period[t];
                } else if (t === start + total) {
                    total += page.period[t];
                } else {
                    compressed[start] = total;
                    start = t;
                    total = page.period[t];
                }
                
                // 最后一个
                if (i === arr.length - 1) {
                    compressed[start] = total;
                }
            });
            
        page.period = compressed;
        
        // 2. 移除短时间记录(小于30秒)
        Object.keys(page.period).forEach(t => {
            if (page.period[parseInt(t)] < 30) {
                delete page.period[parseInt(t)];
            }
        });
    });
}
同步过滤
// 选择性同步实现
function selectiveSync(filters: {
    minReadTime?: number,
    recentDays?: number,
    documentTypes?: string[]
}) {
    const { minReadTime = 300, recentDays = 30, documentTypes = ['pdf'] } = filters;
    
    return addon.history.getAll()
        .filter(h => h)
        .filter(h => {
            // 1. 过滤阅读时间过短的记录
            if (h.record.totalS < minReadTime) return false;
            
            // 2. 过滤太久未访问的记录
            if (recentDays > 0) {
                const cutoffDate = Date.now() - (recentDays * 24 * 60 * 60 * 1000);
                if (h.record.lastTime < cutoffDate / 1000) return false;
            }
            
            // 3. 过滤不支持的文档类型
            const item = Zotero.Items.get(h.note.relatedItemIDs[0]);
            if (item && documentTypes.length > 0) {
                const contentType = item.getField('contentType') || '';
                return documentTypes.some(type => 
                    contentType.toLowerCase().includes(type.toLowerCase())
                );
            }
            
            return true;
        });
}

故障排除与常见问题

同步失败的排查流程

st=>start: 开始排查
op1=>operation: 检查网络连接
cond1=>condition: 网络正常?
op2=>operation: 检查Zotero同步状态
cond2=>condition: Zotero同步正常?
op3=>operation: 检查主条目同步设置
cond3=>condition: 主条目已同步?
op4=>operation: 检查存储空间
cond4=>condition: 空间充足?
op5=>operation: 检查认证信息
cond5=>condition: 认证有效?
op6=>operation: 查看错误日志
e=>end: 解决问题

st->op1->cond1
cond1(yes)->op2->cond2
cond1(no)->op1
cond2(yes)->op3->cond3
cond2(no)->op2
cond3(yes)->op4->cond4
cond3(no)->op3
cond4(yes)->op5->cond5
cond4(no)->op4
cond5(yes)->op6->e
cond5(no)->op5

常见问题及解决方案

问题1:主条目未同步

症状:在设备A创建的阅读记录在设备B不可见。

解决方案

// 修复主条目同步设置
async function fixMainItemSync() {
    const mainItem = await addon.history.getMainItem();
    if (mainItem.getField("excludeFromSync")) {
        mainItem.setField("excludeFromSync", false);
        await mainItem.saveTx();
        return true;
    }
    return false;
}

操作步骤

  1. 在Zotero中找到"Chartero Main Item"
  2. 右键点击,选择"属性"
  3. 确保"排除在同步之外"未被勾选
  4. 强制同步Zotero库
问题2:历史记录冲突

症状:同步后部分阅读记录丢失或出现异常值。

解决方案

// 手动解决历史记录冲突
async function manualConflictResolution(attId: number) {
    const history = addon.history.getByAttachment(attId);
    if (!history) return;
    
    // 1. 创建冲突备份
    const backupNote = new Zotero.Item("note");
    backupNote.parentID = history.note.parentID;
    backupNote.setNote(`CONFLICT_BACKUP_${new Date().toISOString()}\n${history.note.note}`);
    await backupNote.saveTx();
    
    // 2. 获取远程版本
    const remoteHistory = await fetchRemoteHistory(history.key);
    
    // 3. 使用合并策略解决冲突
    const mergedRecord = mergeConflictResolution(history.record, remoteHistory.record);
    
    // 4. 保存合并结果
    history.record = mergedRecord;
    await addon.history.saveNote(history);
    
    return true;
}
问题3:同步速度慢

症状:同步操作耗时过长,影响使用体验。

解决方案

// 同步性能优化
function optimizeSyncPerformance() {
    // 1. 减少同步频率
    addon.setPref("scanPeriod", 120); // 改为2分钟记录一次
    
    // 2. 启用增量同步
    addon.setPref("incrementalSync", true);
    
    // 3. 增加压缩级别
    addon.setPref("compressionLevel", 6);
    
    // 4. 配置同步时间段
    addon.setPref("syncSchedule", {
        enabled: true,
        startTime: 22, // 晚上10点
        endTime: 6 // 早上6点
    });
}

结论与未来展望

方案对比与选择建议

方案适用场景复杂度可靠性隐私性
内置同步个人使用,简单需求
外部存储多设备,跨平台
WebDAV方案团队协作,高级需求

选择建议

  • 普通用户:优先使用内置同步方案
  • 多设备用户:推荐外部存储方案(Dropbox/OneDrive)
  • 隐私敏感用户/团队:选择WebDAV方案

未来发展方向

Chartero团队已计划在未来版本中增强同步功能,主要方向包括:

  1. 区块链同步:利用去中心化技术实现更安全的同步
  2. 实时协作:支持多人实时共同阅读,显示彼此阅读位置
  3. 智能预测同步:基于用户行为预测同步需求,提前准备数据
  4. 端到端加密:增强数据安全性,保护隐私

最佳实践总结

  1. 定期备份:每周至少创建一次阅读历史完整备份
  2. 冲突监控:启用冲突通知,及时处理同步冲突
  3. 性能平衡:根据设备性能和网络状况调整同步参数
  4. 版本控制:保持所有设备使用相同版本的Chartero插件
  5. 存储空间管理:定期清理不再需要的历史记录

通过本文介绍的技术原理和解决方案,你应该能够解决Chartero插件在跨平台使用中的阅读历史记录同步问题。无论你是普通用户还是技术专家,都能找到适合自己的同步方案,实现无缝的跨设备阅读体验。随着Chartero的不断发展,同步功能将更加完善,为文献阅读和协作提供更强大的支持。

附录:实用工具与代码片段

同步状态检查工具

// 同步状态检查工具
async function syncStatusChecker() {
    const result = {
        timestamp: new Date(),
        zoteroOnline: Zotero.isOnline(),
        mainItemSynced: false,
        syncServiceAvailable: false,
        lastSyncTime: null,
        pendingChanges: 0,
        storageUsage: 0
    };
    
    // 检查主条目同步状态
    try {
        const mainItem = await addon.history.getMainItem();
        result.mainItemSynced = !mainItem.getField("excludeFromSync");
    } catch (e) {
        addon.log("主条目检查失败:", e);
    }
    
    // 检查同步服务状态
    try {
        result.syncServiceAvailable = await testSyncServiceConnection();
    } catch (e) {
        addon.log("同步服务检查失败:", e);
    }
    
    // 检查最后同步时间
    result.lastSyncTime = addon.getPref("lastSyncTime");
    
    // 检查待同步更改
    result.pendingChanges = await countPendingChanges();
    
    // 检查存储空间使用
    result.storageUsage = await calculateStorageUsage();
    
    // 生成报告
    generateSyncReport(result);
    
    return result;
}

批量同步工具

// 批量同步所有历史记录
async function batchSyncAllHistories() {
    const allHistories = addon.history.getAll()
        .filter(h => h) as AttachmentHistory[];
    
    const progressWindow = new ProgressWindow({
        title: "批量同步历史记录",
        closeOnComplete: true
    });
    
    const progressItem = progressWindow.addProgressItem({ 
        label: "正在同步...", 
        progress: 0 
    });
    
    progressWindow.show();
    
    for (let i = 0; i < allHistories.length; i++) {
        const history = allHistories[i];
        progressItem.label = `正在同步: ${history.key}`;
        progressItem.progress = (i / allHistories.length) * 100;
        
        try {
            await syncSingleHistory(history);
        } catch (e) {
            addon.log(`同步失败 ${history.key}:`, e);
            progressItem.label += " [失败]";
        }
        
        await Zotero.Promise.delay(100); // 避免服务器过载
    }
    
    progressItem.label = "同步完成";
    progressItem.progress = 100;
    progressWindow.startCloseTimer(2000);
}

历史记录导出/导入工具

// 导出阅读历史到JSON文件
async function exportHistoriesToJSON(filePath: string) {
    const allHistories = addon.history.getAll()
        .filter(h => h) as AttachmentHistory[];
    
    const exportData = {
        version: addon.version,
        exportTime: new Date().toISOString(),
        histories: allHistories.map(h => ({
            key: h.key,
            record: h.record.toJSON(),
            itemKey: h.note.relatedItemIDs[0]
        }))
    };
    
    await Zotero.File.putContentsAsync(filePath, JSON.stringify(exportData, null, 2));
    return exportData.histories.length;
}

// 从JSON文件导入阅读历史
async function importHistoriesFromJSON(filePath: string) {
    const jsonStr = await Zotero.File.getContentsAsync(filePath);
    const importData = JSON.parse(jsonStr);
    
    if (importData.version !== addon.version) {
        throw new Error(`版本不兼容: 导入数据版本 ${importData.version}, 当前版本 ${addon.version}`);
    }
    
    let imported = 0;
    for (const data of importData.histories) {
        const item = Zotero.Items.getByLibraryAndKey(1, data.itemKey);
        if (!item) continue;
        
        const history = addon.history.getByAttachment(item.id);
        if (history) {
            // 合并现有记录
            history.record.mergeJSON(data.record);
        } else {
            // 创建新记录
            await addon.history.createNewHistory(item, data.record);
        }
        imported++;
    }
    
    return imported;
}

通过这些工具和代码片段,你可以更有效地管理和维护Chartero的阅读历史记录同步功能,解决使用过程中遇到的各种问题。

【免费下载链接】Chartero Chart in Zotero 【免费下载链接】Chartero 项目地址: https://gitcode.com/gh_mirrors/ch/Chartero

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值