Autosave: 20260409-175609
This commit is contained in:
parent
721fc0af97
commit
510602945b
35
admin.php
35
admin.php
@ -7,6 +7,7 @@ library_bootstrap();
|
||||
|
||||
$documents = library_fetch_documents(false, []);
|
||||
$metrics = library_catalog_metrics();
|
||||
$lang = library_get_language();
|
||||
|
||||
admin_render_header(library_trans('dashboard'), 'dashboard');
|
||||
?>
|
||||
@ -18,7 +19,7 @@ admin_render_header(library_trans('dashboard'), 'dashboard');
|
||||
<div class="card-body">
|
||||
<h6 class="text-uppercase text-secondary small fw-bold"><?= library_trans('total_documents') ?></h6>
|
||||
<div class="d-flex align-items-center justify-content-between mt-3">
|
||||
<h2 class="display-6 fw-bold mb-0 text-primary"><?= number_format($metrics['total_documents']) ?></h2>
|
||||
<h2 class="display-6 fw-bold mb-0 text-primary"><?= number_format((int) ($metrics['total_count'] ?? 0)) ?></h2>
|
||||
<i class="bi bi-file-earmark-text fs-1 text-primary opacity-25"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,7 +30,7 @@ admin_render_header(library_trans('dashboard'), 'dashboard');
|
||||
<div class="card-body">
|
||||
<h6 class="text-uppercase text-secondary small fw-bold"><?= library_trans('public_titles') ?></h6>
|
||||
<div class="d-flex align-items-center justify-content-between mt-3">
|
||||
<h2 class="display-6 fw-bold mb-0 text-success"><?= number_format($metrics['public_documents']) ?></h2>
|
||||
<h2 class="display-6 fw-bold mb-0 text-success"><?= number_format((int) ($metrics['public_count'] ?? 0)) ?></h2>
|
||||
<i class="bi bi-globe fs-1 text-success opacity-25"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,10 +39,10 @@ admin_render_header(library_trans('dashboard'), 'dashboard');
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="text-uppercase text-secondary small fw-bold"><?= library_trans('total_downloads') ?></h6>
|
||||
<h6 class="text-uppercase text-secondary small fw-bold"><?= library_trans('private_titles') ?></h6>
|
||||
<div class="d-flex align-items-center justify-content-between mt-3">
|
||||
<h2 class="display-6 fw-bold mb-0 text-info"><?= number_format($metrics['total_downloads']) ?></h2>
|
||||
<i class="bi bi-download fs-1 text-info opacity-25"></i>
|
||||
<h2 class="display-6 fw-bold mb-0 text-info"><?= number_format((int) ($metrics['private_count'] ?? 0)) ?></h2>
|
||||
<i class="bi bi-lock fs-1 text-info opacity-25"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,28 +68,34 @@ admin_render_header(library_trans('dashboard'), 'dashboard');
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($documents)): ?>
|
||||
<tr><td colspan="4" class="text-center py-5 text-muted">No documents found.</td></tr>
|
||||
<tr><td colspan="4" class="text-center py-5 text-muted"><?= library_trans('no_documents_found') ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (array_slice($documents, 0, 10) as $doc): ?>
|
||||
<?php
|
||||
$title = library_localized_document_title($doc, $lang);
|
||||
$typeLabel = library_localized_value($doc['type_en'] ?? null, $doc['type_ar'] ?? null, $lang);
|
||||
$categoryLabel = library_localized_value($doc['cat_en'] ?? $doc['category'] ?? null, $doc['cat_ar'] ?? $doc['category_ar'] ?? null, $lang);
|
||||
?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="fw-medium text-dark"><?= h($doc['title_en']) ?></div>
|
||||
<div class="small text-muted" dir="rtl"><?= h($doc['title_ar']) ?></div>
|
||||
<div class="fw-medium text-dark" dir="<?= library_text_dir($title, $lang) ?>"><?= h($title) ?></div>
|
||||
<?php if (!empty($doc['author'])): ?>
|
||||
<div class="small text-primary mt-1"><i class="bi bi-person me-1"></i><?= h($doc['author']) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($doc['type_en'])): ?>
|
||||
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 mb-1"><?= h($doc['type_en']) ?></span><br>
|
||||
<?php if ($typeLabel !== ''): ?>
|
||||
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 mb-1" dir="<?= library_text_dir($typeLabel, $lang) ?>"><?= h($typeLabel) ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($categoryLabel !== ''): ?>
|
||||
<span class="badge bg-light text-dark border" dir="<?= library_text_dir($categoryLabel, $lang) ?>">
|
||||
<?= h($categoryLabel) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<span class="badge bg-light text-dark border">
|
||||
<?= h($doc['cat_en'] ?? $doc['category']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-<?= $doc['visibility'] === 'public' ? 'success' : 'secondary' ?>">
|
||||
<?= h($doc['visibility']) ?>
|
||||
<?= h(library_visibility_label($doc['visibility'])) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
|
||||
@ -17,28 +17,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('Both English and Arabic names are required for Category.');
|
||||
throw new RuntimeException(library_trans('category_name_required'));
|
||||
}
|
||||
library_create_category($nameEn, $nameAr);
|
||||
library_set_flash('success', 'Category created successfully.');
|
||||
library_set_flash('success', library_trans('category_created_success'));
|
||||
header('Location: /admin_categories.php');
|
||||
exit;
|
||||
} elseif ($action === 'update_category') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$id || !$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('ID, English name, and Arabic name are required.');
|
||||
throw new RuntimeException(library_trans('category_update_required'));
|
||||
}
|
||||
library_update_category($id, $nameEn, $nameAr);
|
||||
library_set_flash('success', 'Category updated successfully.');
|
||||
library_set_flash('success', library_trans('category_updated_success'));
|
||||
header('Location: /admin_categories.php');
|
||||
exit;
|
||||
} elseif ($action === 'delete_category') {
|
||||
if (!$id) {
|
||||
throw new RuntimeException('Invalid Category ID.');
|
||||
throw new RuntimeException(library_trans('invalid_category_id'));
|
||||
}
|
||||
library_delete_category($id);
|
||||
library_set_flash('success', 'Category deleted successfully.');
|
||||
library_set_flash('success', library_trans('category_deleted_success'));
|
||||
header('Location: /admin_categories.php');
|
||||
exit;
|
||||
}
|
||||
@ -57,6 +57,7 @@ $result = library_get_categories_paginated($search, $limit, $offset);
|
||||
$categories = $result['data'];
|
||||
$totalCategories = $result['total'];
|
||||
$totalPages = (int)ceil($totalCategories / $limit);
|
||||
$lang = library_get_language();
|
||||
|
||||
admin_render_header(library_trans('categories'), 'categories');
|
||||
?>
|
||||
@ -108,8 +109,8 @@ admin_render_header(library_trans('categories'), 'categories');
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="fw-medium text-dark"><?= h($cat['name_en']) ?></div>
|
||||
<small class="text-muted"><?= h($cat['name_ar']) ?></small>
|
||||
<?php $categoryName = library_localized_value($cat['name_en'] ?? null, $cat['name_ar'] ?? null, $lang); ?>
|
||||
<div class="fw-medium text-dark" dir="<?= library_text_dir($categoryName, $lang) ?>"><?= h($categoryName) ?></div>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||
@ -155,7 +156,7 @@ admin_render_header(library_trans('categories'), 'categories');
|
||||
<label class="form-label"><?= library_trans('name_en') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_en" id="cat_name_en" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('cat_name_en', 'cat_name_ar', 'Arabic')" title="Translate to Arabic">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('cat_name_en', 'cat_name_ar', 'Arabic')" title="<?= library_trans('translation_to_arabic') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -164,7 +165,7 @@ admin_render_header(library_trans('categories'), 'categories');
|
||||
<label class="form-label"><?= library_trans('name_ar') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_ar" id="cat_name_ar" dir="rtl" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('cat_name_ar', 'cat_name_en', 'English')" title="Translate to English">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('cat_name_ar', 'cat_name_en', 'English')" title="<?= library_trans('translation_to_english') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -26,23 +26,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
if ($action === 'create_document') {
|
||||
library_create_document($_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []);
|
||||
library_set_flash('success', 'Document created successfully.');
|
||||
library_set_flash('success', library_trans('document_created_success'));
|
||||
header('Location: /admin_documents.php');
|
||||
exit;
|
||||
} elseif ($action === 'update_document') {
|
||||
if (!$id) {
|
||||
throw new RuntimeException('Invalid Document ID.');
|
||||
throw new RuntimeException(library_trans('invalid_document_id'));
|
||||
}
|
||||
library_update_document($id, $_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []);
|
||||
library_set_flash('success', 'Document updated successfully.');
|
||||
library_set_flash('success', library_trans('document_updated_success'));
|
||||
header('Location: /admin_documents.php');
|
||||
exit;
|
||||
} elseif ($action === 'delete_document') {
|
||||
if (!$id) {
|
||||
throw new RuntimeException('Invalid Document ID.');
|
||||
throw new RuntimeException(library_trans('invalid_document_id'));
|
||||
}
|
||||
library_delete_document($id);
|
||||
library_set_flash('success', 'Document deleted successfully.');
|
||||
library_set_flash('success', library_trans('document_deleted_success'));
|
||||
header('Location: /admin_documents.php');
|
||||
exit;
|
||||
}
|
||||
@ -67,13 +67,14 @@ $totalPages = (int)ceil($totalDocuments / $limit);
|
||||
$categories = library_get_categories();
|
||||
$allSubcategories = library_get_subcategories(null);
|
||||
$types = library_get_types();
|
||||
$lang = library_get_language();
|
||||
|
||||
admin_render_header(library_trans('material_entry'), 'documents');
|
||||
?>
|
||||
<!-- Page Content -->
|
||||
<?php if ($errors): ?>
|
||||
<div class="alert alert-danger">
|
||||
<h6 class="alert-heading fw-bold"><i class="bi bi-exclamation-triangle-fill me-2"></i>Submission Error</h6>
|
||||
<h6 class="alert-heading fw-bold"><i class="bi bi-exclamation-triangle-fill me-2"></i><?= library_trans('submission_error') ?></h6>
|
||||
<ul class="mb-0 ps-3">
|
||||
<?php foreach ($errors as $e): ?>
|
||||
<li><?= h($e) ?></li>
|
||||
@ -124,7 +125,7 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($documents)): ?>
|
||||
<tr><td colspan="6" class="text-center py-5 text-muted">No documents found.</td></tr>
|
||||
<tr><td colspan="6" class="text-center py-5 text-muted"><?= library_trans('no_documents_found') ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($documents as $doc): ?>
|
||||
<tr>
|
||||
@ -139,20 +140,25 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-medium text-dark"><?= h($doc['title_en']) ?></div>
|
||||
<div class="small text-muted" dir="rtl"><?= h($doc['title_ar']) ?></div>
|
||||
<?php $title = library_localized_document_title($doc, $lang); ?>
|
||||
<div class="fw-medium text-dark" dir="<?= library_text_dir($title, $lang) ?>"><?= h($title) ?></div>
|
||||
<?php if (!empty($doc['author'])): ?>
|
||||
<div class="small text-primary mt-1"><i class="bi bi-person me-1"></i><?= h($doc['author']) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($doc['type_en'])): ?>
|
||||
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 mb-1"><?= h($doc['type_en']) ?></span><br>
|
||||
<?php $typeLabel = library_localized_value($doc['type_en'] ?? null, $doc['type_ar'] ?? null, $lang); ?>
|
||||
<?php if ($typeLabel !== ''): ?>
|
||||
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 mb-1" dir="<?= library_text_dir($typeLabel, $lang) ?>"><?= h($typeLabel) ?></span><br>
|
||||
<?php endif; ?>
|
||||
<span class="badge bg-light text-dark border"><?= h($doc['cat_en'] ?? $doc['category']) ?></span>
|
||||
<?php if (!empty($doc['sub_en'])): ?>
|
||||
<?php $categoryLabel = library_localized_value($doc['cat_en'] ?? $doc['category'] ?? null, $doc['cat_ar'] ?? $doc['category_ar'] ?? null, $lang); ?>
|
||||
<?php if ($categoryLabel !== ''): ?>
|
||||
<span class="badge bg-light text-dark border" dir="<?= library_text_dir($categoryLabel, $lang) ?>"><?= h($categoryLabel) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php $subcategoryLabel = library_localized_value($doc['sub_en'] ?? $doc['sub_category'] ?? null, $doc['sub_ar'] ?? $doc['sub_category_ar'] ?? null, $lang); ?>
|
||||
<?php if ($subcategoryLabel !== ''): ?>
|
||||
<i class="bi bi-chevron-right text-muted small"></i>
|
||||
<span class="badge bg-light text-dark border"><?= h($doc['sub_en']) ?></span>
|
||||
<span class="badge bg-light text-dark border" dir="<?= library_text_dir($subcategoryLabel, $lang) ?>"><?= h($subcategoryLabel) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= h((string)$doc['publish_year']) ?></td>
|
||||
@ -208,14 +214,14 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<label class="form-label small fw-bold text-uppercase text-muted"><?= library_trans('name_en') ?> <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="title_en" id="doc_title_en" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_title_en', 'doc_title_ar', 'Arabic')"><i class="bi bi-translate"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_title_en', 'doc_title_ar', 'Arabic')" title="<?= library_trans('translation_to_arabic') ?>"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-uppercase text-muted"><?= library_trans('name_ar') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="title_ar" id="doc_title_ar" dir="rtl">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_title_ar', 'doc_title_en', 'English')"><i class="bi bi-translate"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_title_ar', 'doc_title_en', 'English')" title="<?= library_trans('translation_to_english') ?>"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -239,7 +245,8 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<select class="form-select" name="type_id" id="doc_type_id">
|
||||
<option value=""><?= library_trans('select_type') ?></option>
|
||||
<?php foreach ($types as $t): ?>
|
||||
<option value="<?= $t['id'] ?>"><?= h($t['name_en']) ?> / <?= h($t['name_ar']) ?></option>
|
||||
<?php $typeName = library_localized_value($t['name_en'] ?? null, $t['name_ar'] ?? null, $lang); ?>
|
||||
<option value="<?= $t['id'] ?>" dir="<?= library_text_dir($typeName, $lang) ?>"><?= h($typeName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@ -248,7 +255,8 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<select class="form-select" name="category_id" id="doc_category_id" onchange="updateSubcategories()">
|
||||
<option value=""><?= library_trans('select_category') ?></option>
|
||||
<?php foreach ($categories as $c): ?>
|
||||
<option value="<?= $c['id'] ?>"><?= h($c['name_en']) ?> / <?= h($c['name_ar']) ?></option>
|
||||
<?php $categoryName = library_localized_value($c['name_en'] ?? null, $c['name_ar'] ?? null, $lang); ?>
|
||||
<option value="<?= $c['id'] ?>" dir="<?= library_text_dir($categoryName, $lang) ?>"><?= h($categoryName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@ -274,14 +282,14 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<label class="form-label small fw-bold text-uppercase text-muted"><?= library_trans('summary_en') ?></label>
|
||||
<div class="input-group">
|
||||
<textarea class="form-control" name="summary_en" id="doc_summary_en" rows="3"></textarea>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_summary_en', 'doc_summary_ar', 'Arabic')"><i class="bi bi-translate"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_summary_en', 'doc_summary_ar', 'Arabic')" title="<?= library_trans('translation_to_arabic') ?>"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-uppercase text-muted"><?= library_trans('summary_ar') ?></label>
|
||||
<div class="input-group">
|
||||
<textarea class="form-control" name="summary_ar" id="doc_summary_ar" rows="3" dir="rtl"></textarea>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_summary_ar', 'doc_summary_en', 'English')"><i class="bi bi-translate"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('doc_summary_ar', 'doc_summary_en', 'English')" title="<?= library_trans('translation_to_english') ?>"><i class="bi bi-translate"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -290,7 +298,7 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<label class="form-label small fw-bold text-uppercase text-muted"><?= library_trans('cover_image') ?></label>
|
||||
<input class="form-control" type="file" name="cover_file" accept="image/*">
|
||||
<div id="current_cover_preview" class="mt-2 d-none">
|
||||
<small class="text-muted d-block mb-1">Current Cover:</small>
|
||||
<small class="text-muted d-block mb-1"><?= library_trans('current_cover') ?></small>
|
||||
<img src="" class="rounded border" style="height: 60px;">
|
||||
</div>
|
||||
</div>
|
||||
@ -311,8 +319,8 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<select class="form-select form-select-sm" name="visibility" id="doc_visibility">
|
||||
<option value="public">Public</option>
|
||||
<option value="private">Private</option>
|
||||
<option value="public"><?= library_trans('visibility_public') ?></option>
|
||||
<option value="private"><?= library_trans('visibility_private') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-9 d-flex align-items-center gap-3">
|
||||
@ -347,6 +355,7 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
|
||||
<script>
|
||||
let documentModal;
|
||||
const currentLang = <?= json_encode($lang) ?>;
|
||||
const allSubcategories = <?= json_encode($allSubcategories) ?>;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@ -372,7 +381,7 @@ admin_render_header(library_trans('material_entry'), 'documents');
|
||||
subs.forEach(s => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s.id;
|
||||
opt.textContent = s.name_en + ' / ' + s.name_ar;
|
||||
opt.textContent = currentLang === 'ar' ? (s.name_ar || s.name_en || '') : (s.name_en || s.name_ar || '');
|
||||
if (selectedSubId && s.id == selectedSubId) opt.selected = true;
|
||||
subSelect.appendChild(opt);
|
||||
});
|
||||
|
||||
@ -18,10 +18,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$catId || !$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('Category, English name, and Arabic name are required.');
|
||||
throw new RuntimeException(library_trans('subcategory_name_required'));
|
||||
}
|
||||
library_create_subcategory($catId, $nameEn, $nameAr);
|
||||
library_set_flash('success', 'Subcategory created successfully.');
|
||||
library_set_flash('success', library_trans('subcategory_created_success'));
|
||||
header('Location: /admin_subcategories.php');
|
||||
exit;
|
||||
} elseif ($action === 'update_subcategory') {
|
||||
@ -29,18 +29,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$id || !$catId || !$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('ID, Category, English name, and Arabic name are required.');
|
||||
throw new RuntimeException(library_trans('subcategory_update_required'));
|
||||
}
|
||||
library_update_subcategory($id, $catId, $nameEn, $nameAr);
|
||||
library_set_flash('success', 'Subcategory updated successfully.');
|
||||
library_set_flash('success', library_trans('subcategory_updated_success'));
|
||||
header('Location: /admin_subcategories.php');
|
||||
exit;
|
||||
} elseif ($action === 'delete_subcategory') {
|
||||
if (!$id) {
|
||||
throw new RuntimeException('Invalid Subcategory ID.');
|
||||
throw new RuntimeException(library_trans('invalid_subcategory_id'));
|
||||
}
|
||||
library_delete_subcategory($id);
|
||||
library_set_flash('success', 'Subcategory deleted successfully.');
|
||||
library_set_flash('success', library_trans('subcategory_deleted_success'));
|
||||
header('Location: /admin_subcategories.php');
|
||||
exit;
|
||||
}
|
||||
@ -63,6 +63,7 @@ $result = library_get_subcategories_paginated(null, $search, $limit, $offset);
|
||||
$allSubcategories = $result['data'];
|
||||
$totalSubcategories = $result['total'];
|
||||
$totalPages = (int)ceil($totalSubcategories / $limit);
|
||||
$lang = library_get_language();
|
||||
|
||||
admin_render_header(library_trans('subcategories'), 'subcategories');
|
||||
?>
|
||||
@ -113,21 +114,21 @@ admin_render_header(library_trans('subcategories'), 'subcategories');
|
||||
<tr><td colspan="3" class="text-center py-5 text-muted"><?= library_trans('no_subcategories_found') ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($allSubcategories as $sub):
|
||||
$parentName = 'Unknown';
|
||||
$parentName = library_trans('unknown');
|
||||
foreach ($categories as $c) {
|
||||
if ($c['id'] == $sub['category_id']) {
|
||||
$parentName = $c['name_en'] . ' / ' . $c['name_ar']; // Show bilingual name for context
|
||||
$parentName = library_localized_value($c['name_en'] ?? null, $c['name_ar'] ?? null, $lang, library_trans('unknown'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="fw-medium text-dark"><?= h($sub['name_en']) ?></div>
|
||||
<small class="text-muted"><?= h($sub['name_ar']) ?></small>
|
||||
<?php $subName = library_localized_value($sub['name_en'] ?? null, $sub['name_ar'] ?? null, $lang); ?>
|
||||
<div class="fw-medium text-dark" dir="<?= library_text_dir($subName, $lang) ?>"><?= h($subName) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border"><?= h($parentName) ?></span>
|
||||
<span class="badge bg-light text-dark border" dir="<?= library_text_dir($parentName, $lang) ?>"><?= h($parentName) ?></span>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||
@ -175,7 +176,8 @@ admin_render_header(library_trans('subcategories'), 'subcategories');
|
||||
<select class="form-select" name="category_id" id="sub_category_id" required>
|
||||
<option value=""><?= library_trans('select_category') ?></option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= h($cat['name_en']) ?> / <?= h($cat['name_ar']) ?></option>
|
||||
<?php $categoryName = library_localized_value($cat['name_en'] ?? null, $cat['name_ar'] ?? null, $lang); ?>
|
||||
<option value="<?= $cat['id'] ?>" dir="<?= library_text_dir($categoryName, $lang) ?>"><?= h($categoryName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@ -183,7 +185,7 @@ admin_render_header(library_trans('subcategories'), 'subcategories');
|
||||
<label class="form-label"><?= library_trans('name_en') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_en" id="sub_name_en" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('sub_name_en', 'sub_name_ar', 'Arabic')" title="Translate to Arabic">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('sub_name_en', 'sub_name_ar', 'Arabic')" title="<?= library_trans('translation_to_arabic') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -192,7 +194,7 @@ admin_render_header(library_trans('subcategories'), 'subcategories');
|
||||
<label class="form-label"><?= library_trans('name_ar') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_ar" id="sub_name_ar" dir="rtl" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('sub_name_ar', 'sub_name_en', 'English')" title="Translate to English">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('sub_name_ar', 'sub_name_en', 'English')" title="<?= library_trans('translation_to_english') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -17,28 +17,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('Both English and Arabic names are required for Type.');
|
||||
throw new RuntimeException(library_trans('type_name_required'));
|
||||
}
|
||||
library_create_type($nameEn, $nameAr);
|
||||
library_set_flash('success', 'Type created successfully.');
|
||||
library_set_flash('success', library_trans('type_created_success'));
|
||||
header('Location: /admin_types.php');
|
||||
exit;
|
||||
} elseif ($action === 'update_type') {
|
||||
$nameEn = trim($_POST['name_en'] ?? '');
|
||||
$nameAr = trim($_POST['name_ar'] ?? '');
|
||||
if (!$id || !$nameEn || !$nameAr) {
|
||||
throw new RuntimeException('ID, English name, and Arabic name are required.');
|
||||
throw new RuntimeException(library_trans('type_update_required'));
|
||||
}
|
||||
library_update_type($id, $nameEn, $nameAr);
|
||||
library_set_flash('success', 'Type updated successfully.');
|
||||
library_set_flash('success', library_trans('type_updated_success'));
|
||||
header('Location: /admin_types.php');
|
||||
exit;
|
||||
} elseif ($action === 'delete_type') {
|
||||
if (!$id) {
|
||||
throw new RuntimeException('Invalid Type ID.');
|
||||
throw new RuntimeException(library_trans('invalid_type_id'));
|
||||
}
|
||||
library_delete_type($id);
|
||||
library_set_flash('success', 'Type deleted successfully.');
|
||||
library_set_flash('success', library_trans('type_deleted_success'));
|
||||
header('Location: /admin_types.php');
|
||||
exit;
|
||||
}
|
||||
@ -57,6 +57,7 @@ $result = library_get_types_paginated($search, $limit, $offset);
|
||||
$types = $result['data'];
|
||||
$totalTypes = $result['total'];
|
||||
$totalPages = (int)ceil($totalTypes / $limit);
|
||||
$lang = library_get_language();
|
||||
|
||||
admin_render_header(library_trans('types'), 'types');
|
||||
?>
|
||||
@ -108,8 +109,8 @@ admin_render_header(library_trans('types'), 'types');
|
||||
<?php foreach ($types as $type): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="fw-medium text-dark"><?= h($type['name_en']) ?></div>
|
||||
<small class="text-muted"><?= h($type['name_ar']) ?></small>
|
||||
<?php $typeName = library_localized_value($type['name_en'] ?? null, $type['name_ar'] ?? null, $lang); ?>
|
||||
<div class="fw-medium text-dark" dir="<?= library_text_dir($typeName, $lang) ?>"><?= h($typeName) ?></div>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||
@ -155,7 +156,7 @@ admin_render_header(library_trans('types'), 'types');
|
||||
<label class="form-label"><?= library_trans('name_en') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_en" id="type_name_en" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('type_name_en', 'type_name_ar', 'Arabic')" title="Translate to Arabic">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('type_name_en', 'type_name_ar', 'Arabic')" title="<?= library_trans('translation_to_arabic') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -164,7 +165,7 @@ admin_render_header(library_trans('types'), 'types');
|
||||
<label class="form-label"><?= library_trans('name_ar') ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="name_ar" id="type_name_ar" dir="rtl" required>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('type_name_ar', 'type_name_en', 'English')" title="Translate to English">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="translateText('type_name_ar', 'type_name_en', 'English')" title="<?= library_trans('translation_to_english') ?>">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -390,4 +390,103 @@ footer a {
|
||||
|
||||
#flipbook-toolbar button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-card {
|
||||
display: grid;
|
||||
grid-template-columns: 128px minmax(0, 1fr);
|
||||
gap: 1.25rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.catalog-card-media {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.catalog-card-cover {
|
||||
width: 128px;
|
||||
min-width: 128px;
|
||||
height: 176px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-muted);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.catalog-card-cover-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.catalog-card-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.catalog-card-title,
|
||||
.catalog-card-title-alt {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.catalog-card-title-alt {
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
|
||||
.catalog-card-badges {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.catalog-card-meta {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.catalog-card-meta > div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.catalog-card-label {
|
||||
min-width: 3.75rem;
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.catalog-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.catalog-card-media {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.catalog-card-body {
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
.topbar-lang-switch {
|
||||
border-radius: 999px;
|
||||
padding-inline: 0.9rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.topbar-lang-switch:hover,
|
||||
.topbar-lang-switch:focus {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
186
document.php
186
document.php
@ -11,15 +11,105 @@ $context = ($_GET['context'] ?? '') === 'admin' ? 'admin' : 'public';
|
||||
$publicOnly = $context !== 'admin';
|
||||
$document = $documentId > 0 ? library_fetch_document($documentId, $publicOnly) : null;
|
||||
|
||||
$lang = library_get_language();
|
||||
$pageCopy = [
|
||||
'en' => [
|
||||
'not_found_title' => 'Document not found',
|
||||
'not_found_description' => 'The requested library document could not be found.',
|
||||
'not_found_copy' => 'This item is unavailable or private.',
|
||||
'go_back' => 'Go back',
|
||||
'back_to_admin' => 'Back to Admin Studio',
|
||||
'back_to_catalog' => 'Back to catalog',
|
||||
'detail_fallback' => 'Document detail',
|
||||
'meta_description' => 'Read the document online, review its metadata, and see the selected-language summary and description.',
|
||||
'author' => 'Author',
|
||||
'views' => 'Views',
|
||||
'file' => 'File',
|
||||
'unknown_author' => 'Unknown author',
|
||||
'unavailable' => 'Unavailable',
|
||||
'reader_kicker' => 'Online reader',
|
||||
'reader_title' => 'Read in the browser',
|
||||
'open_fullscreen' => 'Open fullscreen',
|
||||
'open_file' => 'Open file',
|
||||
'private_item' => 'Private item',
|
||||
'private_copy' => 'This title is marked as login-required by the admin, so it stays hidden from the public reading experience.',
|
||||
'loading_book' => 'Loading book...',
|
||||
'page' => 'Page',
|
||||
'previous_page' => 'Previous page',
|
||||
'next_page' => 'Next page',
|
||||
'document_stored' => 'Document stored',
|
||||
'document_stored_copy' => 'This file type is stored successfully, but inline reading is optimized for PDF in this first slice.',
|
||||
'download_or_open' => 'Download / open file',
|
||||
'no_file_title' => 'No file attached',
|
||||
'no_file_copy' => 'Upload a file from the Admin Studio to enable reading.',
|
||||
'summary_kicker' => 'AI summary',
|
||||
'summary_title' => 'Quick summary',
|
||||
'regenerate' => 'Regenerate',
|
||||
'generate' => 'Generate',
|
||||
'no_summary' => 'No summary yet for the selected language. Click Generate to create one from the document content.',
|
||||
'metadata_kicker' => 'Metadata',
|
||||
'metadata_title' => 'Catalog notes',
|
||||
'published' => 'Published',
|
||||
'tags' => 'Tags',
|
||||
'size' => 'Size',
|
||||
'description_kicker' => 'Description',
|
||||
'description_title' => 'Source text used by AI',
|
||||
'no_excerpt' => 'No excerpt yet for the selected language.',
|
||||
],
|
||||
'ar' => [
|
||||
'not_found_title' => 'المستند غير موجود',
|
||||
'not_found_description' => 'تعذر العثور على مستند المكتبة المطلوب.',
|
||||
'not_found_copy' => 'هذا العنصر غير متاح أو خاص.',
|
||||
'go_back' => 'العودة',
|
||||
'back_to_admin' => 'العودة إلى استوديو الإدارة',
|
||||
'back_to_catalog' => 'العودة إلى الفهرس',
|
||||
'detail_fallback' => 'تفاصيل المستند',
|
||||
'meta_description' => 'اقرأ المستند عبر الإنترنت، وراجع بياناته الوصفية، وشاهد الملخص والوصف وفق اللغة المختارة.',
|
||||
'author' => 'المؤلف',
|
||||
'views' => 'المشاهدات',
|
||||
'file' => 'الملف',
|
||||
'unknown_author' => 'مؤلف غير معروف',
|
||||
'unavailable' => 'غير متاح',
|
||||
'reader_kicker' => 'القارئ الإلكتروني',
|
||||
'reader_title' => 'اقرأ داخل المتصفح',
|
||||
'open_fullscreen' => 'فتح بملء الشاشة',
|
||||
'open_file' => 'فتح الملف',
|
||||
'private_item' => 'عنصر خاص',
|
||||
'private_copy' => 'هذا العنوان محدد من قبل المشرف كمحتوى يتطلب تسجيل الدخول، لذلك يظل مخفياً عن تجربة القراءة العامة.',
|
||||
'loading_book' => 'جارٍ تحميل الكتاب...',
|
||||
'page' => 'الصفحة',
|
||||
'previous_page' => 'الصفحة السابقة',
|
||||
'next_page' => 'الصفحة التالية',
|
||||
'document_stored' => 'تم حفظ المستند',
|
||||
'document_stored_copy' => 'تم حفظ هذا النوع من الملفات بنجاح، لكن القراءة المضمنة مهيأة لملفات PDF في هذه النسخة الأولى.',
|
||||
'download_or_open' => 'تنزيل / فتح الملف',
|
||||
'no_file_title' => 'لا يوجد ملف مرفق',
|
||||
'no_file_copy' => 'ارفع ملفاً من استوديو الإدارة لتفعيل القراءة.',
|
||||
'summary_kicker' => 'ملخص الذكاء الاصطناعي',
|
||||
'summary_title' => 'ملخص سريع',
|
||||
'regenerate' => 'إعادة التوليد',
|
||||
'generate' => 'توليد',
|
||||
'no_summary' => 'لا يوجد ملخص بعد للغة المختارة. اضغط على توليد لإنشاء ملخص من محتوى المستند.',
|
||||
'metadata_kicker' => 'البيانات الوصفية',
|
||||
'metadata_title' => 'ملاحظات الفهرس',
|
||||
'published' => 'تاريخ النشر',
|
||||
'tags' => 'الوسوم',
|
||||
'size' => 'الحجم',
|
||||
'description_kicker' => 'الوصف',
|
||||
'description_title' => 'النص المصدر المستخدم من الذكاء الاصطناعي',
|
||||
'no_excerpt' => 'لا يوجد مقتطف بعد للغة المختارة.',
|
||||
],
|
||||
][$lang];
|
||||
|
||||
if (!$document) {
|
||||
http_response_code(404);
|
||||
library_render_header('Document not found', 'The requested library document could not be found.', $context === 'admin' ? 'admin' : 'catalog');
|
||||
library_render_header($pageCopy['not_found_title'], $pageCopy['not_found_description'], $context === 'admin' ? 'admin' : 'catalog');
|
||||
?>
|
||||
<section class="panel empty-panel text-center py-5">
|
||||
<div class="empty-icon mb-3">?</div>
|
||||
<h1 class="h4">Document not found</h1>
|
||||
<p class="text-secondary mb-4">This item is unavailable or private.</p>
|
||||
<a class="btn btn-dark" href="<?= $context === 'admin' ? '/admin.php' : '/index.php' ?>">Go back</a>
|
||||
<h1 class="h4"><?= h($pageCopy['not_found_title']) ?></h1>
|
||||
<p class="text-secondary mb-4"><?= h($pageCopy['not_found_copy']) ?></p>
|
||||
<a class="btn btn-dark" href="<?= $context === 'admin' ? '/admin.php' : '/index.php' ?>"><?= h($pageCopy['go_back']) ?></a>
|
||||
</section>
|
||||
<?php
|
||||
library_render_footer();
|
||||
@ -38,14 +128,20 @@ if ($context !== 'admin') {
|
||||
$document = library_fetch_document((int) $document['id'], true) ?: $document;
|
||||
}
|
||||
|
||||
$selectedTitle = library_localized_document_title($document, $lang, $pageCopy['detail_fallback']);
|
||||
$selectedTitleLang = library_text_lang($selectedTitle, $lang);
|
||||
$selectedTitleDir = library_text_dir($selectedTitle, $selectedTitleLang);
|
||||
$selectedSummary = library_localized_document_summary($document, $lang);
|
||||
$selectedDescription = library_localized_document_description($document, $lang, $pageCopy['no_excerpt']);
|
||||
|
||||
library_render_header(
|
||||
(string) ($document['title_en'] ?: $document['title_ar'] ?: 'Document detail'),
|
||||
'Read a library document online, review metadata, and generate a bilingual AI summary from the document content.',
|
||||
$selectedTitle,
|
||||
$pageCopy['meta_description'],
|
||||
$context === 'admin' ? 'admin' : 'catalog'
|
||||
);
|
||||
?>
|
||||
<section class="mb-4">
|
||||
<a class="back-link" href="<?= $context === 'admin' ? '/admin.php' : '/index.php' ?>">← Back to <?= $context === 'admin' ? 'Admin Studio' : 'catalog' ?></a>
|
||||
<a class="back-link" href="<?= $context === 'admin' ? '/admin.php' : '/index.php' ?>">← <?= h($context === 'admin' ? $pageCopy['back_to_admin'] : $pageCopy['back_to_catalog']) ?></a>
|
||||
</section>
|
||||
|
||||
<section class="row g-4 align-items-start">
|
||||
@ -53,12 +149,7 @@ library_render_header(
|
||||
<div class="panel mb-4">
|
||||
<div class="d-flex flex-wrap justify-content-between gap-3 mb-3">
|
||||
<div>
|
||||
<?php if (!empty($document['title_en'])): ?>
|
||||
<h1 class="display-6 mb-1"><?= h((string) $document['title_en']) ?></h1>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($document['title_ar'])): ?>
|
||||
<div class="lead text-secondary" dir="rtl" lang="ar"><?= h((string) $document['title_ar']) ?></div>
|
||||
<?php endif; ?>
|
||||
<h1 class="display-6 mb-1" lang="<?= h($selectedTitleLang) ?>" dir="<?= h($selectedTitleDir) ?>"><?= h($selectedTitle) ?></h1>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 align-content-start">
|
||||
<span class="badge text-bg-light"><?= h(library_language_label((string) $document['document_language'])) ?></span>
|
||||
@ -68,38 +159,38 @@ library_render_header(
|
||||
</div>
|
||||
|
||||
<div class="row g-3 small text-secondary border-top pt-3">
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1">Author</strong><?= h((string) ($document['author'] ?: 'Unknown author')) ?></div>
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1">Views</strong><?= h((string) $document['view_count']) ?></div>
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1">File</strong><?= h((string) ($document['file_name'] ?: 'Unavailable')) ?></div>
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1"><?= h($pageCopy['author']) ?></strong><?= h((string) ($document['author'] ?: $pageCopy['unknown_author'])) ?></div>
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1"><?= h($pageCopy['views']) ?></strong><?= h((string) $document['view_count']) ?></div>
|
||||
<div class="col-md-4"><strong class="text-dark d-block mb-1"><?= h($pageCopy['file']) ?></strong><?= h((string) ($document['file_name'] ?: $pageCopy['unavailable'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel reader-panel">
|
||||
<div class="d-flex justify-content-between align-items-center gap-3 mb-3">
|
||||
<div>
|
||||
<div class="section-kicker">Online reader</div>
|
||||
<h2 class="h4 mb-0">Read in the browser</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['reader_kicker']) ?></div>
|
||||
<h2 class="h4 mb-0"><?= h($pageCopy['reader_title']) ?></h2>
|
||||
</div>
|
||||
<?php if (!empty($document['file_path'])): ?>
|
||||
<?php if (library_can_preview($document)): ?>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="/viewer.php?id=<?= $document['id'] ?><?= $context === 'admin' ? '&context=admin' : '' ?>" target="_blank">Open fullscreen</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="/viewer.php?id=<?= $document['id'] ?><?= $context === 'admin' ? '&context=admin' : '' ?>" target="_blank"><?= h($pageCopy['open_fullscreen']) ?></a>
|
||||
<?php else: ?>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= h(library_file_url((string) $document['file_path'])) ?>" target="_blank" rel="noopener">Open file</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= h(library_file_url((string) $document['file_path'])) ?>" target="_blank" rel="noopener"><?= h($pageCopy['open_file']) ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($document['visibility'] === 'private' && $context !== 'admin'): ?>
|
||||
<div class="reader-lock">
|
||||
<h3 class="h5 mb-2">Private item</h3>
|
||||
<p class="text-secondary mb-0">This title is marked as login-required by the admin, so it stays hidden from the public reading experience.</p>
|
||||
<h3 class="h5 mb-2"><?= h($pageCopy['private_item']) ?></h3>
|
||||
<p class="text-secondary mb-0"><?= h($pageCopy['private_copy']) ?></p>
|
||||
</div>
|
||||
<?php elseif (library_can_preview($document)): ?>
|
||||
<!-- Flipbook Container -->
|
||||
<div id="flipbook-wrapper" data-book-direction="<?= library_get_language() === 'ar' ? 'rtl' : 'ltr' ?>" dir="ltr" style="position: relative; background: #2d3035; border-radius: 8px; overflow: hidden; height: 700px; display: flex; align-items: center; justify-content: center; direction: ltr;">
|
||||
<div id="flipbook-loader" class="text-center text-white">
|
||||
<div class="spinner-border mb-2" role="status"></div>
|
||||
<div>Loading Book...</div>
|
||||
<div><?= h($pageCopy['loading_book']) ?></div>
|
||||
</div>
|
||||
<!-- The actual book container for PageFlip -->
|
||||
<div id="flipbook" class="shadow-lg" dir="ltr" style="display:none; direction: ltr;"></div>
|
||||
@ -107,12 +198,12 @@ library_render_header(
|
||||
|
||||
<!-- Custom Toolbar -->
|
||||
<div id="flipbook-toolbar" class="d-flex justify-content-center align-items-center gap-3 mt-3 p-2 bg-white border rounded shadow-sm opacity-50 pe-none">
|
||||
<button id="fb-prev" class="btn btn-outline-dark border-0 btn-lg" title="Previous Page">
|
||||
<button id="fb-prev" class="btn btn-outline-dark border-0 btn-lg" title="<?= h($pageCopy['previous_page']) ?>">
|
||||
<i class="bi bi-arrow-left-circle-fill"></i>
|
||||
</button>
|
||||
|
||||
<div class="text-center" style="min-width: 120px;">
|
||||
<span class="small text-uppercase text-secondary fw-bold" style="letter-spacing: 1px;">Page</span>
|
||||
<span class="small text-uppercase text-secondary fw-bold" style="letter-spacing: 1px;"><?= h($pageCopy['page']) ?></span>
|
||||
<div class="d-flex align-items-baseline justify-content-center gap-1">
|
||||
<span id="fb-current" class="fs-5 fw-bold font-monospace">1</span>
|
||||
<span class="text-muted">/</span>
|
||||
@ -120,21 +211,21 @@ library_render_header(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="fb-next" class="btn btn-outline-dark border-0 btn-lg" title="Next Page">
|
||||
<button id="fb-next" class="btn btn-outline-dark border-0 btn-lg" title="<?= h($pageCopy['next_page']) ?>">
|
||||
<i class="bi bi-arrow-right-circle-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php elseif (!empty($document['file_path'])): ?>
|
||||
<div class="reader-lock">
|
||||
<h3 class="h5 mb-2">Document stored</h3>
|
||||
<p class="text-secondary mb-3">This file type is stored successfully, but inline reading is optimized for PDF in this first slice.</p>
|
||||
<a class="btn btn-dark" href="<?= h(library_file_url((string) $document['file_path'])) ?>" target="_blank" rel="noopener">Download / open file</a>
|
||||
<h3 class="h5 mb-2"><?= h($pageCopy['document_stored']) ?></h3>
|
||||
<p class="text-secondary mb-3"><?= h($pageCopy['document_stored_copy']) ?></p>
|
||||
<a class="btn btn-dark" href="<?= h(library_file_url((string) $document['file_path'])) ?>" target="_blank" rel="noopener"><?= h($pageCopy['download_or_open']) ?></a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="reader-lock">
|
||||
<h3 class="h5 mb-2">No file attached</h3>
|
||||
<p class="text-secondary mb-0">Upload a file from the Admin Studio to enable reading.</p>
|
||||
<h3 class="h5 mb-2"><?= h($pageCopy['no_file_title']) ?></h3>
|
||||
<p class="text-secondary mb-0"><?= h($pageCopy['no_file_copy']) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -144,46 +235,41 @@ library_render_header(
|
||||
<div class="panel mb-4" id="summary-card">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
||||
<div>
|
||||
<div class="section-kicker">AI summary</div>
|
||||
<h2 class="h4 mb-0">Bilingual quick summary</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['summary_kicker']) ?></div>
|
||||
<h2 class="h4 mb-0"><?= h($pageCopy['summary_title']) ?></h2>
|
||||
</div>
|
||||
<form method="post" action="/document.php?id=<?= h((string) $document['id']) ?><?= $context === 'admin' ? '&context=admin' : '' ?>#summary-card">
|
||||
<input type="hidden" name="action" value="generate_summary">
|
||||
<button class="btn btn-dark btn-sm" type="submit"><?= !empty($document['summary_text']) ? 'Regenerate' : 'Generate' ?></button>
|
||||
<button class="btn btn-dark btn-sm" type="submit"><?= $selectedSummary !== '' ? h($pageCopy['regenerate']) : h($pageCopy['generate']) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($document['summary_text'])): ?>
|
||||
<div class="summary-box"><?= nl2br(h((string) $document['summary_text'])) ?></div>
|
||||
<?php if ($selectedSummary !== ''): ?>
|
||||
<div class="summary-box" lang="<?= h($lang) ?>" dir="<?= $lang === 'ar' ? 'rtl' : 'ltr' ?>"><?= nl2br(h($selectedSummary)) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="summary-box summary-box-muted">No AI summary yet. Click "Generate" to create a bilingual summary from the document content.</div>
|
||||
<div class="summary-box summary-box-muted"><?= h($pageCopy['no_summary']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="panel mb-4">
|
||||
<div class="section-kicker">Metadata</div>
|
||||
<h2 class="h5 mb-3">Catalog notes</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['metadata_kicker']) ?></div>
|
||||
<h2 class="h5 mb-3"><?= h($pageCopy['metadata_title']) ?></h2>
|
||||
<dl class="row small gy-2 mb-0">
|
||||
<dt class="col-4">Published</dt>
|
||||
<dt class="col-4"><?= h($pageCopy['published']) ?></dt>
|
||||
<dd class="col-8 mb-0"><?= h(date('M d, Y', strtotime((string) $document['created_at']))) ?></dd>
|
||||
<dt class="col-4">Tags</dt>
|
||||
<dt class="col-4"><?= h($pageCopy['tags']) ?></dt>
|
||||
<dd class="col-8 mb-0"><?= h((string) ($document['tags'] ?: '—')) ?></dd>
|
||||
<dt class="col-4">Size</dt>
|
||||
<dt class="col-4"><?= h($pageCopy['size']) ?></dt>
|
||||
<dd class="col-8 mb-0"><?= h((string) ($document['file_size_kb'] ?: 0)) ?> KB</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="section-kicker">Descriptions</div>
|
||||
<h2 class="h5 mb-3">Source text used by AI</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['description_kicker']) ?></div>
|
||||
<h2 class="h5 mb-3"><?= h($pageCopy['description_title']) ?></h2>
|
||||
<div class="description-stack">
|
||||
<div>
|
||||
<div class="small text-uppercase text-secondary mb-2">English</div>
|
||||
<p class="mb-0 text-secondary"><?= h((string) ($document['description_en'] ?: 'No English excerpt yet.')) ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-uppercase text-secondary mb-2">العربية</div>
|
||||
<p class="mb-0 text-secondary" dir="rtl" lang="ar"><?= h((string) ($document['description_ar'] ?: 'لا يوجد مقتطف عربي حتى الآن.')) ?></p>
|
||||
<p class="mb-0 text-secondary" lang="<?= h($lang) ?>" dir="<?= $lang === 'ar' ? 'rtl' : 'ltr' ?>"><?= h($selectedDescription) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -117,12 +117,12 @@ function admin_render_footer(): void {
|
||||
const text = source.value.trim();
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter text to translate.');
|
||||
alert(<?= json_encode(library_trans('translation_enter_text')) ?>);
|
||||
return;
|
||||
}
|
||||
|
||||
const originalPlaceholder = target.placeholder;
|
||||
target.placeholder = 'Translating...';
|
||||
target.placeholder = <?= json_encode(library_trans('translating')) ?>;
|
||||
const originalOpacity = target.style.opacity;
|
||||
target.style.opacity = '0.7';
|
||||
|
||||
@ -133,17 +133,17 @@ function admin_render_footer(): void {
|
||||
body: JSON.stringify({ text: text, target_lang: targetLang })
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Translation failed');
|
||||
if (!response.ok) throw new Error(<?= json_encode(library_trans('translation_failed')) ?>);
|
||||
|
||||
const data = await response.json();
|
||||
if (data.translation) {
|
||||
target.value = data.translation;
|
||||
} else if (data.error) {
|
||||
alert('Translation error: ' + data.error);
|
||||
alert(<?= json_encode(library_trans('translation_error_prefix')) ?> + ' ' + data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Translation failed. Please try again.');
|
||||
alert(<?= json_encode(library_trans('translation_failed')) ?>);
|
||||
} finally {
|
||||
target.placeholder = originalPlaceholder;
|
||||
target.style.opacity = originalOpacity;
|
||||
|
||||
@ -78,7 +78,7 @@ function library_render_header(string $pageTitle, string $pageDescription, strin
|
||||
<li class="nav-item border-start border-secondary mx-2 d-none d-lg-block" style="opacity: 0.3; height: 24px;"></li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fw-bold text-primary" href="<?= h($switchUrl) ?>">
|
||||
<a class="btn btn-sm btn-outline-dark topbar-lang-switch" href="<?= h($switchUrl) ?>" lang="<?= h($targetLang) ?>" dir="<?= $targetLang === 'ar' ? 'rtl' : 'ltr' ?>">
|
||||
<?= h($switchLabel) ?>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -69,6 +69,7 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'return_to_site' => 'Return to Site',
|
||||
'total_documents' => 'Total Documents',
|
||||
'public_titles' => 'Public Titles',
|
||||
'private_titles' => 'Private Titles',
|
||||
'total_downloads' => 'Total Downloads',
|
||||
'recent_documents' => 'Recent Documents',
|
||||
'title_author' => 'Title / Author',
|
||||
@ -128,7 +129,41 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'previous' => 'Previous',
|
||||
'next' => 'Next',
|
||||
'showing_page' => 'Showing page',
|
||||
'of' => 'of'
|
||||
'of' => 'of',
|
||||
'no_documents_found' => 'No documents found.',
|
||||
'submission_error' => 'Submission Error',
|
||||
'current_cover' => 'Current Cover:',
|
||||
'visibility_public' => 'Public',
|
||||
'visibility_private' => 'Private',
|
||||
'unknown' => 'Unknown',
|
||||
'translation_to_arabic' => 'Translate to Arabic',
|
||||
'translation_to_english' => 'Translate to English',
|
||||
'translation_enter_text' => 'Please enter text to translate.',
|
||||
'translating' => 'Translating...',
|
||||
'translation_failed' => 'Translation failed. Please try again.',
|
||||
'translation_error_prefix' => 'Translation error:',
|
||||
'category_name_required' => 'Both English and Arabic names are required for Category.',
|
||||
'category_update_required' => 'ID, English name, and Arabic name are required.',
|
||||
'invalid_category_id' => 'Invalid Category ID.',
|
||||
'category_created_success' => 'Category created successfully.',
|
||||
'category_updated_success' => 'Category updated successfully.',
|
||||
'category_deleted_success' => 'Category deleted successfully.',
|
||||
'subcategory_name_required' => 'Category, English name, and Arabic name are required.',
|
||||
'subcategory_update_required' => 'ID, Category, English name, and Arabic name are required.',
|
||||
'invalid_subcategory_id' => 'Invalid Subcategory ID.',
|
||||
'subcategory_created_success' => 'Subcategory created successfully.',
|
||||
'subcategory_updated_success' => 'Subcategory updated successfully.',
|
||||
'subcategory_deleted_success' => 'Subcategory deleted successfully.',
|
||||
'type_name_required' => 'Both English and Arabic names are required for Type.',
|
||||
'type_update_required' => 'ID, English name, and Arabic name are required.',
|
||||
'invalid_type_id' => 'Invalid Type ID.',
|
||||
'type_created_success' => 'Type created successfully.',
|
||||
'type_updated_success' => 'Type updated successfully.',
|
||||
'type_deleted_success' => 'Type deleted successfully.',
|
||||
'invalid_document_id' => 'Invalid Document ID.',
|
||||
'document_created_success' => 'Document created successfully.',
|
||||
'document_updated_success' => 'Document updated successfully.',
|
||||
'document_deleted_success' => 'Document deleted successfully.'
|
||||
],
|
||||
'ar' => [
|
||||
'catalog' => 'الفهرس',
|
||||
@ -145,12 +180,13 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'admin_panel' => 'لوحة التحكم',
|
||||
'dashboard' => 'لوحة القيادة',
|
||||
'material_entry' => 'إدخال المواد',
|
||||
'categories' => 'الفئات',
|
||||
'subcategories' => 'الفئات الفرعية',
|
||||
'categories' => 'التصنيف',
|
||||
'subcategories' => 'التصنيف الفرعي',
|
||||
'types' => 'الأنواع',
|
||||
'return_to_site' => 'العودة للموقع',
|
||||
'total_documents' => 'إجمالي المستندات',
|
||||
'public_titles' => 'العناوين العامة',
|
||||
'private_titles' => 'العناوين الخاصة',
|
||||
'total_downloads' => 'إجمالي التنزيلات',
|
||||
'recent_documents' => 'أحدث المستندات',
|
||||
'title_author' => 'العنوان / المؤلف',
|
||||
@ -171,7 +207,7 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'description_en' => 'الوصف (إنجليزي)',
|
||||
'description_ar' => 'الوصف (عربي)',
|
||||
'confirm_delete' => 'هل أنت متأكد من حذف هذا العنصر؟',
|
||||
'manage_categories_desc' => 'إدارة فئات المستندات.',
|
||||
'manage_categories_desc' => 'إدارة التصنيف للمستندات',
|
||||
'add_new_category' => 'إضافة فئة جديدة',
|
||||
'search_placeholder' => 'بحث...',
|
||||
'clear' => 'مسح',
|
||||
@ -200,7 +236,7 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'required_new_docs' => 'مطلوب للمستندات الجديدة.',
|
||||
'leave_empty_keep' => 'اتركه فارغاً للاحتفاظ بالملف الحالي.',
|
||||
'save_changes' => 'حفظ التغييرات',
|
||||
'manage_subcategories_desc' => 'إدارة الفئات الفرعية للمستندات.',
|
||||
'manage_subcategories_desc' => 'إدارة التصنيف الفرعي للمستندات.',
|
||||
'add_new_subcategory' => 'إضافة فئة فرعية جديدة',
|
||||
'no_subcategories_found' => 'لم يتم العثور على فئات فرعية.',
|
||||
'category' => 'الفئة',
|
||||
@ -210,7 +246,41 @@ function library_trans(string $key, ?string $lang = null): string
|
||||
'previous' => 'السابق',
|
||||
'next' => 'التالي',
|
||||
'showing_page' => 'عرض صفحة',
|
||||
'of' => 'من'
|
||||
'of' => 'من',
|
||||
'no_documents_found' => 'لم يتم العثور على مستندات.',
|
||||
'submission_error' => 'خطأ في الإرسال',
|
||||
'current_cover' => 'الغلاف الحالي:',
|
||||
'visibility_public' => 'عام',
|
||||
'visibility_private' => 'خاص',
|
||||
'unknown' => 'غير معروف',
|
||||
'translation_to_arabic' => 'ترجمة إلى العربية',
|
||||
'translation_to_english' => 'ترجمة إلى الإنجليزية',
|
||||
'translation_enter_text' => 'يرجى إدخال نص للترجمة.',
|
||||
'translating' => 'جارٍ الترجمة...',
|
||||
'translation_failed' => 'فشلت الترجمة. يرجى المحاولة مرة أخرى.',
|
||||
'translation_error_prefix' => 'خطأ في الترجمة:',
|
||||
'category_name_required' => 'اسما التصنيف بالإنجليزية والعربية مطلوبان.',
|
||||
'category_update_required' => 'المعرف واسم التصنيف بالإنجليزية والعربية مطلوبة.',
|
||||
'invalid_category_id' => 'معرف التصنيف غير صالح.',
|
||||
'category_created_success' => 'تم إنشاء التصنيف بنجاح.',
|
||||
'category_updated_success' => 'تم تحديث التصنيف بنجاح.',
|
||||
'category_deleted_success' => 'تم حذف التصنيف بنجاح.',
|
||||
'subcategory_name_required' => 'التصنيف واسم التصنيف الفرعي بالإنجليزية والعربية مطلوبة.',
|
||||
'subcategory_update_required' => 'المعرف والتصنيف واسم التصنيف الفرعي بالإنجليزية والعربية مطلوبة.',
|
||||
'invalid_subcategory_id' => 'معرف التصنيف الفرعي غير صالح.',
|
||||
'subcategory_created_success' => 'تم إنشاء التصنيف الفرعي بنجاح.',
|
||||
'subcategory_updated_success' => 'تم تحديث التصنيف الفرعي بنجاح.',
|
||||
'subcategory_deleted_success' => 'تم حذف التصنيف الفرعي بنجاح.',
|
||||
'type_name_required' => 'اسما النوع بالإنجليزية والعربية مطلوبان.',
|
||||
'type_update_required' => 'المعرف واسم النوع بالإنجليزية والعربية مطلوبة.',
|
||||
'invalid_type_id' => 'معرف النوع غير صالح.',
|
||||
'type_created_success' => 'تم إنشاء النوع بنجاح.',
|
||||
'type_updated_success' => 'تم تحديث النوع بنجاح.',
|
||||
'type_deleted_success' => 'تم حذف النوع بنجاح.',
|
||||
'invalid_document_id' => 'معرف المستند غير صالح.',
|
||||
'document_created_success' => 'تم إنشاء المستند بنجاح.',
|
||||
'document_updated_success' => 'تم تحديث المستند بنجاح.',
|
||||
'document_deleted_success' => 'تم حذف المستند بنجاح.'
|
||||
]
|
||||
];
|
||||
|
||||
@ -341,31 +411,110 @@ function library_old(string $key, string $default = ''): string
|
||||
return isset($_POST[$key]) ? trim((string) $_POST[$key]) : $default;
|
||||
}
|
||||
|
||||
function library_localized_value(?string $english, ?string $arabic, ?string $lang = null, string $fallback = ''): string
|
||||
{
|
||||
$lang = $lang ?? library_get_language();
|
||||
$primary = trim((string) ($lang === 'ar' ? $arabic : $english));
|
||||
$secondary = trim((string) ($lang === 'ar' ? $english : $arabic));
|
||||
|
||||
if ($primary !== '') {
|
||||
return $primary;
|
||||
}
|
||||
|
||||
if ($secondary !== '') {
|
||||
return $secondary;
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
function library_localized_document_title(array $document, ?string $lang = null, string $fallback = 'Untitled'): string
|
||||
{
|
||||
return library_localized_value(
|
||||
$document['title_en'] ?? null,
|
||||
$document['title_ar'] ?? null,
|
||||
$lang,
|
||||
$fallback
|
||||
);
|
||||
}
|
||||
|
||||
function library_localized_document_description(array $document, ?string $lang = null, string $fallback = ''): string
|
||||
{
|
||||
return library_localized_value(
|
||||
$document['description_en'] ?? null,
|
||||
$document['description_ar'] ?? null,
|
||||
$lang,
|
||||
$fallback
|
||||
);
|
||||
}
|
||||
|
||||
function library_localized_document_summary(array $document, ?string $lang = null, string $fallback = ''): string
|
||||
{
|
||||
$summary = library_localized_value(
|
||||
$document['summary_en'] ?? null,
|
||||
$document['summary_ar'] ?? null,
|
||||
$lang,
|
||||
''
|
||||
);
|
||||
|
||||
if ($summary !== '') {
|
||||
return $summary;
|
||||
}
|
||||
|
||||
$combined = trim((string) ($document['summary_text'] ?? ''));
|
||||
return $combined !== '' ? $combined : $fallback;
|
||||
}
|
||||
|
||||
function library_document_type_label(string $type): string
|
||||
{
|
||||
$lang = library_get_language();
|
||||
$map = [
|
||||
'pdf' => 'PDF reader',
|
||||
'txt' => 'Text note',
|
||||
'doc' => 'Word document',
|
||||
'docx' => 'Word document',
|
||||
'ppt' => 'PowerPoint',
|
||||
'pptx' => 'PowerPoint',
|
||||
'en' => [
|
||||
'pdf' => 'PDF reader',
|
||||
'txt' => 'Text note',
|
||||
'doc' => 'Word document',
|
||||
'docx' => 'Word document',
|
||||
'ppt' => 'PowerPoint',
|
||||
'pptx' => 'PowerPoint',
|
||||
],
|
||||
'ar' => [
|
||||
'pdf' => 'قارئ PDF',
|
||||
'txt' => 'ملاحظة نصية',
|
||||
'doc' => 'مستند Word',
|
||||
'docx' => 'مستند Word',
|
||||
'ppt' => 'عرض PowerPoint',
|
||||
'pptx' => 'عرض PowerPoint',
|
||||
],
|
||||
];
|
||||
return $map[strtolower($type)] ?? strtoupper($type);
|
||||
|
||||
return $map[$lang][strtolower($type)] ?? strtoupper($type);
|
||||
}
|
||||
|
||||
function library_language_label(string $lang): string
|
||||
{
|
||||
$uiLang = library_get_language();
|
||||
$map = [
|
||||
'en' => 'English',
|
||||
'ar' => 'Arabic',
|
||||
'bilingual' => 'Bilingual',
|
||||
'en' => [
|
||||
'en' => 'English',
|
||||
'ar' => 'Arabic',
|
||||
'bilingual' => 'Bilingual',
|
||||
],
|
||||
'ar' => [
|
||||
'en' => 'الإنجليزية',
|
||||
'ar' => 'العربية',
|
||||
'bilingual' => 'ثنائي اللغة',
|
||||
],
|
||||
];
|
||||
return $map[$lang] ?? 'Unknown';
|
||||
|
||||
return $map[$uiLang][$lang] ?? ($uiLang === 'ar' ? 'غير معروف' : 'Unknown');
|
||||
}
|
||||
|
||||
function library_visibility_label(string $visibility): string
|
||||
{
|
||||
if (library_get_language() === 'ar') {
|
||||
return $visibility === 'private' ? 'خاص / يتطلب تسجيل الدخول' : 'عام';
|
||||
}
|
||||
|
||||
return $visibility === 'private' ? 'Private / login' : 'Public';
|
||||
}
|
||||
|
||||
|
||||
295
index.php
295
index.php
@ -6,66 +6,176 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
library_bootstrap();
|
||||
|
||||
$lang = library_get_language();
|
||||
$query = trim((string) ($_GET['q'] ?? ''));
|
||||
$language = trim((string) ($_GET['language'] ?? ''));
|
||||
|
||||
$pageCopy = [
|
||||
'en' => [
|
||||
'meta_title' => 'Digital Catalog',
|
||||
'meta_description' => 'Browse the public digital catalog, switch the interface language from the top bar, and open documents in the browser.',
|
||||
'hero_eyebrow' => 'Electronic library · one language per view',
|
||||
'hero_title' => 'A cleaner catalog with a top-bar language switch.',
|
||||
'hero_copy' => 'The public library now stays in one interface language at a time. Use the switch in the top bar to move between English and Arabic while keeping the same page.',
|
||||
'browse_catalog' => 'Browse catalog',
|
||||
'add_documents' => 'Add documents',
|
||||
'snapshot_kicker' => 'Live shelf snapshot',
|
||||
'snapshot_title' => 'What this delivery includes',
|
||||
'snapshot_badge' => 'Updated UI',
|
||||
'public_titles' => 'Public titles',
|
||||
'private_titles' => 'Private titles',
|
||||
'ai_summaries' => 'AI summaries',
|
||||
'snapshot_item_1' => 'Single-language public pages with a top-bar switch',
|
||||
'snapshot_item_2' => 'Catalog filters for keyword and content language',
|
||||
'snapshot_item_3' => 'Document pages with localized title, summary, and description',
|
||||
'discovery_kicker' => 'Public discovery',
|
||||
'discovery_title' => 'Search the live collection',
|
||||
'keyword' => 'Keyword',
|
||||
'keyword_placeholder' => 'Title, author, tag, or excerpt',
|
||||
'language_filter' => 'Content language',
|
||||
'all_shelves' => 'All shelves',
|
||||
'filter' => 'Filter',
|
||||
'rules_kicker' => 'Visibility rules',
|
||||
'rules_title' => 'Admin-controlled access',
|
||||
'rules_copy' => 'Public items appear in this catalog immediately. Private items stay off the public shelf and remain available only from the admin workspace.',
|
||||
'rules_link' => 'Review publishing controls',
|
||||
'catalog_kicker' => 'Catalog',
|
||||
'catalog_title' => 'Available public titles',
|
||||
'result_singular' => 'result',
|
||||
'result_plural' => 'results',
|
||||
'empty_title' => 'No public documents yet',
|
||||
'empty_copy' => 'Upload your first Arabic or English document from the Admin Studio to turn this into a browsable library.',
|
||||
'open_admin' => 'Open Admin Studio',
|
||||
'no_cover' => 'No cover',
|
||||
'untitled' => 'Untitled document',
|
||||
'cover_alt' => 'Cover image for',
|
||||
'author' => 'Author',
|
||||
'views' => 'Views',
|
||||
'tags' => 'Tags',
|
||||
'author_fallback' => 'Not set',
|
||||
'open_reader' => 'Open reader →',
|
||||
'workflow_kicker' => 'Workflow',
|
||||
'workflow_title' => 'Thin slice, end to end',
|
||||
'workflow_item_1' => 'Admin uploads a document and chooses public or private visibility.',
|
||||
'workflow_item_2' => 'Readers discover public titles from the catalog and open the detail page.',
|
||||
'workflow_item_3' => 'Summaries and descriptions follow the selected interface language.',
|
||||
'recent_kicker' => 'Recently added',
|
||||
'recent_title' => 'Latest public titles',
|
||||
'manage_shelf' => 'Manage shelf',
|
||||
'unknown_author' => 'Unknown author',
|
||||
],
|
||||
'ar' => [
|
||||
'meta_title' => 'الفهرس الرقمي',
|
||||
'meta_description' => 'تصفح الفهرس الرقمي العام، وبدّل لغة الواجهة من الشريط العلوي، وافتح المستندات داخل المتصفح.',
|
||||
'hero_eyebrow' => 'مكتبة إلكترونية · لغة واحدة لكل عرض',
|
||||
'hero_title' => 'فهرس أوضح مع مفتاح تبديل اللغة في الشريط العلوي.',
|
||||
'hero_copy' => 'تعرض المكتبة العامة الآن لغة واجهة واحدة في كل مرة. استخدم المفتاح في الشريط العلوي للتبديل بين العربية والإنجليزية مع البقاء في الصفحة نفسها.',
|
||||
'browse_catalog' => 'تصفح الفهرس',
|
||||
'add_documents' => 'إضافة مستندات',
|
||||
'snapshot_kicker' => 'نظرة مباشرة على الرف',
|
||||
'snapshot_title' => 'ما الذي يتضمنه هذا التحديث',
|
||||
'snapshot_badge' => 'واجهة محدثة',
|
||||
'public_titles' => 'العناوين العامة',
|
||||
'private_titles' => 'العناوين الخاصة',
|
||||
'ai_summaries' => 'ملخصات الذكاء الاصطناعي',
|
||||
'snapshot_item_1' => 'صفحات عامة بلغة واحدة مع مفتاح تبديل في الشريط العلوي',
|
||||
'snapshot_item_2' => 'مرشحات للفهرس حسب الكلمة المفتاحية ولغة المحتوى',
|
||||
'snapshot_item_3' => 'صفحات مستندات بعنوان وملخص ووصف حسب اللغة المختارة',
|
||||
'discovery_kicker' => 'الاكتشاف العام',
|
||||
'discovery_title' => 'ابحث في المجموعة المباشرة',
|
||||
'keyword' => 'الكلمة المفتاحية',
|
||||
'keyword_placeholder' => 'العنوان أو المؤلف أو الوسوم أو المقتطف',
|
||||
'language_filter' => 'لغة المحتوى',
|
||||
'all_shelves' => 'كل الرفوف',
|
||||
'filter' => 'تصفية',
|
||||
'rules_kicker' => 'قواعد الظهور',
|
||||
'rules_title' => 'وصول يتحكم به المشرف',
|
||||
'rules_copy' => 'تظهر العناصر العامة في هذا الفهرس فوراً، بينما تبقى العناصر الخاصة خارج الرف العام ومتاحة فقط من مساحة الإدارة.',
|
||||
'rules_link' => 'مراجعة إعدادات النشر',
|
||||
'catalog_kicker' => 'الفهرس',
|
||||
'catalog_title' => 'العناوين العامة المتاحة',
|
||||
'result_singular' => 'نتيجة',
|
||||
'result_plural' => 'نتائج',
|
||||
'empty_title' => 'لا توجد مستندات عامة بعد',
|
||||
'empty_copy' => 'ارفع أول مستند عربي أو إنجليزي من استوديو الإدارة لتحويل هذا القسم إلى مكتبة قابلة للتصفح.',
|
||||
'open_admin' => 'فتح استوديو الإدارة',
|
||||
'no_cover' => 'بلا غلاف',
|
||||
'untitled' => 'مستند بدون عنوان',
|
||||
'cover_alt' => 'صورة غلاف لـ',
|
||||
'author' => 'المؤلف',
|
||||
'views' => 'المشاهدات',
|
||||
'tags' => 'الوسوم',
|
||||
'author_fallback' => 'غير محدد',
|
||||
'open_reader' => 'فتح القارئ ←',
|
||||
'workflow_kicker' => 'سير العمل',
|
||||
'workflow_title' => 'مسار كامل ومختصر',
|
||||
'workflow_item_1' => 'يرفع المشرف مستنداً ويحدد ما إذا كان عاماً أو خاصاً.',
|
||||
'workflow_item_2' => 'يكتشف القراء العناوين العامة من الفهرس ويفتحون صفحة التفاصيل.',
|
||||
'workflow_item_3' => 'تتبع الملخصات والأوصاف لغة الواجهة المختارة.',
|
||||
'recent_kicker' => 'أضيف مؤخراً',
|
||||
'recent_title' => 'أحدث العناوين العامة',
|
||||
'manage_shelf' => 'إدارة الرف',
|
||||
'unknown_author' => 'مؤلف غير معروف',
|
||||
],
|
||||
][$lang];
|
||||
|
||||
// Pagination Logic
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
$limit = 12; // Grid layout 3x4
|
||||
$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
|
||||
$limit = 12;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$result = library_fetch_documents_paginated(true, ['q' => $query, 'language' => $language], $limit, $offset);
|
||||
$documents = $result['data'];
|
||||
$totalDocuments = $result['total'];
|
||||
$totalPages = (int)ceil($totalDocuments / $limit);
|
||||
$totalPages = (int) ceil($totalDocuments / $limit);
|
||||
|
||||
$metrics = library_catalog_metrics();
|
||||
$recentDocuments = library_recent_documents(3, true);
|
||||
|
||||
library_render_header(
|
||||
'Digital Catalog',
|
||||
'Browse a polished Arabic and English e-library with online reading, public/private publishing controls, and AI-ready summaries.',
|
||||
$pageCopy['meta_title'],
|
||||
$pageCopy['meta_description'],
|
||||
'catalog'
|
||||
);
|
||||
?>
|
||||
<section class="hero-surface mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Electronic library · Arabic + English</span>
|
||||
<h1 class="display-6 mb-3">A focused e-library for bilingual reading, controlled publishing, and AI-ready summaries.</h1>
|
||||
<p class="lead text-secondary mb-4">Readers can search the public shelf, open PDFs in-browser, and review concise AI summaries. Your content team can publish titles as public or private from one admin studio.</p>
|
||||
<span class="eyebrow"><?= h($pageCopy['hero_eyebrow']) ?></span>
|
||||
<h1 class="display-6 mb-3"><?= h($pageCopy['hero_title']) ?></h1>
|
||||
<p class="lead text-secondary mb-4"><?= h($pageCopy['hero_copy']) ?></p>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-dark" href="#catalog-grid">Browse catalog</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin.php">Add documents</a>
|
||||
<a class="btn btn-dark" href="#catalog-grid"><?= h($pageCopy['browse_catalog']) ?></a>
|
||||
<a class="btn btn-outline-secondary" href="/admin.php"><?= h($pageCopy['add_documents']) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="panel h-100">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-4">
|
||||
<div>
|
||||
<div class="section-kicker">Live shelf snapshot</div>
|
||||
<h2 class="h5 mb-1">What this first delivery includes</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['snapshot_kicker']) ?></div>
|
||||
<h2 class="h5 mb-1"><?= h($pageCopy['snapshot_title']) ?></h2>
|
||||
</div>
|
||||
<span class="badge text-bg-light">MVP slice</span>
|
||||
<span class="badge text-bg-light"><?= h($pageCopy['snapshot_badge']) ?></span>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<article class="metric-card">
|
||||
<span class="metric-value"><?= h((string) $metrics['public_count']) ?></span>
|
||||
<span class="metric-label">Public titles</span>
|
||||
<span class="metric-label"><?= h($pageCopy['public_titles']) ?></span>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<span class="metric-value"><?= h((string) $metrics['private_count']) ?></span>
|
||||
<span class="metric-label">Private titles</span>
|
||||
<span class="metric-label"><?= h($pageCopy['private_titles']) ?></span>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<span class="metric-value"><?= h((string) $metrics['summarized_count']) ?></span>
|
||||
<span class="metric-label">AI summaries</span>
|
||||
<span class="metric-label"><?= h($pageCopy['ai_summaries']) ?></span>
|
||||
</article>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0 mt-4 compact-list">
|
||||
<li>Public catalog with search and language filters</li>
|
||||
<li>Admin upload workflow with visibility control</li>
|
||||
<li>Document detail view with embedded PDF reader</li>
|
||||
<li><?= h($pageCopy['snapshot_item_1']) ?></li>
|
||||
<li><?= h($pageCopy['snapshot_item_2']) ?></li>
|
||||
<li><?= h($pageCopy['snapshot_item_3']) ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -75,145 +185,144 @@ library_render_header(
|
||||
<section class="row g-4 mb-4 mb-lg-5">
|
||||
<div class="col-lg-8">
|
||||
<div class="panel h-100">
|
||||
<div class="section-kicker">Public discovery</div>
|
||||
<h2 class="h4 mb-3">Search the live collection</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['discovery_kicker']) ?></div>
|
||||
<h2 class="h4 mb-3"><?= h($pageCopy['discovery_title']) ?></h2>
|
||||
<form class="row g-3 align-items-end" method="get" action="/index.php">
|
||||
<div class="col-md-7">
|
||||
<label class="form-label" for="q">Keyword</label>
|
||||
<input class="form-control" id="q" name="q" type="search" value="<?= h($query) ?>" placeholder="Title, author, tag, or excerpt">
|
||||
<label class="form-label" for="q"><?= h($pageCopy['keyword']) ?></label>
|
||||
<input class="form-control" id="q" name="q" type="search" value="<?= h($query) ?>" placeholder="<?= h($pageCopy['keyword_placeholder']) ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="language">Language</label>
|
||||
<label class="form-label" for="language"><?= h($pageCopy['language_filter']) ?></label>
|
||||
<select class="form-select" id="language" name="language">
|
||||
<option value="">All shelves</option>
|
||||
<option value="en" <?= $language === 'en' ? 'selected' : '' ?>>English</option>
|
||||
<option value="ar" <?= $language === 'ar' ? 'selected' : '' ?>>Arabic</option>
|
||||
<option value="bilingual" <?= $language === 'bilingual' ? 'selected' : '' ?>>Bilingual</option>
|
||||
<option value=""><?= h($pageCopy['all_shelves']) ?></option>
|
||||
<option value="en" <?= $language === 'en' ? 'selected' : '' ?>><?= h(library_language_label('en')) ?></option>
|
||||
<option value="ar" <?= $language === 'ar' ? 'selected' : '' ?>><?= h(library_language_label('ar')) ?></option>
|
||||
<option value="bilingual" <?= $language === 'bilingual' ? 'selected' : '' ?>><?= h(library_language_label('bilingual')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button class="btn btn-dark" type="submit">Filter</button>
|
||||
<button class="btn btn-dark" type="submit"><?= h($pageCopy['filter']) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="panel h-100">
|
||||
<div class="section-kicker">Visibility rules</div>
|
||||
<h2 class="h5 mb-3">Admin-controlled access</h2>
|
||||
<p class="text-secondary mb-3">Public items appear in this catalog immediately. Private items stay out of the public shelf and are marked for member login in the admin workspace.</p>
|
||||
<a class="link-arrow" href="/admin.php">Review publishing controls</a>
|
||||
<div class="section-kicker"><?= h($pageCopy['rules_kicker']) ?></div>
|
||||
<h2 class="h5 mb-3"><?= h($pageCopy['rules_title']) ?></h2>
|
||||
<p class="text-secondary mb-3"><?= h($pageCopy['rules_copy']) ?></p>
|
||||
<a class="link-arrow" href="/admin.php"><?= h($pageCopy['rules_link']) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-5" id="catalog-grid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="section-kicker">Catalog</div>
|
||||
<h2 class="h3 mb-0">Available public titles</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['catalog_kicker']) ?></div>
|
||||
<h2 class="h3 mb-0"><?= h($pageCopy['catalog_title']) ?></h2>
|
||||
</div>
|
||||
<span class="text-secondary small"><?= h((string) $totalDocuments) ?> result<?= $totalDocuments === 1 ? '' : 's' ?></span>
|
||||
<span class="text-secondary small"><?= h((string) $totalDocuments) ?> <?= h($totalDocuments === 1 ? $pageCopy['result_singular'] : $pageCopy['result_plural']) ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (!$documents): ?>
|
||||
<div class="panel empty-panel text-center py-5">
|
||||
<div class="empty-icon mb-3">⌘</div>
|
||||
<h3 class="h5">No public documents yet</h3>
|
||||
<p class="text-secondary mb-4">Upload your first Arabic or English PDF from the Admin Studio to turn this into a browsable library.</p>
|
||||
<a class="btn btn-dark" href="/admin.php">Open Admin Studio</a>
|
||||
<h3 class="h5"><?= h($pageCopy['empty_title']) ?></h3>
|
||||
<p class="text-secondary mb-4"><?= h($pageCopy['empty_copy']) ?></p>
|
||||
<a class="btn btn-dark" href="/admin.php"><?= h($pageCopy['open_admin']) ?></a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
<?php foreach ($documents as $document): ?>
|
||||
<?php
|
||||
$cardSummary = (string) ($document['summary_text'] ?: ($document['description_en'] ?: $document['description_ar'] ?: 'No summary yet.'));
|
||||
$cardSummaryLang = library_text_lang(
|
||||
$cardSummary,
|
||||
!empty($document['description_ar']) && empty($document['summary_text']) && empty($document['description_en']) ? 'ar' : 'en'
|
||||
);
|
||||
$cardSummaryDir = library_text_dir($cardSummary, $cardSummaryLang);
|
||||
$cardTitle = library_localized_document_title($document, $lang, $pageCopy['untitled']);
|
||||
$cardTitleLang = library_text_lang($cardTitle, $lang);
|
||||
$cardTitleDir = library_text_dir($cardTitle, $cardTitleLang);
|
||||
?>
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<article class="panel h-100 d-flex flex-column">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge text-bg-light"><?= h(library_language_label((string) $document['document_language'])) ?></span>
|
||||
<span class="badge text-bg-light"><?= h(library_document_type_label((string) $document['document_type'])) ?></span>
|
||||
<div class="col">
|
||||
<article class="panel catalog-card h-100 position-relative">
|
||||
<div class="catalog-card-media">
|
||||
<?php if (!empty($document['cover_image_path'])): ?>
|
||||
<img
|
||||
src="/<?= h((string) $document['cover_image_path']) ?>"
|
||||
alt="<?= h($pageCopy['cover_alt'] . ' ' . $cardTitle) ?>"
|
||||
class="catalog-card-cover"
|
||||
width="128"
|
||||
height="176"
|
||||
>
|
||||
<?php else: ?>
|
||||
<div class="catalog-card-cover catalog-card-cover-placeholder" aria-hidden="true">
|
||||
<span><?= h($pageCopy['no_cover']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="catalog-card-body">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-2">
|
||||
<div class="min-w-0">
|
||||
<h3 class="h5 mb-1 catalog-card-title" lang="<?= h($cardTitleLang) ?>" dir="<?= h($cardTitleDir) ?>"><?= h($cardTitle) ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="catalog-card-meta small text-secondary">
|
||||
<div><span class="catalog-card-label"><?= h($pageCopy['author']) ?></span><span><?= h((string) ($document['author'] ?: $pageCopy['author_fallback'])) ?></span></div>
|
||||
<div><span class="catalog-card-label"><?= h($pageCopy['views']) ?></span><span><?= h((string) $document['view_count']) ?></span></div>
|
||||
<div><span class="catalog-card-label"><?= h($pageCopy['tags']) ?></span><span><?= h((string) ($document['tags'] ?: '—')) ?></span></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a class="link-arrow stretched-link" href="/document.php?id=<?= h((string) $document['id']) ?>"><?= h($pageCopy['open_reader']) ?></a>
|
||||
</div>
|
||||
<?php if (!empty($document['is_featured'])): ?>
|
||||
<span class="badge text-bg-dark">Featured</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<?php if (!empty($document['title_en'])): ?>
|
||||
<h3 class="h5 mb-1"><?= h((string) $document['title_en']) ?></h3>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($document['title_ar'])): ?>
|
||||
<div class="text-secondary" dir="rtl" lang="ar"><?= h((string) $document['title_ar']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<dl class="row small text-secondary mb-3 gx-2 gy-1">
|
||||
<dt class="col-4">Author</dt>
|
||||
<dd class="col-8 mb-0"><?= h((string) ($document['author'] ?: 'Not set')) ?></dd>
|
||||
<dt class="col-4">Views</dt>
|
||||
<dd class="col-8 mb-0"><?= h((string) $document['view_count']) ?></dd>
|
||||
<dt class="col-4">Tags</dt>
|
||||
<dd class="col-8 mb-0"><?= h((string) ($document['tags'] ?: '—')) ?></dd>
|
||||
</dl>
|
||||
<p class="text-secondary flex-grow-1" lang="<?= h($cardSummaryLang) ?>" dir="<?= h($cardSummaryDir) ?>"><?= h($cardSummary) ?></p>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<a class="btn btn-dark btn-sm" href="/document.php?id=<?= h((string) $document['id']) ?>">Open reader</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="/document.php?id=<?= h((string) $document['id']) ?>#summary-card">View summary</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="mt-5">
|
||||
<?php library_render_pagination($page, $totalPages, '/index.php'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="panel h-100">
|
||||
<div class="section-kicker">Workflow</div>
|
||||
<h2 class="h5 mb-3">Thin slice, end to end</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['workflow_kicker']) ?></div>
|
||||
<h2 class="h5 mb-3"><?= h($pageCopy['workflow_title']) ?></h2>
|
||||
<ol class="compact-list-numbered mb-0 text-secondary">
|
||||
<li>Admin uploads a document and chooses public or private visibility.</li>
|
||||
<li>Readers discover public titles from the catalog and open the detail page.</li>
|
||||
<li>AI summaries can be generated from the saved excerpt for faster review.</li>
|
||||
<li><?= h($pageCopy['workflow_item_1']) ?></li>
|
||||
<li><?= h($pageCopy['workflow_item_2']) ?></li>
|
||||
<li><?= h($pageCopy['workflow_item_3']) ?></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="panel h-100">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="section-kicker">Recently added</div>
|
||||
<h2 class="h5 mb-0">Latest public titles</h2>
|
||||
<div class="section-kicker"><?= h($pageCopy['recent_kicker']) ?></div>
|
||||
<h2 class="h5 mb-0"><?= h($pageCopy['recent_title']) ?></h2>
|
||||
</div>
|
||||
<a class="link-arrow" href="/admin.php">Manage shelf</a>
|
||||
<a class="link-arrow" href="/admin.php"><?= h($pageCopy['manage_shelf']) ?></a>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($recentDocuments as $document): ?>
|
||||
<?php
|
||||
$recentTitle = (string) ($document['title_en'] ?: $document['title_ar'] ?: 'Untitled');
|
||||
$recentTitleLang = library_text_lang($recentTitle, !empty($document['title_ar']) && empty($document['title_en']) ? 'ar' : 'en');
|
||||
$recentTitle = library_localized_document_title($document, $lang, $pageCopy['untitled']);
|
||||
$recentTitleLang = library_text_lang($recentTitle, $lang);
|
||||
$recentTitleDir = library_text_dir($recentTitle, $recentTitleLang);
|
||||
?>
|
||||
<div class="col-md-4">
|
||||
<a class="recent-card text-decoration-none" href="/document.php?id=<?= h((string) $document['id']) ?>">
|
||||
<span class="small text-secondary d-block mb-2"><?= h(library_language_label((string) $document['document_language'])) ?></span>
|
||||
<strong class="d-block text-dark mb-1" lang="<?= h($recentTitleLang) ?>" dir="<?= h($recentTitleDir) ?>"><?= h($recentTitle) ?></strong>
|
||||
<span class="small text-secondary"><?= h((string) ($document['author'] ?: 'Unknown author')) ?></span>
|
||||
<span class="small text-secondary"><?= h((string) ($document['author'] ?: $pageCopy['unknown_author'])) ?></span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -222,4 +331,4 @@ library_render_header(
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
library_render_footer();
|
||||
library_render_footer();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user