Owen Leonard
5fe1ad8ac6
This patch updates the OPAC and staff interface to use Bootstrap 5. Bootstrap CSS assets are now pulled from node_modules and compiled into staff-global.css and opac.css at build time. This update lays the foundations of some other chnages, especially the addition of a dark mode in the future. Hundreds of templates have been updated, mostly with updates to the grid markup. Most of the responsive behavior is still the same with the exception of improved flexibility of headers and footers in both the OPAC and staff interface. The other most common change is to add a new "namespace" to data attributes used by Bootstrap, e.g. "data-bs-target" or "data-bs-toggle". Modal markup has also been updated everywhere. Other common changes: dropdown button markup, alert markup (we now use Bootstrap's "alert alert-warning" and "alert alert-info" instead of our old "dialog alert" and "dialog info"). Bootstrap 5 now uses CSS variables which we can override in our own '_variables.scss' (in both the OPAC and staff) to accomplish a lot of the style overrides which we previously put in staff-global.scss. Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
282 lines
12 KiB
Text
282 lines
12 KiB
Text
[% USE raw %]
|
|
[% USE Asset %]
|
|
[% PROCESS 'i18n.inc' %]
|
|
[% SET footerjs = 1 %]
|
|
[% INCLUDE 'doc-head-open.inc' %]
|
|
<title>[% FILTER collapse %]
|
|
[% t("Localization") | html %] ›
|
|
[% t("Koha") | html %]
|
|
[% END %]</title>
|
|
[% INCLUDE 'doc-head-close.inc' popup => 1 %]
|
|
<style>#localization { margin-top: 1em; }</style>
|
|
</head>
|
|
|
|
<body id="admin_localization" class="admin">
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<h1>Localization</h1>
|
|
<form id="add_translation" method="get">
|
|
[% INCLUDE 'csrf-token.inc' %]
|
|
<input type="hidden" name="entity" value="[% entity | html %]" />
|
|
<input type="hidden" name="code" value="[% code | html %]" />
|
|
<input type="hidden" name="interface" value="[% interface_side | html %]" />
|
|
<fieldset class="rows clearfix">
|
|
<ol>
|
|
<li>
|
|
<span class="label">Authorized value:</span>
|
|
[% code | html %]
|
|
</li>
|
|
<li>
|
|
<label for="lang">Language:</label>
|
|
<select name="lang" id="lang">
|
|
[% FOR language IN languages %]
|
|
[% FOR sublanguage IN language.sublanguages_loop %]
|
|
[% IF language.plural %]
|
|
<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] [% sublanguage.region_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>
|
|
[% ELSE %]
|
|
<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>
|
|
[% END %]
|
|
[% END %]
|
|
[% END %]
|
|
</select>
|
|
</li>
|
|
<li>
|
|
<label for="translation">Translation:</label>
|
|
<input type="text" size="40" name="translation" id="translation" />
|
|
</li>
|
|
<li>
|
|
<span class="label"> </span>
|
|
<input type="submit" class="btn btn-primary" value="Add" />
|
|
</li>
|
|
</ol>
|
|
</fieldset>
|
|
</form>
|
|
</div> <!-- /.col-sm-12 -->
|
|
</div> <!-- /.row -->
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<div id="messages"></div>
|
|
</div> <!-- /.col-sm-12 -->
|
|
</div> <!-- /.row -->
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<table id="localization">
|
|
<thead>
|
|
<tr>
|
|
<th>Id</th>
|
|
<th>Entity</th>
|
|
<th>Code</th>
|
|
<th>Language</th>
|
|
<th>Translation</th>
|
|
<th class="NoSort"> </th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
[% FOR t IN translations %]
|
|
<tr id="row_id_[% t.id | html %]" data-id="[% t.id | html %]">
|
|
<td>[% t.id | html %]</td>
|
|
<td>[% t.entity | html %]</td>
|
|
<td>[% t.code | html %]</td>
|
|
<td class="lang">[% t.lang | html %]</td>
|
|
<td class="translation" contenteditable="true">[% t.translation | html %]</td>
|
|
<td class="actions"><a href="#" class="delete"><i class="fa fa-trash-can"></i> Delete</a></td>
|
|
</tr>
|
|
[% END %]
|
|
</tbody>
|
|
</table>
|
|
</div> <!-- /.col-sm-12 -->
|
|
</div> <!-- /.row -->
|
|
</div> <!-- /.container-fluid -->
|
|
|
|
[% MACRO jsinclude BLOCK %]
|
|
[% INCLUDE 'datatables.inc' %]
|
|
<script>
|
|
|
|
function show_message( params ) {
|
|
var type = params.type;
|
|
var data = params.data;
|
|
var messages = $("#messages");
|
|
var message;
|
|
if ( type == 'success_on_update' ) {
|
|
message = $('<div class="alert alert-info"></div>');
|
|
message.text(_("Entity %s (code %s) for lang %s has correctly been updated with '%s'").format(data.entity, data.code, data.lang, data.translation));
|
|
} else if ( type == 'error_on_update' ) {
|
|
message = $('<div class="alert alert-warning"></div>');
|
|
if ( data.error_code == 'already_exists' ) {
|
|
message.text(_("A translation already exists for this language."));
|
|
} else {
|
|
message.text(_("An error occurred when updating this translation."));
|
|
}
|
|
} else if ( type == 'success_on_delete' ) {
|
|
message = $('<div class="alert alert-info"></div>');
|
|
message.text(_("The translation (id %s) has been removed successfully").format(data.id));
|
|
} else if ( type == 'error_on_delete' ) {
|
|
message = $('<div class="alert alert-warning"></div>');
|
|
message.text(_("An error occurred when deleting this translation"));
|
|
} else if ( type == 'success_on_insert' ) {
|
|
message = $('<div class="alert alert-info"></div>');
|
|
message.text(_("Translation (id %s) has been added successfully").format(data.id));
|
|
} else if ( type == 'error_on_insert' ) {
|
|
message = $('<div class="alert alert-warning"></div>');
|
|
if ( data.error_code == 'already_exists' ) {
|
|
message.text(_("A translation already exists for this language."));
|
|
} else {
|
|
message.text(_("An error occurred when adding this translation"));
|
|
}
|
|
}
|
|
|
|
$(messages).append(message);
|
|
|
|
setTimeout(function(){
|
|
message.hide()
|
|
}, 3000);
|
|
}
|
|
|
|
function send_update_request( data, cell ) {
|
|
const client = APIClient.localization;
|
|
client.localizations.update(data).then(
|
|
success => {
|
|
if ( success.error ) {
|
|
$(cell).css('background-color', '#FF0000');
|
|
show_message({ type: 'error_on_update', data: success });
|
|
} else if ( success.is_changed == 1 ) {
|
|
$(cell).css('background-color', '#00FF00');
|
|
show_message({ type: 'success_on_update', data: success });
|
|
}
|
|
|
|
if ( $(cell).hasClass('lang') ) {
|
|
$(cell).text(success.lang)
|
|
} else if ( $(cell).hasClass('translation') ) {
|
|
$(cell).text(success.translation)
|
|
}
|
|
},
|
|
error => {
|
|
$(cell).css('background-color', '#FF9090');
|
|
if ( $(cell).hasClass('lang') ) {
|
|
$(cell).text(error.lang)
|
|
} else if ( $(cell).hasClass('translation') ) {
|
|
$(cell).text(error.translation)
|
|
}
|
|
show_message({ type: 'error_on_update', data: error });
|
|
console.warn("Something wrong happened: %s".format(error));
|
|
}
|
|
);
|
|
}
|
|
|
|
function send_delete_request( id, cell ) {
|
|
const client = APIClient.localization;
|
|
client.localizations.delete(id).then(
|
|
success => {
|
|
$("#localization").DataTable().row( '#row_id_' + id ).remove().draw();
|
|
show_message({ type: 'success_on_delete', data: {id} });
|
|
},
|
|
error => {
|
|
$(cell).css('background-color', '#FF9090');
|
|
show_message({ type: 'error_on_delete', data: error });
|
|
console.warn("Something wrong happened: %s".format(error));
|
|
}
|
|
);
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
$(".dialog").hide();
|
|
|
|
var table = $("#localization").DataTable($.extend(true, {}, dataTablesDefaults, {
|
|
"dom": 't',
|
|
"columnDefs": [
|
|
{ 'sortable': false, 'targets': [ 'NoSort' ] }
|
|
],
|
|
"paginate": false,
|
|
'autoWidth': false,
|
|
}));
|
|
|
|
var languages_select = $('<select name="lang" id="lang"></select>');
|
|
[% FOR language IN languages %]
|
|
[% FOR sublanguage IN language.sublanguages_loop %]
|
|
var option;
|
|
[% IF language.plural %]
|
|
option = $('<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] [% sublanguage.region_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>');
|
|
$(languages_select).append(option);
|
|
[% ELSE %]
|
|
option = $('<option value="[% sublanguage.rfc4646_subtag | html %]">[% sublanguage.native_description | html %] ([% sublanguage.rfc4646_subtag | html %])</option>');
|
|
[% END %]
|
|
$(languages_select).append(option);
|
|
[% END %]
|
|
[% END %]
|
|
|
|
$("td.translation").on('focus', function(){
|
|
$(this).css('background-color', '');
|
|
});
|
|
$("td.lang").on('click', function(){
|
|
var td = this;
|
|
var lang = $(td).text();
|
|
$(td).css('background-color', '');
|
|
var my_select = $(languages_select).clone();
|
|
$(my_select).find('option[value="' + lang + '"]').attr('selected', 'selected');
|
|
$(my_select).on('click', function(e){
|
|
e.stopPropagation();
|
|
});
|
|
$(my_select).on('change', function(){
|
|
var tr = $(this).parent().parent();
|
|
var id = $(tr).data('id');
|
|
var lang = $(this).find('option:selected').val();
|
|
var translation = $(this).text();
|
|
send_update_request( {id, lang, translation}, td );
|
|
});
|
|
$(my_select).on('blur', function(){
|
|
$(td).html(lang);
|
|
});
|
|
$(this).html(my_select);
|
|
});
|
|
|
|
$("td.translation").on('blur', function(){
|
|
var tr = $(this).parent();
|
|
var id = $(tr).data('id');
|
|
var lang = $(tr).find('td.lang').text();
|
|
var translation = $(this).text();
|
|
send_update_request( {id, lang, translation}, this );
|
|
});
|
|
|
|
$("body").on("click", "a.delete", function(e){
|
|
e.preventDefault();
|
|
if ( confirm(_("Are you sure you want to delete this translation?")) ) {
|
|
var td = $(this).parent();
|
|
var tr = $(td).parent();
|
|
var id = $(tr).data('id');
|
|
send_delete_request( id, td );
|
|
}
|
|
});
|
|
|
|
$("#add_translation").on('submit', function(e){
|
|
e.preventDefault();
|
|
let localization = {
|
|
entity: $(this).find('input[name="entity"]').val(),
|
|
code: $(this).find('input[name="code"]').val(),
|
|
lang: $(this).find('select[name="lang"] option:selected').val(),
|
|
translation: $(this).find('input[name="translation"]').val(),
|
|
};
|
|
const client = APIClient.localization;
|
|
client.localizations.create(localization).then(
|
|
success => {
|
|
if ( success.error ) {
|
|
show_message({ type: 'error_on_insert', data: success });
|
|
} else {
|
|
var new_row = table.row.add( [ success.id, success.entity, success.code, success.lang, success.translation, "<a href=\"#\" class=\"delete\"><i class=\"fa fa-trash-can\"></i> Delete</a>" ] ).draw().node();
|
|
$( new_row ).attr("id", "row_id_" + success.id ).data("id", success.id );
|
|
show_message({ type: 'success_on_insert', data: success });
|
|
}
|
|
},
|
|
error => {
|
|
show_message({ type: 'error_on_insert', data: error });
|
|
console.warn("Something wrong happened: %s".format(error));
|
|
}
|
|
);
|
|
});
|
|
|
|
});
|
|
</script>
|
|
[% END %]
|
|
[% INCLUDE 'popup-bottom.inc' %]
|