prettify admin featured apps tool (bug 763700)
This commit is contained in:
Родитель
2395baa437
Коммит
7d8ae4990b
|
@ -592,7 +592,9 @@ MINIFY_BUNDLES = {
|
|||
'zamboni/admin': (
|
||||
'css/zamboni/admin-django.css',
|
||||
'css/zamboni/admin-mozilla.css',
|
||||
'css/zamboni/admin_features.css'
|
||||
'css/zamboni/admin_features.css',
|
||||
# Datepicker styles and jQuery UI core.
|
||||
'css/zamboni/jquery-ui/custom-1.7.2.css',
|
||||
),
|
||||
},
|
||||
'js': {
|
||||
|
|
|
@ -95,3 +95,130 @@ th {
|
|||
#features tr:hover a.remove:hover {
|
||||
background-color: #2a4364;
|
||||
}
|
||||
|
||||
#featured-webapps {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.app-container {
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.featured-app {
|
||||
border-radius: 5px;
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.featured-app .dashboard {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
.featured-app .dashboard em {
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.featured-app .dashboard a {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
.featured-app .dashboard a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.featured-app h2 {
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
margin: 0 15px 0 10px;
|
||||
}
|
||||
.featured-app li {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.featured-app img.logo {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
.app-intro p {
|
||||
font-size: 10px;
|
||||
}
|
||||
.featured-app p {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
.featured-app .icon {
|
||||
display: inline-block;
|
||||
background: url(../../img/mkt/icons/mkt-reviewer-icons.png) no-repeat 0 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
.featured-app .icon.warning {background-position: 0 -32px;}
|
||||
.featured-app .icon.sponsored {background-position: -32px 0;}
|
||||
#featured-webapps .side {
|
||||
float: right;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
width: 250px;
|
||||
}
|
||||
#featured-webapps .side p {
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
#featured-webapps .side li {
|
||||
font-size: 10px;
|
||||
}
|
||||
#featured-webapps .side label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
#featured-webapps .side .devicelist {
|
||||
float: left;
|
||||
margin: 0 10px 0 0;
|
||||
|
||||
}
|
||||
.featured-app .dates {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.featured-app .dates label {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.featured-app input[type=date], #featured-webapps input.placeholder {
|
||||
border: 0;
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#featured-webapps input.placeholder {
|
||||
border: 1px solid #ccc;
|
||||
width: 350px;
|
||||
}
|
||||
.featured-app .app-intro {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
a.remove {
|
||||
margin-left: 10px;
|
||||
text-indent: -99999px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: #999 url(../../img/mkt/header-icons.png) no-repeat -1px -26px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
a.remove:last-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
li.ui-menu-item .ui-corner-all.ui-state-hover {
|
||||
border: 0;
|
||||
}
|
||||
ul.ui-autocomplete {
|
||||
border-radius: 5px;
|
||||
}
|
||||
#ui-datepicker-div {
|
||||
display: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-header {
|
||||
height: 25px;
|
||||
}
|
||||
|
|
|
@ -1,177 +1,167 @@
|
|||
function registerAddonAutocomplete(node) {
|
||||
var $td = node.closest('td');
|
||||
node.autocomplete({
|
||||
minLength: 3,
|
||||
width: 300,
|
||||
source: function(request, response) {
|
||||
$.getJSON($(node).attr('data-src'), {
|
||||
q: request.term,
|
||||
category: $("#categories").val()
|
||||
}, response);
|
||||
},
|
||||
focus: function(event, ui) {
|
||||
$(node).val(ui.item.name);
|
||||
return false;
|
||||
},
|
||||
select: function(event, ui) {
|
||||
updateAppsList($("#categories"),
|
||||
ui.item.id).then(
|
||||
function(x) {
|
||||
registerDatepickers($("#featured-webapps"));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}).data('autocomplete')._renderItem = function(ul, item) {
|
||||
var html = format('<a>{0}<b>ID: {1}</b></a>', [item.name, item.id]);
|
||||
return $('<li>').data('item.autocomplete', item).append(html).appendTo(ul);
|
||||
};
|
||||
}
|
||||
(function() {
|
||||
var $featured = $('#featured-webapps');
|
||||
|
||||
function registerDatepickers() {
|
||||
$("#featured-webapps .featured-app").each(function (i, n) { registerDatepicker($(n));});
|
||||
}
|
||||
function registerAddonAutocomplete(node) {
|
||||
var $node = $(node);
|
||||
|
||||
function registerDatepicker(node) {
|
||||
var $startPicker = node.find('.start-date-picker');
|
||||
var $tabl = $startPicker.closest('table').last();
|
||||
var url = $tabl.data('url');
|
||||
var appid = $tabl.data('app-id');
|
||||
var $start = node.find('.date-range-start');
|
||||
var $end = node.find('.date-range-end');
|
||||
$startPicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
onSelect: function(dateText) {
|
||||
$start.val(dateText);
|
||||
saveFeaturedDate(url, appid, dateText, $end.val());
|
||||
}
|
||||
});
|
||||
var $endPicker = node.find('.end-date-picker');
|
||||
$endPicker.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
onSelect: function(dateText) {
|
||||
$end.val(dateText);
|
||||
saveFeaturedDate(url, appid, $start.val(), dateText);
|
||||
}
|
||||
});
|
||||
$node.autocomplete({
|
||||
'minLength': 3,
|
||||
'width': 300,
|
||||
'source': function(request, response) {
|
||||
$.getJSON($node.attr('data-src'), {
|
||||
'q': request.term,
|
||||
'category': $('#categories').val()
|
||||
}, response);
|
||||
},
|
||||
'focus': function(event, ui) {
|
||||
$node.val(ui.item.name);
|
||||
return false;
|
||||
},
|
||||
'select': function(event, ui) {
|
||||
updateAppsList($('#categories'), ui.item.id).then(function() {
|
||||
registerDatepickers();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}).data('autocomplete')._renderItem = function(ul, item) {
|
||||
var html = format('<a href="#">{0}<b>ID: {1}</b></a>', [item.name, item.id]);
|
||||
return $('<li>').data('item.autocomplete', item).append(html)
|
||||
.appendTo(ul);
|
||||
};
|
||||
}
|
||||
|
||||
$start.change(
|
||||
function (e) {
|
||||
saveFeaturedDate($tabl.data('url'), $tabl.data('app-id'), $start.val(), $end.val());
|
||||
});
|
||||
$end.change(
|
||||
function (e) {
|
||||
saveFeaturedDate($tabl.data('url'), $tabl.data('app-id'), $start.val(), $end.val());
|
||||
});
|
||||
function registerDatepickers() {
|
||||
$('#featured-webapps input[type=date]').each(function(i, elm) {
|
||||
var $this = $(elm);
|
||||
var $app = $this.closest('.featured-app');
|
||||
var $siblingDate = $this.siblings('input[type=date]');
|
||||
var url = $app.data('url');
|
||||
var appid = $app.data('app-id');
|
||||
var isStartDate = $siblingDate.hasClass('date-range-start');
|
||||
$this.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
onSelect: function(dateText) {
|
||||
if (isStartDate) {
|
||||
saveFeaturedDate(url, appid, dateText, $siblingDate.val());
|
||||
} else {
|
||||
saveFeaturedDate(url, appid, $siblingDate.val(), dateText);
|
||||
}
|
||||
}
|
||||
});
|
||||
$this.change(function(e) {
|
||||
if (isStartDate) {
|
||||
saveFeaturedDate($app.data('url'), $app.data('app-id'),
|
||||
$this.val(), $siblingDate.val());
|
||||
} else {
|
||||
saveFeaturedDate($app.data('url'), $app.data('app-id'),
|
||||
$siblingDate.val(), $this.val());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
function saveFeaturedDate(url, appid, start, end) {
|
||||
var data = {};
|
||||
data.startdate = start;
|
||||
data.enddate = end;
|
||||
data.app = appid;
|
||||
$.ajax({'type': 'POST', 'url': url, 'data': data});
|
||||
}
|
||||
|
||||
function saveFeaturedDate(url, appid, start, end) {
|
||||
var data = {};
|
||||
data["startdate"] = start;
|
||||
data["enddate"] = end;
|
||||
data.app = appid;
|
||||
$.ajax({type: 'POST', url: url, data: data});
|
||||
}
|
||||
function newAddonSlot(id) {
|
||||
var $container = $featured;
|
||||
var $next = $container.next();
|
||||
var $form = $next.children().clone();
|
||||
|
||||
function newAddonSlot(id) {
|
||||
var $tbody = $("#featured-webapps");
|
||||
var $form = $tbody.next().children("tr").clone();
|
||||
var $input = $form.find('input.placeholder');
|
||||
registerAddonAutocomplete($input);
|
||||
$tbody.append($form);
|
||||
}
|
||||
// This seems to be the best way to send the input for autocompletion.
|
||||
registerAddonAutocomplete($form[1]);
|
||||
$container.append($form);
|
||||
}
|
||||
|
||||
function showAppsList(cat) {
|
||||
return appslistXHR('GET', {
|
||||
category: cat.val()
|
||||
});
|
||||
}
|
||||
function showAppsList(cat) {
|
||||
return appslistXHR('GET', {
|
||||
'category': cat.val()
|
||||
});
|
||||
}
|
||||
|
||||
function updateAppsList(cat, newItem) {
|
||||
return appslistXHR('POST', {
|
||||
category: cat.val(),
|
||||
add: newItem
|
||||
});
|
||||
}
|
||||
function updateAppsList(cat, newItem) {
|
||||
return appslistXHR('POST', {
|
||||
'category': cat.val(),
|
||||
'add': newItem
|
||||
});
|
||||
}
|
||||
|
||||
function deleteFromAppsList(cat, oldItem) {
|
||||
return appslistXHR('POST', {
|
||||
category: cat.val(),
|
||||
delete: oldItem
|
||||
});
|
||||
}
|
||||
function deleteFromAppsList(cat, oldItem) {
|
||||
return appslistXHR('POST', {
|
||||
'category': cat.val(),
|
||||
'delete': oldItem
|
||||
});
|
||||
}
|
||||
|
||||
function appslistXHR(verb, data) {
|
||||
var appslist = $("#featured-webapps");
|
||||
var q = $.ajax({type: verb, url: appslist.data("src"), data: data});
|
||||
q.then(function (data) {
|
||||
appslist.html(data);
|
||||
});
|
||||
return q;
|
||||
}
|
||||
var region_carrier_update = _pd(
|
||||
function (e) {
|
||||
function appslistXHR(verb, data) {
|
||||
var appslist = $featured;
|
||||
var q = $.ajax({'type': verb, 'url': appslist.data('src'), 'data': data});
|
||||
q.then(function(data) {
|
||||
appslist.html(data);
|
||||
});
|
||||
return q;
|
||||
}
|
||||
|
||||
var region_carrier_update = _pd(function(e) {
|
||||
var $choices = $(e.target);
|
||||
var $tabl = $choices.closest('table');
|
||||
var $appParent = $choices.closest('ul');
|
||||
function carrierName(v) {
|
||||
var x = v.split(".");
|
||||
if (x[0] == "carrier") {
|
||||
var x = v.split('.');
|
||||
if (x[0] == 'carrier') {
|
||||
return x[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
var regions = $choices.children('option')
|
||||
.filter(function(i, opt) {return opt.selected && !carrierName(opt.value);})
|
||||
.map(function(i, sopt) {return sopt.value;});
|
||||
var carriers = $choices.children('option')
|
||||
.map(function(i, opt) {
|
||||
if (opt.selected) {
|
||||
return carrierName(opt.value);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var regions = $choices.children('option').filter(function(i, opt) {
|
||||
return opt.selected && !carrierName(opt.value);
|
||||
}).map(function(i, sopt) {return sopt.value;});
|
||||
|
||||
var carriers = $choices.children('option').map(function(i, opt) {
|
||||
if (opt.selected) {
|
||||
return carrierName(opt.value);
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: $tabl.data('url'),
|
||||
data: {
|
||||
'region': $.makeArray(regions),
|
||||
'carrier': $.makeArray(carriers),
|
||||
'app': $tabl.data('app-id')
|
||||
}
|
||||
});
|
||||
'type': 'POST',
|
||||
'url': $appParent.data('url'),
|
||||
'data': {
|
||||
'region': $.makeArray(regions),
|
||||
'carrier': $.makeArray(carriers),
|
||||
'app': $appParent.data('app-id')
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#featured-webapps").delegate(
|
||||
'.remove',
|
||||
'click',
|
||||
_pd(function() {
|
||||
deleteFromAppsList($("#categories"), $(this).data("id"));
|
||||
})
|
||||
);
|
||||
$('#featured-webapps').delegate(
|
||||
'select.localepicker',
|
||||
'change',
|
||||
region_carrier_update);
|
||||
$('#featured-webapps').delegate(
|
||||
'select.carrierpicker',
|
||||
'change',
|
||||
region_carrier_update);
|
||||
var categories = $("#categories");
|
||||
var p = $.ajax({type: 'GET',
|
||||
url: categories.data("src")});
|
||||
|
||||
$featured.delegate('.remove', 'click', _pd(function() {
|
||||
deleteFromAppsList($('#categories'), $(this).data('id'));
|
||||
})).delegate('select.localepicker', 'change', region_carrier_update)
|
||||
.delegate('select.carrierpicker', 'change', region_carrier_update);
|
||||
|
||||
var categories = $('#categories');
|
||||
var p = $.ajax({'type': 'GET', 'url': categories.data('src')});
|
||||
|
||||
p.then(function(data) {
|
||||
categories.html(data);
|
||||
showAppsList(categories).then(
|
||||
function () {
|
||||
registerDatepickers();
|
||||
});
|
||||
showAppsList(categories).then(function() {
|
||||
registerDatepickers();
|
||||
});
|
||||
});
|
||||
categories.change(function (e) {
|
||||
showAppsList(categories).then(
|
||||
function () {
|
||||
registerDatepickers();
|
||||
});
|
||||
|
||||
categories.change(function(e) {
|
||||
showAppsList(categories).then(function() {
|
||||
registerDatepickers();
|
||||
});
|
||||
});
|
||||
$('#featured-add').click(_pd(function() { newAddonSlot(); }));
|
||||
});
|
||||
|
||||
$('#featured-add').click(_pd(function() {newAddonSlot();}));
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,61 +1,64 @@
|
|||
{% block content %}
|
||||
{% for row, selected_regions, excluded_regions, selected_carriers in apps_regions_carriers -%}
|
||||
<tr>
|
||||
<td>
|
||||
<table class="featured-app" data-app-id="{{ row.pk }}" data-url="{{ url('zadmin.set_attrs_ajax') }}">
|
||||
<tr>
|
||||
<td><img src="{{ row.app.icon_url }}"></td>
|
||||
<td>{{ row.app.name }}</td>
|
||||
<td>{% for dt in row.app.device_types %}
|
||||
{{ dt.name }}{% if not loop.last %}, {% endif%}
|
||||
{% endfor %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% if row.app.promo %}
|
||||
<a href="{{ row.app.get_dev_url() }}">Manage featured graphics</a>
|
||||
{% else %}
|
||||
<a href="{{ row.app.get_dev_url() }}">No featured graphics</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if row.is_sponsor %}Sponsored{% else %}Not sponsored{% endif %}</td>
|
||||
<td>
|
||||
<select class="localepicker" multiple>
|
||||
{% for row, selected_regions, excluded_regions, selected_carriers in apps_regions_carriers -%}
|
||||
<div class="app-container c">
|
||||
<ul class="featured-app" data-app-id="{{ row.pk }}"
|
||||
data-url="{{ url('zadmin.set_attrs_ajax') }}">
|
||||
<li class="app-intro">
|
||||
<img src="{{ row.app.icon_url }}" class="logo">
|
||||
<h2>{{ row.app.name }}</h2>
|
||||
<div class="dashboard">
|
||||
<a href="{{ row.app.get_dev_url() }}">
|
||||
<img alt="{{ _('Manage featured graphics') }}"
|
||||
title="{{ _('Manage featured graphics') }}"
|
||||
src="{{ MEDIA_URL }}img/mkt/icons/settings.png">
|
||||
</a>
|
||||
{% if not row.app.promo %}
|
||||
<em class="icon warning"
|
||||
title="{{ _('No featured graphics found.') }}"></em>
|
||||
{% endif %}
|
||||
{% if row.is_sponsor %}
|
||||
<em class="icon sponsored" title="{{ _('Sponsored') }}"></em>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
<li class="dates">
|
||||
<label>
|
||||
{{ _('Start date') }}
|
||||
<input type="date" class="date-range-start"
|
||||
value="{{ row.start_date }}">
|
||||
</label>
|
||||
<label>
|
||||
{{ _('End date') }}
|
||||
<input type="date" class="date-range-end" value="{{ row.end_date }}">
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="remove" data-id="{{ row.app.id }}">×</a>
|
||||
<div class="side">
|
||||
<ul class="devicelist">
|
||||
{%- for dt in row.app.device_types -%}
|
||||
<li>{{ dt.name }}</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
<p>
|
||||
<label for="localepicker">{{ _('Locale') }}</label>
|
||||
<select class="localepicker" multiple id="localepicker">
|
||||
{%- for locName, loc in regions -%}
|
||||
<option value="{{ loc.id }}"
|
||||
{%- if loc.id in selected_regions %} selected{%- endif -%}
|
||||
{%- if loc.id in excluded_regions %} disabled{%- endif -%}
|
||||
>{{ loc.name }}</option>
|
||||
{{- " selected" if loc.id in selected_regions -}}
|
||||
{{- " disabled" if loc.id in excluded_regions -}}>
|
||||
{{ loc.name }}
|
||||
</option>
|
||||
{%- endfor -%}
|
||||
{%- for carrier in carriers -%}
|
||||
<option value="carrier.{{ carrier }}"
|
||||
{%- if carrier in selected_carriers %} selected{%- endif -%}
|
||||
>{{ carrier }}</option>
|
||||
{{- " selected" if carrier in selected_carriers -}}>
|
||||
{{ carrier }}
|
||||
</option>
|
||||
{%- endfor -%}
|
||||
</select>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _('Start date') }}
|
||||
</td>
|
||||
<td>
|
||||
<input type="date" class="date-range-start" value="{{ row.start_date }}">
|
||||
<div class="start-date-picker"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _('End date') }}
|
||||
</td>
|
||||
<td>
|
||||
<input type="date" class="date-range-end" value="{{ row.end_date }}">
|
||||
<div class="end-date-picker"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td><input type="hidden"><a class="remove" data-id="{{ row.app.id }}">×</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% set title = 'Feature Manager' %}
|
||||
{% set title = _('Feature Manager') %}
|
||||
{% block title %}{{ mkt_page_title(title) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -28,28 +28,21 @@
|
|||
|
||||
<form method="post">
|
||||
{{ csrf() }}
|
||||
<h3>Featured Apps</h3>
|
||||
Section: <select id="categories"
|
||||
data-src="{{ url('zadmin.featured_categories_ajax') }}"
|
||||
></select>
|
||||
<table>
|
||||
<thead>
|
||||
<th>App</th>
|
||||
<th>Delete</th>
|
||||
</thead>
|
||||
<tbody id="featured-webapps"
|
||||
data-src="{{ url('zadmin.featured_apps_ajax') }}">
|
||||
</tbody>
|
||||
<tfoot class="hidden">
|
||||
<tr><td>
|
||||
<div class="current-webapp js-hidden" style="display: block;"></div>
|
||||
<input placeholder="{{ _('Enter the name of the webapp to include') }}"
|
||||
class="placeholder addon-ac large"
|
||||
data-src="{{ url('search.apps_ajax') }}">
|
||||
<input type="hidden"><a class="remove">×</a>
|
||||
</td></tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p><a href="#" id="featured-add">Add an app</a></p>
|
||||
<h3>{{ _('Featured Apps') }}</h3>
|
||||
{{ _('Section') }}:
|
||||
<select id="categories"
|
||||
data-src="{{ url('zadmin.featured_categories_ajax') }}">
|
||||
</select>
|
||||
<div id="featured-webapps" data-src="{{ url('zadmin.featured_apps_ajax') }}">
|
||||
</div>
|
||||
<div class="hidden">
|
||||
<div class="current-webapp js-hidden"></div>
|
||||
<input placeholder="{{ _('Enter the name of the webapp to include') }}"
|
||||
class="placeholder addon-ac large"
|
||||
data-src="{{ url('search.apps_ajax') }}">
|
||||
<a class="remove">×</a>
|
||||
</div>
|
||||
|
||||
<p><a href="#" id="featured-add">{{ _('Add an app') }}</a></p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
Загрузка…
Ссылка в новой задаче