mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
CorpusFile selection+restore public_corpus page
This commit is contained in:
parent
1c47d2a346
commit
0cf955bd2f
@ -54,6 +54,7 @@ def corpus(corpus_id):
|
|||||||
# TODO: Better solution for filtering admin
|
# TODO: Better solution for filtering admin
|
||||||
users = User.query.filter(User.is_public == True, User.id != current_user.id, User.id != corpus.user.id, User.role_id < 4).all()
|
users = User.query.filter(User.is_public == True, User.id != current_user.id, User.id != corpus.user.id, User.role_id < 4).all()
|
||||||
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||||
|
cfas = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id).all()
|
||||||
if cfa is None:
|
if cfa is None:
|
||||||
if corpus.user == current_user or current_user.is_administrator():
|
if corpus.user == current_user or current_user.is_administrator():
|
||||||
cfr = CorpusFollowerRole.query.filter_by(name='Administrator').first()
|
cfr = CorpusFollowerRole.query.filter_by(name='Administrator').first()
|
||||||
@ -61,14 +62,29 @@ def corpus(corpus_id):
|
|||||||
cfr = CorpusFollowerRole.query.filter_by(name='Anonymous').first()
|
cfr = CorpusFollowerRole.query.filter_by(name='Anonymous').first()
|
||||||
else:
|
else:
|
||||||
cfr = cfa.role
|
cfr = cfa.role
|
||||||
return render_template(
|
if corpus.user == current_user or current_user.is_administrator():
|
||||||
'corpora/corpus.html.j2',
|
return render_template(
|
||||||
title=corpus.title,
|
'corpora/corpus.html.j2',
|
||||||
corpus=corpus,
|
title=corpus.title,
|
||||||
cfrs=cfrs,
|
corpus=corpus,
|
||||||
cfr=cfr,
|
cfr=cfr,
|
||||||
users = users
|
cfrs=cfrs,
|
||||||
)
|
users = users
|
||||||
|
)
|
||||||
|
if (current_user.is_following_corpus(corpus) or corpus.is_public):
|
||||||
|
cfas = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id).all()
|
||||||
|
return render_template(
|
||||||
|
'corpora/public_corpus.html.j2',
|
||||||
|
title=corpus.title,
|
||||||
|
corpus=corpus,
|
||||||
|
cfrs=cfrs,
|
||||||
|
cfr=cfr,
|
||||||
|
cfas=cfas,
|
||||||
|
cfa=cfa,
|
||||||
|
users = users
|
||||||
|
)
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/analysis')
|
@bp.route('/<hashid:corpus_id>/analysis')
|
||||||
|
@ -8,7 +8,11 @@ class CorpusFileList extends ResourceList {
|
|||||||
constructor(listContainerElement, options = {}) {
|
constructor(listContainerElement, options = {}) {
|
||||||
super(listContainerElement, options);
|
super(listContainerElement, options);
|
||||||
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
|
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
|
||||||
|
document.querySelectorAll('.selection-action-trigger[data-selection-action]').forEach((element) => {
|
||||||
|
element.addEventListener('click', (event) => {this.onSelectionAction(event)});
|
||||||
|
});
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
this.selectedItemIds = [];
|
||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
this.corpusId = listContainerElement.dataset.corpusId;
|
this.corpusId = listContainerElement.dataset.corpusId;
|
||||||
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
|
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
|
||||||
@ -29,6 +33,12 @@ class CorpusFileList extends ResourceList {
|
|||||||
return (values) => {
|
return (values) => {
|
||||||
return `
|
return `
|
||||||
<tr class="list-item">
|
<tr class="list-item">
|
||||||
|
<td>
|
||||||
|
<label class="list-action-trigger ${this.hasPermissionView ? '' : 'hide'}" data-list-action="select">
|
||||||
|
<input type="checkbox">
|
||||||
|
<span class="disable-on-click"></span>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
<td><span class="filename"></span></td>
|
<td><span class="filename"></span></td>
|
||||||
<td><span class="author"></span></td>
|
<td><span class="author"></span></td>
|
||||||
<td><span class="title"></span></td>
|
<td><span class="title"></span></td>
|
||||||
@ -68,11 +78,20 @@ class CorpusFileList extends ResourceList {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>
|
||||||
|
<label class="selection-action-trigger ${this.listContainerElement.dataset?.hasPermissionView == 'true' ? '' : 'hide'}" data-selection-action="select-all">
|
||||||
|
<input type="checkbox">
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
<th>Filename</th>
|
<th>Filename</th>
|
||||||
<th>Author</th>
|
<th>Author</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Publishing year</th>
|
<th>Publishing year</th>
|
||||||
<th></th>
|
<th class="right-align">
|
||||||
|
<a class="selection-action-trigger btn-floating red waves-effect waves-light hide" data-selection-action="delete"><i class="material-icons">delete</i></a>
|
||||||
|
<a class="selection-action-trigger btn-floating service-color darken waves-effect waves-light hide" data-selection-action="download" data-service="corpus-analysis"><i class="material-icons">file_download</i></a>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="list"></tbody>
|
<tbody class="list"></tbody>
|
||||||
@ -97,11 +116,12 @@ class CorpusFileList extends ResourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClick(event) {
|
onClick(event) {
|
||||||
|
if (event.target.closest('.disable-on-click') !== null) {return;}
|
||||||
let listItemElement = event.target.closest('.list-item[data-id]');
|
let listItemElement = event.target.closest('.list-item[data-id]');
|
||||||
if (listItemElement === null) {return;}
|
if (listItemElement === null) {return;}
|
||||||
let itemId = listItemElement.dataset.id;
|
let itemId = listItemElement.dataset.id;
|
||||||
let listActionElement = event.target.closest('.list-action-trigger[data-list-action]');
|
let listActionElement = event.target.closest('.list-action-trigger[data-list-action]');
|
||||||
let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
|
let listAction = listActionElement === null ? '' : listActionElement.dataset.listAction;
|
||||||
switch (listAction) {
|
switch (listAction) {
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
let values = this.listjs.get('id', itemId)[0].values();
|
let values = this.listjs.get('id', itemId)[0].values();
|
||||||
@ -145,12 +165,156 @@ class CorpusFileList extends ResourceList {
|
|||||||
window.location.href = `/corpora/${this.corpusId}/files/${itemId}`;
|
window.location.href = `/corpora/${this.corpusId}/files/${itemId}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'select': {
|
||||||
|
if (event.target.checked) {
|
||||||
|
this.selectedItemIds.push(itemId);
|
||||||
|
} else {
|
||||||
|
let index = this.selectedItemIds.indexOf(itemId);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedItemIds.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.renderingItemSelection();
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectionAction(event) {
|
||||||
|
let selectionActionElement = event.target.closest('.selection-action-trigger[data-selection-action]');
|
||||||
|
let selectionAction = selectionActionElement.dataset.selectionAction;
|
||||||
|
let items = this.listjs.items;
|
||||||
|
let selectableItems = Array.from(items)
|
||||||
|
.filter(item => item.elm)
|
||||||
|
.map(item => item.elm.querySelector('input[type="checkbox"]'));
|
||||||
|
|
||||||
|
switch (selectionAction) {
|
||||||
|
case 'select-all': {
|
||||||
|
let selectedIds = Array.from(items)
|
||||||
|
.map(item => item.values().id);
|
||||||
|
if (event.target.checked) {
|
||||||
|
selectableItems.forEach(selectableItem => selectableItem.checked = true);
|
||||||
|
this.selectedItemIds = selectedIds;
|
||||||
|
} else {
|
||||||
|
selectableItems.forEach(checkbox => checkbox.checked = false);
|
||||||
|
this.selectedItemIds = this.selectedItemIds.filter(id => !selectedIds.includes(id));
|
||||||
|
}
|
||||||
|
this.renderingItemSelection();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
let modalElement = Utils.HTMLToElement(
|
||||||
|
`
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Confirm Corpus File deletion</h4>
|
||||||
|
<p>Do you really want to delete the Corpus Files?</p>
|
||||||
|
<ul id="selected-items-list"></ul>
|
||||||
|
<p>All files will be permanently deleted!</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
|
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
document.querySelector('#modals').appendChild(modalElement);
|
||||||
|
let itemList = document.querySelector('#selected-items-list');
|
||||||
|
this.selectedItemIds.forEach(selectedItemId => {
|
||||||
|
let listItem = this.listjs.get('id', selectedItemId)[0].elm;
|
||||||
|
let values = this.listjs.get('id', listItem.dataset.id)[0].values();
|
||||||
|
let itemElement = Utils.HTMLToElement(`<li> - ${values.title}</li>`);
|
||||||
|
itemList.appendChild(itemElement);
|
||||||
|
});
|
||||||
|
let modal = M.Modal.init(
|
||||||
|
modalElement,
|
||||||
|
{
|
||||||
|
dismissible: false,
|
||||||
|
onCloseEnd: () => {
|
||||||
|
modal.destroy();
|
||||||
|
modalElement.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
|
||||||
|
confirmElement.addEventListener('click', (event) => {
|
||||||
|
this.selectedItemIds.forEach(selectedItemId => {
|
||||||
|
Requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId);
|
||||||
|
});
|
||||||
|
this.selectedItemIds = [];
|
||||||
|
this.renderingItemSelection();
|
||||||
|
});
|
||||||
|
modal.open();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'download': {
|
||||||
|
this.selectedItemIds.forEach(selectedItemId => {
|
||||||
|
let downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = `/corpora/${this.corpusId}/files/${selectedItemId}/download`;
|
||||||
|
downloadLink.download = '';
|
||||||
|
downloadLink.click();
|
||||||
|
});
|
||||||
|
selectableItems.forEach(checkbox => checkbox.checked = false);
|
||||||
|
this.selectedItemIds = [];
|
||||||
|
this.renderingItemSelection();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderingItemSelection() {
|
||||||
|
let selectionActionButtons;
|
||||||
|
if (this.hasPermissionManageFiles) {
|
||||||
|
selectionActionButtons = document.querySelectorAll('.selection-action-trigger:not([data-selection-action="select-all"])');
|
||||||
|
} else if (this.hasPermissionView) {
|
||||||
|
selectionActionButtons = document.querySelectorAll('.selection-action-trigger:not([data-selection-action="select-all"]):not([data-selection-action="delete"])');
|
||||||
|
}
|
||||||
|
let selectableItems = this.listjs.items;
|
||||||
|
let actionButtons = [];
|
||||||
|
|
||||||
|
Object.values(selectableItems).forEach(selectableItem => {
|
||||||
|
if (selectableItem.elm) {
|
||||||
|
let checkbox = selectableItem.elm.querySelector('input[type="checkbox"]');
|
||||||
|
if (checkbox.checked) {
|
||||||
|
selectableItem.elm.classList.add('grey', 'lighten-3');
|
||||||
|
} else {
|
||||||
|
selectableItem.elm.classList.remove('grey', 'lighten-3');
|
||||||
|
}
|
||||||
|
let itemActionButtons = [];
|
||||||
|
if (this.hasPermissionManageFiles) {
|
||||||
|
itemActionButtons = selectableItem.elm.querySelectorAll('.list-action-trigger:not([data-list-action="select"])');
|
||||||
|
} else if (this.hasPermissionView) {
|
||||||
|
itemActionButtons = selectableItem.elm.querySelectorAll('.list-action-trigger:not([data-list-action="select"]):not([data-list-action="delete"]):not([data-list-action="view"])');
|
||||||
|
}
|
||||||
|
itemActionButtons.forEach(itemActionButton => {
|
||||||
|
actionButtons.push(itemActionButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Hide item action buttons if > 0 item is selected and show selection action buttons
|
||||||
|
if (this.selectedItemIds.length > 0) {
|
||||||
|
selectionActionButtons.forEach(selectionActionButton => {
|
||||||
|
selectionActionButton.classList.remove('hide');
|
||||||
|
});
|
||||||
|
actionButtons.forEach(actionButton => {
|
||||||
|
actionButton.classList.add('hide');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selectionActionButtons.forEach(selectionActionButton => {
|
||||||
|
selectionActionButton.classList.add('hide');
|
||||||
|
});
|
||||||
|
actionButtons.forEach(actionButton => {
|
||||||
|
actionButton.classList.remove('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPatch(patch) {
|
onPatch(patch) {
|
||||||
let re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)`);
|
let re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)`);
|
||||||
let filteredPatch = patch.filter(operation => re.test(operation.path));
|
let filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||||
|
@ -40,7 +40,6 @@
|
|||||||
'js/ResourceLists/ResourceList.js',
|
'js/ResourceLists/ResourceList.js',
|
||||||
'js/ResourceLists/CorpusFileList.js',
|
'js/ResourceLists/CorpusFileList.js',
|
||||||
'js/ResourceLists/CorpusList.js',
|
'js/ResourceLists/CorpusList.js',
|
||||||
'js/ResourceLists/FollowedCorpusList.js',
|
|
||||||
'js/ResourceLists/PublicCorpusList.js',
|
'js/ResourceLists/PublicCorpusList.js',
|
||||||
'js/ResourceLists/JobList.js',
|
'js/ResourceLists/JobList.js',
|
||||||
'js/ResourceLists/JobInputList.js',
|
'js/ResourceLists/JobInputList.js',
|
||||||
|
379
app/templates/corpora/public_corpus.html.j2
Normal file
379
app/templates/corpora/public_corpus.html.j2
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
{% extends "base.html.j2" %}
|
||||||
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
|
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1>{{ corpus.title }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l7">
|
||||||
|
<div class="card service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="chip corpus-status-text corpus-status-color white-text" data-status="{{ corpus.status.name }}"></span></p>
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="input-field">
|
||||||
|
<label>Description</label>
|
||||||
|
<input disabled type="text" value="{{ corpus.description }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12 m6">
|
||||||
|
<div class="input-field">
|
||||||
|
<label for="corpus-creation-date">Creation date</label>
|
||||||
|
<input disabled type="text" value="{{ corpus.creation_date }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12 m6">
|
||||||
|
<div class="input-field">
|
||||||
|
<label for="corpus-token-ratio">Nr. of tokens used <sup><i class="material-icons tooltipped tiny" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i></sup></label>
|
||||||
|
<input disabled type="text" value="{{ corpus.num_tokens }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if cfr.has_permission('VIEW') %}
|
||||||
|
<div class="col s12 l5">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Actions</span>
|
||||||
|
<div class="row">
|
||||||
|
{% if cfr.has_permission('MANAGE_FILES') %}
|
||||||
|
<div class="col s12 l6" style="padding: 0 2.5px;">
|
||||||
|
<a class="action-button btn disabled waves-effect waves-light" data-action="build-request" style="width: 100%;"><i class="nopaque-icons left">K</i>Build</a>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l6" style="padding: 0 2.5px;">
|
||||||
|
{% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %}
|
||||||
|
<a class="action-button btn 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>
|
||||||
|
{% else %}
|
||||||
|
<a class="action-button btn disabled waves-effect waves-light" data-action="analyze" style="width: 100%;"><i class="material-icons left">search</i>Analyze</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.is_following_corpus(corpus) %}
|
||||||
|
<div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;">
|
||||||
|
<a class="action-button btn red waves-effect waves-light" data-action="unfollow-request" style="width: 100%;"><i class="material-icons left outlined">close</i>Unfollow Corpus</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
|
||||||
|
<span class="card-title">Social</span>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 l6" style="padding: 0 2.5px;">
|
||||||
|
<a class="btn waves-effect waves-light modal-trigger" href="#invite-user-modal" style="width: 100%;"><i class="material-icons left">person_add</i>invite user</a>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l6" style="padding: 0 2.5px;">
|
||||||
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title" id="files">Corpus Owner</span>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width:10%; margin-top:25px;">
|
||||||
|
<img src="{{ url_for('users.user_avatar', user_id=corpus.user.id) }}" alt="user-image" class="circle responsive-img">
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li><b>{{ corpus.user.username }}</b></li>
|
||||||
|
{% if corpus.user.full_name %}
|
||||||
|
<li>{{ corpus.user.full_name }}</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if corpus.user.show_email %}
|
||||||
|
<li></li><a href="mailto:{{ corpus.user.email }}">{{ corpus.user.email }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<p></p>
|
||||||
|
{% if not current_user.is_following_corpus(corpus) and corpus.user.has_profile_privacy_setting('SHOW_EMAIL') %}
|
||||||
|
<a class="waves-effect waves-light btn-small" href="mailto:{{ corpus.user.email }}">Request Corpus</a>
|
||||||
|
{% endif %}
|
||||||
|
<a class="waves-effect waves-light btn-small" href="{{ url_for('users.user', user_id=corpus.user.id) }}">View profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title" id="corpus-files">Corpus files</span>
|
||||||
|
<div class="corpus-file-list no-autoinit" id="corpus-file-list" data-has-permission-view="{{ cfr.has_permission('VIEW')|tojson }}" data-has-permission-manage-files="{{ cfr.has_permission('MANAGE_FILES')|tojson }}" data-corpus-id="{{ corpus.hashid }}"></div>
|
||||||
|
</div>
|
||||||
|
{% if cfr.has_permission('MANAGE_FILES') %}
|
||||||
|
<div class="card-action right-align">
|
||||||
|
<a href="{{ url_for('corpora.create_corpus_file', corpus_id=corpus.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add corpus file</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title" id="corpus-followers">Corpus followers</span>
|
||||||
|
<div class="corpus-follower-list no-autoinit"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
|
||||||
|
<div class="modal no-autoinit" id="invite-user-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Invite a nopaque user by username</h4>
|
||||||
|
<p>
|
||||||
|
Add other nopaque users as followers to your corpus. You can also add multiple
|
||||||
|
users at the same time. Added users get the role of "viewer"
|
||||||
|
by default, so they are only allowed to analyze files within nopaque, but not
|
||||||
|
to download or edit them. You can customize the roles later below.
|
||||||
|
</p>
|
||||||
|
<p><b>Please make sure that the invited users are legally allowed to view the included corpus files.</b></p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s10">
|
||||||
|
<div class="chips no-autoinit" id="invite-user-modal-search"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col s2">
|
||||||
|
<br class="hide-on-med-and-down">
|
||||||
|
<a class="btn modal-close waves-effect waves-light" id="invite-user-modal-invite-button">Invite<i class="material-icons right">send</i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a class="modal-close waves-effect waves-green btn-flat">Close</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal no-autoinit" id="share-link-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Create a link to share your corpus</h4>
|
||||||
|
<p>
|
||||||
|
With the link other users follow your corpus directly, if it has not expired.
|
||||||
|
You can set different roles via the link, you can also edit them later in the menu below.
|
||||||
|
It is recommended not to set the expiration date of the link too far.
|
||||||
|
</p>
|
||||||
|
<p><b>Please make sure that the invited users are legally allowed to view the included corpus files.</b></p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 l2">
|
||||||
|
<div class="input-field">
|
||||||
|
<i class="material-icons prefix">badge</i>
|
||||||
|
<select id="share-link-modal-corpus-follower-role-select">
|
||||||
|
{% for cfr in cfrs %}
|
||||||
|
<option value="{{ cfr.name }}">{{ cfr.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<label>Role</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l3">
|
||||||
|
<div class="input-field">
|
||||||
|
<i class="material-icons prefix">calendar_month</i>
|
||||||
|
<input type="text" class="datepicker no-autoinit" id="share-link-modal-expiration-date-datepicker">
|
||||||
|
<label for="expiration-date">Expiration date</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l2">
|
||||||
|
<br class="hide-on-med-and-down">
|
||||||
|
<a class="btn waves-effect waves-light" id="share-link-modal-create-button">Create<i class="material-icons right">send</i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12 l5">
|
||||||
|
<div class="row hide" id="share-link-modal-output-container">
|
||||||
|
<div class="col s9">
|
||||||
|
<div class="input-field">
|
||||||
|
<input disabled id="share-link-modal-output-field" readonly type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s3">
|
||||||
|
<br class="hide-on-med-and-down">
|
||||||
|
<a class="btn-small waves-effect waves-light" id="share-link-modal-output-copy-button"><i class="material-icons left">content_copy</i>Copy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a class="modal-close waves-effect waves-green btn-flat">Close</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock modals %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let publicCorpusFileList = new CorpusFileList(document.querySelector('#corpus-file-list'));
|
||||||
|
publicCorpusFileList.add(
|
||||||
|
[
|
||||||
|
{% for corpus_file in corpus.files %}
|
||||||
|
{{ corpus_file.to_json_serializeable()|tojson }},
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
|
||||||
|
let publicCorpusFollowerList = new CorpusFollowerList(document.querySelector('.corpus-follower-list'));
|
||||||
|
publicCorpusFollowerList.add(
|
||||||
|
[
|
||||||
|
{% for cfa in cfas %}
|
||||||
|
{{ cfa.to_json_serializeable()|tojson }},
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
// #region Corpus Unfollow Request
|
||||||
|
{% if current_user.is_following_corpus(corpus) %}
|
||||||
|
let unfollowRequestElement = document.querySelector('.action-button[data-action="unfollow-request"]');
|
||||||
|
unfollowRequestElement.addEventListener('click', () => {
|
||||||
|
Requests.corpora.entity.followers.entity.delete({{ corpus.hashid|tojson }}, {{ current_user.hashid|tojson }})
|
||||||
|
.then((response) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
// #endregion Corpus Unfollow Request
|
||||||
|
|
||||||
|
{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
|
||||||
|
// #region Invite user
|
||||||
|
let inviteUserModalElement = document.querySelector('#invite-user-modal');
|
||||||
|
let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search');
|
||||||
|
let inviteUserModalInviteButtonElement = document.querySelector('#invite-user-modal-invite-button');
|
||||||
|
let users = {
|
||||||
|
{% for user in users %}
|
||||||
|
{{ user.username|tojson }}: {{ url_for('users.user_avatar', user_id=user.id)|tojson }}
|
||||||
|
{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
|
||||||
|
let inviteUserModalSearch = M.Chips.init(
|
||||||
|
inviteUserModalSearchElement,
|
||||||
|
{
|
||||||
|
autocompleteOptions: {
|
||||||
|
data: users
|
||||||
|
},
|
||||||
|
limit: 3,
|
||||||
|
onChipAdd: (a, chipElement) => {
|
||||||
|
if (!(chipElement.firstChild.data in inviteUserModalSearch.autocomplete.options.data)) {
|
||||||
|
chipElement.firstElementChild.click();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder: 'Enter username',
|
||||||
|
secondaryPlaceholder: 'Add more users'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
M.Modal.init(
|
||||||
|
inviteUserModalElement,
|
||||||
|
{
|
||||||
|
onOpenStart: (modalElement, modalTriggerElement) => {
|
||||||
|
while (inviteUserModalSearch.chipsData.length > 0) {
|
||||||
|
inviteUserModalSearch.deleteChip(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
inviteUserModalInviteButtonElement.addEventListener('click', (event) => {
|
||||||
|
let usernames = inviteUserModalSearch.chipsData.map((chipData) => chipData.tag);
|
||||||
|
Requests.corpora.entity.followers.add({{ corpus.hashid|tojson }}, usernames);
|
||||||
|
});
|
||||||
|
// #endregion Invite user
|
||||||
|
|
||||||
|
// #region Share link
|
||||||
|
let shareLinkModalElement = document.querySelector('#share-link-modal');
|
||||||
|
let shareLinkModalCorpusFollowerRoleSelectElement = document.querySelector('#share-link-modal-corpus-follower-role-select');
|
||||||
|
let shareLinkModalExpirationDateDatepickerElement = document.querySelector('#share-link-modal-expiration-date-datepicker');
|
||||||
|
let shareLinkModalCreateButtonElement = document.querySelector('#share-link-modal-create-button');
|
||||||
|
let shareLinkModalOutputContainerElement = document.querySelector('#share-link-modal-output-container');
|
||||||
|
let shareLinkModalOutputFieldElement = document.querySelector('#share-link-modal-output-field');
|
||||||
|
let shareLinkModalOutputCopyButtonElement = document.querySelector('#share-link-modal-output-copy-button');
|
||||||
|
|
||||||
|
let today = new Date();
|
||||||
|
let tomorrow = new Date();
|
||||||
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
let oneWeekLater = new Date();
|
||||||
|
oneWeekLater.setDate(today.getDate() + 7);
|
||||||
|
let fourWeeksLater = new Date();
|
||||||
|
fourWeeksLater.setDate(today.getDate() + 28);
|
||||||
|
|
||||||
|
M.Datepicker.init(
|
||||||
|
shareLinkModalExpirationDateDatepickerElement,
|
||||||
|
{
|
||||||
|
container: document.querySelector('main'),
|
||||||
|
defaultDate: oneWeekLater,
|
||||||
|
setDefaultDate: true,
|
||||||
|
minDate: tomorrow,
|
||||||
|
maxDate: fourWeeksLater
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
M.Modal.init(
|
||||||
|
shareLinkModalElement,
|
||||||
|
{
|
||||||
|
onOpenStart: (modalElement, modalTriggerElement) => {
|
||||||
|
shareLinkModalOutputFieldElement.value = '';
|
||||||
|
shareLinkModalOutputContainerElement.classList.add('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
||||||
|
let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||||
|
let expiration = shareLinkModalExpirationDateDatepickerElement.value
|
||||||
|
Requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
|
||||||
|
.then((response) => {
|
||||||
|
response.json()
|
||||||
|
.then((json) => {
|
||||||
|
shareLinkModalOutputContainerElement.classList.remove('hide');
|
||||||
|
shareLinkModalOutputFieldElement.value = json.corpusShareLink;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||||
|
navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value)
|
||||||
|
.then(
|
||||||
|
() => {app.flash('Copied!');},
|
||||||
|
() => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
// #endregion Share link
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
Loading…
Reference in New Issue
Block a user