diff --git a/assets/css/content.css b/assets/css/content.css
new file mode 100644
index 0000000..55b4f35
--- /dev/null
+++ b/assets/css/content.css
@@ -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; }
+}
\ No newline at end of file
diff --git a/chapitre.php b/chapitre.php
index 8d5a5b1..d66655c 100644
--- a/chapitre.php
+++ b/chapitre.php
@@ -1,6 +1,7 @@
{$text}";
- }
- if (!empty($op['attributes']['italic'])) {
- $text = "{$text}";
- }
- if (!empty($op['attributes']['underline'])) {
- $text = "{$text}";
- }
- // Ajouter d'autres styles si nécessaire
- }
-
- // Convertir les retours à la ligne en paragraphes
- if ($text === "\n") {
- $html .= "
";
- } 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 .= "";
- }
- }
-
- // 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}
"; - } - } - } - - return $html; + return DeltaConverter::toHtml($content); } function isJson($string) { @@ -126,6 +63,7 @@ $config = Config::load(); + diff --git a/includes/DeltaConverter.php b/includes/DeltaConverter.php new file mode 100644 index 0000000..2300d8d --- /dev/null +++ b/includes/DeltaConverter.php @@ -0,0 +1,225 @@ + '', + '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 + foreach ($attrs as $key => $value) { + if (in_array($key, self::$blockAttrs)) { + $currentBlock['attrs'][$key] = $value; + } + } + + // 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'; + } + + // Appliquer l'alignement au niveau du bloc + if (!empty($block['attrs']['align'])) { + $styles[] = "text-align: " . $block['attrs']['align'] . " !important"; + } + + // Appliquer la police au niveau du bloc si présente + if (!empty($block['attrs']['font'])) { + $classes[] = 'font-' . strtolower($block['attrs']['font']); + } + + // 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 = "$content
";
+ }
+
+ // Construire les attributs HTML finaux
+ $attributes = '';
+ if (!empty($styles)) {
+ $attributes .= ' style="' . implode('; ', $styles) . '"';
+ }
+ if (!empty($classes)) {
+ $attributes .= ' class="' . implode(' ', $classes) . '"';
+ }
+
+ return "<$tag$attributes>$content$tag>";
+ }
+
+ private static function applyInlineAttributes($text, $attrs) {
+ $text = htmlspecialchars($text);
+
+ if (!empty($attrs['bold'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['italic'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['underline'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['color'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['background'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['link'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['font'])) {
+ $text = "$text";
+ }
+ if (!empty($attrs['script'])) {
+ if ($attrs['script'] === 'super') $text = "$text";
+ if ($attrs['script'] === 'sub') $text = "$text";
+ }
+
+ 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(
+ '/{$p}
"; - } - } - } - - return $html; + return DeltaConverter::toHtml($content); } function isJson($string) { @@ -113,6 +41,7 @@ $config = Config::load(); +