Guides4 min read
PotatoのカスタムHTMLテンプレート
HTMLテンプレート、CSSスタイリング、JavaScriptインタラクティビティを使用したカスタムアノテーションインターフェースの作成方法。
Potato Team·
PotatoのカスタムHTMLテンプレート
Potatoは組み込みのアノテーションタイプを提供していますが、カスタムHTMLテンプレートを使用すると、独自のアノテーションニーズに特化したインターフェースを作成できます。このガイドでは、テンプレートの作成、スタイリング、JavaScript統合について説明します。
カスタムテンプレートを使用する場面
カスタムテンプレートは以下の場合に有用です:
- 組み込みタイプでサポートされていない複雑なレイアウト
- ドメイン固有のインターフェース(医療、法律など)
- インタラクティブな可視化
- カスタム入力ウィジェット
- ブランド化されたアノテーション体験
基本的なテンプレート構造
テンプレート設定
yaml
annotation_task_name: "Custom Template Annotation"
html_layout: "templates/my_template.html"テンプレートファイル
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>テンプレート変数
利用可能な変数
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 -->カスタムテンプレートのスタイリング
インラインCSS
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>JavaScript統合
基本的なインタラクティビティ
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>高度なテンプレート
サイドバイサイド比較
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>インタラクティブハイライト
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>タブインターフェース
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>ベストプラクティス
- テンプレートをシンプルに:複雑なロジックはテンプレートではなく設定に
- レスポンシブ対応のテスト:異なる画面サイズでテンプレートが動作することを確認
- アクセシブルなデザイン:適切なコントラスト、ラベル、キーボードナビゲーションを使用
- パフォーマンス:JavaScriptを最小限にし、画像を最適化
- フォールバック:欠損データに対するフォールバックを提供
- ドキュメント:保守性のためにテンプレートにコメントを追加
完全なドキュメントは/docs/core-concepts/annotation-typesをご覧ください。