Étape 1 — Remplir les métadonnées
Choisis la catégorie, la date, le temps de lecture, l'auteur et l'URL slug (l'URL courte de l'article, ex. parcours-manuscrit). Les champs marqués d'une * sont obligatoires pour générer le prompt.
Étape 2 — Saisir le titre et le chapô
Écris le titre (obligatoire) et optionnellement le chapô (1 à 2 phrases qui donnent envie de lire). Ces éléments guident Claude pour la rédaction.
Étape 3 — Ajouter les infos à inclure (optionnel)
Liste les faits, chiffres, angles ou consignes que Claude doit faire apparaître dans l'article (ex. « 85 % de nos publications viennent de manuscrits spontanés »).
Étape 4 — Générer le prompt pour Claude
Clique sur « 1. Générer le prompt pour Claude ». Le prompt apparaît à droite, prêt à être copié (bouton Copier le prompt) et collé dans une conversation Claude.
Étape 5 — Importer la réponse de Claude
Claude renvoie un brief (JSON avec titre, chapô, corps en Markdown, articles liés…). Clique sur « 2. Importer un brief », colle la réponse complète, valide. Tous les champs se remplissent automatiquement.
Étape 6 — Ajouter l'image à la une (optionnel)
Remplis l'URL de l'image, le texte alternatif (alt) et la légende si nécessaire.
Étape 7 — Régénérer et prévisualiser
Clique sur « Régénérer le HTML ». L'aperçu s'affiche à droite. Tu peux revenir aux champs pour ajuster et régénérer autant de fois que nécessaire.
Étape 8 — Publier
Deux options :
• Copier le HTML puis le coller dans un article WordPress (éditeur HTML).
• Créer un brouillon WP : configure une fois l'URL de ton site + identifiants d'application WordPress, puis le brouillon est créé en un clic dans ton admin.
Rappel syntaxe Markdown du corps : ## Titre, ### Sous-titre, **gras**, *italique*, [lien](/url), - item, 1. item, > citation, --- pour un séparateur. Le 1er paragraphe reçoit automatiquement le style « intro ».
Métadonnées
En-tête
Infos à inclure dans l'article (optionnel, utilisé dans le prompt Claude)
Image à la une (optionnel)
Corps de l'article (Markdown)
Articles liés (rempli automatiquement par l'import du brief)
Articles publiés sur le site
…
Liste récupérée automatiquement depuis ton WordPress. Elle se met à jour à chaque publication.
Chargement…
Paramètres WordPress
Pour créer des brouillons directement depuis le générateur, il faut un mot de passe d'application WordPress (différent de ton mot de passe habituel).
Comment créer un mot de passe d'application ?
Connecte-toi à helloeditions.fr/wp-admin
Va dans Utilisateurs → Profil (ton profil)
Descends jusqu'à la section « Mots de passe d'application »
Tape un nom (ex : « Générateur ») et clique sur « Ajouter un nouveau mot de passe »
Copie le mot de passe affiché (4 groupes de 4 caractères). Tu ne pourras plus le revoir.
Colle-le ci-dessous.
Stocké uniquement dans ton navigateur (localStorage). Jamais envoyé ailleurs qu'à ton WordPress.
Importer un brief
Colle ici un brief au format YAML + Markdown (voir exemple en plaçant le curseur dans le champ).
Les champs du formulaire seront remplis automatiquement.
${html}`);
doc.close();
}
// ============================================
// Parse d'un brief (frontmatter YAML-like + Markdown)
// ============================================
function parseBrief(text) {
if (!text || !text.trim()) throw new Error('Le brief est vide.');
const lines = text.split(/\r?\n/);
const meta = {};
let bodyStart = 0;
// Détection du frontmatter
if (lines[0] && lines[0].trim() === '---') {
let frontmatterEnd = -1;
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '---') {
frontmatterEnd = i;
break;
}
// key: value (peut contenir ':' dans la valeur)
const match = lines[i].match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
if (match) {
meta[match[1].trim()] = match[2].trim();
}
}
if (frontmatterEnd === -1) {
throw new Error('Frontmatter non fermé — ajoute "---" à la fin des métadonnées.');
}
bodyStart = frontmatterEnd + 1;
}
const body = lines.slice(bodyStart).join('\n').trim();
return { meta, body };
}
// ============================================
// Applique un brief aux champs du formulaire
// ============================================
function applyBrief(brief) {
const m = brief.meta;
const setVal = (id, val) => {
if (val !== undefined && val !== '') {
document.getElementById(id).value = val;
}
};
// Mapping catégorie (slug)
if (m.categorie) {
const sel = document.getElementById('f-slug');
const validSlugs = Array.from(sel.options).map(o => o.value);
if (validSlugs.includes(m.categorie)) {
sel.value = m.categorie;
}
}
setVal('f-date', m.date);
setVal('f-temps', m.temps);
setVal('f-auteur', m.auteur);
setVal('f-titre', m.titre);
setVal('f-chapeau', m.chapeau);
setVal('f-img-url', m.image_url);
setVal('f-img-alt', m.image_alt);
setVal('f-img-legende', m.image_legende);
setVal('f-slug-url', m.slug);
setVal('f-related', m.related);
if (brief.body) {
document.getElementById('f-contenu').value = brief.body;
}
generate();
}
// ============================================
// Génération du prompt pour Claude
// ============================================
function generatePrompt() {
const slug = document.getElementById('f-slug').value;
const catNom = CATEGORIES[slug] || slug;
const dateIso = document.getElementById('f-date').value;
const titre = document.getElementById('f-titre').value.trim();
const chapeau = document.getElementById('f-chapeau').value.trim();
const slugUrl = document.getElementById('f-slug-url').value.trim();
const infos = document.getElementById('f-infos').value.trim();
// Validation des champs obligatoires
const missing = [];
if (!titre) missing.push('Titre de l\'article');
if (!slugUrl) missing.push('URL slug');
if (missing.length > 0) {
alert('Pour générer le prompt, remplis d\'abord :\n\n• ' + missing.join('\n• ') + '\n\n(Les champs obligatoires sont marqués d\'un * orange.)');
if (!titre) document.getElementById('f-titre').focus();
else document.getElementById('f-slug-url').focus();
return;
}
const archiveText = (FETCH_STATE === 'ok' && PUBLISHED_ARTICLES.length > 0)
? exportArchiveText()
: 'Aucun article publié pour le moment — ne propose pas de liens internes ni d\'articles liés.';
const sujet = titre || '[sujet à remplir — mets-le dans le champ Titre]';
const chapeauBlock = chapeau
? `ANGLE / CHAPÔ SOUHAITÉ :\n${chapeau}\n\n`
: '';
const infosBlock = infos
? `INFOS À FAIRE APPARAÎTRE DANS L'ARTICLE (obligatoires) :\n${infos}\n\n`
: '';
const prompt = `Tu es rédacteur pour Hello Éditions, maison d'édition française. Tu dois écrire un article pour notre rubrique « Conseils aux auteurs », destinée aux auteurs qui cherchent à publier leur manuscrit.
SUJET DE L'ARTICLE :
${sujet}
CATÉGORIE : ${slug} (${catNom})
DATE DE PUBLICATION : ${dateIso || '[à définir]'}
SLUG URL SOUHAITÉ : ${slugUrl || '[à définir, kebab-case, sans accent]'}
${chapeauBlock}${infosBlock}${archiveText}
INSTRUCTIONS DE RÉDACTION :
- Ton : expert, bienveillant, rassurant pour un primo-auteur
- Longueur : entre 900 et 1500 mots
- Structure : chapô de 2 phrases max + un paragraphe d'intro + 3 à 5 sections en H2 + sous-sections H3 si pertinent + mini-conclusion pratique
- Intègre 2 à 4 liens internes vers les articles publiés listés ci-dessus quand c'est pertinent (ancre naturelle dans le corps du texte). Si la liste est vide, n'en mets aucun.
- Dans le champ « related » du frontmatter, propose 2 à 3 slugs d'articles publiés complémentaires (vide si aucun article publié)
FORMAT DE SORTIE ATTENDU (YAML frontmatter + corps Markdown, rien d'autre) :
---
titre: ${titre || '[titre accrocheur, 60 caractères max]'}
categorie: ${slug}
date: ${dateIso || '[YYYY-MM-DD]'}
temps: [nombre de minutes de lecture estimé]
auteur: Hello Éditions
chapeau: ${chapeau || '[1 à 2 phrases qui donnent envie de lire]'}
slug: ${slugUrl || '[slug-url-en-kebab-case]'}
image_url:
image_alt:
image_legende:
related: [slug1, slug2]
---
[corps de l'article en Markdown : paragraphes, ## H2, ### H3, - listes, > citations]
Rédige maintenant l'article complet en respectant ce format exact.`;
const ta = document.getElementById('output-prompt');
ta.value = prompt;
// Basculer sur l'onglet Prompt
document.querySelectorAll('.output__tab').forEach(t => t.classList.remove('is-active'));
document.querySelectorAll('.output__pane').forEach(p => p.classList.remove('is-active'));
document.querySelector('.output__tab[data-pane="prompt"]').classList.add('is-active');
document.getElementById('pane-prompt').classList.add('is-active');
}
// ============================================
// ARTICLES PUBLIÉS — lecture depuis l'API WordPress
// ============================================
// URL de base de l'API. Le chemin relatif marche si le générateur
// est hébergé sur le même domaine que WordPress.
const WP_API_BASE = (window.location.hostname && window.location.hostname.endsWith('helloeditions.fr'))
? '/wp-json/wp/v2'
: 'https://helloeditions.fr/wp-json/wp/v2';
// Cache local des articles récupérés depuis WP (pas de persistence)
let PUBLISHED_ARTICLES = [];
let FETCH_STATE = 'idle'; // 'idle' | 'loading' | 'error' | 'ok'
function stripHtml(html) {
const tmp = document.createElement('div');
tmp.innerHTML = html || '';
return (tmp.textContent || tmp.innerText || '').trim();
}
// Compat : certains endroits du code appellent encore loadArchive()
function loadArchive() { return PUBLISHED_ARTICLES; }
async function fetchPublishedArticles() {
FETCH_STATE = 'loading';
renderArchive();
try {
const url = `${WP_API_BASE}/posts?per_page=100&_embed=1&orderby=date&order=desc&status=publish`;
const res = await fetch(url, { credentials: 'same-origin' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const posts = await res.json();
PUBLISHED_ARTICLES = posts.map(p => {
const terms = (p._embedded && p._embedded['wp:term']) || [];
const cats = terms[0] || [];
const mainCat = cats[0];
// Si le slug de catégorie WP matche un de nos slugs connus, on le garde tel quel
const catSlug = mainCat ? mainCat.slug : '';
return {
slug: p.slug,
titre: stripHtml((p.title && p.title.rendered) || ''),
categorie: catSlug,
categorieNom: mainCat ? mainCat.name : '',
date: p.date ? p.date.split('T')[0] : '',
chapeau: stripHtml((p.excerpt && p.excerpt.rendered) || ''),
link: p.link || ''
};
});
FETCH_STATE = 'ok';
} catch (e) {
console.error('Erreur fetch WP:', e);
FETCH_STATE = 'error';
PUBLISHED_ARTICLES = [];
}
renderArchive();
}
function renderArchive() {
const list = document.getElementById('archive-list');
const count = document.getElementById('archive-count');
if (!list || !count) return;
if (FETCH_STATE === 'loading') {
count.textContent = '…';
list.innerHTML = '