Add continuous collection reading view
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -2,66 +2,37 @@
|
|||||||
{% load i18n markdown_extras %}
|
{% load i18n markdown_extras %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-md-none alert alert-info">{% trans "Collections are currently available on desktop only." %}</div>
|
<div class="d-md-none alert alert-info">{% trans "Collections are currently available on desktop only." %}</div>
|
||||||
<div class="d-none d-md-block">
|
<article class="d-none d-md-block collection-document">
|
||||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-4">
|
<header class="mb-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-sm btn-outline-secondary mb-3" href="{% url 'collection_list' %}">← {% trans "Collections" %}</a>
|
<a class="btn btn-sm btn-outline-secondary mb-3" href="{% if user.is_authenticated %}{% url 'collection_list' %}{% else %}/{% endif %}">← {% if user.is_authenticated %}{% trans "Collections" %}{% else %}Start{% endif %}</a>
|
||||||
<h1 class="display-6 mb-2">{{ collection.title }}</h1>
|
<h1 class="display-5 mb-3">{{ collection.title }}</h1>
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}
|
{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}
|
||||||
{% for tag in collection.tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
|
{% for tag in collection.tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if can_edit %}<div class="d-flex gap-2"><a class="btn btn-outline-secondary" href="{% url 'collection_edit' collection.id %}">{% trans "Edit collection" %}</a>{% if can_delete %}<form method="post" action="{% url 'collection_delete' collection.id %}" onsubmit="return confirm('{% trans "Delete collection? The notes remain available." %}')">{% csrf_token %}<button class="btn btn-outline-danger">{% trans "Delete" %}</button></form>{% endif %}</div>{% endif %}
|
{% if can_edit %}<a class="btn btn-dark" href="{% url 'collection_manage' collection.id %}">{% trans "Edit collection" %}</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if collection.description %}<div class="content lead mt-4">{{ collection.description|markdown }}</div>{% endif %}
|
||||||
{% if collection.description %}<div class="content lead mb-4">{{ collection.description|markdown }}</div>{% endif %}
|
</header>
|
||||||
|
|
||||||
{% if sections %}
|
{% if sections %}
|
||||||
<div class="card item-card mb-4"><div class="card-body"><div class="fw-semibold mb-2">{% trans "Contents" %}</div><ol class="mb-0">{% for section in sections %}<li><a href="#section-{{ section.id }}">{{ section.title|default:section.item.content|truncatechars:90 }}</a></li>{% endfor %}</ol></div></div>
|
<nav class="collection-toc mb-5" aria-label="{% trans 'Contents' %}">
|
||||||
|
<div class="small fw-semibold text-uppercase text-muted mb-2">{% trans "Contents" %}</div>
|
||||||
|
<ol class="mb-0">{% for section in sections %}<li><a href="#section-{{ section.id }}">{{ section.title|default:section.item.content|truncatechars:90 }}</a></li>{% endfor %}</ol>
|
||||||
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if pending_item %}
|
<div class="collection-flow content">
|
||||||
<div class="alert alert-warning">
|
|
||||||
<h2 class="h5">{% trans "The note has a different visibility" %}</h2>
|
|
||||||
<p>{% trans "To add it, change its visibility or create a separate copy for this collection." %}</p>
|
|
||||||
<div class="border rounded bg-white p-2 mb-3">{{ pending_item.content|truncatechars:180 }}</div>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
{% if pending_can_convert %}<form method="post">{% csrf_token %}<input type="hidden" name="action" value="confirm_add"><input type="hidden" name="item_id" value="{{ pending_item.id }}"><input type="hidden" name="mode" value="convert"><button class="btn btn-warning">{% if collection.visibility == 'public' %}{% trans "Make note public" %}{% elif collection.team %}{% trans "Share note with team" %}{% else %}{% trans "Make note private" %}{% endif %}</button></form>{% endif %}
|
|
||||||
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="confirm_add"><input type="hidden" name="item_id" value="{{ pending_item.id }}"><input type="hidden" name="mode" value="copy"><button class="btn btn-outline-secondary">{% trans "Create compatible copy" %}</button></form>
|
|
||||||
<a class="btn btn-link" href="{{ collection.get_absolute_url }}">{% trans "Cancel" %}</a>
|
|
||||||
</div>
|
|
||||||
{% if not pending_can_convert %}<div class="small text-muted mt-2">{% trans "The original cannot be changed because it is used elsewhere or you cannot edit it." %}</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="vstack gap-4 mb-5">
|
|
||||||
{% for section in sections %}
|
{% for section in sections %}
|
||||||
<section id="section-{{ section.id }}" class="card item-card"><div class="card-body">
|
<section id="section-{{ section.id }}" class="collection-flow-section">
|
||||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
{% if section.title %}<h2>{{ section.title }}</h2>{% endif %}
|
||||||
<h2 class="h3 mb-0">{{ section.title|default:section.item.content|truncatechars:100 }}</h2>
|
{{ section.item|item_markdown }}
|
||||||
<a class="small text-muted" href="{{ section.item.get_absolute_url }}">#{{ section.item.id }}</a>
|
{% if section.item.comment %}<div class="text-muted border-start ps-3 mt-3">{{ section.item.comment|markdown }}</div>{% endif %}
|
||||||
</div>
|
</section>
|
||||||
<div class="content">{{ section.item|item_markdown }}</div>
|
|
||||||
{% if section.item.comment %}<div class="content text-muted border-start ps-3 mt-3">{{ section.item.comment|markdown }}</div>{% endif %}
|
|
||||||
{% if can_edit %}<div class="border-top mt-4 pt-3 d-flex align-items-center gap-2">
|
|
||||||
<form method="post" class="d-flex gap-2 flex-grow-1">{% csrf_token %}<input type="hidden" name="action" value="title"><input type="hidden" name="section_id" value="{{ section.id }}"><input class="form-control form-control-sm" name="title" value="{{ section.title }}" placeholder="{% trans 'Optional section title' %}"><button class="btn btn-sm btn-outline-secondary">{% trans "Save title" %}</button></form>
|
|
||||||
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="up"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-secondary" title="{% trans 'Move up' %}">↑</button></form>
|
|
||||||
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="down"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-secondary" title="{% trans 'Move down' %}">↓</button></form>
|
|
||||||
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="remove"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-danger">{% trans "Remove" %}</button></form>
|
|
||||||
</div>{% endif %}
|
|
||||||
</div></section>
|
|
||||||
{% empty %}<div class="text-center text-muted py-5">{% trans "This collection has no sections yet." %}</div>{% endfor %}
|
{% empty %}<div class="text-center text-muted py-5">{% trans "This collection has no sections yet." %}</div>{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</article>
|
||||||
{% if can_edit %}
|
|
||||||
<div class="card item-card"><div class="card-body">
|
|
||||||
<h2 class="h4">{% trans "Add existing note" %}</h2>
|
|
||||||
<form method="get" class="input-group mb-3"><input class="form-control" name="q" value="{{ q }}" placeholder="{% trans 'Search notes' %}"><button class="btn btn-outline-secondary">{% trans "Search" %}</button></form>
|
|
||||||
<div class="vstack gap-2">
|
|
||||||
{% for item in candidates %}<div class="border rounded p-3 d-flex justify-content-between align-items-start gap-3"><div><div>{{ item.content|truncatechars:180 }}</div><div class="small text-muted">#{{ item.id }} · {{ item.get_visibility_display }}{% if item.team %} · {{ item.team.name }}{% endif %}</div></div><form method="post">{% csrf_token %}<input type="hidden" name="action" value="add"><input type="hidden" name="item_id" value="{{ item.id }}"><button class="btn btn-sm btn-dark">{% trans "Add" %}</button></form></div>{% empty %}<div class="text-muted">{% trans "No matching notes." %}</div>{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% load i18n markdown_extras %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-md-none alert alert-info">{% trans "Collections are currently available on desktop only." %}</div>
|
||||||
|
<div class="d-none d-md-block">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3 mb-4">
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-sm btn-outline-secondary mb-3" href="{{ collection.get_absolute_url }}">← {% trans "View collection" %}</a>
|
||||||
|
<h1 class="display-6 mb-2">{{ collection.title }}</h1>
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}
|
||||||
|
{% for tag in collection.tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if can_edit %}<div class="d-flex gap-2"><a class="btn btn-outline-secondary" href="{% url 'collection_edit' collection.id %}">{% trans "Collection settings" %}</a>{% if can_delete %}<form method="post" action="{% url 'collection_delete' collection.id %}" onsubmit="return confirm('{% trans "Delete collection? The notes remain available." %}')">{% csrf_token %}<button class="btn btn-outline-danger">{% trans "Delete" %}</button></form>{% endif %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if collection.description %}<div class="content lead mb-4">{{ collection.description|markdown }}</div>{% endif %}
|
||||||
|
|
||||||
|
{% if sections %}
|
||||||
|
<div class="card item-card mb-4"><div class="card-body"><div class="fw-semibold mb-2">{% trans "Contents" %}</div><ol class="mb-0">{% for section in sections %}<li><a href="#section-{{ section.id }}">{{ section.title|default:section.item.content|truncatechars:90 }}</a></li>{% endfor %}</ol></div></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if pending_item %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h2 class="h5">{% trans "The note has a different visibility" %}</h2>
|
||||||
|
<p>{% trans "To add it, change its visibility or create a separate copy for this collection." %}</p>
|
||||||
|
<div class="border rounded bg-white p-2 mb-3">{{ pending_item.content|truncatechars:180 }}</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
{% if pending_can_convert %}<form method="post">{% csrf_token %}<input type="hidden" name="action" value="confirm_add"><input type="hidden" name="item_id" value="{{ pending_item.id }}"><input type="hidden" name="mode" value="convert"><button class="btn btn-warning">{% if collection.visibility == 'public' %}{% trans "Make note public" %}{% elif collection.team %}{% trans "Share note with team" %}{% else %}{% trans "Make note private" %}{% endif %}</button></form>{% endif %}
|
||||||
|
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="confirm_add"><input type="hidden" name="item_id" value="{{ pending_item.id }}"><input type="hidden" name="mode" value="copy"><button class="btn btn-outline-secondary">{% trans "Create compatible copy" %}</button></form>
|
||||||
|
<a class="btn btn-link" href="{% url 'collection_manage' collection.id %}">{% trans "Cancel" %}</a>
|
||||||
|
</div>
|
||||||
|
{% if not pending_can_convert %}<div class="small text-muted mt-2">{% trans "The original cannot be changed because it is used elsewhere or you cannot edit it." %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="vstack gap-4 mb-5">
|
||||||
|
{% for section in sections %}
|
||||||
|
<section id="section-{{ section.id }}" class="card item-card"><div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
||||||
|
<h2 class="h3 mb-0">{{ section.title|default:section.item.content|truncatechars:100 }}</h2>
|
||||||
|
<a class="small text-muted" href="{{ section.item.get_absolute_url }}">#{{ section.item.id }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="content">{{ section.item|item_markdown }}</div>
|
||||||
|
{% if section.item.comment %}<div class="content text-muted border-start ps-3 mt-3">{{ section.item.comment|markdown }}</div>{% endif %}
|
||||||
|
{% if can_edit %}<div class="border-top mt-4 pt-3 d-flex align-items-center gap-2">
|
||||||
|
<form method="post" class="d-flex gap-2 flex-grow-1">{% csrf_token %}<input type="hidden" name="action" value="title"><input type="hidden" name="section_id" value="{{ section.id }}"><input class="form-control form-control-sm" name="title" value="{{ section.title }}" placeholder="{% trans 'Optional section title' %}"><button class="btn btn-sm btn-outline-secondary">{% trans "Save title" %}</button></form>
|
||||||
|
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="up"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-secondary" title="{% trans 'Move up' %}">↑</button></form>
|
||||||
|
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="down"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-secondary" title="{% trans 'Move down' %}">↓</button></form>
|
||||||
|
<form method="post">{% csrf_token %}<input type="hidden" name="action" value="remove"><input type="hidden" name="section_id" value="{{ section.id }}"><button class="btn btn-sm btn-outline-danger">{% trans "Remove" %}</button></form>
|
||||||
|
</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
|
{% empty %}<div class="text-center text-muted py-5">{% trans "This collection has no sections yet." %}</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if can_edit %}
|
||||||
|
<div class="card item-card"><div class="card-body">
|
||||||
|
<h2 class="h4">{% trans "Add existing note" %}</h2>
|
||||||
|
<form method="get" class="input-group mb-3"><input class="form-control" name="q" value="{{ q }}" placeholder="{% trans 'Search notes' %}"><button class="btn btn-outline-secondary">{% trans "Search" %}</button></form>
|
||||||
|
<div class="vstack gap-2">
|
||||||
|
{% for item in candidates %}<div class="border rounded p-3 d-flex justify-content-between align-items-start gap-3"><div><div>{{ item.content|truncatechars:180 }}</div><div class="small text-muted">#{{ item.id }} · {{ item.get_visibility_display }}{% if item.team %} · {{ item.team.name }}{% endif %}</div></div><form method="post">{% csrf_token %}<input type="hidden" name="action" value="add"><input type="hidden" name="item_id" value="{{ item.id }}"><button class="btn btn-sm btn-dark">{% trans "Add" %}</button></form></div>{% empty %}<div class="text-muted">{% trans "No matching notes." %}</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
+22
-6
@@ -159,20 +159,22 @@ class CollectionTests(TestCase):
|
|||||||
collection = Collection.objects.get()
|
collection = Collection.objects.get()
|
||||||
self.assertRedirects(response, collection.get_absolute_url())
|
self.assertRedirects(response, collection.get_absolute_url())
|
||||||
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Introduction')
|
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Introduction')
|
||||||
response = self.client.post(collection.get_absolute_url(), {'action': 'add', 'item_id': note.id})
|
manage_url = reverse('collection_manage', args=[collection.id])
|
||||||
self.assertRedirects(response, collection.get_absolute_url())
|
response = self.client.post(manage_url, {'action': 'add', 'item_id': note.id})
|
||||||
|
self.assertRedirects(response, manage_url)
|
||||||
self.assertTrue(CollectionSection.objects.filter(collection=collection, item=note).exists())
|
self.assertTrue(CollectionSection.objects.filter(collection=collection, item=note).exists())
|
||||||
|
|
||||||
def test_public_collection_asks_before_copying_private_note(self):
|
def test_public_collection_asks_before_copying_private_note(self):
|
||||||
collection = Collection.objects.create(owner=self.user, title='Public guide', visibility=Item.Visibility.PUBLIC)
|
collection = Collection.objects.create(owner=self.user, title='Public guide', visibility=Item.Visibility.PUBLIC)
|
||||||
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Private section')
|
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Private section')
|
||||||
response = self.client.post(collection.get_absolute_url(), {'action': 'add', 'item_id': note.id})
|
manage_url = reverse('collection_manage', args=[collection.id])
|
||||||
|
response = self.client.post(manage_url, {'action': 'add', 'item_id': note.id})
|
||||||
self.assertContains(response, 'name="mode" value="copy"')
|
self.assertContains(response, 'name="mode" value="copy"')
|
||||||
|
|
||||||
response = self.client.post(collection.get_absolute_url(), {
|
response = self.client.post(manage_url, {
|
||||||
'action': 'confirm_add', 'item_id': note.id, 'mode': 'copy',
|
'action': 'confirm_add', 'item_id': note.id, 'mode': 'copy',
|
||||||
})
|
})
|
||||||
self.assertRedirects(response, collection.get_absolute_url())
|
self.assertRedirects(response, manage_url)
|
||||||
copied = collection.sections.get().item
|
copied = collection.sections.get().item
|
||||||
self.assertNotEqual(copied.id, note.id)
|
self.assertNotEqual(copied.id, note.id)
|
||||||
self.assertEqual(copied.visibility, Item.Visibility.PUBLIC)
|
self.assertEqual(copied.visibility, Item.Visibility.PUBLIC)
|
||||||
@@ -199,10 +201,24 @@ class CollectionTests(TestCase):
|
|||||||
self.assertEqual(collection.team, team)
|
self.assertEqual(collection.team, team)
|
||||||
self.assertEqual(collection.visibility, Item.Visibility.TEAM)
|
self.assertEqual(collection.visibility, Item.Visibility.TEAM)
|
||||||
|
|
||||||
|
def test_collection_opens_as_continuous_reading_view(self):
|
||||||
|
collection = Collection.objects.create(owner=self.user, title='Reading view')
|
||||||
|
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First paragraph')
|
||||||
|
second = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Second paragraph')
|
||||||
|
CollectionSection.objects.create(collection=collection, item=first, title='First', position=0)
|
||||||
|
CollectionSection.objects.create(collection=collection, item=second, position=1)
|
||||||
|
response = self.client.get(collection.get_absolute_url())
|
||||||
|
self.assertContains(response, 'collection-flow')
|
||||||
|
self.assertContains(response, reverse('collection_manage', args=[collection.id]))
|
||||||
|
self.assertNotContains(response, 'Add existing note')
|
||||||
|
self.assertNotContains(response, 'section_id')
|
||||||
|
|
||||||
def test_public_collection_is_visible_without_login(self):
|
def test_public_collection_is_visible_without_login(self):
|
||||||
collection = Collection.objects.create(owner=self.user, title='Public', visibility=Item.Visibility.PUBLIC)
|
collection = Collection.objects.create(owner=self.user, title='Public', visibility=Item.Visibility.PUBLIC)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
self.assertEqual(self.client.get(collection.get_absolute_url()).status_code, 200)
|
response = self.client.get(collection.get_absolute_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, reverse('collection_manage', args=[collection.id]))
|
||||||
|
|
||||||
def test_collection_tag_is_not_removed_as_unused(self):
|
def test_collection_tag_is_not_removed_as_unused(self):
|
||||||
tag = Tag.objects.create(name='documentation')
|
tag = Tag.objects.create(name='documentation')
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ urlpatterns = [
|
|||||||
path('collections/', views.collection_list, name='collection_list'),
|
path('collections/', views.collection_list, name='collection_list'),
|
||||||
path('collections/new/', views.collection_create, name='collection_create'),
|
path('collections/new/', views.collection_create, name='collection_create'),
|
||||||
path('collections/<int:pk>/', views.collection_detail, name='collection_detail'),
|
path('collections/<int:pk>/', views.collection_detail, name='collection_detail'),
|
||||||
|
path('collections/<int:pk>/manage/', views.collection_manage, name='collection_manage'),
|
||||||
path('collections/<int:pk>/edit/', views.collection_edit, name='collection_edit'),
|
path('collections/<int:pk>/edit/', views.collection_edit, name='collection_edit'),
|
||||||
path('collections/<int:pk>/delete/', views.collection_delete, name='collection_delete'),
|
path('collections/<int:pk>/delete/', views.collection_delete, name='collection_delete'),
|
||||||
path('items/<int:pk>/', views.item_detail, name='item_detail'),
|
path('items/<int:pk>/', views.item_detail, name='item_detail'),
|
||||||
|
|||||||
+19
-8
@@ -692,29 +692,40 @@ def collection_edit(request, pk):
|
|||||||
|
|
||||||
def collection_detail(request, pk):
|
def collection_detail(request, pk):
|
||||||
collection = get_object_or_404(visible_collections(request.user), pk=pk)
|
collection = get_object_or_404(visible_collections(request.user), pk=pk)
|
||||||
if request.method == 'POST':
|
return render(request, 'core/collection_detail.html', {
|
||||||
|
'collection': collection,
|
||||||
|
'sections': collection.sections.select_related('item').prefetch_related('item__tags', 'item__attachments'),
|
||||||
|
'can_edit': user_can_edit_collection(request.user, collection),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def collection_manage(request, pk):
|
||||||
|
collection = get_object_or_404(visible_collections(request.user), pk=pk)
|
||||||
if not user_can_edit_collection(request.user, collection):
|
if not user_can_edit_collection(request.user, collection):
|
||||||
return JsonResponse({'error': 'forbidden'}, status=403)
|
return JsonResponse({'error': 'forbidden'}, status=403)
|
||||||
|
manage_url = reverse('collection_manage', args=[collection.pk])
|
||||||
|
if request.method == 'POST':
|
||||||
action = request.POST.get('action')
|
action = request.POST.get('action')
|
||||||
if action in {'add', 'confirm_add'}:
|
if action in {'add', 'confirm_add'}:
|
||||||
item = get_object_or_404(visible_items(request.user), pk=request.POST.get('item_id'), kind=Item.Kind.NOTE)
|
item = get_object_or_404(visible_items(request.user), pk=request.POST.get('item_id'), kind=Item.Kind.NOTE)
|
||||||
if collection_item_is_compatible(collection, item):
|
if collection_item_is_compatible(collection, item):
|
||||||
add_collection_section(collection, item)
|
add_collection_section(collection, item)
|
||||||
return redirect(collection)
|
return redirect(manage_url)
|
||||||
if action == 'add':
|
if action == 'add':
|
||||||
return render(request, 'core/collection_detail.html', collection_detail_context(request, collection, item))
|
return render(request, 'core/collection_manage.html', collection_detail_context(request, collection, item))
|
||||||
mode = request.POST.get('mode')
|
mode = request.POST.get('mode')
|
||||||
if mode == 'convert':
|
if mode == 'convert':
|
||||||
if not item_can_change_for_collection(request.user, item, collection):
|
if not item_can_change_for_collection(request.user, item, collection):
|
||||||
messages.error(request, _('This note is used by an incompatible collection or cannot be edited. Create a copy instead.'))
|
messages.error(request, _('This note is used by an incompatible collection or cannot be edited. Create a copy instead.'))
|
||||||
return redirect(collection)
|
return redirect(manage_url)
|
||||||
item = adapt_item_for_collection(item, collection)
|
item = adapt_item_for_collection(item, collection)
|
||||||
elif mode == 'copy':
|
elif mode == 'copy':
|
||||||
item = copy_item_for_collection(item, collection, request.user)
|
item = copy_item_for_collection(item, collection, request.user)
|
||||||
else:
|
else:
|
||||||
return redirect(collection)
|
return redirect(manage_url)
|
||||||
add_collection_section(collection, item)
|
add_collection_section(collection, item)
|
||||||
return redirect(collection)
|
return redirect(manage_url)
|
||||||
section = get_object_or_404(CollectionSection, pk=request.POST.get('section_id'), collection=collection)
|
section = get_object_or_404(CollectionSection, pk=request.POST.get('section_id'), collection=collection)
|
||||||
if action == 'remove':
|
if action == 'remove':
|
||||||
section.delete()
|
section.delete()
|
||||||
@@ -733,8 +744,8 @@ def collection_detail(request, pk):
|
|||||||
CollectionSection.objects.filter(pk=section.pk).update(position=temporary)
|
CollectionSection.objects.filter(pk=section.pk).update(position=temporary)
|
||||||
CollectionSection.objects.filter(pk=other.pk).update(position=section.position)
|
CollectionSection.objects.filter(pk=other.pk).update(position=section.position)
|
||||||
CollectionSection.objects.filter(pk=section.pk).update(position=other.position)
|
CollectionSection.objects.filter(pk=section.pk).update(position=other.position)
|
||||||
return redirect(collection)
|
return redirect(manage_url)
|
||||||
return render(request, 'core/collection_detail.html', collection_detail_context(request, collection))
|
return render(request, 'core/collection_manage.html', collection_detail_context(request, collection))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -664,6 +664,14 @@ msgstr "Sammlungen sind derzeit nur auf dem Desktop verfügbar."
|
|||||||
msgid "Edit collection"
|
msgid "Edit collection"
|
||||||
msgstr "Sammlung bearbeiten"
|
msgstr "Sammlung bearbeiten"
|
||||||
|
|
||||||
|
#: core/templates/core/collection_manage.html
|
||||||
|
msgid "View collection"
|
||||||
|
msgstr "Sammlung ansehen"
|
||||||
|
|
||||||
|
#: core/templates/core/collection_manage.html
|
||||||
|
msgid "Collection settings"
|
||||||
|
msgstr "Sammlungseinstellungen"
|
||||||
|
|
||||||
#: core/templates/core/collection_detail.html
|
#: core/templates/core/collection_detail.html
|
||||||
msgid "Delete collection? The notes remain available."
|
msgid "Delete collection? The notes remain available."
|
||||||
msgstr "Sammlung löschen? Die Notizen bleiben erhalten."
|
msgstr "Sammlung löschen? Die Notizen bleiben erhalten."
|
||||||
|
|||||||
Reference in New Issue
Block a user