1. Tester l’intégration dans wordpress

Résultat 1 :

  1. 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

    1ère G 1x2h.pdf

https://drive.google.com/file/d/1TSwh11l8NDxcl7xo3v6diJiQzk9hdnf0/view?usp=drive_link

  1. Interface sabi

Résultat 2 :

Capture d’écran 2026-01-06 à 22.21.50.png

  1. Test de l’API notion

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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
    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>