- Add autolink Jinja2 filter to detect URLs and make them clickable - Add link picker dropdown to insert existing link URLs into list item content - Add inline edit with link picker on each list item row - Apply autolink filter on list detail and focus available list items Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
222 lines
10 KiB
HTML
222 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<!-- Breadcrumb -->
|
|
<div class="breadcrumb">
|
|
<a href="/lists">Lists</a>
|
|
<span class="sep">/</span>
|
|
{% if domain %}<span style="color: {{ domain.color or 'var(--accent)' }}">{{ domain.name }}</span><span class="sep">/</span>{% endif %}
|
|
{% if project %}<a href="/projects/{{ project.id }}">{{ project.name }}</a><span class="sep">/</span>{% endif %}
|
|
<span>{{ item.name }}</span>
|
|
</div>
|
|
|
|
<div class="detail-header">
|
|
<h1 class="detail-title">{{ item.name }}</h1>
|
|
<div class="flex gap-2">
|
|
<a href="/lists/{{ item.id }}/edit" class="btn btn-secondary btn-sm">Edit</a>
|
|
<form action="/lists/{{ item.id }}/delete" method="post" data-confirm="Delete this list?" style="display:inline">
|
|
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-meta mt-2">
|
|
<span class="detail-meta-item">
|
|
<span class="row-tag">{{ item.list_type }}</span>
|
|
</span>
|
|
{% if item.description %}
|
|
<p class="text-secondary mt-1">{{ item.description }}</p>
|
|
{% endif %}
|
|
{% if item.tags %}
|
|
<div class="mt-1">
|
|
{% for tag in item.tags %}
|
|
<span class="row-tag">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Add item form -->
|
|
<form class="quick-add mt-3" action="/lists/{{ item.id }}/items/add" method="post">
|
|
<input type="text" name="content" id="add-item-content" placeholder="Add item..." required style="flex:1">
|
|
{% if all_links %}
|
|
<select class="link-picker" data-target="add-item-content" style="max-width:180px">
|
|
<option value="">Insert link...</option>
|
|
{% for lnk in all_links %}
|
|
<option value="{{ lnk.url }}">{{ lnk.label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
<button type="submit" class="btn btn-primary btn-sm">Add</button>
|
|
</form>
|
|
|
|
<!-- List items -->
|
|
{% if list_items %}
|
|
<div class="card mt-2">
|
|
{% for li in list_items %}
|
|
<div class="list-row {{ 'completed' if li.completed }}">
|
|
{% with reorder_url="/lists/" ~ item.id ~ "/items/reorder", item_id=li.id %}
|
|
{% include 'partials/reorder_arrows.html' %}
|
|
{% endwith %}
|
|
{% if item.list_type == 'checklist' %}
|
|
<div class="row-check">
|
|
<form action="/lists/{{ item.id }}/items/{{ li.id }}/toggle" method="post" style="display:inline">
|
|
<input type="checkbox" id="li-{{ li.id }}" {{ 'checked' if li.completed }}
|
|
onchange="this.form.submit()">
|
|
<label for="li-{{ li.id }}"></label>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
<span class="row-title" data-display-for="{{ li.id }}" style="{{ 'text-decoration: line-through; opacity: 0.6;' if li.completed }}">
|
|
{{ li.content|autolink }}
|
|
</span>
|
|
<div class="row-actions" data-display-for="{{ li.id }}">
|
|
<button type="button" class="btn btn-ghost btn-xs edit-item-btn" data-item-id="{{ li.id }}">Edit</button>
|
|
<form action="/lists/{{ item.id }}/items/{{ li.id }}/delete" method="post" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs" style="color: var(--red)">Del</button>
|
|
</form>
|
|
</div>
|
|
<!-- Inline edit form -->
|
|
<form class="inline-edit-form" data-edit-for="{{ li.id }}" action="/lists/{{ item.id }}/items/{{ li.id }}/edit" method="post" style="display:none;">
|
|
<input type="text" name="content" id="edit-content-{{ li.id }}" value="{{ li.content }}" required style="flex:1">
|
|
{% if all_links %}
|
|
<select class="link-picker" data-target="edit-content-{{ li.id }}" style="max-width:160px">
|
|
<option value="">Insert link...</option>
|
|
{% for lnk in all_links %}
|
|
<option value="{{ lnk.url }}">{{ lnk.label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
<button type="submit" class="btn btn-primary btn-xs">Save</button>
|
|
<button type="button" class="btn btn-ghost btn-xs cancel-edit-btn" data-item-id="{{ li.id }}">Cancel</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Child items -->
|
|
{% for child in child_map.get(li.id|string, []) %}
|
|
<div class="list-row {{ 'completed' if child.completed }}" style="padding-left: 48px;">
|
|
{% with reorder_url="/lists/" ~ item.id ~ "/items/reorder", item_id=child.id, extra_fields={"parent_id": li.id|string} %}
|
|
{% include 'partials/reorder_arrows.html' %}
|
|
{% endwith %}
|
|
{% if item.list_type == 'checklist' %}
|
|
<div class="row-check">
|
|
<form action="/lists/{{ item.id }}/items/{{ child.id }}/toggle" method="post" style="display:inline">
|
|
<input type="checkbox" id="li-{{ child.id }}" {{ 'checked' if child.completed }}
|
|
onchange="this.form.submit()">
|
|
<label for="li-{{ child.id }}"></label>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
<span class="row-title" data-display-for="{{ child.id }}" style="{{ 'text-decoration: line-through; opacity: 0.6;' if child.completed }}">
|
|
{{ child.content|autolink }}
|
|
</span>
|
|
<div class="row-actions" data-display-for="{{ child.id }}">
|
|
<button type="button" class="btn btn-ghost btn-xs edit-item-btn" data-item-id="{{ child.id }}">Edit</button>
|
|
<form action="/lists/{{ item.id }}/items/{{ child.id }}/delete" method="post" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs" style="color: var(--red)">Del</button>
|
|
</form>
|
|
</div>
|
|
<!-- Inline edit form -->
|
|
<form class="inline-edit-form" data-edit-for="{{ child.id }}" action="/lists/{{ item.id }}/items/{{ child.id }}/edit" method="post" style="display:none;">
|
|
<input type="text" name="content" id="edit-content-{{ child.id }}" value="{{ child.content }}" required style="flex:1">
|
|
{% if all_links %}
|
|
<select class="link-picker" data-target="edit-content-{{ child.id }}" style="max-width:160px">
|
|
<option value="">Insert link...</option>
|
|
{% for lnk in all_links %}
|
|
<option value="{{ lnk.url }}">{{ lnk.label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
<button type="submit" class="btn btn-primary btn-xs">Save</button>
|
|
<button type="button" class="btn btn-ghost btn-xs cancel-edit-btn" data-item-id="{{ child.id }}">Cancel</button>
|
|
</form>
|
|
</div>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state mt-3">
|
|
<div class="empty-state-icon">☐</div>
|
|
<div class="empty-state-text">No items yet. Add one above.</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Contacts -->
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Contacts<span class="page-count">{{ contacts|length }}</span></h3>
|
|
</div>
|
|
<form action="/lists/{{ item.id }}/contacts/add" method="post" class="flex gap-2 items-end" style="padding: 12px; border-bottom: 1px solid var(--border);">
|
|
<div class="form-group" style="flex:1; margin:0;">
|
|
<select name="contact_id" class="form-select" required>
|
|
<option value="">Select contact...</option>
|
|
{% for c in all_contacts %}
|
|
<option value="{{ c.id }}">{{ c.first_name }} {{ c.last_name or '' }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="flex:1; margin:0;">
|
|
<input type="text" name="role" class="form-input" placeholder="Role (optional)">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-sm">Add</button>
|
|
</form>
|
|
{% for c in contacts %}
|
|
<div class="list-row">
|
|
<span class="row-title"><a href="/contacts/{{ c.id }}">{{ c.first_name }} {{ c.last_name or '' }}</a></span>
|
|
{% if c.role %}<span class="row-tag">{{ c.role }}</span>{% endif %}
|
|
<div class="row-actions">
|
|
<form action="/lists/{{ item.id }}/contacts/{{ c.id }}/remove" method="post" style="display:inline">
|
|
<button class="btn btn-ghost btn-xs" title="Remove">Remove</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div style="padding: 12px; color: var(--muted); font-size: 0.85rem;">No contacts linked</div>
|
|
{% endfor %}
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
// Link picker: insert URL at cursor position in target input
|
|
document.querySelectorAll('.link-picker').forEach(function(sel) {
|
|
sel.addEventListener('change', function() {
|
|
var url = sel.value;
|
|
if (!url) return;
|
|
var targetId = sel.getAttribute('data-target');
|
|
var input = document.getElementById(targetId);
|
|
if (!input) return;
|
|
var start = input.selectionStart || input.value.length;
|
|
var end = input.selectionEnd || input.value.length;
|
|
var before = input.value.substring(0, start);
|
|
var after = input.value.substring(end);
|
|
// Add space padding if needed
|
|
if (before.length > 0 && before[before.length - 1] !== ' ') before += ' ';
|
|
if (after.length > 0 && after[0] !== ' ') url += ' ';
|
|
input.value = before + url + after;
|
|
input.focus();
|
|
var pos = before.length + url.length;
|
|
input.setSelectionRange(pos, pos);
|
|
sel.selectedIndex = 0;
|
|
});
|
|
});
|
|
|
|
// Inline edit: toggle edit form
|
|
document.querySelectorAll('.edit-item-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var id = btn.getAttribute('data-item-id');
|
|
document.querySelectorAll('[data-display-for="' + id + '"]').forEach(function(el) { el.style.display = 'none'; });
|
|
var form = document.querySelector('[data-edit-for="' + id + '"]');
|
|
if (form) { form.style.display = 'flex'; form.querySelector('input[name="content"]').focus(); }
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.cancel-edit-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var id = btn.getAttribute('data-item-id');
|
|
document.querySelectorAll('[data-display-for="' + id + '"]').forEach(function(el) { el.style.display = ''; });
|
|
var form = document.querySelector('[data-edit-for="' + id + '"]');
|
|
if (form) form.style.display = 'none';
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|