Skip to content
Guides5 min read

Modèles HTML personnalisés dans Potato

Créez des interfaces d'annotation personnalisées avec des modèles HTML, du style CSS et de l'interactivité JavaScript.

Potato Team·

Modèles HTML personnalisés dans Potato

Bien que Potato fournisse des types d'annotation intégrés, les modèles HTML personnalisés vous permettent de créer des interfaces spécialisées pour des besoins d'annotation uniques. Ce guide couvre la création de modèles, le style et l'intégration JavaScript.

Quand utiliser des modèles personnalisés

Les modèles personnalisés sont utiles pour :

  • Des mises en page complexes non prises en charge par les types intégrés
  • Des interfaces spécifiques au domaine (médical, juridique, etc.)
  • Des visualisations interactives
  • Des widgets de saisie personnalisés
  • Des expériences d'annotation de marque

Structure de base d'un modèle

Configuration du modèle

yaml
annotation_task_name: "Custom Template Annotation"
 
html_layout: "templates/my_template.html"

Fichier de modèle

html
<!-- templates/my_template.html -->
<div class="custom-annotation-container">
  <!-- Display the text -->
  <div class="content-display">
    <div class="text-content">
      {{text}}
    </div>
  </div>
 
  <!-- Custom annotation interface -->
  <div class="annotation-area">
    <!-- Potato will inject annotation schemes here -->
    {{annotation_schemes}}
  </div>
</div>

Variables de modèle

Variables disponibles

html
<!-- Item data -->
{{id}}           <!-- Item ID -->
{{text}}         <!-- Main text content -->
{{image_url}}    <!-- Image URL if present -->
{{audio_url}}    <!-- Audio URL if present -->
 
<!-- Metadata -->
{{metadata.field_name}}  <!-- Any metadata field -->

Style des modèles personnalisés

CSS en ligne

html
<style>
  .custom-annotation-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  }
 
  .task-header {
    background: linear-gradient(135deg, #6E56CF, #9F7AEA);
    color: white;
    padding: 15px 20px;
    border-radius: 8px;
    margin-bottom: 20px;
  }
 
  .content-display {
    background: #F8FAFC;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 20px;
  }
 
  .text-content {
    font-size: 16px;
    line-height: 1.6;
    color: #1E293B;
  }
 
  .metadata {
    margin-top: 15px;
    padding-top: 15px;
    border-top: 1px solid #E2E8F0;
    font-size: 14px;
    color: #64748B;
  }
 
  .annotation-area {
    background: white;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
  }
 
  /* Custom highlight styles */
  .highlight-positive {
    background-color: #D1FAE5;
    padding: 2px 4px;
    border-radius: 3px;
  }
 
  .highlight-negative {
    background-color: #FEE2E2;
    padding: 2px 4px;
    border-radius: 3px;
  }
</style>

Intégration JavaScript

Interactivité de base

html
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Get the text content element
    const textContent = document.querySelector('.text-content');
 
    // Add click-to-highlight functionality
    textContent.addEventListener('mouseup', function() {
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        highlightSelection(selection);
      }
    });
 
    function highlightSelection(selection) {
      const range = selection.getRangeAt(0);
      const span = document.createElement('span');
      span.className = 'user-highlight';
      range.surroundContents(span);
    }
  });
</script>

Modèles avancés

Comparaison côte à côte

html
<div class="comparison-container">
  <style>
    .comparison-container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
    }
 
    .comparison-item {
      border: 1px solid #E2E8F0;
      border-radius: 8px;
      padding: 15px;
    }
 
    .comparison-item.selected {
      border-color: #6E56CF;
      box-shadow: 0 0 0 2px rgba(110, 86, 207, 0.2);
    }
 
    .item-label {
      font-weight: 600;
      margin-bottom: 10px;
      color: #6E56CF;
    }
  </style>
 
  <div class="comparison-item" data-option="A" onclick="selectOption('A')">
    <div class="item-label">Option A</div>
    <div class="item-content">{{option_a}}</div>
  </div>
 
  <div class="comparison-item" data-option="B" onclick="selectOption('B')">
    <div class="item-label">Option B</div>
    <div class="item-content">{{option_b}}</div>
  </div>
 
  <script>
    function selectOption(option) {
      // Update visual selection
      document.querySelectorAll('.comparison-item').forEach(el => {
        el.classList.remove('selected');
      });
      document.querySelector(`[data-option="${option}"]`).classList.add('selected');
    }
  </script>
</div>

Surlignage interactif

html
<div class="highlight-annotation">
  <style>
    .highlight-toolbar {
      display: flex;
      gap: 10px;
      margin-bottom: 15px;
    }
 
    .highlight-btn {
      padding: 8px 16px;
      border: 1px solid #E2E8F0;
      border-radius: 6px;
      cursor: pointer;
      background: white;
    }
 
    .highlight-btn.active {
      background: #6E56CF;
      color: white;
      border-color: #6E56CF;
    }
 
    .highlightable-text {
      line-height: 1.8;
    }
 
    .highlight-positive { background: #D1FAE5; }
    .highlight-negative { background: #FEE2E2; }
    .highlight-neutral { background: #FEF3C7; }
  </style>
 
  <div class="highlight-toolbar">
    <button class="highlight-btn" data-color="positive" onclick="setHighlightMode('positive')">
      Positive
    </button>
    <button class="highlight-btn" data-color="negative" onclick="setHighlightMode('negative')">
      Negative
    </button>
    <button class="highlight-btn" data-color="neutral" onclick="setHighlightMode('neutral')">
      Neutral
    </button>
    <button class="highlight-btn" onclick="clearHighlights()">
      Clear All
    </button>
  </div>
 
  <div class="highlightable-text" id="textContent">
    {{text}}
  </div>
 
  <script>
    let currentMode = null;
    let highlights = [];
 
    function setHighlightMode(mode) {
      currentMode = mode;
      document.querySelectorAll('.highlight-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.color === mode);
      });
    }
 
    document.getElementById('textContent').addEventListener('mouseup', function() {
      if (!currentMode) return;
 
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        const range = selection.getRangeAt(0);
        const span = document.createElement('span');
        span.className = `highlight-${currentMode}`;
 
        // Store highlight data
        highlights.push({
          text: selection.toString(),
          type: currentMode,
          start: range.startOffset,
          end: range.endOffset
        });
 
        range.surroundContents(span);
        selection.removeAllRanges();
      }
    });
 
    function clearHighlights() {
      highlights = [];
      document.getElementById('textContent').innerHTML = '{{text}}';
    }
  </script>
</div>

Interface à onglets

html
<div class="tabbed-interface">
  <style>
    .tab-buttons {
      display: flex;
      border-bottom: 2px solid #E2E8F0;
    }
 
    .tab-btn {
      padding: 12px 24px;
      border: none;
      background: none;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #64748B;
      border-bottom: 2px solid transparent;
      margin-bottom: -2px;
    }
 
    .tab-btn.active {
      color: #6E56CF;
      border-bottom-color: #6E56CF;
    }
 
    .tab-content {
      display: none;
      padding: 20px 0;
    }
 
    .tab-content.active {
      display: block;
    }
  </style>
 
  <div class="tab-buttons">
    <button class="tab-btn active" onclick="showTab('text')">Text</button>
    <button class="tab-btn" onclick="showTab('metadata')">Metadata</button>
    <button class="tab-btn" onclick="showTab('context')">Context</button>
  </div>
 
  <div class="tab-content active" id="tab-text">
    <div class="text-content">{{text}}</div>
  </div>
 
  <div class="tab-content" id="tab-metadata">
    <pre>{{metadata | json}}</pre>
  </div>
 
  <div class="tab-content" id="tab-context">
    <p><strong>Source:</strong> {{metadata.source}}</p>
    <p><strong>Date:</strong> {{metadata.date}}</p>
    <p><strong>Author:</strong> {{metadata.author}}</p>
  </div>
 
  <script>
    function showTab(tabName) {
      document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
      });
      document.querySelectorAll('.tab-content').forEach(content => {
        content.classList.remove('active');
      });
 
      event.target.classList.add('active');
      document.getElementById(`tab-${tabName}`).classList.add('active');
    }
  </script>
</div>

Bonnes pratiques

  1. Gardez les modèles simples : La logique complexe devrait être dans la configuration, pas dans les modèles
  2. Testez la réactivité : Assurez-vous que les modèles fonctionnent sur différentes tailles d'écran
  3. Conception accessible : Utilisez un contraste approprié, des labels et la navigation clavier
  4. Performance : Minimisez le JavaScript et optimisez les images
  5. Solutions de repli : Fournissez des alternatives pour les données manquantes
  6. Documentation : Commentez vos modèles pour la maintenabilité

Documentation complète sur /docs/core-concepts/annotation-types.