// Conflict Resolution Utilities // Handles bookmark conflict resolution strategies (function() { 'use strict'; const ConflictResolver = { // Sync mode configurations MODES: { 'bi-directional': { name: 'bi-directional', description: 'Bi-directional Sync', keepBoth: true, prioritizeLinkding: false, prioritizeBrowser: false }, 'write-only': { name: 'write-only', description: 'Write-Only (Browser Authoritative)', keepBoth: false, prioritizeLinkding: false, prioritizeBrowser: true }, 'read-only': { name: 'read-only', description: 'Read-Only (Linkding Authoritative)', keepBoth: false, prioritizeLinkding: true, prioritizeBrowser: false } }, // Get mode config getModeConfig(mode) { return this.MODES[mode] || this.MODES['bi-directional']; }, // Resolve conflict when same URL exists in different locations resolve(browserBookmark, linkdingBookmark, browserNotes, linkdingNotes, mode, action) { const config = this.getModeConfig(mode); const timestamp = Date.now(); switch (action) { case 'create': // New bookmark in browser return { action: 'create', result: { ...linkdingBookmark, notes: this.formatNotes(browserNotes), title: browserBookmark.title, description: browserBookmark.description }, note: `Created new bookmark: ${browserBookmark.url}`, timestamp: timestamp }; case 'update': // Update existing bookmark if (config.keepBoth) { // Bi-directional: keep Linkding data, merge browser notes return { action: 'update', result: { ...linkdingBookmark, notes: this.mergeNotes(linkdingNotes, browserNotes), title: browserBookmark.title || linkdingBookmark.title, description: browserBookmark.description || linkdingBookmark.description }, note: `Updated existing bookmark. Kept Linkding data.`, timestamp: timestamp }; } // Write-only or read-only: use browser data return { action: 'update', result: { ...linkdingBookmark, notes: this.formatNotes(browserNotes), title: browserBookmark.title, description: browserBookmark.description }, note: `Updated from browser`, timestamp: timestamp }; case 'delete': // Bookmark deleted if (config.prioritizeBrowser) { return { action: 'delete', result: linkdingBookmark, note: `Deleted from Linkding (browser is authoritative)`, timestamp: timestamp }; } if (config.prioritizeLinkding) { return { action: 'ignore', result: linkdingBookmark, note: `Kept Linkding version (read-only mode)`, timestamp: timestamp }; } return { action: 'delete', result: linkdingBookmark, note: `Deleted from Linkding`, timestamp: timestamp }; default: return { action: 'none', result: linkdingBookmark, note: 'No action needed', timestamp: timestamp }; } }, // Format notes object for storage formatNotes(notes) { if (!notes) { return ''; } return JSON.stringify({ path: notes.path || '', userNotes: notes.userNotes || '', autoTags: notes.autoTags || [] }); }, // Merge two notes objects mergeNotes(notes1, notes2) { const p1 = this.parseNotes(notes1); const p2 = this.parseNotes(notes2); return { path: p1.path || p2.path, userNotes: p1.userNotes || p2.userNotes, autoTags: [...(p1.autoTags || []), ...(p2.autoTags || [])] .filter((v, i, a) => a.indexOf(v) === i) }; }, // Parse notes string parseNotes(noteString) { if (!noteString) { return { path: '', userNotes: '', autoTags: [] }; } try { const parsed = JSON.parse(noteString); if (typeof parsed === 'object' && parsed !== null) { return { path: parsed.path || '', userNotes: parsed.userNotes || '', autoTags: parsed.autoTags || [] }; } } catch (e) { // Invalid JSON } return { path: '', userNotes: noteString, autoTags: [] }; }, // Get conflict report getConflictReport(browserBookmark, linkdingBookmark) { const browserNotes = this.parseNotes(browserBookmark.notes || ''); const linkdingNotes = this.parseNotes(linkdingBookmark.notes || ''); return { url: browserBookmark.url, browserPath: browserNotes.path, linkdingPath: linkdingNotes.path, browserNotes: browserNotes.userNotes, linkdingNotes: linkdingNotes.userNotes, differs: browserNotes.path !== linkdingNotes.path || browserNotes.userNotes !== linkdingNotes.userNotes }; }, // Check if sync is needed needsSync(browserBookmark, linkdingBookmark) { if (!linkdingBookmark) { return true; // New bookmark } const browserNotes = this.parseNotes(browserBookmark.notes || ''); const linkdingNotes = this.parseNotes(linkdingBookmark.notes || ''); // Check if paths differ if (browserNotes.path !== linkdingNotes.path) { return true; } // Check if notes differ if (browserNotes.userNotes !== linkdingNotes.userNotes) { return true; } // Check if titles differ if (browserBookmark.title !== linkdingBookmark.title) { return true; } return false; } }; // Export for use in other scripts window.ConflictResolver = ConflictResolver; })();