diff options
-rw-r--r-- | fietsboek/routes.py | 1 | ||||
-rw-r--r-- | fietsboek/static/fietsboek.js | 52 | ||||
-rw-r--r-- | fietsboek/templates/edit.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/edit_form.jinja2 | 37 | ||||
-rw-r--r-- | fietsboek/templates/finish_upload.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/layout.jinja2 | 4 | ||||
-rw-r--r-- | fietsboek/views/edit.py | 14 | ||||
-rw-r--r-- | fietsboek/views/profile.py | 15 | ||||
-rw-r--r-- | fietsboek/views/upload.py | 15 |
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) |