feat: mini apps customizados com importacao por arquivo/URL, paleta de cores, avatar autenticado e documentacao
This commit is contained in:
209
assets/miniapps/tasks.html
Normal file
209
assets/miniapps/tasks.html
Normal file
@@ -0,0 +1,209 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Tasks</title>
|
||||
<style>
|
||||
:root {
|
||||
--accent: #00D084;
|
||||
--bg: #ffffff;
|
||||
--fg: #1a1a1a;
|
||||
--card: #f6f6f6;
|
||||
--border: #e3e3e3;
|
||||
--muted: #757575;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #121212;
|
||||
--fg: #e3e3e3;
|
||||
--card: #1e1e1e;
|
||||
--border: #333333;
|
||||
--muted: #9e9e9e;
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
min-height: 100vh;
|
||||
}
|
||||
main { max-width: 640px; margin: 0 auto; padding: 20px 16px 32px; }
|
||||
header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
||||
header .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
|
||||
header h1 { font-size: 1.3rem; font-weight: 700; }
|
||||
#addForm { display: flex; gap: 8px; margin-bottom: 20px; }
|
||||
#taskInput {
|
||||
flex: 1;
|
||||
padding: 11px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--card);
|
||||
color: var(--fg);
|
||||
font-size: 0.95rem;
|
||||
outline: none;
|
||||
}
|
||||
#taskInput:focus { border-color: var(--accent); }
|
||||
#addBtn {
|
||||
width: 44px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
ul { list-style: none; }
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--card);
|
||||
}
|
||||
li.done span { text-decoration: line-through; opacity: .55; }
|
||||
li input[type="checkbox"] {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
li span { flex: 1; word-break: break-word; }
|
||||
li button {
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
li button:hover { color: var(--fg); }
|
||||
.empty { text-align: center; padding: 48px 0 24px; color: var(--muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div class="dot"></div>
|
||||
<h1>Tasks</h1>
|
||||
</header>
|
||||
<form id="addForm" autocomplete="off">
|
||||
<input id="taskInput" placeholder="What do you need to do?" />
|
||||
<button id="addBtn" type="submit">+</button>
|
||||
</form>
|
||||
<ul id="list"></ul>
|
||||
</main>
|
||||
<script>
|
||||
const APP_ID = 'tasks';
|
||||
|
||||
// ----- Bridge: the app talks to the TatuChat client through postMessage.
|
||||
// The host replies with {api, widgetId, requestId, response:{data,error}}.
|
||||
const _pending = new Map();
|
||||
let _requestCounter = 0;
|
||||
|
||||
function bridgeRequest(action, data) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const requestId = 'r' + (++_requestCounter);
|
||||
_pending.set(requestId, { resolve: resolve, reject: reject });
|
||||
window.parent.postMessage({
|
||||
api: 'fromMiniApp',
|
||||
widgetId: APP_ID,
|
||||
requestId: requestId,
|
||||
action: action,
|
||||
data: data
|
||||
}, '*');
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
const msg = event.data;
|
||||
if (!msg || msg.api !== 'fromMiniApp' || !msg.requestId) return;
|
||||
if (!_pending.has(msg.requestId)) return;
|
||||
const pending = _pending.get(msg.requestId);
|
||||
_pending.delete(msg.requestId);
|
||||
const response = msg.response || {};
|
||||
if (response.error) {
|
||||
pending.reject(new Error(response.error));
|
||||
} else {
|
||||
pending.resolve(response.data);
|
||||
}
|
||||
});
|
||||
|
||||
// ----- Tasks application logic.
|
||||
let tasks = [];
|
||||
const listEl = document.getElementById('list');
|
||||
const addForm = document.getElementById('addForm');
|
||||
const taskInput = document.getElementById('taskInput');
|
||||
|
||||
addForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
const text = taskInput.value.trim();
|
||||
if (!text) return;
|
||||
tasks.push({ id: String(Date.now()), text: text, done: false });
|
||||
taskInput.value = '';
|
||||
render();
|
||||
save();
|
||||
});
|
||||
|
||||
function toggleTask(id) {
|
||||
const task = tasks.find(function (t) { return t.id === id; });
|
||||
if (!task) return;
|
||||
task.done = !task.done;
|
||||
render();
|
||||
save();
|
||||
}
|
||||
|
||||
function deleteTask(id) {
|
||||
tasks = tasks.filter(function (t) { return t.id !== id; });
|
||||
render();
|
||||
save();
|
||||
}
|
||||
|
||||
function render() {
|
||||
listEl.textContent = '';
|
||||
if (tasks.length === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'empty';
|
||||
empty.textContent = 'No tasks yet';
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
tasks.forEach(function (task) {
|
||||
const li = document.createElement('li');
|
||||
if (task.done) li.className = 'done';
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = task.done;
|
||||
checkbox.addEventListener('change', function () { toggleTask(task.id); });
|
||||
const span = document.createElement('span');
|
||||
span.textContent = task.text;
|
||||
const del = document.createElement('button');
|
||||
del.textContent = '\u2715';
|
||||
del.setAttribute('aria-label', 'Delete task');
|
||||
del.addEventListener('click', function () { deleteTask(task.id); });
|
||||
li.append(checkbox, span, del);
|
||||
listEl.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function load() {
|
||||
return bridgeRequest('io.tatuchat.storage.load').then(function (data) {
|
||||
if (data && Array.isArray(data.tasks)) tasks = data.tasks;
|
||||
}).catch(function () {});
|
||||
}
|
||||
|
||||
function save() {
|
||||
return bridgeRequest('io.tatuchat.storage.save', { tasks: tasks }).catch(function () {});
|
||||
}
|
||||
|
||||
load().finally(render);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user