The TriliumNext Task-Hub plugin is now available for trial use. #9226
ZangXincz
started this conversation in
Plugin Ideas
Replies: 3 comments 7 replies
-
|
This is great! I'll test it. |
Beta Was this translation helpful? Give feedback.
1 reply
-
|
Hi, Two remarks:
|
Beta Was this translation helpful? Give feedback.
6 replies
-
|
Update 4: You can now mark tasks as complete directly from the open tasks list. Details
/**
* ╔═══════════════════════════════════════════╗
* ║ Painel de Tarefas — TriliumNext ║
* ╚═══════════════════════════════════════════╝
*
* INTERAÇÕES:
* ☐ clicar no quadradinho → marca a tarefa como concluída na nota
* texto da tarefa → abre a nota que contém a tarefa
* título do grupo → abre a nota (comportamento original)
*/
(async function () {
const $root = $container;
$root.css({
padding: '28px 32px',
fontFamily: 'var(--detail-font-family, "Segoe UI", sans-serif)',
fontSize: '16px',
lineHeight: '1.5',
color: 'var(--main-text-color)',
boxSizing: 'border-box'
});
// =========================================================
// ESTILOS
// =========================================================
if (!document.getElementById('th-task-styles')) {
const style = document.createElement('style');
style.id = 'th-task-styles';
style.textContent = `
.th-task-check {
flex-shrink: 0;
width: 15px;
height: 15px;
margin-top: 3px;
border: 1.5px solid var(--main-border-color, #45475a);
border-radius: 3px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color .15s, background .15s, color .15s;
color: transparent;
font-size: 10px;
line-height: 1;
user-select: none;
}
.th-task-check:hover {
border-color: var(--main-text-color);
background: var(--accented-background-color, #313244);
color: var(--muted-text-color, #888);
}
.th-task-check.th-completing {
border-color: var(--active-item-background-color, #a6e3a1);
background: rgba(166, 227, 161, .15);
color: var(--active-item-background-color, #a6e3a1);
pointer-events: none;
}
.th-task-text {
cursor: pointer;
transition: color .15s;
}
.th-task-text:hover {
color: var(--link-color, var(--main-text-color));
text-decoration: underline;
}
.th-note-link {
cursor: pointer;
transition: color .15s;
}
.th-note-link:hover {
color: var(--main-text-color) !important;
}
.th-task-row {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 3px 0;
font-size: 16px;
line-height: 1.45;
}
`;
document.head.appendChild(style);
}
// =========================================================
// RENDER
// =========================================================
async function load() {
$root.html(`
<p style="color:var(--muted-text-color,#888)">
⏳ Carregando tarefas…
</p>
`);
try {
// ── Backend: busca todas as notas de texto ──────────
const rawNotes = await api.runOnBackend(() => {
const rows = api.sql.getRows(`
SELECT noteId, title
FROM notes
WHERE isDeleted = 0 AND type = 'text'
ORDER BY title COLLATE NOCASE
`);
return rows.map(row => {
const note = api.getNote(row.noteId);
return {
noteId: row.noteId,
title: row.title || '(sem título)',
content: note ? note.getContent() : ''
};
});
});
// ── Frontend: processa checkboxes ───────────────────
const groups = [];
for (const row of rawNotes) {
if (!row.content) continue;
const $tmp = $('<div>').html(String(row.content));
const tasks = [];
let cbIndex = 0; // índice global de todos os checkboxes da nota
$tmp.find('input[type="checkbox"]').each((_, input) => {
const idx = cbIndex++; // captura antes de incrementar
// pula tarefas já concluídas
if (input.checked || input.hasAttribute('checked')) return;
let text = '';
const $span = $(input).next('span');
if ($span.length) {
text = $span.text();
} else {
text = $(input).parent().text();
}
text = text.replace(/\s+/g, ' ').trim();
if (text) {
tasks.push({ text, checkboxIndex: idx });
}
});
if (tasks.length > 0) {
groups.push({
noteId: row.noteId,
title: row.title,
count: tasks.length,
tasks
});
}
}
// ── Sem tarefas ─────────────────────────────────────
if (groups.length === 0) {
$root.html(`
<p style="color:var(--muted-text-color,#888)">
✓ Nenhuma tarefa aberta encontrada.
</p>
`);
return;
}
// ── Monta HTML ──────────────────────────────────────
const totalTasks = groups.reduce((s, g) => s + g.count, 0);
const esc = s => String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
let html = `
<div style="
display: flex;
align-items: baseline;
gap: 10px;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 1px solid var(--main-border-color, #313244);
">
<span style="font-size:19px;font-weight:700;">Tarefas abertas</span>
<span class="th-total-count" style="font-size:14px;color:var(--muted-text-color,#888);">
${totalTasks} tarefa${totalTasks !== 1 ? 's' : ''}
</span>
</div>
`;
for (const group of groups) {
html += `
<div class="th-group" style="margin-bottom:20px;">
<div
class="th-note-link"
data-note-id="${esc(group.noteId)}"
style="
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--muted-text-color, #888);
"
>
${esc(group.title)}
<span
class="th-group-count"
style="
font-size: 13px;
background: var(--accented-background-color, #313244);
color: var(--muted-text-color, #888);
padding: 1px 7px;
border-radius: 10px;
"
>${group.count}</span>
</div>
<div
class="th-task-list"
style="
border-left: 2px solid var(--main-border-color, #313244);
padding-left: 14px;
"
>
${group.tasks.map(task => `
<div
class="th-task-row"
data-note-id="${esc(group.noteId)}"
data-cb-index="${task.checkboxIndex}"
>
<span
class="th-task-check"
title="Marcar como concluída"
>✓</span>
<span
class="th-task-text"
data-note-id="${esc(group.noteId)}"
title="Abrir nota"
>${esc(task.text)}</span>
</div>
`).join('')}
</div>
</div>
`;
}
$root.html(html);
} catch (err) {
console.error(err);
$root.html(`
<div style="
color: #f38ba8;
background: rgba(243,139,168,.08);
padding: 12px 14px;
border-radius: 8px;
font-size: 15px;
">
✗ Erro ao carregar tarefas<br><br>
${String(err.message || err)}
</div>
`);
}
}
await load();
// =========================================================
// EVENTOS (delegados ao $root para sobreviver ao re-render)
// =========================================================
// Título do grupo → abre nota
$root.on('click', '.th-note-link', function () {
api.activateNote($(this).data('noteId'));
});
// Texto da tarefa → abre nota
$root.on('click', '.th-task-text', function () {
api.activateNote($(this).data('noteId'));
});
// Quadradinho → marca como concluída
$root.on('click', '.th-task-check', async function () {
const $check = $(this);
if ($check.hasClass('th-completing')) return; // evita duplo clique
const $row = $check.closest('.th-task-row');
const noteId = String($row.data('noteId'));
const cbIndex = parseInt($row.data('cbIndex'), 10);
$check.addClass('th-completing');
try {
// ── Backend: aplica checked no HTML da nota ─────────
await api.runOnBackend((noteId, cbIndex) => {
const note = api.getNote(noteId);
let content = note.getContent();
let count = 0;
content = content.replace(
/<input\s+type="checkbox"([^>]*?)>/gi,
(match, attrs) => {
if (count++ === cbIndex) {
// adiciona checked apenas se ainda não estiver
if (/\bchecked\b/i.test(attrs)) return match;
return `<input type="checkbox"${attrs} checked>`;
}
return match;
}
);
note.setContent(content);
}, [noteId, cbIndex]);
// ── UI: remove a linha com animação ─────────────────
$row.fadeOut(250, function () {
const $group = $(this).closest('.th-group');
$(this).remove();
// atualiza contador do grupo
const remainingInGroup = $group.find('.th-task-row').length;
if (remainingInGroup === 0) {
$group.fadeOut(200, function () { $(this).remove(); });
} else {
$group.find('.th-group-count').text(remainingInGroup);
}
// atualiza contador global
const total = $root.find('.th-task-row').length;
$root.find('.th-total-count').text(
`${total} tarefa${total !== 1 ? 's' : ''}`
);
// estado vazio
if (total === 0) {
$root.html(`
<p style="color:var(--muted-text-color,#888)">
✓ Nenhuma tarefa aberta encontrada.
</p>
`);
}
});
} catch (err) {
console.error(err);
$check.removeClass('th-completing'); // reverte visual em caso de erro
}
});
})(); |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment


Uh oh!
There was an error while loading. Please reload this page.
-
It provides a clean task overview on the right panel, helping you collect todos scattered across different notes, including due dates, priorities, recurring tasks, and quick navigation.
Features
Who Is It For
This project is a good fit if you manage tasks in TriliumNext like this:
ZIP compressed file:task-hub0.1.0.zip
Github:https://github.com/ZangXincz/TriliumNext-Task-Hub
Beta Was this translation helpful? Give feedback.
All reactions