Initial commit: Everclone self-hosted Evernote alternative
This commit is contained in:
commit
ee2ee84de6
51 files changed
+17728
No files matched your search
@@ -0,0 +1,183 @@
|
||||
const DEFAULT_SERVER = "https://everclone.vloser.us";
|
||||
const DEFAULT_KEY = "evc_master_vloser_key_2026";
|
||||
|
||||
let currentMode = 'article';
|
||||
let activeTab = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const stored = await chrome.storage.local.get(['serverUrl', 'apiKey', 'lastNotebook']);
|
||||
const serverUrl = (stored.serverUrl || DEFAULT_SERVER).replace(/\/$/, '');
|
||||
const apiKey = stored.apiKey || DEFAULT_KEY;
|
||||
|
||||
document.getElementById('serverUrl').value = serverUrl;
|
||||
document.getElementById('apiKey').value = apiKey;
|
||||
|
||||
if (!apiKey || apiKey === '<YOUR_API_KEY>') {
|
||||
document.getElementById('settingsPanel').style.display = 'block';
|
||||
}
|
||||
|
||||
document.getElementById('toggleSettings').addEventListener('click', () => {
|
||||
const p = document.getElementById('settingsPanel');
|
||||
p.style.display = p.style.display === 'block' ? 'none' : 'block';
|
||||
});
|
||||
|
||||
document.getElementById('saveSettingsBtn').addEventListener('click', async () => {
|
||||
const s = document.getElementById('serverUrl').value.trim().replace(/\/$/, '');
|
||||
const k = document.getElementById('apiKey').value.trim();
|
||||
await chrome.storage.local.set({ serverUrl: s, apiKey: k });
|
||||
document.getElementById('settingsPanel').style.display = 'none';
|
||||
loadNotebooks(s, k);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.mode-btn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.mode-btn').forEach((b) => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentMode = btn.getAttribute('data-mode');
|
||||
});
|
||||
});
|
||||
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
activeTab = tab;
|
||||
if (tab && tab.title) {
|
||||
document.getElementById('noteTitle').value = tab.title;
|
||||
}
|
||||
|
||||
await loadNotebooks(serverUrl, apiKey, stored.lastNotebook);
|
||||
|
||||
document.getElementById('clipBtn').addEventListener('click', handleClip);
|
||||
});
|
||||
|
||||
async function loadNotebooks(serverUrl, apiKey, preferredNb) {
|
||||
if (!serverUrl || !apiKey) return;
|
||||
try {
|
||||
const res = await fetch(`${serverUrl}/api/hooks/note?action=notebooks`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data.notebooks) && data.notebooks.length > 0) {
|
||||
const sel = document.getElementById('notebookSelect');
|
||||
sel.innerHTML = '';
|
||||
data.notebooks.forEach((nb) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = nb.name;
|
||||
opt.textContent = nb.name;
|
||||
if (preferredNb ? nb.name === preferredNb : nb.name === 'Web Clips' || nb.isDefault) {
|
||||
opt.selected = true;
|
||||
}
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// keep default options
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClip() {
|
||||
const btn = document.getElementById('clipBtn');
|
||||
const statusBox = document.getElementById('statusBox');
|
||||
statusBox.className = 'status';
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Clipping & saving graphics...';
|
||||
|
||||
try {
|
||||
const serverUrl = document.getElementById('serverUrl').value.trim().replace(/\/$/, '');
|
||||
const apiKey = document.getElementById('apiKey').value.trim();
|
||||
const title = document.getElementById('noteTitle').value.trim();
|
||||
const notebook = document.getElementById('notebookSelect').value;
|
||||
const tags = document.getElementById('noteTags').value.split(',').map((t) => t.trim()).filter(Boolean);
|
||||
const comment = document.getElementById('noteComment').value.trim();
|
||||
|
||||
await chrome.storage.local.set({ serverUrl, apiKey, lastNotebook: notebook });
|
||||
|
||||
let captured = { pageHtml: '', selectionHtml: '', url: activeTab?.url || '' };
|
||||
if (activeTab?.id && currentMode !== 'bookmark') {
|
||||
const results = await chrome.scripting.executeScript({
|
||||
target: { tabId: activeTab.id },
|
||||
func: () => {
|
||||
// Clone document and resolve lazy-loaded images & relative URLs inside the live tab
|
||||
const clone = document.documentElement.cloneNode(true);
|
||||
clone.querySelectorAll('script, style, noscript, iframe').forEach((el) => el.remove());
|
||||
clone.querySelectorAll('img').forEach((img) => {
|
||||
const best =
|
||||
img.getAttribute('data-img-url') ||
|
||||
img.getAttribute('data-src') ||
|
||||
img.getAttribute('data-lazy-src') ||
|
||||
img.getAttribute('data-original') ||
|
||||
img.currentSrc ||
|
||||
img.src;
|
||||
if (best) {
|
||||
try {
|
||||
img.setAttribute('src', new URL(best, location.href).href);
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
let selHtml = '';
|
||||
const sel = window.getSelection();
|
||||
if (sel && sel.rangeCount > 0 && !sel.isCollapsed) {
|
||||
const container = document.createElement('div');
|
||||
for (let i = 0; i < sel.rangeCount; i++) {
|
||||
container.appendChild(sel.getRangeAt(i).cloneContents());
|
||||
}
|
||||
container.querySelectorAll('img').forEach((img) => {
|
||||
if (img.src) img.setAttribute('src', img.src);
|
||||
});
|
||||
selHtml = container.innerHTML;
|
||||
}
|
||||
return {
|
||||
pageHtml: '<!DOCTYPE html><html>' + clone.innerHTML + '</html>',
|
||||
bodyHtml: clone.querySelector('body')?.innerHTML || '',
|
||||
selectionHtml: selHtml,
|
||||
url: location.href,
|
||||
};
|
||||
},
|
||||
});
|
||||
if (results && results[0] && results[0].result) {
|
||||
captured = results[0].result;
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title,
|
||||
url: captured.url || activeTab?.url || '',
|
||||
notebook,
|
||||
tags,
|
||||
comment,
|
||||
clipMode: currentMode,
|
||||
};
|
||||
|
||||
if (currentMode === 'article') {
|
||||
payload.pageHtml = captured.pageHtml;
|
||||
} else if (currentMode === 'selection') {
|
||||
payload.html = captured.selectionHtml || '<p>No text selected on page.</p>';
|
||||
} else if (currentMode === 'full') {
|
||||
payload.html = captured.bodyHtml || captured.pageHtml;
|
||||
} else if (currentMode === 'bookmark') {
|
||||
payload.html = `<p>Bookmarked: <a href="${payload.url}" target="_blank">${payload.url}</a></p>`;
|
||||
}
|
||||
|
||||
const res = await fetch(`${serverUrl}/api/hooks/note`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(data.error || `HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
statusBox.className = 'status ok';
|
||||
statusBox.innerHTML = `✓ Saved to <strong>${notebook}</strong>! <a href="${serverUrl}/?noteId=${data.noteId}" target="_blank" style="color:#fff;text-decoration:underline;margin-left:6px;">Open Note</a>`;
|
||||
btn.textContent = 'Clipped!';
|
||||
} catch (err) {
|
||||
statusBox.className = 'status err';
|
||||
statusBox.textContent = 'Error: ' + (err.message || String(err));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Save Clip to Everclone';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user