| |
| function bridgeSend(eventType, payload) { |
| const el = document.querySelector('#bridge_input textarea'); |
| if (!el) return; |
| const data = JSON.stringify({ type: eventType, ts: Date.now(), ...(payload || {}) }); |
| |
| const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; |
| setter.call(el, data); |
| el.dispatchEvent(new Event('input', { bubbles: true })); |
| } |
|
|
| |
| document.addEventListener('click', function (e) { |
| const el = e.target.closest('[data-action]'); |
| if (!el) return; |
| const action = el.dataset.action; |
| const id = parseInt(el.dataset.id, 10); |
| if (!action || isNaN(id)) return; |
| bridgeSend(action, { id }); |
| if (action === 'remove_todo') _removeTodoFromStorage(id); |
| }); |
|
|
| document.addEventListener('change', function (e) { |
| const el = e.target.closest('[data-action]'); |
| if (!el) return; |
| const action = el.dataset.action; |
| const id = parseInt(el.dataset.id, 10); |
| if (!action || isNaN(id)) return; |
| bridgeSend(action, { id }); |
| if (action === 'complete_todo') { |
| _toggleTodoDoneInStorage(id); |
| if (el.checked && window.avatarPlay) window.avatarPlay('celebrate'); |
| } |
| }); |
|
|
| |
| |
| |
| function runPyCmd(cmd) { |
| if (!cmd) return; |
| try { eval(cmd); } catch (e) { console.error('[py_cmd]', e); } |
| } |
|
|
| |
| const _TODOS_LS_KEY = 'focus_buddy_todos'; |
|
|
| function _escTodo(s) { |
| return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); |
| } |
|
|
| function _parseTodosFromDOM() { |
| const todos = []; |
| document.querySelectorAll('.todo-item[id^="todo-"]').forEach(el => { |
| const id = parseInt(el.id.replace('todo-', ''), 10); |
| const checkbox = el.querySelector('.todo-check'); |
| const span = el.querySelector('.todo-task'); |
| if (span && !isNaN(id)) { |
| todos.push({ id, task: span.textContent.trim(), done: checkbox?.checked || false }); |
| } |
| }); |
| return todos; |
| } |
|
|
| function _saveTodosToStorage() { |
| try { |
| localStorage.setItem(_TODOS_LS_KEY, JSON.stringify(_parseTodosFromDOM())); |
| } catch (_) {} |
| } |
|
|
| function _removeTodoFromStorage(id) { |
| try { |
| const saved = JSON.parse(localStorage.getItem(_TODOS_LS_KEY) || '[]'); |
| localStorage.setItem(_TODOS_LS_KEY, JSON.stringify(saved.filter(t => t.id !== id))); |
| } catch (_) {} |
| } |
|
|
| function _toggleTodoDoneInStorage(id) { |
| try { |
| const saved = JSON.parse(localStorage.getItem(_TODOS_LS_KEY) || '[]'); |
| localStorage.setItem(_TODOS_LS_KEY, JSON.stringify( |
| saved.map(t => t.id === id ? { ...t, done: !t.done } : t) |
| )); |
| } catch (_) {} |
| } |
|
|
| function _buildTodoHTML(todos) { |
| const sorted = todos; |
| const items = sorted.map(t => { |
| const checked = t.done ? 'checked' : ''; |
| const doneCls = t.done ? ' todo-done' : ''; |
| return `<div class="todo-item" id="todo-${t.id}">` + |
| `<input type="checkbox" ${checked} class="todo-check" data-action="complete_todo" data-id="${t.id}">` + |
| `<span class="todo-task${doneCls}">${_escTodo(t.task)}</span>` + |
| `<button class="todo-del" data-action="remove_todo" data-id="${t.id}">β</button>` + |
| `</div>`; |
| }).join(''); |
| return `<div class="todo-panel" id="todo-panel">` + |
| `<h3 class="panel-title">π Quests</h3>` + |
| `<div class="todo-list">${items}</div>` + |
| `</div>`; |
| } |
|
|
| function _loadTodosFromStorage() { |
| try { |
| const saved = JSON.parse(localStorage.getItem(_TODOS_LS_KEY) || 'null'); |
| if (!saved || saved.length === 0) return; |
|
|
| |
| const existing = document.querySelectorAll('.todo-item[id^="todo-"]'); |
| if (existing.length > 0) return; |
|
|
| const panel = document.getElementById('todo-panel'); |
| if (!panel) return; |
| panel.outerHTML = _buildTodoHTML(saved); |
| } catch (_) {} |
| } |
|
|
| function _initTodosStorage() { |
| const panel = document.getElementById('todo-panel'); |
| if (!panel) { |
| setTimeout(_initTodosStorage, 300); |
| return; |
| } |
|
|
| |
| _loadTodosFromStorage(); |
|
|
| |
| const watchTarget = panel.closest('[data-testid]') || panel.parentElement; |
| new MutationObserver(_saveTodosToStorage).observe(watchTarget, { childList: true, subtree: true }); |
| } |
|
|
| setTimeout(_initTodosStorage, 500); |
|
|
| |
| function showPanel(which) { |
| const isInv = which === 'inv'; |
| const invWrap = document.getElementById('inventory-panel-wrap'); |
| const chatWrap = document.getElementById('chatbot-wrap'); |
| const inputWrap = document.getElementById('chat-input-wrap'); |
| if (invWrap) invWrap.style.display = isInv ? '' : 'none'; |
| if (chatWrap) chatWrap.style.display = isInv ? 'none' : ''; |
| if (inputWrap) inputWrap.style.display = isInv ? 'none' : ''; |
| document.querySelectorAll('.panel-tab').forEach(btn => { |
| btn.classList.toggle('active', btn.dataset.panel === which); |
| }); |
| } |
|
|
| function _initPanelToggle() { |
| const invWrap = document.getElementById('inventory-panel-wrap'); |
| if (!invWrap) { setTimeout(_initPanelToggle, 300); return; } |
| invWrap.style.display = 'none'; |
| } |
| setTimeout(_initPanelToggle, 400); |
|
|