Each standalone focus item now auto-creates a linked note and checklist. Clicking a focus item opens a detail page with side-by-side note editor (left) and checklist (right) with drag-to-reorder. Save & Return writes the note and goes back to the focus list. Added focus_id FK to notes and lists tables, made domain optional when creating from focus context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
8.9 KiB
HTML
174 lines
8.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="breadcrumb">
|
|
<a href="/focus">Focus</a>
|
|
<span class="sep">/</span>
|
|
<span>{{ item.title }}</span>
|
|
</div>
|
|
|
|
<div class="detail-header">
|
|
<div class="flex items-center justify-between">
|
|
<h1 class="detail-title" style="{{ 'text-decoration:line-through;opacity:0.6;' if item.completed }}">{{ item.title }}</h1>
|
|
<div class="flex gap-2">
|
|
<a href="/focus/{{ item.id }}/edit" class="btn btn-secondary btn-sm">Edit</a>
|
|
<form action="/focus/{{ item.id }}/toggle" method="post" style="display:inline">
|
|
<button class="btn {{ 'btn-secondary' if item.completed else 'btn-primary' }} btn-sm">
|
|
{{ 'Reopen' if item.completed else 'Complete' }}
|
|
</button>
|
|
</form>
|
|
<form action="/focus/{{ item.id }}/remove" method="post" data-confirm="Delete this focus item?" style="display:inline">
|
|
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="detail-meta mt-2">
|
|
{% if item.completed %}<span class="status-badge status-done">completed</span>{% endif %}
|
|
{% if domain %}<span class="row-domain-tag" style="background: {{ domain.color or '#4F6EF7' }}22; color: {{ domain.color or '#4F6EF7' }}">{{ domain.name }}</span>{% endif %}
|
|
{% if project %}<span class="row-tag">{{ project.name }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Convert row -->
|
|
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:end;margin-bottom:16px;padding:8px 0;">
|
|
<span style="font-size:0.78rem;color:var(--muted);font-weight:600;">Convert to:</span>
|
|
<form action="/focus/{{ item.id }}/convert-to-task" method="post" data-confirm="Convert to task?" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs">Task</button>
|
|
</form>
|
|
<form action="/focus/{{ item.id }}/convert-to-note" method="post" data-confirm="Convert to note?" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs">Note</button>
|
|
</form>
|
|
<form action="/focus/{{ item.id }}/convert-to-link" method="post" data-confirm="Convert to link?" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs">Link</button>
|
|
</form>
|
|
<form action="/focus/{{ item.id }}/convert-to-list-item" method="post" data-confirm="Add to selected list?" style="display:inline-flex;gap:4px;align-items:end;">
|
|
<select name="list_id" class="form-select" style="min-width:140px;height:28px;font-size:12px;" required>
|
|
<option value="">Select list...</option>
|
|
{% for l in all_lists %}
|
|
<option value="{{ l.id }}">{{ l.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="submit" class="btn btn-ghost btn-xs">List Item</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Side-by-side: Note (left) + List (right) -->
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;">
|
|
|
|
<!-- Note panel -->
|
|
<div class="card" style="padding:0;">
|
|
<div style="padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;">
|
|
<span style="font-weight:600;font-size:0.82rem;color:var(--text);">Notes</span>
|
|
</div>
|
|
<form action="/focus/{{ item.id }}/save-note" method="post">
|
|
<input type="hidden" name="note_id" value="{{ note.id }}">
|
|
<textarea name="body" style="width:100%;min-height:320px;padding:10px 12px;border:none;background:transparent;color:var(--text);font-family:var(--font-mono);font-size:0.85rem;resize:vertical;outline:none;box-sizing:border-box;" placeholder="Start typing notes...">{{ note.body if note.body else '' }}</textarea>
|
|
<div style="padding:8px 12px;border-top:1px solid var(--border);display:flex;gap:8px;">
|
|
<button type="submit" class="btn btn-primary btn-sm">Save & Return</button>
|
|
<a href="/focus" class="btn btn-ghost btn-sm">Back</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- List panel -->
|
|
<div class="card" style="padding:0;">
|
|
<div style="padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;">
|
|
<span style="font-weight:600;font-size:0.82rem;color:var(--text);">Checklist</span>
|
|
<span style="font-size:0.72rem;color:var(--muted);">{{ list_items|length }} item{{ 's' if list_items|length != 1 }}</span>
|
|
</div>
|
|
|
|
<!-- Add item -->
|
|
<form action="/focus/{{ item.id }}/list-item/add" method="post" style="display:flex;gap:6px;padding:8px 12px;border-bottom:1px solid var(--border);">
|
|
<input type="hidden" name="list_id" value="{{ list_id }}">
|
|
<input type="text" name="content" class="form-input" placeholder="Add item..." required style="flex:1;padding:5px 8px;font-size:0.82rem;">
|
|
<button type="submit" class="btn btn-primary btn-sm">+</button>
|
|
</form>
|
|
|
|
<!-- Items -->
|
|
<div id="focus-checklist" style="max-height:340px;overflow-y:auto;">
|
|
{% for li in list_items %}
|
|
<div class="list-row focus-cl-row" draggable="true" data-id="{{ li.id }}" style="padding:4px 8px;min-height:0;border-bottom:1px solid var(--border);{{ 'opacity:0.5;' if li.completed }}cursor:grab;">
|
|
<span class="reorder-grip" style="margin-right:4px;color:var(--muted);font-size:0.7rem;cursor:grab;">☰</span>
|
|
<form action="/focus/{{ item.id }}/list-item/{{ li.id }}/toggle" method="post" style="display:inline">
|
|
<div class="row-check" style="margin-right:6px;">
|
|
<input type="checkbox" id="li-{{ li.id }}" {{ 'checked' if li.completed }} onchange="this.form.submit()">
|
|
<label for="li-{{ li.id }}"></label>
|
|
</div>
|
|
</form>
|
|
<span style="flex:1;font-size:0.82rem;{{ 'text-decoration:line-through;' if li.completed }}">{{ li.content }}</span>
|
|
<form action="/focus/{{ item.id }}/list-item/{{ li.id }}/delete" method="post" style="display:inline">
|
|
<button class="btn btn-ghost btn-xs" style="color:var(--red);padding:0 4px;" title="Remove">×</button>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
<div style="padding:16px 12px;color:var(--muted);font-size:0.82rem;text-align:center;">No items yet</div>
|
|
{% endfor %}
|
|
</div>
|
|
<form id="cl-reorder-form" action="/focus/{{ item.id }}/list-item/reorder-all" method="post" style="display:none;">
|
|
<input type="hidden" name="item_ids" id="cl-reorder-ids">
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
var container = document.getElementById('focus-checklist');
|
|
if (!container) return;
|
|
var dragRow = null;
|
|
|
|
container.querySelectorAll('.focus-cl-row').forEach(function(row) {
|
|
row.addEventListener('dragstart', function(e) {
|
|
dragRow = row;
|
|
row.classList.add('dragging');
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
e.dataTransfer.setData('text/plain', row.dataset.id);
|
|
});
|
|
|
|
row.addEventListener('dragend', function() {
|
|
row.classList.remove('dragging');
|
|
container.querySelectorAll('.focus-cl-row.drag-over').forEach(function(el) {
|
|
el.classList.remove('drag-over');
|
|
});
|
|
if (dragRow) {
|
|
var allIds = [];
|
|
container.querySelectorAll('.focus-cl-row').forEach(function(r) {
|
|
allIds.push(r.dataset.id);
|
|
});
|
|
document.getElementById('cl-reorder-ids').value = allIds.join(',');
|
|
document.getElementById('cl-reorder-form').submit();
|
|
}
|
|
dragRow = null;
|
|
});
|
|
|
|
row.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
if (!dragRow || row === dragRow) return;
|
|
e.dataTransfer.dropEffect = 'move';
|
|
container.querySelectorAll('.focus-cl-row.drag-over').forEach(function(el) {
|
|
el.classList.remove('drag-over');
|
|
});
|
|
row.classList.add('drag-over');
|
|
});
|
|
|
|
row.addEventListener('dragleave', function() {
|
|
row.classList.remove('drag-over');
|
|
});
|
|
|
|
row.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
if (!dragRow || row === dragRow) return;
|
|
row.classList.remove('drag-over');
|
|
var rows = Array.from(container.querySelectorAll('.focus-cl-row'));
|
|
var dragIdx = rows.indexOf(dragRow);
|
|
var targetIdx = rows.indexOf(row);
|
|
if (dragIdx < targetIdx) {
|
|
container.insertBefore(dragRow, row.nextSibling);
|
|
} else {
|
|
container.insertBefore(dragRow, row);
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|