Résultat 1 :
intégration impossible sans publier la page notion
Intégration réussie uniquement après publication de la page notion
Lien de la page test : https://blog.capmission.ma/test-2/
Tester l’intégration et le téléchargement des images / fichiers
Constitution de groupe (2).pdf
https://drive.google.com/file/d/1TSwh11l8NDxcl7xo3v6diJiQzk9hdnf0/view?usp=drive_link
https://drive.google.com/file/d/1TSwh11l8NDxcl7xo3v6diJiQzk9hdnf0/view?usp=drive_link
Résultat 2 :

code secret : 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
lien de la page ajoutée : https://www.notion.so/Test-sabi-2e139562731a8080a81dc6dc13176890
lien de test : https://playcode.io/javascript-template
Code pour tester les les éléments :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notion vers HTML - Testeur</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: white;
}
.container { max-width: 1200px; margin: 0 auto; }
.header { text-align: center; margin-bottom: 30px; }
.header h1 { font-size: 2.8em; margin-bottom: 8px; }
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.panel {
background: white;
border-radius: 12px;
padding: 28px;
box-shadow: 0 10px 35px rgba(0,0,0,0.25);
color: #222;
}
#output {
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13.5px;
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 6px;
padding: 16px;
min-height: 300px;
max-height: 600px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
#preview {
border: 1px solid #ddd;
border-radius: 6px;
padding: 24px;
background: white;
min-height: 300px;
max-height: 600px;
overflow-y: auto;
}
.loading { text-align:center; padding:60px; color:#666; }
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 48px;
height: 48px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.success-message, .error-message {
padding: 12px;
border-radius: 6px;
margin-bottom: 16px;
display: none;
}
.success-message { background: #d1fae5; color: #065f46; }
.error-message { background: #fee2e2; color: #991b1b; }
button {
padding: 12px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary { background: #667eea; color:white; }
.btn-primary:hover { background: #5a6fe6; transform: translateY(-1px); }
.btn-secondary { background: #e5e7eb; color:#374151; }
.btn-success { background: #10b981; color:white; }
.btn-success:hover { background: #059669; }
.button-group { display:flex; gap:12px; margin-top:16px; }
@media (max-width: 900px) {
.content { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Notion → HTML</h1>
<p>Convertissez vos pages Notion en HTML propre</p>
</div>
<div class="content">
<!-- Panneau gauche -->
<div class="panel">
<h2>⚙️ Configuration</h2>
<div class="success-message" id="successMessage">Page récupérée avec succès !</div>
<div class="error-message" id="errorMessage">Erreur : <span id="errorText"></span></div>
<div class="input-group">
<label for="token">Token d'intégration Notion :</label>
<input type="password" id="token" placeholder="secret_xxx...">
</div>
<div class="input-group">
<label for="pageId">ID de la page :</label>
<input type="text" id="pageId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
</div>
<div class="button-group">
<button class="btn-primary" onclick="fetchNotionPage()">Récupérer la page</button>
</div>
<div style="margin-top:24px;">
<label>Code HTML généré :</label>
<div id="output">Cliquez sur "Récupérer la page" pour commencer...</div>
</div>
<div class="button-group">
<button class="btn-secondary" onclick="copyHTML()">📋 Copier</button>
<button class="btn-success" onclick="downloadHTML()">💾 Télécharger</button>
</div>
</div>
<!-- Panneau droit -->
<div class="panel">
<h2>👁️ Aperçu</h2>
<div id="preview">
<div class="loading">
<div class="spinner"></div>
Aperçu apparaîtra ici...
</div>
</div>
</div>
</div>
</div>
<script>
let generatedHTML = '';
async function fetchNotionPage() {
const token = document.getElementById('token').value.trim();
const pageId = document.getElementById('pageId').value.trim().replace(/-/g, '');
const output = document.getElementById('output');
const preview = document.getElementById('preview');
const successMsg = document.getElementById('successMessage');
const errorMsg = document.getElementById('errorMessage');
const errorText = document.getElementById('errorText');
successMsg.style.display = 'none';
errorMsg.style.display = 'none';
if (!token || !pageId) {
showError('Token et ID de page sont obligatoires');
return;
}
output.innerHTML = '<div class="loading"><div class="spinner"></div>Chargement...</div>';
preview.innerHTML = '<div class="loading"><div class="spinner"></div>Préparation aperçu...</div>';
const proxy = '<https://corsproxy.io/?'>;
const baseUrl = '<https://api.notion.com/v1>';
const version = '2022-06-28';
try {
// Page principale
const pageRes = await fetch(proxy + baseUrl + '/pages/' + pageId, {
headers: {
'Authorization': `Bearer ${token}`,
'Notion-Version': version
}
});
if (!pageRes.ok) throw new Error(`Erreur page : ${pageRes.status}`);
const page = await pageRes.json();
// Blocs (première page seulement pour le moment)
const blocksRes = await fetch(proxy + baseUrl + '/blocks/' + pageId + '/children?page_size=100', {
headers: {
'Authorization': `Bearer ${token}`,
'Notion-Version': version
}
});
if (!blocksRes.ok) throw new Error(`Erreur blocs : ${blocksRes.status}`);
const { results: blocks } = await blocksRes.json();
generatedHTML = generateHTML(page, blocks);
output.textContent = generatedHTML;
preview.innerHTML = generatedHTML;
successMsg.style.display = 'block';
} catch (err) {
console.error(err);
const msg = err.message.includes('401') ? 'Token invalide' : err.message;
showError(msg);
output.textContent = 'Erreur : ' + msg;
preview.innerHTML = `<p style="color:#c00; padding:20px;">Erreur : ${msg}</p>`;
}
}
function generateHTML(page, blocks) {
const title = page.properties?.title?.title?.[0]?.plain_text || 'Sans titre';
let html = `<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>${escapeHtml(title)}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 900px;
margin: 60px auto;
padding: 0 24px;
line-height: 1.65;
color: #1f2937;
}
h1,h2,h3 { margin: 2em 0 0.8em; }
h1 { font-size: 2.4rem; }
h2 { font-size: 1.9rem; }
h3 { font-size: 1.5rem; }
ul, ol { padding-left: 2rem; margin: 1em 0; }
code { background:#f1f5f9; padding:0.2em 0.4em; border-radius:3px; font-family: 'SFMono-Regular', Consolas, monospace; }
pre { background:#f1f5f9; padding:1.2em; border-radius:6px; overflow-x:auto; margin:1.5em 0; }
blockquote { border-left:4px solid #cbd5e1; padding-left:1.2em; color:#64748b; margin:1.5em 0; }
.callout { padding:1em 1.2em; border-radius:6px; background:#f8fafc; border-left:4px solid #64748b; margin:1.2em 0; }
</style>
</head>
<body>
<h1>${escapeHtml(title)}</h1>
`;
blocks.forEach(block => {
html += convertBlock(block);
});
html += '</body>\\n</html>';
return html;
}
function convertBlock(block) {
if (!block) return '';
const t = block.type;
let content = '';
switch (t) {
case 'paragraph':
content = richTextToHtml(block.paragraph?.rich_text);
if (content) return ` <p>${content}</p>\\n`;
break;
case 'heading_1':
return ` <h1>${richTextToHtml(block.heading_1?.rich_text)}</h1>\\n`;
case 'heading_2':
return ` <h2>${richTextToHtml(block.heading_2?.rich_text)}</h2>\\n`;
case 'heading_3':
return ` <h3>${richTextToHtml(block.heading_3?.rich_text)}</h3>\\n`;
case 'bulleted_list_item':
return ` <ul><li>${richTextToHtml(block.bulleted_list_item?.rich_text)}</li></ul>\\n`;
case 'numbered_list_item':
return ` <ol><li>${richTextToHtml(block.numbered_list_item?.rich_text)}</li></ol>\\n`;
case 'to_do':
const checked = block.to_do?.checked ? '✓ ' : '☐ ';
return ` <p>${checked}${richTextToHtml(block.to_do?.rich_text)}</p>\\n`;
case 'code':
const codeText = richTextToHtml(block.code?.rich_text, true);
return ` <pre><code>${codeText}</code></pre>\\n`;
case 'quote':
return ` <blockquote>${richTextToHtml(block.quote?.rich_text)}</blockquote>\\n`;
case 'callout':
return ` <div class="callout">${richTextToHtml(block.callout?.rich_text)}</div>\\n`;
case 'divider':
return ' <hr />\\n';
// À ajouter plus tard : toggle, image, file, embed, table, etc.
}
return '';
}
// Fusionne tous les rich_text en une seule chaîne HTML
function richTextToHtml(richTexts, escapeOnly = false) {
if (!Array.isArray(richTexts) || richTexts.length === 0) return '';
return richTexts.map(r => {
let text = escapeOnly ? escapeHtml(r.plain_text) : r.plain_text;
if (r.annotations?.bold) text = `<strong>${text}</strong>`;
if (r.annotations?.italic) text = `<em>${text}</em>`;
if (r.annotations?.strikethrough) text = `<s>${text}</s>`;
if (r.annotations?.underline) text = `<u>${text}</u>`;
if (r.annotations?.code) text = `<code>${text}</code>`;
if (r.href) text = `<a href="${r.href}" target="_blank" rel="noopener">${text}</a>`;
return text;
}).join('');
}
function escapeHtml(text) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return text.replace(/[&<>"']/g, m => map[m]);
}
function showError(msg) {
const el = document.getElementById('errorMessage');
document.getElementById('errorText').textContent = msg;
el.style.display = 'block';
}
function copyHTML() {
if (!generatedHTML) return showError("Rien à copier");
navigator.clipboard.writeText(generatedHTML)
.then(() => {
const btn = event.target;
const txt = btn.textContent;
btn.textContent = 'Copié ✓';
setTimeout(() => btn.textContent = txt, 1800);
})
.catch(() => showError("Échec copie"));
}
function downloadHTML() {
if (!generatedHTML) return showError("Rien à télécharger");
const blob = new Blob([generatedHTML], {type: 'text/html'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'notion-export.html';
a.click();
URL.revokeObjectURL(url);
}
</script>
</body>
</html>