Initial commit
This commit is contained in:
commit
53a0522417
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Datenbankverbindung (enthält Passwörter)
|
||||
ajax/db_connection.php
|
||||
|
||||
# Logdateien
|
||||
*.log
|
||||
|
||||
# Backup-Dateien
|
||||
*.bak
|
||||
|
||||
# Datenbankdumps
|
||||
*.sql
|
||||
|
||||
# Versionierte Backup-Dateien
|
||||
*.20*
|
||||
477
Classifications.php
Normal file
477
Classifications.php
Normal file
@ -0,0 +1,477 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Classification">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">🗂️ Klassifikationsverwaltung</h1>
|
||||
<button id="add" class="btn btn-primary" onclick="newClassificationsShow()">
|
||||
<i class="fas fa-plus"></i> Neue Klassifikation
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<table
|
||||
id="table"
|
||||
data-toolbar="#toolbar"
|
||||
data-search="true"
|
||||
data-query-params-type="limit"
|
||||
data-show-refresh="false"
|
||||
data-show-toggle="false"
|
||||
data-show-fullscreen="false"
|
||||
data-show-columns="false"
|
||||
data-show-columns-toggle-all="false"
|
||||
data-detail-view="false"
|
||||
data-show-export="false"
|
||||
data-click-to-select="false"
|
||||
data-trim-on-search="false"
|
||||
data-minimum-count-columns="2"
|
||||
data-show-pagination-switch="false"
|
||||
data-page-size="25"
|
||||
data-pagination="true"
|
||||
data-id-field="ID"
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-show-footer="false"
|
||||
data-side-pagination="server"
|
||||
data-server-sort="false"
|
||||
data-url="/Thesaurus/ajax/getClassificationData.php"
|
||||
data-response-handler="responseHandler">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Records Content -->
|
||||
<div class="row">
|
||||
<div class="records_content"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var $table = $('#table')
|
||||
var $remove = $('#remove')
|
||||
var selections = []
|
||||
|
||||
function getIdSelections() {
|
||||
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
|
||||
function responseHandler(res) {
|
||||
$.each(res.rows, function (i, row) {
|
||||
row.state = $.inArray(row.ID, selections) !== -1
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function detailFormatter(index, row) {
|
||||
var html = []
|
||||
$.each(row, function (key, value) {
|
||||
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||
if (value !== null && value !== undefined && String(value).length > 0)
|
||||
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||
}
|
||||
})
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
function copyFormatter(value, row, index) {
|
||||
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||
'title="Begriff kopieren">' +
|
||||
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||
'</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function operateFormatter(value, row, index) {
|
||||
return [
|
||||
'<div class="table-actions">',
|
||||
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||
'<i class="fas fa-table"></i>',
|
||||
'</a>',
|
||||
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||
'<i class="fas fa-edit"></i>',
|
||||
'</a>',
|
||||
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||
'<i class="fas fa-trash"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
window.operateEvents = {
|
||||
'click .relations': function (e, value, row, index) {
|
||||
ShowRelationsModal(row.ID, 'Classification')
|
||||
},
|
||||
'click .modify': function (e, value, row, index) {
|
||||
ModifyTerm([row.ID]),
|
||||
$table.bootstrapTable('refresh')
|
||||
},
|
||||
'click .remove': function (e, value, row, index) {
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: [row.ID]
|
||||
})
|
||||
DeleteTerm([row.ID])
|
||||
}
|
||||
}
|
||||
|
||||
function totalTextFormatter(data) {
|
||||
return 'Total'
|
||||
}
|
||||
|
||||
function totalNameFormatter(data) {
|
||||
return data.length
|
||||
}
|
||||
|
||||
function initTableClassification() {
|
||||
$table.bootstrapTable('destroy').bootstrapTable({
|
||||
locale: $('#locale').val(),
|
||||
columns: [
|
||||
[{
|
||||
title: 'Klassifikation',
|
||||
colspan: 4,
|
||||
align: 'center'
|
||||
}],
|
||||
[{
|
||||
field: 'copy',
|
||||
title: '',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
clickToSelect: false,
|
||||
formatter: copyFormatter
|
||||
},
|
||||
{
|
||||
field: 'Notation',
|
||||
title: 'Notation',
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
field: 'Text',
|
||||
title: 'Bezeichnung',
|
||||
sortable: true,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: 'Funktionen',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
clickToSelect: false,
|
||||
events: window.operateEvents,
|
||||
formatter: operateFormatter
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function () {
|
||||
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||
selections = getIdSelections()
|
||||
}
|
||||
)
|
||||
|
||||
$table.on('all.bs.table', function (e, name, args) {
|
||||
console.log(name, args)
|
||||
})
|
||||
|
||||
$remove.click(function () {
|
||||
var ids = getIdSelections()
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: ids
|
||||
})
|
||||
$remove.prop('disabled', true)
|
||||
})
|
||||
}
|
||||
|
||||
function ReadDetails(RecordID) {
|
||||
$('#ID').val(RecordID);
|
||||
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||
$('#search_relation').data('record-id', RecordID);
|
||||
getData(1, 1, RecordID);
|
||||
}
|
||||
|
||||
function getData(number, size, idvalue) {
|
||||
var Type = document.getElementById("authType").value;
|
||||
if (typeof idvalue == "undefined") {
|
||||
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||
var id = 0
|
||||
} else {
|
||||
var id = idvalue
|
||||
var search = ''
|
||||
}
|
||||
$.get('/Thesaurus/ajax/getClassificationData.php', {
|
||||
id: id,
|
||||
search: search,
|
||||
offset: (number - 1) * size,
|
||||
authType: Type,
|
||||
limit: size
|
||||
}, function (res) {
|
||||
$table.bootstrapTable('load', res)
|
||||
})
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||
getData(number, size, id)
|
||||
})
|
||||
var options = $table.bootstrapTable('getOptions')
|
||||
getData(options.pageNumber, options.pageSize)
|
||||
})
|
||||
|
||||
$(function() {
|
||||
initTableClassification()
|
||||
$('#locale').change(initTableClassification)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Globale searchRelation Funktion
|
||||
window.searchRelation = function() {
|
||||
const $input = $('#search_relation');
|
||||
|
||||
if ($input.length === 0) {
|
||||
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||
$input.off('input blur focus');
|
||||
|
||||
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||
|
||||
let currentRequest = null;
|
||||
|
||||
$input.on('input', function() {
|
||||
const query = $(this).val().trim();
|
||||
console.log('🔍 Input erkannt:', query);
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||
$('.autocomplete-dropdown').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Sende AJAX Anfrage...');
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: query,
|
||||
authType: 'Classification'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||
showAutocompleteResults(data, $input);
|
||||
currentRequest = null;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (status !== 'abort') {
|
||||
console.error('❌ AJAX Fehler:', error);
|
||||
}
|
||||
currentRequest = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
setTimeout(function() {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$input.on('focus', function() {
|
||||
const query = $(this).val().trim();
|
||||
if (query.length >= 2) {
|
||||
$(this).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll resize', function() {
|
||||
if ($('.autocomplete-dropdown').length > 0) {
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$('.autocomplete-dropdown').css({
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showAutocompleteResults(data, $input) {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('⚠️ Keine Ergebnisse');
|
||||
return;
|
||||
}
|
||||
|
||||
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||
|
||||
data.forEach(function(item) {
|
||||
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||
const label = match ? match[1].trim() : item;
|
||||
const value = match ? match[2] : item;
|
||||
|
||||
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||
.text(label)
|
||||
.data('value', value)
|
||||
.data('label', label)
|
||||
.data('fulltext', item);
|
||||
|
||||
$item.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const selectedItem = {
|
||||
label: $(this).data('label'),
|
||||
value: $(this).data('value'),
|
||||
fullText: $(this).data('fulltext')
|
||||
};
|
||||
|
||||
console.log('✅ Ausgewählt:', selectedItem);
|
||||
|
||||
$input.val(selectedItem.label);
|
||||
$input.data('selected-id', selectedItem.value);
|
||||
$input.trigger('autocomplete:select', [selectedItem]);
|
||||
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
// Speichere die Werte in Data-Attributen auf dem Button
|
||||
const recordID = $('#ID').val();
|
||||
console.log('🔍 recordID:', recordID);
|
||||
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||
|
||||
$('#classification-anlegen').data('record-id', recordID);
|
||||
$('#classification-anlegen').data('relation-id', selectedItem.value);
|
||||
|
||||
console.log('✅ Data-Attribute gesetzt auf #classification-anlegen');
|
||||
});
|
||||
|
||||
$dropdown.append($item);
|
||||
});
|
||||
|
||||
$('body').append($dropdown);
|
||||
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const inputWidth = $input.outerWidth();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$dropdown.css({
|
||||
position: 'fixed',
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft,
|
||||
width: inputWidth,
|
||||
'min-width': inputWidth,
|
||||
'z-index': 9999
|
||||
});
|
||||
|
||||
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||
}
|
||||
};
|
||||
|
||||
// Automatische Initialisierung
|
||||
$(document).ready(function() {
|
||||
console.log('jQuery bereit');
|
||||
|
||||
$('.modal').on('shown.bs.modal', function () {
|
||||
console.log('📂 Modal geöffnet');
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
|
||||
// Click-Handler für den "Hinzufügen" Button
|
||||
$(document).on('click', '#classification-anlegen', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const recordID = $(this).data('record-id');
|
||||
const relationID = $(this).data('relation-id');
|
||||
|
||||
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||
|
||||
if (recordID && relationID) {
|
||||
CreateNewRelation(recordID, relationID, event);
|
||||
} else {
|
||||
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modals -->
|
||||
<?php
|
||||
$fp = fopen("templates/ClassificationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/ClassificationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/ClassificationDetailsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/ClassificationDetailsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/newClassification.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/newClassification.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Custom JS file -->
|
||||
<script type="text/javascript" src="js/ClassificationScript.js"></script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
476
Corporates.php
Normal file
476
Corporates.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Corporate">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">🏢 Körperschaftsverwaltung</h1>
|
||||
<button id="add" class="btn btn-primary" onclick="newCorporatesShow()">
|
||||
<i class="fas fa-plus"></i> Neue Körperschaft
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<table
|
||||
id="table"
|
||||
data-toolbar="#toolbar"
|
||||
data-search="true"
|
||||
data-query-params-type="limit"
|
||||
data-show-refresh="false"
|
||||
data-show-toggle="false"
|
||||
data-show-fullscreen="false"
|
||||
data-show-columns="false"
|
||||
data-show-columns-toggle-all="false"
|
||||
data-detail-view="false"
|
||||
data-show-export="false"
|
||||
data-click-to-select="false"
|
||||
data-trim-on-search="false"
|
||||
data-minimum-count-columns="2"
|
||||
data-show-pagination-switch="false"
|
||||
data-page-size="25"
|
||||
data-pagination="true"
|
||||
data-id-field="ID"
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-show-footer="false"
|
||||
data-side-pagination="server"
|
||||
data-server-sort="false"
|
||||
data-url="/Thesaurus/ajax/getCorporateData.php"
|
||||
data-response-handler="responseHandler">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Records Content -->
|
||||
<div class="row">
|
||||
<div class="records_content"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var $table = $('#table')
|
||||
var $remove = $('#remove')
|
||||
var selections = []
|
||||
|
||||
function getIdSelections() {
|
||||
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
|
||||
function responseHandler(res) {
|
||||
$.each(res.rows, function (i, row) {
|
||||
row.state = $.inArray(row.ID, selections) !== -1
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function detailFormatter(index, row) {
|
||||
var html = []
|
||||
$.each(row, function (key, value) {
|
||||
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||
if (value !== null && value !== undefined && String(value).length > 0)
|
||||
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||
}
|
||||
})
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
function copyFormatter(value, row, index) {
|
||||
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||
'title="Begriff kopieren">' +
|
||||
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||
'</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function operateFormatter(value, row, index) {
|
||||
return [
|
||||
'<div class="table-actions">',
|
||||
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||
'<i class="fas fa-table"></i>',
|
||||
'</a>',
|
||||
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||
'<i class="fas fa-project-diagram"></i>',
|
||||
'</a>',
|
||||
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||
'<i class="fas fa-edit"></i>',
|
||||
'</a>',
|
||||
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||
'<i class="fas fa-trash"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
window.operateEvents = {
|
||||
'click .relations': function (e, value, row, index) {
|
||||
ShowRelationsModal(row.ID, 'Corporate')
|
||||
},
|
||||
'click .details': function (e, value, row, index) {
|
||||
ShowModalDetails(row.ID)
|
||||
},
|
||||
'click .modify': function (e, value, row, index) {
|
||||
ModifyTerm([row.ID]),
|
||||
$table.bootstrapTable('refresh')
|
||||
},
|
||||
'click .remove': function (e, value, row, index) {
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: [row.ID]
|
||||
})
|
||||
DeleteTerm([row.ID])
|
||||
}
|
||||
}
|
||||
|
||||
function totalTextFormatter(data) {
|
||||
return 'Total'
|
||||
}
|
||||
|
||||
function totalNameFormatter(data) {
|
||||
return data.length
|
||||
}
|
||||
|
||||
function initTableCorporate() {
|
||||
$table.bootstrapTable('destroy').bootstrapTable({
|
||||
locale: $('#locale').val(),
|
||||
columns: [
|
||||
[{
|
||||
title: 'Körperschaften',
|
||||
colspan: 3,
|
||||
align: 'center'
|
||||
}],
|
||||
[{
|
||||
field: 'copy',
|
||||
title: '',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
clickToSelect: false,
|
||||
formatter: copyFormatter
|
||||
},
|
||||
{
|
||||
field: 'Text',
|
||||
title: 'Name',
|
||||
sortable: true,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: 'Funktionen',
|
||||
align: 'center',
|
||||
width: 190,
|
||||
clickToSelect: false,
|
||||
events: window.operateEvents,
|
||||
formatter: operateFormatter
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function () {
|
||||
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||
selections = getIdSelections()
|
||||
}
|
||||
)
|
||||
|
||||
$table.on('all.bs.table', function (e, name, args) {
|
||||
console.log(name, args)
|
||||
})
|
||||
|
||||
$remove.click(function () {
|
||||
var ids = getIdSelections()
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: ids
|
||||
})
|
||||
$remove.prop('disabled', true)
|
||||
})
|
||||
}
|
||||
|
||||
function ReadDetails(RecordID) {
|
||||
$('#ID').val(RecordID);
|
||||
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||
$('#search_relation').data('record-id', RecordID);
|
||||
getData(1, 1, RecordID);
|
||||
}
|
||||
|
||||
function getData(number, size, idvalue) {
|
||||
var Type = document.getElementById("authType").value;
|
||||
if (typeof idvalue == "undefined") {
|
||||
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||
var id = 0
|
||||
} else {
|
||||
var id = idvalue
|
||||
var search = ''
|
||||
}
|
||||
$.get('/Thesaurus/ajax/getCorporateData.php', {
|
||||
id: id,
|
||||
search: search,
|
||||
offset: (number - 1) * size,
|
||||
authType: Type,
|
||||
limit: size
|
||||
}, function (res) {
|
||||
$table.bootstrapTable('load', res)
|
||||
})
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||
getData(number, size, id)
|
||||
})
|
||||
var options = $table.bootstrapTable('getOptions')
|
||||
getData(options.pageNumber, options.pageSize)
|
||||
})
|
||||
|
||||
$(function() {
|
||||
initTableCorporate()
|
||||
$('#locale').change(initTableCorporate)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Globale searchRelation Funktion
|
||||
window.searchRelation = function() {
|
||||
const $input = $('#search_relation');
|
||||
|
||||
if ($input.length === 0) {
|
||||
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||
$input.off('input blur focus');
|
||||
|
||||
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||
|
||||
let currentRequest = null;
|
||||
|
||||
$input.on('input', function() {
|
||||
const query = $(this).val().trim();
|
||||
console.log('🔍 Input erkannt:', query);
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||
$('.autocomplete-dropdown').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Sende AJAX Anfrage...');
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: query,
|
||||
authType: 'Corporate'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||
showAutocompleteResults(data, $input);
|
||||
currentRequest = null;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (status !== 'abort') {
|
||||
console.error('❌ AJAX Fehler:', error);
|
||||
}
|
||||
currentRequest = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
setTimeout(function() {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$input.on('focus', function() {
|
||||
const query = $(this).val().trim();
|
||||
if (query.length >= 2) {
|
||||
$(this).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll resize', function() {
|
||||
if ($('.autocomplete-dropdown').length > 0) {
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$('.autocomplete-dropdown').css({
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showAutocompleteResults(data, $input) {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('⚠️ Keine Ergebnisse');
|
||||
return;
|
||||
}
|
||||
|
||||
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||
|
||||
data.forEach(function(item) {
|
||||
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||
const label = match ? match[1].trim() : item;
|
||||
const value = match ? match[2] : item;
|
||||
|
||||
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||
.text(label)
|
||||
.data('value', value)
|
||||
.data('label', label)
|
||||
.data('fulltext', item);
|
||||
|
||||
$item.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const selectedItem = {
|
||||
label: $(this).data('label'),
|
||||
value: $(this).data('value'),
|
||||
fullText: $(this).data('fulltext')
|
||||
};
|
||||
|
||||
console.log('✅ Ausgewählt:', selectedItem);
|
||||
|
||||
$input.val(selectedItem.label);
|
||||
$input.data('selected-id', selectedItem.value);
|
||||
$input.trigger('autocomplete:select', [selectedItem]);
|
||||
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
// Speichere die Werte in Data-Attributen auf dem Button
|
||||
const recordID = $('#ID').val();
|
||||
console.log('🔍 recordID:', recordID);
|
||||
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||
|
||||
$('#corporate-anlegen').data('record-id', recordID);
|
||||
$('#corporate-anlegen').data('relation-id', selectedItem.value);
|
||||
|
||||
console.log('✅ Data-Attribute gesetzt auf #corporate-anlegen');
|
||||
});
|
||||
|
||||
$dropdown.append($item);
|
||||
});
|
||||
|
||||
$('body').append($dropdown);
|
||||
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const inputWidth = $input.outerWidth();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$dropdown.css({
|
||||
position: 'fixed',
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft,
|
||||
width: inputWidth,
|
||||
'min-width': inputWidth,
|
||||
'z-index': 9999
|
||||
});
|
||||
|
||||
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||
}
|
||||
};
|
||||
|
||||
// Automatische Initialisierung
|
||||
$(document).ready(function() {
|
||||
console.log('jQuery bereit');
|
||||
|
||||
$('.modal').on('shown.bs.modal', function () {
|
||||
console.log('📂 Modal geöffnet');
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
|
||||
// Click-Handler für den "Hinzufügen" Button
|
||||
$(document).on('click', '#corporate-anlegen', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const recordID = $(this).data('record-id');
|
||||
const relationID = $(this).data('relation-id');
|
||||
|
||||
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||
|
||||
if (recordID && relationID) {
|
||||
CreateNewRelation(recordID, relationID, event);
|
||||
} else {
|
||||
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modals -->
|
||||
<?php
|
||||
$fp = fopen("templates/CorporatesModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/CorporatesModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/CorporateDetailsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/CorporateDetailsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/newCorporate.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/newCorporate.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Custom JS file -->
|
||||
<script type="text/javascript" src="js/CorporateScript.js"></script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
532
Persons.php
Normal file
532
Persons.php
Normal file
@ -0,0 +1,532 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Person">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">👥 Personenverwaltung</h1>
|
||||
<button id="add" class="btn btn-primary" onclick="newPersonShow()">
|
||||
<i class="fas fa-plus"></i> Neue Person
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<table
|
||||
id="table"
|
||||
data-toolbar="#toolbar"
|
||||
data-search="true"
|
||||
data-query-params-type="limit"
|
||||
data-show-refresh="false"
|
||||
data-show-toggle="false"
|
||||
data-show-fullscreen="false"
|
||||
data-show-columns="false"
|
||||
data-show-columns-toggle-all="false"
|
||||
data-detail-view="false"
|
||||
data-show-export="false"
|
||||
data-click-to-select="false"
|
||||
data-trim-on-search="false"
|
||||
data-minimum-count-columns="2"
|
||||
data-show-pagination-switch="false"
|
||||
data-page-size="25"
|
||||
data-pagination="true"
|
||||
data-id-field="ID"
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-show-footer="true"
|
||||
data-side-pagination="server"
|
||||
data-server-sort="false"
|
||||
data-url="/Thesaurus/ajax/getPersonData.php"
|
||||
data-response-handler="responseHandler">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Records Content -->
|
||||
<div class="row">
|
||||
<div class="records_content"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var $table = $('#table')
|
||||
var $remove = $('#remove')
|
||||
var selections = []
|
||||
|
||||
function getIdSelections() {
|
||||
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
|
||||
function responseHandler(res) {
|
||||
$.each(res.rows, function (i, row) {
|
||||
row.state = $.inArray(row.ID, selections) !== -1
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function detailFormatter(index, row) {
|
||||
var html = []
|
||||
$.each(row, function (key, value) {
|
||||
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||
if (value !== null && value !== undefined && String(value).length > 0)
|
||||
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||
}
|
||||
})
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
function copyFormatter(value, row, index) {
|
||||
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||
'title="Begriff kopieren">' +
|
||||
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||
'</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dspaceCountFormatter(value, row, index) {
|
||||
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
|
||||
return '<span class="text-muted" title="Kein Deskriptor">–</span>';
|
||||
}
|
||||
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '"') + '" data-authtype="Person">' +
|
||||
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
|
||||
'</span>';
|
||||
}
|
||||
|
||||
function operateFormatter(value, row, index) {
|
||||
return [
|
||||
'<div class="table-actions">',
|
||||
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||
'<i class="fas fa-table"></i>',
|
||||
'</a>',
|
||||
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||
'<i class="fas fa-project-diagram"></i>',
|
||||
'</a>',
|
||||
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||
'<i class="fas fa-edit"></i>',
|
||||
'</a>',
|
||||
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||
'<i class="fas fa-trash"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
window.operateEvents = {
|
||||
'click .relations': function (e, value, row, index) {
|
||||
ShowRelationsModal(row.ID, 'Person')
|
||||
},
|
||||
'click .details': function (e, value, row, index) {
|
||||
ShowModalDetails(row.ID)
|
||||
},
|
||||
'click .modify': function (e, value, row, index) {
|
||||
ModifyPerson([row.ID]),
|
||||
$table.bootstrapTable('refresh')
|
||||
},
|
||||
'click .remove': function (e, value, row, index) {
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: [row.ID]
|
||||
})
|
||||
DeleteTerm([row.ID])
|
||||
}
|
||||
}
|
||||
|
||||
function totalTextFormatter(data) {
|
||||
return 'Total'
|
||||
}
|
||||
|
||||
function totalNameFormatter(data) {
|
||||
return data.length
|
||||
}
|
||||
|
||||
function initTablePerson() {
|
||||
$table.bootstrapTable('destroy').bootstrapTable({
|
||||
locale: $('#locale').val(),
|
||||
columns: [
|
||||
[{
|
||||
title: 'Personen',
|
||||
colspan: 4,
|
||||
align: 'center'
|
||||
}],
|
||||
[{
|
||||
field: 'copy',
|
||||
title: '',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
clickToSelect: false,
|
||||
formatter: copyFormatter
|
||||
},
|
||||
{
|
||||
field: 'Text',
|
||||
title: 'Name',
|
||||
sortable: true,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'DSpaceCount',
|
||||
title: 'DSpace',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
clickToSelect: false,
|
||||
formatter: dspaceCountFormatter
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: 'Funktionen',
|
||||
align: 'center',
|
||||
width: 190,
|
||||
clickToSelect: false,
|
||||
events: window.operateEvents,
|
||||
formatter: operateFormatter
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function () {
|
||||
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||
selections = getIdSelections()
|
||||
}
|
||||
)
|
||||
|
||||
$table.on('all.bs.table', function (e, name, args) {
|
||||
console.log(name, args)
|
||||
})
|
||||
|
||||
$remove.click(function () {
|
||||
var ids = getIdSelections()
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: ids
|
||||
})
|
||||
$remove.prop('disabled', true)
|
||||
})
|
||||
}
|
||||
|
||||
function ReadDetails(RecordID) {
|
||||
$('#ID').val(RecordID);
|
||||
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||
$('#search_relation').data('record-id', RecordID);
|
||||
getData(1, 1, RecordID);
|
||||
}
|
||||
|
||||
function getData(number, size, idvalue) {
|
||||
var Type = document.getElementById("authType").value;
|
||||
if (typeof idvalue == "undefined") {
|
||||
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||
var id = 0
|
||||
} else {
|
||||
var id = idvalue
|
||||
var search = ''
|
||||
}
|
||||
$.get('/Thesaurus/ajax/getPersonData.php', {
|
||||
id: id,
|
||||
search: search,
|
||||
offset: (number - 1) * size,
|
||||
authType: Type,
|
||||
limit: size
|
||||
}, function (res) {
|
||||
$table.bootstrapTable('load', res)
|
||||
})
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||
getData(number, size, id)
|
||||
})
|
||||
var options = $table.bootstrapTable('getOptions')
|
||||
getData(options.pageNumber, options.pageSize)
|
||||
})
|
||||
|
||||
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
|
||||
$(function() {
|
||||
$table.on('post-body.bs.table', function () {
|
||||
loadDSpaceCounts();
|
||||
})
|
||||
})
|
||||
|
||||
function loadDSpaceCounts() {
|
||||
$('.dspace-count-cell').each(function() {
|
||||
var $cell = $(this);
|
||||
var term = $cell.data('term');
|
||||
var authType = $cell.data('authtype');
|
||||
|
||||
if (!term || $cell.data('loaded')) return;
|
||||
$cell.data('loaded', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||
type: 'GET',
|
||||
data: { authType: authType, query: term },
|
||||
dataType: 'json',
|
||||
timeout: 15000,
|
||||
success: function(data) {
|
||||
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||
if (count === -1) {
|
||||
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||
} else if (count > 0) {
|
||||
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
|
||||
} else {
|
||||
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
initTablePerson()
|
||||
$('#locale').change(initTablePerson)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Globale searchRelation Funktion
|
||||
window.searchRelation = function() {
|
||||
const $input = $('#search_relation');
|
||||
|
||||
if ($input.length === 0) {
|
||||
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||
$input.off('input blur focus');
|
||||
|
||||
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||
|
||||
let currentRequest = null;
|
||||
|
||||
$input.on('input', function() {
|
||||
const query = $(this).val().trim();
|
||||
console.log('🔍 Input erkannt:', query);
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||
$('.autocomplete-dropdown').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Sende AJAX Anfrage...');
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: query,
|
||||
authType: 'Person'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||
showAutocompleteResults(data, $input);
|
||||
currentRequest = null;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (status !== 'abort') {
|
||||
console.error('❌ AJAX Fehler:', error);
|
||||
}
|
||||
currentRequest = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
setTimeout(function() {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$input.on('focus', function() {
|
||||
const query = $(this).val().trim();
|
||||
if (query.length >= 2) {
|
||||
$(this).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll resize', function() {
|
||||
if ($('.autocomplete-dropdown').length > 0) {
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$('.autocomplete-dropdown').css({
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showAutocompleteResults(data, $input) {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('⚠️ Keine Ergebnisse');
|
||||
return;
|
||||
}
|
||||
|
||||
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||
|
||||
data.forEach(function(item) {
|
||||
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||
const label = match ? match[1].trim() : item;
|
||||
const value = match ? match[2] : item;
|
||||
|
||||
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||
.text(label)
|
||||
.data('value', value)
|
||||
.data('label', label)
|
||||
.data('fulltext', item);
|
||||
|
||||
$item.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const selectedItem = {
|
||||
label: $(this).data('label'),
|
||||
value: $(this).data('value'),
|
||||
fullText: $(this).data('fulltext')
|
||||
};
|
||||
|
||||
console.log('✅ Ausgewählt:', selectedItem);
|
||||
|
||||
$input.val(selectedItem.label);
|
||||
$input.data('selected-id', selectedItem.value);
|
||||
$input.trigger('autocomplete:select', [selectedItem]);
|
||||
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
// Speichere die Werte in Data-Attributen auf dem Button
|
||||
const recordID = $('#ID').val();
|
||||
console.log('🔍 recordID:', recordID);
|
||||
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||
|
||||
$('#subject-anlegen').data('record-id', recordID);
|
||||
$('#subject-anlegen').data('relation-id', selectedItem.value);
|
||||
|
||||
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
|
||||
});
|
||||
|
||||
$dropdown.append($item);
|
||||
});
|
||||
|
||||
$('body').append($dropdown);
|
||||
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const inputWidth = $input.outerWidth();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$dropdown.css({
|
||||
position: 'fixed',
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft,
|
||||
width: inputWidth,
|
||||
'min-width': inputWidth,
|
||||
'z-index': 9999
|
||||
});
|
||||
|
||||
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||
}
|
||||
};
|
||||
|
||||
// Automatische Initialisierung
|
||||
$(document).ready(function() {
|
||||
console.log('jQuery bereit');
|
||||
|
||||
$('.modal').on('shown.bs.modal', function () {
|
||||
console.log('📂 Modal geöffnet');
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
|
||||
// Click-Handler für den "Hinzufügen" Button
|
||||
$(document).on('click', '#subject-anlegen', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const recordID = $(this).data('record-id');
|
||||
const relationID = $(this).data('relation-id');
|
||||
|
||||
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||
|
||||
if (recordID && relationID) {
|
||||
CreateNewRelation(recordID, relationID, event);
|
||||
} else {
|
||||
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modals -->
|
||||
<?php
|
||||
$fp = fopen("templates/PersonsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/PersonsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/PersonDetailsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/PersonDetailsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/newPerson.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/newPerson.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Custom JS file -->
|
||||
<script type="text/javascript" src="js/PersonScript.js"></script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
476
Publishers.php
Normal file
476
Publishers.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Publisher">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">📚 Verlagsverwaltung</h1>
|
||||
<button id="add" class="btn btn-primary" onclick="newPublishersShow()">
|
||||
<i class="fas fa-plus"></i> Neuer Verlag
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<table
|
||||
id="table"
|
||||
data-toolbar="#toolbar"
|
||||
data-search="true"
|
||||
data-query-params-type="limit"
|
||||
data-show-refresh="false"
|
||||
data-show-toggle="false"
|
||||
data-show-fullscreen="false"
|
||||
data-show-columns="false"
|
||||
data-show-columns-toggle-all="false"
|
||||
data-detail-view="false"
|
||||
data-show-export="false"
|
||||
data-click-to-select="false"
|
||||
data-trim-on-search="false"
|
||||
data-minimum-count-columns="2"
|
||||
data-show-pagination-switch="false"
|
||||
data-page-size="25"
|
||||
data-pagination="true"
|
||||
data-id-field="ID"
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-show-footer="false"
|
||||
data-side-pagination="server"
|
||||
data-server-sort="false"
|
||||
data-url="/Thesaurus/ajax/getPublisherData.php"
|
||||
data-response-handler="responseHandler">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Records Content -->
|
||||
<div class="row">
|
||||
<div class="records_content"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var $table = $('#table')
|
||||
var $remove = $('#remove')
|
||||
var selections = []
|
||||
|
||||
function getIdSelections() {
|
||||
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
|
||||
function responseHandler(res) {
|
||||
$.each(res.rows, function (i, row) {
|
||||
row.state = $.inArray(row.ID, selections) !== -1
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function detailFormatter(index, row) {
|
||||
var html = []
|
||||
$.each(row, function (key, value) {
|
||||
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||
if (value !== null && value !== undefined && String(value).length > 0)
|
||||
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||
}
|
||||
})
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
function copyFormatter(value, row, index) {
|
||||
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||
'title="Begriff kopieren">' +
|
||||
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||
'</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function operateFormatter(value, row, index) {
|
||||
return [
|
||||
'<div class="table-actions">',
|
||||
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||
'<i class="fas fa-table"></i>',
|
||||
'</a>',
|
||||
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||
'<i class="fas fa-project-diagram"></i>',
|
||||
'</a>',
|
||||
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||
'<i class="fas fa-edit"></i>',
|
||||
'</a>',
|
||||
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||
'<i class="fas fa-trash"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
window.operateEvents = {
|
||||
'click .relations': function (e, value, row, index) {
|
||||
ShowRelationsModal(row.ID, 'Publisher')
|
||||
},
|
||||
'click .details': function (e, value, row, index) {
|
||||
ShowModalDetails(row.ID)
|
||||
},
|
||||
'click .modify': function (e, value, row, index) {
|
||||
ModifyTerm([row.ID]),
|
||||
$table.bootstrapTable('refresh')
|
||||
},
|
||||
'click .remove': function (e, value, row, index) {
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: [row.ID]
|
||||
})
|
||||
DeleteTerm([row.ID])
|
||||
}
|
||||
}
|
||||
|
||||
function totalTextFormatter(data) {
|
||||
return 'Total'
|
||||
}
|
||||
|
||||
function totalNameFormatter(data) {
|
||||
return data.length
|
||||
}
|
||||
|
||||
function initTablePublisher() {
|
||||
$table.bootstrapTable('destroy').bootstrapTable({
|
||||
locale: $('#locale').val(),
|
||||
columns: [
|
||||
[{
|
||||
title: 'Verlage',
|
||||
colspan: 3,
|
||||
align: 'center'
|
||||
}],
|
||||
[{
|
||||
field: 'copy',
|
||||
title: '',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
clickToSelect: false,
|
||||
formatter: copyFormatter
|
||||
},
|
||||
{
|
||||
field: 'Text',
|
||||
title: 'Name',
|
||||
sortable: true,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: 'Funktionen',
|
||||
align: 'center',
|
||||
width: 190,
|
||||
clickToSelect: false,
|
||||
events: window.operateEvents,
|
||||
formatter: operateFormatter
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function () {
|
||||
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||
selections = getIdSelections()
|
||||
}
|
||||
)
|
||||
|
||||
$table.on('all.bs.table', function (e, name, args) {
|
||||
console.log(name, args)
|
||||
})
|
||||
|
||||
$remove.click(function () {
|
||||
var ids = getIdSelections()
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: ids
|
||||
})
|
||||
$remove.prop('disabled', true)
|
||||
})
|
||||
}
|
||||
|
||||
function ReadDetails(RecordID) {
|
||||
$('#ID').val(RecordID);
|
||||
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||
$('#search_relation').data('record-id', RecordID);
|
||||
getData(1, 1, RecordID);
|
||||
}
|
||||
|
||||
function getData(number, size, idvalue) {
|
||||
var Type = document.getElementById("authType").value;
|
||||
if (typeof idvalue == "undefined") {
|
||||
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||
var id = 0
|
||||
} else {
|
||||
var id = idvalue
|
||||
var search = ''
|
||||
}
|
||||
$.get('/Thesaurus/ajax/getPublisherData.php', {
|
||||
id: id,
|
||||
search: search,
|
||||
offset: (number - 1) * size,
|
||||
authType: Type,
|
||||
limit: size
|
||||
}, function (res) {
|
||||
$table.bootstrapTable('load', res)
|
||||
})
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||
getData(number, size, id)
|
||||
})
|
||||
var options = $table.bootstrapTable('getOptions')
|
||||
getData(options.pageNumber, options.pageSize)
|
||||
})
|
||||
|
||||
$(function() {
|
||||
initTablePublisher()
|
||||
$('#locale').change(initTablePublisher)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Globale searchRelation Funktion
|
||||
window.searchRelation = function() {
|
||||
const $input = $('#search_relation');
|
||||
|
||||
if ($input.length === 0) {
|
||||
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||
$input.off('input blur focus');
|
||||
|
||||
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||
|
||||
let currentRequest = null;
|
||||
|
||||
$input.on('input', function() {
|
||||
const query = $(this).val().trim();
|
||||
console.log('🔍 Input erkannt:', query);
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||
$('.autocomplete-dropdown').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Sende AJAX Anfrage...');
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: query,
|
||||
authType: 'Publisher'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||
showAutocompleteResults(data, $input);
|
||||
currentRequest = null;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (status !== 'abort') {
|
||||
console.error('❌ AJAX Fehler:', error);
|
||||
}
|
||||
currentRequest = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
setTimeout(function() {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$input.on('focus', function() {
|
||||
const query = $(this).val().trim();
|
||||
if (query.length >= 2) {
|
||||
$(this).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll resize', function() {
|
||||
if ($('.autocomplete-dropdown').length > 0) {
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$('.autocomplete-dropdown').css({
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showAutocompleteResults(data, $input) {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('⚠️ Keine Ergebnisse');
|
||||
return;
|
||||
}
|
||||
|
||||
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||
|
||||
data.forEach(function(item) {
|
||||
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||
const label = match ? match[1].trim() : item;
|
||||
const value = match ? match[2] : item;
|
||||
|
||||
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||
.text(label)
|
||||
.data('value', value)
|
||||
.data('label', label)
|
||||
.data('fulltext', item);
|
||||
|
||||
$item.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const selectedItem = {
|
||||
label: $(this).data('label'),
|
||||
value: $(this).data('value'),
|
||||
fullText: $(this).data('fulltext')
|
||||
};
|
||||
|
||||
console.log('✅ Ausgewählt:', selectedItem);
|
||||
|
||||
$input.val(selectedItem.label);
|
||||
$input.data('selected-id', selectedItem.value);
|
||||
$input.trigger('autocomplete:select', [selectedItem]);
|
||||
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
// Speichere die Werte in Data-Attributen auf dem Button
|
||||
const recordID = $('#ID').val();
|
||||
console.log('🔍 recordID:', recordID);
|
||||
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||
|
||||
$('#publisher-anlegen').data('record-id', recordID);
|
||||
$('#publisher-anlegen').data('relation-id', selectedItem.value);
|
||||
|
||||
console.log('✅ Data-Attribute gesetzt auf #publisher-anlegen');
|
||||
});
|
||||
|
||||
$dropdown.append($item);
|
||||
});
|
||||
|
||||
$('body').append($dropdown);
|
||||
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const inputWidth = $input.outerWidth();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$dropdown.css({
|
||||
position: 'fixed',
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft,
|
||||
width: inputWidth,
|
||||
'min-width': inputWidth,
|
||||
'z-index': 9999
|
||||
});
|
||||
|
||||
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||
}
|
||||
};
|
||||
|
||||
// Automatische Initialisierung
|
||||
$(document).ready(function() {
|
||||
console.log('jQuery bereit');
|
||||
|
||||
$('.modal').on('shown.bs.modal', function () {
|
||||
console.log('📂 Modal geöffnet');
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
|
||||
// Click-Handler für den "Hinzufügen" Button
|
||||
$(document).on('click', '#publisher-anlegen', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const recordID = $(this).data('record-id');
|
||||
const relationID = $(this).data('relation-id');
|
||||
|
||||
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||
|
||||
if (recordID && relationID) {
|
||||
CreateNewRelation(recordID, relationID, event);
|
||||
} else {
|
||||
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modals -->
|
||||
<?php
|
||||
$fp = fopen("templates/PublishersModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/PublishersModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/PublisherDetailsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/PublisherDetailsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/newPublisher.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/newPublisher.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Custom JS file -->
|
||||
<script type="text/javascript" src="js/PublisherScript.js"></script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Thesaurus
|
||||
|
||||
Webbasierte Thesaurus-Verwaltung für Schlagwörter, Personen, Körperschaften, Verlage und Klassifikationen.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- PHP 7.4 oder höher
|
||||
- MySQL/MariaDB
|
||||
- Apache Webserver
|
||||
|
||||
## Installation
|
||||
|
||||
1. Repository klonen
|
||||
2. `ajax/db_connection.php.example` kopieren und umbenennen:
|
||||
```bash
|
||||
cp ajax/db_connection.php.example ajax/db_connection.php
|
||||
```
|
||||
3. Zugangsdaten in `ajax/db_connection.php` eintragen
|
||||
4. Abhängigkeiten installieren:
|
||||
```bash
|
||||
bash setup-libs.sh
|
||||
```
|
||||
545
Subjects.php
Normal file
545
Subjects.php
Normal file
@ -0,0 +1,545 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Subject">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">🏷️ Schlagwortverwaltung</h1>
|
||||
<button id="add" class="btn btn-primary" onclick="newSubjectShow()">
|
||||
<i class="fas fa-plus"></i> Neuer Deskriptor
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<table
|
||||
id="table"
|
||||
data-toolbar="#toolbar"
|
||||
data-search="true"
|
||||
data-query-params-type="limit"
|
||||
data-show-refresh="false"
|
||||
data-show-toggle="false"
|
||||
data-show-fullscreen="false"
|
||||
data-show-columns="false"
|
||||
data-show-columns-toggle-all="false"
|
||||
data-detail-view="false"
|
||||
data-show-export="false"
|
||||
data-click-to-select="false"
|
||||
data-trim-on-search="false"
|
||||
data-minimum-count-columns="2"
|
||||
data-show-pagination-switch="false"
|
||||
data-page-size="25"
|
||||
data-pagination="true"
|
||||
data-id-field="ID"
|
||||
data-page-list="[10, 25, 50, 100, all]"
|
||||
data-show-footer="true"
|
||||
data-side-pagination="server"
|
||||
data-server-sort="true"
|
||||
data-url="/Thesaurus/ajax/getSubjectData.php"
|
||||
data-response-handler="responseHandler">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Records Content -->
|
||||
<div class="row">
|
||||
<div class="records_content"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
var $table = $('#table')
|
||||
var $remove = $('#remove')
|
||||
var selections = []
|
||||
|
||||
function getIdSelections() {
|
||||
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
|
||||
function responseHandler(res) {
|
||||
$.each(res.rows, function (i, row) {
|
||||
row.state = $.inArray(row.ID, selections) !== -1
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function detailFormatter(index, row) {
|
||||
var html = []
|
||||
$.each(row, function (key, value) {
|
||||
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||
if (value !== null && value !== undefined && String(value).length > 0)
|
||||
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||
}
|
||||
})
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
function copyFormatter(value, row, index) {
|
||||
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||
'title="Begriff kopieren">' +
|
||||
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||
'</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dspaceCountFormatter(value, row, index) {
|
||||
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
|
||||
return '<span class="text-muted" title="Kein Deskriptor">–</span>';
|
||||
}
|
||||
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '"') + '" data-authtype="Subject">' +
|
||||
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
|
||||
'</span>';
|
||||
}
|
||||
|
||||
function operateFormatter(value, row, index) {
|
||||
return [
|
||||
'<div class="table-actions">',
|
||||
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||
'<i class="fas fa-table"></i>',
|
||||
'</a>',
|
||||
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||
'<i class="fas fa-project-diagram"></i>',
|
||||
'</a>',
|
||||
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||
'<i class="fas fa-edit"></i>',
|
||||
'</a>',
|
||||
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||
'<i class="fas fa-trash"></i>',
|
||||
'</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
window.operateEvents = {
|
||||
'click .relations': function (e, value, row, index) {
|
||||
ShowRelationsModal(row.ID, 'Subject')
|
||||
},
|
||||
'click .details': function (e, value, row, index) {
|
||||
ShowModalDetails(row.ID)
|
||||
},
|
||||
'click .modify': function (e, value, row, index) {
|
||||
ModifyTerm([row.ID]),
|
||||
$table.bootstrapTable('refresh')
|
||||
},
|
||||
'click .remove': function (e, value, row, index) {
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: [row.ID]
|
||||
})
|
||||
DeleteTerm([row.ID])
|
||||
}
|
||||
}
|
||||
|
||||
function totalTextFormatter(data) {
|
||||
return 'Total'
|
||||
}
|
||||
|
||||
function totalNameFormatter(data) {
|
||||
return data.length
|
||||
}
|
||||
|
||||
function initTableSubject() {
|
||||
$table.bootstrapTable('destroy').bootstrapTable({
|
||||
locale: $('#locale').val(),
|
||||
columns: [
|
||||
[{
|
||||
title: 'Schlagworte',
|
||||
colspan: 4,
|
||||
align: 'center'
|
||||
}],
|
||||
[{
|
||||
field: 'copy',
|
||||
title: '',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
clickToSelect: false,
|
||||
formatter: copyFormatter
|
||||
},
|
||||
{
|
||||
field: 'Text',
|
||||
title: 'Begriff',
|
||||
sortable: true,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'DSpaceCount',
|
||||
title: 'DSpace',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
clickToSelect: false,
|
||||
formatter: dspaceCountFormatter
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: 'Funktionen',
|
||||
align: 'center',
|
||||
width: 190,
|
||||
clickToSelect: false,
|
||||
events: window.operateEvents,
|
||||
formatter: operateFormatter
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function () {
|
||||
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||
selections = getIdSelections()
|
||||
}
|
||||
)
|
||||
|
||||
$table.on('all.bs.table', function (e, name, args) {
|
||||
console.log(name, args)
|
||||
})
|
||||
|
||||
$remove.click(function () {
|
||||
var ids = getIdSelections()
|
||||
$table.bootstrapTable('remove', {
|
||||
field: 'ID',
|
||||
values: ids
|
||||
})
|
||||
$remove.prop('disabled', true)
|
||||
})
|
||||
}
|
||||
|
||||
function ReadDetails(RecordID) {
|
||||
$('#ID').val(RecordID);
|
||||
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||
$('#search_relation').data('record-id', RecordID);
|
||||
getData(1, 1, RecordID);
|
||||
}
|
||||
|
||||
function getData(number, size, idvalue) {
|
||||
var Type = document.getElementById("authType").value;
|
||||
if (typeof idvalue == "undefined") {
|
||||
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||
var id = 0
|
||||
} else {
|
||||
var id = idvalue
|
||||
var search = ''
|
||||
}
|
||||
$.get('/Thesaurus/ajax/getSubjectData.php', {
|
||||
id: id,
|
||||
search: search,
|
||||
offset: (number - 1) * size,
|
||||
authType: Type,
|
||||
limit: size
|
||||
}, function (res) {
|
||||
$table.bootstrapTable('load', res)
|
||||
})
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||
getData(number, size, id)
|
||||
})
|
||||
var options = $table.bootstrapTable('getOptions')
|
||||
getData(options.pageNumber, options.pageSize)
|
||||
})
|
||||
|
||||
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
|
||||
$(function() {
|
||||
$table.on('post-body.bs.table', function () {
|
||||
loadDSpaceCounts();
|
||||
})
|
||||
})
|
||||
|
||||
function loadDSpaceCounts() {
|
||||
$('.dspace-count-cell').each(function() {
|
||||
var $cell = $(this);
|
||||
var term = $cell.data('term');
|
||||
var authType = $cell.data('authtype');
|
||||
|
||||
if (!term || $cell.data('loaded')) return;
|
||||
$cell.data('loaded', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||
type: 'GET',
|
||||
data: { authType: authType, query: term },
|
||||
dataType: 'json',
|
||||
timeout: 15000,
|
||||
success: function(data) {
|
||||
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||
if (count === -1) {
|
||||
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||
} else if (count > 0) {
|
||||
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
|
||||
} else {
|
||||
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
initTableSubject()
|
||||
$('#locale').change(initTableSubject)
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- Custom JS file - muss vor dem Autocomplete-Script geladen werden -->
|
||||
<script type="text/javascript" src="js/SubjectScript.js"></script>
|
||||
|
||||
<script>
|
||||
// Globale searchRelation Funktion
|
||||
window.searchRelation = function() {
|
||||
const $input = $('#search_relation');
|
||||
|
||||
if ($input.length === 0) {
|
||||
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||
$input.off('input blur focus');
|
||||
|
||||
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||
|
||||
let currentRequest = null;
|
||||
|
||||
$input.on('input', function() {
|
||||
const query = $(this).val().trim();
|
||||
console.log('🔍 Input erkannt:', query);
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||
$('.autocomplete-dropdown').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Sende AJAX Anfrage...');
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: query,
|
||||
authType: 'Subject'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||
showAutocompleteResults(data, $input);
|
||||
currentRequest = null;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (status !== 'abort') {
|
||||
console.error('❌ AJAX Fehler:', error);
|
||||
}
|
||||
currentRequest = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
setTimeout(function() {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$input.on('focus', function() {
|
||||
const query = $(this).val().trim();
|
||||
if (query.length >= 2) {
|
||||
$(this).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll resize', function() {
|
||||
if ($('.autocomplete-dropdown').length > 0) {
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$('.autocomplete-dropdown').css({
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showAutocompleteResults(data, $input) {
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log('⚠️ Keine Ergebnisse');
|
||||
return;
|
||||
}
|
||||
|
||||
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||
|
||||
data.forEach(function(item) {
|
||||
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||
const label = match ? match[1].trim() : item;
|
||||
const value = match ? match[2] : item;
|
||||
|
||||
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||
.text(label)
|
||||
.data('value', value)
|
||||
.data('label', label)
|
||||
.data('fulltext', item);
|
||||
|
||||
$item.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const selectedItem = {
|
||||
label: $(this).data('label'),
|
||||
value: $(this).data('value'),
|
||||
fullText: $(this).data('fulltext')
|
||||
};
|
||||
|
||||
console.log('✅ Ausgewählt:', selectedItem);
|
||||
|
||||
$input.val(selectedItem.label);
|
||||
$input.data('selected-id', selectedItem.value);
|
||||
$input.trigger('autocomplete:select', [selectedItem]);
|
||||
|
||||
$('.autocomplete-dropdown').remove();
|
||||
|
||||
// Speichere die Werte in Data-Attributen auf dem Button
|
||||
const recordID = $('#ID').val();
|
||||
const recordText = $('#new_term').val(); // Text des aktuellen Schlagworts
|
||||
console.log('🔍 recordID:', recordID);
|
||||
console.log('🔍 recordText:', recordText);
|
||||
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||
console.log('🔍 selectedItem.label:', selectedItem.label);
|
||||
|
||||
$('#subject-anlegen').data('record-id', recordID);
|
||||
$('#subject-anlegen').data('record-text', recordText);
|
||||
$('#subject-anlegen').data('relation-id', selectedItem.value);
|
||||
$('#subject-anlegen').data('relation-text', selectedItem.label);
|
||||
|
||||
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
|
||||
});
|
||||
|
||||
$dropdown.append($item);
|
||||
});
|
||||
|
||||
$('body').append($dropdown);
|
||||
|
||||
const offset = $input.offset();
|
||||
const inputHeight = $input.outerHeight();
|
||||
const inputWidth = $input.outerWidth();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
|
||||
$dropdown.css({
|
||||
position: 'fixed',
|
||||
top: offset.top - scrollTop + inputHeight,
|
||||
left: offset.left - scrollLeft,
|
||||
width: inputWidth,
|
||||
'min-width': inputWidth,
|
||||
'z-index': 9999
|
||||
});
|
||||
|
||||
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||
}
|
||||
};
|
||||
|
||||
// Automatische Initialisierung
|
||||
$(document).ready(function() {
|
||||
console.log('jQuery bereit');
|
||||
|
||||
$('.modal').on('shown.bs.modal', function () {
|
||||
console.log('📂 Modal geöffnet');
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
});
|
||||
|
||||
if ($('#search_relation').length > 0) {
|
||||
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||
searchRelation();
|
||||
}
|
||||
|
||||
// Click-Handler für den "Hinzufügen" Button
|
||||
$(document).on('click', '#subject-anlegen', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const recordID = $(this).data('record-id');
|
||||
const recordText = $(this).data('record-text');
|
||||
const relationID = $(this).data('relation-id');
|
||||
const relationText = $(this).data('relation-text');
|
||||
const relationType = $('#new_relationtype').val();
|
||||
|
||||
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID, '- relationType:', relationType);
|
||||
|
||||
if (recordID && relationID) {
|
||||
// Bei Synonym-Typ: andere Speicherfunktion verwenden
|
||||
if (relationType === 'SYN') {
|
||||
CreateNewSynonym(recordText, relationText, recordID, event);
|
||||
} else {
|
||||
CreateNewRelation(recordID, relationID, event);
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modals -->
|
||||
<?php
|
||||
$fp = fopen("templates/SubjectsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/SubjectsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/SubjectDetailsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/SubjectDetailsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/newSubject.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/newSubject.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||
if ($fp) {
|
||||
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||
fclose($fp);
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
671
Treeview.php
Normal file
671
Treeview.php
Normal file
@ -0,0 +1,671 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input type="hidden" id="locale" value="de-DE">
|
||||
<input type="hidden" id="ID" value="">
|
||||
<input type="hidden" id="authType" value="Classification">
|
||||
|
||||
<!-- jsTree CSS -->
|
||||
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css" onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';" />
|
||||
|
||||
<style>
|
||||
/* Treeview Container */
|
||||
.treeview-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
min-height: 600px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tree-panel {
|
||||
flex: 3;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 2;
|
||||
min-width: 0;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
/* Tree Styling */
|
||||
#classification-tree {
|
||||
padding: 15px;
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
min-height: 500px;
|
||||
max-height: 75vh;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* jsTree Anpassungen für BIBB-Blau - verstärkt */
|
||||
.jstree-default .jstree-clicked,
|
||||
.jstree-default .jstree-wholerow-clicked {
|
||||
background: #1a3a5c !important;
|
||||
color: white !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.jstree-default .jstree-clicked .jstree-icon,
|
||||
.jstree-default .jstree-wholerow-clicked + .jstree-anchor .jstree-icon {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.jstree-default .jstree-hovered,
|
||||
.jstree-default .jstree-wholerow-hovered {
|
||||
background: #e9ecef !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.jstree-default .jstree-anchor {
|
||||
color: var(--text-primary);
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
max-width: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.jstree-default .jstree-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Detail Panel */
|
||||
.detail-card {
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 20px;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.detail-card h4 {
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 50px 20px;
|
||||
}
|
||||
|
||||
.no-selection i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.tree-toolbar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tree-toolbar .btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.tree-search {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.tree-search input {
|
||||
width: 100%;
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
.tree-search i.fa-search {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-muted);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Suchergebnisse Dropdown */
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-results.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.search-result-item:hover {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.search-result-item .result-text {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.search-result-item .result-id {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.search-no-results {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1200px) {
|
||||
.treeview-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tree-panel, .detail-panel {
|
||||
min-width: 100%;
|
||||
flex: none
|
||||
}
|
||||
|
||||
.tree-panel, .detail-panel {
|
||||
min-width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
position: static; /* kein sticky bei gestapelter Ansicht */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="card">
|
||||
<div class="d-flex justify-between align-center mb-20">
|
||||
<h1 class="card-title" style="margin: 0;">🌳 Klassifikationsbaum</h1>
|
||||
</div>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="tree-toolbar">
|
||||
<div class="tree-search">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="tree-search-input" class="form-control" placeholder="Klassifikation suchen..." autocomplete="off" />
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" id="btn-expand-all" title="Alle aufklappen">
|
||||
<i class="fas fa-expand-alt"></i> Alle öffnen
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="btn-collapse-all" title="Alle zuklappen">
|
||||
<i class="fas fa-compress-alt"></i> Alle schließen
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="btn-refresh" title="Aktualisieren">
|
||||
<i class="fas fa-sync"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Treeview Container -->
|
||||
<div class="treeview-container">
|
||||
<!-- Tree Panel -->
|
||||
<div class="tree-panel">
|
||||
<div id="classification-tree"></div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Panel -->
|
||||
<div class="detail-panel">
|
||||
<div class="detail-card">
|
||||
<div id="detail-content">
|
||||
<div class="no-selection">
|
||||
<i class="fas fa-hand-pointer"></i>
|
||||
<p>Wählen Sie eine Klassifikation aus dem Baum aus, um Details anzuzeigen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- jsTree JS -->
|
||||
<script src="/libs/jstree/current/jstree.min.js"></script>
|
||||
<script>if (typeof $.jstree === "undefined") { document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/jstree.min.js"><\/script>'); }</script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
// Tree initialisieren
|
||||
initTree();
|
||||
|
||||
// Toolbar Events
|
||||
$('#btn-expand-all').on('click', function() {
|
||||
$('#classification-tree').jstree('open_all');
|
||||
});
|
||||
|
||||
$('#btn-collapse-all').on('click', function() {
|
||||
$('#classification-tree').jstree('close_all');
|
||||
});
|
||||
|
||||
$('#btn-refresh').on('click', function() {
|
||||
$('#classification-tree').jstree('refresh');
|
||||
});
|
||||
|
||||
// Serverseitige Suche
|
||||
var searchTimeout = null;
|
||||
$('#tree-search-input').on('keyup', function() {
|
||||
var searchString = $(this).val().trim();
|
||||
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
if (searchString.length < 2) {
|
||||
$('#search-results').removeClass('show').empty();
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimeout = setTimeout(function() {
|
||||
performSearch(searchString);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Suche schließen bei Klick außerhalb
|
||||
$(document).on('click', function(e) {
|
||||
if (!$(e.target).closest('.tree-search').length) {
|
||||
$('#search-results').removeClass('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Focus auf Suchfeld zeigt Ergebnisse wieder
|
||||
$('#tree-search-input').on('focus', function() {
|
||||
if ($('#search-results').children().length > 0) {
|
||||
$('#search-results').addClass('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function initTree() {
|
||||
$('#classification-tree').jstree({
|
||||
'core': {
|
||||
'data': {
|
||||
'url': function(node) {
|
||||
return '/Thesaurus/ajax/getTreeData.php';
|
||||
},
|
||||
'data': function(node) {
|
||||
return {
|
||||
'id': node.id
|
||||
};
|
||||
}
|
||||
},
|
||||
'themes': {
|
||||
'name': 'default',
|
||||
'responsive': true,
|
||||
'dots': true,
|
||||
'icons': true
|
||||
},
|
||||
'multiple': false,
|
||||
'animation': 200
|
||||
},
|
||||
'plugins': ['wholerow', 'sort'],
|
||||
'sort': function(a, b) {
|
||||
var nodeA = this.get_text(a);
|
||||
var nodeB = this.get_text(b);
|
||||
return nodeA.localeCompare(nodeB, 'de', {numeric: true});
|
||||
}
|
||||
});
|
||||
|
||||
// Node ausgewählt
|
||||
$('#classification-tree').on('select_node.jstree', function(e, data) {
|
||||
var node = data.node;
|
||||
loadDetails(node.id, node.text, node.data);
|
||||
});
|
||||
|
||||
// Tree geladen
|
||||
$('#classification-tree').on('loaded.jstree', function() {
|
||||
console.log('✅ Klassifikationsbaum geladen');
|
||||
});
|
||||
}
|
||||
|
||||
function performSearch(searchString) {
|
||||
console.log('🔍 Suche nach:', searchString);
|
||||
|
||||
$.get('/Thesaurus/ajax/searchTreeData.php', {
|
||||
q: searchString
|
||||
}, function(data) {
|
||||
var results = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
var html = '';
|
||||
|
||||
if (results && results.length > 0) {
|
||||
results.forEach(function(item) {
|
||||
html += '<div class="search-result-item" data-id="' + item.id + '">';
|
||||
html += '<div class="result-text">' + escapeHtml(item.text) + '</div>';
|
||||
html += '<div class="result-id">ID: ' + item.id + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
} else {
|
||||
html = '<div class="search-no-results"><i class="fas fa-search"></i> Keine Ergebnisse gefunden</div>';
|
||||
}
|
||||
|
||||
$('#search-results').html(html).addClass('show');
|
||||
|
||||
// Klick auf Suchergebnis
|
||||
$('.search-result-item').on('click', function() {
|
||||
var id = $(this).data('id');
|
||||
selectAndShowNode(id);
|
||||
$('#search-results').removeClass('show');
|
||||
$('#tree-search-input').val('');
|
||||
});
|
||||
}).fail(function() {
|
||||
$('#search-results').html('<div class="search-no-results">Fehler bei der Suche</div>').addClass('show');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// PATCH für Treeview.php – Funktion selectAndShowNode ersetzen (v3)
|
||||
// Ersetzt die bisherige selectAndShowNode() und activateNode() komplett.
|
||||
|
||||
function selectAndShowNode(id) {
|
||||
id = String(id);
|
||||
|
||||
$.get('/Thesaurus/ajax/getTreePath.php', { id: id }, function(path) {
|
||||
|
||||
if (!path || path.length === 0) {
|
||||
console.warn('⚠️ Kein Pfad gefunden für ID:', id);
|
||||
loadDetails(id, '', {});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📂 Pfad zum Knoten:', path);
|
||||
|
||||
// Vorfahren = alle Elemente außer dem Zielknoten selbst
|
||||
var ancestors = path.slice(0, -1).map(String);
|
||||
var targetId = String(path[path.length - 1]);
|
||||
|
||||
// Sequenziell öffnen: erst wenn ein Knoten vollständig geöffnet
|
||||
// (und seine Kinder geladen) sind, wird der nächste geöffnet.
|
||||
openSequential(ancestors, 0, function() {
|
||||
activateNode(targetId);
|
||||
});
|
||||
|
||||
}).fail(function() {
|
||||
console.error('❌ getTreePath.php fehlgeschlagen');
|
||||
loadDetails(id, '', {});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet Knoten aus dem ancestors-Array nacheinander.
|
||||
* Wartet nach jedem open_node auf after_open.jstree bevor weitergemacht wird.
|
||||
* Knoten die bereits offen sind werden übersprungen.
|
||||
*/
|
||||
function openSequential(ancestors, index, callback) {
|
||||
if (index >= ancestors.length) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = $('#classification-tree').jstree(true);
|
||||
var nodeId = ancestors[index];
|
||||
var node = tree.get_node(nodeId);
|
||||
|
||||
// Knoten bereits offen oder nicht auffindbar → nächster
|
||||
if (!node || node.state.opened) {
|
||||
console.log('⏭️ Überspringe (bereits offen oder nicht geladen):', nodeId);
|
||||
openSequential(ancestors, index + 1, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔓 Öffne Knoten:', nodeId);
|
||||
|
||||
// Einmaliger after_open-Handler nur für diesen Knoten
|
||||
function onOpen(e, data) {
|
||||
if (String(data.node.id) !== nodeId) return; // anderer Knoten
|
||||
$('#classification-tree').off('after_open.jstree', onOpen);
|
||||
console.log('✅ Geöffnet:', nodeId);
|
||||
openSequential(ancestors, index + 1, callback);
|
||||
}
|
||||
|
||||
$('#classification-tree').on('after_open.jstree', onOpen);
|
||||
|
||||
// Sicherheits-Timeout pro Knoten (3 Sekunden)
|
||||
setTimeout(function() {
|
||||
$('#classification-tree').off('after_open.jstree', onOpen);
|
||||
console.warn('⏱️ Timeout beim Öffnen von:', nodeId);
|
||||
openSequential(ancestors, index + 1, callback);
|
||||
}, 3000);
|
||||
|
||||
tree.open_node(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt den Zielknoten aus und scrollt ihn in Sicht.
|
||||
*/
|
||||
function activateNode(id) {
|
||||
var tree = $('#classification-tree').jstree(true);
|
||||
console.log('🎯 Aktiviere Knoten:', id);
|
||||
|
||||
tree.deselect_all();
|
||||
tree.select_node(id);
|
||||
|
||||
setTimeout(function() {
|
||||
var anchor = document.getElementById(id + '_anchor');
|
||||
if (anchor) {
|
||||
anchor.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
console.log('📜 Gescrollt zu:', id);
|
||||
} else {
|
||||
console.warn('⚠️ Anchor nicht gefunden für ID:', id);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function loadDetails(id, text, nodeData) {
|
||||
console.log('📖 Lade Details für ID:', id);
|
||||
|
||||
// Loading anzeigen
|
||||
$('#detail-content').html('<div class="text-center p-4"><div class="spinner-border text-primary" role="status"></div><p class="mt-2">Laden...</p></div>');
|
||||
|
||||
$.get('/Thesaurus/ajax/getDetailAuthorityData.php', {
|
||||
authType: 'Classification',
|
||||
id: id,
|
||||
offset: 0,
|
||||
limit: 1
|
||||
}, function(data, status) {
|
||||
if (status === 'success') {
|
||||
try {
|
||||
var metadata = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'] ? metadata['rows'][0] : null;
|
||||
|
||||
if (row) {
|
||||
renderDetails(row);
|
||||
} else {
|
||||
// Fallback: Zeige zumindest die Basis-Infos aus dem Baum
|
||||
renderBasicDetails(id, text, nodeData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Parse-Fehler:', e);
|
||||
renderBasicDetails(id, text, nodeData);
|
||||
}
|
||||
}
|
||||
}).fail(function() {
|
||||
renderBasicDetails(id, text, nodeData);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDetails(row) {
|
||||
var html = '';
|
||||
|
||||
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(row.Text || row.name || 'Unbekannt') + '</h4>';
|
||||
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">ID:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(row.ID || '') + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (row.Notation) {
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">Notation:</span>';
|
||||
html += '<span class="detail-value"><strong>' + escapeHtml(row.Notation) + '</strong></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
/* if (row.Descriptor) {
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">Deskriptor:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(row.Descriptor) + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
*/
|
||||
if (row.Type) {
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">Typ:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(row.Type) + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (row.Scopenote) {
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">Anmerkung:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(row.Scopenote) + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Relationen
|
||||
if (row.Relations && row.Relations.length > 0) {
|
||||
html += '<hr>';
|
||||
html += '<h5><i class="fas fa-link me-2"></i>Relationen</h5>';
|
||||
html += '<div class="relations-list">';
|
||||
|
||||
row.Relations.forEach(function(rel) {
|
||||
html += '<div class="relation-item" style="display: flex; align-items: center; padding: 8px 12px; background: white; border-radius: 4px; margin-bottom: 8px; border: 1px solid #dee2e6;">';
|
||||
html += '<span style="font-weight: 600; color: var(--primary-color); width: 80px; flex-shrink: 0;">' + escapeHtml(rel.Relationtype || '') + '</span>';
|
||||
html += '<span style="flex: 1;">';
|
||||
html += '<a href="javascript:void(0)" onclick="selectAndShowNode(\'' + rel.IDRelation + '\')" style="color: var(--primary-color); cursor: pointer;">' + escapeHtml(rel.TextRelation || '') + '</a>';
|
||||
html += ' <small class="text-muted">(ID: ' + escapeHtml(rel.IDRelation || '') + ')</small>';
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Aktionen
|
||||
html += '<hr>';
|
||||
html += '<div class="d-flex gap-2">';
|
||||
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + row.ID + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
|
||||
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(row.Text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
|
||||
html += '</div>';
|
||||
|
||||
$('#detail-content').html(html);
|
||||
}
|
||||
|
||||
function renderBasicDetails(id, text, nodeData) {
|
||||
var html = '';
|
||||
|
||||
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(text || 'Klassifikation') + '</h4>';
|
||||
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">ID:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(id) + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (nodeData && nodeData.name) {
|
||||
html += '<div class="detail-row">';
|
||||
html += '<span class="detail-label">Name:</span>';
|
||||
html += '<span class="detail-value">' + escapeHtml(nodeData.name) + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '<hr>';
|
||||
html += '<div class="d-flex gap-2">';
|
||||
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + id + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
|
||||
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
|
||||
html += '</div>';
|
||||
|
||||
$('#detail-content').html(html);
|
||||
}
|
||||
|
||||
function editClassification(id) {
|
||||
window.location.href = 'Classifications.php?edit=' + id;
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
var tempInput = document.createElement('input');
|
||||
tempInput.value = text;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tempInput);
|
||||
|
||||
// Feedback
|
||||
alert('"' + text + '" wurde in die Zwischenablage kopiert.');
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
31
ajax/Autocomplete.php
Normal file
31
ajax/Autocomplete.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Autocomplete.php - Liefert Vorschläge für Autocomplete-Felder
|
||||
*/
|
||||
|
||||
include "db_connection.php";
|
||||
require_once "libAuthorities.php";
|
||||
|
||||
$request = isset($_POST['query']) ? strtolower($_POST["query"]) : '';
|
||||
$authType = isset($_POST['authType']) ? $_POST['authType'] : 'Subject';
|
||||
|
||||
if (empty($request)) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$object = new CRUD();
|
||||
$response = $object->getEntryText($authType, $request);
|
||||
|
||||
$data = [];
|
||||
foreach ($response as $entry) {
|
||||
$data[] = $entry['Text'] . " (ID: " . $entry['ID'] . ")";
|
||||
}
|
||||
|
||||
echo json_encode($data);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([]);
|
||||
}
|
||||
?>
|
||||
84
ajax/UpdateTerm.php
Normal file
84
ajax/UpdateTerm.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* UpdateTerm.php - Aktualisiert einen bestehenden Normdaten-Eintrag
|
||||
*
|
||||
* Parameter (POST) - von JavaScript:
|
||||
* - AnchorID: ID des Eintrags
|
||||
* - authType: Typ (Subject, Person, etc.)
|
||||
* - term: Neuer Anzeigetext
|
||||
* - detailtype: Neuer Untertyp (optional)
|
||||
* - classification: Neuer Klassifikationscode (optional)
|
||||
* - scopenote: Neuer Kommentar (optional)
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parameter aus JavaScript
|
||||
$id = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
|
||||
if ($id === 0) {
|
||||
$id = isset($_POST['ID']) ? intval($_POST['ID']) : 0;
|
||||
}
|
||||
$text = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$comment = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
if ($id === 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'ID muss angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (empty($text)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Text darf nicht leer sein']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_begin_transaction($conn);
|
||||
|
||||
try {
|
||||
// 1. Anchor aktualisieren (Text, DetailType, Classification)
|
||||
$updateAnchorSql = "UPDATE Anchor SET
|
||||
Text = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||
DetailType = '" . mysqli_real_escape_string($conn, $detailType) . "',
|
||||
Classification = '" . mysqli_real_escape_string($conn, $classification) . "'
|
||||
WHERE ID = $id";
|
||||
|
||||
if (!mysqli_query($conn, $updateAnchorSql)) {
|
||||
throw new Exception('Fehler beim Aktualisieren des Anchor: ' . mysqli_error($conn));
|
||||
}
|
||||
|
||||
// 2. Entry aktualisieren
|
||||
$updateEntrySql = "UPDATE Entry SET
|
||||
Text = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||
CompleteText = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||
Comment = '" . mysqli_real_escape_string($conn, $comment) . "'
|
||||
WHERE ID = $id";
|
||||
|
||||
if (!mysqli_query($conn, $updateEntrySql)) {
|
||||
throw new Exception('Fehler beim Aktualisieren des Entry: ' . mysqli_error($conn));
|
||||
}
|
||||
|
||||
mysqli_commit($conn);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Eintrag erfolgreich aktualisiert'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
mysqli_rollback($conn);
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
27
ajax/db_connection.php.example
Normal file
27
ajax/db_connection.php.example
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Datenbankverbindung für BIBB Thesaurus
|
||||
* Kopiere diese Datei als db_connection.php und trage deine Zugangsdaten ein.
|
||||
*/
|
||||
|
||||
define('HOST', 'localhost');
|
||||
define('USER', 'DEIN_DATENBANKBENUTZER');
|
||||
define('PASSWORD', 'DEIN_PASSWORT');
|
||||
define('DATABASE', 'TSData');
|
||||
define('CHARSET', 'utf8');
|
||||
|
||||
function DB()
|
||||
{
|
||||
static $instance;
|
||||
if ($instance === null) {
|
||||
$opt = array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => FALSE,
|
||||
);
|
||||
$dsn = 'mysql:host=' . HOST . ';dbname=' . DATABASE . ';charset=' . CHARSET;
|
||||
$instance = new PDO($dsn, USER, PASSWORD, $opt);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
?>
|
||||
86
ajax/deleteRelation.php
Normal file
86
ajax/deleteRelation.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* deleteRelation.php - Löscht eine Relation
|
||||
*
|
||||
* Parameter (POST):
|
||||
* - IDAnchor: ID des Ausgangseintrags
|
||||
* - LinkingID: ID der Relation in der Linking-Tabelle
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
|
||||
$linkingId = isset($_POST['LinkingID']) ? intval($_POST['LinkingID']) : 0;
|
||||
|
||||
if ($linkingId === 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'LinkingID muss angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Zuerst Relation lesen für reziproke Löschung
|
||||
$getSql = "SELECT IDAnchor, IDEntry, Relationtype FROM Linking WHERE ID = $linkingId";
|
||||
$getResult = mysqli_query($conn, $getSql);
|
||||
|
||||
if (!$getResult || mysqli_num_rows($getResult) === 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Relation nicht gefunden']);
|
||||
mysqli_close($conn);
|
||||
exit();
|
||||
}
|
||||
|
||||
$relation = mysqli_fetch_assoc($getResult);
|
||||
$targetId = $relation['IDEntry'];
|
||||
$relationType = $relation['Relationtype'];
|
||||
|
||||
// Reziproke Relation bestimmen
|
||||
$reciprocalType = null;
|
||||
switch ($relationType) {
|
||||
case 'BT':
|
||||
$reciprocalType = 'NT';
|
||||
break;
|
||||
case 'NT':
|
||||
$reciprocalType = 'BT';
|
||||
break;
|
||||
case 'RT':
|
||||
$reciprocalType = 'RT';
|
||||
break;
|
||||
case 'USE':
|
||||
$reciprocalType = 'UF';
|
||||
break;
|
||||
case 'UF':
|
||||
$reciprocalType = 'USE';
|
||||
break;
|
||||
}
|
||||
|
||||
// Relation löschen
|
||||
$deleteSql = "DELETE FROM Linking WHERE ID = $linkingId";
|
||||
|
||||
if (mysqli_query($conn, $deleteSql)) {
|
||||
$affectedRows = mysqli_affected_rows($conn);
|
||||
|
||||
// Reziproke Relation ebenfalls löschen
|
||||
if ($reciprocalType) {
|
||||
$deleteRecSql = "DELETE FROM Linking WHERE IDAnchor = $targetId AND IDEntry = " . $relation['IDAnchor'] . " AND Relationtype = '$reciprocalType'";
|
||||
mysqli_query($conn, $deleteRecSql);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Relation erfolgreich gelöscht',
|
||||
'affected' => $affectedRows
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . mysqli_error($conn)]);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
59
ajax/deleteTerm.php
Normal file
59
ajax/deleteTerm.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* deleteTerm.php - Löscht einen Normdaten-Eintrag
|
||||
*
|
||||
* Parameter (POST):
|
||||
* - AnchorID: ID des Eintrags (primär, gesendet von SubjectScript.js)
|
||||
* - ID: ID des Eintrags (alternativ, für Rückwärtskompatibilität)
|
||||
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parameter: AnchorID (von JS) oder ID (Fallback)
|
||||
$id = 0;
|
||||
if (isset($_POST['AnchorID']) && intval($_POST['AnchorID']) > 0) {
|
||||
$id = intval($_POST['AnchorID']);
|
||||
} elseif (isset($_POST['ID']) && intval($_POST['ID']) > 0) {
|
||||
$id = intval($_POST['ID']);
|
||||
}
|
||||
|
||||
if ($id === 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Gültige ID muss angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Transaktion starten
|
||||
mysqli_begin_transaction($conn);
|
||||
try {
|
||||
// 1. Alle Relationen löschen (als Anchor und als Entry)
|
||||
mysqli_query($conn, "DELETE FROM Linking WHERE IDAnchor = $id");
|
||||
mysqli_query($conn, "DELETE FROM Linking WHERE IDEntry = $id");
|
||||
|
||||
// 2. Entry löschen
|
||||
mysqli_query($conn, "DELETE FROM Entry WHERE ID = $id");
|
||||
|
||||
// 3. Anchor löschen
|
||||
mysqli_query($conn, "DELETE FROM Anchor WHERE ID = $id");
|
||||
|
||||
mysqli_commit($conn);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Eintrag erfolgreich gelöscht'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
mysqli_rollback($conn);
|
||||
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
167
ajax/getAuthorityData.php
Normal file
167
ajax/getAuthorityData.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* getAuthorityData.php - Liefert Normdaten für Bootstrap Table
|
||||
*
|
||||
* Parameter (Bootstrap Table Server-Side):
|
||||
* - limit: Anzahl Einträge pro Seite
|
||||
* - offset: Start-Position
|
||||
* - search: Suchbegriff
|
||||
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||
*
|
||||
* Rückgabe: JSON mit total und rows
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['total' => 0, 'rows' => [], 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parameter auslesen
|
||||
$limit = isset($_GET['limit']) ? $_GET['limit'] : 25;
|
||||
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;
|
||||
|
||||
// "all" oder 0 bedeutet: alle Treffer ohne Limit
|
||||
if ($limit === 'all' || $limit === '0' || intval($limit) <= 0) {
|
||||
$limitAll = true;
|
||||
$limit = 0;
|
||||
} else {
|
||||
$limitAll = false;
|
||||
$limit = intval($limit);
|
||||
}
|
||||
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : 'Subject';
|
||||
|
||||
// Erlaubte Typen
|
||||
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||
if (!in_array($authType, $allowedTypes)) {
|
||||
$authType = 'Subject';
|
||||
}
|
||||
|
||||
// Basis-SQL
|
||||
$baseSql = "FROM Anchor a
|
||||
LEFT JOIN Entry e ON a.ID = e.ID
|
||||
WHERE a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
|
||||
|
||||
// Suchbedingung hinzufügen (case-insensitive)
|
||||
// Scope Note (e.Comment) wird bewusst nicht durchsucht
|
||||
if (!empty($search)) {
|
||||
// Trunkierung auswerten
|
||||
$leftTrunc = (substr($search, 0, 1) === '*');
|
||||
$rightTrunc = (substr($search, -1) === '*');
|
||||
|
||||
// Sternchen entfernen, dann escapen
|
||||
$clean = trim($search, '*');
|
||||
$searchEscaped = mysqli_real_escape_string($conn, $clean);
|
||||
// LIKE-Sonderzeichen in den Daten escapen
|
||||
$searchEscaped = str_replace(['%', '_'], ['\\%', '\\_'], $searchEscaped);
|
||||
|
||||
// Pattern bauen
|
||||
$pattern = ($leftTrunc ? '%' : '') .
|
||||
$searchEscaped .
|
||||
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
|
||||
|
||||
$baseSql .= " AND (LOWER(e.Text) LIKE LOWER('{$pattern}')
|
||||
OR LOWER(e.CompleteText) LIKE LOWER('{$pattern}'))";
|
||||
}
|
||||
|
||||
// Gesamtanzahl ermitteln
|
||||
$countSql = "SELECT COUNT(*) as total " . $baseSql;
|
||||
$countResult = mysqli_query($conn, $countSql);
|
||||
$total = 0;
|
||||
if ($countResult) {
|
||||
$countRow = mysqli_fetch_assoc($countResult);
|
||||
$total = intval($countRow['total']);
|
||||
}
|
||||
|
||||
// ORDER BY je nach authType
|
||||
// Classification: numerisch nach Notation (z.B. T 4.10 nach T 4.9)
|
||||
// Alle anderen: deutsch-korrekt nach Unicode-Kollation (Umlaute, Groß/Klein, Sonderzeichen)
|
||||
if ($authType === 'Classification') {
|
||||
// Dreistufige Sortierung:
|
||||
// 1. Buchstabenpräfix alphabetisch (G, S, T ...)
|
||||
// 2. Erste Zahl numerisch (Hauptgruppe vor Untergruppe: NULL kommt zuerst)
|
||||
// 3. Zweite Zahl numerisch (T 4.9 vor T 4.10)
|
||||
$orderClause = "
|
||||
SUBSTRING_INDEX(a.Classification, ' ', 1),
|
||||
CAST(
|
||||
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(a.Classification, ' ', -1), '.', 1),
|
||||
a.Classification)
|
||||
AS UNSIGNED
|
||||
),
|
||||
CAST(
|
||||
NULLIF(SUBSTRING_INDEX(a.Classification, '.', -1),
|
||||
a.Classification)
|
||||
AS UNSIGNED
|
||||
)
|
||||
";
|
||||
} else {
|
||||
// utf8_unicode_ci: Umlaute korrekt (Ä=A, Ö=O, Ü=U),
|
||||
// Sonderzeichen nach Grundbuchstabe, Groß-/Klein ignoriert
|
||||
$orderClause = "e.Text COLLATE utf8_unicode_ci ASC";
|
||||
}
|
||||
|
||||
// Daten abrufen
|
||||
$dataSql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
|
||||
e.Text, e.CompleteText, e.Comment,
|
||||
e.DateCreated, e.DateModified " . $baseSql . "
|
||||
ORDER BY " . $orderClause;
|
||||
|
||||
if (!$limitAll) {
|
||||
$dataSql .= " LIMIT $offset, $limit";
|
||||
}
|
||||
|
||||
$dataResult = mysqli_query($conn, $dataSql);
|
||||
|
||||
$rows = [];
|
||||
if ($dataResult) {
|
||||
while ($row = mysqli_fetch_assoc($dataResult)) {
|
||||
// Relationen zählen
|
||||
$relSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']);
|
||||
$relResult = mysqli_query($conn, $relSql);
|
||||
$relCount = 0;
|
||||
if ($relResult) {
|
||||
$relRow = mysqli_fetch_assoc($relResult);
|
||||
$relCount = intval($relRow['cnt']);
|
||||
}
|
||||
|
||||
// Deskriptor-Status ermitteln (USE-Relation = Non-Deskriptor)
|
||||
$useSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']) . " AND UPPER(Relationtype) = 'USE'";
|
||||
$useResult = mysqli_query($conn, $useSql);
|
||||
$isDescriptor = true;
|
||||
if ($useResult) {
|
||||
$useRow = mysqli_fetch_assoc($useResult);
|
||||
if (intval($useRow['cnt']) > 0) {
|
||||
$isDescriptor = false;
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'ID' => $row['ID'],
|
||||
'Type' => $row['Type'],
|
||||
'DetailType' => $row['DetailType'] ?? '',
|
||||
'Classification' => $row['Classification'] ?? '',
|
||||
'Text' => $row['Text'] ?? '',
|
||||
'CompleteText' => $row['CompleteText'] ?? '',
|
||||
'Comment' => $row['Comment'] ?? '',
|
||||
'DateCreated' => $row['DateCreated'] ?? '',
|
||||
'DateModified' => $row['DateModified'] ?? '',
|
||||
'RelationCount' => $relCount,
|
||||
'Descriptor' => $isDescriptor
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
echo json_encode([
|
||||
'total' => $total,
|
||||
'rows' => $rows
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
111
ajax/getAuthorityDataRaw.php
Normal file
111
ajax/getAuthorityDataRaw.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* getAuthorityDataRaw.php - Liefert Daten für Autocomplete-Suche ODER einzelnen Eintrag per ID
|
||||
*
|
||||
* Parameter:
|
||||
* - id: ID eines einzelnen Eintrags (für ModifyTerm)
|
||||
* - search: Suchbegriff für Autocomplete (min. 2 Zeichen)
|
||||
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||
* - limit: Max. Anzahl Ergebnisse (Standard: 10)
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['rows' => []]);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Prüfen ob ID-Abfrage (für ModifyTerm)
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
|
||||
if ($id > 0) {
|
||||
// Einzelnen Eintrag per ID laden
|
||||
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
|
||||
e.Text, e.CompleteText, e.Comment as Scopenote
|
||||
FROM Anchor a
|
||||
LEFT JOIN Entry e ON a.ID = e.ID
|
||||
WHERE a.ID = $id";
|
||||
|
||||
$result = mysqli_query($conn, $sql);
|
||||
|
||||
if ($result && mysqli_num_rows($result) > 0) {
|
||||
$row = mysqli_fetch_assoc($result);
|
||||
|
||||
// Descriptor erstellen
|
||||
$descriptor = strtolower($row['Text'] ?? '');
|
||||
$descriptor = str_replace(' ', '_', $descriptor);
|
||||
|
||||
echo json_encode([
|
||||
'rows' => [[
|
||||
'ID' => $row['ID'],
|
||||
'Type' => $row['Type'],
|
||||
'DetailType' => $row['DetailType'] ?? '',
|
||||
'Classification' => $row['Classification'] ?? '',
|
||||
'Text' => $row['Text'] ?? '',
|
||||
'CompleteText' => $row['CompleteText'] ?? '',
|
||||
'Scopenote' => $row['Scopenote'] ?? '',
|
||||
'Descriptor' => $descriptor
|
||||
]]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
echo json_encode(['rows' => []]);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Sonst: Autocomplete-Suche
|
||||
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
|
||||
|
||||
if (strlen($search) < 2) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Erlaubte Typen
|
||||
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||
|
||||
// SQL aufbauen
|
||||
$searchEscaped = mysqli_real_escape_string($conn, $search);
|
||||
|
||||
$sql = "SELECT a.ID, a.Type, a.DetailType, e.Text, e.CompleteText
|
||||
FROM Anchor a
|
||||
LEFT JOIN Entry e ON a.ID = e.ID
|
||||
WHERE (e.Text LIKE '%{$searchEscaped}%' OR e.CompleteText LIKE '%{$searchEscaped}%')";
|
||||
|
||||
// Typ-Filter falls angegeben
|
||||
if (!empty($authType) && in_array($authType, $allowedTypes)) {
|
||||
$sql .= " AND a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY e.Text ASC LIMIT $limit";
|
||||
|
||||
$result = mysqli_query($conn, $sql);
|
||||
|
||||
$data = [];
|
||||
if ($result) {
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
$data[] = [
|
||||
'id' => $row['ID'],
|
||||
'value' => $row['Text'],
|
||||
'text' => $row['Text'],
|
||||
'type' => $row['Type'],
|
||||
'detailType' => $row['DetailType'] ?? '',
|
||||
'completeText' => $row['CompleteText'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
8
ajax/getClassificationData.php
Normal file
8
ajax/getClassificationData.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* getClassificationData.php - Wrapper für getAuthorityData.php mit authType=Classification
|
||||
*/
|
||||
|
||||
$_GET['authType'] = 'Classification';
|
||||
include 'getAuthorityData.php';
|
||||
?>
|
||||
8
ajax/getCorporateData.php
Normal file
8
ajax/getCorporateData.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* getCorporateData.php - Wrapper für getAuthorityData.php mit authType=Corporate
|
||||
*/
|
||||
|
||||
$_GET['authType'] = 'Corporate';
|
||||
include 'getAuthorityData.php';
|
||||
?>
|
||||
65
ajax/getDSpaceCount.php
Normal file
65
ajax/getDSpaceCount.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* getDSpaceCount.php - Proxy für DSpace Authority-Zählung
|
||||
*
|
||||
* Ruft den DSpace-API-Endpoint getAuthorityCount.php auf und gibt
|
||||
* die Anzahl der Datensätze zurück, die den Begriff enthalten.
|
||||
*
|
||||
* Parameter:
|
||||
* - authType: 'Subject' oder 'Person'
|
||||
* - query: Der zu suchende Begriff
|
||||
*
|
||||
* Response: JSON { "query": "...", "authType": "...", "count": n }
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// DSpace API Basis-URL
|
||||
$DSPACE_API_URL = 'https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php';
|
||||
|
||||
// Parameter
|
||||
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
|
||||
|
||||
// Validierung
|
||||
$allowedTypes = array('Subject', 'Person');
|
||||
if (!in_array($authType, $allowedTypes)) {
|
||||
echo json_encode(array('error' => 'Invalid authType', 'count' => 0));
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strlen($query) === 0) {
|
||||
echo json_encode(array('error' => 'Missing query', 'count' => 0));
|
||||
exit;
|
||||
}
|
||||
|
||||
// DSpace API aufrufen
|
||||
$url = $DSPACE_API_URL . '?authType=' . urlencode($authType) . '&query=' . urlencode($query);
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'timeout' => 15,
|
||||
'header' => "Accept: application/json\r\n"
|
||||
),
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
)
|
||||
));
|
||||
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
// Fallback: Count 0 zurückgeben, nicht die ganze Anzeige blockieren
|
||||
echo json_encode(array(
|
||||
'query' => $query,
|
||||
'authType' => $authType,
|
||||
'count' => -1,
|
||||
'error' => 'DSpace API not reachable'
|
||||
));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Response direkt durchreichen
|
||||
echo $response;
|
||||
?>
|
||||
125
ajax/getDashboardStats.php
Normal file
125
ajax/getDashboardStats.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* getDashboardStats.php - Liefert Statistiken für das Dashboard
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$stats = [];
|
||||
|
||||
// 1. Anzahl Datensätze pro Typ
|
||||
$types = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||
$counts = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$sql = "SELECT COUNT(*) as cnt FROM Anchor WHERE Type = '$type'";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if ($res) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$counts[$type] = intval($row['cnt']);
|
||||
} else {
|
||||
$counts[$type] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$stats['counts'] = $counts;
|
||||
$stats['total'] = array_sum($counts);
|
||||
|
||||
// 2. Anzahl Relationen
|
||||
$sql = "SELECT COUNT(*) as cnt FROM Linking";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if ($res) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$stats['relationsTotal'] = intval($row['cnt']);
|
||||
} else {
|
||||
$stats['relationsTotal'] = 0;
|
||||
}
|
||||
|
||||
// 3. Zuletzt geänderte Einträge (Top 10)
|
||||
$sql = "SELECT a.ID, a.Text, a.Type, e.DateModified
|
||||
FROM Anchor a
|
||||
JOIN Entry e ON a.Text = e.Text
|
||||
WHERE e.DateModified > '2000-01-01'
|
||||
ORDER BY e.DateModified DESC
|
||||
LIMIT 10";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
$recentlyModified = [];
|
||||
if ($res) {
|
||||
while ($row = mysqli_fetch_assoc($res)) {
|
||||
$recentlyModified[] = [
|
||||
'id' => $row['ID'],
|
||||
'text' => $row['Text'],
|
||||
'type' => $row['Type'],
|
||||
'date' => $row['DateModified']
|
||||
];
|
||||
}
|
||||
}
|
||||
$stats['recentlyModified'] = $recentlyModified;
|
||||
|
||||
// 4. Zuletzt hinzugefügte Einträge (Top 10)
|
||||
$sql = "SELECT a.ID, a.Text, a.Type, e.DateCreated
|
||||
FROM Anchor a
|
||||
JOIN Entry e ON a.Text = e.Text
|
||||
WHERE e.DateCreated > '2000-01-01'
|
||||
ORDER BY e.DateCreated DESC
|
||||
LIMIT 10";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
$recentlyCreated = [];
|
||||
if ($res) {
|
||||
while ($row = mysqli_fetch_assoc($res)) {
|
||||
$recentlyCreated[] = [
|
||||
'id' => $row['ID'],
|
||||
'text' => $row['Text'],
|
||||
'type' => $row['Type'],
|
||||
'date' => $row['DateCreated']
|
||||
];
|
||||
}
|
||||
}
|
||||
$stats['recentlyCreated'] = $recentlyCreated;
|
||||
|
||||
// 5. Qualitäts-Check: Einträge ohne Relationen (Waisen)
|
||||
$orphanCounts = [];
|
||||
foreach ($types as $type) {
|
||||
$sql = "SELECT COUNT(*) as cnt FROM Anchor a
|
||||
WHERE a.Type = '$type'
|
||||
AND a.ID NOT IN (SELECT DISTINCT IDAnchor FROM Linking)";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if ($res) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$orphanCounts[$type] = intval($row['cnt']);
|
||||
} else {
|
||||
$orphanCounts[$type] = 0;
|
||||
}
|
||||
}
|
||||
$stats['orphans'] = $orphanCounts;
|
||||
$stats['orphansTotal'] = array_sum($orphanCounts);
|
||||
|
||||
// 6. Deskriptoren vs. Non-Deskriptoren (für Schlagworte)
|
||||
$sql = "SELECT
|
||||
SUM(CASE WHEN DetailType = 'Deskriptor' OR DetailType = 'deskriptor' THEN 1 ELSE 0 END) as descriptors,
|
||||
SUM(CASE WHEN DetailType != 'Deskriptor' AND DetailType != 'deskriptor' THEN 1 ELSE 0 END) as nonDescriptors
|
||||
FROM Anchor WHERE Type = 'Subject'";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if ($res) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$stats['descriptors'] = intval($row['descriptors']);
|
||||
$stats['nonDescriptors'] = intval($row['nonDescriptors']);
|
||||
} else {
|
||||
$stats['descriptors'] = 0;
|
||||
$stats['nonDescriptors'] = 0;
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
echo json_encode($stats, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
122
ajax/getDetailAuthorityData.php
Normal file
122
ajax/getDetailAuthorityData.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* getDetailAuthorityData.php - Liefert vollständige Details zu einem Eintrag
|
||||
*
|
||||
* Parameter:
|
||||
* - id: ID des Eintrags (kleingeschrieben von JS)
|
||||
* - ID: ID des Eintrags (alternativ)
|
||||
* - authType: Typ (optional)
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['rows' => []]);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Beide Parameter-Namen unterstützen (JS sendet 'id')
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
if ($id === 0) {
|
||||
$id = isset($_GET['ID']) ? intval($_GET['ID']) : 0;
|
||||
}
|
||||
|
||||
if ($id === 0) {
|
||||
echo json_encode(['rows' => []]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Hauptdaten laden
|
||||
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification, a.Text as AnchorText,
|
||||
e.Text, e.CompleteText, e.Comment, e.DateCreated, e.DateModified
|
||||
FROM Anchor a
|
||||
LEFT JOIN Entry e ON a.ID = e.ID
|
||||
WHERE a.ID = $id";
|
||||
|
||||
$result = mysqli_query($conn, $sql);
|
||||
|
||||
if (!$result || mysqli_num_rows($result) === 0) {
|
||||
echo json_encode(['rows' => []]);
|
||||
mysqli_close($conn);
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = mysqli_fetch_assoc($result);
|
||||
|
||||
// Relationen laden im Format das JS erwartet
|
||||
$relSql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
|
||||
e.Text as EntryText, a.Type as EntryType
|
||||
FROM Linking l
|
||||
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||
LEFT JOIN Anchor a ON l.IDEntry = a.ID
|
||||
WHERE l.IDAnchor = $id
|
||||
ORDER BY l.Relationtype, e.Text";
|
||||
|
||||
$relResult = mysqli_query($conn, $relSql);
|
||||
|
||||
$relations = [];
|
||||
if ($relResult) {
|
||||
while ($relRow = mysqli_fetch_assoc($relResult)) {
|
||||
$relations[] = [
|
||||
'Relationtype' => $relRow['Relationtype'],
|
||||
'TextRelation' => $relRow['EntryText'] ?? '',
|
||||
'IDRelation' => $relRow['IDEntry']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Synonyme laden (nur für Subject)
|
||||
$synonymsText = '';
|
||||
if ($data['Type'] === 'Subject' && !empty($data['Text'])) {
|
||||
$desc = strtolower($data['Text']);
|
||||
$desc = str_replace(' ', '_', $desc);
|
||||
|
||||
$synSql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
|
||||
$synResult = mysqli_query($conn, $synSql);
|
||||
|
||||
if ($synResult && mysqli_num_rows($synResult) > 0) {
|
||||
$synRow = mysqli_fetch_assoc($synResult);
|
||||
$idLinking = $synRow['IDLinking'];
|
||||
|
||||
$synAllSql = "SELECT Text FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
|
||||
$synAllResult = mysqli_query($conn, $synAllSql);
|
||||
|
||||
$synTexts = [];
|
||||
if ($synAllResult) {
|
||||
while ($syn = mysqli_fetch_assoc($synAllResult)) {
|
||||
$synTexts[] = $syn['Text'];
|
||||
}
|
||||
}
|
||||
$synonymsText = implode(', ', $synTexts);
|
||||
}
|
||||
}
|
||||
|
||||
// Descriptor erstellen
|
||||
$descriptor = strtolower($data['Text'] ?? '');
|
||||
$descriptor = str_replace(' ', '_', $descriptor);
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
// Rückgabe im Format das JavaScript erwartet
|
||||
echo json_encode([
|
||||
'rows' => [[
|
||||
'ID' => $data['ID'],
|
||||
'Type' => $data['Type'],
|
||||
'Detailtype' => $data['DetailType'] ?? '',
|
||||
'Classification' => $data['Classification'] ?? '',
|
||||
'Text' => $data['Text'] ?? '',
|
||||
'CompleteText' => $data['CompleteText'] ?? '',
|
||||
'Scopenote' => $data['Comment'] ?? '',
|
||||
'Descriptor' => $descriptor,
|
||||
'Synonyms' => $synonymsText,
|
||||
'Relations' => $relations,
|
||||
'DateCreated' => $data['DateCreated'] ?? '',
|
||||
'DateModified' => $data['DateModified'] ?? ''
|
||||
]]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
8
ajax/getPersonData.php
Normal file
8
ajax/getPersonData.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* getPersonData.php - Wrapper für getAuthorityData.php mit authType=Person
|
||||
*/
|
||||
|
||||
$_GET['authType'] = 'Person';
|
||||
include 'getAuthorityData.php';
|
||||
?>
|
||||
8
ajax/getPublisherData.php
Normal file
8
ajax/getPublisherData.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* getPublisherData.php - Wrapper für getAuthorityData.php mit authType=Publisher
|
||||
*/
|
||||
|
||||
$_GET['authType'] = 'Publisher';
|
||||
include 'getAuthorityData.php';
|
||||
?>
|
||||
104
ajax/getRelations.php
Normal file
104
ajax/getRelations.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* getRelations.php - Lädt Relationen für einen Eintrag und gibt HTML zurück
|
||||
*
|
||||
* Parameter:
|
||||
* - anchorID: ID des Eintrags (JavaScript-Format)
|
||||
* - IDAnchor: ID des Eintrags (alternatives Format)
|
||||
* - authType: Typ des Eintrags
|
||||
*/
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo '';
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Beide Parameter-Namen unterstützen (JavaScript sendet anchorID)
|
||||
$idAnchor = isset($_GET['anchorID']) ? intval($_GET['anchorID']) : 0;
|
||||
if ($idAnchor === 0) {
|
||||
$idAnchor = isset($_GET['IDAnchor']) ? intval($_GET['IDAnchor']) : 0;
|
||||
}
|
||||
|
||||
if ($idAnchor === 0) {
|
||||
echo ''; // Leere Rückgabe bei ID 0 (neuer Eintrag)
|
||||
exit();
|
||||
}
|
||||
|
||||
// Relationen mit verknüpften Entry-Daten laden
|
||||
// NUR Relationen mit einem echten Relationstyp (keine leeren, keine Selbstreferenzen)
|
||||
$sql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
|
||||
e.Text as EntryText, e.CompleteText, a.Type as EntryType
|
||||
FROM Linking l
|
||||
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||
LEFT JOIN Anchor a ON l.IDEntry = a.ID
|
||||
WHERE l.IDAnchor = $idAnchor
|
||||
AND l.Relationtype != ''
|
||||
AND l.IDAnchor != l.IDEntry
|
||||
ORDER BY l.Relationtype, e.Text";
|
||||
|
||||
$result = mysqli_query($conn, $sql);
|
||||
|
||||
// Prüfen ob Relationen vorhanden
|
||||
if (!$result || mysqli_num_rows($result) === 0) {
|
||||
echo ''; // Leere Rückgabe - Bereich wird ausgeblendet
|
||||
mysqli_close($conn);
|
||||
exit();
|
||||
}
|
||||
|
||||
// HTML-Tabellenzeilen generieren (analog zu Synonymen)
|
||||
$html = '';
|
||||
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
$relType = htmlspecialchars($row['Relationtype']);
|
||||
$entryText = htmlspecialchars($row['EntryText'] ?? '');
|
||||
$linkingId = intval($row['LinkingID']);
|
||||
$entryId = intval($row['IDEntry']);
|
||||
|
||||
// Badge-Farbe je nach Relationstyp
|
||||
$badgeClass = 'bg-secondary';
|
||||
$badgeTitle = $relType;
|
||||
switch ($relType) {
|
||||
case 'BT':
|
||||
$badgeClass = 'bg-success';
|
||||
$badgeTitle = 'BT - Oberbegriff';
|
||||
break;
|
||||
case 'NT':
|
||||
$badgeClass = 'bg-danger';
|
||||
$badgeTitle = 'NT - Unterbegriff';
|
||||
break;
|
||||
case 'RT':
|
||||
$badgeClass = 'bg-warning text-dark';
|
||||
$badgeTitle = 'RT - Verwandter Begriff';
|
||||
break;
|
||||
case 'USE':
|
||||
$badgeClass = 'bg-info';
|
||||
$badgeTitle = 'USE - Benutze';
|
||||
break;
|
||||
case 'UF':
|
||||
$badgeClass = 'bg-secondary';
|
||||
$badgeTitle = 'UF - Benutzt für';
|
||||
break;
|
||||
}
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td><span class="badge ' . $badgeClass . '" title="' . $badgeTitle . '">' . $relType . '</span></td>';
|
||||
$html .= '<td>' . $entryText . '</td>';
|
||||
$html .= '<td class="text-end">';
|
||||
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteRelation(' . $idAnchor . ', ' . $linkingId . ')" title="Relation löschen">';
|
||||
$html .= '<i class="fas fa-trash"></i>';
|
||||
$html .= '</button>';
|
||||
$html .= '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
echo $html;
|
||||
?>
|
||||
8
ajax/getSubjectData.php
Normal file
8
ajax/getSubjectData.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* getSubjectData.php - Wrapper für getAuthorityData.php mit authType=Subject
|
||||
*/
|
||||
|
||||
$_GET['authType'] = 'Subject';
|
||||
include 'getAuthorityData.php';
|
||||
?>
|
||||
82
ajax/getSynonyms.php
Normal file
82
ajax/getSynonyms.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* getSynonyms.php - Lädt Synonyme für einen Begriff
|
||||
*
|
||||
* Parameter:
|
||||
* - text: Der Begriff, für den Synonyme gesucht werden
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$text = isset($_GET['text']) ? trim($_GET['text']) : '';
|
||||
|
||||
if (empty($text)) {
|
||||
echo json_encode(['success' => true, 'synonyms' => [], 'html' => '']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
|
||||
function prepare_desc($text) {
|
||||
$desc = strtolower($text);
|
||||
$desc = str_replace(' ', '_', $desc);
|
||||
return $desc;
|
||||
}
|
||||
|
||||
$desc = prepare_desc($text);
|
||||
|
||||
// Erst IDLinking für den Begriff finden
|
||||
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
|
||||
$synonyms = [];
|
||||
$html = '';
|
||||
|
||||
if ($res && mysqli_num_rows($res) > 0) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$idLinking = $row['IDLinking'];
|
||||
|
||||
// Alle Synonyme mit dieser IDLinking laden (außer dem Begriff selbst)
|
||||
$sql2 = "SELECT ID, Text, Descriptor FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
|
||||
$res2 = mysqli_query($conn, $sql2);
|
||||
|
||||
if ($res2) {
|
||||
while ($syn = mysqli_fetch_assoc($res2)) {
|
||||
$synonyms[] = [
|
||||
'id' => $syn['ID'],
|
||||
'text' => $syn['Text'],
|
||||
'descriptor' => $syn['Descriptor']
|
||||
];
|
||||
|
||||
// HTML für Anzeige generieren
|
||||
$html .= '<tr>';
|
||||
$html .= '<td><span class="badge" style="background-color: #e91e63;">SYN</span></td>';
|
||||
$html .= '<td>' . htmlspecialchars($syn['Text']) . '</td>';
|
||||
$html .= '<td class="text-end">';
|
||||
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteSynonym(\'' . addslashes($text) . '\', \'' . addslashes($syn['Text']) . '\')" title="Synonym entfernen">';
|
||||
$html .= '<i class="fas fa-trash"></i>';
|
||||
$html .= '</button>';
|
||||
$html .= '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'synonyms' => $synonyms,
|
||||
'html' => $html,
|
||||
'count' => count($synonyms)
|
||||
]);
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
75
ajax/getTreeData.php
Normal file
75
ajax/getTreeData.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* getTreeData.php - Liefert Klassifikationsdaten im jsTree-Format
|
||||
*
|
||||
* Nutzt die bestehende Treeview-Tabelle
|
||||
*
|
||||
* Parameter:
|
||||
* - id: Parent-ID (# oder 0 für Root-Ebene)
|
||||
*
|
||||
* Rückgabe: JSON-Array im jsTree-Format
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Datenbankverbindung
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parameter auslesen
|
||||
$parentId = isset($_GET['id']) ? $_GET['id'] : '#';
|
||||
|
||||
// Root-Ebene: parent_id ist NULL oder 0
|
||||
if ($parentId === '#' || $parentId === '0' || $parentId === 0) {
|
||||
$sql = "SELECT id, name, text, link as href, parent_id
|
||||
FROM Treeview
|
||||
WHERE parent_id IS NULL OR parent_id = 0 OR parent_id = ''
|
||||
ORDER BY text";
|
||||
} else {
|
||||
// Unterebene: Kinder des angegebenen Parents
|
||||
$parentId = intval($parentId);
|
||||
$sql = "SELECT id, name, text, link as href, parent_id
|
||||
FROM Treeview
|
||||
WHERE parent_id = $parentId
|
||||
ORDER BY text";
|
||||
}
|
||||
|
||||
$res = mysqli_query($conn, $sql);
|
||||
|
||||
if (!$res) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
while ($row = mysqli_fetch_assoc($res)) {
|
||||
// Prüfen ob dieser Knoten Kinder hat
|
||||
$childCheckSql = "SELECT COUNT(*) as cnt FROM Treeview WHERE parent_id = " . intval($row['id']);
|
||||
$childRes = mysqli_query($conn, $childCheckSql);
|
||||
$childRow = mysqli_fetch_assoc($childRes);
|
||||
$hasChildren = ($childRow['cnt'] > 0);
|
||||
|
||||
$result[] = [
|
||||
'id' => strval($row['id']),
|
||||
'text' => $row['text'] ? $row['text'] : $row['name'],
|
||||
'children' => $hasChildren,
|
||||
'data' => [
|
||||
'name' => $row['name'],
|
||||
'href' => $row['href']
|
||||
],
|
||||
'icon' => $hasChildren ? 'fas fa-folder text-warning' : 'fas fa-file text-secondary'
|
||||
];
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
46
ajax/getTreePath.php
Normal file
46
ajax/getTreePath.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
|
||||
*
|
||||
* Parameter (GET):
|
||||
* - id: Treeview-ID des Zielknotens
|
||||
*
|
||||
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
|
||||
* Beispiel: [98106, 98107, 98113, 98115]
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
if ($targetId === 0) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
|
||||
$path = [];
|
||||
$curId = $targetId;
|
||||
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
|
||||
|
||||
while ($curId > 0 && $limit-- > 0) {
|
||||
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
|
||||
if (!$res) break;
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
if (!$row) break;
|
||||
|
||||
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
|
||||
$parentId = intval($row['parent_id']);
|
||||
if ($parentId === 0) break;
|
||||
$curId = $parentId;
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
echo json_encode($path, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
561
ajax/libAuthorities.php
Normal file
561
ajax/libAuthorities.php
Normal file
@ -0,0 +1,561 @@
|
||||
<?php
|
||||
/**
|
||||
* libAuthorities.php - CRUD-Klasse für Thesaurus-Datenbank
|
||||
*
|
||||
* Sicherheitsverbesserungen:
|
||||
* - Prepared Statements mit benannten Parametern
|
||||
* - Whitelist für authType
|
||||
* - Input-Validierung
|
||||
* - Fehlerbehandlung
|
||||
*/
|
||||
|
||||
class CRUD
|
||||
{
|
||||
protected $db;
|
||||
|
||||
// Whitelist für erlaubte Typen
|
||||
private $allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||
|
||||
// Whitelist für erlaubte Sortierfelder
|
||||
private $allowedSortFields = [
|
||||
'Anchor.ID', 'Anchor.Text', 'Anchor.Type', 'Anchor.DetailType',
|
||||
'Entry.Text', 'Entry.ID', 'Entry.DateCreated', 'Entry.DateModified'
|
||||
];
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->db = DB();
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->db = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert den authType gegen Whitelist
|
||||
*/
|
||||
private function validateAuthType($authType)
|
||||
{
|
||||
if (!in_array($authType, $this->allowedTypes)) {
|
||||
throw new InvalidArgumentException("Ungültiger Typ: $authType");
|
||||
}
|
||||
return $authType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert und bereinigt Sortierfeld
|
||||
*/
|
||||
private function validateSortField($sort)
|
||||
{
|
||||
if (empty($sort)) {
|
||||
return 'Entry.Text';
|
||||
}
|
||||
|
||||
$sortField = trim(str_replace(['ASC', 'DESC', 'asc', 'desc'], '', $sort));
|
||||
$sortField = trim($sortField);
|
||||
|
||||
if (!in_array($sortField, $this->allowedSortFields)) {
|
||||
return 'Entry.Text';
|
||||
}
|
||||
|
||||
return $sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert Integer-Werte
|
||||
*/
|
||||
private function validateInt($value, $min = 0)
|
||||
{
|
||||
$intVal = intval($value);
|
||||
return ($intVal >= $min) ? $intVal : $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut ein SQL-LIKE-Pattern aus einem Suchterm.
|
||||
* Führendes * → Linkstrunkierung (%...)
|
||||
* Abschließendes * → Rechtstrunkierung (...%)
|
||||
* Kein * → nur Rechtstrunkierung (Standard-Verhalten)
|
||||
*/
|
||||
private function buildSearchPattern(string $term): string
|
||||
{
|
||||
$leftTrunc = str_starts_with($term, '*');
|
||||
$rightTrunc = str_ends_with($term, '*');
|
||||
|
||||
$clean = trim($term, '*');
|
||||
$clean = addcslashes($clean, '%_\\');
|
||||
|
||||
return ($leftTrunc ? '%' : '') .
|
||||
$clean .
|
||||
($rightTrunc ? '%' : '%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dubletten-Prüfung über Anchor.Text (normalisierte Form).
|
||||
*
|
||||
* Anchor.Text enthält den normalisierten Descriptor (Kleinbuchstaben,
|
||||
* Sonderzeichen durch '_' ersetzt, erzeugt durch prepare_desc()).
|
||||
* Diese Prüfung ist robuster als ein Vergleich auf Entry.Text, weil
|
||||
* sie Schreibvarianten (Groß-/Kleinschreibung, Umlaute, Leerzeichen)
|
||||
* zuverlässig als Dublette erkennt.
|
||||
*
|
||||
* @param string $authType Typ-Whitelist-Wert ('Subject', 'Person', ...)
|
||||
* @param string $normalizedText Ergebnis von prepare_desc($term)
|
||||
* @return int Anzahl gefundener Einträge (0 = keine Dublette)
|
||||
*/
|
||||
public function checkDuplicateByNormalizedText($authType, $normalizedText)
|
||||
{
|
||||
$authType = $this->validateAuthType($authType);
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT COUNT(*) AS count
|
||||
FROM Anchor
|
||||
WHERE Text = :normalizedText
|
||||
AND Type = :authType"
|
||||
);
|
||||
$stmt->execute([
|
||||
':normalizedText' => $normalizedText,
|
||||
':authType' => $authType
|
||||
]);
|
||||
|
||||
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $rec ? intval($rec['count']) : 0;
|
||||
}
|
||||
|
||||
public function readAuthorityNumbersOfRecords($authType, $search, $id, $sharp)
|
||||
{
|
||||
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'");
|
||||
|
||||
$authType = $this->validateAuthType($authType);
|
||||
$id = $this->validateInt($id);
|
||||
|
||||
$params = [':authType' => $authType];
|
||||
|
||||
$query = "SELECT COUNT(*) as count FROM Anchor
|
||||
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||
WHERE Type = :authType";
|
||||
|
||||
if (strlen($search) > 0 && $sharp == false) {
|
||||
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
|
||||
$params[':search'] = $this->buildSearchPattern($search);
|
||||
}
|
||||
if (strlen($search) > 0 && $sharp == true) {
|
||||
$query .= " AND LOWER(Entry.Text) = LOWER(:search)";
|
||||
$params[':search'] = $search;
|
||||
}
|
||||
if ($id > 0) {
|
||||
$query .= " AND Entry.ID = :id";
|
||||
$params[':id'] = $id;
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute($params);
|
||||
|
||||
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $rec ? intval($rec['count']) : 0;
|
||||
}
|
||||
|
||||
public function readAuthorityEntriesByID($authType, $id)
|
||||
{
|
||||
$authType = $this->validateAuthType($authType);
|
||||
$id = $this->validateInt($id, 1);
|
||||
|
||||
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
|
||||
Anchor.Type AS Type, Anchor.Classification AS Classification,
|
||||
Entry.Text as Text, Entry.Comment as Scopenote
|
||||
FROM Anchor
|
||||
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||
WHERE Anchor.ID = :id AND Type = :authType";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute([':id' => $id, ':authType' => $authType]);
|
||||
|
||||
$count = $stmt->rowCount();
|
||||
$ret = [];
|
||||
|
||||
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$relations = $this->getRelations($authType, $rec['ID']);
|
||||
$ret[] = [
|
||||
"ID" => $rec['ID'],
|
||||
"Descriptor" => $relations['Descriptor'],
|
||||
"Text" => $rec['Text'],
|
||||
"DetailType" => $rec['DetailType'],
|
||||
"Type" => $rec['Type'],
|
||||
"Scopenote" => $rec['Scopenote'],
|
||||
"Relations" => $relations['Data'],
|
||||
"Classification" => $rec['Classification']
|
||||
];
|
||||
}
|
||||
|
||||
return ["COUNT" => $count, "RECORDS" => $ret];
|
||||
}
|
||||
|
||||
public function readAuthorityEntries($authType, $offset, $search, $id, $sort, $max)
|
||||
{
|
||||
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'", 3, "error.log");
|
||||
|
||||
|
||||
$authType = $this->validateAuthType($authType);
|
||||
$offset = $this->validateInt($offset);
|
||||
$max = $this->validateInt($max, 1);
|
||||
$id = $this->validateInt($id);
|
||||
$sort = $this->validateSortField($sort);
|
||||
|
||||
$params = [':authType' => $authType];
|
||||
|
||||
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
|
||||
Anchor.Type AS Type, Anchor.Classification AS Classification,
|
||||
Entry.Text as Text, Entry.Comment as Scopenote
|
||||
FROM Anchor
|
||||
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||
WHERE Type = :authType";
|
||||
|
||||
if (strlen($search) > 0) {
|
||||
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
|
||||
$params[':search'] = $this->buildSearchPattern($search);
|
||||
}
|
||||
if ($id > 0) {
|
||||
$query .= " AND Entry.ID = :id";
|
||||
$params[':id'] = $id;
|
||||
}
|
||||
|
||||
// $query .= " ORDER BY " . $sort . " LIMIT " . intval($offset) . "," . intval($max);
|
||||
|
||||
if ($authType === 'Classification') {
|
||||
$orderClause = "
|
||||
SUBSTRING_INDEX(Anchor.Text, ' ', 1),
|
||||
CAST(
|
||||
CASE
|
||||
WHEN LOCATE(' ', Anchor.Text) > 0
|
||||
THEN SUBSTRING_INDEX(SUBSTRING_INDEX(Anchor.Text, ' ', -1), '.', 1)
|
||||
ELSE NULL
|
||||
END AS UNSIGNED
|
||||
),
|
||||
CAST(
|
||||
CASE
|
||||
WHEN LOCATE('.', Anchor.Text) > 0
|
||||
THEN SUBSTRING_INDEX(Anchor.Text, '.', -1)
|
||||
ELSE NULL
|
||||
END AS UNSIGNED
|
||||
)
|
||||
";
|
||||
} else {
|
||||
$orderClause = $sort . " COLLATE utf8mb4_unicode_ci";
|
||||
}
|
||||
|
||||
error_log("DEBUG ORDER BY: " . $orderClause, 3, "/var/www/tools/html/Thesaurus/error.log");
|
||||
|
||||
$query .= " ORDER BY " . $orderClause . " LIMIT " . intval($offset) . "," . intval($max);
|
||||
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute($params);
|
||||
|
||||
$count = $stmt->rowCount();
|
||||
$ret = [];
|
||||
|
||||
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$relations = $this->getRelations($authType, $rec['ID']);
|
||||
|
||||
if ($authType === 'Subject') {
|
||||
$synonyms = $this->getSynonyms($rec['Text']);
|
||||
$ret[] = [
|
||||
"ID" => $rec['ID'],
|
||||
"Descriptor" => $relations['Descriptor'],
|
||||
"Text" => $rec['Text'],
|
||||
"DetailType" => $rec['DetailType'],
|
||||
"Type" => $rec['Type'],
|
||||
"Scopenote" => $rec['Scopenote'],
|
||||
"Relations" => $relations['Data'],
|
||||
"Classification" => $rec['Classification'],
|
||||
"Synonyms" => $synonyms['Synonyms'],
|
||||
"Search" => $synonyms['Search']
|
||||
];
|
||||
} else {
|
||||
$ret[] = [
|
||||
"ID" => $rec['ID'],
|
||||
"Descriptor" => $relations['Descriptor'],
|
||||
"Text" => $rec['Text'],
|
||||
"DetailType" => $rec['DetailType'],
|
||||
"Type" => $rec['Type'],
|
||||
"Scopenote" => $rec['Scopenote'],
|
||||
"Relations" => $relations['Data'],
|
||||
"Classification" => $rec['Classification']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ["COUNT" => $count, "RECORDS" => $ret];
|
||||
}
|
||||
|
||||
public function getRelations($authType, $id)
|
||||
{
|
||||
$authType = $this->validateAuthType($authType);
|
||||
$id = $this->validateInt($id, 1);
|
||||
|
||||
$query = "SELECT Linking.ID as LinkingID, IDEntry, Entry.Text, Entry.Comment,
|
||||
Relationtype, DetailType
|
||||
FROM Linking
|
||||
JOIN Entry ON (Linking.IDEntry = Entry.ID)
|
||||
JOIN Anchor ON (IDAnchor = Anchor.ID)
|
||||
WHERE IDAnchor = :id AND Anchor.Type = :authType AND Relationtype != ''
|
||||
ORDER BY Relationtype";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute([':id' => $id, ':authType' => $authType]);
|
||||
|
||||
$descriptor = true;
|
||||
$ret = [];
|
||||
|
||||
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (strtolower($rec['Relationtype']) === "use") {
|
||||
$descriptor = false;
|
||||
}
|
||||
$ret[] = [
|
||||
"IDLinking" => $rec['LinkingID'],
|
||||
"IDEntry" => $rec['IDEntry'],
|
||||
"IDRelation" => $rec['IDEntry'],
|
||||
"Detailtype" => $rec['DetailType'],
|
||||
"TextRelation" => $rec['Text'],
|
||||
"CommentRelation"=> $rec['Comment'],
|
||||
"Relationtype" => $rec['Relationtype']
|
||||
];
|
||||
}
|
||||
|
||||
return ["Descriptor" => $descriptor, "Data" => $ret];
|
||||
}
|
||||
|
||||
public function getSynonyms($input)
|
||||
{
|
||||
$desc = $this->prepare_desc($input);
|
||||
|
||||
$query = "SELECT IDLinking FROM Synonyms WHERE Descriptor = :desc";
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute([':desc' => $desc]);
|
||||
|
||||
$ret = '';
|
||||
$retsearch = "topic:'" . addslashes($input) . "' OR ";
|
||||
|
||||
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$idSearch = intval($rec['IDLinking']);
|
||||
|
||||
$query2 = "SELECT Text FROM Synonyms WHERE IDLinking = :idLinking";
|
||||
$stmt2 = $this->db->prepare($query2);
|
||||
$stmt2->execute([':idLinking' => $idSearch]);
|
||||
|
||||
while ($rec2 = $stmt2->fetch(PDO::FETCH_ASSOC)) {
|
||||
if ($rec2['Text'] === $input) continue;
|
||||
$ret .= $rec2['Text'] . ", ";
|
||||
$retsearch .= "topic:'" . addslashes($rec2['Text']) . "' OR ";
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($ret) == 0) {
|
||||
return ["Synonyms" => '', "Search" => ''];
|
||||
} else {
|
||||
$synonyms = trim(substr($ret, 0, -2));
|
||||
$search = trim(substr($retsearch, 0, -4));
|
||||
return ["Synonyms" => $synonyms, "Search" => $search];
|
||||
}
|
||||
}
|
||||
|
||||
public function getEntryText($authType, $request)
|
||||
{
|
||||
$authType = $this->validateAuthType($authType);
|
||||
|
||||
$query = "SELECT Anchor.ID AS AnchorID, Entry.Text as Text
|
||||
FROM Anchor
|
||||
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||
WHERE Anchor.Text LIKE LOWER(:request) AND Anchor.Type = :authType
|
||||
ORDER BY Entry.Text";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->execute([':request' => '%' . $request . '%', ':authType' => $authType]);
|
||||
|
||||
$ret = [];
|
||||
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$ret[] = ["Text" => $rec['Text'], "ID" => $rec['AnchorID']];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function insertNewTerm($authType, $term, $desc, $detailtype, $classification, $scopenote, $completetext)
|
||||
{
|
||||
$authType = $this->validateAuthType($authType);
|
||||
|
||||
$query = $this->db->prepare(
|
||||
"INSERT INTO Anchor (ID, Text, DetailType, Type, Classification)
|
||||
VALUES (:ID, :Text, :DetailType, :Type, :Classification)"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':ID' => null,
|
||||
':Text' => $desc,
|
||||
':DetailType' => $detailtype,
|
||||
':Type' => $authType,
|
||||
':Classification' => $classification
|
||||
]);
|
||||
|
||||
$IDAnchor = $this->db->lastInsertId();
|
||||
|
||||
$query = $this->db->prepare(
|
||||
"INSERT INTO Entry (ID, Text, Comment, Language, CompleteText, DateCreated, DateModified)
|
||||
VALUES (:ID, :Text, :Comment, :Language, :CompleteText, :DateCreated, :DateModified)"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':ID' => $IDAnchor,
|
||||
':Text' => $term,
|
||||
':Comment' => $scopenote,
|
||||
':Language' => 'de',
|
||||
':CompleteText' => $completetext,
|
||||
':DateCreated' => date('Y-m-d H:i:s'),
|
||||
':DateModified' => '0000-00-00 00:00:00'
|
||||
]);
|
||||
|
||||
$IDEntry = $IDAnchor;
|
||||
|
||||
return ["IDAnchor" => $IDAnchor, "IDEntry" => $IDEntry, "IDLinking" => 0];
|
||||
}
|
||||
|
||||
public function writeNewRelation($anchorID, $relationType, $relationID)
|
||||
{
|
||||
$anchorID = $this->validateInt($anchorID, 1);
|
||||
$relationID = $this->validateInt($relationID, 1);
|
||||
|
||||
$allowedRelations = ['BT', 'NT', 'RT', 'USE', 'UF'];
|
||||
if (!in_array(strtoupper($relationType), $allowedRelations)) {
|
||||
throw new InvalidArgumentException("Ungültiger Relationstyp: $relationType");
|
||||
}
|
||||
|
||||
$query = $this->db->prepare(
|
||||
"INSERT INTO Linking (ID, IDAnchor, IDEntry, Relationtype)
|
||||
VALUES (:ID, :IDAnchor, :IDEntry, :Relationtype)"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':ID' => null,
|
||||
':IDAnchor' => $anchorID,
|
||||
':IDEntry' => $relationID,
|
||||
':Relationtype' => $relationType
|
||||
]);
|
||||
|
||||
return $this->db->lastInsertId();
|
||||
}
|
||||
|
||||
public function deleteRelation($anchorID, $linkingID)
|
||||
{
|
||||
$linkingID = $this->validateInt($linkingID, 1);
|
||||
|
||||
$query = $this->db->prepare("DELETE FROM Linking WHERE ID = :id");
|
||||
$query->execute([':id' => $linkingID]);
|
||||
|
||||
return $linkingID;
|
||||
}
|
||||
|
||||
public function deleteTerm($anchorID)
|
||||
{
|
||||
$anchorID = $this->validateInt($anchorID, 1);
|
||||
$ret = [];
|
||||
|
||||
$query = $this->db->prepare("DELETE FROM Linking WHERE IDAnchor = :id");
|
||||
$query->execute([':id' => $anchorID]);
|
||||
$ret[] = "Linking gelöscht";
|
||||
|
||||
$query = $this->db->prepare("DELETE FROM Entry WHERE ID = :id");
|
||||
$query->execute([':id' => $anchorID]);
|
||||
$ret[] = "Entry gelöscht";
|
||||
|
||||
$query = $this->db->prepare("DELETE FROM Anchor WHERE ID = :id");
|
||||
$query->execute([':id' => $anchorID]);
|
||||
$ret[] = "Anchor gelöscht";
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function updateTerm($anchorID, $authType, $desc, $term, $type, $detailtype, $classification, $scopenote, $completetext)
|
||||
{
|
||||
$anchorID = $this->validateInt($anchorID, 1);
|
||||
$authType = $this->validateAuthType($authType);
|
||||
|
||||
$query = $this->db->prepare(
|
||||
"UPDATE Anchor
|
||||
SET Text = :text, Type = :type, DetailType = :detailtype, Classification = :classification
|
||||
WHERE ID = :id"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':text' => $desc,
|
||||
':type' => $authType,
|
||||
':detailtype' => $detailtype,
|
||||
':classification' => $classification,
|
||||
':id' => $anchorID
|
||||
]);
|
||||
|
||||
$query = $this->db->prepare(
|
||||
"UPDATE Entry
|
||||
SET Text = :text, Comment = :comment, Language = :language,
|
||||
CompleteText = :completetext, DateModified = :datemodified
|
||||
WHERE ID = :id"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':text' => $term,
|
||||
':comment' => $scopenote,
|
||||
':language' => 'de',
|
||||
':completetext' => $completetext,
|
||||
':datemodified' => date('Y-m-d H:i:s'),
|
||||
':id' => $anchorID
|
||||
]);
|
||||
|
||||
return "OK";
|
||||
}
|
||||
|
||||
public function getStatistics($dateStart, $dateEnd)
|
||||
{
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateStart) ||
|
||||
!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateEnd)) {
|
||||
throw new InvalidArgumentException("Ungültiges Datumsformat");
|
||||
}
|
||||
|
||||
$r = [];
|
||||
$dateTypes = ["DateCreated", "DateModified"];
|
||||
|
||||
foreach ($this->allowedTypes as $type) {
|
||||
foreach ($dateTypes as $dateType) {
|
||||
$query = $this->db->prepare(
|
||||
"SELECT COUNT(*) AS COUNT
|
||||
FROM Anchor
|
||||
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||
WHERE Type = :type AND $dateType BETWEEN :dateStart AND :dateEnd"
|
||||
);
|
||||
|
||||
$query->execute([
|
||||
':type' => $type,
|
||||
':dateStart' => $dateStart,
|
||||
':dateEnd' => $dateEnd
|
||||
]);
|
||||
|
||||
$rec = $query->fetch(PDO::FETCH_ASSOC);
|
||||
$r[$type][$dateType] = $rec ? intval($rec['COUNT']) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function prepare_desc($text)
|
||||
{
|
||||
$desc = is_array($text) ? $text[0] : $text;
|
||||
$text = strtolower($desc);
|
||||
$desc = str_replace(' ', '_', $text);
|
||||
|
||||
$search = ['ü', 'ä', 'ö', 'ß', '.', ',', 'Ö', 'Ü', 'Ä', '[', ']', '<', '>', '""'];
|
||||
$replace = ['ue', 'ae', 'oe', 'ss', '', '', 'oe', 'ue', 'ae', '_', '_', '<', '>', '"'];
|
||||
$desc = str_replace($search, $replace, $desc);
|
||||
|
||||
return $desc;
|
||||
}
|
||||
}
|
||||
?>
|
||||
134
ajax/newClassification.php
Normal file
134
ajax/newClassification.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* newClassification.php - Erstellt einen neuen Klassifikationseintrag
|
||||
*
|
||||
* Nutzt die CRUD-Klasse aus libAuthorities.php.
|
||||
* Nach dem Anlegen in Anchor/Entry wird automatisch ein Eintrag
|
||||
* in der Treeview-Tabelle erstellt und an der korrekten Position
|
||||
* im Baum eingehängt.
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
include "libAuthorities.php";
|
||||
|
||||
// Parameter aus JavaScript
|
||||
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$notation = isset($_POST['notation']) ? trim($_POST['notation']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
// Notation hat Vorrang vor Classification
|
||||
if (!empty($notation)) {
|
||||
$classification = $notation;
|
||||
}
|
||||
|
||||
if (empty($term)) {
|
||||
echo json_encode(['status' => 400, 'message' => 'Bitte Text eingeben!']);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt die Eltern-Notation durch Abschneiden des letzten Segments.
|
||||
*
|
||||
* Beispiele:
|
||||
* "T 4.10" → "T 4"
|
||||
* "G 1.1.1" → "G 1.1"
|
||||
* "G 1.1" → "G 1"
|
||||
* "G 1" → "G"
|
||||
* "G" → null (Root-Ebene)
|
||||
*/
|
||||
function getParentNotation($notation) {
|
||||
$lastDot = strrpos($notation, '.');
|
||||
if ($lastDot !== false) {
|
||||
return substr($notation, 0, $lastDot);
|
||||
}
|
||||
$lastSpace = strrpos($notation, ' ');
|
||||
if ($lastSpace !== false) {
|
||||
return substr($notation, 0, $lastSpace);
|
||||
}
|
||||
return null; // Root
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht die Treeview-ID für eine gegebene Notation.
|
||||
* Die Notation steht am Anfang des name-Feldes, gefolgt von einem Leerzeichen
|
||||
* oder ist identisch mit dem name-Feld (bei Blatt-Knoten ohne Titel).
|
||||
*
|
||||
* @param mysqli $conn
|
||||
* @param string $notation z.B. "T 4"
|
||||
* @return int Treeview-ID oder 0 wenn nicht gefunden
|
||||
*/
|
||||
function findTreeviewIdByNotation($conn, $notation) {
|
||||
$escaped = mysqli_real_escape_string($conn, $notation);
|
||||
// Entweder exakter Match oder Notation + Leerzeichen am Anfang
|
||||
$sql = "SELECT id FROM Treeview
|
||||
WHERE name = '$escaped'
|
||||
OR name LIKE '$escaped %'
|
||||
ORDER BY id
|
||||
LIMIT 1";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if ($res) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
if ($row) return intval($row['id']);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$crud = new CRUD();
|
||||
|
||||
// Normalisierten Descriptor erzeugen
|
||||
$desc = $crud->prepare_desc($term);
|
||||
$completetext = $term;
|
||||
|
||||
// Dubletten-Prüfung (normalisiert)
|
||||
$existing = $crud->checkDuplicateByNormalizedText('Classification', $desc);
|
||||
if ($existing > 0) {
|
||||
echo json_encode(['status' => 409, 'message' => 'Eine Klassifikation mit diesem Text existiert bereits']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Neuen Eintrag in Anchor + Entry erstellen
|
||||
$result = $crud->insertNewTerm('Classification', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||
|
||||
// --- Treeview-Eintrag anlegen ---
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (!mysqli_connect_errno()) {
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parent-ID ermitteln
|
||||
$parentId = 0; // Default: Root
|
||||
if (!empty($classification)) {
|
||||
$parentNotation = getParentNotation($classification);
|
||||
if ($parentNotation !== null) {
|
||||
$foundId = findTreeviewIdByNotation($conn, $parentNotation);
|
||||
if ($foundId > 0) {
|
||||
$parentId = $foundId;
|
||||
}
|
||||
// Hinweis: wenn Parent nicht gefunden → landet im Root (parent_id = 0)
|
||||
}
|
||||
}
|
||||
|
||||
// name und text = vollständiger Term (Notation + Bezeichnung), link = '#'
|
||||
$nameEscaped = mysqli_real_escape_string($conn, $term);
|
||||
$insertSql = "INSERT INTO Treeview (name, text, link, parent_id)
|
||||
VALUES ('$nameEscaped', '$nameEscaped', '#', '$parentId')";
|
||||
mysqli_query($conn, $insertSql);
|
||||
$treeviewId = mysqli_insert_id($conn);
|
||||
mysqli_close($conn);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Klassifikation erfolgreich erstellt',
|
||||
'Anchor' => $result['IDAnchor'],
|
||||
'Entry' => $result['IDEntry'],
|
||||
'Linking' => $result['IDLinking'],
|
||||
'TreeviewID' => $treeviewId ?? 0
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
52
ajax/newCorporate.php
Normal file
52
ajax/newCorporate.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* newCorporate.php - Erstellt einen neuen Körperschaftseintrag
|
||||
*
|
||||
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
include "libAuthorities.php";
|
||||
|
||||
// Parameter aus JavaScript
|
||||
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
if (empty($term)) {
|
||||
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$crud = new CRUD();
|
||||
|
||||
// Descriptor erstellen (für Anchor.Text)
|
||||
$desc = $crud->prepare_desc($term);
|
||||
$completetext = $term;
|
||||
|
||||
// Prüfen ob bereits existiert
|
||||
$existing = $crud->readAuthorityNumbersOfRecords('Corporate', $term, 0, true);
|
||||
if ($existing > 0) {
|
||||
echo json_encode(['status' => 409, 'message' => 'Eine Körperschaft mit diesem Namen existiert bereits']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Neuen Eintrag erstellen
|
||||
$result = $crud->insertNewTerm('Corporate', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Körperschaft erfolgreich erstellt',
|
||||
'Anchor' => $result['IDAnchor'],
|
||||
'Entry' => $result['IDEntry'],
|
||||
'Linking' => $result['IDLinking']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
51
ajax/newPerson.php
Normal file
51
ajax/newPerson.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* newPerson.php - Erstellt einen neuen Personeneintrag
|
||||
*
|
||||
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
include "libAuthorities.php";
|
||||
|
||||
// Parameter aus JavaScript
|
||||
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
if (empty($term)) {
|
||||
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$crud = new CRUD();
|
||||
|
||||
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
|
||||
$desc = $crud->prepare_desc($term);
|
||||
$completetext = $term;
|
||||
|
||||
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
|
||||
// Verhindert Fehlalarme durch abweichende Groß-/Kleinschreibung oder Sonderzeichen
|
||||
$existing = $crud->checkDuplicateByNormalizedText('Person', $desc);
|
||||
if ($existing > 0) {
|
||||
echo json_encode(['status' => 409, 'message' => 'Eine Person mit diesem Namen existiert bereits']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Neuen Eintrag erstellen
|
||||
$result = $crud->insertNewTerm('Person', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Person erfolgreich erstellt',
|
||||
'Anchor' => $result['IDAnchor'],
|
||||
'Entry' => $result['IDEntry'],
|
||||
'Linking' => $result['IDLinking']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
52
ajax/newPublisher.php
Normal file
52
ajax/newPublisher.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* newPublisher.php - Erstellt einen neuen Verlagseintrag
|
||||
*
|
||||
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
include "libAuthorities.php";
|
||||
|
||||
// Parameter aus JavaScript
|
||||
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
if (empty($term)) {
|
||||
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$crud = new CRUD();
|
||||
|
||||
// Descriptor erstellen (für Anchor.Text)
|
||||
$desc = $crud->prepare_desc($term);
|
||||
$completetext = $term;
|
||||
|
||||
// Prüfen ob bereits existiert
|
||||
$existing = $crud->readAuthorityNumbersOfRecords('Publisher', $term, 0, true);
|
||||
if ($existing > 0) {
|
||||
echo json_encode(['status' => 409, 'message' => 'Ein Verlag mit diesem Namen existiert bereits']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Neuen Eintrag erstellen
|
||||
$result = $crud->insertNewTerm('Publisher', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Verlag erfolgreich erstellt',
|
||||
'Anchor' => $result['IDAnchor'],
|
||||
'Entry' => $result['IDEntry'],
|
||||
'Linking' => $result['IDLinking']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
50
ajax/newSubject.php
Normal file
50
ajax/newSubject.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* newSubject.php - Erstellt einen neuen Schlagwort-Eintrag
|
||||
*
|
||||
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
include "libAuthorities.php";
|
||||
|
||||
// Parameter aus JavaScript (SubjectScript.js → CreateNewEntry)
|
||||
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||
|
||||
if (empty($term)) {
|
||||
echo json_encode(['status' => 400, 'message' => 'Bitte Schlagwort eingeben!']);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$crud = new CRUD();
|
||||
|
||||
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
|
||||
$desc = $crud->prepare_desc($term);
|
||||
$completetext = $term;
|
||||
|
||||
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
|
||||
$existing = $crud->checkDuplicateByNormalizedText('Subject', $desc);
|
||||
if ($existing > 0) {
|
||||
echo json_encode(['status' => 409, 'message' => 'Ein Schlagwort mit diesem Begriff existiert bereits']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Neuen Eintrag erstellen
|
||||
$result = $crud->insertNewTerm('Subject', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 200,
|
||||
'message' => 'Schlagwort erfolgreich erstellt',
|
||||
'Anchor' => $result['IDAnchor'],
|
||||
'Entry' => $result['IDEntry'],
|
||||
'Linking' => $result['IDLinking']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
123
ajax/saveSynonym.php
Normal file
123
ajax/saveSynonym.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* saveSynonym.php - Speichert ein neues Synonym
|
||||
*
|
||||
* Parameter:
|
||||
* - text1: Erster Begriff (bereits existierend)
|
||||
* - text2: Zweiter Begriff (neues Synonym)
|
||||
* - action: 'add' oder 'remove'
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$text1 = isset($_POST['text1']) ? trim($_POST['text1']) : '';
|
||||
$text2 = isset($_POST['text2']) ? trim($_POST['text2']) : '';
|
||||
$action = isset($_POST['action']) ? $_POST['action'] : 'add';
|
||||
|
||||
if (empty($text1) || empty($text2)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Beide Begriffe müssen angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
|
||||
function prepare_desc($text) {
|
||||
$desc = strtolower($text);
|
||||
$desc = str_replace(' ', '_', $desc);
|
||||
return $desc;
|
||||
}
|
||||
|
||||
$desc1 = prepare_desc($text1);
|
||||
$desc2 = prepare_desc($text2);
|
||||
|
||||
if ($action === 'add') {
|
||||
// Prüfen ob text1 bereits eine IDLinking hat
|
||||
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc1) . "' LIMIT 1";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
$idLinking = null;
|
||||
|
||||
if ($res && mysqli_num_rows($res) > 0) {
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
$idLinking = $row['IDLinking'];
|
||||
}
|
||||
|
||||
// Prüfen ob text2 bereits eine IDLinking hat
|
||||
$sql2 = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "' LIMIT 1";
|
||||
$res2 = mysqli_query($conn, $sql2);
|
||||
$idLinking2 = null;
|
||||
|
||||
if ($res2 && mysqli_num_rows($res2) > 0) {
|
||||
$row2 = mysqli_fetch_assoc($res2);
|
||||
$idLinking2 = $row2['IDLinking'];
|
||||
}
|
||||
|
||||
// Fall 1: Beide haben noch keine IDLinking - neue Gruppe erstellen
|
||||
if ($idLinking === null && $idLinking2 === null) {
|
||||
// Höchste IDLinking finden und +1
|
||||
$sqlMax = "SELECT MAX(IDLinking) as maxId FROM Synonyms";
|
||||
$resMax = mysqli_query($conn, $sqlMax);
|
||||
$rowMax = mysqli_fetch_assoc($resMax);
|
||||
$newIdLinking = ($rowMax['maxId'] ?? 0) + 1;
|
||||
|
||||
// Beide Begriffe einfügen
|
||||
$sqlInsert1 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
|
||||
$sqlInsert2 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
|
||||
|
||||
mysqli_query($conn, $sqlInsert1);
|
||||
mysqli_query($conn, $sqlInsert2);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Neue Synonym-Gruppe erstellt', 'idLinking' => $newIdLinking]);
|
||||
}
|
||||
// Fall 2: text1 hat bereits eine IDLinking - text2 zur Gruppe hinzufügen
|
||||
else if ($idLinking !== null && $idLinking2 === null) {
|
||||
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
|
||||
mysqli_query($conn, $sqlInsert);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Synonym zur bestehenden Gruppe hinzugefügt', 'idLinking' => $idLinking]);
|
||||
}
|
||||
// Fall 3: text2 hat bereits eine IDLinking - text1 zur Gruppe hinzufügen
|
||||
else if ($idLinking === null && $idLinking2 !== null) {
|
||||
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking2, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
|
||||
mysqli_query($conn, $sqlInsert);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Begriff zur bestehenden Synonym-Gruppe hinzugefügt', 'idLinking' => $idLinking2]);
|
||||
}
|
||||
// Fall 4: Beide haben bereits IDLinking - Gruppen zusammenführen
|
||||
else {
|
||||
if ($idLinking == $idLinking2) {
|
||||
echo json_encode(['success' => false, 'error' => 'Diese Begriffe sind bereits Synonyme']);
|
||||
} else {
|
||||
// Alle Einträge von idLinking2 auf idLinking umstellen
|
||||
$sqlUpdate = "UPDATE Synonyms SET IDLinking = $idLinking WHERE IDLinking = $idLinking2";
|
||||
mysqli_query($conn, $sqlUpdate);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Synonym-Gruppen zusammengeführt', 'idLinking' => $idLinking]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($action === 'remove') {
|
||||
// Synonym entfernen (nur den einen Eintrag löschen)
|
||||
$sqlDelete = "DELETE FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "'";
|
||||
mysqli_query($conn, $sqlDelete);
|
||||
|
||||
if (mysqli_affected_rows($conn) > 0) {
|
||||
echo json_encode(['success' => true, 'message' => 'Synonym entfernt']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Synonym nicht gefunden']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(['success' => false, 'error' => 'Unbekannte Aktion']);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
75
ajax/searchTreeData.php
Normal file
75
ajax/searchTreeData.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* searchTreeData.php - Sucht in der Treeview-Tabelle
|
||||
*
|
||||
* Parameter:
|
||||
* - q: Suchbegriff (führendes * = Linkstrunkierung, abschließendes * = Rechtstrunkierung)
|
||||
* Ohne * → automatische Rechtstrunkierung (bisheriges Verhalten)
|
||||
*
|
||||
* Rückgabe: JSON-Array mit gefundenen Einträgen
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$searchTerm = isset($_GET['q']) ? trim($_GET['q']) : '';
|
||||
|
||||
if (strlen($searchTerm) < 2) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Trunkierung auswerten
|
||||
$leftTrunc = (substr($searchTerm, 0, 1) === '*');
|
||||
$rightTrunc = (substr($searchTerm, -1) === '*');
|
||||
|
||||
// Sternchen entfernen, dann escapen
|
||||
$clean = trim($searchTerm, '*');
|
||||
if (strlen($clean) < 2) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$escaped = mysqli_real_escape_string($conn, $clean);
|
||||
// LIKE-Sonderzeichen in den Daten escapen
|
||||
$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $escaped);
|
||||
|
||||
// Pattern bauen:
|
||||
// *Begriff → %Begriff% (links- und rechtstrunkiert)
|
||||
// Begriff* → Begriff% (nur rechtstrunkiert)
|
||||
// *Begriff* → %Begriff% (beidseitig)
|
||||
// Begriff → Begriff% (Standard: Rechtstrunkierung)
|
||||
$pattern = ($leftTrunc ? '%' : '') .
|
||||
$escaped .
|
||||
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
|
||||
|
||||
$sql = "SELECT id, name, text, parent_id
|
||||
FROM Treeview
|
||||
WHERE LOWER(text) LIKE LOWER('$pattern') OR LOWER(name) LIKE LOWER('$pattern')
|
||||
ORDER BY text
|
||||
LIMIT 50";
|
||||
|
||||
$res = mysqli_query($conn, $sql);
|
||||
if (!$res) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$result = [];
|
||||
while ($row = mysqli_fetch_assoc($res)) {
|
||||
$result[] = [
|
||||
'id' => strval($row['id']),
|
||||
'text' => $row['text'] ? $row['text'] : $row['name'],
|
||||
'parent_id' => $row['parent_id']
|
||||
];
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
113
ajax/writeNewRelation.php
Normal file
113
ajax/writeNewRelation.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* writeNewRelation.php - Speichert eine neue Relation
|
||||
*
|
||||
* Parameter (POST):
|
||||
* - IDAnchor: ID des Ausgangseintrags
|
||||
* - IDEntry: ID des Zieleintrags
|
||||
* - Relationtype: Art der Relation (BT, NT, RT, USE, UF)
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||
exit();
|
||||
}
|
||||
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
// Parameter aus JavaScript (AnchorID, relationType, relationID)
|
||||
$idAnchor = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
|
||||
if ($idAnchor === 0) {
|
||||
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
|
||||
}
|
||||
|
||||
$idEntry = isset($_POST['relationID']) ? intval($_POST['relationID']) : 0;
|
||||
if ($idEntry === 0) {
|
||||
$idEntry = isset($_POST['IDEntry']) ? intval($_POST['IDEntry']) : 0;
|
||||
}
|
||||
|
||||
$relationtype = isset($_POST['relationType']) ? trim($_POST['relationType']) : '';
|
||||
if (empty($relationtype)) {
|
||||
$relationtype = isset($_POST['Relationtype']) ? trim($_POST['Relationtype']) : '';
|
||||
}
|
||||
|
||||
if ($idAnchor === 0 || $idEntry === 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'IDAnchor und IDEntry müssen angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (empty($relationtype)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Relationtype muss angegeben werden']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Erlaubte Relationstypen
|
||||
$allowedTypes = ['BT', 'NT', 'RT', 'USE', 'UF'];
|
||||
if (!in_array($relationtype, $allowedTypes)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Ungültiger Relationstyp']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Prüfen ob Relation bereits existiert
|
||||
$checkSql = "SELECT ID FROM Linking WHERE IDAnchor = $idAnchor AND IDEntry = $idEntry AND Relationtype = '" . mysqli_real_escape_string($conn, $relationtype) . "'";
|
||||
$checkResult = mysqli_query($conn, $checkSql);
|
||||
|
||||
if ($checkResult && mysqli_num_rows($checkResult) > 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Diese Relation existiert bereits']);
|
||||
mysqli_close($conn);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Relation einfügen
|
||||
$insertSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idAnchor, $idEntry, '" . mysqli_real_escape_string($conn, $relationtype) . "')";
|
||||
|
||||
if (mysqli_query($conn, $insertSql)) {
|
||||
$newId = mysqli_insert_id($conn);
|
||||
|
||||
// Reziproke Relation erstellen (BT <-> NT, RT <-> RT)
|
||||
$reciprocalType = null;
|
||||
switch ($relationtype) {
|
||||
case 'BT':
|
||||
$reciprocalType = 'NT';
|
||||
break;
|
||||
case 'NT':
|
||||
$reciprocalType = 'BT';
|
||||
break;
|
||||
case 'RT':
|
||||
$reciprocalType = 'RT';
|
||||
break;
|
||||
case 'USE':
|
||||
$reciprocalType = 'UF';
|
||||
break;
|
||||
case 'UF':
|
||||
$reciprocalType = 'USE';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($reciprocalType) {
|
||||
// Prüfen ob reziproke Relation schon existiert
|
||||
$checkRecSql = "SELECT ID FROM Linking WHERE IDAnchor = $idEntry AND IDEntry = $idAnchor AND Relationtype = '$reciprocalType'";
|
||||
$checkRecResult = mysqli_query($conn, $checkRecSql);
|
||||
|
||||
if (!$checkRecResult || mysqli_num_rows($checkRecResult) === 0) {
|
||||
$insertRecSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idEntry, $idAnchor, '$reciprocalType')";
|
||||
mysqli_query($conn, $insertRecSql);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Relation erfolgreich erstellt',
|
||||
'id' => $newId
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Fehler beim Speichern: ' . mysqli_error($conn)]);
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
?>
|
||||
115
dspace-api/getAuthorityCount.php
Normal file
115
dspace-api/getAuthorityCount.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* getAuthorityCount.php - Zählt DSpace-Datensätze für einen Authority-Begriff
|
||||
*
|
||||
* Parameter:
|
||||
* - authType: 'Subject' oder 'Person'
|
||||
* - query: Der zu suchende Begriff (exakte Übereinstimmung)
|
||||
*
|
||||
* Response: JSON mit count
|
||||
*
|
||||
* Beispiel:
|
||||
* curl "https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php?authType=Subject&query=Berufsbildung"
|
||||
* => {"query":"Berufsbildung","authType":"Subject","count":42}
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// IP-Prüfung
|
||||
require_once __DIR__ . '/checkAccess.php';
|
||||
|
||||
// Parameter
|
||||
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
|
||||
|
||||
// Validierung
|
||||
$allowedTypes = array('Subject', 'Person');
|
||||
if (!in_array($authType, $allowedTypes)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(array('error' => 'Invalid authType. Allowed: Subject, Person'));
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strlen($query) === 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(array('error' => 'Missing parameter: query'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Solr-Feld je nach authType
|
||||
switch ($authType) {
|
||||
case 'Subject':
|
||||
$queryField = 'dc.subject.other';
|
||||
break;
|
||||
case 'Person':
|
||||
$queryField = 'dc.contributor.author';
|
||||
break;
|
||||
}
|
||||
|
||||
// Query bereinigen
|
||||
$query = urldecode($query);
|
||||
$query = str_replace("'", "", $query);
|
||||
|
||||
// Solr-Abfrage
|
||||
$solr_baseurl = "http://localhost:8080/solr/search/query?";
|
||||
$solr_query = $queryField . ':' . $query;
|
||||
$rows = "&rows=10000";
|
||||
$fl = "&fl=" . $queryField;
|
||||
$solr_URL = $solr_baseurl . "wt=phps" . $fl . $rows . "&q=" . urlencode($solr_query . " AND withdrawn:false");
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array('timeout' => 10)
|
||||
));
|
||||
|
||||
$response = @file_get_contents($solr_URL, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array('error' => 'Solr query failed'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = unserialize($response);
|
||||
|
||||
if (!isset($result['response']['docs'])) {
|
||||
echo json_encode(array(
|
||||
'query' => $query,
|
||||
'authType' => $authType,
|
||||
'count' => 0
|
||||
));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Exakte Treffer zählen (analog zu DSpaceCountModel)
|
||||
$count = 0;
|
||||
foreach ($result['response']['docs'] as $entry) {
|
||||
foreach ($entry as $hit) {
|
||||
if (!is_array($hit)) {
|
||||
$hit = array($hit);
|
||||
}
|
||||
foreach ($hit as $h) {
|
||||
if ($authType === "Subject") {
|
||||
if (strcmp($query, trim($h)) === 0) {
|
||||
$count++;
|
||||
}
|
||||
} else {
|
||||
// Person: Authority-ID in eckigen Klammern abschneiden
|
||||
$name = trim(substr($h, 0, strpos($h, "[")));
|
||||
if (strlen($name) === 0) {
|
||||
$name = trim($h);
|
||||
}
|
||||
if (strcmp($query, $name) === 0) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(array(
|
||||
'query' => $query,
|
||||
'authType' => $authType,
|
||||
'count' => $count
|
||||
));
|
||||
?>
|
||||
46
getTreePath.php
Normal file
46
getTreePath.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
|
||||
*
|
||||
* Parameter (GET):
|
||||
* - id: Treeview-ID des Zielknotens
|
||||
*
|
||||
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
|
||||
* Beispiel: [98106, 98107, 98113, 98115]
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
include "db_connection.php";
|
||||
|
||||
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||
if (mysqli_connect_errno()) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
mysqli_set_charset($conn, "utf8");
|
||||
|
||||
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
if ($targetId === 0) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
|
||||
$path = [];
|
||||
$curId = $targetId;
|
||||
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
|
||||
|
||||
while ($curId > 0 && $limit-- > 0) {
|
||||
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
|
||||
if (!$res) break;
|
||||
$row = mysqli_fetch_assoc($res);
|
||||
if (!$row) break;
|
||||
|
||||
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
|
||||
$parentId = intval($row['parent_id']);
|
||||
if ($parentId === 0) break;
|
||||
$curId = $parentId;
|
||||
}
|
||||
|
||||
mysqli_close($conn);
|
||||
echo json_encode($path, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
539
index.php
Normal file
539
index.php
Normal file
@ -0,0 +1,539 @@
|
||||
<?php
|
||||
include "templates/Header.html";
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* Dashboard Styles */
|
||||
.dashboard-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Statistik-Karten */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stat-card.subject { border-top: 4px solid #28a745; }
|
||||
.stat-card.subject .stat-icon { color: #28a745; }
|
||||
|
||||
.stat-card.person { border-top: 4px solid #007bff; }
|
||||
.stat-card.person .stat-icon { color: #007bff; }
|
||||
|
||||
.stat-card.corporate { border-top: 4px solid #fd7e14; }
|
||||
.stat-card.corporate .stat-icon { color: #fd7e14; }
|
||||
|
||||
.stat-card.publisher { border-top: 4px solid #6f42c1; }
|
||||
.stat-card.publisher .stat-icon { color: #6f42c1; }
|
||||
|
||||
.stat-card.classification { border-top: 4px solid #20c997; }
|
||||
.stat-card.classification .stat-icon { color: #20c997; }
|
||||
|
||||
.stat-card.total { border-top: 4px solid var(--primary-color); }
|
||||
.stat-card.total .stat-icon { color: var(--primary-color); }
|
||||
|
||||
/* Zwei-Spalten Layout */
|
||||
.dashboard-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Listen-Karten */
|
||||
.list-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-card-header {
|
||||
background: var(--bg-light);
|
||||
padding: 12px 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.list-card-body {
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item-text {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.list-item-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-item-type {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-light);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.list-item-date {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.list-item-link {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.list-item-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Qualitäts-Indikatoren */
|
||||
.quality-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.quality-item {
|
||||
background: white;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quality-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.quality-value {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.quality-value.warning {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.quality-value.ok {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.loading-spinner i {
|
||||
font-size: 2rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Navigation Cards */
|
||||
.nav-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 25px 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.nav-card-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-card-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="card dashboard-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fas fa-th-large"></i> Normdateien verwalten
|
||||
</h2>
|
||||
|
||||
<div class="nav-grid">
|
||||
<a href="Subjects.php" class="nav-card">
|
||||
<div class="nav-card-icon">🏷️</div>
|
||||
<div class="nav-card-title">Schlagworte</div>
|
||||
</a>
|
||||
|
||||
<a href="Persons.php" class="nav-card">
|
||||
<div class="nav-card-icon">👥</div>
|
||||
<div class="nav-card-title">Personen</div>
|
||||
</a>
|
||||
|
||||
<a href="Corporates.php" class="nav-card">
|
||||
<div class="nav-card-icon">🏢</div>
|
||||
<div class="nav-card-title">Körperschaften</div>
|
||||
</a>
|
||||
|
||||
<a href="Publishers.php" class="nav-card">
|
||||
<div class="nav-card-icon">📚</div>
|
||||
<div class="nav-card-title">Verlage</div>
|
||||
</a>
|
||||
|
||||
<a href="Classifications.php" class="nav-card">
|
||||
<div class="nav-card-icon">🗂️</div>
|
||||
<div class="nav-card-title">Klassifikation</div>
|
||||
</a>
|
||||
|
||||
<a href="Treeview.php" class="nav-card">
|
||||
<div class="nav-card-icon">🌳</div>
|
||||
<div class="nav-card-title">Klassifikationsbaum</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Übersicht Card -->
|
||||
<div class="card dashboard-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fas fa-chart-bar"></i> Übersicht
|
||||
</h2>
|
||||
|
||||
<!-- Statistik-Karten -->
|
||||
<div id="stats-container">
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
<span>Lade Statistiken...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aktivität & Qualität -->
|
||||
<div class="dashboard-row dashboard-section">
|
||||
|
||||
<!-- Zuletzt geändert -->
|
||||
<div class="list-card">
|
||||
<div class="list-card-header">
|
||||
<i class="fas fa-clock"></i> Zuletzt geändert
|
||||
</div>
|
||||
<div class="list-card-body" id="recently-modified">
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zuletzt hinzugefügt -->
|
||||
<div class="list-card">
|
||||
<div class="list-card-header">
|
||||
<i class="fas fa-plus-circle"></i> Zuletzt hinzugefügt
|
||||
</div>
|
||||
<div class="list-card-body" id="recently-created">
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Qualitäts-Check -->
|
||||
<div class="card dashboard-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fas fa-check-circle"></i> Qualitäts-Check
|
||||
</h2>
|
||||
<p class="text-muted mb-15">Einträge ohne Relationen (Waisen)</p>
|
||||
<div id="quality-container">
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
loadDashboardStats();
|
||||
});
|
||||
|
||||
function loadDashboardStats() {
|
||||
$.get('/Thesaurus/ajax/getDashboardStats.php', function(data) {
|
||||
var stats = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
|
||||
if (stats.error) {
|
||||
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
// Statistik-Karten rendern
|
||||
renderStatsCards(stats);
|
||||
|
||||
// Zuletzt geändert
|
||||
renderRecentList('#recently-modified', stats.recentlyModified);
|
||||
|
||||
// Zuletzt hinzugefügt
|
||||
renderRecentList('#recently-created', stats.recentlyCreated);
|
||||
|
||||
// Qualitäts-Check
|
||||
renderQualityCheck(stats);
|
||||
|
||||
}).fail(function() {
|
||||
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
|
||||
});
|
||||
}
|
||||
|
||||
function renderStatsCards(stats) {
|
||||
var html = '<div class="stats-grid">';
|
||||
|
||||
html += '<div class="stat-card subject">';
|
||||
html += '<div class="stat-icon">🏷️</div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.counts.Subject) + '</div>';
|
||||
html += '<div class="stat-label">Schlagworte</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="stat-card person">';
|
||||
html += '<div class="stat-icon">👥</div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.counts.Person) + '</div>';
|
||||
html += '<div class="stat-label">Personen</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="stat-card corporate">';
|
||||
html += '<div class="stat-icon">🏢</div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.counts.Corporate) + '</div>';
|
||||
html += '<div class="stat-label">Körperschaften</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="stat-card publisher">';
|
||||
html += '<div class="stat-icon">📚</div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.counts.Publisher) + '</div>';
|
||||
html += '<div class="stat-label">Verlage</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="stat-card classification">';
|
||||
html += '<div class="stat-icon">🗂️</div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.counts.Classification) + '</div>';
|
||||
html += '<div class="stat-label">Klassifikationen</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="stat-card total">';
|
||||
html += '<div class="stat-icon"><i class="fas fa-database"></i></div>';
|
||||
html += '<div class="stat-value">' + formatNumber(stats.total) + '</div>';
|
||||
html += '<div class="stat-label">Gesamt</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Zusätzliche Info-Zeile
|
||||
html += '<div class="d-flex gap-4 mt-15 text-muted" style="font-size: 0.9rem;">';
|
||||
html += '<span><i class="fas fa-link me-1"></i> ' + formatNumber(stats.relationsTotal) + ' Relationen</span>';
|
||||
html += '<span><i class="fas fa-check me-1"></i> ' + formatNumber(stats.descriptors) + ' Deskriptoren</span>';
|
||||
html += '<span><i class="fas fa-times me-1"></i> ' + formatNumber(stats.nonDescriptors) + ' Non-Deskriptoren</span>';
|
||||
html += '</div>';
|
||||
|
||||
$('#stats-container').html(html);
|
||||
}
|
||||
|
||||
function renderRecentList(container, items) {
|
||||
if (!items || items.length === 0) {
|
||||
$(container).html('<div class="text-center text-muted p-4">Keine Einträge</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
items.forEach(function(item) {
|
||||
var typeLabel = getTypeLabel(item.type);
|
||||
var link = getTypeLink(item.type, item.id);
|
||||
var date = formatDate(item.date);
|
||||
|
||||
html += '<div class="list-item">';
|
||||
html += '<a href="' + link + '" class="list-item-text list-item-link" title="' + escapeHtml(item.text) + '">' + escapeHtml(item.text) + '</a>';
|
||||
html += '<div class="list-item-meta">';
|
||||
html += '<span class="list-item-type">' + typeLabel + '</span>';
|
||||
html += '<span class="list-item-date">' + date + '</span>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
$(container).html(html);
|
||||
}
|
||||
|
||||
function renderQualityCheck(stats) {
|
||||
var html = '<div class="quality-grid">';
|
||||
|
||||
var types = [
|
||||
{ key: 'Subject', label: 'Schlagworte', icon: '🏷️' },
|
||||
{ key: 'Person', label: 'Personen', icon: '👥' },
|
||||
{ key: 'Corporate', label: 'Körperschaften', icon: '🏢' },
|
||||
{ key: 'Publisher', label: 'Verlage', icon: '📚' },
|
||||
{ key: 'Classification', label: 'Klassifikation', icon: '🗂️' }
|
||||
];
|
||||
|
||||
types.forEach(function(type) {
|
||||
var count = stats.orphans[type.key] || 0;
|
||||
var valueClass = count > 0 ? 'warning' : 'ok';
|
||||
|
||||
html += '<div class="quality-item">';
|
||||
html += '<span class="quality-label">' + type.icon + ' ' + type.label + '</span>';
|
||||
html += '<span class="quality-value ' + valueClass + '">' + formatNumber(count) + '</span>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Gesamt-Waisen
|
||||
var totalOrphans = stats.orphansTotal || 0;
|
||||
html += '<div class="mt-15 text-muted" style="font-size: 0.9rem;">';
|
||||
html += '<i class="fas fa-exclamation-triangle me-1"></i> ';
|
||||
html += 'Gesamt: <strong>' + formatNumber(totalOrphans) + '</strong> Einträge ohne Relationen';
|
||||
html += '</div>';
|
||||
|
||||
$('#quality-container').html(html);
|
||||
}
|
||||
|
||||
function getTypeLabel(type) {
|
||||
var labels = {
|
||||
'Subject': 'Schlagwort',
|
||||
'Person': 'Person',
|
||||
'Corporate': 'Körperschaft',
|
||||
'Publisher': 'Verlag',
|
||||
'Classification': 'Klassifikation'
|
||||
};
|
||||
return labels[type] || type;
|
||||
}
|
||||
|
||||
function getTypeLink(type, id) {
|
||||
var links = {
|
||||
'Subject': 'Subjects.php?edit=',
|
||||
'Person': 'Persons.php?edit=',
|
||||
'Corporate': 'Corporates.php?edit=',
|
||||
'Publisher': 'Publishers.php?edit=',
|
||||
'Classification': 'Classifications.php?edit='
|
||||
};
|
||||
return (links[type] || 'Subjects.php?edit=') + id;
|
||||
}
|
||||
|
||||
function formatNumber(num) {
|
||||
if (!num) return '0';
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr || dateStr === '0000-00-00 00:00:00') return '-';
|
||||
var date = new Date(dateStr);
|
||||
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include "templates/Footer.html";
|
||||
?>
|
||||
639
js/ClassificationScript.js
Normal file
639
js/ClassificationScript.js
Normal file
@ -0,0 +1,639 @@
|
||||
/**
|
||||
* Created by Roland on 2021/07/01.
|
||||
* Updated for BIBB Layout 2024
|
||||
*/
|
||||
// CLASSIFICATIONS
|
||||
|
||||
// Show blank screen to add new Classification
|
||||
function newClassificationsShow() {
|
||||
|
||||
// Sichere Element-Zugriffe mit Null-Checks
|
||||
var saveBtn = document.getElementById("newClassificationSave");
|
||||
var dismissBtn = document.getElementById("newClassificationDismiss");
|
||||
var modifyBtn = document.getElementById("ClassificationModifySave");
|
||||
var termField = document.getElementById("new_term");
|
||||
var notationField = document.getElementById("new_notation");
|
||||
var scopenoteField = document.getElementById("new_scopenote");
|
||||
var relationLabel = document.getElementById("new_relation_label");
|
||||
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
var errorDiv = document.getElementById("errorNewClassification");
|
||||
|
||||
if (saveBtn) saveBtn.style.display = 'inline';
|
||||
if (dismissBtn) dismissBtn.style.display = 'inline';
|
||||
if (modifyBtn) modifyBtn.style.display = 'none';
|
||||
if (termField) termField.disabled = false;
|
||||
if (notationField) notationField.disabled = false;
|
||||
if (scopenoteField) scopenoteField.disabled = false;
|
||||
|
||||
// Relationen-Bereich verstecken bei neuer Klassifikation
|
||||
if (relationLabel) relationLabel.style.display = 'none';
|
||||
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
|
||||
showRelations(0,0,0);
|
||||
if (relationTable) relationTable.style.display = 'none';
|
||||
if (errorDiv) errorDiv.style.display = 'none';
|
||||
|
||||
$("#new_term").val("");
|
||||
$("#new_notation").val("");
|
||||
$("#new_scopenote").val("");
|
||||
|
||||
$("#NewClassificationModal").modal("show");
|
||||
}
|
||||
|
||||
// Insert new classification record
|
||||
function CreateNewEntry(type) {
|
||||
// get values
|
||||
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var notation = $("#new_notation").val();
|
||||
notation = notation.trim();
|
||||
var detailtype = "";
|
||||
var classification = "";
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewClassification");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Klassifikation eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewClassification");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
$.post("ajax/newClassification.php", {
|
||||
term : term,
|
||||
type : type,
|
||||
notation : notation,
|
||||
detailtype : detailtype,
|
||||
classification: classification,
|
||||
scopenote : scopenote
|
||||
}, function (data, status) {
|
||||
|
||||
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var e = document.getElementById("errorNewClassification");
|
||||
|
||||
if (dt['status'] == 200)
|
||||
{
|
||||
var AnchorID = dt["Anchor"];
|
||||
var EntryID = dt["Entry"];
|
||||
var LinkingID = dt["Linking"];
|
||||
|
||||
var b = document.getElementById("newClassificationSave");
|
||||
b.style.display='none';
|
||||
var c = document.getElementById("newClassificationDismiss");
|
||||
c.style.display='none';
|
||||
document.getElementById("new_term").disabled = true;
|
||||
document.getElementById("new_notation").disabled = true;
|
||||
document.getElementById("new_scopenote").disabled = true;
|
||||
$table.bootstrapTable('refresh');
|
||||
|
||||
e.classList.remove('alert-danger');
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = 'Neue Klassifikation aufgenommen';
|
||||
e.style.display = 'block';
|
||||
|
||||
// ID setzen für Relationen
|
||||
$('#ID').val(AnchorID);
|
||||
|
||||
// Relationen-Bereich anzeigen nach Speichern
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("newRelationClassificationDismiss").style.display = 'inline';
|
||||
showRelations(AnchorID,EntryID,LinkingID);
|
||||
} else {
|
||||
e.classList.remove('alert-success');
|
||||
e.classList.add('alert-danger');
|
||||
e.textContent = dt['message'];
|
||||
e.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Show relations of a classification record
|
||||
function showRelations(Anchor,Entry,Linking)
|
||||
{
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:Anchor,
|
||||
authType:"Classification"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
if (relationTable) {
|
||||
if (data && data.trim().length > 0) {
|
||||
relationTable.style.display = 'block';
|
||||
} else {
|
||||
relationTable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
$("#NewClassificationModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// create new relation for a classification record
|
||||
function CreateNewRelation(AnchorID, relationID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!AnchorID || !relationID) {
|
||||
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationTypeElement = document.getElementById("new_relationtype");
|
||||
|
||||
if (!relationTypeElement) {
|
||||
showError('Relationstyp-Element nicht gefunden');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationType = relationTypeElement.value;
|
||||
|
||||
if (!relationType || relationType.trim() === '') {
|
||||
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "ajax/writeNewRelation.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
AnchorID: AnchorID,
|
||||
relationType: relationType,
|
||||
relationID: relationID
|
||||
},
|
||||
dataType: "text",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Success! Response:", response);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if (data.error) {
|
||||
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success === true) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||
|
||||
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||
'<code>' + response.substring(0, 200) + '</code>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||
if (xhr.responseText) {
|
||||
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||
}
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||
} else if (status === 'parsererror') {
|
||||
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
if (error) {
|
||||
errorMessage += '<br><small>' + error + '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function(xhr, status) {
|
||||
console.log("🏁 Request abgeschlossen:", status);
|
||||
if (status !== 'success') {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-danger';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-success';
|
||||
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(message) {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-warning';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-info';
|
||||
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOutAlert() {
|
||||
var errorElement = document.getElementById("errorNewClassification");
|
||||
if (errorElement && errorElement.style.display !== 'none') {
|
||||
$(errorElement).fadeOut(500, function() {
|
||||
errorElement.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
var relationTextElement = document.getElementById("search_relation");
|
||||
if (relationTextElement) {
|
||||
relationTextElement.value = '';
|
||||
}
|
||||
$('#classification-anlegen').removeData('record-id');
|
||||
$('#classification-anlegen').removeData('relation-id');
|
||||
hideAlert();
|
||||
}
|
||||
|
||||
|
||||
// Delete relation for a classification
|
||||
function DeleteRelation(AnchorID, LinkingID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteRelation.php", {
|
||||
AnchorID : AnchorID,
|
||||
LinkingID : LinkingID
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
var e = document.getElementById("errorNewClassification");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Relation gelöscht';
|
||||
e.style.display = 'block';
|
||||
|
||||
showRelations(AnchorID,0,0)
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete the whole classification entry
|
||||
function DeleteTerm(AnchorID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteTerm.php", {
|
||||
AnchorID: AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
|
||||
if (status === "success" && metadata.success === true) {
|
||||
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||
$table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||
$table.bootstrapTable('refresh');
|
||||
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Modify classification and its relations
|
||||
function ModifyTerm(AnchorID)
|
||||
{
|
||||
$("#ID").val(AnchorID[0]);
|
||||
|
||||
// Sichere Element-Zugriffe mit Null-Checks
|
||||
var termField = document.getElementById("new_term");
|
||||
var notationField = document.getElementById("new_notation");
|
||||
var scopenoteField = document.getElementById("new_scopenote");
|
||||
var relationLabel = document.getElementById("new_relation_label");
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
var saveBtn = document.getElementById("newClassificationSave");
|
||||
var dismissBtn = document.getElementById("newClassificationDismiss");
|
||||
var modifyBtn = document.getElementById("ClassificationModifySave");
|
||||
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
|
||||
var errorDiv = document.getElementById("errorNewClassification");
|
||||
|
||||
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||
if (termField) termField.disabled = true;
|
||||
|
||||
// Andere Felder aktivieren
|
||||
if (notationField) notationField.disabled = false;
|
||||
if (scopenoteField) scopenoteField.disabled = false;
|
||||
|
||||
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||
if (relationLabel) relationLabel.style.display = 'block';
|
||||
if (relationTable) relationTable.style.display = 'block';
|
||||
|
||||
// Buttons konfigurieren
|
||||
if (saveBtn) saveBtn.style.display = 'none';
|
||||
if (dismissBtn) dismissBtn.style.display = 'inline';
|
||||
if (modifyBtn) modifyBtn.style.display = 'inline';
|
||||
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
|
||||
|
||||
// Alert verstecken
|
||||
if (errorDiv) errorDiv.style.display = 'none';
|
||||
|
||||
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
|
||||
|
||||
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||
id : AnchorID[0],
|
||||
authType :"Classification"
|
||||
},
|
||||
function (data, status) {
|
||||
console.log('📥 getAuthorityDataRaw status:', status);
|
||||
console.log('📥 getAuthorityDataRaw data:', data);
|
||||
if (status == "success")
|
||||
{
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
console.log('📥 metadata:', metadata);
|
||||
dt = (metadata['rows'][0]);
|
||||
console.log('📥 dt:', dt);
|
||||
if (dt) {
|
||||
$("#new_term").val(dt.Text);
|
||||
$("#new_id").val(dt.ID);
|
||||
$("#new_notation").val(dt.Notation);
|
||||
$("#new_descriptor").val(dt.Descriptor);
|
||||
$("#new_scopenote").val(dt.Scopenote);
|
||||
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||
} else {
|
||||
console.error('❌ Keine Daten in rows[0]');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
).fail(function(xhr, status, error) {
|
||||
console.error('❌ AJAX Fehler:', status, error);
|
||||
});
|
||||
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:AnchorID[0],
|
||||
authType: "Classification"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
$("#NewClassificationModal").modal("show");
|
||||
|
||||
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||
setTimeout(function() {
|
||||
if (typeof searchRelation === 'function') {
|
||||
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||
searchRelation();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// rewrite records after updating a classification entry
|
||||
function UpdateEntry(authType)
|
||||
{
|
||||
var AnchorID = $("#ID").val();
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var notation = $("#new_notation").val();
|
||||
notation = notation.trim();
|
||||
var type = 'Classification';
|
||||
var detailtype = '';
|
||||
var classification = '';
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewClassification");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Klassifikation eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewClassification");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
|
||||
|
||||
$.post("ajax/UpdateTerm.php", {
|
||||
AnchorID: AnchorID,
|
||||
authType: "Classification",
|
||||
term: term,
|
||||
notation: notation,
|
||||
type: type,
|
||||
detailtype: detailtype,
|
||||
classification: classification,
|
||||
scopenote: scopenote
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
document.getElementById("relation_table").style.display = 'block'
|
||||
document.getElementById("newClassificationSave").style.display = 'none'
|
||||
document.getElementById("newClassificationDismiss").style.display = 'inline'
|
||||
document.getElementById("ClassificationModifySave").style.display = 'none'
|
||||
document.getElementById("newRelationClassificationDismiss").style.display = 'none'
|
||||
var e = document.getElementById("errorNewClassification");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Klassifikation geändert ';
|
||||
e.style.display = 'block';
|
||||
|
||||
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
|
||||
$("#NewClassificationModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
$table.bootstrapTable('refresh')
|
||||
}
|
||||
|
||||
function ShowModalDetails(id) {
|
||||
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType : 'Classification',
|
||||
offset : 0,
|
||||
id : id,
|
||||
sort : 'Anchor.Text',
|
||||
limit : 1
|
||||
},
|
||||
function(data, status) {
|
||||
if (status == "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
//Assign existing values to the modal popup fields
|
||||
$("#display_term").val(row[0].Text);
|
||||
$("#display_id").val(row[0].ID);
|
||||
$("#display_notation").val(row[0].Notation);
|
||||
$("#display_descriptor").val(row[0].Descriptor);
|
||||
$("#display_scopenote").val(row[0].Scopenote);
|
||||
|
||||
var html = []
|
||||
var txt = ''
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
txt = txt +'<div class="col-sm-12">';
|
||||
txt = txt +'<div class="col-sm-2"></div>';
|
||||
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||
txt = txt +'</div>';
|
||||
})
|
||||
$(".modal_content").html(txt);
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||
})
|
||||
$("#display_relations").val(txt);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$("#DetailsModal").modal("show");
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshPage()
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function CopyLink(value)
|
||||
{
|
||||
var tempInput = document.createElement("input");
|
||||
tempInput.value = value;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
633
js/CorporateScript.js
Normal file
633
js/CorporateScript.js
Normal file
@ -0,0 +1,633 @@
|
||||
/**
|
||||
* Created by Roland on 2021/07/01.
|
||||
* Updated for BIBB Layout 2024
|
||||
*/
|
||||
// CORPORATES
|
||||
|
||||
// Show blank screen to add new Corporate
|
||||
function newCorporatesShow() {
|
||||
|
||||
var b = document.getElementById("newCorporateSave");
|
||||
b.style.display='inline';
|
||||
var c = document.getElementById("newCorporateDismiss");
|
||||
c.style.display='inline';
|
||||
document.getElementById("CorporateModifySave").style.display = 'none';
|
||||
document.getElementById("new_term").disabled = false;
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
// Relationen-Bereich verstecken bei neuer Körperschaft
|
||||
document.getElementById("new_relation_label").style.display = 'none';
|
||||
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
|
||||
showRelations(0,0,0);
|
||||
var a = document.getElementById("relation_table");
|
||||
if (a != undefined) {
|
||||
document.getElementById("relation_table").style.display = 'none';
|
||||
}
|
||||
var e = document.getElementById("errorNewCorporate");
|
||||
e.style.display = 'none';
|
||||
|
||||
$("#new_term").val("");
|
||||
$("#new_scopenote").val("");
|
||||
|
||||
$("#NewCorporateModal").modal("show");
|
||||
}
|
||||
|
||||
// Insert new corporate record
|
||||
function CreateNewEntry(type) {
|
||||
// get values
|
||||
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = "";
|
||||
var classification = "";
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewCorporate");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Körperschaft eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewCorporate");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
$.post("ajax/newCorporate.php", {
|
||||
term : term,
|
||||
type : type,
|
||||
detailtype : detailtype,
|
||||
classification: classification,
|
||||
scopenote : scopenote
|
||||
}, function (data, status) {
|
||||
|
||||
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var e = document.getElementById("errorNewCorporate");
|
||||
|
||||
if (dt['status'] == 200)
|
||||
{
|
||||
var AnchorID = dt["Anchor"];
|
||||
var EntryID = dt["Entry"];
|
||||
var LinkingID = dt["Linking"];
|
||||
|
||||
var b = document.getElementById("newCorporateSave");
|
||||
b.style.display='none';
|
||||
var c = document.getElementById("newCorporateDismiss");
|
||||
c.style.display='none';
|
||||
document.getElementById("new_term").disabled = true;
|
||||
document.getElementById("new_type").disabled = true;
|
||||
document.getElementById("new_scopenote").disabled = true;
|
||||
$table.bootstrapTable('refresh');
|
||||
|
||||
e.classList.remove('alert-danger');
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = 'Neue Körperschaft aufgenommen';
|
||||
e.style.display = 'block';
|
||||
|
||||
// ID setzen für Relationen
|
||||
$('#ID').val(AnchorID);
|
||||
|
||||
// Relationen-Bereich anzeigen nach Speichern
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("newRelationCorporateDismiss").style.display = 'inline';
|
||||
showRelations(AnchorID,EntryID,LinkingID);
|
||||
} else {
|
||||
e.classList.remove('alert-success');
|
||||
e.classList.add('alert-danger');
|
||||
e.textContent = dt['message'];
|
||||
e.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Show relations of a corporate record
|
||||
function showRelations(Anchor,Entry,Linking)
|
||||
{
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:Anchor,
|
||||
authType:"Corporate"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
if (relationTable) {
|
||||
if (data && data.trim().length > 0) {
|
||||
relationTable.style.display = 'block';
|
||||
} else {
|
||||
relationTable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
$("#NewCorporateModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// create new relation for a corporate record
|
||||
function CreateNewRelation(AnchorID, relationID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!AnchorID || !relationID) {
|
||||
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationTypeElement = document.getElementById("new_relationtype");
|
||||
|
||||
if (!relationTypeElement) {
|
||||
showError('Relationstyp-Element nicht gefunden');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationType = relationTypeElement.value;
|
||||
|
||||
if (!relationType || relationType.trim() === '') {
|
||||
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "ajax/writeNewRelation.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
AnchorID: AnchorID,
|
||||
relationType: relationType,
|
||||
relationID: relationID
|
||||
},
|
||||
dataType: "text",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Success! Response:", response);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if (data.error) {
|
||||
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success === true) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||
|
||||
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||
'<code>' + response.substring(0, 200) + '</code>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||
if (xhr.responseText) {
|
||||
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||
}
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||
} else if (status === 'parsererror') {
|
||||
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
if (error) {
|
||||
errorMessage += '<br><small>' + error + '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function(xhr, status) {
|
||||
console.log("🏁 Request abgeschlossen:", status);
|
||||
if (status !== 'success') {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-danger';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-success';
|
||||
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(message) {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-warning';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-info';
|
||||
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOutAlert() {
|
||||
var errorElement = document.getElementById("errorNewCorporate");
|
||||
if (errorElement && errorElement.style.display !== 'none') {
|
||||
$(errorElement).fadeOut(500, function() {
|
||||
errorElement.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
var relationTextElement = document.getElementById("search_relation");
|
||||
if (relationTextElement) {
|
||||
relationTextElement.value = '';
|
||||
}
|
||||
$('#corporate-anlegen').removeData('record-id');
|
||||
$('#corporate-anlegen').removeData('relation-id');
|
||||
hideAlert();
|
||||
}
|
||||
|
||||
|
||||
// Delete relation for a corporate
|
||||
function DeleteRelation(AnchorID, LinkingID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteRelation.php", {
|
||||
AnchorID : AnchorID,
|
||||
LinkingID : LinkingID
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
var e = document.getElementById("errorNewCorporate");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Relation gelöscht';
|
||||
e.style.display = 'block';
|
||||
|
||||
showRelations(AnchorID,0,0)
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete the whole corporate entry
|
||||
function DeleteTerm(AnchorID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteTerm.php", {
|
||||
AnchorID: AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
|
||||
if (status === "success" && metadata.success === true) {
|
||||
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||
$table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||
$table.bootstrapTable('refresh');
|
||||
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Modify corporate and its relations
|
||||
function ModifyTerm(AnchorID)
|
||||
{
|
||||
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||
document.getElementById("new_term").disabled = true;
|
||||
|
||||
// Andere Felder aktivieren
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
$("#ID").val(AnchorID[0]);
|
||||
|
||||
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("relation_table").style.display = 'block';
|
||||
|
||||
// Buttons konfigurieren
|
||||
document.getElementById("newCorporateSave").style.display = 'none';
|
||||
document.getElementById("newCorporateDismiss").style.display = 'inline';
|
||||
document.getElementById("CorporateModifySave").style.display = 'inline';
|
||||
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
|
||||
|
||||
// Alert verstecken
|
||||
document.getElementById("errorNewCorporate").style.display = 'none';
|
||||
|
||||
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
|
||||
|
||||
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||
id : AnchorID[0],
|
||||
authType :"Corporate"
|
||||
},
|
||||
function (data, status) {
|
||||
console.log('📥 getAuthorityDataRaw status:', status);
|
||||
console.log('📥 getAuthorityDataRaw data:', data);
|
||||
if (status == "success")
|
||||
{
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
console.log('📥 metadata:', metadata);
|
||||
dt = (metadata['rows'][0]);
|
||||
console.log('📥 dt:', dt);
|
||||
if (dt) {
|
||||
$("#new_term").val(dt.Text);
|
||||
$("#new_id").val(dt.ID);
|
||||
$("#new_descriptor").val(dt.Descriptor);
|
||||
$("#new_type").val(dt.Type);
|
||||
$("#new_scopenote").val(dt.Scopenote);
|
||||
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||
} else {
|
||||
console.error('❌ Keine Daten in rows[0]');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
).fail(function(xhr, status, error) {
|
||||
console.error('❌ AJAX Fehler:', status, error);
|
||||
});
|
||||
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:AnchorID[0],
|
||||
authType: "Corporate"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
$("#NewCorporateModal").modal("show");
|
||||
|
||||
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||
setTimeout(function() {
|
||||
if (typeof searchRelation === 'function') {
|
||||
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||
searchRelation();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// rewrite records after updating a corporate entry
|
||||
function UpdateEntry(authType)
|
||||
{
|
||||
var AnchorID = $("#ID").val();
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = '';
|
||||
var classification = '';
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewCorporate");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Körperschaft eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewCorporate");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
|
||||
|
||||
$.post("ajax/UpdateTerm.php", {
|
||||
AnchorID: AnchorID,
|
||||
authType: "Corporate",
|
||||
term: term,
|
||||
type: type,
|
||||
detailtype: detailtype,
|
||||
classification: classification,
|
||||
scopenote: scopenote
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
document.getElementById("relation_table").style.display = 'block'
|
||||
document.getElementById("newCorporateSave").style.display = 'none'
|
||||
document.getElementById("newCorporateDismiss").style.display = 'inline'
|
||||
document.getElementById("CorporateModifySave").style.display = 'none'
|
||||
document.getElementById("newRelationCorporateDismiss").style.display = 'none'
|
||||
var e = document.getElementById("errorNewCorporate");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Körperschaft geändert ';
|
||||
e.style.display = 'block';
|
||||
|
||||
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
|
||||
$("#NewCorporateModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
$table.bootstrapTable('refresh')
|
||||
}
|
||||
|
||||
function ShowModalDetails(id) {
|
||||
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType : 'Corporate',
|
||||
offset : 0,
|
||||
id : id,
|
||||
sort : 'Anchor.Text',
|
||||
limit : 1
|
||||
},
|
||||
function(data, status) {
|
||||
if (status == "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (!row || row.length === 0) {
|
||||
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||
return;
|
||||
}
|
||||
|
||||
//Assign existing values to the modal popup fields
|
||||
$("#display_term").val(row[0].Text);
|
||||
$("#display_id").val(row[0].ID);
|
||||
$("#display_id_show").val(row[0].ID);
|
||||
$("#display_descriptor").val(row[0].Descriptor);
|
||||
$("#display_type").val(row[0].Type);
|
||||
$("#display_scopenote").val(row[0].Scopenote);
|
||||
|
||||
var html = []
|
||||
var txt = ''
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
txt = txt +'<div class="col-sm-12">';
|
||||
txt = txt +'<div class="col-sm-2"></div>';
|
||||
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||
txt = txt +'</div>';
|
||||
})
|
||||
$(".modal_content").html(txt);
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||
})
|
||||
$("#display_relations").val(txt);
|
||||
|
||||
// Modal anzeigen
|
||||
$("#DetailsModal").modal("show");
|
||||
|
||||
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||
setTimeout(function() {
|
||||
if (typeof createSemanticNetwork === 'function') {
|
||||
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshPage()
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function CopyLink(value)
|
||||
{
|
||||
var tempInput = document.createElement("input");
|
||||
tempInput.value = value;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
629
js/PersonScript.js
Normal file
629
js/PersonScript.js
Normal file
@ -0,0 +1,629 @@
|
||||
/**
|
||||
* Created by Roland on 2021/07/01.
|
||||
* Updated for BIBB Layout 2024
|
||||
*/
|
||||
// PERSONS
|
||||
|
||||
// Show blank screen to add new Person
|
||||
function newPersonShow() {
|
||||
|
||||
var b = document.getElementById("newSubjectSave");
|
||||
b.style.display='inline';
|
||||
var c = document.getElementById("newSubjectDismiss");
|
||||
c.style.display='inline';
|
||||
document.getElementById("SubjectModifySave").style.display = 'none';
|
||||
document.getElementById("new_term").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
// Relationen-Bereich verstecken bei neuer Person
|
||||
document.getElementById("new_relation_label").style.display = 'none';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||
showRelations(0,0,0);
|
||||
var a = document.getElementById("relation_table");
|
||||
if (a != undefined) {
|
||||
document.getElementById("relation_table").style.display = 'none';
|
||||
}
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.style.display = 'none';
|
||||
|
||||
$("#new_term").val("");
|
||||
$("#new_scopenote").val("");
|
||||
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
|
||||
// Insert new person record
|
||||
function CreateNewEntry(type) {
|
||||
// get values
|
||||
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var detailtype = '';
|
||||
var classification = '';
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Name eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
$.post("ajax/newPerson.php", {
|
||||
term : term,
|
||||
type : type,
|
||||
detailtype : detailtype,
|
||||
classification: classification,
|
||||
scopenote : scopenote
|
||||
}, function (data, status) {
|
||||
|
||||
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
|
||||
if (dt['status'] == 200)
|
||||
{
|
||||
var AnchorID = dt["Anchor"];
|
||||
var EntryID = dt["Entry"];
|
||||
var LinkingID = dt["Linking"];
|
||||
|
||||
var b = document.getElementById("newSubjectSave");
|
||||
b.style.display='none';
|
||||
var c = document.getElementById("newSubjectDismiss");
|
||||
c.style.display='none';
|
||||
document.getElementById("new_term").disabled = true;
|
||||
document.getElementById("new_scopenote").disabled = true;
|
||||
$table.bootstrapTable('refresh');
|
||||
|
||||
e.classList.remove('alert-danger');
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = 'Neuen Namen aufgenommen';
|
||||
e.style.display = 'block';
|
||||
|
||||
// ID setzen für Relationen
|
||||
$('#ID').val(AnchorID);
|
||||
|
||||
// Relationen-Bereich anzeigen nach Speichern
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
|
||||
showRelations(AnchorID,EntryID,LinkingID);
|
||||
} else {
|
||||
e.classList.remove('alert-success');
|
||||
e.classList.add('alert-danger');
|
||||
e.textContent = dt['message'];
|
||||
e.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Show relations of a person record
|
||||
function showRelations(Anchor,Entry,Linking)
|
||||
{
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:Anchor,
|
||||
authType:"Person"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
if (relationTable) {
|
||||
if (data && data.trim().length > 0) {
|
||||
relationTable.style.display = 'block';
|
||||
} else {
|
||||
relationTable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// create new relation for a person record
|
||||
function CreateNewRelation(AnchorID, relationID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!AnchorID || !relationID) {
|
||||
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationTypeElement = document.getElementById("new_relationtype");
|
||||
|
||||
if (!relationTypeElement) {
|
||||
showError('Relationstyp-Element nicht gefunden');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationType = relationTypeElement.value;
|
||||
|
||||
if (!relationType || relationType.trim() === '') {
|
||||
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "ajax/writeNewRelation.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
AnchorID: AnchorID,
|
||||
relationType: relationType,
|
||||
relationID: relationID
|
||||
},
|
||||
dataType: "text",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Success! Response:", response);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if (data.error) {
|
||||
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success === true) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||
|
||||
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||
'<code>' + response.substring(0, 200) + '</code>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||
if (xhr.responseText) {
|
||||
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||
}
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||
} else if (status === 'parsererror') {
|
||||
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
if (error) {
|
||||
errorMessage += '<br><small>' + error + '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function(xhr, status) {
|
||||
console.log("🏁 Request abgeschlossen:", status);
|
||||
if (status !== 'success') {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-danger';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-success';
|
||||
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-warning';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-info';
|
||||
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOutAlert() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement && errorElement.style.display !== 'none') {
|
||||
$(errorElement).fadeOut(500, function() {
|
||||
errorElement.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
var relationTextElement = document.getElementById("search_relation");
|
||||
if (relationTextElement) {
|
||||
relationTextElement.value = '';
|
||||
}
|
||||
$('#subject-anlegen').removeData('record-id');
|
||||
$('#subject-anlegen').removeData('relation-id');
|
||||
hideAlert();
|
||||
}
|
||||
|
||||
|
||||
// Delete relation for a person
|
||||
function DeleteRelation(AnchorID, LinkingID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteRelation.php", {
|
||||
AnchorID : AnchorID,
|
||||
LinkingID : LinkingID
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Relation gelöscht';
|
||||
e.style.display = 'block';
|
||||
|
||||
showRelations(AnchorID,0,0)
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete the whole person entry
|
||||
function DeleteTerm(AnchorID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteTerm.php", {
|
||||
AnchorID: AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
|
||||
if (status === "success" && metadata.success === true) {
|
||||
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||
$table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||
$table.bootstrapTable('refresh');
|
||||
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Modify person and its relations
|
||||
function ModifyPerson(AnchorID)
|
||||
{
|
||||
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||
document.getElementById("new_term").disabled = true;
|
||||
|
||||
// Andere Felder aktivieren
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
$("#ID").val(AnchorID[0]);
|
||||
|
||||
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("relation_table").style.display = 'block';
|
||||
|
||||
// Buttons konfigurieren
|
||||
document.getElementById("newSubjectSave").style.display = 'none';
|
||||
document.getElementById("newSubjectDismiss").style.display = 'inline';
|
||||
document.getElementById("SubjectModifySave").style.display = 'inline';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||
|
||||
// Alert verstecken
|
||||
document.getElementById("errorNewSubject").style.display = 'none';
|
||||
|
||||
$("#NewSubjectModalHeadline").text("Person ändern");
|
||||
|
||||
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||
id : AnchorID[0],
|
||||
authType :"Person"
|
||||
},
|
||||
function (data, status) {
|
||||
console.log('📥 getAuthorityDataRaw status:', status);
|
||||
console.log('📥 getAuthorityDataRaw data:', data);
|
||||
if (status == "success")
|
||||
{
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
console.log('📥 metadata:', metadata);
|
||||
dt = (metadata['rows'][0]);
|
||||
console.log('📥 dt:', dt);
|
||||
if (dt) {
|
||||
$("#new_term").val(dt.Text);
|
||||
$("#new_id").val(dt.ID);
|
||||
$("#new_descriptor").val(dt.Descriptor);
|
||||
$("#new_scopenote").val(dt.Scopenote);
|
||||
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||
} else {
|
||||
console.error('❌ Keine Daten in rows[0]');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
).fail(function(xhr, status, error) {
|
||||
console.error('❌ AJAX Fehler:', status, error);
|
||||
});
|
||||
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:AnchorID[0],
|
||||
authType: "Person"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
$("#NewSubjectModal").modal("show");
|
||||
|
||||
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||
setTimeout(function() {
|
||||
if (typeof searchRelation === 'function') {
|
||||
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||
searchRelation();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// rewrite records after updating a person entry
|
||||
function UpdateEntry(authType)
|
||||
{
|
||||
var AnchorID = $("#ID").val();
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = 'Person';
|
||||
type = type.trim();
|
||||
var detailtype = '';
|
||||
detailtype = detailtype.trim();
|
||||
var classification = ''
|
||||
classification = classification.trim();
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Name eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
|
||||
|
||||
$.post("ajax/UpdateTerm.php", {
|
||||
AnchorID: AnchorID,
|
||||
authType: "Person",
|
||||
term: term,
|
||||
type: type,
|
||||
detailtype: detailtype,
|
||||
classification: classification,
|
||||
scopenote: scopenote
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
document.getElementById("relation_table").style.display = 'block'
|
||||
document.getElementById("newSubjectSave").style.display = 'none'
|
||||
document.getElementById("newSubjectDismiss").style.display = 'inline'
|
||||
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Personenname geändert ';
|
||||
e.style.display = 'block';
|
||||
|
||||
$("#NewSubjectModalHeadline").text("Name ändern");
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
$table.bootstrapTable('refresh')
|
||||
}
|
||||
|
||||
function ShowModalDetails(id) {
|
||||
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType : 'Person',
|
||||
offset : 0,
|
||||
id : id,
|
||||
sort : 'Anchor.Text',
|
||||
limit : 1
|
||||
},
|
||||
function(data, status) {
|
||||
if (status == "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (!row || row.length === 0) {
|
||||
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||
return;
|
||||
}
|
||||
|
||||
//Assign existing values to the modal popup fields
|
||||
$("#display_term").val(row[0].Text);
|
||||
$("#display_id").val(row[0].ID);
|
||||
$("#display_id_show").val(row[0].ID);
|
||||
$("#display_descriptor").val(row[0].Descriptor);
|
||||
$("#display_type").val(row[0].Type);
|
||||
$("#display_scopenote").val(row[0].Scopenote);
|
||||
|
||||
var html = []
|
||||
var txt = ''
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
txt = txt +'<div class="col-sm-12">';
|
||||
txt = txt +'<div class="col-sm-2"></div>';
|
||||
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||
txt = txt +'</div>';
|
||||
})
|
||||
$(".modal_content").html(txt);
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||
})
|
||||
$("#display_relations").val(txt);
|
||||
|
||||
// Modal anzeigen
|
||||
$("#DetailsModal").modal("show");
|
||||
|
||||
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||
setTimeout(function() {
|
||||
if (typeof createSemanticNetwork === 'function') {
|
||||
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshPage()
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function CopyLink(value)
|
||||
{
|
||||
var tempInput = document.createElement("input");
|
||||
tempInput.value = value;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
634
js/PublisherScript.js
Normal file
634
js/PublisherScript.js
Normal file
@ -0,0 +1,634 @@
|
||||
/**
|
||||
* Created by Roland on 2021/07/01.
|
||||
* Updated for BIBB Layout 2024
|
||||
*/
|
||||
// PUBLISHERS
|
||||
|
||||
// Show blank screen to add new Publisher
|
||||
function newPublishersShow() {
|
||||
|
||||
var b = document.getElementById("newPublisherSave");
|
||||
b.style.display='inline';
|
||||
var c = document.getElementById("newPublisherDismiss");
|
||||
c.style.display='inline';
|
||||
document.getElementById("PublisherModifySave").style.display = 'none';
|
||||
document.getElementById("new_term").disabled = false;
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
// Relationen-Bereich verstecken bei neuem Verlag
|
||||
document.getElementById("new_relation_label").style.display = 'none';
|
||||
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
|
||||
showRelations(0,0,0);
|
||||
var a = document.getElementById("relation_table");
|
||||
if (a != undefined) {
|
||||
document.getElementById("relation_table").style.display = 'none';
|
||||
}
|
||||
var e = document.getElementById("errorNewPublisher");
|
||||
e.style.display = 'none';
|
||||
|
||||
$("#new_term").val("");
|
||||
$("#new_scopenote").val("");
|
||||
|
||||
$("#NewPublisherModal").modal("show");
|
||||
}
|
||||
|
||||
// Insert new publisher record
|
||||
function CreateNewEntry(type) {
|
||||
// get values
|
||||
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = "";
|
||||
var classification = "";
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewPublisher");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Verlag eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewPublisher");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
$.post("ajax/newPublisher.php", {
|
||||
term : term,
|
||||
type : type,
|
||||
detailtype : detailtype,
|
||||
classification: classification,
|
||||
scopenote : scopenote
|
||||
}, function (data, status) {
|
||||
|
||||
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var e = document.getElementById("errorNewPublisher");
|
||||
|
||||
if (dt['status'] == 200)
|
||||
{
|
||||
var AnchorID = dt["Anchor"];
|
||||
var EntryID = dt["Entry"];
|
||||
var LinkingID = dt["Linking"];
|
||||
|
||||
var b = document.getElementById("newPublisherSave");
|
||||
b.style.display='none';
|
||||
var c = document.getElementById("newPublisherDismiss");
|
||||
c.style.display='none';
|
||||
document.getElementById("new_term").disabled = true;
|
||||
document.getElementById("new_type").disabled = true;
|
||||
document.getElementById("new_scopenote").disabled = true;
|
||||
$table.bootstrapTable('refresh');
|
||||
|
||||
e.classList.remove('alert-danger');
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = 'Neuen Verlag aufgenommen';
|
||||
e.style.display = 'block';
|
||||
|
||||
// ID setzen für Relationen
|
||||
$('#ID').val(AnchorID);
|
||||
|
||||
// Relationen-Bereich anzeigen nach Speichern
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("newRelationPublisherDismiss").style.display = 'inline';
|
||||
showRelations(AnchorID,EntryID,LinkingID);
|
||||
} else {
|
||||
e.classList.remove('alert-success');
|
||||
e.classList.add('alert-danger');
|
||||
e.textContent = dt['message'];
|
||||
e.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Show relations of a publisher record
|
||||
function showRelations(Anchor,Entry,Linking)
|
||||
{
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:Anchor,
|
||||
authType:"Publisher"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
if (relationTable) {
|
||||
if (data && data.trim().length > 0) {
|
||||
relationTable.style.display = 'block';
|
||||
} else {
|
||||
relationTable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
$("#NewPublisherModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// create new relation for a publisher record
|
||||
function CreateNewRelation(AnchorID, relationID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!AnchorID || !relationID) {
|
||||
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationTypeElement = document.getElementById("new_relationtype");
|
||||
|
||||
if (!relationTypeElement) {
|
||||
showError('Relationstyp-Element nicht gefunden');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationType = relationTypeElement.value;
|
||||
|
||||
if (!relationType || relationType.trim() === '') {
|
||||
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "ajax/writeNewRelation.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
AnchorID: AnchorID,
|
||||
relationType: relationType,
|
||||
relationID: relationID
|
||||
},
|
||||
dataType: "text",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Success! Response:", response);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if (data.error) {
|
||||
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success === true) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||
|
||||
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||
'<code>' + response.substring(0, 200) + '</code>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||
if (xhr.responseText) {
|
||||
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||
}
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||
} else if (status === 'parsererror') {
|
||||
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
if (error) {
|
||||
errorMessage += '<br><small>' + error + '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function(xhr, status) {
|
||||
console.log("🏁 Request abgeschlossen:", status);
|
||||
if (status !== 'success') {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-danger';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-success';
|
||||
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(message) {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-warning';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-info';
|
||||
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOutAlert() {
|
||||
var errorElement = document.getElementById("errorNewPublisher");
|
||||
if (errorElement && errorElement.style.display !== 'none') {
|
||||
$(errorElement).fadeOut(500, function() {
|
||||
errorElement.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
var relationTextElement = document.getElementById("search_relation");
|
||||
if (relationTextElement) {
|
||||
relationTextElement.value = '';
|
||||
}
|
||||
$('#publisher-anlegen').removeData('record-id');
|
||||
$('#publisher-anlegen').removeData('relation-id');
|
||||
hideAlert();
|
||||
}
|
||||
|
||||
|
||||
// Delete relation for a publisher
|
||||
function DeleteRelation(AnchorID, LinkingID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteRelation.php", {
|
||||
AnchorID : AnchorID,
|
||||
LinkingID : LinkingID
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
var e = document.getElementById("errorNewPublisher");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Relation gelöscht';
|
||||
e.style.display = 'block';
|
||||
|
||||
showRelations(AnchorID,0,0)
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete the whole publisher entry
|
||||
function DeleteTerm(AnchorID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteTerm.php", {
|
||||
AnchorID: AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
|
||||
if (status === "success" && metadata.success === true) {
|
||||
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||
$table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||
$table.bootstrapTable('refresh');
|
||||
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Modify publisher and its relations
|
||||
function ModifyTerm(AnchorID)
|
||||
{
|
||||
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||
document.getElementById("new_term").disabled = true;
|
||||
|
||||
// Andere Felder aktivieren
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
$("#ID").val(AnchorID[0]);
|
||||
|
||||
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("relation_table").style.display = 'block';
|
||||
|
||||
// Buttons konfigurieren
|
||||
document.getElementById("newPublisherSave").style.display = 'none';
|
||||
document.getElementById("newPublisherDismiss").style.display = 'inline';
|
||||
document.getElementById("PublisherModifySave").style.display = 'inline';
|
||||
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
|
||||
|
||||
// Alert verstecken
|
||||
document.getElementById("errorNewPublisher").style.display = 'none';
|
||||
|
||||
$("#NewPublisherModalHeadline").text("Verlag ändern");
|
||||
|
||||
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||
id : AnchorID[0],
|
||||
authType :"Publisher"
|
||||
},
|
||||
function (data, status) {
|
||||
console.log('📥 getAuthorityDataRaw status:', status);
|
||||
console.log('📥 getAuthorityDataRaw data:', data);
|
||||
if (status == "success")
|
||||
{
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
console.log('📥 metadata:', metadata);
|
||||
dt = (metadata['rows'][0]);
|
||||
console.log('📥 dt:', dt);
|
||||
if (dt) {
|
||||
$("#new_term").val(dt.Text);
|
||||
$("#new_id").val(dt.ID);
|
||||
$("#new_descriptor").val(dt.Descriptor);
|
||||
$("#new_type").val(dt.Type);
|
||||
$("#new_scopenote").val(dt.Scopenote);
|
||||
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||
} else {
|
||||
console.error('❌ Keine Daten in rows[0]');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
).fail(function(xhr, status, error) {
|
||||
console.error('❌ AJAX Fehler:', status, error);
|
||||
});
|
||||
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:AnchorID[0],
|
||||
authType: "Publisher"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
$("#NewPublisherModal").modal("show");
|
||||
|
||||
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||
setTimeout(function() {
|
||||
if (typeof searchRelation === 'function') {
|
||||
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||
searchRelation();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// rewrite records after updating a publisher entry
|
||||
function UpdateEntry(authType)
|
||||
{
|
||||
var AnchorID = $("#ID").val();
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = '';
|
||||
var classification = '';
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewPublisher");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Verlag eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewPublisher");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
|
||||
|
||||
$.post("ajax/UpdateTerm.php", {
|
||||
AnchorID: AnchorID,
|
||||
authType: "Publisher",
|
||||
term: term,
|
||||
type: type,
|
||||
detailtype: detailtype,
|
||||
classification: classification,
|
||||
scopenote: scopenote
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
document.getElementById("relation_table").style.display = 'block'
|
||||
document.getElementById("newPublisherSave").style.display = 'none'
|
||||
document.getElementById("newPublisherDismiss").style.display = 'inline'
|
||||
document.getElementById("PublisherModifySave").style.display = 'none'
|
||||
document.getElementById("newRelationPublisherDismiss").style.display = 'none'
|
||||
var e = document.getElementById("errorNewPublisher");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Verlag geändert ';
|
||||
e.style.display = 'block';
|
||||
|
||||
$("#NewPublisherModalHeadline").text("Verlag ändern");
|
||||
$("#NewPublisherModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
$table.bootstrapTable('refresh')
|
||||
}
|
||||
|
||||
function ShowModalDetails(id) {
|
||||
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType : 'Publisher',
|
||||
offset : 0,
|
||||
id : id,
|
||||
sort : 'Anchor.Text',
|
||||
limit : 1
|
||||
},
|
||||
function(data, status) {
|
||||
if (status == "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (!row || row.length === 0) {
|
||||
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||
return;
|
||||
}
|
||||
|
||||
//Assign existing values to the modal popup fields
|
||||
$("#display_term").val(row[0].Text);
|
||||
$("#display_id").val(row[0].ID);
|
||||
$("#display_id_show").val(row[0].ID);
|
||||
$("#display_descriptor").val(row[0].Descriptor);
|
||||
$("#display_type").val(row[0].Type);
|
||||
$("#display_scopenote").val(row[0].Scopenote);
|
||||
|
||||
var html = []
|
||||
var txt = ''
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
txt = txt +'<div class="col-sm-12">';
|
||||
txt = txt +'<div class="col-sm-2"></div>';
|
||||
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||
txt = txt +'</div>';
|
||||
})
|
||||
$(".modal_content").html(txt);
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||
})
|
||||
$("#display_relations").val(txt);
|
||||
|
||||
// Modal anzeigen
|
||||
$("#DetailsModal").modal("show");
|
||||
|
||||
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||
setTimeout(function() {
|
||||
if (typeof createSemanticNetwork === 'function') {
|
||||
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshPage()
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function CopyLink(value)
|
||||
{
|
||||
var tempInput = document.createElement("input");
|
||||
var words = value.split('(');
|
||||
tempInput.value = words[0];
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
882
js/SubjectScript.js
Normal file
882
js/SubjectScript.js
Normal file
@ -0,0 +1,882 @@
|
||||
/**
|
||||
* Created by Roland on 2021/07/01.
|
||||
*/
|
||||
// SUBJECTS
|
||||
|
||||
// Show blank screen to add new Subject
|
||||
function newSubjectShow() {
|
||||
|
||||
var b = document.getElementById("newSubjectSave");
|
||||
b.style.display='inline';
|
||||
var c = document.getElementById("newSubjectDismiss");
|
||||
c.style.display='inline';
|
||||
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||
document.getElementById("new_term").disabled = false;
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_detailtype").disabled = false;
|
||||
document.getElementById("new_classification").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
// Relationen-Bereich verstecken bei neuem Schlagwort
|
||||
document.getElementById("new_relation_label").style.display = 'none';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||
|
||||
// Synonyme-Bereich verstecken bei neuem Schlagwort
|
||||
var synSection = document.getElementById("synonyms_section");
|
||||
if (synSection) {
|
||||
synSection.style.display = 'none';
|
||||
var tbody = document.getElementById("synonyms_body");
|
||||
if (tbody) tbody.innerHTML = '';
|
||||
}
|
||||
|
||||
showRelations(0,0,0);
|
||||
var a = document.getElementById("relation_table");
|
||||
if (a != undefined) {
|
||||
document.getElementById("relation_table").style.display = 'none';
|
||||
}
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.style.display = 'none';
|
||||
|
||||
$("#new_term").val("");
|
||||
$("#new_classification").val("");
|
||||
$("#new_scopenote").val("");
|
||||
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
|
||||
// Insert new subject record
|
||||
function CreateNewEntry(type) {
|
||||
// get values
|
||||
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = $("#new_detailtype").val();
|
||||
detailtype = detailtype.trim();
|
||||
var classification = $("#new_classification").val();
|
||||
classification = classification.trim();
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Schlagwort eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
$.post("ajax/newSubject.php", {
|
||||
term : term,
|
||||
type : type,
|
||||
detailtype : detailtype,
|
||||
classification: classification,
|
||||
scopenote : scopenote
|
||||
}, function (data, status) {
|
||||
|
||||
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
|
||||
if (dt['status'] == 200)
|
||||
{
|
||||
var AnchorID = dt["Anchor"];
|
||||
var EntryID = dt["Entry"];
|
||||
var LinkingID = dt["Linking"];
|
||||
|
||||
// alert ("ANCHOR: " +AnchorID);
|
||||
var b = document.getElementById("newSubjectSave");
|
||||
b.style.display='none';
|
||||
var c = document.getElementById("newSubjectDismiss");
|
||||
c.style.display='none';
|
||||
document.getElementById("new_term").disabled = true;
|
||||
document.getElementById("new_type").disabled = true;
|
||||
document.getElementById("new_detailtype").disabled = true;
|
||||
document.getElementById("new_classification").disabled = true;
|
||||
document.getElementById("new_scopenote").disabled = true;
|
||||
$table.bootstrapTable('refresh');
|
||||
|
||||
e.classList.remove('alert-danger');
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = 'Neues Schlagwort aufgenommen';
|
||||
e.style.display = 'block';
|
||||
|
||||
// ID setzen für Relationen
|
||||
$('#ID').val(AnchorID);
|
||||
|
||||
// Relationen-Bereich anzeigen nach Speichern
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
|
||||
showRelations(AnchorID,EntryID,LinkingID);
|
||||
} else {
|
||||
e.classList.remove('alert-success');
|
||||
e.classList.add('alert-danger');
|
||||
e.textContent = dt['message'];
|
||||
e.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Show relations of a subect record
|
||||
function showRelations(Anchor,Entry,Linking)
|
||||
{
|
||||
// alert ("showRelations AnchorID : " + Anchor);
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:Anchor,
|
||||
authType: "Subject"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||
var relationTable = document.getElementById("relation_table");
|
||||
if (relationTable) {
|
||||
if (data && data.trim().length > 0) {
|
||||
relationTable.style.display = 'block';
|
||||
} else {
|
||||
relationTable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// create new relation for a subject record
|
||||
|
||||
function CreateNewRelation(AnchorID, relationID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!AnchorID || !relationID) {
|
||||
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationTypeElement = document.getElementById("new_relationtype");
|
||||
|
||||
if (!relationTypeElement) {
|
||||
showError('Relationstyp-Element nicht gefunden');
|
||||
return false;
|
||||
}
|
||||
|
||||
var relationType = relationTypeElement.value;
|
||||
|
||||
if (!relationType || relationType.trim() === '') {
|
||||
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "ajax/writeNewRelation.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
AnchorID: AnchorID,
|
||||
relationType: relationType,
|
||||
relationID: relationID
|
||||
},
|
||||
dataType: "text",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Success! Response:", response);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if (data.error) {
|
||||
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success === true) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||
|
||||
// Relations neu laden nach kurzer Verzögerung
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||
|
||||
// Fallback für alte API (nur ID zurück)
|
||||
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||
|
||||
if (typeof showRelations === 'function') {
|
||||
setTimeout(function() {
|
||||
showRelations(AnchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||
'<code>' + response.substring(0, 200) + '</code>');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||
if (xhr.responseText) {
|
||||
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||
}
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||
} else if (status === 'parsererror') {
|
||||
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
if (error) {
|
||||
errorMessage += '<br><small>' + error + '</small>';
|
||||
}
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function(xhr, status) {
|
||||
console.log("🏁 Request abgeschlossen:", status);
|
||||
// Loading nur ausblenden wenn kein Erfolg (Success zeigt eigene Meldung)
|
||||
if (status !== 'success') {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Neue Funktion für Synonyme
|
||||
function CreateNewSynonym(text1, text2, anchorID, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
console.log('🔗 CreateNewSynonym:', { text1, text2 });
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!text1 || !text2) {
|
||||
showError('Fehlende Parameter: text1 oder text2');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text1.trim() === text2.trim()) {
|
||||
showError('Ein Begriff kann nicht sein eigenes Synonym sein');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading anzeigen
|
||||
showLoading();
|
||||
|
||||
$.ajax({
|
||||
url: "/Thesaurus/ajax/saveSynonym.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
text1: text1,
|
||||
text2: text2,
|
||||
action: 'add'
|
||||
},
|
||||
dataType: "json",
|
||||
timeout: 10000,
|
||||
|
||||
success: function(response) {
|
||||
console.log("✅ Synonym Success! Response:", response);
|
||||
|
||||
if (response.error) {
|
||||
showError('<strong>Fehler:</strong><br>' + response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.success === true) {
|
||||
showSuccess('Synonym erfolgreich hinzugefügt!');
|
||||
|
||||
// Synonyme neu laden
|
||||
var termText = $('#new_term').val();
|
||||
if (termText) {
|
||||
loadSynonyms(termText);
|
||||
}
|
||||
|
||||
// Relations neu laden nach kurzer Verzögerung
|
||||
if (typeof showRelations === 'function' && anchorID) {
|
||||
setTimeout(function() {
|
||||
showRelations(anchorID, 0, 0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
resetForm();
|
||||
} else {
|
||||
showError('Unerwartete Antwort vom Server');
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||
|
||||
var errorMessage = '<strong>Fehler beim Speichern des Synonyms</strong><br><br>';
|
||||
|
||||
if (xhr.status === 0) {
|
||||
errorMessage += '❌ Keine Verbindung zum Server möglich';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||
errorMessage += '<small>ajax/saveSynonym.php existiert nicht</small>';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage += '❌ Interner Serverfehler (500)';
|
||||
} else {
|
||||
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
|
||||
complete: function() {
|
||||
hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
console.error("💥 Exception:", e);
|
||||
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Synonyme für einen Begriff laden
|
||||
function loadSynonyms(text) {
|
||||
if (!text) return;
|
||||
|
||||
console.log('📋 Lade Synonyme für:', text);
|
||||
|
||||
$.ajax({
|
||||
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||
type: "GET",
|
||||
data: { text: text },
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
console.log('✅ Synonyme geladen:', response);
|
||||
|
||||
var section = document.getElementById('synonyms_section');
|
||||
var tbody = document.getElementById('synonyms_body');
|
||||
var noSyn = document.getElementById('no_synonyms');
|
||||
|
||||
if (response.success && response.count > 0) {
|
||||
// Synonyme vorhanden - Bereich anzeigen
|
||||
if (section) section.style.display = 'block';
|
||||
if (tbody) tbody.innerHTML = response.html;
|
||||
if (noSyn) noSyn.style.display = 'none';
|
||||
} else {
|
||||
// Keine Synonyme - Bereich komplett ausblenden
|
||||
if (section) section.style.display = 'none';
|
||||
if (tbody) tbody.innerHTML = '';
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('❌ Fehler beim Laden der Synonyme:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Synonym löschen
|
||||
function DeleteSynonym(text1, text2) {
|
||||
if (!confirm('Möchten Sie das Synonym "' + text2 + '" wirklich entfernen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🗑️ Lösche Synonym:', { text1, text2 });
|
||||
|
||||
$.ajax({
|
||||
url: "/Thesaurus/ajax/saveSynonym.php",
|
||||
type: "POST",
|
||||
data: {
|
||||
text1: text1,
|
||||
text2: text2,
|
||||
action: 'remove'
|
||||
},
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
console.log('✅ Synonym gelöscht:', response);
|
||||
|
||||
if (response.success) {
|
||||
showSuccess('Synonym entfernt!');
|
||||
// Synonyme neu laden
|
||||
loadSynonyms(text1);
|
||||
} else {
|
||||
showError(response.error || 'Fehler beim Löschen');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('❌ Fehler beim Löschen:', error);
|
||||
showError('Fehler beim Löschen des Synonyms');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-danger';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
// Scroll zum Alert
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
// Optional: Auto-Hide nach 8 Sekunden
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-success';
|
||||
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
// Scroll zum Alert
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
// Auto-Hide nach 3 Sekunden
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showWarning(message) {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-warning';
|
||||
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
// Scroll zum Alert
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
// Auto-Hide nach 5 Sekunden
|
||||
setTimeout(function() {
|
||||
fadeOutAlert();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.className = 'alert alert-info';
|
||||
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
// Scroll zum Alert
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOutAlert() {
|
||||
var errorElement = document.getElementById("errorNewSubject");
|
||||
if (errorElement && errorElement.style.display !== 'none') {
|
||||
$(errorElement).fadeOut(500, function() {
|
||||
errorElement.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
var relationTextElement = document.getElementById("search_relation");
|
||||
if (relationTextElement) {
|
||||
relationTextElement.value = '';
|
||||
}
|
||||
// Data-Attribute zurücksetzen
|
||||
$('#subject-anlegen').removeData('record-id');
|
||||
$('#subject-anlegen').removeData('record-text');
|
||||
$('#subject-anlegen').removeData('relation-id');
|
||||
$('#subject-anlegen').removeData('relation-text');
|
||||
hideAlert();
|
||||
}
|
||||
|
||||
|
||||
// Delete relation for a subjects
|
||||
function DeleteRelation(AnchorID, LinkingID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteRelation.php", {
|
||||
AnchorID : AnchorID,
|
||||
LinkingID : LinkingID
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Relation gelöscht';
|
||||
e.style.display = 'block';
|
||||
|
||||
showRelations(AnchorID,0,0)
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete the whole subject entry
|
||||
|
||||
function DeleteTerm(AnchorID)
|
||||
{
|
||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post("ajax/deleteTerm.php", {
|
||||
AnchorID: AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
|
||||
if (status === "success" && metadata.success === true) {
|
||||
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||
$table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||
$table.bootstrapTable('refresh');
|
||||
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Modify subject and its relations
|
||||
function ModifyTerm(AnchorID)
|
||||
{
|
||||
// Schlagwort-Feld deaktivieren (darf nicht geändert werden)
|
||||
document.getElementById("new_term").disabled = true;
|
||||
|
||||
// Andere Felder aktivieren
|
||||
document.getElementById("new_type").disabled = false;
|
||||
document.getElementById("new_detailtype").disabled = false;
|
||||
document.getElementById("new_classification").disabled = false;
|
||||
document.getElementById("new_scopenote").disabled = false;
|
||||
|
||||
$("#ID").val(AnchorID[0]);
|
||||
|
||||
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||
document.getElementById("new_relation_label").style.display = 'block';
|
||||
document.getElementById("relation_table").style.display = 'block';
|
||||
|
||||
// Synonyme-Bereich sichtbar machen
|
||||
var synSection = document.getElementById("synonyms_section");
|
||||
if (synSection) synSection.style.display = 'block';
|
||||
|
||||
// Buttons konfigurieren
|
||||
document.getElementById("newSubjectSave").style.display = 'none';
|
||||
document.getElementById("newSubjectDismiss").style.display = 'inline';
|
||||
document.getElementById("SubjectModifySave").style.display = 'inline';
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||
|
||||
// Alert verstecken
|
||||
document.getElementById("errorNewSubject").style.display = 'none';
|
||||
|
||||
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
|
||||
|
||||
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||
id : AnchorID[0]
|
||||
},
|
||||
function (data, status) {
|
||||
console.log('📥 getAuthorityDataRaw status:', status);
|
||||
console.log('📥 getAuthorityDataRaw data:', data);
|
||||
if (status == "success")
|
||||
{
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
console.log('📥 metadata:', metadata);
|
||||
dt = (metadata['rows'][0]);
|
||||
console.log('📥 dt:', dt);
|
||||
if (dt) {
|
||||
$("#new_term").val(dt.Text);
|
||||
$("#new_id").val(dt.ID);
|
||||
$("#new_detailtype").val(dt.DetailType);
|
||||
$("#new_classification").val(dt.Classification);
|
||||
$("#new_descriptor").val(dt.Descriptor);
|
||||
$("#new_type").val(dt.Type);
|
||||
$("#new_scopenote").val(dt.Scopenote);
|
||||
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||
|
||||
// Synonyme laden
|
||||
if (typeof loadSynonyms === 'function') {
|
||||
loadSynonyms(dt.Text);
|
||||
}
|
||||
} else {
|
||||
console.error('❌ Keine Daten in rows[0]');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
).fail(function(xhr, status, error) {
|
||||
console.error('❌ AJAX Fehler:', status, error);
|
||||
});
|
||||
|
||||
$.get("ajax/getRelations.php", {
|
||||
anchorID:AnchorID[0],
|
||||
authType: "Subject"
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
$(".new_modal_content").html(data);
|
||||
$("#NewSubjectModal").modal("show");
|
||||
|
||||
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||
setTimeout(function() {
|
||||
if (typeof searchRelation === 'function') {
|
||||
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||
searchRelation();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// rewrite records after updating a subject entry
|
||||
function UpdateEntry(authType)
|
||||
{
|
||||
var AnchorID = $("#ID").val();
|
||||
var term = $("#new_term").val();
|
||||
term = term.trim();
|
||||
var type = $("#new_type").val();
|
||||
type = type.trim();
|
||||
var detailtype = $("#new_detailtype").val();
|
||||
detailtype = detailtype.trim();
|
||||
var classification = $("#new_classification").val();
|
||||
classification = classification.trim();
|
||||
var scopenote = $("#new_scopenote").val();
|
||||
scopenote = scopenote.trim();
|
||||
|
||||
if (term.length == 0) {
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.add('alert-danger');
|
||||
a.textContent = 'Bitte Schlagwort eingeben!';
|
||||
a.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var a = document.getElementById("errorNewSubject");
|
||||
a.classList.remove('alert-danger');
|
||||
a.textContent = '';
|
||||
a.style.display = 'none';
|
||||
|
||||
|
||||
|
||||
$.post("ajax/UpdateTerm.php", {
|
||||
AnchorID: AnchorID,
|
||||
authType: authType,
|
||||
term: term,
|
||||
type: type,
|
||||
detailtype: detailtype,
|
||||
classification: classification,
|
||||
scopenote: scopenote
|
||||
},
|
||||
function (data, status) {
|
||||
if (status=="success")
|
||||
{
|
||||
document.getElementById("relation_table").style.display = 'block'
|
||||
document.getElementById("newSubjectSave").style.display = 'none'
|
||||
document.getElementById("newSubjectDismiss").style.display = 'inline'
|
||||
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
|
||||
var e = document.getElementById("errorNewSubject");
|
||||
e.classList.add('alert-success');
|
||||
e.textContent = ' Schlagwort geändert ';
|
||||
e.style.display = 'block';
|
||||
|
||||
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
|
||||
$("#NewSubjectModal").modal("show");
|
||||
}
|
||||
}
|
||||
);
|
||||
$table.bootstrapTable('refresh')
|
||||
}
|
||||
|
||||
function ShowModalDetails(id) {
|
||||
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType : 'Subject',
|
||||
offset : 0,
|
||||
id : id,
|
||||
sort : 'Anchor.Text',
|
||||
limit : 1
|
||||
},
|
||||
function(data, status) {
|
||||
if (status == "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (!row || row.length === 0) {
|
||||
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||
return;
|
||||
}
|
||||
|
||||
//Assign existing values to the modal popup fields
|
||||
$("#display_term").val(row[0].Text);
|
||||
$("#display_id").val(row[0].ID);
|
||||
$("#display_id_show").val(row[0].ID);
|
||||
$("#display_detailtype").val(row[0].Detailtype);
|
||||
$("#display_classification").val(row[0].Classification);
|
||||
$("#display_descriptor").val(row[0].Descriptor);
|
||||
$("#display_type").val(row[0].Type);
|
||||
$("#display_scopenote").val(row[0].Scopenote);
|
||||
$("#display_synonyms").val(row[0].Synonyms || '');
|
||||
|
||||
// Alte tabellarische Darstellung (versteckt)
|
||||
var html = []
|
||||
var txt = ''
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
txt = txt +'<div class="col-sm-12">';
|
||||
txt = txt +'<div class="col-sm-2"></div>';
|
||||
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||
txt = txt +'</div>';
|
||||
})
|
||||
$(".modal_content").html(txt);
|
||||
$.each(row[0].Relations, function (key, value) {
|
||||
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||
})
|
||||
$("#display_relations").val(txt);
|
||||
|
||||
// Modal anzeigen
|
||||
$("#DetailsModal").modal("show");
|
||||
|
||||
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||
setTimeout(function() {
|
||||
if (typeof createSemanticNetwork === 'function') {
|
||||
var synonyms = row[0].Synonyms || '';
|
||||
|
||||
// Falls keine Synonyme im Response, versuche sie separat zu laden
|
||||
if (!synonyms || synonyms.trim() === '') {
|
||||
console.log('📝 Keine Synonyme im Response, lade separat...');
|
||||
|
||||
$.ajax({
|
||||
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||
type: "GET",
|
||||
data: { text: row[0].Text },
|
||||
dataType: "json",
|
||||
async: false, // Synchron um auf Ergebnis zu warten
|
||||
success: function(synResponse) {
|
||||
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
|
||||
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
|
||||
console.log('✅ Synonyme separat geladen:', synonyms);
|
||||
// Auch das Textfeld aktualisieren
|
||||
$("#display_synonyms").val(synonyms);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations, synonyms);
|
||||
} else {
|
||||
console.warn('⚠️ createSemanticNetwork Funktion nicht gefunden');
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function CopyLink(value)
|
||||
{
|
||||
var tempInput = document.createElement("input");
|
||||
tempInput.value = value;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function refreshPage()
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
305
migrate_tsdata.sh
Executable file
305
migrate_tsdata.sh
Executable file
@ -0,0 +1,305 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# TSData Migration Script
|
||||
# Migriert Daten von DSpace (MySQL 5.5) nach Tools (MariaDB 10.5)
|
||||
#
|
||||
# Verwendung:
|
||||
# 1. Skript auf DSpace ausführen: ./migrate_tsdata.sh export
|
||||
# 2. SQL-Datei auf Tools kopieren
|
||||
# 3. Skript auf Tools ausführen: ./migrate_tsdata.sh import /pfad/zur/datei.sql
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Konfiguration
|
||||
DB_NAME="TSData"
|
||||
DB_USER="root"
|
||||
BACKUP_DIR="/tmp/tsdata_migration"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
EXPORT_FILE="${BACKUP_DIR}/TSData_export_${TIMESTAMP}.sql"
|
||||
|
||||
# Farben für Output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Verzeichnis erstellen
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
#######################################
|
||||
# EXPORT (auf DSpace ausführen)
|
||||
#######################################
|
||||
do_export() {
|
||||
log_info "Starte Export auf DSpace..."
|
||||
|
||||
# Prüfen ob Datenbank existiert
|
||||
if ! mysql -u "$DB_USER" -p -e "USE $DB_NAME" 2>/dev/null; then
|
||||
log_error "Datenbank $DB_NAME nicht gefunden!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Erstelle Daten-Export (ohne Struktur)..."
|
||||
|
||||
# Export mit vollständigen INSERT-Statements
|
||||
mysqldump -u "$DB_USER" -p "$DB_NAME" \
|
||||
--no-create-info \
|
||||
--complete-insert \
|
||||
--skip-triggers \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--lock-tables=false \
|
||||
--set-charset \
|
||||
--default-character-set=utf8 \
|
||||
> "$EXPORT_FILE"
|
||||
|
||||
# Wrapper für SQL-Mode hinzufügen
|
||||
TEMP_FILE="${EXPORT_FILE}.tmp"
|
||||
cat > "$TEMP_FILE" << 'HEADER'
|
||||
-- TSData Migration Export
|
||||
-- Erstellt am: TIMESTAMP_PLACEHOLDER
|
||||
-- Quelle: DSpace Server
|
||||
|
||||
SET @OLD_SQL_MODE=@@SQL_MODE;
|
||||
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ALLOW_INVALID_DATES';
|
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS;
|
||||
SET UNIQUE_CHECKS=0;
|
||||
|
||||
-- DateModified '0000-00-00' Werte korrigieren (für MariaDB Strict Mode)
|
||||
-- Dies geschieht nach dem Import
|
||||
|
||||
HEADER
|
||||
|
||||
sed -i "s/TIMESTAMP_PLACEHOLDER/$(date)/" "$TEMP_FILE"
|
||||
cat "$EXPORT_FILE" >> "$TEMP_FILE"
|
||||
|
||||
cat >> "$TEMP_FILE" << 'FOOTER'
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||
|
||||
-- Export Ende
|
||||
FOOTER
|
||||
|
||||
mv "$TEMP_FILE" "$EXPORT_FILE"
|
||||
|
||||
# Statistiken anzeigen
|
||||
log_info "Export abgeschlossen: $EXPORT_FILE"
|
||||
log_info "Dateigröße: $(du -h "$EXPORT_FILE" | cut -f1)"
|
||||
|
||||
echo ""
|
||||
log_info "Datensätze pro Tabelle:"
|
||||
mysql -u "$DB_USER" -p -N -e "
|
||||
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
|
||||
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
|
||||
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
|
||||
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
|
||||
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
|
||||
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
|
||||
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
|
||||
" 2>/dev/null | column -t
|
||||
|
||||
echo ""
|
||||
log_info "Nächster Schritt: Datei auf Tools-Server kopieren:"
|
||||
echo " scp $EXPORT_FILE user@tools.bibb.de:/tmp/"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# IMPORT (auf Tools ausführen)
|
||||
#######################################
|
||||
do_import() {
|
||||
IMPORT_FILE="$1"
|
||||
|
||||
if [ -z "$IMPORT_FILE" ]; then
|
||||
log_error "Keine Import-Datei angegeben!"
|
||||
echo "Verwendung: $0 import /pfad/zur/export_datei.sql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$IMPORT_FILE" ]; then
|
||||
log_error "Datei nicht gefunden: $IMPORT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Starte Import auf Tools..."
|
||||
|
||||
# Backup der bestehenden Daten erstellen
|
||||
BACKUP_FILE="${BACKUP_DIR}/TSData_backup_tools_${TIMESTAMP}.sql"
|
||||
log_info "Erstelle Backup der bestehenden Daten: $BACKUP_FILE"
|
||||
|
||||
mysqldump -u "$DB_USER" -p "$DB_NAME" \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
> "$BACKUP_FILE"
|
||||
|
||||
log_info "Backup erstellt: $(du -h "$BACKUP_FILE" | cut -f1)"
|
||||
|
||||
# Bestätigung anfordern
|
||||
echo ""
|
||||
log_warn "ACHTUNG: Alle bestehenden Daten in $DB_NAME werden gelöscht!"
|
||||
read -p "Fortfahren? (ja/nein): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "ja" ]; then
|
||||
log_info "Abgebrochen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Tabellen leeren
|
||||
log_info "Leere bestehende Tabellen..."
|
||||
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
TRUNCATE TABLE Synonyms;
|
||||
TRUNCATE TABLE Linking;
|
||||
TRUNCATE TABLE EntryNew;
|
||||
TRUNCATE TABLE Entry;
|
||||
TRUNCATE TABLE Anchor;
|
||||
TRUNCATE TABLE IDSynonym;
|
||||
TRUNCATE TABLE Treeview;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
EOF
|
||||
|
||||
# Daten importieren
|
||||
log_info "Importiere Daten aus $IMPORT_FILE..."
|
||||
mysql -u "$DB_USER" -p "$DB_NAME" < "$IMPORT_FILE"
|
||||
|
||||
# DateModified '0000-00-00' Werte korrigieren
|
||||
log_info "Korrigiere ungültige Datumswerte..."
|
||||
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||
UPDATE Entry
|
||||
SET DateModified = DateCreated
|
||||
WHERE DateModified = '0000-00-00 00:00:00'
|
||||
OR DateModified IS NULL;
|
||||
EOF
|
||||
|
||||
# Verifizierung
|
||||
echo ""
|
||||
log_info "Import abgeschlossen. Verifizierung:"
|
||||
mysql -u "$DB_USER" -p -N -e "
|
||||
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
|
||||
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
|
||||
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
|
||||
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
|
||||
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
|
||||
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
|
||||
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
|
||||
" 2>/dev/null | column -t
|
||||
|
||||
echo ""
|
||||
log_info "Migration erfolgreich abgeschlossen!"
|
||||
log_info "Backup liegt unter: $BACKUP_FILE"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# VERIFY (Datenintegrität prüfen)
|
||||
#######################################
|
||||
do_verify() {
|
||||
log_info "Prüfe Datenintegrität..."
|
||||
|
||||
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||
-- Prüfe ob alle Linking-Einträge gültige Anchor-IDs haben
|
||||
SELECT 'Linking -> Anchor (fehlend)' AS Check_Type, COUNT(*) AS Anzahl
|
||||
FROM Linking l
|
||||
LEFT JOIN Anchor a ON l.IDAnchor = a.ID
|
||||
WHERE a.ID IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Prüfe ob alle Linking-Einträge gültige Entry-IDs haben
|
||||
SELECT 'Linking -> Entry (fehlend)', COUNT(*)
|
||||
FROM Linking l
|
||||
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||
WHERE e.ID IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Prüfe auf ungültige Datumswerte in Entry
|
||||
SELECT 'Entry mit ungültigem DateModified', COUNT(*)
|
||||
FROM Entry
|
||||
WHERE DateModified = '0000-00-00 00:00:00';
|
||||
EOF
|
||||
|
||||
log_info "Integritätsprüfung abgeschlossen."
|
||||
}
|
||||
|
||||
#######################################
|
||||
# ROLLBACK (Backup wiederherstellen)
|
||||
#######################################
|
||||
do_rollback() {
|
||||
BACKUP_FILE="$1"
|
||||
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
log_info "Verfügbare Backups:"
|
||||
ls -la ${BACKUP_DIR}/TSData_backup_tools_*.sql 2>/dev/null || echo "Keine Backups gefunden."
|
||||
echo ""
|
||||
echo "Verwendung: $0 rollback /pfad/zum/backup.sql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
log_error "Backup-Datei nicht gefunden: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_warn "Stelle Backup wieder her: $BACKUP_FILE"
|
||||
read -p "Fortfahren? (ja/nein): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "ja" ]; then
|
||||
log_info "Abgebrochen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mysql -u "$DB_USER" -p "$DB_NAME" < "$BACKUP_FILE"
|
||||
log_info "Rollback abgeschlossen."
|
||||
}
|
||||
|
||||
#######################################
|
||||
# MAIN
|
||||
#######################################
|
||||
case "$1" in
|
||||
export)
|
||||
do_export
|
||||
;;
|
||||
import)
|
||||
do_import "$2"
|
||||
;;
|
||||
verify)
|
||||
do_verify
|
||||
;;
|
||||
rollback)
|
||||
do_rollback "$2"
|
||||
;;
|
||||
*)
|
||||
echo "TSData Migration Script"
|
||||
echo ""
|
||||
echo "Verwendung: $0 {export|import|verify|rollback} [optionen]"
|
||||
echo ""
|
||||
echo "Befehle:"
|
||||
echo " export Daten aus DSpace exportieren"
|
||||
echo " import <datei> Daten in Tools importieren"
|
||||
echo " verify Datenintegrität prüfen"
|
||||
echo " rollback <backup> Backup wiederherstellen"
|
||||
echo ""
|
||||
echo "Workflow:"
|
||||
echo " 1. Auf DSpace: ./migrate_tsdata.sh export"
|
||||
echo " 2. Kopieren: scp /tmp/tsdata_migration/TSData_export_*.sql user@tools:/tmp/"
|
||||
echo " 3. Auf Tools: ./migrate_tsdata.sh import /tmp/TSData_export_*.sql"
|
||||
echo " 4. Prüfen: ./migrate_tsdata.sh verify"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
263
setup-libs.sh
Executable file
263
setup-libs.sh
Executable file
@ -0,0 +1,263 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# setup-libs.sh - Richtet lokale JavaScript/CSS Libraries ein
|
||||
#
|
||||
# Verwendung:
|
||||
# chmod +x setup-libs.sh
|
||||
# sudo ./setup-libs.sh
|
||||
#
|
||||
# Struktur:
|
||||
# /var/www/html/libs/
|
||||
# ├── bootstrap/
|
||||
# │ ├── 5.3.0/
|
||||
# │ └── current -> 5.3.0
|
||||
# ├── jquery/
|
||||
# │ ├── 3.6.0/
|
||||
# │ └── current -> 3.6.0
|
||||
# ...
|
||||
#
|
||||
# Nutzung in HTML:
|
||||
# <link href="/libs/bootstrap/current/css/bootstrap.min.css">
|
||||
# <script src="/libs/jquery/current/jquery.min.js">
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Konfiguration
|
||||
LIBS_BASE="/var/www/html/tools/libs"
|
||||
WGET_OPTS="-q --show-progress"
|
||||
|
||||
# Farben für Ausgabe
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} Lokale Libraries Setup${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Prüfe Root-Rechte
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${YELLOW}Hinweis: Ohne Root-Rechte wird nach $HOME/libs installiert${NC}"
|
||||
LIBS_BASE="$HOME/libs"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Zielverzeichnis: ${LIBS_BASE}${NC}"
|
||||
echo ""
|
||||
|
||||
# Basisverzeichnis erstellen
|
||||
mkdir -p "$LIBS_BASE"
|
||||
|
||||
# ============================================
|
||||
# 1. Bootstrap 5.3.0
|
||||
# ============================================
|
||||
echo -e "${BLUE}[1/7] Bootstrap 5.3.0${NC}"
|
||||
BOOTSTRAP_VERSION="5.3.0"
|
||||
BOOTSTRAP_DIR="$LIBS_BASE/bootstrap/$BOOTSTRAP_VERSION"
|
||||
mkdir -p "$BOOTSTRAP_DIR/css" "$BOOTSTRAP_DIR/js"
|
||||
|
||||
# CSS
|
||||
if [ ! -f "$BOOTSTRAP_DIR/css/bootstrap.min.css" ]; then
|
||||
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css"
|
||||
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css.map" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css.map" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# JS
|
||||
if [ ! -f "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js"
|
||||
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js.map" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js.map" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Symlink
|
||||
ln -sfn "$BOOTSTRAP_VERSION" "$LIBS_BASE/bootstrap/current"
|
||||
echo -e "${GREEN} ✓ Bootstrap $BOOTSTRAP_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 2. jQuery 3.6.0
|
||||
# ============================================
|
||||
echo -e "${BLUE}[2/7] jQuery 3.6.0${NC}"
|
||||
JQUERY_VERSION="3.6.0"
|
||||
JQUERY_DIR="$LIBS_BASE/jquery/$JQUERY_VERSION"
|
||||
mkdir -p "$JQUERY_DIR"
|
||||
|
||||
if [ ! -f "$JQUERY_DIR/jquery.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.js" \
|
||||
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.js"
|
||||
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.map" \
|
||||
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.map" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
ln -sfn "$JQUERY_VERSION" "$LIBS_BASE/jquery/current"
|
||||
echo -e "${GREEN} ✓ jQuery $JQUERY_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 3. Bootstrap Table 1.21.0
|
||||
# ============================================
|
||||
echo -e "${BLUE}[3/7] Bootstrap Table 1.21.0${NC}"
|
||||
BSTABLE_VERSION="1.21.0"
|
||||
BSTABLE_DIR="$LIBS_BASE/bootstrap-table/$BSTABLE_VERSION"
|
||||
mkdir -p "$BSTABLE_DIR/css" "$BSTABLE_DIR/js" "$BSTABLE_DIR/locale"
|
||||
|
||||
if [ ! -f "$BSTABLE_DIR/css/bootstrap-table.min.css" ]; then
|
||||
wget $WGET_OPTS -O "$BSTABLE_DIR/css/bootstrap-table.min.css" \
|
||||
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.css"
|
||||
fi
|
||||
|
||||
if [ ! -f "$BSTABLE_DIR/js/bootstrap-table.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$BSTABLE_DIR/js/bootstrap-table.min.js" \
|
||||
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.js"
|
||||
fi
|
||||
|
||||
if [ ! -f "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" \
|
||||
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/locale/bootstrap-table-de-DE.min.js"
|
||||
fi
|
||||
|
||||
ln -sfn "$BSTABLE_VERSION" "$LIBS_BASE/bootstrap-table/current"
|
||||
echo -e "${GREEN} ✓ Bootstrap Table $BSTABLE_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 4. Font Awesome 6.4.0
|
||||
# ============================================
|
||||
echo -e "${BLUE}[4/7] Font Awesome 6.4.0${NC}"
|
||||
FA_VERSION="6.4.0"
|
||||
FA_DIR="$LIBS_BASE/fontawesome/$FA_VERSION"
|
||||
mkdir -p "$FA_DIR"
|
||||
|
||||
if [ ! -f "$FA_DIR/css/all.min.css" ]; then
|
||||
# Font Awesome als ZIP herunterladen und entpacken
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
wget $WGET_OPTS -O "$TEMP_DIR/fontawesome.zip" \
|
||||
"https://use.fontawesome.com/releases/v${FA_VERSION}/fontawesome-free-${FA_VERSION}-web.zip"
|
||||
unzip -q "$TEMP_DIR/fontawesome.zip" -d "$TEMP_DIR"
|
||||
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/css" "$FA_DIR/"
|
||||
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/webfonts" "$FA_DIR/"
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
|
||||
ln -sfn "$FA_VERSION" "$LIBS_BASE/fontawesome/current"
|
||||
echo -e "${GREEN} ✓ Font Awesome $FA_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 5. vis.js Network 9.1.2
|
||||
# ============================================
|
||||
echo -e "${BLUE}[5/7] vis.js Network 9.1.2${NC}"
|
||||
VIS_VERSION="9.1.2"
|
||||
VIS_DIR="$LIBS_BASE/vis-network/$VIS_VERSION"
|
||||
mkdir -p "$VIS_DIR"
|
||||
|
||||
if [ ! -f "$VIS_DIR/vis-network.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$VIS_DIR/vis-network.min.js" \
|
||||
"https://unpkg.com/vis-network@${VIS_VERSION}/standalone/umd/vis-network.min.js"
|
||||
fi
|
||||
|
||||
ln -sfn "$VIS_VERSION" "$LIBS_BASE/vis-network/current"
|
||||
echo -e "${GREEN} ✓ vis.js Network $VIS_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 6. jsTree 3.3.16
|
||||
# ============================================
|
||||
echo -e "${BLUE}[6/7] jsTree 3.3.16${NC}"
|
||||
JSTREE_VERSION="3.3.16"
|
||||
JSTREE_DIR="$LIBS_BASE/jstree/$JSTREE_VERSION"
|
||||
mkdir -p "$JSTREE_DIR/themes/default"
|
||||
|
||||
if [ ! -f "$JSTREE_DIR/jstree.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$JSTREE_DIR/jstree.min.js" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/jstree.min.js"
|
||||
fi
|
||||
|
||||
if [ ! -f "$JSTREE_DIR/themes/default/style.min.css" ]; then
|
||||
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/style.min.css" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/style.min.css"
|
||||
|
||||
# Theme-Bilder
|
||||
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/32px.png" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/32px.png" 2>/dev/null || true
|
||||
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/40px.png" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/40px.png" 2>/dev/null || true
|
||||
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/throbber.gif" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/throbber.gif" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
ln -sfn "$JSTREE_VERSION" "$LIBS_BASE/jstree/current"
|
||||
echo -e "${GREEN} ✓ jsTree $JSTREE_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 7. Bootstrap Icons 1.11.0 (optional)
|
||||
# ============================================
|
||||
echo -e "${BLUE}[7/8] Bootstrap Icons 1.11.0${NC}"
|
||||
BSI_VERSION="1.11.0"
|
||||
BSI_DIR="$LIBS_BASE/bootstrap-icons/$BSI_VERSION"
|
||||
mkdir -p "$BSI_DIR"
|
||||
|
||||
if [ ! -f "$BSI_DIR/bootstrap-icons.min.css" ]; then
|
||||
wget $WGET_OPTS -O "$BSI_DIR/bootstrap-icons.min.css" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/bootstrap-icons.min.css"
|
||||
|
||||
# Fonts-Verzeichnis
|
||||
mkdir -p "$BSI_DIR/fonts"
|
||||
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff" 2>/dev/null || true
|
||||
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff2" \
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff2" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
ln -sfn "$BSI_VERSION" "$LIBS_BASE/bootstrap-icons/current"
|
||||
echo -e "${GREEN} ✓ Bootstrap Icons $BSI_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# 8. Bootstrap 3 Typeahead 4.0.2
|
||||
# ============================================
|
||||
echo -e "${BLUE}[8/8] Bootstrap 3 Typeahead 4.0.2${NC}"
|
||||
TYPEAHEAD_VERSION="4.0.2"
|
||||
TYPEAHEAD_DIR="$LIBS_BASE/bootstrap3-typeahead/$TYPEAHEAD_VERSION"
|
||||
mkdir -p "$TYPEAHEAD_DIR"
|
||||
|
||||
if [ ! -f "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" ]; then
|
||||
wget $WGET_OPTS -O "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" \
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/${TYPEAHEAD_VERSION}/bootstrap3-typeahead.min.js"
|
||||
fi
|
||||
|
||||
ln -sfn "$TYPEAHEAD_VERSION" "$LIBS_BASE/bootstrap3-typeahead/current"
|
||||
echo -e "${GREEN} ✓ Bootstrap 3 Typeahead $TYPEAHEAD_VERSION installiert${NC}"
|
||||
|
||||
# ============================================
|
||||
# Berechtigungen setzen
|
||||
# ============================================
|
||||
echo ""
|
||||
echo -e "${BLUE}Setze Berechtigungen...${NC}"
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
chown -R www-data:www-data "$LIBS_BASE"
|
||||
fi
|
||||
chmod -R 755 "$LIBS_BASE"
|
||||
|
||||
# ============================================
|
||||
# Übersicht anzeigen
|
||||
# ============================================
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} Installation abgeschlossen!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo "Struktur:"
|
||||
echo ""
|
||||
find "$LIBS_BASE" -maxdepth 3 -type d | head -30
|
||||
echo ""
|
||||
echo "Symlinks:"
|
||||
ls -la "$LIBS_BASE"/*/current 2>/dev/null || true
|
||||
echo ""
|
||||
echo -e "${YELLOW}Hinweis: Passen Sie die Header.html entsprechend an.${NC}"
|
||||
echo ""
|
||||
echo "Beispiel-Pfade:"
|
||||
echo " /libs/bootstrap/current/css/bootstrap.min.css"
|
||||
echo " /libs/jquery/current/jquery.min.js"
|
||||
echo " /libs/fontawesome/current/css/all.min.css"
|
||||
echo ""
|
||||
840
styles.css
Normal file
840
styles.css
Normal file
@ -0,0 +1,840 @@
|
||||
/* styles.css - BIBB Thesaurus (ORCID zu DSpace Layout) */
|
||||
|
||||
/* ===========================
|
||||
Variablen & Reset
|
||||
=========================== */
|
||||
:root {
|
||||
--primary-color: #1a3a5c;
|
||||
--primary-light: #2a5a8c;
|
||||
--primary-dark: #0f2840;
|
||||
--secondary-color: #006699;
|
||||
--accent-color: #00a3cc;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
--danger-color: #dc3545;
|
||||
--info-color: #17a2b8;
|
||||
|
||||
--text-primary: #333;
|
||||
--text-secondary: #666;
|
||||
--text-muted: #999;
|
||||
|
||||
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
|
||||
--bg-light: #f8f9fa;
|
||||
--bg-lighter: #fafbfc;
|
||||
--border-color: #e0e0e0;
|
||||
--border-light: #f0f0f0;
|
||||
|
||||
--shadow-sm: 0 2px 4px rgba(0,0,0,0.08);
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
|
||||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-gradient);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Layout Container
|
||||
=========================== */
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Header & Navigation
|
||||
=========================== */
|
||||
.site-header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.site-header .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.site-subtitle {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius-sm);
|
||||
display: block;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.main-nav a:hover,
|
||||
.main-nav a.active {
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Cards
|
||||
=========================== */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: 35px 40px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.6em;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1em;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Nav Cards (Feature Cards)
|
||||
=========================== */
|
||||
.nav-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 25px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 25px 30px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid var(--border-color);
|
||||
transition: var(--transition);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-card:hover {
|
||||
border-left-color: var(--primary-color);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.nav-card-icon {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-card h3 {
|
||||
font-size: 1.15em;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Section Headers
|
||||
=========================== */
|
||||
.section-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Project Items / List Items
|
||||
=========================== */
|
||||
.project-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px 25px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid var(--primary-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.project-item:hover {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.project-info h4 {
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.project-meta {
|
||||
font-size: 0.85em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.project-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Buttons
|
||||
=========================== */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-light);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: var(--info-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
background: #138496;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 14px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 14px 28px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Tables - Bootstrap Table Anpassungen
|
||||
=========================== */
|
||||
.fixed-table-container {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fixed-table-toolbar {
|
||||
background: var(--bg-light);
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.fixed-table-toolbar .search input {
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 10px 15px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.fixed-table-toolbar .search input:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.bootstrap-table .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .table thead th {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
padding: 14px 12px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.bootstrap-table .table tbody tr {
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.bootstrap-table .table tbody tr:hover {
|
||||
background: rgba(26,58,92,0.03);
|
||||
}
|
||||
|
||||
.bootstrap-table .table tbody td {
|
||||
padding: 14px 12px;
|
||||
vertical-align: middle;
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
/* Suchfeld doppelt so breit */
|
||||
.bootstrap-table .search input.form-control {
|
||||
width: 400px; /* Standard ist ~200px, Wert nach Bedarf anpassen */
|
||||
}
|
||||
|
||||
.fixed-table-pagination {
|
||||
padding: 15px 20px;
|
||||
background: var(--bg-light);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.page-link {
|
||||
color: var(--primary-color);
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.page-link:hover {
|
||||
color: var(--primary-dark);
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
/* Action Buttons in Tabelle */
|
||||
.table-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-actions .btn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.table-actions .btn:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Forms
|
||||
=========================== */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1em;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-control:disabled {
|
||||
background: var(--bg-light);
|
||||
cursor: not-allowed;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1em;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Modals
|
||||
=========================== */
|
||||
.modal-content {
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
padding: 18px 25px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-header .modal-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.15em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.modal-header .btn-close {
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.modal-header .btn-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 18px 25px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
background: var(--bg-lighter);
|
||||
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Alerts
|
||||
=========================== */
|
||||
.alert {
|
||||
padding: 15px 20px;
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 20px;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.alert i {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Autocomplete Dropdown
|
||||
=========================== */
|
||||
.autocomplete-dropdown {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
transition: var(--transition);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item:hover,
|
||||
.autocomplete-dropdown .dropdown-item.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Relations Table
|
||||
=========================== */
|
||||
.relations-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.relations-table th {
|
||||
background: var(--bg-light);
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.relations-table td {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.relations-table tr:hover {
|
||||
background: rgba(26,58,92,0.02);
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Stats Grid
|
||||
=========================== */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: var(--bg-light);
|
||||
padding: 20px;
|
||||
border-radius: var(--radius-md);
|
||||
text-align: center;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.2em;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Footer
|
||||
=========================== */
|
||||
.site-footer {
|
||||
background: var(--primary-dark);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.site-footer .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
color: rgba(255,255,255,0.6);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Utilities
|
||||
=========================== */
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
|
||||
.mt-10 { margin-top: 10px; }
|
||||
.mt-20 { margin-top: 20px; }
|
||||
.mt-30 { margin-top: 30px; }
|
||||
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.mb-20 { margin-bottom: 20px; }
|
||||
.mb-30 { margin-bottom: 30px; }
|
||||
|
||||
.d-none { display: none; }
|
||||
.d-block { display: block; }
|
||||
.d-flex { display: flex; }
|
||||
|
||||
.justify-between { justify-content: space-between; }
|
||||
.align-center { align-items: center; }
|
||||
|
||||
.gap-10 { gap: 10px; }
|
||||
.gap-20 { gap: 20px; }
|
||||
|
||||
.w-100 { width: 100%; }
|
||||
|
||||
/* ===========================
|
||||
Responsive
|
||||
=========================== */
|
||||
@media (max-width: 992px) {
|
||||
.site-header .container {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.nav-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.site-footer .container {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.project-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.project-actions .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card-title {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Print Styles
|
||||
=========================== */
|
||||
@media print {
|
||||
.site-header,
|
||||
.site-footer,
|
||||
.btn,
|
||||
.table-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
55
templates/ClassificationDetailsModal.tpl
Normal file
55
templates/ClassificationDetailsModal.tpl
Normal file
@ -0,0 +1,55 @@
|
||||
<!-- Modal - Klassifikation Details (Read-Only) -->
|
||||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||
<i class="fas fa-folder-tree"></i> Klassifikation - Details
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- Formular (Read-Only) -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="display_notation">Notation</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" id="display_notation" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="display_term">Bezeichnung</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="display_term" class="form-control" disabled />
|
||||
<input type="hidden" id="display_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="display_descriptor">Deskriptor</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="display_scopenote">Anmerkung</label>
|
||||
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
<div class="modal_content"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
templates/ClassificationsModal.tpl
Normal file
1
templates/ClassificationsModal.tpl
Normal file
@ -0,0 +1 @@
|
||||
<!-- ClassificationsModal - Platzhalter für zukünftige Erweiterungen -->
|
||||
317
templates/CorporateDetailsModal.tpl
Normal file
317
templates/CorporateDetailsModal.tpl
Normal file
@ -0,0 +1,317 @@
|
||||
<!-- Modal - Körperschaft Details mit semantischem Netz -->
|
||||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||
<i class="fas fa-building"></i> Detailanzeige Körperschaft
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row">
|
||||
<!-- Linke Spalte: Stammdaten -->
|
||||
<div class="col-md-4">
|
||||
<div class="detail-info-panel">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Name</label>
|
||||
<input type="text" id="display_term" class="form-control" disabled />
|
||||
<input type="hidden" id="display_id" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Deskriptor</label>
|
||||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">Type</label>
|
||||
<input type="text" id="display_type" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">ID</label>
|
||||
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Scopenote</label>
|
||||
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Semantisches Netz -->
|
||||
<div class="col-md-8">
|
||||
<div class="network-panel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||
<div class="network-toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="network-container"></div>
|
||||
<div class="network-legend">
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Körperschaft</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal_content" style="display: none;"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#network-container {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.network-panel {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.detail-info-panel {
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.network-toolbar {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.network-toolbar .btn {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.network-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#DetailsModal .modal-body {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||
|
||||
<script>
|
||||
var network = null;
|
||||
var networkData = null;
|
||||
|
||||
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||
console.log('🕸️ Erstelle semantisches Netz für Körperschaft:', centralTerm);
|
||||
|
||||
var nodes = [];
|
||||
var edges = [];
|
||||
|
||||
nodes.push({
|
||||
id: centralId,
|
||||
label: wrapLabel(centralTerm, 20),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: '#1a3a5c',
|
||||
border: '#0d1f30',
|
||||
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||
},
|
||||
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||
size: 35,
|
||||
borderWidth: 3,
|
||||
shadow: true
|
||||
});
|
||||
|
||||
// Farben für Körperschafts-Relationstypen
|
||||
var relationColors = {
|
||||
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
|
||||
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
|
||||
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
|
||||
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||
};
|
||||
|
||||
if (relations && relations.length > 0) {
|
||||
relations.forEach(function(rel, index) {
|
||||
var relType = rel.Relationtype || 'USE';
|
||||
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
|
||||
var relId = rel.IDRelation || ('rel_' + index);
|
||||
var relText = rel.TextRelation || 'Unbekannt';
|
||||
|
||||
nodes.push({
|
||||
id: relId,
|
||||
label: wrapLabel(relText, 18),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: relColor.node,
|
||||
border: shadeColor(relColor.node, -20),
|
||||
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||
},
|
||||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||
size: 25,
|
||||
borderWidth: 2,
|
||||
shadow: true,
|
||||
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||
});
|
||||
|
||||
var arrowDirection = {};
|
||||
if (relType === 'USEDEARLIER') {
|
||||
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||
} else if (relType === 'USEDLATER') {
|
||||
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||
}
|
||||
|
||||
edges.push({
|
||||
from: centralId,
|
||||
to: relId,
|
||||
label: relType,
|
||||
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||
width: 2,
|
||||
arrows: arrowDirection,
|
||||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var container = document.getElementById('network-container');
|
||||
|
||||
networkData = {
|
||||
nodes: new vis.DataSet(nodes),
|
||||
edges: new vis.DataSet(edges)
|
||||
};
|
||||
|
||||
var options = {
|
||||
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||
physics: {
|
||||
enabled: true,
|
||||
barnesHut: {
|
||||
gravitationalConstant: -3000,
|
||||
centralGravity: 0.3,
|
||||
springLength: 150,
|
||||
springConstant: 0.04,
|
||||
damping: 0.09
|
||||
},
|
||||
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||
layout: { improvedLayout: true }
|
||||
};
|
||||
|
||||
network = new vis.Network(container, networkData, options);
|
||||
|
||||
network.on('click', function(params) {
|
||||
if (params.nodes.length > 0) {
|
||||
var clickedNodeId = params.nodes[0];
|
||||
if (clickedNodeId != centralId) {
|
||||
ShowModalDetails(clickedNodeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
network.once('stabilizationIterationsDone', function() {
|
||||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
});
|
||||
}
|
||||
|
||||
function wrapLabel(text, maxLength) {
|
||||
if (!text) return '';
|
||||
if (text.length <= maxLength) return text;
|
||||
var words = text.split(' ');
|
||||
var lines = [];
|
||||
var currentLine = '';
|
||||
words.forEach(function(word) {
|
||||
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||
currentLine = (currentLine + ' ' + word).trim();
|
||||
} else {
|
||||
if (currentLine) lines.push(currentLine);
|
||||
currentLine = word;
|
||||
}
|
||||
});
|
||||
if (currentLine) lines.push(currentLine);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function shadeColor(color, percent) {
|
||||
var R = parseInt(color.substring(1,3), 16);
|
||||
var G = parseInt(color.substring(3,5), 16);
|
||||
var B = parseInt(color.substring(5,7), 16);
|
||||
R = parseInt(R * (100 + percent) / 100);
|
||||
G = parseInt(G * (100 + percent) / 100);
|
||||
B = parseInt(B * (100 + percent) / 100);
|
||||
R = (R < 255) ? R : 255;
|
||||
G = (G < 255) ? G : 255;
|
||||
B = (B < 255) ? B : 255;
|
||||
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||
return "#" + RR + GG + BB;
|
||||
}
|
||||
|
||||
function networkFit() {
|
||||
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
}
|
||||
|
||||
function networkZoomIn() {
|
||||
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
|
||||
function networkZoomOut() {
|
||||
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
</script>
|
||||
1
templates/CorporatesModal.tpl
Normal file
1
templates/CorporatesModal.tpl
Normal file
@ -0,0 +1 @@
|
||||
<!-- CorporatesModal - Platzhalter für zukünftige Erweiterungen -->
|
||||
16
templates/Footer.html
Normal file
16
templates/Footer.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- Site Footer -->
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<div class="footer-copyright">
|
||||
© <?php echo date('Y'); ?> Bundesinstitut für Berufsbildung (BIBB)
|
||||
</div>
|
||||
<ul class="footer-links">
|
||||
<li><a href="https://www.bibb.de/de/impressum_8945.php" target="_blank">Impressum</a></li>
|
||||
<li><a href="https://www.bibb.de/de/datenschutzerklaerung_81271.php" target="_blank">Datenschutz</a></li>
|
||||
<li><a href="https://www.bibb.de/de/kontakt_3922.php" target="_blank">Kontakt</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
709
templates/Header.html
Normal file
709
templates/Header.html
Normal file
@ -0,0 +1,709 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BIBB Thesaurus-Verwaltung</title>
|
||||
|
||||
<!-- ============================================
|
||||
LOKALE LIBRARIES MIT CDN-FALLBACK
|
||||
|
||||
Pfade verweisen auf /libs/[library]/current/
|
||||
Falls lokal nicht verfügbar, wird CDN geladen.
|
||||
|
||||
Setup: ./setup-libs.sh ausführen
|
||||
============================================ -->
|
||||
|
||||
<!-- Bootstrap 5 CSS (lokal mit Fallback) -->
|
||||
<link href="/libs/bootstrap/current/css/bootstrap.min.css" rel="stylesheet"
|
||||
onerror="this.onerror=null; this.href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css';">
|
||||
|
||||
<!-- Font Awesome 6 (lokal mit Fallback) -->
|
||||
<link rel="stylesheet" href="/libs/fontawesome/current/css/all.min.css"
|
||||
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';">
|
||||
|
||||
<!-- Bootstrap Table CSS (lokal mit Fallback) -->
|
||||
<link rel="stylesheet" href="/libs/bootstrap-table/current/css/bootstrap-table.min.css"
|
||||
onerror="this.onerror=null; this.href='https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.css';">
|
||||
|
||||
<!-- jsTree CSS (lokal mit Fallback) -->
|
||||
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css"
|
||||
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';">
|
||||
|
||||
<!-- BIBB Custom Styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
|
||||
<!-- Kritische Styles als Fallback direkt eingebettet -->
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #1a3a5c;
|
||||
--primary-light: #2a5a8c;
|
||||
--primary-dark: #0f2840;
|
||||
--secondary-color: #006699;
|
||||
--text-primary: #333;
|
||||
--text-secondary: #666;
|
||||
--text-muted: #999;
|
||||
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
|
||||
--bg-light: #f8f9fa;
|
||||
--border-color: #e0e0e0;
|
||||
--border-light: #f0f0f0;
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-gradient);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.site-header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.site-header .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.site-subtitle {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
padding: 10px 18px;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
font-size: 0.95em;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.main-nav a:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
/* Dropdown Menu Styles */
|
||||
.main-nav .nav-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
min-width: 200px;
|
||||
padding: 8px 0;
|
||||
z-index: 1000;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-nav .nav-item:hover .dropdown-menu,
|
||||
.main-nav .nav-item.show .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-menu li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-menu .dropdown-item {
|
||||
color: var(--text-primary) !important;
|
||||
padding: 10px 20px;
|
||||
font-weight: 400;
|
||||
border-radius: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-menu .dropdown-item:hover {
|
||||
background: var(--bg-light) !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: 35px 40px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.6em;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1em;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* Nav Cards Grid */
|
||||
.nav-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 25px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 25px 30px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid var(--border-color);
|
||||
transition: var(--transition);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-card:hover {
|
||||
border-left-color: var(--primary-color);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.nav-card-icon {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-card h3 {
|
||||
font-size: 1.15em;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Section Title */
|
||||
.section-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: var(--bg-light);
|
||||
padding: 20px;
|
||||
border-radius: var(--radius-md);
|
||||
text-align: center;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.2em;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.site-footer {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.site-footer .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site-footer a {
|
||||
color: rgba(255,255,255,0.8);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-footer a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-light);
|
||||
border-color: var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
}
|
||||
|
||||
/* Relations Table */
|
||||
#relation_table,
|
||||
.new_modal_content {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#relation_table table,
|
||||
.new_modal_content table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#relation_table table thead th,
|
||||
.new_modal_content table thead th {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
#relation_table table thead tr:first-child th:first-child,
|
||||
.new_modal_content table thead tr:first-child th:first-child {
|
||||
border-top-left-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
#relation_table table thead tr:first-child th:last-child,
|
||||
.new_modal_content table thead tr:first-child th:last-child {
|
||||
border-top-right-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
#relation_table table tbody tr:last-child td:first-child,
|
||||
.new_modal_content table tbody tr:last-child td:first-child {
|
||||
border-bottom-left-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
#relation_table table tbody tr:last-child td:last-child,
|
||||
.new_modal_content table tbody tr:last-child td:last-child {
|
||||
border-bottom-right-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
#relation_table .table,
|
||||
.new_modal_content .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Autocomplete Dropdown Styles */
|
||||
.autocomplete-dropdown {
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item {
|
||||
display: block;
|
||||
padding: 10px 15px;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.autocomplete-dropdown .dropdown-item:hover {
|
||||
background: var(--bg-light);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Bootstrap Table Pagination in BIBB-Blau */
|
||||
.pagination .page-link {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.pagination .page-link:hover {
|
||||
color: white;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pagination .page-item.disabled .page-link {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Kopier-Icons und Links in Tabelle */
|
||||
.fixed-table-container a,
|
||||
.bootstrap-table a,
|
||||
.fixed-table-body a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.fixed-table-container a:hover,
|
||||
.bootstrap-table a:hover,
|
||||
.fixed-table-body a:hover {
|
||||
color: var(--primary-light);
|
||||
}
|
||||
|
||||
/* Toolbar Icons */
|
||||
.fixed-table-toolbar .btn-secondary,
|
||||
.fixed-table-toolbar .btn-default {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.fixed-table-toolbar .btn-secondary:hover,
|
||||
.fixed-table-toolbar .btn-default:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Dropdown und Page-Size Selector */
|
||||
.fixed-table-pagination .page-list .btn,
|
||||
.fixed-table-pagination select,
|
||||
.fixed-table-pagination .page-list .dropdown-toggle,
|
||||
.fixed-table-pagination .page-list button {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--border-color);
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.fixed-table-pagination .page-list .dropdown-menu {
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.fixed-table-pagination .page-list .dropdown-item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.fixed-table-pagination .page-list .dropdown-item:hover,
|
||||
.fixed-table-pagination .page-list .dropdown-item.active {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Detail-View Toggle Icons */
|
||||
.detail-icon {
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
/* Bearbeiten/Löschen Icons in Tabelle weiß */
|
||||
.table-actions .btn i,
|
||||
.table-actions .btn-primary i,
|
||||
.table-actions .btn-danger i {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 14px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.bootstrap-table .table thead th {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
padding: 14px 12px;
|
||||
}
|
||||
|
||||
.fixed-table-container {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fixed-table-toolbar {
|
||||
background: var(--bg-light);
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Modals */
|
||||
.modal-header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 18px 25px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-header .btn-close {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.d-flex { display: flex; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.align-center { align-items: center; }
|
||||
.mb-20 { margin-bottom: 20px; }
|
||||
.mt-20 { margin-top: 20px; }
|
||||
.mt-30 { margin-top: 30px; }
|
||||
.w-100 { width: 100%; }
|
||||
|
||||
/* Table Actions */
|
||||
.table-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-actions .btn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 992px) {
|
||||
.site-header .container {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.nav-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.site-footer .container {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.card {
|
||||
padding: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- jQuery (lokal mit CDN-Fallback) -->
|
||||
<script src="/libs/jquery/current/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="https://code.jquery.com/jquery-3.6.0.min.js"><\/script>')</script>
|
||||
|
||||
<!-- Bootstrap 5 JS (lokal mit CDN-Fallback) -->
|
||||
<script src="/libs/bootstrap/current/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
if (typeof bootstrap === 'undefined') {
|
||||
document.write('<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"><\/script>');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
if ($.fn.bootstrapTable && $.fn.bootstrapTable.locales) {
|
||||
|
||||
// Deutsch
|
||||
if ($.fn.bootstrapTable.locales['de-DE']) {
|
||||
$.fn.bootstrapTable.locales['de-DE'].formatColumnsToggle = function () {
|
||||
return 'Spalten';
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback: globaler Default (greift wenn Locale nicht explizit gesetzt)
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
formatColumnsToggle: function () { return 'Spalten'; }
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Läuft nach jedem Table-Init und prüft ob der Columns-Button Einträge hat.
|
||||
// Ist das Dropdown leer (z. B. in Modulen ohne data-show-columns-Konfiguration),
|
||||
// wird der gesamte Button-Container versteckt.
|
||||
$(document).on('init.bs.table', 'table', function () {
|
||||
var $table = $(this);
|
||||
|
||||
// Kurze Verzögerung damit Bootstrap Table seinen DOM vollständig gerendert hat
|
||||
setTimeout(function () {
|
||||
var $btnGroup = $table.closest('.bootstrap-table')
|
||||
.find('.columns-btn-group, .btn-group.columns');
|
||||
|
||||
if ($btnGroup.length > 0) {
|
||||
// Prüfe ob der Dropdown-Inhalt tatsächlich Einträge hat
|
||||
var itemCount = $btnGroup.find('li').length;
|
||||
if (itemCount === 0) {
|
||||
$btnGroup.hide();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Bootstrap Typeahead (lokal mit CDN-Fallback) -->
|
||||
<script src="/libs/bootstrap3-typeahead/current/bootstrap3-typeahead.min.js"></script>
|
||||
<script>
|
||||
if (typeof $.fn.typeahead === 'undefined') {
|
||||
document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/4.0.2/bootstrap3-typeahead.min.js"><\/script>');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Table JS (lokal mit CDN-Fallback) -->
|
||||
<script src="/libs/bootstrap-table/current/js/bootstrap-table.min.js"></script>
|
||||
<script>
|
||||
if (typeof $.fn.bootstrapTable === 'undefined') {
|
||||
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.js"><\/script>');
|
||||
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/locale/bootstrap-table-de-DE.min.js"><\/script>');
|
||||
}
|
||||
</script>
|
||||
<script src="/libs/bootstrap-table/current/locale/bootstrap-table-de-DE.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="page-wrapper">
|
||||
|
||||
<!-- Site Header -->
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<a href="index.php" class="site-logo">
|
||||
<span class="site-title">BIBB Normdateienverwaltung</span>
|
||||
</a>
|
||||
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<li><a href="index.php">Startseite</a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Normdateien</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="Subjects.php">Schlagworte</a></li>
|
||||
<li><a class="dropdown-item" href="Persons.php">Personen</a></li>
|
||||
<li><a class="dropdown-item" href="Corporates.php">Körperschaften</a></li>
|
||||
<li><a class="dropdown-item" href="Publishers.php">Verlage</a></li>
|
||||
<li><a class="dropdown-item" href="Classifications.php">Klassifikation</a></li>
|
||||
<li><a class="dropdown-item" href="Treeview.php">Klassifikationsbaum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="Statistics.php">Statistik</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
318
templates/PersonDetailsModal.tpl
Normal file
318
templates/PersonDetailsModal.tpl
Normal file
@ -0,0 +1,318 @@
|
||||
<!-- Modal - Person Details mit semantischem Netz -->
|
||||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||
<i class="fas fa-user"></i> Detailanzeige Person
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row">
|
||||
<!-- Linke Spalte: Stammdaten -->
|
||||
<div class="col-md-4">
|
||||
<div class="detail-info-panel">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Name</label>
|
||||
<input type="text" id="display_term" class="form-control" disabled />
|
||||
<input type="hidden" id="display_id" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Deskriptor</label>
|
||||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">Type</label>
|
||||
<input type="text" id="display_type" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">ID</label>
|
||||
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Scopenote</label>
|
||||
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Semantisches Netz -->
|
||||
<div class="col-md-8">
|
||||
<div class="network-panel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||
<div class="network-toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="network-container"></div>
|
||||
<div class="network-legend">
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Person</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früherer Name</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Späterer Name</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Versteckter Container für alte Relationen-Tabelle -->
|
||||
<div class="modal_content" style="display: none;"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#network-container {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.network-panel {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.detail-info-panel {
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.network-toolbar {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.network-toolbar .btn {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.network-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#DetailsModal .modal-body {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- vis.js Network Bibliothek -->
|
||||
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||
|
||||
<script>
|
||||
var network = null;
|
||||
var networkData = null;
|
||||
|
||||
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||
console.log('🕸️ Erstelle semantisches Netz für Person:', centralTerm);
|
||||
|
||||
var nodes = [];
|
||||
var edges = [];
|
||||
|
||||
// Zentraler Knoten
|
||||
nodes.push({
|
||||
id: centralId,
|
||||
label: wrapLabel(centralTerm, 20),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: '#1a3a5c',
|
||||
border: '#0d1f30',
|
||||
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||
},
|
||||
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||
size: 35,
|
||||
borderWidth: 3,
|
||||
shadow: true
|
||||
});
|
||||
|
||||
// Farben für Personen-Relationstypen
|
||||
var relationColors = {
|
||||
'FORMERNAME': { node: '#28a745', edge: '#28a745', label: 'Früherer Name' },
|
||||
'LATERNAME': { node: '#dc3545', edge: '#dc3545', label: 'Späterer Name' },
|
||||
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||
};
|
||||
|
||||
if (relations && relations.length > 0) {
|
||||
relations.forEach(function(rel, index) {
|
||||
var relType = rel.Relationtype || 'USE';
|
||||
var relColor = relationColors[relType] || { node: '#fd7e14', edge: '#fd7e14' };
|
||||
var relId = rel.IDRelation || ('rel_' + index);
|
||||
var relText = rel.TextRelation || 'Unbekannt';
|
||||
|
||||
nodes.push({
|
||||
id: relId,
|
||||
label: wrapLabel(relText, 18),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: relColor.node,
|
||||
border: shadeColor(relColor.node, -20),
|
||||
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||
},
|
||||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||
size: 25,
|
||||
borderWidth: 2,
|
||||
shadow: true,
|
||||
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||
});
|
||||
|
||||
var arrowDirection = {};
|
||||
if (relType === 'FORMERNAME') {
|
||||
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||
} else if (relType === 'LATERNAME') {
|
||||
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||
}
|
||||
|
||||
edges.push({
|
||||
from: centralId,
|
||||
to: relId,
|
||||
label: relType,
|
||||
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||
width: 2,
|
||||
arrows: arrowDirection,
|
||||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var container = document.getElementById('network-container');
|
||||
|
||||
networkData = {
|
||||
nodes: new vis.DataSet(nodes),
|
||||
edges: new vis.DataSet(edges)
|
||||
};
|
||||
|
||||
var options = {
|
||||
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||
physics: {
|
||||
enabled: true,
|
||||
barnesHut: {
|
||||
gravitationalConstant: -3000,
|
||||
centralGravity: 0.3,
|
||||
springLength: 150,
|
||||
springConstant: 0.04,
|
||||
damping: 0.09
|
||||
},
|
||||
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||
layout: { improvedLayout: true }
|
||||
};
|
||||
|
||||
network = new vis.Network(container, networkData, options);
|
||||
|
||||
network.on('click', function(params) {
|
||||
if (params.nodes.length > 0) {
|
||||
var clickedNodeId = params.nodes[0];
|
||||
if (clickedNodeId != centralId) {
|
||||
ShowModalDetails(clickedNodeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
network.once('stabilizationIterationsDone', function() {
|
||||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
});
|
||||
}
|
||||
|
||||
function wrapLabel(text, maxLength) {
|
||||
if (!text) return '';
|
||||
if (text.length <= maxLength) return text;
|
||||
var words = text.split(' ');
|
||||
var lines = [];
|
||||
var currentLine = '';
|
||||
words.forEach(function(word) {
|
||||
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||
currentLine = (currentLine + ' ' + word).trim();
|
||||
} else {
|
||||
if (currentLine) lines.push(currentLine);
|
||||
currentLine = word;
|
||||
}
|
||||
});
|
||||
if (currentLine) lines.push(currentLine);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function shadeColor(color, percent) {
|
||||
var R = parseInt(color.substring(1,3), 16);
|
||||
var G = parseInt(color.substring(3,5), 16);
|
||||
var B = parseInt(color.substring(5,7), 16);
|
||||
R = parseInt(R * (100 + percent) / 100);
|
||||
G = parseInt(G * (100 + percent) / 100);
|
||||
B = parseInt(B * (100 + percent) / 100);
|
||||
R = (R < 255) ? R : 255;
|
||||
G = (G < 255) ? G : 255;
|
||||
B = (B < 255) ? B : 255;
|
||||
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||
return "#" + RR + GG + BB;
|
||||
}
|
||||
|
||||
function networkFit() {
|
||||
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
}
|
||||
|
||||
function networkZoomIn() {
|
||||
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
|
||||
function networkZoomOut() {
|
||||
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
</script>
|
||||
1
templates/PersonsModal.tpl
Normal file
1
templates/PersonsModal.tpl
Normal file
@ -0,0 +1 @@
|
||||
<!-- PersonsModal - Platzhalter für zukünftige Erweiterungen -->
|
||||
317
templates/PublisherDetailsModal.tpl
Normal file
317
templates/PublisherDetailsModal.tpl
Normal file
@ -0,0 +1,317 @@
|
||||
<!-- Modal - Verlag Details mit semantischem Netz -->
|
||||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||
<i class="fas fa-book"></i> Detailanzeige Verlag
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row">
|
||||
<!-- Linke Spalte: Stammdaten -->
|
||||
<div class="col-md-4">
|
||||
<div class="detail-info-panel">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Name</label>
|
||||
<input type="text" id="display_term" class="form-control" disabled />
|
||||
<input type="hidden" id="display_id" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Deskriptor</label>
|
||||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">Type</label>
|
||||
<input type="text" id="display_type" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">ID</label>
|
||||
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Scopenote</label>
|
||||
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Semantisches Netz -->
|
||||
<div class="col-md-8">
|
||||
<div class="network-panel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||
<div class="network-toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="network-container"></div>
|
||||
<div class="network-legend">
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Verlag</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal_content" style="display: none;"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#network-container {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.network-panel {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.detail-info-panel {
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.network-toolbar {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.network-toolbar .btn {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.network-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#DetailsModal .modal-body {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||
|
||||
<script>
|
||||
var network = null;
|
||||
var networkData = null;
|
||||
|
||||
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||
console.log('🕸️ Erstelle semantisches Netz für Verlag:', centralTerm);
|
||||
|
||||
var nodes = [];
|
||||
var edges = [];
|
||||
|
||||
nodes.push({
|
||||
id: centralId,
|
||||
label: wrapLabel(centralTerm, 20),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: '#1a3a5c',
|
||||
border: '#0d1f30',
|
||||
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||
},
|
||||
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||
size: 35,
|
||||
borderWidth: 3,
|
||||
shadow: true
|
||||
});
|
||||
|
||||
// Farben für Verlags-Relationstypen
|
||||
var relationColors = {
|
||||
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
|
||||
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
|
||||
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
|
||||
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||
};
|
||||
|
||||
if (relations && relations.length > 0) {
|
||||
relations.forEach(function(rel, index) {
|
||||
var relType = rel.Relationtype || 'USE';
|
||||
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
|
||||
var relId = rel.IDRelation || ('rel_' + index);
|
||||
var relText = rel.TextRelation || 'Unbekannt';
|
||||
|
||||
nodes.push({
|
||||
id: relId,
|
||||
label: wrapLabel(relText, 18),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: relColor.node,
|
||||
border: shadeColor(relColor.node, -20),
|
||||
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||
},
|
||||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||
size: 25,
|
||||
borderWidth: 2,
|
||||
shadow: true,
|
||||
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||
});
|
||||
|
||||
var arrowDirection = {};
|
||||
if (relType === 'USEDEARLIER') {
|
||||
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||
} else if (relType === 'USEDLATER') {
|
||||
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||
}
|
||||
|
||||
edges.push({
|
||||
from: centralId,
|
||||
to: relId,
|
||||
label: relType,
|
||||
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||
width: 2,
|
||||
arrows: arrowDirection,
|
||||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var container = document.getElementById('network-container');
|
||||
|
||||
networkData = {
|
||||
nodes: new vis.DataSet(nodes),
|
||||
edges: new vis.DataSet(edges)
|
||||
};
|
||||
|
||||
var options = {
|
||||
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||
physics: {
|
||||
enabled: true,
|
||||
barnesHut: {
|
||||
gravitationalConstant: -3000,
|
||||
centralGravity: 0.3,
|
||||
springLength: 150,
|
||||
springConstant: 0.04,
|
||||
damping: 0.09
|
||||
},
|
||||
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||
layout: { improvedLayout: true }
|
||||
};
|
||||
|
||||
network = new vis.Network(container, networkData, options);
|
||||
|
||||
network.on('click', function(params) {
|
||||
if (params.nodes.length > 0) {
|
||||
var clickedNodeId = params.nodes[0];
|
||||
if (clickedNodeId != centralId) {
|
||||
ShowModalDetails(clickedNodeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
network.once('stabilizationIterationsDone', function() {
|
||||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
});
|
||||
}
|
||||
|
||||
function wrapLabel(text, maxLength) {
|
||||
if (!text) return '';
|
||||
if (text.length <= maxLength) return text;
|
||||
var words = text.split(' ');
|
||||
var lines = [];
|
||||
var currentLine = '';
|
||||
words.forEach(function(word) {
|
||||
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||
currentLine = (currentLine + ' ' + word).trim();
|
||||
} else {
|
||||
if (currentLine) lines.push(currentLine);
|
||||
currentLine = word;
|
||||
}
|
||||
});
|
||||
if (currentLine) lines.push(currentLine);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function shadeColor(color, percent) {
|
||||
var R = parseInt(color.substring(1,3), 16);
|
||||
var G = parseInt(color.substring(3,5), 16);
|
||||
var B = parseInt(color.substring(5,7), 16);
|
||||
R = parseInt(R * (100 + percent) / 100);
|
||||
G = parseInt(G * (100 + percent) / 100);
|
||||
B = parseInt(B * (100 + percent) / 100);
|
||||
R = (R < 255) ? R : 255;
|
||||
G = (G < 255) ? G : 255;
|
||||
B = (B < 255) ? B : 255;
|
||||
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||
return "#" + RR + GG + BB;
|
||||
}
|
||||
|
||||
function networkFit() {
|
||||
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
}
|
||||
|
||||
function networkZoomIn() {
|
||||
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
|
||||
function networkZoomOut() {
|
||||
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||
}
|
||||
</script>
|
||||
1
templates/PublishersModal.tpl
Normal file
1
templates/PublishersModal.tpl
Normal file
@ -0,0 +1 @@
|
||||
<!-- PublishersModal - Platzhalter für zukünftige Erweiterungen -->
|
||||
349
templates/RelationsModal.tpl
Normal file
349
templates/RelationsModal.tpl
Normal file
@ -0,0 +1,349 @@
|
||||
<!-- Modal - Relationen tabellarisch -->
|
||||
<div class="modal fade" id="RelationsModal" tabindex="-1" aria-labelledby="RelationsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 900px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="RelationsModalHeadline">
|
||||
<i class="fas fa-table"></i> Relationen
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- Term-Info -->
|
||||
<div class="relations-modal-info mb-3">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Begriff</label>
|
||||
<input type="text" id="relations_modal_term" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label fw-bold">ID</label>
|
||||
<input type="text" id="relations_modal_id" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label fw-bold">Deskriptor</label>
|
||||
<input type="text" id="relations_modal_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label fw-bold">DSpace</label>
|
||||
<div id="relations_modal_dspace" class="form-control" style="background: #e9ecef;">
|
||||
<span class="text-muted">–</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lade-Anzeige -->
|
||||
<div id="relations_modal_loading" class="text-center py-4" style="display: none;">
|
||||
<div class="spinner-border spinner-border-sm text-primary me-2" role="status"></div>
|
||||
<span class="text-muted">Lade Relationen...</span>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Tabelle -->
|
||||
<div id="relations_modal_content" style="display: none;">
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 120px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-center">ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="relations_modal_body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Keine Relationen -->
|
||||
<div id="relations_modal_empty" class="text-center py-4 text-muted" style="display: none;">
|
||||
<i class="fas fa-info-circle me-1"></i> Keine Relationen vorhanden.
|
||||
</div>
|
||||
|
||||
<!-- Synonyme-Bereich (nur bei Subjects) -->
|
||||
<div id="relations_modal_synonyms" style="display: none; margin-top: 15px;">
|
||||
<h6 class="mb-2"><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 120px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="relations_modal_synonyms_body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.relations-modal-info {
|
||||
background: var(--bg-light, #f8f9fa);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color, #e0e0e0);
|
||||
}
|
||||
|
||||
#RelationsModal .badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 8px;
|
||||
min-width: 42px;
|
||||
}
|
||||
|
||||
#RelationsModal .badge.bg-syn {
|
||||
background-color: #e91e63;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#RelationsModal .relation-link {
|
||||
color: var(--primary-color, #1a3a5c);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#RelationsModal .relation-link:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--primary-light, #2a5a8c);
|
||||
}
|
||||
|
||||
#RelationsModal .modal-body {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Relationen-Modal öffnen und Daten laden
|
||||
*/
|
||||
function ShowRelationsModal(id, authType) {
|
||||
console.log('📋 Lade Relationen für ID:', id, 'Typ:', authType);
|
||||
|
||||
// Reset
|
||||
$('#relations_modal_term').val('');
|
||||
$('#relations_modal_id').val(id);
|
||||
$('#relations_modal_descriptor').val('');
|
||||
$('#relations_modal_body').html('');
|
||||
$('#relations_modal_synonyms_body').html('');
|
||||
$('#relations_modal_content').hide();
|
||||
$('#relations_modal_empty').hide();
|
||||
$('#relations_modal_synonyms').hide();
|
||||
$('#relations_modal_loading').show();
|
||||
$('#RelationsModalHeadline').html('<i class="fas fa-table"></i> Relationen');
|
||||
|
||||
// Modal öffnen
|
||||
$('#RelationsModal').modal('show');
|
||||
|
||||
// Daten laden
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType: authType,
|
||||
offset: 0,
|
||||
id: id,
|
||||
sort: 'Anchor.Text',
|
||||
limit: 1
|
||||
}, function(data, status) {
|
||||
if (status === "success") {
|
||||
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (!row || row.length === 0) {
|
||||
$('#relations_modal_loading').hide();
|
||||
$('#relations_modal_empty').show();
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = row[0];
|
||||
|
||||
// Info-Felder befüllen
|
||||
$('#relations_modal_term').val(entry.Text || '');
|
||||
$('#relations_modal_id').val(entry.ID || id);
|
||||
$('#relations_modal_descriptor').val(entry.Descriptor === false ? 'Nein' : 'Ja');
|
||||
|
||||
// DSpace-Count laden (nur bei Subject/Person und Deskriptor)
|
||||
var $dspaceField = $('#relations_modal_dspace');
|
||||
$dspaceField.html('<span class="text-muted">–</span>');
|
||||
|
||||
if ((authType === 'Subject' || authType === 'Person') && entry.Descriptor !== false) {
|
||||
$dspaceField.html('<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>');
|
||||
|
||||
$.ajax({
|
||||
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||
type: 'GET',
|
||||
data: { authType: authType, query: entry.Text },
|
||||
dataType: 'json',
|
||||
timeout: 15000,
|
||||
success: function(data) {
|
||||
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||
if (count === -1) {
|
||||
$dspaceField.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle"></i></span>');
|
||||
} else if (count > 0) {
|
||||
$dspaceField.html('<span class="badge" style="background-color: #1a3a5c;">' + count + ' Datensätze</span>');
|
||||
} else {
|
||||
$dspaceField.html('<span class="badge bg-danger">0</span>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$dspaceField.html('<span class="text-muted"><i class="fas fa-exclamation-triangle"></i></span>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Badge-Konfiguration
|
||||
var badgeConfig = {
|
||||
'BT': { css: 'bg-success', title: 'BT - Oberbegriff' },
|
||||
'NT': { css: 'bg-danger', title: 'NT - Unterbegriff' },
|
||||
'RT': { css: 'bg-warning text-dark', title: 'RT - Verwandter Begriff' },
|
||||
'USE': { css: 'bg-info', title: 'USE - Benutze' },
|
||||
'UF': { css: 'bg-secondary', title: 'UF - Benutzt für' },
|
||||
'USEDFOR': { css: 'bg-secondary', title: 'USEDFOR - Benutzt für' },
|
||||
'FORMERNAME': { css: 'bg-success', title: 'Früherer Name' },
|
||||
'LATERNAME': { css: 'bg-danger', title: 'Späterer Name' },
|
||||
'USEDEARLIER': { css: 'bg-success', title: 'Früher verwendet' },
|
||||
'USEDLATER': { css: 'bg-danger', title: 'Später verwendet' },
|
||||
'ALTERNATIVENAME': { css: 'bg-warning text-dark', title: 'Alternativer Name' },
|
||||
'SYN': { css: 'bg-syn', title: 'SYN - Synonym' }
|
||||
};
|
||||
|
||||
// Relationen rendern
|
||||
var relations = entry.Relations || [];
|
||||
var html = '';
|
||||
|
||||
if (relations.length > 0) {
|
||||
relations.forEach(function(rel) {
|
||||
var relId = rel.IDRelation || '';
|
||||
|
||||
// Selbst-Relation überspringen
|
||||
if (relId == id) return;
|
||||
|
||||
var relType = rel.Relationtype || 'RT';
|
||||
var badge = badgeConfig[relType] || { css: 'bg-secondary', title: relType };
|
||||
var relText = _escRelModal(rel.TextRelation || '');
|
||||
var relId = rel.IDRelation || '';
|
||||
|
||||
html += '<tr>';
|
||||
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">' + relType + '</span></td>';
|
||||
html += '<td><a class="relation-link" href="javascript:void(0)" onclick="ShowRelationsModal(\'' + relId + '\', \'' + authType + '\')" title="Relationen anzeigen">' + relText + '</a></td>';
|
||||
html += '<td class="text-center text-muted"><small>' + relId + '</small></td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
$('#relations_modal_body').html(html);
|
||||
if (html.length > 0) {
|
||||
$('#relations_modal_content').show();
|
||||
} else {
|
||||
$('#relations_modal_empty').show();
|
||||
}
|
||||
} else {
|
||||
$('#relations_modal_empty').show();
|
||||
}
|
||||
|
||||
// Synonyme (nur bei Subjects)
|
||||
if (authType === 'Subject') {
|
||||
var synonyms = entry.Synonyms || '';
|
||||
|
||||
// Falls leer, separat laden
|
||||
if (!synonyms || synonyms.trim() === '') {
|
||||
$.ajax({
|
||||
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||
type: "GET",
|
||||
data: { text: entry.Text },
|
||||
dataType: "json",
|
||||
success: function(synResponse) {
|
||||
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
|
||||
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
|
||||
_renderSynonymsInModal(synonyms, badgeConfig);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_renderSynonymsInModal(synonyms, badgeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
$('#relations_modal_loading').hide();
|
||||
}
|
||||
}).fail(function() {
|
||||
$('#relations_modal_loading').hide();
|
||||
$('#relations_modal_empty').html('<i class="fas fa-exclamation-circle me-1 text-danger"></i> Fehler beim Laden der Relationen.').show();
|
||||
});
|
||||
}
|
||||
|
||||
function _renderSynonymsInModal(synonyms, badgeConfig) {
|
||||
if (!synonyms || synonyms.trim() === '') return;
|
||||
|
||||
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
||||
if (synArray.length === 0) return;
|
||||
|
||||
var badge = badgeConfig['SYN'];
|
||||
var html = '';
|
||||
|
||||
synArray.forEach(function(synText) {
|
||||
html += '<tr>';
|
||||
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">SYN</span></td>';
|
||||
html += '<td>' + _escRelModal(synText) + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
$('#relations_modal_synonyms_body').html(html);
|
||||
$('#relations_modal_synonyms').show();
|
||||
}
|
||||
|
||||
function _escRelModal(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text in die Zwischenablage kopieren (modern mit Fallback)
|
||||
*/
|
||||
function copyTermToClipboard(text, btnElement) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
_showCopyFeedback(btnElement);
|
||||
}).catch(function() {
|
||||
_fallbackCopy(text, btnElement);
|
||||
});
|
||||
} else {
|
||||
_fallbackCopy(text, btnElement);
|
||||
}
|
||||
}
|
||||
|
||||
function _fallbackCopy(text, btnElement) {
|
||||
var tempInput = document.createElement("input");
|
||||
tempInput.value = text;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tempInput);
|
||||
_showCopyFeedback(btnElement);
|
||||
}
|
||||
|
||||
function _showCopyFeedback(btnElement) {
|
||||
if (!btnElement) return;
|
||||
var $btn = $(btnElement).closest('.copy-btn');
|
||||
var $icon = $btn.find('i');
|
||||
$icon.removeClass('fa-copy').addClass('fa-check');
|
||||
$btn.addClass('btn-success').removeClass('btn-outline-secondary');
|
||||
setTimeout(function() {
|
||||
$icon.removeClass('fa-check').addClass('fa-copy');
|
||||
$btn.removeClass('btn-success').addClass('btn-outline-secondary');
|
||||
}, 1500);
|
||||
}
|
||||
</script>
|
||||
|
||||
745
templates/SubjectDetailsModal.tpl
Normal file
745
templates/SubjectDetailsModal.tpl
Normal file
@ -0,0 +1,745 @@
|
||||
<!-- Modal - Schlagwort Details mit semantischem Netz (2 Ebenen) -->
|
||||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" style="max-width: 1400px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||
<i class="fas fa-tag"></i> Detailanzeige
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row">
|
||||
<!-- Linke Spalte: Stammdaten -->
|
||||
<div class="col-md-3">
|
||||
<div class="detail-info-panel">
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Schlagwort</label>
|
||||
<input type="text" id="display_term" class="form-control" disabled />
|
||||
<input type="hidden" id="display_id" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Deskriptor</label>
|
||||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">Type</label>
|
||||
<input type="text" id="display_type" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-bold">ID</label>
|
||||
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Detailtype</label>
|
||||
<input type="text" id="display_detailtype" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Klassifikation</label>
|
||||
<input type="text" id="display_classification" class="form-control" disabled />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold">Scopenote</label>
|
||||
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label fw-bold"><i class="fas fa-equals me-1" style="color: #e91e63;"></i>Synonyme</label>
|
||||
<textarea class="form-control" id="display_synonyms" rows="2" disabled style="font-size: 0.9rem;"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Lade-Status -->
|
||||
<div id="network-status" class="mt-3">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-circle-notch fa-spin me-1"></i>
|
||||
<span id="status-text">Lade Netzwerk...</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Semantisches Netz -->
|
||||
<div class="col-md-9">
|
||||
<div class="network-panel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz (2 Ebenen)</h6>
|
||||
<div class="network-toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="toggleSecondLevel()" title="2. Ebene ein/aus" id="btn-level">
|
||||
<i class="fas fa-layer-group"></i> <span id="btn-level-text">2</span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="togglePhysics()" title="Physik/Dynamik ein/aus" id="btn-physics">
|
||||
<i class="fas fa-atom"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="network-container"></div>
|
||||
<div class="network-legend">
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Begriff</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Oberbegriff (BT)</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Unterbegriff (NT)</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Verwandt (RT)</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #e91e63;"></span> Synonym (SYN)</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/UF</span>
|
||||
<span class="legend-item"><span class="legend-dot" style="background: #adb5bd; opacity: 0.7;"></span> 2. Ebene</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Versteckter Container für alte Relationen-Tabelle (falls benötigt) -->
|
||||
<div class="modal_content" style="display: none;"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Netzwerk-Container - größer für 2 Ebenen */
|
||||
#network-container {
|
||||
width: 100%;
|
||||
height: 550px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.network-panel {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.detail-info-panel {
|
||||
background: var(--bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.network-toolbar {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.network-toolbar .btn {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.network-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Modal extra breit für 2 Ebenen */
|
||||
#DetailsModal .modal-body {
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#network-status {
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- vis.js Network Bibliothek -->
|
||||
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||
|
||||
<script>
|
||||
var network = null;
|
||||
var networkData = null;
|
||||
var nodesDataSet = null;
|
||||
var edgesDataSet = null;
|
||||
var centralNodeId = null;
|
||||
var firstLevelNodeIds = [];
|
||||
var secondLevelNodeIds = [];
|
||||
var secondLevelEdgeIds = [];
|
||||
var secondLevelVisible = true;
|
||||
var secondLevelLoaded = false;
|
||||
var physicsEnabled = true;
|
||||
|
||||
// Farben für verschiedene Relationstypen
|
||||
var relationColors = {
|
||||
'BT': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||||
'BROADERTERM': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||||
'NT': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||||
'NARROWERTERM': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||||
'RT': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||||
'RELATEDTERM': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||||
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||
'UF': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||||
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||||
'SYN': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' },
|
||||
'SYNONYM': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' }
|
||||
};
|
||||
|
||||
function createSemanticNetwork(centralTerm, centralId, relations, synonyms) {
|
||||
console.log('🕸️ Erstelle semantisches Netz (2 Ebenen) für:', centralTerm);
|
||||
console.log('📝 Synonyme:', synonyms);
|
||||
|
||||
centralNodeId = centralId;
|
||||
firstLevelNodeIds = [];
|
||||
secondLevelNodeIds = [];
|
||||
secondLevelEdgeIds = [];
|
||||
secondLevelVisible = true;
|
||||
secondLevelLoaded = false;
|
||||
physicsEnabled = true;
|
||||
|
||||
// Buttons zurücksetzen
|
||||
var btn = document.getElementById('btn-level');
|
||||
var btnText = document.getElementById('btn-level-text');
|
||||
if (btn) {
|
||||
btn.classList.remove('btn-warning');
|
||||
btn.classList.add('btn-outline-secondary');
|
||||
}
|
||||
if (btnText) btnText.textContent = '2';
|
||||
|
||||
var btnPhysics = document.getElementById('btn-physics');
|
||||
if (btnPhysics) {
|
||||
btnPhysics.classList.remove('btn-warning');
|
||||
btnPhysics.classList.add('btn-outline-secondary');
|
||||
}
|
||||
|
||||
updateStatus('Baue erste Ebene auf...');
|
||||
|
||||
// DataSets erstellen
|
||||
nodesDataSet = new vis.DataSet();
|
||||
edgesDataSet = new vis.DataSet();
|
||||
|
||||
// Zentraler Knoten
|
||||
nodesDataSet.add({
|
||||
id: centralId,
|
||||
label: wrapLabel(centralTerm, 20),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: '#1a3a5c',
|
||||
border: '#0d1f30',
|
||||
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||
},
|
||||
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||
size: 40,
|
||||
borderWidth: 3,
|
||||
shadow: true,
|
||||
level: 0
|
||||
});
|
||||
|
||||
// Erste Ebene hinzufügen
|
||||
if (relations && relations.length > 0) {
|
||||
relations.forEach(function(rel, index) {
|
||||
var relType = rel.Relationtype || 'RT';
|
||||
var relColor = relationColors[relType] || relationColors['RT'];
|
||||
var relId = rel.IDRelation || ('rel_' + index);
|
||||
var relText = rel.TextRelation || 'Unbekannt';
|
||||
|
||||
// ID für späteres Nachladen speichern
|
||||
if (relId && relId !== centralId) {
|
||||
firstLevelNodeIds.push({
|
||||
id: relId,
|
||||
text: relText,
|
||||
type: relType
|
||||
});
|
||||
}
|
||||
|
||||
// Knoten für die Relation (1. Ebene)
|
||||
nodesDataSet.add({
|
||||
id: relId,
|
||||
label: wrapLabel(relText, 18),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: relColor.node,
|
||||
border: shadeColor(relColor.node, -20),
|
||||
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||
},
|
||||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||
size: 28,
|
||||
borderWidth: 2,
|
||||
shadow: true,
|
||||
title: relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||||
level: 1
|
||||
});
|
||||
|
||||
// Kante zum zentralen Knoten
|
||||
var arrowDirection = getArrowDirection(relType);
|
||||
|
||||
edgesDataSet.add({
|
||||
from: centralId,
|
||||
to: relId,
|
||||
label: relType,
|
||||
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||
width: 2,
|
||||
arrows: arrowDirection,
|
||||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Synonyme hinzufügen (kommaseparierter String)
|
||||
console.log('📝 Synonyme Typ:', typeof synonyms);
|
||||
console.log('📝 Synonyme Wert:', synonyms);
|
||||
console.log('📝 Synonyme Länge:', synonyms ? synonyms.length : 'null/undefined');
|
||||
|
||||
if (synonyms && typeof synonyms === 'string' && synonyms.trim().length > 0) {
|
||||
var synColor = relationColors['SYN'];
|
||||
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
||||
|
||||
console.log('📝 Synonym-Array:', synArray);
|
||||
|
||||
synArray.forEach(function(synText, index) {
|
||||
var synId = 'syn_' + index + '_' + synText.replace(/\s+/g, '_');
|
||||
|
||||
console.log('➕ Füge Synonym hinzu:', synText, '→ ID:', synId);
|
||||
|
||||
// Knoten für das Synonym
|
||||
nodesDataSet.add({
|
||||
id: synId,
|
||||
label: wrapLabel(synText, 18),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: synColor.node,
|
||||
border: shadeColor(synColor.node, -20),
|
||||
highlight: { background: shadeColor(synColor.node, 20), border: synColor.node },
|
||||
hover: { background: shadeColor(synColor.node, 20), border: synColor.node }
|
||||
},
|
||||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||
size: 28,
|
||||
borderWidth: 2,
|
||||
shadow: true,
|
||||
title: 'Synonym: ' + synText,
|
||||
level: 1,
|
||||
isSynonym: true,
|
||||
synonymText: synText
|
||||
});
|
||||
|
||||
// Kante zum zentralen Knoten (bidirektional, keine Pfeile)
|
||||
edgesDataSet.add({
|
||||
from: centralId,
|
||||
to: synId,
|
||||
label: 'SYN',
|
||||
color: { color: synColor.edge, highlight: synColor.edge, hover: synColor.edge },
|
||||
width: 2,
|
||||
arrows: { to: { enabled: false }, from: { enabled: false } },
|
||||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 },
|
||||
dashes: [5, 5]
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ ' + synArray.length + ' Synonyme hinzugefügt');
|
||||
} else {
|
||||
console.log('⚠️ Keine Synonyme vorhanden oder leer');
|
||||
}
|
||||
|
||||
// Netzwerk-Container
|
||||
var container = document.getElementById('network-container');
|
||||
|
||||
// Netzwerk-Daten
|
||||
networkData = {
|
||||
nodes: nodesDataSet,
|
||||
edges: edgesDataSet
|
||||
};
|
||||
|
||||
// Optionen
|
||||
var options = {
|
||||
nodes: {
|
||||
shape: 'ellipse',
|
||||
scaling: { min: 15, max: 40 }
|
||||
},
|
||||
edges: {
|
||||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||
},
|
||||
physics: {
|
||||
enabled: true,
|
||||
barnesHut: {
|
||||
gravitationalConstant: -4000,
|
||||
centralGravity: 0.2,
|
||||
springLength: 180,
|
||||
springConstant: 0.03,
|
||||
damping: 0.09
|
||||
},
|
||||
stabilization: {
|
||||
enabled: true,
|
||||
iterations: 300,
|
||||
updateInterval: 25
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
hover: true,
|
||||
tooltipDelay: 200,
|
||||
zoomView: true,
|
||||
dragView: true
|
||||
},
|
||||
layout: {
|
||||
improvedLayout: true
|
||||
}
|
||||
};
|
||||
|
||||
// Netzwerk erstellen
|
||||
network = new vis.Network(container, networkData, options);
|
||||
|
||||
// Klick auf Knoten
|
||||
network.on('click', function(params) {
|
||||
if (params.nodes.length > 0) {
|
||||
var clickedNodeId = params.nodes[0];
|
||||
if (clickedNodeId != centralNodeId) {
|
||||
console.log('🖱️ Knoten geklickt:', clickedNodeId);
|
||||
ShowModalDetails(clickedNodeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Stabilisierung abgeschlossen - dann zweite Ebene laden
|
||||
network.once('stabilizationIterationsDone', function() {
|
||||
console.log('✅ Erste Ebene stabilisiert');
|
||||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
|
||||
// Zweite Ebene laden
|
||||
setTimeout(function() {
|
||||
loadSecondLevel();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function loadSecondLevel() {
|
||||
if (firstLevelNodeIds.length === 0) {
|
||||
updateStatus('Fertig - keine weiteren Relationen');
|
||||
hideStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus('Lade 2. Ebene (0/' + firstLevelNodeIds.length + ')...');
|
||||
|
||||
var loadedCount = 0;
|
||||
var totalCount = firstLevelNodeIds.length;
|
||||
|
||||
// Für jeden Knoten der ersten Ebene die Relationen laden
|
||||
firstLevelNodeIds.forEach(function(nodeInfo, index) {
|
||||
setTimeout(function() {
|
||||
loadRelationsForNode(nodeInfo.id, nodeInfo.text, function() {
|
||||
loadedCount++;
|
||||
updateStatus('Lade 2. Ebene (' + loadedCount + '/' + totalCount + ')...');
|
||||
|
||||
if (loadedCount >= totalCount) {
|
||||
secondLevelLoaded = true;
|
||||
updateStatus('Fertig - ' + nodesDataSet.length + ' Begriffe geladen');
|
||||
|
||||
// Netzwerk neu stabilisieren
|
||||
network.stabilize(100);
|
||||
|
||||
setTimeout(function() {
|
||||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
hideStatus();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}, index * 100); // Gestaffeltes Laden um Server nicht zu überlasten
|
||||
});
|
||||
}
|
||||
|
||||
function loadRelationsForNode(nodeId, nodeText, callback) {
|
||||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||
authType: 'Subject',
|
||||
offset: 0,
|
||||
id: nodeId,
|
||||
sort: 'Anchor.Text',
|
||||
limit: 1
|
||||
}, function(data, status) {
|
||||
if (status === "success") {
|
||||
try {
|
||||
var metadata = JSON.parse(data);
|
||||
var row = metadata['rows'];
|
||||
|
||||
if (row && row[0] && row[0].Relations) {
|
||||
row[0].Relations.forEach(function(rel) {
|
||||
var relId = rel.IDRelation;
|
||||
var relText = rel.TextRelation;
|
||||
var relType = rel.Relationtype || 'RT';
|
||||
|
||||
// Nicht den zentralen Knoten oder bereits existierende Knoten
|
||||
if (relId && relId != centralNodeId) {
|
||||
var existingNode = nodesDataSet.get(relId);
|
||||
|
||||
if (!existingNode) {
|
||||
// Neuer Knoten der 2. Ebene (kleiner, blasser)
|
||||
var relColor = relationColors[relType] || relationColors['RT'];
|
||||
var fadedColor = fadeColor(relColor.node, 0.6);
|
||||
|
||||
nodesDataSet.add({
|
||||
id: relId,
|
||||
label: wrapLabel(relText, 15),
|
||||
shape: 'ellipse',
|
||||
color: {
|
||||
background: fadedColor,
|
||||
border: shadeColor(fadedColor, -15),
|
||||
highlight: { background: relColor.node, border: shadeColor(relColor.node, -20) },
|
||||
hover: { background: relColor.node, border: shadeColor(relColor.node, -20) }
|
||||
},
|
||||
font: { color: '#ffffff', size: 10, face: 'Arial' },
|
||||
size: 18,
|
||||
borderWidth: 1,
|
||||
shadow: false,
|
||||
title: '2. Ebene - ' + relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||||
level: 2
|
||||
});
|
||||
|
||||
// ID für Toggle speichern
|
||||
secondLevelNodeIds.push(relId);
|
||||
}
|
||||
|
||||
// Kante hinzufügen (wenn noch nicht vorhanden)
|
||||
var edgeId = nodeId + '_' + relId;
|
||||
var reverseEdgeId = relId + '_' + nodeId;
|
||||
|
||||
if (!edgesDataSet.get(edgeId) && !edgesDataSet.get(reverseEdgeId)) {
|
||||
var relColor = relationColors[relType] || relationColors['RT'];
|
||||
var arrowDirection = getArrowDirection(relType);
|
||||
|
||||
edgesDataSet.add({
|
||||
id: edgeId,
|
||||
from: nodeId,
|
||||
to: relId,
|
||||
color: { color: fadeColor(relColor.edge, 0.5), highlight: relColor.edge, hover: relColor.edge },
|
||||
width: 1,
|
||||
arrows: arrowDirection,
|
||||
dashes: true,
|
||||
smooth: { type: 'curvedCW', roundness: 0.3 }
|
||||
});
|
||||
|
||||
// ID für Toggle speichern
|
||||
secondLevelEdgeIds.push(edgeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Fehler beim Parsen für Node', nodeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
}).fail(function() {
|
||||
console.warn('⚠️ AJAX-Fehler für Node', nodeId);
|
||||
if (callback) callback();
|
||||
});
|
||||
}
|
||||
|
||||
function getArrowDirection(relType) {
|
||||
if (relType === 'BT' || relType === 'BROADERTERM') {
|
||||
return { to: { enabled: true, scaleFactor: 1 } };
|
||||
} else if (relType === 'NT' || relType === 'NARROWERTERM') {
|
||||
return { from: { enabled: true, scaleFactor: 1 } };
|
||||
}
|
||||
return { to: { enabled: false }, from: { enabled: false } };
|
||||
}
|
||||
|
||||
function updateStatus(text) {
|
||||
$('#status-text').text(text);
|
||||
$('#network-status').show();
|
||||
}
|
||||
|
||||
function hideStatus() {
|
||||
setTimeout(function() {
|
||||
$('#network-status').fadeOut();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Hilfsfunktion: Farbe verblassen
|
||||
function fadeColor(color, opacity) {
|
||||
var R = parseInt(color.substring(1,3), 16);
|
||||
var G = parseInt(color.substring(3,5), 16);
|
||||
var B = parseInt(color.substring(5,7), 16);
|
||||
|
||||
// Mit Weiß mischen für verblassten Effekt
|
||||
R = Math.round(R + (255 - R) * (1 - opacity));
|
||||
G = Math.round(G + (255 - G) * (1 - opacity));
|
||||
B = Math.round(B + (255 - B) * (1 - opacity));
|
||||
|
||||
var RR = R.toString(16).padStart(2, '0');
|
||||
var GG = G.toString(16).padStart(2, '0');
|
||||
var BB = B.toString(16).padStart(2, '0');
|
||||
|
||||
return "#" + RR + GG + BB;
|
||||
}
|
||||
|
||||
// Hilfsfunktion: Label umbrechen
|
||||
function wrapLabel(text, maxLength) {
|
||||
if (!text) return '';
|
||||
if (text.length <= maxLength) return text;
|
||||
|
||||
var words = text.split(' ');
|
||||
var lines = [];
|
||||
var currentLine = '';
|
||||
|
||||
words.forEach(function(word) {
|
||||
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||
currentLine = (currentLine + ' ' + word).trim();
|
||||
} else {
|
||||
if (currentLine) lines.push(currentLine);
|
||||
currentLine = word;
|
||||
}
|
||||
});
|
||||
if (currentLine) lines.push(currentLine);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Hilfsfunktion: Farbe aufhellen/abdunkeln
|
||||
function shadeColor(color, percent) {
|
||||
var R = parseInt(color.substring(1,3), 16);
|
||||
var G = parseInt(color.substring(3,5), 16);
|
||||
var B = parseInt(color.substring(5,7), 16);
|
||||
|
||||
R = parseInt(R * (100 + percent) / 100);
|
||||
G = parseInt(G * (100 + percent) / 100);
|
||||
B = parseInt(B * (100 + percent) / 100);
|
||||
|
||||
R = (R < 255) ? R : 255;
|
||||
G = (G < 255) ? G : 255;
|
||||
B = (B < 255) ? B : 255;
|
||||
R = (R > 0) ? R : 0;
|
||||
G = (G > 0) ? G : 0;
|
||||
B = (B > 0) ? B : 0;
|
||||
|
||||
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||
|
||||
return "#" + RR + GG + BB;
|
||||
}
|
||||
|
||||
// Netzwerk-Steuerung
|
||||
function networkFit() {
|
||||
if (network) {
|
||||
network.fit({
|
||||
animation: { duration: 500, easingFunction: 'easeInOutQuad' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function networkZoomIn() {
|
||||
if (network) {
|
||||
var scale = network.getScale() * 1.3;
|
||||
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||||
}
|
||||
}
|
||||
|
||||
function networkZoomOut() {
|
||||
if (network) {
|
||||
var scale = network.getScale() / 1.3;
|
||||
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSecondLevel() {
|
||||
if (!network || !secondLevelLoaded) {
|
||||
console.log('⚠️ 2. Ebene noch nicht geladen');
|
||||
return;
|
||||
}
|
||||
|
||||
secondLevelVisible = !secondLevelVisible;
|
||||
|
||||
var btn = document.getElementById('btn-level');
|
||||
var btnText = document.getElementById('btn-level-text');
|
||||
|
||||
if (secondLevelVisible) {
|
||||
// 2. Ebene einblenden
|
||||
secondLevelNodeIds.forEach(function(nodeId) {
|
||||
nodesDataSet.update({ id: nodeId, hidden: false });
|
||||
});
|
||||
secondLevelEdgeIds.forEach(function(edgeId) {
|
||||
edgesDataSet.update({ id: edgeId, hidden: false });
|
||||
});
|
||||
|
||||
btn.classList.remove('btn-warning');
|
||||
btn.classList.add('btn-outline-secondary');
|
||||
btnText.textContent = '2';
|
||||
|
||||
console.log('✅ 2. Ebene eingeblendet');
|
||||
} else {
|
||||
// 2. Ebene ausblenden
|
||||
secondLevelNodeIds.forEach(function(nodeId) {
|
||||
nodesDataSet.update({ id: nodeId, hidden: true });
|
||||
});
|
||||
secondLevelEdgeIds.forEach(function(edgeId) {
|
||||
edgesDataSet.update({ id: edgeId, hidden: true });
|
||||
});
|
||||
|
||||
btn.classList.remove('btn-outline-secondary');
|
||||
btn.classList.add('btn-warning');
|
||||
btnText.textContent = '1';
|
||||
|
||||
console.log('✅ 2. Ebene ausgeblendet');
|
||||
}
|
||||
|
||||
// Ansicht anpassen
|
||||
setTimeout(function() {
|
||||
network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function togglePhysics() {
|
||||
if (!network) return;
|
||||
|
||||
physicsEnabled = !physicsEnabled;
|
||||
network.setOptions({ physics: { enabled: physicsEnabled } });
|
||||
|
||||
var btn = document.getElementById('btn-physics');
|
||||
if (physicsEnabled) {
|
||||
btn.classList.remove('btn-warning');
|
||||
btn.classList.add('btn-outline-secondary');
|
||||
console.log('✅ Physik aktiviert');
|
||||
} else {
|
||||
btn.classList.remove('btn-outline-secondary');
|
||||
btn.classList.add('btn-warning');
|
||||
console.log('✅ Physik deaktiviert - Knoten können manuell verschoben werden');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
2
templates/SubjectsModal.tpl
Normal file
2
templates/SubjectsModal.tpl
Normal file
@ -0,0 +1,2 @@
|
||||
<!-- Modal - Subjects Modal (für weitere Funktionen) -->
|
||||
<!-- Dieses Template kann bei Bedarf erweitert werden -->
|
||||
111
templates/newClassification.tpl
Normal file
111
templates/newClassification.tpl
Normal file
@ -0,0 +1,111 @@
|
||||
<!-- Modal - Neue Klassifikation -->
|
||||
<div class="modal fade" id="NewClassificationModal" tabindex="-1" aria-labelledby="NewClassificationModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="NewClassificationModalHeadline">
|
||||
<i class="fas fa-folder-tree"></i> Klassifikation
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Alert für Meldungen -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="errorNewClassification" class="alert" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_notation">Notation</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" id="new_notation" class="form-control" placeholder="z.B. 1.2.3" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_term">Bezeichnung</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_term" class="form-control" />
|
||||
<input type="hidden" id="new_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<div id="new_relation_label" style="display: none;">
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
|
||||
<!-- Neue Relation hinzufügen -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_relationtype">
|
||||
<option value="BT">BT - Oberbegriff</option>
|
||||
<option value="NT">NT - Unterbegriff</option>
|
||||
<option value="RT">RT - Verwandter Begriff</option>
|
||||
<option value="USE">USE - Benutze</option>
|
||||
<option value="UF">UF - Benutzt für</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" id="search_relation" class="form-control" placeholder="Klassifikation eingeben..." />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-success w-100" id="classification-anlegen">
|
||||
<i class="fas fa-plus"></i> Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vorhandene Relationen -->
|
||||
<div id="relation_table" style="display: none;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="new_modal_content">
|
||||
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="newClassificationSave" type="button" class="btn btn-success" title="New Classification" onclick="CreateNewEntry('Classification')">
|
||||
<i class="fas fa-save"></i> speichern
|
||||
</button>
|
||||
<button id="ClassificationModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Classification" onclick="UpdateEntry('Classification')">
|
||||
<i class="fas fa-sync"></i> aktualisieren
|
||||
</button>
|
||||
<button id="newClassificationDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
<button id="newRelationClassificationDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
115
templates/newCorporate.tpl
Normal file
115
templates/newCorporate.tpl
Normal file
@ -0,0 +1,115 @@
|
||||
<!-- Modal - Neue Körperschaft -->
|
||||
<div class="modal fade" id="NewCorporateModal" tabindex="-1" aria-labelledby="NewCorporateModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="NewCorporateModalHeadline">
|
||||
<i class="fas fa-building"></i> Körperschaft
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Alert für Meldungen -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="errorNewCorporate" class="alert" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_term" class="form-control" />
|
||||
<input type="hidden" id="new_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_type">
|
||||
<option value="Corporates">Körperschaft</option>
|
||||
<option value="Conference">Konferenz</option>
|
||||
<option value="Project">Projekt</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<div id="new_relation_label" style="display: none;">
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
|
||||
<!-- Neue Relation hinzufügen -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_relationtype">
|
||||
<option value="USEDEARLIER">Früher verwendet</option>
|
||||
<option value="USEDLATER">Später verwendet</option>
|
||||
<option value="ALTERNATIVENAME">Alternativer Name</option>
|
||||
<option value="USE">Benutze</option>
|
||||
<option value="USEDFOR">Benutzt für</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" id="search_relation" class="form-control" placeholder="Körperschaft eingeben..." />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-success w-100" id="corporate-anlegen">
|
||||
<i class="fas fa-plus"></i> Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vorhandene Relationen -->
|
||||
<div id="relation_table" style="display: none;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="new_modal_content">
|
||||
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="newCorporateSave" type="button" class="btn btn-success" title="New Corporate" onclick="CreateNewEntry('Corporates')">
|
||||
<i class="fas fa-save"></i> speichern
|
||||
</button>
|
||||
<button id="CorporateModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Corporate" onclick="UpdateEntry('Corporate')">
|
||||
<i class="fas fa-sync"></i> aktualisieren
|
||||
</button>
|
||||
<button id="newCorporateDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
<button id="newRelationCorporateDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
103
templates/newPerson.tpl
Normal file
103
templates/newPerson.tpl
Normal file
@ -0,0 +1,103 @@
|
||||
<!-- Modal - Neue Person -->
|
||||
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="NewSubjectModalHeadline">
|
||||
<i class="fas fa-user"></i> Person
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Alert für Meldungen -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="errorNewSubject" class="alert" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_term" class="form-control" />
|
||||
<input type="hidden" id="new_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<div id="new_relation_label" style="display: none;">
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
|
||||
<!-- Neue Relation hinzufügen -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_relationtype">
|
||||
<option value="FORMERNAME">Früherer Name</option>
|
||||
<option value="LATERNAME">Späterer Name</option>
|
||||
<option value="USE">Benutze</option>
|
||||
<option value="USEDFOR">Benutzt für</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" id="search_relation" class="form-control" placeholder="Name eingeben..." />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
|
||||
<i class="fas fa-plus"></i> Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vorhandene Relationen -->
|
||||
<div id="relation_table" style="display: none;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="new_modal_content">
|
||||
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Person" onclick="CreateNewEntry('Person')">
|
||||
<i class="fas fa-save"></i> speichern
|
||||
</button>
|
||||
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Person" onclick="UpdateEntry('Person')">
|
||||
<i class="fas fa-sync"></i> aktualisieren
|
||||
</button>
|
||||
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
114
templates/newPublisher.tpl
Normal file
114
templates/newPublisher.tpl
Normal file
@ -0,0 +1,114 @@
|
||||
<!-- Modal - Neuer Verlag -->
|
||||
<div class="modal fade" id="NewPublisherModal" tabindex="-1" aria-labelledby="NewPublisherModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="NewPublisherModalHeadline">
|
||||
<i class="fas fa-book"></i> Verlag
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Alert für Meldungen -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="errorNewPublisher" class="alert" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_term" class="form-control" />
|
||||
<input type="hidden" id="new_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_type">
|
||||
<option value="Publisher">Verlag</option>
|
||||
<option value="Imprint">Imprint</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<div id="new_relation_label" style="display: none;">
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
|
||||
<!-- Neue Relation hinzufügen -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_relationtype">
|
||||
<option value="USEDEARLIER">Früher verwendet</option>
|
||||
<option value="USEDLATER">Später verwendet</option>
|
||||
<option value="ALTERNATIVENAME">Alternativer Name</option>
|
||||
<option value="USE">Benutze</option>
|
||||
<option value="USEDFOR">Benutzt für</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" id="search_relation" class="form-control" placeholder="Verlag eingeben..." />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-success w-100" id="publisher-anlegen">
|
||||
<i class="fas fa-plus"></i> Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vorhandene Relationen -->
|
||||
<div id="relation_table" style="display: none;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="new_modal_content">
|
||||
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="newPublisherSave" type="button" class="btn btn-success" title="New Publisher" onclick="CreateNewEntry('Publisher')">
|
||||
<i class="fas fa-save"></i> speichern
|
||||
</button>
|
||||
<button id="PublisherModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Publisher" onclick="UpdateEntry('Publisher')">
|
||||
<i class="fas fa-sync"></i> aktualisieren
|
||||
</button>
|
||||
<button id="newPublisherDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
<button id="newRelationPublisherDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
157
templates/newSubject.tpl
Normal file
157
templates/newSubject.tpl
Normal file
@ -0,0 +1,157 @@
|
||||
<!-- Modal - Neues Schlagwort -->
|
||||
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="NewSubjectModalHeadline">
|
||||
<i class="fas fa-tag"></i> Schlagwort
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Alert für Meldungen -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="errorNewSubject" class="alert" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_term">Schlagwort</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_term" class="form-control" />
|
||||
<input type="hidden" id="new_id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_type">
|
||||
<option value="Subject">Schlagwort</option>
|
||||
<option value="Person">Personenname</option>
|
||||
<option value="Corporates">Körperschaft</option>
|
||||
<option value="Publisher">Verlag</option>
|
||||
<option value="Classification">Klassifikation</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_detailtype">Detailtype</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_detailtype">
|
||||
<option value="Sachschlagwort">Sachschlagwort</option>
|
||||
<option value="geografisches Schlagwort">geografisches Schlagwort</option>
|
||||
<option value="Formschlagwort">Formschlagwort</option>
|
||||
<option value="Zeitschlagwort">Zeitschlagwort</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_classification">Klassifikation</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="new_classification" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="new_scopenote">Scopenote</label>
|
||||
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Relationen-Bereich -->
|
||||
<div id="new_relation_label" style="display: none;">
|
||||
<hr>
|
||||
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||
|
||||
<!-- Neue Relation hinzufügen -->
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" id="new_relationtype">
|
||||
<option value="BT">BT - Oberbegriff</option>
|
||||
<option value="NT">NT - Unterbegriff</option>
|
||||
<option value="RT">RT - Verwandter Begriff</option>
|
||||
<option value="USE">USE - Benutze</option>
|
||||
<option value="UF">UF - Benutzt für</option>
|
||||
<option value="SYN">SYN - Synonym</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" id="search_relation" class="form-control" placeholder="Begriff eingeben..." />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
|
||||
<i class="fas fa-plus"></i> Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vorhandene Relationen -->
|
||||
<div id="relation_table" style="display: none;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="new_modal_content">
|
||||
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Synonyme-Bereich -->
|
||||
<div id="synonyms_section" style="display: none; margin-top: 20px;">
|
||||
<hr>
|
||||
<h6><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
|
||||
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||
<table class="table table-sm table-hover table-bordered mb-0" id="synonyms_table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 80px;">Typ</th>
|
||||
<th>Begriff</th>
|
||||
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="synonyms_body">
|
||||
<!-- Synonyme werden hier per AJAX eingefügt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Subject" onclick="CreateNewEntry('Subject')">
|
||||
<i class="fas fa-save"></i> speichern
|
||||
</button>
|
||||
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Subject" onclick="UpdateEntry('Subject')">
|
||||
<i class="fas fa-sync"></i> aktualisieren
|
||||
</button>
|
||||
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user