Compare commits

...

16 Commits

Author SHA1 Message Date
Nonimart
98043b37dc FEATURE Introducing tag list
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-23 16:20:16 +02:00
Nonimart
ad86d2b086 FEATURE Improving vite Reference 2025-06-23 16:19:40 +02:00
Nonimart
568f4de9a5 FEATURE Handling single components behaviour 2025-06-23 16:19:04 +02:00
Nonimart
27357e5c3c FEATURE Introducing panel behaviour handling 2025-06-23 16:17:11 +02:00
Nonimart
3b0b0d226d REFACTOR Moving singles files to a dedicated folder 2025-06-23 16:15:55 +02:00
Nonimart
2a64076f85 REFACTOR including new single styles 2025-06-23 16:15:27 +02:00
Nonimart
388a9e6b35 FEATURE introducing top toolbar and sidebar styles 2025-06-23 16:15:09 +02:00
Nonimart
469d568f38 FEATURE Introducing component 2025-06-23 16:14:38 +02:00
Nonimart
b7549f05d1 STYLE refgining component with latest modifications 2025-06-23 16:14:15 +02:00
Nonimart
7d51bb057a STYLE refining title style 2025-06-23 16:13:43 +02:00
Nonimart
54ea02fc15 INCLUDE introducing the component 2025-06-23 16:13:20 +02:00
Nonimart
59a65c3eb5 INCLUDE new css footnote components 2025-06-23 16:12:54 +02:00
Nonimart
c2ad212895 STYLE Extra padding on taglist 2025-06-23 16:12:37 +02:00
Nonimart
3a8887d47b INCLUDE new css components 2025-06-23 16:12:12 +02:00
Nonimart
49ab88505b FEATURE Introducing index builder footnotes utilities 2025-06-23 16:11:45 +02:00
Nonimart
d1b3ffd81c FIX useless mistaken character 2025-06-23 16:11:01 +02:00
15 changed files with 372 additions and 79 deletions

View File

@ -28,7 +28,6 @@ function getRevueAuthors($revueID)
}
}
return array_unique($authors);
s;
}
@ -47,3 +46,82 @@ function count_user_articles($userID, $postType)
$query = new WP_Query($args);
return $query->found_posts;
}
function build_sommaire_from_content($postID)
{
$blocks = parse_blocks(get_the_content($postID));
$titleBlocks = array_filter(
$blocks,
function ($block) {
return $block['blockName'] === 'core/heading' && isset($block['attrs']['level']) && in_array($block['attrs']['level'], array(2, 3), true);
}
);
$outputIndex = [];
foreach ($titleBlocks as $block) {
$title = strip_tags($block['innerHTML']);
$anchor = $block['attrs']['idName'] ?? sanitize_title($title);
$level = $block['attrs']['level'];
$outputIndex[] = [
'title' => $title,
'anchor' => $anchor,
];
}
return $outputIndex;
}
function build_footnotes_index_from_content($content)
{
if (empty($content)) {
return [];
}
$footnotes = [];
$dom = new DOMDocument();
// On supprime les erreurs de parsing pour le HTML5
@$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$links = $dom->getElementsByTagName('a');
foreach ($links as $key => $link) {
if ($link->hasAttribute('class') && strpos($link->getAttribute('class'), 'footnote-reference') !== false) {
$footnote_content = $link->getAttribute('footnote-content');
if (!empty($footnote_content)) {
$footnotes[] = array(
'key' => $key + 1,
'anchorID' => $key + 1,
'content' => $footnote_content
);
}
}
}
return $footnotes;
}
add_filter('the_content', 'apply_footnotes_urls_to_content', 10);
function apply_footnotes_urls_to_content($content)
{
$post_type = get_post_type();
if ($post_type !== 'articles' && !is_admin()) return $content;
$footnotes = build_footnotes_index_from_content($content);
$dom = new DOMDocument();
@$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$links = $dom->getElementsByTagName('a');
foreach ($links as $key => $link) {
if ($link->hasAttribute('class') && strpos($link->getAttribute('class'), 'footnote-reference') !== false) {
$link->setAttribute('id', 'footnote-' . $key + 1);
}
}
return $dom->saveHTML();
}

View File

@ -27,6 +27,9 @@
@import './components/article-content.css';
@import './components/content-meta.css';
@import './components/post-header.css';
@import './components/sommaire-index.css';
@import './components/index-panel.css';
@import './components/footnotes-index.css';
/* ########### PAGES ############ */
@import './pages/singles.css';

View File

@ -1,2 +1,5 @@
.article-content {
.article-tags-list {
@apply mb-8;
}
}

View File

@ -0,0 +1,19 @@
.footnotes-index {
counter-reset: footnote-index;
@apply mb-4;
li {
@apply mb-4;
&:before {
content: counter(footnote-index);
counter-increment: footnote-index;
@apply mr-2 text-white text-base inline-flex bg-primary rounded-full w-8 h-8 items-center justify-center;
}
}
a {
@apply text-carhop-gray opacity-80;
&[active='true'] {
@apply text-primary font-bold;
}
}
}

View File

@ -0,0 +1,34 @@
.index-panel {
@apply p-6 m-0 border-primary border my-2;
max-height: 80vh;
overflow-y: auto;
&__header {
@apply flex gap-4 border-b border-gray-300 mb-8;
button {
@apply pb-3 text-carhop-gray opacity-60 relative;
box-sizing: border-box;
}
button[aria-selected='true'] {
@apply text-primary opacity-100;
&:after {
@apply bg-primary;
content: '';
display: block;
width: 100%;
height: 3px;
position: absolute;
bottom: -1px;
left: 0;
}
}
}
&__content {
overflow-wrap: anywhere;
ul[aria-hidden='true'] {
@apply hidden;
}
}
}

View File

@ -13,7 +13,7 @@
}
}
&__title {
@apply text-3xl font-normal;
@apply text-3xl font-normal uppercase;
}
&__date {
@apply capitalize pt-3 block;

View File

@ -1,9 +1,9 @@
.post-header {
@apply bg-purple-50 text-primary py-32;
@apply bg-primary text-white py-32;
h1.post-header__title,
h2.post-header__title {
@apply uppercase font-medium text-7xl;
@apply uppercase font-medium text-7xl text-white;
line-height: 1.2;
}
&__inner {
@ -15,7 +15,9 @@
grid-template-columns: 1fr;
}
}
.content-meta__revue-issue {
@apply bg-white text-primary;
}
.thumbnail-wrapper {
@apply bg-red-200;
img {
@ -29,7 +31,7 @@
grid-template-columns: 1fr 1fr;
&__label {
@apply uppercase font-bold text-lg;
@apply uppercase font-bold text-lg text-white;
letter-spacing: 0.2em;
}
}
@ -38,7 +40,7 @@
@apply flex gap-4;
&__button {
@apply bg-white text-carhop-green-700 px-4 py-2 font-normal rounded-full border-primary w-fit border-2 flex items-center gap-2;
@apply bg-white text-carhop-green-700 px-4 py-2 font-normal rounded-full w-fit flex items-center gap-2;
transition: transform 0.3s ease-in-out;
&:hover {
transform: scale(1.05);
@ -55,5 +57,12 @@
}
.article-meta__related-revue a {
@apply hover:underline underline-offset-8;
@apply hover:underline underline-offset-8 text-white;
text-decoration-color: #fff;
text-decoration-thickness: 1px;
}
.article-meta__value {
@apply text-white font-light tracking-wide;
letter-spacing: 0.0015em;
}

View File

@ -0,0 +1,9 @@
.sommaire-index {
@apply list-none;
li {
@apply mb-4;
}
a {
@apply text-xl text-carhop-gray opacity-80;
}
}

View File

@ -1,11 +1,30 @@
.page--single-revue,
.page--single-articles {
.content-wrapper {
@apply container mx-auto grid grid-cols-12 gap-12 py-12;
grid-template-columns: 1fr 4fr;
@apply container mx-auto grid grid-cols-12 gap-12 py-12 items-start;
grid-template-columns: 1fr 2fr;
}
.top-toolbar {
@apply col-span-2;
.tablist {
@apply flex gap-12 border-b border-primary;
button {
@apply text-xl mt-8 nunito pb-4;
&[aria-selected='true'] {
@apply text-primary border-b-4 border-primary;
}
&[aria-selected='false'] {
@apply text-carhop-gray opacity-80;
}
}
}
}
.sidebar {
@apply sticky top-0 left-0 h-fit;
.search-field {
input {
@apply border border-primary w-full;

View File

@ -1,6 +1,5 @@
import menuInit from './header';
import singleRevue from './single-revue';
import singlesInit from './singles';
import singlesInit from './singles/singles';
window.addEventListener('DOMContentLoaded', function () {
menuInit();

View File

@ -1,63 +0,0 @@
export default function singles(): void {
const isSingleRevue: HTMLElement | null =
document.querySelector('.page--single-revue');
const isSingleArticle: HTMLElement | null =
document.querySelector('.page--single-articles');
if (!isSingleRevue && !isSingleArticle) return;
handleCiteButton();
}
function handleCiteButton(): void {
const citeButton: HTMLElement | null =
document.querySelector(
'.socials-buttons__button--cite'
);
const citeReference: HTMLElement | null =
document.querySelector('#cite-reference');
if (!citeButton || !citeReference) return;
if (!window.isSecureContext) {
citeButton.setAttribute('disabled', 'true');
citeButton.setAttribute(
'title',
'Vous devez utiliser un navigation sécurisé (https) pour copier la citation'
);
}
citeButton.addEventListener('click', () => {
const textToCopy = citeReference.textContent;
if (!textToCopy) return;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(textToCopy)
.then(() => {
const notyf = new Notyf({
duration: 4000,
ripple: false,
dismissible: true,
types: [
{
type: 'success',
icon: {
className: 'notyf__icon--success',
tagName: 'i',
},
},
],
position: {
x: 'right',
y: 'top',
},
});
notyf.success(
'Citation copiée dans le presse-papiers ! <br> Vous pouvez maintenant la coller dans votre document.'
);
})
.catch((err) => {
console.error('Failed to copy text: ', err);
});
}
});
}

View File

@ -0,0 +1,80 @@
export default function handleIndexPanels(): void {
const indexPanel = document.querySelector('.index-panel');
if (!indexPanel) return;
observeTabsButtons();
observeFootnotesLinks();
}
// HANDLE TABS
function observeTabsButtons(): void {
const buttons = document.querySelectorAll('.index-panel__header button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
toggleActiveTabsButton(button as HTMLElement);
toggleActiveTabsPanel(button as HTMLElement);
});
});
}
function toggleActiveTabsButton(button: HTMLElement): void {
const buttons = document.querySelectorAll('.index-panel__header button');
buttons.forEach((button) => {
button.setAttribute('aria-selected', 'false');
});
button.setAttribute('aria-selected', 'true');
}
function toggleActiveTabsPanel(activeButton: HTMLElement): void {
const dataIndex = activeButton.getAttribute('data-index');
const activePanel = document.querySelector<HTMLElement>(
`.index-panel__content > [data-index="${dataIndex}"]`
);
if (!dataIndex || !activePanel) return;
// Hide all buttons and panels
const allButtons = document.querySelectorAll<HTMLElement>('.index-panel__header button');
const allPanels = document.querySelectorAll<HTMLElement>('.index-panel__content > [data-index]');
allButtons.forEach((button) => {
button.setAttribute('aria-selected', 'false');
});
allPanels.forEach((panel) => {
panel.setAttribute('aria-hidden', 'true');
});
// Active the button and the panel
activeButton.setAttribute('aria-selected', 'true');
activePanel.setAttribute('aria-hidden', 'false');
}
function observeFootnotesLinks(): void {
const footnotesButtons = document.querySelectorAll('.footnotes-index a');
footnotesButtons.forEach((footnoteButton) => {
footnoteButton.addEventListener('click', (e) => {
e.preventDefault();
const target = e.target as HTMLElement;
const targetId = target.getAttribute('href');
if (!targetId) return;
document.querySelector(targetId)?.scrollIntoView({ behavior: 'smooth' });
toggleActiveFootnote(footnoteButton as HTMLElement);
});
});
}
function toggleActiveFootnote(button: HTMLElement): void {
const buttons = document.querySelectorAll('footnote-reference-item');
const footnotesItems = document.querySelectorAll('.footnote-reference-item');
footnotesItems.forEach((footnoteItem) => {
footnoteItem.setAttribute('active', 'false');
});
button.setAttribute('active', 'true');
}

View File

@ -0,0 +1,94 @@
import handleIndexPanels from './index-panel';
export default function singles(): void {
const isSingleRevue: HTMLElement | null = document.querySelector('.page--single-revue');
const isSingleArticle: HTMLElement | null = document.querySelector('.page--single-articles');
if (!isSingleRevue && !isSingleArticle) return;
injectIdToNativeTitles();
handleIndexPanels();
handleCiteButton();
handleSmoothScrollToTitle();
}
function handleCiteButton(): void {
const citeButton: HTMLElement | null = document.querySelector('.socials-buttons__button--cite');
const citeReference: HTMLElement | null = document.querySelector('#cite-reference');
if (!citeButton || !citeReference) return;
if (!window.isSecureContext) {
citeButton.setAttribute('disabled', 'true');
citeButton.setAttribute(
'title',
'Vous devez utiliser un navigation sécurisé (https) pour copier la citation'
);
}
citeButton.addEventListener('click', () => {
const textToCopy = citeReference.textContent;
if (!textToCopy) return;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(textToCopy)
.then(() => {
const notyf = new Notyf({
duration: 4000,
ripple: false,
dismissible: true,
types: [
{
type: 'success',
icon: {
className: 'notyf__icon--success',
tagName: 'i',
},
},
],
position: {
x: 'right',
y: 'top',
},
});
notyf.success(
'Citation copiée dans le presse-papiers ! <br> Vous pouvez maintenant la coller dans votre document.'
);
})
.catch((err) => {
console.error('Failed to copy text: ', err);
});
}
});
}
function injectIdToNativeTitles(): void {
const titles = document.querySelectorAll('.content-area h2, .content-area h3');
titles.forEach((title) => {
const titleText = title.textContent || '';
const slug = titleText
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '');
title.setAttribute('id', slug);
});
}
function handleSmoothScrollToTitle(): void {
const sommaireTitles: NodeListOf<Element> = document.querySelectorAll('.sommaire-index li a');
for (const title of sommaireTitles) {
title.addEventListener('click', (e) => {
e.preventDefault();
const target = title.getAttribute('href');
if (!target) return;
const targetElement = document.querySelector(target);
if (!targetElement) return;
targetElement.scrollIntoView({ behavior: 'smooth' });
});
}
}

View File

@ -3,11 +3,18 @@ $articleID = $args['ID'];
$articleContent = get_the_content($articleID);
$articleTitle = get_the_title($articleID);
$citeReference = get_field('cite_reference', $articleID);
$tags = get_the_terms($articleID, 'etiquettes');
?>
<article class="article-content">
<h1><?php echo $articleTitle; ?></h1>
<?php echo $articleContent; ?>
<ul class="article-tags-list">
<?php foreach ($tags as $tag) : ?>
<li class="article-tag">
<?php echo $tag->name; ?>
</li>
<?php endforeach; ?>
</ul>
<?php the_content(); ?>
<?php if ($citeReference) : ?>
<p id="cite-reference">
<?php echo $citeReference; ?>

View File

@ -14,6 +14,8 @@ $issueNumber = get_field('issue_number', $currentRevueID);
$post_type = get_post_type();
$hasThumbnail = has_post_thumbnail();
$citeReference = get_field('cite_reference', $currentRevueID);
?>
<section class="post-header post-header--<?php echo $post_type; ?> ">
@ -42,7 +44,7 @@ $hasThumbnail = has_post_thumbnail();
<?php if ($post_type === 'revues') : ?>
<h1 class="post-header__title"> <?php echo $revueTitle; ?></h1>
<?php elseif ($post_type === 'articles') : ?>
<h2 class="post-header__title"> <?php echo $revueTitle; ?></h2>
<h2 class="post-header__title"> <?php echo get_the_title(); ?></h2>
<?php endif; ?>
<div class="post-details">
@ -62,7 +64,7 @@ $hasThumbnail = has_post_thumbnail();
</div>
<?php endif; ?>
<div class="socials-buttons">
<button class="socials-buttons__button socials-buttons__button--cite">
<button class="socials-buttons__button socials-buttons__button--cite" disabled="<?php echo empty($citeReference) ? 'true' : 'false'; ?>" title="<?php echo empty($citeReference) ? 'Citation non disponible' : 'Copier la citation'; ?>">
<img src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-citer-article.svg" alt="">
Citer
</button>