feat: loading AI + tooltips + auto-save silencieux + hover transparent fix
This commit is contained in:
parent
a662a4a3c2
commit
adbeb54887
@ -96,6 +96,8 @@ function createMenu(items, parentEl) {
|
|||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
color: 'var(--text-primary)',
|
color: 'var(--text-primary)',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
background: 'transparent',
|
||||||
|
transition: 'background 0.1s',
|
||||||
});
|
});
|
||||||
el.innerHTML = item.label;
|
el.innerHTML = item.label;
|
||||||
if (item.hint) {
|
if (item.hint) {
|
||||||
@ -230,35 +232,41 @@ export async function createAIToolbar(container, getEditorView) {
|
|||||||
padding: '3px 8px',
|
padding: '3px 8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
});
|
});
|
||||||
aiBtn.title = 'AI Inline Completion (Ctrl+J)';
|
aiBtn.title = 'AI Inline Completion (Ctrl+J) — Complète le texte automatiquement';
|
||||||
aiBtn.addEventListener('click', async () => {
|
aiBtn.addEventListener('click', async () => {
|
||||||
const v = ev();
|
const v = ev();
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
const text = getSelection(v);
|
const text = getSelection(v);
|
||||||
if (!text.trim()) return;
|
if (!text.trim()) return;
|
||||||
|
const origHTML = aiBtn.innerHTML;
|
||||||
|
aiBtn.innerHTML = '⏳ AI...';
|
||||||
|
aiBtn.disabled = true;
|
||||||
try {
|
try {
|
||||||
const result = await aiAction('inline-complete', text);
|
const result = await aiAction('inline-complete', text);
|
||||||
replaceSelection(v, result, 'append');
|
replaceSelection(v, result, 'append');
|
||||||
showToast('AI: complétion ajoutée', 'success');
|
showToast('AI: complétion ajoutée', 'success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast('AI: ' + (String(e.message || e).includes('401') ? 'clé API invalide' : e.message), 'error');
|
showToast('AI: ' + (String(e.message || e).includes('401') ? 'clé API invalide' : e.message), 'error');
|
||||||
|
} finally {
|
||||||
|
aiBtn.innerHTML = origHTML;
|
||||||
|
aiBtn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Edit menu ──
|
// ── Edit menu ──
|
||||||
const editBtn = createDropdownBtn('Éditer', [
|
const editBtn = createDropdownBtn('Éditer', [
|
||||||
{ label: '🪄 Improve writing', action: () => action('improve') },
|
{ label: '🪄 Improve writing', action: () => action('improve'), hint: 'Améliore la qualité du texte' },
|
||||||
{ label: '🔤 Fix spelling & grammar', action: () => action('fix-spelling') },
|
{ label: '🔤 Fix spelling & grammar', action: () => action('fix-spelling'), hint: 'Corrige les fautes' },
|
||||||
{ label: '📏 Make shorter', action: () => action('make-shorter') },
|
{ label: '📏 Make shorter', action: () => action('make-shorter'), hint: 'Rend le texte plus concis' },
|
||||||
{ label: '📐 Make longer', action: () => action('make-longer') },
|
{ label: '📐 Make longer', action: () => action('make-longer'), hint: 'Ajoute des détails' },
|
||||||
{ label: '📋 Simplify language', action: () => action('simplify') },
|
{ label: '📋 Simplify language', action: () => action('simplify'), hint: 'Simplifie le langage' },
|
||||||
]);
|
], 'Modifie le texte sélectionné');
|
||||||
|
|
||||||
// ── Tone menu ──
|
// ── Tone menu ──
|
||||||
const toneBtn = createDropdownBtn('Ton', [
|
const toneBtn = createDropdownBtn('Ton', [
|
||||||
{ label: '💼 Professional tone', action: () => action('tone', { tone: 'professional' }) },
|
{ label: '💼 Professional tone', action: () => action('tone', { tone: 'professional' }) },
|
||||||
{ label: '💬 Casual tone', action: () => action('tone', { tone: 'casual' }) },
|
{ label: '💬 Casual tone', action: () => action('tone', { tone: 'casual' }) },
|
||||||
]);
|
], 'Change le ton du texte');
|
||||||
|
|
||||||
// ── Translate menu ──
|
// ── Translate menu ──
|
||||||
const translateBtn = createDropdownBtn('Traduire', [
|
const translateBtn = createDropdownBtn('Traduire', [
|
||||||
@ -268,22 +276,23 @@ export async function createAIToolbar(container, getEditorView) {
|
|||||||
{ label: '🇩🇪 German', action: () => action('translate', { target_lang: 'German' }) },
|
{ label: '🇩🇪 German', action: () => action('translate', { target_lang: 'German' }) },
|
||||||
{ label: '🇫🇷 French', action: () => action('translate', { target_lang: 'French' }) },
|
{ label: '🇫🇷 French', action: () => action('translate', { target_lang: 'French' }) },
|
||||||
{ label: '🇪🇸 Spanish', action: () => action('translate', { target_lang: 'Spanish' }) },
|
{ label: '🇪🇸 Spanish', action: () => action('translate', { target_lang: 'Spanish' }) },
|
||||||
]);
|
], 'Traduit le texte sélectionné');
|
||||||
|
|
||||||
// ── Generate menu ──
|
// ── Generate menu ──
|
||||||
const genBtn = createDropdownBtn('Générer', [
|
const genBtn = createDropdownBtn('Générer', [
|
||||||
{ label: 'ℹ️ Explain this', action: () => action('explain', {}, 'append') },
|
{ label: 'ℹ️ Explain this', action: () => action('explain', {}, 'append') },
|
||||||
{ label: '📝 Summarize', action: () => action('summarize', {}, 'append') },
|
{ label: '📝 Summarize', action: () => action('summarize', {}, 'append') },
|
||||||
{ label: '✏️ Continue writing', action: () => action('continue', {}, 'append') },
|
{ label: '✏️ Continue writing', action: () => action('continue', {}, 'append') },
|
||||||
]);
|
], 'Génère du contenu à partir de la sélection');
|
||||||
|
|
||||||
// ── Custom Rewrite ──
|
// ── Custom Rewrite ──
|
||||||
const rewriteBtn = document.createElement('button');
|
const rewriteBtn = document.createElement('button');
|
||||||
rewriteBtn.className = 'ai-toolbar-btn';
|
rewriteBtn.className = 'ai-toolbar-btn';
|
||||||
rewriteBtn.innerHTML = '💬 Réécrire';
|
rewriteBtn.innerHTML = '💬 Réécrire';
|
||||||
|
rewriteBtn.title = 'Réécrit le texte selon vos instructions';
|
||||||
Object.assign(rewriteBtn.style, {
|
Object.assign(rewriteBtn.style, {
|
||||||
fontSize: '0.7rem',
|
fontSize: '0.7rem',
|
||||||
background: 'none',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: '3px 6px',
|
padding: '3px 6px',
|
||||||
@ -311,7 +320,7 @@ export async function createAIToolbar(container, getEditorView) {
|
|||||||
{ label: '📊 Convert to table', action: () => action('to-table') },
|
{ label: '📊 Convert to table', action: () => action('to-table') },
|
||||||
{ label: '⚙️ Generate frontmatter', action: () => action('frontmatter', {}, 'before') },
|
{ label: '⚙️ Generate frontmatter', action: () => action('frontmatter', {}, 'before') },
|
||||||
{ label: '🔷 Convert to canvas', action: () => action('to-canvas', {}, 'append') },
|
{ label: '🔷 Convert to canvas', action: () => action('to-canvas', {}, 'append') },
|
||||||
]);
|
], 'Outils de conversion');
|
||||||
|
|
||||||
toolbar.appendChild(aiBtn);
|
toolbar.appendChild(aiBtn);
|
||||||
toolbar.appendChild(createSeparator());
|
toolbar.appendChild(createSeparator());
|
||||||
@ -363,10 +372,11 @@ export async function createAIToolbar(container, getEditorView) {
|
|||||||
return toolbar;
|
return toolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDropdownBtn(text, items) {
|
function createDropdownBtn(text, items, tooltip = '') {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'ai-toolbar-btn';
|
btn.className = 'ai-toolbar-btn';
|
||||||
btn.innerHTML = text + ' ▾';
|
btn.innerHTML = text + ' ▾';
|
||||||
|
btn.title = tooltip || text;
|
||||||
Object.assign(btn.style, {
|
Object.assign(btn.style, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
fontSize: '0.7rem',
|
fontSize: '0.7rem',
|
||||||
|
|||||||
@ -319,7 +319,7 @@ async function openEditor(vaultName, filePath) {
|
|||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
clearTimeout(window._obsigateAutoSaveTimer);
|
clearTimeout(window._obsigateAutoSaveTimer);
|
||||||
window._obsigateAutoSaveTimer = setTimeout(() => saveFile(), 2000);
|
window._obsigateAutoSaveTimer = setTimeout(() => saveFile(true), 2000);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@ -417,17 +417,19 @@ function closeEditor() {
|
|||||||
state.editorPath = null;
|
state.editorPath = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveFile() {
|
async function saveFile(silent = false) {
|
||||||
if ((!state.editorView && !state.fallbackEditorEl) || !state.editorVault || !state.editorPath) return;
|
if ((!state.editorView && !state.fallbackEditorEl) || !state.editorVault || !state.editorPath) return;
|
||||||
|
|
||||||
const content = state.editorView ? state.editorView.state.doc.toString() : state.fallbackEditorEl.value;
|
const content = state.editorView ? state.editorView.state.doc.toString() : state.fallbackEditorEl.value;
|
||||||
const saveBtn = document.getElementById("editor-save");
|
const saveBtn = document.getElementById("editor-save");
|
||||||
const originalHTML = saveBtn.innerHTML;
|
const originalHTML = saveBtn ? saveBtn.innerHTML : '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (saveBtn && !silent) {
|
||||||
saveBtn.disabled = true;
|
saveBtn.disabled = true;
|
||||||
saveBtn.innerHTML = '<i data-lucide="loader" style="width:16px;height:16px"></i>';
|
saveBtn.innerHTML = '<i data-lucide="loader" style="width:16px;height:16px"></i>';
|
||||||
safeCreateIcons();
|
safeCreateIcons();
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/file/${encodeURIComponent(state.editorVault)}/save?path=${encodeURIComponent(state.editorPath)}`, {
|
const response = await fetch(`/api/file/${encodeURIComponent(state.editorVault)}/save?path=${encodeURIComponent(state.editorPath)}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@ -440,6 +442,16 @@ async function saveFile() {
|
|||||||
throw new Error(error.detail || "Erreur de sauvegarde");
|
throw new Error(error.detail || "Erreur de sauvegarde");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (silent) {
|
||||||
|
// Auto-save: brief visual feedback
|
||||||
|
if (saveBtn) {
|
||||||
|
saveBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px"></i>';
|
||||||
|
safeCreateIcons();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (saveBtn) saveBtn.innerHTML = originalHTML;
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
saveBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px"></i>';
|
saveBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px"></i>';
|
||||||
safeCreateIcons();
|
safeCreateIcons();
|
||||||
|
|
||||||
@ -451,6 +463,7 @@ async function saveFile() {
|
|||||||
openFile(_savedVault, _savedPath);
|
openFile(_savedVault, _savedPath);
|
||||||
}
|
}
|
||||||
}, 800);
|
}, 800);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Save error:", err);
|
console.error("Save error:", err);
|
||||||
alert(`Erreur: ${err.message}`);
|
alert(`Erreur: ${err.message}`);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user