User:Polygnotus/Scripts/Backlog.js
// <nowiki>
/**
* Wikipedia Task Manager for common.js - Sidebar Version
* Add this to your User:YourUsername/common.js page
* A "Tasks" tab appears next to Article/Talk tabs - click to activate
*/
$(document).ready(function() {
const TASK_PAGE = 'User:Polygnotus/barfoo2';
class WikiTaskManager {
constructor() {
this.tasks = [];
this.currentIndex = 0;
this.pageContent = '';
this.baseTimestamp = '';
this.sidebarWidth = localStorage.getItem('task_sidebar_width') || '450px';
this.isVisible = localStorage.getItem('task_sidebar_visible') !== 'false';
this.isActive = localStorage.getItem('task_manager_active') === 'true';
this.init();
}
async init() {
// Create tab on article pages
this.createTaskTab();
// Check if task manager is active
if (this.isActive) {
console.log('Task manager active - creating UI and loading from storage');
this.createUI();
this.loadTasksFromStorage();
}
}
createTaskTab() {
// Only add tab on article pages (namespace 0) or if already active
if (mw.config.get('wgNamespaceNumber') === 0 || this.isActive) {
let portletId = 'p-namespaces';
if (mw.config.get('skin') === 'vector-2022') {
portletId = 'p-associated-pages';
}
const tabText = this.isActive ? 'Tasks ✓' : 'Tasks';
const tabTitle = this.isActive ? 'Task manager active (click to toggle sidebar)' : 'Start task manager';
const taskLink = mw.util.addPortletLink(
portletId,
'#',
tabText,
'ca-tasks',
tabTitle,
't'
);
taskLink.addEventListener('click', (e) => {
e.preventDefault();
if (this.isActive) {
// Toggle sidebar visibility
this.toggleSidebar();
} else {
// Activate task manager
this.activateTaskManager();
}
});
}
}
async activateTaskManager() {
localStorage.setItem('task_manager_active', 'true');
localStorage.setItem('task_sidebar_visible', 'true');
this.isActive = true;
this.isVisible = true;
// Create UI and load tasks
this.createUI();
await this.loadTasks();
// Reload to update the tab
location.reload();
}
createUI() {
console.log('Creating UI...');
const sidebar = document.createElement('div');
sidebar.id = 'task-manager-sidebar';
sidebar.innerHTML = `
<div id="task-sidebar-header">
<h3>Task Manager</h3>
<div id="task-sidebar-controls">
<button id="task-close-btn" title="Hide sidebar">−</button>
</div>
</div>
<div id="task-sidebar-content">
<div id="task-info-section">
<div id="task-counter">Loading...</div>
<div id="task-article-info">
<strong id="task-article-title">...</strong>
<div id="task-article-url-container">
<a id="task-article-url" href="#" target="_blank">Open article in new tab →</a>
</div>
</div>
</div>
<div id="task-instructions-section">
<h4>Instructions</h4>
<pre id="task-instructions-text">Loading tasks...</pre>
</div>
<div id="task-controls-section">
<button class="task-btn task-btn-done" id="task-btn-done" disabled>✓ Done</button>
<button class="task-btn task-btn-next" id="task-btn-next" disabled>Next →</button>
<button class="task-btn task-btn-refresh" id="task-btn-refresh">↻ Refresh</button>
<button class="task-btn task-btn-storage" id="task-btn-storage">📊 Storage Info</button>
<button class="task-btn task-btn-deactivate" id="task-btn-deactivate">✕ Close Task Manager</button>
</div>
</div>
<div id="task-resize-handle"></div>
`;
this.createStyles();
console.log('Appending sidebar to body...');
document.body.appendChild(sidebar);
console.log('Sidebar appended. Element exists:', !!document.getElementById('task-manager-sidebar'));
this.attachEventListeners();
this.makeResizable();
if (!this.isVisible) {
console.log('Hiding sidebar (isVisible is false)');
this.hideSidebar();
} else {
console.log('Sidebar should be visible');
}
}
createStyles() {
const style = document.createElement('style');
style.textContent = `
#task-manager-sidebar {
position: fixed;
top: 0;
right: 0;
width: ${this.sidebarWidth};
height: 100vh;
background: #fff;
border-left: 2px solid #0645ad;
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
}
#task-manager-sidebar.hidden {
transform: translateX(100%);
}
#task-sidebar-header {
background: #0645ad;
color: white;
padding: 12px 15px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
#task-sidebar-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
#task-close-btn {
background: none;
border: none;
color: white;
font-size: 24px;
line-height: 20px;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
#task-close-btn:hover {
opacity: 0.8;
}
#task-sidebar-content {
padding: 20px;
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
#task-info-section {
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
border: 1px solid #ddd;
}
#task-counter {
font-size: 13px;
color: #666;
margin-bottom: 10px;
font-weight: 500;
}
#task-article-info {
margin-top: 10px;
}
#task-article-title {
font-size: 16px;
color: #333;
display: block;
margin-bottom: 8px;
}
#task-article-url-container {
margin-top: 8px;
}
#task-article-url {
color: #0645ad;
text-decoration: none;
font-size: 13px;
display: inline-block;
}
#task-article-url:hover {
text-decoration: underline;
}
#task-instructions-section {
flex: 1;
min-height: 200px;
display: flex;
flex-direction: column;
}
#task-instructions-section h4 {
margin: 0 0 10px 0;
font-size: 14px;
font-weight: 600;
color: #333;
}
#task-instructions-text {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 13px;
line-height: 1.6;
font-family: 'Courier New', monospace;
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
border: 1px solid #ddd;
margin: 0;
flex: 1;
overflow-y: auto;
}
#task-controls-section {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.task-btn {
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s;
width: 100%;
}
.task-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.task-btn-done {
background: #28a745;
color: white;
}
.task-btn-done:hover:not(:disabled) {
background: #218838;
}
.task-btn-next {
background: #007bff;
color: white;
}
.task-btn-next:hover:not(:disabled) {
background: #0056b3;
}
.task-btn-refresh {
background: #6c757d;
color: white;
}
.task-btn-refresh:hover:not(:disabled) {
background: #5a6268;
}
.task-btn-storage {
background: #17a2b8;
color: white;
font-size: 13px;
}
.task-btn-storage:hover:not(:disabled) {
background: #138496;
}
.task-btn-deactivate {
background: #dc3545;
color: white;
margin-top: 10px;
font-size: 13px;
}
.task-btn-deactivate:hover:not(:disabled) {
background: #c82333;
}
#task-resize-handle {
position: absolute;
left: 0;
top: 0;
width: 5px;
height: 100%;
cursor: ew-resize;
background: transparent;
}
#task-resize-handle:hover {
background: rgba(6, 69, 173, 0.2);
}
.task-error {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 6px;
border: 1px solid #f5c6cb;
}
.task-success {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 6px;
border: 1px solid #c3e6cb;
}
body {
margin-right: ${this.sidebarWidth};
transition: margin-right 0.3s ease;
}
body.task-sidebar-hidden {
margin-right: 0;
}
`;
document.head.appendChild(style);
this.styleElement = style;
}
attachEventListeners() {
document.getElementById('task-btn-done').addEventListener('click', () => this.markTaskDone());
document.getElementById('task-btn-next').addEventListener('click', () => this.nextTask());
document.getElementById('task-btn-refresh').addEventListener('click', () => this.loadTasks());
document.getElementById('task-close-btn').addEventListener('click', () => this.toggleSidebar());
document.getElementById('task-btn-storage').addEventListener('click', () => this.showStorageInfo());
document.getElementById('task-btn-deactivate').addEventListener('click', () => this.deactivateTaskManager());
}
showStorageInfo() {
let total = 0;
let itemCount = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage[key].length + key.length;
itemCount++;
}
}
const taskData = localStorage.getItem('task_manager_data');
const taskSize = taskData ? taskData.length : 0;
const info = `Storage Usage:
━━━━━━━━━━━━━━━━━━━━━━
Total localStorage: ${(total / 1024).toFixed(2)} KB
Task Manager data: ${(taskSize / 1024).toFixed(2)} KB
Total items: ${itemCount}
Limit: ~5-10 MB (browser dependent)
Used: ${((total / (5 * 1024 * 1024)) * 100).toFixed(1)}%`;
alert(info);
}
deactivateTaskManager() {
if (confirm('Close task manager? You can restart it by clicking "Start Tasks" tab')) {
localStorage.setItem('task_manager_active', 'false');
localStorage.removeItem('task_manager_data');
localStorage.setItem('task_sidebar_visible', 'false');
location.reload();
}
}
makeResizable() {
const handle = document.getElementById('task-resize-handle');
const sidebar = document.getElementById('task-manager-sidebar');
let isResizing = false;
handle.addEventListener('mousedown', (e) => {
isResizing = true;
document.body.style.cursor = 'ew-resize';
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const newWidth = window.innerWidth - e.clientX;
if (newWidth >= 300 && newWidth <= 800) {
this.sidebarWidth = newWidth + 'px';
sidebar.style.width = this.sidebarWidth;
document.body.style.marginRight = this.sidebarWidth;
this.styleElement.textContent = this.styleElement.textContent.replace(
/width: \d+px;/g,
`width: ${this.sidebarWidth};`
).replace(
/margin-right: \d+px;/g,
`margin-right: ${this.sidebarWidth};`
);
localStorage.setItem('task_sidebar_width', this.sidebarWidth);
}
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
});
}
toggleSidebar() {
const sidebar = document.getElementById('task-manager-sidebar');
this.isVisible = !this.isVisible;
if (this.isVisible) {
sidebar.classList.remove('hidden');
document.body.classList.remove('task-sidebar-hidden');
} else {
sidebar.classList.add('hidden');
document.body.classList.add('task-sidebar-hidden');
}
localStorage.setItem('task_sidebar_visible', this.isVisible);
}
hideSidebar() {
document.getElementById('task-manager-sidebar').classList.add('hidden');
document.body.classList.add('task-sidebar-hidden');
}
async loadTasks() {
try {
document.getElementById('task-btn-done').disabled = true;
document.getElementById('task-btn-next').disabled = true;
document.getElementById('task-btn-refresh').disabled = true;
document.getElementById('task-instructions-text').textContent = 'Loading tasks from Wikipedia...';
const data = await this.fetchWikiPage(TASK_PAGE);
this.pageContent = data.content;
this.baseTimestamp = data.timestamp;
this.tasks = this.parseTasks(this.pageContent);
if (this.tasks.length === 0) {
this.showError('No tasks found on the page');
return;
}
this.currentIndex = 0;
this.saveTasksToStorage();
this.displayCurrentTask();
document.getElementById('task-btn-refresh').disabled = false;
} catch (error) {
this.showError('Error loading tasks: ' + error.message);
document.getElementById('task-btn-refresh').disabled = false;
}
}
loadTasksFromStorage() {
console.log('Loading tasks from storage...');
const storedData = localStorage.getItem('task_manager_data');
console.log('Stored data:', storedData ? 'Found' : 'Not found');
if (storedData) {
try {
const data = JSON.parse(storedData);
this.tasks = data.tasks || [];
this.currentIndex = data.currentIndex || 0;
this.baseTimestamp = data.baseTimestamp || '';
console.log('Loaded tasks:', this.tasks.length, 'Current index:', this.currentIndex);
if (this.tasks.length > 0) {
this.displayCurrentTask();
const refreshBtn = document.getElementById('task-btn-refresh');
if (refreshBtn) refreshBtn.disabled = false;
} else {
const instructionsText = document.getElementById('task-instructions-text');
if (instructionsText) {
instructionsText.textContent = 'No tasks loaded. Click Refresh to load tasks.';
}
}
} catch (e) {
console.error('Error parsing stored tasks:', e);
const instructionsText = document.getElementById('task-instructions-text');
if (instructionsText) {
instructionsText.textContent = 'Error loading saved tasks. Click Refresh to reload.';
}
}
} else {
const instructionsText = document.getElementById('task-instructions-text');
if (instructionsText) {
instructionsText.textContent = 'No tasks loaded. Click Refresh to load tasks.';
}
}
}
saveTasksToStorage() {
try {
// Don't store pageContent - it's too large and not needed
const data = {
tasks: this.tasks,
currentIndex: this.currentIndex,
baseTimestamp: this.baseTimestamp
};
localStorage.setItem('task_manager_data', JSON.stringify(data));
} catch (e) {
// If quota exceeded, try to store minimal data
if (e.name === 'QuotaExceededError') {
console.warn('LocalStorage quota exceeded, storing minimal data');
try {
const minimalData = {
tasks: this.tasks.slice(0, 3), // Only store first 3 tasks
currentIndex: this.currentIndex,
baseTimestamp: this.baseTimestamp
};
localStorage.setItem('task_manager_data', JSON.stringify(minimalData));
} catch (e2) {
console.error('Failed to save even minimal task data:', e2);
}
}
}
}
async fetchWikiPage(pageTitle) {
const url = `/w/api.php?action=query&titles=${encodeURIComponent(pageTitle)}&prop=revisions&rvprop=content|timestamp&format=json&formatversion=2`;
const username = mw.config.get('wgUserName') || 'Anonymous';
const response = await fetch(url, {
headers: {
'Api-User-Agent': `WikiTaskManager/1.0 (User:${username})`
}
});
const data = await response.json();
const page = data.query.pages[0];
if (page.missing) {
throw new Error('Page not found');
}
return {
content: page.revisions[0].content,
timestamp: page.revisions[0].timestamp
};
}
parseTasks(content) {
const tasks = content.split('================================================================================');
return tasks
.map(task => task.trim())
.filter(task => task.length > 0 && !task.startsWith('__NOTOC__'));
}
parseTask(taskLine) {
const match = taskLine.match(/^Article: (.+?)\nURL: (.+?)\n\nCLAUDE'S ANALYSIS:\n([\s\S]+)$/);
if (!match) {
return {
title: 'Unknown',
url: '',
instructions: taskLine
};
}
return {
title: match[1].trim(),
url: match[2].trim(),
instructions: match[3].trim()
};
}
displayCurrentTask() {
if (this.currentIndex >= this.tasks.length) {
this.showCompletion();
return;
}
const taskLine = this.tasks[this.currentIndex];
const task = this.parseTask(taskLine);
console.log('Displaying task:', {
index: this.currentIndex,
title: task.title,
url: task.url
});
document.getElementById('task-counter').textContent = `Task ${this.currentIndex + 1} of ${this.tasks.length}`;
document.getElementById('task-instructions-text').textContent = task.instructions;
document.getElementById('task-article-title').textContent = task.title;
document.getElementById('task-article-url').textContent = task.url;
document.getElementById('task-article-url').href = task.url;
document.getElementById('task-btn-done').disabled = false;
document.getElementById('task-btn-next').disabled = this.currentIndex >= this.tasks.length - 1;
this.saveTasksToStorage();
// Check if we need to navigate to the article
// Extract the page title from the task URL
const urlMatch = task.url.match(/\/wiki\/(.+)$/);
const targetPage = urlMatch ? decodeURIComponent(urlMatch[1]) : '';
const currentPage = mw.config.get('wgPageName');
console.log('Navigation check:', {
targetPage: targetPage,
currentPage: currentPage,
needsNavigation: targetPage && currentPage !== targetPage
});
// Only navigate if we're not already on the target page
if (targetPage && currentPage !== targetPage) {
console.log('Navigating to:', task.url);
window.location.href = task.url;
}
}
async markTaskDone() {
document.getElementById('task-btn-done').disabled = true;
document.getElementById('task-btn-next').disabled = true;
try {
const taskToRemove = this.tasks[this.currentIndex];
// Remove task from local array
this.tasks.splice(this.currentIndex, 1);
// Rebuild page content
const newContent = this.tasks.join('\n================================================================================\n') +
(this.tasks.length > 0 ? '\n================================================================================\n' : '');
// Save to Wikipedia
await this.saveWikiPage(TASK_PAGE, newContent, 'Task completed and removed');
// Save state
this.saveTasksToStorage();
// Display next task
if (this.tasks.length === 0) {
this.showCompletion();
} else {
if (this.currentIndex >= this.tasks.length) {
this.currentIndex = this.tasks.length - 1;
}
this.displayCurrentTask();
}
} catch (error) {
this.showError('Error marking task as done: ' + error.message);
// Reload tasks to get current state
await this.loadTasks();
}
}
async saveWikiPage(pageTitle, content, summary) {
const username = mw.config.get('wgUserName') || 'Anonymous';
const userAgent = `WikiTaskManager/1.0 (User:${username})`;
// Get edit token
const tokenUrl = `/w/api.php?action=query&meta=tokens&format=json&formatversion=2`;
const tokenResponse = await fetch(tokenUrl, {
headers: {
'Api-User-Agent': userAgent
}
});
const tokenData = await tokenResponse.json();
const csrfToken = tokenData.query.tokens.csrftoken;
// Edit page
const editUrl = `/w/api.php`;
const formData = new FormData();
formData.append('action', 'edit');
formData.append('title', pageTitle);
formData.append('text', content);
formData.append('summary', summary);
formData.append('token', csrfToken);
formData.append('format', 'json');
formData.append('basetimestamp', this.baseTimestamp);
const editResponse = await fetch(editUrl, {
method: 'POST',
headers: {
'Api-User-Agent': userAgent
},
body: formData
});
const editData = await editResponse.json();
if (editData.error) {
throw new Error(editData.error.info);
}
if (editData.edit && editData.edit.result !== 'Success') {
throw new Error('Edit failed: ' + JSON.stringify(editData.edit));
}
// Update base timestamp for next edit
this.baseTimestamp = editData.edit.newtimestamp;
this.saveTasksToStorage();
}
nextTask() {
if (this.currentIndex < this.tasks.length - 1) {
this.currentIndex++;
this.saveTasksToStorage();
this.displayCurrentTask();
}
}
showCompletion() {
document.getElementById('task-instructions-text').innerHTML = '<div class="task-success">All tasks completed!</div>';
document.getElementById('task-counter').textContent = 'No tasks remaining';
document.getElementById('task-article-title').textContent = 'All done';
document.getElementById('task-article-url').textContent = '';
document.getElementById('task-article-url').href = '#';
document.getElementById('task-btn-done').disabled = true;
document.getElementById('task-btn-next').disabled = true;
}
showError(message) {
document.getElementById('task-instructions-text').innerHTML = `<div class="task-error">${message}</div>`;
document.getElementById('task-btn-done').disabled = true;
document.getElementById('task-btn-next').disabled = true;
}
}
// Initialize the task manager
new WikiTaskManager();
});
// </nowiki>
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.