Add start of bulk tag editor
This commit is contained in:
parent
aa6892da82
commit
04db19d1ac
|
@ -22,7 +22,7 @@ from . import bp
|
||||||
|
|
||||||
from app.rediscache import has_key, set_key, make_download_key
|
from app.rediscache import has_key, set_key, make_download_key
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from app.tasks.importtasks import makeVCSRelease, checkZipRelease
|
from app.tasks.importtasks import makeVCSRelease, checkZipRelease, updateMetaFromRelease
|
||||||
from app.utils import *
|
from app.utils import *
|
||||||
|
|
||||||
from celery import uuid
|
from celery import uuid
|
||||||
|
@ -111,6 +111,7 @@ def create_release(package):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
checkZipRelease.apply_async((rel.id, uploadedPath), task_id=rel.task_id)
|
checkZipRelease.apply_async((rel.id, uploadedPath), task_id=rel.task_id)
|
||||||
|
updateMetaFromRelease.delay(rel.id, uploadedPath)
|
||||||
|
|
||||||
msg = "Release {} created".format(rel.title)
|
msg = "Release {} created".format(rel.title)
|
||||||
addNotification(package.maintainers, current_user, msg, rel.getEditURL(), package)
|
addNotification(package.maintainers, current_user, msg, rel.getEditURL(), package)
|
||||||
|
@ -120,6 +121,7 @@ def create_release(package):
|
||||||
|
|
||||||
return render_template("packages/release_new.html", package=package, form=form)
|
return render_template("packages/release_new.html", package=package, form=form)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/releases/<id>/download/")
|
@bp.route("/packages/<author>/<name>/releases/<id>/download/")
|
||||||
@is_package_page
|
@is_package_page
|
||||||
def download_release(package, id):
|
def download_release(package, id):
|
||||||
|
@ -149,6 +151,7 @@ def download_release(package, id):
|
||||||
|
|
||||||
return redirect(release.url, code=300)
|
return redirect(release.url, code=300)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/releases/<id>/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/releases/<id>/", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@is_package_page
|
@is_package_page
|
||||||
|
|
|
@ -100,3 +100,12 @@ def topics():
|
||||||
topic_count=topic_count, query=qb.search, show_discarded=qb.show_discarded, \
|
topic_count=topic_count, query=qb.search, show_discarded=qb.show_discarded, \
|
||||||
next_url=next_url, prev_url=prev_url, page=page, page_max=query.pages, \
|
next_url=next_url, prev_url=prev_url, page=page, page_max=query.pages, \
|
||||||
n=num, sort_by=qb.order_by)
|
n=num, sort_by=qb.order_by)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/todo/tags/")
|
||||||
|
@login_required
|
||||||
|
def tags():
|
||||||
|
packages = Package.query.filter_by(approved=True, soft_deleted=False).all()
|
||||||
|
tags = Tag.query.order_by(db.asc(Tag.title)).all()
|
||||||
|
|
||||||
|
return render_template("todo/tags.html", packages=packages, tags=tags)
|
||||||
|
|
|
@ -19,80 +19,80 @@
|
||||||
|
|
||||||
$.fn.selectSelector = function(source, select) {
|
$.fn.selectSelector = function(source, select) {
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
var selector = $(this),
|
var selector = $(this),
|
||||||
input = $('input[type=text]', this);
|
input = $('input[type=text]', this);
|
||||||
|
|
||||||
selector.click(function() { input.focus(); })
|
selector.click(function() { input.focus(); })
|
||||||
.delegate('.badge a', 'click', function() {
|
.delegate('.badge a', 'click', function() {
|
||||||
var id = $(this).parent().data("id");
|
var id = $(this).parent().data("id");
|
||||||
select.find("option[value=" + id + "]").attr("selected", false)
|
select.find("option[value=" + id + "]").attr("selected", false)
|
||||||
recreate();
|
recreate();
|
||||||
});
|
});
|
||||||
|
|
||||||
function addTag(id, text) {
|
function addTag(id, text) {
|
||||||
$('<span class="badge badge-pill badge-primary"/>')
|
$('<span class="badge badge-pill badge-primary"/>')
|
||||||
.text(text + ' ')
|
.text(text + ' ')
|
||||||
.data("id", id)
|
.data("id", id)
|
||||||
.append('<a>x</a>')
|
.append('<a>x</a>')
|
||||||
.insertBefore(input);
|
.insertBefore(input);
|
||||||
input.attr("placeholder", null);
|
input.attr("placeholder", null);
|
||||||
select.find("option[value='" + id + "']").attr("selected", "selected")
|
select.find("option[value='" + id + "']").attr("selected", "selected")
|
||||||
hide_error(input);
|
hide_error(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
function recreate() {
|
function recreate() {
|
||||||
selector.find("span").remove();
|
selector.find("span").remove();
|
||||||
select.find("option").each(function() {
|
select.find("option").each(function() {
|
||||||
if (this.hasAttribute("selected")) {
|
if (this.hasAttribute("selected")) {
|
||||||
addTag(this.getAttribute("value"), this.innerText);
|
addTag(this.getAttribute("value"), this.innerText);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
recreate();
|
|
||||||
|
|
||||||
input.focusout(function(e) {
|
|
||||||
var value = input.val().trim()
|
|
||||||
if (value != "") {
|
|
||||||
show_error(input, "Please select an existing tag, it;s not possible to add custom ones.");
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
recreate();
|
||||||
|
|
||||||
|
input.focusout(function(e) {
|
||||||
|
var value = input.val().trim()
|
||||||
|
if (value != "") {
|
||||||
|
show_error(input, "Please select an existing tag, it;s not possible to add custom ones.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
input.keydown(function(e) {
|
||||||
|
if (e.keyCode === $.ui.keyCode.TAB && $(this).data('ui-autocomplete').menu.active)
|
||||||
|
e.preventDefault();
|
||||||
})
|
})
|
||||||
|
.autocomplete({
|
||||||
|
minLength: 0,
|
||||||
|
source: source,
|
||||||
|
select: function(event, ui) {
|
||||||
|
addTag(ui.item.id, ui.item.toString());
|
||||||
|
input.val("");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).focus(function() {
|
||||||
|
// The following works only once.
|
||||||
|
// $(this).trigger('keydown.autocomplete');
|
||||||
|
// As suggested by digitalPBK, works multiple times
|
||||||
|
// $(this).data("autocomplete").search($(this).val());
|
||||||
|
// As noted by Jonny in his answer, with newer versions use uiAutocomplete
|
||||||
|
$(this).data("ui-autocomplete").search($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
input.keydown(function(e) {
|
input.data('ui-autocomplete')._renderItem = function(ul, item) {
|
||||||
if (e.keyCode === $.ui.keyCode.TAB && $(this).data('ui-autocomplete').menu.active)
|
return $('<li/>')
|
||||||
e.preventDefault();
|
.data('item.autocomplete', item)
|
||||||
})
|
.append($('<a/>').text(item.toString()))
|
||||||
.autocomplete({
|
.appendTo(ul);
|
||||||
minLength: 0,
|
};
|
||||||
source: source,
|
|
||||||
select: function(event, ui) {
|
|
||||||
addTag(ui.item.id, ui.item.toString());
|
|
||||||
input.val("");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}).focus(function() {
|
|
||||||
// The following works only once.
|
|
||||||
// $(this).trigger('keydown.autocomplete');
|
|
||||||
// As suggested by digitalPBK, works multiple times
|
|
||||||
// $(this).data("autocomplete").search($(this).val());
|
|
||||||
// As noted by Jonny in his answer, with newer versions use uiAutocomplete
|
|
||||||
$(this).data("ui-autocomplete").search($(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
input.data('ui-autocomplete')._renderItem = function(ul, item) {
|
input.data('ui-autocomplete')._resizeMenu = function(ul, item) {
|
||||||
return $('<li/>')
|
var ul = this.menu.element;
|
||||||
.data('item.autocomplete', item)
|
ul.outerWidth(Math.max(
|
||||||
.append($('<a/>').text(item.toString()))
|
ul.width('').outerWidth(),
|
||||||
.appendTo(ul);
|
selector.outerWidth()
|
||||||
};
|
));
|
||||||
|
};
|
||||||
input.data('ui-autocomplete')._resizeMenu = function(ul, item) {
|
});
|
||||||
var ul = this.menu.element;
|
|
||||||
ul.outerWidth(Math.max(
|
|
||||||
ul.width('').outerWidth(),
|
|
||||||
selector.outerWidth()
|
|
||||||
));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$.fn.csvSelector = function(source, name, result, allowSlash) {
|
$.fn.csvSelector = function(source, name, result, allowSlash) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.ui-autocomplete, ui-front {
|
.ui-autocomplete, ui-front {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
cursor:default;
|
cursor:default;
|
||||||
z-index:1001 !important
|
z-index:1100 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-autocomplete {
|
.ui-autocomplete {
|
||||||
|
|
|
@ -299,6 +299,8 @@ def makeVCSRelease(id, branch):
|
||||||
release.approve(release.package.author)
|
release.approve(release.package.author)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
updateMetaFromRelease.delay(release.id, destPath)
|
||||||
|
|
||||||
return release.url
|
return release.url
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(gitDir)
|
shutil.rmtree(gitDir)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=12">
|
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=13">
|
||||||
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
||||||
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
||||||
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Tags
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width:auto;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
table td:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Package</th>
|
||||||
|
<th>Tags</th>
|
||||||
|
</tr>
|
||||||
|
{% for package in packages %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ package.getDetailsURL() }}">
|
||||||
|
{{ package.title }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
by {{ package.author.display_name }}
|
||||||
|
</td>
|
||||||
|
<td class="tags">
|
||||||
|
{% for tag in package.tags %}
|
||||||
|
<span class="badge badge-primary mr-1">{{ tag.title }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
<a class="badge badge-secondary add-btn px-2" href="#">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{ _("Edit tags") }}</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<select name="tags" multiple>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<option value="{{ tag.name }}">{{ tag.title }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% from "macros/forms.html" import form_scripts %}
|
||||||
|
|
||||||
|
{% block scriptextra %}
|
||||||
|
{{ form_scripts() }}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(".add-btn").click(function() {
|
||||||
|
const row = $(this).parent().parent()
|
||||||
|
|
||||||
|
$(".modal select option").removeAttr("selected");
|
||||||
|
$(".multichoice_selector").remove();
|
||||||
|
|
||||||
|
$(".modal .modal-body").prepend(`
|
||||||
|
<div class="multichoice_selector bulletselector form-control">
|
||||||
|
<input type="text" placeholder="Start typing to see suggestions">
|
||||||
|
<div class="clearboth"></div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
$(".modal").modal("show");
|
||||||
|
$(".modal input").focus();
|
||||||
|
$(".multichoice_selector").each(function() {
|
||||||
|
var ele = $(this);
|
||||||
|
var sel = ele.parent().find("select");
|
||||||
|
sel.hide();
|
||||||
|
|
||||||
|
var options = [];
|
||||||
|
sel.find("option").each(function() {
|
||||||
|
var text = $(this).text();
|
||||||
|
options.push({
|
||||||
|
id: $(this).attr("value"),
|
||||||
|
value: text,
|
||||||
|
toString: function() { return text; },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ele.selectSelector(options, sel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue