Add backlink
This commit is contained in:
@ -5,6 +5,7 @@ import { markdown } from '@codemirror/lang-markdown';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { LinkInserter } from './link-inserter.js';
|
||||
|
||||
// Import du mode Vim
|
||||
let vimExtension = null;
|
||||
@ -254,6 +255,9 @@ class MarkdownEditor {
|
||||
htmx.process(this.preview);
|
||||
}
|
||||
|
||||
// Intercepter les clics sur les liens internes (avec hx-get)
|
||||
this.setupInternalLinkHandlers();
|
||||
|
||||
if (typeof hljs !== 'undefined') {
|
||||
this.preview.querySelectorAll('pre code').forEach(block => {
|
||||
hljs.highlightElement(block);
|
||||
@ -264,6 +268,41 @@ class MarkdownEditor {
|
||||
}
|
||||
}
|
||||
|
||||
setupInternalLinkHandlers() {
|
||||
// Trouver tous les liens avec hx-get (liens internes)
|
||||
const internalLinks = this.preview.querySelectorAll('a[hx-get]');
|
||||
|
||||
internalLinks.forEach(link => {
|
||||
// Retirer les anciens listeners pour éviter les doublons
|
||||
link.replaceWith(link.cloneNode(true));
|
||||
});
|
||||
|
||||
// Ré-sélectionner après clonage
|
||||
const freshLinks = this.preview.querySelectorAll('a[hx-get]');
|
||||
|
||||
freshLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = link.getAttribute('hx-get');
|
||||
const targetElement = link.getAttribute('hx-target') || '#editor-container';
|
||||
const swapMethod = link.getAttribute('hx-swap') || 'innerHTML';
|
||||
|
||||
console.log('[InternalLink] Clicked:', target);
|
||||
|
||||
if (target && typeof htmx !== 'undefined') {
|
||||
htmx.ajax('GET', target, {
|
||||
target: targetElement,
|
||||
swap: swapMethod
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('[Preview] Setup', freshLinks.length, 'internal link handlers');
|
||||
}
|
||||
|
||||
syncToTextarea() {
|
||||
if (this.editorView && this.textarea) {
|
||||
this.textarea.value = this.editorView.state.doc.toString();
|
||||
@ -332,6 +371,7 @@ class SlashCommands {
|
||||
{ name: 'list', snippet: '- ' },
|
||||
{ name: 'date', snippet: () => new Date().toLocaleDateString('fr-FR') },
|
||||
{ name: 'link', snippet: '[texte](url)' },
|
||||
{ name: 'ilink', isModal: true, handler: () => this.openLinkInserter() },
|
||||
{ name: 'bold', snippet: '**texte**' },
|
||||
{ name: 'italic', snippet: '*texte*' },
|
||||
{ name: 'code', snippet: '`code`' },
|
||||
@ -612,6 +652,15 @@ class SlashCommands {
|
||||
return;
|
||||
}
|
||||
|
||||
// Commande spéciale avec modal (comme /ilink)
|
||||
if (command.isModal && command.handler) {
|
||||
console.log('Executing modal command:', command.name);
|
||||
// NE PAS cacher la palette tout de suite car le handler a besoin de slashPos
|
||||
// La palette sera cachée par le handler lui-même
|
||||
command.handler();
|
||||
return;
|
||||
}
|
||||
|
||||
let snippet = command.snippet;
|
||||
if (typeof snippet === 'function') {
|
||||
snippet = snippet();
|
||||
@ -632,6 +681,59 @@ class SlashCommands {
|
||||
this.hidePalette();
|
||||
}
|
||||
|
||||
openLinkInserter() {
|
||||
// Sauvegarder la position du slash IMMÉDIATEMENT avant toute autre opération
|
||||
const savedSlashPos = this.slashPos;
|
||||
|
||||
console.log('[SlashCommands] openLinkInserter - savedSlashPos:', savedSlashPos);
|
||||
|
||||
if (!savedSlashPos) {
|
||||
console.error('[SlashCommands] No slash position available!');
|
||||
this.hidePalette();
|
||||
return;
|
||||
}
|
||||
|
||||
// Maintenant on peut cacher la palette en toute sécurité
|
||||
this.hidePalette();
|
||||
|
||||
// S'assurer que le LinkInserter global existe, le créer si nécessaire
|
||||
if (!window.linkInserter) {
|
||||
console.log('Initializing LinkInserter...');
|
||||
window.linkInserter = new LinkInserter();
|
||||
}
|
||||
|
||||
// Ouvrir le modal de sélection de lien
|
||||
window.linkInserter.open({
|
||||
editorView: this.editorView,
|
||||
onSelect: ({ title, path }) => {
|
||||
console.log('[SlashCommands] onSelect callback received:', { title, path });
|
||||
console.log('[SlashCommands] savedSlashPos:', savedSlashPos);
|
||||
|
||||
// Créer un lien HTMX cliquable dans le preview
|
||||
// Format : <a href="#" onclick="return false;" hx-get="/api/notes/path" hx-target="#editor-container" hx-swap="innerHTML">Title</a>
|
||||
// Le onclick="return false;" empêche le comportement par défaut du # qui pourrait rediriger
|
||||
const linkHtml = `<a href="#" onclick="return false;" hx-get="/api/notes/${path}" hx-target="#editor-container" hx-swap="innerHTML">${title}</a>`;
|
||||
console.log('[SlashCommands] Inserting:', linkHtml);
|
||||
|
||||
const { state, dispatch } = this.editorView;
|
||||
const { from } = state.selection.main;
|
||||
|
||||
// Remplacer depuis le "/" jusqu'au curseur actuel
|
||||
const replaceFrom = savedSlashPos.absolutePos;
|
||||
|
||||
console.log('[SlashCommands] Replacing from', replaceFrom, 'to', from);
|
||||
|
||||
dispatch(state.update({
|
||||
changes: { from: replaceFrom, to: from, insert: linkHtml },
|
||||
selection: { anchor: replaceFrom + linkHtml.length }
|
||||
}));
|
||||
|
||||
this.editorView.focus();
|
||||
console.log('[SlashCommands] Link inserted successfully');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Retirer tous les listeners d'événements
|
||||
if (this.editorView) {
|
||||
|
||||
Reference in New Issue
Block a user