diff --git a/src/knowmarks/web/static/app.js b/src/knowmarks/web/static/app.js
index 712076d..bd8739c 100644
--- a/src/knowmarks/web/static/app.js
+++ b/src/knowmarks/web/static/app.js
@@ -3074,9 +3074,18 @@ function renderConnectorsPage() {
if (!activeTypes.has('raindrop_api')) availableConnectors.push({name: 'Raindrop.io', icon: 'droplet', desc: 'Sync Raindrop bookmarks', action: 'showConnectorRaindropSetup()'});
if (!activeTypes.has('karakeep_api')) availableConnectors.push({name: 'Karakeep', icon: 'bookmark-check', desc: 'Connect Karakeep instance', action: 'showConnectorKarakeepSetup()'});
if (!activeTypes.has('readwise_reader')) availableConnectors.push({name: 'Readwise Reader', icon: 'book-open', desc: 'Sync documents + highlights', action: 'showConnectorReadwiseSetup()'});
+ if (!activeTypes.has('hackernews')) availableConnectors.push({name: 'Hacker News', icon: 'flame', desc: 'Import favorites or submissions', action: 'showConnectorHNSetup()'});
+ if (!activeTypes.has('reddit_saved')) availableConnectors.push({name: 'Reddit', icon: 'message-circle', desc: 'Sync saved posts', action: 'showConnectorRedditSetup()'});
+ if (!activeTypes.has('linkwarden_api')) availableConnectors.push({name: 'Linkwarden', icon: 'link', desc: 'Connect Linkwarden instance', action: 'showConnectorLinkwardenSetup()'});
+ if (!activeTypes.has('linkding_api')) availableConnectors.push({name: 'Linkding', icon: 'bookmark', desc: 'Connect Linkding instance', action: 'showConnectorLinkdingSetup()'});
+ if (!activeTypes.has('shiori_api')) availableConnectors.push({name: 'Shiori', icon: 'archive', desc: 'Connect Shiori instance', action: 'showConnectorShioriSetup()'});
+ if (!activeTypes.has('wallabag_api')) availableConnectors.push({name: 'Wallabag', icon: 'package', desc: 'Connect Wallabag instance', action: 'showConnectorWallabagSetup()'});
let html = '';
+ // Connector types that support credential reconfiguration
+ const configurableTypes = new Set(['karakeep_api', 'readwise_reader', 'raindrop_api', 'hackernews', 'reddit_saved', 'linkwarden_api', 'linkding_api', 'shiori_api', 'wallabag_api']);
+
// --- Active Connectors ---
if (apiConnectors.length || feeds.length) {
html += `
@@ -3086,6 +3095,7 @@ function renderConnectorsPage() {
const statusClass = c.health_status === 'ok' ? 'vitality-good' : 'vitality-bad';
const syncInfo = c.last_sync_at ? `synced ${timeAgo(c.last_sync_at)}` : 'never synced';
const schedule = c.sync_schedule ? ` · every ${c.sync_schedule}` : '';
+ const canConfigure = configurableTypes.has(c.type);
return `
`;
}).join('')}
+
`;
}
@@ -3282,6 +3294,379 @@ async function connectConnectorReadwise() {
}
window.connectConnectorReadwise = connectConnectorReadwise;
+// --- Hacker News connector ---
+function showConnectorHNSetup() {
+ _connectorInlineSetup('Connect to Hacker News', 'Enter your HN username to import your favorited stories',
+ [{id: 'username', placeholder: 'your_hn_username'}],
+ 'connectConnectorHN()');
+}
+window.showConnectorHNSetup = showConnectorHNSetup;
+
+async function connectConnectorHN() {
+ const username = document.getElementById('cis-username')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!username) { status.textContent = 'Username is required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/hackernews', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, mode: 'favorites', schedule: '1d'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Hacker News: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorHN = connectConnectorHN;
+
+// --- Reddit connector ---
+function showConnectorRedditSetup() {
+ _connectorInlineSetup('Connect to Reddit', 'Enter your Reddit username and session cookie to sync saved posts. Get the cookie value from your browser\'s devtools (Application → Cookies → reddit_session).',
+ [{id: 'username', placeholder: 'your_reddit_username'}, {id: 'session', type: 'password', placeholder: 'reddit_session cookie value'}],
+ 'connectConnectorReddit()');
+}
+window.showConnectorRedditSetup = showConnectorRedditSetup;
+
+async function connectConnectorReddit() {
+ const username = document.getElementById('cis-username')?.value.trim();
+ const session = document.getElementById('cis-session')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!username || !session) { status.textContent = 'Username and session cookie are required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/reddit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, session_cookie: session, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Reddit: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorReddit = connectConnectorReddit;
+
+// --- Linkwarden connector ---
+function showConnectorLinkwardenSetup() {
+ _connectorInlineSetup('Connect to Linkwarden', 'Enter your Linkwarden server URL and API token',
+ [{id: 'url', placeholder: 'https://linkwarden.example.com'}, {id: 'token', type: 'password', placeholder: 'API token'}],
+ 'connectConnectorLinkwarden()');
+}
+window.showConnectorLinkwardenSetup = showConnectorLinkwardenSetup;
+
+async function connectConnectorLinkwarden() {
+ const url = document.getElementById('cis-url')?.value.trim();
+ const token = document.getElementById('cis-token')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!url || !token) { status.textContent = 'Server URL and API token are required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/linkwarden', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, api_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Linkwarden: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorLinkwarden = connectConnectorLinkwarden;
+
+// --- Linkding connector ---
+function showConnectorLinkdingSetup() {
+ _connectorInlineSetup('Connect to Linkding', 'Enter your Linkding server URL and API token (Settings → API Access)',
+ [{id: 'url', placeholder: 'https://linkding.example.com'}, {id: 'token', type: 'password', placeholder: 'API token'}],
+ 'connectConnectorLinkding()');
+}
+window.showConnectorLinkdingSetup = showConnectorLinkdingSetup;
+
+async function connectConnectorLinkding() {
+ const url = document.getElementById('cis-url')?.value.trim();
+ const token = document.getElementById('cis-token')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!url || !token) { status.textContent = 'Server URL and API token are required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/linkding', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, api_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Linkding: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorLinkding = connectConnectorLinkding;
+
+// --- Shiori connector ---
+function showConnectorShioriSetup() {
+ _connectorInlineSetup('Connect to Shiori', 'Enter your Shiori server URL, username, and password',
+ [{id: 'url', placeholder: 'https://shiori.example.com'}, {id: 'username', placeholder: 'username'}, {id: 'password', type: 'password', placeholder: 'password'}],
+ 'connectConnectorShiori()');
+}
+window.showConnectorShioriSetup = showConnectorShioriSetup;
+
+async function connectConnectorShiori() {
+ const url = document.getElementById('cis-url')?.value.trim();
+ const username = document.getElementById('cis-username')?.value.trim();
+ const password = document.getElementById('cis-password')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!url) { status.textContent = 'Server URL is required.'; return; }
+ if (!username || !password) { status.textContent = 'Username and password are required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/shiori', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, username, password, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Shiori: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorShiori = connectConnectorShiori;
+
+// --- Wallabag connector ---
+function showConnectorWallabagSetup() {
+ _connectorInlineSetup('Connect to Wallabag', 'Enter your Wallabag server URL and OAuth2 credentials (Settings → API Clients Management)',
+ [
+ {id: 'url', placeholder: 'https://wallabag.example.com'},
+ {id: 'client-id', placeholder: 'Client ID'},
+ {id: 'client-secret', type: 'password', placeholder: 'Client secret'},
+ {id: 'username', placeholder: 'Username'},
+ {id: 'password', type: 'password', placeholder: 'Password'},
+ ],
+ 'connectConnectorWallabag()');
+}
+window.showConnectorWallabagSetup = showConnectorWallabagSetup;
+
+async function connectConnectorWallabag() {
+ const url = document.getElementById('cis-url')?.value.trim();
+ const clientId = document.getElementById('cis-client-id')?.value.trim();
+ const clientSecret = document.getElementById('cis-client-secret')?.value.trim();
+ const username = document.getElementById('cis-username')?.value.trim();
+ const password = document.getElementById('cis-password')?.value.trim();
+ const status = document.getElementById('cis-status');
+ if (!url) { status.textContent = 'Server URL is required.'; return; }
+ if (!clientId || !clientSecret || !username || !password) { status.textContent = 'All OAuth2 credentials are required.'; return; }
+ status.textContent = 'Connecting...';
+ try {
+ const resp = await fetch('/api/connector/wallabag', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, client_id: clientId, client_secret: clientSecret, username, password, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Wallabag: ${data.new_items} items imported`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.connectConnectorWallabag = connectConnectorWallabag;
+
+// --- Configure form for active connectors ---
+function _connectorConfigPanel(title, hint, fields, connectFn) {
+ const container = document.getElementById('connector-configure-panel');
+ if (!container) return;
+ // Toggle off if same form is already open
+ if (container._currentTitle === title && container.style.display !== 'none') {
+ container.style.display = 'none';
+ container._currentTitle = null;
+ return;
+ }
+ container._currentTitle = title;
+ const inputs = fields.map(f => ``).join('');
+ container.style.display = 'block';
+ container.innerHTML = `
+
+
${title}
+
${hint}
+ ${inputs}
+
+
+
+
+
+
`;
+}
+
+function showConnectorConfigureForm(connectorType) {
+ if (connectorType === 'karakeep_api') {
+ _connectorConfigPanel('Reconfigure Karakeep', 'Update your Karakeep server URL and API key',
+ [{id: 'url', placeholder: 'https://karakeep.example.com'}, {id: 'key', type: 'password', placeholder: 'API key'}],
+ 'configureConnectorKarakeep()');
+ } else if (connectorType === 'readwise_reader') {
+ _connectorConfigPanel('Reconfigure Readwise Reader', 'Update your access token (from readwise.io/access_token)',
+ [{id: 'token', type: 'password', placeholder: 'Access token'}],
+ 'configureConnectorReadwise()');
+ } else if (connectorType === 'raindrop_api') {
+ _connectorConfigPanel('Reconfigure Raindrop.io', 'Update your test token (from app.raindrop.io/settings/integrations)',
+ [{id: 'token', type: 'password', placeholder: 'Test token'}],
+ 'configureConnectorRaindrop()');
+ } else if (connectorType === 'hackernews') {
+ _connectorConfigPanel('Reconfigure Hacker News', 'Update your HN username',
+ [{id: 'username', placeholder: 'your_hn_username'}],
+ 'configureConnectorHN()');
+ } else if (connectorType === 'reddit_saved') {
+ _connectorConfigPanel('Reconfigure Reddit', 'Update your Reddit username and session cookie',
+ [{id: 'username', placeholder: 'your_reddit_username'}, {id: 'session', type: 'password', placeholder: 'reddit_session cookie value'}],
+ 'configureConnectorReddit()');
+ } else if (connectorType === 'linkwarden_api') {
+ _connectorConfigPanel('Reconfigure Linkwarden', 'Update your Linkwarden server URL and API token',
+ [{id: 'url', placeholder: 'https://linkwarden.example.com'}, {id: 'token', type: 'password', placeholder: 'API token'}],
+ 'configureConnectorLinkwarden()');
+ } else if (connectorType === 'linkding_api') {
+ _connectorConfigPanel('Reconfigure Linkding', 'Update your Linkding server URL and API token',
+ [{id: 'url', placeholder: 'https://linkding.example.com'}, {id: 'token', type: 'password', placeholder: 'API token'}],
+ 'configureConnectorLinkding()');
+ } else if (connectorType === 'shiori_api') {
+ _connectorConfigPanel('Reconfigure Shiori', 'Update your Shiori server URL and credentials',
+ [{id: 'url', placeholder: 'https://shiori.example.com'}, {id: 'username', placeholder: 'username'}, {id: 'password', type: 'password', placeholder: 'password'}],
+ 'configureConnectorShiori()');
+ } else if (connectorType === 'wallabag_api') {
+ _connectorConfigPanel('Reconfigure Wallabag', 'Update your Wallabag server URL and OAuth2 credentials',
+ [
+ {id: 'url', placeholder: 'https://wallabag.example.com'},
+ {id: 'client-id', placeholder: 'Client ID'},
+ {id: 'client-secret', type: 'password', placeholder: 'Client secret'},
+ {id: 'username', placeholder: 'Username'},
+ {id: 'password', type: 'password', placeholder: 'Password'},
+ ],
+ 'configureConnectorWallabag()');
+ }
+}
+window.showConnectorConfigureForm = showConnectorConfigureForm;
+
+async function configureConnectorKarakeep() {
+ const url = document.getElementById('ccp-url')?.value.trim();
+ const key = document.getElementById('ccp-key')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!url || !key) { status.textContent = 'Both URL and API key are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/karakeep', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, api_key: key, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Karakeep: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorKarakeep = configureConnectorKarakeep;
+
+async function configureConnectorReadwise() {
+ const token = document.getElementById('ccp-token')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!token) { status.textContent = 'Access token is required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/readwise', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({access_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Readwise: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorReadwise = configureConnectorReadwise;
+
+async function configureConnectorRaindrop() {
+ const token = document.getElementById('ccp-token')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!token) { status.textContent = 'Token is required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/raindrop', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({api_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Raindrop: updated, ${data.total_found} bookmarks`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorRaindrop = configureConnectorRaindrop;
+
+async function configureConnectorHN() {
+ const username = document.getElementById('ccp-username')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!username) { status.textContent = 'Username is required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/hackernews', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, mode: 'favorites', schedule: '1d'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Hacker News: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorHN = configureConnectorHN;
+
+async function configureConnectorReddit() {
+ const username = document.getElementById('ccp-username')?.value.trim();
+ const session = document.getElementById('ccp-session')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!username || !session) { status.textContent = 'Username and session cookie are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/reddit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, session_cookie: session, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Reddit: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorReddit = configureConnectorReddit;
+
+async function configureConnectorLinkwarden() {
+ const url = document.getElementById('ccp-url')?.value.trim();
+ const token = document.getElementById('ccp-token')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!url || !token) { status.textContent = 'Server URL and API token are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/linkwarden', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, api_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Linkwarden: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorLinkwarden = configureConnectorLinkwarden;
+
+async function configureConnectorLinkding() {
+ const url = document.getElementById('ccp-url')?.value.trim();
+ const token = document.getElementById('ccp-token')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!url || !token) { status.textContent = 'Server URL and API token are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/linkding', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, api_token: token, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Linkding: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorLinkding = configureConnectorLinkding;
+
+async function configureConnectorShiori() {
+ const url = document.getElementById('ccp-url')?.value.trim();
+ const username = document.getElementById('ccp-username')?.value.trim();
+ const password = document.getElementById('ccp-password')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!url || !username || !password) { status.textContent = 'Server URL, username, and password are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/shiori', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, username, password, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Shiori: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorShiori = configureConnectorShiori;
+
+async function configureConnectorWallabag() {
+ const url = document.getElementById('ccp-url')?.value.trim();
+ const clientId = document.getElementById('ccp-client-id')?.value.trim();
+ const clientSecret = document.getElementById('ccp-client-secret')?.value.trim();
+ const username = document.getElementById('ccp-username')?.value.trim();
+ const password = document.getElementById('ccp-password')?.value.trim();
+ const status = document.getElementById('ccp-status');
+ if (!url || !clientId || !clientSecret || !username || !password) { status.textContent = 'All fields are required.'; return; }
+ status.textContent = 'Updating...';
+ try {
+ const resp = await fetch('/api/connector/wallabag', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_url: url, client_id: clientId, client_secret: clientSecret, username, password, schedule: '6h'}) });
+ const data = await resp.json();
+ if (data.error || data.error_message) { status.textContent = data.error || data.error_message; return; }
+ showToast(`Wallabag: updated, ${data.new_items} new items`);
+ setTimeout(() => showConnectorsPage(), 1500);
+ } catch (e) { status.textContent = 'Failed: ' + e.message; }
+}
+window.configureConnectorWallabag = configureConnectorWallabag;
+
function showAddFeedForm() {
const container = document.getElementById('add-feed-form');
if (!container) return;
@@ -4812,49 +5197,6 @@ function renderSettings(data) {
settingsField('github.token', 'GitHub Token', s.github?.token, 'password', ''),
], ``);
- // Connectors
- html += settingsSection('Karakeep', [
- settingsField('karakeep.url', 'Server URL', s.karakeep?.url, 'text', 'https://karakeep.example.com'),
- settingsField('karakeep.api_key', 'API Key', s.karakeep?.api_key, 'password', ''),
- ], ``);
-
- html += settingsSection('Readwise Reader', [
- settingsField('readwise.token', 'Access Token', s.readwise?.token, 'password', ''),
- ], ``);
-
- html += settingsSection('Hacker News', [
- settingsField('hackernews.username', 'Username', s.hackernews?.username, 'text', 'your_hn_username'),
- ], ``);
-
- html += settingsSection('Reddit', [
- settingsField('reddit.username', 'Username', s.reddit?.username, 'text', 'your_reddit_username'),
- settingsField('reddit.session_cookie', 'Session Cookie', s.reddit?.session_cookie, 'password', 'reddit_session value'),
- ], ``);
-
- html += settingsSection('Linkwarden', [
- settingsField('linkwarden.url', 'Server URL', s.linkwarden?.url, 'text', 'https://linkwarden.example.com'),
- settingsField('linkwarden.api_key', 'API Key', s.linkwarden?.api_key, 'password', ''),
- ], ``);
-
- html += settingsSection('Linkding', [
- settingsField('linkding.url', 'Server URL', s.linkding?.url, 'text', 'https://linkding.example.com'),
- settingsField('linkding.api_key', 'API Key', s.linkding?.api_key, 'password', ''),
- ], ``);
-
- html += settingsSection('Shiori', [
- settingsField('shiori.url', 'Server URL', s.shiori?.url, 'text', 'https://shiori.example.com'),
- settingsField('shiori.username', 'Username', s.shiori?.username, 'text', ''),
- settingsField('shiori.password', 'Password', s.shiori?.password, 'password', ''),
- ], ``);
-
- html += settingsSection('Wallabag', [
- settingsField('wallabag.url', 'Server URL', s.wallabag?.url, 'text', 'https://wallabag.example.com'),
- settingsField('wallabag.client_id', 'Client ID', s.wallabag?.client_id, 'text', ''),
- settingsField('wallabag.client_secret', 'Client Secret', s.wallabag?.client_secret, 'password', ''),
- settingsField('wallabag.username', 'Username', s.wallabag?.username, 'text', ''),
- settingsField('wallabag.password', 'Password', s.wallabag?.password, 'password', ''),
- ], ``);
-
// About
const pythonVer = data.python_version || '';
const embedModel = data.embedding_model || '';
@@ -5154,22 +5496,6 @@ async function testConnection(provider) {
const v = (id) => document.getElementById(`settings-${id}`)?.value || '';
if (provider === 'llm') {
body = { provider: 'llm', base_url: v('llm.base_url') };
- } else if (provider === 'karakeep') {
- body = { provider: 'karakeep', base_url: v('karakeep.url'), api_key: v('karakeep.api_key') };
- } else if (provider === 'readwise') {
- body = { provider: 'readwise', token: v('readwise.token') };
- } else if (provider === 'hackernews') {
- body = { provider: 'hackernews', username: v('hackernews.username') };
- } else if (provider === 'reddit') {
- body = { provider: 'reddit', username: v('reddit.username'), session_cookie: v('reddit.session_cookie') };
- } else if (provider === 'linkwarden') {
- body = { provider: 'linkwarden', base_url: v('linkwarden.url'), api_key: v('linkwarden.api_key') };
- } else if (provider === 'linkding') {
- body = { provider: 'linkding', base_url: v('linkding.url'), api_key: v('linkding.api_key') };
- } else if (provider === 'shiori') {
- body = { provider: 'shiori', base_url: v('shiori.url'), username: v('shiori.username'), password: v('shiori.password') };
- } else if (provider === 'wallabag') {
- body = { provider: 'wallabag', base_url: v('wallabag.url'), client_id: v('wallabag.client_id'), client_secret: v('wallabag.client_secret'), username: v('wallabag.username'), password: v('wallabag.password') };
} else {
const embedType = document.getElementById('settings-embedding.provider')?.value || 'ollama';
body = { provider: 'embedding', type: embedType, api_url: v('embedding.api_url') };