229 lines
6.4 KiB
JavaScript
229 lines
6.4 KiB
JavaScript
// 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;
|
|
|
|
})(); |