aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/routes.py1
-rw-r--r--fietsboek/static/fietsboek.js52
-rw-r--r--fietsboek/templates/edit.jinja22
-rw-r--r--fietsboek/templates/edit_form.jinja237
-rw-r--r--fietsboek/templates/finish_upload.jinja22
-rw-r--r--fietsboek/templates/layout.jinja24
-rw-r--r--fietsboek/views/edit.py14
-rw-r--r--fietsboek/views/profile.py15
-rw-r--r--fietsboek/views/upload.py15
9 files changed, 134 insertions, 8 deletions
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index 018c1d3..fb8536e 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -33,3 +33,4 @@ def includeme(config):
config.add_route('add-friend', '/me/send-friend-request')
config.add_route('delete-friend', '/me/delete-friend')
config.add_route('accept-friend', '/me/accept-friend')
+ config.add_route('json-friends', '/me/friends.json')
diff --git a/fietsboek/static/fietsboek.js b/fietsboek/static/fietsboek.js
index 5a26cbc..f21f0b5 100644
--- a/fietsboek/static/fietsboek.js
+++ b/fietsboek/static/fietsboek.js
@@ -47,10 +47,48 @@ function checkNameValidity(name) {
}
}
+function searchFriends() {
+ let searchPattern = document.querySelector("#friendSearchQuery").value.toLowerCase();
+ let friendSearch = document.querySelector("#friendSearch");
+ friendSearch.innerHTML = "";
+ fetch(FRIENDS_URL)
+ .then((response) => response.json())
+ .then((response) => {
+ let blueprint = document.querySelector("#friendSearchBlueprint");
+
+ // Only show friends with a matching name
+ let friends = response.filter((obj) => obj.name.toLowerCase().indexOf(searchPattern) != -1);
+
+ friends.forEach((friend) => {
+ let copy = blueprint.cloneNode(true);
+ copy.removeAttribute("id");
+ copy.querySelector(".friend-name").textContent = friend.name;
+ copy.querySelector("button").addEventListener("click", (event) => {
+ let button = event.target.closest("button");
+ button.parentNode.parentNode.removeChild(button.parentNode);
+
+ let added = document.querySelector("#friendAddedBlueprint").cloneNode(true);
+ added.removeAttribute("id");
+ added.querySelector(".friend-name").textContent = friend.name
+ added.querySelector("input").value = friend.id;
+ added.querySelector("input").removeAttribute("disabled");
+ added.querySelector("button").addEventListener("click", removeFriendClicked);
+ document.querySelector('#taggedFriends').appendChild(added);
+ });
+ friendSearch.appendChild(copy);
+ });
+ });
+}
+
+function removeFriendClicked(event) {
+ let button = event.target.closest("button");
+ button.parentNode.parentNode.removeChild(button.parentNode);
+}
+
document.addEventListener('DOMContentLoaded', function(event) {
- /* Enable the "Add tag" button */
+ /* Enable the "Add tag" button in the track edit page */
let $ = (selector) => document.querySelector(selector);
- let button = $("#add-tag-btn");
+ var button = $("#add-tag-btn");
if (button) {
button.addEventListener('click', function() {
let node = document.createElement("span");
@@ -71,8 +109,16 @@ document.addEventListener('DOMContentLoaded', function(event) {
$("#formTags").appendChild(space);
$("#new-tag").value = "";
updateTagList();
- })
+ });
+ }
+
+ /* Enable the "Add friend" button in the track edit page */
+ var button = $("#add-friend-btn");
+ if (button) {
+ button.addEventListener('click', () => searchFriends());
}
+ /* Also enable any "Remove friend" buttons */
+ document.querySelectorAll(".remove-friend-button").forEach((t) => t.addEventListener("click", removeFriendClicked));
/* Enable clicking on a tag to remove it */
document.querySelectorAll(".tag-badge").forEach((t) => t.addEventListener("click", tagClicked));
diff --git a/fietsboek/templates/edit.jinja2 b/fietsboek/templates/edit.jinja2
index 33d18f5..b0f9849 100644
--- a/fietsboek/templates/edit.jinja2
+++ b/fietsboek/templates/edit.jinja2
@@ -9,7 +9,7 @@
<noscript><p>{{ _("page.noscript") }}<p></noscript>
</div>
<form method="POST">
- {{ edit_form.edit_track(track.title, track.date, track.visibility, track.description, track.text_tags(), badges) }}
+ {{ edit_form.edit_track(track.title, track.date, track.visibility, track.description, track.text_tags(), badges, track.tagged_people) }}
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> {{ _("page.edit.form.submit") }}</button>
<a href="{{ request.route_url('details', id=track.id) }}" class="btn btn-secondary"><i class="bi bi-x-circle"></i> {{ _("page.edit.form.cancel") }}</a>
diff --git a/fietsboek/templates/edit_form.jinja2 b/fietsboek/templates/edit_form.jinja2
index 2406c86..6c3cd25 100644
--- a/fietsboek/templates/edit_form.jinja2
+++ b/fietsboek/templates/edit_form.jinja2
@@ -1,4 +1,4 @@
-{% macro edit_track(title, date, visibility, description, tags, badges) %}
+{% macro edit_track(title, date, visibility, description, tags, badges, friends) %}
<div class="mb-3">
<label for="formTitle" class="form-label">{{ _("page.track.form.title") }}</label>
<input class="form-control" type="text" id="formTitle" name="title" value="{{ title | default("", true) }}">
@@ -37,6 +37,41 @@
<input type="hidden" name="tags" id="formTagsInput">
</div>
<div class="mb-3">
+ <div>{{ _("page.track.form.tagged_people") }}</div>
+ <ul class="list-group" id="taggedFriends">
+ {% for friend in friends %}
+ <li class="list-group-item">
+ <button type="button" class="btn btn-danger remove-friend-button"><i class="bi bi-x-square-fill"></i></button>
+ <input name="tagged-friend[]" value="{{ friend.id }}" type="hidden">
+ <span class="friend-name">{{ friend.name }}</span>
+ </li>
+ {% endfor %}
+ </ul>
+ <div class="row my-2">
+ <div class="col-lg-3">
+ <input type="text" id="friendSearchQuery" class="form-control">
+ </div>
+ <div class="col-lg-3">
+ <button type="button" class="btn btn-primary" id="add-friend-btn"><i class="bi bi-search"></i> {{ _("page.track.form.add_friend") }}</buttton>
+ </div>
+ </div>
+ <!-- Hidden templates for Javascript to use -->
+ <div style="display: none;">
+ <li class="list-group-item" id="friendSearchBlueprint">
+ <button type="button" class="btn btn-success"><i class="bi bi-plus-square-fill"></i></button>
+ <span class="friend-name"></span>
+ </li>
+ <li class="list-group-item" id="friendAddedBlueprint">
+ <button type="button" class="btn btn-danger remove-friend-button"><i class="bi bi-x-square-fill"></i></button>
+ <input name="tagged-friend[]" type="hidden" disabled>
+ <span class="friend-name"></span>
+ </li>
+ </div>
+ <!-- End of templates -->
+ <ul class="list-group" id="friendSearch">
+ </ul>
+</div>
+<div class="mb-3">
<div>{{ _("page.track.form.badges") }}</div>
{% for (state, badge) in badges %}
<div class="form-check">
diff --git a/fietsboek/templates/finish_upload.jinja2 b/fietsboek/templates/finish_upload.jinja2
index 2ec7dec..e6072f4 100644
--- a/fietsboek/templates/finish_upload.jinja2
+++ b/fietsboek/templates/finish_upload.jinja2
@@ -9,7 +9,7 @@
<noscript><p>{{ _("page.noscript") }}<p></noscript>
</div>
<form method="POST">
- {{ edit_form.edit_track(upload_title, upload_date, upload_visibility, upload_description, upload_tags, badges) }}
+ {{ edit_form.edit_track(upload_title, upload_date, upload_visibility, upload_description, upload_tags, badges, upload_tagged_people) }}
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary">{{ _("page.upload.form.submit") }}</button>
<a href="{{ request.route_url('cancel-upload', id=preview_id) }}" class="btn btn-danger">{{ _("page.upload.form.cancel") }}</a>
diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2
index 4bbb9c0..c60723f 100644
--- a/fietsboek/templates/layout.jinja2
+++ b/fietsboek/templates/layout.jinja2
@@ -24,6 +24,10 @@
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('fietsboek:static/theme.css')}}" rel="stylesheet">
+ <script>
+var FRIENDS_URL = "{{ request.route_url('json-friends') }}";
+ </script>
+
</head>
<body>
diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py
index 1f739d5..21aeeb2 100644
--- a/fietsboek/views/edit.py
+++ b/fietsboek/views/edit.py
@@ -2,7 +2,7 @@
import datetime
from pyramid.view import view_config
-from pyramid.httpexceptions import HTTPForbidden, HTTPFound
+from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPBadRequest
from sqlalchemy import select
@@ -46,9 +46,21 @@ def do_edit(request):
if request.identity != track.owner:
return HTTPForbidden()
+ user_friends = request.identity.get_friends()
badges = request.dbsession.execute(select(models.Badge)).scalars()
active_badges = util.parse_badges(badges, request.params)
+ tagged_people = request.params.getall("tagged-friend[]")
+ new_tagged_people = []
+ for friend_id in tagged_people:
+ user = request.dbsession.execute(
+ select(models.User).filter_by(id=friend_id)).scalar_one_or_none()
+ if user is None:
+ return HTTPBadRequest()
+ if user in track.tagged_people or user in user_friends:
+ new_tagged_people.append(user)
+ track.tagged_people = new_tagged_people
+
track.title = request.params["title"]
track.visibility = Visibility[request.params["visibility"]]
track.description = request.params["description"]
diff --git a/fietsboek/views/profile.py b/fietsboek/views/profile.py
index c14b138..7085c56 100644
--- a/fietsboek/views/profile.py
+++ b/fietsboek/views/profile.py
@@ -143,3 +143,18 @@ def do_accept_friend(request):
friend_request.sender.add_friend(friend_request.recipient)
request.dbsession.delete(friend_request)
return HTTPFound(request.route_url('profile'))
+
+
+@view_config(route_name='json-friends', renderer='json', permission='user')
+def json_friends(request):
+ """Returns a JSON-ified list of the user's friends.
+
+ :param request: The Pyramid request.
+ :type request: pyramid.request.Request
+ :return: The HTTP response.
+ :rtype: pyramid.response.Response
+ """
+ friends = [
+ {'name': friend.name, 'id': friend.id} for friend in request.identity.get_friends()
+ ]
+ return friends
diff --git a/fietsboek/views/upload.py b/fietsboek/views/upload.py
index 74b8cb5..567c9ba 100644
--- a/fietsboek/views/upload.py
+++ b/fietsboek/views/upload.py
@@ -2,7 +2,7 @@
import datetime
import logging
-from pyramid.httpexceptions import HTTPFound, HTTPForbidden
+from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.i18n import TranslationString as _
@@ -118,6 +118,7 @@ def finish_upload(request):
'upload_visibility': Visibility.PRIVATE,
'upload_description': gpx.description,
'upload_tags': set(),
+ 'upload_tagged_people': [],
'badges': badges,
}
@@ -136,9 +137,20 @@ def do_finish_upload(request):
if upload.owner != request.identity:
return HTTPForbidden()
+ user_friends = request.identity.get_friends()
badges = request.dbsession.execute(select(models.Badge)).scalars()
active_badges = util.parse_badges(badges, request.params)
+ tagged_people = request.params.getall("tagged-friend[]")
+ new_tagged_people = []
+ for friend_id in tagged_people:
+ user = request.dbsession.execute(
+ select(models.User).filter_by(id=friend_id)).scalar_one_or_none()
+ if user is None:
+ return HTTPBadRequest()
+ if user in user_friends:
+ new_tagged_people.append(user)
+
track = models.Track(
owner=request.identity,
title=request.params["title"],
@@ -147,6 +159,7 @@ def do_finish_upload(request):
description=request.params["description"],
badges=active_badges,
link_secret=util.random_alphanum_string(),
+ tagged_people=new_tagged_people,
)
tags = set(filter(bool, request.params["tags"].split(" ")))
track.sync_tags(tags)