Compare commits

...

3 Commits

Author SHA1 Message Date
Patrick Jentsch
f4d3415c11 First work to bring back Community Update functionality 2023-10-24 16:11:08 +02:00
Patrick Jentsch
965f2854b2 Add comments to JavaScript and some restructuring 2023-10-24 15:09:20 +02:00
Patrick Jentsch
f101a742a9 Fix broken dependency with Flask-Assets >2.0 2023-10-24 13:18:46 +02:00
14 changed files with 266 additions and 219 deletions

View File

@ -12,65 +12,65 @@ from ..decorators import corpus_follower_permission_required
from . import bp from . import bp
# @bp.route('/<hashid:corpus_id>/followers', methods=['POST']) @bp.route('/<hashid:corpus_id>/followers', methods=['POST'])
# @corpus_follower_permission_required('MANAGE_FOLLOWERS') @corpus_follower_permission_required('MANAGE_FOLLOWERS')
# @content_negotiation(consumes='application/json', produces='application/json') @content_negotiation(consumes='application/json', produces='application/json')
# def create_corpus_followers(corpus_id): def create_corpus_followers(corpus_id):
# usernames = request.json usernames = request.json
# if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)): if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)):
# abort(400) abort(400)
# corpus = Corpus.query.get_or_404(corpus_id) corpus = Corpus.query.get_or_404(corpus_id)
# for username in usernames: for username in usernames:
# user = User.query.filter_by(username=username, is_public=True).first_or_404() user = User.query.filter_by(username=username, is_public=True).first_or_404()
# user.follow_corpus(corpus) user.follow_corpus(corpus)
# db.session.commit() db.session.commit()
# response_data = { response_data = {
# 'message': f'Users are now following "{corpus.title}"', 'message': f'Users are now following "{corpus.title}"',
# 'category': 'corpus' 'category': 'corpus'
# } }
# return response_data, 200 return response_data, 200
# @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT']) @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT'])
# @corpus_follower_permission_required('MANAGE_FOLLOWERS') @corpus_follower_permission_required('MANAGE_FOLLOWERS')
# @content_negotiation(consumes='application/json', produces='application/json') @content_negotiation(consumes='application/json', produces='application/json')
# def update_corpus_follower_role(corpus_id, follower_id): def update_corpus_follower_role(corpus_id, follower_id):
# role_name = request.json role_name = request.json
# if not isinstance(role_name, str): if not isinstance(role_name, str):
# abort(400) abort(400)
# cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
# if cfr is None: if cfr is None:
# abort(400) abort(400)
# cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
# cfa.role = cfr cfa.role = cfr
# db.session.commit() db.session.commit()
# response_data = { response_data = {
# 'message': f'User "{cfa.follower.username}" is now {cfa.role.name}', 'message': f'User "{cfa.follower.username}" is now {cfa.role.name}',
# 'category': 'corpus' 'category': 'corpus'
# } }
# return response_data, 200 return response_data, 200
# @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE']) @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE'])
# def delete_corpus_follower(corpus_id, follower_id): def delete_corpus_follower(corpus_id, follower_id):
# cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
# if not ( if not (
# current_user.id == follower_id current_user.id == follower_id
# or current_user == cfa.corpus.user or current_user == cfa.corpus.user
# or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS') or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS')
# or current_user.is_administrator()): or current_user.is_administrator()):
# abort(403) abort(403)
# if current_user.id == follower_id: if current_user.id == follower_id:
# flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus') flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus')
# response = make_response() response = make_response()
# response.status_code = 204 response.status_code = 204
# else: else:
# response_data = { response_data = {
# 'message': f'"{cfa.follower.username}" is not following "{cfa.corpus.title}" anymore', 'message': f'"{cfa.follower.username}" is not following "{cfa.corpus.title}" anymore',
# 'category': 'corpus' 'category': 'corpus'
# } }
# response = jsonify(response_data) response = jsonify(response_data)
# response.status_code = 200 response.status_code = 200
# cfa.follower.unfollow_corpus(cfa.corpus) cfa.follower.unfollow_corpus(cfa.corpus)
# db.session.commit() db.session.commit()
# return response return response

View File

@ -78,15 +78,15 @@ def terms_of_use():
) )
# @bp.route('/social-area') @bp.route('/social-area')
# @register_breadcrumb(bp, '.social_area', '<i class="material-icons left">group</i>Social Area') @register_breadcrumb(bp, '.social_area', '<i class="material-icons left">group</i>Social Area')
# @login_required @login_required
# def social_area(): def social_area():
# corpora = Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() corpora = Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all()
# users = User.query.filter(User.is_public == True, User.id != current_user.id).all() users = User.query.filter(User.is_public == True, User.id != current_user.id).all()
# return render_template( return render_template(
# 'main/social_area.html.j2', 'main/social_area.html.j2',
# title='Social Area', title='Social Area',
# corpora=corpora, corpora=corpora,
# users=users users=users
# ) )

View File

@ -1,3 +1,8 @@
// IDEA: Split the App logic into seperate units
// - App.Data
// - App.IO (name is WIP)
// - App.UI
App.App = class App { App.App = class App {
constructor() { constructor() {
this.data = { this.data = {
@ -101,4 +106,94 @@ App.App = class App {
// Apply Patch // Apply Patch
jsonpatch.applyPatch(this.data, filteredPatch); jsonpatch.applyPatch(this.data, filteredPatch);
} }
init() {
this.initUi();
}
initUi() {
/* Pre-Initialization fixes */
// #region
// Flask-WTF sets the standard HTML maxlength Attribute on input/textarea
// elements to specify their maximum length (in characters). Unfortunatly
// Materialize won't recognize the maxlength Attribute, instead it uses
// the data-length Attribute. It's conversion time :)
for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) {
elem.dataset.length = elem.getAttribute('maxlength');
elem.removeAttribute('maxlength');
}
// To work around some limitations with the Form setup of Flask-WTF.
// HTML option elements with an empty value are considered as placeholder
// elements. The user should not be able to actively select these options.
// So they get the disabled attribute.
for (let optionElement of document.querySelectorAll('option[value=""]')) {
optionElement.disabled = true;
}
// TODO: Check why we are doing this.
for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) {
for (let c of optgroupElement.children) {
optgroupElement.parentElement.insertAdjacentElement('afterbegin', c);
}
optgroupElement.remove();
}
// #endregion
/* Initialize Materialize Components */
// #region
// Automatically initialize Materialize Components that do not require
// additional configuration.
M.AutoInit();
// CharacterCounters
// Materialize didn't include the CharacterCounter plugin within the
// AutoInit method (maybe they forgot it?). Anyway... We do it here. :)
M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)'));
// Header navigation "more" Dropdown.
M.Dropdown.init(
document.querySelector('#nav-more-dropdown-trigger'),
{
alignment: 'right',
constrainWidth: false,
coverTrigger: false
}
);
// Manual modal
M.Modal.init(
document.querySelector('#manual-modal'),
{
onOpenStart: (modalElement, modalTriggerElement) => {
if ('manualModalChapter' in modalTriggerElement.dataset) {
let manualModalTocElement = document.querySelector('#manual-modal-toc');
let manualModalToc = M.Tabs.getInstance(manualModalTocElement);
manualModalToc.select(modalTriggerElement.dataset.manualModalChapter);
}
}
}
);
// Terms of use modal
M.Modal.init(
document.querySelector('#terms-of-use-modal'),
{
dismissible: false,
onCloseEnd: (modalElement) => {
Requests.users.entity.acceptTermsOfUse();
}
}
);
// #endregion
// #region Nopaque Components
ResourceDisplays.AutoInit();
ResourceLists.AutoInit();
Forms.AutoInit();
// #endregion Nopaque Components
}
}; };

View File

@ -1,6 +1,6 @@
var Forms = {}; var Forms = {};
Forms.autoInit = () => { Forms.AutoInit = () => {
for (let propertyName in Forms) { for (let propertyName in Forms) {
let property = Forms[propertyName]; let property = Forms[propertyName];
// Call autoInit of all properties that are subclasses of Forms.BaseForm. // Call autoInit of all properties that are subclasses of Forms.BaseForm.

View File

@ -1,6 +1,6 @@
var ResourceDisplays = {}; var ResourceDisplays = {};
ResourceDisplays.autoInit = () => { ResourceDisplays.AutoInit = () => {
for (let propertyName in ResourceDisplays) { for (let propertyName in ResourceDisplays) {
let property = ResourceDisplays[propertyName]; let property = ResourceDisplays[propertyName];
// Call autoInit of all properties that are subclasses of `ResourceDisplays.ResourceDisplay`. // Call autoInit of all properties that are subclasses of `ResourceDisplays.ResourceDisplay`.

View File

@ -1,6 +1,6 @@
var ResourceLists = {}; var ResourceLists = {};
ResourceLists.autoInit = () => { ResourceLists.AutoInit = () => {
for (let propertyName in ResourceLists) { for (let propertyName in ResourceLists) {
let property = ResourceLists[propertyName]; let property = ResourceLists[propertyName];
// Call autoInit of all properties that are subclasses of `ResourceLists.ResourceList`. // Call autoInit of all properties that are subclasses of `ResourceLists.ResourceList`.

View File

@ -119,76 +119,29 @@
<script> <script>
// TODO: Implement an app.run method and use this for all of the following // TODO: Implement an app.run method and use this for all of the following
const app = new App.App(); const app = new App.App();
app.init();
// Check if the current user is authenticated
{%- if current_user.is_authenticated %} {%- if current_user.is_authenticated %}
// TODO: Set this as a property of the app object
const currentUserId = {{ current_user.hashid|tojson }}; const currentUserId = {{ current_user.hashid|tojson }};
// Initialize components for current user // Subscribe to the current user's data events
app.subscribeUser(currentUserId) app.subscribeUser(currentUserId)
.catch((error) => {throw JSON.stringify(error);}); .catch((error) => {throw JSON.stringify(error);});
// Get the current user's data
app.getUser(currentUserId, true, true) app.getUser(currentUserId, true, true)
.catch((error) => {throw JSON.stringify(error);}); .catch((error) => {throw JSON.stringify(error);});
// Check if the current user hasn't accepted the terms of use yet
{%- if not current_user.terms_of_use_accepted %}
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
{%- endif %}
{%- endif %} {%- endif %}
// Disable all option elements with no value
for (let optionElement of document.querySelectorAll('option[value=""]')) {
optionElement.disabled = true;
}
for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) {
for (let c of optgroupElement.children) {
optgroupElement.parentElement.insertAdjacentElement('afterbegin', c);
}
optgroupElement.remove();
}
// Set the data-length attribute on textareas/inputs with the maxlength attribute
for (let inputElement of document.querySelectorAll('textarea[maxlength], input[maxlength]')) {
inputElement.dataset.length = inputElement.getAttribute('maxlength');
}
// Initialize components
M.AutoInit();
M.CharacterCounter.init(document.querySelectorAll('input[data-length], textarea[data-length]'));
M.Dropdown.init(
document.querySelectorAll('#nav-more-dropdown-trigger'),
{alignment: 'right', constrainWidth: false, coverTrigger: false}
);
ResourceDisplays.autoInit();
ResourceLists.autoInit();
Forms.autoInit();
// Display flashed messages // Display flashed messages
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) { for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {
app.flash(message, message); app.flash(message, message);
} }
// Initialize manual modal
let manualModalTableOfContentsElement = document.querySelector('#manual-modal-table-of-contents');
let manualModalTableOfContents = M.Tabs.init(manualModalTableOfContentsElement);
let manualModalElement = document.querySelector('#manual-modal');
let manualModal = M.Modal.init(
manualModalElement,
{
onOpenStart: (manualModalElement, modalTriggerElement) => {
if ('manualModalChapter' in modalTriggerElement.dataset) {
manualModalTableOfContents.select(modalTriggerElement.dataset.manualModalChapter);
}
}
}
);
// Initialize terms of use modal
const termsOfUseModal = document.getElementById('terms-of-use-modal');
M.Modal.init(
termsOfUseModal,
{
dismissible: false,
onCloseEnd: () => {
requests.users.entity.acceptTermsOfUse();
}
}
);
{% if current_user.is_authenticated and not current_user.terms_of_use_accepted %}
termsOfUseModal.M_Modal.open();
{% endif %}
</script> </script>

View File

@ -3,13 +3,12 @@
<div class="user-view" style="padding-top: 1px; padding-left: 20px !important; padding-right: 20px !important; height: 112px;"> <div class="user-view" style="padding-top: 1px; padding-left: 20px !important; padding-right: 20px !important; height: 112px;">
<div class="background primary-color"></div> <div class="background primary-color"></div>
<div class="row"> <div class="row">
{# <div class="col s5"> <div class="col s5">
<a href="{{ url_for('users.user', user_id=current_user.id) }}"> <a href="{{ url_for('users.user', user_id=current_user.id) }}">
<img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 22px;"> <img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 22px;">
</a> </a>
</div> #} </div>
{# Change col s12 to col s5 to show user image #} <div class="col s5" style="word-wrap: break-word; margin-left:-10px;">
<div class="col s12" style="word-wrap: break-word; margin-left:-10px;">
<span class="white-text name"> <span class="white-text name">
{% if current_user.username|length > 18 %} {% if current_user.username|length > 18 %}
{{ current_user.username[:15] + '...' }} {{ current_user.username[:15] + '...' }}
@ -71,7 +70,7 @@
<li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"> <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;">
<a class="waves-effect" href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a> <a class="waves-effect" href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a>
</li> </li>
{# <li><div class="divider"></div></li> <li><div class="divider"></div></li>
<li> <li>
<a class="waves-effect" class="waves-effect" href="{{ url_for('main.social_area') }}"><i class="material-icons">rocket_launch</i>Social Area</a> <a class="waves-effect" class="waves-effect" href="{{ url_for('main.social_area') }}"><i class="material-icons">rocket_launch</i>Social Area</a>
<ul> <ul>
@ -82,7 +81,7 @@
<a class="waves-effect" href="{{ url_for('main.social_area', _anchor='public-corpora') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>Public Corpora</a> <a class="waves-effect" href="{{ url_for('main.social_area', _anchor='public-corpora') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>Public Corpora</a>
</li> </li>
</ul> </ul>
</li> #} </li>
<li class="hide-on-large-only"><div class="divider"></div></li> <li class="hide-on-large-only"><div class="divider"></div></li>
<li class="hide-on-large-only"><a class="subheader">Account</a></li> <li class="hide-on-large-only"><a class="subheader">Account</a></li>
<li class="hide-on-large-only"> <li class="hide-on-large-only">

View File

@ -65,14 +65,14 @@
<div class="col s12 l6" style="padding: 0 2.5px;"> <div class="col s12 l6" style="padding: 0 2.5px;">
<a class="action-button btn disabled waves-effect waves-light" data-action="analyze" href="{{ url_for('corpora.analysis', corpus_id=corpus.id) }}" style="width: 100%;"><i class="material-icons left">search</i>Analyze</a> <a class="action-button btn disabled waves-effect waves-light" data-action="analyze" href="{{ url_for('corpora.analysis', corpus_id=corpus.id) }}" style="width: 100%;"><i class="material-icons left">search</i>Analyze</a>
</div> </div>
{# <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;"> <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;">
<a class="btn waves-effect waves-light modal-trigger" href="#publishing-modal" style="width: 100%;"><i class="material-icons left">publish</i>Publishing</a> <a class="btn waves-effect waves-light modal-trigger" href="#publishing-modal" style="width: 100%;"><i class="material-icons left">publish</i>Publishing</a>
</div> #} </div>
<div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;"> <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;">
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-modal" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a> <a class="btn red waves-effect waves-light modal-trigger" href="#delete-modal" style="width: 100%;"><i class="material-icons left">delete</i>Delete</a>
</div> </div>
</div> </div>
{# {% if cfr.has_permission('MANAGE_FOLLOWERS') %} {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
<span class="card-title">Social</span> <span class="card-title">Social</span>
<div class="row"> <div class="row">
<div class="col s12 l6" style="padding: 0 2.5px;"> <div class="col s12 l6" style="padding: 0 2.5px;">
@ -82,7 +82,7 @@
<a class="btn waves-effect waves-light modal-trigger" href="#share-link-modal" style="width: 100%;"><i class="material-icons left">link</i>Share link</a> <a class="btn waves-effect waves-light modal-trigger" href="#share-link-modal" style="width: 100%;"><i class="material-icons left">link</i>Share link</a>
</div> </div>
</div> </div>
{% endif %} #} {% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -102,7 +102,7 @@
</div> </div>
</div> </div>
{# {% if cfr.has_permission('MANAGE_FOLLOWERS') %} {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
<div class="col s12"> <div class="col s12">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
@ -111,7 +111,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %} #} {% endif %}
</div> </div>
</div> </div>
@ -121,7 +121,7 @@
{{ super() }} {{ super() }}
{% if current_user == corpus.user or current_user.is_administrator() %} {% if current_user == corpus.user or current_user.is_administrator() %}
{# <div class="modal" id="publishing-modal"> <div class="modal" id="publishing-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Change your Corpus publishing status</h4> <h4>Change your Corpus publishing status</h4>
<p><i>Other users can only see the meta data of your corpus. The files of the corpus remain private and can only be viewed via a share link.</i></p> <p><i>Other users can only see the meta data of your corpus. The files of the corpus remain private and can only be viewed via a share link.</i></p>
@ -138,7 +138,7 @@
<div class="modal-footer"> <div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Close</a> <a class="modal-close waves-effect waves-green btn-flat">Close</a>
</div> </div>
</div> #} </div>
<div class="modal" id="delete-modal"> <div class="modal" id="delete-modal">
<div class="modal-content"> <div class="modal-content">
@ -152,7 +152,7 @@
</div> </div>
{% endif %} {% endif %}
{# {% if cfr.has_permission('MANAGE_FOLLOWERS') %} {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
<div class="modal no-autoinit" id="invite-user-modal"> <div class="modal no-autoinit" id="invite-user-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Invite a nopaque user by username</h4> <h4>Invite a nopaque user by username</h4>
@ -230,14 +230,14 @@
<a class="modal-close waves-effect waves-green btn-flat">Close</a> <a class="modal-close waves-effect waves-green btn-flat">Close</a>
</div> </div>
</div> </div>
{% endif %} #} {% endif %}
{% endblock modals %} {% endblock modals %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script>
{# {% if current_user.is_following_corpus(corpus) %} {% if current_user.is_following_corpus(corpus) %}
let unfollowRequestElement = document.querySelector('.action-button[data-action="unfollow-request"]'); let unfollowRequestElement = document.querySelector('.action-button[data-action="unfollow-request"]');
unfollowRequestElement.addEventListener('click', () => { unfollowRequestElement.addEventListener('click', () => {
requests.corpora.entity.followers.entity.delete({{ corpus.hashid|tojson }}, {{ current_user.hashid|tojson }}) requests.corpora.entity.followers.entity.delete({{ corpus.hashid|tojson }}, {{ current_user.hashid|tojson }})
@ -245,18 +245,18 @@
window.location.reload(); window.location.reload();
}); });
}); });
{% endif %} #} {% endif %}
{% if current_user == corpus.user or current_user.is_administrator() %} {% if current_user == corpus.user or current_user.is_administrator() %}
// #region Publishing // #region Publishing
{# let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch'); let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch');
publishingModalIsPublicSwitchElement.addEventListener('change', (event) => { publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
let newIsPublic = publishingModalIsPublicSwitchElement.checked; let newIsPublic = publishingModalIsPublicSwitchElement.checked;
requests.corpora.entity.isPublic.update({{ corpus.hashid|tojson }}, newIsPublic) requests.corpora.entity.isPublic.update({{ corpus.hashid|tojson }}, newIsPublic)
.catch((response) => { .catch((response) => {
publishingModalIsPublicSwitchElement.checked = !newIsPublic; publishingModalIsPublicSwitchElement.checked = !newIsPublic;
}); });
}); #} });
// #endregion Publishing // #endregion Publishing
// #region Delete // #region Delete
@ -272,7 +272,7 @@ deleteModalDeleteButtonElement.addEventListener('click', (event) => {
{% if cfr.has_permission('MANAGE_FOLLOWERS') %} {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
// #region Invite user // #region Invite user
{# let inviteUserModalElement = document.querySelector('#invite-user-modal'); let inviteUserModalElement = document.querySelector('#invite-user-modal');
let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search'); let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search');
let inviteUserModalInviteButtonElement = document.querySelector('#invite-user-modal-invite-button'); let inviteUserModalInviteButtonElement = document.querySelector('#invite-user-modal-invite-button');
let users = { let users = {
@ -374,7 +374,7 @@ shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
() => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');} () => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');}
); );
}); #} });
// #endregion Share link // #endregion Share link
{% endif %} {% endif %}

View File

@ -1,7 +1,7 @@
<div id="manual-modal" class="modal no-autoinit"> <div id="manual-modal" class="modal no-autoinit">
<div class="modal-content"> <div class="modal-content">
<h2>Manual</h2> <h2>Manual</h2>
<ul class="tabs no-autoinit" id="manual-modal-table-of-contents"> <ul class="tabs" id="manual-modal-toc">
<li class="tab"><a href="#manual-modal-introduction">Introduction</a></li> <li class="tab"><a href="#manual-modal-introduction">Introduction</a></li>
<li class="tab"><a href="#manual-modal-registration-and-log-in">Registration and Log in</a></li> <li class="tab"><a href="#manual-modal-registration-and-log-in">Registration and Log in</a></li>
<li class="tab"><a href="#manual-modal-dashboard">Dashboard</a></li> <li class="tab"><a href="#manual-modal-dashboard">Dashboard</a></li>

View File

@ -8,7 +8,7 @@
<h1 id="title">{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
{# <div class="col s12 l4"> <div class="col s12 l4">
<h4>Profile Settings</h4> <h4>Profile Settings</h4>
<p>You can edit your public profile here and share it with other nopaque users. <p>You can edit your public profile here and share it with other nopaque users.
Tell others about your (scientific) background so they can relate and network with you. Tell others about your (scientific) background so they can relate and network with you.
@ -112,7 +112,7 @@
</ul> </ul>
</div> </div>
<div class="col s12"></div> #} <div class="col s12"></div>
<div class="col s12 l4"> <div class="col s12 l4">
<h4>General Settings</h4> <h4>General Settings</h4>
@ -181,7 +181,7 @@
{% block modals %} {% block modals %}
{{ super() }} {{ super() }}
{# <div class="modal" id="delete-avatar-modal"> <div class="modal" id="delete-avatar-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Confirm Avatar deletion</h4> <h4>Confirm Avatar deletion</h4>
<p>Do you really want to delete <b>{{ user.username }}</b>s avatar?</p> <p>Do you really want to delete <b>{{ user.username }}</b>s avatar?</p>
@ -190,7 +190,7 @@
<a class="btn modal-close waves-effect waves-light">Cancel</a> <a class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a> <a class="btn modal-close red waves-effect waves-light" id="delete-avatar">Delete</a>
</div> </div>
</div> #} </div>
<div class="modal" id="delete-user-modal"> <div class="modal" id="delete-user-modal">
<div class="modal-content"> <div class="modal-content">
@ -212,7 +212,7 @@
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script>
{# let avatarPreviewElement = document.querySelector('#update-avatar-form-avatar-preview'); let avatarPreviewElement = document.querySelector('#update-avatar-form-avatar-preview');
let avatarUploadElement = document.querySelector('#update-avatar-form-avatar'); let avatarUploadElement = document.querySelector('#update-avatar-form-avatar');
avatarUploadElement.addEventListener('change', () => { avatarUploadElement.addEventListener('change', () => {
@ -221,16 +221,16 @@ avatarUploadElement.addEventListener('change', () => {
}); });
document.querySelector('#delete-avatar').addEventListener('click', () => { document.querySelector('#delete-avatar').addEventListener('click', () => {
requests.users.entity.avatar.delete({{ user.hashid|tojson }}) Requests.users.entity.avatar.delete({{ user.hashid|tojson }})
.then( .then(
(response) => { (response) => {
avatarPreviewElement.src = {{ url_for('static', filename='images/user_avatar.png')|tojson }}; avatarPreviewElement.src = {{ url_for('static', filename='images/user_avatar.png')|tojson }};
} }
); );
}); #} });
document.querySelector('#delete-user').addEventListener('click', (event) => { document.querySelector('#delete-user').addEventListener('click', (event) => {
requests.users.entity.delete({{ user.hashid|tojson }}) Requests.users.entity.delete({{ user.hashid|tojson }})
.then((response) => {window.location.href = '/';}); .then((response) => {window.location.href = '/';});
}); });
@ -251,11 +251,11 @@ for (let collapsibleElement of document.querySelectorAll('.collapsible.no-autoin
} }
// #region Profile Privacy settings // #region Profile Privacy settings
{# let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch'); let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch');
let profilePrivacySettingCheckboxElements = document.querySelectorAll('.profile-privacy-setting-checkbox'); let profilePrivacySettingCheckboxElements = document.querySelectorAll('.profile-privacy-setting-checkbox');
profileIsPublicSwitchElement.addEventListener('change', (event) => { profileIsPublicSwitchElement.addEventListener('change', (event) => {
let newEnabled = profileIsPublicSwitchElement.checked; let newEnabled = profileIsPublicSwitchElement.checked;
requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, 'is-public', newEnabled) Requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, 'is-public', newEnabled)
.then( .then(
(response) => { (response) => {
for (let profilePrivacySettingCheckboxElement of document.querySelectorAll('.profile-privacy-setting-checkbox')) { for (let profilePrivacySettingCheckboxElement of document.querySelectorAll('.profile-privacy-setting-checkbox')) {
@ -271,12 +271,12 @@ for (let profilePrivacySettingCheckboxElement of profilePrivacySettingCheckboxEl
profilePrivacySettingCheckboxElement.addEventListener('change', (event) => { profilePrivacySettingCheckboxElement.addEventListener('change', (event) => {
let newEnabled = profilePrivacySettingCheckboxElement.checked; let newEnabled = profilePrivacySettingCheckboxElement.checked;
let valueName = profilePrivacySettingCheckboxElement.dataset.profilePrivacySettingName; let valueName = profilePrivacySettingCheckboxElement.dataset.profilePrivacySettingName;
requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, valueName, newEnabled) Requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, valueName, newEnabled)
.catch((response) => { .catch((response) => {
profilePrivacySettingCheckboxElement.checked = !newEnabled; profilePrivacySettingCheckboxElement.checked = !newEnabled;
}); });
}); });
} #} }
// #endregion Profile Privacy settings // #endregion Profile Privacy settings
</script> </script>
{% endblock scripts %} {% endblock scripts %}

View File

@ -32,29 +32,29 @@ def delete_user(user_id):
return response_data, 202 return response_data, 202
# @bp.route('/<hashid:user_id>/avatar', methods=['DELETE']) @bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
# @content_negotiation(produces='application/json') @content_negotiation(produces='application/json')
# def delete_user_avatar(user_id): def delete_user_avatar(user_id):
# def _delete_avatar(app, avatar_id): def _delete_avatar(app, avatar_id):
# with app.app_context(): with app.app_context():
# avatar = Avatar.query.get(avatar_id) avatar = Avatar.query.get(avatar_id)
# avatar.delete() avatar.delete()
# db.session.commit() db.session.commit()
# user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
# if user.avatar is None: if user.avatar is None:
# abort(404) abort(404)
# if not (user == current_user or current_user.is_administrator()): if not (user == current_user or current_user.is_administrator()):
# abort(403) abort(403)
# thread = Thread( thread = Thread(
# target=_delete_avatar, target=_delete_avatar,
# args=(current_app._get_current_object(), user.avatar.id) args=(current_app._get_current_object(), user.avatar.id)
# ) )
# thread.start() thread.start()
# response_data = { response_data = {
# 'message': f'Avatar marked for deletion' 'message': f'Avatar marked for deletion'
# } }
# return response_data, 202 return response_data, 202
@bp.route('/accept-terms-of-use', methods=['POST']) @bp.route('/accept-terms-of-use', methods=['POST'])
@content_negotiation(produces='application/json') @content_negotiation(produces='application/json')

View File

@ -13,36 +13,36 @@ from . import bp
from .utils import user_dynamic_list_constructor as user_dlc from .utils import user_dynamic_list_constructor as user_dlc
# @bp.route('') @bp.route('')
# @register_breadcrumb(bp, '.', '<i class="material-icons left">group</i>Users') @register_breadcrumb(bp, '.', '<i class="material-icons left">group</i>Users')
# def users(): def users():
# return redirect(url_for('main.social_area', _anchor='users')) return redirect(url_for('main.social_area', _anchor='users'))
# @bp.route('/<hashid:user_id>') @bp.route('/<hashid:user_id>')
# @register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=user_dlc) @register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=user_dlc)
# def user(user_id): def user(user_id):
# user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
# if not (user.is_public or user == current_user or current_user.is_administrator()): if not (user.is_public or user == current_user or current_user.is_administrator()):
# abort(403) abort(403)
# return render_template( return render_template(
# 'users/user.html.j2', 'users/user.html.j2',
# title=user.username, title=user.username,
# user=user user=user
# ) )
# @bp.route('/<hashid:user_id>/avatar') @bp.route('/<hashid:user_id>/avatar')
# def user_avatar(user_id): def user_avatar(user_id):
# user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
# if not (user.is_public or user == current_user or current_user.is_administrator()): if not (user.is_public or user == current_user or current_user.is_administrator()):
# abort(403) abort(403)
# if user.avatar is None: if user.avatar is None:
# return redirect(url_for('static', filename='images/user_avatar.png')) return redirect(url_for('static', filename='images/user_avatar.png'))
# return send_from_directory( return send_from_directory(
# os.path.dirname(user.avatar.path), os.path.dirname(user.avatar.path),
# os.path.basename(user.avatar.path), os.path.basename(user.avatar.path),
# as_attachment=True, as_attachment=True,
# attachment_filename=user.avatar.filename, attachment_filename=user.avatar.filename,
# mimetype=user.avatar.mimetype mimetype=user.avatar.mimetype
# ) )

View File

@ -5,7 +5,7 @@ docker
eventlet eventlet
Flask==2.1.3 Flask==2.1.3
Flask-APScheduler Flask-APScheduler
Flask-Assets Flask-Assets==2.0
Flask-Breadcrumbs Flask-Breadcrumbs
Flask-Hashids>=1.0.1 Flask-Hashids>=1.0.1
Flask-HTTPAuth Flask-HTTPAuth