Issue #1193 : Tag Manager redesign

* Create the new design of tag manager
 * Replace all actions by ajax actions
 * Add delete, rename, duplicate and merge tag's functions in the Piwigo API
 * Modification of group manager to match both designs
This commit is contained in:
Zacharie
2020-06-16 16:44:14 +02:00
committed by plegall
parent 21dc3b8490
commit a3ab495446
12 changed files with 1527 additions and 630 deletions

View File

@@ -14,16 +14,6 @@ if( !defined("PHPWG_ROOT_PATH") )
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
check_status(ACCESS_ADMINISTRATOR);
if (!empty($_POST))
{
check_pwg_token();
check_input_parameter('tags', $_POST, true, PATTERN_ID);
check_input_parameter('selectAction', $_POST, false, '/^[a-zA-Z0-9_-]+$/');
check_input_parameter('edit_list', $_POST, false, '/^\d+(,\d+)*$/');
check_input_parameter('merge_list', $_POST, false, '/^\d+(,\d+)*$/');
check_input_parameter('destination_tag', $_POST, false, PATTERN_ID);
}
// +-----------------------------------------------------------------------+
// | tabs |
// +-----------------------------------------------------------------------+
@@ -37,64 +27,6 @@ $tabsheet->set_id('tags');
$tabsheet->select('');
$tabsheet->assign();
// +-----------------------------------------------------------------------+
// | edit tags |
// +-----------------------------------------------------------------------+
if (isset($_POST['edit_submit']))
{
$query = '
SELECT name
FROM '.TAGS_TABLE.'
;';
$existing_names = array_from_query($query, 'name');
$current_name_of = array();
$query = '
SELECT id, name
FROM '.TAGS_TABLE.'
WHERE id IN ('.$_POST['edit_list'].')
;';
$result = pwg_query($query);
while ($row = pwg_db_fetch_assoc($result))
{
$current_name_of[ $row['id'] ] = $row['name'];
}
$updates = array();
// we must not rename tag with an already existing name
foreach (explode(',', $_POST['edit_list']) as $tag_id)
{
$tag_name = stripslashes($_POST['tag_name-'.$tag_id]);
if ($tag_name != $current_name_of[$tag_id])
{
if (in_array($tag_name, $existing_names))
{
$page['errors'][] = l10n('Tag "%s" already exists', $tag_name);
}
else if (!empty($tag_name))
{
$updates[] = array(
'id' => $tag_id,
'name' => addslashes($tag_name),
'url_name' => trigger_change('render_tag_url', $tag_name),
);
}
}
}
mass_updates(
TAGS_TABLE,
array(
'primary' => array('id'),
'update' => array('name', 'url_name'),
),
$updates
);
pwg_activity('tag', explode(',', $_POST['edit_list']), 'edit');
}
// +-----------------------------------------------------------------------+
// | dulicate tags |
// +-----------------------------------------------------------------------+
@@ -282,67 +214,21 @@ SELECT
}
}
// +-----------------------------------------------------------------------+
// | delete tags |
// +-----------------------------------------------------------------------+
if (isset($_POST['delete']) and isset($_POST['tags']))
{
if (!isset($_POST['confirm_deletion']))
{
$page['errors'][] = l10n('You need to confirm deletion');
}
else
{
$query = '
SELECT name
FROM '.TAGS_TABLE.'
WHERE id IN ('.implode(',', $_POST['tags']).')
;';
$tag_names = array_from_query($query, 'name');
delete_tags($_POST['tags']);
$page['infos'][] = l10n_dec(
'The following tag was deleted', 'The %d following tags were deleted',
count($tag_names)
)
.' : '.implode(', ', $tag_names);
}
}
// +-----------------------------------------------------------------------+
// | delete orphan tags |
// +-----------------------------------------------------------------------+
$message_tags = "";
if (isset($_GET['action']) and 'delete_orphans' == $_GET['action'])
{
check_pwg_token();
delete_orphan_tags();
$_SESSION['page_infos'] = array(l10n('Orphan tags deleted'));
$message_tags = array(l10n('Orphan tags deleted'));
redirect(get_root_url().'admin.php?page=tags');
}
// +-----------------------------------------------------------------------+
// | add a tag |
// +-----------------------------------------------------------------------+
if (isset($_POST['add']) and !empty($_POST['add_tag']))
{
$ret = create_tag($_POST['add_tag']);
if (isset($ret['error']))
{
$page['errors'][] = $ret['error'];
}
else
{
$page['infos'][] = $ret['info'];
}
}
// +-----------------------------------------------------------------------+
// | template init |
// +-----------------------------------------------------------------------+
@@ -360,6 +246,8 @@ $template->assign(
// | orphan tags |
// +-----------------------------------------------------------------------+
$warning_tags = "";
$orphan_tags = get_orphan_tags();
$orphan_tag_names = array();
@@ -370,14 +258,23 @@ foreach ($orphan_tags as $tag)
if (count($orphan_tag_names) > 0)
{
$page['warnings'][] = sprintf(
l10n('You have %d orphan tags: %s.').' <a href="%s" class="icon-trash">'.l10n('Delete orphan tags').'</a>',
$warning_tags = sprintf(
l10n('You have %d orphan tags: %s.'),
count($orphan_tag_names),
implode(', ', $orphan_tag_names),
get_root_url().'admin.php?page=tags&amp;action=delete_orphans&amp;pwg_token='.get_pwg_token()
'<a
data-tags=\'["'.implode('" ,"', $orphan_tag_names).'"]\'
data-url="'.get_root_url().'admin.php?page=tags&amp;action=delete_orphans&amp;pwg_token='.get_pwg_token().'">'
.l10n('See details').'</a>'
);
}
$template->assign(
array(
'warning_tags' => $warning_tags,
'message_tags' => $message_tags
)
);
// +-----------------------------------------------------------------------+
// | form creation |
// +-----------------------------------------------------------------------+
@@ -423,38 +320,6 @@ $template->assign(
)
);
if ((isset($_POST['edit']) or isset($_POST['duplicate']) or isset($_POST['merge'])) and isset($_POST['tags']))
{
$list_name = 'EDIT_TAGS_LIST';
if (isset($_POST['duplicate']))
{
$list_name = 'DUPLIC_TAGS_LIST';
}
elseif (isset($_POST['merge']))
{
$list_name = 'MERGE_TAGS_LIST';
}
$template->assign($list_name, implode(',', $_POST['tags']));
$query = '
SELECT id, name
FROM '.TAGS_TABLE.'
WHERE id IN ('.implode(',', $_POST['tags']).')
;';
$result = pwg_query($query);
while ($row = pwg_db_fetch_assoc($result))
{
$template->append(
'tags',
array(
'ID' => $row['id'],
'NAME' => $row['name'],
)
);
}
}
// +-----------------------------------------------------------------------+
// | sending html code |
// +-----------------------------------------------------------------------+

View File

@@ -513,12 +513,12 @@ input:focus + .slider {
color:#a0a0a0;
}
.SelectionModeGroup button{
#selection-mode-block button{
border: 1px solid #e7e7e7;
}
.SelectionModeGroup button:hover{
#selection-mode-block button:hover{
background-color: #ffa744;
border: 1px solid #ffa744;
}

View File

@@ -125,4 +125,137 @@ function sprintf() {
}
return o.join('');
}
// Class to implement a temporary state and reverse it
class TemporaryState {
//Arrays to reverse changes
attrChanges = []; //Attribute changes : {object(s), attribute, value}
classChanges = []; //Class changes : {object(s), state(add:true/remove:false), class}
htmlChanges = []; //Html changes : {object(s), html}
/**
* Change temporaly an attribute of an object
* @param {Jquery Object(s)} obj HTML Object(s)
* @param {String} attr Attribute
* @param {String} tempVal Temporary value of the attribute
*/
changeAttribute(obj, attr, tempVal) {
for (let i = 0; i < obj.length; i++) {
this.attrChanges.push({
object: $(obj[i]),
attribute: attr,
value: $(obj[i]).attr(attr)
})
}
obj.attr(attr, tempVal)
}
/**
* Add/remove a class temporarily
* @param {Jquery Object(s)} obj HTML Object
* @param {Boolean} st Add (true) or Remove (false) the class
* @param {String} loadclass Class Name
*/
changeClass(obj, st, tempclass) {
for (let i = 0; i < obj.length; i++) {
if (!($(obj[i]).hasClass(tempclass) && st)) {
this.classChanges.push({
object: $(obj[i]),
state: !st,
class: tempclass
})
if (st)
$(obj[i]).addClass(tempclass)
else
$(obj[i]).removeClass(tempclass)
}
}
}
/**
* Add temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
addClass(obj, tempclass) {
this.changeClass(obj, true, tempclass);
}
/**
* Remove temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
removeClass(obj, tempclass) {
this.changeClass(obj, false, tempclass);
}
/**
* Change temporaly the html of objects (remove event handlers on the actual content)
* @param {Jquery Object(s)} obj
* @param {string} temphtml
*/
changeHTML(obj, temphtml) {
for (let i = 0; i < obj.length; i++) {
this.htmlChanges.push({
object:$(obj[i]),
html:$(obj[i]).html()
})
}
obj.html(temphtml);
}
/**
* Reverse all the changes and clear the history
*/
reverse() {
this.attrChanges.forEach(function(change) {
if (change.value == undefined) {
change.object.removeAttr(change.attribute);
} else {
change.object.attr(change.attribute, change.value)
}
})
this.classChanges.forEach(function(change) {
if (change.state)
change.object.addClass(change.class)
else
change.object.removeClass(change.class)
})
this.htmlChanges.forEach(function(change) {
change.object.html(change.html);
})
this.attrChanges = [];
this.classChanges = [];
this.htmlChanges = [];
}
}
const jConfirm_alert_options = {
icon: 'icon-ok',
titleClass: "jconfirmAlert",
theme:"modern",
closeIcon: true,
draggable: false,
animation: "zoom",
boxWidth: '20%',
useBootstrap: false,
backgroundDismiss: true,
animateFromElement: false,
typeAnimated: false,
}
const jConfirm_confirm_options = {
draggable: false,
titleClass: "jconfirmDeleteConfirm",
theme: "modern",
content: "",
animation: "zoom",
boxWidth: '30%',
useBootstrap: false,
type: 'red',
animateFromElement: false,
backgroundDismiss: true,
typeAnimated: false,
}

View File

@@ -1,17 +1,4 @@
const DELAY_FEEDBACK = 3000;
const jConfirm_alert_options = {
icon: 'icon-ok',
titleClass: "groupAlert",
theme:"modern",
closeIcon: true,
draggable: false,
animation: "zoom",
boxWidth: '20%',
useBootstrap: false,
backgroundDismiss: true,
animateFromElement: false,
typeAnimated: false,
}
/*-------
Group Popin
-------*/
@@ -265,7 +252,7 @@ var deleteGroup = function (id) {
$.confirm({
title: str_delete.replace("%s",$("#group-"+id+" #group_name").html()),
draggable: false,
titleClass: "groupDeleteConfirm",
titleClass: "jconfirmDeleteConfirm",
theme: "modern",
content: "",
animation: "zoom",
@@ -939,109 +926,4 @@ $(".input-user-name").on("input", function() {
while ($(".UsersInGroupList").height() > maxOffsetUserCont) {
$(".UsernameBlock").last().remove();
}
})
// Class to implement a temporary state and reverse it
class TemporaryState {
//Arrays to reverse changes
attrChanges = []; //Attribute changes : {object(s), attribute, (old) value}
classChanges = []; //Class changes : {object(s), state(add:true/remove:false), class}
htmlChanges = []; //Html changes : {object(s), (old) html}
/**
* Change temporaly an attribute of an object
* @param {Jquery Object(s)} obj HTML Object(s)
* @param {String} attr Attribute
* @param {String} tempVal Temporary value of the attribute
*/
changeAttribute(obj, attr, tempVal) {
for (let i = 0; i < obj.length; i++) {
this.attrChanges.push({
object: $(obj[i]),
attribute: attr,
value: $(obj[i]).attr(attr)
})
}
obj.attr(attr, tempVal)
}
/**
* Add/remove a class temporarily
* @param {Jquery Object(s)} obj HTML Object
* @param {Boolean} st Add (true) or Remove (false) the class
* @param {String} loadclass Class Name
*/
changeClass(obj, st, tempclass) {
for (let i = 0; i < obj.length; i++) {
if (!($(obj[i]).hasClass(tempclass) && st)) {
this.classChanges.push({
object: $(obj[i]),
state: !st,
class: tempclass
})
if (st)
$(obj[i]).addClass(tempclass)
else
$(obj[i]).removeClass(tempclass)
}
}
}
/**
* Add temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
addClass(obj, tempclass) {
this.changeClass(obj, true, tempclass);
}
/**
* Remove temporarily a class to the object
* @param {Jquery Object(s)} obj
* @param {string} tempclass
*/
removeClass(obj, tempclass) {
this.changeClass(obj, false, tempclass);
}
/**
* Change temporaly the html of objects (remove event handlers on the actual content)
* @param {Jquery Object(s)} obj
* @param {string} temphtml
*/
changeHTML(obj, temphtml) {
for (let i = 0; i < obj.length; i++) {
this.htmlChanges.push({
object:$(obj[i]),
html:$(obj[i]).html()
})
}
obj.html(temphtml);
}
/**
* Reverse all the changes and clear the history
*/
reverse() {
this.attrChanges.forEach(function(change) {
if (change.value == undefined) {
change.object.removeAttr(change.attribute);
} else {
change.object.attr(change.attribute, change.value)
}
})
this.classChanges.forEach(function(change) {
if (change.state)
change.object.addClass(change.class)
else
change.object.removeClass(change.class)
})
this.htmlChanges.forEach(function(change) {
change.object.html(change.html);
})
this.attrChanges = [];
this.classChanges = [];
this.htmlChanges = [];
}
}
})

View File

@@ -0,0 +1,568 @@
//Orphan tags
$('.tag-warning p a').on('click', () => {
let url = $('.tag-warning p a').data('url');
let tags = $('.tag-warning p a').data('tags');
let str_orphans = str_orphan_tags.replace('%s1', tags.length).replace('%s2', tags.join(', '));
$.confirm({
content : str_orphans,
title : str_delete_orphan_tags,
draggable: false,
theme: "modern",
animation: "zoom",
boxWidth: '30%',
useBootstrap: false,
type: 'red',
animateFromElement: false,
backgroundDismiss: true,
typeAnimated: false,
buttons: {
delete : {
text:str_delete_them,
btnClass: 'btn-red',
action: function() {
window.location.href = url.replaceAll('amp;', '');
}
},
keep : {
text:str_keep_them,
}
}
})
})
//Add a tag
$('.add-tag-container').on('click', function() {
$('#add-tag').addClass('input-mode');
})
$('#add-tag .icon-cancel').on('click', function() {
$('#add-tag').removeClass('input-mode');
})
//Display/Hide tag option
$('.tag-box').each(function() {
setupTagbox($(this))
})
/*-------
Add a tag
-------*/
$('#add-tag').submit(function (e) {
e.preventDefault();
if ($('#add-tag-input').val() != "") {
loadState = new TemporaryState();
loadState.removeClass($('#add-tag .icon-validate'),'icon-plus-circled');
loadState.changeHTML($('#add-tag .icon-validate') , "<i class='icon-spin6 animate-spin'> </i>")
loadState.changeAttribute($('#add-tag .icon-validate'), 'style','pointer-event:none')
addTag($('#add-tag-input').val()).then(function () {
showMessage(str_tag_created.replace('%s', $('#add-tag-input').val()))
loadState.reverse();
$('#add-tag-input').val("");
$('#add-tag').removeClass('input-mode');
}).catch(message => {
loadState.reverse();
showError(message)
})
}
});
$('#add-tag .icon-validate').on('click', function () {
if ($('#add-tag').hasClass('input-mode')) {
$('#add-tag').submit();
}
})
function addTag(name) {
return new Promise((resolve, reject) => {
jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.add",
type: "POST",
data: "name=" + name + "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
newTag = createTagBox(data.result.id, name);
$('.tag-container').prepend(newTag);
setupTagbox(newTag);
resolve();
} else {
reject(str_already_exist.replace('%s', name));
}
},
error : function (err) {
reject(err);
}
})
})
}
function createTagBox(id, name) {
let u_edit = 'admin.php?page=batch_manager&filter=tag-'+id;
let u_view = 'index.php?/tags/'+id+'-'+name.toLowerCase().replace(' ', '_');
let html = $('.tag-template').html()
.replaceAll('%name%', unescape(name))
.replace('%U_VIEW%', u_view)
.replace('%U_EDIT%', u_edit);
newTag = $('<div class="tag-box" data-id='+data.result.id+' data-selected="0">'+html+'</div>');
if ($("#toggleSelectionMode").is(":checked")) {
newTag.addClass('selection');
newTag.find(".in-selection-mode").show();
newTag.find(".not-in-selection-mode").hide();
}
return newTag;
}
/*-------
Setup Tag Box
-------*/
function setupTagbox(tagBox) {
let id = tagBox.data('id');
let name = tagBox.find('.tag-name').html();
//Dropdown options
tagBox.find('.showOptions').on('click', function () {
tagBox.find(".tag-dropdown-block").css('display', 'grid');
})
$(document).mouseup(function (e) {
e.stopPropagation();
let option_is_clicked = false
tagBox.find('.tag-dropdown-action').each(function () {
if (!($(this).has(e.target).length === 0)) {
option_is_clicked = true;
}
})
if (!option_is_clicked) {
tagBox.find(".tag-dropdown-block").hide();
}
});
tagBox.on('click', function() {
if (tagBox.hasClass('selection')) {
if (tagBox.attr('data-selected') == '1') {
tagBox.attr('data-selected', '0');
} else {
tagBox.attr('data-selected', '1');
}
updateListItem();
}
})
//Edit Name
tagBox.find('.tag-dropdown-action.edit').on('click', function() {
tagBox.addClass('edit-name');
})
tagBox.find('.tag-rename .icon-cancel').on('click', function() {
tagBox.removeClass('edit-name');
})
tagBox.find('.tag-rename .validate').on('click', function() {
tagBox.find('.tag-rename form').submit();
})
tagBox.find('.tag-rename form').submit(function (e) {
e.preventDefault();
new_name = tagBox.find('.tag-rename .tag-name-editable').val();
if (new_name != "") {
let loadState = new TemporaryState();
loadState.removeClass(tagBox.find('.tag-rename .validate'), 'icon-ok');
loadState.changeHTML(tagBox.find('.tag-rename .validate'), "<i class='icon-spin6 animate-spin'> </i>");
renameTag(id, new_name).then(() => {
showMessage(str_tag_renamed.replace('%s1', name).replace('%s2', new_name));
loadState.reverse();
tagBox.removeClass('edit-name');
name = new_name;
}).catch((message) => {
loadState.reverse();
showError(message);
})
}
})
//Delete Tag
tagBox.find('.tag-dropdown-action.delete').on('click', function () {
$.confirm({
title: str_delete.replace("%s",name),
buttons: {
confirm: {
text: str_yes_delete_confirmation,
btnClass: 'btn-red',
action: function () {
removeTag(id, name);
},
},
cancel: {
text: str_no_delete_confirmation
}
},
...jConfirm_confirm_options
})
})
//Duplicate Tag
tagBox.find('.tag-dropdown-action.duplicate').on('click', function () {
duplicateTag(id, name).then((data) => {
showMessage(str_tag_created.replace('%s',data.result.name))
})
})
}
function removeTag(id, name) {
$.alert({
title : str_tag_deleted.replace("%s",name),
content: function() {
return jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.delete",
type: "POST",
data: "tag_id=" + id + "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
showMessage(str_tag_deleted.replace('%s', name));
if (data.stat === "ok") {
$('.tag-box[data-id='+id+']').remove();
}
}
})
},
...jConfirm_alert_options
});
}
function renameTag(id, new_name) {
return new Promise((resolve, reject) => {
jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.rename",
type: "POST",
data: "tag_id=" + id + "&new_name=" + new_name + "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
$('.tag-box[data-id='+id+'] p').html(data.result.name);
$('.tag-box[data-id='+id+'] .tag-name-editable').attr('placeholder', data.result.name);
resolve(data);
} else {
reject(str_already_exist.replace('%s', new_name))
}
},
error:function(XMLHttpRequest) {
reject(XMLHttpRequest.statusText);
}
})
})
}
function duplicateTag(id, name) {
return new Promise((resolve, reject) => {
copy_name = name + str_copy;
let name_exist = function(name) {
exist = false;
$(".tag-box .tag-name").each(function () {
if ($(this).html() === name)
exist = true
})
return exist;
}
let i = 1;
while (name_exist(copy_name))
{
copy_name = name + str_other_copy.replace("%s", i++)
}
jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.duplicate",
type: "POST",
data: "tag_id=" + id + "&copy_name=" + copy_name + "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
newTag = createTagBox(data.result.id, data.result.name);
newTag.insertAfter($('.tag-box[data-id='+id+']'));
if ($('.tag-box[data-id='+id+'] .tag-dropdown-action.view').css('display') == 'inline') {
newTag.find('.tag-dropdown-action.view').show();
newTag.find('.tag-dropdown-action.manage').show();
}
setupTagbox(newTag);
resolve(data);
}
},
error:function(XMLHttpRequest) {
reject(XMLHttpRequest.statusText);
}
})
})
}
/*-------
Selection mode
-------*/
$("#toggleSelectionMode").attr("checked", false)
$("#toggleSelectionMode").click(function () {
if ($(this).is(":checked")) {
$(".in-selection-mode").show();
$(".not-in-selection-mode").removeAttr('style');
$(".tag-box").addClass("selection");
$(".tag-box").removeClass('edit-name');
} else {
$(".in-selection-mode").removeAttr('style');
$(".not-in-selection-mode").show();
$(".tag-box").removeClass("selection");
$(".tag-box").attr("data-selected", '0');
updateListItem();
}
});
function updateListItem() {
let nowSelected = [];
let selected = [];
let shouldBeItem = [];
let shouldNotBeItem = [];
let names = {};
$('.tag-box[data-selected="1"]').each(function () {
let id = $(this).attr('data-id');
nowSelected.push(id);
names[id] = $(this).find('.tag-name').html();
});
$('.selection-mode-tag .tag-list div').each(function () {
let id = $(this).attr('data-id');
selected.push(id);
});
shouldNotBeItem = [...selected];
shouldNotBeItem = shouldNotBeItem.filter(x => !nowSelected.includes(x));
shouldBeItem = [...nowSelected];
shouldBeItem = shouldBeItem.filter(x => !selected.includes(x));
selected = nowSelected;
shouldBeItem.forEach(function(id) {
let newItemStructure = $('<div data-id="'+id+'"><a class="icon-cancel"></a><p>'+names[id]+'</p> </div>');
$('.selection-mode-tag .tag-list').prepend(newItemStructure);
$('.selection-mode-tag .tag-list div[data-id='+id+'] a').on('click', function () {
$('.tag-box[data-id='+id+']').attr('data-selected', '0');
updateListItem();
})
})
shouldNotBeItem.forEach(function(id) {
$('.selection-mode-tag .tag-list div[data-id='+id+']').remove();
})
$('#MergeOptionsChoices').html('');
nowSelected.forEach(id => {
$('#MergeOptionsChoices').append(
$('<option value="'+id+'">'+names[id]+'</option>')
)
})
updateSelectionContent()
}
mergeOption = false;
function updateSelectionContent() {
number = $('.tag-box[data-selected="1"]').length;
if (number == 0) {
$('#nothing-selected').show();
$('.selection-mode-tag').hide();
$('#MergeOptionsBlock').hide();
} else if (number == 1) {
mergeOption = false;
$('#nothing-selected').hide();
$('.selection-mode-tag').show();
$('#MergeOptionsBlock').hide();
$('#MergeSelectionMode').addClass('unavailable');
} else if (number > 1) {
$('#MergeSelectionMode').removeClass('unavailable');
if (mergeOption) {
$('#MergeOptionsBlock').show();
$('.selection-mode-tag').hide();
} else {
$('#MergeOptionsBlock').hide();
$('.selection-mode-tag').show();
}
}
}
$('#MergeSelectionMode').on('click', function() {
mergeOption = true;
updateSelectionContent()
});
$('#CancelMerge').on('click', function() {
mergeOption = false;
updateSelectionContent()
});
/*-------
Actions in selection mode
-------*/
//Remove tags
$('#DeleteSelectionMode').on('click', function() {
names = [];
$('.tag-box[data-selected=1]').each(function() {
names.push($(this).find('.tag-name').html());
})
$.confirm({
title: str_delete_tags.replace("%s",names.join(', ')),
buttons: {
confirm: {
text: str_yes_delete_confirmation,
btnClass: 'btn-red',
action: function () {
removeSelectedTags();
}
},
cancel: {
text: str_no_delete_confirmation
}
},
...jConfirm_confirm_options
});
})
function removeSelectedTags() {
str_id = "";
names = [];
ids = [];
$('.tag-box[data-selected=1]').each(function() {
id = $(this).data('id');
ids.push(id);
names.push($(this).find('.tag-name').html());
str_id += "tag_id[]=" + id + "&";
})
console.log(names);
$.alert({
title : str_tags_deleted.replace("%s",names.join(', ')),
content: function() {
return jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.delete",
type: "POST",
data: str_id + "pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
ids.forEach(function(id) {
$('.tag-box[data-id='+id+']').remove();
})
updateListItem();
showMessage(str_tags_deleted.replace('%s', names.join(', ')));
}
}
})
},
...jConfirm_alert_options
});
}
//Merge Tags
$('.ConfirmMergeButton').on('click',() => {
merge_ids = [];
$('.tag-box[data-selected=1]').each(function() {
merge_ids.push($(this).data('id'))
})
dest_id = $('#MergeOptionsChoices').val();
mergeGroups(dest_id, merge_ids)
})
function mergeGroups(destination_id, merge_ids) {
destination_name = $('.tag-box[data-id='+destination_id+'] .tag-name').html();
merge_name = [];
merge_ids.forEach((id) =>{
merge_name.push($('.tag-box[data-id='+id+'] .tag-name').html());
})
str_message = str_merged_into
.replace('%s1', merge_name.join(', '))
.replace('%s2', destination_name)
$.alert({
title : str_message,
content: function() {
return jQuery.ajax({
url: "ws.php?format=json&method=pwg.tags.merge",
type: "POST",
data: "destination_tag_id=" + destination_id
+ "&merge_tag_id[]=" + merge_ids.join('&merge_tag_id[]=')
+ "&pwg_token=" + pwg_token,
success: function (raw_data) {
data = jQuery.parseJSON(raw_data);
if (data.stat === "ok") {
console.log()
data.result.deleted_tag.forEach((id) => {
if (data.result.destination_tag != id)
$('.tag-box[data-id='+id+']').remove();
})
if (data.result.images_in_merged_tag.length > 0) {
tagBox = $('.tag-box[data-id='+data.result.destination_tag+']')
tagBox.find('.tag-dropdown-action.view, .tag-dropdown-action.manage').show();
}
showMessage(str_message);
$(".tag-box").attr("data-selected", '0');
updateListItem();
}
}
})
},
...jConfirm_alert_options
});
}
/*-------
Filter research
-------*/
$("#search-tag .search-input").on("input", function() {
let text = $(this).val().toLowerCase();
var searchNumber = 0;
$('.tag-box').each(function () {
if (text == "") {
$(this).fadeIn()
searchNumber++;
} else {
let name = $(this).find("p").text().toLowerCase();
if (name.search(text) != -1){
$(this).delay(300).fadeIn()
searchNumber++;
} else {
$(this).fadeOut()
}
}
})
if (searchNumber == 0) {
$('.emptyResearch').delay(300).fadeIn();
} else {
$('.emptyResearch').fadeOut();
}
})
/*-------
Show Info
-------*/
function showError(message) {
$('.tag-error p').html(message);
$('.tag-info').hide()
$('.tag-error').css('display', 'flex');
}
function showMessage(message) {
$('.tag-message p').html(message);
$('.tag-info').hide()
$('.tag-message').css('display', 'flex');
}

View File

@@ -24,6 +24,9 @@ var serverId = '{$CACHE_KEYS._hash}'
var rootUrl = '{$ROOT_URL}'
{/footer_script}
{combine_script id='common' load='footer' path='admin/themes/default/js/common.js'}
{combine_script id='group_list' load='footer' path='admin/themes/default/js/group_list.js'}
{combine_script id='jquery.selectize' load='footer' path='themes/default/js/plugins/selectize.min.js'}
{combine_css path="themes/default/js/plugins/selectize.{$themeconf.colorscheme}.css"}
@@ -33,8 +36,6 @@ var rootUrl = '{$ROOT_URL}'
{combine_css path="themes/default/js/plugins/jquery-confirm.min.css"}
{combine_css path="admin/themes/default/fontello/css/animation.css"}
{combine_script id='common' load='footer' path='admin/themes/default/js/group_list.js'}
{* Define template function for the content of Groups*}
{function name=groupContent}
{function groupContent}

View File

@@ -1,15 +0,0 @@
{footer_script require='jquery'}{literal}
jQuery(document).ready(function(){
jQuery(".tagSelection").on("click", "label", function () {
var parent = jQuery(this).parent('li');
var checkbox = jQuery(this).children("input[type=checkbox]");
if (jQuery(checkbox).is(':checked')) {
parent.addClass("tagSelected");
}
else {
parent.removeClass('tagSelected');
}
});
});
{/literal}{/footer_script}

View File

@@ -1,336 +1,135 @@
{combine_script id='common' load='footer' path='admin/themes/default/js/common.js'}
{include file='include/tag_selection.inc.tpl'}
{html_style}
.showInfo { text-indent:5px; }
form fieldset p { margin-left:0; }
{/html_style}
{footer_script require='jquery'}
/**
* Add tag
*/
jQuery("#addTag").click(function() {
jQuery("#addTagForm").toggle();
jQuery("input[name=add_tag]").focus();
return false;
});
jQuery("#addTagClose").click(function() {
jQuery("#addTagForm").hide();
return false;
});
jQuery("#selectionMode").click(function() {
if (jQuery(this).hasClass("icon-check-empty")) {
jQuery("#selectionMode").removeClass("icon-check-empty").addClass("icon-check");
jQuery('label.font-checkbox span').show();
jQuery('ul.tagSelection a.showInfo').hide();
jQuery('fieldset#action').show();
jQuery('fieldset#selectTags legend').html("{'Tag selection'|translate|escape:javascript}");
}
else {
jQuery("#selectionMode").removeClass("icon-check").addClass("icon-check-empty");
jQuery('label.font-checkbox span').hide();
jQuery('ul.tagSelection a.showInfo').show();
jQuery('fieldset#action').hide();
jQuery('fieldset#selectTags legend').html("{'Tags'|translate|escape:javascript}");
}
return false;
});
jQuery('.showInfo').tipTip({
'delay' : 0,
'fadeIn' : 200,
'fadeOut' : 200,
'maxWidth':'300px',
'keepAlive':true,
'activation':'click'
});
function displayDeletionWarnings() {
jQuery(".warningDeletion").show();
jQuery("input[name=destination_tag]:checked").parent("label").children(".warningDeletion").hide();
}
displayDeletionWarnings();
jQuery("#mergeTags label").click(function() {
displayDeletionWarnings();
});
$("#searchInput").on("keydown", function(e) {
var $this = $(this),
timer = $this.data("timer");
if (timer) {
clearTimeout(timer);
}
$this.data("timer", setTimeout(function() {
var val = $this.val();
if (!val) {
$(".tagSelection>li").show();
$("#filterIcon").css("visibility","hidden");
}
else {
$("#filterIcon").css("visibility","visible");
var regex = new RegExp( val.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"), "i" );
$(".tagSelection>li").each(function() {
var $li = $(this),
text = $.trim( $("label", $li).text() );
$li.toggle(regex.test(text));
});
}
}, 300) );
if (e.keyCode == 13) { // Enter
e.preventDefault();
}
});
jQuery('input[name="tags[]"]').click(function() {
var nbSelected = 0;
nbSelected = jQuery('input[name="tags[]"]').filter(':checked').length;
if (nbSelected == 0) {
jQuery("#permitAction").hide();
jQuery("#forbidAction").show();
}
else {
jQuery("#permitAction").show();
jQuery("#forbidAction").hide();
}
});
jQuery("[id^=action_]").hide();
jQuery("select[name=selectAction]").change(function () {
jQuery("[id^=action_]").hide();
jQuery("#action_"+jQuery(this).prop("value")).show();
jQuery("#displayFormBlock").hide();
jQuery("#applyActionBlock").hide();
if (jQuery(this).val() != -1 ) {
if (jQuery(this).val() == 'delete') {
jQuery("#applyActionBlock").show();
jQuery("#applyAction").attr("name", jQuery(this).val());
}
else {
jQuery("#displayForm").attr("name", jQuery(this).val());
jQuery("#displayFormBlock").show();
}
}
else {
}
});
jQuery("form").submit(function() {
if (jQuery("select[name=selectAction]").val() == "delete") {
if (!jQuery("input[name=confirm_deletion]").is(":checked")) {
jQuery("#action_delete .errors").show();
return false;
}
}
if (jQuery("select[name=selectAction]").val() == "merge") {
if (jQuery("ul.tagSelection input[type=checkbox]:checked").length < 2) {
alert("{'Select at least two tags for merging'|@translate}");
return false;
}
}
});
jQuery("input[name=confirm_deletion]").change(function() {
jQuery("#action_delete .errors").hide();
});
{footer_script}
var pwg_token = "{$PWG_TOKEN}";
var str_delete = '{'Delete tag "%s"?'|@translate}'
var str_delete_tags = '{'Delete tags \{%s\}?'|@translate}'
var str_yes_delete_confirmation = "{'Yes, delete'|@translate}"
var str_no_delete_confirmation = "{"No, I have changed my mind"|@translate}"
var str_tag_deleted = '{'Tag "%s" succesfully deleted'|@translate}'
var str_tags_deleted = '{'Tags \{%s\} succesfully deleted'|@translate}'
var str_already_exist = '{'Tag "%s" already exists'|@translate}'
var str_tag_created = '{'Tag "%s" created'|@translate}'
var str_tag_renamed = '{'Tag "%s1" renamed in "%s2"'|@translate}'
var str_delete_orphan_tags = '{'Delete orphan tags ?'|@translate}'
var str_orphan_tags = '{'You have %s1 orphan : %s2'|@translate}';
var str_delete_them = '{'Delete them'|@translate}';
var str_keep_them = '{'Keep them'|@translate}';
var str_copy = '{' (copy)'|@translate}'
var str_other_copy = '{' (copy %s)'|@translate}'
var str_merged_into = '{'Tag(s) \{%s1\} succesfully merged into "%s2"'|@translate}'
{/footer_script}
{combine_script id='common' load='footer' path='admin/themes/default/js/common.js'}
{combine_script id='jquery.confirm' load='footer' require='jquery' path='themes/default/js/plugins/jquery-confirm.min.js'}
{combine_css path="themes/default/js/plugins/jquery-confirm.min.css"}
{combine_css path="admin/themes/default/fontello/css/animation.css"}
{combine_script id='tiptip' load='header' path='themes/default/js/plugins/jquery.tipTip.minified.js'}
{combine_script id='tags' load='footer' path='admin/themes/default/js/tags.js'}
{function name=tagContent}
{function tagContent}
<p class='tag-name'>{$tag_name}</p>
<a class="icon-ellipsis-vert showOptions not-in-selection-mode"></a>
<div class="tag-dropdown-block">
<a class='tag-dropdown-action icon-eye view' href="{$tag_U_VIEW}" {if !$has_image} style='display:none' {/if}>{'View in gallery'|@translate}</a>
<a class='tag-dropdown-action icon-picture manage' href="{$tag_U_EDIT}" {if !$has_image} style='display:none' {/if}>{'Manage photos'|@translate}</a>
<a class='tag-dropdown-action icon-pencil edit'> {'Edit'|@translate}</a>
<a class='tag-dropdown-action icon-trash delete'> {'Delete'|@translate}</a>
<a class='tag-dropdown-action icon-docs duplicate'> {'Duplicate'|@translate}</a>
</div>
<span class="select-checkbox in-selection-mode">
<i class="icon-ok"> </i>
</span>
<div class="tag-rename">
<form>
<input type="text" class="tag-name-editable" placeholder="{$tag_name}">
<input type="submit" hidden>
</form>
<span class="icon-ok validate"></span>
<span class="icon-cancel"></span>
</div>
{/function}
{/function}
<div class="titrePage">
<h2>{'Manage tags'|@translate}</h2>
</div>
{if !isset($EDIT_TAGS_LIST) and !isset($DUPLIC_TAGS_LIST) and !isset($MERGE_TAGS_LIST)}
<p class="showCreateAlbum" id="showAddTag">
<a class="icon-plus-circled" href="#" id="addTag">{'Add a tag'|translate}</a>
<a class="icon-check-empty" href="#" id="selectionMode">{'Select tags'|translate}</a>
</p>
<div class="selection-mode-group-manager">
<label class="switch">
<input type="checkbox" id="toggleSelectionMode">
<span class="slider round"></span>
</label>
<p>{'Selection mode'|@translate}</p>
</div>
<form method="post" style="display:none" id="addTagForm" name="add_user" action="{$F_ACTION}" class="properties">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
<div id="selection-mode-block" class="in-selection-mode tag-selection">
<div class="tag-selection-content">
<fieldset class="with-border">
<legend>{'Add a tag'|@translate}</legend>
<p id="nothing-selected">{'No tag selected, no action possible.'|@translate}</p>
<label>
{'New tag'|@translate}
<input type="text" name="add_tag" size="50">
</label>
<div class="selection-mode-tag">
<p>{'Your selection'|@translate}</p>
<div class="tag-list">
</div>
<button id="MergeSelectionMode" class="icon-object-group unavailable">{'Merge'|@translate}</button>
<button id="DeleteSelectionMode" class="icon-trash-1">{'Delete selected tags'|@translate}</button>
</div>
<p class="actionButtons">
<input class="submit" type="submit" name="add" value="{'Submit'|@translate}">
<a href="#" id="addTagClose">{'Cancel'|@translate}</a>
</p>
</fieldset>
</form>
{/if}
<form action="{$F_ACTION}" method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
{if isset($EDIT_TAGS_LIST)}
<fieldset>
<legend>{'Edit tags'|@translate}</legend>
<input type="hidden" name="edit_list" value="{$EDIT_TAGS_LIST}">
<table class="table2">
<tr class="throw">
<th>{'Current name'|@translate}</th>
<th>{'New name'|@translate}</th>
</tr>
{foreach from=$tags item=tag}
<tr>
<td>{$tag.NAME}</td>
<td><input type="text" name="tag_name-{$tag.ID}" value="{$tag.NAME}" size="50"></td>
</tr>
{/foreach}
</table>
<p>
<input type="submit" name="edit_submit" value="{'Submit'|@translate}">
<input type="submit" name="edit_cancel" value="{'Cancel'|@translate}">
</p>
</fieldset>
{/if}
{if isset($DUPLIC_TAGS_LIST)}
<fieldset>
<legend>{'Edit tags'|@translate}</legend>
<input type="hidden" name="edit_list" value="{$DUPLIC_TAGS_LIST}">
<table class="table2">
<tr class="throw">
<th>{'Source tag'|@translate}</th>
<th>{'Name of the duplicate'|@translate}</th>
</tr>
{foreach from=$tags item=tag}
<tr>
<td>{$tag.NAME}</td>
<td><input type="text" name="tag_name-{$tag.ID}" value="{$tag.NAME}" size="50"></td>
</tr>
{/foreach}
</table>
<p>
<input type="submit" name="duplic_submit" value="{'Submit'|@translate}">
<input type="submit" name="duplic_cancel" value="{'Cancel'|@translate}">
</p>
</fieldset>
{/if}
{if isset($MERGE_TAGS_LIST)}
<fieldset id="mergeTags">
<legend>{'Merge tags'|@translate}</legend>
{'Select the destination tag'|@translate}
<p>
{foreach from=$tags item=tag name=tagloop}
<label><input type="radio" name="destination_tag" value="{$tag.ID}"{if $smarty.foreach.tagloop.index == 0} checked="checked"{/if}> {$tag.NAME}<span class="warningDeletion"> {'(this tag will be deleted)'|@translate}</span></label><br>
{/foreach}
</p>
<p>
<input type="hidden" name="merge_list" value="{$MERGE_TAGS_LIST}">
<input type="submit" name="merge_submit" value="{'Confirm merge'|@translate}">
<input type="submit" name="merge_cancel" value="{'Cancel'|@translate}">
</p>
</fieldset>
{/if}
{if !isset($EDIT_TAGS_LIST) and !isset($DUPLIC_TAGS_LIST) and !isset($MERGE_TAGS_LIST)}
<fieldset id="selectTags">
<legend>{'Tags'|@translate}</legend>
{if count($all_tags)}
<div><label><span class="icon-filter" style="visibility:hidden" id="filterIcon"></span>{'Search'|@translate} <input id="searchInput" type="text" size="12"></label></div>
{/if}
<ul class="tagSelection">
{foreach from=$all_tags item=tag}
<li>
{capture name='showInfo'}{strip}
<b>{$tag.name}</b> ({$tag.counter|@translate_dec:'%d photo':'%d photos'})<br>
<a href="{$tag.U_VIEW}">{'View in gallery'|@translate}</a> |
<a href="{$tag.U_EDIT}">{'Manage photos'|@translate}</a>
{if !empty($tag.alt_names)}<br>{$tag.alt_names}{/if}
{/strip}{/capture}
<a class="icon-info-circled-1 showInfo" title="{$smarty.capture.showInfo|@htmlspecialchars}"></a>
<label class="font-checkbox no-bold">
<span class="icon-check" style="display:none"></span>
<input type="checkbox" name="tags[]" value="{$tag.id}">
{$tag.name}
</label>
</li>
{/foreach}
</ul>
</fieldset>
<fieldset id="action" style="display:none">
<legend>{'Action'|@translate}</legend>
<div id="forbidAction">{'No tag selected, no action possible.'|@translate}</div>
<div id="permitAction" style="display:none">
<select name="selectAction">
<option value="-1">{'Choose an action'|@translate}</option>
<option disabled="disabled">------------------</option>
<option value="edit">{'Edit selected tags'|@translate}</option>
<option value="duplicate">{'Duplicate selected tags'|@translate}</option>
<option value="merge">{'Merge selected tags'|@translate}</option>
<option value="delete">{'Delete selected tags'|@translate}</option>
{if !empty($tag_manager_plugin_actions)}
{foreach from=$tag_manager_plugin_actions item=action}
<option value="{$action.ID}">{$action.NAME}</option>
{/foreach}
{/if}
<div id="MergeOptionsBlock">
<p>{'Choose which tag to merge these tags into'|@translate}</p>
<p class="ItalicTextInfo">{'The other tags will be removed'|@translate}</p>
<div class="MergeOptionsContainer">
<select id="MergeOptionsChoices">
</select>
</div>
<button class="icon-ok ConfirmMergeButton">Confirm merge</button>
<a id="CancelMerge">Cancel</a>
</div>
<!-- delete -->
<div id="action_delete" class="bulkAction">
<p>
<label class="font-checkbox">
<span class="icon-check"></span>
<input type="checkbox" name="confirm_deletion" value="1">
{'Are you sure?'|@translate}
</label>
<span class="errors" style="display:none"><i class="icon-cancel"></i> we really need you to confirm</span>
</p>
</div>
</div>
</div>
{* plugins *}
{if !empty($tag_manage_plugin_actions)}
{foreach from=$element_set_groupe_plugins_actions item=action}
<div id="action_{$action.ID}" class="bulkAction">
{if !empty($action.CONTENT)}{$action.CONTENT}{/if}
</div>
<div class='tag-header'>
<div id='search-tag'>
<span class='icon-filter'> </span>
<input class='search-input' type='text' placeholder='{'Search'|@translate}'>
</div>
<form id='add-tag'>
<span class='icon-cancel'></span>
<span class='icon-plus-circled icon-validate'></span>
<label class='add-tag-container'>
<p>{'Add a tag'|@translate}</p>
<input type='text' id='add-tag-input' placeholder="{'Add a tag'|@translate}">
<input type='submit' hidden>
</label>
</form>
{if $warning_tags != ""}
<div class='tag-warning tag-info icon-attention'><p> {$warning_tags} </p></div>
{/if}
<div class='tag-message tag-info icon-ok' {if $message_tags != ""}style='display:flex'{/if}> <p> {$message_tags} </p> </div>
<div class='tag-error tag-info icon-cancel'> <p> </p> </div>
</div>
<div class='tag-container'>
{foreach from=$all_tags item=tag}
<div class='tag-box' data-id='{$tag.id}' data-selected='0'>
{tagContent
tag_name=$tag.name
tag_U_VIEW=$tag.U_VIEW
tag_U_EDIT=$tag.U_EDIT
has_image=($tag.counter > 0)
}
</div>
{/foreach}
{/if}
<span id="displayFormBlock" style="display:none">
<button id="displayForm" class="buttonLike" type="submit" name="">{'Display form'|translate} <i class="icon-right"></i></button>
</span>
</div>
<div class="emptyResearch"> {'No tag found'|@translate} </div>
<p id="applyActionBlock" style="display:none" class="actionButtons">
<button id="applyAction" name="submit" type="submit" class="buttonLike">
<i class="icon-trash"></i> {'Apply action'|translate} {* icon-trash because the only action is deletion *}
</button>
<span id="applyOnDetails"></span>
</p>
</div> {* #permitAction *}
</fieldset>
{/if}
</form>
<div class='tag-template' style='display:none'>
{tagContent
tag_name='%name%'
tag_U_VIEW='%U_VIEW%'
tag_U_EDIT='%U_EDIT%'
has_image=false
}
</div>

View File

@@ -2194,7 +2194,7 @@ input[type="text"].dError {border-color:#ff7070; background-color:#FFe5e5;}
.selection-mode-group-manager{
position:absolute;
right:15px;
z-index:1;
z-index:11;
}
.switch {
@@ -2257,9 +2257,9 @@ input:checked + .slider:before {
position: absolute;
right: 0;
width: 223px;
min-height: 584px;
height:calc(100% - 171px);
min-height:calc(100% - 171px);
top: 169.5px;
z-index: 10;
}
.Selection-mode-content{
@@ -2304,7 +2304,7 @@ input:checked + .slider:before {
white-space: nowrap;
}
.SelectionModeGroup button{
#selection-mode-block button{
display:block;
margin:20px auto;
font-size: 12px;
@@ -2314,11 +2314,11 @@ input:checked + .slider:before {
width: 180px;
}
.SelectionModeGroup button:hover{
#selection-mode-block button:hover{
cursor: pointer;
}
.SelectionModeGroup button.unavailable{
#selection-mode-block button.unavailable{
opacity: 0.3;
background: none !important;
border: none !important;
@@ -2503,26 +2503,26 @@ input:checked + .slider:before {
font-size: 13px;
}
.groupDeleteConfirm, .groupAlert {
.jconfirmDeleteConfirm, .jconfirmAlert {
padding-bottom: 0 !important;
color: #3c3c3c !important;
line-height: 28px !important;
}
.groupAlert {
.jconfirmAlert {
margin-bottom: -2px !important;
}
.groupDeleteConfirm ~ .jconfirm-content-pane, .groupAlert ~ .jconfirm-content-pane {
.jconfirmDeleteConfirm ~ .jconfirm-content-pane, .jconfirmAlert ~ .jconfirm-content-pane {
height: 0px !important;
margin: 0px !important;
}
.groupDeleteConfirm ~ .jconfirm-buttons button {
.jconfirmDeleteConfirm ~ .jconfirm-buttons button {
text-transform: none !important;
}
.groupAlert .jconfirm-icon-c i {
.jconfirmAlert .jconfirm-icon-c i {
color: #0a0 !important;
background-color:#c2f5c2 !important;
border-radius: 20px;
@@ -2532,11 +2532,11 @@ input:checked + .slider:before {
font-size: 45px;
}
.groupAlert .jconfirm-icon-c {
.jconfirmAlert .jconfirm-icon-c {
margin-bottom: 25px !important;
}
.groupAlert .jconfirm-title {
.jconfirmAlert .jconfirm-title {
font-size: 20px !important;
}
@@ -3223,4 +3223,344 @@ li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important;
}
.selectedAlbum.cat-list-album-path span {
background-color: transparent;
}
.tag-header {
display: flex;
flex-wrap: nowrap;
align-items: center;
margin-left: 10px;
}
.tag-header #search-tag{
position: relative;
margin-left: 10px;
}
.tag-header #search-tag .search-input{
padding: 10px;
box-shadow: 0px 2px #00000024;
border: none;
background-color: #fafafa;
padding-left: 30px;
width: 300px;
}
.tag-header #search-tag span {
position: absolute;
top: 50%;
transform: translate(4px, -50%);
font-size: 18px;
}
.tag-header #add-tag {
position: relative;
}
.tag-header #add-tag .add-tag-container {
position: relative;
padding: 10px;
background-color: #fafafa;
padding-left: 30px;
box-shadow: 0px 2px #00000024;
border-radius: 5px;
font-weight: bold;
display: inline-flex;
transition: ease 0.2s;
cursor: pointer;
}
.tag-header #add-tag .add-tag-container:hover {
background-color: #f0f0f0;
}
.tag-header #add-tag.input-mode .add-tag-container {
width: 200px;
cursor: auto;
}
.tag-header #add-tag input[type=text] {
position: absolute;
left: 5%;
background: none;
width: calc(90% - 40px);
border: none;
opacity: 0;
display: none;
transition: ease 0.2s;
}
.tag-header #add-tag span{
z-index: 100;
cursor: pointer;
}
.tag-header #add-tag.input-mode input[type=text] {
opacity: 1;
display: block;
}
.tag-header #add-tag .icon-validate {
position: absolute;
top: 50%;
transform: translate(5px, -50%);
font-size: 18px;
position: absolute;
left: 0;
right: auto;
transition: transform ease 0.8s, color ease 0.2s;
}
.tag-header #add-tag.input-mode .icon-validate{
transform: translate(180px, -50%) rotate(360deg);
color: #FFA646;
font-size: 22px;
cursor: pointer;
}
.tag-header #add-tag.input-mode span:hover{
color: #ff7700;
}
.tag-header #add-tag p {
margin: 0;
white-space: nowrap;
}
.tag-header #add-tag.input-mode p {
opacity: 0;
}
.tag-header #add-tag .icon-cancel {
position: absolute;
top: 50%;
transform: translate(209px, -50%);
font-size: 18px;
position: absolute;
left: 0;
transition: ease 0.2s;
display: none;
opacity: 0;
}
.tag-header #add-tag.input-mode .icon-cancel {
display: block;
opacity: 1;
}
.tag-info {
height: 35px;
overflow: hidden;
border-radius: 20px;
display: flex;
padding: 0px 10px;
font-weight: bold;
}
.tag-info p {
margin: auto;
white-space: nowrap;
}
.tag-info::before {
line-height: 35px;
margin: 0px 10px;
font-size: 16px;
}
.tag-info.tag-warning {
color: #ee8800;
background-color:#ffdd99;
}
.tag-info.tag-message {
color: #0a0;
background-color:#c2f5c2;
display: none;
}
.tag-info.tag-error {
color: #f22;
background-color: #ffd5dc;
display: none;
animation-name: tag-error-appear ;
animation-duration: 0.4s;
animation-timing-function: ease;
}
@keyframes tag-error-appear {
25% { transform: translateX(-10px)}
50% { transform: translateX(10px)}
75% { transform: translateX(-10px)}
100% { transform: translateX(0px)}
}
.tag-container {
display: flex;
padding: 25px;
flex-flow: wrap;
padding-right: 223px;
}
.tag-container .tag-box{
display: inline-flex;
align-items: center;
padding: 10px;
background-color: #fafafa;
margin: 5px;
box-shadow: 0px 2px 1px #00000024;
border-radius: 18px;
position: relative;
}
.tag-container .tag-box.selection {
opacity: 0.6;
cursor: pointer;
}
.tag-container .tag-box[data-selected='1'] {
opacity: 1;
}
.tag-container .tag-box p {
white-space: nowrap;
margin: 0;
}
.tag-container .tag-box.edit-name p {
display: none;
}
.tag-container .tag-box.edit-name .showOptions {
display: none;
}
.tag-container .tag-box .tag-dropdown-block {
display:none;
position:absolute;
right: 0;
top: 30px;
background-color:white;
z-index:2;
padding:5px 0px;
box-shadow: 0px 0px 5px #d7d7d7;
border-radius: 10px;
transform: translateX(85%);
}
.tag-container .tag-box .tag-dropdown-block .tag-dropdown-action {
white-space: nowrap;
text-align: initial;
padding: 5px 10px;
font-size: 13px;
}
.tag-container .tag-box .tag-dropdown-block .tag-dropdown-action:hover {
color: #3A3A3A;
text-decoration: none;
background-color: #e7e7e7;
}
.tag-container .tag-box .select-checkbox {
display: none;
width: 18.4px;
height: 19px;
background-color: #e7e7e7;
border-radius: 100%;
margin: 2px 0px;
margin-left: 0px;
margin-left: 4px;
}
.tag-container .tag-box[data-selected='1'] .select-checkbox {
background-color: #ddd;
}
.tag-container .tag-box .select-checkbox i {
display: none;
font-size: 20px;
transform: translate(-12px , -6px);
position: absolute;
animation-name: icon-check-animation;
animation-duration: 0.4s;
animation-timing-function: ease-out;
}
.tag-container .tag-box[data-selected='1'] .select-checkbox i {
display: inline;
}
@keyframes icon-check-animation {
0% {
transform: translate(-12px , -6px) scale(0);
}
75% {
transform: translate(-12px , -6px) scale(1.3);
}
100% {
transform: translate(-12px , -6px);
}
}
.tag-container .tag-box .tag-rename {
display: none;
}
.tag-container .tag-box.edit-name .tag-rename {
display: flex;
}
.tag-container .tag-box .tag-rename .tag-name-editable {
text-align: left;
width: 100px;
color: #3c3c3c;
font-family: "Open Sans", "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
margin: 0px;
background-color: white;
border: 1px solid #d5d5d5;
}
.tag-container .tag-box .tag-rename span:hover {
color: #ffa744;
}
.tag-container .tag-box .tag-rename span {
padding: 2px 3px;
margin: auto;
cursor: pointer;
color: #3A3A3A;
}
.tag-selection .tag-selection-content {
margin-top: 90px;
padding: 5px;
}
.tag-selection .tag-selection-content .selection-mode-tag{
display: none;
}
.selection-mode-tag .tag-list {
margin: 10px;
text-align: start;
font-weight: 700;
font-size: 15px;
}
.selection-mode-tag .tag-list p {
width: 85%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
color: #a0a0a0;
margin: 0;
}
.selection-mode-tag .tag-list div {
display: flex;
margin: 10px;
text-align: start;
}

View File

@@ -558,13 +558,13 @@ input:focus + .slider {
color:#c0c0c0;
}
.SelectionModeGroup button{
#selection-mode-block button{
border: 1px solid #e7e7e7;
color:#c0c0c0;
}
.SelectionModeGroup button:hover{
#selection-mode-block button:hover{
background-color: #ffa744;
border: 1px solid #ffa744;
color:#3c3c3c;
@@ -862,7 +862,7 @@ li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important;
color: #777 !important;
}
.groupAlert .jconfirm-icon-c i {
.jconfirmAlert .jconfirm-icon-c i {
color:#c2f5c2 !important;
background-color:#0a0 !important;
}

View File

@@ -220,14 +220,281 @@ function ws_tags_add($params, &$service)
{
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
if (get_pwg_token() != $params['pwg_token'])
{
return new PwgError(403, 'Invalid security token');
}
$creation_output = create_tag($params['name']);
if (isset($creation_output['error']))
{
return new PwgError(500, $creation_output['error']);
return new PwgError(WS_ERR_INVALID_PARAM, $creation_output['error']);
}
pwg_activity('tag', $creation_output['id'], 'add');
return $creation_output;
}
function ws_tags_delete($params, &$service)
{
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
if (get_pwg_token() != $params['pwg_token'])
{
return new PwgError(403, 'Invalid security token');
}
$query = '
SELECT COUNT(*)
FROM `'. TAGS_TABLE .'`
WHERE id in ('.implode(',', $params['tag_id']) .')
;';
list($count) = pwg_db_fetch_row(pwg_query($query));
if ($count != count($params['tag_id']))
{
return new PwgError(WS_ERR_INVALID_PARAM, 'All tags does not exist.');
}
$tag_ids = $params['tag_id'];
if (count($tag_ids) > 0)
{
delete_tags($params['tag_id']);
return array('id' => $tag_ids);
foreach ($tag_ids as $ids) {
pwg_activity('tag', $creation_output['id'], 'delete');
}
} else {
return array('id' => array());
}
}
function ws_tags_rename($params, &$service)
{
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
if (get_pwg_token() != $params['pwg_token'])
{
return new PwgError(403, 'Invalid security token');
}
$tag_id = $params['tag_id'];
$tag_name = $params['new_name'];
// does the tag exist ?
$query = '
SELECT COUNT(*)
FROM `'. TAGS_TABLE .'`
WHERE id = '. $tag_id .'
;';
list($count) = pwg_db_fetch_row(pwg_query($query));
if ($count == 0)
{
return new PwgError(WS_ERR_INVALID_PARAM, 'This tag does not exist.');
}
$query = '
SELECT name
FROM '.TAGS_TABLE.'
WHERE id != '.$tag_id.'
;';
$existing_names = array_from_query($query, 'name');
$update = array();
if (in_array($tag_name, $existing_names))
{
return new PwgError(WS_ERR_INVALID_PARAM, 'This name is already token');
}
else if (!empty($tag_name))
{
$update = array(
'name' => addslashes($tag_name),
'url_name' => trigger_change('render_tag_url', $tag_name),
);
}
pwg_activity('tag', $tag_id, 'edit');
single_update(
TAGS_TABLE,
$update,
array('id' => $tag_id)
);
return array(
'id' => $tag_id,
'name' => addslashes($tag_name),
'url_name' => trigger_change('render_tag_url', $tag_name)
);
}
function ws_tags_duplicate($params, &$service) {
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
if (get_pwg_token() != $params['pwg_token'])
{
return new PwgError(403, 'Invalid security token');
}
$tag_id = $params['tag_id'];
$copy_name = $params['copy_name'];
// does the tag exist ?
$query = '
SELECT COUNT(*)
FROM `'. TAGS_TABLE .'`
WHERE id = '. $tag_id .'
;';
list($count) = pwg_db_fetch_row(pwg_query($query));
if ($count == 0)
{
return new PwgError(WS_ERR_INVALID_PARAM, 'This tag does not exist.');
}
$query = '
SELECT COUNT(*)
FROM `'. TAGS_TABLE .'`
WHERE name = "'. $copy_name .'"
;';
list($count) = pwg_db_fetch_row(pwg_query($query));
if ($count != 0)
{
return new PwgError(WS_ERR_INVALID_PARAM, 'This name is already taken.');
}
single_insert(
TAGS_TABLE,
array(
'name' => $copy_name,
'url_name' => trigger_change('render_tag_url', $copy_name),
)
);
$destination_tag_id = pwg_db_insert_id(TAGS_TABLE);
pwg_activity('tag', $destination_tag_id, 'add', array('action'=>'duplicate', 'source_tag'=>$tag_id));
$query = '
SELECT image_id
FROM '.IMAGE_TAG_TABLE.'
WHERE tag_id = '.$tag_id.'
;';
$destination_tag_image_ids = array_from_query($query, 'image_id');
$inserts = array();
foreach ($destination_tag_image_ids as $image_id)
{
$inserts[] = array(
'tag_id' => $destination_tag_id,
'image_id' => $image_id
);
pwg_activity('image', $image_id, 'edit', array("add-tag" => $destination_tag_id));
}
if (count($inserts) > 0)
{
mass_inserts(
IMAGE_TAG_TABLE,
array_keys($inserts[0]),
$inserts
);
}
return array(
'id' => $destination_tag_id,
'name' => $copy_name,
'url_name' => trigger_change('render_tag_url', $copy_name),
);
}
function ws_tags_merge($params, &$service) {
if (get_pwg_token() != $params['pwg_token'])
{
return new PwgError(403, 'Invalid security token');
}
$all_tags = $params['merge_tag_id'];
array_push($all_tags, $params['destination_tag_id']);
$all_tags = array_unique($all_tags);
$merge_tag = array_diff($params['merge_tag_id'], array($params['destination_tag_id']));
$query = '
SELECT COUNT(*)
FROM `'. TAGS_TABLE .'`
WHERE id in ('.implode(',', $all_tags) .')
;';
list($count) = pwg_db_fetch_row(pwg_query($query));
if ($count != count($all_tags))
{
return new PwgError(WS_ERR_INVALID_PARAM, 'All tags does not exist.');
}
$image_in_merge_tags = array();
$image_in_dest = array();
$image_to_add = array();
$query = '
SELECT DISTINCT(image_id)
FROM `'. IMAGE_TAG_TABLE .'`
WHERE
tag_id IN ('.implode(',', $merge_tag) .')
;';
$image_in_merge_tags = query2array($query, null, 'image_id');
$query = '
SELECT image_id
FROM `'. IMAGE_TAG_TABLE .'`
WHERE tag_id = '.$params['destination_tag_id'].'
;';
$image_in_dest = query2array($query, null, 'image_id');;
$image_to_add = array_diff($image_in_merge_tags, $image_in_dest);
$inserts = array();
foreach ($image_to_add as $image)
{
$inserts[] = array(
'tag_id' => $params['destination_tag_id'],
'image_id' => $image,
);
}
mass_inserts(
IMAGE_TAG_TABLE,
array('tag_id', 'image_id'),
$inserts,
array('ignore'=>true)
);
pwg_activity('tag', $params['destination_tag_id'], 'edit');
foreach ($image_to_add as $image_id)
{
pwg_activity('image', $image_id, 'edit', array("tag-add" => $params['destination_tag_id']));
}
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
delete_tags($merge_tag);
$image_in_merged = array_merge($image_in_dest, $image_to_add);
return array(
"destination_tag" => $params['destination_tag_id'],
"deleted_tag" => $params['merge_tag_id'],
"images_in_merged_tag" => $image_in_merged
);
}
?>

59
ws.php
View File

@@ -640,12 +640,69 @@ function ws_addDefaultMethods( $arr )
$service->addMethod( // TODO: create multiple tags
'pwg.tags.add',
'ws_tags_add',
array('name'),
array(
'name' => array(),
'pwg_token' => array(),
),
'Adds a new tag.',
$ws_functions_root . 'pwg.tags.php',
array('admin_only'=>true)
);
$service->addMethod(
'pwg.tags.delete',
'ws_tags_delete',
array(
'tag_id' => array('type'=>WS_TYPE_ID,
'flags'=>WS_PARAM_FORCE_ARRAY),
'pwg_token' => array(),
),
'Delete tag(s) by ID.',
$ws_functions_root . 'pwg.tags.php',
array('admin_only'=>true)
);
$service->addMethod(
'pwg.tags.rename',
'ws_tags_rename',
array(
'tag_id' => array('type'=>WS_TYPE_ID),
'new_name' => array(),
'pwg_token' => array(),
),
'Rename tag',
$ws_functions_root . 'pwg.tags.php',
array('admin_only'=>true)
);
$service->addMethod(
'pwg.tags.duplicate',
'ws_tags_duplicate',
array(
'tag_id' => array('type'=>WS_TYPE_ID),
'copy_name' => array(),
'pwg_token' => array(),
),
'Create a copy of a tag',
$ws_functions_root . 'pwg.tags.php',
array('admin_only'=>true, 'post_only'=>true)
);
$service->addMethod(
'pwg.tags.merge',
'ws_tags_merge',
array(
'destination_tag_id' => array('type'=>WS_TYPE_ID,
'info'=>'Is not necessarily part of groups to merge'),
'merge_tag_id' => array('flags'=>WS_PARAM_FORCE_ARRAY,
'type'=>WS_TYPE_ID),
'pwg_token' => array(),
),
'Merge tags in one other group',
$ws_functions_root . 'pwg.tags.php',
array('admin_only'=>true, 'post_only'=>true)
);
$service->addMethod(
'pwg.images.exist',
'ws_images_exist',