mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-13 13:03:20 +00:00
[skip ci]
PHRAS-3381_tx-as-classification-plan_MASTER dlgbox
This commit is contained in:
@@ -16,6 +16,12 @@ const thesaurusService = services => {
|
||||
|
||||
let dragging = false; // true when an object is dragged over the th zone
|
||||
let dragTarget = null; // the target where the mouse is over
|
||||
let dragUniqueSbid = null; // will end-up as : null (nothing dragged) ; false (many sbids) ; sbid (same sbid for all)
|
||||
let dragLstRecords = '' // list or records, format as expected for RecordsRequest::fromRequest
|
||||
const url = configService.get('baseUrl');
|
||||
|
||||
let searchSelection = {asArray: [], serialized: ''};
|
||||
|
||||
|
||||
const initialize = params => {
|
||||
let { $container } = params;
|
||||
@@ -105,6 +111,18 @@ const thesaurusService = services => {
|
||||
$('#THPD_T_tree')
|
||||
.droppable({
|
||||
accept: function(elem) {
|
||||
let lstbr = searchSelection.asArray;
|
||||
console.log("lstbr", lstbr);
|
||||
|
||||
dragUniqueSbid = null;
|
||||
lstbr.forEach(sbid_rid => {
|
||||
sbid_rid = sbid_rid.split('_');
|
||||
let sbid = sbid_rid[0];
|
||||
let rid = sbid_rid[1];
|
||||
dragUniqueSbid = (dragUniqueSbid===null) ? sbid : (sbid===dragUniqueSbid ? sbid : false);
|
||||
});
|
||||
dragLstRecords = lstbr.join(';'); // a list as expected for RecordsRequest::fromRequest
|
||||
|
||||
$(this).removeClass('draggingOver');
|
||||
console.log("accept", elem);
|
||||
// if ($(elem).hasClass('grouping') && !$(elem).hasClass('SSTT')) {
|
||||
@@ -115,15 +133,37 @@ const thesaurusService = services => {
|
||||
|
||||
// the th zone can accet drags only when in front (activated tab)
|
||||
// 'hash' is set by the 'workzone' js code.
|
||||
return $('#idFrameC .tabs').data('hash') === '#thesaurus_tab';
|
||||
// return $('#idFrameC .tabs').data('hash') === '#thesaurus_tab';
|
||||
|
||||
if($('#idFrameC .tabs').data('hash') !== '#thesaurus_tab') {
|
||||
return false; // can't drop on th if the th tab is not front
|
||||
}
|
||||
|
||||
// by using classes on both main container AND the (unique) acceptable thesaurus zone
|
||||
// we can have custom drag/drop css for both ok / reject
|
||||
$('#THPD_T_tree', $container).removeClass('draggingOver'); // the container
|
||||
$('#THPD_T_tree>LI', $container).removeClass('draggingOver'); // all thesaurus
|
||||
|
||||
if(dragUniqueSbid === null || dragUniqueSbid === false) {
|
||||
// many sbids
|
||||
// return false; // don't return false, as it will prevent "over" and will not apply css (no "not-allowed" cursor)
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
scope: 'objects',
|
||||
hoverClass: 'groupDrop',
|
||||
tolerance: 'pointer',
|
||||
over: function(event, ui) {
|
||||
$(this).addClass('draggingOver');
|
||||
console.log("over", event, ui, event.toElement);
|
||||
|
||||
$('#THPD_T_tree', $container).addClass('draggingOver');
|
||||
if(dragUniqueSbid !== null && dragUniqueSbid !== false) {
|
||||
$('#TX_P\\.'+dragUniqueSbid+'\\.T', $container).addClass('draggingOver');
|
||||
}
|
||||
/*
|
||||
$(this).addClass('draggingOver');
|
||||
|
||||
if(dragTarget) {
|
||||
// something was already hilighted (should no happen)
|
||||
dragTarget.removeClass('dragOver');
|
||||
@@ -139,28 +179,50 @@ const thesaurusService = services => {
|
||||
dragTarget.addClass('dragOver');
|
||||
console.log("IN : " + dragTarget.attr('id'));
|
||||
}
|
||||
*/
|
||||
},
|
||||
out: function(event, ui) {
|
||||
$(this).removeClass('draggingOver');
|
||||
console.log("out", event, ui, event.toElement);
|
||||
$('#THPD_T_tree', $container).removeClass('draggingOver');
|
||||
$('#THPD_T_tree>LI', $container).removeClass('draggingOver');
|
||||
/*
|
||||
$(this).removeClass('draggingOver');
|
||||
if(dragTarget) {
|
||||
// something was hilighted
|
||||
dragTarget.removeClass('dragOver');
|
||||
}
|
||||
dragging = false; // == no more dragging something over th
|
||||
dragTarget = null;
|
||||
|
||||
*/
|
||||
},
|
||||
drop: (event, ui) => {
|
||||
$(this).removeClass('draggingOver');
|
||||
console.log("drop", event, ui);
|
||||
$('#THPD_T_tree', $container).removeClass('draggingOver');
|
||||
$('#THPD_T_tree>LI', $container).removeClass('draggingOver');
|
||||
|
||||
const target = $(event.toElement);
|
||||
const sbas_id = target.data('sbas_id').toString(); // set on html by ThesaurusXmlHttpController.php
|
||||
const tx_term_id = target.data('tx_term_id').toString(); // set on html by ThesaurusXmlHttpController.php
|
||||
|
||||
if(sbas_id === dragUniqueSbid) {
|
||||
dropRecordsOnTerm(sbas_id, tx_term_id, dragLstRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
$(this).removeClass('draggingOver');
|
||||
if(dragTarget) {
|
||||
// const tid = $(event.toElement).data('tx_term_id');
|
||||
console.log("DROP ON id=" + dragTarget.attr('id'));
|
||||
dragTarget.removeClass('dragOver');
|
||||
// appEvents.emit('searchAdvancedForm.activateDatabase', { databases: [sbid] });
|
||||
// appEvents.emit('searchAdvancedForm.activateDatabase', { databases: [sbid] });
|
||||
}
|
||||
dragging = false; // == no more dragging something over th
|
||||
dragTarget = null;
|
||||
|
||||
*/
|
||||
}
|
||||
})
|
||||
// track the mouse
|
||||
@@ -194,6 +256,57 @@ const thesaurusService = services => {
|
||||
searchValue = _.debounce(searchValue, 300);
|
||||
};
|
||||
|
||||
|
||||
|
||||
function dropRecordsOnTerm(sbas_id, tx_term_id, lstRecords) {
|
||||
let dlg = dialog.create(
|
||||
services,
|
||||
{
|
||||
size: 'Custom',
|
||||
customWidth: 770,
|
||||
customHeight: 400,
|
||||
title: localeService.t('add data'),
|
||||
loading: true
|
||||
},
|
||||
0
|
||||
);
|
||||
// dlg.setContent("");
|
||||
|
||||
// let parms = {
|
||||
// url: '/thesaurus/drop_records.php',
|
||||
// data: {
|
||||
// 'sbas_id': sbas_id,
|
||||
// 'tx_term_id': tx_term_id,
|
||||
// 'lst': lstRecords
|
||||
// },
|
||||
// async: true,
|
||||
// cache: false,
|
||||
// dataType: 'json',
|
||||
// // timeout: 10 * 60 * 1000, // 10 minutes !
|
||||
// success: function (result, textStatus) {
|
||||
// dlg.setContent(result);
|
||||
// }
|
||||
// };
|
||||
|
||||
$.get(
|
||||
`${url}prod/thesaurus/droprecords`,
|
||||
{
|
||||
'dlg_level': 0,
|
||||
'sbas_id': sbas_id,
|
||||
'tx_term_id': tx_term_id,
|
||||
'lst': lstRecords
|
||||
},
|
||||
function (data, textStatus) {
|
||||
dlg.setContent(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function show() {
|
||||
// first show of thesaurus
|
||||
if (options.currentWizard === '???') {
|
||||
@@ -1462,6 +1575,13 @@ const thesaurusService = services => {
|
||||
});
|
||||
}
|
||||
|
||||
appEvents.listenAll({
|
||||
'broadcast.searchResultSelection': (selection) => {
|
||||
searchSelection = selection;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return { initialize, show };
|
||||
};
|
||||
|
||||
|
@@ -189,10 +189,25 @@ dans l'onglet thesaurus : arbres, menus contextuels
|
||||
}
|
||||
|
||||
#THPD_T_tree.draggingOver {
|
||||
//background-color: #FF0000;
|
||||
/* something is currently dragged over, we change some hover */
|
||||
&:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
span.tx_term:hover {
|
||||
color: $mediumTextColor; // no more highlight
|
||||
cursor: not-allowed;
|
||||
}
|
||||
LI.draggingOver {
|
||||
//background-color: #00FF00;
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
span.tx_term:hover {
|
||||
background-color: $highlightBackgroundColor;
|
||||
color: $highlightTextColor;
|
||||
cursor: copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -65,6 +65,7 @@ class RouteLoader
|
||||
'/prod/share/' => Providers\Prod\Share::class,
|
||||
'/prod/story' => Providers\Prod\Story::class,
|
||||
'/prod/subdefs' => Providers\Prod\Subdefs::class,
|
||||
'/prod/thesaurus/' => Providers\Prod\Thesaurus::class,
|
||||
'/prod/tools/' => Providers\Prod\Tools::class,
|
||||
'/prod/tooltip' => Providers\Prod\Tooltip::class,
|
||||
'/prod/TOU/' => Providers\Prod\TOU::class,
|
||||
|
138
lib/Alchemy/Phrasea/Controller/Prod/ThesaurusController.php
Normal file
138
lib/Alchemy/Phrasea/Controller/Prod/ThesaurusController.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace Alchemy\Phrasea\Controller\Prod;
|
||||
|
||||
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
|
||||
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
|
||||
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
|
||||
use Alchemy\Phrasea\Controller\Controller;
|
||||
use Alchemy\Phrasea\Controller\RecordsRequest;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ThesaurusController extends Controller
|
||||
{
|
||||
use DataboxLoggerAware;
|
||||
use DispatcherAware;
|
||||
use FilesystemAware;
|
||||
|
||||
public function dropRecordsAction(Request $request): string
|
||||
{
|
||||
$sbas_id = $request->get('sbas_id');
|
||||
$tx_term_id = $request->get('tx_term_id');
|
||||
$records = RecordsRequest::fromRequest($this->app, $request, false);
|
||||
|
||||
// twig parameters
|
||||
$twp = [
|
||||
'error' => null,
|
||||
'dlg_level' => $request->get('dlg_level'),
|
||||
// 'fields' => [], // fields the can receive the value
|
||||
// 'fvalue' => 'Europe',
|
||||
'up_paths' => [],
|
||||
];
|
||||
|
||||
// find which field(s) can be updated, that is what tbranches are linked to a parent of the term
|
||||
|
||||
try {
|
||||
$dbox = $this->app->getApplicationBox()->get_databox($sbas_id);
|
||||
|
||||
if (!($domth = $dbox->get_dom_thesaurus())) {
|
||||
throw new Exception("error fetching th");
|
||||
}
|
||||
|
||||
$upPaths = [];
|
||||
$xpath = new DOMXPath($domth);
|
||||
foreach ($dbox->get_meta_structure() as $field) {
|
||||
if (!($q = $field->get_tbranch())) {
|
||||
continue;
|
||||
}
|
||||
$fieldName = $field->get_name();
|
||||
$roots = $xpath->query($q); // linked nodes for this field
|
||||
$q = '(' . $q . ')//sy[@id=\'' . $tx_term_id . '\']'; // can we find the term under the tbranch(es) ?
|
||||
// normally we should find only one linked parent, since we search from a unique term
|
||||
// ...but... a bad th could link a field onto 2 points of the same branch :
|
||||
// root
|
||||
// |---A <-- "keyword" is linked here...
|
||||
// |---B <-- ...but also linked here (bad idea btw)
|
||||
// |---terms
|
||||
// going up, we decide to stop at the first link (B) (easier)
|
||||
if (($droppedSy = $xpath->query($q))->length > 0) {
|
||||
// yes (and since the query targets a unique id, there is only one result)
|
||||
$droppedSy = $droppedSy->item(0);
|
||||
/** @var DOMElement $droppedSy */
|
||||
$droppedlng = $droppedSy->getAttribute('lng'); // the lng of the dropped term is prefered
|
||||
|
||||
// go from the sy upto a linked branch (possibly multiples if the field is linked to many branches)
|
||||
|
||||
$ok = true; // == the term (level up-to top) can populate the current field
|
||||
for ($te = $droppedSy->parentNode; $te->nodeType === XML_ELEMENT_NODE; $te = $te->parentNode) {
|
||||
/** @var DOMElement $te */
|
||||
$teid = $te->getAttribute('id');
|
||||
for ($i = 0; $i < $roots->length; $i++) {
|
||||
if ($te->isSameNode($roots->item($i))) {
|
||||
$ok = false; // we met the link point, upmost terms are not "values" anymore
|
||||
}
|
||||
}
|
||||
if ($ok) { // acceptable value for the current field
|
||||
if (!array_key_exists($teid, $upPaths)) {
|
||||
$upPaths[$teid] = [
|
||||
'synonyms' => [],
|
||||
'fields' => []
|
||||
];
|
||||
// get all the sy so the user can choose which is prefered
|
||||
$preferedId = null;
|
||||
foreach ($te->childNodes as $sy) {
|
||||
if ($sy->nodeName != 'sy') {
|
||||
continue; // skip 'te' children
|
||||
}
|
||||
$lng = $sy->getAttribute('lng');
|
||||
$id = $sy->getAttribute('id');
|
||||
$s = [
|
||||
'value' => $sy->getAttribute('v'),
|
||||
'lng' => $lng,
|
||||
'selected' => false
|
||||
];
|
||||
// this sy is prefered if...
|
||||
if ($sy->getAttribute('lng') === $droppedlng) {
|
||||
$preferedId = $id; // ... it has the same lng as the dropped
|
||||
}
|
||||
if ($sy->isSameNode($droppedSy)) {
|
||||
$preferedId = $id; // ... better : it was the dropped target
|
||||
}
|
||||
$upPaths[$teid]['synonyms'][$id] = $s;
|
||||
}
|
||||
if ($preferedId) {
|
||||
$upPaths[$teid]['synonyms'][$preferedId]['selected'] = true;
|
||||
}
|
||||
}
|
||||
$upPaths[$teid]['fields'][$field->get_id()] = $field;
|
||||
}
|
||||
}
|
||||
$twp['up_paths'] = array_reverse($upPaths);
|
||||
$twp['fields'][] = $field;
|
||||
// $field->
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($upPaths)) {
|
||||
// no fields (could happen if one drops on a top-level branch, or if the th is not linked, or...)
|
||||
throw new Exception("this branch is not linked");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$twp['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $this->render('prod/Thesaurus/droppedrecords.html.twig', $twp);
|
||||
}
|
||||
}
|
@@ -87,6 +87,7 @@ class ControllerProviderServiceProvider implements ServiceProviderInterface
|
||||
Prod\Share::class => [],
|
||||
Prod\Story::class => [],
|
||||
Prod\Subdefs::class => [],
|
||||
Prod\Thesaurus::class => [],
|
||||
Prod\Tools::class => [],
|
||||
Prod\Tooltip::class => [],
|
||||
Prod\TOU::class => [],
|
||||
|
55
lib/Alchemy/Phrasea/ControllerProvider/Prod/Thesaurus.php
Normal file
55
lib/Alchemy/Phrasea/ControllerProvider/Prod/Thesaurus.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\ControllerProvider\Prod;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Alchemy\Phrasea\Controller\Prod\ThesaurusController;
|
||||
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
|
||||
use Alchemy\Phrasea\Core\LazyLocator;
|
||||
use Silex\Application;
|
||||
use Silex\ControllerProviderInterface;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class Thesaurus implements ControllerProviderInterface, ServiceProviderInterface
|
||||
{
|
||||
use ControllerProviderTrait;
|
||||
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['controller.prod.thesaurus'] = $app->share(function (PhraseaApplication $app) {
|
||||
return (new ThesaurusController($app))
|
||||
->setDataboxLoggerLocator($app['phraseanet.logger'])
|
||||
->setDispatcher($app['dispatcher'])
|
||||
->setFileSystemLocator(new LazyLocator($app, 'filesystem'))
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public function connect(Application $app)
|
||||
{
|
||||
$controllers = $this->createAuthenticatedCollection($app);
|
||||
// $firewall = $this->getFirewall($app);
|
||||
|
||||
// $controllers->before(function () use ($firewall) {
|
||||
// $firewall->requireRight(\ACL::CANMODIFRECORD);
|
||||
// });
|
||||
|
||||
$controllers->get('/droprecords', 'controller.prod.thesaurus:dropRecordsAction');
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
}
|
122
templates/web/prod/Thesaurus/droppedrecords.html.twig
Normal file
122
templates/web/prod/Thesaurus/droppedrecords.html.twig
Normal file
@@ -0,0 +1,122 @@
|
||||
{% if error %}
|
||||
{{ error }}
|
||||
{% else %}
|
||||
<form id="DroppedOnTH_form">
|
||||
<table>
|
||||
<tr>
|
||||
<th>set value</th>
|
||||
<th/>
|
||||
<th>to field</th>
|
||||
<th class="multiTitle" style="display: none">multi</th>
|
||||
</tr>
|
||||
{% if up_paths|length > 1 %}
|
||||
<tr class="toggleMoreFields">
|
||||
<td colspan="4">
|
||||
<button>+</button> Other value(s)...
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% set n=1 %}
|
||||
{% for tid,up_path in up_paths %}
|
||||
{% set cl = (n == up_paths|length) ? "" : "class='other' style='display:none'"%}
|
||||
<tr {{ cl|raw }}>
|
||||
<td>
|
||||
{% if 0 and up_path.synonyms|length == 1 %}
|
||||
{% set synonym = up_path.synonyms|first %}
|
||||
<option {{ synonym.selected ? "selected" : "" }}>{{ synonym.value }}</option>
|
||||
{% else %}
|
||||
<select>
|
||||
{% for synonym in up_path.synonyms %}
|
||||
<option {{ synonym.selected ? "selected" : "" }}>{{ synonym.value }}</option>
|
||||
{% endfor%}
|
||||
</select>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="alignment: center; font-size: xx-large">
|
||||
→
|
||||
</td>
|
||||
<td>
|
||||
<select class="fieldSelect" data-n="{{ n }}">
|
||||
{% set sel="" %}
|
||||
{% if n == up_paths|length %}
|
||||
<option data-multi="0">select...</option>
|
||||
{% if up_path.fields|length == 1 %}
|
||||
{% set sel="selected" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<option data-multi="0">none</option>
|
||||
{% endif %}
|
||||
{% for field in up_path.fields %}
|
||||
<option {{ sel }} data-multi="{{ field.is_multi() ? 1 : 0 }}">{{ field.get_name() }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td class="multiRadio _{{ n }}" style="padding-left:10px; display: none">
|
||||
<input type="radio" checked> add
|
||||
|
||||
<input type="radio"> replace
|
||||
</td>
|
||||
</tr>
|
||||
{% set n = n+1 %}
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<script type="application/javascript">
|
||||
let $container = $('#DroppedOnTH_form');
|
||||
|
||||
let dlg = dialog.get({{ dlg_level }});
|
||||
dlg.setOption("buttons",
|
||||
[
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* when a destination field is selected, show/hide the "multi" radio
|
||||
*/
|
||||
$(' .fieldSelect', $container)
|
||||
.change(function (event) {
|
||||
let select = $(this);
|
||||
let n = select.data('n');
|
||||
console.log(n);
|
||||
if( $('option:eq(' + select.prop('selectedIndex') + ')', select).data('multi') ) {
|
||||
// this select is on a multi-valued field
|
||||
$(' .multiRadio._'+n, $container).show();
|
||||
}
|
||||
else {
|
||||
// this select is on a mono-valued field
|
||||
$(' .multiRadio._'+n, $container).hide();
|
||||
}
|
||||
// show the column title only if one field is multi
|
||||
if( $(' .multiRadio:visible', $container).length > 0 ) {
|
||||
$(' .multiTitle', $container).show();
|
||||
}
|
||||
else {
|
||||
$(' .multiTitle', $container).hide();
|
||||
}
|
||||
})
|
||||
.change(); // enforce initial update
|
||||
|
||||
|
||||
$(' .toggleMoreFields BUTTON', $container).click(function (event) {
|
||||
$(' .toggleMoreFields', $container).hide();
|
||||
$(' .other').show();
|
||||
return false;
|
||||
})
|
||||
|
||||
</script>
|
@@ -1082,8 +1082,10 @@
|
||||
url += "&typ=" + typ;
|
||||
url += "&sylng=" + encodeURIComponent(lng);
|
||||
|
||||
var title = typ=="TS" ? "{{ 'thesaurus:: Nouveau terme specifique' | trans }}" : "{{ 'thesaurus:: Nouveau synonyme' | trans }}";
|
||||
|
||||
// w = window.open(url, "NEWTERM", "directories=no, height=290, width=490, location=no, menubar=no, resizable=yes, scrollbars=yes, status=no, toolbar=no");
|
||||
w = loadDataAjax(url,490, 350,"#NEWSY_DLG_CONFIRM",'{% if typ == "TS" %}{{ 'thesaurus:: Nouveau terme specifique' | trans }}{% else %}{{ 'thesaurus:: Nouveau synonyme' | trans }}{% endif %}');
|
||||
w = loadDataAjax(url, 490, 350, "#NEWSY_DLG_CONFIRM", title);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Reference in New Issue
Block a user