feat: GitHub完全活用ガイド - 包括的な解説書とWebサイトを追加
🎯 主要機能: - GitHub機能の網羅的解説書 (10章構成) - 外部ツール代替戦略とコスト分析 - 実践的な設定例とベストプラクティス - 実務ケーススタディと段階的移行計画 🌐 GitHub Pages Webサイト: - Jekyll設定とレスポンシブデザイン - 自動デプロイワークフロー - 美しいランディングページ - SEO最適化とモバイル対応 📊 期待効果: - 年間37%のコスト削減 - 開発効率2倍向上 - セキュリティ強化 🚀 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
1e2b71e1b3
17 changed files with 4250 additions and 0 deletions
412
practice-project/script.js
Normal file
412
practice-project/script.js
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
// Personal Task Manager - GitHub学習用JavaScript
|
||||
|
||||
class TaskManager {
|
||||
constructor() {
|
||||
this.tasks = this.loadTasks();
|
||||
this.taskIdCounter = this.getNextTaskId();
|
||||
this.initializeApp();
|
||||
}
|
||||
|
||||
// アプリケーション初期化
|
||||
initializeApp() {
|
||||
this.setupEventListeners();
|
||||
this.renderTasks();
|
||||
this.updateStats();
|
||||
this.setupDragAndDrop();
|
||||
}
|
||||
|
||||
// イベントリスナー設定
|
||||
setupEventListeners() {
|
||||
// タスク追加フォーム
|
||||
document.getElementById('addTaskForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.addTask();
|
||||
});
|
||||
|
||||
// フィルタ変更
|
||||
document.getElementById('categoryFilter').addEventListener('change', () => this.applyFilters());
|
||||
document.getElementById('statusFilter').addEventListener('change', () => this.applyFilters());
|
||||
document.getElementById('priorityFilter').addEventListener('change', () => this.applyFilters());
|
||||
|
||||
// タスク操作(イベント委譲)
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('edit-btn')) {
|
||||
this.editTask(e.target.closest('.task-card').dataset.taskId);
|
||||
}
|
||||
if (e.target.classList.contains('delete-btn')) {
|
||||
this.deleteTask(e.target.closest('.task-card').dataset.taskId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// タスク追加
|
||||
addTask() {
|
||||
const title = document.getElementById('taskTitle').value.trim();
|
||||
const description = document.getElementById('taskDescription').value.trim();
|
||||
const category = document.getElementById('taskCategory').value;
|
||||
const priority = document.getElementById('taskPriority').value;
|
||||
|
||||
if (!title) {
|
||||
alert('タスクタイトルを入力してください');
|
||||
return;
|
||||
}
|
||||
|
||||
const task = {
|
||||
id: this.taskIdCounter++,
|
||||
title,
|
||||
description,
|
||||
category,
|
||||
priority,
|
||||
status: 'todo',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.tasks.push(task);
|
||||
this.saveTasks();
|
||||
this.renderTasks();
|
||||
this.updateStats();
|
||||
this.clearForm();
|
||||
|
||||
// GitHub風の成功メッセージ(実際のGitHubではIssue作成時に表示される)
|
||||
this.showNotification(`タスク #${task.id} を作成しました`, 'success');
|
||||
}
|
||||
|
||||
// タスク編集
|
||||
editTask(taskId) {
|
||||
const task = this.tasks.find(t => t.id === parseInt(taskId));
|
||||
if (!task) return;
|
||||
|
||||
const newTitle = prompt('タスクタイトル:', task.title);
|
||||
if (newTitle === null) return;
|
||||
|
||||
const newDescription = prompt('タスク説明:', task.description);
|
||||
if (newDescription === null) return;
|
||||
|
||||
task.title = newTitle.trim();
|
||||
task.description = newDescription.trim();
|
||||
task.updatedAt = new Date().toISOString();
|
||||
|
||||
this.saveTasks();
|
||||
this.renderTasks();
|
||||
this.showNotification(`タスク #${task.id} を更新しました`, 'info');
|
||||
}
|
||||
|
||||
// タスク削除
|
||||
deleteTask(taskId) {
|
||||
const task = this.tasks.find(t => t.id === parseInt(taskId));
|
||||
if (!task) return;
|
||||
|
||||
if (confirm(`タスク「${task.title}」を削除しますか?`)) {
|
||||
this.tasks = this.tasks.filter(t => t.id !== parseInt(taskId));
|
||||
this.saveTasks();
|
||||
this.renderTasks();
|
||||
this.updateStats();
|
||||
this.showNotification(`タスク #${task.id} を削除しました`, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
// タスクレンダリング
|
||||
renderTasks() {
|
||||
const todoContainer = document.getElementById('todoTasks');
|
||||
const inProgressContainer = document.getElementById('inProgressTasks');
|
||||
const doneContainer = document.getElementById('doneTasks');
|
||||
|
||||
// コンテナをクリア
|
||||
todoContainer.innerHTML = '';
|
||||
inProgressContainer.innerHTML = '';
|
||||
doneContainer.innerHTML = '';
|
||||
|
||||
// フィルタリングされたタスクを取得
|
||||
const filteredTasks = this.getFilteredTasks();
|
||||
|
||||
// 各タスクをレンダリング
|
||||
filteredTasks.forEach(task => {
|
||||
const taskElement = this.createTaskElement(task);
|
||||
|
||||
switch (task.status) {
|
||||
case 'todo':
|
||||
todoContainer.appendChild(taskElement);
|
||||
break;
|
||||
case 'in-progress':
|
||||
inProgressContainer.appendChild(taskElement);
|
||||
break;
|
||||
case 'done':
|
||||
doneContainer.appendChild(taskElement);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// タスク要素作成
|
||||
createTaskElement(task) {
|
||||
const template = document.getElementById('taskTemplate');
|
||||
const taskElement = template.content.cloneNode(true);
|
||||
const taskCard = taskElement.querySelector('.task-card');
|
||||
|
||||
taskCard.dataset.taskId = task.id;
|
||||
taskCard.dataset.status = task.status;
|
||||
|
||||
// タスク情報を設定
|
||||
taskElement.querySelector('.task-id').textContent = `#${task.id}`;
|
||||
taskElement.querySelector('.task-title').textContent = task.title;
|
||||
taskElement.querySelector('.task-description').textContent = task.description || '説明なし';
|
||||
|
||||
// 優先度設定
|
||||
const priorityElement = taskElement.querySelector('.task-priority');
|
||||
priorityElement.textContent = this.getPriorityLabel(task.priority);
|
||||
priorityElement.className = `task-priority ${task.priority}`;
|
||||
|
||||
// カテゴリ設定
|
||||
const categoryElement = taskElement.querySelector('.task-category');
|
||||
categoryElement.textContent = this.getCategoryLabel(task.category);
|
||||
categoryElement.className = `task-category ${task.category}`;
|
||||
|
||||
// 作成日時
|
||||
taskElement.querySelector('.task-created').textContent =
|
||||
new Date(task.createdAt).toLocaleDateString('ja-JP');
|
||||
|
||||
return taskElement;
|
||||
}
|
||||
|
||||
// フィルタリング適用
|
||||
applyFilters() {
|
||||
this.renderTasks();
|
||||
}
|
||||
|
||||
// フィルタリングされたタスク取得
|
||||
getFilteredTasks() {
|
||||
const categoryFilter = document.getElementById('categoryFilter').value;
|
||||
const statusFilter = document.getElementById('statusFilter').value;
|
||||
const priorityFilter = document.getElementById('priorityFilter').value;
|
||||
|
||||
return this.tasks.filter(task => {
|
||||
const matchesCategory = categoryFilter === 'all' || task.category === categoryFilter;
|
||||
const matchesStatus = statusFilter === 'all' || task.status === statusFilter;
|
||||
const matchesPriority = priorityFilter === 'all' || task.priority === priorityFilter;
|
||||
|
||||
return matchesCategory && matchesStatus && matchesPriority;
|
||||
});
|
||||
}
|
||||
|
||||
// 統計更新
|
||||
updateStats() {
|
||||
const totalTasks = this.tasks.length;
|
||||
const completedTasks = this.tasks.filter(t => t.status === 'done').length;
|
||||
const inProgressTasks = this.tasks.filter(t => t.status === 'in-progress').length;
|
||||
const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
||||
|
||||
document.getElementById('totalTasks').textContent = totalTasks;
|
||||
document.getElementById('completedTasks').textContent = completedTasks;
|
||||
document.getElementById('inProgressCount').textContent = inProgressTasks;
|
||||
document.getElementById('completionRate').textContent = `${completionRate}%`;
|
||||
}
|
||||
|
||||
// ドラッグ&ドロップ設定
|
||||
setupDragAndDrop() {
|
||||
// タスクカードのドラッグ開始
|
||||
document.addEventListener('dragstart', (e) => {
|
||||
if (e.target.classList.contains('task-card')) {
|
||||
e.target.classList.add('dragging');
|
||||
e.dataTransfer.setData('text/plain', e.target.dataset.taskId);
|
||||
}
|
||||
});
|
||||
|
||||
// ドラッグ終了
|
||||
document.addEventListener('dragend', (e) => {
|
||||
if (e.target.classList.contains('task-card')) {
|
||||
e.target.classList.remove('dragging');
|
||||
}
|
||||
});
|
||||
|
||||
// ドロップゾーンの設定
|
||||
document.querySelectorAll('.task-column').forEach(column => {
|
||||
column.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
column.classList.add('drag-over');
|
||||
});
|
||||
|
||||
column.addEventListener('dragleave', (e) => {
|
||||
if (!column.contains(e.relatedTarget)) {
|
||||
column.classList.remove('drag-over');
|
||||
}
|
||||
});
|
||||
|
||||
column.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
column.classList.remove('drag-over');
|
||||
|
||||
const taskId = e.dataTransfer.getData('text/plain');
|
||||
const newStatus = column.dataset.status;
|
||||
this.updateTaskStatus(parseInt(taskId), newStatus);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// タスクステータス更新
|
||||
updateTaskStatus(taskId, newStatus) {
|
||||
const task = this.tasks.find(t => t.id === taskId);
|
||||
if (!task || task.status === newStatus) return;
|
||||
|
||||
const oldStatus = task.status;
|
||||
task.status = newStatus;
|
||||
task.updatedAt = new Date().toISOString();
|
||||
|
||||
this.saveTasks();
|
||||
this.renderTasks();
|
||||
this.updateStats();
|
||||
|
||||
// GitHub風のステータス更新メッセージ
|
||||
this.showNotification(
|
||||
`タスク #${taskId} を ${this.getStatusLabel(oldStatus)} から ${this.getStatusLabel(newStatus)} に移動しました`,
|
||||
'info'
|
||||
);
|
||||
}
|
||||
|
||||
// ユーティリティ関数
|
||||
getPriorityLabel(priority) {
|
||||
const labels = {
|
||||
'high': '高',
|
||||
'medium': '中',
|
||||
'low': '低'
|
||||
};
|
||||
return labels[priority] || priority;
|
||||
}
|
||||
|
||||
getCategoryLabel(category) {
|
||||
const labels = {
|
||||
'feature': '新機能',
|
||||
'bug': 'バグ',
|
||||
'improvement': '改善',
|
||||
'documentation': 'ドキュメント'
|
||||
};
|
||||
return labels[category] || category;
|
||||
}
|
||||
|
||||
getStatusLabel(status) {
|
||||
const labels = {
|
||||
'todo': 'TODO',
|
||||
'in-progress': '進行中',
|
||||
'done': '完了'
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
|
||||
// 通知表示(GitHub風)
|
||||
showNotification(message, type = 'info') {
|
||||
// 簡易的な通知実装
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 16px;
|
||||
background: #0366d6;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
max-width: 300px;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
`;
|
||||
|
||||
// タイプ別スタイル
|
||||
const colors = {
|
||||
'success': '#28a745',
|
||||
'warning': '#ffd33d',
|
||||
'error': '#d73a49',
|
||||
'info': '#0366d6'
|
||||
};
|
||||
if (colors[type]) {
|
||||
notification.style.background = colors[type];
|
||||
}
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 3秒後に削除
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// フォームクリア
|
||||
clearForm() {
|
||||
document.getElementById('addTaskForm').reset();
|
||||
}
|
||||
|
||||
// データ永続化
|
||||
saveTasks() {
|
||||
localStorage.setItem('github-learning-tasks', JSON.stringify(this.tasks));
|
||||
localStorage.setItem('github-learning-task-counter', this.taskIdCounter.toString());
|
||||
}
|
||||
|
||||
loadTasks() {
|
||||
const saved = localStorage.getItem('github-learning-tasks');
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
}
|
||||
|
||||
getNextTaskId() {
|
||||
const saved = localStorage.getItem('github-learning-task-counter');
|
||||
return saved ? parseInt(saved) : 1;
|
||||
}
|
||||
|
||||
// デモデータ生成(学習用)
|
||||
generateSampleTasks() {
|
||||
const sampleTasks = [
|
||||
{
|
||||
id: this.taskIdCounter++,
|
||||
title: 'GitHub Issues の基本操作を学習',
|
||||
description: 'Issue の作成、編集、クローズの方法を実践で学ぶ',
|
||||
category: 'documentation',
|
||||
priority: 'high',
|
||||
status: 'todo',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: this.taskIdCounter++,
|
||||
title: 'Pull Request のワークフローを実践',
|
||||
description: 'ブランチ作成からマージまでの一連の流れを体験',
|
||||
category: 'feature',
|
||||
priority: 'high',
|
||||
status: 'in-progress',
|
||||
createdAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: this.taskIdCounter++,
|
||||
title: 'GitHub Actions でCI/CDを設定',
|
||||
description: '自動テストとデプロイの仕組みを構築',
|
||||
category: 'improvement',
|
||||
priority: 'medium',
|
||||
status: 'todo',
|
||||
createdAt: new Date(Date.now() - 172800000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 172800000).toISOString()
|
||||
}
|
||||
];
|
||||
|
||||
if (this.tasks.length === 0) {
|
||||
this.tasks = sampleTasks;
|
||||
this.saveTasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// アプリケーション開始
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const taskManager = new TaskManager();
|
||||
|
||||
// デモデータ生成(初回のみ)
|
||||
taskManager.generateSampleTasks();
|
||||
taskManager.renderTasks();
|
||||
taskManager.updateStats();
|
||||
|
||||
// グローバルに公開(デバッグ用)
|
||||
window.taskManager = taskManager;
|
||||
|
||||
console.log('🎯 Personal Task Manager が起動しました!');
|
||||
console.log('GitHub機能学習用のタスク管理アプリです。');
|
||||
console.log('タスクを追加して、GitHub の各機能を実践的に学習しましょう!');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue