Compare commits
4 Commits
f2acf10fab
...
6887b41fcc
Author | SHA1 | Date | |
---|---|---|---|
6887b41fcc | |||
38b7596569 | |||
27076900bb | |||
117999c60a |
@ -143,14 +143,20 @@ function generateSlug($title) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="chapterEditor" class="modal" style="display: none;">
|
||||
<div id="chapterEditor" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Éditer le chapitre</h2>
|
||||
<input type="text" id="chapterTitle" placeholder="Titre du chapitre">
|
||||
<div id="editor"></div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="button" id="saveChapter">Enregistrer</button>
|
||||
<button type="button" class="button" id="cancelEdit">Annuler</button>
|
||||
<div class="modal-header">
|
||||
<h2>Éditer un chapitre</h2>
|
||||
<input type="text" id="chapterTitle" placeholder="Titre du chapitre">
|
||||
</div>
|
||||
|
||||
<div class="editor-container">
|
||||
<div id="editor"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="cancelEdit" class="button dark">Annuler</button>
|
||||
<button type="button" id="saveChapter" class="button">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -185,38 +185,43 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
/* Styles pour la modal et l'éditeur */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
color: var(--text-primary);
|
||||
/* En-tête de la modal */
|
||||
.modal-header {
|
||||
padding: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0 0 var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
#chapterTitle {
|
||||
@ -227,14 +232,55 @@
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
margin-bottom: var(--spacing-md);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
#chapterTitle:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 2px rgba(139, 69, 19, 0.2);
|
||||
/* Container de l'éditeur */
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Barre d'outils Quill */
|
||||
.ql-toolbar.ql-snow {
|
||||
background: var(--bg-secondary);
|
||||
border-color: var(--border-color);
|
||||
padding: var(--spacing-sm);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Container principal de Quill */
|
||||
.ql-container.ql-snow {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-color: var(--border-color);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
|
||||
/* Zone d'édition */
|
||||
.ql-editor {
|
||||
min-height: 100%;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Pied de la modal */
|
||||
.modal-footer {
|
||||
padding: var(--spacing-lg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
@ -274,14 +320,19 @@
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: var(--spacing-md);
|
||||
width: 95%;
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
|
||||
#chapterTitle {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-actions button {
|
||||
|
||||
.modal-footer button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
210
assets/css/content.css
Normal file
210
assets/css/content.css
Normal file
@ -0,0 +1,210 @@
|
||||
/* content.css - Styles pour le contenu de l'éditeur */
|
||||
|
||||
/* Conteneur principal du contenu */
|
||||
.chapter-content,
|
||||
.novel-description {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary);
|
||||
font-weight: normal;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Titres */
|
||||
.chapter-content h1,
|
||||
.chapter-content h2,
|
||||
.chapter-content h3,
|
||||
.chapter-content h4,
|
||||
.chapter-content h5,
|
||||
.chapter-content h6,
|
||||
.novel-description h1,
|
||||
.novel-description h2,
|
||||
.novel-description h3,
|
||||
.novel-description h4,
|
||||
.novel-description h5,
|
||||
.novel-description h6 {
|
||||
margin: 1.5em 0 0.8em;
|
||||
line-height: 1.3;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chapter-content h1,
|
||||
.novel-description h1 { font-size: 2em; }
|
||||
|
||||
.chapter-content h2,
|
||||
.novel-description h2 { font-size: 1.75em; }
|
||||
|
||||
.chapter-content h3,
|
||||
.novel-description h3 { font-size: 1.5em; }
|
||||
|
||||
.chapter-content h4,
|
||||
.novel-description h4 { font-size: 1.25em; }
|
||||
|
||||
.chapter-content h5,
|
||||
.novel-description h5 { font-size: 1.1em; }
|
||||
|
||||
.chapter-content h6,
|
||||
.novel-description h6 { font-size: 1em; }
|
||||
|
||||
/* Paragraphes et espacement */
|
||||
.chapter-content p,
|
||||
.novel-description p {
|
||||
margin: 0 0 1.5em 0;
|
||||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
/* Listes */
|
||||
.chapter-content ul,
|
||||
.chapter-content ol,
|
||||
.novel-description ul,
|
||||
.novel-description ol {
|
||||
margin: 1em 0;
|
||||
padding-left: 2em;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.chapter-content li,
|
||||
.novel-description li {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
/* Citations */
|
||||
.chapter-content blockquote,
|
||||
.novel-description blockquote {
|
||||
margin: 1.5em 0;
|
||||
padding: 1em 1.5em;
|
||||
border-left: 4px solid var(--accent-primary);
|
||||
background-color: var(--bg-secondary);
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Blocs de code */
|
||||
.chapter-content pre,
|
||||
.novel-description pre {
|
||||
margin: 1.5em 0;
|
||||
padding: 1em;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chapter-content code,
|
||||
.novel-description code {
|
||||
font-family: "Consolas", "Monaco", monospace;
|
||||
font-size: 0.9em;
|
||||
padding: 0.2em 0.4em;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.chapter-content img,
|
||||
.novel-description img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 1.5em 0;
|
||||
border-radius: var(--radius-sm);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Alignements */
|
||||
.chapter-content [style*="text-align"],
|
||||
.novel-description [style*="text-align"],
|
||||
.novel-description p[style*="text-align"] {
|
||||
display: block !important;
|
||||
margin: 1em 0 !important;
|
||||
}
|
||||
|
||||
.novel-description .font-serif,
|
||||
.novel-description .font-sans,
|
||||
.novel-description .font-mono {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Polices */
|
||||
.font-serif {
|
||||
font-family: Georgia, "Times New Roman", serif !important;
|
||||
}
|
||||
|
||||
.font-sans {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: "Consolas", "Monaco", monospace !important;
|
||||
}
|
||||
|
||||
/* Barre de séparation */
|
||||
.chapter-divider {
|
||||
margin: 2em auto;
|
||||
border: none;
|
||||
border-top: 2px solid var(--accent-primary);
|
||||
opacity: 0.5;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Styles pour les liens */
|
||||
.chapter-content a,
|
||||
.novel-description a {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.chapter-content a:hover,
|
||||
.novel-description a:hover {
|
||||
color: var(--accent-secondary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Styles pour les indices et exposants */
|
||||
.chapter-content sub,
|
||||
.novel-description sub {
|
||||
vertical-align: sub;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.chapter-content sup,
|
||||
.novel-description sup {
|
||||
vertical-align: super;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
/* Media queries pour le responsive */
|
||||
@media (max-width: 768px) {
|
||||
.chapter-content,
|
||||
.novel-description {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.chapter-content blockquote,
|
||||
.novel-description blockquote {
|
||||
margin: 1em 0;
|
||||
padding: 0.8em 1em;
|
||||
}
|
||||
|
||||
.chapter-content pre,
|
||||
.novel-description pre {
|
||||
padding: 0.8em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.chapter-content h1,
|
||||
.novel-description h1 { font-size: 1.75em; }
|
||||
|
||||
.chapter-content h2,
|
||||
.novel-description h2 { font-size: 1.5em; }
|
||||
|
||||
.chapter-content h3,
|
||||
.novel-description h3 { font-size: 1.25em; }
|
||||
}
|
68
chapitre.php
68
chapitre.php
@ -1,6 +1,7 @@
|
||||
<?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'] ?? '';
|
||||
@ -36,71 +37,7 @@ if (!$currentChapter) {
|
||||
|
||||
// Fonction pour convertir le contenu Delta en HTML
|
||||
function deltaToHtml($content) {
|
||||
if (empty($content)) return '';
|
||||
|
||||
// Si le contenu est déjà en HTML (ancien format)
|
||||
if (is_string($content) && !isJson($content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Convertir la chaîne JSON en tableau si nécessaire
|
||||
if (is_string($content)) {
|
||||
$delta = json_decode($content, true);
|
||||
} else {
|
||||
$delta = $content;
|
||||
}
|
||||
|
||||
if (!isset($delta['ops'])) return '';
|
||||
|
||||
$html = '';
|
||||
foreach ($delta['ops'] as $op) {
|
||||
if (is_string($op['insert'])) {
|
||||
$text = htmlspecialchars($op['insert']);
|
||||
|
||||
// Gérer les styles de texte
|
||||
if (isset($op['attributes'])) {
|
||||
if (!empty($op['attributes']['bold'])) {
|
||||
$text = "<strong>{$text}</strong>";
|
||||
}
|
||||
if (!empty($op['attributes']['italic'])) {
|
||||
$text = "<em>{$text}</em>";
|
||||
}
|
||||
if (!empty($op['attributes']['underline'])) {
|
||||
$text = "<u>{$text}</u>";
|
||||
}
|
||||
// Ajouter d'autres styles si nécessaire
|
||||
}
|
||||
|
||||
// Convertir les retours à la ligne en paragraphes
|
||||
if ($text === "\n") {
|
||||
$html .= "<br>";
|
||||
} else {
|
||||
$html .= $text;
|
||||
}
|
||||
}
|
||||
// Gérer les images
|
||||
elseif (is_array($op['insert']) && isset($op['insert']['image'])) {
|
||||
$imageUrl = $op['insert']['image'];
|
||||
error_log('URL originale: ' . $imageUrl);
|
||||
// Retirer tous les "../" au début de l'URL
|
||||
$imageUrl = preg_replace('/^(?:\.\.\/)+/', '', $imageUrl);
|
||||
error_log('URL nettoyée: ' . $imageUrl);
|
||||
$html .= "<img src=\"{$imageUrl}\" alt=\"Image du chapitre\">";
|
||||
}
|
||||
}
|
||||
|
||||
// Envelopper le contenu dans des balises p si nécessaire
|
||||
if (!empty($html)) {
|
||||
$paragraphs = explode("\n\n", $html);
|
||||
$html = '';
|
||||
foreach ($paragraphs as $p) {
|
||||
if (trim($p) !== '') {
|
||||
$html .= "<p>{$p}</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
return DeltaConverter::toHtml($content);
|
||||
}
|
||||
|
||||
function isJson($string) {
|
||||
@ -126,6 +63,7 @@ $config = Config::load();
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/public.css">
|
||||
<link rel="stylesheet" href="assets/css/content.css">
|
||||
|
||||
<meta name="description" content="<?= htmlspecialchars($story['title']) ?> - <?= htmlspecialchars($currentChapter['title']) ?>">
|
||||
</head>
|
||||
|
268
includes/DeltaConverter.php
Normal file
268
includes/DeltaConverter.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
77
roman.php
77
roman.php
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/stories.php';
|
||||
require_once 'includes/DeltaConverter.php';
|
||||
|
||||
// Récupération de l'ID du roman depuis l'URL
|
||||
$storyId = $_GET['id'] ?? '';
|
||||
@ -18,80 +19,7 @@ if (!$story) {
|
||||
|
||||
// Fonction pour convertir le contenu Delta en HTML
|
||||
function deltaToHtml($content) {
|
||||
if (empty($content)) return '';
|
||||
|
||||
// Si le contenu est déjà en HTML
|
||||
if (is_string($content) && !isJson($content)) {
|
||||
// Nettoyer les URLs des images dans le HTML
|
||||
return preg_replace_callback(
|
||||
'/<img[^>]+src=([\'"])((?:\.\.\/)*(?:assets\/[^"\']+))\1[^>]*>/',
|
||||
function($matches) {
|
||||
// $matches[2] contient l'URL
|
||||
$cleanUrl = preg_replace('/^(?:\.\.\/)+/', '', $matches[2]);
|
||||
return str_replace($matches[2], $cleanUrl, $matches[0]);
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir la chaîne JSON en tableau si nécessaire
|
||||
if (is_string($content)) {
|
||||
$delta = json_decode($content, true);
|
||||
} else {
|
||||
$delta = $content;
|
||||
}
|
||||
|
||||
if (!isset($delta['ops'])) return '';
|
||||
|
||||
$html = '';
|
||||
foreach ($delta['ops'] as $op) {
|
||||
if (is_string($op['insert'])) {
|
||||
$text = htmlspecialchars($op['insert']);
|
||||
|
||||
// Gérer les styles de texte
|
||||
if (isset($op['attributes'])) {
|
||||
if (!empty($op['attributes']['bold'])) {
|
||||
$text = "<strong>{$text}</strong>";
|
||||
}
|
||||
if (!empty($op['attributes']['italic'])) {
|
||||
$text = "<em>{$text}</em>";
|
||||
}
|
||||
if (!empty($op['attributes']['underline'])) {
|
||||
$text = "<u>{$text}</u>";
|
||||
}
|
||||
// Ajouter d'autres styles si nécessaire
|
||||
}
|
||||
|
||||
// Convertir les retours à la ligne en paragraphes
|
||||
if ($text === "\n") {
|
||||
$html .= "<br>";
|
||||
} else {
|
||||
$html .= $text;
|
||||
}
|
||||
}
|
||||
// Gérer les images
|
||||
elseif (is_array($op['insert']) && isset($op['insert']['image'])) {
|
||||
$imageUrl = $op['insert']['image'];
|
||||
error_log('URL originale: ' . $imageUrl);
|
||||
// Retirer tous les "../" au début de l'URL
|
||||
$imageUrl = preg_replace('/^(?:\.\.\/)+/', '', $imageUrl);
|
||||
error_log('URL nettoyée: ' . $imageUrl);
|
||||
$html .= "<img src=\"{$imageUrl}\" alt=\"Image du chapitre\">";
|
||||
}
|
||||
}
|
||||
|
||||
// Envelopper le contenu dans des balises p si nécessaire
|
||||
if (!empty($html)) {
|
||||
$paragraphs = explode("\n\n", $html);
|
||||
$html = '';
|
||||
foreach ($paragraphs as $p) {
|
||||
if (trim($p) !== '') {
|
||||
$html .= "<p>{$p}</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
return DeltaConverter::toHtml($content);
|
||||
}
|
||||
|
||||
function isJson($string) {
|
||||
@ -113,6 +41,7 @@ $config = Config::load();
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/public.css">
|
||||
<link rel="stylesheet" href="assets/css/content.css">
|
||||
|
||||
<meta name="description" content="<?= htmlspecialchars(strip_tags($story['description'])) ?>">
|
||||
</head>
|
||||
|
@ -1 +1 @@
|
||||
1.0.0
|
||||
1.1.0
|
Loading…
x
Reference in New Issue
Block a user