simplification et amélioration de la gestion du texte pour les chapitres
This commit is contained in:
parent
fd134917f0
commit
c99146c38a
@ -12,7 +12,7 @@ $input = json_decode(file_get_contents('php://input'), true);
|
||||
$storyId = $input['storyId'] ?? null;
|
||||
$chapterId = $input['chapterId'] ?? null;
|
||||
$title = $input['title'] ?? '';
|
||||
$content = $input['content'] ?? '';
|
||||
$content = $input['content'];
|
||||
|
||||
try {
|
||||
$story = Stories::get($storyId);
|
||||
|
@ -308,7 +308,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
storyId: storyId,
|
||||
chapterId: currentChapterId,
|
||||
title: title,
|
||||
content: JSON.stringify(quill.getContents())
|
||||
content: quill.root.innerHTML
|
||||
})
|
||||
});
|
||||
|
||||
|
18
chapitre.php
18
chapitre.php
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/stories.php';
|
||||
require_once 'includes/DeltaConverter.php';
|
||||
|
||||
// Récupération des paramètres
|
||||
$storyId = $_GET['story'] ?? '';
|
||||
@ -35,16 +34,6 @@ if (!$currentChapter) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fonction pour convertir le contenu Delta en HTML
|
||||
function deltaToHtml($content) {
|
||||
return DeltaConverter::toHtml($content);
|
||||
}
|
||||
|
||||
function isJson($string) {
|
||||
json_decode($string);
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
// Récupération des chapitres précédent et suivant
|
||||
$prevChapter = $currentIndex > 0 ? $story['chapters'][$currentIndex - 1] : null;
|
||||
$nextChapter = $currentIndex < count($story['chapters']) - 1 ? $story['chapters'][$currentIndex + 1] : null;
|
||||
@ -76,7 +65,7 @@ $config = Config::load();
|
||||
<!-- Contenu principal -->
|
||||
<div class="novel-content">
|
||||
<div class="novel-description chapter-content">
|
||||
<?= deltaToHtml($currentChapter['content']) ?>
|
||||
<?= $currentChapter['content'] ?>
|
||||
|
||||
<!-- Navigation entre chapitres -->
|
||||
<div class="chapter-navigation">
|
||||
@ -112,7 +101,8 @@ $config = Config::load();
|
||||
</div>
|
||||
|
||||
<div class="back-to-home">
|
||||
<a href="index.php">← Retour à l'accueil</a> | <a href="roman.php?id=<?= urlencode($storyId) ?>">← Retour au roman</a>
|
||||
<a href="index.php">← Retour à l'accueil</a> |
|
||||
<a href="roman.php?id=<?= urlencode($storyId) ?>">← Retour au roman</a>
|
||||
</div>
|
||||
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">↑</button>
|
||||
@ -121,7 +111,7 @@ $config = Config::load();
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const scrollTopBtn = document.querySelector('.scroll-top');
|
||||
|
||||
// Afficher/masquer le bouton
|
||||
// Afficher/masquer le bouton de retour en haut
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.pageYOffset > 300) {
|
||||
scrollTopBtn.classList.add('visible');
|
||||
|
@ -1,268 +0,0 @@
|
||||
<?php
|
||||
|
||||
class DeltaConverter {
|
||||
private static $blockAttrs = ['align', 'header', 'blockquote', 'code-block', 'list'];
|
||||
private static $inlineAttrs = ['bold', 'italic', 'underline', 'strike', 'color', 'background', 'link', 'font', 'script'];
|
||||
|
||||
public static function toHtml($content) {
|
||||
if (empty($content)) return '';
|
||||
|
||||
// Si le contenu est déjà en HTML
|
||||
if (is_string($content) && !self::isJson($content)) {
|
||||
// Convertir les classes ql-align-* en styles d'alignement
|
||||
$content = preg_replace_callback(
|
||||
'/class="ql-align-(justify|center|right)"/',
|
||||
function($matches) {
|
||||
return 'style="text-align: ' . $matches[1] . ' !important"';
|
||||
},
|
||||
$content
|
||||
);
|
||||
|
||||
// Convertir les classes ql-font-* en classes de police
|
||||
$content = preg_replace_callback(
|
||||
'/class="ql-font-(serif|monospace|sans-serif)"/',
|
||||
function($matches) {
|
||||
switch($matches[1]) {
|
||||
case 'serif':
|
||||
return 'class="font-serif"';
|
||||
case 'monospace':
|
||||
return 'class="font-mono"';
|
||||
case 'sans-serif':
|
||||
return 'class="font-sans"';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
$content
|
||||
);
|
||||
|
||||
return self::cleanImageUrls($content);
|
||||
}
|
||||
|
||||
// Convertir la chaîne JSON en tableau si nécessaire
|
||||
if (is_string($content)) {
|
||||
$content = json_decode($content, true);
|
||||
}
|
||||
|
||||
if (!isset($content['ops'])) return '';
|
||||
|
||||
$html = '';
|
||||
$currentBlock = [
|
||||
'content' => '',
|
||||
'attrs' => [],
|
||||
'inlineAttrs' => []
|
||||
];
|
||||
|
||||
foreach ($content['ops'] as $op) {
|
||||
$text = '';
|
||||
$attrs = isset($op['attributes']) ? $op['attributes'] : [];
|
||||
|
||||
// Gérer l'insertion de texte
|
||||
if (is_string($op['insert'])) {
|
||||
$text = $op['insert'];
|
||||
|
||||
// Si c'est un saut de ligne, on finalise le bloc
|
||||
if ($text === "\n") {
|
||||
// Vérifier les attributs de bloc pour ce saut de ligne
|
||||
if (!empty($attrs['align'])) {
|
||||
$currentBlock['attrs']['text-align'] = $attrs['align'];
|
||||
}
|
||||
if (!empty($attrs['header'])) {
|
||||
$currentBlock['attrs']['header'] = $attrs['header'];
|
||||
}
|
||||
if (!empty($attrs['blockquote'])) {
|
||||
$currentBlock['attrs']['blockquote'] = true;
|
||||
}
|
||||
if (!empty($attrs['code-block'])) {
|
||||
$currentBlock['attrs']['code-block'] = true;
|
||||
}
|
||||
|
||||
// Finaliser le bloc courant
|
||||
if (!empty($currentBlock['content']) || !empty($currentBlock['attrs'])) {
|
||||
$html .= self::renderBlock($currentBlock);
|
||||
}
|
||||
|
||||
// Réinitialiser pour le prochain bloc
|
||||
$currentBlock = [
|
||||
'content' => '',
|
||||
'attrs' => [],
|
||||
'inlineAttrs' => []
|
||||
];
|
||||
} else {
|
||||
// Ajouter le texte avec ses attributs inline
|
||||
$currentBlock['content'] .= $text;
|
||||
if (!empty($attrs)) {
|
||||
$currentBlock['inlineAttrs'][] = [
|
||||
'start' => mb_strlen($currentBlock['content']) - mb_strlen($text),
|
||||
'length' => mb_strlen($text),
|
||||
'attrs' => $attrs
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Gérer les images
|
||||
elseif (is_array($op['insert']) && isset($op['insert']['image'])) {
|
||||
if (!empty($currentBlock['content'])) {
|
||||
$html .= self::renderBlock($currentBlock);
|
||||
$currentBlock = [
|
||||
'content' => '',
|
||||
'attrs' => [],
|
||||
'inlineAttrs' => []
|
||||
];
|
||||
}
|
||||
|
||||
$imageUrl = self::cleanImageUrl($op['insert']['image']);
|
||||
$imgAttributes = ['class' => 'chapter-image'];
|
||||
if (isset($attrs['alt'])) {
|
||||
$imgAttributes['alt'] = $attrs['alt'];
|
||||
} else {
|
||||
$imgAttributes['alt'] = "Image";
|
||||
}
|
||||
|
||||
$html .= self::createImageTag($imageUrl, $imgAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
// Finaliser le dernier bloc si nécessaire
|
||||
if (!empty($currentBlock['content'])) {
|
||||
$html .= self::renderBlock($currentBlock);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private static function renderBlock($block) {
|
||||
if (empty($block['content'])) return '';
|
||||
|
||||
$tag = 'p';
|
||||
$styles = [];
|
||||
$classes = [];
|
||||
|
||||
// Déterminer le tag et attributs de base
|
||||
if (!empty($block['attrs']['header'])) {
|
||||
$tag = 'h' . $block['attrs']['header'];
|
||||
} elseif (!empty($block['attrs']['blockquote'])) {
|
||||
$tag = 'blockquote';
|
||||
} elseif (!empty($block['attrs']['code-block'])) {
|
||||
$tag = 'pre';
|
||||
$classes[] = 'code-block';
|
||||
}
|
||||
|
||||
// Appliquer l'alignement au niveau du bloc
|
||||
if (!empty($block['attrs']['text-align'])) {
|
||||
$styles[] = "text-align: " . $block['attrs']['text-align'] . " !important";
|
||||
}
|
||||
|
||||
// Construire le contenu avec les attributs inline
|
||||
$content = $block['content'];
|
||||
if (!empty($block['inlineAttrs'])) {
|
||||
// Trier les attributs par position de fin pour les appliquer de la fin vers le début
|
||||
usort($block['inlineAttrs'], function($a, $b) {
|
||||
return ($a['start'] + $a['length']) - ($b['start'] + $b['length']);
|
||||
});
|
||||
|
||||
// Appliquer les attributs inline
|
||||
foreach (array_reverse($block['inlineAttrs']) as $attr) {
|
||||
$start = $attr['start'];
|
||||
$length = $attr['length'];
|
||||
$segment = mb_substr($content, $start, $length);
|
||||
|
||||
if (!empty($attr['attrs'])) {
|
||||
$segment = self::applyInlineAttributes($segment, $attr['attrs']);
|
||||
}
|
||||
|
||||
$content = mb_substr($content, 0, $start) . $segment . mb_substr($content, $start + $length);
|
||||
}
|
||||
}
|
||||
|
||||
// Si c'est un bloc de code, wrapper dans code
|
||||
if ($tag === 'pre') {
|
||||
$content = "<code>$content</code>";
|
||||
}
|
||||
|
||||
// Construire les attributs HTML finaux
|
||||
$attributes = '';
|
||||
if (!empty($styles)) {
|
||||
$attributes .= ' style="' . implode('; ', array_unique($styles)) . '"';
|
||||
}
|
||||
if (!empty($classes)) {
|
||||
$attributes .= ' class="' . implode(' ', array_unique($classes)) . '"';
|
||||
}
|
||||
|
||||
return "<$tag$attributes>$content</$tag>";
|
||||
}
|
||||
|
||||
private static function applyInlineAttributes($text, $attrs) {
|
||||
$text = htmlspecialchars($text);
|
||||
|
||||
if (!empty($attrs['bold'])) {
|
||||
$text = "<strong>$text</strong>";
|
||||
}
|
||||
if (!empty($attrs['italic'])) {
|
||||
$text = "<em>$text</em>";
|
||||
}
|
||||
if (!empty($attrs['underline'])) {
|
||||
$text = "<u>$text</u>";
|
||||
}
|
||||
if (!empty($attrs['strike'])) {
|
||||
$text = "<s>$text</s>";
|
||||
}
|
||||
if (!empty($attrs['color'])) {
|
||||
$text = "<span style=\"color: {$attrs['color']} !important\">$text</span>";
|
||||
}
|
||||
if (!empty($attrs['background'])) {
|
||||
$text = "<span style=\"background-color: {$attrs['background']} !important\">$text</span>";
|
||||
}
|
||||
if (!empty($attrs['link'])) {
|
||||
$text = "<a href=\"{$attrs['link']}\" target=\"_blank\">$text</a>";
|
||||
}
|
||||
if (!empty($attrs['font'])) {
|
||||
switch($attrs['font']) {
|
||||
case 'serif':
|
||||
$text = "<span class=\"font-serif\">$text</span>";
|
||||
break;
|
||||
case 'monospace':
|
||||
$text = "<span class=\"font-mono\">$text</span>";
|
||||
break;
|
||||
case 'sans-serif':
|
||||
$text = "<span class=\"font-sans\">$text</span>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($attrs['script'])) {
|
||||
if ($attrs['script'] === 'super') $text = "<sup>$text</sup>";
|
||||
if ($attrs['script'] === 'sub') $text = "<sub>$text</sub>";
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function isJson($string) {
|
||||
json_decode($string);
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
private static function cleanImageUrl($url) {
|
||||
return preg_replace('/^(?:\.\.\/)+/', '', $url);
|
||||
}
|
||||
|
||||
private static function cleanImageUrls($html) {
|
||||
return preg_replace_callback(
|
||||
'/<img[^>]+src=([\'"])((?:\.\.\/)*(?:assets\/[^"\']+))\1[^>]*>/',
|
||||
function($matches) {
|
||||
$cleanUrl = self::cleanImageUrl($matches[2]);
|
||||
return str_replace($matches[2], $cleanUrl, $matches[0]);
|
||||
},
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
private static function createImageTag($src, $attributes) {
|
||||
$html = '<img src="' . htmlspecialchars($src) . '"';
|
||||
foreach ($attributes as $key => $value) {
|
||||
$html .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
|
||||
}
|
||||
$html .= '>';
|
||||
return $html;
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
1.1.2
|
||||
1.1.3
|
Loading…
x
Reference in New Issue
Block a user