Plugin follows duplicate behavior behavior of new-entry

This commit is contained in:
2026-08-28 09:37:14 +02:00
parent 50d61371e1
commit 5696c89dee
14 changed files with 105 additions and 16 deletions
+60 -4
View File
@@ -13,6 +13,7 @@ const feedLink = document.getElementById('feed-link');
const authWarning = document.getElementById('auth-warning');
const warningSettingsButton = document.getElementById('warning-settings');
const authSession = document.getElementById('auth-session');
const refetchTitleButton = document.getElementById('refetch-title');
const sessionStore = browser.storage.session;
const t = window.linklogI18n;
@@ -35,8 +36,14 @@ async function hasBackendPermission(backendUrl) {
return browser.permissions.contains({origins: [`${origin}/*`]});
}
// Splits on \n into real <br> line breaks without using innerHTML.
function setStatus(message, isError = false) {
statusEl.textContent = message;
const lines = message.split('\n');
statusEl.replaceChildren(
...lines.flatMap((line, index) => (
index === 0 ? [document.createTextNode(line)] : [document.createElement('br'), document.createTextNode(line)]
)),
);
statusEl.classList.remove('hidden');
statusEl.classList.toggle('error', isError);
statusEl.classList.toggle('success', !isError);
@@ -225,20 +232,54 @@ async function checkExistingLink() {
const settings = await getSettings();
if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl)) || !titleInput.value || !urlInput.value) return;
try {
const url = removeKnownTrackingParams(urlInput.value);
const response = await fetch(`${settings.backendUrl}/api/links/check?${new URLSearchParams({
title: titleInput.value,
url: removeKnownTrackingParams(urlInput.value),
url,
})}`, {
headers: {'Authorization': `Bearer ${settings.accessToken}`},
});
if (response.ok && (await response.json()).exists) {
setStatus(t('duplicateLinkWarning'), true);
if (!response.ok) return;
const data = await response.json();
if (data.exists) {
// Re-derive the match locally: browser URL normalization (e.g. trailing slashes) can
// make the server's raw string comparison disagree even when the URLs are equivalent.
const urlMatches = data.url_matches || (data.stored_url && removeKnownTrackingParams(data.stored_url) === url);
const message = urlMatches ? t('duplicateLinkSameUrl') : t('duplicateLinkDifferentUrl');
setStatus(message, true);
}
} catch (error) {
// Duplicate checking is advisory; submission remains available.
}
}
// Re-fetch the title from the backend scraper, for when the URL was edited after the initial tab-based fill.
let lastScrapedUrl = null;
async function fetchTitle(url, { force = false } = {}) {
const settings = await getSettings();
if (!settings.backendUrl || !settings.accessToken || !(await hasBackendPermission(settings.backendUrl))) return;
if (!url || (!force && (titleInput.value.trim() || url === lastScrapedUrl))) return;
try {
const response = await fetch(`${settings.backendUrl}/api/scrape?url=${encodeURIComponent(url)}`, {
headers: {'Authorization': `Bearer ${settings.accessToken}`},
});
if (!response.ok) {
if (force) setStatus(t('titleFetchFailed'), true);
return;
}
lastScrapedUrl = url;
const data = await response.json();
if (data.title) {
titleInput.value = data.title;
} else if (force) {
setStatus(t('titleFetchFailed'), true);
}
} catch (error) {
if (force) setStatus(t('titleFetchFailed'), true);
}
}
async function handleSubmit(event) {
event.preventDefault();
setStatus(t('submitting'), false);
@@ -305,6 +346,21 @@ async function handleSubmit(event) {
openSettingsButton.addEventListener('click', () => browser.runtime.openOptionsPage());
warningSettingsButton.addEventListener('click', () => browser.runtime.openOptionsPage());
form.addEventListener('submit', handleSubmit);
urlInput.addEventListener('blur', async () => {
await fetchTitle(removeKnownTrackingParams(urlInput.value.trim()));
checkExistingLink();
});
titleInput.addEventListener('blur', checkExistingLink);
refetchTitleButton.addEventListener('click', (e) => {
e.preventDefault();
const url = removeKnownTrackingParams(urlInput.value.trim());
if (!url) {
setStatus(t('titleFetchFailed'), true);
return;
}
titleInput.value = '';
fetchTitle(url, { force: true });
});
populateCurrentTab().then(checkExistingLink);
loadExistingTags();
updateFeedLink();